Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 17 Apr 2017 10:33:46 -0400
changeset 563757 2b6a66a98e253ba158f3960f1c68ad49b2ebcdb4
parent 563618 5854dacc5d723c2b2b3b3c04cb2f96e500f4eb4f (current diff)
parent 563687 05c212a94183838f12feebb2c3fd483a6eec18c2 (diff)
child 563758 9cf43f2bfbdb861f93677313b8e90e93963fe98b
child 563761 d606cd4e928a4c6dbc6ee701b559303c8d2a55f5
child 563762 136dc9bcbf23736cd504bd6875a59fd26b6ea282
child 563765 6885b0c9863695975878a2bac3a8f59824010d0d
child 563766 abe9f0b89948148a33e86adf11a4c455f650e13d
child 563772 9358bdb7f1c62c1be009b2913a4b571625101309
child 563773 1c5c5d5576b50b7d0be70480c49d791ff034a173
child 563801 cac374fb0de1d5a5ccf53e0d61e3f47a66fe0603
child 564069 78699f5c92cac52c09826ce914540f4575ffb3da
child 564558 6b3dcbd98eb9d2692573bd7a25f493c812652f9d
child 564561 b1d2f73cdaa0419f428b3a5496df81c45f5e67ce
child 564598 8f60345ef66e14960015323fcdceefbe58a0bceb
child 564714 bc096bdba034ab4d6a2a34404f1d8d6bd3826980
child 564722 4a76500cbe5bfa7c663d364b3c9e1c8bf7e155e1
child 565510 f431a0f0d33e03631037655104351dda745bb913
child 565786 25f0d6c67a4ec59f71e5082d74e6f033b4ab1f00
child 566586 3008dc18acefa9833795a7393135b809e1fe2330
child 567269 9f2cbc8d1cc1310e1a0fb5fcb1b812124f477e23
child 567273 d2f6a40f677b9841b4964253d3d58581b361a4f4
child 568329 ae4aad5ba8dc093a7a9049130aceb8a15ccdfba3
child 570320 07b657a6074c8c10ed6d95a644d41e2995580f32
child 574967 efc9792fa888ba3964977385d550c9f8c73a2751
child 575050 52f57027518e3e8ce526a06d0c513b8511374947
child 581447 37da0e233ec3f3961d3b6b754a0689c4204b7052
child 583343 f60d59360bb86dee6c8a99fda1828334eaf76463
child 584605 cae8b0e4c9b66cbfecde27d252f3b73bad3933b7
child 585511 7771c859de1b8d2cc40ca4c50404082b636a55ad
child 586716 7f4923b4682881d7d74395dc8678c78dbf580681
child 592951 ac21b983365c97ffbb7bda27f78bb4947e7015ec
child 596992 883a1cd527d4bff7d758c44aea8c71c49fc9a672
child 597365 4ec6e5804286805ea9a1949614b6232941280b45
child 597600 559eb544feea8f43751dd7363c66605c791e8da9
child 597629 5d15e9791846c75600e66c83a53bc453001f916f
push id54413
push userbmo:kgilbert@mozilla.com
push dateMon, 17 Apr 2017 19:10:42 +0000
milestone55.0a1
Merge m-c to graphics MozReview-Commit-ID: KwTegQDnKGG
js/src/devtools/rootAnalysis/build/gcc-b2g.manifest
testing/web-platform/meta/storage/interfaces.https.html.ini
--- a/browser/branding/unofficial/pref/firefox-branding.js
+++ b/browser/branding/unofficial/pref/firefox-branding.js
@@ -25,9 +25,9 @@ pref("app.update.url.details", "https://
 pref("app.update.checkInstallTime.days", 2);
 
 // Give the user x seconds to reboot before showing a badge on the hamburger
 // button. default=immediately
 pref("app.update.badgeWaitTime", 0);
 
 // Number of usages of the web console or scratchpad.
 // If this is less than 5, then pasting code into the web console or scratchpad is disabled
-pref("devtools.selfxss.count", 0);
+pref("devtools.selfxss.count", 5);
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -98,16 +98,27 @@ body,
   overflow: auto;
 }
 
 .cropped-textbox .textbox-input {
   /* workaround for textbox not supporting the @crop attribute */
   text-overflow: ellipsis;
 }
 
+.learn-more-link {
+  color: var(--theme-highlight-blue);
+  cursor: pointer;
+  margin: 0 5px;
+  white-space: nowrap;
+}
+
+.learn-more-link:hover {
+  text-decoration: underline;
+}
+
 /* Status bar */
 
 .status-bar-label {
   display: inline-flex;
   align-content: stretch;
   margin-inline-end: 10px;
 
   /* Status bar has just one line so, don't wrap labels */
@@ -147,16 +158,18 @@ body,
   padding: 12px;
   font-size: 120%;
   flex: 1;
   overflow: auto;
 }
 
 .notice-perf-message {
   margin-top: 2px;
+  display: flex;
+  align-items: center;
 }
 
 .requests-list-perf-notice-button {
   min-width: 30px;
   min-height: 26px;
   margin: 0 5px;
   vertical-align: middle;
 }
@@ -863,28 +876,16 @@ body,
   color: inherit;
   padding-inline-start: 3px;
 }
 
 .theme-dark .tabpanel-summary-value {
   color: var(--theme-selection-color);
 }
 
-.learn-more-link {
-  color: var(--theme-highlight-blue);
-  cursor: pointer;
-  margin: 0 5px;
-  white-space: nowrap;
-  flex-grow: 1;
-}
-
-.learn-more-link:hover {
-  text-decoration: underline;
-}
-
 /* Headers tabpanel */
 
 .headers-overview {
   background: var(--theme-toolbar-background);
 }
 
 .headers-summary,
 .response-summary {
@@ -925,16 +926,20 @@ body,
 .headers-summary .textbox-input {
   margin-inline-end: 2px;
 }
 
 .headers-summary .status-text {
     width: auto!important;
 }
 
+.headers-summary .learn-more-link {
+  flex-grow: 1;
+}
+
 /* Response tabpanel */
 
 .response-error-header {
   margin: 0;
   padding: 3px 8px;
   background-color: var(--theme-highlight-red);
   color: var(--theme-selection-color);
 }
@@ -1158,16 +1163,24 @@ body,
 }
 
 .statistics-panel .charts,
 .statistics-panel .pie-table-chart-container {
   width: 100%;
   height: 100%;
 }
 
+.statistics-panel .learn-more-link {
+  font-weight: 400;
+}
+
+.statistics-panel .table-chart-title {
+  display: flex;
+}
+
 .pie-table-chart-container {
   display: flex;
   justify-content: center;
   align-items: center;
 }
 
 .statistics-panel .pie-chart-container {
   margin-inline-start: 3vw;
--- a/devtools/client/netmonitor/src/components/request-list-empty-notice.js
+++ b/devtools/client/netmonitor/src/components/request-list-empty-notice.js
@@ -1,24 +1,29 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   createClass,
+  createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const Actions = require("../actions/index");
 const { ACTIVITY_TYPE } = require("../constants");
 const { NetMonitorController } = require("../netmonitor-controller");
 const { L10N } = require("../utils/l10n");
+const { getPerformanceAnalysisURL } = require("../utils/mdn-utils");
+
+// Components
+const MDNLink = createFactory(require("./mdn-link"));
 
 const { button, div, span } = DOM;
 
 /**
  * UI displayed when the request list is empty. Contains instructions on reloading
  * the page and on triggering performance analysis of the page.
  */
 const RequestListEmptyNotice = createClass({
@@ -49,17 +54,18 @@ const RequestListEmptyNotice = createCla
       div({ className: "notice-perf-message" },
         span(null, L10N.getStr("netmonitor.perfNotice1")),
         button({
           title: L10N.getStr("netmonitor.perfNotice3"),
           className: "devtools-button requests-list-perf-notice-button",
           "data-standalone": true,
           onClick: this.props.onPerfClick,
         }),
-        span(null, L10N.getStr("netmonitor.perfNotice2"))
+        span(null, L10N.getStr("netmonitor.perfNotice2")),
+        MDNLink({ url: getPerformanceAnalysisURL() })
       )
     );
   }
 });
 
 module.exports = connect(
   undefined,
   dispatch => ({
--- a/devtools/client/netmonitor/src/components/statistics-panel.js
+++ b/devtools/client/netmonitor/src/components/statistics-panel.js
@@ -1,29 +1,35 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const {
   createClass,
+  createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { Chart } = require("devtools/client/shared/widgets/Chart");
 const { PluralForm } = require("devtools/shared/plural-form");
 const Actions = require("../actions/index");
 const { Filters } = require("../utils/filter-predicates");
 const {
   getSizeWithDecimals,
   getTimeWithDecimals
 } = require("../utils/format-utils");
 const { L10N } = require("../utils/l10n");
+const { getPerformanceAnalysisURL } = require("../utils/mdn-utils");
+
+// Components
+const MDNLink = createFactory(require("./mdn-link"));
 
 const { button, div } = DOM;
 const MediaQueryList = window.matchMedia("(min-width: 700px)");
 
 const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200;
 const BACK_BUTTON = L10N.getStr("netmonitor.backButton");
 const CHARTS_CACHE_ENABLED = L10N.getStr("charts.cacheEnabled");
 const CHARTS_CACHE_DISABLED = L10N.getStr("charts.cacheDisabled");
@@ -43,16 +49,20 @@ const StatisticsPanel = createClass({
   },
 
   getInitialState() {
     return {
       isVerticalSpliter: MediaQueryList.matches,
     };
   },
 
+  componentWillMount() {
+    this.mdnLinkContainerNodes = new Map();
+  },
+
   componentDidUpdate(prevProps) {
     MediaQueryList.addListener(this.onLayoutChange);
 
     const { requests } = this.props;
     let ready = requests && !requests.isEmpty() && requests.every((req) =>
       req.contentSize !== undefined && req.mimeType && req.responseHeaders &&
       req.status !== undefined && req.totalTime !== undefined
     );
@@ -63,20 +73,45 @@ const StatisticsPanel = createClass({
       data: ready ? this.sanitizeChartDataSource(requests, false) : null,
     });
 
     this.createChart({
       id: "emptyCacheChart",
       title: CHARTS_CACHE_DISABLED,
       data: ready ? this.sanitizeChartDataSource(requests, true) : null,
     });
+
+    this.createMDNLink("primedCacheChart", getPerformanceAnalysisURL());
+    this.createMDNLink("emptyCacheChart", getPerformanceAnalysisURL());
   },
 
   componentWillUnmount() {
     MediaQueryList.removeListener(this.onLayoutChange);
+    this.unmountMDNLinkContainers();
+  },
+
+  createMDNLink(chartId, url) {
+    if (this.mdnLinkContainerNodes.has(chartId)) {
+      ReactDOM.unmountComponentAtNode(this.mdnLinkContainerNodes.get(chartId));
+    }
+
+    // MDNLink is a React component but Chart isn't.  To get the link
+    // into the chart we mount a new ReactDOM at the appropriate
+    // location after the chart has been created.
+    let title = this.refs[chartId].querySelector(".table-chart-title");
+    let containerNode = document.createElement("span");
+    title.appendChild(containerNode);
+    ReactDOM.render(MDNLink({ url }), containerNode);
+    this.mdnLinkContainerNodes.set(chartId, containerNode);
+  },
+
+  unmountMDNLinkContainers() {
+    for (let [, node] of this.mdnLinkContainerNodes) {
+      ReactDOM.unmountComponentAtNode(node);
+    }
   },
 
   createChart({ id, title, data }) {
     // Create a new chart.
     let chart = Chart.PieTable(document, {
       diameter: NETWORK_ANALYSIS_PIE_CHART_DIAMETER,
       title,
       header: {
--- a/devtools/client/netmonitor/src/utils/mdn-utils.js
+++ b/devtools/client/netmonitor/src/utils/mdn-utils.js
@@ -135,55 +135,64 @@ const SUPPORTED_HTTP_CODES = [
     "501",
     "502",
     "503",
     "504",
     "505",
     "511"
 ];
 
+const MDN_URL = "https://developer.mozilla.org/docs/";
 const GA_PARAMS =
   "?utm_source=mozilla&utm_medium=devtools-netmonitor&utm_campaign=default";
 
-const NETWORK_MONITOR_TIMINGS_MDN_URL =
-  "https://developer.mozilla.org/docs/Tools/Network_Monitor#Timings";
-
 /**
  * Get the MDN URL for the specified header.
  *
  * @param {string} header Name of the header for the baseURL to use.
  *
  * @return {string} The MDN URL for the header, or null if not available.
  */
 function getHeadersURL(header) {
   const lowerCaseHeader = header.toLowerCase();
   let idx = SUPPORTED_HEADERS.findIndex(item =>
     item.toLowerCase() === lowerCaseHeader);
   return idx > -1 ?
-    `https://developer.mozilla.org/docs/Web/HTTP/Headers/${SUPPORTED_HEADERS[idx] + GA_PARAMS}` : null;
+    `${MDN_URL}Web/HTTP/Headers/${SUPPORTED_HEADERS[idx] + GA_PARAMS}` : null;
 }
 
 /**
  * Get the MDN URL for the specified HTTP status code.
  *
  * @param {string} HTTP status code for the baseURL to use.
  *
  * @return {string} The MDN URL for the HTTP status code, or null if not available.
  */
 function getHTTPStatusCodeURL(statusCode) {
   let idx = SUPPORTED_HTTP_CODES.indexOf(statusCode);
-  return idx > -1 ? `https://developer.mozilla.org/docs/Web/HTTP/Status/${SUPPORTED_HTTP_CODES[idx] + GA_PARAMS}` : null;
+  return idx > -1 ?
+    `${MDN_URL}Web/HTTP/Status/${SUPPORTED_HTTP_CODES[idx] + GA_PARAMS}` : null;
 }
 
 /**
  * Get the MDN URL of the Timings tag for Network Monitor.
  *
  * @return {string} the MDN URL of the Timings tag for Network Monitor.
  */
 function getNetMonitorTimingsURL() {
-  return NETWORK_MONITOR_TIMINGS_MDN_URL;
+  return `${MDN_URL}Tools/Network_Monitor${GA_PARAMS}#Timings`;
+}
+
+/**
+ * Get the MDN URL for Performance Analysis
+ *
+ * @return {string} The MDN URL for the documentation of Performance Analysis.
+ */
+function getPerformanceAnalysisURL() {
+  return `${MDN_URL}Tools/Network_Monitor${GA_PARAMS}#Performance_analysis`;
 }
 
 module.exports = {
   getHeadersURL,
   getHTTPStatusCodeURL,
   getNetMonitorTimingsURL,
+  getPerformanceAnalysisURL,
 };
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -221,16 +221,17 @@ support-files =
   file_bug1268962.sjs
   mozbrowser_api_utils.js
   websocket_helpers.js
   websocket_tests.js
   !/dom/security/test/cors/file_CrossSiteXHR_server.sjs
   !/image/test/mochitest/blue.png
   script_bug1238440.js
   intersectionobserver_iframe.html
+  intersectionobserver_cross_domain_iframe.html
   intersectionobserver_window.html
 
 [test_anchor_area_referrer.html]
 [test_anchor_area_referrer_changing.html]
 [test_anchor_area_referrer_invalid.html]
 [test_anchor_area_referrer_rel.html]
 [test_anonymousContent_api.html]
 [test_anonymousContent_append_after_reflow.html]
--- a/dom/base/test/test_intersectionobservers.html
+++ b/dom/base/test/test_intersectionobservers.html
@@ -364,57 +364,57 @@ limitations under the License.
           function(done) {
             io.observe(targetEl1);
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(1);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl1.style.display = 'none';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(2);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(0);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl1.style.display = 'block';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(3);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             rootEl.style.display = 'none';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(4);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(0);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             rootEl.style.display = 'block';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(5);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
         ], done);
       });
 
 
       it('handles container elements with non-visible overflow',
           function(done) {
 
@@ -425,37 +425,37 @@ limitations under the License.
           function(done) {
             io.observe(targetEl1);
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(1);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl1.style.left = '-40px';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(2);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(0);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             parentEl.style.overflow = 'visible';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(3);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           }
         ], done);
       });
 
 
       it('observes one target at a single threshold correctly', function(done) {
 
         var spy = sinon.spy();
@@ -466,44 +466,44 @@ limitations under the License.
             targetEl1.style.left = '-5px';
             io.observe(targetEl1);
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(1);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be.greaterThan(0.5);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl1.style.left = '-15px';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(2);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be.lessThan(0.5);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl1.style.left = '-25px';
             callDelayed(function() {
               expect(spy.callCount).to.be(2);
               done();
             }, ASYNC_TIMEOUT);
           },
           function(done) {
             targetEl1.style.left = '-10px';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(3);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(0.5);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           }
         ], done);
 
       });
 
 
       it('observes multiple targets at multiple thresholds correctly',
           function(done) {
@@ -529,17 +529,17 @@ limitations under the License.
               expect(spy.callCount).to.be(1);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(2);
               expect(records[0].target).to.be(targetEl1);
               expect(records[0].intersectionRatio).to.be(0.25);
               expect(records[1].target).to.be(targetEl2);
               expect(records[1].intersectionRatio).to.be(0.75);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl1.style.top = '0px';
             targetEl1.style.left = '-5px';
             targetEl2.style.top = '-15px';
             targetEl2.style.left = '0px';
             targetEl3.style.top = '0px';
             targetEl3.style.left = '195px';
@@ -549,17 +549,17 @@ limitations under the License.
               expect(records.length).to.be(3);
               expect(records[0].target).to.be(targetEl1);
               expect(records[0].intersectionRatio).to.be(0.75);
               expect(records[1].target).to.be(targetEl2);
               expect(records[1].intersectionRatio).to.be(0.25);
               expect(records[2].target).to.be(targetEl3);
               expect(records[2].intersectionRatio).to.be(0.25);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl1.style.top = '0px';
             targetEl1.style.left = '5px';
             targetEl2.style.top = '-25px';
             targetEl2.style.left = '0px';
             targetEl3.style.top = '0px';
             targetEl3.style.left = '185px';
@@ -569,33 +569,33 @@ limitations under the License.
               expect(records.length).to.be(3);
               expect(records[0].target).to.be(targetEl1);
               expect(records[0].intersectionRatio).to.be(1);
               expect(records[1].target).to.be(targetEl2);
               expect(records[1].intersectionRatio).to.be(0);
               expect(records[2].target).to.be(targetEl3);
               expect(records[2].intersectionRatio).to.be(0.75);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl1.style.top = '0px';
             targetEl1.style.left = '15px';
             targetEl2.style.top = '-35px';
             targetEl2.style.left = '0px';
             targetEl3.style.top = '0px';
             targetEl3.style.left = '175px';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(4);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].target).to.be(targetEl3);
               expect(records[0].intersectionRatio).to.be(1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           }
         ], done);
       });
 
 
       it('handles rootMargin properly', function(done) {
 
         parentEl.style.overflow = 'visible';
@@ -704,48 +704,48 @@ limitations under the License.
             io.observe(targetEl2);
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(1);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(0);
               expect(records[0].target).to.be(targetEl2);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl1.style.top = '0px';
             targetEl1.style.left = '-20px';
             targetEl2.style.top = '-21px';
             targetEl2.style.left = '0px';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(2);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(2);
               expect(records[0].intersectionRatio).to.be(0);
               expect(records[0].isIntersecting).to.be.ok();
               expect(records[0].target).to.be(targetEl1);
               expect(records[1].intersectionRatio).to.be(0);
               expect(records[1].target).to.be(targetEl2);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl1.style.top = '-20px';
             targetEl1.style.left = '200px';
             targetEl2.style.top = '200px';
             targetEl2.style.left = '200px';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(3);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(0);
               expect(records[0].target).to.be(targetEl2);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl3.style.top = '20px';
             targetEl3.style.left = '-20px';
             targetEl4.style.top = '-20px';
             targetEl4.style.left = '20px';
             io.observe(targetEl3);
             io.observe(targetEl4);
@@ -754,17 +754,17 @@ limitations under the License.
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(2);
               expect(records[0].intersectionRatio).to.be(0);
               expect(records[0].isIntersecting).to.be.ok();
               expect(records[0].target).to.be(targetEl3);
               expect(records[1].intersectionRatio).to.be(0);
               expect(records[1].target).to.be(targetEl4);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           }
         ], done);
 
       });
 
 
       it('handles zero-size targets within the root coordinate space',
           function(done) {
@@ -780,27 +780,27 @@ limitations under the License.
             targetEl1.style.height = '0px';
             io.observe(targetEl1);
             spy.waitForNotification(function() {
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(0);
               expect(records[0].isIntersecting).to.be.ok();
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             targetEl1.style.top = '-1px';
             spy.waitForNotification(function() {
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(0);
               expect(records[0].isIntersecting).to.be(false);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           }
         ], done);
       });
 
 
       it('handles root/target elements not yet in the DOM', function(done) {
 
         rootEl.remove();
@@ -825,50 +825,50 @@ limitations under the License.
             parentEl.insertBefore(targetEl1, targetEl2);
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(1);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(1);
               expect(records[0].target).to.be(targetEl1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             grandParentEl.remove();
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(2);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(0);
               expect(records[0].target).to.be(targetEl1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             rootEl.appendChild(targetEl1);
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(3);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(1);
               expect(records[0].target).to.be(targetEl1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             rootEl.remove();
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(4);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].intersectionRatio).to.be(0);
               expect(records[0].target).to.be(targetEl1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           }
         ], done);
       });
 
 
       it('handles sub-root element scrolling', function(done) {
         io = new IntersectionObserver(function(records) {
           expect(records.length).to.be(1);
@@ -913,20 +913,22 @@ limitations under the License.
 
       it('triggers only once if observed multiple times (and does not crash when collected)', function(done) {
         var spy = sinon.spy();
         io = new IntersectionObserver(spy, {root: rootEl});
         io.observe(targetEl1);
         io.observe(targetEl1);
         io.observe(targetEl1);
 
-        callDelayed(function () {
-          expect(spy.callCount).to.be(1);
-          done();
-        }, ASYNC_TIMEOUT * 3);
+        spy.waitForNotification(function() {
+          callDelayed(function () {
+            expect(spy.callCount).to.be(1);
+            done();
+          }, ASYNC_TIMEOUT);
+        });
       });
 
     });
 
     describe('observe subframe', function () {
       
       it('should not trigger if target and root are not in the same document',
           function(done) {
@@ -962,54 +964,56 @@ limitations under the License.
         targetEl4.onload = function () {
           targetEl5 = targetEl4.contentDocument.getElementById('target5');
           io.observe(targetEl5);
         }
 
         targetEl4.src = "intersectionobserver_iframe.html";
       });
 
-      it('rootBounds should is set to null for cross-origin observations', function(done) {
+      it('rootBounds is set to null for cross-origin observations', function(done) {
 
         window.onmessage = function (e) {
           expect(e.data).to.be(true);
           done();
         };
 
         targetEl4.src = "http://example.org/tests/dom/base/test/intersectionobserver_cross_domain_iframe.html";
 
       });
     
     });
 
     describe('takeRecords', function() {
 
-      it('supports getting records before the callback is invoked',
-          function(done) {
+      it('supports getting records before the callback is invoked', function(done) {
 
         var lastestRecords = [];
         io = new IntersectionObserver(function(records) {
           lastestRecords = lastestRecords.concat(records);
         }, {root: rootEl});
         io.observe(targetEl1);
 
-        window.requestAnimationFrame && requestAnimationFrame(function() {
+        window.requestAnimationFrame && requestAnimationFrame(function wait() {
           lastestRecords = lastestRecords.concat(io.takeRecords());
+          if (!lastestRecords.length) {
+            requestAnimationFrame(wait);
+            return;
+          }
+          callDelayed(function() {
+            expect(lastestRecords.length).to.be(1);
+            expect(lastestRecords[0].intersectionRatio).to.be(1);
+            done();
+          }, ASYNC_TIMEOUT);
         });
-
-        callDelayed(function() {
-          expect(lastestRecords.length).to.be(1);
-          expect(lastestRecords[0].intersectionRatio).to.be(1);
-          done();
-        }, ASYNC_TIMEOUT);
+        
       });
 
     });
 
-
     describe('unobserve', function() {
 
       it('removes targets from the internal store', function(done) {
 
         var spy = sinon.spy();
         io = new IntersectionObserver(spy, {root: rootEl});
 
         runSequence([
@@ -1022,30 +1026,30 @@ limitations under the License.
               expect(spy.callCount).to.be(1);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(2);
               expect(records[0].target).to.be(targetEl1);
               expect(records[0].intersectionRatio).to.be(1);
               expect(records[1].target).to.be(targetEl2);
               expect(records[1].intersectionRatio).to.be(1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             io.unobserve(targetEl1);
             targetEl1.style.top = targetEl2.style.top = '0px';
             targetEl1.style.left = targetEl2.style.left = '-40px';
             spy.waitForNotification(function() {
               expect(spy.callCount).to.be(2);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(1);
               expect(records[0].target).to.be(targetEl2);
               expect(records[0].intersectionRatio).to.be(0);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             io.unobserve(targetEl2);
             targetEl1.style.top = targetEl2.style.top = '0px';
             targetEl1.style.left = targetEl2.style.left = '0px';
             callDelayed(function() {
               expect(spy.callCount).to.be(2);
               done();
@@ -1074,17 +1078,17 @@ limitations under the License.
               expect(spy.callCount).to.be(1);
               var records = sortRecords(spy.lastCall.args[0]);
               expect(records.length).to.be(2);
               expect(records[0].target).to.be(targetEl1);
               expect(records[0].intersectionRatio).to.be(1);
               expect(records[1].target).to.be(targetEl2);
               expect(records[1].intersectionRatio).to.be(1);
               done();
-            }, ASYNC_TIMEOUT);
+            });
           },
           function(done) {
             io.disconnect();
             targetEl1.style.top = targetEl2.style.top = '0px';
             targetEl1.style.left = targetEl2.style.left = '-40px';
             callDelayed(function() {
               expect(spy.callCount).to.be(1);
               done();
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -5458,22 +5458,22 @@ CanvasRenderingContext2D::DrawDirectlyTo
                        Scale(1.0 / contextScale.width,
                              1.0 / contextScale.height).
                        Translate(aDest.x - aSrc.x, aDest.y - aSrc.y));
 
   // FLAG_CLAMP is added for increased performance, since we never tile here.
   uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP;
 
   CSSIntSize sz(scaledImageSize.width, scaledImageSize.height); // XXX hmm is scaledImageSize really in CSS pixels?
-  SVGImageContext svgContext(Some(sz), Nothing(), CurrentState().globalAlpha);
+  SVGImageContext svgContext(Some(sz));
 
   auto result = aImage.mImgContainer->
     Draw(context, scaledImageSize,
          ImageRegion::Create(gfxRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height)),
-         aImage.mWhichFrame, SamplingFilter::GOOD, Some(svgContext), modifiedFlags, 1.0);
+         aImage.mWhichFrame, SamplingFilter::GOOD, Some(svgContext), modifiedFlags, CurrentState().globalAlpha);
 
   if (result != DrawResult::SUCCESS) {
     NS_WARNING("imgIContainer::Draw failed");
   }
 }
 
 void
 CanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& aOp,
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -2808,16 +2808,28 @@ QuotaManager::
 ShutdownObserver::Observe(nsISupports* aSubject,
                           const char* aTopic,
                           const char16_t* aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
   MOZ_ASSERT(gInstance);
 
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (NS_WARN_IF(!observerService)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Unregister ourselves from the observer service first to make sure the
+  // nested event loop below will not cause re-entrancy issues.
+  Unused <<
+    observerService->RemoveObserver(this,
+                                    PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
+
   QuotaManagerService* qms = QuotaManagerService::Get();
   MOZ_ASSERT(qms);
 
   qms->NoteShuttingDownManager();
 
   for (RefPtr<Client>& client : gInstance->mClients) {
     client->WillShutdown();
   }
--- a/dom/quota/StorageManager.cpp
+++ b/dom/quota/StorageManager.cpp
@@ -6,241 +6,622 @@
 
 #include "StorageManager.h"
 
 #include "mozilla/dom/PromiseWorkerProxy.h"
 #include "mozilla/dom/quota/QuotaManagerService.h"
 #include "mozilla/dom/StorageManagerBinding.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/ErrorResult.h"
+#include "nsContentPermissionHelper.h"
 #include "nsIQuotaCallbacks.h"
 #include "nsIQuotaRequests.h"
 #include "nsPIDOMWindow.h"
 
 using namespace mozilla::dom::workers;
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
-// This class is used to get quota usage callback.
-class EstimateResolver final
-  : public nsIQuotaUsageCallback
+// This class is used to get quota usage, request persist and check persisted
+// status callbacks.
+class RequestResolver final
+  : public nsIQuotaCallback
+  , public nsIQuotaUsageCallback
 {
+public:
+  enum Type
+  {
+    Estimate,
+    Persist,
+    Persisted
+  };
+
+private:
   class FinishWorkerRunnable;
 
   // If this resolver was created for a window then mPromise must be non-null.
   // Otherwise mProxy must be non-null.
   RefPtr<Promise> mPromise;
   RefPtr<PromiseWorkerProxy> mProxy;
 
   nsresult mResultCode;
   StorageEstimate mStorageEstimate;
+  const Type mType;
+  bool mPersisted;
 
 public:
-  explicit EstimateResolver(Promise* aPromise)
+  RequestResolver(Type aType, Promise* aPromise)
     : mPromise(aPromise)
     , mResultCode(NS_OK)
+    , mType(aType)
+    , mPersisted(false)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aPromise);
   }
 
-  explicit EstimateResolver(PromiseWorkerProxy* aProxy)
+  RequestResolver(Type aType, PromiseWorkerProxy* aProxy)
     : mProxy(aProxy)
     , mResultCode(NS_OK)
+    , mType(aType)
+    , mPersisted(false)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aProxy);
   }
 
+  Type
+  GetType() const
+  {
+    return mType;
+  }
+
   void
-  ResolveOrReject(Promise* aPromise);
+  ResolveOrReject();
 
   NS_DECL_THREADSAFE_ISUPPORTS
-
+  NS_DECL_NSIQUOTACALLBACK
   NS_DECL_NSIQUOTAUSAGECALLBACK
 
 private:
-  ~EstimateResolver()
+  ~RequestResolver()
   { }
+
+  nsresult
+  GetStorageEstimate(nsIVariant* aResult);
+
+  nsresult
+  GetPersisted(nsIVariant* aResult);
+
+  template <typename T>
+  nsresult
+  OnCompleteOrUsageResult(T* aRequest);
+
+  nsresult
+  Finish();
 };
 
 // This class is used to return promise on worker thread.
-class EstimateResolver::FinishWorkerRunnable final
+class RequestResolver::FinishWorkerRunnable final
   : public WorkerRunnable
 {
-  RefPtr<EstimateResolver> mResolver;
+  RefPtr<RequestResolver> mResolver;
 
 public:
-  explicit FinishWorkerRunnable(EstimateResolver* aResolver)
+  explicit FinishWorkerRunnable(RequestResolver* aResolver)
     : WorkerRunnable(aResolver->mProxy->GetWorkerPrivate())
     , mResolver(aResolver)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aResolver);
   }
 
-  virtual bool
+  bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
 };
 
-class EstimateWorkerMainThreadRunnable
+class EstimateWorkerMainThreadRunnable final
   : public WorkerMainThreadRunnable
 {
   RefPtr<PromiseWorkerProxy> mProxy;
 
 public:
   EstimateWorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
                                    PromiseWorkerProxy* aProxy)
     : WorkerMainThreadRunnable(aWorkerPrivate,
                                NS_LITERAL_CSTRING("StorageManager :: Estimate"))
     , mProxy(aProxy)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
     MOZ_ASSERT(aProxy);
   }
 
-  virtual bool
+  bool
+  MainThreadRun() override;
+};
+
+class PersistedWorkerMainThreadRunnable final
+  : public WorkerMainThreadRunnable
+{
+  RefPtr<PromiseWorkerProxy> mProxy;
+
+public:
+  PersistedWorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+                                    PromiseWorkerProxy* aProxy)
+    : WorkerMainThreadRunnable(aWorkerPrivate,
+                               NS_LITERAL_CSTRING("StorageManager :: Persisted"))
+    , mProxy(aProxy)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(aProxy);
+  }
+
+  bool
   MainThreadRun() override;
 };
 
+/*******************************************************************************
+ * PersistentStoragePermissionRequest
+ ******************************************************************************/
+
+class PersistentStoragePermissionRequest final
+  : public nsIContentPermissionRequest
+{
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
+  RefPtr<Promise> mPromise;
+  nsCOMPtr<nsIContentPermissionRequester> mRequester;
+
+public:
+  PersistentStoragePermissionRequest(nsIPrincipal* aPrincipal,
+                                     nsPIDOMWindowInner* aWindow,
+                                     Promise* aPromise)
+    : mPrincipal(aPrincipal)
+    , mWindow(aWindow)
+    , mPromise(aPromise)
+  {
+    MOZ_ASSERT(aPrincipal);
+    MOZ_ASSERT(aWindow);
+    MOZ_ASSERT(aPromise);
+
+    mRequester = new nsContentPermissionRequester(mWindow);
+  }
+
+  nsresult
+  Start();
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICONTENTPERMISSIONREQUEST
+
+private:
+  ~PersistentStoragePermissionRequest()
+  { }
+};
+
 nsresult
 GetUsageForPrincipal(nsIPrincipal* aPrincipal,
                      nsIQuotaUsageCallback* aCallback,
                      nsIQuotaUsageRequest** aRequest)
 {
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(aCallback);
   MOZ_ASSERT(aRequest);
 
   nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate();
   if (NS_WARN_IF(!qms)) {
     return NS_ERROR_FAILURE;
   }
 
-  nsresult rv = qms->GetUsageForPrincipal(aPrincipal, aCallback, true, aRequest);
+  nsresult rv = qms->GetUsageForPrincipal(aPrincipal,
+                                          aCallback,
+                                          true,
+                                          aRequest);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 };
 
 nsresult
-GetStorageEstimate(nsIQuotaUsageRequest* aRequest,
-                   StorageEstimate& aStorageEstimate)
+Persisted(nsIPrincipal* aPrincipal,
+          nsIQuotaCallback* aCallback,
+          nsIQuotaRequest** aRequest)
 {
+  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(aCallback);
   MOZ_ASSERT(aRequest);
 
-  nsCOMPtr<nsIVariant> result;
-  nsresult rv = aRequest->GetResult(getter_AddRefs(result));
+  nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate();
+  if (NS_WARN_IF(!qms)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIQuotaRequest> request;
+  nsresult rv = qms->Persisted(aPrincipal, getter_AddRefs(request));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  // All the methods in nsIQuotaManagerService shouldn't synchronously fire
+  // any callbacks when they are being executed. Even when a result is ready,
+  // a new runnable should be dispatched to current thread to fire the callback
+  // asynchronously. It's safe to set the callback after we call Persisted().
+  MOZ_ALWAYS_SUCCEEDS(request->SetCallback(aCallback));
+
+  request.forget(aRequest);
+
+  return NS_OK;
+};
+
+already_AddRefed<Promise>
+ExecuteOpOnMainOrWorkerThread(nsIGlobalObject* aGlobal,
+                              RequestResolver::Type aType,
+                              ErrorResult& aRv)
+{
+  MOZ_ASSERT(aGlobal);
+  MOZ_ASSERT_IF(aType == RequestResolver::Type::Persist,
+                NS_IsMainThread());
+
+  RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+  if (NS_WARN_IF(!promise)) {
+    return nullptr;
+  }
+
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
+    if (NS_WARN_IF(!window)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+
+    nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+    if (NS_WARN_IF(!doc)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+
+    nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
+    MOZ_ASSERT(principal);
+
+    // Storage Standard 7. API
+    // If origin is an opaque origin, then reject promise with a TypeError.
+    if (principal->GetIsNullPrincipal()) {
+      promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR);
+      return promise.forget();
+    }
+
+    switch (aType) {
+      case RequestResolver::Type::Persisted: {
+        RefPtr<RequestResolver> resolver =
+          new RequestResolver(RequestResolver::Type::Persisted, promise);
+
+        RefPtr<nsIQuotaRequest> request;
+        aRv = Persisted(principal, resolver, getter_AddRefs(request));
+
+        break;
+      }
+
+      case RequestResolver::Type::Persist: {
+        RefPtr<PersistentStoragePermissionRequest> request =
+          new PersistentStoragePermissionRequest(principal, window, promise);
+
+        // In private browsing mode, no permission prompt.
+        if (nsContentUtils::IsInPrivateBrowsing(doc)) {
+          aRv = request->Cancel();
+        } else {
+          aRv = request->Start();
+        }
+
+        break;
+      }
+
+      case RequestResolver::Type::Estimate: {
+        RefPtr<RequestResolver> resolver =
+          new RequestResolver(RequestResolver::Type::Estimate, promise);
+
+        RefPtr<nsIQuotaUsageRequest> request;
+        aRv = GetUsageForPrincipal(principal,
+                                   resolver,
+                                   getter_AddRefs(request));
+
+        break;
+      }
+
+      default:
+        MOZ_CRASH("Invalid aRequest type!");
+    }
+
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
+
+    return promise.forget();
+  }
+
+  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(workerPrivate);
+
+  RefPtr<PromiseWorkerProxy> promiseProxy =
+    PromiseWorkerProxy::Create(workerPrivate, promise);
+  if (NS_WARN_IF(!promiseProxy)) {
+    return nullptr;
+  }
+
+  switch (aType) {
+    case RequestResolver::Type::Estimate: {
+      RefPtr<EstimateWorkerMainThreadRunnable> runnnable =
+        new EstimateWorkerMainThreadRunnable(promiseProxy->GetWorkerPrivate(),
+                                             promiseProxy);
+      runnnable->Dispatch(Terminating, aRv);
+
+      break;
+    }
+
+    case RequestResolver::Type::Persisted: {
+      RefPtr<PersistedWorkerMainThreadRunnable> runnnable =
+        new PersistedWorkerMainThreadRunnable(promiseProxy->GetWorkerPrivate(),
+                                              promiseProxy);
+      runnnable->Dispatch(Terminating, aRv);
+
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Invalid aRequest type");
+  }
+
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return promise.forget();
+};
+
+} // namespace
+
+/*******************************************************************************
+ * Local class implementations
+ ******************************************************************************/
+
+void
+RequestResolver::ResolveOrReject()
+{
+  class MOZ_STACK_CLASS AutoCleanup final
+  {
+    RefPtr<PromiseWorkerProxy> mProxy;
+
+  public:
+    explicit AutoCleanup(PromiseWorkerProxy* aProxy)
+      : mProxy(aProxy)
+    {
+      MOZ_ASSERT(aProxy);
+    }
+
+    ~AutoCleanup()
+    {
+      MOZ_ASSERT(mProxy);
+
+      mProxy->CleanUp();
+    }
+  };
+
+  RefPtr<Promise> promise;
+  Maybe<AutoCleanup> autoCleanup;
+
+  if (mPromise) {
+    promise = mPromise;
+  } else {
+    MOZ_ASSERT(mProxy);
+
+    promise = mProxy->WorkerPromise();
+
+    // Only clean up for worker case.
+    autoCleanup.emplace(mProxy);
+  }
+
+  MOZ_ASSERT(promise);
+
+  if (mType == Type::Estimate) {
+    if (NS_SUCCEEDED(mResultCode)) {
+      promise->MaybeResolve(mStorageEstimate);
+    } else {
+      promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR);
+    }
+
+    return;
+  }
+
+  MOZ_ASSERT(mType == Type::Persist || mType == Type::Persisted);
+
+  if (NS_SUCCEEDED(mResultCode)) {
+    promise->MaybeResolve(mPersisted);
+  } else {
+    promise->MaybeResolve(false);
+  }
+}
+
+NS_IMPL_ISUPPORTS(RequestResolver, nsIQuotaUsageCallback, nsIQuotaCallback)
+
+nsresult
+RequestResolver::GetStorageEstimate(nsIVariant* aResult)
+{
+  MOZ_ASSERT(aResult);
+  MOZ_ASSERT(mType == Type::Estimate);
+
+#ifdef DEBUG
+  uint16_t dataType;
+  MOZ_ALWAYS_SUCCEEDS(aResult->GetDataType(&dataType));
+  MOZ_ASSERT(dataType == nsIDataType::VTYPE_INTERFACE_IS);
+#endif
+
   nsID* iid;
   nsCOMPtr<nsISupports> supports;
-  rv = result->GetAsInterface(&iid, getter_AddRefs(supports));
+  nsresult rv = aResult->GetAsInterface(&iid, getter_AddRefs(supports));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   free(iid);
 
   nsCOMPtr<nsIQuotaOriginUsageResult> originUsageResult =
     do_QueryInterface(supports);
   MOZ_ASSERT(originUsageResult);
 
   MOZ_ALWAYS_SUCCEEDS(
-    originUsageResult->GetUsage(&aStorageEstimate.mUsage.Construct()));
+    originUsageResult->GetUsage(&mStorageEstimate.mUsage.Construct()));
 
   MOZ_ALWAYS_SUCCEEDS(
-    originUsageResult->GetLimit(&aStorageEstimate.mQuota.Construct()));
+    originUsageResult->GetLimit(&mStorageEstimate.mQuota.Construct()));
 
   return NS_OK;
 }
 
-} // namespace
+nsresult
+RequestResolver::GetPersisted(nsIVariant* aResult)
+{
+  MOZ_ASSERT(aResult);
+  MOZ_ASSERT(mType == Type::Persist || mType == Type::Persisted);
 
-/*******************************************************************************
- * Local class implementations
- ******************************************************************************/
+#ifdef DEBUG
+  uint16_t dataType;
+  MOZ_ALWAYS_SUCCEEDS(aResult->GetDataType(&dataType));
+#endif
+
+  if (mType == Type::Persist) {
+    MOZ_ASSERT(dataType == nsIDataType::VTYPE_VOID);
 
-void
-EstimateResolver::ResolveOrReject(Promise* aPromise)
-{
-  MOZ_ASSERT(aPromise);
+    mPersisted = true;
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(dataType == nsIDataType::VTYPE_BOOL);
 
-  if (NS_SUCCEEDED(mResultCode)) {
-    aPromise->MaybeResolve(mStorageEstimate);
-  } else {
-    aPromise->MaybeReject(mResultCode);
+  bool persisted;
+  nsresult rv = aResult->GetAsBool(&persisted);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
+
+  mPersisted = persisted;
+  return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS(EstimateResolver, nsIQuotaUsageCallback)
-
-NS_IMETHODIMP
-EstimateResolver::OnUsageResult(nsIQuotaUsageRequest *aRequest)
+template <typename T>
+nsresult
+RequestResolver::OnCompleteOrUsageResult(T* aRequest)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRequest);
 
-  nsresult rv = aRequest->GetResultCode(&mResultCode);
+  nsresult resultCode;
+  nsresult rv = aRequest->GetResultCode(&resultCode);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    mResultCode = rv;
-  } else if (NS_SUCCEEDED(mResultCode)) {
-    rv = GetStorageEstimate(aRequest, mStorageEstimate);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      mResultCode = rv;
-    }
+    return rv;
+  }
+
+  if (NS_FAILED(resultCode)) {
+    return resultCode;
+  }
+
+  nsCOMPtr<nsIVariant> result;
+  rv = aRequest->GetResult(getter_AddRefs(result));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
+  if (mType == Type::Estimate) {
+    rv = GetStorageEstimate(result);
+  } else {
+    MOZ_ASSERT(mType == Type::Persist || mType == Type::Persisted);
+
+    rv = GetPersisted(result);
+  }
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+RequestResolver::Finish()
+{
   // In a main thread request.
   if (!mProxy) {
     MOZ_ASSERT(mPromise);
 
-    ResolveOrReject(mPromise);
+    ResolveOrReject();
     return NS_OK;
   }
 
-  // In a worker thread request.
-  MutexAutoLock lock(mProxy->Lock());
+  {
+    // In a worker thread request.
+    MutexAutoLock lock(mProxy->Lock());
 
-  if (NS_WARN_IF(mProxy->CleanedUp())) {
-    return NS_ERROR_FAILURE;
+    if (NS_WARN_IF(mProxy->CleanedUp())) {
+      return NS_ERROR_FAILURE;
+    }
+
+    RefPtr<FinishWorkerRunnable> runnable = new FinishWorkerRunnable(this);
+    if (NS_WARN_IF(!runnable->Dispatch())) {
+      return NS_ERROR_FAILURE;
+    }
   }
 
-  RefPtr<FinishWorkerRunnable> runnable = new FinishWorkerRunnable(this);
-  if (NS_WARN_IF(!runnable->Dispatch())) {
-    return NS_ERROR_FAILURE;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestResolver::OnComplete(nsIQuotaRequest *aRequest)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRequest);
+
+  mResultCode = OnCompleteOrUsageResult(aRequest);
+
+  nsresult rv = Finish();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+RequestResolver::OnUsageResult(nsIQuotaUsageRequest *aRequest)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRequest);
+
+  mResultCode = OnCompleteOrUsageResult(aRequest);
+
+  nsresult rv = Finish();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
   return NS_OK;
 }
 
 bool
-EstimateResolver::
+RequestResolver::
 FinishWorkerRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
 {
+  MOZ_ASSERT(aCx);
   MOZ_ASSERT(aWorkerPrivate);
   aWorkerPrivate->AssertIsOnWorkerThread();
 
-  RefPtr<PromiseWorkerProxy> proxy = mResolver->mProxy;
-  MOZ_ASSERT(proxy);
-
-  RefPtr<Promise> promise = proxy->WorkerPromise();
-  MOZ_ASSERT(promise);
-
-  mResolver->ResolveOrReject(promise);
-
-  proxy->CleanUp();
+  MOZ_ASSERT(mResolver);
+  mResolver->ResolveOrReject();
 
   return true;
 }
 
 bool
 EstimateWorkerMainThreadRunnable::MainThreadRun()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -252,100 +633,221 @@ EstimateWorkerMainThreadRunnable::MainTh
     if (mProxy->CleanedUp()) {
       return true;
     }
     principal = mProxy->GetWorkerPrivate()->GetPrincipal();
   }
 
   MOZ_ASSERT(principal);
 
-  RefPtr<EstimateResolver> resolver = new EstimateResolver(mProxy);
+  RefPtr<RequestResolver> resolver =
+    new RequestResolver(RequestResolver::Type::Estimate, mProxy);
 
   RefPtr<nsIQuotaUsageRequest> request;
   nsresult rv =
     GetUsageForPrincipal(principal, resolver, getter_AddRefs(request));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
 
   return true;
 }
 
+bool
+PersistedWorkerMainThreadRunnable::MainThreadRun()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIPrincipal> principal;
+
+  {
+    MutexAutoLock lock(mProxy->Lock());
+    if (mProxy->CleanedUp()) {
+      return true;
+    }
+    principal = mProxy->GetWorkerPrivate()->GetPrincipal();
+  }
+
+  MOZ_ASSERT(principal);
+
+  RefPtr<RequestResolver> resolver =
+    new RequestResolver(RequestResolver::Type::Persisted, mProxy);
+
+  RefPtr<nsIQuotaRequest> request;
+  nsresult rv = Persisted(principal, resolver, getter_AddRefs(request));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  return true;
+}
+
+nsresult
+PersistentStoragePermissionRequest::Start()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Grant permission if pref'ed on.
+  if (Preferences::GetBool("dom.storageManager.prompt.testing", false)) {
+    if (Preferences::GetBool("dom.storageManager.prompt.testing.allow",
+                             false)) {
+      return Allow(JS::UndefinedHandleValue);
+    }
+
+    return Cancel();
+  }
+
+  return nsContentPermissionUtils::AskPermission(this, mWindow);
+}
+
+NS_IMPL_ISUPPORTS(PersistentStoragePermissionRequest,
+                  nsIContentPermissionRequest)
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::GetPrincipal(nsIPrincipal** aPrincipal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(mPrincipal);
+
+  NS_ADDREF(*aPrincipal = mPrincipal);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::GetWindow(mozIDOMWindow** aRequestingWindow)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRequestingWindow);
+  MOZ_ASSERT(mWindow);
+
+  NS_ADDREF(*aRequestingWindow = mWindow);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::GetElement(nsIDOMElement** aElement)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aElement);
+
+  *aElement = nullptr;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::Cancel()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mPromise);
+
+  RefPtr<RequestResolver> resolver =
+    new RequestResolver(RequestResolver::Type::Persisted, mPromise);
+
+  RefPtr<nsIQuotaRequest> request;
+
+  return Persisted(mPrincipal, resolver, getter_AddRefs(request));
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::Allow(JS::HandleValue aChoices)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<RequestResolver> resolver =
+    new RequestResolver(RequestResolver::Type::Persist, mPromise);
+
+  nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate();
+  if (NS_WARN_IF(!qms)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<nsIQuotaRequest> request;
+
+  nsresult rv = qms->Persist(mPrincipal, getter_AddRefs(request));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ALWAYS_SUCCEEDS(request->SetCallback(resolver));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::GetRequester(
+                                     nsIContentPermissionRequester** aRequester)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRequester);
+
+  nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+  requester.forget(aRequester);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PersistentStoragePermissionRequest::GetTypes(nsIArray** aTypes)
+{
+  MOZ_ASSERT(aTypes);
+
+  nsTArray<nsString> emptyOptions;
+
+  return nsContentPermissionUtils::CreatePermissionArray(
+                                       NS_LITERAL_CSTRING("persistent-storage"),
+                                       NS_LITERAL_CSTRING("unused"),
+                                       emptyOptions,
+                                       aTypes);
+}
+
 /*******************************************************************************
  * StorageManager
  ******************************************************************************/
 
 StorageManager::StorageManager(nsIGlobalObject* aGlobal)
   : mOwner(aGlobal)
 {
   MOZ_ASSERT(aGlobal);
 }
 
 StorageManager::~StorageManager()
 {
 }
 
 already_AddRefed<Promise>
+StorageManager::Persisted(ErrorResult& aRv)
+{
+  MOZ_ASSERT(mOwner);
+
+  return ExecuteOpOnMainOrWorkerThread(mOwner,
+                                       RequestResolver::Type::Persisted,
+                                       aRv);
+}
+
+already_AddRefed<Promise>
+StorageManager::Persist(ErrorResult& aRv)
+{
+  MOZ_ASSERT(mOwner);
+
+  return ExecuteOpOnMainOrWorkerThread(mOwner,
+                                       RequestResolver::Type::Persist,
+                                       aRv);
+}
+
+already_AddRefed<Promise>
 StorageManager::Estimate(ErrorResult& aRv)
 {
   MOZ_ASSERT(mOwner);
 
-  RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
-  if (NS_WARN_IF(!promise)) {
-    return nullptr;
-  }
-
-  if (NS_IsMainThread()) {
-    nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mOwner);
-    if (NS_WARN_IF(!window)) {
-      aRv.Throw(NS_ERROR_FAILURE);
-      return nullptr;
-    }
-
-    nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
-    if (NS_WARN_IF(!doc)) {
-      aRv.Throw(NS_ERROR_FAILURE);
-      return nullptr;
-    }
-
-    nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
-    MOZ_ASSERT(principal);
-
-    RefPtr<EstimateResolver> resolver = new EstimateResolver(promise);
-
-    RefPtr<nsIQuotaUsageRequest> request;
-    nsresult rv =
-      GetUsageForPrincipal(principal, resolver, getter_AddRefs(request));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      aRv.Throw(rv);
-      return nullptr;
-    }
-
-    return promise.forget();
-  }
-
-  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
-  MOZ_ASSERT(workerPrivate);
-
-  RefPtr<PromiseWorkerProxy> promiseProxy =
-    PromiseWorkerProxy::Create(workerPrivate, promise);
-  if (NS_WARN_IF(!promiseProxy)) {
-    return nullptr;
-  }
-
-  RefPtr<EstimateWorkerMainThreadRunnable> runnnable =
-    new EstimateWorkerMainThreadRunnable(promiseProxy->GetWorkerPrivate(),
-                                         promiseProxy);
-
-  runnnable->Dispatch(Terminating, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
-  return promise.forget();
+  return ExecuteOpOnMainOrWorkerThread(mOwner,
+                                       RequestResolver::Type::Estimate,
+                                       aRv);
 }
 
 // static
 bool
 StorageManager::PrefEnabled(JSContext* aCx, JSObject* aObj)
 {
   if (NS_IsMainThread()) {
     return Preferences::GetBool("dom.storageManager.enabled");
--- a/dom/quota/StorageManager.h
+++ b/dom/quota/StorageManager.h
@@ -35,16 +35,22 @@ public:
   nsIGlobalObject*
   GetParentObject() const
   {
     return mOwner;
   }
 
   // WebIDL
   already_AddRefed<Promise>
+  Persisted(ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Persist(ErrorResult& aRv);
+
+  already_AddRefed<Promise>
   Estimate(ErrorResult& aRv);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(StorageManager)
 
   // nsWrapperCache
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
--- a/dom/quota/moz.build
+++ b/dom/quota/moz.build
@@ -2,16 +2,20 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM: Quota Manager")
 
+MOCHITEST_MANIFESTS += ['test/mochitest.ini']
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
 XPCSHELL_TESTS_MANIFESTS += [
     'test/unit/xpcshell.ini'
 ]
 
 XPIDL_SOURCES += [
     'nsIQuotaCallbacks.idl',
     'nsIQuotaManagerService.idl',
     'nsIQuotaRequests.idl',
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/browser.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+skip-if = (buildapp != "browser")
+support-files =
+  head.js
+  browserHelpers.js
+  browser_permissionsPrompt.html
+
+[browser_permissionsPromptAllow.js]
+[browser_permissionsPromptDeny.js]
+[browser_permissionsPromptUnknown.js]
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/browserHelpers.js
@@ -0,0 +1,49 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+var testResult;
+
+function clearAllDatabases(callback)
+{
+  let qms = SpecialPowers.Services.qms;
+  let principal = SpecialPowers.wrap(document).nodePrincipal;
+  let request = qms.clearStoragesForPrincipal(principal);
+  let cb = SpecialPowers.wrapCallback(callback);
+  request.callback = cb;
+}
+
+function runTest()
+{
+  clearAllDatabases(() => {
+    testGenerator.next();
+  });
+}
+
+function finishTestNow()
+{
+  if (testGenerator) {
+    testGenerator.return();
+    testGenerator = undefined;
+  }
+}
+
+function finishTest()
+{
+  clearAllDatabases(() => {
+    setTimeout(finishTestNow, 0);
+    setTimeout(() => {
+      window.parent.postMessage(testResult, "*");
+    }, 0);
+  });
+}
+
+function continueToNextStep()
+{
+  setTimeout(() => {
+    testGenerator.next();
+  }, 0);
+}
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/browser_permissionsPrompt.html
@@ -0,0 +1,35 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+  <head>
+    <meta charset=UTF-8>
+    <title>Persistent-Storage Permission Prompt Test</title>
+
+    <script type="text/javascript">
+      function* testSteps()
+      {
+        SpecialPowers.pushPrefEnv({
+          "set": [["dom.storageManager.enabled", true],
+                  ["dom.storageManager.prompt.testing", false],
+                  ["dom.storageManager.prompt.testing.allow", false]]
+        }, continueToNextStep);
+        yield undefined;
+
+        navigator.storage.persist().then(result => {
+          testGenerator.next(result);
+        });
+        testResult = yield undefined;
+
+        finishTest();
+      }
+    </script>
+
+    <script type="text/javascript" src="browserHelpers.js"></script>
+
+  </head>
+
+  <body onload="runTest();" onunload="finishTestNow();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/browser_permissionsPromptAllow.js
@@ -0,0 +1,65 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const testPageURL =
+  "https://example.com/browser/dom/quota/test/browser_permissionsPrompt.html";
+
+add_task(function* testPermissionAllow() {
+  removePermission(testPageURL, "persistent-storage");
+  info("Creating tab");
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  info("Loading test page: " + testPageURL);
+  gBrowser.selectedBrowser.loadURI(testPageURL);
+  yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+  registerPopupEventHandler("popupshowing", function () {
+    ok(true, "prompt showing");
+  });
+  registerPopupEventHandler("popupshown", function () {
+    ok(true, "prompt shown");
+    triggerMainCommand(this);
+  });
+  registerPopupEventHandler("popuphidden", function () {
+    ok(true, "prompt hidden");
+  });
+
+  yield promiseMessage(true, gBrowser);
+
+  is(getPermission(testPageURL, "persistent-storage"),
+     Components.interfaces.nsIPermissionManager.ALLOW_ACTION,
+     "Correct permission set");
+  gBrowser.removeCurrentTab();
+  unregisterAllPopupEventHandlers();
+  // Keep persistent-storage permission for the next test.
+});
+
+add_task(function* testNoPermissionPrompt() {
+  info("Creating tab");
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  info("Loading test page: " + testPageURL);
+  gBrowser.selectedBrowser.loadURI(testPageURL);
+  yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+  registerPopupEventHandler("popupshowing", function () {
+    ok(false, "Shouldn't show a popup this time");
+  });
+  registerPopupEventHandler("popupshown", function () {
+    ok(false, "Shouldn't show a popup this time");
+  });
+  registerPopupEventHandler("popuphidden", function () {
+    ok(false, "Shouldn't show a popup this time");
+  });
+
+  yield promiseMessage(true, gBrowser);
+
+  is(getPermission(testPageURL, "persistent-storage"),
+     Components.interfaces.nsIPermissionManager.ALLOW_ACTION,
+     "Correct permission set");
+  gBrowser.removeCurrentTab();
+  unregisterAllPopupEventHandlers();
+  removePermission(testPageURL, "persistent-storage");
+});
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/browser_permissionsPromptDeny.js
@@ -0,0 +1,95 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const testPageURL =
+  "https://example.com/browser/dom/quota/test/browser_permissionsPrompt.html";
+
+add_task(function* testPermissionDenied() {
+  removePermission(testPageURL, "persistent-storage");
+  info("Creating tab");
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  info("Loading test page: " + testPageURL);
+  gBrowser.selectedBrowser.loadURI(testPageURL);
+  yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+  registerPopupEventHandler("popupshowing", function () {
+    ok(true, "prompt showing");
+  });
+  registerPopupEventHandler("popupshown", function () {
+    ok(true, "prompt shown");
+    triggerSecondaryCommand(this);
+  });
+  registerPopupEventHandler("popuphidden", function () {
+    ok(true, "prompt hidden");
+  });
+
+  yield promiseMessage(false, gBrowser);
+
+  is(getPermission(testPageURL, "persistent-storage"),
+     Components.interfaces.nsIPermissionManager.DENY_ACTION,
+     "Correct permission set");
+  unregisterAllPopupEventHandlers();
+  gBrowser.removeCurrentTab();
+  // Keep persistent-storage permission for the next test.
+});
+
+add_task(function* testNoPermissionPrompt() {
+  info("Creating tab");
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  info("Loading test page: " + testPageURL);
+  gBrowser.selectedBrowser.loadURI(testPageURL);
+  yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+  registerPopupEventHandler("popupshowing", function () {
+    ok(false, "Shouldn't show a popup this time");
+  });
+  registerPopupEventHandler("popupshown", function () {
+    ok(false, "Shouldn't show a popup this time");
+  });
+  registerPopupEventHandler("popuphidden", function () {
+    ok(false, "Shouldn't show a popup this time");
+  });
+
+  yield promiseMessage(false, gBrowser);
+
+  is(getPermission(testPageURL, "persistent-storage"),
+     Components.interfaces.nsIPermissionManager.DENY_ACTION,
+     "Correct permission set");
+  unregisterAllPopupEventHandlers();
+  gBrowser.removeCurrentTab();
+  removePermission(testPageURL, "persistent-storage");
+});
+
+add_task(function* testPermissionDeniedDismiss() {
+  info("Creating tab");
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  info("Loading test page: " + testPageURL);
+  gBrowser.selectedBrowser.loadURI(testPageURL);
+  yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+  registerPopupEventHandler("popupshowing", function () {
+    ok(true, "prompt showing");
+  });
+  registerPopupEventHandler("popupshown", function () {
+    ok(true, "prompt shown");
+    // Dismiss permission prompt.
+    dismissNotification(this);
+  });
+  registerPopupEventHandler("popuphidden", function () {
+    ok(true, "prompt hidden");
+  });
+
+  yield promiseMessage(false, gBrowser);
+
+  is(getPermission(testPageURL, "persistent-storage"),
+     Components.interfaces.nsIPermissionManager.DENY_ACTION,
+     "Correct permission set");
+  unregisterAllPopupEventHandlers();
+  gBrowser.removeCurrentTab();
+  removePermission(testPageURL, "persistent-storage");
+});
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/browser_permissionsPromptUnknown.js
@@ -0,0 +1,40 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const testPageURL =
+  "https://example.com/browser/dom/quota/test/browser_permissionsPrompt.html";
+
+add_task(function* testPermissionUnknownInPrivateWindow() {
+  removePermission(testPageURL, "persistent-storage");
+  info("Creating private window");
+  let win = yield BrowserTestUtils.openNewBrowserWindow({ private : true });
+
+  info("Creating private tab");
+  win.gBrowser.selectedTab = win.gBrowser.addTab();
+
+  info("Loading test page: " + testPageURL);
+  win.gBrowser.selectedBrowser.loadURI(testPageURL);
+  yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+
+  registerPopupEventHandler("popupshowing", function () {
+    ok(false, "Shouldn't show a popup this time");
+  }, win);
+  registerPopupEventHandler("popupshown", function () {
+    ok(false, "Shouldn't show a popup this time");
+  }, win);
+  registerPopupEventHandler("popuphidden", function () {
+    ok(false, "Shouldn't show a popup this time");
+  }, win);
+
+  yield promiseMessage(false, win.gBrowser);
+
+  is(getPermission(testPageURL, "persistent-storage"),
+     Components.interfaces.nsIPermissionManager.UNKNOWN_ACTION,
+     "Correct permission set");
+  unregisterAllPopupEventHandlers(win);
+  win.gBrowser.removeCurrentTab();
+  win.close();
+  removePermission(testPageURL, "persistent-storage");
+});
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/head.js
@@ -0,0 +1,121 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var gActiveListeners = {};
+
+// These event (un)registration handlers only work for one window, DONOT use
+// them with multiple windows.
+
+function registerPopupEventHandler(eventName, callback, win)
+{
+  if (!win) {
+    win = window;
+  }
+  gActiveListeners[eventName] = function (event) {
+    if (event.target != win.PopupNotifications.panel)
+      return;
+    win.PopupNotifications.panel.removeEventListener(
+                                                   eventName,
+                                                   gActiveListeners[eventName]);
+    delete gActiveListeners[eventName];
+
+    callback.call(win.PopupNotifications.panel);
+  }
+  win.PopupNotifications.panel.addEventListener(eventName,
+                                                gActiveListeners[eventName]);
+}
+
+function unregisterAllPopupEventHandlers(win)
+{
+  if (!win) {
+    win = window;
+  }
+  for (let eventName in gActiveListeners) {
+    win.PopupNotifications.panel.removeEventListener(
+                                                   eventName,
+                                                   gActiveListeners[eventName]);
+  }
+  gActiveListeners = {};
+}
+
+function triggerMainCommand(popup, win)
+{
+  if (!win) {
+    win = window;
+  }
+  info("triggering main command");
+  let notifications = popup.childNodes;
+  ok(notifications.length > 0, "at least one notification displayed");
+  let notification = notifications[0];
+  info("triggering command: " + notification.getAttribute("buttonlabel"));
+
+  EventUtils.synthesizeMouseAtCenter(notification.button, {}, win);
+}
+
+function triggerSecondaryCommand(popup, win)
+{
+  if (!win) {
+    win = window;
+  }
+  info("triggering secondary command");
+  let notifications = popup.childNodes;
+  ok(notifications.length > 0, "at least one notification displayed");
+  let notification = notifications[0];
+  EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {}, win);
+}
+
+function dismissNotification(popup, win)
+{
+  if (!win) {
+    win = window;
+  }
+  info("dismissing notification");
+  executeSoon(function () {
+    EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+  });
+}
+
+function promiseMessage(aMessage, browser)
+{
+  return ContentTask.spawn(browser.selectedBrowser, aMessage, function* (aMessage) {
+    yield new Promise((resolve, reject) => {
+      content.addEventListener("message", function(event) {
+        is(event.data, aMessage, "received " + aMessage);
+        if (event.data == aMessage)
+          resolve();
+        else
+          reject();
+      }, {once: true});
+    });
+  });
+}
+
+function removePermission(url, permission)
+{
+  let uri = Components.classes["@mozilla.org/network/io-service;1"]
+                      .getService(Components.interfaces.nsIIOService)
+                      .newURI(url);
+  let ssm = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+                      .getService(Ci.nsIScriptSecurityManager);
+  let principal = ssm.createCodebasePrincipal(uri, {});
+
+  Components.classes["@mozilla.org/permissionmanager;1"]
+            .getService(Components.interfaces.nsIPermissionManager)
+            .removeFromPrincipal(principal, permission);
+}
+
+function getPermission(url, permission)
+{
+  let uri = Components.classes["@mozilla.org/network/io-service;1"]
+                      .getService(Components.interfaces.nsIIOService)
+                      .newURI(url);
+  let ssm = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+                      .getService(Ci.nsIScriptSecurityManager);
+  let principal = ssm.createCodebasePrincipal(uri, {});
+
+  return Components.classes["@mozilla.org/permissionmanager;1"]
+                   .getService(Components.interfaces.nsIPermissionManager)
+                   .testPermissionFromPrincipal(principal, permission);
+}
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/helpers.js
@@ -0,0 +1,293 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function clearAllDatabases(callback)
+{
+  let qms = SpecialPowers.Services.qms;
+  let principal = SpecialPowers.wrap(document).nodePrincipal;
+  let request = qms.clearStoragesForPrincipal(principal);
+  let cb = SpecialPowers.wrapCallback(callback);
+  request.callback = cb;
+}
+
+var testHarnessGenerator = testHarnessSteps();
+testHarnessGenerator.next();
+
+function* testHarnessSteps()
+{
+  function nextTestHarnessStep(val)
+  {
+    testHarnessGenerator.next(val);
+  }
+
+  let testScriptPath;
+  let testScriptFilename;
+
+  let scripts = document.getElementsByTagName("script");
+  for (let i = 0; i < scripts.length; i++) {
+    let src = scripts[i].src;
+    let match = src.match(/quota\/test\/unit\/(test_[^\/]+\.js)$/);
+    if (match && match.length == 2) {
+      testScriptPath = src;
+      testScriptFilename = match[1];
+      break;
+    }
+  }
+
+  yield undefined;
+
+  info("Clearing old databases");
+
+  clearAllDatabases(nextTestHarnessStep);
+  yield undefined;
+
+  info("Running" +
+       (testScriptFilename ? " '" + testScriptFilename + "'" : ""));
+
+  if (testScriptFilename && !window.disableWorkerTest) {
+    info("Running test in a worker");
+
+    let workerScriptBlob =
+      new Blob([ "(" + workerScript.toString() + ")();" ],
+               { type: "text/javascript" });
+    let workerScriptURL = URL.createObjectURL(workerScriptBlob);
+
+    let worker = new Worker(workerScriptURL);
+
+    worker.onerror = function(event) {
+      ok(false, "Worker had an error: " + event.message);
+      worker.terminate();
+      nextTestHarnessStep();
+    };
+
+    worker.onmessage = function(event) {
+      let message = event.data;
+      switch (message.op) {
+        case "ok":
+          ok(message.condition, message.name, message.diag);
+          break;
+
+        case "todo":
+          todo(message.condition, message.name, message.diag);
+          break;
+
+        case "info":
+          info(message.msg);
+          break;
+
+        case "ready":
+          worker.postMessage({ op: "load", files: [ testScriptPath ] });
+          break;
+
+        case "loaded":
+          worker.postMessage({ op: "start" });
+          break;
+
+        case "done":
+          ok(true, "Worker finished");
+          nextTestHarnessStep();
+          break;
+
+        case "clearAllDatabases":
+          clearAllDatabases(function(){
+            worker.postMessage({ op: "clearAllDatabasesDone" });
+          });
+          break;
+
+        default:
+          ok(false,
+             "Received a bad message from worker: " + JSON.stringify(message));
+          nextTestHarnessStep();
+      }
+    };
+
+    URL.revokeObjectURL(workerScriptURL);
+
+    yield undefined;
+
+    worker.terminate();
+    worker = null;
+
+    clearAllDatabases(nextTestHarnessStep);
+    yield undefined;
+  } else if (testScriptFilename) {
+    todo(false,
+         "Skipping test in a worker because it is explicitly disabled: " +
+         disableWorkerTest);
+  } else {
+    todo(false,
+         "Skipping test in a worker because it's not structured properly");
+  }
+
+  info("Running test in main thread");
+
+  // Now run the test script in the main thread.
+  testGenerator.next();
+
+  yield undefined;
+}
+
+if (!window.runTest) {
+  window.runTest = function()
+  {
+    SimpleTest.waitForExplicitFinish();
+    testHarnessGenerator.next();
+  }
+}
+
+function finishTest()
+{
+  SimpleTest.executeSoon(function() {
+    clearAllDatabases(function() { SimpleTest.finish(); });
+  });
+}
+
+function grabArgAndContinueHandler(arg)
+{
+  testGenerator.next(arg);
+}
+
+function continueToNextStep()
+{
+  SimpleTest.executeSoon(function() {
+    testGenerator.next();
+  });
+}
+
+function continueToNextStepSync()
+{
+  testGenerator.next();
+}
+
+function workerScript()
+{
+  "use strict";
+
+  self.repr = function(_thing_) {
+    if (typeof(_thing_) == "undefined") {
+      return "undefined";
+    }
+
+    let str;
+
+    try {
+      str = _thing_ + "";
+    } catch (e) {
+      return "[" + typeof(_thing_) + "]";
+    }
+
+    if (typeof(_thing_) == "function") {
+      str = str.replace(/^\s+/, "");
+      let idx = str.indexOf("{");
+      if (idx != -1) {
+        str = str.substr(0, idx) + "{...}";
+      }
+    }
+
+    return str;
+  };
+
+  self.ok = function(_condition_, _name_, _diag_) {
+    self.postMessage({ op: "ok",
+                       condition: !!_condition_,
+                       name: _name_,
+                       diag: _diag_ });
+  };
+
+  self.is = function(_a_, _b_, _name_) {
+    let pass = (_a_ == _b_);
+    let diag = pass ? "" : "got " + repr(_a_) + ", expected " + repr(_b_);
+    ok(pass, _name_, diag);
+  };
+
+  self.isnot = function(_a_, _b_, _name_) {
+    let pass = (_a_ != _b_);
+    let diag = pass ? "" : "didn't expect " + repr(_a_) + ", but got it";
+    ok(pass, _name_, diag);
+  };
+
+  self.todo = function(_condition_, _name_, _diag_) {
+    self.postMessage({ op: "todo",
+                       condition: !!_condition_,
+                       name: _name_,
+                       diag: _diag_ });
+  };
+
+  self.info = function(_msg_) {
+    self.postMessage({ op: "info", msg: _msg_ });
+  };
+
+  self.executeSoon = function(_fun_) {
+    var channel = new MessageChannel();
+    channel.port1.postMessage("");
+    channel.port2.onmessage = function(event) { _fun_(); };
+  };
+
+  self.finishTest = function() {
+    self.postMessage({ op: "done" });
+  };
+
+  self.grabArgAndContinueHandler = function(_arg_) {
+    testGenerator.next(_arg_);
+  };
+
+  self.continueToNextStep = function() {
+    executeSoon(function() {
+      testGenerator.next();
+    });
+  };
+
+  self.continueToNextStepSync = function() {
+    testGenerator.next();
+  };
+
+  self._clearAllDatabasesCallback = undefined;
+  self.clearAllDatabases = function(_callback_) {
+    self._clearAllDatabasesCallback = _callback_;
+    self.postMessage({ op: "clearAllDatabases" });
+  }
+
+  self.onerror = function(_message_, _file_, _line_) {
+    ok(false,
+       "Worker: uncaught exception [" + _file_ + ":" + _line_ + "]: '" +
+         _message_ + "'");
+    self.finishTest();
+    self.close();
+    return true;
+  };
+
+  self.onmessage = function(_event_) {
+    let message = _event_.data;
+    switch (message.op) {
+      case "load":
+        info("Worker: loading " + JSON.stringify(message.files));
+        self.importScripts(message.files);
+        self.postMessage({ op: "loaded" });
+        break;
+
+      case "start":
+        executeSoon(function() {
+          info("Worker: starting tests");
+          testGenerator.next();
+        });
+        break;
+
+      case "clearAllDatabasesDone":
+        info("Worker: all databases are cleared");
+        if (self._clearAllDatabasesCallback) {
+          self._clearAllDatabasesCallback();
+        }
+        break;
+
+      default:
+        throw new Error("Received a bad message from parent: " +
+                        JSON.stringify(message));
+    }
+  };
+
+  self.postMessage({ op: "ready" });
+}
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/mochitest.ini
@@ -0,0 +1,17 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+support-files =
+  helpers.js
+  unit/test_storage_manager_persist_allow.js
+  unit/test_storage_manager_persist_deny.js
+  unit/test_storage_manager_persisted.js
+
+[test_storage_manager_persist_allow.html]
+scheme=https
+[test_storage_manager_persist_deny.html]
+scheme=https
+[test_storage_manager_persisted.html]
+scheme=https
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/test_storage_manager_persist_allow.html
@@ -0,0 +1,19 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Allow Persist Prompt for StorageManager</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript" src="unit/test_storage_manager_persist_allow.js"></script>
+  <script type="text/javascript" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/test_storage_manager_persist_deny.html
@@ -0,0 +1,19 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Deny Persist Prompt for StorageManager</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript" src="unit/test_storage_manager_persist_deny.js"></script>
+  <script type="text/javascript" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/test_storage_manager_persisted.html
@@ -0,0 +1,19 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Storage Manager Persisted Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript" src="unit/test_storage_manager_persisted.js"></script>
+  <script type="text/javascript" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_storage_manager_persist_allow.js
@@ -0,0 +1,24 @@
+var disableWorkerTest = "Persist doesn't work in workers";
+var testGenerator = testSteps();
+
+function* testSteps()
+{
+  SpecialPowers.pushPrefEnv({
+    "set": [["dom.storageManager.enabled", true],
+            ["dom.storageManager.prompt.testing", true],
+            ["dom.storageManager.prompt.testing.allow", true]]
+  }, continueToNextStep);
+  yield undefined;
+
+  navigator.storage.persist().then(grabArgAndContinueHandler);
+  let persistResult = yield undefined;
+
+  is(persistResult, true, "Persist succeeded");
+
+  navigator.storage.persisted().then(grabArgAndContinueHandler);
+  let persistedResult = yield undefined;
+
+  is(persistResult, persistedResult, "Persist/persisted results are consistent");
+
+  finishTest();
+}
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_storage_manager_persist_deny.js
@@ -0,0 +1,24 @@
+var disableWorkerTest = "Persist doesn't work in workers";
+var testGenerator = testSteps();
+
+function* testSteps()
+{
+  SpecialPowers.pushPrefEnv({
+    "set": [["dom.storageManager.enabled", true],
+            ["dom.storageManager.prompt.testing", true],
+            ["dom.storageManager.prompt.testing.allow", false]]
+  }, continueToNextStep);
+  yield undefined;
+
+  navigator.storage.persist().then(grabArgAndContinueHandler);
+  let persistResult = yield undefined;
+
+  is(persistResult, false, "Cancel the persist prompt and resolve a promise with false");
+
+  navigator.storage.persisted().then(grabArgAndContinueHandler);
+  let persistedResult = yield undefined;
+
+  is(persistResult, persistedResult, "Persist/persisted results are consistent");
+
+  finishTest();
+}
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_storage_manager_persisted.js
@@ -0,0 +1,11 @@
+var testGenerator = testSteps();
+
+function* testSteps()
+{
+  navigator.storage.persisted().then(grabArgAndContinueHandler);
+  let persistedResult = yield undefined;
+
+  is(persistedResult, false, "Persisted returns false");
+
+  finishTest();
+}
--- a/dom/webidl/StorageManager.webidl
+++ b/dom/webidl/StorageManager.webidl
@@ -7,20 +7,22 @@
  * https://storage.spec.whatwg.org/#storagemanager
  *
  */
 
 [SecureContext,
  Exposed=(Window,Worker),
  Func="mozilla::dom::StorageManager::PrefEnabled"]
 interface StorageManager {
-  // [Throws]
-  // Promise<boolean> persisted();
-  // [Throws]
-  // [Exposed=Window] Promise<boolean> persist();
+  [Throws]
+  Promise<boolean> persisted();
+
+  [Exposed=Window, Throws]
+  Promise<boolean> persist();
+
   [Throws]
   Promise<StorageEstimate> estimate();
 };
 
 dictionary StorageEstimate {
   unsigned long long usage;
   unsigned long long quota;
 };
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -8072,71 +8072,72 @@ HTMLEditRules::RemoveListStructure(Eleme
   return NS_OK;
 }
 
 nsresult
 HTMLEditRules::ConfirmSelectionInBody()
 {
   // get the body
   NS_ENSURE_STATE(mHTMLEditor);
-  nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(mHTMLEditor->GetRoot());
-  NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED);
+  RefPtr<Element> rootElement = mHTMLEditor->GetRoot();
+  if (NS_WARN_IF(!rootElement)) {
+    return NS_ERROR_UNEXPECTED;
+  }
 
   // get the selection
   NS_ENSURE_STATE(mHTMLEditor);
   RefPtr<Selection> selection = mHTMLEditor->GetSelection();
-  NS_ENSURE_STATE(selection);
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_UNEXPECTED;
+  }
 
   // get the selection start location
-  nsCOMPtr<nsIDOMNode> selNode, temp, parent;
+  nsCOMPtr<nsINode> selNode;
   int32_t selOffset;
   nsresult rv =
     EditorBase::GetStartNodeAndOffset(selection,
                                       getter_AddRefs(selNode), &selOffset);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  temp = selNode;
+  nsINode* temp = selNode;
 
   // check that selNode is inside body
-  while (temp && !TextEditUtils::IsBody(temp)) {
-    temp->GetParentNode(getter_AddRefs(parent));
-    temp = parent;
+  while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
+    temp = temp->GetParentNode();
   }
 
   // if we aren't in the body, force the issue
   if (!temp) {
 //    uncomment this to see when we get bad selections
 //    NS_NOTREACHED("selection not in body");
     selection->Collapse(rootElement, 0);
+    return NS_OK;
   }
 
   // get the selection end location
   rv = EditorBase::GetEndNodeAndOffset(selection,
                                        getter_AddRefs(selNode), &selOffset);
   NS_ENSURE_SUCCESS(rv, rv);
   temp = selNode;
 
   // check that selNode is inside body
-  while (temp && !TextEditUtils::IsBody(temp)) {
-    rv = temp->GetParentNode(getter_AddRefs(parent));
-    temp = parent;
+  while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
+    temp = temp->GetParentNode();
   }
 
   // if we aren't in the body, force the issue
   if (!temp) {
 //    uncomment this to see when we get bad selections
 //    NS_NOTREACHED("selection not in body");
     selection->Collapse(rootElement, 0);
   }
 
-  // XXX This is the result of the last call of GetParentNode(), it doesn't
-  //     make sense...
-  return rv;
+  return NS_OK;
 }
 
 nsresult
 HTMLEditRules::UpdateDocChangeRange(nsRange* aRange)
 {
   // first make sure aRange is in the document.  It might not be if
   // portions of our editting action involved manipulating nodes
   // prior to placing them in the document (e.g., populating a list item
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -1086,16 +1086,19 @@ ShadowLayerForwarder::SyncWithCompositor
   }
 }
 
 void
 ShadowLayerForwarder::ReleaseCompositable(const CompositableHandle& aHandle)
 {
   AssertInForwarderThread();
   if (!DestroyInTransaction(aHandle)) {
+    if (!IPCOpen()) {
+      return;
+    }
     mShadowManager->SendReleaseCompositable(aHandle);
   }
   mCompositables.Remove(aHandle.Value());
 }
 
 ShadowableLayer::~ShadowableLayer()
 {
   if (mShadow) {
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -140,17 +140,17 @@ gfxFontCache::Observer::Observe(nsISuppo
     }
     return NS_OK;
 }
 
 nsresult
 gfxFontCache::Init()
 {
     NS_ASSERTION(!gGlobalCache, "Where did this come from?");
-    gGlobalCache = new gfxFontCache();
+    gGlobalCache = new gfxFontCache(SystemGroup::EventTargetFor(TaskCategory::Other));
     if (!gGlobalCache) {
         return NS_ERROR_OUT_OF_MEMORY;
     }
     RegisterStrongMemoryReporter(new MemoryReporter());
     return NS_OK;
 }
 
 void
@@ -167,30 +167,33 @@ gfxFontCache::Shutdown()
     printf("Total glyph extents width-storage size allocated=%d\n", gGlyphExtentsWidthsTotalSize);
     printf("Number of simple glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerSimple);
     printf("Number of tight glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerTight);
     printf("Number of tight glyph extents lazily requested=%d\n", gGlyphExtentsSetupLazyTight);
     printf("Number of simple glyph extent setups that fell back to tight=%d\n", gGlyphExtentsSetupFallBackToTight);
 #endif
 }
 
-gfxFontCache::gfxFontCache()
+gfxFontCache::gfxFontCache(nsIEventTarget* aEventTarget)
     : nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000,
-                                     "gfxFontCache")
+                                     "gfxFontCache", aEventTarget)
 {
     nsCOMPtr<nsIObserverService> obs = GetObserverService();
     if (obs) {
         obs->AddObserver(new Observer, "memory-pressure", false);
     }
 
 #ifndef RELEASE_OR_BETA
     // Currently disabled for release builds, due to unexplained crashes
     // during expiration; see bug 717175 & 894798.
     mWordCacheExpirationTimer = do_CreateInstance("@mozilla.org/timer;1");
     if (mWordCacheExpirationTimer) {
+        if (XRE_IsContentProcess() && NS_IsMainThread()) {
+            mWordCacheExpirationTimer->SetTarget(aEventTarget);
+        }
         mWordCacheExpirationTimer->
             InitWithFuncCallback(WordCacheExpirationTimerCallback, this,
                                  SHAPED_WORD_TIMEOUT_SECONDS * 1000,
                                  nsITimer::TYPE_REPEATING_SLACK);
     }
 #endif
 }
 
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -280,17 +280,17 @@ struct FontCacheSizes {
 
 class gfxFontCache final : public nsExpirationTracker<gfxFont,3> {
 public:
     enum {
         FONT_TIMEOUT_SECONDS = 10,
         SHAPED_WORD_TIMEOUT_SECONDS = 60
     };
 
-    gfxFontCache();
+    explicit gfxFontCache(nsIEventTarget* aEventTarget);
     ~gfxFontCache();
 
     /*
      * Get the global gfxFontCache.  You must call Init() before
      * calling this method --- the result will not be null.
      */
     static gfxFontCache* GetCache() {
         return gGlobalCache;
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -788,17 +788,17 @@ struct SVGDrawingParameters
     : context(aContext)
     , size(aSize.width, aSize.height)
     , region(aRegion)
     , samplingFilter(aSamplingFilter)
     , svgContext(aSVGContext)
     , viewportSize(aSize)
     , animationTime(aAnimationTime)
     , flags(aFlags)
-    , opacity(aSVGContext ? aSVGContext->GetGlobalOpacity() : aOpacity)
+    , opacity(aOpacity)
   {
     if (aSVGContext) {
       auto sz = aSVGContext->GetViewportSize();
       if (sz) {
         viewportSize = nsIntSize(sz->width, sz->height); // XXX losing unit
       }
     }
   }
deleted file mode 100644
--- a/js/src/devtools/rootAnalysis/build/gcc-b2g.manifest
+++ /dev/null
@@ -1,11 +0,0 @@
-[
-  {
-    "version": "gcc 4.9.3",
-    "size": 102421980,
-    "visibility": "public",
-    "digest": "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
-    "algorithm": "sha512",
-    "filename": "gcc.tar.xz",
-    "unpack": true
-  }
-]
--- a/layout/reftests/floats/reftest-stylo.list
+++ b/layout/reftests/floats/reftest-stylo.list
@@ -91,24 +91,24 @@ fails == float-in-rtl-vrl-4d.html float-
 == orthogonal-floats-1c.html orthogonal-floats-1c.html
 == orthogonal-floats-1d.html orthogonal-floats-1d.html
 
 pref(layout.css.float-logical-values.enabled,true) == logical-float-side-1.html logical-float-side-1.html
 pref(layout.css.float-logical-values.enabled,true) == logical-float-side-2.html logical-float-side-2.html
 pref(layout.css.float-logical-values.enabled,true) == logical-float-side-3.html logical-float-side-3.html
 pref(layout.css.float-logical-values.enabled,true) == logical-float-side-4.html logical-float-side-4.html
 
-fails == float-in-rtl-slr-1a.html float-in-rtl-slr-1a.html
-fails == float-in-rtl-slr-1b.html float-in-rtl-slr-1b.html
-fails == float-in-rtl-slr-1c.html float-in-rtl-slr-1c.html
-fails == float-in-rtl-slr-1d.html float-in-rtl-slr-1d.html
-fails == float-in-rtl-slr-2a.html float-in-rtl-slr-2a.html
-fails == float-in-rtl-slr-2b.html float-in-rtl-slr-2b.html
-fails == float-in-rtl-slr-2c.html float-in-rtl-slr-2c.html
+== float-in-rtl-slr-1a.html float-in-rtl-slr-1a.html
+== float-in-rtl-slr-1b.html float-in-rtl-slr-1b.html
+== float-in-rtl-slr-1c.html float-in-rtl-slr-1c.html
+== float-in-rtl-slr-1d.html float-in-rtl-slr-1d.html
+== float-in-rtl-slr-2a.html float-in-rtl-slr-2a.html
+== float-in-rtl-slr-2b.html float-in-rtl-slr-2b.html
+== float-in-rtl-slr-2c.html float-in-rtl-slr-2c.html
 fails == float-in-rtl-slr-2d.html float-in-rtl-slr-2d.html
-fails == float-in-rtl-slr-3a.html float-in-rtl-slr-3a.html
-fails == float-in-rtl-slr-3b.html float-in-rtl-slr-3b.html
-fails == float-in-rtl-slr-3c.html float-in-rtl-slr-3c.html
+== float-in-rtl-slr-3a.html float-in-rtl-slr-3a.html
+== float-in-rtl-slr-3b.html float-in-rtl-slr-3b.html
+== float-in-rtl-slr-3c.html float-in-rtl-slr-3c.html
 fails == float-in-rtl-slr-3d.html float-in-rtl-slr-3d.html
-fails == float-in-rtl-slr-4a.html float-in-rtl-slr-4a.html
-fails == float-in-rtl-slr-4b.html float-in-rtl-slr-4b.html
-fails == float-in-rtl-slr-4c.html float-in-rtl-slr-4c.html
+== float-in-rtl-slr-4a.html float-in-rtl-slr-4a.html
+== float-in-rtl-slr-4b.html float-in-rtl-slr-4b.html
+== float-in-rtl-slr-4c.html float-in-rtl-slr-4c.html
 fails == float-in-rtl-slr-4d.html float-in-rtl-slr-4d.html
--- a/layout/reftests/w3c-css/submitted/shapes1/reftest-stylo.list
+++ b/layout/reftests/w3c-css/submitted/shapes1/reftest-stylo.list
@@ -23,16 +23,16 @@ default-preferences pref(layout.css.shap
 == shape-outside-border-box-border-radius-001.html shape-outside-border-box-border-radius-001.html
 == shape-outside-border-box-border-radius-002.html shape-outside-border-box-border-radius-002.html
 == shape-outside-border-box-border-radius-003.html shape-outside-border-box-border-radius-003.html
 == shape-outside-border-box-border-radius-004.html shape-outside-border-box-border-radius-004.html
 == shape-outside-border-box-border-radius-005.html shape-outside-border-box-border-radius-005.html
 == shape-outside-border-box-border-radius-006.html shape-outside-border-box-border-radius-006.html
 == shape-outside-border-box-border-radius-007.html shape-outside-border-box-border-radius-007.html
 == shape-outside-border-box-border-radius-008.html shape-outside-border-box-border-radius-008.html
-fails == shape-outside-border-box-border-radius-009.html shape-outside-border-box-border-radius-009.html
-fails == shape-outside-border-box-border-radius-010.html shape-outside-border-box-border-radius-010.html
-fails == shape-outside-border-box-border-radius-011.html shape-outside-border-box-border-radius-011.html
-fails == shape-outside-border-box-border-radius-012.html shape-outside-border-box-border-radius-012.html
+== shape-outside-border-box-border-radius-009.html shape-outside-border-box-border-radius-009.html
+== shape-outside-border-box-border-radius-010.html shape-outside-border-box-border-radius-010.html
+== shape-outside-border-box-border-radius-011.html shape-outside-border-box-border-radius-011.html
+== shape-outside-border-box-border-radius-012.html shape-outside-border-box-border-radius-012.html
 == shape-outside-padding-box-border-radius-001.html shape-outside-padding-box-border-radius-001.html
 == shape-outside-padding-box-border-radius-002.html shape-outside-padding-box-border-radius-002.html
 == shape-outside-content-box-border-radius-001.html shape-outside-content-box-border-radius-001.html
 == shape-outside-content-box-border-radius-002.html shape-outside-content-box-border-radius-002.html
--- a/layout/reftests/w3c-css/submitted/writing-modes-3/reftest-stylo.list
+++ b/layout/reftests/w3c-css/submitted/writing-modes-3/reftest-stylo.list
@@ -8,11 +8,11 @@ fails == text-combine-upright-compressio
 fails == text-combine-upright-compression-003.html text-combine-upright-compression-003.html
 fails == text-combine-upright-compression-004.html text-combine-upright-compression-004.html
 fails == text-combine-upright-compression-005.html text-combine-upright-compression-005.html
 fails == text-combine-upright-compression-005a.html text-combine-upright-compression-005a.html
 fails == text-combine-upright-compression-006.html text-combine-upright-compression-006.html
 fails == text-combine-upright-compression-006a.html text-combine-upright-compression-006a.html
 fails == text-combine-upright-compression-007.html text-combine-upright-compression-007.html
 
-fails == text-orientation-upright-directionality-001.html text-orientation-upright-directionality-001.html
+== text-orientation-upright-directionality-001.html text-orientation-upright-directionality-001.html
 
-fails == logical-physical-mapping-001.html logical-physical-mapping-001.html
+== logical-physical-mapping-001.html logical-physical-mapping-001.html
--- a/layout/reftests/writing-mode/reftest-stylo.list
+++ b/layout/reftests/writing-mode/reftest-stylo.list
@@ -148,24 +148,24 @@ skip-if(stylo) == 1172774-percent-paddin
 skip-if(stylo) == 1172774-percent-padding-3.html 1172774-percent-padding-3.html
 skip-if(stylo) == 1172774-percent-padding-4.html 1172774-percent-padding-4.html
 == 1174450-intrinsic-sizing.html 1174450-intrinsic-sizing.html
 fails == 1175789-underline-overline-1.html 1175789-underline-overline-1.html
 == 1188061-1-nsChangeHint_ClearAncestorIntrinsics.html 1188061-1-nsChangeHint_ClearAncestorIntrinsics.html
 == 1188061-2-nsChangeHint_UpdateComputedBSize.html 1188061-2-nsChangeHint_UpdateComputedBSize.html
 
 # tests involving sideways-lr mode
-fails == 1193519-sideways-lr-1.html 1193519-sideways-lr-1.html
-fails == 1193519-sideways-lr-2.html 1193519-sideways-lr-2.html
-fails == 1193519-sideways-lr-3.html 1193519-sideways-lr-3.html
-fails == 1193519-sideways-lr-4.html 1193519-sideways-lr-4.html
-fails == 1193519-sideways-lr-decoration-1.html 1193519-sideways-lr-decoration-1.html
+== 1193519-sideways-lr-1.html 1193519-sideways-lr-1.html
+== 1193519-sideways-lr-2.html 1193519-sideways-lr-2.html
+== 1193519-sideways-lr-3.html 1193519-sideways-lr-3.html
+== 1193519-sideways-lr-4.html 1193519-sideways-lr-4.html
+== 1193519-sideways-lr-decoration-1.html 1193519-sideways-lr-decoration-1.html
 
 == 1196887-1-computed-display-inline-block.html 1196887-1-computed-display-inline-block.html
-fails == 1205787-legacy-svg-values-1.html 1205787-legacy-svg-values-1.html
+== 1205787-legacy-svg-values-1.html 1205787-legacy-svg-values-1.html
 
 fails == 1216747-1.html 1216747-1.html
 fails == 1216747-1.html 1216747-1.html
 
 == 1243125-1-floats-overflowing.html 1243125-1-floats-overflowing.html
 
 == 1248248-1-orientation-break-glyphrun.html 1248248-1-orientation-break-glyphrun.html
 
--- a/layout/reftests/writing-mode/tables/reftest-stylo.list
+++ b/layout/reftests/writing-mode/tables/reftest-stylo.list
@@ -83,12 +83,12 @@ HTTP(../..) == s72-border-spacing-005.xh
 fuzzy-if(stylo,1,7400) == border-collapse-bevels-1a.html border-collapse-bevels-1a.html
 fuzzy-if(stylo,1,7400) fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1b.html border-collapse-bevels-1b.html
 fuzzy-if(stylo,1,7400) fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1c.html border-collapse-bevels-1c.html
 fuzzy-if(stylo,1,7400) fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1d.html border-collapse-bevels-1d.html
 fuzzy-if(stylo,1,7400) fuzzy-if(cocoaWidget,23,162) == border-collapse-bevels-1e.html border-collapse-bevels-1e.html
 
 == vertical-rl-row-progression-1a.html vertical-rl-row-progression-1a.html
 == vertical-rl-row-progression-1b.html vertical-rl-row-progression-1b.html
-fails == sideways-lr-row-progression-1a.html sideways-lr-row-progression-1a.html
-fails == sideways-lr-row-progression-1b.html sideways-lr-row-progression-1b.html
-fails == sideways-rl-row-progression-1a.html sideways-rl-row-progression-1a.html
-fails == sideways-rl-row-progression-1b.html sideways-rl-row-progression-1b.html
+== sideways-lr-row-progression-1a.html sideways-lr-row-progression-1a.html
+== sideways-lr-row-progression-1b.html sideways-lr-row-progression-1b.html
+== sideways-rl-row-progression-1a.html sideways-rl-row-progression-1a.html
+== sideways-rl-row-progression-1b.html sideways-rl-row-progression-1b.html
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -237,19 +237,16 @@ to mochitest command.
     * test_value_storage.html `pointer-events` [8]
   * new syntax of rgba?() and hsla?() functions servo/rust-cssparser#113
     * test_computed_style.html `css-color-4` [2]
   * color interpolation hint not supported servo/servo#15166
     * test_value_storage.html `'linear-gradient` [50]
   * SVG-in-OpenType values not supported servo/servo#15211 bug 1355412
     * test_value_storage.html `context-` [7]
     * test_bug798843_pref.html [7]
-  * writing-mode: sideways-{lr,rl} and SVG values servo/servo#15213
-    * test_logical_properties.html `sideways` [1224]
-    * test_value_storage.html `writing-mode` [8]
   * -moz-box-orient: {block,inline}-axis bug 1355005
     * test_value_storage.html `box-orient` [6]
 * Incorrect parsing
   * Incorrect bounds
     * test_bug664955.html `font size is larger than max font size` [2]
   * calc() doesn't support dividing expression servo/servo#15192
     * test_value_storage.html `calc(50px/` [7]
     * ... `calc(2em / ` [9]
--- a/layout/svg/SVGImageContext.h
+++ b/layout/svg/SVGImageContext.h
@@ -19,18 +19,17 @@ namespace mozilla {
 // Used to pass information such as
 //  - viewport information from CSS, and
 //  - overridden attributes from an SVG <image> element
 // to the image's internal SVG document when it's drawn.
 class SVGImageContext
 {
 public:
   SVGImageContext()
-    : mGlobalOpacity(1.0)
-    , mIsPaintingSVGImageElement(false)
+    : mIsPaintingSVGImageElement(false)
   { }
 
   /**
    * Currently it seems that the aViewportSize parameter ends up being used
    * for different things by different pieces of code, and probably in some
    * cases being used incorrectly (specifically in the case of pixel snapping
    * under the nsLayoutUtils::Draw*Image() methods).  An unfortunate result of
    * the messy code is that aViewportSize is currently a Maybe<T> since it
@@ -43,22 +42,20 @@ public:
    * in order to get the size that's created for |fallbackContext|.  At some
    * point we need to clean this code up, make our abstractions clear, create
    * that utility and stop using Maybe for this parameter.
    *
    * Note: 'aIsPaintingSVGImageElement' should be used to indicate whether
    * the SVG image in question is being painted for an SVG <image> element.
    */
   explicit SVGImageContext(const Maybe<CSSIntSize>& aViewportSize,
-                           const Maybe<SVGPreserveAspectRatio>& aPreserveAspectRatio = Nothing(),
-                           gfxFloat aOpacity = 1.0,
+                           const Maybe<SVGPreserveAspectRatio>& aPreserveAspectRatio  = Nothing(),
                            bool aIsPaintingSVGImageElement = false)
     : mViewportSize(aViewportSize)
     , mPreserveAspectRatio(aPreserveAspectRatio)
-    , mGlobalOpacity(aOpacity)
     , mIsPaintingSVGImageElement(aIsPaintingSVGImageElement)
   { }
 
   static void MaybeInitAndStoreContextPaint(Maybe<SVGImageContext>& aContext,
                                             nsIFrame* aFromFrame,
                                             imgIContainer* aImgContainer);
 
   const Maybe<CSSIntSize>& GetViewportSize() const {
@@ -72,20 +69,16 @@ public:
   const Maybe<SVGPreserveAspectRatio>& GetPreserveAspectRatio() const {
     return mPreserveAspectRatio;
   }
 
   void SetPreserveAspectRatio(const Maybe<SVGPreserveAspectRatio>& aPAR) {
     mPreserveAspectRatio = aPAR;
   }
 
-  gfxFloat GetGlobalOpacity() const {
-    return mGlobalOpacity;
-  }
-
   const SVGEmbeddingContextPaint* GetContextPaint() const {
     return mContextPaint.get();
   }
 
   bool IsPaintingForSVGImageElement() const {
     return mIsPaintingSVGImageElement;
   }
 
@@ -95,47 +88,44 @@ public:
       (mContextPaint == aOther.mContextPaint) ||
       // or both have context paint that are different but equivalent objects:
       (mContextPaint && aOther.mContextPaint &&
        *mContextPaint == *aOther.mContextPaint);
 
     return contextPaintIsEqual &&
            mViewportSize == aOther.mViewportSize &&
            mPreserveAspectRatio == aOther.mPreserveAspectRatio &&
-           mGlobalOpacity == aOther.mGlobalOpacity &&
            mIsPaintingSVGImageElement == aOther.mIsPaintingSVGImageElement;
   }
 
   bool operator!=(const SVGImageContext& aOther) const {
     return !(*this == aOther);
   }
 
   uint32_t Hash() const {
     uint32_t hash = 0;
     if (mContextPaint) {
       hash = HashGeneric(hash, mContextPaint->Hash());
     }
     return HashGeneric(hash,
                        mViewportSize.map(HashSize).valueOr(0),
                        mPreserveAspectRatio.map(HashPAR).valueOr(0),
-                       HashBytes(&mGlobalOpacity, sizeof(mGlobalOpacity)),
                        mIsPaintingSVGImageElement);
   }
 
 private:
   static uint32_t HashSize(const CSSIntSize& aSize) {
     return HashGeneric(aSize.width, aSize.height);
   }
   static uint32_t HashPAR(const SVGPreserveAspectRatio& aPAR) {
     return aPAR.Hash();
   }
 
   // NOTE: When adding new member-vars, remember to update Hash() & operator==.
   RefPtr<SVGEmbeddingContextPaint> mContextPaint;
   Maybe<CSSIntSize>             mViewportSize;
   Maybe<SVGPreserveAspectRatio> mPreserveAspectRatio;
-  gfxFloat                      mGlobalOpacity;
   bool                          mIsPaintingSVGImageElement;
 };
 
 } // namespace mozilla
 
 #endif // MOZILLA_SVGCONTEXT_H_
--- a/layout/svg/nsSVGImageFrame.cpp
+++ b/layout/svg/nsSVGImageFrame.cpp
@@ -402,17 +402,17 @@ nsSVGImageFrame::PaintSVG(gfxContext& aC
       // attributes of mImageContainer's internal SVG document.  The 'width' &
       // 'height' values we're passing in here are in CSS units (though they
       // come from width/height *attributes* in SVG). They influence the region
       // of the SVG image's internal document that is visible, in combination
       // with preserveAspectRatio and viewBox.
       const Maybe<SVGImageContext> context(
         Some(SVGImageContext(Some(CSSIntSize::Truncate(width, height)),
                              Some(imgElem->mPreserveAspectRatio.GetAnimValue()),
-                             1.0, /* aIsPaintingSVGImageElement */ true)));
+                             /* aIsPaintingSVGImageElement */ true)));
 
       // For the actual draw operation to draw crisply (and at the right size),
       // our destination rect needs to be |width|x|height|, *in dev pixels*.
       LayoutDeviceSize devPxSize(width, height);
       nsRect destRect(nsPoint(),
                       LayoutDevicePixel::ToAppUnits(devPxSize,
                                                     appUnitsPerDevPx));
 
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -224,17 +224,17 @@ public class BrowserApp extends GeckoApp
     private BrowserSearch mBrowserSearch;
     private View mBrowserSearchContainer;
 
     public ViewGroup mBrowserChrome;
     public ViewFlipper mActionBarFlipper;
     public ActionModeCompatView mActionBar;
     private VideoPlayer mVideoPlayer;
     private BrowserToolbar mBrowserToolbar;
-    private View mDoorhangerOverlay;
+    private View doorhangerOverlay;
     // We can't name the TabStrip class because it's not included on API 9.
     private TabStripInterface mTabStrip;
     private ToolbarProgressView mProgressView;
     private FirstrunAnimationContainer mFirstrunAnimationContainer;
     private HomeScreen mHomeScreen;
     private TabsPanel mTabsPanel;
     /**
      * Container for the home screen implementation. This will be populated with any valid
@@ -724,17 +724,17 @@ public class BrowserApp extends GeckoApp
             mBrowserSearch.setUserVisibleHint(false);
         }
 
         setBrowserToolbarListeners();
 
         mFindInPageBar = (FindInPageBar) findViewById(R.id.find_in_page);
         mMediaCastingBar = (MediaCastingBar) findViewById(R.id.media_casting);
 
-        mDoorhangerOverlay = findViewById(R.id.doorhanger_overlay);
+        doorhangerOverlay = findViewById(R.id.doorhanger_overlay);
 
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
             "Search:Keyword",
             null);
 
         EventDispatcher.getInstance().registerUiThreadListener(this,
             "Menu:Open",
             "Menu:Update",
@@ -1614,29 +1614,17 @@ public class BrowserApp extends GeckoApp
             onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null);
             invalidateOptionsMenu();
         }
     }
 
     @Override
     public void onDoorHangerShow() {
         mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
-
-        final Animator alphaAnimator = ObjectAnimator.ofFloat(mDoorhangerOverlay, "alpha", 1);
-        alphaAnimator.setDuration(250);
-
-        alphaAnimator.start();
-    }
-
-    @Override
-    public void onDoorHangerHide() {
-        final Animator alphaAnimator = ObjectAnimator.ofFloat(mDoorhangerOverlay, "alpha", 0);
-        alphaAnimator.setDuration(200);
-
-        alphaAnimator.start();
+        super.onDoorHangerShow();
     }
 
     private void setToolbarMargin(int margin) {
         ((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).topMargin = margin;
         mGeckoLayout.requestLayout();
     }
 
     @Override
@@ -4070,16 +4058,21 @@ public class BrowserApp extends GeckoApp
     @Override
     public void onEditSuggestion(String suggestion) {
         mBrowserToolbar.onEditSuggestion(suggestion);
     }
 
     @Override
     public int getLayout() { return R.layout.gecko_app; }
 
+    @Override
+    public View getDoorhangerOverlay() {
+        return doorhangerOverlay;
+    }
+
     public SearchEngineManager getSearchEngineManager() {
         return mSearchEngineManager;
     }
 
     // For use from tests only.
     @RobocopTarget
     public ReadingListHelper getReadingListHelper() {
         return mReadingListHelper;
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -43,16 +43,18 @@ import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.webapps.WebAppActivity;
 
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -104,16 +106,17 @@ import android.widget.RelativeLayout;
 import android.widget.SimpleAdapter;
 import android.widget.TextView;
 import android.widget.Toast;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.util.ViewUtil;
+import org.mozilla.gecko.widget.AnchoredPopup;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
 import java.net.URL;
 import java.util.ArrayList;
@@ -132,17 +135,18 @@ public abstract class GeckoApp
     implements
     BundleEventListener,
     ContextGetter,
     GeckoAppShell.GeckoInterface,
     ScreenOrientationDelegate,
     GeckoMenu.Callback,
     GeckoMenu.MenuPresenter,
     Tabs.OnTabsChangedListener,
-    ViewTreeObserver.OnGlobalLayoutListener {
+    ViewTreeObserver.OnGlobalLayoutListener,
+    AnchoredPopup.OnVisibilityChangeListener {
 
     private static final String LOGTAG = "GeckoApp";
     private static final long ONE_DAY_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
 
     public static final String ACTION_ALERT_CALLBACK       = "org.mozilla.gecko.ALERT_CALLBACK";
     public static final String ACTION_HOMESCREEN_SHORTCUT  = "org.mozilla.gecko.BOOKMARK";
     public static final String ACTION_WEBAPP               = "org.mozilla.gecko.WEBAPP";
     public static final String ACTION_DEBUG                = "org.mozilla.gecko.DEBUG";
@@ -346,16 +350,18 @@ public abstract class GeckoApp
     private volatile Locale mLastLocale;
 
     protected Intent mRestartIntent;
 
     private boolean mWasFirstTabShownAfterActivityUnhidden;
 
     abstract public int getLayout();
 
+    abstract public View getDoorhangerOverlay();
+
     protected void processTabQueue() {};
 
     protected void openQueuedTabs() {};
 
     @SuppressWarnings("serial")
     class SessionRestoreException extends Exception {
         public SessionRestoreException(Exception e) {
             super(e);
@@ -1559,20 +1565,43 @@ public abstract class GeckoApp
         // We don't call this.onConfigurationChanged, because (a) that does
         // work that's unnecessary after this locale action, and (b) it can
         // cause a loop! See Bug 1011008, Comment 12.
         super.onConfigurationChanged(getResources().getConfiguration());
     }
 
     protected void initializeChrome() {
         mDoorHangerPopup = new DoorHangerPopup(this);
+        mDoorHangerPopup.setOnVisibilityChangeListener(this);
         mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
     }
 
+    @Override
+    public void onDoorHangerShow() {
+        final View overlay = getDoorhangerOverlay();
+        if (overlay != null) {
+            final Animator alphaAnimator = ObjectAnimator.ofFloat(overlay, "alpha", 1);
+            alphaAnimator.setDuration(250);
+
+            alphaAnimator.start();
+        }
+    }
+
+    @Override
+    public void onDoorHangerHide() {
+        final View overlay = getDoorhangerOverlay();
+        if (overlay != null) {
+            final Animator alphaAnimator = ObjectAnimator.ofFloat(overlay, "alpha", 0);
+            alphaAnimator.setDuration(200);
+
+            alphaAnimator.start();
+        }
+    }
+
     /**
      * Loads the initial tab at Fennec startup. If we don't restore tabs, this
      * tab will be about:home, or the homepage if the user has set one.
      * If we've temporarily disabled restoring to break out of a crash loop, we'll show
      * the Recent Tabs folder of the Combined History panel, so the user can manually
      * restore tabs as needed.
      * If we restore tabs, we don't need to create a new tab, unless launch intent specify action
      * to be #android.Intent.ACTION_VIEW, which is launched from widget to create a new tab.
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -49,16 +49,17 @@ import org.mozilla.gecko.widget.GeckoPop
 import java.util.List;
 
 public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedListener {
     private static final String LOGTAG = "CustomTabsActivity";
     private static final String SAVED_START_INTENT = "saved_intent_which_started_this_activity";
 
     private final SparseArrayCompat<PendingIntent> menuItemsIntent = new SparseArrayCompat<>();
     private GeckoPopupMenu popupMenu;
+    private View doorhangerOverlay;
     private ActionBarPresenter actionBarPresenter;
     private ProgressBar mProgressView;
     // A state to indicate whether this activity is finishing with customize animation
     private boolean usingCustomAnimation = false;
 
     // Bug 1351605 - getIntent() not always returns the intent which started this activity.
     // Therefore we make a copy in case of this Activity is re-created.
     private Intent startIntent;
@@ -73,16 +74,18 @@ public class CustomTabsActivity extends 
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab");
             startIntent = getIntent();
             final String host = getReferrerHost();
             recordCustomTabUsage(host);
         }
 
         setThemeFromToolbarColor();
 
+        doorhangerOverlay = findViewById(R.id.custom_tabs_doorhanger_overlay);
+
         mProgressView = (ProgressBar) findViewById(R.id.page_progress);
         final Toolbar toolbar = (Toolbar) findViewById(R.id.actionbar);
         setSupportActionBar(toolbar);
         final ActionBar actionBar = getSupportActionBar();
         bindNavigationCallback(toolbar);
 
         actionBarPresenter = new ActionBarPresenter(actionBar);
         actionBarPresenter.displayUrlOnly(startIntent.getDataString());
@@ -150,16 +153,21 @@ public class CustomTabsActivity extends 
     }
 
     @Override
     public int getLayout() {
         return R.layout.customtabs_activity;
     }
 
     @Override
+    public View getDoorhangerOverlay() {
+        return doorhangerOverlay;
+    }
+
+    @Override
     protected void onDone() {
         finish();
     }
 
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
         if (!Tabs.getInstance().isSelectedTab(tab)) {
             return;
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java
@@ -5,16 +5,17 @@
 package org.mozilla.gecko.home.activitystream.topsites;
 
 import android.content.Context;
 import android.database.Cursor;
 import android.support.v4.view.PagerAdapter;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.widget.RecyclerViewClickSupport;
 
 import java.util.ArrayList;
 import java.util.List;
 
@@ -63,16 +64,22 @@ public class TopSitesPagerAdapter extend
     public boolean isViewFromObject(View view, Object object) {
         return view == object;
     }
 
     @Override
     public Object instantiateItem(ViewGroup container, int position) {
         TopSitesPage page = pages.get(position);
 
+        final ViewParent viewParent = page.getParent();
+        if (viewParent != null && viewParent instanceof ViewGroup) {
+            ViewGroup viewGroup = (ViewGroup) viewParent;
+            viewGroup.removeView(page);
+        }
+
         container.addView(page);
 
         return page;
     }
 
     @Override
     public int getItemPosition(Object object) {
         if (pages.contains(object)) {
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -37,25 +37,28 @@ import org.mozilla.gecko.icons.decoders.
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoBundle;
+import org.mozilla.gecko.widget.AnchoredPopup;
 
 public class WebAppActivity extends GeckoApp {
 
     public static final String INTENT_KEY = "IS_A_WEBAPP";
     public static final String MANIFEST_PATH = "MANIFEST_PATH";
 
     private static final String LOGTAG = "WebAppActivity";
 
     private TextView mUrlView;
+    private View doorhangerOverlay;
+
     private String mManifestPath;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "webapp");
 
@@ -73,28 +76,35 @@ public class WebAppActivity extends Geck
         progressBar.setVisibility(View.GONE);
 
         final ActionBar actionBar = getSupportActionBar();
         actionBar.setCustomView(R.layout.webapps_action_bar_custom_view);
         actionBar.setDisplayShowCustomEnabled(true);
         actionBar.setDisplayShowTitleEnabled(false);
         actionBar.hide();
 
+        doorhangerOverlay = findViewById(R.id.custom_tabs_doorhanger_overlay);
+
         final View customView = actionBar.getCustomView();
         mUrlView = (TextView) customView.findViewById(R.id.webapps_action_bar_url);
 
         EventDispatcher.getInstance().registerUiThreadListener(this,
-                    "Website:AppEntered",
-                    "Website:AppLeft",
-                    null);
+                "Website:AppEntered",
+                "Website:AppLeft",
+                null);
 
         Tabs.registerOnTabsChangedListener(this);
     }
 
     @Override
+    public View getDoorhangerOverlay() {
+        return doorhangerOverlay;
+    }
+
+    @Override
     public int getLayout() {
         return R.layout.customtabs_activity;
     }
 
     @Override
     public void handleMessage(final String event, final GeckoBundle message,
                               final EventCallback callback) {
         switch (event) {
@@ -125,19 +135,19 @@ public class WebAppActivity extends Geck
 
         outState.putString(WebAppActivity.MANIFEST_PATH, mManifestPath);
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
         EventDispatcher.getInstance().unregisterUiThreadListener(this,
-              "Website:AppEntered",
-              "Website:AppLeft",
-              null);
+                "Website:AppEntered",
+                "Website:AppLeft",
+                null);
         Tabs.unregisterOnTabsChangedListener(this);
     }
 
     @Override
     protected int getNewTabFlags() {
         return Tabs.LOADURL_WEBAPP | super.getNewTabFlags();
     }
 
@@ -150,17 +160,17 @@ public class WebAppActivity extends Geck
     protected void onNewIntent(Intent externalIntent) {
 
         restoreLastSelectedTab();
 
         final SafeIntent intent = new SafeIntent(externalIntent);
         final String launchUrl = intent.getDataString();
         final String currentUrl = Tabs.getInstance().getSelectedTab().getURL();
         final boolean isSameDomain = Uri.parse(currentUrl).getHost()
-            .equals(Uri.parse(launchUrl).getHost());
+                .equals(Uri.parse(launchUrl).getHost());
 
         if (!isSameDomain) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "webapp");
             mManifestPath = externalIntent.getStringExtra(WebAppActivity.MANIFEST_PATH);
             loadManifest(mManifestPath);
             Tabs.getInstance().loadUrl(launchUrl);
         }
     }
@@ -178,18 +188,18 @@ public class WebAppActivity extends Geck
         try {
             final File manifestFile = new File(manifestPath);
             final JSONObject manifest = FileUtils.readJSONObjectFromFile(manifestFile);
             final JSONObject manifestField = manifest.getJSONObject("manifest");
             final Integer color = readColorFromManifest(manifestField);
             final String name = readNameFromManifest(manifestField);
             final Bitmap icon = readIconFromManifest(manifest);
             final ActivityManager.TaskDescription taskDescription = (color == null)
-                ? new ActivityManager.TaskDescription(name, icon)
-                : new ActivityManager.TaskDescription(name, icon, color);
+                    ? new ActivityManager.TaskDescription(name, icon)
+                    : new ActivityManager.TaskDescription(name, icon, color);
 
             updateStatusBarColor(color);
             setTaskDescription(taskDescription);
 
         } catch (IOException | JSONException e) {
             Log.e(LOGTAG, "Failed to read manifest", e);
         }
     }
@@ -220,14 +230,14 @@ public class WebAppActivity extends Geck
         }
         return name;
     }
 
     private Bitmap readIconFromManifest(JSONObject manifest) {
         final String iconStr = manifest.optString("cached_icon", null);
         if (iconStr != null) {
             return FaviconDecoder
-                .decodeDataURI(getContext(), iconStr)
-                .getBestBitmap(GeckoAppShell.getPreferredIconSize());
+                    .decodeDataURI(getContext(), iconStr)
+                    .getBestBitmap(GeckoAppShell.getPreferredIconSize());
         }
         return null;
     }
 }
--- a/mobile/android/base/resources/layout/customtabs_activity.xml
+++ b/mobile/android/base/resources/layout/customtabs_activity.xml
@@ -53,9 +53,15 @@
         style="@style/Base.Widget.AppCompat.ProgressBar.Horizontal"
         android:layout_width="match_parent"
         android:layout_height="4dp"
         android:layout_alignTop="@id/main_layout"
         android:background="@drawable/url_bar_bg"
         android:progressDrawable="@drawable/progressbar"
         tools:progress="70"/>
 
+    <View android:id="@+id/custom_tabs_doorhanger_overlay"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"
+          android:background="@color/dark_transparent_overlay"
+          android:alpha="0"
+          android:layerType="hardware"/>
 </RelativeLayout>
\ No newline at end of file
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/Codec.java
@@ -142,25 +142,28 @@ import java.util.concurrent.ConcurrentLi
                         sample.writeToByteBuffer(buf);
                     } catch (IOException e) {
                         e.printStackTrace();
                         len = 0;
                     }
                     mSamplePool.recycleInput(sample);
                 }
 
-                if (cryptoInfo != null && len > 0) {
-                    mCodec.queueSecureInputBuffer(index, 0, cryptoInfo, pts, flags);
-                } else {
-                    mCodec.queueInputBuffer(index, 0, len, pts, flags);
-                }
                 try {
+                    if (cryptoInfo != null && len > 0) {
+                        mCodec.queueSecureInputBuffer(index, 0, cryptoInfo, pts, flags);
+                    } else {
+                        mCodec.queueInputBuffer(index, 0, len, pts, flags);
+                    }
                     mCallbacks.onInputQueued(pts);
                 } catch (RemoteException e) {
                     e.printStackTrace();
+                } catch (Exception e) {
+                    reportError(Error.FATAL, e);
+                    return;
                 }
             }
             reportPendingInputs();
         }
 
         private void reportPendingInputs() {
             try {
                 for (Input i : mInputSamples) {
@@ -506,17 +509,21 @@ import java.util.concurrent.ConcurrentLi
         } catch (Exception e) {
             // Translate allocation error to remote exception.
             throw new RemoteException(e.getMessage());
         }
     }
 
     @Override
     public synchronized void queueInput(Sample sample) throws RemoteException {
-        mInputProcessor.onSample(sample);
+        try {
+            mInputProcessor.onSample(sample);
+        } catch (Exception e) {
+            throw new RemoteException(e.getMessage());
+        }
     }
 
     @Override
     public synchronized void setRates(int newBitRate) {
         try {
             mCodec.setRates(newBitRate);
         } catch (Exception e) {
             reportError(Error.FATAL, e);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
@@ -188,17 +188,17 @@ public final class CodecProxy {
         mCallbacks.setEndOfInput(eos);
 
         if (eos) {
             return sendInput(Sample.EOS);
         }
 
         try {
             return sendInput(mRemote.dequeueInput(info.size).set(bytes, info, cryptoInfo));
-        } catch (RemoteException e) {
+        } catch (RemoteException | NullPointerException e) {
             Log.e(LOGTAG, "fail to dequeue input buffer", e);
             return false;
         } catch (IOException e) {
             Log.e(LOGTAG, "fail to copy input data.", e);
             // Balance dequeue/queue.
             return sendInput(null);
         }
     }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5650,16 +5650,19 @@ pref ("security.data_uri.inherit_securit
 
 // 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);
+pref("dom.storageManager.prompt.testing.allow", false);
+
 // Enable the Storage management in about:preferences and persistent-storage permission request
 // To enable the DOM implementation, turn on "dom.storageManager.enabled"
 #ifdef NIGHTLY_BUILD
 pref("browser.storageManager.enabled", true);
 #else
 pref("browser.storageManager.enabled", false);
 #endif
 pref("browser.storageManager.pressureNotification.minIntervalMS", 1200000);
--- a/netwerk/cookie/nsCookie.h
+++ b/netwerk/cookie/nsCookie.h
@@ -104,16 +104,17 @@ class nsCookie : public nsICookie2
     inline const nsDependentCString Path()  const { return nsDependentCString(mPath, mEnd); }
     inline int64_t Expiry()                 const { return mExpiry; }        // in seconds
     inline int64_t LastAccessed()           const { return mLastAccessed; }  // in microseconds
     inline int64_t CreationTime()           const { return mCreationTime; }  // in microseconds
     inline bool IsSession()               const { return mIsSession; }
     inline bool IsDomain()                const { return *mHost == '.'; }
     inline bool IsSecure()                const { return mIsSecure; }
     inline bool IsHttpOnly()              const { return mIsHttpOnly; }
+    inline const OriginAttributes& OriginAttributesRef() const { return mOriginAttributes; }
 
     // setters
     inline void SetExpiry(int64_t aExpiry)        { mExpiry = aExpiry; }
     inline void SetLastAccessed(int64_t aTime)    { mLastAccessed = aTime; }
     inline void SetIsSession(bool aIsSession)     { mIsSession = aIsSession; }
     // Set the creation time manually, overriding the monotonicity checks in
     // Create(). Use with caution!
     inline void SetCreationTime(int64_t aTime)    { mCreationTime = aTime; }
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -268,16 +268,21 @@ LogCookie(nsCookie *aCookie)
       ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : ""));
 
     PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime);
     PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime);
     MOZ_LOG(gCookieLog, LogLevel::Debug,("created: %s", timeString));
 
     MOZ_LOG(gCookieLog, LogLevel::Debug,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false"));
     MOZ_LOG(gCookieLog, LogLevel::Debug,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false"));
+
+    nsAutoCString suffix;
+    aCookie->OriginAttributesRef().CreateSuffix(suffix);
+    MOZ_LOG(gCookieLog, LogLevel::Debug,("origin attributes: %s\n",
+            suffix.IsEmpty() ? "{empty}" : suffix.get()));
   }
 }
 
 static void
 LogSuccess(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie, bool aReplacing)
 {
   // if logging isn't enabled, return now to save cycles
   if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) {
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -2390,16 +2390,17 @@ dependencies = [
 name = "selectors"
 version = "0.18.0"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "semver"
 version = "0.1.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
--- a/servo/README.md
+++ b/servo/README.md
@@ -16,17 +16,17 @@ Visit the [Servo Project page](https://s
 
 Please select your operating system:
 * [OS X](#os-x)
 * [Debian-based Linuxes](#on-debian-based-linuxes)
 * [Fedora](#on-fedora)
 * [Arch Linux](#on-arch-linux)
 * [openSUSE](#on-opensuse-linux)
 * [Gentoo Linux](#on-gentoo-linux)
-* [Microsoft Windows](#on-windows-msvc--mingw)
+* [Microsoft Windows](#on-windows-msvc)
 * [Android](#cross-compilation-for-android)
 
 #### OS X
 #### On OS X (homebrew)
 
 ``` sh
 brew install automake pkg-config python cmake yasm
 pip install virtualenv
@@ -89,62 +89,35 @@ sudo pacman -S --needed base-devel git p
 #### On Gentoo Linux
 
 ```sh
 sudo emerge net-misc/curl media-libs/freeglut \
     media-libs/freetype media-libs/mesa dev-util/gperf \
     dev-python/virtualenv dev-python/pip dev-libs/openssl \
     x11-libs/libXmu media-libs/glu x11-base/xorg-server
 ```
-#### On Windows (MSVC & MinGW)
+#### On Windows (MSVC)
 
 1. Install Python for Windows (https://www.python.org/downloads/release/python-2711/). The windows x86-64 MSI installer is fine.
 You should change the installation to install the "Add python.exe to Path" feature.
 
 2. Install virtualenv.
 
  In a normal Windows Shell (cmd.exe or "Command Prompt" from the start menu), do:
  ```
 pip install virtualenv
 ```
  If this does not work, you may need to reboot for the changed PATH settings (by the python installer) to take effect.
 
-3. __(MSVC only)__ Install Git for Windows (https://git-scm.com/download/win). DO allow it to add git.exe to the PATH (default
+3. Install Git for Windows (https://git-scm.com/download/win). DO allow it to add git.exe to the PATH (default
 settings for the installer are fine).
 
-4. __(MSVC only)__ Install Visual Studio 2015 Community Edition (https://www.visualstudio.com/). You MUST add "Visual C++" to the
+4. Install Visual Studio 2015 Community Edition (https://www.visualstudio.com/). You MUST add "Visual C++" to the
 list of installed components. It is not on by default.
 
-5. __(MinGW only)__ Install MSYS2 (https://msys2.github.io/). After you have done so, open an MSYS shell
-window and update the core libraries and install new packages. The extra step at the end is to
-downgrade GCC to 5.4, as the GCC6 versions in mingw currently fail to compile some of our
-dependencies. We are upgrading to a gcc-free build on Windows as soon as possible:
-
- ```sh
-pacman -Su
-pacman -Sy git mingw-w64-x86_64-toolchain mingw-w64-x86_64-icu \
-    mingw-w64-x86_64-nspr mingw-w64-x86_64-ca-certificates \
-    mingw-w64-x86_64-expat mingw-w64-x86_64-cmake tar diffutils patch \
-    patchutils make python2-setuptools
-export GCC_URL=http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc
-export GCC_EXT=5.4.0-1-any.pkg.tar.xz
-pacman -U --noconfirm $GCC_URL-$GCC_EXT $GCC_URL-ada-$GCC_EXT \
-    $GCC_URL-fortran-$GCC_EXT $GCC_URL-libgfortran-$GCC_EXT $GCC_URL-libs-$GCC_EXT \
-    $GCC_URL-objc-$GCC_EXT
-```
-
- Add the following line to the end of `.profile` in your home directory:
-
- ```
-export PATH=/c/Python27:/c/Python27/Scripts:$PATH
-```
-
- Now, open a MINGW64 (not MSYS!) shell window, and you should be able to build
-servo as usual!
-
 #### Cross-compilation for Android
 
 Pre-installed Android tools are needed. See wiki for
 [details](https://github.com/servo/servo/wiki/Building-for-Android)
 
 ## The Rust compiler
 
 Servo's build system automatically downloads a Rust compiler to build itself.
--- a/servo/appveyor.yml
+++ b/servo/appveyor.yml
@@ -25,47 +25,29 @@ environment:
     C:\\Program Files\\Microsoft Windows Performance Toolkit\\;\
     C:\\Program Files\\LLVM\\bin;\
     C:\\Program Files\\Git LFS;\
     C:\\Program Files\\Git\\cmd;\
     C:\\Program Files\\Git\\usr\\bin;\
     C:\\Program Files\\AppVeyor\\BuildAgent;"
   matrix:
   - TARGET: nightly-x86_64-pc-windows-msvc
-  - TARGET: nightly-x86_64-pc-windows-gnu
 
 branches:
   only:
     - master
 
 cache:
   - .servo -> rust-commit-hash, cargo-commit-hash
   - .cargo -> rust-commit-hash, cargo-commit-hash
   - .ccache
 
-install:
-  - if %TARGET:*-msvc=msvc%==msvc set BUILD_ENV=msvc
-  - if %TARGET:*-gnu=gnu%==gnu set BUILD_ENV=gnu
-  - if %BUILD_ENV%==gnu  set PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin\;%PATH%
-  - if %BUILD_ENV%==gnu  set MSYSTEM=MINGW64
-  - if %BUILD_ENV%==gnu  set MSYS=winsymlinks=lnk
-  - if %BUILD_ENV%==gnu  bash -lc "echo $MSYSTEM; pacman --needed --noconfirm -Sy pacman-mirrors"
-  - if %BUILD_ENV%==gnu  bash -lc "pacman --noconfirm -Sy"
-  - if %BUILD_ENV%==gnu  bash -lc "pacman -Sy --needed --noconfirm mingw-w64-x86_64-ccache mingw-w64-x86_64-toolchain mingw-w64-x86_64-icu mingw-w64-x86_64-nspr mingw-w64-x86_64-ca-certificates mingw-w64-x86_64-expat mingw-w64-x86_64-cmake tar diffutils patch patchutils make python2-setuptools"
-  # Downgrade msys2 build GCC to 5.4.0-1 - https://github.com/servo/servo/issues/12512
-  - if %BUILD_ENV%==gnu  set GCC_URL=http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc
-  - if %BUILD_ENV%==gnu  set GCC_EXT=5.4.0-1-any.pkg.tar.xz
-  - if %BUILD_ENV%==gnu  bash -lc "pacman -U --noconfirm $GCC_URL-$GCC_EXT $GCC_URL-ada-$GCC_EXT $GCC_URL-fortran-$GCC_EXT $GCC_URL-libgfortran-$GCC_EXT $GCC_URL-libs-$GCC_EXT $GCC_URL-objc-$GCC_EXT"
-
 # Uncomment these lines to expose RDP access information to the build machine in the build log.
 #init:
 #  - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
 #
 #on_finish:
 #  - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
 
 build_script:
-  - if %BUILD_ENV%==gnu  bash -lc "ccache -s"
-  - if %BUILD_ENV%==msvc mach build -d -v && mach test-unit
-  - if %BUILD_ENV%==gnu  bash -lc "export PATH=/c/Python27:$PATH; export CCACHE=/mingw64/bin/ccache; cd $APPVEYOR_BUILD_FOLDER; ./mach build -d -v && ./mach test-unit"
-  - if %BUILD_ENV%==gnu  bash -lc "ccache -s"
+  - mach build -d -v && mach test-unit
 
 test: off
--- a/servo/components/gfx/font.rs
+++ b/servo/components/gfx/font.rs
@@ -43,17 +43,17 @@ static TEXT_SHAPING_PERFORMANCE_COUNTER:
 // needed by the text shaper as well as access to the underlying font
 // resources needed by the graphics layer to draw glyphs.
 
 pub trait FontHandleMethods: Sized {
     fn new_from_template(fctx: &FontContextHandle, template: Arc<FontTemplateData>, pt_size: Option<Au>)
                     -> Result<Self, ()>;
     fn template(&self) -> Arc<FontTemplateData>;
     fn family_name(&self) -> String;
-    fn face_name(&self) -> String;
+    fn face_name(&self) -> Option<String>;
     fn is_italic(&self) -> bool;
     fn boldness(&self) -> font_weight::T;
     fn stretchiness(&self) -> font_stretch::T;
 
     fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
     fn glyph_h_advance(&self, GlyphId) -> Option<FractionalPixel>;
     fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel;
     /// Can this font do basic horizontal LTR shaping without Harfbuzz?
@@ -249,17 +249,17 @@ impl Font {
     }
 
     pub fn table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
         let result = self.handle.table_for_tag(tag);
         let status = if result.is_some() { "Found" } else { "Didn't find" };
 
         debug!("{} font table[{}] with family={}, face={}",
                status, tag.tag_to_str(),
-               self.handle.family_name(), self.handle.face_name());
+               self.handle.family_name(), self.handle.face_name().unwrap_or("unavailable".to_owned()));
 
         result
     }
 
     #[inline]
     pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
         let codepoint = match self.variant {
             font_variant_caps::T::small_caps => codepoint.to_uppercase().next().unwrap(), //FIXME: #5938
--- a/servo/components/gfx/platform/freetype/font.rs
+++ b/servo/components/gfx/platform/freetype/font.rs
@@ -108,19 +108,25 @@ impl FontHandleMethods for FontHandle {
     fn template(&self) -> Arc<FontTemplateData> {
         self.font_data.clone()
     }
     fn family_name(&self) -> String {
         unsafe {
             c_str_to_string((*self.face).family_name as *const c_char)
         }
     }
-    fn face_name(&self) -> String {
+    fn face_name(&self) -> Option<String> {
         unsafe {
-            c_str_to_string(FT_Get_Postscript_Name(self.face) as *const c_char)
+            let name = FT_Get_Postscript_Name(self.face) as *const c_char;
+
+            if !name.is_null() {
+                Some(c_str_to_string(name))
+            } else {
+                None
+            }
         }
     }
     fn is_italic(&self) -> bool {
         unsafe { (*self.face).style_flags & FT_STYLE_FLAG_ITALIC as c_long != 0 }
     }
     fn boldness(&self) -> font_weight::T {
         let default_weight = font_weight::T::Weight400;
         if unsafe { (*self.face).style_flags & FT_STYLE_FLAG_BOLD as c_long == 0 } {
--- a/servo/components/gfx/platform/macos/font.rs
+++ b/servo/components/gfx/platform/macos/font.rs
@@ -197,18 +197,18 @@ impl FontHandleMethods for FontHandle {
     fn template(&self) -> Arc<FontTemplateData> {
         self.font_data.clone()
     }
 
     fn family_name(&self) -> String {
         self.ctfont.family_name()
     }
 
-    fn face_name(&self) -> String {
-        self.ctfont.face_name()
+    fn face_name(&self) -> Option<String> {
+        Some(self.ctfont.face_name())
     }
 
     fn is_italic(&self) -> bool {
         self.ctfont.symbolic_traits().is_italic()
     }
 
     fn boldness(&self) -> font_weight::T {
         let normalized = self.ctfont.all_traits().normalized_weight();  // [-1.0, 1.0]
--- a/servo/components/gfx/platform/windows/font.rs
+++ b/servo/components/gfx/platform/windows/font.rs
@@ -294,18 +294,18 @@ impl FontHandleMethods for FontHandle {
     fn template(&self) -> Arc<FontTemplateData> {
         self.font_data.clone()
     }
 
     fn family_name(&self) -> String {
         self.info.family_name.clone()
     }
 
-    fn face_name(&self) -> String {
-        self.info.face_name.clone()
+    fn face_name(&self) -> Option<String> {
+        Some(self.info.face_name.clone())
     }
 
     fn is_italic(&self) -> bool {
         match self.info.style {
             FontStyle::Normal => false,
             FontStyle::Oblique | FontStyle::Italic => true,
         }
     }
--- a/servo/components/script/dom/htmlinputelement.rs
+++ b/servo/components/script/dom/htmlinputelement.rs
@@ -1112,17 +1112,17 @@ impl VirtualMethods for HTMLInputElement
                                 let translated_x = mouse_event.ClientX() + window.PageXOffset();
                                 let translated_y = mouse_event.ClientY() + window.PageYOffset();
                                 let TextIndexResponse(index) = window.text_index_query(
                                     self.upcast::<Node>().to_trusted_node_address(),
                                     translated_x,
                                     translated_y
                                 );
                                 if let Some(i) = index {
-                                    self.textinput.borrow_mut().edit_point.index = i as usize;
+                                    self.textinput.borrow_mut().set_edit_point_index(i as usize);
                                     // trigger redraw
                                     self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
                                     event.PreventDefault();
                                 }
                             }
                     }
                 }
         } else if event.type_() == atom!("keydown") && !event.DefaultPrevented() &&
--- a/servo/components/script/textinput.rs
+++ b/servo/components/script/textinput.rs
@@ -372,28 +372,26 @@ impl<T: ClipboardProvider> TextInput<T> 
     }
 
     pub fn adjust_horizontal_by_one(&mut self, direction: Direction, select: Selection) {
         if self.adjust_selection_for_horizontal_change(direction, select) {
             return
         }
         let adjust = {
             let current_line = &self.lines[self.edit_point.line];
-            // FIXME: We adjust by one code point, but it proably should be one grapheme cluster
-            // https://github.com/unicode-rs/unicode-segmentation
             match direction {
                 Direction::Forward => {
-                    match current_line[self.edit_point.index..].chars().next() {
-                        Some(c) => c.len_utf8() as isize,
+                    match current_line[self.edit_point.index..].graphemes(true).next() {
+                        Some(c) => c.len() as isize,
                         None => 1,  // Going to the next line is a "one byte" offset
                     }
                 }
                 Direction::Backward => {
-                    match current_line[..self.edit_point.index].chars().next_back() {
-                        Some(c) => -(c.len_utf8() as isize),
+                    match current_line[..self.edit_point.index].graphemes(true).next_back() {
+                        Some(c) => -(c.len() as isize),
                         None => -1,  // Going to the previous line is a "one byte" offset
                     }
                 }
             }
         };
         self.perform_horizontal_adjustment(adjust, select);
     }
 
@@ -840,9 +838,17 @@ impl<T: ClipboardProvider> TextInput<T> 
             Some(selection_begin_point) => {
                 self.get_absolute_point_for_text_point(&selection_begin_point)
             },
             None => self.get_absolute_insertion_point()
         };
 
         selection_start as u32
     }
+
+    pub fn set_edit_point_index(&mut self, index: usize) {
+        let byte_size = self.lines[self.edit_point.line]
+            .graphemes(true)
+            .take(index)
+            .fold(0, |acc, x| acc + x.len());
+        self.edit_point.index = byte_size;
+    }
 }
--- a/servo/components/selectors/Cargo.toml
+++ b/servo/components/selectors/Cargo.toml
@@ -16,8 +16,9 @@ name = "selectors"
 path = "lib.rs"
 
 [dependencies]
 bitflags = "0.7"
 matches = "0.1"
 cssparser = "0.12.1"
 fnv = "1.0"
 precomputed-hash = "0.1"
+smallvec = "0.3"
--- a/servo/components/selectors/lib.rs
+++ b/servo/components/selectors/lib.rs
@@ -2,16 +2,17 @@
  * 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/. */
 
 #[macro_use] extern crate bitflags;
 #[macro_use] extern crate cssparser;
 #[macro_use] extern crate matches;
 extern crate fnv;
 extern crate precomputed_hash;
+extern crate smallvec;
 
 pub mod bloom;
 pub mod matching;
 pub mod parser;
 mod tree;
 pub mod visitor;
 
 pub use parser::{SelectorImpl, Parser, SelectorList};
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -358,22 +358,39 @@ impl<Impl: SelectorImpl> ToCss for Selec
             pseudo.to_css(dest)?;
         }
         Ok(())
     }
 }
 
 impl<Impl: SelectorImpl> ToCss for ComplexSelector<Impl> {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        if let Some((ref next, ref combinator)) = self.next {
-            next.to_css(dest)?;
-            combinator.to_css(dest)?;
-        }
-        for simple in &self.compound_selector {
-            simple.to_css(dest)?;
+       use smallvec::SmallVec;
+       let mut current = self;
+       let mut nodes = SmallVec::<[&Self;8]>::new();
+       nodes.push(current);
+
+       loop {
+           match current.next {
+               None => break,
+               Some((ref next, _)) => {
+                   current = &**next;
+                   nodes.push(next);
+               }
+           }
+       }
+
+       for selector in nodes.iter().rev() {
+           if let Some((_, ref combinator)) = selector.next {
+               combinator.to_css(dest)?;
+           }
+
+           for simple in &selector.compound_selector {
+               simple.to_css(dest)?;
+           }
         }
         Ok(())
     }
 }
 
 impl ToCss for Combinator {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
--- a/servo/components/style/logical_geometry.rs
+++ b/servo/components/style/logical_geometry.rs
@@ -24,18 +24,22 @@ pub enum InlineBaseDirection {
 
 // TODO: improve the readability of the WritingMode serialization, refer to the Debug:fmt()
 bitflags!(
     #[cfg_attr(feature = "servo", derive(HeapSizeOf, Serialize))]
     pub flags WritingMode: u8 {
         const FLAG_RTL = 1 << 0,
         const FLAG_VERTICAL = 1 << 1,
         const FLAG_VERTICAL_LR = 1 << 2,
-        const FLAG_SIDEWAYS = 1 << 3,
-        const FLAG_UPRIGHT = 1 << 4,
+        /// For vertical writing modes only.  When set, line-over/line-under
+        /// sides are inverted from block-start/block-end.  This flag is
+        /// set when sideways-lr is used.
+        const FLAG_LINE_INVERTED = 1 << 3,
+        const FLAG_SIDEWAYS = 1 << 4,
+        const FLAG_UPRIGHT = 1 << 5,
     }
 );
 
 impl WritingMode {
     #[inline]
     pub fn is_vertical(&self) -> bool {
         self.intersects(FLAG_VERTICAL)
     }
@@ -45,17 +49,17 @@ impl WritingMode {
     pub fn is_vertical_lr(&self) -> bool {
         self.intersects(FLAG_VERTICAL_LR)
     }
 
     /// Assuming .is_vertical(), does the inline direction go top to bottom?
     #[inline]
     pub fn is_inline_tb(&self) -> bool {
         // https://drafts.csswg.org/css-writing-modes-3/#logical-to-physical
-        !self.intersects(FLAG_RTL)
+        self.intersects(FLAG_RTL) == self.intersects(FLAG_LINE_INVERTED)
     }
 
     #[inline]
     pub fn is_bidi_ltr(&self) -> bool {
         !self.intersects(FLAG_RTL)
     }
 
     #[inline]
@@ -140,16 +144,19 @@ impl fmt::Display for WritingMode {
             if self.is_vertical_lr() {
                 try!(write!(formatter, " LR"));
             } else {
                 try!(write!(formatter, " RL"));
             }
             if self.intersects(FLAG_SIDEWAYS) {
                 try!(write!(formatter, " Sideways"));
             }
+            if self.intersects(FLAG_LINE_INVERTED) {
+                try!(write!(formatter, " Inverted"));
+            }
         } else {
             try!(write!(formatter, "H"));
         }
         if self.is_bidi_ltr() {
             write!(formatter, " LTR")
         } else {
             write!(formatter, " RTL")
         }
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -28,49 +28,80 @@ def to_rust_ident(name):
         name += "_"
     return name
 
 
 def to_camel_case(ident):
     return re.sub("(^|_|-)([a-z])", lambda m: m.group(2).upper(), ident.strip("_").strip("-"))
 
 
+def parse_aliases(value):
+    aliases = {}
+    for pair in value.split():
+        [a, v] = pair.split("=")
+        aliases[a] = v
+    return aliases
+
+
 class Keyword(object):
     def __init__(self, name, values, gecko_constant_prefix=None,
                  gecko_enum_prefix=None, custom_consts=None,
                  extra_gecko_values=None, extra_servo_values=None,
+                 aliases=None,
+                 extra_gecko_aliases=None, extra_servo_aliases=None,
                  gecko_strip_moz_prefix=True,
                  gecko_inexhaustive=None):
         self.name = name
         self.values = values.split()
         if gecko_constant_prefix and gecko_enum_prefix:
             raise TypeError("Only one of gecko_constant_prefix and gecko_enum_prefix can be specified")
         self.gecko_constant_prefix = gecko_constant_prefix or \
             "NS_STYLE_" + self.name.upper().replace("-", "_")
         self.gecko_enum_prefix = gecko_enum_prefix
         self.extra_gecko_values = (extra_gecko_values or "").split()
         self.extra_servo_values = (extra_servo_values or "").split()
+        self.aliases = parse_aliases(aliases or "")
+        self.extra_gecko_aliases = parse_aliases(extra_gecko_aliases or "")
+        self.extra_servo_aliases = parse_aliases(extra_servo_aliases or "")
         self.consts_map = {} if custom_consts is None else custom_consts
         self.gecko_strip_moz_prefix = gecko_strip_moz_prefix
         self.gecko_inexhaustive = gecko_inexhaustive or (gecko_enum_prefix is None)
 
     def gecko_values(self):
         return self.values + self.extra_gecko_values
 
     def servo_values(self):
         return self.values + self.extra_servo_values
 
+    def gecko_aliases(self):
+        aliases = self.aliases.copy()
+        aliases.update(self.extra_gecko_aliases)
+        return aliases
+
+    def servo_aliases(self):
+        aliases = self.aliases.copy()
+        aliases.update(self.extra_servo_aliases)
+        return aliases
+
     def values_for(self, product):
         if product == "gecko":
             return self.gecko_values()
         elif product == "servo":
             return self.servo_values()
         else:
             raise Exception("Bad product: " + product)
 
+    def aliases_for(self, product):
+        if product == "gecko":
+            return self.gecko_aliases()
+        elif product == "servo":
+            return self.servo_aliases()
+        else:
+            raise Exception("Bad product: " + product)
+
     def gecko_constant(self, value):
         moz_stripped = value.replace("-moz-", '') if self.gecko_strip_moz_prefix else value.replace("-moz-", 'moz-')
         mapped = self.consts_map.get(value)
         if self.gecko_enum_prefix:
             parts = moz_stripped.replace('-', '_').split('_')
             parts = mapped if mapped else [p.title() for p in parts]
             return self.gecko_enum_prefix + "::" + "".join(parts)
         else:
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -2118,16 +2118,27 @@ fn static_assert() {
         self.gecko.mPerspectiveOrigin[1].set(v.vertical);
     }
 
     pub fn copy_perspective_origin_from(&mut self, other: &Self) {
         self.gecko.mPerspectiveOrigin[0].copy_from(&other.gecko.mPerspectiveOrigin[0]);
         self.gecko.mPerspectiveOrigin[1].copy_from(&other.gecko.mPerspectiveOrigin[1]);
     }
 
+    pub fn clone_perspective_origin(&self) -> longhands::perspective_origin::computed_value::T {
+        use properties::longhands::perspective_origin::computed_value::T;
+        use values::computed::LengthOrPercentage;
+        T {
+            horizontal: LengthOrPercentage::from_gecko_style_coord(&self.gecko.mPerspectiveOrigin[0])
+                .expect("Expected length or percentage for horizontal value of perspective-origin"),
+            vertical: LengthOrPercentage::from_gecko_style_coord(&self.gecko.mPerspectiveOrigin[1])
+                .expect("Expected length or percentage for vertical value of perspective-origin"),
+        }
+    }
+
     pub fn set_transform_origin(&mut self, v: longhands::transform_origin::computed_value::T) {
         self.gecko.mTransformOrigin[0].set(v.horizontal);
         self.gecko.mTransformOrigin[1].set(v.vertical);
         self.gecko.mTransformOrigin[2].set(v.depth);
     }
 
     pub fn copy_transform_origin_from(&mut self, other: &Self) {
         self.gecko.mTransformOrigin[0].copy_from(&other.gecko.mTransformOrigin[0]);
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -390,19 +390,43 @@
                 }
             }
         % endif
     }
 </%def>
 
 <%def name="single_keyword(name, values, vector=False, **kwargs)">
     <%call expr="single_keyword_computed(name, values, vector, **kwargs)">
-        use values::computed::ComputedValueAsSpecified;
+        % if not "extra_specified" in kwargs and ("aliases" in kwargs or (("extra_%s_aliases" % product) in kwargs)):
+            impl ToComputedValue for SpecifiedValue {
+                type ComputedValue = computed_value::T;
+
+                #[inline]
+                fn to_computed_value(&self, _context: &Context) -> computed_value::T {
+                    match *self {
+                        % for value in data.longhands_by_name[name].keyword.values_for(product):
+                            SpecifiedValue::${to_rust_ident(value)} => computed_value::T::${to_rust_ident(value)},
+                        % endfor
+                    }
+                }
+                #[inline]
+                fn from_computed_value(computed: &computed_value::T) -> Self {
+                    match *computed {
+                        % for value in data.longhands_by_name[name].keyword.values_for(product):
+                            computed_value::T::${to_rust_ident(value)} => SpecifiedValue::${to_rust_ident(value)},
+                        % endfor
+                    }
+                }
+            }
+        % else:
+            use values::computed::ComputedValueAsSpecified;
+            impl ComputedValueAsSpecified for SpecifiedValue {}
+        % endif
+
         use values::HasViewportPercentage;
-        impl ComputedValueAsSpecified for SpecifiedValue {}
         no_viewport_percentage!(SpecifiedValue);
     </%call>
 </%def>
 
 <%def name="gecko_keyword_conversion(keyword, values=None, type='SpecifiedValue')">
     <%
         if not values:
             values = keyword.values_for(product)
@@ -439,27 +463,35 @@
 </%def>
 
 <%def name="single_keyword_computed(name, values, vector=False,
             extra_specified=None, needs_conversion=False, **kwargs)">
     <%
         keyword_kwargs = {a: kwargs.pop(a, None) for a in [
             'gecko_constant_prefix', 'gecko_enum_prefix',
             'extra_gecko_values', 'extra_servo_values',
+            'aliases', 'extra_gecko_aliases', 'extra_servo_aliases',
             'custom_consts', 'gecko_inexhaustive',
         ]}
     %>
 
     <%def name="inner_body(keyword, extra_specified=None, needs_conversion=False)">
-        % if extra_specified:
+        % if extra_specified or keyword.aliases_for(product):
             use style_traits::ToCss;
             define_css_keyword_enum! { SpecifiedValue:
-                % for value in keyword.values_for(product) + extra_specified.split():
-                    "${value}" => ${to_rust_ident(value)},
-                % endfor
+                values {
+                    % for value in keyword.values_for(product) + (extra_specified or "").split():
+                        "${value}" => ${to_rust_ident(value)},
+                    % endfor
+                }
+                aliases {
+                    % for alias, value in keyword.aliases_for(product).iteritems():
+                        "${alias}" => ${to_rust_ident(value)},
+                    % endfor
+                }
             }
         % else:
             pub use self::computed_value::T as SpecifiedValue;
         % endif
         pub mod computed_value {
             use style_traits::ToCss;
             define_css_keyword_enum! { T:
                 % for value in data.longhands_by_name[name].keyword.values_for(product):
@@ -488,16 +520,17 @@
             }
         }
 
         % if needs_conversion:
             <%
                 conversion_values = keyword.values_for(product)
                 if extra_specified:
                     conversion_values += extra_specified.split()
+                conversion_values += keyword.aliases_for(product).keys()
             %>
             ${gecko_keyword_conversion(keyword, values=conversion_values)}
         % endif
     </%def>
     % if vector:
         <%call expr="vector_longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)">
             ${inner_body(Keyword(name, values, **keyword_kwargs))}
             ${caller.body()}
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -1863,41 +1863,29 @@
                           "parse_non_negative_length",
                           gecko_ffi_name="mChildPerspective",
                           spec="https://drafts.csswg.org/css-transforms/#perspective",
                           extra_prefixes="moz webkit",
                           creates_stacking_context=True,
                           fixpos_cb=True,
                           animation_type="normal")}
 
-// FIXME: This prop should be animatable
-<%helpers:longhand name="perspective-origin" boxed="True" animation_type="none" extra_prefixes="moz webkit"
+<%helpers:longhand name="perspective-origin" boxed="True" animation_type="normal" extra_prefixes="moz webkit"
                    spec="https://drafts.csswg.org/css-transforms/#perspective-origin-property">
     use std::fmt;
     use style_traits::ToCss;
     use values::HasViewportPercentage;
     use values::specified::{LengthOrPercentage, Percentage};
 
     pub mod computed_value {
+        use properties::animated_properties::Interpolate;
         use values::computed::LengthOrPercentage;
+        use values::computed::Position;
 
-        #[derive(Clone, Copy, Debug, PartialEq)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub struct T {
-            pub horizontal: LengthOrPercentage,
-            pub vertical: LengthOrPercentage,
-        }
-    }
-
-    impl ToCss for computed_value::T {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            try!(self.horizontal.to_css(dest));
-            try!(dest.write_str(" "));
-            self.vertical.to_css(dest)
-        }
+        pub type T = Position;
     }
 
     impl HasViewportPercentage for SpecifiedValue {
         fn has_viewport_percentage(&self) -> bool {
             self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage()
         }
     }
 
--- a/servo/components/style/properties/longhand/inherited_box.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_box.mako.rs
@@ -13,61 +13,36 @@
                          gecko_ffi_name="mVisible",
                          animation_type="normal",
                          spec="https://drafts.csswg.org/css-box/#propdef-visibility")}
 
 // CSS Writing Modes Level 3
 // https://drafts.csswg.org/css-writing-modes-3
 ${helpers.single_keyword("writing-mode",
                          "horizontal-tb vertical-rl vertical-lr",
+                         extra_gecko_values="sideways-rl sideways-lr",
+                         extra_gecko_aliases="lr=horizontal-tb lr-tb=horizontal-tb \
+                                              rl=horizontal-tb rl-tb=horizontal-tb \
+                                              tb=vertical-rl   tb-rl=vertical-rl",
                          experimental=True,
                          need_clone=True,
                          animation_type="none",
                          spec="https://drafts.csswg.org/css-writing-modes/#propdef-writing-mode")}
 
 ${helpers.single_keyword("direction", "ltr rtl", need_clone=True, animation_type="none",
                          spec="https://drafts.csswg.org/css-writing-modes/#propdef-direction",
                          needs_conversion=True)}
 
-<%helpers:single_keyword_computed
-    name="text-orientation"
-    values="mixed upright sideways"
-    extra_specified="sideways-right"
-    products="gecko"
-    need_clone="True"
-    animation_type="none"
-    spec="https://drafts.csswg.org/css-writing-modes/#propdef-text-orientation"
->
-    use values::HasViewportPercentage;
-    no_viewport_percentage!(SpecifiedValue);
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, _: &Context) -> computed_value::T {
-            match *self {
-                % for value in "mixed upright sideways".split():
-                    SpecifiedValue::${value} => computed_value::T::${value},
-                % endfor
-                // https://drafts.csswg.org/css-writing-modes-3/#valdef-text-orientation-sideways-right
-                SpecifiedValue::sideways_right => computed_value::T::sideways,
-            }
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> SpecifiedValue {
-            match *computed {
-                % for value in "mixed upright sideways".split():
-                    computed_value::T::${value} => SpecifiedValue::${value},
-                % endfor
-            }
-        }
-    }
-</%helpers:single_keyword_computed>
+${helpers.single_keyword("text-orientation",
+                         "mixed upright sideways",
+                         extra_gecko_aliases="sideways-right=sideways",
+                         products="gecko",
+                         need_clone=True,
+                         animation_type="none",
+                         spec="https://drafts.csswg.org/css-writing-modes/#propdef-text-orientation")}
 
 // CSS Color Module Level 4
 // https://drafts.csswg.org/css-color/
 ${helpers.single_keyword("color-adjust",
                          "economy exact", products="gecko",
                          animation_type="none",
                          spec="https://drafts.csswg.org/css-color/#propdef-color-adjust")}
 
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -1269,19 +1269,16 @@
 
 ${helpers.single_keyword("ruby-position", "over under",
                          products="gecko", animation_type="none",
                          spec="https://drafts.csswg.org/css-ruby/#ruby-position-property")}
 
 // CSS Writing Modes Module Level 3
 // https://drafts.csswg.org/css-writing-modes-3/
 
-// The spec has "digits <integer>?" value in addition. But that value is
-// at-risk, and Gecko's layout code doesn't support that either. So we
-// can just take the easy way for now.
 ${helpers.single_keyword("text-combine-upright", "none all",
                          products="gecko", animation_type="none",
                          spec="https://drafts.csswg.org/css-writing-modes-3/#text-combine-upright")}
 
 // SVG 1.1: Section 11 - Painting: Filling, Stroking and Marker Symbols
 ${helpers.single_keyword("text-rendering",
                          "auto optimizespeed optimizelegibility geometricprecision",
                          animation_type="none",
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -1931,26 +1931,43 @@ pub fn get_writing_mode(inheritedbox_sty
         computed_values::writing_mode::T::horizontal_tb => {},
         computed_values::writing_mode::T::vertical_rl => {
             flags.insert(logical_geometry::FLAG_VERTICAL);
         },
         computed_values::writing_mode::T::vertical_lr => {
             flags.insert(logical_geometry::FLAG_VERTICAL);
             flags.insert(logical_geometry::FLAG_VERTICAL_LR);
         },
+        % if product == "gecko":
+        computed_values::writing_mode::T::sideways_rl => {
+            flags.insert(logical_geometry::FLAG_VERTICAL);
+            flags.insert(logical_geometry::FLAG_SIDEWAYS);
+        },
+        computed_values::writing_mode::T::sideways_lr => {
+            flags.insert(logical_geometry::FLAG_VERTICAL);
+            flags.insert(logical_geometry::FLAG_VERTICAL_LR);
+            flags.insert(logical_geometry::FLAG_LINE_INVERTED);
+            flags.insert(logical_geometry::FLAG_SIDEWAYS);
+        },
+        % endif
     }
     % if product == "gecko":
-    match inheritedbox_style.clone_text_orientation() {
-        computed_values::text_orientation::T::mixed => {},
-        computed_values::text_orientation::T::upright => {
-            flags.insert(logical_geometry::FLAG_UPRIGHT);
-        },
-        computed_values::text_orientation::T::sideways => {
-            flags.insert(logical_geometry::FLAG_SIDEWAYS);
-        },
+    // If FLAG_SIDEWAYS is already set, this means writing-mode is either
+    // sideways-rl or sideways-lr, and for both of these values,
+    // text-orientation has no effect.
+    if !flags.intersects(logical_geometry::FLAG_SIDEWAYS) {
+        match inheritedbox_style.clone_text_orientation() {
+            computed_values::text_orientation::T::mixed => {},
+            computed_values::text_orientation::T::upright => {
+                flags.insert(logical_geometry::FLAG_UPRIGHT);
+            },
+            computed_values::text_orientation::T::sideways => {
+                flags.insert(logical_geometry::FLAG_SIDEWAYS);
+            },
+        }
     }
     % endif
     flags
 }
 
 
 #[cfg(feature = "servo")]
 pub use self::lazy_static_module::INITIAL_SERVO_VALUES;
--- a/servo/components/style_traits/values.rs
+++ b/servo/components/style_traits/values.rs
@@ -77,47 +77,72 @@ impl_to_css_for_predefined_type!(i32);
 impl_to_css_for_predefined_type!(u32);
 impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
 impl_to_css_for_predefined_type!(::cssparser::RGBA);
 impl_to_css_for_predefined_type!(::cssparser::Color);
 impl_to_css_for_predefined_type!(::cssparser::UnicodeRange);
 
 #[macro_export]
 macro_rules! define_css_keyword_enum {
+    ($name: ident: values { $( $css: expr => $variant: ident),+, }
+                   aliases { $( $alias: expr => $alias_variant: ident ),+, }) => {
+        __define_css_keyword_enum__add_optional_traits!($name [ $( $css => $variant ),+ ]
+                                                              [ $( $alias => $alias_variant ),+ ]);
+    };
+    ($name: ident: values { $( $css: expr => $variant: ident),+, }
+                   aliases { $( $alias: expr => $alias_variant: ident ),* }) => {
+        __define_css_keyword_enum__add_optional_traits!($name [ $( $css => $variant ),+ ]
+                                                              [ $( $alias => $alias_variant ),* ]);
+    };
+    ($name: ident: values { $( $css: expr => $variant: ident),+ }
+                   aliases { $( $alias: expr => $alias_variant: ident ),+, }) => {
+        __define_css_keyword_enum__add_optional_traits!($name [ $( $css => $variant ),+ ]
+                                                              [ $( $alias => $alias_variant ),+ ]);
+    };
+    ($name: ident: values { $( $css: expr => $variant: ident),+ }
+                   aliases { $( $alias: expr => $alias_variant: ident ),* }) => {
+        __define_css_keyword_enum__add_optional_traits!($name [ $( $css => $variant ),+ ]
+                                                              [ $( $alias => $alias_variant ),* ]);
+    };
     ($name: ident: $( $css: expr => $variant: ident ),+,) => {
-        __define_css_keyword_enum__add_optional_traits!($name [ $( $css => $variant ),+ ]);
+        __define_css_keyword_enum__add_optional_traits!($name [ $( $css => $variant ),+ ] []);
     };
     ($name: ident: $( $css: expr => $variant: ident ),+) => {
-        __define_css_keyword_enum__add_optional_traits!($name [ $( $css => $variant ),+ ]);
+        __define_css_keyword_enum__add_optional_traits!($name [ $( $css => $variant ),+ ] []);
     };
 }
 
 #[cfg(feature = "servo")]
 #[macro_export]
 macro_rules! __define_css_keyword_enum__add_optional_traits {
-    ($name: ident [ $( $css: expr => $variant: ident ),+ ]) => {
+    ($name: ident [ $( $css: expr => $variant: ident ),+ ]
+                  [ $( $alias: expr => $alias_variant: ident),* ]) => {
         __define_css_keyword_enum__actual! {
-            $name [ Deserialize, Serialize, HeapSizeOf ] [ $( $css => $variant ),+ ]
+            $name [ Deserialize, Serialize, HeapSizeOf ]
+                  [ $( $css => $variant ),+ ]
+                  [ $( $alias => $alias_variant ),* ]
         }
     };
 }
 
 #[cfg(not(feature = "servo"))]
 #[macro_export]
 macro_rules! __define_css_keyword_enum__add_optional_traits {
-    ($name: ident [ $( $css: expr => $variant: ident ),+ ]) => {
+    ($name: ident [ $( $css: expr => $variant: ident ),+ ] [ $( $alias: expr => $alias_variant: ident),* ]) => {
         __define_css_keyword_enum__actual! {
-            $name [] [ $( $css => $variant ),+ ]
+            $name [] [ $( $css => $variant ),+ ] [ $( $alias => $alias_variant ),* ]
         }
     };
 }
 
 #[macro_export]
 macro_rules! __define_css_keyword_enum__actual {
-    ($name: ident [ $( $derived_trait: ident),* ] [ $( $css: expr => $variant: ident ),+ ]) => {
+    ($name: ident [ $( $derived_trait: ident),* ]
+                  [ $( $css: expr => $variant: ident ),+ ]
+                  [ $( $alias: expr => $alias_variant: ident ),* ]) => {
         #[allow(non_camel_case_types, missing_docs)]
         #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq $(, $derived_trait )* )]
         pub enum $name {
             $( $variant ),+
         }
 
         impl $name {
             /// Parse this property from a CSS input stream.
@@ -125,16 +150,17 @@ macro_rules! __define_css_keyword_enum__
                 let ident = input.expect_ident()?;
                 Self::from_ident(&ident)
             }
 
             /// Parse this property from an already-tokenized identifier.
             pub fn from_ident(ident: &str) -> Result<$name, ()> {
                 match_ignore_ascii_case! { ident,
                                            $( $css => Ok($name::$variant), )+
+                                           $( $alias => Ok($name::$alias_variant), )*
                                            _ => Err(())
                 }
             }
         }
 
         impl ToCss for $name {
             fn to_css<W>(&self, dest: &mut W) -> ::std::fmt::Result
                 where W: ::std::fmt::Write
--- a/servo/python/mach_bootstrap.py
+++ b/servo/python/mach_bootstrap.py
@@ -206,17 +206,17 @@ def _ensure_case_insensitive_if_windows(
     # system is case sensitive or not.
     if _is_windows() and not os.path.exists('Python'):
         print('Cannot run mach in a path on a case-sensitive file system on Windows.')
         print('For more details, see https://github.com/pypa/virtualenv/issues/935')
         sys.exit(1)
 
 
 def _is_windows():
-    return sys.platform == 'win32' or sys.platform == 'msys'
+    return sys.platform == 'win32'
 
 
 def bootstrap(topdir):
     _ensure_case_insensitive_if_windows()
 
     topdir = os.path.abspath(topdir)
 
     # We don't support paths with Unicode characters for now
@@ -230,25 +230,16 @@ def bootstrap(topdir):
 
     # We don't support paths with spaces for now
     # https://github.com/servo/servo/issues/9442
     if ' ' in topdir:
         print('Cannot run mach in a path with spaces.')
         print('Current path:', topdir)
         sys.exit(1)
 
-    # We don't support MinGW Python
-    if os.path.join(os.sep, 'mingw64', 'bin') in sys.executable:
-        print('Cannot run mach with MinGW or MSYS Python.')
-        print('\nPlease add the path to Windows Python (usually /c/Python27) to your path.')
-        print('You can do this by appending the line:')
-        print('    export PATH=/c/Python27:$PATH')
-        print('to your ~/.profile.')
-        sys.exit(1)
-
     # Ensure we are running Python 2.7+. We put this check here so we generate a
     # user-friendly error message rather than a cryptic stack trace on module import.
     if not (3, 0) > sys.version_info >= (2, 7):
         print('Python 2.7 or above (but not Python 3) is required to run mach.')
         print('You are running Python', platform.python_version())
         sys.exit(1)
 
     # See if we're inside a Firefox checkout.
--- a/servo/python/servo/bootstrap.py
+++ b/servo/python/servo/bootstrap.py
@@ -165,49 +165,16 @@ def salt(context, force=False):
     retcode = run_as_root(cmd + ['--retcode-passthrough'])
     if retcode == 0:
         print('Salt bootstrapping complete')
     else:
         print('Salt bootstrapping encountered errors')
     return retcode
 
 
-def windows_gnu(context, force=False):
-    '''Bootstrapper for msys2 based environments for building in Windows.'''
-
-    if not find_executable('pacman'):
-        print(
-            'The Windows GNU bootstrapper only works with msys2 with pacman. '
-            'Get msys2 at http://msys2.github.io/'
-        )
-        return 1
-
-    # Ensure repositories are up to date
-    command = ['pacman', '--sync', '--refresh']
-    subprocess.check_call(command)
-
-    # Install packages
-    command = ['pacman', '--sync', '--needed']
-    if force:
-        command.append('--noconfirm')
-    subprocess.check_call(command + list(packages.WINDOWS_GNU))
-
-    # Downgrade GCC to 5.4.0-1
-    gcc_pkgs = ["gcc", "gcc-ada", "gcc-fortran", "gcc-libgfortran", "gcc-libs", "gcc-objc"]
-    gcc_version = "5.4.0-1"
-    mingw_url = "http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-{}-{}-any.pkg.tar.xz"
-    gcc_list = [mingw_url.format(gcc, gcc_version) for gcc in gcc_pkgs]
-
-    # Note: `--upgrade` also does downgrades
-    downgrade_command = ['pacman', '--upgrade']
-    if force:
-        downgrade_command.append('--noconfirm')
-    subprocess.check_call(downgrade_command + gcc_list)
-
-
 def windows_msvc(context, force=False):
     '''Bootstrapper for MSVC building on Windows.'''
 
     deps_dir = os.path.join(context.sharedir, "msvc-dependencies")
     deps_url = "https://servo-deps.s3.amazonaws.com/msvc-deps/"
 
     def version(package):
         return packages.WINDOWS_MSVC[package]
@@ -259,19 +226,17 @@ def windows_msvc(context, force=False):
     return 0
 
 
 def bootstrap(context, force=False):
     '''Dispatches to the right bootstrapping function for the OS.'''
 
     bootstrapper = None
 
-    if "windows-gnu" in host_triple():
-        bootstrapper = windows_gnu
-    elif "windows-msvc" in host_triple():
+    if "windows-msvc" in host_triple():
         bootstrapper = windows_msvc
     elif "linux-gnu" in host_triple():
         distro, version, _ = platform.linux_distribution()
         if distro.lower() in [
             'centos',
             'centos linux',
             'debian',
             'fedora',
--- a/servo/python/servo/build_commands.py
+++ b/servo/python/servo/build_commands.py
@@ -292,57 +292,53 @@ class MachCommands(CommandBase):
             env['CXXFLAGS'] = ' '.join([
                 "--sysroot", env['ANDROID_SYSROOT'],
                 "-I" + support_include,
                 "-I" + cxx_include,
                 "-I" + cxxabi_include])
 
         cargo_binary = "cargo" + BIN_SUFFIX
 
-        if sys.platform in ("win32", "msys"):
-            if "msvc" not in host_triple():
-                env[b'RUSTFLAGS'] = b'-C link-args=-Wl,--subsystem,windows'
-
         status = call(
             [cargo_binary, "build"] + opts,
             env=env, cwd=self.servo_crate(), verbose=verbose)
         elapsed = time() - build_start
 
         # Do some additional things if the build succeeded
         if status == 0:
-            if sys.platform in ("win32", "msys"):
+            if sys.platform == "win32":
                 servo_exe_dir = path.join(base_path, "debug" if dev else "release")
                 # On windows, copy in our manifest
                 shutil.copy(path.join(self.get_top_dir(), "components", "servo", "servo.exe.manifest"),
                             servo_exe_dir)
-                if "msvc" in (target or host_triple()):
-                    msvc_x64 = "64" if "x86_64" in (target or host_triple()) else ""
-                    # on msvc builds, use editbin to change the subsystem to windows, but only
-                    # on release builds -- on debug builds, it hides log output
-                    if not dev:
-                        call(["editbin", "/nologo", "/subsystem:windows", path.join(servo_exe_dir, "servo.exe")],
-                             verbose=verbose)
-                    # on msvc, we need to copy in some DLLs in to the servo.exe dir
-                    for ssl_lib in ["libcryptoMD.dll", "libsslMD.dll"]:
-                        shutil.copy(path.join(env['OPENSSL_LIB_DIR'], "../bin" + msvc_x64, ssl_lib),
-                                    servo_exe_dir)
+
+                msvc_x64 = "64" if "x86_64" in (target or host_triple()) else ""
+                # on msvc builds, use editbin to change the subsystem to windows, but only
+                # on release builds -- on debug builds, it hides log output
+                if not dev:
+                    call(["editbin", "/nologo", "/subsystem:windows", path.join(servo_exe_dir, "servo.exe")],
+                         verbose=verbose)
+                # on msvc, we need to copy in some DLLs in to the servo.exe dir
+                for ssl_lib in ["libcryptoMD.dll", "libsslMD.dll"]:
+                    shutil.copy(path.join(env['OPENSSL_LIB_DIR'], "../bin" + msvc_x64, ssl_lib),
+                                servo_exe_dir)
 
-                elif sys.platform == "darwin":
-                    # On the Mac, set a lovely icon. This makes it easier to pick out the Servo binary in tools
-                    # like Instruments.app.
-                    try:
-                        import Cocoa
-                        icon_path = path.join(self.get_top_dir(), "resources", "servo.png")
-                        icon = Cocoa.NSImage.alloc().initWithContentsOfFile_(icon_path)
-                        if icon is not None:
-                            Cocoa.NSWorkspace.sharedWorkspace().setIcon_forFile_options_(icon,
-                                                                                         servo_path,
-                                                                                         0)
-                    except ImportError:
-                        pass
+            elif sys.platform == "darwin":
+                # On the Mac, set a lovely icon. This makes it easier to pick out the Servo binary in tools
+                # like Instruments.app.
+                try:
+                    import Cocoa
+                    icon_path = path.join(self.get_top_dir(), "resources", "servo.png")
+                    icon = Cocoa.NSImage.alloc().initWithContentsOfFile_(icon_path)
+                    if icon is not None:
+                        Cocoa.NSWorkspace.sharedWorkspace().setIcon_forFile_options_(icon,
+                                                                                     servo_path,
+                                                                                     0)
+                except ImportError:
+                    pass
 
         # Generate Desktop Notification if elapsed-time > some threshold value
         notify_build_done(self.config, elapsed, status == 0)
 
         print("Build %s in %s" % ("Completed" if status == 0 else "FAILED", format_duration(elapsed)))
         return status
 
     @Command('build-cef',
--- a/servo/python/servo/command_base.py
+++ b/servo/python/servo/command_base.py
@@ -163,18 +163,17 @@ def check_call(*args, **kwargs):
         except KeyboardInterrupt:
             pass
 
     if status:
         raise subprocess.CalledProcessError(status, ' '.join(*args))
 
 
 def is_windows():
-    """ Detect windows, mingw, cygwin """
-    return sys.platform == 'win32' or sys.platform == 'msys' or sys.platform == 'cygwin'
+    return sys.platform == 'win32'
 
 
 def is_macosx():
     return sys.platform == 'darwin'
 
 
 def is_linux():
     return sys.platform.startswith('linux')
@@ -415,19 +414,16 @@ class CommandBase(object):
             if not os.environ.get("NATIVE_WIN32_PYTHON"):
                 env["NATIVE_WIN32_PYTHON"] = sys.executable
             # Always build harfbuzz from source
             env["HARFBUZZ_SYS_NO_PKG_CONFIG"] = "true"
 
         if not self.config["tools"]["system-rust"] \
                 or self.config["tools"]["rust-root"]:
             env["RUST_ROOT"] = self.config["tools"]["rust-root"]
-            # Add mingw64 binary path before rust paths to avoid conflict with libstdc++-6.dll
-            if sys.platform == "msys":
-                extra_path += [path.join(os.sep, "mingw64", "bin")]
             # These paths are for when rust-root points to an unpacked installer
             extra_path += [path.join(self.config["tools"]["rust-root"], "rustc", "bin")]
             extra_lib += [path.join(self.config["tools"]["rust-root"], "rustc", "lib")]
             # These paths are for when rust-root points to a rustc sysroot
             extra_path += [path.join(self.config["tools"]["rust-root"], "bin")]
             extra_lib += [path.join(self.config["tools"]["rust-root"], "lib")]
 
         if not self.config["tools"]["system-cargo"] \
@@ -484,17 +480,17 @@ class CommandBase(object):
             env['HOST_FILE'] = hosts_file_path
 
         env['RUSTDOC'] = path.join(self.context.topdir, 'etc', 'rustdoc-with-private')
 
         if self.config["build"]["rustflags"]:
             env['RUSTFLAGS'] = env.get('RUSTFLAGS', "") + " " + self.config["build"]["rustflags"]
 
         # Don't run the gold linker if on Windows https://github.com/servo/servo/issues/9499
-        if self.config["tools"]["rustc-with-gold"] and sys.platform not in ("win32", "msys"):
+        if self.config["tools"]["rustc-with-gold"] and sys.platform != "win32":
             if subprocess.call(['which', 'ld.gold'], stdout=PIPE, stderr=PIPE) == 0:
                 env['RUSTFLAGS'] = env.get('RUSTFLAGS', "") + " -C link-args=-fuse-ld=gold"
 
         if not (self.config["build"]["ccache"] == ""):
             env['CCACHE'] = self.config["build"]["ccache"]
 
         # Ensure Rust uses hard floats and SIMD on ARM devices
         if target:
--- a/servo/python/servo/package_commands.py
+++ b/servo/python/servo/package_commands.py
@@ -102,35 +102,22 @@ def copy_dependencies(binary_path, lib_p
                 shutil.copyfile(f, new_path)
             change_non_system_libraries_path(need_relinked, relative_path, new_path)
             need_checked.update(need_relinked)
         checked.update(checking)
         need_checked.difference_update(checked)
 
 
 def copy_windows_dependencies(binary_path, destination):
-    try:
-        [shutil.copy(path.join(binary_path, d), destination) for d in ["libcryptoMD.dll", "libsslMD.dll"]]
-    except:
-        deps = [
-            "libstdc++-6.dll",
-            "libwinpthread-1.dll",
-            "libbz2-1.dll",
-            "libgcc_s_seh-1.dll",
-            "libexpat-1.dll",
-            "zlib1.dll",
-            "libiconv-2.dll",
-            "libintl-8.dll",
-            "libcryptoMD.dll",
-            "libsslMD.dll",
-        ]
-        for d in deps:
-            dep_path = path.join("C:\\msys64\\mingw64\\bin", d)
-            if path.exists(dep_path):
-                shutil.copy(dep_path, path.join(destination, d))
+    deps = [
+        "libcryptoMD.dll",
+        "libsslMD.dll",
+    ]
+    for d in deps:
+        shutil.copy(path.join(binary_path, d), destination)
 
 
 def change_prefs(resources_path, platform):
     print("Swapping prefs")
     prefs_path = path.join(resources_path, "prefs.json")
     package_prefs_path = path.join(resources_path, "package-prefs.json")
     os_type = "os:{}".format(platform)
     with open(prefs_path) as prefs, open(package_prefs_path) as package_prefs:
--- a/servo/python/servo/packages.py
+++ b/servo/python/servo/packages.py
@@ -1,25 +1,10 @@
 # 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/.
 
-WINDOWS_GNU = set([
-    "diffutils",
-    "make",
-    "mingw-w64-x86_64-toolchain",
-    "mingw-w64-x86_64-icu",
-    "mingw-w64-x86_64-nspr",
-    "mingw-w64-x86_64-ca-certificates",
-    "mingw-w64-x86_64-expat",
-    "mingw-w64-x86_64-cmake",
-    "patch",
-    "patchutils",
-    "python2-setuptools",
-    "tar",
-])
-
 WINDOWS_MSVC = {
     "cmake": "3.7.2",
     "moztools": "0.0.1-5",
     "ninja": "1.7.1",
     "openssl": "1.1.0e-vs2015",
 }
--- a/servo/python/servo/testing_commands.py
+++ b/servo/python/servo/testing_commands.py
@@ -240,23 +240,20 @@ class MachCommands(CommandBase):
         try:
             packages.remove('style')
         except KeyError:
             has_style = False
 
         env = self.build_env()
         env["RUST_BACKTRACE"] = "1"
 
-        if sys.platform in ("win32", "msys"):
-            if "msvc" in host_triple():
-                # on MSVC, we need some DLLs in the path. They were copied
-                # in to the servo.exe build dir, so just point PATH to that.
-                env["PATH"] = "%s%s%s" % (path.dirname(self.get_binary_path(False, False)), os.pathsep, env["PATH"])
-            else:
-                env["RUSTFLAGS"] = "-C link-args=-Wl,--subsystem,windows"
+        if "msvc" in host_triple():
+            # on MSVC, we need some DLLs in the path. They were copied
+            # in to the servo.exe build dir, so just point PATH to that.
+            env["PATH"] = "%s%s%s" % (path.dirname(self.get_binary_path(False, False)), os.pathsep, env["PATH"])
 
         features = self.servo_features()
         if len(packages) > 0:
             args = ["cargo", "bench" if bench else "test"]
             for crate in packages:
                 args += ["-p", "%s_tests" % crate]
             args += test_patterns
 
--- a/servo/python/servo/util.py
+++ b/servo/python/servo/util.py
@@ -27,22 +27,18 @@ def host_platform():
     elif os_type == "darwin":
         os_type = "apple-darwin"
     elif os_type == "android":
         os_type = "linux-androideabi"
     elif os_type == "windows":
         # If we are in a Visual Studio environment, use msvc
         if os.getenv("PLATFORM") is not None:
             os_type = "pc-windows-msvc"
-        elif os.getenv("MSYSTEM") is not None:
-            os_type = "pc-windows-gnu"
         else:
             os_type = "unknown"
-    elif os_type.startswith("mingw64_nt-") or os_type.startswith("cygwin_nt-"):
-        os_type = "pc-windows-gnu"
     elif os_type == "freebsd":
         os_type = "unknown-freebsd"
     else:
         os_type = "unknown"
     return os_type
 
 
 def host_triple():
--- a/servo/tests/unit/script/textinput.rs
+++ b/servo/tests/unit/script/textinput.rs
@@ -597,8 +597,18 @@ fn test_textinput_set_selection_with_dir
     textinput.set_selection_range(2, 6);
     assert_eq!(textinput.edit_point.line, 0);
     assert_eq!(textinput.edit_point.index, 2);
 
     assert!(textinput.selection_begin.is_some());
     assert_eq!(textinput.selection_begin.unwrap().line, 0);
     assert_eq!(textinput.selection_begin.unwrap().index, 6);
 }
+
+#[test]
+fn test_textinput_unicode_handling() {
+    let mut textinput = text_input(Lines::Single, "éèùµ$£");
+    assert_eq!(textinput.edit_point.index, 0);
+    textinput.set_edit_point_index(1);
+    assert_eq!(textinput.edit_point.index, 2);
+    textinput.set_edit_point_index(4);
+    assert_eq!(textinput.edit_point.index, 8);
+}
--- a/testing/mozharness/configs/hazards/common.py
+++ b/testing/mozharness/configs/hazards/common.py
@@ -40,17 +40,16 @@ config = {
     "default_blob_upload_servers": [
         "https://blobupload.elasticbeanstalk.com",
     ],
     "blob_uploader_auth_file": os.path.join(os.getcwd(), "oauth.txt"),
 
     "virtualenv_path": '%s/venv' % os.getcwd(),
     'tools_dir': "/tools",
     'compiler_manifest': "build/gcc.manifest",
-    'b2g_compiler_manifest': "build/gcc-b2g.manifest",
     'sixgill_manifest': "build/sixgill.manifest",
 
     # Mock.
     "mock_packages": [
         "autoconf213", "mozilla-python27-mercurial", "ccache",
         "zip", "zlib-devel", "glibc-static",
         "openssh-clients", "mpfr", "wget", "rsync",
 
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -2570,16 +2570,22 @@
     ]
    ],
    "service-workers/service-worker/fetch-event-within-sw-manual.https.html": [
     [
      "/service-workers/service-worker/fetch-event-within-sw-manual.https.html",
      {}
     ]
    ],
+   "storage/persist-permission-manual.https.html": [
+    [
+     "/storage/persist-permission-manual.https.html",
+     {}
+    ]
+   ],
    "svg/import/animate-dom-01-f-manual.svg": [
     [
      "/svg/import/animate-dom-01-f-manual.svg",
      {}
     ]
    ],
    "svg/import/animate-dom-02-f-manual.svg": [
     [
@@ -61431,16 +61437,21 @@
      {}
     ]
    ],
    "storage/storage-estimate-indexeddb.js": [
     [
      {}
     ]
    ],
+   "storage/storage-persisted.js": [
+    [
+     {}
+    ]
+   ],
    "streams/OWNERS": [
     [
      {}
     ]
    ],
    "streams/README.md": [
     [
      {}
@@ -120885,16 +120896,28 @@
     ]
    ],
    "storage/opaque-origin.https.html": [
     [
      "/storage/opaque-origin.https.html",
      {}
     ]
    ],
+   "storage/persisted-worker.https.html": [
+    [
+     "/storage/persisted-worker.https.html",
+     {}
+    ]
+   ],
+   "storage/persisted.https.html": [
+    [
+     "/storage/persisted.https.html",
+     {}
+    ]
+   ],
    "streams/byte-length-queuing-strategy.dedicatedworker.html": [
     [
      "/streams/byte-length-queuing-strategy.dedicatedworker.html",
      {}
     ]
    ],
    "streams/byte-length-queuing-strategy.html": [
     [
@@ -165525,17 +165548,17 @@
    "2eadcace244bb97aae5aee14657bf07f19df22aa",
    "testharness"
   ],
   "fetch/api/headers/headers-casing.html": [
    "c83fdd0119c17b30051ab437e66934dc22c8c420",
    "testharness"
   ],
   "fetch/api/headers/headers-combine.html": [
-   "9aad3af9b8979037a1a5f2be709457c59ed480c6",
+   "0edea47e706ac6fae40ae78665f6a839f04c2b22",
    "testharness"
   ],
   "fetch/api/headers/headers-errors.html": [
    "71dc2b17b89ab6ec65f775cb6d1cab02808c0bc6",
    "testharness"
   ],
   "fetch/api/headers/headers-idl.html": [
    "13804a9850b05753cf2ad85c926fd071f9fc1888",
@@ -166989,17 +167012,17 @@
    "ecfd307fb7f2aab2ab72384963e39f2878c7d189",
    "testharness"
   ],
   "html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-basic.html": [
    "07869d8baa29c9e0baf7e987f8e40f4f3c7df2e7",
    "testharness"
   ],
   "html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html": [
-   "18bd98353460da1c9d376e9453d9a96b7c273291",
+   "0f8425ba4fcdd12e357ec975c6439c89c72c1c3e",
    "testharness"
   ],
   "html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-samedoc.html": [
    "58663b2992725c767aaabaf685f49db895e798b6",
    "testharness"
   ],
   "html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-navigation-cross-origin.html": [
    "9a2631564f17e1e156fc6fd4fff0a595e490ba07",
@@ -179653,17 +179676,17 @@
    "997cee37dcd202498196e63e0f66035979121b7f",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/nomodule-reflect.html": [
    "ac2b3c16e9e9263cd4c14de205b63709c14ec2e3",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/nomodule-set-on-async-classic-script.html": [
-   "5b4a532b21caa6235bed10a28878c65523a816aa",
+   "6ef870db74ba0dbb6171c74437a78cef4a6f6062",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/nomodule-set-on-external-module-script.html": [
    "f43755d9dffe2983a377f2c00b855f106776b617",
    "testharness"
   ],
   "html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-classic-scripts.html": [
    "2b6654342c5a2e2d85df2bc8ec805b5fa7ed1550",
@@ -201921,17 +201944,17 @@
    "72da19e038b6ab32ea04a0e91d117bc67d25a301",
    "testharness"
   ],
   "service-workers/service-worker/claim-using-registration.https.html": [
    "fb56cc3ae802669bb7898e76ac55e75ba6ac1441",
    "testharness"
   ],
   "service-workers/service-worker/client-navigate.https.html": [
-   "e0c704605ec008a29662e1804d512284899ddd6f",
+   "c0403a4538bf063745a59f5848e30b13d1b3afc1",
    "testharness"
   ],
   "service-workers/service-worker/clients-get-cross-origin.https.html": [
    "21ed1eab21bb6f0b342895c8185ecb92afe93b79",
    "testharness"
   ],
   "service-workers/service-worker/clients-get.https.html": [
    "32ccf7734a7d0d40205c4fd30b393b175e6507bd",
@@ -202252,20 +202275,16 @@
   "service-workers/service-worker/registration-iframe.https.html": [
    "6d11b9ecf339e6e476fe594d5cb4e0873b0845d1",
    "testharness"
   ],
   "service-workers/service-worker/registration-service-worker-attributes.https.html": [
    "7c49ce1c6170733033add6253a3f4a7e0483452e",
    "testharness"
   ],
-  "service-workers/service-worker/registration-useCache.https.html": [
-   "73d662eafa93ca3dee4a4e5d34623cf069c2b8f8",
-   "testharness"
-  ],
   "service-workers/service-worker/registration.https.html": [
    "0a06c368a14c008c385c9df3cde35f090d96d58b",
    "testharness"
   ],
   "service-workers/service-worker/rejections.https.html": [
    "785a18ac3c8001034f583a8e97195aa47093bd0d",
    "testharness"
   ],
@@ -202305,17 +202324,17 @@
    "e779a28c42928ff10219073171c1216c6623b4d4",
    "support"
   ],
   "service-workers/service-worker/resources/client-navigate-frame.html": [
    "ecad40948e5d00ca737ea91b702ebbecc268e53b",
    "support"
   ],
   "service-workers/service-worker/resources/client-navigate-worker.js": [
-   "6b277e1dcde40babec32046f9e637a47830bf29b",
+   "876f60f8c7112cc0a7a2df2fe2b298a3c9504214",
    "support"
   ],
   "service-workers/service-worker/resources/client-navigated-frame.html": [
    "efb9dd2b3468305396a3767fc780d07525bd8e61",
    "support"
   ],
   "service-workers/service-worker/resources/clients-get-frame.html": [
    "2a34fdfb584674ef7b534fb91b313fc630ac0ffa",
@@ -203624,20 +203643,36 @@
   "storage/interfaces.worker.js": [
    "da11cf56486fe08214f91d181b3a19775f6aa59c",
    "testharness"
   ],
   "storage/opaque-origin.https.html": [
    "6ce5a9b14d80030f0adfa1808857294e8c923cb2",
    "testharness"
   ],
+  "storage/persist-permission-manual.https.html": [
+   "6b7c0b9d5c8cee3922f6797dace85b441e5ea45c",
+   "manual"
+  ],
+  "storage/persisted-worker.https.html": [
+   "87d7bf4c615b07b3fa701239fc1823826a054e80",
+   "testharness"
+  ],
+  "storage/persisted.https.html": [
+   "98be04abdc48c76b30f90af007f214f9759083dd",
+   "testharness"
+  ],
   "storage/storage-estimate-indexeddb.js": [
    "660d3d068314c34d215df19c0b849ec711f57854",
    "support"
   ],
+  "storage/storage-persisted.js": [
+   "dbf6e5bed3dec6ca59926c439ec9d6aca89d78b9",
+   "support"
+  ],
   "streams/OWNERS": [
    "5ed27d1c21178be00e972816933945e094a0e170",
    "support"
   ],
   "streams/README.md": [
    "301e457a14a26ed154a55d2811e32d5ceb4b004c",
    "support"
   ],
@@ -207581,21 +207616,21 @@
    "eee8ff07b3ec5e83e5f18305f5bc00eb72468443",
    "testharness"
   ],
   "web-animations/animation-model/animation-types/interpolation-per-property.html": [
    "55100f7d505bc8cbc966ced0d1337ed78534a553",
    "testharness"
   ],
   "web-animations/animation-model/animation-types/property-list.js": [
-   "282a8b243a82d44de3e477ee14df585d1276fd82",
+   "4fa529fe470abb7a6fb4582d0174f1696b141f87",
    "support"
   ],
   "web-animations/animation-model/animation-types/property-types.js": [
-   "ebccba780b163032d4aba54cdbbf1b892464bcfa",
+   "7b79c51f6dc5c33aae127406509770159815c290",
    "support"
   ],
   "web-animations/animation-model/animation-types/spacing-keyframes-filters.html": [
    "bd771a8a18245560221d92ea3495f81918c09848",
    "testharness"
   ],
   "web-animations/animation-model/animation-types/spacing-keyframes-shapes.html": [
    "03c3ab6815cfa96c07d5f95b6059fb276c50a25f",
deleted file mode 100644
--- a/testing/web-platform/meta/storage/interfaces.https.html.ini
+++ /dev/null
@@ -1,15 +0,0 @@
-[interfaces.https.html]
-  type: testharness
-  prefs: [dom.storageManager.enabled:true]
-  [StorageManager interface: operation persisted()]
-    expected: FAIL
-
-  [StorageManager interface: operation persist()]
-    expected: FAIL
-
-  [StorageManager interface: navigator.storage must inherit property "persisted" with the proper type (0)]
-    expected: FAIL
-
-  [StorageManager interface: navigator.storage must inherit property "persist" with the proper type (1)]
-    expected: FAIL
-
--- a/testing/web-platform/meta/storage/opaque-origin.https.html.ini
+++ b/testing/web-platform/meta/storage/opaque-origin.https.html.ini
@@ -1,18 +1,6 @@
 [opaque-origin.https.html]
   type: testharness
-  prefs: [dom.storageManager.enabled:true]
-  [navigator.storage.persist() in non-sandboxed iframe should not reject]
-    expected: FAIL
-
-  [navigator.storage.persist() in sandboxed iframe should reject with TypeError]
-    expected: FAIL
+  prefs: [dom.storageManager.enabled:true,
+          dom.storageManager.prompt.testing:true,
+          dom.storageManager.prompt.testing.allow:true]
 
-  [navigator.storage.persisted() in non-sandboxed iframe should not reject]
-    expected: FAIL
-
-  [navigator.storage.persisted() in sandboxed iframe should reject with TypeError]
-    expected: FAIL
-
-  [navigator.storage.estimate() in sandboxed iframe should reject with TypeError]
-    expected: FAIL
-
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/storage/persist-permission-manual.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>StorageManager: permission state is granted</title>
+    <p>Clear all persistent storage permissions before running this test.</p>
+    <p>Test passes if there is a permission prompt and click allow store persistent data</p>
+    <meta name="help" href="https://storage.spec.whatwg.org/#dom-storagemanager-persist">
+    <meta name="author" title="Mozilla" href="https://www.mozilla.org">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <script>
+    promise_test(function(t) {
+      return navigator.storage.persist()
+      .then(function(result) {
+        assert_true(result);
+        return navigator.storage.persisted();
+      })
+      .then(function(result) {
+        assert_true(result);
+      })
+    }, 'Expect permission state is granted after calling persist()');
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/storage/persisted-worker.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>StorageManager: persisted() from worker</title>
+    <meta name="help" href="https://storage.spec.whatwg.org/#dom-storagemanager-persisted">
+    <meta name="author" title="Mozilla" href="https://www.mozilla.org">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <script>
+      fetch_tests_from_worker(new Worker("storage-persisted.js"));
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/storage/persisted.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>StorageManager: persisted()</title>
+    <meta name="help" href="https://storage.spec.whatwg.org/#dom-storagemanager-persisted">
+    <meta name="author" title="Mozilla" href="https://www.mozilla.org">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+  <body>
+    <script src="storage-persisted.js"></script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/storage/storage-persisted.js
@@ -0,0 +1,18 @@
+if (this.document === undefined) {
+  importScripts("/resources/testharness.js");
+}
+
+test(function(t) {
+  assert_true('persisted' in navigator.storage);
+  assert_equals(typeof navigator.storage.persisted, 'function');
+  assert_true(navigator.storage.persisted() instanceof Promise);
+}, 'persisted() method exists and returns a Promise');
+
+promise_test(function(t) {
+  return navigator.storage.persisted().then(function(result) {
+    assert_equals(typeof result, 'boolean');
+    assert_equals(result, false);
+  });
+}, 'persisted() returns a promise and resolves as boolean with false');
+
+done();
--- a/testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js
+++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-list.js
@@ -1125,18 +1125,17 @@ var gCSSProperties = {
     ]
   },
   'perspective': {
     // https://drafts.csswg.org/css-transforms-1/#propdef-perspective
     types: [ 'length' ]
   },
   'perspective-origin': {
     // https://drafts.csswg.org/css-transforms-1/#propdef-perspective-origin
-    types: [
-    ]
+    types: [ 'position' ]
   },
   'pointer-events': {
     // https://svgwg.org/svg2-draft/interact.html#PointerEventsProperty
     types: [
       { type: 'discrete', options: [ [ 'fill', 'none' ] ] }
     ]
   },
   'position': {
@@ -1515,8 +1514,18 @@ function propertyToIDL(property) {
   if (property === 'float') {
     return 'cssFloat';
   }
   return property.replace(/-[a-z]/gi,
                           function (str) {
                             return str.substr(1).toUpperCase(); });
 }
 
+function calcFromPercentage(idlName, percentageValue) {
+  var examElem = document.createElement('div');
+  document.body.appendChild(examElem);
+  examElem.style[idlName] = percentageValue;
+
+  var calcValue = getComputedStyle(examElem)[idlName];
+  document.body.removeChild(examElem);
+
+  return calcValue;
+}
--- a/testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js
+++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js
@@ -950,23 +950,93 @@ const boxShadowListType = {
                        { duration: 1000, composite: 'add' });
       testAnimationSamples(animation, idlName,
         [ { time: 0, expected: 'rgb(0, 0, 0) 0px 0px 0px 0px, ' +
                                'rgb(120, 120, 120) 10px 10px 10px 0px' }]);
     }, property + ': shadow');
   },
 };
 
+const positionType = {
+  testInterpolation: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['10px 10px', '50px 50px'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '10px 10px' },
+                            { time: 500,  expected: '30px 30px' },
+                            { time: 1000, expected: '50px 50px' }]);
+    }, property + ' supports animating as a position');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['1rem 1rem', '5rem 5rem'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(animation, idlName,
+                           [{ time: 0,    expected: '10px 10px' },
+                            { time: 500,  expected: '30px 30px' },
+                            { time: 1000, expected: '50px 50px' }]);
+    }, property + ' supports animating as a position of rem');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      var animation = target.animate({ [idlName]: ['10% 10%', '50% 50%'] },
+                                     { duration: 1000, fill: 'both' });
+      testAnimationSamples(
+        animation, idlName,
+        [{ time: 0,    expected: calcFromPercentage(idlName, '10% 10%') },
+         { time: 500,  expected: calcFromPercentage(idlName, '30% 30%') },
+         { time: 1000, expected: calcFromPercentage(idlName, '50% 50%') }]);
+    }, property + ' supports animating as a position of percent');
+  },
+
+  testAddition: function(property, setup) {
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '10px 10px';
+      var animation = target.animate({ [idlName]: ['10px 10px', '50px 50px'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName, [{ time: 0, expected: '20px 20px' }]);
+    }, property + ': position');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '1rem 1rem';
+      var animation = target.animate({ [idlName]: ['1rem 1rem', '5rem 5rem'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(animation, idlName, [{ time: 0, expected: '20px 20px' }]);
+    }, property + ': position of rem');
+
+    test(function(t) {
+      var idlName = propertyToIDL(property);
+      var target = createTestElement(t, setup);
+      target.style[idlName] = '60% 60%';
+      var animation = target.animate({ [idlName]: ['70% 70%', '100% 100%'] },
+                                     { duration: 1000, composite: 'add' });
+      testAnimationSamples(
+        animation, idlName,
+        [{ time: 0, expected: calcFromPercentage(idlName, '130% 130%') }]);
+    }, property + ': position of percentage');
+  },
+};
+
 const types = {
   color: colorType,
   discrete: discreteType,
   filterList: filterListType,
   integer: integerType,
   length: lengthType,
   percentage: percentageType,
   lengthPercentageOrCalc: lengthPercentageOrCalcType,
   positiveNumber: positiveNumberType,
   transformList: transformListType,
   visibility: visibilityType,
   boxShadowList: boxShadowListType,
   textShadowList: textShadowListType,
+  position: positionType,
 };
 
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -662,18 +662,16 @@ this.ExtensionData = class {
       let results = yield Promise.all(promises);
 
       this.localeData.selectedLocale = locale;
       return results[0];
     }.bind(this));
   }
 };
 
-let _browserUpdated = false;
-
 const PROXIED_EVENTS = new Set(["test-harness-message", "add-permissions", "remove-permissions"]);
 
 // We create one instance of this class per extension. |addonData|
 // comes directly from bootstrap.js when initializing.
 this.Extension = class extends ExtensionData {
   constructor(addonData, startupReason) {
     super(addonData.resourceURI);
 
@@ -740,24 +738,16 @@ this.Extension = class extends Extension
 
       for (let origin of permissions.origins) {
         this.whiteListedHosts.removeOne(origin);
       }
     });
     /* eslint-enable mozilla/balanced-listeners */
   }
 
-  static set browserUpdated(updated) {
-    _browserUpdated = updated;
-  }
-
-  static get browserUpdated() {
-    return _browserUpdated;
-  }
-
   static generateXPI(data) {
     return ExtensionTestCommon.generateXPI(data);
   }
 
   static generateZipFile(files, baseName = "generated-extension.xpi") {
     return ExtensionTestCommon.generateZipFile(files, baseName);
   }
 
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -23,19 +23,16 @@ XPCOMUtils.defineLazyGetter(this, "conso
 
 XPCOMUtils.defineLazyGetter(this, "UUIDMap", () => {
   let {UUIDMap} = Cu.import("resource://gre/modules/Extension.jsm", {});
   return UUIDMap;
 });
 
 const {appinfo} = Services;
 const isParentProcess = appinfo.processType === appinfo.PROCESS_TYPE_DEFAULT;
-if (isParentProcess) {
-  Services.ppmm.loadProcessScript("chrome://extensions/content/extension-process-script.js", true);
-}
 
 var ExtensionManagement;
 
 /*
  * This file should be kept short and simple since it's loaded even
  * when no extensions are running.
  */
 
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -1,12 +1,14 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
+                                  "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 
 this.runtime = class extends ExtensionAPI {
@@ -29,17 +31,17 @@ this.runtime = class extends ExtensionAP
             extension.off("startup", listener);
           };
         }).api(),
 
         onInstalled: new SingletonEventManager(context, "runtime.onInstalled", fire => {
           let listener = () => {
             switch (extension.startupReason) {
               case "APP_STARTUP":
-                if (Extension.browserUpdated) {
+                if (AddonManagerPrivate.browserUpdated) {
                   fire.sync({reason: "browser_update"});
                 }
                 break;
               case "ADDON_INSTALL":
                 fire.sync({reason: "install"});
                 break;
               case "ADDON_UPGRADE":
                 fire.sync({reason: "update", previousVersion: extension.addonData.oldVersion});
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -33,17 +33,20 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPageChild",
                                   "resource://gre/modules/ExtensionPageChild.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
                                   "resource://gre/modules/ExtensionUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "console", () => ExtensionUtils.getConsole());
 XPCOMUtils.defineLazyGetter(this, "getInnerWindowID", () => ExtensionUtils.getInnerWindowID);
 
-const isContentProcess = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+// We need to avoid touching Services.appinfo here in order to prevent
+// the wrong version from being cached during xpcshell test startup.
+const appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+const isContentProcess = appinfo.processType == appinfo.PROCESS_TYPE_CONTENT;
 
 
 class ScriptMatcher {
   constructor(extension, options) {
     this.extension = extension;
     this.options = options;
 
     this._script = null;
--- a/toolkit/components/telemetry/TelemetryHistogram.cpp
+++ b/toolkit/components/telemetry/TelemetryHistogram.cpp
@@ -494,17 +494,17 @@ internal_GetHistogramByEnumId(mozilla::T
     return NS_OK;
   }
 
   const HistogramInfo &p = gHistograms[id];
   if (p.keyed) {
     return NS_ERROR_FAILURE;
   }
 
-  nsCString histogramName;
+  nsAutoCString histogramName;
   histogramName.Append(p.id());
   if (const char* suffix = SuffixForProcessType(aProcessType)) {
     histogramName.AppendASCII(suffix);
   }
 
   nsresult rv = internal_HistogramGet(histogramName.get(), p.expiration(),
                                       p.histogramType, p.min, p.max,
                                       p.bucketCount, true, &h);
@@ -1398,17 +1398,17 @@ internal_AccumulateChildKeyed(GeckoProce
   const char* suffix = SuffixForProcessType(aProcessType);
   if (!suffix) {
     MOZ_ASSERT_UNREACHABLE("suffix should not be null");
     return;
   }
 
   const HistogramInfo& th = gHistograms[aId];
 
-  nsCString id;
+  nsAutoCString id;
   id.Append(th.id());
   id.AppendASCII(suffix);
 
   KeyedHistogram* keyed = internal_GetKeyedHistogramById(id);
   MOZ_ASSERT(keyed);
   keyed->Add(aKey, aSample);
 }
 
--- a/toolkit/library/gtest/rust/Cargo.lock
+++ b/toolkit/library/gtest/rust/Cargo.lock
@@ -662,16 +662,17 @@ dependencies = [
 name = "selectors"
 version = "0.18.0"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "serde"
 version = "0.9.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
--- a/toolkit/library/rust/Cargo.lock
+++ b/toolkit/library/rust/Cargo.lock
@@ -649,16 +649,17 @@ dependencies = [
 name = "selectors"
 version = "0.18.0"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "serde"
 version = "0.9.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -100,16 +100,18 @@ XPCOMUtils.defineLazyGetter(this, "CertU
   let certUtils = {};
   Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
   return certUtils;
 });
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "WEBEXT_PERMISSION_PROMPTS",
                                       PREF_WEBEXT_PERM_PROMPTS, false);
 
+Services.ppmm.loadProcessScript("chrome://extensions/content/extension-process-script.js", true);
+
 const INTEGER = /^[1-9]\d*$/;
 
 this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ];
 
 const CATEGORY_PROVIDER_MODULE = "addon-provider-module";
 
 // A list of providers to load by default
 const DEFAULT_PROVIDERS = [
@@ -638,16 +640,17 @@ var gCheckUpdateSecurity = gCheckUpdateS
 var gUpdateEnabled = true;
 var gAutoUpdateDefault = true;
 var gHotfixID = "";
 var gWebExtensionsMinPlatformVersion = "";
 var gShutdownBarrier = null;
 var gRepoShutdownState = "";
 var gShutdownInProgress = false;
 var gPluginPageListener = null;
+var gBrowserUpdated = null;
 
 /**
  * This is the real manager, kept here rather than in AddonManager to keep its
  * contents hidden from API users.
  */
 var AddonManagerInternal = {
   managerListeners: [],
   installListeners: [],
@@ -810,17 +813,17 @@ var AddonManagerInternal = {
       let appChanged = undefined;
 
       let oldAppVersion = null;
       try {
         oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION);
         appChanged = Services.appinfo.version != oldAppVersion;
       } catch (e) { }
 
-      Extension.browserUpdated = appChanged;
+      gBrowserUpdated = appChanged;
 
       let oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION, "");
 
       if (appChanged !== false) {
         logger.debug("Application has been upgraded");
         Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION,
                                    Services.appinfo.version);
         Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION,
@@ -3093,16 +3096,20 @@ var AddonManagerInternal = {
  * AddonManagerInternal for documentation however note that these methods are
  * subject to change at any time.
  */
 this.AddonManagerPrivate = {
   startup() {
     AddonManagerInternal.startup();
   },
 
+  get browserUpdated() {
+    return gBrowserUpdated;
+  },
+
   registerProvider(aProvider, aTypes) {
     AddonManagerInternal.registerProvider(aProvider, aTypes);
   },
 
   unregisterProvider(aProvider) {
     AddonManagerInternal.unregisterProvider(aProvider);
   },
 
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -88,17 +88,16 @@ XPCOMUtils.defineLazyGetter(this, "CertU
   return certUtils;
 });
 
 XPCOMUtils.defineLazyGetter(this, "IconDetails", () => {
   const {ExtensionUtils} = Cu.import("resource://gre/modules/ExtensionUtils.jsm", {});
   return ExtensionUtils.IconDetails;
 });
 
-
 Cu.importGlobalProperties(["URL"]);
 
 const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
                                        "initWithPath");
 
 const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
 const PREF_INSTALL_CACHE              = "extensions.installCache";
 const PREF_XPI_STATE                  = "extensions.xpiState";
--- a/tools/rewriting/ThirdPartyPaths.txt
+++ b/tools/rewriting/ThirdPartyPaths.txt
@@ -58,13 +58,33 @@ modules/freetype2/
 modules/libbz2/
 modules/libmar/
 modules/zlib/
 netwerk/sctp/src/
 netwerk/srtp/src/
 nsprpub/
 other-licenses/
 parser/expat/
+python/altgraph/
+python/blessings/
+python/configobj/
+python/futures/
+python/jsmin/
+python/mock-*/
+python/psutil/
+python/py/
+python/pyasn1/
+python/pyasn1-modules/
+python/PyECC/
+python/pytest/
+python/pyyaml/
+python/pytoml/
+python/redo/
+python/requests/
+python/rsa/
+python/which/
+security/nss/
 security/sandbox/chromium/
 testing/gtest/gmock/
 testing/gtest/gtest/
+testing/talos/talos/tests/dromaeo/
 toolkit/components/protobuf/
 toolkit/crashreporter/google-breakpad/