merge autoland to mozilla-central a=merge
authorIris Hsiao <ihsiao@mozilla.com>
Fri, 14 Apr 2017 17:09:58 +0800
changeset 401216 cda24082bff8864a6e53726feeae33cae9e17309
parent 401165 8f806306fb83a79183401dff7e3fd97e67de36ba (current diff)
parent 401215 2a494e1f39e78f9bb25681a3b6616c6697e48685 (diff)
child 401217 29a732d9396128fcf834c68e6e130922f8951aec
child 401301 3ef72a802ecaba76bfa2e159650fce2ff6e887c2
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
cda24082bff8 / 55.0a1 / 20170414100243 / files
nightly linux64
cda24082bff8 / 55.0a1 / 20170414100243 / files
nightly mac
cda24082bff8 / 55.0a1 / 20170414030225 / files
nightly win32
cda24082bff8 / 55.0a1 / 20170414030225 / files
nightly win64
cda24082bff8 / 55.0a1 / 20170414030225 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central a=merge
browser/modules/HiddenFrame.jsm
dom/base/nsDocument.cpp
dom/base/nsDocument.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSValue.cpp
--- a/addon-sdk/source/lib/sdk/addon/window.js
+++ b/addon-sdk/source/lib/sdk/addon/window.js
@@ -7,17 +7,17 @@ module.metadata = {
   "stability": "experimental"
 };
 
 const { Ci, Cc, Cu } = require("chrome");
 const { when: unload } = require("../system/unload");
 const prefs = require("../preferences/service");
 
 if (!prefs.get("extensions.usehiddenwindow", false)) {
-  const {HiddenFrame} = require("resource:///modules/HiddenFrame.jsm", {});
+  const {HiddenFrame} = require("resource://gre/modules/HiddenFrame.jsm", {});
   let hiddenFrame = new HiddenFrame();
   exports.window = hiddenFrame.getWindow();
   exports.ready = hiddenFrame.get();
 
   // Still destroy frame on unload to claim memory back early.
   // NOTE: this doesn't seem to work and just doesn't get called. :-\
   unload(function() {
     hiddenFrame.destroy();
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -178,18 +178,18 @@ const gXPInstallObserver = {
     let brandBundle = document.getElementById("bundle_brand");
     let brandShortName = brandBundle.getString("brandShortName");
 
     messageString = PluralForm.get(installInfo.installs.length, messageString);
     messageString = messageString.replace("#1", brandShortName);
     messageString = messageString.replace("#2", installInfo.installs.length);
 
     let action = {
-      label: gNavigatorBundle.getString("addonInstall.acceptButton.label"),
-      accessKey: gNavigatorBundle.getString("addonInstall.acceptButton.accesskey"),
+      label: gNavigatorBundle.getString("addonInstall.acceptButton2.label"),
+      accessKey: gNavigatorBundle.getString("addonInstall.acceptButton2.accesskey"),
       callback: acceptInstallation,
     };
 
     let secondaryAction = {
       label: gNavigatorBundle.getString("addonInstall.cancelButton.label"),
       accessKey: gNavigatorBundle.getString("addonInstall.cancelButton.accesskey"),
       callback: () => {},
     };
@@ -333,18 +333,18 @@ const gXPInstallObserver = {
             break;
           case "removed":
             options.contentWindow = null;
             options.sourceURI = null;
             break;
         }
       };
       action = {
-        label: gNavigatorBundle.getString("addonInstall.acceptButton.label"),
-        accessKey: gNavigatorBundle.getString("addonInstall.acceptButton.accesskey"),
+        label: gNavigatorBundle.getString("addonInstall.acceptButton2.label"),
+        accessKey: gNavigatorBundle.getString("addonInstall.acceptButton2.accesskey"),
         callback: () => {},
       };
       let secondaryAction = {
         label: gNavigatorBundle.getString("addonInstall.cancelButton.label"),
         accessKey: gNavigatorBundle.getString("addonInstall.cancelButton.accesskey"),
         callback: () => {
           for (let install of installInfo.installs) {
             if (install.state != AddonManager.STATE_CANCELLED) {
--- a/browser/base/content/test/appUpdate/head.js
+++ b/browser/base/content/test/appUpdate/head.js
@@ -23,16 +23,19 @@ const LOG_FUNCTION = info;
 /* import-globals-from testConstants.js */
 Services.scriptloader.loadSubScript(DATA_URI_SPEC + "testConstants.js", this);
 /* import-globals-from ../../../../../toolkit/mozapps/update/tests/data/shared.js */
 Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this);
 
 var gURLData = URL_HOST + "/" + REL_PATH_DATA;
 const URL_MANUAL_UPDATE = gURLData + "downloadPage.html";
 
+const gEnv = Cc["@mozilla.org/process/environment;1"].
+             getService(Components.interfaces.nsIEnvironment);
+
 const NOTIFICATIONS = [
   "update-available",
   "update-manual",
   "update-restart"
 ];
 
 /**
  * Delay for a very short period. Useful for moving the code after this
@@ -95,21 +98,23 @@ function setUpdateTimerPrefs() {
  *         A list of test steps to perform, specifying expected doorhangers
  *         and additional validation/cleanup callbacks.
  * @return A promise which will resolve once all of the steps have been run
  *         and cleanup has been performed.
  */
 function runUpdateTest(updateParams, checkAttempts, steps) {
   return Task.spawn(function*() {
     registerCleanupFunction(() => {
+      gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "");
       gMenuButtonUpdateBadge.uninit();
       gMenuButtonUpdateBadge.init();
       cleanUpUpdates();
     });
 
+    gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1");
     setUpdateTimerPrefs();
     yield SpecialPowers.pushPrefEnv({
       set: [
         [PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS, 0],
         [PREF_APP_UPDATE_ENABLED, true],
         [PREF_APP_UPDATE_IDLETIME, 0],
         [PREF_APP_UPDATE_URL_MANUAL, URL_MANUAL_UPDATE],
         [PREF_APP_UPDATE_LOG, DEBUG_AUS_TEST],
@@ -150,21 +155,23 @@ function runUpdateTest(updateParams, che
  *         A list of test steps to perform, specifying expected doorhangers
  *         and additional validation/cleanup callbacks.
  * @return A promise which will resolve once all of the steps have been run
  *         and cleanup has been performed.
  */
 function runUpdateProcessingTest(updates, steps) {
   return Task.spawn(function*() {
     registerCleanupFunction(() => {
+      gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "");
       gMenuButtonUpdateBadge.reset();
       cleanUpUpdates();
     });
 
     setUpdateTimerPrefs();
+    gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1");
     SpecialPowers.pushPrefEnv({
       set: [
         [PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS, 0],
         [PREF_APP_UPDATE_ENABLED, true],
         [PREF_APP_UPDATE_IDLETIME, 0],
         [PREF_APP_UPDATE_URL_MANUAL, URL_MANUAL_UPDATE],
         [PREF_APP_UPDATE_LOG, DEBUG_AUS_TEST],
       ]});
--- a/browser/base/content/test/general/browser_bug431826.js
+++ b/browser/base/content/test/general/browser_bug431826.js
@@ -1,20 +1,16 @@
 function remote(task) {
   return ContentTask.spawn(gBrowser.selectedBrowser, null, task);
 }
 
 add_task(function* () {
   gBrowser.selectedTab = gBrowser.addTab();
 
-  let promise = remote(function() {
-    return ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true, event => {
-      return content.document.documentURI != "about:blank";
-    }).then(() => 0); // don't want to send the event to the chrome process
-  });
+  let promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
   gBrowser.loadURI("https://nocert.example.com/");
   yield promise;
 
   yield remote(() => {
     // Confirm that we are displaying the contributed error page, not the default
     let uri = content.document.documentURI;
     Assert.ok(uri.startsWith("about:certerror"), "Broken page should go to about:certerror, not about:neterror");
   });
@@ -25,19 +21,17 @@ add_task(function* () {
     Assert.ok(div, "Advanced content div should exist");
     Assert.equal(div.ownerGlobal.getComputedStyle(div).display,
       "none", "Advanced content should not be visible by default");
   });
 
   // Tweak the expert mode pref
   gPrefService.setBoolPref("browser.xul.error_pages.expert_bad_cert", true);
 
-  promise = remote(function() {
-    return ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true);
-  });
+  promise = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
   gBrowser.reload();
   yield promise;
 
   yield remote(() => {
     let div = content.document.getElementById("badCertAdvancedPanel");
     Assert.ok(div, "Advanced content div should exist");
     Assert.equal(div.ownerGlobal.getComputedStyle(div).display,
       "block", "Advanced content should be visible by default");
--- a/browser/base/content/test/static/browser_parsable_css.js
+++ b/browser/base/content/test/static/browser_parsable_css.js
@@ -252,17 +252,17 @@ add_task(function* checkAllTheCSS() {
   // This asynchronously produces a list of URLs (sadly, mostly sync on our
   // test infrastructure because it runs against jarfiles there, and
   // our zipreader APIs are all sync)
   let uris = yield generateURIsFromDirTree(appDir, [".css", ".manifest"]);
 
   // Create a clean iframe to load all the files into. This needs to live at a
   // chrome URI so that it's allowed to load and parse any styles.
   let testFile = getRootDirectory(gTestPath) + "dummy_page.html";
-  let HiddenFrame = Cu.import("resource:///modules/HiddenFrame.jsm", {}).HiddenFrame;
+  let HiddenFrame = Cu.import("resource://gre/modules/HiddenFrame.jsm", {}).HiddenFrame;
   let hiddenFrame = new HiddenFrame();
   let win = yield hiddenFrame.get();
   let iframe = win.document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
   win.document.documentElement.appendChild(iframe);
   let iframeLoaded = BrowserTestUtils.waitForEvent(iframe, "load", true);
   iframe.contentWindow.location = testFile;
   yield iframeLoaded;
   let doc = iframe.contentWindow.document;
--- a/browser/components/extensions/test/browser/browser_ext_webRequest.js
+++ b/browser/components/extensions/test/browser/browser_ext_webRequest.js
@@ -1,17 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 /* globals makeExtension */
 "use strict";
 
 Services.scriptloader.loadSubScript(new URL("head_webrequest.js", gTestPath).href,
                                     this);
 
-Cu.import("resource:///modules/HiddenFrame.jsm", this);
+Cu.import("resource://gre/modules/HiddenFrame.jsm", this);
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 function createHiddenBrowser(url) {
   let frame = new HiddenFrame();
   return new Promise(resolve =>
     frame.get().then(subframe => {
       let doc = subframe.document;
       let browser = doc.createElementNS(XUL_NS, "browser");
--- a/browser/components/uitour/test/browser_no_tabs.js
+++ b/browser/components/uitour/test/browser_no_tabs.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-var HiddenFrame = Cu.import("resource:///modules/HiddenFrame.jsm", {}).HiddenFrame;
+var HiddenFrame = Cu.import("resource://gre/modules/HiddenFrame.jsm", {}).HiddenFrame;
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 /**
  * Create a frame in the |hiddenDOMWindow| to host a |browser|, then load the URL in the
  * latter.
  *
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -156,18 +156,18 @@ addonPostInstall.okay.key=O
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # Also see https://bugzilla.mozilla.org/show_bug.cgi?id=570012 for mockups
 addonDownloadingAndVerifying=Downloading and verifying add-on…;Downloading and verifying #1 add-ons…
 addonDownloadVerifying=Verifying
 
 addonInstall.unsigned=(Unverified)
 addonInstall.cancelButton.label=Cancel
 addonInstall.cancelButton.accesskey=C
-addonInstall.acceptButton.label=Install
-addonInstall.acceptButton.accesskey=I
+addonInstall.acceptButton2.label=Add
+addonInstall.acceptButton2.accesskey=A
 
 # LOCALIZATION NOTE (addonConfirmInstallMessage,addonConfirmInstallUnsigned):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is brandShortName
 # #2 is the number of add-ons being installed
 addonConfirmInstall.message=This site would like to install an add-on in #1:;This site would like to install #2 add-ons in #1:
 addonConfirmInstallUnsigned.message=Caution: This site would like to install an unverified add-on in #1. Proceed at your own risk.;Caution: This site would like to install #2 unverified add-ons in #1. Proceed at your own risk.
--- a/browser/modules/SelfSupportBackend.jsm
+++ b/browser/modules/SelfSupportBackend.jsm
@@ -12,17 +12,17 @@ const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "HiddenFrame",
-  "resource:///modules/HiddenFrame.jsm");
+  "resource://gre/modules/HiddenFrame.jsm");
 
 // Enables or disables the Self Support.
 const PREF_ENABLED = "browser.selfsupport.enabled";
 // Url to open in the Self Support browser, in the urlFormatter service format.
 const PREF_URL = "browser.selfsupport.url";
 // Unified Telemetry status.
 const PREF_TELEMETRY_UNIFIED = "toolkit.telemetry.unified";
 // UITour status.
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -25,17 +25,16 @@ EXTRA_JS_MODULES += [
     'ContentWebRTC.jsm',
     'DirectoryLinksProvider.jsm',
     'E10SUtils.jsm',
     'ExtensionsUI.jsm',
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
     'FullZoomUI.jsm',
-    'HiddenFrame.jsm',
     'LaterRun.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'PermissionUI.jsm',
     'PluginContent.jsm',
     'ProcessHangMonitor.jsm',
     'ReaderParent.jsm',
     'RecentWindow.jsm',
--- a/build/virtualenv_packages.txt
+++ b/build/virtualenv_packages.txt
@@ -33,8 +33,11 @@ pyasn1_modules.pth:python/pyasn1-modules
 redo.pth:python/redo
 requests.pth:python/requests
 rsa.pth:python/rsa
 futures.pth:python/futures
 ecc.pth:python/PyECC
 xpcshell.pth:testing/xpcshell
 pyyaml.pth:python/pyyaml/lib
 pytoml.pth:python/pytoml
+pylru.pth:python/pylru
+taskcluster.pth:taskcluster
+dlmanager.pth:python/dlmanager
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -402,16 +402,20 @@ netmonitor.toolbar.status3=Status
 # LOCALIZATION NOTE (netmonitor.toolbar.method): This is the label displayed
 # in the network table toolbar, above the "method" column.
 netmonitor.toolbar.method=Method
 
 # LOCALIZATION NOTE (netmonitor.toolbar.file): This is the label displayed
 # in the network table toolbar, above the "file" column.
 netmonitor.toolbar.file=File
 
+# LOCALIZATION NOTE (netmonitor.toolbar.protocol): This is the label displayed
+# in the network table toolbar, above the "protocol" column.
+netmonitor.toolbar.protocol=Protocol
+
 # LOCALIZATION NOTE (netmonitor.toolbar.domain): This is the label displayed
 # in the network table toolbar, above the "domain" column.
 netmonitor.toolbar.domain=Domain
 
 # LOCALIZATION NOTE (netmonitor.toolbar.remoteip): This is the label displayed
 # in the network table toolbar, above the "remoteip" column.
 netmonitor.toolbar.remoteip=Remote IP
 
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -359,16 +359,20 @@ body,
 .requests-list-security-and-domain {
   width: 14vw;
 }
 
 .requests-list-remoteip {
   width: 8vw;
 }
 
+.requests-list-protocol {
+  width: 7vw;
+}
+
 .requests-security-state-icon {
   flex: none;
   width: 16px;
   height: 16px;
   margin-inline-end: 4px;
 }
 
 .request-list-item.selected .requests-security-state-icon {
--- a/devtools/client/netmonitor/src/components/request-list-item.js
+++ b/devtools/client/netmonitor/src/components/request-list-item.js
@@ -129,16 +129,17 @@ const RequestListItem = createClass({
         "data-id": item.id,
         tabIndex: 0,
         onContextMenu,
         onMouseDown,
       },
         columns.get("status") && StatusColumn({ item }),
         columns.get("method") && MethodColumn({ item }),
         columns.get("file") && FileColumn({ item }),
+        columns.get("protocol") && ProtocolColumn({ item }),
         columns.get("domain") && DomainColumn({ item, onSecurityIconClick }),
         columns.get("remoteip") && RemoteIPColumn({ item }),
         columns.get("cause") && CauseColumn({ item, onCauseBadgeClick }),
         columns.get("type") && TypeColumn({ item }),
         columns.get("transferred") && TransferredSizeColumn({ item }),
         columns.get("contentSize") && ContentSizeColumn({ item }),
         columns.get("waterfall") && WaterfallColumn({ item, firstRequestStartedMillis }),
       )
@@ -252,16 +253,37 @@ const FileColumn = createFactory(createC
         },
           urlDetails.baseNameWithQuery,
         ),
       )
     );
   }
 }));
 
+const ProtocolColumn = createFactory(createClass({
+  displayName: "Protocol",
+
+  propTypes: {
+    item: PropTypes.object.isRequired,
+  },
+
+  shouldComponentUpdate(nextProps) {
+    return this.props.item.httpVersion !== nextProps.item.httpVersion;
+  },
+
+  render() {
+    const { httpVersion } = this.props.item;
+    return (
+      div({ className: "requests-list-subitem requests-list-protocol" },
+        span({ className: "subitem-label", title: httpVersion }, httpVersion),
+      )
+    );
+  }
+}));
+
 const UPDATED_DOMAIN_PROPS = [
   "urlDetails",
   "remoteAddress",
   "securityState",
 ];
 
 const DomainColumn = createFactory(createClass({
   displayName: "DomainColumn",
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -122,16 +122,20 @@ const HEADERS = [
     canFilter: true,
   },
   {
     name: "file",
     boxName: "icon-and-file",
     canFilter: false,
   },
   {
+    name: "protocol",
+    canFilter: true,
+  },
+  {
     name: "domain",
     boxName: "security-and-domain",
     canFilter: true,
   },
   {
     name: "remoteip",
     canFilter: true,
     filterKey: "remote-ip",
--- a/devtools/client/netmonitor/src/reducers/ui.js
+++ b/devtools/client/netmonitor/src/reducers/ui.js
@@ -17,16 +17,17 @@ const {
   TOGGLE_COLUMN,
   WATERFALL_RESIZE,
 } = require("../constants");
 
 const Columns = I.Record({
   status: true,
   method: true,
   file: true,
+  protocol: false,
   domain: true,
   remoteip: false,
   cause: true,
   type: true,
   transferred: true,
   contentSize: true,
   waterfall: true,
 });
--- a/devtools/client/netmonitor/src/utils/filter-text-utils.js
+++ b/devtools/client/netmonitor/src/utils/filter-text-utils.js
@@ -117,16 +117,21 @@ function isFlagFilterMatch(item, { type,
   let match = true;
   switch (type) {
     case "status-code":
       match = item.status === value;
       break;
     case "method":
       match = item.method.toLowerCase() === value;
       break;
+    case "protocol":
+      let protocol = item.httpVersion;
+      match = typeof protocol === "string" ?
+                protocol.toLowerCase().includes(value) : false;
+      break;
     case "domain":
       match = item.urlDetails.host.toLowerCase().includes(value);
       break;
     case "remote-ip":
       match = `${item.remoteAddress}:${item.remotePort}`.toLowerCase().includes(value);
       break;
     case "cause":
       let causeType = item.cause.type;
--- a/devtools/client/netmonitor/src/utils/sort-predicates.js
+++ b/devtools/client/netmonitor/src/utils/sort-predicates.js
@@ -46,16 +46,21 @@ function method(first, second) {
 
 function file(first, second) {
   const firstUrl = first.urlDetails.baseNameWithQuery.toLowerCase();
   const secondUrl = second.urlDetails.baseNameWithQuery.toLowerCase();
   const result = compareValues(firstUrl, secondUrl);
   return result || waterfall(first, second);
 }
 
+function protocol(first, second) {
+  const result = compareValues(first.httpVersion, second.httpVersion);
+  return result || waterfall(first, second);
+}
+
 function domain(first, second) {
   const firstDomain = first.urlDetails.host.toLowerCase();
   const secondDomain = second.urlDetails.host.toLowerCase();
   const result = compareValues(firstDomain, secondDomain);
   return result || waterfall(first, second);
 }
 
 function remoteip(first, second) {
@@ -88,16 +93,17 @@ function contentSize(first, second) {
   const result = compareValues(first.contentSize, second.contentSize);
   return result || waterfall(first, second);
 }
 
 exports.Sorters = {
   status,
   method,
   file,
+  protocol,
   domain,
   remoteip,
   cause,
   type,
   transferred,
   contentSize,
   waterfall,
 };
--- a/devtools/client/netmonitor/test/browser_net_throttle.js
+++ b/devtools/client/netmonitor/test/browser_net_throttle.js
@@ -37,21 +37,21 @@ function* throttleTest(actuallyThrottle)
       downloadBPSMax: size,
       uploadBPSMean: 10000,
       uploadBPSMax: 10000,
     },
   };
   let client = NetMonitorController.webConsoleClient;
 
   info("sending throttle request");
-  let deferred = promise.defer();
-  client.setPreferences(request, response => {
-    deferred.resolve(response);
+  yield new Promise((resolve) => {
+    client.setPreferences(request, response => {
+      resolve(response);
+    });
   });
-  yield deferred.promise;
 
   let eventPromise = monitor.panelWin.once(EVENTS.RECEIVED_EVENT_TIMINGS);
   yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED);
   yield eventPromise;
 
   let requestItem = getSortedRequests(gStore.getState()).get(0);
   const reportedOneSecond = requestItem.eventTimings.timings.receive > 1000;
   if (actuallyThrottle) {
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -90,29 +90,29 @@ registerCleanupFunction(() => {
   info("finish() was called, cleaning up...");
 
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setCharPref("devtools.netmonitor.filters", gDefaultFilters);
   Services.prefs.clearUserPref("devtools.cache.disabled");
 });
 
 function waitForNavigation(target) {
-  let deferred = promise.defer();
-  target.once("will-navigate", () => {
-    target.once("navigate", () => {
-      deferred.resolve();
+  return new Promise((resolve) => {
+    target.once("will-navigate", () => {
+      target.once("navigate", () => {
+        resolve();
+      });
     });
   });
-  return deferred.promise;
 }
 
 function reconfigureTab(target, options) {
-  let deferred = promise.defer();
-  target.activeTab.reconfigure(options, deferred.resolve);
-  return deferred.promise;
+  return new Promise((resolve) => {
+    target.activeTab.reconfigure(options, resolve);
+  });
 }
 
 function toggleCache(target, disabled) {
   let options = { cacheDisabled: disabled, performReload: true };
   let navigationFinished = waitForNavigation(target);
 
   // Disable the cache for any toolbox that it is opened from this point on.
   Services.prefs.setBoolPref("devtools.cache.disabled", disabled);
@@ -174,92 +174,92 @@ function teardown(monitor) {
 
     let onDestroyed = monitor.once("destroyed");
     yield removeTab(tab);
     yield onDestroyed;
   });
 }
 
 function waitForNetworkEvents(monitor, getRequests, postRequests = 0) {
-  let deferred = promise.defer();
-  let panel = monitor.panelWin;
-  let { windowRequire } = panel;
-  let { NetMonitorController } =
-    windowRequire("devtools/client/netmonitor/src/netmonitor-controller");
-  let progress = {};
-  let genericEvents = 0;
-  let postEvents = 0;
-  let awaitedEventsToListeners = [
-    ["UPDATING_REQUEST_HEADERS", onGenericEvent],
-    ["RECEIVED_REQUEST_HEADERS", onGenericEvent],
-    ["UPDATING_REQUEST_COOKIES", onGenericEvent],
-    ["RECEIVED_REQUEST_COOKIES", onGenericEvent],
-    ["UPDATING_REQUEST_POST_DATA", onPostEvent],
-    ["RECEIVED_REQUEST_POST_DATA", onPostEvent],
-    ["UPDATING_RESPONSE_HEADERS", onGenericEvent],
-    ["RECEIVED_RESPONSE_HEADERS", onGenericEvent],
-    ["UPDATING_RESPONSE_COOKIES", onGenericEvent],
-    ["RECEIVED_RESPONSE_COOKIES", onGenericEvent],
-    ["STARTED_RECEIVING_RESPONSE", onGenericEvent],
-    ["UPDATING_RESPONSE_CONTENT", onGenericEvent],
-    ["RECEIVED_RESPONSE_CONTENT", onGenericEvent],
-    ["UPDATING_EVENT_TIMINGS", onGenericEvent],
-    ["RECEIVED_EVENT_TIMINGS", onGenericEvent]
-  ];
+  return new Promise((resolve) => {
+    let panel = monitor.panelWin;
+    let { windowRequire } = panel;
+    let { NetMonitorController } =
+      windowRequire("devtools/client/netmonitor/src/netmonitor-controller");
+    let progress = {};
+    let genericEvents = 0;
+    let postEvents = 0;
+    let awaitedEventsToListeners = [
+      ["UPDATING_REQUEST_HEADERS", onGenericEvent],
+      ["RECEIVED_REQUEST_HEADERS", onGenericEvent],
+      ["UPDATING_REQUEST_COOKIES", onGenericEvent],
+      ["RECEIVED_REQUEST_COOKIES", onGenericEvent],
+      ["UPDATING_REQUEST_POST_DATA", onPostEvent],
+      ["RECEIVED_REQUEST_POST_DATA", onPostEvent],
+      ["UPDATING_RESPONSE_HEADERS", onGenericEvent],
+      ["RECEIVED_RESPONSE_HEADERS", onGenericEvent],
+      ["UPDATING_RESPONSE_COOKIES", onGenericEvent],
+      ["RECEIVED_RESPONSE_COOKIES", onGenericEvent],
+      ["STARTED_RECEIVING_RESPONSE", onGenericEvent],
+      ["UPDATING_RESPONSE_CONTENT", onGenericEvent],
+      ["RECEIVED_RESPONSE_CONTENT", onGenericEvent],
+      ["UPDATING_EVENT_TIMINGS", onGenericEvent],
+      ["RECEIVED_EVENT_TIMINGS", onGenericEvent]
+    ];
 
-  function initProgressForURL(url) {
-    if (progress[url]) {
-      return;
+    function initProgressForURL(url) {
+      if (progress[url]) {
+        return;
+      }
+      progress[url] = {};
+      awaitedEventsToListeners.forEach(function ([e]) {
+        progress[url][e] = 0;
+      });
     }
-    progress[url] = {};
-    awaitedEventsToListeners.forEach(function ([e]) {
-      progress[url][e] = 0;
-    });
-  }
 
-  function updateProgressForURL(url, event) {
-    initProgressForURL(url);
-    progress[url][Object.keys(EVENTS).find(e => EVENTS[e] == event)] = 1;
-  }
+    function updateProgressForURL(url, event) {
+      initProgressForURL(url);
+      progress[url][Object.keys(EVENTS).find(e => EVENTS[e] == event)] = 1;
+    }
 
-  function onGenericEvent(event, actor) {
-    genericEvents++;
-    maybeResolve(event, actor);
-  }
+    function onGenericEvent(event, actor) {
+      genericEvents++;
+      maybeResolve(event, actor);
+    }
 
-  function onPostEvent(event, actor) {
-    postEvents++;
-    maybeResolve(event, actor);
-  }
+    function onPostEvent(event, actor) {
+      postEvents++;
+      maybeResolve(event, actor);
+    }
 
-  function maybeResolve(event, actor) {
-    info("> Network events progress: " +
-      genericEvents + "/" + ((getRequests + postRequests) * 13) + ", " +
-      postEvents + "/" + (postRequests * 2) + ", " +
-      "got " + event + " for " + actor);
+    function maybeResolve(event, actor) {
+      info("> Network events progress: " +
+        genericEvents + "/" + ((getRequests + postRequests) * 13) + ", " +
+        postEvents + "/" + (postRequests * 2) + ", " +
+        "got " + event + " for " + actor);
 
-    let networkInfo = NetMonitorController.webConsoleClient.getNetworkRequest(actor);
-    let url = networkInfo.request.url;
-    updateProgressForURL(url, event);
+      let networkInfo = NetMonitorController.webConsoleClient.getNetworkRequest(actor);
+      let url = networkInfo.request.url;
+      updateProgressForURL(url, event);
 
-    // Uncomment this to get a detailed progress logging (when debugging a test)
-    // info("> Current state: " + JSON.stringify(progress, null, 2));
+      // Uncomment this to get a detailed progress logging (when debugging a test)
+      // info("> Current state: " + JSON.stringify(progress, null, 2));
 
-    // There are 15 updates which need to be fired for a request to be
-    // considered finished. The "requestPostData" packet isn't fired for
-    // non-POST requests.
-    if (genericEvents >= (getRequests + postRequests) * 13 &&
-        postEvents >= postRequests * 2) {
-      awaitedEventsToListeners.forEach(([e, l]) => panel.off(EVENTS[e], l));
-      executeSoon(deferred.resolve);
+      // There are 15 updates which need to be fired for a request to be
+      // considered finished. The "requestPostData" packet isn't fired for
+      // non-POST requests.
+      if (genericEvents >= (getRequests + postRequests) * 13 &&
+          postEvents >= postRequests * 2) {
+        awaitedEventsToListeners.forEach(([e, l]) => panel.off(EVENTS[e], l));
+        executeSoon(resolve);
+      }
     }
-  }
 
-  awaitedEventsToListeners.forEach(([e, l]) => panel.on(EVENTS[e], l));
-  return deferred.promise;
+    awaitedEventsToListeners.forEach(([e, l]) => panel.on(EVENTS[e], l));
+  });
 }
 
 function verifyRequestItemTarget(document, requestList, requestItem, method,
                                  url, data = {}) {
   info("> Verifying: " + method + " " + url + " " + data.toSource());
 
   let visibleIndex = requestList.indexOf(requestItem);
 
@@ -380,19 +380,19 @@ function verifyRequestItemTarget(documen
  * @param object subject
  *        The event emitter object that is being listened to.
  * @param string eventName
  *        The name of the event to listen to.
  * @return object
  *        Returns a promise that resolves upon firing of the event.
  */
 function waitFor(subject, eventName) {
-  let deferred = promise.defer();
-  subject.once(eventName, deferred.resolve);
-  return deferred.promise;
+  return new Promise((resolve) => {
+    subject.once(eventName, resolve);
+  });
 }
 
 /**
  * Tests if a button for a filter of given type is the only one checked.
  *
  * @param string filterType
  *        The type of the filter that should be the only one checked.
  */
@@ -495,15 +495,15 @@ function executeInContent(name, data = {
  * messagemanager is used).
  * @param {String} name The message name
  * @return {Promise} A promise that resolves to the response data when the
  * message has been received
  */
 function waitForContentMessage(name) {
   let mm = gBrowser.selectedBrowser.messageManager;
 
-  let def = promise.defer();
-  mm.addMessageListener(name, function onMessage(msg) {
-    mm.removeMessageListener(name, onMessage);
-    def.resolve(msg);
+  return new Promise((resolve) => {
+    mm.addMessageListener(name, function onMessage(msg) {
+      mm.removeMessageListener(name, onMessage);
+      resolve(msg);
+    });
   });
-  return def.promise;
 }
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -145,17 +145,17 @@ pref("devtools.serviceWorkers.testing.en
 
 // Enable the Network Monitor
 pref("devtools.netmonitor.enabled", true);
 
 // The default Network Monitor UI settings
 pref("devtools.netmonitor.panes-network-details-width", 550);
 pref("devtools.netmonitor.panes-network-details-height", 450);
 pref("devtools.netmonitor.filters", "[\"all\"]");
-pref("devtools.netmonitor.hiddenColumns", "[\"remoteip\"]");
+pref("devtools.netmonitor.hiddenColumns", "[\"remoteip\",\"protocol\"]");
 
 // The default Network monitor HAR export setting
 pref("devtools.netmonitor.har.defaultLogDir", "");
 pref("devtools.netmonitor.har.defaultFileName", "Archive %date");
 pref("devtools.netmonitor.har.jsonp", false);
 pref("devtools.netmonitor.har.jsonpCallback", "");
 pref("devtools.netmonitor.har.includeResponseBodies", true);
 pref("devtools.netmonitor.har.compress", false);
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -3167,17 +3167,16 @@ exports.CSS_PROPERTIES = {
       9,
       10,
       11
     ],
     "values": [
       "COLOR",
       "-moz-all",
       "-moz-alt-content",
-      "-moz-anchor-decoration",
       "-moz-available",
       "-moz-block-height",
       "-moz-box",
       "-moz-button",
       "-moz-center",
       "-moz-crisp-edges",
       "-moz-deck",
       "-moz-desktop",
@@ -9106,17 +9105,16 @@ exports.CSS_PROPERTIES = {
       "text-decoration-line",
       "text-decoration-style"
     ],
     "supports": [
       2
     ],
     "values": [
       "COLOR",
-      "-moz-anchor-decoration",
       "-moz-none",
       "blink",
       "currentColor",
       "dashed",
       "dotted",
       "double",
       "hsl",
       "hsla",
@@ -9157,17 +9155,16 @@ exports.CSS_PROPERTIES = {
   },
   "text-decoration-line": {
     "isInherited": false,
     "subproperties": [
       "text-decoration-line"
     ],
     "supports": [],
     "values": [
-      "-moz-anchor-decoration",
       "blink",
       "inherit",
       "initial",
       "line-through",
       "none",
       "overline",
       "underline",
       "unset"
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -273,16 +273,20 @@
 using namespace mozilla;
 using namespace mozilla::dom;
 
 typedef nsTArray<Link*> LinkArray;
 
 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
 static LazyLogModule gCspPRLog("CSP");
 
+static const char kChromeInContentPref[] = "security.allow_chrome_frames_inside_content";
+static bool sChromeInContentAllowed = false;
+static bool sChromeInContentPrefCached = false;
+
 static nsresult
 GetHttpChannelHelper(nsIChannel* aChannel, nsIHttpChannel** aHttpChannel)
 {
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
   if (httpChannel) {
     httpChannel.forget(aHttpChannel);
     return NS_OK;
   }
@@ -2074,16 +2078,18 @@ nsDocument::Reset(nsIChannel* aChannel, 
     nsIScriptSecurityManager *securityManager =
       nsContentUtils::GetSecurityManager();
     if (securityManager) {
       securityManager->GetChannelResultPrincipal(aChannel,
                                                  getter_AddRefs(principal));
     }
   }
 
+  principal = MaybeDowngradePrincipal(principal);
+
   ResetToURI(uri, aLoadGroup, principal);
 
   // Note that, since mTiming does not change during a reset, the
   // navigationStart time remains unchanged and therefore any future new
   // timeline will have the same global clock time as the old one.
   mDocumentTimeline = nullptr;
 
   nsCOMPtr<nsIPropertyBag2> bag = do_QueryInterface(aChannel);
@@ -2220,16 +2226,53 @@ nsDocument::ResetToURI(nsIURI *aURI, nsI
   }
 
   // Refresh the principal on the compartment.
   if (nsPIDOMWindowInner* win = GetInnerWindow()) {
     nsGlobalWindow::Cast(win)->RefreshCompartmentPrincipal();
   }
 }
 
+already_AddRefed<nsIPrincipal>
+nsDocument::MaybeDowngradePrincipal(nsIPrincipal* aPrincipal)
+{
+  if (!aPrincipal) {
+    return nullptr;
+  }
+
+  if (!sChromeInContentPrefCached) {
+    sChromeInContentPrefCached = true;
+    Preferences::AddBoolVarCache(&sChromeInContentAllowed,
+                                 kChromeInContentPref, false);
+  }
+  if (!sChromeInContentAllowed &&
+      nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+    // We basically want the parent document here, but because this is very
+    // early in the load, GetParentDocument() returns null, so we use the
+    // docshell hierarchy to get this information instead.
+    if (mDocumentContainer) {
+      nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem;
+      mDocumentContainer->GetParent(getter_AddRefs(parentDocShellItem));
+      nsCOMPtr<nsIDocShell> parentDocShell = do_QueryInterface(parentDocShellItem);
+      if (parentDocShell) {
+        nsCOMPtr<nsIDocument> parentDoc;
+        parentDoc = parentDocShell->GetDocument();
+        if (!parentDoc ||
+            !nsContentUtils::IsSystemPrincipal(parentDoc->NodePrincipal())) {
+          nsCOMPtr<nsIPrincipal> nullPrincipal =
+            do_CreateInstance("@mozilla.org/nullprincipal;1");
+          return nullPrincipal.forget();
+        }
+      }
+    }
+  }
+  nsCOMPtr<nsIPrincipal> principal(aPrincipal);
+  return principal.forget();
+}
+
 void
 nsDocument::RemoveDocStyleSheetsFromStyleSets()
 {
   // The stylesheets should forget us
   for (StyleSheet* sheet : Reversed(mStyleSheets)) {
     sheet->ClearAssociatedDocument();
 
     if (sheet->IsApplicable()) {
@@ -10591,24 +10634,28 @@ public:
     MaybeUnblockParser();
 
     mPromise->MaybeReject(aCx, aValue);
   }
 
 protected:
   virtual ~UnblockParsingPromiseHandler()
   {
-    MaybeUnblockParser();
+    // If we're being cleaned up by the cycle collector, our mDocument reference
+    // may have been unlinked while our mParser weak reference is still alive.
+    if (mDocument) {
+      MaybeUnblockParser();
+    }
   }
 
 private:
   void MaybeUnblockParser() {
     nsCOMPtr<nsIParser> parser = do_QueryReferent(mParser);
     if (parser) {
-      MOZ_ASSERT(mDocument);
+      MOZ_DIAGNOSTIC_ASSERT(mDocument);
       nsCOMPtr<nsIParser> docParser = mDocument->CreatorParserOrNull();
       if (parser == docParser) {
         parser->UnblockParser();
         parser->ContinueInterruptedParsingAsync();
       }
     }
     mParser = nullptr;
     mDocument = nullptr;
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -577,16 +577,18 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
   NS_DECL_SIZEOF_EXCLUDING_THIS
 
   virtual void Reset(nsIChannel *aChannel, nsILoadGroup *aLoadGroup) override;
   virtual void ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
                           nsIPrincipal* aPrincipal) override;
 
+  already_AddRefed<nsIPrincipal> MaybeDowngradePrincipal(nsIPrincipal* aPrincipal);
+
   // StartDocumentLoad is pure virtual so that subclasses must override it.
   // The nsDocument StartDocumentLoad does some setup, but does NOT set
   // *aDocListener; this is the job of subclasses.
   virtual nsresult StartDocumentLoad(const char* aCommand,
                                      nsIChannel* aChannel,
                                      nsILoadGroup* aLoadGroup,
                                      nsISupports* aContainer,
                                      nsIStreamListener **aDocListener,
--- a/dom/base/test/test_blockParsing.html
+++ b/dom/base/test/test_blockParsing.html
@@ -76,13 +76,56 @@ add_task(function* () {
   yield runTest("http://mochi.test:8888/chrome/dom/base/test/file_external_script.html",
                 '<html lang="en"></html>',
                 '<html lang="en"><head>\n  <script src="file_script.js"><\/script>\n  <meta charset="utf-8">\n  <title></title>\n</head>\n<body>\n  <p>Hello Mochitest</p>\n\n\n</body></html>');
 
   yield runTest("http://mochi.test:8888/chrome/dom/base/test/file_external_script.xhtml",
                 '<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"></html>',
                 '<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">\n<head>\n  <script src="file_script.js"><\/script>\n  <title></title>\n</head>\n<body>\n  <p>Hello Mochitest</p>\n</body>\n</html>');
 });
+
+add_task(function* test_cleanup() {
+  const TOPIC = "blocking-promise-destroyed";
+
+  const finalizationWitness = Components.classes["@mozilla.org/toolkit/finalizationwitness;1"]
+      .getService(Components.interfaces.nsIFinalizationWitnessService);
+
+  for (let url of ["http://mochi.test:8888/chrome/dom/base/test/file_inline_script.html",
+                   "http://mochi.test:8888/chrome/dom/base/test/file_inline_script.xhtml"]) {
+    let iframe = document.createElement("iframe");
+    iframe.src = url;
+
+    // Create a promise that never resolves.
+    let blockerPromise = new Promise(() => {});
+
+    // Create a finalization witness so we can be sure that the promises
+    // have been collected before the end of the test.
+    let destroyedPromise = TestUtils.topicObserved(TOPIC);
+    let witness = finalizationWitness.make(TOPIC, url);
+    blockerPromise.witness = witness;
+
+    let insertedPromise = TestUtils.topicObserved("document-element-inserted", document => {
+      document.blockParsing(blockerPromise).witness = witness;
+
+      return true;
+    });
+
+    document.body.appendChild(iframe);
+    yield insertedPromise;
+
+    // Clear the promise reference, destroy the document, and force GC/CC. This should
+    // trigger any potential leaks or cleanup issues.
+    blockerPromise = null;
+    witness = null;
+    iframe.remove();
+
+    Components.utils.forceGC();
+    Components.utils.forceCC();
+    Components.utils.forceGC();
+
+    // Make sure the blocker promise has been collected.
+    let [, data] = yield destroyedPromise;
+    is(data, url, "Should have correct finalizer URL");
+  }
+});
 </script>
 </body>
 </html>
-
-
new file mode 100644
--- /dev/null
+++ b/dom/canvas/crashtests/779426.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+  var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
+  var canvas2d = canvas.getContext('2d');
+  canvas2d.rect(0, 0, 1, 1);
+  canvas2d.transform(1, 0, 0, 1, 0, 0);
+  canvas.setAttributeNS(null, "height", "99");
+  canvas2d.rect(0, 0, 1, 1);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
--- a/dom/canvas/crashtests/crashtests.list
+++ b/dom/canvas/crashtests/crashtests.list
@@ -6,16 +6,17 @@ load 553938-1.html
 load 647480.html
 load 727547.html
 load 729116.html
 asserts-if(stylo,1) load 745699-1.html # bug 1324700
 load 746813-1.html
 load 743499-negative-size.html
 skip-if(Android) load 745818-large-source.html # Bug XXX - Crashes Android mid-run w/o a stack
 load 767337-1.html
+load 779426.html
 asserts-if(stylo,1) skip-if(Android) load 780392-1.html # bug 1324700
 skip-if(Android) skip-if(gtkWidget&&isDebugBuild) load 789933-1.html # bug 1155252 for linux
 load 794463-1.html
 load 802926-1.html
 load 896047-1.html
 load 916128-1.html
 load 934939-1.html
 asserts-if(stylo,1) load 1099143-1.html # bug 1324700
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -42,16 +42,20 @@ interface nsIServiceWorkerInfo : nsISupp
   readonly attribute DOMString cacheName;
 
   readonly attribute unsigned short state;
 
   readonly attribute nsIWorkerDebugger debugger;
 
   readonly attribute bool handlesFetchEvents;
 
+  readonly attribute PRTime installedTime;
+  readonly attribute PRTime activatedTime;
+  readonly attribute PRTime redundantTime;
+
   void attachDebugger();
 
   void detachDebugger();
 };
 
 [scriptable, uuid(87e63548-d440-4b8a-b158-65ad1de0211E)]
 interface nsIServiceWorkerRegistrationInfoListener : nsISupports
 {
@@ -61,16 +65,18 @@ interface nsIServiceWorkerRegistrationIn
 [scriptable, builtinclass, uuid(ddbc1fd4-2f2e-4fca-a395-6e010bbedfe3)]
 interface nsIServiceWorkerRegistrationInfo : nsISupports
 {
   readonly attribute nsIPrincipal principal;
 
   readonly attribute DOMString scope;
   readonly attribute DOMString scriptSpec;
 
+  readonly attribute PRTime lastUpdateTime;
+
   readonly attribute nsIServiceWorkerInfo installingWorker;
   readonly attribute nsIServiceWorkerInfo waitingWorker;
   readonly attribute nsIServiceWorkerInfo activeWorker;
 
   // Allows to get the related nsIServiceWorkerInfo for a given
   // nsIWorkerDebugger. Over time we shouldn't need this anymore,
   // and instead always control then nsIWorkerDebugger from
   // nsIServiceWorkerInfo and not the other way around.  Returns
--- a/dom/workers/ServiceWorkerInfo.cpp
+++ b/dom/workers/ServiceWorkerInfo.cpp
@@ -65,16 +65,43 @@ ServiceWorkerInfo::GetHandlesFetchEvents
 {
   MOZ_ASSERT(aValue);
   AssertIsOnMainThread();
   *aValue = HandlesFetch();
   return NS_OK;
 }
 
 NS_IMETHODIMP
+ServiceWorkerInfo::GetInstalledTime(PRTime* _retval)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(_retval);
+  *_retval = mInstalledTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerInfo::GetActivatedTime(PRTime* _retval)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(_retval);
+  *_retval = mActivatedTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ServiceWorkerInfo::GetRedundantTime(PRTime* _retval)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(_retval);
+  *_retval = mRedundantTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 ServiceWorkerInfo::AttachDebugger()
 {
   return mServiceWorkerPrivate->AttachDebugger();
 }
 
 NS_IMETHODIMP
 ServiceWorkerInfo::DetachDebugger()
 {
@@ -184,16 +211,21 @@ ServiceWorkerInfo::ServiceWorkerInfo(nsI
                                      nsLoadFlags aLoadFlags)
   : mPrincipal(aPrincipal)
   , mScope(aScope)
   , mScriptSpec(aScriptSpec)
   , mCacheName(aCacheName)
   , mLoadFlags(aLoadFlags)
   , mState(ServiceWorkerState::EndGuard_)
   , mServiceWorkerID(GetNextID())
+  , mCreationTime(PR_Now())
+  , mCreationTimeStamp(TimeStamp::Now())
+  , mInstalledTime(0)
+  , mActivatedTime(0)
+  , mRedundantTime(0)
   , mServiceWorkerPrivate(new ServiceWorkerPrivate(this))
   , mSkipWaitingFlag(false)
   , mHandlesFetch(Unknown)
 {
   MOZ_ASSERT(mPrincipal);
   // cache origin attributes so we can use them off main thread
   mOriginAttributes = mPrincipal->OriginAttributesRef();
   MOZ_ASSERT(!mScope.IsEmpty());
@@ -233,9 +265,42 @@ ServiceWorkerInfo::GetOrCreateInstance(n
 
   if (!ref) {
     ref = new ServiceWorker(aWindow, this);
   }
 
   return ref.forget();
 }
 
+void
+ServiceWorkerInfo::UpdateInstalledTime()
+{
+  MOZ_ASSERT(mState == ServiceWorkerState::Installed);
+  MOZ_ASSERT(mInstalledTime == 0);
+
+  mInstalledTime =
+    mCreationTime + static_cast<PRTime>((TimeStamp::Now() -
+                                         mCreationTimeStamp).ToMicroseconds());
+}
+
+void
+ServiceWorkerInfo::UpdateActivatedTime()
+{
+  MOZ_ASSERT(mState == ServiceWorkerState::Activated);
+  MOZ_ASSERT(mActivatedTime == 0);
+
+  mActivatedTime =
+    mCreationTime + static_cast<PRTime>((TimeStamp::Now() -
+                                         mCreationTimeStamp).ToMicroseconds());
+}
+
+void
+ServiceWorkerInfo::UpdateRedundantTime()
+{
+  MOZ_ASSERT(mState == ServiceWorkerState::Redundant);
+  MOZ_ASSERT(mRedundantTime == 0);
+
+  mRedundantTime =
+    mCreationTime + static_cast<PRTime>((TimeStamp::Now() -
+                                         mCreationTimeStamp).ToMicroseconds());
+}
+
 END_WORKERS_NAMESPACE
--- a/dom/workers/ServiceWorkerInfo.h
+++ b/dom/workers/ServiceWorkerInfo.h
@@ -34,16 +34,26 @@ private:
   const nsLoadFlags mLoadFlags;
   ServiceWorkerState mState;
   OriginAttributes mOriginAttributes;
 
   // This id is shared with WorkerPrivate to match requests issued by service
   // workers to their corresponding serviceWorkerInfo.
   uint64_t mServiceWorkerID;
 
+  // Timestamp to track SW's state
+  PRTime mCreationTime;
+  TimeStamp mCreationTimeStamp;
+
+  // The time of states are 0, if SW has not reached that state yet. Besides, we
+  // update each of them after UpdateState() is called in SWRegistrationInfo.
+  PRTime mInstalledTime;
+  PRTime mActivatedTime;
+  PRTime mRedundantTime;
+
   // We hold rawptrs since the ServiceWorker constructor and destructor ensure
   // addition and removal.
   // There is a high chance of there being at least one ServiceWorker
   // associated with this all the time.
   AutoTArray<ServiceWorker*, 1> mInstances;
 
   RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
   bool mSkipWaitingFlag;
@@ -168,15 +178,56 @@ public:
   void
   AppendWorker(ServiceWorker* aWorker);
 
   void
   RemoveWorker(ServiceWorker* aWorker);
 
   already_AddRefed<ServiceWorker>
   GetOrCreateInstance(nsPIDOMWindowInner* aWindow);
+
+  void
+  UpdateInstalledTime();
+
+  void
+  UpdateActivatedTime();
+
+  void
+  UpdateRedundantTime();
+
+  int64_t
+  GetInstalledTime() const
+  {
+    return mInstalledTime;
+  }
+
+  void
+  SetInstalledTime(const int64_t aTime)
+  {
+    if (aTime == 0) {
+      return;
+    }
+
+    mInstalledTime = aTime;
+  }
+
+  int64_t
+  GetActivatedTime() const
+  {
+    return mActivatedTime;
+  }
+
+  void
+  SetActivatedTime(const int64_t aTime)
+  {
+    if (aTime == 0) {
+      return;
+    }
+
+    mActivatedTime = aTime;
+  }
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_workers_serviceworkerinfo_h
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -182,20 +182,27 @@ PopulateRegistrationData(nsIPrincipal* a
   if (NS_WARN_IF(!newest)) {
     return NS_ERROR_FAILURE;
   }
 
   if (aRegistration->GetActive()) {
     aData.currentWorkerURL() = aRegistration->GetActive()->ScriptSpec();
     aData.cacheName() = aRegistration->GetActive()->CacheName();
     aData.currentWorkerHandlesFetch() = aRegistration->GetActive()->HandlesFetch();
+
+    aData.currentWorkerInstalledTime() =
+      aRegistration->GetActive()->GetInstalledTime();
+    aData.currentWorkerActivatedTime() =
+      aRegistration->GetActive()->GetActivatedTime();
   }
 
   aData.loadFlags() = aRegistration->GetLoadFlags();
 
+  aData.lastUpdateTime() = aRegistration->GetLastUpdateTime();
+
   return NS_OK;
 }
 
 class TeardownRunnable final : public Runnable
 {
 public:
   explicit TeardownRunnable(ServiceWorkerManagerChild* aActor)
     : mActor(aActor)
@@ -2019,26 +2026,29 @@ ServiceWorkerManager::LoadRegistration(
     // the CacheName is an UUID generated when a new script is found.
     if (registration->GetActive() &&
         registration->GetActive()->CacheName() == aRegistration.cacheName()) {
       // No needs for updates.
       return;
     }
   }
 
+  registration->SetLastUpdateTime(aRegistration.lastUpdateTime());
+
   const nsCString& currentWorkerURL = aRegistration.currentWorkerURL();
   if (!currentWorkerURL.IsEmpty()) {
     registration->SetActive(
       new ServiceWorkerInfo(registration->mPrincipal,
                             registration->mScope,
                             currentWorkerURL,
                             aRegistration.cacheName(),
                             registration->GetLoadFlags()));
     registration->GetActive()->SetHandlesFetch(aRegistration.currentWorkerHandlesFetch());
-    registration->GetActive()->SetActivateStateUncheckedWithoutEvent(ServiceWorkerState::Activated);
+    registration->GetActive()->SetInstalledTime(aRegistration.currentWorkerInstalledTime());
+    registration->GetActive()->SetActivatedTime(aRegistration.currentWorkerActivatedTime());
   }
 }
 
 void
 ServiceWorkerManager::LoadRegistrations(
                   const nsTArray<ServiceWorkerRegistrationData>& aRegistrations)
 {
   AssertIsOnMainThread();
--- a/dom/workers/ServiceWorkerRegistrar.cpp
+++ b/dom/workers/ServiceWorkerRegistrar.cpp
@@ -36,16 +36,17 @@ using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 static const char* gSupportedRegistrarVersions[] = {
   SERVICEWORKERREGISTRAR_VERSION,
+  "6",
   "5",
   "4",
   "3",
   "2"
 };
 
 StaticRefPtr<ServiceWorkerRegistrar> gServiceWorkerRegistrar;
 
@@ -375,16 +376,82 @@ ServiceWorkerRegistrar::ReadData()
       GET_LINE(loadFlags);
       entry->loadFlags() = loadFlags.ToInteger(&rv, 16);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       } else if (entry->loadFlags() != nsIRequest::LOAD_NORMAL &&
                  entry->loadFlags() != nsIRequest::VALIDATE_ALWAYS) {
         return NS_ERROR_INVALID_ARG;
       }
+
+      nsAutoCString installedTimeStr;
+      GET_LINE(installedTimeStr);
+      int64_t installedTime = installedTimeStr.ToInteger64(&rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      entry->currentWorkerInstalledTime() = installedTime;
+
+      nsAutoCString activatedTimeStr;
+      GET_LINE(activatedTimeStr);
+      int64_t activatedTime = activatedTimeStr.ToInteger64(&rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      entry->currentWorkerActivatedTime() = activatedTime;
+
+      nsAutoCString lastUpdateTimeStr;
+      GET_LINE(lastUpdateTimeStr);
+      int64_t lastUpdateTime = lastUpdateTimeStr.ToInteger64(&rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      entry->lastUpdateTime() = lastUpdateTime;
+    } else if (version.EqualsLiteral("6")) {
+      nsAutoCString suffix;
+      GET_LINE(suffix);
+
+      OriginAttributes attrs;
+      if (!attrs.PopulateFromSuffix(suffix)) {
+        return NS_ERROR_INVALID_ARG;
+      }
+
+      GET_LINE(entry->scope());
+
+      entry->principal() =
+        mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope());
+
+      GET_LINE(entry->currentWorkerURL());
+
+      nsAutoCString fetchFlag;
+      GET_LINE(fetchFlag);
+      if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
+          !fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
+        return NS_ERROR_INVALID_ARG;
+      }
+      entry->currentWorkerHandlesFetch() =
+        fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
+
+      nsAutoCString cacheName;
+      GET_LINE(cacheName);
+      CopyUTF8toUTF16(cacheName, entry->cacheName());
+
+      nsAutoCString loadFlags;
+      GET_LINE(loadFlags);
+      entry->loadFlags() = loadFlags.ToInteger(&rv, 16);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      } else if (entry->loadFlags() != nsIRequest::LOAD_NORMAL &&
+                 entry->loadFlags() != nsIRequest::VALIDATE_ALWAYS) {
+        return NS_ERROR_INVALID_ARG;
+      }
+
+      entry->currentWorkerInstalledTime() = 0;
+      entry->currentWorkerActivatedTime() = 0;
+      entry->lastUpdateTime() = 0;
     } else if (version.EqualsLiteral("5")) {
       overwrite = true;
       dedupe = true;
 
       nsAutoCString suffix;
       GET_LINE(suffix);
 
       OriginAttributes attrs;
@@ -408,16 +475,20 @@ ServiceWorkerRegistrar::ReadData()
       entry->currentWorkerHandlesFetch() =
         fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
 
       nsAutoCString cacheName;
       GET_LINE(cacheName);
       CopyUTF8toUTF16(cacheName, entry->cacheName());
 
       entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
+
+      entry->currentWorkerInstalledTime() = 0;
+      entry->currentWorkerActivatedTime() = 0;
+      entry->lastUpdateTime() = 0;
     } else if (version.EqualsLiteral("4")) {
       overwrite = true;
       dedupe = true;
 
       nsAutoCString suffix;
       GET_LINE(suffix);
 
       OriginAttributes attrs;
@@ -435,16 +506,20 @@ ServiceWorkerRegistrar::ReadData()
       // default handlesFetch flag to Enabled
       entry->currentWorkerHandlesFetch() = true;
 
       nsAutoCString cacheName;
       GET_LINE(cacheName);
       CopyUTF8toUTF16(cacheName, entry->cacheName());
 
       entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
+
+      entry->currentWorkerInstalledTime() = 0;
+      entry->currentWorkerActivatedTime() = 0;
+      entry->lastUpdateTime() = 0;
     } else if (version.EqualsLiteral("3")) {
       overwrite = true;
       dedupe = true;
 
       nsAutoCString suffix;
       GET_LINE(suffix);
 
       OriginAttributes attrs;
@@ -465,16 +540,20 @@ ServiceWorkerRegistrar::ReadData()
       // default handlesFetch flag to Enabled
       entry->currentWorkerHandlesFetch() = true;
 
       nsAutoCString cacheName;
       GET_LINE(cacheName);
       CopyUTF8toUTF16(cacheName, entry->cacheName());
 
       entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
+
+      entry->currentWorkerInstalledTime() = 0;
+      entry->currentWorkerActivatedTime() = 0;
+      entry->lastUpdateTime() = 0;
     } else if (version.EqualsLiteral("2")) {
       overwrite = true;
       dedupe = true;
 
       nsAutoCString suffix;
       GET_LINE(suffix);
 
       OriginAttributes attrs;
@@ -501,16 +580,20 @@ ServiceWorkerRegistrar::ReadData()
       nsAutoCString cacheName;
       GET_LINE(cacheName);
       CopyUTF8toUTF16(cacheName, entry->cacheName());
 
       // waitingCacheName is no more used in latest version.
       GET_LINE(unused);
 
       entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
+
+      entry->currentWorkerInstalledTime() = 0;
+      entry->currentWorkerActivatedTime() = 0;
+      entry->lastUpdateTime() = 0;
     } else {
       MOZ_ASSERT_UNREACHABLE("Should never get here!");
     }
 
 #undef GET_LINE
 
     rv = lineInputStream->ReadLine(line, &hasMoreLines);
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -817,16 +900,25 @@ ServiceWorkerRegistrar::WriteData()
     MOZ_DIAGNOSTIC_ASSERT(data[i].loadFlags() == nsIRequest::LOAD_NORMAL ||
                           data[i].loadFlags() == nsIRequest::VALIDATE_ALWAYS);
 
     static_assert(nsIRequest::LOAD_NORMAL == 0,
                   "LOAD_NORMAL matches serialized value.");
     static_assert(nsIRequest::VALIDATE_ALWAYS == (1 << 11),
                   "VALIDATE_ALWAYS matches serialized value");
 
+    buffer.AppendInt(data[i].currentWorkerInstalledTime());
+    buffer.Append('\n');
+
+    buffer.AppendInt(data[i].currentWorkerActivatedTime());
+    buffer.Append('\n');
+
+    buffer.AppendInt(data[i].lastUpdateTime());
+    buffer.Append('\n');
+
     buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR);
     buffer.Append('\n');
 
     rv = stream->Write(buffer.Data(), buffer.Length(), &count);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
--- a/dom/workers/ServiceWorkerRegistrar.h
+++ b/dom/workers/ServiceWorkerRegistrar.h
@@ -11,17 +11,17 @@
 #include "mozilla/Telemetry.h"
 #include "nsClassHashtable.h"
 #include "nsIObserver.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 #define SERVICEWORKERREGISTRAR_FILE "serviceworker.txt"
-#define SERVICEWORKERREGISTRAR_VERSION "6"
+#define SERVICEWORKERREGISTRAR_VERSION "7"
 #define SERVICEWORKERREGISTRAR_TERMINATOR "#"
 #define SERVICEWORKERREGISTRAR_TRUE "true"
 #define SERVICEWORKERREGISTRAR_FALSE "false"
 
 class nsIFile;
 
 namespace mozilla {
 
--- a/dom/workers/ServiceWorkerRegistrarTypes.ipdlh
+++ b/dom/workers/ServiceWorkerRegistrarTypes.ipdlh
@@ -15,12 +15,16 @@ struct ServiceWorkerRegistrationData
   nsCString currentWorkerURL;
   bool currentWorkerHandlesFetch;
 
   nsString cacheName;
 
   PrincipalInfo principal;
 
   uint32_t loadFlags;
+
+  int64_t currentWorkerInstalledTime;
+  int64_t currentWorkerActivatedTime;
+  int64_t lastUpdateTime;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/workers/ServiceWorkerRegistrationInfo.cpp
+++ b/dom/workers/ServiceWorkerRegistrationInfo.cpp
@@ -49,40 +49,47 @@ ServiceWorkerRegistrationInfo::Clear()
   }
 
   UpdateRegistrationStateProperties(WhichServiceWorker::INSTALLING_WORKER |
                                     WhichServiceWorker::WAITING_WORKER |
                                     WhichServiceWorker::ACTIVE_WORKER, Invalidate);
 
   if (mInstallingWorker) {
     mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
+    mInstallingWorker->UpdateRedundantTime();
     mInstallingWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo();
     mInstallingWorker = nullptr;
     // FIXME(nsm): Abort any inflight requests from installing worker.
   }
 
   if (mWaitingWorker) {
     mWaitingWorker->UpdateState(ServiceWorkerState::Redundant);
+    mWaitingWorker->UpdateRedundantTime();
     mWaitingWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo();
     mWaitingWorker = nullptr;
   }
 
   if (mActiveWorker) {
     mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
+    mActiveWorker->UpdateRedundantTime();
     mActiveWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo();
     mActiveWorker = nullptr;
   }
+
+  NotifyChromeRegistrationListeners();
 }
 
 ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& aScope,
                                                              nsIPrincipal* aPrincipal,
                                                              nsLoadFlags aLoadFlags)
   : mControlledDocumentsCounter(0)
   , mUpdateState(NoUpdate)
-  , mLastUpdateCheckTime(0)
+  , mCreationTime(PR_Now())
+  , mCreationTimeStamp(TimeStamp::Now())
+  , mLastUpdateTime(0)
   , mLoadFlags(aLoadFlags)
   , mScope(aScope)
   , mPrincipal(aPrincipal)
   , mPendingUninstall(false)
 {}
 
 ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo()
 {
@@ -116,16 +123,25 @@ ServiceWorkerRegistrationInfo::GetScript
   RefPtr<ServiceWorkerInfo> newest = Newest();
   if (newest) {
     CopyUTF8toUTF16(newest->ScriptSpec(), aScriptSpec);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+ServiceWorkerRegistrationInfo::GetLastUpdateTime(PRTime* _retval)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(_retval);
+  *_retval = mLastUpdateTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 ServiceWorkerRegistrationInfo::GetInstallingWorker(nsIServiceWorkerInfo **aResult)
 {
   AssertIsOnMainThread();
   nsCOMPtr<nsIServiceWorkerInfo> info = do_QueryInterface(mInstallingWorker);
   info.forget(aResult);
   return NS_OK;
 }
 
@@ -278,45 +294,57 @@ ServiceWorkerRegistrationInfo::FinishAct
 {
   if (mPendingUninstall || !mActiveWorker ||
       mActiveWorker->State() != ServiceWorkerState::Activating) {
     return;
   }
 
   // Activation never fails, so aSuccess is ignored.
   mActiveWorker->UpdateState(ServiceWorkerState::Activated);
+  mActiveWorker->UpdateActivatedTime();
+  NotifyChromeRegistrationListeners();
+
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   if (!swm) {
     // browser shutdown started during async activation completion step
     return;
   }
   swm->StoreRegistration(mPrincipal, this);
 }
 
 void
 ServiceWorkerRegistrationInfo::RefreshLastUpdateCheckTime()
 {
   AssertIsOnMainThread();
-  mLastUpdateCheckTime = PR_IntervalNow() / PR_MSEC_PER_SEC;
+
+  mLastUpdateTime =
+    mCreationTime + static_cast<PRTime>((TimeStamp::Now() -
+                                         mCreationTimeStamp).ToMicroseconds());
+  NotifyChromeRegistrationListeners();
 }
 
 bool
 ServiceWorkerRegistrationInfo::IsLastUpdateCheckTimeOverOneDay() const
 {
   AssertIsOnMainThread();
 
   // For testing.
   if (Preferences::GetBool("dom.serviceWorkers.testUpdateOverOneDay")) {
     return true;
   }
 
-  const uint64_t kSecondsPerDay = 86400;
-  const uint64_t now = PR_IntervalNow() / PR_MSEC_PER_SEC;
+  const int64_t kSecondsPerDay = 86400;
+  const int64_t now =
+    mCreationTime + static_cast<PRTime>((TimeStamp::Now() -
+                                         mCreationTimeStamp).ToMicroseconds());
 
-  if ((now - mLastUpdateCheckTime) > kSecondsPerDay) {
+  // now < mLastUpdateTime if the system time is reset between storing
+  // and loading mLastUpdateTime from ServiceWorkerRegistrar.
+  if (now < mLastUpdateTime ||
+      (now - mLastUpdateTime) / PR_MSEC_PER_SEC > kSecondsPerDay) {
     return true;
   }
   return false;
 }
 
 void
 ServiceWorkerRegistrationInfo::AsyncUpdateRegistrationStateProperties(WhichServiceWorker aWorker,
                                                                       TransitionType aTransition)
@@ -345,18 +373,16 @@ ServiceWorkerRegistrationInfo::UpdateReg
                                                                  TransitionType aTransition)
 {
   AssertIsOnMainThread();
 
   nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<WhichServiceWorker, TransitionType>(
          this,
          &ServiceWorkerRegistrationInfo::AsyncUpdateRegistrationStateProperties, aWorker, aTransition);
   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
-
-  NotifyChromeRegistrationListeners();
 }
 
 void
 ServiceWorkerRegistrationInfo::NotifyChromeRegistrationListeners()
 {
   nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> listeners(mListeners);
   for (size_t index = 0; index < listeners.Length(); ++index) {
     listeners[index]->OnChange();
@@ -475,32 +501,37 @@ ServiceWorkerRegistrationInfo::ClearEval
 {
   AssertIsOnMainThread();
 
   if (!mEvaluatingWorker) {
     return;
   }
 
   mEvaluatingWorker->UpdateState(ServiceWorkerState::Redundant);
+  // We don't update the redundant time for the sw here, since we've not expose
+  // evalutingWorker yet.
   mEvaluatingWorker = nullptr;
 }
 
 void
 ServiceWorkerRegistrationInfo::ClearInstalling()
 {
   AssertIsOnMainThread();
 
   if (!mInstallingWorker) {
     return;
   }
 
   UpdateRegistrationStateProperties(WhichServiceWorker::INSTALLING_WORKER,
                                     Invalidate);
   mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
+  mInstallingWorker->UpdateRedundantTime();
   mInstallingWorker = nullptr;
+
+  NotifyChromeRegistrationListeners();
 }
 
 void
 ServiceWorkerRegistrationInfo::TransitionEvaluatingToInstalling()
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(mEvaluatingWorker);
   MOZ_ASSERT(!mInstallingWorker);
@@ -514,22 +545,25 @@ void
 ServiceWorkerRegistrationInfo::TransitionInstallingToWaiting()
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(mInstallingWorker);
 
   if (mWaitingWorker) {
     MOZ_ASSERT(mInstallingWorker->CacheName() != mWaitingWorker->CacheName());
     mWaitingWorker->UpdateState(ServiceWorkerState::Redundant);
+    mWaitingWorker->UpdateRedundantTime();
   }
 
   mWaitingWorker = mInstallingWorker.forget();
   UpdateRegistrationStateProperties(WhichServiceWorker::INSTALLING_WORKER,
                                     TransitionToNextState);
   mWaitingWorker->UpdateState(ServiceWorkerState::Installed);
+  mWaitingWorker->UpdateInstalledTime();
+  NotifyChromeRegistrationListeners();
 
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   if (!swm) {
     // browser shutdown began
     return;
   }
   swm->StoreRegistration(mPrincipal, this);
 }
@@ -546,43 +580,49 @@ ServiceWorkerRegistrationInfo::SetActive
   //       overrides.
   MOZ_ASSERT(mInstallingWorker != aServiceWorker);
   MOZ_ASSERT(mWaitingWorker != aServiceWorker);
   MOZ_ASSERT(mActiveWorker != aServiceWorker);
 
   if (mActiveWorker) {
     MOZ_ASSERT(aServiceWorker->CacheName() != mActiveWorker->CacheName());
     mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
+    mActiveWorker->UpdateRedundantTime();
   }
 
   // The active worker is being overriden due to initial load or
   // another process activating a worker.  Move straight to the
   // Activated state.
   mActiveWorker = aServiceWorker;
   mActiveWorker->SetActivateStateUncheckedWithoutEvent(ServiceWorkerState::Activated);
+  // We don't need to update activated time when we load registration from
+  // registrar.
   UpdateRegistrationStateProperties(WhichServiceWorker::ACTIVE_WORKER, Invalidate);
+  NotifyChromeRegistrationListeners();
 }
 
 void
 ServiceWorkerRegistrationInfo::TransitionWaitingToActive()
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(mWaitingWorker);
 
   if (mActiveWorker) {
     MOZ_ASSERT(mWaitingWorker->CacheName() != mActiveWorker->CacheName());
     mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
+    mActiveWorker->UpdateRedundantTime();
   }
 
   // We are transitioning from waiting to active normally, so go to
   // the activating state.
   mActiveWorker = mWaitingWorker.forget();
   UpdateRegistrationStateProperties(WhichServiceWorker::WAITING_WORKER,
                                     TransitionToNextState);
   mActiveWorker->UpdateState(ServiceWorkerState::Activating);
+  NotifyChromeRegistrationListeners();
 }
 
 bool
 ServiceWorkerRegistrationInfo::IsIdle() const
 {
   return !mActiveWorker || mActiveWorker->WorkerPrivate()->IsIdle();
 }
 
@@ -593,9 +633,25 @@ ServiceWorkerRegistrationInfo::GetLoadFl
 }
 
 void
 ServiceWorkerRegistrationInfo::SetLoadFlags(nsLoadFlags aLoadFlags)
 {
   mLoadFlags = aLoadFlags;
 }
 
+int64_t
+ServiceWorkerRegistrationInfo::GetLastUpdateTime() const
+{
+  return mLastUpdateTime;
+}
+
+void
+ServiceWorkerRegistrationInfo::SetLastUpdateTime(const int64_t aTime)
+{
+  if (aTime == 0) {
+    return;
+  }
+
+  mLastUpdateTime = aTime;
+}
+
 END_WORKERS_NAMESPACE
--- a/dom/workers/ServiceWorkerRegistrationInfo.h
+++ b/dom/workers/ServiceWorkerRegistrationInfo.h
@@ -20,17 +20,21 @@ class ServiceWorkerRegistrationInfo fina
 
   enum
   {
     NoUpdate,
     NeedTimeCheckAndUpdate,
     NeedUpdate
   } mUpdateState;
 
-  uint64_t mLastUpdateCheckTime;
+  // Timestamp to track SWR's last update time
+  PRTime mCreationTime;
+  TimeStamp mCreationTimeStamp;
+  // The time of update is 0, if SWR've never been updated yet.
+  PRTime mLastUpdateTime;
 
   nsLoadFlags mLoadFlags;
 
   RefPtr<ServiceWorkerInfo> mEvaluatingWorker;
   RefPtr<ServiceWorkerInfo> mActiveWorker;
   RefPtr<ServiceWorkerInfo> mWaitingWorker;
   RefPtr<ServiceWorkerInfo> mInstallingWorker;
 
@@ -180,16 +184,22 @@ public:
   IsIdle() const;
 
   nsLoadFlags
   GetLoadFlags() const;
 
   void
   SetLoadFlags(nsLoadFlags aLoadFlags);
 
+  int64_t
+  GetLastUpdateTime() const;
+
+  void
+  SetLastUpdateTime(const int64_t aTime);
+
 private:
   enum TransitionType {
     TransitionToNextState = 0,
     Invalidate
   };
 
   // Queued as a runnable from UpdateRegistrationStateProperties.
   void
--- a/dom/workers/test/gtest/TestReadWrite.cpp
+++ b/dom/workers/test/gtest/TestReadWrite.cpp
@@ -11,16 +11,18 @@
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIFile.h"
 #include "nsIOutputStream.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 
+#include "prtime.h"
+
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 class ServiceWorkerRegistrarTest : public ServiceWorkerRegistrar
 {
 public:
   ServiceWorkerRegistrarTest()
   {
@@ -152,24 +154,37 @@ TEST(ServiceWorkerRegistrar, TestReadDat
   nsAutoCString buffer(SERVICEWORKERREGISTRAR_VERSION "\n");
 
   buffer.Append("^appId=123&inBrowser=1\n");
   buffer.Append("scope 0\ncurrentWorkerURL 0\n");
   buffer.Append(SERVICEWORKERREGISTRAR_TRUE "\n");
   buffer.Append("cacheName 0\n");
   buffer.AppendInt(nsIRequest::LOAD_NORMAL, 16);
   buffer.Append("\n");
+  buffer.AppendInt(0);
+  buffer.Append("\n");
+  buffer.AppendInt(0);
+  buffer.Append("\n");
+  buffer.AppendInt(0);
+  buffer.Append("\n");
   buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
 
   buffer.Append("\n");
   buffer.Append("scope 1\ncurrentWorkerURL 1\n");
   buffer.Append(SERVICEWORKERREGISTRAR_FALSE "\n");
   buffer.Append("cacheName 1\n");
   buffer.AppendInt(nsIRequest::VALIDATE_ALWAYS, 16);
   buffer.Append("\n");
+  PRTime ts = PR_Now();
+  buffer.AppendInt(ts);
+  buffer.Append("\n");
+  buffer.AppendInt(ts);
+  buffer.Append("\n");
+  buffer.AppendInt(ts);
+  buffer.Append("\n");
   buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
 
   ASSERT_TRUE(CreateFile(buffer)) << "CreateFile should not fail";
 
   RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
 
   nsresult rv = swr->TestReadData();
   ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
@@ -186,31 +201,37 @@ TEST(ServiceWorkerRegistrar, TestReadDat
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIRequest::LOAD_NORMAL, data[0].loadFlags());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
   nsAutoCString suffix1;
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_FALSE(data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ((int64_t)ts, data[1].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)ts, data[1].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)ts, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestDeleteData)
 {
   ASSERT_TRUE(CreateFile(NS_LITERAL_CSTRING("Foobar"))) << "CreateFile should not fail";
 
   RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
 
@@ -235,16 +256,20 @@ TEST(ServiceWorkerRegistrar, TestWriteDa
 
       reg.scope() = nsPrintfCString("scope write %d", i);
       reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
       reg.currentWorkerHandlesFetch() = true;
       reg.cacheName() =
         NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
       reg.loadFlags() = nsIRequest::VALIDATE_ALWAYS;
 
+      reg.currentWorkerInstalledTime() = PR_Now();
+      reg.currentWorkerActivatedTime() = PR_Now();
+      reg.lastUpdateTime() = PR_Now();
+
       nsAutoCString spec;
       spec.AppendPrintf("spec write %d", i);
       reg.principal() =
         mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(i, i % 2),
                                            mozilla::void_t(), spec);
 
       swr->TestRegisterServiceWorker(reg);
     }
@@ -287,16 +312,20 @@ TEST(ServiceWorkerRegistrar, TestWriteDa
 
     ASSERT_EQ(true, data[i].currentWorkerHandlesFetch());
 
     test.Truncate();
     test.AppendPrintf("cacheName write %d", i);
     ASSERT_STREQ(test.get(), NS_ConvertUTF16toUTF8(data[i].cacheName()).get());
 
     ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[i].loadFlags());
+
+    ASSERT_NE((int64_t)0, data[i].currentWorkerInstalledTime());
+    ASSERT_NE((int64_t)0, data[i].currentWorkerActivatedTime());
+    ASSERT_NE((int64_t)0, data[i].lastUpdateTime());
   }
 }
 
 TEST(ServiceWorkerRegistrar, TestVersion2Migration)
 {
   nsAutoCString buffer("2" "\n");
 
   buffer.Append("^appId=123&inBrowser=1\n");
@@ -326,31 +355,37 @@ TEST(ServiceWorkerRegistrar, TestVersion
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("activeCache 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
   nsAutoCString suffix1;
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("activeCache 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestVersion3Migration)
 {
   nsAutoCString buffer("3" "\n");
 
   buffer.Append("^appId=123&inBrowser=1\n");
   buffer.Append("spec 0\nscope 0\ncurrentWorkerURL 0\ncacheName 0\n");
@@ -379,31 +414,37 @@ TEST(ServiceWorkerRegistrar, TestVersion
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
   nsAutoCString suffix1;
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestVersion4Migration)
 {
   nsAutoCString buffer("4" "\n");
 
   buffer.Append("^appId=123&inBrowser=1\n");
   buffer.Append("scope 0\ncurrentWorkerURL 0\ncacheName 0\n");
@@ -433,32 +474,38 @@ TEST(ServiceWorkerRegistrar, TestVersion
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   // default is true
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
   nsAutoCString suffix1;
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   // default is true
   ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestVersion5Migration)
 {
   nsAutoCString buffer("5" "\n");
 
   buffer.Append("^appId=123&inBrowser=1\n");
   buffer.Append("scope 0\ncurrentWorkerURL 0\n");
@@ -491,31 +538,104 @@ TEST(ServiceWorkerRegistrar, TestVersion
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
   nsAutoCString suffix1;
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_FALSE(data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
+}
+
+TEST(ServiceWorkerRegistrar, TestVersion6Migration)
+{
+  nsAutoCString buffer("6" "\n");
+
+  buffer.Append("^appId=123&inBrowser=1\n");
+  buffer.Append("scope 0\ncurrentWorkerURL 0\n");
+  buffer.Append(SERVICEWORKERREGISTRAR_TRUE "\n");
+  buffer.Append("cacheName 0\n");
+  buffer.AppendInt(nsIRequest::LOAD_NORMAL, 16);
+  buffer.Append("\n");
+  buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+  buffer.Append("\n");
+  buffer.Append("scope 1\ncurrentWorkerURL 1\n");
+  buffer.Append(SERVICEWORKERREGISTRAR_FALSE "\n");
+  buffer.Append("cacheName 1\n");
+  buffer.AppendInt(nsIRequest::VALIDATE_ALWAYS, 16);
+  buffer.Append("\n");
+  buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+  ASSERT_TRUE(CreateFile(buffer)) << "CreateFile should not fail";
+
+  RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+  ASSERT_EQ((uint32_t)2, data.Length()) << "2 entries should be found";
+
+  const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
+  ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+  const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
+
+  nsAutoCString suffix0;
+  cInfo0.attrs().CreateSuffix(suffix0);
+
+  ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
+  ASSERT_STREQ("scope 0", cInfo0.spec().get());
+  ASSERT_STREQ("scope 0", data[0].scope().get());
+  ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+  ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
+  ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+  ASSERT_EQ(nsIRequest::LOAD_NORMAL, data[0].loadFlags());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
+
+  const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
+  ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+  const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
+
+  nsAutoCString suffix1;
+  cInfo1.attrs().CreateSuffix(suffix1);
+
+  ASSERT_STREQ("", suffix1.get());
+  ASSERT_STREQ("scope 1", cInfo1.spec().get());
+  ASSERT_STREQ("scope 1", data[1].scope().get());
+  ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+  ASSERT_FALSE(data[1].currentWorkerHandlesFetch());
+  ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestDedupeRead)
 {
   nsAutoCString buffer("3" "\n");
 
   // unique entries
   buffer.Append("^appId=123&inBrowser=1\n");
@@ -558,31 +678,37 @@ TEST(ServiceWorkerRegistrar, TestDedupeR
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
   nsAutoCString suffix1;
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestDedupeWrite)
 {
   {
     RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
 
     for (int i = 0; i < 10; ++i) {
@@ -630,16 +756,19 @@ TEST(ServiceWorkerRegistrar, TestDedupeW
   ASSERT_STREQ(expectSuffix.get(), suffix.get());
   ASSERT_STREQ("scope write dedupe", cInfo.spec().get());
   ASSERT_STREQ("scope write dedupe", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL write 9", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName write 9",
                NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 }
 
 int main(int argc, char** argv) {
   ::testing::InitGoogleTest(&argc, argv);
 
   int rv = RUN_ALL_TESTS();
   return rv;
 }
--- a/dom/workers/test/serviceworkers/chrome.ini
+++ b/dom/workers/test/serviceworkers/chrome.ini
@@ -8,12 +8,13 @@ support-files =
   serviceworker.html
   serviceworkerinfo_iframe.html
   serviceworkermanager_iframe.html
   serviceworkerregistrationinfo_iframe.html
   worker.js
   worker2.js
 
 [test_devtools_serviceworker_interception.html]
+[test_devtools_track_serviceworker_time.html]
 [test_privateBrowsing.html]
 [test_serviceworkerinfo.xul]
 [test_serviceworkermanager.xul]
 [test_serviceworkerregistrationinfo.xul]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_devtools_track_serviceworker_time.html
@@ -0,0 +1,218 @@
+<html>
+<head>
+  <title>Bug 1251238 - track service worker install time</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+</head>
+<iframe id="iframe"></iframe>
+<body>
+
+<script type="text/javascript">
+
+const State = {
+  BYTECHECK: -1,
+  INSTALLING: 0,
+  INSTALLED: 1,
+  ACTIVATING: 2,
+  ACTIVATED: 3,
+  REDUNDANT: 4
+};
+
+let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+let swm = Cc["@mozilla.org/serviceworkers/manager;1"].
+          getService(Ci.nsIServiceWorkerManager);
+
+let EXAMPLE_URL = "https://example.com/chrome/dom/workers/test/serviceworkers/";
+
+let swrlistener = null;
+let registrationInfo = null;
+
+// Use it to keep the sw after unregistration.
+let astrayServiceWorkerInfo = null;
+
+let expectedResults = [
+  {
+    // Speacial state for verifying update since we will do the byte-check
+    // first.
+    state: State.BYTECHECK, installedTimeRecorded: false,
+    activatedTimeRecorded: false, redundantTimeRecorded: false
+  },
+  {
+    state: State.INSTALLING, installedTimeRecorded: false,
+    activatedTimeRecorded: false, redundantTimeRecorded: false
+  },
+  {
+    state: State.INSTALLED, installedTimeRecorded: true,
+    activatedTimeRecorded: false, redundantTimeRecorded: false
+  },
+  {
+    state: State.ACTIVATING, installedTimeRecorded: true,
+    activatedTimeRecorded: false, redundantTimeRecorded: false
+  },
+  {
+    state: State.ACTIVATED, installedTimeRecorded: true,
+    activatedTimeRecorded: true, redundantTimeRecorded: false
+  },
+  {
+    state: State.REDUNDANT, installedTimeRecorded: true,
+    activatedTimeRecorded: true, redundantTimeRecorded: true
+  }
+];
+
+function waitForRegister(aScope, aCallback) {
+  return new Promise(function (aResolve) {
+    let listener = {
+      onRegister: function (aRegistration) {
+        if (aRegistration.scope !== aScope) {
+          return;
+        }
+        swm.removeListener(listener);
+        registrationInfo = aRegistration;
+        aResolve();
+      }
+    };
+    swm.addListener(listener);
+  });
+}
+
+function waitForUnregister(aScope) {
+  return new Promise(function (aResolve) {
+    let listener = {
+      onUnregister: function (aRegistration) {
+        if (aRegistration.scope !== aScope) {
+          return;
+        }
+        swm.removeListener(listener);
+        aResolve();
+      }
+    };
+    swm.addListener(listener);
+  });
+}
+
+function register() {
+  info("Register a ServiceWorker in the iframe");
+
+  let iframe = document.querySelector("iframe");
+  iframe.src = EXAMPLE_URL + "serviceworkerinfo_iframe.html";
+
+  let promise = new Promise(function(aResolve) {
+    iframe.onload = aResolve;
+  });
+
+  return promise.then(function() {
+    iframe.contentWindow.postMessage("register", "*");
+    return waitForRegister(EXAMPLE_URL);
+  })
+}
+
+function verifyServiceWorkTime(aSWRInfo) {
+  let expectedResult = expectedResults.shift();
+  ok(!!expectedResult, "We should be able to get test from expectedResults");
+
+  info("Check the ServiceWorker time in its state is " + expectedResult.state);
+
+  // Get serviceWorkerInfo from swrInfo or get the astray one which we hold.
+  let swInfo = aSWRInfo.installingWorker ||
+               aSWRInfo.waitingWorker ||
+               aSWRInfo.activeWorker ||
+               astrayServiceWorkerInfo;
+
+  ok(!!aSWRInfo.lastUpdateTime,
+     "We should do the byte-check and update the update timeStamp");
+
+  if (!swInfo) {
+    is(expectedResult.state, State.BYTECHECK,
+       "We shouldn't get sw when we are notified for frist time updating");
+    return;
+  }
+
+  ok(!!swInfo);
+
+  is(expectedResult.state, swInfo.state,
+     "The service worker's state should be " + swInfo.state + ", but got " +
+     expectedResult.state);
+
+  is(expectedResult.installedTimeRecorded, !!swInfo.installedTime,
+     "InstalledTime should be recorded when their state is greater than " +
+     "INSTALLING");
+
+  is(expectedResult.activatedTimeRecorded, !!swInfo.activatedTime,
+     "ActivatedTime should be recorded when their state is greater than " +
+     "ACTIVATING");
+
+  is(expectedResult.redundantTimeRecorded, !!swInfo.redundantTime,
+     "RedundantTime should be recorded when their state is REDUNDANT");
+
+  // We need to hold sw to avoid losing it since we'll unregister the swr later.
+  if (expectedResult.state === State.ACTIVATED) {
+    astrayServiceWorkerInfo = aSWRInfo.activeWorker;
+  }
+}
+
+function testServiceWorkerInfo() {
+  info("Listen onChange event and verify service worker's information");
+
+  let promise_resolve;
+  let promise = new Promise(aResolve => promise_resolve = aResolve);
+
+  let counter = 0;
+  swrlistener = {
+    onChange: () => {
+      verifyServiceWorkTime(registrationInfo)
+      counter++;
+
+      // Resolve the promise after sw is activated.
+      if (counter >= State.ACTIVATED) {
+        promise_resolve();
+      }
+    }
+  };
+
+  registrationInfo.addListener(swrlistener);
+
+  return promise;
+}
+
+function unregister() {
+  info("Unregister the ServiceWorker");
+
+  let iframe = document.querySelector("iframe");
+  iframe.contentWindow.postMessage("unregister", "*");
+  return waitForUnregister(EXAMPLE_URL);
+}
+
+function cleanAll() {
+  return new Promise((aResolve, aReject) => {
+    is(expectedResults.length, 0, "All the tests should be tested");
+
+    registrationInfo.removeListener(swrlistener);
+
+    swm = null;
+    swrlistener = null;
+    registrationInfo = null;
+    astrayServiceWorkerInfo = null;
+    aResolve();
+  })
+}
+
+function runTest() {
+  return Promise.resolve()
+    .then(register)
+    .then(testServiceWorkerInfo)
+    .then(unregister)
+    .catch(aError => ok(false, "Some test failed with error " + aError))
+    .then(cleanAll)
+    .then(SimpleTest.finish);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [
+  ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+  ["dom.serviceWorkers.enabled", true],
+  ["dom.serviceWorkers.testing.enabled", true]
+]}, runTest);
+
+</script>
+</body>
+</html>
--- a/dom/workers/test/serviceworkers/test_serviceworkerregistrationinfo.xul
+++ b/dom/workers/test/serviceworkers/test_serviceworkerregistrationinfo.xul
@@ -41,58 +41,88 @@
                "listeners when its state changes.");
           promise = waitForRegister(EXAMPLE_URL, function (registration) {
             is(registration.scriptSpec, "");
             ok(registration.installingWorker === null);
             ok(registration.waitingWorker === null);
             ok(registration.activeWorker === null);
 
             return waitForServiceWorkerRegistrationChange(registration, function  () {
-              is(registration.scriptSpec, EXAMPLE_URL + "worker.js");
-              ok(registration.installingWorker !== null);
-              is(registration.installingWorker.scriptSpec, EXAMPLE_URL + "worker.js");
+              // Got change event for updating (byte-check)
+              ok(registration.installingWorker === null);
               ok(registration.waitingWorker === null);
               ok(registration.activeWorker === null);
 
-              return waitForServiceWorkerRegistrationChange(registration, function () {
-                ok(registration.installingWorker === null);
-                ok(registration.waitingWorker !== null);
+              return waitForServiceWorkerRegistrationChange(registration, function  () {
+                is(registration.scriptSpec, EXAMPLE_URL + "worker.js");
+                ok(registration.installingWorker !== null);
+                is(registration.installingWorker.scriptSpec, EXAMPLE_URL + "worker.js");
+                ok(registration.waitingWorker === null);
                 ok(registration.activeWorker === null);
 
                 return waitForServiceWorkerRegistrationChange(registration, function () {
                   ok(registration.installingWorker === null);
-                  ok(registration.waitingWorker === null);
-                  ok(registration.activeWorker !== null);
+                  ok(registration.waitingWorker !== null);
+                  ok(registration.activeWorker === null);
+
+                  return waitForServiceWorkerRegistrationChange(registration, function () {
+                    // Activating
+                    ok(registration.installingWorker === null);
+                    ok(registration.waitingWorker === null);
+                    ok(registration.activeWorker !== null);
 
-                  return registration;
+                    return waitForServiceWorkerRegistrationChange(registration, function () {
+                      // Activated
+                      ok(registration.installingWorker === null);
+                      ok(registration.waitingWorker === null);
+                      ok(registration.activeWorker !== null);
+
+                      return registration;
+                    });
+                  });
                 });
               });
             });
           });
           iframe.contentWindow.postMessage("register", "*");
           let registration = yield promise;
 
           promise = waitForServiceWorkerRegistrationChange(registration, function () {
-            is(registration.scriptSpec, EXAMPLE_URL + "worker2.js");
-            ok(registration.installingWorker !== null);
-            is(registration.installingWorker.scriptSpec, EXAMPLE_URL + "worker2.js");
+            // Got change event for updating (byte-check)
+            ok(registration.installingWorker === null);
             ok(registration.waitingWorker === null);
             ok(registration.activeWorker !== null);
 
             return waitForServiceWorkerRegistrationChange(registration, function () {
-              ok(registration.installingWorker === null);
-              ok(registration.waitingWorker !== null);
+              is(registration.scriptSpec, EXAMPLE_URL + "worker2.js");
+              ok(registration.installingWorker !== null);
+              is(registration.installingWorker.scriptSpec, EXAMPLE_URL + "worker2.js");
+              ok(registration.waitingWorker === null);
               ok(registration.activeWorker !== null);
 
               return waitForServiceWorkerRegistrationChange(registration, function () {
                 ok(registration.installingWorker === null);
-                ok(registration.waitingWorker === null);
+                ok(registration.waitingWorker !== null);
                 ok(registration.activeWorker !== null);
 
-                return registration;
+                return waitForServiceWorkerRegistrationChange(registration, function () {
+                  // Activating
+                  ok(registration.installingWorker === null);
+                  ok(registration.waitingWorker === null);
+                  ok(registration.activeWorker !== null);
+
+                  return waitForServiceWorkerRegistrationChange(registration, function () {
+                    // Activated
+                    ok(registration.installingWorker === null);
+                    ok(registration.waitingWorker === null);
+                    ok(registration.activeWorker !== null);
+
+                    return registration;
+                  });
+                });
               });
             });
           });
           iframe.contentWindow.postMessage("register", "*");
           yield promise;
 
           iframe.contentWindow.postMessage("unregister", "*");
           yield waitForUnregister(EXAMPLE_URL);
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -398,17 +398,25 @@ XULDocument::StartDocumentLoad(const cha
     mDocumentLoadGroup = do_GetWeakReference(aLoadGroup);
 
     mChannel = aChannel;
 
     // Get the URI.  Note that this should match nsDocShell::OnLoadingSite
     nsresult rv =
         NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI));
     NS_ENSURE_SUCCESS(rv, rv);
-    
+
+    mOriginalURI = mDocumentURI;
+
+    // Get the document's principal
+    nsCOMPtr<nsIPrincipal> principal;
+    nsContentUtils::GetSecurityManager()->
+        GetChannelResultPrincipal(mChannel, getter_AddRefs(principal));
+    principal = MaybeDowngradePrincipal(principal);
+
     ResetStylesheetsToURI(mDocumentURI);
 
     RetrieveRelevantHeaders(aChannel);
 
     // Look in the chrome cache: we've got this puppy loaded
     // already.
     nsXULPrototypeDocument* proto = IsChromeURI(mDocumentURI) ?
             nsXULPrototypeCache::GetInstance()->GetPrototype(mDocumentURI) :
@@ -455,18 +463,18 @@ XULDocument::StartDocumentLoad(const cha
         bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
         bool fillXULCache = (useXULCache && IsChromeURI(mDocumentURI));
 
 
         // It's just a vanilla document load. Create a parser to deal
         // with the stream n' stuff.
 
         nsCOMPtr<nsIParser> parser;
-        rv = PrepareToLoad(aContainer, aCommand, aChannel, aLoadGroup,
-                           getter_AddRefs(parser));
+        rv = PrepareToLoadPrototype(mDocumentURI, aCommand, principal,
+                                    getter_AddRefs(parser));
         if (NS_FAILED(rv)) return rv;
 
         // Predicate mIsWritingFastLoad on the XUL cache being enabled,
         // so we don't have to re-check whether the cache is enabled all
         // the time.
         mIsWritingFastLoad = useXULCache;
 
         nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(parser, &rv);
@@ -1967,31 +1975,16 @@ XULDocument::MatchAttribute(Element* aEl
                                       *attrValue, eCaseMatters);
         }
     }
 
     return false;
 }
 
 nsresult
-XULDocument::PrepareToLoad(nsISupports* aContainer,
-                           const char* aCommand,
-                           nsIChannel* aChannel,
-                           nsILoadGroup* aLoadGroup,
-                           nsIParser** aResult)
-{
-    // Get the document's principal
-    nsCOMPtr<nsIPrincipal> principal;
-    nsContentUtils::GetSecurityManager()->
-        GetChannelResultPrincipal(aChannel, getter_AddRefs(principal));
-    return PrepareToLoadPrototype(mDocumentURI, aCommand, principal, aResult);
-}
-
-
-nsresult
 XULDocument::PrepareToLoadPrototype(nsIURI* aURI, const char* aCommand,
                                     nsIPrincipal* aDocumentPrincipal,
                                     nsIParser** aResult)
 {
     nsresult rv;
 
     // Create a new prototype document.
     rv = NS_NewXULPrototypeDocument(getter_AddRefs(mCurrentPrototype));
@@ -4433,16 +4426,17 @@ XULDocument::ParserObserver::OnStartRequ
     // Guard against buggy channels calling OnStartRequest multiple times.
     if (mPrototype) {
         nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
         nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
         if (channel && secMan) {
             nsCOMPtr<nsIPrincipal> principal;
             secMan->GetChannelResultPrincipal(channel, getter_AddRefs(principal));
 
+            principal = mDocument->MaybeDowngradePrincipal(principal);
             // Failure there is ok -- it'll just set a (safe) null principal
             mPrototype->SetDocumentPrincipal(principal);
         }
 
         // Make sure to avoid cycles
         mPrototype = nullptr;
     }
         
--- a/editor/composer/res/EditorOverride.css
+++ b/editor/composer/res/EditorOverride.css
@@ -40,17 +40,16 @@ a:visited, a:active {
 a:link img, a:visited img {
   -moz-user-input: none;
 }
 
 /* We suppress user/author's prefs for link underline, 
    so we must set explicitly. This isn't good!
 */
 a:link {
-  text-decoration: underline -moz-anchor-decoration;
   color: -moz-hyperlinktext;
 }
 
 /* Allow double-clicks on these widgets to open properties dialogs
    XXX except when the widget has disabled attribute */
 input, button, textarea {
   -moz-user-select: all !important;
   -moz-user-input: auto !important;
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -1192,60 +1192,69 @@ VRSystemManagerOculus::HandleInput()
   for (uint32_t i = 0; i < mOculusController.Length(); ++i) {
     controller = mOculusController[i];
     const GamepadHand hand = controller->GetHand();
     const uint32_t handIdx = static_cast<uint32_t>(hand) - 1;
     uint32_t buttonIdx = 0;
 
     switch (hand) {
       case dom::GamepadHand::Left:
-        HandleButtonPress(i, buttonIdx, ovrButton_LThumb, inputState.Buttons);
+        HandleButtonPress(i, buttonIdx, ovrButton_LThumb, inputState.Buttons,
+                          inputState.Touches);
         ++buttonIdx;
-        HandleTriggerPress(i, buttonIdx, inputState.IndexTrigger[handIdx]);
+        HandleIndexTriggerPress(i, buttonIdx, ovrTouch_LIndexTrigger,
+                                inputState.IndexTrigger[handIdx], inputState.Touches);
         ++buttonIdx;
-        HandleTriggerPress(i, buttonIdx, inputState.HandTrigger[handIdx]);
+        HandleHandTriggerPress(i, buttonIdx, inputState.HandTrigger[handIdx]);
         ++buttonIdx;
-        HandleButtonPress(i, buttonIdx, ovrButton_X, inputState.Buttons);
+        HandleButtonPress(i, buttonIdx, ovrButton_X, inputState.Buttons,
+                          inputState.Touches);
         ++buttonIdx;
-        HandleButtonPress(i, buttonIdx, ovrButton_Y, inputState.Buttons);
+        HandleButtonPress(i, buttonIdx, ovrButton_Y, inputState.Buttons,
+                          inputState.Touches);
         ++buttonIdx;
         HandleTouchEvent(i, buttonIdx, ovrTouch_LThumbRest, inputState.Touches);
         ++buttonIdx;
         break;
       case dom::GamepadHand::Right:
-        HandleButtonPress(i, buttonIdx, ovrButton_RThumb, inputState.Buttons);
+        HandleButtonPress(i, buttonIdx, ovrButton_RThumb, inputState.Buttons,
+                          inputState.Touches);
         ++buttonIdx;
-        HandleTriggerPress(i, buttonIdx, inputState.IndexTrigger[handIdx]);
+        HandleIndexTriggerPress(i, buttonIdx, ovrTouch_RIndexTrigger,
+                                inputState.IndexTrigger[handIdx], inputState.Touches);
         ++buttonIdx;
-        HandleTriggerPress(i, buttonIdx, inputState.HandTrigger[handIdx]);
+        HandleHandTriggerPress(i, buttonIdx, inputState.HandTrigger[handIdx]);
         ++buttonIdx;
-        HandleButtonPress(i, buttonIdx, ovrButton_A, inputState.Buttons);
+        HandleButtonPress(i, buttonIdx, ovrButton_A, inputState.Buttons,
+                          inputState.Touches);
         ++buttonIdx;
-        HandleButtonPress(i, buttonIdx, ovrButton_B, inputState.Buttons);
+        HandleButtonPress(i, buttonIdx, ovrButton_B, inputState.Buttons,
+                          inputState.Touches);
         ++buttonIdx;
         HandleTouchEvent(i, buttonIdx, ovrTouch_RThumbRest, inputState.Touches);
         ++buttonIdx;
         break;
       default:
         MOZ_ASSERT(false);
         break;
     }
     controller->SetButtonPressed(inputState.Buttons);
+    controller->SetButtonTouched(inputState.Touches);
 
     axis = static_cast<uint32_t>(OculusControllerAxisType::ThumbstickXAxis);
     HandleAxisMove(i, axis, inputState.Thumbstick[i].x);
 
     axis = static_cast<uint32_t>(OculusControllerAxisType::ThumbstickYAxis);
     HandleAxisMove(i, axis, -inputState.Thumbstick[i].y);
 
     // Start to process pose
     ovrTrackingState state = ovr_GetTrackingState(mSession, 0.0, false);
+
     // HandPoses is ordered by ovrControllerType_LTouch and ovrControllerType_RTouch,
     // therefore, we can't get its state by the index of mOculusController.
-
     ovrPoseStatef& pose(state.HandPoses[handIdx]);
     GamepadPoseState poseState;
 
     if (state.HandStatusFlags[handIdx] & ovrStatus_OrientationTracked) {
       poseState.flags |= GamepadCapabilityFlags::Cap_Orientation;
       poseState.orientation[0] = pose.ThePose.Orientation.x;
       poseState.orientation[1] = pose.ThePose.Orientation.y;
       poseState.orientation[2] = pose.ThePose.Orientation.z;
@@ -1279,68 +1288,86 @@ VRSystemManagerOculus::HandleInput()
     HandlePoseTracking(i, poseState, controller);
   }
 }
 
 void
 VRSystemManagerOculus::HandleButtonPress(uint32_t aControllerIdx,
                                          uint32_t aButton,
                                          uint64_t aButtonMask,
-                                         uint64_t aButtonPressed)
+                                         uint64_t aButtonPressed,
+                                         uint64_t aButtonTouched)
 {
   RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
   MOZ_ASSERT(controller);
-  const uint64_t diff = (controller->GetButtonPressed() ^ aButtonPressed);
+  const uint64_t pressedDiff = (controller->GetButtonPressed() ^ aButtonPressed);
+  const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched);
+
+  if (!pressedDiff && !touchedDiff) {
+    return;
+  }
 
-  if (diff & aButtonMask) {
-    // TODO: Bug 1336003 for button touched support.
+  if (pressedDiff & aButtonMask ||
+      touchedDiff & aButtonMask) {
+    // diff & (aButtonPressed, aButtonTouched) would be true while a new button pressed or
+    // touched event, otherwise it is an old event and needs to notify
+    // the button has been released.
     NewButtonEvent(aControllerIdx, aButton, aButtonMask & aButtonPressed,
-                   aButtonMask & aButtonPressed,
+                   aButtonMask & aButtonTouched,
                    (aButtonMask & aButtonPressed) ? 1.0L : 0.0L);
   }
 }
 
 void
-VRSystemManagerOculus::HandleTriggerPress(uint32_t aControllerIdx, uint32_t aButton,
-                                          float aValue)
+VRSystemManagerOculus::HandleIndexTriggerPress(uint32_t aControllerIdx,
+                                               uint32_t aButton,
+                                               uint64_t aTouchMask,
+                                               float aValue,
+                                               uint64_t aButtonTouched)
 {
   RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
   MOZ_ASSERT(controller);
-  const uint32_t indexTrigger = static_cast<const uint32_t>
-                                (OculusLeftControllerButtonType::IndexTrigger);
-  const uint32_t handTrigger =  static_cast<const uint32_t>
-                                (OculusLeftControllerButtonType::HandTrigger);
-  float oldValue = 0.0f;
+  const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched);
+  const float oldValue = controller->GetIndexTrigger();
 
   // Avoid sending duplicated events in IPC channels.
-  if (aButton == indexTrigger) {
-    oldValue = controller->GetIndexTrigger();
-    if (oldValue == aValue) {
-      return;
-    }
+  if ((oldValue != aValue) ||
+      (touchedDiff & aTouchMask)) {
+    NewButtonEvent(aControllerIdx, aButton, aValue > 0.1f, aTouchMask & aButtonTouched, aValue);
     controller->SetIndexTrigger(aValue);
-  } else if (aButton == handTrigger) {
-    oldValue = controller->GetHandTrigger();
-    if (oldValue == aValue) {
-      return;
-    }
+  }
+}
+
+void
+VRSystemManagerOculus::HandleHandTriggerPress(uint32_t aControllerIdx,
+                                              uint32_t aButton,
+                                              float aValue)
+{
+  RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
+  MOZ_ASSERT(controller);
+  const float oldValue = controller->GetHandTrigger();
+
+  // Avoid sending duplicated events in IPC channels.
+  if (oldValue != aValue) {
+    NewButtonEvent(aControllerIdx, aButton, aValue > 0.1f, aValue > 0.1f, aValue);
     controller->SetHandTrigger(aValue);
-  } else {
-    MOZ_ASSERT(false, "We only support indexTrigger and handTrigger in Oculus.");
   }
-
-  // TODO: Bug 1336003 for button touched support.
-  NewButtonEvent(aControllerIdx, aButton, aValue > 0.1f, aValue > 0.1f, aValue);
 }
 
 void
 VRSystemManagerOculus::HandleTouchEvent(uint32_t aControllerIdx, uint32_t aButton,
-                                        uint64_t aTouchMask, uint64_t aTouched)
+                                        uint64_t aTouchMask, uint64_t aButtonTouched)
 {
-  // TODO: Bug 1336003
+  RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
+  MOZ_ASSERT(controller);
+  const uint64_t touchedDiff = (controller->GetButtonTouched() ^ aButtonTouched);
+
+  if (touchedDiff & aTouchMask) {
+    NewButtonEvent(aControllerIdx, aButton, false, aTouchMask & aButtonTouched, 0.0f);
+  }
 }
 
 void
 VRSystemManagerOculus::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
                                       float aValue)
 {
   RefPtr<impl::VRControllerOculus> controller(mOculusController[aControllerIdx]);
   MOZ_ASSERT(controller);
--- a/gfx/vr/gfxVROculus.h
+++ b/gfx/vr/gfxVROculus.h
@@ -147,34 +147,37 @@ public:
   virtual void RemoveControllers() override;
   virtual void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                              double aIntensity, double aDuration, uint32_t aPromiseID) override;
   virtual void StopVibrateHaptic(uint32_t aControllerIdx) override;
 
 protected:
   VRSystemManagerOculus()
     : mOvrLib(nullptr), mSession(nullptr), mStarted(false)
-  { }
+  {}
 
   bool Startup();
   bool LoadOvrLib();
   void UnloadOvrLib();
 
 private:
   void HandleButtonPress(uint32_t aControllerIdx,
                          uint32_t aButton,
                          uint64_t aButtonMask,
-                         uint64_t aButtonPressed);
+                         uint64_t aButtonPressed,
+                         uint64_t aButtonTouched);
   void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
                       float aValue);
   void HandlePoseTracking(uint32_t aControllerIdx,
                           const dom::GamepadPoseState& aPose,
                           VRControllerHost* aController);
-  void HandleTriggerPress(uint32_t aControllerIdx, uint32_t aButton,
-                          float aValue);
+  void HandleIndexTriggerPress(uint32_t aControllerIdx, uint32_t aButton,
+                               uint64_t aTouchMask, float aValue, uint64_t aButtonTouched);
+  void HandleHandTriggerPress(uint32_t aControllerIdx, uint32_t aButton,
+                              float aValue);
   void HandleTouchEvent(uint32_t aControllerIdx, uint32_t aButton,
                         uint64_t aTouchMask, uint64_t aTouched);
   PRLibrary* mOvrLib;
   RefPtr<impl::VRDisplayOculus> mHMDInfo;
   nsTArray<RefPtr<impl::VRControllerOculus>> mOculusController;
   RefPtr<nsIThread> mOculusThread;
   ovrSession mSession;
   bool mStarted;
--- a/intl/locale/DateTimeFormat.h
+++ b/intl/locale/DateTimeFormat.h
@@ -42,16 +42,18 @@ public:
 
 private:
   DateTimeFormat() = delete;
 
   static nsresult Initialize();
 
   FRIEND_TEST(DateTimeFormat, FormatPRExplodedTime);
   FRIEND_TEST(DateTimeFormat, DateFormatSelectors);
+  FRIEND_TEST(DateTimeFormat, FormatPRExplodedTimeForeign);
+  FRIEND_TEST(DateTimeFormat, DateFormatSelectorsForeign);
 
 #ifdef ENABLE_INTL_API
   // performs a locale sensitive date formatting operation on the UDate parameter
   static nsresult FormatUDateTime(const nsDateFormatSelector aDateFormatSelector,
                                   const nsTimeFormatSelector aTimeFormatSelector,
                                   const UDate aUDateTime,
                                   const PRTimeParameters* aTimeParameters,
                                   nsAString& aStringOut);
--- a/intl/locale/tests/gtest/TestDateTimeFormat.cpp
+++ b/intl/locale/tests/gtest/TestDateTimeFormat.cpp
@@ -108,9 +108,114 @@ TEST(DateTimeFormat, DateFormatSelectors
   ASSERT_TRUE(NS_SUCCEEDED(rv));
   ASSERT_STREQ("Thu, 12:00 AM", nt(NS_ConvertUTF16toUTF8(formattedTime)).get());
 
   rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatWeekday, kTimeFormatSeconds, &prExplodedTime, formattedTime);
   ASSERT_TRUE(NS_SUCCEEDED(rv));
   ASSERT_STREQ("Thu, 12:00:00 AM", nt(NS_ConvertUTF16toUTF8(formattedTime)).get());
 }
 
+// Normalise time.
+static nsAutoCString ntd(nsAutoCString aDatetime)
+{
+  nsAutoCString datetime = aDatetime;
+
+  // Strip trailing " GMT" (found on Mac/Linux).
+  int32_t ind = datetime.Find(" GMT");
+  if (ind != kNotFound)
+    datetime.Truncate(ind);
+
+  // Strip leading "Donnerstag, " or "Mittwoch, " (found on Windows).
+  ind = datetime.Find("Donnerstag, ");
+  if (ind == 0)
+    datetime.Replace(0, 12, "");
+  ind = datetime.Find("Mittwoch, ");
+  if (ind == 0)
+    datetime.Replace(0, 10, "");
+
+  return datetime;
 }
+
+TEST(DateTimeFormat, FormatPRExplodedTimeForeign) {
+  PRTime prTime = 0;
+  PRExplodedTime prExplodedTime;
+  PR_ExplodeTime(prTime, PR_GMTParameters, &prExplodedTime);
+
+  mozilla::DateTimeFormat::mLocale = new nsCString("de-DE");
+
+  nsAutoString formattedTime;
+  nsresult rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatLong, kTimeFormatSeconds, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("1. Januar 1970, 00:00:00", ntd(NS_ConvertUTF16toUTF8(formattedTime)).get());
+
+  prExplodedTime = { 0, 0, 19, 0, 1, 0, 1970, 4, 0, { (19 * 60), 0 } };
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatLong, kTimeFormatSeconds, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("1. Januar 1970, 00:19:00", ntd(NS_ConvertUTF16toUTF8(formattedTime)).get());
+
+  prExplodedTime = { 0, 0, 0, 7, 1, 0, 1970, 4, 0, { (6 * 60 * 60), (1 * 60 * 60) } };
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatLong, kTimeFormatSeconds, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("1. Januar 1970, 07:00:00", ntd(NS_ConvertUTF16toUTF8(formattedTime)).get());
+
+  prExplodedTime = { 0, 0, 29, 11, 1, 0, 1970, 4, 0, { (10 * 60 * 60) + (29 * 60), (1 * 60 * 60) } };
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatLong, kTimeFormatSeconds, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("1. Januar 1970, 11:29:00", ntd(NS_ConvertUTF16toUTF8(formattedTime)).get());
+
+  prExplodedTime = { 0, 0, 37, 23, 31, 11, 1969, 3, 364, { -(23 * 60), 0 } };
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatLong, kTimeFormatSeconds, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("31. Dezember 1969, 23:37:00", ntd(NS_ConvertUTF16toUTF8(formattedTime)).get());
+
+  prExplodedTime = { 0, 0, 0, 17, 31, 11, 1969, 3, 364, { -(7 * 60 * 60), 0 } };
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatLong, kTimeFormatSeconds, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("31. Dezember 1969, 17:00:00", ntd(NS_ConvertUTF16toUTF8(formattedTime)).get());
+
+  prExplodedTime = { 0, 0, 47, 14, 31, 11, 1969, 3, 364, { -((10 * 60 * 60) + (13 * 60)), (1 * 60 * 60) } };
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatLong, kTimeFormatSeconds, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("31. Dezember 1969, 14:47:00", ntd(NS_ConvertUTF16toUTF8(formattedTime)).get());
+}
+
+TEST(DateTimeFormat, DateFormatSelectorsForeign) {
+  PRTime prTime = 0;
+  PRExplodedTime prExplodedTime;
+  PR_ExplodeTime(prTime, PR_GMTParameters, &prExplodedTime);
+
+  mozilla::DateTimeFormat::mLocale = new nsCString("de-DE");
+
+  nsAutoString formattedTime;
+  nsresult rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatYearMonth, kTimeFormatNone, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("01.1970", NS_ConvertUTF16toUTF8(formattedTime).get());
+
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatYearMonthLong, kTimeFormatNone, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("Januar 1970", NS_ConvertUTF16toUTF8(formattedTime).get());
+
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatMonthLong, kTimeFormatNone, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("Januar", NS_ConvertUTF16toUTF8(formattedTime).get());
+
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatYearMonth, kTimeFormatNoSeconds, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("01.1970, 00:00", ntd(NS_ConvertUTF16toUTF8(formattedTime)).get());
+
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatYearMonth, kTimeFormatSeconds, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("01.1970, 00:00:00", ntd(NS_ConvertUTF16toUTF8(formattedTime)).get());
+
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatWeekday, kTimeFormatNone, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("Do", NS_ConvertUTF16toUTF8(formattedTime).get());
+
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatWeekday, kTimeFormatNoSeconds, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("Do, 00:00", ntd(NS_ConvertUTF16toUTF8(formattedTime)).get());
+
+  rv = mozilla::DateTimeFormat::FormatPRExplodedTime(kDateFormatWeekday, kTimeFormatSeconds, &prExplodedTime, formattedTime);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  ASSERT_STREQ("Do, 00:00:00", ntd(NS_ConvertUTF16toUTF8(formattedTime)).get());
+}
+
+}
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -115,17 +115,17 @@ static const uint32_t MAX_MESSAGE_SIZE =
  */
 template <typename E, typename EnumValidator>
 struct EnumSerializer {
   typedef E paramType;
   typedef typename mozilla::UnsignedStdintTypeForSize<sizeof(paramType)>::Type
           uintParamType;
 
   static void Write(Message* aMsg, const paramType& aValue) {
-    MOZ_ASSERT(EnumValidator::IsLegalValue(aValue));
+    MOZ_RELEASE_ASSERT(EnumValidator::IsLegalValue(aValue));
     WriteParam(aMsg, uintParamType(aValue));
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) {
     uintParamType value;
     if (!ReadParam(aMsg, aIter, &value)) {
 #ifdef MOZ_CRASHREPORTER
       CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCReadErrorReason"),
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -574,187 +574,194 @@ js::regexp_clone(JSContext* cx, unsigned
 
     regexp->initAndZeroLastIndex(sourceAtom, flags, cx);
 
     args.rval().setObject(*regexp);
     return true;
 }
 
 MOZ_ALWAYS_INLINE bool
-IsRegExpInstanceOrPrototype(HandleValue v)
+IsRegExpPrototype(HandleValue v)
 {
-    if (!v.isObject())
+    if (IsRegExpObject(v) || !v.isObject())
         return false;
 
+    // Note: The prototype shares its JSClass with instances.
     return StandardProtoKeyOrNull(&v.toObject()) == JSProto_RegExp;
 }
 
 // ES 2017 draft 21.2.5.4.
 MOZ_ALWAYS_INLINE bool
 regexp_global_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv()));
-
-    // Step 3.a.
-    if (!IsRegExpObject(args.thisv())) {
-        args.rval().setUndefined();
-        return true;
-    }
+    MOZ_ASSERT(IsRegExpObject(args.thisv()));
 
     // Steps 4-6.
-    Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
+    RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
     args.rval().setBoolean(reObj->global());
     return true;
 }
 
 bool
 js::regexp_global(JSContext* cx, unsigned argc, JS::Value* vp)
 {
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Step 3.a.
+    if (IsRegExpPrototype(args.thisv())) {
+        args.rval().setUndefined();
+        return true;
+    }
+
     // Steps 1-3.
-    CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_global_impl>(cx, args);
+    return CallNonGenericMethod<IsRegExpObject, regexp_global_impl>(cx, args);
 }
 
 // ES 2017 draft 21.2.5.5.
 MOZ_ALWAYS_INLINE bool
 regexp_ignoreCase_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv()));
-
-    // Step 3.a
-    if (!IsRegExpObject(args.thisv())) {
-        args.rval().setUndefined();
-        return true;
-    }
+    MOZ_ASSERT(IsRegExpObject(args.thisv()));
 
     // Steps 4-6.
-    Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
+    RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
     args.rval().setBoolean(reObj->ignoreCase());
     return true;
 }
 
 bool
 js::regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp)
 {
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Step 3.a.
+    if (IsRegExpPrototype(args.thisv())) {
+        args.rval().setUndefined();
+        return true;
+    }
+
     // Steps 1-3.
-    CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_ignoreCase_impl>(cx, args);
+    return CallNonGenericMethod<IsRegExpObject, regexp_ignoreCase_impl>(cx, args);
 }
 
 // ES 2017 draft 21.2.5.7.
 MOZ_ALWAYS_INLINE bool
 regexp_multiline_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv()));
-
-    // Step 3.a.
-    if (!IsRegExpObject(args.thisv())) {
-        args.rval().setUndefined();
-        return true;
-    }
+    MOZ_ASSERT(IsRegExpObject(args.thisv()));
 
     // Steps 4-6.
-    Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
+    RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
     args.rval().setBoolean(reObj->multiline());
     return true;
 }
 
 bool
 js::regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp)
 {
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Step 3.a.
+    if (IsRegExpPrototype(args.thisv())) {
+        args.rval().setUndefined();
+        return true;
+    }
+
     // Steps 1-3.
-    CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_multiline_impl>(cx, args);
+    return CallNonGenericMethod<IsRegExpObject, regexp_multiline_impl>(cx, args);
 }
 
-// ES 2017 draft rev32 21.2.5.10.
+// ES 2017 draft 21.2.5.10.
 MOZ_ALWAYS_INLINE bool
 regexp_source_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv()));
-
-    // Step 3.a.
-    if (!IsRegExpObject(args.thisv())) {
-        args.rval().setString(cx->names().emptyRegExp);
-        return true;
-    }
+    MOZ_ASSERT(IsRegExpObject(args.thisv()));
 
     // Step 5.
-    Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
+    RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
     RootedAtom src(cx, reObj->getSource());
     if (!src)
         return false;
 
     // Step 7.
-    RootedString str(cx, EscapeRegExpPattern(cx, src));
+    JSString* str = EscapeRegExpPattern(cx, src);
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
 static bool
 regexp_source(JSContext* cx, unsigned argc, JS::Value* vp)
 {
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Step 3.a.
+    if (IsRegExpPrototype(args.thisv())) {
+        args.rval().setString(cx->names().emptyRegExp);
+        return true;
+    }
+
     // Steps 1-4.
-    CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_source_impl>(cx, args);
+    return CallNonGenericMethod<IsRegExpObject, regexp_source_impl>(cx, args);
 }
 
 // ES 2017 draft 21.2.5.12.
 MOZ_ALWAYS_INLINE bool
 regexp_sticky_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv()));
-
-    // Step 3.a.
-    if (!IsRegExpObject(args.thisv())) {
-        args.rval().setUndefined();
-        return true;
-    }
+    MOZ_ASSERT(IsRegExpObject(args.thisv()));
 
     // Steps 4-6.
-    Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
+    RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
     args.rval().setBoolean(reObj->sticky());
     return true;
 }
 
 bool
 js::regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp)
 {
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Step 3.a.
+    if (IsRegExpPrototype(args.thisv())) {
+        args.rval().setUndefined();
+        return true;
+    }
+
     // Steps 1-3.
-    CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_sticky_impl>(cx, args);
+    return CallNonGenericMethod<IsRegExpObject, regexp_sticky_impl>(cx, args);
 }
 
 // ES 2017 draft 21.2.5.15.
 MOZ_ALWAYS_INLINE bool
 regexp_unicode_impl(JSContext* cx, const CallArgs& args)
 {
-    MOZ_ASSERT(IsRegExpInstanceOrPrototype(args.thisv()));
-
-    // Step 3.a.
-    if (!IsRegExpObject(args.thisv())) {
-        args.rval().setUndefined();
-        return true;
-    }
+    MOZ_ASSERT(IsRegExpObject(args.thisv()));
 
     // Steps 4-6.
-    Rooted<RegExpObject*> reObj(cx, &args.thisv().toObject().as<RegExpObject>());
+    RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
     args.rval().setBoolean(reObj->unicode());
     return true;
 }
 
 bool
 js::regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp)
 {
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Step 3.a.
+    if (IsRegExpPrototype(args.thisv())) {
+        args.rval().setUndefined();
+        return true;
+    }
+
     // Steps 1-3.
-    CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsRegExpInstanceOrPrototype, regexp_unicode_impl>(cx, args);
+    return CallNonGenericMethod<IsRegExpObject, regexp_unicode_impl>(cx, args);
 }
 
 const JSPropertySpec js::regexp_properties[] = {
     JS_SELF_HOSTED_GET("flags", "RegExpFlagsGetter", 0),
     JS_PSG("global", regexp_global, 0),
     JS_PSG("ignoreCase", regexp_ignoreCase, 0),
     JS_PSG("multiline", regexp_multiline, 0),
     JS_PSG("source", regexp_source, 0),
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -3610,19 +3610,24 @@ Parser<ParseHandler>::functionFormalPara
 {
     // Given a properly initialized parse context, try to parse an actual
     // function without concern for conversion to strict mode, use of lazy
     // parsing and such.
 
     FunctionBox* funbox = pc->functionBox();
     RootedFunction fun(context, funbox->function());
 
-    AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, funbox->isAsync());
-    if (!functionArguments(yieldHandling, kind, pn))
-        return false;
+    // See below for an explanation why arrow function parameters and arrow
+    // function bodies are parsed with different yield/await settings.
+    {
+        bool asyncOrArrowInAsync = funbox->isAsync() || (kind == Arrow && awaitIsKeyword());
+        AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, asyncOrArrowInAsync);
+        if (!functionArguments(yieldHandling, kind, pn))
+            return false;
+    }
 
     Maybe<ParseContext::VarScope> varScope;
     if (funbox->hasParameterExprs) {
         varScope.emplace(this);
         if (!varScope->init(pc))
             return false;
     } else {
         pc->functionScope().useAsVarScope(pc);
@@ -3678,42 +3683,46 @@ Parser<ParseHandler>::functionFormalPara
         openedPos = pos().begin;
     }
 
     // Arrow function parameters inherit yieldHandling from the enclosing
     // context, but the arrow body doesn't. E.g. in |(a = yield) => yield|,
     // |yield| in the parameters is either a name or keyword, depending on
     // whether the arrow function is enclosed in a generator function or not.
     // Whereas the |yield| in the function body is always parsed as a name.
+    // The same goes when parsing |await| in arrow functions.
     YieldHandling bodyYieldHandling = GetYieldHandling(pc->generatorKind());
-    Node body = functionBody(inHandling, bodyYieldHandling, kind, bodyType);
-    if (!body)
-        return false;
-
-    if ((kind != Method && !IsConstructorKind(kind)) && fun->explicitName()) {
+    Node body;
+    {
+        AutoAwaitIsKeyword<ParseHandler> awaitIsKeyword(this, funbox->isAsync());
+        body = functionBody(inHandling, bodyYieldHandling, kind, bodyType);
+        if (!body)
+            return false;
+    }
+
+    if ((kind == Statement || kind == Expression) && fun->explicitName()) {
         RootedPropertyName propertyName(context, fun->explicitName()->asPropertyName());
-        // `await` cannot be checked at this point because of different context.
-        // It should already be checked before this point.
-        if (propertyName != context->names().await) {
-            YieldHandling nameYieldHandling;
-            if (kind == Expression) {
-                // Named lambda has binding inside it.
-                nameYieldHandling = bodyYieldHandling;
-            } else {
-                // Otherwise YieldHandling cannot be checked at this point
-                // because of different context.
-                // It should already be checked before this point.
-                nameYieldHandling = YieldIsName;
-            }
-
-            if (!checkBindingIdentifier(propertyName, handler.getPosition(pn).begin,
-                                        nameYieldHandling))
-            {
-                return false;
-            }
+        YieldHandling nameYieldHandling;
+        if (kind == Expression) {
+            // Named lambda has binding inside it.
+            nameYieldHandling = bodyYieldHandling;
+        } else {
+            // Otherwise YieldHandling cannot be checked at this point
+            // because of different context.
+            // It should already be checked before this point.
+            nameYieldHandling = YieldIsName;
+        }
+
+        // We already use the correct await-handling at this point, therefore
+        // we don't need call AutoAwaitIsKeyword here.
+
+        if (!checkBindingIdentifier(propertyName, handler.getPosition(pn).begin,
+                                    nameYieldHandling))
+        {
+            return false;
         }
     }
 
     if (bodyType == StatementListBody) {
         MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
                                          reportMissingClosing(JSMSG_CURLY_AFTER_BODY,
                                                               JSMSG_CURLY_OPENED, openedPos));
         funbox->bufEnd = pos().end;
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/AsyncFunctions/await-in-arrow-parameters.js
@@ -0,0 +1,94 @@
+var ieval = eval;
+var AsyncFunction = async function(){}.constructor;
+
+var functionContext = {
+    Function: {
+        constructor: Function,
+        toSourceBody: code => `function f() { ${code} }`,
+        toSourceParameter: code => `function f(x = ${code}) { }`,
+    },
+    AsyncFunction: {
+        constructor: AsyncFunction,
+        toSourceBody: code => `async function f() { ${code} }`,
+        toSourceParameter: code => `async function f(x = ${code}) { }`,
+    },
+};
+
+function assertSyntaxError(kind, code) {
+    var {constructor, toSourceBody, toSourceParameter} = functionContext[kind];
+    var body = toSourceBody(code);
+    var parameter = toSourceParameter(code);
+
+    assertThrowsInstanceOf(() => { constructor(code); }, SyntaxError, constructor.name + ":" + code);
+    assertThrowsInstanceOf(() => { constructor(`x = ${code}`, ""); }, SyntaxError, constructor.name + ":" + code);
+
+    assertThrowsInstanceOf(() => { eval(body); }, SyntaxError, "eval:" + body);
+    assertThrowsInstanceOf(() => { ieval(body); }, SyntaxError, "indirect eval:" + body);
+
+    assertThrowsInstanceOf(() => { eval(parameter); }, SyntaxError, "eval:" + parameter);
+    assertThrowsInstanceOf(() => { ieval(parameter); }, SyntaxError, "indirect eval:" + parameter);
+}
+
+function assertNoSyntaxError(kind, code) {
+    var {constructor, toSourceBody, toSourceParameter} = functionContext[kind];
+    var body = toSourceBody(code);
+    var parameter = toSourceParameter(code);
+
+    constructor(code);
+    constructor(`x = ${code}`, "");
+
+    eval(body);
+    ieval(body);
+
+    eval(parameter);
+    ieval(parameter);
+}
+
+function assertSyntaxErrorAsync(code) {
+    assertNoSyntaxError("Function", code);
+    assertSyntaxError("AsyncFunction", code);
+}
+
+function assertSyntaxErrorBoth(code) {
+    assertSyntaxError("Function", code);
+    assertSyntaxError("AsyncFunction", code);
+}
+
+
+// Bug 1353691
+// |await| expression is invalid in arrow functions in async-context.
+// |await/r/g| first parses as |AwaitExpression RegularExpressionLiteral|, when reparsing the
+// arrow function, it is parsed as |IdentRef DIV IdentRef DIV IdentRef|. We need to ensure in this
+// case, that we still treat |await| as a keyword and hence throw a SyntaxError.
+assertSyntaxErrorAsync("(a = await/r/g) => {}");
+assertSyntaxErrorBoth("async(a = await/r/g) => {}");
+
+// Also applies when nesting arrow functions.
+assertSyntaxErrorAsync("(a = (b = await/r/g) => {}) => {}");
+assertSyntaxErrorBoth("async(a = (b = await/r/g) => {}) => {}");
+assertSyntaxErrorBoth("(a = async(b = await/r/g) => {}) => {}");
+assertSyntaxErrorBoth("async(a = async(b = await/r/g) => {}) => {}");
+
+
+// Bug 1355860
+// |await| cannot be used as rest-binding parameter in arrow functions in async-context.
+assertSyntaxErrorAsync("(...await) => {}");
+assertSyntaxErrorBoth("async(...await) => {}");
+
+assertSyntaxErrorAsync("(a, ...await) => {}");
+assertSyntaxErrorBoth("async(a, ...await) => {}");
+
+// Also test nested arrow functions.
+assertSyntaxErrorAsync("(a = (...await) => {}) => {}");
+assertSyntaxErrorBoth("(a = async(...await) => {}) => {}");
+assertSyntaxErrorBoth("async(a = (...await) => {}) => {}");
+assertSyntaxErrorBoth("async(a = async(...await) => {}) => {}");
+
+assertSyntaxErrorAsync("(a = (b, ...await) => {}) => {}");
+assertSyntaxErrorBoth("(a = async(b, ...await) => {}) => {}");
+assertSyntaxErrorBoth("async(a = (b, ...await) => {}) => {}");
+assertSyntaxErrorBoth("async(a = async(b, ...await) => {}) => {}");
+
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2018/RegExp/prototype-different-global.js
@@ -0,0 +1,19 @@
+
+var otherGlobal = newGlobal();
+var otherRegExp = otherGlobal.RegExp;
+
+for (let name of ["global", "ignoreCase", "multiline", "sticky", "unicode", "source"]) {
+    let getter = Object.getOwnPropertyDescriptor(RegExp.prototype, name).get;
+    assertEq(typeof getter, "function");
+
+    // Note: TypeError gets reported from wrong global!
+    assertThrowsInstanceOf(() => getter.call(otherRegExp.prototype), otherGlobal.TypeError);
+}
+
+let flagsGetter = Object.getOwnPropertyDescriptor(RegExp.prototype, "flags").get;
+assertEq(flagsGetter.call(otherRegExp.prototype), "");
+
+assertEq(RegExp.prototype.toString.call(otherRegExp.prototype), "/(?:)/");
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- a/layout/style/contenteditable.css
+++ b/layout/style/contenteditable.css
@@ -46,17 +46,16 @@ a:link:-moz-read-write img, a:visited:-m
 a:active:-moz-read-write img {
   -moz-user-input: none;
 }
 
 /* We suppress user/author's prefs for link underline, 
    so we must set explicitly. This isn't good!
 */
 a:link:-moz-read-write {
-  text-decoration: underline -moz-anchor-decoration;
   color: -moz-hyperlinktext;
 }
 
 /* Allow double-clicks on these widgets to open properties dialogs
    XXX except when the widget has disabled attribute */
 *|*:-moz-read-write > input:-moz-read-only,
 *|*:-moz-read-write > button:-moz-read-only,
 *|*:-moz-read-write > textarea:-moz-read-only {
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -33,17 +33,16 @@
  ******/
 
 // OUTPUT_CLASS=nsCSSKeywords
 // MACRO_NAME=CSS_KEY
 
 CSS_KEY(-moz-activehyperlinktext, _moz_activehyperlinktext)
 CSS_KEY(-moz-all, _moz_all)
 CSS_KEY(-moz-alt-content, _moz_alt_content)
-CSS_KEY(-moz-anchor-decoration, _moz_anchor_decoration)
 CSS_KEY(-moz-available, _moz_available)
 CSS_KEY(-moz-box, _moz_box)
 CSS_KEY(-moz-button, _moz_button)
 CSS_KEY(-moz-buttondefault, _moz_buttondefault)
 CSS_KEY(-moz-buttonhoverface, _moz_buttonhoverface)
 CSS_KEY(-moz-buttonhovertext, _moz_buttonhovertext)
 CSS_KEY(-moz-cellhighlight, _moz_cellhighlight)
 CSS_KEY(-moz-cellhighlighttext, _moz_cellhighlighttext)
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -15563,24 +15563,22 @@ CSSParserImpl::ParseTextAlignLast(nsCSSV
 
 bool
 CSSParserImpl::ParseTextDecorationLine(nsCSSValue& aValue)
 {
   static_assert((NS_STYLE_TEXT_DECORATION_LINE_NONE ^
                  NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE ^
                  NS_STYLE_TEXT_DECORATION_LINE_OVERLINE ^
                  NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH ^
-                 NS_STYLE_TEXT_DECORATION_LINE_BLINK ^
-                 NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS) ==
+                 NS_STYLE_TEXT_DECORATION_LINE_BLINK) ==
                 (NS_STYLE_TEXT_DECORATION_LINE_NONE |
                  NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE |
                  NS_STYLE_TEXT_DECORATION_LINE_OVERLINE |
                  NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH |
-                 NS_STYLE_TEXT_DECORATION_LINE_BLINK |
-                 NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS),
+                 NS_STYLE_TEXT_DECORATION_LINE_BLINK),
                 "text decoration constants need to be bitmasks");
   if (ParseSingleTokenVariant(aValue, VARIANT_HK,
                               nsCSSProps::kTextDecorationLineKTable)) {
     if (eCSSUnit_Enumerated == aValue.GetUnit()) {
       int32_t intValue = aValue.GetIntValue();
       if (intValue != NS_STYLE_TEXT_DECORATION_LINE_NONE) {
         // look for more keywords
         nsCSSValue  keyword;
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -2023,17 +2023,16 @@ const KTableEntry nsCSSProps::kTextCombi
 };
 
 const KTableEntry nsCSSProps::kTextDecorationLineKTable[] = {
   { eCSSKeyword_none, NS_STYLE_TEXT_DECORATION_LINE_NONE },
   { eCSSKeyword_underline, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE },
   { eCSSKeyword_overline, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE },
   { eCSSKeyword_line_through, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH },
   { eCSSKeyword_blink, NS_STYLE_TEXT_DECORATION_LINE_BLINK },
-  { eCSSKeyword__moz_anchor_decoration, NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kTextDecorationStyleKTable[] = {
   { eCSSKeyword__moz_none, NS_STYLE_TEXT_DECORATION_STYLE_NONE },
   { eCSSKeyword_solid, NS_STYLE_TEXT_DECORATION_STYLE_SOLID },
   { eCSSKeyword_double, NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE },
   { eCSSKeyword_dotted, NS_STYLE_TEXT_DECORATION_STYLE_DOTTED },
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -1533,17 +1533,17 @@ nsCSSValue::AppendToString(nsCSSProperty
                            aResult);
       } else {
         // Ignore the "override all" internal value.
         // (It doesn't have a string representation.)
         intValue &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL;
         nsStyleUtil::AppendBitmaskCSSValue(
           aProperty, intValue,
           NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
-          NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS,
+          NS_STYLE_TEXT_DECORATION_LINE_BLINK,
           aResult);
       }
       break;
 
     case eCSSProperty_paint_order:
       static_assert
         (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8,
          "SVGStyleStruct::mPaintOrder and the following cast not big enough");
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -3923,20 +3923,19 @@ nsComputedDOMStyle::DoGetTextDecorationL
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
 
   int32_t intValue = StyleTextReset()->mTextDecorationLine;
 
   if (NS_STYLE_TEXT_DECORATION_LINE_NONE == intValue) {
     val->SetIdent(eCSSKeyword_none);
   } else {
     nsAutoString decorationLineString;
-    // Clear the -moz-anchor-decoration bit and the OVERRIDE_ALL bits -- we
-    // don't want these to appear in the computed style.
-    intValue &= ~(NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS |
-                  NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL);
+    // Clear the OVERRIDE_ALL bits -- we don't want these to appear in
+    // the computed style.
+    intValue &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL;
     nsStyleUtil::AppendBitmaskCSSValue(eCSSProperty_text_decoration_line,
       intValue, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
       NS_STYLE_TEXT_DECORATION_LINE_BLINK, decorationLineString);
     val->SetString(decorationLineString);
   }
 
   return val.forget();
 }
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -5110,28 +5110,17 @@ nsRuleNode::ComputeTextResetData(void* a
                                  const RuleNodeCacheConditions aConditions)
 {
   COMPUTE_START_RESET(TextReset, text, parentText)
 
   // text-decoration-line: enum (bit field), inherit, initial
   const nsCSSValue* decorationLineValue =
     aRuleData->ValueForTextDecorationLine();
   if (eCSSUnit_Enumerated == decorationLineValue->GetUnit()) {
-    int32_t td = decorationLineValue->GetIntValue();
-    text->mTextDecorationLine = td;
-    if (td & NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS) {
-      bool underlineLinks =
-        mPresContext->GetCachedBoolPref(kPresContext_UnderlineLinks);
-      if (underlineLinks) {
-        text->mTextDecorationLine |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
-      }
-      else {
-        text->mTextDecorationLine &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
-      }
-    }
+    text->mTextDecorationLine = decorationLineValue->GetIntValue();
   } else if (eCSSUnit_Inherit == decorationLineValue->GetUnit()) {
     conditions.SetUncacheable();
     text->mTextDecorationLine = parentText->mTextDecorationLine;
   } else if (eCSSUnit_Initial == decorationLineValue->GetUnit() ||
              eCSSUnit_Unset == decorationLineValue->GetUnit()) {
     text->mTextDecorationLine = NS_STYLE_TEXT_DECORATION_LINE_NONE;
   }
 
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -855,20 +855,19 @@ enum class StyleGridTrackBreadth : uint8
 // the smallest NS_STYLE_VERTICAL_ALIGN_* value below!
 
 // See nsStyleText, nsStyleFont
 #define NS_STYLE_TEXT_DECORATION_LINE_NONE         0
 #define NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE    0x01
 #define NS_STYLE_TEXT_DECORATION_LINE_OVERLINE     0x02
 #define NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH 0x04
 #define NS_STYLE_TEXT_DECORATION_LINE_BLINK        0x08
-#define NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS 0x10
 // OVERRIDE_ALL does not occur in stylesheets; it only comes from HTML
 // attribute mapping (and thus appears in computed data)
-#define NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL 0x20
+#define NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL 0x10
 #define NS_STYLE_TEXT_DECORATION_LINE_LINES_MASK   (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE | NS_STYLE_TEXT_DECORATION_LINE_OVERLINE | NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH)
 
 // See nsStyleText
 #define NS_STYLE_TEXT_DECORATION_STYLE_NONE     0 // not in CSS spec, mapped to -moz-none
 #define NS_STYLE_TEXT_DECORATION_STYLE_DOTTED   1
 #define NS_STYLE_TEXT_DECORATION_STYLE_DASHED   2
 #define NS_STYLE_TEXT_DECORATION_STYLE_SOLID    3
 #define NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE   4
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -3767,17 +3767,17 @@ var gCSSProperties = {
     invalid_values: []
   },
   "text-decoration": {
     domProp: "textDecoration",
     inherited: false,
     type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
     subproperties: [ "text-decoration-color", "text-decoration-line", "text-decoration-style" ],
     initial_values: [ "none" ],
-    other_values: [ "underline", "overline", "line-through", "blink", "blink line-through underline", "underline overline line-through blink", "-moz-anchor-decoration", "blink -moz-anchor-decoration",
+    other_values: [ "underline", "overline", "line-through", "blink", "blink line-through underline", "underline overline line-through blink",
                     "underline red solid", "underline #ff0000", "solid underline", "red underline", "#ff0000 underline", "dotted underline" ],
     invalid_values: [ "none none", "underline none", "none underline", "blink none", "none blink", "line-through blink line-through", "underline overline line-through blink none", "underline overline line-throuh blink blink", "rgb(0, rubbish, 0) underline" ]
   },
   "text-decoration-color": {
     domProp: "textDecorationColor",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     prerequisites: { "color": "black" },
@@ -3785,17 +3785,17 @@ var gCSSProperties = {
     other_values: [ "green", "rgba(255,128,0,0.5)", "transparent" ],
     invalid_values: [ "#0", "#00", "#00000", "#0000000", "#000000000", "000000", "ff00ff" ]
   },
   "text-decoration-line": {
     domProp: "textDecorationLine",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "none" ],
-    other_values: [ "underline", "overline", "line-through", "blink", "blink line-through underline", "underline overline line-through blink", "-moz-anchor-decoration", "blink -moz-anchor-decoration" ],
+    other_values: [ "underline", "overline", "line-through", "blink", "blink line-through underline", "underline overline line-through blink" ],
     invalid_values: [ "none none", "underline none", "none underline", "line-through blink line-through", "underline overline line-through blink none", "underline overline line-throuh blink blink" ]
   },
   "text-decoration-style": {
     domProp: "textDecorationStyle",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "solid" ],
     other_values: [ "double", "dotted", "dashed", "wavy", "-moz-none" ],
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -88,17 +88,17 @@ to mochitest command.
   * Events:
     * test_animations_event_handler_attribute.html [10]
     * test_animations_event_order.html [11]
 * test_computed_style.html `gradient`: -moz-prefixed radient value [9]
 * ... `mask`: mask-image isn't set properly bug 1341667 [10]
 * ... `fill`: svg paint should distinguish whether there is fallback bug 1347409 [2]
 * ... `stroke`: svg paint should distinguish whether there is fallback bug 1347409 [2]
 * character not properly escaped servo/servo#15947
-  * test_parse_url.html [4]
+  * test_parse_url.html [1]
   * test_bug829816.html [8]
 * test_compute_data_with_start_struct.html `timing-function`: incorrectly computing keywords to bezier function servo/servo#15086 [2]
 * \@counter-style support bug 1328319
   * test_counter_descriptor_storage.html [1]
   * test_counter_style.html [1]
   * test_rule_insertion.html `@counter-style` [4]
   * ... `cjk-decimal` [1]
   * test_value_storage.html `symbols(` [30]
@@ -112,19 +112,20 @@ to mochitest command.
   * font-display bug 1355345
     * test_descriptor_storage.html `font-display` [5]
     * test_font_face_parser.html `font-display` [15]
   * test_font_face_parser.html `font-language-override`: bug 1355364 [8]
   * ... `font-feature-settings`: bug 1355366 [10]
 * test_font_face_parser.html `font-weight`: keyword values should be preserved in \@font-face [4]
 * unicode-range parsing bugs
   * servo/rust-cssparser#133
-    * test_descriptor_storage.html `U+4????` [1]
-    * test_font_face_parser.html `U+0121` [4]
   * test_font_face_parser.html `4E00`: servo/rust-cssparser#135 [2]
+* @font-face support bug 1290237
+  * test_descriptor_storage.html [1]
+  * test_font_face_parser.html `@font-face` [8]
 * @namespace support:
   * test_namespace_rule.html: bug 1355715 [17]
 * test_dont_use_document_colors.html: support of disabling document color bug 1355716 [21]
 * test_exposed_prop_accessors.html: mainly various unsupported properties [*]
 * test_font_feature_values_parsing.html: \@font-feature-values support bug 1355721 [107]
 * Grid support bug 1341802
   * test_grid_computed_values.html [4]
   * test_grid_container_shorthands.html [65]
@@ -238,20 +239,17 @@ to mochitest command.
 * Unsupported values
   * SVG-only values of pointer-events not recognized
     * test_compute_data_with_start_struct.html `pointer-events` [2]
     * test_inherit_computation.html `pointer-events` [4]
     * test_initial_computation.html `pointer-events` [2]
     * test_pointer-events.html [2]
     * test_value_storage.html `pointer-events` [8]
   * new syntax of rgba?() and hsla?() functions servo/rust-cssparser#113
-    * test_value_storage.html `'color'` [35]
-    * ... `rgb(100, 100.0, 100)` [1]
-    * test_computed_style.html `css-color-4` [8]
-    * test_specified_value_serialization.html `css-color-4` [8]
+    * 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]
@@ -359,33 +357,26 @@ to mochitest command.
 ## Assertions
 
 ## Need Gecko change
 
 * Servo is correct but Gecko is wrong
   * flex-basis should be 0px when omitted in flex shorthand bug 1331530
     * test_flexbox_flex_shorthand.html `flex-basis` [10]
   * should reject whole value bug 1355352
-    * test_descriptor_storage.html `U+100-17F,U+200-17F` [1]
-    * test_font_face_parser.html `U+90-30` [2]
-    * ... `U+220043` [2]
   * Gecko clamps rather than rejects invalid unicode range bug 1355356
-    * test_font_face_parser.html `U+??????` [2]
-    * ... `12FFFF` [2]
 * test_default_computed_style.html: unship getDefaultComputedStyle bug 1355683 [1]
-* -moz-anchor-decoration value on text-decoration bug 1355734
-  * test_value_storage.html `-moz-anchor-decoration` [10]
 
 ## Spec Unclear
 
 * test_property_syntax_errors.html `'background'`: whether background shorthand should accept "text" [200]
 
 ## Unknown / Unsure
 
 * test_additional_sheets.html: one sub-test cascade order is wrong [1]
 * test_selectors_on_anonymous_content.html: xbl and :nth-child [1]
-* test_parse_rule.html `rgb(0, 128, 0)`: color properties not getting computed [6]
+* test_parse_rule.html `rgb(0, 128, 0)`: color properties not getting computed [5]
 
 ## Ignore
 
 * Ignore for now since should be mostly identical to test_value_storage.html
   * test_value_cloning.html [*]
   * test_value_computation.html [*]
--- a/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
+++ b/media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c
@@ -453,17 +453,17 @@ static void sdp_attr_fmtp_invalid_value(
 }
 
 /*
  * sdp_verify_attr_fmtp_telephone_event
  * Helper function for verifying the telephone-event fmtp format
  */
 static sdp_result_e sdp_verify_attr_fmtp_telephone_event(char *fmtpVal)
 {
-  size_t len = PL_strlen(fmtpVal);
+  size_t len = fmtpVal ? strlen(fmtpVal) : 0;
 
   // make sure the basics are good:
   // - at least 1 character
   // - no illegal chars
   // - first char is a number
   if (len < 1
       || strspn(fmtpVal, "0123456789,-") != len
       || PL_strstr(fmtpVal, ",,")
@@ -477,17 +477,17 @@ static sdp_result_e sdp_verify_attr_fmtp
   // the input string.
   char dtmf_tones[SDP_MAX_STRING_LEN+1];
   PL_strncpyz(dtmf_tones, fmtpVal, sizeof(dtmf_tones));
 
   char *strtok_state;
   char *temp = PL_strtok_r(dtmf_tones, ",", &strtok_state);
 
   while (temp != NULL) {
-    len = PL_strlen(temp);
+    len = strlen(temp);
     if (len > 5) {
       // an example of a max size token is "11-15", so if the
       // token is longer than 5 it is bad
       return SDP_INVALID_PARAMETER;
     }
 
     // case where we have 1 or 2 characters, example 4 or 23
     if (len < 3 && strspn(temp, "0123456789") != len) {
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -400,16 +400,17 @@ public class CustomTabsActivity extends 
     private void onOpenInClicked() {
         final Tab tab = Tabs.getInstance().getSelectedTab();
         if (tab != null) {
             // To launch default browser with url of current tab.
             final Intent intent = new Intent();
             intent.setData(Uri.parse(tab.getURL()));
             intent.setAction(Intent.ACTION_VIEW);
             startActivity(intent);
+            finish();
         }
     }
 
     private void onActionButtonClicked() {
         PendingIntent pendingIntent = IntentUtil.getActionButtonPendingIntent(startIntent);
         performPendingIntent(pendingIntent);
     }
 
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -1714,16 +1714,44 @@ Preferences::RemoveObservers(nsIObserver
 
   for (uint32_t i = 0; aPrefs[i]; i++) {
     nsresult rv = RemoveObserver(aObserver, aPrefs[i]);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
 
+static void NotifyObserver(const char* aPref, void* aClosure)
+{
+  nsCOMPtr<nsIObserver> observer = static_cast<nsIObserver*>(aClosure);
+  observer->Observe(nullptr, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID,
+                    NS_ConvertASCIItoUTF16(aPref).get());
+}
+
+static void RegisterPriorityCallback(PrefChangedFunc aCallback,
+                                     const char* aPref,
+                                     void* aClosure)
+{
+  MOZ_ASSERT(Preferences::IsServiceAvailable());
+
+  ValueObserverHashKey hashKey(aPref, aCallback, Preferences::ExactMatch);
+  RefPtr<ValueObserver> observer;
+  gObserverTable->Get(&hashKey, getter_AddRefs(observer));
+  if (observer) {
+    observer->AppendClosure(aClosure);
+    return;
+  }
+
+  observer = new ValueObserver(aPref, aCallback, Preferences::ExactMatch);
+  observer->AppendClosure(aClosure);
+  PREF_RegisterPriorityCallback(aPref, NotifyObserver,
+                                static_cast<nsIObserver*>(observer));
+  gObserverTable->Put(observer, observer);
+}
+
 // static
 nsresult
 Preferences::RegisterCallback(PrefChangedFunc aCallback,
                               const char* aPref,
                               void* aClosure,
                               MatchKind aMatchKind)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
@@ -1781,16 +1809,20 @@ Preferences::UnregisterCallback(PrefChan
   observer->RemoveClosure(aClosure);
   if (observer->HasNoClosures()) {
     // Delete the callback since its list of closures is empty.
     gObserverTable->Remove(observer);
   }
   return NS_OK;
 }
 
+// We insert cache observers using RegisterPriorityCallback to ensure they
+// are called prior to ordinary pref observers.  Doing this ensures that
+// ordinary observers will never get stale values from cache variables.
+
 static void BoolVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
   *((bool*)cache->cacheLocation) =
     Preferences::GetBool(aPref, cache->defaultValueBool);
 }
 
 // static
@@ -1804,17 +1836,18 @@ Preferences::AddBoolVarCache(bool* aCach
 #ifdef DEBUG
   AssertNotAlreadyCached("bool", aPref, aCache);
 #endif
   *aCache = GetBool(aPref, aDefault);
   CacheData* data = new CacheData();
   data->cacheLocation = aCache;
   data->defaultValueBool = aDefault;
   gCacheData->AppendElement(data);
-  return RegisterCallback(BoolVarChanged, aPref, data, ExactMatch);
+  RegisterPriorityCallback(BoolVarChanged, aPref, data);
+  return NS_OK;
 }
 
 static void IntVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
   *((int32_t*)cache->cacheLocation) =
     Preferences::GetInt(aPref, cache->defaultValueInt);
 }
@@ -1830,17 +1863,18 @@ Preferences::AddIntVarCache(int32_t* aCa
 #ifdef DEBUG
   AssertNotAlreadyCached("int", aPref, aCache);
 #endif
   *aCache = Preferences::GetInt(aPref, aDefault);
   CacheData* data = new CacheData();
   data->cacheLocation = aCache;
   data->defaultValueInt = aDefault;
   gCacheData->AppendElement(data);
-  return RegisterCallback(IntVarChanged, aPref, data, ExactMatch);
+  RegisterPriorityCallback(IntVarChanged, aPref, data);
+  return NS_OK;
 }
 
 static void UintVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
   *((uint32_t*)cache->cacheLocation) =
     Preferences::GetUint(aPref, cache->defaultValueUint);
 }
@@ -1856,17 +1890,18 @@ Preferences::AddUintVarCache(uint32_t* a
 #ifdef DEBUG
   AssertNotAlreadyCached("uint", aPref, aCache);
 #endif
   *aCache = Preferences::GetUint(aPref, aDefault);
   CacheData* data = new CacheData();
   data->cacheLocation = aCache;
   data->defaultValueUint = aDefault;
   gCacheData->AppendElement(data);
-  return RegisterCallback(UintVarChanged, aPref, data, ExactMatch);
+  RegisterPriorityCallback(UintVarChanged, aPref, data);
+  return NS_OK;
 }
 
 template <MemoryOrdering Order>
 static void AtomicUintVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
   *((Atomic<uint32_t, Order>*)cache->cacheLocation) =
     Preferences::GetUint(aPref, cache->defaultValueUint);
@@ -1884,17 +1919,18 @@ Preferences::AddAtomicUintVarCache(Atomi
 #ifdef DEBUG
   AssertNotAlreadyCached("uint", aPref, aCache);
 #endif
   *aCache = Preferences::GetUint(aPref, aDefault);
   CacheData* data = new CacheData();
   data->cacheLocation = aCache;
   data->defaultValueUint = aDefault;
   gCacheData->AppendElement(data);
-  return RegisterCallback(AtomicUintVarChanged<Order>, aPref, data, ExactMatch);
+  RegisterPriorityCallback(AtomicUintVarChanged<Order>, aPref, data);
+  return NS_OK;
 }
 
 // Since the definition of this template function is not in a header file,
 // we need to explicitly specify the instantiations that are required.
 // Currently only the order=Relaxed variant is needed.
 template
 nsresult Preferences::AddAtomicUintVarCache(Atomic<uint32_t,Relaxed>*,
                                             const char*, uint32_t);
@@ -1917,17 +1953,18 @@ Preferences::AddFloatVarCache(float* aCa
 #ifdef DEBUG
   AssertNotAlreadyCached("float", aPref, aCache);
 #endif
   *aCache = Preferences::GetFloat(aPref, aDefault);
   CacheData* data = new CacheData();
   data->cacheLocation = aCache;
   data->defaultValueFloat = aDefault;
   gCacheData->AppendElement(data);
-  return RegisterCallback(FloatVarChanged, aPref, data, ExactMatch);
+  RegisterPriorityCallback(FloatVarChanged, aPref, data);
+  return NS_OK;
 }
 
 // static
 nsresult
 Preferences::GetDefaultBool(const char* aPref, bool* aResult)
 {
   NS_PRECONDITION(aResult, "aResult must not be NULL");
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2023,18 +2023,18 @@ pref("network.auth.subresource-http-auth
 // credentials, those will be sent automatically in Private Browsing windows.
 //
 // This preference has no effect when the browser is set to "Never Remember History",
 // in that case default credentials will always be used.
 pref("network.auth.private-browsing-sso", false);
 
 // Control how the throttling service works - number of ms that each
 // suspend and resume period lasts (prefs named appropriately)
-pref("network.throttle.suspend-for", 2000);
-pref("network.throttle.resume-for", 2000);
+pref("network.throttle.suspend-for", 3000);
+pref("network.throttle.resume-for", 200);
 pref("network.throttle.enable", true);
 
 pref("permissions.default.image",           1); // 1-Accept, 2-Deny, 3-dontAcceptForeign
 
 pref("network.proxy.type",                  5);
 pref("network.proxy.ftp",                   "");
 pref("network.proxy.ftp_port",              0);
 pref("network.proxy.http",                  "");
@@ -5038,21 +5038,22 @@ pref("jsloader.reuseGlobal", false);
 
 // When we're asked to take a screenshot, don't wait more than 2000ms for the
 // event loop to become idle before actually taking the screenshot.
 pref("dom.browserElement.maxScreenshotDelayMS", 2000);
 
 // Whether we should show the placeholder when the element is focused but empty.
 pref("dom.placeholder.show_on_focus", true);
 
-// VR is disabled by default in release and enabled for nightly and aurora
-#ifdef RELEASE_OR_BETA
+// WebVR is enabled by default in beta and release for Windows and for all
+// platforms in nightly and aurora.
+#if defined(XP_WIN) || !defined(RELEASE_OR_BETA)
+pref("dom.vr.enabled", true);
+#else
 pref("dom.vr.enabled", false);
-#else
-pref("dom.vr.enabled", true);
 #endif
 // It is often desirable to automatically start vr presentation when
 // a user puts on the VR headset.  This is done by emitting the
 // Window.vrdisplayactivate event when the headset's sensors detect it
 // being worn.  This can result in WebVR content taking over the headset
 // when the user is using it outside the browser or inadvertent start of
 // presentation due to the high sensitivity of the proximity sensor in some
 // headsets, so it is off by default.
--- a/modules/libpref/moz.build
+++ b/modules/libpref/moz.build
@@ -2,16 +2,19 @@
 # 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', 'Preferences: Backend')
 
+if CONFIG['ENABLE_TESTS']:
+    DIRS += ['test/gtest']
+
 XPCSHELL_TESTS_MANIFESTS += [
     'test/unit/xpcshell.ini',
     'test/unit_ipc/xpcshell.ini',
 ]
 
 XPIDL_SOURCES += [
     'nsIPrefBranch.idl',
     'nsIPrefBranch2.idl',
--- a/modules/libpref/prefapi.cpp
+++ b/modules/libpref/prefapi.cpp
@@ -65,17 +65,18 @@ matchPrefEntry(const PLDHashEntryHdr* en
 
     const char *otherKey = reinterpret_cast<const char*>(key);
     return (strcmp(prefEntry->key, otherKey) == 0);
 }
 
 PLDHashTable*       gHashTable;
 static ArenaAllocator<8192,4> gPrefNameArena;
 
-static struct CallbackNode* gCallbacks = nullptr;
+static struct CallbackNode* gFirstCallback = nullptr;
+static struct CallbackNode* gLastPriorityNode = nullptr;
 static bool         gIsAnyPrefLocked = false;
 // These are only used during the call to pref_DoCallback
 static bool         gCallbacksInProgress = false;
 static bool         gShouldCleanupDeadNodes = false;
 
 
 static PLDHashTableOps     pref_HashTableOps = {
     PLDHashTable::HashStringKey,
@@ -152,27 +153,27 @@ void PREF_Init()
     }
 }
 
 /* Frees the callback list. */
 void PREF_Cleanup()
 {
     NS_ASSERTION(!gCallbacksInProgress,
         "PREF_Cleanup was called while gCallbacksInProgress is true!");
-    struct CallbackNode* node = gCallbacks;
+    struct CallbackNode* node = gFirstCallback;
     struct CallbackNode* next_node;
 
     while (node)
     {
         next_node = node->next;
         PL_strfree(node->domain);
         free(node);
         node = next_node;
     }
-    gCallbacks = nullptr;
+    gLastPriorityNode = gFirstCallback = nullptr;
 
     PREF_CleanupPrefs();
 }
 
 /* Frees up all the objects except the callback list. */
 void PREF_CleanupPrefs()
 {
     if (gHashTable) {
@@ -856,17 +857,17 @@ nsresult pref_HashPref(const char *key, 
     }
     return NS_OK;
 }
 
 size_t
 pref_SizeOfPrivateData(MallocSizeOf aMallocSizeOf)
 {
     size_t n = gPrefNameArena.SizeOfExcludingThis(aMallocSizeOf);
-    for (struct CallbackNode* node = gCallbacks; node; node = node->next) {
+    for (struct CallbackNode* node = gFirstCallback; node; node = node->next) {
         n += aMallocSizeOf(node);
         n += aMallocSizeOf(node->domain);
     }
     return n;
 }
 
 PrefType
 PREF_GetPrefType(const char *pref_name)
@@ -893,77 +894,108 @@ PREF_PrefIsLocked(const char *pref_name)
         }
     }
 
     return result;
 }
 
 /* Adds a node to the beginning of the callback list. */
 void
+PREF_RegisterPriorityCallback(const char *pref_node,
+                              PrefChangedFunc callback,
+                              void * instance_data)
+{
+    NS_PRECONDITION(pref_node, "pref_node must not be nullptr");
+    NS_PRECONDITION(callback, "callback must not be nullptr");
+
+    struct CallbackNode* node = (struct CallbackNode*) malloc(sizeof(struct CallbackNode));
+    if (node)
+    {
+        node->domain = PL_strdup(pref_node);
+        node->func = callback;
+        node->data = instance_data;
+        node->next = gFirstCallback;
+        gFirstCallback = node;
+        if (!gLastPriorityNode) {
+            gLastPriorityNode = node;
+        }
+    }
+}
+
+/* Adds a node to the end of the callback list. */
+void
 PREF_RegisterCallback(const char *pref_node,
                        PrefChangedFunc callback,
                        void * instance_data)
 {
     NS_PRECONDITION(pref_node, "pref_node must not be nullptr");
     NS_PRECONDITION(callback, "callback must not be nullptr");
 
     struct CallbackNode* node = (struct CallbackNode*) malloc(sizeof(struct CallbackNode));
     if (node)
     {
         node->domain = PL_strdup(pref_node);
         node->func = callback;
         node->data = instance_data;
-        node->next = gCallbacks;
-        gCallbacks = node;
+        if (gLastPriorityNode) {
+            node->next = gLastPriorityNode->next;
+            gLastPriorityNode->next = node;
+        } else {
+            node->next = gFirstCallback;
+            gFirstCallback = node;
+        }
     }
-    return;
 }
 
-/* Removes |node| from gCallbacks list.
+/* Removes |node| from callback list.
    Returns the node after the deleted one. */
 struct CallbackNode*
 pref_RemoveCallbackNode(struct CallbackNode* node,
                         struct CallbackNode* prev_node)
 {
     NS_PRECONDITION(!prev_node || prev_node->next == node, "invalid params");
-    NS_PRECONDITION(prev_node || gCallbacks == node, "invalid params");
+    NS_PRECONDITION(prev_node || gFirstCallback == node, "invalid params");
 
     NS_ASSERTION(!gCallbacksInProgress,
         "modifying the callback list while gCallbacksInProgress is true");
 
     struct CallbackNode* next_node = node->next;
-    if (prev_node)
+    if (prev_node) {
         prev_node->next = next_node;
-    else
-        gCallbacks = next_node;
+    } else {
+        gFirstCallback = next_node;
+    }
+    if (gLastPriorityNode == node) {
+        gLastPriorityNode = prev_node;
+    }
     PL_strfree(node->domain);
     free(node);
     return next_node;
 }
 
 /* Deletes a node from the callback list or marks it for deletion. */
 nsresult
 PREF_UnregisterCallback(const char *pref_node,
                          PrefChangedFunc callback,
                          void * instance_data)
 {
     nsresult rv = NS_ERROR_FAILURE;
-    struct CallbackNode* node = gCallbacks;
+    struct CallbackNode* node = gFirstCallback;
     struct CallbackNode* prev_node = nullptr;
 
     while (node != nullptr)
     {
         if ( node->func == callback &&
              node->data == instance_data &&
              strcmp(node->domain, pref_node) == 0)
         {
             if (gCallbacksInProgress)
             {
                 // postpone the node removal until after
-                // gCallbacks enumeration is finished.
+                // callbacks enumeration is finished.
                 node->func = nullptr;
                 gShouldCleanupDeadNodes = true;
                 prev_node = node;
                 node = node->next;
             }
             else
             {
                 node = pref_RemoveCallbackNode(node, prev_node);
@@ -986,33 +1018,33 @@ static nsresult pref_DoCallback(const ch
 
     bool reentered = gCallbacksInProgress;
     gCallbacksInProgress = true;
     // Nodes must not be deleted while gCallbacksInProgress is true.
     // Nodes that need to be deleted are marked for deletion by nulling
     // out the |func| pointer. We release them at the end of this function
     // if we haven't reentered.
 
-    for (node = gCallbacks; node != nullptr; node = node->next)
+    for (node = gFirstCallback; node != nullptr; node = node->next)
     {
         if ( node->func &&
              PL_strncmp(changed_pref,
                         node->domain,
                         strlen(node->domain)) == 0 )
         {
             (*node->func) (changed_pref, node->data);
         }
     }
 
     gCallbacksInProgress = reentered;
 
     if (gShouldCleanupDeadNodes && !gCallbacksInProgress)
     {
         struct CallbackNode* prev_node = nullptr;
-        node = gCallbacks;
+        node = gFirstCallback;
 
         while (node != nullptr)
         {
             if (!node->func)
             {
                 node = pref_RemoveCallbackNode(node, prev_node);
             }
             else
--- a/modules/libpref/prefapi.h
+++ b/modules/libpref/prefapi.h
@@ -225,16 +225,19 @@ typedef void (*PrefChangedFunc) (const c
 /*
 // <font color=blue>
 // Register a callback.  This takes a node in the preference tree and will
 // call the callback function if anything below that node is modified.
 // Unregister returns PREF_NOERROR if a callback was found that
 // matched all the parameters; otherwise it returns PREF_ERROR.
 // </font>
 */
+void PREF_RegisterPriorityCallback(const char* domain,
+                                   PrefChangedFunc callback,
+                                   void* instance_data );
 void PREF_RegisterCallback(const char* domain,
                            PrefChangedFunc callback, void* instance_data );
 nsresult PREF_UnregisterCallback(const char* domain,
                                  PrefChangedFunc callback, void* instance_data );
 
 /*
  * Used by nsPrefService as the callback function of the 'pref' parser
  */
new file mode 100644
--- /dev/null
+++ b/modules/libpref/test/gtest/CallbackAndVarCacheOrder.cpp
@@ -0,0 +1,152 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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/. */
+
+// Test that var caches are updated before callbacks.
+
+#include "gtest/gtest.h"
+#include "Preferences.h"
+
+namespace mozilla {
+
+template<typename T, typename U>
+struct Closure {
+  U* location;
+  T expected;
+  bool called;
+};
+
+template<typename T, typename U>
+void VarChanged(const char* aPrefName, void* aData)
+{
+  auto closure = static_cast<Closure<T, U>*>(aData);
+  ASSERT_EQ(*closure->location, closure->expected);
+  ASSERT_FALSE(closure->called);
+  closure->called = true;
+}
+
+void SetFunc(const char* prefName, bool value)
+{
+  nsresult rv = Preferences::SetBool(prefName, value);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+}
+
+void SetFunc(const char* prefName, int32_t value)
+{
+  nsresult rv = Preferences::SetInt(prefName, value);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+}
+
+void SetFunc(const char* prefName, uint32_t value)
+{
+  nsresult rv = Preferences::SetUint(prefName, value);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+}
+
+void SetFunc(const char* prefName, float value)
+{
+  nsresult rv = Preferences::SetFloat(prefName, value);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+}
+
+void AddVarCacheFunc(bool* var, const char* prefName)
+{
+  nsresult rv = Preferences::AddBoolVarCache(var, prefName);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+}
+
+void AddVarCacheFunc(int32_t* var, const char* prefName)
+{
+  nsresult rv = Preferences::AddIntVarCache(var, prefName);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+}
+
+void AddVarCacheFunc(uint32_t* var, const char* prefName)
+{
+  nsresult rv = Preferences::AddUintVarCache(var, prefName);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+}
+
+void AddVarCacheFunc(Atomic<uint32_t,Relaxed>* var,
+                     const char* prefName)
+{
+  nsresult rv = Preferences::AddAtomicUintVarCache(var, prefName);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+}
+
+void AddVarCacheFunc(float* var, const char* prefName)
+{
+  nsresult rv = Preferences::AddFloatVarCache(var, prefName);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+}
+
+template<typename T, typename U = T>
+void RunTest(const char* prefName1, const char* prefName2,
+             T value1, T value2)
+{
+  static U var1, var2;
+  static Closure<T, U> closure1, closure2;
+  nsresult rv;
+
+  ASSERT_STRNE(prefName1, prefName2);
+  ASSERT_NE(value1, value2);
+
+  //
+  // Call Add*VarCache first.
+  //
+  SetFunc(prefName1, value1);
+
+  AddVarCacheFunc(&var1, prefName1);
+  ASSERT_EQ(var1, value1);
+
+  closure1 = { &var1, value2 };
+  rv = Preferences::RegisterCallback(VarChanged<T, U>, prefName1, &closure1);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  ASSERT_FALSE(closure1.called);
+  SetFunc(prefName1, value2);
+  ASSERT_EQ(var1, value2);
+  ASSERT_TRUE(closure1.called);
+
+  //
+  // Call RegisterCallback first.
+  //
+  SetFunc(prefName2, value1);
+
+  closure2 = { &var2, value2 };
+  rv = Preferences::RegisterCallback(VarChanged<T, U>, prefName2, &closure2);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  AddVarCacheFunc(&var2, prefName2);
+  ASSERT_EQ(var2, value1);
+
+  ASSERT_FALSE(closure2.called);
+  SetFunc(prefName2, value2);
+  ASSERT_EQ(var2, value2);
+  ASSERT_TRUE(closure2.called);
+}
+
+TEST(CallbackAndVarCacheOrder, Bool) {
+  RunTest<bool>("test_pref.bool.1", "test_pref.bool.2", false, true);
+}
+
+TEST(CallbackAndVarCacheOrder, Int) {
+  RunTest<int32_t>("test_pref.int.1", "test_pref.int.2", -2, 3);
+}
+
+TEST(CallbackAndVarCacheOrder, Uint) {
+  RunTest<uint32_t>("test_pref.uint.1", "test_pref.uint.2", 4u, 5u);
+}
+
+TEST(CallbackAndVarCacheOrder, AtomicUint) {
+  RunTest<uint32_t,Atomic<uint32_t,Relaxed>>("test_pref.atomic_uint.1",
+                                             "test_pref.atomic_uint.2",
+                                             6u, 7u);
+}
+
+TEST(CallbackAndVarCacheOrder, Float) {
+  RunTest<float>("test_pref.float.1", "test_pref.float.2", -8.0f, 9.0f);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/modules/libpref/test/gtest/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+Library('libpreftests')
+
+LOCAL_INCLUDES += [
+    '../..',
+]
+
+UNIFIED_SOURCES = [
+    'CallbackAndVarCacheOrder.cpp',
+]
+
+if CONFIG['GNU_CXX']:
+    CXXFLAGS += ['-Wno-error=shadow']
+
+# THE MOCK_METHOD2 macro from gtest triggers this clang warning and it's hard
+# to work around, so we just ignore it.
+if CONFIG['CLANG_CXX']:
+  CXXFLAGS += ['-Wno-inconsistent-missing-override']
+  # Workaround bug 1142396. Suppress the warning from gmock library for clang.
+  CXXFLAGS += ['-Wno-null-dereference']
+
+FINAL_LIBRARY = 'xul-gtest'
--- a/netwerk/base/ThrottlingService.cpp
+++ b/netwerk/base/ThrottlingService.cpp
@@ -17,20 +17,26 @@
 
 #include "mozilla/net/NeckoChild.h"
 
 namespace mozilla {
 namespace net{
 
 static const char kEnabledPref[] = "network.throttle.enable";
 static const bool kDefaultEnabled = true;
+
+// During a page load presure, every channel that is marked as Throttleable
+// is being periodically suspended and resumed for the suspend-for and
+// resume-for intervals respectively.  This gives more bandwidth to other
+// more priority responses.
+
 static const char kSuspendPeriodPref[] = "network.throttle.suspend-for";
-static const uint32_t kDefaultSuspendPeriod = 2000;
+static const uint32_t kDefaultSuspendPeriod = 3000;
 static const char kResumePeriodPref[] = "network.throttle.resume-for";
-static const uint32_t kDefaultResumePeriod = 2000;
+static const uint32_t kDefaultResumePeriod = 200;
 
 NS_IMPL_ISUPPORTS(ThrottlingService, nsIThrottlingService, nsIObserver, nsITimerCallback)
 
 ThrottlingService::ThrottlingService()
   :mEnabled(kDefaultEnabled)
   ,mInitCalled(false)
   ,mSuspended(false)
   ,mPressureCount(0)
@@ -132,18 +138,17 @@ ThrottlingService::AddChannel(nsIHttpCha
   }
 
   uint64_t key;
   nsresult rv = channel->GetChannelId(&key);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mChannelHash.Get(key, nullptr)) {
     // We already have this channel under our control, not adding it again.
-    MOZ_ASSERT(false, "Trying to throttle an already-throttled channel");
-    return NS_ERROR_ILLEGAL_VALUE;
+    return NS_OK;
   }
 
   if (!mIteratingHash) {
     // This should be the common case, and as such is easy to handle
     mChannelHash.Put(key, channel);
 
     if (mSuspended) {
       channel->Suspend();
--- a/netwerk/cache2/CacheHashUtils.cpp
+++ b/netwerk/cache2/CacheHashUtils.cpp
@@ -33,17 +33,16 @@ static inline void hashmix(uint32_t& a, 
 }
 
 CacheHash::Hash32_t
 CacheHash::Hash(const char *aData, uint32_t aSize, uint32_t aInitval)
 {
   const uint8_t *k = reinterpret_cast<const uint8_t*>(aData);
   uint32_t a, b, c, len;
 
-//  length = PL_strlen(key);
   /* Set up the internal state */
   len = aSize;
   a = b = 0x9e3779b9;  /* the golden ratio; an arbitrary value */
   c = aInitval;        /* variable initialization of internal state */
 
   /*---------------------------------------- handle most of the key */
   while (len >= 12)
   {
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -4228,10 +4228,16 @@ Http2Session::JoinConnection(const nsACS
       if (NS_SUCCEEDED(rv) && isJoined) {
         return true;
       }
     }
   }
   return false;
 }
 
+void
+Http2Session::ThrottleResponse(bool aThrottle)
+{
+  // Response throttling on an h2 connection will be implemented later.
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/Http2Session.h
+++ b/netwerk/protocol/http/Http2Session.h
@@ -38,25 +38,26 @@ class Http2Session final : public ASpdyS
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSAHTTPTRANSACTION
   NS_DECL_NSAHTTPCONNECTION(mConnection)
   NS_DECL_NSAHTTPSEGMENTREADER
   NS_DECL_NSAHTTPSEGMENTWRITER
 
- Http2Session(nsISocketTransport *, uint32_t version, bool attemptingEarlyData);
+  Http2Session(nsISocketTransport *, uint32_t version, bool attemptingEarlyData);
 
   MOZ_MUST_USE bool AddStream(nsAHttpTransaction *, int32_t,
                               bool, nsIInterfaceRequestor *) override;
   bool CanReuse() override { return !mShouldGoAway && !mClosed; }
   bool RoomForMoreStreams() override;
   uint32_t SpdyVersion() override;
   bool TestJoinConnection(const nsACString &hostname, int32_t port) override;
   bool JoinConnection(const nsACString &hostname, int32_t port) override;
+  void ThrottleResponse(bool aThrottle) override;
 
   // When the connection is active this is called up to once every 1 second
   // return the interval (in seconds) that the connection next wants to
   // have this invoked. It might happen sooner depending on the needs of
   // other connections.
   uint32_t  ReadTimeoutTick(PRIntervalTime now) override;
 
   // Idle time represents time since "goodput".. e.g. a data or header frame
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -141,16 +141,24 @@ public:
     virtual int64_t BytesWritten() = 0;
 
     // Update the callbacks used to provide security info. May be called on
     // any thread.
     virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) = 0;
 
     // nsHttp.h version
     virtual uint32_t Version() = 0;
+
+    // Throttling control, can be called only on the socket thread. HTTP/1
+    // implementation effects whether we AsyncWait on the socket input stream
+    // after reading data.  This doesn't have a counter-like logic, hence
+    // calling it with aThrottle = false will re-enable read from the socket
+    // immediately.  Calling more than once with the same argument value has
+    // no effect.
+    virtual void ThrottleResponse(bool aThrottle) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID)
 
 #define NS_DECL_NSAHTTPCONNECTION(fwdObject)                    \
     MOZ_MUST_USE nsresult OnHeadersAvailable(nsAHttpTransaction *,  \
                                              nsHttpRequestHead *,   \
                                              nsHttpResponseHead *,  \
@@ -243,12 +251,15 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpCon
     {     return fwdObject ? (fwdObject)->BytesWritten() : 0; } \
     void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) \
       override                                              \
     {                                                       \
         if (fwdObject)                                      \
             (fwdObject)->SetSecurityCallbacks(aCallbacks);  \
     }
 
+    // ThrottleResponse deliberately ommited since we want different implementation
+    // for h1 and h2 connections.
+
 } // namespace net
 } // namespace mozilla
 
 #endif // nsAHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -591,16 +591,20 @@ nsHttpChannel::ContinueConnect()
 
     rv = mTransactionPump->AsyncRead(this, nullptr);
     if (NS_FAILED(rv)) return rv;
 
     uint32_t suspendCount = mSuspendCount;
     while (suspendCount--)
         mTransactionPump->Suspend();
 
+    if (mSuspendCount && mClassOfService & nsIClassOfService::Throttleable) {
+        gHttpHandler->ThrottleTransaction(mTransaction, true);
+    }
+
     return NS_OK;
 }
 
 void
 nsHttpChannel::SpeculativeConnect()
 {
     // Before we take the latency hit of dealing with the cache, try and
     // get the TCP (and SSL) handshakes going so they can overlap.
@@ -1365,26 +1369,16 @@ nsHttpChannel::CallOnStartRequest()
         mOnStartRequestCalled = true;
         if (NS_FAILED(rv))
             return rv;
     } else {
         NS_WARNING("OnStartRequest skipped because of null listener");
         mOnStartRequestCalled = true;
     }
 
-    if (mClassOfService & nsIClassOfService::Throttleable) {
-        nsIThrottlingService *throttler = gHttpHandler->GetThrottlingService();
-        if (throttler) {
-            // This may immediately Suspend() this channel. We also may have
-            // done this already, during AsyncOpen. However, calling AddChannel
-            // twice doesn't hurt anything.
-            throttler->AddChannel(this);
-        }
-    }
-
     // Install stream converter if required.
     // If we use unknownDecoder, stream converters will be installed later (in
     // nsUnknownDecoder) after OnStartRequest is called for the real listener.
     if (!unknownDecoderStarted) {
       nsCOMPtr<nsIStreamListener> listener;
       nsISupports *ctxt = mListenerContext;
       rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), ctxt);
       if (NS_FAILED(rv)) {
@@ -6215,24 +6209,16 @@ nsHttpChannel::BeginConnect()
 
     // We may have been cancelled already, either by on-modify-request
     // listeners or load group observers; in that case, we should not send the
     // request to the server
     if (mCanceled) {
         return mStatus;
     }
 
-    if (mClassOfService & nsIClassOfService::Throttleable) {
-        nsIThrottlingService *throttler = gHttpHandler->GetThrottlingService();
-        if (throttler) {
-            // This may immediately Suspend() this channel.
-            throttler->AddChannel(this);
-        }
-    }
-
     if (!(mLoadFlags & LOAD_CLASSIFY_URI)) {
         return ContinueBeginConnectWithResult();
     }
 
     // We are about to do a async lookup to check if the URI is a
     // tracker. The result will be delivered along with the callback.
     // Chances are the lookup is not needed so InitLocalBlockList()
     // will return false and then we can BeginConnectActual() right away.
@@ -6431,34 +6417,66 @@ nsHttpChannel::ContinueBeginConnect()
         CloseCacheEntry(false);
         Unused << AsyncAbort(rv);
     }
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannel::nsIClassOfService
 //-----------------------------------------------------------------------------
+
+void
+nsHttpChannel::OnClassOfServiceUpdated()
+{
+    bool throttleable = !!(mClassOfService & nsIClassOfService::Throttleable);
+
+    if (mSuspendCount && mTransaction) {
+        gHttpHandler->ThrottleTransaction(mTransaction, throttleable);
+    }
+
+    nsIThrottlingService *throttler = gHttpHandler->GetThrottlingService();
+    if (throttler) {
+        if (throttleable) {
+            throttler->AddChannel(this);
+        } else {
+            throttler->RemoveChannel(this);
+        }
+    }
+}
+
 NS_IMETHODIMP
 nsHttpChannel::SetClassFlags(uint32_t inFlags)
 {
+    uint32_t previous = mClassOfService;
     mClassOfService = inFlags;
+    if (previous != mClassOfService) {
+        OnClassOfServiceUpdated();
+    }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::AddClassFlags(uint32_t inFlags)
 {
+    uint32_t previous = mClassOfService;
     mClassOfService |= inFlags;
+    if (previous != mClassOfService) {
+        OnClassOfServiceUpdated();
+    }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpChannel::ClearClassFlags(uint32_t inFlags)
 {
+    uint32_t previous = mClassOfService;
     mClassOfService &= ~inFlags;
+    if (previous != mClassOfService) {
+        OnClassOfServiceUpdated();
+    }
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIProtocolProxyCallback
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
@@ -7775,16 +7793,20 @@ nsHttpChannel::DoAuthRetry(nsAHttpConnec
 
     rv = mTransactionPump->AsyncRead(this, nullptr);
     if (NS_FAILED(rv)) return rv;
 
     uint32_t suspendCount = mSuspendCount;
     while (suspendCount--)
         mTransactionPump->Suspend();
 
+    if (mSuspendCount && mClassOfService & nsIClassOfService::Throttleable) {
+        gHttpHandler->ThrottleTransaction(mTransaction, true);
+    }
+
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel::nsIApplicationCacheChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
@@ -8481,16 +8503,20 @@ nsHttpChannel::SuspendInternal()
     NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE);
 
     LOG(("nsHttpChannel::SuspendInternal [this=%p]\n", this));
 
     ++mSuspendCount;
 
     if (mSuspendCount == 1) {
         mSuspendTimestamp = TimeStamp::NowLoRes();
+
+        if (mClassOfService & nsIClassOfService::Throttleable && mTransaction) {
+            gHttpHandler->ThrottleTransaction(mTransaction, true);
+        }
     }
 
     nsresult rvTransaction = NS_OK;
     if (mTransactionPump) {
         rvTransaction = mTransactionPump->Suspend();
     }
     nsresult rvCache = NS_OK;
     if (mCachePump) {
@@ -8506,16 +8532,20 @@ nsHttpChannel::ResumeInternal()
     NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
 
     LOG(("nsHttpChannel::ResumeInternal [this=%p]\n", this));
 
     if (--mSuspendCount == 0) {
         mSuspendTotalTime += (TimeStamp::NowLoRes() - mSuspendTimestamp).
                                ToMilliseconds();
 
+        if (mClassOfService & nsIClassOfService::Throttleable && mTransaction) {
+            gHttpHandler->ThrottleTransaction(mTransaction, false);
+        }
+
         if (mCallOnResume) {
             nsresult rv = AsyncCall(mCallOnResume);
             mCallOnResume = nullptr;
             NS_ENSURE_SUCCESS(rv, rv);
         }
     }
 
     nsresult rvTransaction = NS_OK;
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -336,16 +336,18 @@ private:
     MOZ_MUST_USE nsresult EnsureAssocReq();
     void     ProcessSSLInformation();
     bool     IsHTTPS();
 
     MOZ_MUST_USE nsresult ContinueOnStartRequest1(nsresult);
     MOZ_MUST_USE nsresult ContinueOnStartRequest2(nsresult);
     MOZ_MUST_USE nsresult ContinueOnStartRequest3(nsresult);
 
+    void OnClassOfServiceUpdated();
+
     bool InitLocalBlockList(const InitLocalBlockListCallback& aCallback);
 
     // redirection specific methods
     void     HandleAsyncRedirect();
     void     HandleAsyncAPIRedirect();
     MOZ_MUST_USE nsresult ContinueHandleAsyncRedirect(nsresult);
     void     HandleAsyncNotModified();
     void     HandleAsyncFallback();
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -80,16 +80,18 @@ nsHttpConnection::nsHttpConnection()
     , mResponseTimeoutEnabled(false)
     , mTCPKeepaliveConfig(kTCPKeepaliveDisabled)
     , mForceSendPending(false)
     , m0RTTChecked(false)
     , mWaitingFor0RTTResponse(false)
     , mContentBytesWritten0RTT(0)
     , mEarlyDataNegotiated(false)
     , mDid0RTTSpdy(false)
+    , mResponseThrottled(false)
+    , mResumeRecvOnUnthrottle(false)
 {
     LOG(("Creating nsHttpConnection @%p\n", this));
 
     // the default timeout is for when this connection has not yet processed a
     // transaction
     static const PRIntervalTime k5Sec = PR_SecondsToInterval(5);
     mIdleTimeout =
         (k5Sec < gHttpHandler->IdleTimeout()) ? k5Sec : gHttpHandler->IdleTimeout();
@@ -1389,16 +1391,31 @@ nsHttpConnection::ResumeSend()
 
 nsresult
 nsHttpConnection::ResumeRecv()
 {
     LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this));
 
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
+    // mResponseThrottled is an indication from above layers to stop reading
+    // the socket.
+    if (mResponseThrottled) {
+        mResumeRecvOnUnthrottle = true;
+
+        if (mSocketIn) {
+            LOG(("  throttled, waiting for closure only"));
+            return mSocketIn->AsyncWait(this,
+                                        nsIAsyncInputStream::WAIT_CLOSURE_ONLY,
+                                        0, nullptr);
+        }
+        LOG(("  throttled, and no socket input stream"));
+        return NS_OK;
+    }
+
     // the mLastReadTime timestamp is used for finding slowish readers
     // and can be pretty sensitive. For that reason we actually reset it
     // when we ask to read (resume recv()) so that when we get called back
     // with actual read data in OnSocketReadable() we are only measuring
     // the latency between those two acts and not all the processing that
     // may get done before the ResumeRecv() call
     mLastReadTime = PR_IntervalNow();
 
@@ -2083,16 +2100,42 @@ nsHttpConnection::DisableTCPKeepalives()
     }
     if (mTCPKeepaliveTransitionTimer) {
         mTCPKeepaliveTransitionTimer->Cancel();
         mTCPKeepaliveTransitionTimer = nullptr;
     }
     return NS_OK;
 }
 
+void nsHttpConnection::ThrottleResponse(bool aThrottle)
+{
+    LOG(("nsHttpConnection::ThrottleResponse this=%p, throttle=%d", this, aThrottle));
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    if (aThrottle) {
+        mResponseThrottled = true;
+        return;
+    }
+
+    mResponseThrottled = false;
+
+    if (!mResumeRecvOnUnthrottle) {
+        // We didn't get to the point when ResumeRecv was called
+        // during the throttle period, nothing to do.
+        return;
+    }
+
+    mResumeRecvOnUnthrottle = false;
+
+    nsresult rv = ResumeRecv();
+    if (NS_FAILED(rv)) {
+        CloseTransaction(mTransaction, rv);
+    }
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpConnection::nsISupports
 //-----------------------------------------------------------------------------
 
 NS_IMPL_ADDREF(nsHttpConnection)
 NS_IMPL_RELEASE(nsHttpConnection)
 
 NS_INTERFACE_MAP_BEGIN(nsHttpConnection)
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -220,16 +220,18 @@ public:
             (mTrafficCount == (mTotalBytesWritten + mTotalBytesRead));
     }
     // override of nsAHttpConnection
     virtual uint32_t Version();
 
     bool TestJoinConnection(const nsACString &hostname, int32_t port);
     bool JoinConnection(const nsACString &hostname, int32_t port);
 
+    void ThrottleResponse(bool aThrottle);
+
 private:
     // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
     enum TCPKeepaliveConfig {
       kTCPKeepaliveDisabled = 0,
       kTCPKeepaliveShortLivedConfig,
       kTCPKeepaliveLongLivedConfig
     };
 
@@ -378,16 +380,23 @@ private:
                                                              // data and we
                                                              // are waiting
                                                              // for the end of
                                                              // the handsake.
     int64_t                        mContentBytesWritten0RTT;
     bool                           mEarlyDataNegotiated; //Only used for telemetry
     nsCString                      mEarlyNegotiatedALPN;
     bool                           mDid0RTTSpdy;
+
+    // Reflects throttling request, effects if we resume read from the socket.
+    // Accessed only on the socket thread.
+    bool                           mResponseThrottled;
+    // A read from the socket was requested while we where throttled, means
+    // to ResumeRecv() when untrottled again. Only accessed on the socket thread.
+    bool                           mResumeRecvOnUnthrottle;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnection, NS_HTTPCONNECTION_IID)
 
 } // namespace net
 } // namespace mozilla
 
 #endif // nsHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -340,16 +340,25 @@ nsHttpConnectionMgr::AddTransaction(nsHt
 
 nsresult
 nsHttpConnectionMgr::RescheduleTransaction(nsHttpTransaction *trans, int32_t priority)
 {
     LOG(("nsHttpConnectionMgr::RescheduleTransaction [trans=%p %d]\n", trans, priority));
     return PostEvent(&nsHttpConnectionMgr::OnMsgReschedTransaction, priority, trans);
 }
 
+void
+nsHttpConnectionMgr::ThrottleTransaction(nsHttpTransaction *trans, bool throttle)
+{
+    LOG(("nsHttpConnectionMgr::ThrottleTransaction [trans=%p throttle=%" PRIx32 "]\n",
+         trans, static_cast<uint32_t>(throttle)));
+    Unused << PostEvent(&nsHttpConnectionMgr::OnMsgThrottleTransaction,
+                        static_cast<int32_t>(throttle), trans);
+}
+
 nsresult
 nsHttpConnectionMgr::CancelTransaction(nsHttpTransaction *trans, nsresult reason)
 {
     LOG(("nsHttpConnectionMgr::CancelTransaction [trans=%p reason=%" PRIx32 "]\n",
          trans, static_cast<uint32_t>(reason)));
     return PostEvent(&nsHttpConnectionMgr::OnMsgCancelTransaction,
                      static_cast<int32_t>(reason), trans);
 }
@@ -1582,30 +1591,38 @@ nsHttpConnectionMgr::DispatchTransaction
 // need for consumer code to know when to give the connection back to the
 // connection manager.
 //
 class ConnectionHandle : public nsAHttpConnection
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSAHTTPCONNECTION(mConn)
+    virtual void ThrottleResponse(bool aThrottle) override;
 
     explicit ConnectionHandle(nsHttpConnection *conn) : mConn(conn) { }
     void Reset() { mConn = nullptr; }
 private:
     virtual ~ConnectionHandle();
     RefPtr<nsHttpConnection> mConn;
 };
 
 nsAHttpConnection *
 nsHttpConnectionMgr::MakeConnectionHandle(nsHttpConnection *aWrapped)
 {
     return new ConnectionHandle(aWrapped);
 }
 
+void ConnectionHandle::ThrottleResponse(bool aThrottle)
+{
+    if (mConn) {
+        mConn->ThrottleResponse(aThrottle);
+    }
+}
+
 ConnectionHandle::~ConnectionHandle()
 {
     if (mConn) {
         nsresult rv = gHttpHandler->ReclaimConnection(mConn);
         if (NS_FAILED(rv)) {
             LOG(("ConnectionHandle::~ConnectionHandle\n"
                  "    failed to reclaim connection\n"));
         }
@@ -2157,16 +2174,27 @@ nsHttpConnectionMgr::OnMsgReschedTransac
         if (index >= 0) {
             RefPtr<PendingTransactionInfo> pendingTransInfo = (*pendingQ)[index];
             pendingQ->RemoveElementAt(index);
             InsertTransactionSorted(*pendingQ, pendingTransInfo);
         }
     }
 }
 
+void nsHttpConnectionMgr::OnMsgThrottleTransaction(int32_t arg, ARefBase *param)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    LOG(("nsHttpConnectionMgr::OnMsgThrottleTransaction [trans=%p]\n", param));
+
+    bool throttle = static_cast<bool>(arg);
+    nsHttpTransaction *trans = static_cast<nsHttpTransaction *>(param);
+
+    trans->ThrottleResponse(throttle);
+}
+
 void
 nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
 
     nsresult closeCode = static_cast<nsresult>(reason);
 
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -89,16 +89,24 @@ public:
     // adds a transaction to the list of managed transactions.
     MOZ_MUST_USE nsresult AddTransaction(nsHttpTransaction *, int32_t priority);
 
     // called to reschedule the given transaction.  it must already have been
     // added to the connection manager via AddTransaction.
     MOZ_MUST_USE nsresult RescheduleTransaction(nsHttpTransaction *,
                                                 int32_t priority);
 
+    // tells the transaction to stop receiving the response when |throttle|
+    // is true.  to start receiving again, this must be called with |throttle|
+    // set to false.  calling multiple times with the same value of |throttle|
+    // has no effect.  called by nsHttpChannels with the Throttleable class set
+    // and controlled by net::ThrottlingService.
+    // there is nothing to do when this fails, hence the void result.
+    void ThrottleTransaction(nsHttpTransaction *, bool throttle);
+
     // cancels a transaction w/ the given reason.
     MOZ_MUST_USE nsresult CancelTransaction(nsHttpTransaction *,
                                             nsresult reason);
     MOZ_MUST_USE nsresult CancelTransactions(nsHttpConnectionInfo *,
                                              nsresult reason);
 
     // called to force the connection manager to prune its list of idle
     // connections.
@@ -539,16 +547,17 @@ private:
         const nsConnectionEntry *ent,
         nsresult reason);
 
     // message handlers
     void OnMsgShutdown             (int32_t, ARefBase *);
     void OnMsgShutdownConfirm      (int32_t, ARefBase *);
     void OnMsgNewTransaction       (int32_t, ARefBase *);
     void OnMsgReschedTransaction   (int32_t, ARefBase *);
+    void OnMsgThrottleTransaction  (int32_t, ARefBase *);
     void OnMsgCancelTransaction    (int32_t, ARefBase *);
     void OnMsgCancelTransactions   (int32_t, ARefBase *);
     void OnMsgProcessPendingQ      (int32_t, ARefBase *);
     void OnMsgPruneDeadConnections (int32_t, ARefBase *);
     void OnMsgSpeculativeConnect   (int32_t, ARefBase *);
     void OnMsgReclaimConnection    (int32_t, ARefBase *);
     void OnMsgCompleteUpgrade      (int32_t, ARefBase *);
     void OnMsgUpdateParam          (int32_t, ARefBase *);
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -199,16 +199,22 @@ public:
     // Called to change the priority of an existing transaction that has
     // already been initiated.
     MOZ_MUST_USE nsresult RescheduleTransaction(nsHttpTransaction *trans,
                                                 int32_t priority)
     {
         return mConnMgr->RescheduleTransaction(trans, priority);
     }
 
+    void ThrottleTransaction(nsHttpTransaction *trans,
+                                              bool throttle)
+    {
+        mConnMgr->ThrottleTransaction(trans, throttle);
+    }
+
     // Called to cancel a transaction, which may or may not be assigned to
     // a connection.  Callable from any thread.
     MOZ_MUST_USE nsresult CancelTransaction(nsHttpTransaction *trans,
                                             nsresult reason)
     {
         return mConnMgr->CancelTransaction(trans, reason);
     }
 
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -104,16 +104,17 @@ nsHttpTransaction::nsHttpTransaction()
     , mPriority(0)
     , mRestartCount(0)
     , mCaps(0)
     , mHttpVersion(NS_HTTP_VERSION_UNKNOWN)
     , mHttpResponseCode(0)
     , mCurrentHttpResponseHeaderSize(0)
     , mCapsToClear(0)
     , mResponseIsComplete(false)
+    , mThrottleResponse(false)
     , mClosed(false)
     , mConnected(false)
     , mHaveStatusLine(false)
     , mHaveAllHeaders(false)
     , mTransactionDone(false)
     , mDidContentStart(false)
     , mNoContent(false)
     , mSentData(false)
@@ -149,16 +150,28 @@ nsHttpTransaction::nsHttpTransaction()
 #ifdef MOZ_VALGRIND
     memset(&mSelfAddr, 0, sizeof(NetAddr));
     memset(&mPeerAddr, 0, sizeof(NetAddr));
 #endif
     mSelfAddr.raw.family = PR_AF_UNSPEC;
     mPeerAddr.raw.family = PR_AF_UNSPEC;
 }
 
+void nsHttpTransaction::ThrottleResponse(bool aThrottle)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    // Just in case we suspend, get a connection, release a connection, get another connection.
+    mThrottleResponse = aThrottle;
+
+    if (mConnection) {
+        mConnection->ThrottleResponse(aThrottle);
+    }
+}
+
 nsHttpTransaction::~nsHttpTransaction()
 {
     LOG(("Destroying nsHttpTransaction @%p\n", this));
     if (mTransactionObserver) {
         mTransactionObserver->Complete(this, NS_OK);
     }
     if (mPushedStream) {
         mPushedStream->OnPushFailed();
@@ -467,16 +480,20 @@ nsHttpTransaction::TakeSubTransactions(
 
 void
 nsHttpTransaction::SetConnection(nsAHttpConnection *conn)
 {
     {
         MutexAutoLock lock(mLock);
         mConnection = conn;
     }
+
+    if (conn && mThrottleResponse) {
+        gHttpHandler->ThrottleTransaction(this, true);
+    }
 }
 
 void
 nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb)
 {
     MutexAutoLock lock(mLock);
     nsCOMPtr<nsIInterfaceRequestor> tmp(mCallbacks);
     tmp.forget(cb);
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -302,16 +302,19 @@ private:
     // redundant requests on the network. The member itself is atomic, but
     // access to it from the networking thread may happen either before or
     // after the main thread modifies it. To deal with raciness, only unsetting
     // bitfields should be allowed: 'lost races' will thus err on the
     // conservative side, e.g. by going ahead with a 2nd DNS refresh.
     Atomic<uint32_t>                mCapsToClear;
     Atomic<bool, ReleaseAcquire>    mResponseIsComplete;
 
+    // If true, this transaction was asked to stop receiving the response.
+    bool                            mThrottleResponse;
+
     // state flags, all logically boolean, but not packed together into a
     // bitfield so as to avoid bitfield-induced races.  See bug 560579.
     bool                            mClosed;
     bool                            mConnected;
     bool                            mHaveStatusLine;
     bool                            mHaveAllHeaders;
     bool                            mTransactionDone;
     bool                            mDidContentStart;
@@ -364,16 +367,20 @@ public:
     void OnTokenBucketAdmitted() override; // ATokenBucketEvent
 
     // CancelPacing() can be used to tell the token bucket to remove this
     // transaction from the list of pending transactions. This is used when a
     // transaction is believed to be HTTP/1 (and thus subject to rate pacing)
     // but later can be dispatched via spdy (not subject to rate pacing).
     void CancelPacing(nsresult reason);
 
+    // Forwards to the connection's ThrottleResponse.  If there is no connection
+    // at the time, we set a flag to do it on connection assignment.
+    void ThrottleResponse(bool aThrottle);
+
 private:
     bool mSubmittedRatePacing;
     bool mPassedRatePacing;
     bool mSynchronousRatePaceRequest;
     nsCOMPtr<nsICancelable> mTokenBucketCancel;
 public:
     void     SetClassOfService(uint32_t cos) { mClassOfService = cos; }
     uint32_t ClassOfService() { return mClassOfService; }
--- a/netwerk/wifi/nsWifiScannerSolaris.cpp
+++ b/netwerk/wifi/nsWifiScannerSolaris.cpp
@@ -4,17 +4,16 @@
 
 #include "nsWifiMonitor.h"
 #include "nsWifiAccessPoint.h"
 
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIMutableArray.h"
 
-#include "plstr.h"
 #include <glib.h>
 
 #define DLADM_STRSIZE 256
 #define DLADM_SECTIONS 3
 
 using namespace mozilla;
 
 struct val_strength_t {
@@ -45,17 +44,18 @@ do_parse_str(char *bssid_str, char *essi
       break;
     }
   }
 
   nsWifiAccessPoint *ap = new nsWifiAccessPoint();
   if (ap) {
     ap->setMac(mac_as_int);
     ap->setSignal(signal);
-    ap->setSSID(essid_str, PL_strnlen(essid_str, DLADM_STRSIZE));
+    size_t len = essid_str ? strnlen(essid_str, DLADM_STRSIZE) : 0;
+    ap->setSSID(essid_str, len);
   }
   return ap;
 }
 
 static void
 do_dladm(nsCOMArray<nsWifiAccessPoint> &accessPoints)
 {
   GError *err = nullptr;
--- a/python/mozbuild/mozbuild/artifacts.py
+++ b/python/mozbuild/mozbuild/artifacts.py
@@ -62,17 +62,16 @@ import zipfile
 
 import pylru
 from taskgraph.util.taskcluster import (
     find_task_id,
     get_artifact_url,
     list_artifacts,
 )
 
-from mozbuild.action.test_archive import OBJDIR_TEST_FILES
 from mozbuild.util import (
     ensureParentDir,
     FileAvoidWrite,
 )
 import mozinstall
 from mozpack.files import (
     JarFinder,
     TarFinder,
@@ -94,19 +93,22 @@ NUM_PUSHHEADS_TO_QUERY_PER_PARENT = 50  
 # There isn't really such a thing as a reasonable default here, because we don't
 # know how many pushheads we'll need to look at to find a build with our artifacts,
 # and we don't know how many changesets will be in each push. For now we assume
 # we'll find a build in the last 50 pushes, assuming each push contains 10 changesets.
 NUM_REVISIONS_TO_QUERY = 500
 
 MAX_CACHED_TASKS = 400  # Number of pushheads to cache Task Cluster task data for.
 
-# Number of downloaded artifacts to cache.  Each artifact can be very large,
-# so don't make this to large!  TODO: make this a size (like 500 megs) rather than an artifact count.
-MAX_CACHED_ARTIFACTS = 6
+# Minimum number of downloaded artifacts to keep. Each artifact can be very large,
+# so don't make this to large!
+MIN_CACHED_ARTIFACTS = 6
+
+# Maximum size of the downloaded artifacts to keep in cache, in bytes (1GiB).
+MAX_CACHED_ARTIFACTS_SIZE = 1024 * 1024 * 1024
 
 # Downloaded artifacts are cached, and a subset of their contents extracted for
 # easy installation.  This is most noticeable on Mac OS X: since mounting and
 # copying from DMG files is very slow, we extract the desired binaries to a
 # separate archive for fast re-installation.
 PROCESSED_SUFFIX = '.processed.jar'
 
 CANDIDATE_TREES = (
@@ -180,16 +182,17 @@ class ArtifactJob(object):
         if self._symbols_archive_suffix and filename.endswith(self._symbols_archive_suffix):
             return self.process_symbols_archive(filename, processed_filename)
         return self.process_package_artifact(filename, processed_filename)
 
     def process_package_artifact(self, filename, processed_filename):
         raise NotImplementedError("Subclasses must specialize process_package_artifact!")
 
     def process_tests_artifact(self, filename, processed_filename):
+        from mozbuild.action.test_archive import OBJDIR_TEST_FILES
         added_entry = False
 
         with JarWriter(file=processed_filename, optimize=False, compress_level=5) as writer:
             reader = JarReader(filename)
             for filename, entry in reader.entries.iteritems():
                 for pattern, (src_prefix, dest_prefix) in self.test_artifact_patterns:
                     if not mozpath.match(filename, pattern):
                         continue
@@ -659,47 +662,112 @@ class TaskCache(CacheManager):
             # public/build/buildprops.json for this purpose.
             url = get_artifact_url(taskId, artifact_name)
             urls.append(url)
         if not urls:
             raise ValueError('Task for {namespace} existed, but no artifacts found!'.format(namespace=namespace))
         return urls
 
 
-class ArtifactCache(CacheManager):
+class ArtifactPersistLimit(PersistLimit):
+    '''Handle persistence for artifacts cache
+
+    When instantiating a DownloadManager, it starts by filling the
+    PersistLimit instance it's given with register_dir_content.
+    In practice, this registers all the files already in the cache directory.
+    After a download finishes, the newly downloaded file is registered, and the
+    oldest files registered to the PersistLimit instance are removed depending
+    on the size and file limits it's configured for.
+    This is all good, but there are a few tweaks we want here:
+    - We have pickle files in the cache directory that we don't want purged.
+    - Files that were just downloaded in the same session shouldn't be purged.
+      (if for some reason we end up downloading more than the default max size,
+       we don't want the files to be purged)
+    To achieve this, this subclass of PersistLimit inhibits the register_file
+    method for pickle files and tracks what files were downloaded in the same
+    session to avoid removing them.
+
+    The register_file method may be used to register cache matches too, so that
+    later sessions know they were freshly used.
+    '''
+
+    def __init__(self, log=None):
+        super(ArtifactPersistLimit, self).__init__(
+            size_limit=MAX_CACHED_ARTIFACTS_SIZE,
+            file_limit=MIN_CACHED_ARTIFACTS)
+        self._log = log
+        self._registering_dir = False
+        self._downloaded_now = set()
+
+    def log(self, *args, **kwargs):
+        if self._log:
+            self._log(*args, **kwargs)
+
+    def register_file(self, path):
+        if path.endswith('.pickle'):
+            return
+        if not self._registering_dir:
+            # Touch the file so that subsequent calls to a mach artifact
+            # command know it was recently used. While remove_old_files
+            # is based on access time, in various cases, the access time is not
+            # updated when just reading the file, so we force an update.
+            try:
+                os.utime(path, None)
+            except OSError:
+                pass
+            self._downloaded_now.add(path)
+        super(ArtifactPersistLimit, self).register_file(path)
+
+    def register_dir_content(self, directory, pattern="*"):
+        self._registering_dir = True
+        super(ArtifactPersistLimit, self).register_dir_content(
+            directory, pattern)
+        self._registering_dir = False
+
+    def remove_old_files(self):
+        from dlmanager import fs
+        files = sorted(self.files, key=lambda f: f.stat.st_atime)
+        kept = []
+        while len(files) > self.file_limit and \
+                self._files_size >= self.size_limit:
+            f = files.pop(0)
+            if f.path in self._downloaded_now:
+                kept.append(f)
+                continue
+            fs.remove(f.path)
+            self.log(logging.INFO, 'artifact',
+                {'filename': f.path},
+                'Purged artifact {filename}')
+            self._files_size -= f.stat.st_size
+        self.files = files + kept
+
+    def remove_all(self):
+        from dlmanager import fs
+        for f in self.files:
+            fs.remove(f.path)
+        self._files_size = 0
+        self.files = []
+
+
+class ArtifactCache(object):
     '''Fetch Task Cluster artifact URLs and purge least recently used artifacts from disk.'''
 
     def __init__(self, cache_dir, log=None, skip_cache=False):
-        # TODO: instead of storing N artifact packages, store M megabytes.
-        CacheManager.__init__(self, cache_dir, 'fetch', MAX_CACHED_ARTIFACTS, cache_callback=self.delete_file, log=log, skip_cache=skip_cache)
         self._cache_dir = cache_dir
-        size_limit = 1024 * 1024 * 1024 # 1Gb in bytes.
-        file_limit = 4 # But always keep at least 4 old artifacts around.
-        persist_limit = PersistLimit(size_limit, file_limit)
-        self._download_manager = DownloadManager(self._cache_dir, persist_limit=persist_limit)
+        self._log = log
+        self._skip_cache = skip_cache
+        self._persist_limit = ArtifactPersistLimit(log)
+        self._download_manager = DownloadManager(
+            self._cache_dir, persist_limit=self._persist_limit)
         self._last_dl_update = -1
 
-    def delete_file(self, key, value):
-        try:
-            os.remove(value)
-            self.log(logging.INFO, 'artifact',
-                {'filename': value},
-                'Purged artifact {filename}')
-        except (OSError, IOError):
-            pass
+    def log(self, *args, **kwargs):
+        if self._log:
+            self._log(*args, **kwargs)
 
-        try:
-            os.remove(value + PROCESSED_SUFFIX)
-            self.log(logging.INFO, 'artifact',
-                {'filename': value + PROCESSED_SUFFIX},
-                'Purged processed artifact {filename}')
-        except (OSError, IOError):
-            pass
-
-    @cachedmethod(operator.attrgetter('_cache'))
     def fetch(self, url, force=False):
         # We download to a temporary name like HASH[:16]-basename to
         # differentiate among URLs with the same basenames.  We used to then
         # extract the build ID from the downloaded artifact and use it to make a
         # human readable unique name, but extracting build IDs is time consuming
         # (especially on Mac OS X, where we must mount a large DMG file).
         hash = hashlib.sha256(url).hexdigest()[:16]
         fname = hash + '-' + os.path.basename(url)
@@ -713,36 +781,52 @@ class ArtifactCache(CacheManager):
 
         self.log(logging.INFO, 'artifact',
             {'path': path},
             'Downloading to temporary location {path}')
         try:
             dl = self._download_manager.download(url, fname)
 
             def download_progress(dl, bytes_so_far, total_size):
+                if not total_size:
+                    return
                 percent = (float(bytes_so_far) / total_size) * 100
                 now = int(percent / 5)
                 if now == self._last_dl_update:
                     return
                 self._last_dl_update = now
                 self.log(logging.INFO, 'artifact',
                          {'bytes_so_far': bytes_so_far, 'total_size': total_size, 'percent': percent},
                          'Downloading... {percent:02.1f} %')
 
             if dl:
                 dl.set_progress(download_progress)
                 dl.wait()
+            else:
+                # Avoid the file being removed if it was in the cache already.
+                path = os.path.join(self._cache_dir, fname)
+                self._persist_limit.register_file(path)
+
             self.log(logging.INFO, 'artifact',
                 {'path': os.path.abspath(mozpath.join(self._cache_dir, fname))},
                 'Downloaded artifact to {path}')
             return os.path.abspath(mozpath.join(self._cache_dir, fname))
         finally:
             # Cancel any background downloads in progress.
             self._download_manager.cancel()
 
+    def clear_cache(self):
+        if self._skip_cache:
+            self.log(logging.DEBUG, 'artifact',
+                {},
+                'Skipping cache: ignoring clear_cache!')
+            return
+
+        self._persist_limit.remove_all()
+
 
 class Artifacts(object):
     '''Maintain state to efficiently fetch build artifacts from a Firefox tree.'''
 
     def __init__(self, tree, substs, defines, job=None, log=None,
                  cache_dir='.', hg=None, git=None, skip_cache=False,
                  topsrcdir=None):
         if (hg and git) or (not hg and not git):
@@ -934,16 +1018,18 @@ class Artifacts(object):
             self.log(logging.INFO, 'artifact',
                 {'filename': filename},
                 'Processing contents of {filename}')
             self.log(logging.INFO, 'artifact',
                 {'processed_filename': processed_filename},
                 'Writing processed {processed_filename}')
             self._artifact_job.process_artifact(filename, processed_filename)
 
+        self._artifact_cache._persist_limit.register_file(processed_filename)
+
         self.log(logging.INFO, 'artifact',
             {'processed_filename': processed_filename},
             'Installing from processed {processed_filename}')
 
         # Copy all .so files, avoiding modification where possible.
         ensureParentDir(mozpath.join(distdir, '.dummy'))
 
         with zipfile.ZipFile(processed_filename) as zf:
@@ -964,18 +1050,17 @@ class Artifacts(object):
                     perms |= stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH # u+w, a+r.
                     os.chmod(n, perms)
         return 0
 
     def install_from_url(self, url, distdir):
         self.log(logging.INFO, 'artifact',
             {'url': url},
             'Installing from {url}')
-        with self._artifact_cache as artifact_cache:  # The with block handles persistence.
-            filename = artifact_cache.fetch(url)
+        filename = self._artifact_cache.fetch(url)
         return self.install_from_file(filename, distdir)
 
     def _install_from_hg_pushheads(self, hg_pushheads, distdir):
         """Iterate pairs (hg_hash, {tree-set}) associating hg revision hashes
         and tree-sets they are known to be in, trying to download and
         install from each.
         """
 
--- a/python/mozbuild/mozbuild/test/python.ini
+++ b/python/mozbuild/mozbuild/test/python.ini
@@ -21,16 +21,17 @@
 [configure/test_util.py]
 [controller/test_ccachestats.py]
 [controller/test_clobber.py]
 [frontend/test_context.py]
 [frontend/test_emitter.py]
 [frontend/test_namespaces.py]
 [frontend/test_reader.py]
 [frontend/test_sandbox.py]
+[test_artifacts.py]
 [test_base.py]
 [test_containers.py]
 [test_dotproperties.py]
 [test_expression.py]
 [test_jarmaker.py]
 [test_line_endings.py]
 [test_makeutil.py]
 [test_mozconfig.py]
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/test_artifacts.py
@@ -0,0 +1,145 @@
+# 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/.
+
+from __future__ import unicode_literals
+
+import os
+import mozunit
+import time
+import unittest
+from tempfile import mkdtemp
+from shutil import rmtree
+
+from mozbuild.artifacts import ArtifactCache
+from mozbuild import artifacts
+
+
+CONTENTS = {
+    'http://server/foo': b'foo',
+    'http://server/bar': b'bar' * 400,
+    'http://server/qux': b'qux' * 400,
+    'http://server/fuga': b'fuga' * 300,
+    'http://server/hoge': b'hoge' * 300,
+    'http://server/larger': b'larger' * 3000,
+}
+
+class FakeResponse(object):
+    def __init__(self, content):
+        self._content = content
+
+    @property
+    def headers(self):
+        return {
+            'Content-length': str(len(self._content))
+        }
+
+    def iter_content(self, chunk_size):
+        content = memoryview(self._content)
+        while content:
+            yield content[:chunk_size]
+            content = content[chunk_size:]
+
+    def raise_for_status(self):
+        pass
+
+    def close(self):
+        pass
+
+
+class FakeSession(object):
+    def get(self, url, stream=True):
+        assert stream is True
+        return FakeResponse(CONTENTS[url])
+
+
+class TestArtifactCache(unittest.TestCase):
+    def setUp(self):
+        self.min_cached_artifacts = artifacts.MIN_CACHED_ARTIFACTS
+        self.max_cached_artifacts_size = artifacts.MAX_CACHED_ARTIFACTS_SIZE
+        artifacts.MIN_CACHED_ARTIFACTS = 2
+        artifacts.MAX_CACHED_ARTIFACTS_SIZE = 4096
+
+        self._real_utime = os.utime
+        os.utime = self.utime
+        self.timestamp = time.time() - 86400
+
+        self.tmpdir = mkdtemp()
+
+    def tearDown(self):
+        rmtree(self.tmpdir)
+        artifacts.MIN_CACHED_ARTIFACTS = self.min_cached_artifacts
+        artifacts.MAX_CACHED_ARTIFACTS_SIZE = self.max_cached_artifacts_size
+        os.utime = self._real_utime
+
+    def utime(self, path, times):
+        if times is None:
+            # Ensure all downloaded files have a different timestamp
+            times = (self.timestamp, self.timestamp)
+            self.timestamp += 2
+        self._real_utime(path, times)
+
+    def test_artifact_cache_persistence(self):
+        cache = ArtifactCache(self.tmpdir)
+        cache._download_manager.session = FakeSession()
+
+        path = cache.fetch('http://server/foo')
+        expected = [os.path.basename(path)]
+        self.assertEqual(os.listdir(self.tmpdir), expected)
+
+        path = cache.fetch('http://server/bar')
+        expected.append(os.path.basename(path))
+        self.assertEqual(sorted(os.listdir(self.tmpdir)), sorted(expected))
+
+        # We're downloading more than the cache allows us, but since it's all
+        # in the same session, no purge happens.
+        path = cache.fetch('http://server/qux')
+        expected.append(os.path.basename(path))
+        self.assertEqual(sorted(os.listdir(self.tmpdir)), sorted(expected))
+
+        path = cache.fetch('http://server/fuga')
+        expected.append(os.path.basename(path))
+        self.assertEqual(sorted(os.listdir(self.tmpdir)), sorted(expected))
+
+        cache = ArtifactCache(self.tmpdir)
+        cache._download_manager.session = FakeSession()
+
+        # Downloading a new file in a new session purges the oldest files in
+        # the cache.
+        path = cache.fetch('http://server/hoge')
+        expected.append(os.path.basename(path))
+        expected = expected[2:]
+        self.assertEqual(sorted(os.listdir(self.tmpdir)), sorted(expected))
+
+        # Downloading a file already in the cache leaves the cache untouched
+        cache = ArtifactCache(self.tmpdir)
+        cache._download_manager.session = FakeSession()
+
+        path = cache.fetch('http://server/qux')
+        self.assertEqual(sorted(os.listdir(self.tmpdir)), sorted(expected))
+
+        # bar was purged earlier, re-downloading it should purge the oldest
+        # downloaded file, which at this point would be qux, but we also
+        # re-downloaded it in the mean time, so the next one (fuga) should be
+        # the purged one.
+        cache = ArtifactCache(self.tmpdir)
+        cache._download_manager.session = FakeSession()
+
+        path = cache.fetch('http://server/bar')
+        expected.append(os.path.basename(path))
+        expected = [p for p in expected if 'fuga' not in p]
+        self.assertEqual(sorted(os.listdir(self.tmpdir)), sorted(expected))
+
+        # Downloading one file larger than the cache size should still leave
+        # MIN_CACHED_ARTIFACTS files.
+        cache = ArtifactCache(self.tmpdir)
+        cache._download_manager.session = FakeSession()
+
+        path = cache.fetch('http://server/larger')
+        expected.append(os.path.basename(path))
+        expected = expected[-2:]
+        self.assertEqual(sorted(os.listdir(self.tmpdir)), sorted(expected))
+
+
+if __name__ == '__main__':
+    mozunit.main()
--- a/security/certverifier/OCSPRequestor.cpp
+++ b/security/certverifier/OCSPRequestor.cpp
@@ -81,17 +81,17 @@ DoOCSPRequest(const UniquePLArenaPool& a
 {
   MOZ_ASSERT(arena.get());
   MOZ_ASSERT(url);
   MOZ_ASSERT(encodedRequest);
   MOZ_ASSERT(encodedRequest->data);
   if (!arena.get() || !url || !encodedRequest || !encodedRequest->data) {
     return Result::FATAL_ERROR_INVALID_ARGS;
   }
-  uint32_t urlLen = PL_strlen(url);
+  uint32_t urlLen = strlen(url);
   if (urlLen > static_cast<uint32_t>(std::numeric_limits<int32_t>::max())) {
     return Result::FATAL_ERROR_INVALID_ARGS;
   }
 
   nsCOMPtr<nsIURLParser> urlParser = do_GetService(NS_STDURLPARSER_CONTRACTID);
   if (!urlParser) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -1,15 +1,15 @@
 [root]
 name = "webvr_traits"
 version = "0.0.1"
 dependencies = [
  "ipc-channel 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
- "rust-webvr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rust-webvr 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "adler32"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2188,19 +2188,20 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "rust-webvr"
-version = "0.2.0"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
+ "gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libloading 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -3490,17 +3491,17 @@ dependencies = [
 "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
 "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
 "checksum rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50c575b58c2b109e2fbc181820cbe177474f35610ff9e357dc75f6bac854ffbf"
 "checksum redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "29dbdfd4b9df8ab31dec47c6087b7b13cbf4a776f335e4de8efba8288dda075b"
 "checksum ref_filter_map 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2b5ceb840e4009da4841ed22a15eb49f64fdd00a2138945c5beacf506b2fb5ed"
 "checksum ref_slice 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "825740057197b7d43025e7faf6477eaabc03434e153233da02d1f44602f71527"
 "checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01"
 "checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457"
-"checksum rust-webvr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ae0560bf176cd49f08d3df2784f9bfe74df6f6346b71b98ca3358160316e271"
+"checksum rust-webvr 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "454fc4c3a786029ab82c5528c14f01bf965f60f61b3f9b1ed51b4646223eab59"
 "checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95"
 "checksum rustc-serialize 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "684ce48436d6465300c9ea783b6b14c4361d6b8dcbb1375b486a69cc19e2dfb0"
 "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
 "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
 "checksum scoped_threadpool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef399c8893e8cb7aa9696e895427fab3a6bf265977bb96e126f24ddd2cda85a"
 "checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac"
 "checksum serde 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)" = "a702319c807c016e51f672e5c77d6f0b46afddd744b5e437d6b8436b888b458f"
 "checksum serde_codegen_internals 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d52006899f910528a10631e5b727973fe668f3228109d1707ccf5bad5490b6e"
--- a/servo/components/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -97,17 +97,16 @@ use profile_traits::time;
 use script_traits::{AnimationState, AnimationTickType, CompositorEvent};
 use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorMsg, DiscardBrowsingContext};
 use script_traits::{DocumentActivity, DocumentState, LayoutControlMsg, LoadData};
 use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerSchedulerMsg};
 use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory};
 use script_traits::{LogEntry, ServiceWorkerMsg, webdriver_msg};
 use script_traits::{MozBrowserErrorType, MozBrowserEvent, WebDriverCommandMsg, WindowSizeData};
 use script_traits::{SWManagerMsg, ScopeThings, WindowSizeType};
-use script_traits::WebVREventMsg;
 use serde::{Deserialize, Serialize};
 use servo_config::opts;
 use servo_config::prefs::PREFS;
 use servo_rand::{Rng, SeedableRng, ServoRng, random};
 use servo_remutex::ReentrantMutex;
 use servo_url::{Host, ImmutableOrigin, ServoUrl};
 use std::borrow::ToOwned;
 use std::cmp::Ordering;
@@ -119,17 +118,17 @@ use std::rc::{Rc, Weak};
 use std::sync::Arc;
 use std::sync::mpsc::{Receiver, Sender, channel};
 use std::thread;
 use style_traits::CSSPixel;
 use style_traits::cursor::Cursor;
 use style_traits::viewport::ViewportConstraints;
 use timer_scheduler::TimerScheduler;
 use webrender_traits;
-use webvr_traits::WebVRMsg;
+use webvr_traits::{WebVREvent, WebVRMsg};
 
 /// The `Constellation` itself. In the servo browser, there is one
 /// constellation, which maintains all of the browser global data.
 /// In embedded applications, there may be more than one constellation,
 /// which are independent of each other.
 ///
 /// The constellation may be in a different process from the pipelines,
 /// and communicates using IPC.
@@ -892,19 +891,19 @@ impl<Message, LTF, STF> Constellation<Me
             }
             FromCompositorMsg::LogEntry(top_level_frame_id, thread_name, entry) => {
                 self.handle_log_entry(top_level_frame_id, thread_name, entry);
             }
             FromCompositorMsg::SetWebVRThread(webvr_thread) => {
                 assert!(self.webvr_thread.is_none());
                 self.webvr_thread = Some(webvr_thread)
             }
-            FromCompositorMsg::WebVREvent(pipeline_ids, event) => {
-                debug!("constellation got WebVR event");
-                self.handle_webvr_event(pipeline_ids, event);
+            FromCompositorMsg::WebVREvents(pipeline_ids, events) => {
+                debug!("constellation got {:?} WebVR events", events.len());
+                self.handle_webvr_events(pipeline_ids, events);
             }
         }
     }
 
     fn handle_request_from_script(&mut self, message: FromScriptMsg) {
         match message {
             FromScriptMsg::PipelineExited(pipeline_id) => {
                 self.handle_pipeline_exited(pipeline_id);
@@ -1321,22 +1320,22 @@ impl<Message, LTF, STF> Constellation<Me
                 if WARNINGS_BUFFER_SIZE <= self.handled_warnings.len() {
                     self.handled_warnings.pop_front();
                 }
                 self.handled_warnings.push_back((thread_name, reason));
             },
         }
     }
 
-    fn handle_webvr_event(&mut self, ids: Vec<PipelineId>, event: WebVREventMsg) {
+    fn handle_webvr_events(&mut self, ids: Vec<PipelineId>, events: Vec<WebVREvent>) {
         for id in ids {
             match self.pipelines.get_mut(&id) {
                 Some(ref pipeline) => {
                     // Notify script thread
-                    let _ = pipeline.event_loop.send(ConstellationControlMsg::WebVREvent(id, event.clone()));
+                    let _ = pipeline.event_loop.send(ConstellationControlMsg::WebVREvents(id, events.clone()));
                 },
                 None => warn!("constellation got webvr event for dead pipeline")
             }
         }
     }
 
     fn handle_init_load(&mut self, url: ServoUrl) {
         let window_size = self.window_size.initial_viewport;
--- a/servo/components/script/dom/bindings/trace.rs
+++ b/servo/components/script/dom/bindings/trace.rs
@@ -102,16 +102,17 @@ use style::shared_lock::{SharedRwLock as
 use style::stylesheets::{CssRules, FontFaceRule, KeyframesRule, MediaRule};
 use style::stylesheets::{NamespaceRule, StyleRule, ImportRule, SupportsRule};
 use style::values::specified::Length;
 use style::viewport::ViewportRule;
 use time::Duration;
 use uuid::Uuid;
 use webrender_traits::{WebGLBufferId, WebGLError, WebGLFramebufferId, WebGLProgramId};
 use webrender_traits::{WebGLRenderbufferId, WebGLShaderId, WebGLTextureId};
+use webvr_traits::WebVRGamepadHand;
 
 /// A trait to allow tracing (only) DOM objects.
 pub unsafe trait JSTraceable {
     /// Trace `self`.
     unsafe fn trace(&self, trc: *mut JSTracer);
 }
 
 unsafe_no_jsmanaged_fields!(CSSError);
@@ -376,16 +377,17 @@ unsafe_no_jsmanaged_fields!(PathBuf);
 unsafe_no_jsmanaged_fields!(CSSErrorReporter);
 unsafe_no_jsmanaged_fields!(WebGLBufferId);
 unsafe_no_jsmanaged_fields!(WebGLFramebufferId);
 unsafe_no_jsmanaged_fields!(WebGLProgramId);
 unsafe_no_jsmanaged_fields!(WebGLRenderbufferId);
 unsafe_no_jsmanaged_fields!(WebGLShaderId);
 unsafe_no_jsmanaged_fields!(WebGLTextureId);
 unsafe_no_jsmanaged_fields!(MediaList);
+unsafe_no_jsmanaged_fields!(WebVRGamepadHand);
 
 unsafe impl<'a> JSTraceable for &'a str {
     #[inline]
     unsafe fn trace(&self, _: *mut JSTracer) {
         // Do nothing
     }
 }
 
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/gamepad.rs
@@ -0,0 +1,206 @@
+/* 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 core::nonzero::NonZero;
+use dom::bindings::codegen::Bindings::GamepadBinding;
+use dom::bindings::codegen::Bindings::GamepadBinding::GamepadMethods;
+use dom::bindings::inheritance::Castable;
+use dom::bindings::js::{JS, Root};
+use dom::bindings::num::Finite;
+use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
+use dom::bindings::str::DOMString;
+use dom::event::Event;
+use dom::eventtarget::EventTarget;
+use dom::gamepadbuttonlist::GamepadButtonList;
+use dom::gamepadevent::{GamepadEvent, GamepadEventType};
+use dom::globalscope::GlobalScope;
+use dom::vrpose::VRPose;
+use dom_struct::dom_struct;
+use js::jsapi::{Heap, JSContext, JSObject};
+use js::typedarray::{Float64Array, CreateWith};
+use std::cell::Cell;
+use std::ptr;
+use webvr_traits::{WebVRGamepadData, WebVRGamepadHand, WebVRGamepadState};
+
+#[dom_struct]
+pub struct Gamepad {
+    reflector_: Reflector,
+    gamepad_id: u32,
+    id: String,
+    index: Cell<i32>,
+    connected: Cell<bool>,
+    timestamp: Cell<f64>,
+    mapping_type: String,
+    axes: Heap<*mut JSObject>,
+    buttons: JS<GamepadButtonList>,
+    pose: Option<JS<VRPose>>,
+    #[ignore_heap_size_of = "Defined in rust-webvr"]
+    hand: WebVRGamepadHand,
+    display_id: u32
+}
+
+impl Gamepad {
+    fn new_inherited(gamepad_id: u32,
+                     id: String,
+                     index: i32,
+                     connected: bool,
+                     timestamp: f64,
+                     mapping_type: String,
+                     axes: *mut JSObject,
+                     buttons: &GamepadButtonList,
+                     pose: Option<&VRPose>,
+                     hand: WebVRGamepadHand,
+                     display_id: u32) -> Gamepad {
+        Self {
+            reflector_: Reflector::new(),
+            gamepad_id: gamepad_id,
+            id: id,
+            index: Cell::new(index),
+            connected: Cell::new(connected),
+            timestamp: Cell::new(timestamp),
+            mapping_type: mapping_type,
+            axes: Heap::new(axes),
+            buttons: JS::from_ref(buttons),
+            pose: pose.map(JS::from_ref),
+            hand: hand,
+            display_id: display_id
+        }
+    }
+
+    #[allow(unsafe_code)]
+    pub fn new_from_vr(global: &GlobalScope,
+                       index: i32,
+                       data: &WebVRGamepadData,
+                       state: &WebVRGamepadState) -> Root<Gamepad> {
+        let buttons = GamepadButtonList::new_from_vr(&global, &state.buttons);
+        let pose = VRPose::new(&global, &state.pose);
+        let cx = global.get_cx();
+        rooted!(in (cx) let mut axes = ptr::null_mut());
+        unsafe {
+            let _ = Float64Array::create(cx,
+                                         CreateWith::Slice(&state.axes),
+                                         axes.handle_mut());
+        }
+
+        reflect_dom_object(box Gamepad::new_inherited(state.gamepad_id,
+                                                      data.name.clone(),
+                                                      index,
+                                                      state.connected,
+                                                      state.timestamp,
+                                                      "".into(),
+                                                      axes.get(),
+                                                      &buttons,
+                                                      Some(&pose),
+                                                      data.hand.clone(),
+                                                      data.display_id),
+                           global,
+                           GamepadBinding::Wrap)
+
+    }
+}
+
+impl GamepadMethods for Gamepad {
+    // https://w3c.github.io/gamepad/#dom-gamepad-id
+    fn Id(&self) -> DOMString {
+        DOMString::from(self.id.clone())
+    }
+
+    // https://w3c.github.io/gamepad/#dom-gamepad-index
+    fn Index(&self) -> i32 {
+        self.index.get()
+    }
+
+    // https://w3c.github.io/gamepad/#dom-gamepad-connected
+    fn Connected(&self) -> bool {
+        self.connected.get()
+    }
+
+    // https://w3c.github.io/gamepad/#dom-gamepad-timestamp
+    fn Timestamp(&self) -> Finite<f64> {
+        Finite::wrap(self.timestamp.get())
+    }
+
+    // https://w3c.github.io/gamepad/#dom-gamepad-mapping
+    fn Mapping(&self) -> DOMString {
+        DOMString::from(self.mapping_type.clone())
+    }
+
+    #[allow(unsafe_code)]
+    // https://w3c.github.io/gamepad/#dom-gamepad-axes
+    unsafe fn Axes(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> {
+        NonZero::new(self.axes.get())
+    }
+
+    // https://w3c.github.io/gamepad/#dom-gamepad-buttons
+    fn Buttons(&self) -> Root<GamepadButtonList> {
+        Root::from_ref(&*self.buttons)
+    }
+
+    // https://w3c.github.io/gamepad/extensions.html#gamepadhand-enum
+    fn Hand(&self) -> DOMString {
+        let value = match self.hand {
+            WebVRGamepadHand::Unknown => "",
+            WebVRGamepadHand::Left => "left",
+            WebVRGamepadHand::Right => "right"
+        };
+        value.into()
+    }
+
+    // https://w3c.github.io/gamepad/extensions.html#dom-gamepad-pose
+    fn GetPose(&self) -> Option<Root<VRPose>> {
+        self.pose.as_ref().map(|p| Root::from_ref(&**p))
+    }
+
+    // https://w3c.github.io/webvr/spec/1.1/#gamepad-getvrdisplays-attribute
+    fn DisplayId(&self) -> u32 {
+        self.display_id
+    }
+}
+
+impl Gamepad {
+    #[allow(unsafe_code)]
+    pub fn update_from_vr(&self, state: &WebVRGamepadState) {
+        self.timestamp.set(state.timestamp);
+        unsafe {
+            let cx = self.global().get_cx();
+            typedarray!(in(cx) let axes: Float64Array = self.axes.get());
+            if let Ok(mut array) = axes {
+                array.update(&state.axes);
+            }
+        }
+        self.buttons.sync_from_vr(&state.buttons);
+        if let Some(ref pose) = self.pose {
+            pose.update(&state.pose);
+        }
+        self.update_connected(state.connected);
+    }
+
+    pub fn gamepad_id(&self) -> u32 {
+        self.gamepad_id
+    }
+
+    pub fn update_connected(&self, connected: bool) {
+        if self.connected.get() == connected {
+            return;
+        }
+        self.connected.set(connected);
+
+        let event_type = if connected {
+            GamepadEventType::Connected
+        } else {
+            GamepadEventType::Disconnected
+        };
+
+        self.notify_event(event_type);
+    }
+
+    pub fn update_index(&self, index: i32) {
+        self.index.set(index);
+    }
+
+    pub fn notify_event(&self, event_type: GamepadEventType) {
+        let event = GamepadEvent::new_with_type(&self.global(), event_type, &self);
+        event.upcast::<Event>().fire(self.global().as_window().upcast::<EventTarget>());
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/gamepadbutton.rs
@@ -0,0 +1,61 @@
+/* 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 dom::bindings::codegen::Bindings::GamepadButtonBinding;
+use dom::bindings::codegen::Bindings::GamepadButtonBinding::GamepadButtonMethods;
+use dom::bindings::js::Root;
+use dom::bindings::num::Finite;
+use dom::bindings::reflector::{Reflector, reflect_dom_object};
+use dom::globalscope::GlobalScope;
+use dom_struct::dom_struct;
+use std::cell::Cell;
+
+#[dom_struct]
+pub struct GamepadButton {
+    reflector_: Reflector,
+    pressed: Cell<bool>,
+    touched: Cell<bool>,
+    value: Cell<f64>,
+}
+
+impl GamepadButton {
+    pub fn new_inherited(pressed: bool, touched: bool) -> GamepadButton {
+        Self {
+            reflector_: Reflector::new(),
+            pressed: Cell::new(pressed),
+            touched: Cell::new(touched),
+            value: Cell::new(0.0),
+        }
+    }
+
+    pub fn new(global: &GlobalScope, pressed: bool, touched: bool) -> Root<GamepadButton> {
+        reflect_dom_object(box GamepadButton::new_inherited(pressed, touched),
+                           global,
+                           GamepadButtonBinding::Wrap)
+    }
+}
+
+impl GamepadButtonMethods for GamepadButton {
+    // https://www.w3.org/TR/gamepad/#widl-GamepadButton-pressed
+    fn Pressed(&self) -> bool {
+        self.pressed.get()
+    }
+
+    // https://www.w3.org/TR/gamepad/#widl-GamepadButton-touched
+    fn Touched(&self) -> bool {
+        self.touched.get()
+    }
+
+    // https://www.w3.org/TR/gamepad/#widl-GamepadButton-value
+    fn Value(&self) -> Finite<f64> {
+        Finite::wrap(self.value.get())
+    }
+}
+
+impl GamepadButton {
+    pub fn update(&self, pressed: bool, touched: bool) {
+        self.pressed.set(pressed);
+        self.touched.set(touched);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/gamepadbuttonlist.rs
@@ -0,0 +1,63 @@
+/* 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 dom::bindings::codegen::Bindings::GamepadButtonListBinding;
+use dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods;
+use dom::bindings::js::{JS, Root, RootedReference};
+use dom::bindings::reflector::{Reflector, reflect_dom_object};
+use dom::gamepadbutton::GamepadButton;
+use dom::globalscope::GlobalScope;
+use dom_struct::dom_struct;
+use webvr_traits::WebVRGamepadButton;
+
+// https://w3c.github.io/gamepad/#gamepadbutton-interface
+#[dom_struct]
+pub struct GamepadButtonList {
+    reflector_: Reflector,
+    list: Vec<JS<GamepadButton>>
+}
+
+impl GamepadButtonList {
+    #[allow(unrooted_must_root)]
+    fn new_inherited(list: &[&GamepadButton]) -> GamepadButtonList {
+        GamepadButtonList {
+            reflector_: Reflector::new(),
+            list: list.iter().map(|button| JS::from_ref(*button)).collect(),
+        }
+    }
+
+    pub fn new_from_vr(global: &GlobalScope, buttons: &[WebVRGamepadButton]) -> Root<GamepadButtonList> {
+        rooted_vec!(let list <- buttons.iter()
+                                       .map(|btn| GamepadButton::new(&global, btn.pressed, btn.touched)));
+
+        reflect_dom_object(box GamepadButtonList::new_inherited(list.r()),
+                           global,
+                           GamepadButtonListBinding::Wrap)
+    }
+
+    pub fn sync_from_vr(&self, vr_buttons: &[WebVRGamepadButton]) {
+        let mut index = 0;
+        for btn in vr_buttons {
+            self.list.get(index).as_ref().unwrap().update(btn.pressed, btn.touched);
+            index += 1;
+        }
+    }
+}
+
+impl GamepadButtonListMethods for GamepadButtonList {
+    // https://w3c.github.io/gamepad/#dom-gamepad-buttons
+    fn Length(&self) -> u32 {
+        self.list.len() as u32
+    }
+
+    // https://w3c.github.io/gamepad/#dom-gamepad-buttons
+    fn Item(&self, index: u32) -> Option<Root<GamepadButton>> {
+        self.list.get(index as usize).map(|button| Root::from_ref(&**button))
+    }
+
+    // https://w3c.github.io/gamepad/#dom-gamepad-buttons
+    fn IndexedGetter(&self, index: u32) -> Option<Root<GamepadButton>> {
+        self.Item(index)
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/gamepadevent.rs
@@ -0,0 +1,92 @@
+/* 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 dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods;
+use dom::bindings::codegen::Bindings::GamepadEventBinding;
+use dom::bindings::codegen::Bindings::GamepadEventBinding::GamepadEventMethods;
+use dom::bindings::error::Fallible;
+use dom::bindings::inheritance::Castable;
+use dom::bindings::js::{JS, Root};
+use dom::bindings::reflector::{DomObject, reflect_dom_object};
+use dom::bindings::str::DOMString;
+use dom::event::Event;
+use dom::gamepad::Gamepad;
+use dom::globalscope::GlobalScope;
+use dom::window::Window;
+use dom_struct::dom_struct;
+use servo_atoms::Atom;
+
+#[dom_struct]
+pub struct GamepadEvent {
+    event: Event,
+    gamepad: JS<Gamepad>,
+}
+
+pub enum GamepadEventType {
+    Connected,
+    Disconnected
+}
+
+impl GamepadEvent {
+    fn new_inherited(gamepad: &Gamepad) -> GamepadEvent {
+        GamepadEvent {
+            event: Event::new_inherited(),
+            gamepad: JS::from_ref(gamepad),
+        }
+    }
+
+    pub fn new(global: &GlobalScope,
+               type_: Atom,
+               bubbles: bool,
+               cancelable: bool,
+               gamepad: &Gamepad)
+               -> Root<GamepadEvent> {
+        let ev = reflect_dom_object(box GamepadEvent::new_inherited(&gamepad),
+                           global,
+                           GamepadEventBinding::Wrap);
+        {
+            let event = ev.upcast::<Event>();
+            event.init_event(type_, bubbles, cancelable);
+        }
+        ev
+    }
+
+    pub fn new_with_type(global: &GlobalScope, event_type: GamepadEventType, gamepad: &Gamepad)
+                         -> Root<GamepadEvent> {
+        let name = match event_type {
+            GamepadEventType::Connected => "gamepadconnected",
+            GamepadEventType::Disconnected => "gamepaddisconnected"
+        };
+
+        GamepadEvent::new(&global,
+                          name.into(),
+                          false,
+                          false,
+                          &gamepad)
+    }
+
+    // https://w3c.github.io/gamepad/#gamepadevent-interface
+    pub fn Constructor(window: &Window,
+                       type_: DOMString,
+                       init: &GamepadEventBinding::GamepadEventInit)
+                       -> Fallible<Root<GamepadEvent>> {
+        Ok(GamepadEvent::new(&window.global(),
+                             Atom::from(type_),
+                             init.parent.bubbles,
+                             init.parent.cancelable,
+                             &init.gamepad))
+    }
+}
+
+impl GamepadEventMethods for GamepadEvent {
+    // https://w3c.github.io/gamepad/#gamepadevent-interface
+    fn Gamepad(&self) -> Root<Gamepad> {
+        Root::from_ref(&*self.gamepad)
+    }
+
+    // https://dom.spec.whatwg.org/#dom-event-istrusted
+    fn IsTrusted(&self) -> bool {
+        self.event.IsTrusted()
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/gamepadlist.rs
@@ -0,0 +1,61 @@
+/* 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 dom::bindings::cell::DOMRefCell;
+use dom::bindings::codegen::Bindings::GamepadListBinding;
+use dom::bindings::codegen::Bindings::GamepadListBinding::GamepadListMethods;
+use dom::bindings::js::{JS, Root};
+use dom::bindings::reflector::{Reflector, reflect_dom_object};
+use dom::gamepad::Gamepad;
+use dom::globalscope::GlobalScope;
+use dom_struct::dom_struct;
+
+// https://www.w3.org/TR/gamepad/
+#[dom_struct]
+pub struct GamepadList {
+    reflector_: Reflector,
+    list: DOMRefCell<Vec<JS<Gamepad>>>
+}
+
+impl GamepadList {
+    fn new_inherited(list: &[&Gamepad]) -> GamepadList {
+        GamepadList {
+            reflector_: Reflector::new(),
+            list: DOMRefCell::new(list.iter().map(|g| JS::from_ref(&**g)).collect())
+        }
+    }
+
+    pub fn new(global: &GlobalScope, list: &[&Gamepad]) -> Root<GamepadList> {
+        reflect_dom_object(box GamepadList::new_inherited(list),
+                           global,
+                           GamepadListBinding::Wrap)
+    }
+
+    pub fn add_if_not_exists(&self, gamepads: &[Root<Gamepad>]) {
+        for gamepad in gamepads {
+            if !self.list.borrow().iter().any(|g| g.gamepad_id() == gamepad.gamepad_id()) {
+                self.list.borrow_mut().push(JS::from_ref(&*gamepad));
+                // Ensure that the gamepad has the correct index
+                gamepad.update_index(self.list.borrow().len() as i32 - 1);
+            }
+        }
+    }
+}
+
+impl GamepadListMethods for GamepadList {
+    // https://w3c.github.io/gamepad/#dom-navigator-getgamepads
+    fn Length(&self) -> u32 {
+        self.list.borrow().len() as u32
+    }
+
+    // https://w3c.github.io/gamepad/#dom-navigator-getgamepads
+    fn Item(&self, index: u32) -> Option<Root<Gamepad>> {
+        self.list.borrow().get(index as usize).map(|gamepad| Root::from_ref(&**gamepad))
+    }
+
+    // https://w3c.github.io/gamepad/#dom-navigator-getgamepads
+    fn IndexedGetter(&self, index: u32) -> Option<Root<Gamepad>> {
+        self.Item(index)
+    }
+}
--- a/servo/components/script/dom/htmllinkelement.rs
+++ b/servo/components/script/dom/htmllinkelement.rs
@@ -19,16 +19,17 @@ use dom::element::{cors_setting_for_elem
 use dom::globalscope::GlobalScope;
 use dom::htmlelement::HTMLElement;
 use dom::node::{Node, UnbindContext, document_from_node, window_from_node};
 use dom::stylesheet::StyleSheet as DOMStyleSheet;
 use dom::virtualmethods::VirtualMethods;
 use dom_struct::dom_struct;
 use html5ever_atoms::LocalName;
 use net_traits::ReferrerPolicy;
+use script_layout_interface::message::Msg;
 use script_traits::{MozBrowserEvent, ScriptMsg as ConstellationMsg};
 use std::ascii::AsciiExt;
 use std::borrow::ToOwned;
 use std::cell::Cell;
 use std::default::Default;
 use std::sync::Arc;
 use style::attr::AttrValue;
 use style::media_queries::parse_media_query_list;
@@ -92,18 +93,19 @@ impl HTMLLinkElement {
                            HTMLLinkElementBinding::Wrap)
     }
 
     pub fn get_request_generation_id(&self) -> RequestGenerationId {
         self.request_generation_id.get()
     }
 
     pub fn set_stylesheet(&self, s: Arc<Stylesheet>) {
-        assert!(self.stylesheet.borrow().is_none()); // Useful for catching timing issues.
-        *self.stylesheet.borrow_mut() = Some(s);
+        *self.stylesheet.borrow_mut() = Some(s.clone());
+        window_from_node(self).layout_chan().send(Msg::AddStylesheet(s)).unwrap();
+        document_from_node(self).invalidate_stylesheets();
     }
 
     pub fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
         self.stylesheet.borrow().clone()
     }
 
     pub fn get_cssom_stylesheet(&self) -> Option<Root<CSSStyleSheet>> {
         self.get_stylesheet().map(|sheet| {
--- a/servo/components/script/dom/htmlstyleelement.rs
+++ b/servo/components/script/dom/htmlstyleelement.rs
@@ -35,31 +35,33 @@ pub struct HTMLStyleElement {
     #[ignore_heap_size_of = "Arc"]
     stylesheet: DOMRefCell<Option<Arc<Stylesheet>>>,
     cssom_stylesheet: MutNullableJS<CSSStyleSheet>,
     /// https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts
     parser_inserted: Cell<bool>,
     in_stack_of_open_elements: Cell<bool>,
     pending_loads: Cell<u32>,
     any_failed_load: Cell<bool>,
+    line_number: u64,
 }
 
 impl HTMLStyleElement {
     fn new_inherited(local_name: LocalName,
                      prefix: Option<DOMString>,
                      document: &Document,
                      creator: ElementCreator) -> HTMLStyleElement {
         HTMLStyleElement {
             htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
             stylesheet: DOMRefCell::new(None),
             cssom_stylesheet: MutNullableJS::new(None),
             parser_inserted: Cell::new(creator.is_parser_created()),
             in_stack_of_open_elements: Cell::new(creator.is_parser_created()),
             pending_loads: Cell::new(0),
             any_failed_load: Cell::new(false),
+            line_number: creator.return_line_number(),
         }
     }
 
     #[allow(unrooted_must_root)]
     pub fn new(local_name: LocalName,
                prefix: Option<DOMString>,
                document: &Document,
                creator: ElementCreator) -> Root<HTMLStyleElement> {
@@ -87,17 +89,18 @@ impl HTMLStyleElement {
                                                       win.css_error_reporter(),
                                                       Some(CssRuleType::Media));
         let shared_lock = node.owner_doc().style_shared_lock().clone();
         let mq = Arc::new(shared_lock.wrap(
                     parse_media_query_list(&context, &mut CssParser::new(&mq_str))));
         let loader = StylesheetLoader::for_element(self.upcast());
         let sheet = Stylesheet::from_str(&data, win.get_url(), Origin::Author, mq,
                                          shared_lock, Some(&loader),
-                                         win.css_error_reporter());
+                                         win.css_error_reporter(),
+                                         self.line_number);
 
         let sheet = Arc::new(sheet);
 
         // No subresource loads were triggered, just fire the load event now.
         if self.pending_loads.get() == 0 {
             self.upcast::<EventTarget>().fire_event(atom!("load"));
         }
 
--- a/servo/components/script/dom/mod.rs
+++ b/servo/components/script/dom/mod.rs
@@ -285,16 +285,21 @@ pub mod extendableevent;
 pub mod extendablemessageevent;
 pub mod file;
 pub mod filelist;
 pub mod filereader;
 pub mod filereadersync;
 pub mod focusevent;
 pub mod forcetouchevent;
 pub mod formdata;
+pub mod gamepad;
+pub mod gamepadbutton;
+pub mod gamepadbuttonlist;
+pub mod gamepadevent;
+pub mod gamepadlist;
 pub mod globalscope;
 pub mod hashchangeevent;
 pub mod headers;
 pub mod history;
 pub mod htmlanchorelement;
 pub mod htmlappletelement;
 pub mod htmlareaelement;
 pub mod htmlaudioelement;
--- a/servo/components/script/dom/navigator.rs
+++ b/servo/components/script/dom/navigator.rs
@@ -3,46 +3,48 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use dom::bindings::codegen::Bindings::NavigatorBinding;
 use dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
 use dom::bindings::js::{MutNullableJS, Root};
 use dom::bindings::reflector::{Reflector, DomObject, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::bluetooth::Bluetooth;
+use dom::gamepadlist::GamepadList;
 use dom::mimetypearray::MimeTypeArray;
 use dom::navigatorinfo;
 use dom::permissions::Permissions;
 use dom::pluginarray::PluginArray;
 use dom::serviceworkercontainer::ServiceWorkerContainer;
 use dom::vr::VR;
 use dom::window::Window;
 use dom_struct::dom_struct;
-use script_traits::WebVREventMsg;
 
 #[dom_struct]
 pub struct Navigator {
     reflector_: Reflector,
     bluetooth: MutNullableJS<Bluetooth>,
     plugins: MutNullableJS<PluginArray>,
     mime_types: MutNullableJS<MimeTypeArray>,
     service_worker: MutNullableJS<ServiceWorkerContainer>,
     vr: MutNullableJS<VR>,
+    gamepads: MutNullableJS<GamepadList>,
     permissions: MutNullableJS<Permissions>,
 }
 
 impl Navigator {
     fn new_inherited() -> Navigator {
         Navigator {
             reflector_: Reflector::new(),
             bluetooth: Default::default(),
             plugins: Default::default(),
             mime_types: Default::default(),
             service_worker: Default::default(),
             vr: Default::default(),
+            gamepads: Default::default(),
             permissions: Default::default(),
         }
     }
 
     pub fn new(window: &Window) -> Root<Navigator> {
         reflect_dom_object(box Navigator::new_inherited(),
                            window,
                            NavigatorBinding::Wrap)
@@ -123,20 +125,24 @@ impl NavigatorMethods for Navigator {
     }
 
     #[allow(unrooted_must_root)]
     // https://w3c.github.io/webvr/#interface-navigator
     fn Vr(&self) -> Root<VR> {
         self.vr.or_init(|| VR::new(&self.global()))
     }
 
+    // https://www.w3.org/TR/gamepad/#navigator-interface-extension
+    fn GetGamepads(&self) -> Root<GamepadList> {
+        let root = self.gamepads.or_init(|| {
+            GamepadList::new(&self.global(), &[])
+        });
+
+        let vr_gamepads = self.Vr().get_gamepads();
+        root.add_if_not_exists(&vr_gamepads);
+        // TODO: Add not VR related gamepads
+        root
+    }
     // https://w3c.github.io/permissions/#navigator-and-workernavigator-extension
     fn Permissions(&self) -> Root<Permissions> {
         self.permissions.or_init(|| Permissions::new(&self.global()))
     }
 }
-
-impl Navigator {
-     pub fn handle_webvr_event(&self, event: WebVREventMsg) {
-         self.vr.get().expect("Shouldn't arrive here with an empty VR instance")
-                      .handle_webvr_event(event);
-     }
-}
--- a/servo/components/script/dom/vr.rs
+++ b/servo/components/script/dom/vr.rs
@@ -1,44 +1,48 @@
 /* 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 dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::VRBinding;
 use dom::bindings::codegen::Bindings::VRBinding::VRMethods;
+use dom::bindings::codegen::Bindings::VRDisplayBinding::VRDisplayMethods;
 use dom::bindings::error::Error;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{JS, Root};
 use dom::bindings::reflector::{DomObject, reflect_dom_object};
 use dom::event::Event;
 use dom::eventtarget::EventTarget;
+use dom::gamepad::Gamepad;
+use dom::gamepadevent::GamepadEventType;
 use dom::globalscope::GlobalScope;
 use dom::promise::Promise;
 use dom::vrdisplay::VRDisplay;
 use dom::vrdisplayevent::VRDisplayEvent;
 use dom_struct::dom_struct;
 use ipc_channel::ipc;
 use ipc_channel::ipc::IpcSender;
-use script_traits::WebVREventMsg;
 use std::rc::Rc;
-use webvr_traits::WebVRMsg;
-use webvr_traits::webvr;
+use webvr_traits::{WebVRDisplayData, WebVRDisplayEvent, WebVREvent, WebVRMsg};
+use webvr_traits::{WebVRGamepadData, WebVRGamepadEvent, WebVRGamepadState};
 
 #[dom_struct]
 pub struct VR {
     eventtarget: EventTarget,
-    displays: DOMRefCell<Vec<JS<VRDisplay>>>
+    displays: DOMRefCell<Vec<JS<VRDisplay>>>,
+    gamepads: DOMRefCell<Vec<JS<Gamepad>>>
 }
 
 impl VR {
     fn new_inherited() -> VR {
         VR {
             eventtarget: EventTarget::new_inherited(),
-            displays: DOMRefCell::new(Vec::new())
+            displays: DOMRefCell::new(Vec::new()),
+            gamepads: DOMRefCell::new(Vec::new()),
         }
     }
 
     pub fn new(global: &GlobalScope) -> Root<VR> {
         let root = reflect_dom_object(box VR::new_inherited(),
                            global,
                            VRBinding::Wrap);
         root.register();
@@ -90,20 +94,20 @@ impl VRMethods for VR {
 }
 
 
 impl VR {
     fn webvr_thread(&self) -> Option<IpcSender<WebVRMsg>> {
         self.global().as_window().webvr_thread()
     }
 
-    fn find_display(&self, display_id: u64) -> Option<Root<VRDisplay>> {
+    fn find_display(&self, display_id: u32) -> Option<Root<VRDisplay>> {
         self.displays.borrow()
                      .iter()
-                     .find(|d| d.get_display_id() == display_id)
+                     .find(|d| d.DisplayId() == display_id)
                      .map(|d| Root::from_ref(&**d))
     }
 
     fn register(&self) {
         if let Some(webvr_thread) = self.webvr_thread() {
              let msg = WebVRMsg::RegisterContext(self.global().pipeline_id());
              webvr_thread.send(msg).unwrap();
         }
@@ -111,51 +115,137 @@ impl VR {
 
     fn unregister(&self) {
         if let Some(webvr_thread) = self.webvr_thread() {
              let msg = WebVRMsg::UnregisterContext(self.global().pipeline_id());
              webvr_thread.send(msg).unwrap();
         }
     }
 
-    fn sync_display(&self, display: &webvr::VRDisplayData) -> Root<VRDisplay> {
+    fn sync_display(&self, display: &WebVRDisplayData) -> Root<VRDisplay> {
         if let Some(existing) = self.find_display(display.display_id) {
             existing.update_display(&display);
             existing
         } else {
             let root = VRDisplay::new(&self.global(), display.clone());
             self.displays.borrow_mut().push(JS::from_ref(&*root));
             root
         }
     }
 
-    pub fn handle_webvr_event(&self, event: WebVREventMsg) {
-        let WebVREventMsg::DisplayEvent(event) = event;
-        match &event {
-            &webvr::VRDisplayEvent::Connect(ref display) => {
+    fn handle_display_event(&self, event: WebVRDisplayEvent) {
+        match event {
+            WebVRDisplayEvent::Connect(ref display) => {
                 let display = self.sync_display(&display);
                 display.handle_webvr_event(&event);
-                self.notify_event(&display, &event);
+                self.notify_display_event(&display, &event);
             },
-            &webvr::VRDisplayEvent::Disconnect(id) => {
+            WebVRDisplayEvent::Disconnect(id) => {
                 if let Some(display) = self.find_display(id) {
                     display.handle_webvr_event(&event);
-                    self.notify_event(&display, &event);
+                    self.notify_display_event(&display, &event);
                 }
             },
-            &webvr::VRDisplayEvent::Activate(ref display, _) |
-            &webvr::VRDisplayEvent::Deactivate(ref display, _) |
-            &webvr::VRDisplayEvent::Blur(ref display) |
-            &webvr::VRDisplayEvent::Focus(ref display) |
-            &webvr::VRDisplayEvent::PresentChange(ref display, _) |
-            &webvr::VRDisplayEvent::Change(ref display) => {
+            WebVRDisplayEvent::Activate(ref display, _) |
+            WebVRDisplayEvent::Deactivate(ref display, _) |
+            WebVRDisplayEvent::Blur(ref display) |
+            WebVRDisplayEvent::Focus(ref display) |
+            WebVRDisplayEvent::PresentChange(ref display, _) |
+            WebVRDisplayEvent::Change(ref display) => {
                 let display = self.sync_display(&display);
                 display.handle_webvr_event(&event);
             }
         };
     }
 
-    fn notify_event(&self, display: &VRDisplay, event: &webvr::VRDisplayEvent) {
+    fn handle_gamepad_event(&self, event: WebVRGamepadEvent) {
+        match event {
+            WebVRGamepadEvent::Connect(data, state) => {
+                if let Some(gamepad) = self.find_gamepad(state.gamepad_id) {
+                    gamepad.update_from_vr(&state);
+                } else {
+                    // new gamepad
+                    self.sync_gamepad(Some(data), &state);
+                }
+            },
+            WebVRGamepadEvent::Disconnect(id) => {
+                if let Some(gamepad) = self.find_gamepad(id) {
+                    gamepad.update_connected(false);
+                }
+            }
+        };
+    }
+
+    pub fn handle_webvr_event(&self, event: WebVREvent) {
+        match event {
+            WebVREvent::Display(event) => {
+                self.handle_display_event(event);
+            },
+            WebVREvent::Gamepad(event) => {
+                self.handle_gamepad_event(event);
+            }
+        };
+    }
+
+    pub fn handle_webvr_events(&self, events: Vec<WebVREvent>) {
+        for event in events {
+            self.handle_webvr_event(event);
+        }
+    }
+
+    fn notify_display_event(&self, display: &VRDisplay, event: &WebVRDisplayEvent) {
         let event = VRDisplayEvent::new_from_webvr(&self.global(), &display, &event);
         event.upcast::<Event>().fire(self.upcast());
     }
 }
 
+// Gamepad
+impl VR {
+    fn find_gamepad(&self, gamepad_id: u32) -> Option<Root<Gamepad>> {
+        self.gamepads.borrow()
+                     .iter()
+                     .find(|g| g.gamepad_id() == gamepad_id)
+                     .map(|g| Root::from_ref(&**g))
+    }
+
+    fn sync_gamepad(&self, data: Option<WebVRGamepadData>, state: &WebVRGamepadState) {
+        if let Some(existing) = self.find_gamepad(state.gamepad_id) {
+            existing.update_from_vr(&state);
+        } else {
+            let index = self.gamepads.borrow().len();
+            let data = data.unwrap_or_default();
+            let root = Gamepad::new_from_vr(&self.global(),
+                                            index as i32,
+                                            &data,
+                                            &state);
+            self.gamepads.borrow_mut().push(JS::from_ref(&*root));
+            if state.connected {
+                root.notify_event(GamepadEventType::Connected);
+            }
+        }
+    }
+
+    // Gamepads are synced immediately in response to the API call.
+    // The current approach allows the to sample gamepad state multiple times per frame. This
+    // guarantees that the gamepads always have a valid state and can be very useful for
+    // motion capture or drawing applications.
+    pub fn get_gamepads(&self) -> Vec<Root<Gamepad>> {
+        if let Some(wevbr_sender) = self.webvr_thread() {
+            let (sender, receiver) = ipc::channel().unwrap();
+            let synced_ids = self.gamepads.borrow().iter().map(|g| g.gamepad_id()).collect();
+            wevbr_sender.send(WebVRMsg::GetGamepads(synced_ids, sender)).unwrap();
+            match receiver.recv().unwrap() {
+                Ok(gamepads) => {
+                    // Sync displays
+                    for gamepad in gamepads {
+                        self.sync_gamepad(gamepad.0, &gamepad.1);
+                    }
+                },
+                Err(_) => {}
+            }
+        }
+
+        // We can add other not VR related gamepad providers here
+        self.gamepads.borrow().iter()
+                              .map(|g| Root::from_ref(&**g))
+                              .collect()
+    }
+}
--- a/servo/components/script/dom/vrdisplay.rs
+++ b/servo/components/script/dom/vrdisplay.rs
@@ -156,17 +156,17 @@ impl VRDisplayMethods for VRDisplay {
         match eye {
             VREye::Left => Root::from_ref(&*self.left_eye_params.get()),
             VREye::Right => Root::from_ref(&*self.right_eye_params.get())
         }
     }
 
     // https://w3c.github.io/webvr/#dom-vrdisplay-displayid
     fn DisplayId(&self) -> u32 {
-        self.display.borrow().display_id as u32
+        self.display.borrow().display_id
     }
 
     // https://w3c.github.io/webvr/#dom-vrdisplay-displayname
     fn DisplayName(&self) -> DOMString {
         DOMString::from(self.display.borrow().display_name.clone())
     }
 
     // https://w3c.github.io/webvr/#dom-vrdisplay-getframedata-framedata-framedata
@@ -183,17 +183,17 @@ impl VRDisplayMethods for VRDisplay {
             }
             frameData.update(& self.frame_data.borrow());
             return true;
         }
 
         // If not presenting we fetch inmediante VRFrameData
         let (sender, receiver) = ipc::channel().unwrap();
         self.webvr_thread().send(WebVRMsg::GetFrameData(self.global().pipeline_id(),
-                                                        self.get_display_id(),
+                                                        self.DisplayId(),
                                                         self.depth_near.get(),
                                                         self.depth_far.get(),
                                                         sender)).unwrap();
         return match receiver.recv().unwrap() {
             Ok(data) => {
                 frameData.update(&data);
                 true
             },
@@ -208,17 +208,17 @@ impl VRDisplayMethods for VRDisplay {
     fn GetPose(&self) -> Root<VRPose> {
         VRPose::new(&self.global(), &self.frame_data.borrow().pose)
     }
 
     // https://w3c.github.io/webvr/#dom-vrdisplay-resetpose
     fn ResetPose(&self) {
         let (sender, receiver) = ipc::channel().unwrap();
         self.webvr_thread().send(WebVRMsg::ResetPose(self.global().pipeline_id(),
-                                                     self.get_display_id(),
+                                                     self.DisplayId(),
                                                      sender)).unwrap();
         if let Ok(data) = receiver.recv().unwrap() {
             // Some VRDisplay data might change after calling ResetPose()
             *self.display.borrow_mut() = data;
         }
     }
 
     // https://w3c.github.io/webvr/#dom-vrdisplay-depthnear
@@ -373,32 +373,28 @@ impl VRDisplayMethods for VRDisplay {
     // https://w3c.github.io/webvr/#dom-vrdisplay-submitframe
     fn SubmitFrame(&self) {
         if !self.presenting.get() {
             warn!("VRDisplay not presenting");
             return;
         }
 
         let api_sender = self.layer_ctx.get().unwrap().ipc_renderer();
-        let display_id = self.display.borrow().display_id;
+        let display_id = self.display.borrow().display_id as u64;
         let layer = self.layer.borrow();
         let msg = VRCompositorCommand::SubmitFrame(display_id, layer.left_bounds, layer.right_bounds);
         api_sender.send(CanvasMsg::WebVR(msg)).unwrap();
     }
 }
 
 impl VRDisplay {
     fn webvr_thread(&self) -> IpcSender<WebVRMsg> {
         self.global().as_window().webvr_thread().expect("Shouldn't arrive here with WebVR disabled")
     }
 
-    pub fn get_display_id(&self) -> u64 {
-        self.display.borrow().display_id
-    }
-
     pub fn update_display(&self, display: &WebVRDisplayData) {
         *self.display.borrow_mut() = display.clone();
         if let Some(ref stage) = display.stage_parameters {
             if self.stage_params.get().is_none() {
                 let params = Some(VRStageParameters::new(stage.clone(), &self.global()));
                 self.stage_params.set(params.as_ref().map(|v| v.deref()));
             } else {
                 self.stage_params.get().unwrap().update(&stage);
@@ -442,17 +438,17 @@ impl VRDisplay {
         event.upcast::<Event>().fire(self.upcast());
     }
 
     fn init_present(&self) {
         self.presenting.set(true);
         let (sync_sender, sync_receiver) = ipc::channel().unwrap();
         *self.frame_data_receiver.borrow_mut() = Some(sync_receiver);
 
-        let display_id = self.display.borrow().display_id;
+        let display_id = self.display.borrow().display_id as u64;
         let api_sender = self.layer_ctx.get().unwrap().ipc_renderer();
         let js_sender = self.global().script_chan();
         let address = Trusted::new(&*self);
         let near_init = self.depth_near.get();
         let far_init = self.depth_far.get();
 
         // The render loop at native headset frame rate is implemented using a dedicated thread.
         // Every loop iteration syncs pose data with the HMD, submits the pixels to the display and waits for Vsync.
@@ -492,17 +488,17 @@ impl VRDisplay {
         }).expect("Thread spawning failed");
     }
 
     fn stop_present(&self) {
         self.presenting.set(false);
         *self.frame_data_receiver.borrow_mut() = None;
 
         let api_sender = self.layer_ctx.get().unwrap().ipc_renderer();
-        let display_id = self.display.borrow().display_id;
+        let display_id = self.display.borrow().display_id as u64;
         let msg = VRCompositorCommand::Release(display_id);
         api_sender.send(CanvasMsg::WebVR(msg)).unwrap();
     }
 
     // Only called when the JSContext is destroyed while presenting.
     // In this case we don't want to wait for WebVR Thread response.
     fn force_stop_present(&self) {
         self.webvr_thread().send(WebVRMsg::ExitPresent(self.global().pipeline_id(),
--- a/servo/components/script/dom/vrpose.rs
+++ b/servo/components/script/dom/vrpose.rs
@@ -27,17 +27,19 @@ pub struct VRPose {
 
 #[allow(unsafe_code)]
 unsafe fn update_or_create_typed_array(cx: *mut JSContext,
                       src: Option<&[f32]>,
                       dst: &Heap<*mut JSObject>) {
     match src {
         Some(data) => {
             if dst.get().is_null() {
-                let _ = Float32Array::create(cx, CreateWith::Slice(data), dst.handle_mut());
+                rooted!(in (cx) let mut array = ptr::null_mut());
+                let _ = Float32Array::create(cx, CreateWith::Slice(data), array.handle_mut());
+                (*dst).set(array.get());
             } else {
                 typedarray!(in(cx) let array: Float32Array = dst.get());
                 if let Ok(mut array) = array {
                     array.update(data);
                 }
             }
         },
         None => {
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/webidls/Gamepad.webidl
@@ -0,0 +1,26 @@
+/* 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/. */
+
+// https://w3c.github.io/gamepad/#gamepad-interface
+[Pref="dom.gamepad.enabled"]
+interface Gamepad {
+    readonly attribute DOMString id;
+    readonly attribute long index;
+    readonly attribute boolean connected;
+    readonly attribute DOMHighResTimeStamp timestamp;
+    readonly attribute DOMString mapping;
+    readonly attribute Float64Array axes;
+    [SameObject] readonly attribute GamepadButtonList buttons;
+};
+
+// https://w3c.github.io/gamepad/extensions.html#dom-gamepad
+partial interface Gamepad {
+  readonly attribute DOMString hand;
+  readonly attribute VRPose? pose;
+};
+
+// https://w3c.github.io/webvr/spec/1.1/#interface-gamepad
+partial interface Gamepad {
+  readonly attribute unsigned long displayId;
+};
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/webidls/GamepadButton.webidl
@@ -0,0 +1,11 @@
+/* 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/. */
+
+// https://w3c.github.io/gamepad/#gamepadbutton-interface
+[Pref="dom.gamepad.enabled"]
+interface GamepadButton {
+    readonly attribute boolean pressed;
+    readonly attribute boolean touched;
+    readonly attribute double value;
+};
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/webidls/GamepadButtonList.webidl
@@ -0,0 +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/. */
+
+// https://w3c.github.io/gamepad/#dom-gamepad-buttons
+[Pref="dom.gamepad.enabled"]
+interface GamepadButtonList {
+  getter GamepadButton? item(unsigned long index);
+  readonly attribute unsigned long length;
+};
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/webidls/GamepadEvent.webidl
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// https://w3c.github.io/gamepad/#gamepadevent-interface
+[Pref="dom.gamepad.enabled", Constructor(DOMString type, GamepadEventInit eventInitDict)]
+interface GamepadEvent : Event {
+  readonly attribute Gamepad gamepad;
+};
+
+dictionary GamepadEventInit : EventInit {
+  required Gamepad gamepad;
+};
new file mode 100644
--- /dev/null
+++ b/servo/components/script/dom/webidls/GamepadList.webidl
@@ -0,0 +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/. */
+
+// https://w3c.github.io/gamepad/#navigator-interface-extension
+[Pref="dom.gamepad.enabled"]
+interface GamepadList {
+  getter Gamepad? item(unsigned long index);
+  readonly attribute unsigned long length;
+};
--- a/servo/components/script/dom/webidls/Navigator.webidl
+++ b/servo/components/script/dom/webidls/Navigator.webidl
@@ -63,8 +63,13 @@ partial interface Navigator {
   [SameObject, Pref="dom.webvr.enabled"] readonly attribute VR vr;
 };
 
 // https://w3c.github.io/permissions/#navigator-and-workernavigator-extension
 [Exposed=(Window)]
 partial interface Navigator {
   [Pref="dom.permissions.enabled"] readonly attribute Permissions permissions;
 };
+
+// https://w3c.github.io/gamepad/#navigator-interface-extension
+partial interface Navigator {
+    [Pref="dom.gamepad.enabled"] GamepadList getGamepads();
+};
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -22,16 +22,17 @@ use devtools;
 use devtools_traits::{DevtoolScriptControlMsg, DevtoolsPageInfo};
 use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
 use devtools_traits::CSSError;
 use document_loader::DocumentLoader;
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
 use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
 use dom::bindings::codegen::Bindings::EventBinding::EventInit;
+use dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
 use dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit;
 use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
 use dom::bindings::conversions::{ConversionResult, FromJSValConvertible, StringificationBehavior};
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{JS, MutNullableJS, Root, RootCollection};
 use dom::bindings::js::{RootCollectionPtr, RootedReference};
 use dom::bindings::num::Finite;
 use dom::bindings::reflector::DomObject;
@@ -86,17 +87,16 @@ use script_runtime::{ScriptPort, StackRo
 use script_traits::{CompositorEvent, ConstellationControlMsg};
 use script_traits::{DocumentActivity, DiscardBrowsingContext, EventResult};
 use script_traits::{InitialScriptState, LayoutMsg, LoadData, MouseButton, MouseEventType, MozBrowserEvent};
 use script_traits::{NewLayoutInfo, ScriptMsg as ConstellationMsg};
 use script_traits::{ScriptThreadFactory, TimerEvent, TimerSchedulerMsg, TimerSource};
 use script_traits::{TouchEventType, TouchId, UntrustedNodeAddress, WindowSizeData, WindowSizeType};
 use script_traits::CompositorEvent::{KeyEvent, MouseButtonEvent, MouseMoveEvent, ResizeEvent};
 use script_traits::CompositorEvent::{TouchEvent, TouchpadPressureEvent};
-use script_traits::WebVREventMsg;
 use script_traits::webdriver_msg::WebDriverScriptCommand;
 use serviceworkerjob::{Job, JobQueue, AsyncJobHandler};
 use servo_config::opts;
 use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
 use std::cell::Cell;
 use std::collections::{hash_map, HashMap, HashSet};
 use std::default::Default;
 use std::ops::Deref;
@@ -114,17 +114,17 @@ use style::thread_state;
 use task_source::dom_manipulation::{DOMManipulationTask, DOMManipulationTaskSource};
 use task_source::file_reading::FileReadingTaskSource;
 use task_source::history_traversal::HistoryTraversalTaskSource;
 use task_source::networking::NetworkingTaskSource;
 use task_source::user_interaction::{UserInteractionTask, UserInteractionTaskSource};
 use time::Tm;
 use url::Position;
 use webdriver_handlers;
-use webvr_traits::WebVRMsg;
+use webvr_traits::{WebVREvent, WebVRMsg};
 
 pub type ImageCacheMsg = (PipelineId, PendingImageResponse);
 
 thread_local!(pub static STACK_ROOTS: Cell<Option<RootCollectionPtr>> = Cell::new(None));
 thread_local!(static SCRIPT_THREAD_ROOT: Cell<Option<*const ScriptThread>> = Cell::new(None));
 
 pub unsafe fn trace_thread(tr: *mut JSTracer) {
     SCRIPT_THREAD_ROOT.with(|root| {
@@ -1065,18 +1065,18 @@ impl ScriptThread {
             ConstellationControlMsg::FramedContentChanged(parent_pipeline_id, frame_id) =>
                 self.handle_framed_content_changed(parent_pipeline_id, frame_id),
             ConstellationControlMsg::ReportCSSError(pipeline_id, filename, line, column, msg) =>
                 self.handle_css_error_reporting(pipeline_id, filename, line, column, msg),
             ConstellationControlMsg::Reload(pipeline_id) =>
                 self.handle_reload(pipeline_id),
             ConstellationControlMsg::ExitPipeline(pipeline_id, discard_browsing_context) =>
                 self.handle_exit_pipeline_msg(pipeline_id, discard_browsing_context),
-            ConstellationControlMsg::WebVREvent(pipeline_id, event) =>
-                self.handle_webvr_event(pipeline_id, event),
+            ConstellationControlMsg::WebVREvents(pipeline_id, events) =>
+                self.handle_webvr_events(pipeline_id, events),
             msg @ ConstellationControlMsg::AttachLayout(..) |
             msg @ ConstellationControlMsg::Viewport(..) |
             msg @ ConstellationControlMsg::SetScrollState(..) |
             msg @ ConstellationControlMsg::Resize(..) |
             msg @ ConstellationControlMsg::ExitScriptThread =>
                       panic!("should have handled {:?} already", msg),
         }
     }
@@ -2181,21 +2181,21 @@ impl ScriptThread {
 
     fn handle_reload(&self, pipeline_id: PipelineId) {
         let window = self.documents.borrow().find_window(pipeline_id);
         if let Some(window) = window {
             window.Location().reload_without_origin_check();
         }
     }
 
-    fn handle_webvr_event(&self, pipeline_id: PipelineId, event: WebVREventMsg) {
+    fn handle_webvr_events(&self, pipeline_id: PipelineId, events: Vec<WebVREvent>) {
         let window = self.documents.borrow().find_window(pipeline_id);
         if let Some(window) = window {
-            let navigator = window.Navigator();
-            navigator.handle_webvr_event(event);
+            let vr = window.Navigator().Vr();
+            vr.handle_webvr_events(events);
         }
     }
 
     pub fn enqueue_microtask(job: Microtask) {
         SCRIPT_THREAD_ROOT.with(|root| {
             let script_thread = unsafe { &*root.get().unwrap() };
             script_thread.microtask_queue.enqueue(job);
         });
--- a/servo/components/script/stylesheet_loader.rs
+++ b/servo/components/script/stylesheet_loader.rs
@@ -17,17 +17,16 @@ use encoding::all::UTF_8;
 use hyper::header::ContentType;
 use hyper::mime::{Mime, TopLevel, SubLevel};
 use hyper_serde::Serde;
 use ipc_channel::ipc;
 use ipc_channel::router::ROUTER;
 use net_traits::{FetchResponseListener, FetchMetadata, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy};
 use net_traits::request::{CorsSettings, CredentialsMode, Destination, RequestInit, RequestMode, Type as RequestType};
 use network_listener::{NetworkListener, PreInvoke};
-use script_layout_interface::message::Msg;
 use servo_url::ServoUrl;
 use std::mem;
 use std::sync::{Arc, Mutex};
 use style::media_queries::MediaList;
 use style::shared_lock::Locked as StyleLocked;
 use style::stylesheets::{ImportRule, Stylesheet, Origin};
 use style::stylesheets::StylesheetLoader as StyleStylesheetLoader;
 
@@ -146,19 +145,17 @@ impl FetchResponseListener for Styleshee
                                                             shared_lock,
                                                             Some(&loader),
                                                             win.css_error_reporter()));
 
                         if link.is_alternate() {
                             sheet.set_disabled(true);
                         }
 
-                        link.set_stylesheet(sheet.clone());
-
-                        win.layout_chan().send(Msg::AddStylesheet(sheet)).unwrap();
+                        link.set_stylesheet(sheet);
                     }
                 }
                 StylesheetContextSource::Import(ref stylesheet) => {
                     Stylesheet::update_from_bytes(&stylesheet,
                                                   &data,
                                                   protocol_encoding_label,
                                                   Some(environment_encoding),
                                                   &final_url,
--- a/servo/components/script_layout_interface/reporter.rs
+++ b/servo/components/script_layout_interface/reporter.rs
@@ -21,22 +21,24 @@ pub struct CSSErrorReporter {
     pub script_chan: Arc<Mutex<IpcSender<ConstellationControlMsg>>>,
 }
 
 impl ParseErrorReporter for CSSErrorReporter {
      fn report_error(&self,
                      input: &mut Parser,
                      position: SourcePosition,
                      message: &str,
-                     url: &ServoUrl) {
+                     url: &ServoUrl,
+                     line_number_offset: u64) {
         let location = input.source_location(position);
+        let line_offset = location.line + line_number_offset as usize;
         if log_enabled!(log::LogLevel::Info) {
              info!("Url:\t{}\n{}:{} {}",
                    url.as_str(),
-                   location.line,
+                   line_offset,
                    location.column,
                    message)
         }
 
          //TODO: report a real filename
          let _ = self.script_chan.lock().unwrap().send(
              ConstellationControlMsg::ReportCSSError(self.pipelineid,
                                                      "".to_owned(),
--- a/servo/components/script_traits/lib.rs
+++ b/servo/components/script_traits/lib.rs
@@ -66,17 +66,17 @@ use serde::{Deserialize, Deserializer, S
 use servo_url::ImmutableOrigin;
 use servo_url::ServoUrl;
 use std::collections::HashMap;
 use std::fmt;
 use std::sync::Arc;
 use std::sync::mpsc::{Receiver, Sender};
 use style_traits::{CSSPixel, UnsafeNode};
 use webdriver_msg::{LoadStatus, WebDriverScriptCommand};
-use webvr_traits::{WebVRDisplayEvent, WebVRMsg};
+use webvr_traits::{WebVREvent, WebVRMsg};
 
 pub use script_msg::{LayoutMsg, ScriptMsg, EventResult, LogEntry};
 pub use script_msg::{ServiceWorkerMsg, ScopeThings, SWManagerMsg, SWManagerSenders, DOMMessage};
 
 /// The address of a node. Layout sends these back. They must be validated via
 /// `from_untrusted_node_address` before they can be used, because we do not trust layout.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub struct UntrustedNodeAddress(pub *const c_void);
@@ -275,18 +275,18 @@ pub enum ConstellationControlMsg {
     DispatchStorageEvent(PipelineId, StorageType, ServoUrl, Option<String>, Option<String>, Option<String>),
     /// Notifies a parent pipeline that one of its child frames is now active.
     /// PipelineId is for the parent, FrameId is the child frame.
     FramedContentChanged(PipelineId, FrameId),
     /// Report an error from a CSS parser for the given pipeline
     ReportCSSError(PipelineId, String, usize, usize, String),
     /// Reload the given page.
     Reload(PipelineId),
-    /// Notifies the script thread of a WebVR device event
-    WebVREvent(PipelineId, WebVREventMsg)
+    /// Notifies the script thread of WebVR events.
+    WebVREvents(PipelineId, Vec<WebVREvent>)
 }
 
 impl fmt::Debug for ConstellationControlMsg {
     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
         use self::ConstellationControlMsg::*;
         let variant = match *self {
             AttachLayout(..) => "AttachLayout",
             Resize(..) => "Resize",
@@ -309,17 +309,17 @@ impl fmt::Debug for ConstellationControl
             TickAllAnimations(..) => "TickAllAnimations",
             TransitionEnd(..) => "TransitionEnd",
             WebFontLoaded(..) => "WebFontLoaded",
             DispatchFrameLoadEvent { .. } => "DispatchFrameLoadEvent",
             DispatchStorageEvent(..) => "DispatchStorageEvent",
             FramedContentChanged(..) => "FramedContentChanged",
             ReportCSSError(..) => "ReportCSSError",
             Reload(..) => "Reload",
-            WebVREvent(..) => "WebVREvent",
+            WebVREvents(..) => "WebVREvents",
         };
         write!(formatter, "ConstellationMsg::{}", variant)
     }
 }
 
 /// Used to determine if a script has any pending asynchronous activity.
 #[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize)]
 pub enum DocumentState {
@@ -746,26 +746,18 @@ pub enum ConstellationMsg {
     /// Dispatch a webdriver command
     WebDriverCommand(WebDriverCommandMsg),
     /// Reload the current page.
     Reload,
     /// A log entry, with the top-level frame id and thread name
     LogEntry(Option<FrameId>, Option<String>, LogEntry),
     /// Set the WebVR thread channel.
     SetWebVRThread(IpcSender<WebVRMsg>),
-    /// Dispatch a WebVR event to the subscribed script threads.
-    WebVREvent(Vec<PipelineId>, WebVREventMsg),
-}
-
-/// Messages to the constellation originating from the WebVR thread.
-/// Used to dispatch VR Headset state events: connected, unconnected, and more.
-#[derive(Deserialize, Serialize, Clone)]
-pub enum WebVREventMsg {
-    /// Inform the constellation of a VR display event.
-    DisplayEvent(WebVRDisplayEvent)
+    /// Dispatch WebVR events to the subscribed script threads.
+    WebVREvents(Vec<PipelineId>, Vec<WebVREvent>),
 }
 
 /// Resources required by workerglobalscopes
 #[derive(Serialize, Deserialize, Clone)]
 pub struct WorkerGlobalScopeInit {
     /// Chan to a resource thread
     pub resource_threads: ResourceThreads,
     /// Chan to the memory profiler
--- a/servo/components/style/encoding_support.rs
+++ b/servo/components/style/encoding_support.rs
@@ -61,17 +61,18 @@ impl Stylesheet {
         let (string, _) = decode_stylesheet_bytes(
             bytes, protocol_encoding_label, environment_encoding);
         Stylesheet::from_str(&string,
                              url_data,
                              origin,
                              Arc::new(shared_lock.wrap(media)),
                              shared_lock,
                              stylesheet_loader,
-                             error_reporter)
+                             error_reporter,
+                             0u64)
     }
 
     /// Updates an empty stylesheet with a set of bytes that reached over the
     /// network.
     pub fn update_from_bytes(existing: &Stylesheet,
                              bytes: &[u8],
                              protocol_encoding_label: Option<&str>,
                              environment_encoding: Option<EncodingRef>,
--- a/servo/components/style/error_reporting.rs
+++ b/servo/components/style/error_reporting.rs
@@ -7,35 +7,38 @@
 #![deny(missing_docs)]
 
 use cssparser::{Parser, SourcePosition};
 use log;
 use stylesheets::UrlExtraData;
 
 /// A generic trait for an error reporter.
 pub trait ParseErrorReporter : Sync + Send {
-    /// Called the style engine detects an error.
+    /// Called when the style engine detects an error.
     ///
     /// Returns the current input being parsed, the source position it was
     /// reported from, and a message.
     fn report_error(&self,
                     input: &mut Parser,
                     position: SourcePosition,
                     message: &str,
-                    url: &UrlExtraData);
+                    url: &UrlExtraData,
+                    line_number_offset: u64);
 }
 
 /// An error reporter that reports the errors to the `info` log channel.
 ///
 /// TODO(emilio): The name of this reporter is a lie, and should be renamed!
 pub struct StdoutErrorReporter;
 impl ParseErrorReporter for StdoutErrorReporter {
     fn report_error(&self,
                     input: &mut Parser,
                     position: SourcePosition,
                     message: &str,
-                    url: &UrlExtraData) {
+                    url: &UrlExtraData,
+                    line_number_offset: u64) {
         if log_enabled!(log::LogLevel::Info) {
             let location = input.source_location(position);
-            info!("Url:\t{}\n{}:{} {}", url.as_str(), location.line, location.column, message)
+            let line_offset = location.line + line_number_offset as usize;
+            info!("Url:\t{}\n{}:{} {}", url.as_str(), line_offset, location.column, message)
         }
     }
 }
--- a/servo/components/style/parser.rs
+++ b/servo/components/style/parser.rs
@@ -17,66 +17,90 @@ pub struct ParserContext<'a> {
     /// user-agent stylesheet.
     pub stylesheet_origin: Origin,
     /// The extra data we need for resolving url values.
     pub url_data: &'a UrlExtraData,
     /// An error reporter to report syntax errors.
     pub error_reporter: &'a ParseErrorReporter,
     /// The current rule type, if any.
     pub rule_type: Option<CssRuleType>,
+    ///  line number offsets for inline stylesheets
+    pub line_number_offset: u64,
 }
 
 impl<'a> ParserContext<'a> {
     /// Create a parser context.
     pub fn new(stylesheet_origin: Origin,
                url_data: &'a UrlExtraData,
                error_reporter: &'a ParseErrorReporter,
                rule_type: Option<CssRuleType>)
                -> ParserContext<'a> {
         ParserContext {
             stylesheet_origin: stylesheet_origin,
             url_data: url_data,
             error_reporter: error_reporter,
             rule_type: rule_type,
+            line_number_offset: 0u64,
         }
     }
 
     /// Create a parser context for on-the-fly parsing in CSSOM
     pub fn new_for_cssom(url_data: &'a UrlExtraData,
                          error_reporter: &'a ParseErrorReporter,
                          rule_type: Option<CssRuleType>)
                          -> ParserContext<'a> {
         Self::new(Origin::Author, url_data, error_reporter, rule_type)
     }
 
     /// Create a parser context based on a previous context, but with a modified rule type.
     pub fn new_with_rule_type(context: &'a ParserContext,
                               rule_type: Option<CssRuleType>)
                               -> ParserContext<'a> {
-        Self::new(context.stylesheet_origin,
-                  context.url_data,
-                  context.error_reporter,
-                  rule_type)
+        ParserContext {
+            stylesheet_origin: context.stylesheet_origin,
+            url_data: context.url_data,
+            error_reporter: context.error_reporter,
+            rule_type: rule_type,
+            line_number_offset: context.line_number_offset,
+        }
     }
 
     /// Get the rule type, which assumes that one is available.
     pub fn rule_type(&self) -> CssRuleType {
         self.rule_type.expect("Rule type expected, but none was found.")
     }
+
+    /// Create a parser context for inline CSS which accepts additional line offset argument.
+    pub fn new_with_line_number_offset(stylesheet_origin: Origin,
+                                       url_data: &'a UrlExtraData,
+                                       error_reporter: &'a ParseErrorReporter,
+                                       line_number_offset: u64)
+                                       -> ParserContext<'a> {
+        ParserContext {
+            stylesheet_origin: stylesheet_origin,
+            url_data: url_data,
+            error_reporter: error_reporter,
+            rule_type: None,
+            line_number_offset: line_number_offset,
+        }
+    }
 }
 
 /// Defaults to a no-op.
 /// Set a `RUST_LOG=style::errors` environment variable
 /// to log CSS parse errors to stderr.
 pub fn log_css_error(input: &mut Parser,
                      position: SourcePosition,
                      message: &str,
                      parsercontext: &ParserContext) {
     let url_data = parsercontext.url_data;
-    parsercontext.error_reporter.report_error(input, position, message, url_data);
+    let line_number_offset = parsercontext.line_number_offset;
+    parsercontext.error_reporter.report_error(input, position,
+                                              message, url_data,
+                                              line_number_offset);
 }
 
 // XXXManishearth Replace all specified value parse impls with impls of this
 // trait. This will make it easy to write more generic values in the future.
 /// A trait to abstract parsing of a specified value given a `ParserContext` and
 /// CSS input.
 pub trait Parse : Sized {
     /// Parse a value of this type.
--- a/servo/components/style/stylesheets.rs
+++ b/servo/components/style/stylesheets.rs
@@ -323,17 +323,18 @@ pub enum CssRuleType {
 /// Error reporter which silently forgets errors
 pub struct MemoryHoleReporter;
 
 impl ParseErrorReporter for MemoryHoleReporter {
     fn report_error(&self,
             _: &mut Parser,
             _: SourcePosition,
             _: &str,
-            _: &UrlExtraData) {
+            _: &UrlExtraData,
+            _: u64) {
         // do nothing
     }
 }
 
 #[allow(missing_docs)]
 pub enum SingleRuleParseError {
     Syntax,
     Hierarchy,
@@ -661,43 +662,45 @@ impl Stylesheet {
                            stylesheet_loader: Option<&StylesheetLoader>,
                            error_reporter: &ParseErrorReporter) {
         let mut namespaces = Namespaces::default();
         // FIXME: we really should update existing.url_data with the given url_data,
         // otherwise newly inserted rule may not have the right base url.
         let (rules, dirty_on_viewport_size_change) = Stylesheet::parse_rules(
             css, url_data, existing.origin, &mut namespaces,
             &existing.shared_lock, stylesheet_loader, error_reporter,
-        );
+            0u64);
 
         *existing.namespaces.write() = namespaces;
         existing.dirty_on_viewport_size_change
             .store(dirty_on_viewport_size_change, Ordering::Release);
 
         // Acquire the lock *after* parsing, to minimize the exclusive section.
         let mut guard = existing.shared_lock.write();
         *existing.rules.write_with(&mut guard) = CssRules(rules);
     }
 
     fn parse_rules(css: &str,
                    url_data: &UrlExtraData,
                    origin: Origin,
                    namespaces: &mut Namespaces,
                    shared_lock: &SharedRwLock,
                    stylesheet_loader: Option<&StylesheetLoader>,
-                   error_reporter: &ParseErrorReporter)
+                   error_reporter: &ParseErrorReporter,
+                   line_number_offset: u64)
                    -> (Vec<CssRule>, bool) {
         let mut rules = Vec::new();
         let mut input = Parser::new(css);
         let rule_parser = TopLevelRuleParser {
             stylesheet_origin: origin,
             namespaces: namespaces,
             shared_lock: shared_lock,
             loader: stylesheet_loader,
-            context: ParserContext::new(origin, url_data, error_reporter, None),
+            context: ParserContext::new_with_line_number_offset(origin, url_data, error_reporter,
+                                                                line_number_offset),
             state: Cell::new(State::Start),
         };
 
         input.look_for_viewport_percentages();
 
         {
             let mut iter = RuleListParser::new_for_stylesheet(&mut input, rule_parser);
             while let Some(result) = iter.next() {
@@ -721,21 +724,22 @@ impl Stylesheet {
     /// Effectively creates a new stylesheet and forwards the hard work to
     /// `Stylesheet::update_from_str`.
     pub fn from_str(css: &str,
                     url_data: UrlExtraData,
                     origin: Origin,
                     media: Arc<Locked<MediaList>>,
                     shared_lock: SharedRwLock,
                     stylesheet_loader: Option<&StylesheetLoader>,
-                    error_reporter: &ParseErrorReporter) -> Stylesheet {
+                    error_reporter: &ParseErrorReporter,
+                    line_number_offset: u64) -> Stylesheet {
         let mut namespaces = Namespaces::default();
         let (rules, dirty_on_viewport_size_change) = Stylesheet::parse_rules(
             css, &url_data, origin, &mut namespaces,
-            &shared_lock, stylesheet_loader, error_reporter,
+            &shared_lock, stylesheet_loader, error_reporter, line_number_offset
         );
         Stylesheet {
             origin: origin,
             url_data: url_data,
             namespaces: RwLock::new(namespaces),
             rules: CssRules::new(rules, &shared_lock),
             media: media,
             shared_lock: shared_lock,
--- a/servo/components/webvr/webvr_thread.rs
+++ b/servo/components/webvr/webvr_thread.rs
@@ -1,16 +1,16 @@
 /* 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 ipc_channel::ipc;
 use ipc_channel::ipc::{IpcReceiver, IpcSender};
 use msg::constellation_msg::PipelineId;
-use script_traits::{ConstellationMsg, WebVREventMsg};
+use script_traits::ConstellationMsg;
 use servo_config::prefs::PREFS;
 use std::{thread, time};
 use std::collections::{HashMap, HashSet};
 use std::sync::mpsc;
 use std::sync::mpsc::{Receiver, Sender};
 use webrender_traits;
 use webvr_traits::{WebVRMsg, WebVRResult};
 use webvr_traits::webvr::*;
@@ -37,17 +37,17 @@ use webvr_traits::webvr::*;
 pub struct WebVRThread {
     receiver: IpcReceiver<WebVRMsg>,
     sender: IpcSender<WebVRMsg>,
     service: VRServiceManager,
     contexts: HashSet<PipelineId>,
     constellation_chan: Sender<ConstellationMsg>,
     vr_compositor_chan: WebVRCompositorSender,
     polling_events: bool,
-    presenting: HashMap<u64, PipelineId>
+    presenting: HashMap<u32, PipelineId>
 }
 
 impl WebVRThread {
     fn new(receiver: IpcReceiver<WebVRMsg>,
            sender: IpcSender<WebVRMsg>,
            constellation_chan: Sender<ConstellationMsg>,
            vr_compositor_chan: WebVRCompositorSender)
            -> WebVRThread {
@@ -103,16 +103,19 @@ impl WebVRThread {
                     self.handle_request_present(pipeline_id, display_id, sender);
                 },
                 WebVRMsg::ExitPresent(pipeline_id, display_id, sender) => {
                     self.handle_exit_present(pipeline_id, display_id, sender);
                 },
                 WebVRMsg::CreateCompositor(display_id) => {
                     self.handle_create_compositor(display_id);
                 },
+                WebVRMsg::GetGamepads(synced_ids, sender) => {
+                    self.handle_get_gamepads(synced_ids, sender);
+                }
                 WebVRMsg::Exit => {
                     break
                 },
             }
         }
     }
 
     fn handle_register_context(&mut self, ctx: PipelineId) {
@@ -129,31 +132,31 @@ impl WebVRThread {
         for display in displays {
             result.push(display.borrow().data());
         }
         sender.send(Ok(result)).unwrap();
     }
 
     fn handle_framedata(&mut self,
                         pipeline: PipelineId,
-                        display_id: u64,
+                        display_id: u32,
                         near: f64,
                         far: f64,
                         sender: IpcSender<WebVRResult<VRFrameData>>) {
       match self.access_check(pipeline, display_id) {
             Ok(display) => {
                 sender.send(Ok(display.borrow().inmediate_frame_data(near, far))).unwrap()
             },
             Err(msg) => sender.send(Err(msg.into())).unwrap()
         }
     }
 
     fn handle_reset_pose(&mut self,
                          pipeline: PipelineId,
-                         display_id: u64,
+                         display_id: u32,
                          sender: IpcSender<WebVRResult<VRDisplayData>>) {
         match self.access_check(pipeline, display_id) {
             Ok(display) => {
                 display.borrow_mut().reset_pose();
                 sender.send(Ok(display.borrow().data())).unwrap();
             },
             Err(msg) => {
                 sender.send(Err(msg.into())).unwrap()
@@ -161,90 +164,104 @@ impl WebVRThread {
         }
     }
 
     // This method implements the privacy and security guidelines defined in the WebVR spec.
     // For example a secondary tab is not allowed to read VRDisplay data or stop a VR presentation
     // while the user is having a VR experience in the current tab.
     // These security rules also avoid multithreading race conditions between WebVRThread and
     // Webrender thread. See WebVRCompositorHandler implementation notes for more details about this.
-    fn access_check(&self, pipeline: PipelineId, display_id: u64) -> Result<&VRDisplayPtr, &'static str> {
+    fn access_check(&self, pipeline: PipelineId, display_id: u32) -> Result<&VRDisplayPtr, &'static str> {
         if *self.presenting.get(&display_id).unwrap_or(&pipeline) != pipeline {
             return Err("No access granted to this Display because it's presenting on other JavaScript Tab");
         }
         self.service.get_display(display_id).ok_or("Device not found")
     }
 
     fn handle_request_present(&mut self,
                               pipeline: PipelineId,
-                              display_id: u64,
+                              display_id: u32,
                               sender: IpcSender<WebVRResult<()>>) {
         match self.access_check(pipeline, display_id).map(|d| d.clone()) {
             Ok(display) => {
                 self.presenting.insert(display_id, pipeline);
                 let data = display.borrow().data();
                 sender.send(Ok(())).unwrap();
-                self.notify_event(VRDisplayEvent::PresentChange(data, true));
+                self.notify_event(VRDisplayEvent::PresentChange(data, true).into());
             },
             Err(msg) => {
                 sender.send(Err(msg.into())).unwrap();
             }
         }
     }
 
     fn handle_exit_present(&mut self,
                          pipeline: PipelineId,
-                         display_id: u64,
+                         display_id: u32,
                          sender: Option<IpcSender<WebVRResult<()>>>) {
         match self.access_check(pipeline, display_id).map(|d| d.clone()) {
             Ok(display) => {
                 self.presenting.remove(&display_id);
                 if let Some(sender) = sender {
                     sender.send(Ok(())).unwrap();
                 }
                 let data = display.borrow().data();
-                self.notify_event(VRDisplayEvent::PresentChange(data, false));
+                self.notify_event(VRDisplayEvent::PresentChange(data, false).into());
             },
             Err(msg) => {
                 if let Some(sender) = sender {
                     sender.send(Err(msg.into())).unwrap();
                 }
             }
         }
     }
 
-    fn handle_create_compositor(&mut self, display_id: u64) {
+    fn handle_create_compositor(&mut self, display_id: u32) {
         let compositor = self.service.get_display(display_id).map(|d| WebVRCompositor(d.as_ptr()));
         self.vr_compositor_chan.send(compositor).unwrap();
     }
 
+    fn handle_get_gamepads(&mut self,
+                           synced_ids: Vec<u32>,
+                           sender: IpcSender<WebVRResult<Vec<(Option<VRGamepadData>, VRGamepadState)>>>) {
+        let gamepads = self.service.get_gamepads();
+        let data = gamepads.iter().map(|g| {
+            let g = g.borrow();
+            // Optimization, don't fetch and send gamepad static data when the gamepad is already synced.
+            let data = if synced_ids.iter().any(|v| *v == g.id()) {
+                None
+            } else {
+                Some(g.data())
+            };
+            (data, g.state())
+        }).collect();
+        sender.send(Ok(data)).unwrap();
+    }
+
     fn poll_events(&mut self, sender: IpcSender<bool>) {
         loop {
             let events = self.service.poll_events();
             if events.is_empty() {
                 break;
             }
             self.notify_events(events)
         }
 
         // Stop polling events if the callers are not using VR
         self.polling_events = self.contexts.len() > 0;
         sender.send(self.polling_events).unwrap();
     }
 
-    fn notify_events(&self, events: Vec<VRDisplayEvent>) {
+    fn notify_events(&self, events: Vec<VREvent>) {
         let pipeline_ids: Vec<PipelineId> = self.contexts.iter().map(|c| *c).collect();
-        for event in events {
-            let event = WebVREventMsg::DisplayEvent(event);
-            self.constellation_chan.send(ConstellationMsg::WebVREvent(pipeline_ids.clone(), event)).unwrap();
-        }
+        self.constellation_chan.send(ConstellationMsg::WebVREvents(pipeline_ids.clone(), events)).unwrap();
     }
 
     #[inline]
-    fn notify_event(&self, event: VRDisplayEvent) {
+    fn notify_event(&self, event: VREvent) {
         self.notify_events(vec![event]);
     }
 
     fn schedule_poll_events(&mut self) {
         if !self.service.is_initialized() || self.polling_events {
             return;
         }
         self.polling_events = true;
@@ -329,17 +346,18 @@ impl webrender_traits::VRCompositorHandl
                 }
             }
             webrender_traits::VRCompositorCommand::SubmitFrame(compositor_id, left_bounds, right_bounds) => {
                 if let Some(compositor) = self.compositors.get(&compositor_id) {
                     if let Some(texture_id) = texture_id {
                         let layer = VRLayer {
                             texture_id: texture_id,
                             left_bounds: left_bounds,
-                            right_bounds: right_bounds
+                            right_bounds: right_bounds,
+                            texture_size: None
                         };
                         unsafe {
                             (*compositor.0).submit_frame(&layer);
                         }
                     }
                 }
             }
             webrender_traits::VRCompositorCommand::Release(compositor_id) => {
@@ -352,17 +370,17 @@ impl webrender_traits::VRCompositorHandl
 impl WebVRCompositorHandler {
     #[allow(unsafe_code)]
     fn create_compositor(&mut self, display_id: webrender_traits::VRCompositorId) {
         let sender = match self.webvr_thread_sender {
             Some(ref s) => s,
             None => return,
         };
 
-        sender.send(WebVRMsg::CreateCompositor(display_id)).unwrap();
+        sender.send(WebVRMsg::CreateCompositor(display_id as u32)).unwrap();
         let display = self.webvr_thread_receiver.recv().unwrap();
 
         match display {
             Some(display) => {
                 self.compositors.insert(display_id, display);
             },
             None => {
                 error!("VRDisplay not found when creating a new VRCompositor");
--- a/servo/components/webvr_traits/Cargo.toml
+++ b/servo/components/webvr_traits/Cargo.toml
@@ -7,11 +7,11 @@ publish = false
 
 [lib]
 name = "webvr_traits"
 path = "lib.rs"
 
 [dependencies]
 ipc-channel = "0.7"
 msg = {path = "../msg"}
-rust-webvr = {version = "0.2", features = ["serde-serialization"]}
+rust-webvr = {version = "0.3", features = ["serde-serialization"]}
 serde = "0.9"
 serde_derive = "0.9"
--- a/servo/components/webvr_traits/lib.rs
+++ b/servo/components/webvr_traits/lib.rs
@@ -11,16 +11,22 @@ extern crate serde_derive;
 pub extern crate rust_webvr as webvr;
 
 mod webvr_traits;
 
 pub use webvr::VRDisplayData as WebVRDisplayData;
 pub use webvr::VRDisplayCapabilities as WebVRDisplayCapabilities;
 pub use webvr::VRDisplayEvent as WebVRDisplayEvent;
 pub use webvr::VRDisplayEventReason as WebVRDisplayEventReason;
+pub use webvr::VREvent as WebVREvent;
 pub use webvr::VREye as WebVREye;
 pub use webvr::VREyeParameters as WebVREyeParameters;
 pub use webvr::VRFieldOfView as WebVRFieldOfView;
+pub use webvr::VRGamepadButton as WebVRGamepadButton;
+pub use webvr::VRGamepadData as WebVRGamepadData;
+pub use webvr::VRGamepadEvent as WebVRGamepadEvent;
+pub use webvr::VRGamepadHand as WebVRGamepadHand;
+pub use webvr::VRGamepadState as WebVRGamepadState;
 pub use webvr::VRFrameData as WebVRFrameData;
 pub use webvr::VRLayer as WebVRLayer;
 pub use webvr::VRPose as WebVRPose;
 pub use webvr::VRStageParameters as WebVRStageParameters;
 pub use webvr_traits::{WebVRMsg, WebVRResult};
--- a/servo/components/webvr_traits/webvr_traits.rs
+++ b/servo/components/webvr_traits/webvr_traits.rs
@@ -10,15 +10,16 @@ pub type WebVRResult<T> = Result<T, Stri
 
 // Messages from Script thread to WebVR thread.
 #[derive(Deserialize, Serialize)]
 pub enum WebVRMsg {
     RegisterContext(PipelineId),
     UnregisterContext(PipelineId),
     PollEvents(IpcSender<bool>),
     GetDisplays(IpcSender<WebVRResult<Vec<VRDisplayData>>>),
-    GetFrameData(PipelineId, u64, f64, f64, IpcSender<WebVRResult<VRFrameData>>),
-    ResetPose(PipelineId, u64, IpcSender<WebVRResult<VRDisplayData>>),
-    RequestPresent(PipelineId, u64, IpcSender<WebVRResult<()>>),
-    ExitPresent(PipelineId, u64, Option<IpcSender<WebVRResult<()>>>),
-    CreateCompositor(u64),
+    GetFrameData(PipelineId, u32, f64, f64, IpcSender<WebVRResult<VRFrameData>>),
+    ResetPose(PipelineId, u32, IpcSender<WebVRResult<VRDisplayData>>),
+    RequestPresent(PipelineId, u32, IpcSender<WebVRResult<()>>),
+    ExitPresent(PipelineId, u32, Option<IpcSender<WebVRResult<()>>>),
+    CreateCompositor(u32),
+    GetGamepads(Vec<u32>, IpcSender<WebVRResult<Vec<(Option<VRGamepadData>, VRGamepadState)>>>),
     Exit,
 }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -491,17 +491,17 @@ pub extern "C" fn Servo_StyleSheet_Empty
         SheetParsingMode::eAuthorSheetFeatures => Origin::Author,
         SheetParsingMode::eUserSheetFeatures => Origin::User,
         SheetParsingMode::eAgentSheetFeatures => Origin::UserAgent,
     };
     let shared_lock = global_style_data.shared_lock.clone();
     Arc::new(Stylesheet::from_str(
         "", unsafe { dummy_url_data() }.clone(), origin,
         Arc::new(shared_lock.wrap(MediaList::empty())),
-        shared_lock, None, &StdoutErrorReporter)
+        shared_lock, None, &StdoutErrorReporter, 0u64)
     ).into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSheet_FromUTF8Bytes(loader: *mut Loader,
                                                  stylesheet: *mut ServoStyleSheet,
                                                  data: *const nsACString,
                                                  mode: SheetParsingMode,
@@ -534,17 +534,17 @@ pub extern "C" fn Servo_StyleSheet_FromU
     let media = if media_list.is_null() {
         Arc::new(shared_lock.wrap(MediaList::empty()))
     } else {
         Locked::<MediaList>::as_arc(unsafe { &&*media_list }).clone()
     };
 
     Arc::new(Stylesheet::from_str(
         input, url_data.clone(), origin, media,
-        shared_lock, loader, &StdoutErrorReporter)
+        shared_lock, loader, &StdoutErrorReporter, 0u64)
     ).into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSheet_ClearAndUpdate(stylesheet: RawServoStyleSheetBorrowed,
                                                   loader: *mut Loader,
                                                   gecko_stylesheet: *mut ServoStyleSheet,
                                                   data: *const nsACString,
--- a/servo/resources/prefs.json
+++ b/servo/resources/prefs.json
@@ -1,12 +1,13 @@
 {
   "dom.bluetooth.enabled": false,
   "dom.bluetooth.testing.enabled": false,
   "dom.forcetouch.enabled": false,
+  "dom.gamepad.enabled": false,
   "dom.mouseevent.which.enabled": false,
   "dom.mozbrowser.enabled": false,
   "dom.mutation_observer.enabled": false,
   "dom.permissions.enabled": false,
   "dom.permissions.testing.allowed_in_nonsecure_contexts": false,
   "dom.serviceworker.timeout_seconds": 60,
   "dom.testable_crash.enabled": false,
   "dom.testbinding.enabled": false,
new file mode 100644
--- /dev/null
+++ b/servo/tests/html/webvr/vr-controllers.html
@@ -0,0 +1,420 @@
+<!doctype html>
+<!--
+Copyright 2016 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+    <meta name="mobile-web-app-capable" content="yes">
+    <meta name="apple-mobile-web-app-capable" content="yes">
+
+    <!--Chrome origin trial header-->
+
+    <meta http-equiv="origin-trial" data-feature="WebVR" data-expires="2017-04-13" content="AtpEVUJTjLpU/IMdKA/u8TRWqVUKfA6aJQsonwi01IPxqA16zX7L4BMa9E4g4DdJW80v3N6jqde4pXeqd2GYCg4AAABJeyJvcmlnaW4iOiJodHRwczovL3dlYnZyLmluZm86NDQzIiwiZmVhdHVyZSI6IldlYlZSIiwiZXhwaXJ5IjoxNDkyMTAzODUyfQ==">
+    <title>XX - VR Controllers</title>
+
+    <!--
+      This sample demonstrates how to handle gamepads with 6DoF support, such as
+      the Vive controllers or Oculus touch. PLEASE NOTE: The additions to the
+      gamepad API used here are not yet part of the standard, and subject to
+      change at any time!
+    -->
+
+    <style>
+      #webgl-canvas {
+        box-sizing: border-box;
+        height: 100%;
+        left: 0;
+        margin: 0;
+        position: absolute;
+        top: 0;
+        width: 100%;
+      }
+    </style>
+
+    <!-- This entire block in only to facilitate dynamically enabling and
+    disabling the WebVR polyfill, and is not necessary for most WebVR apps.
+    If you want to use the polyfill in your app, just include the js file and
+    everything will work the way you want it to by default. -->
+    <script>
+      var WebVRConfig = {
+        // Prevents the polyfill from initializing automatically.
+        DEFER_INITIALIZATION: true,
+        // Ensures the polyfill is always active when initialized, even if the
+        // native API is available. This is probably NOT what most pages want.
+        POLYFILL_MODE: "ALWAYS",
+        // Polyfill optimizations
+        DIRTY_SUBMIT_FRAME_BINDINGS: true,
+        BUFFER_SCALE: 0.75,
+      };
+    </script>
+    <script src="js/third-party/webvr-polyfill.js"></script>
+    <script src="js/third-party/wglu/wglu-url.js"></script>
+    <script>
+      // Dynamically turn the polyfill on if requested by the query args.
+      if (WGLUUrl.getBool('polyfill', false)) {
+        InitializeWebVRPolyfill();
+      } else {
+        // Shim for migration from older version of WebVR. Shouldn't be necessary for very long.
+        InitializeSpecShim();
+      }
+    </script>
+    <!-- End sample polyfill enabling logic -->
+
+    <script src="js/third-party/gl-matrix-min.js"></script>
+
+    <script src="js/third-party/wglu/wglu-debug-geometry.js"></script>
+    <script src="js/third-party/wglu/wglu-program.js"></script>
+    <script src="js/third-party/wglu/wglu-stats.js"></script>
+    <script src="js/third-party/wglu/wglu-texture.js"></script>
+
+    <script src="js/vr-cube-island.js"></script>
+    <script src="js/vr-samples-util.js"></script>
+  </head>
+  <body>
+    <canvas id="webgl-canvas"></canvas>
+    <script>
+      /* global mat4, vec3, VRCubeIsland, WGLUDebugGeometry, WGLUStats, WGLUTextureLoader, VRSamplesUtil */
+      (function () {
+      "use strict";
+
+      var PLAYER_HEIGHT = 1.65;
+
+      var vrDisplay = null;
+      var frameData = null;
+      var projectionMat = mat4.create();
+      var viewMat = mat4.create();
+      var poseMat = mat4.create();
+      var gamepadMat = mat4.create();
+      var gamepadMat2 = mat4.create();
+      var gamepadColor = vec4.create();
+      var standingPosition = vec3.create();
+      var vrPresentButton = null;
+      var orientation = [0, 0, 0, 1];
+      var position = [0, 0, 0];
+
+
+      window.addEventListener("gamepadconnected", function(ev) {
+        var gamepad = ev.gamepad;
+        console.log("Gamepad connected", gamepad.index, gamepad.id, gamepad.displayId);
+      });
+
+        window.addEventListener("gamepaddisconnected", function(ev) {
+        var gamepad = ev.gamepad;
+        console.log("Gamepad disconnected", gamepad.index, gamepad.id, gamepad.displayId);
+      });
+
+      // ===================================================
+      // WebGL scene setup. This code is not WebVR specific.
+      // ===================================================
+
+      // WebGL setup.
+      var webglCanvas = document.getElementById("webgl-canvas");
+      var gl = null;
+      var cubeIsland = null;
+      var stats = null;
+      var debugGeom = null;
+
+      function initWebGL (preserveDrawingBuffer) {
+        var glAttribs = {
+          alpha: false,
+          antialias: false,
+          preserveDrawingBuffer: false
+        };
+        gl = webglCanvas.getContext("webgl", glAttribs);
+        if (!gl) {
+          gl = webglCanvas.getContext("experimental-webgl", glAttribs);
+          if (!gl) {
+            VRSamplesUtil.addError("Your browser does not support WebGL.");
+            return;
+          }
+        }
+        //gl.clearColor(0.1, 0.2, 0.3, 1.0);
+        gl.clearColor(0.0, 119.0/255.0, 51.0/255.0, 1.0);
+        gl.enable(gl.DEPTH_TEST);
+        gl.enable(gl.CULL_FACE);
+
+        var textureLoader = new WGLUTextureLoader(gl);
+        var texture = textureLoader.loadTexture("media/textures/cube-sea.png");
+
+        cubeIsland = new VRCubeIsland(gl, texture, 2, 2);
+
+        stats = new WGLUStats(gl);
+        debugGeom = new WGLUDebugGeometry(gl);
+
+        // Wait until we have a WebGL context to resize and start rendering.
+        window.addEventListener("resize", onResize, false);
+        onResize();
+        window.requestAnimationFrame(onAnimationFrame);
+      }
+
+      // ================================
+      // WebVR-specific code begins here.
+      // ================================
+
+      function onVRRequestPresent () {
+        vrDisplay.requestPresent([{ source: webglCanvas }]).then(function () {
+        }, function (err) {
+          var errMsg = "requestPresent failed.";
+          if (err && err.message) {
+            errMsg += "<br/>" + err.message
+          }
+          VRSamplesUtil.addError(errMsg, 2000);
+        });
+      }
+
+      function onVRExitPresent () {
+        if (!vrDisplay.isPresenting)
+          return;
+
+        vrDisplay.exitPresent().then(function () {
+        }, function () {
+          VRSamplesUtil.addError("exitPresent failed.", 2000);
+        });
+      }
+
+      function onVRPresentChange () {
+        onResize();
+
+        if (vrDisplay.isPresenting) {
+          if (vrDisplay.capabilities.hasExternalDisplay) {
+            VRSamplesUtil.removeButton(vrPresentButton);
+            vrPresentButton = VRSamplesUtil.addButton("Exit VR", "E", "media/icons/cardboard64.png", onVRExitPresent);
+          }
+        } else {
+          if (vrDisplay.capabilities.hasExternalDisplay) {
+            VRSamplesUtil.removeButton(vrPresentButton);
+            vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent);
+          }
+        }
+      }
+
+      if (navigator.vr || navigator.getVRDisplays) {
+        frameData = new VRFrameData();
+        navigator.vr.getDisplays().then(function (displays) {
+         // navigator.getVRDisplays().then(function (displays) {
+          if (displays.length > 0) {
+            vrDisplay = displays[displays.length - 1];
+            vrDisplay.depthNear = 0.1;
+            vrDisplay.depthFar = 1024.0;
+
+            initWebGL(true);
+
+            if (vrDisplay.stageParameters &&
+                vrDisplay.stageParameters.sizeX > 0 &&
+                vrDisplay.stageParameters.sizeZ > 0) {
+              cubeIsland.resize(vrDisplay.stageParameters.sizeX, vrDisplay.stageParameters.sizeZ);
+            }
+
+            VRSamplesUtil.addButton("Reset Pose", "R", null, function () { vrDisplay.resetPose(); });
+
+            if (vrDisplay.capabilities.canPresent)
+              vrPresentButton = VRSamplesUtil.addButton("Enter VR", "E", "media/icons/cardboard64.png", onVRRequestPresent);
+
+            vrDisplay.addEventListener('presentchange', onVRPresentChange, false);
+            //vrDisplay.addEventListener('vrdisplayactivate', onVRRequestPresent, false);
+            //vrDisplay.addEventListener('vrdisplaydeactivate', onVRExitPresent, false);
+            console.log(navigator.getGamepads());
+            setTimeout(function(){
+                onVRRequestPresent();
+            }, 5);
+
+          } else {
+            initWebGL(false);
+            VRSamplesUtil.addInfo("WebVR supported, but no VRDisplays found.", 3000);
+          }
+        });
+      } else if (navigator.getVRDevices) {
+        initWebGL(false);
+        VRSamplesUtil.addError("Your browser supports WebVR but not the latest version. See <a href='http://webvr.info'>webvr.info</a> for more info.");
+      } else {
+        initWebGL(false);
+        VRSamplesUtil.addError("Your browser does not support WebVR. See <a href='http://webvr.info'>webvr.info</a> for assistance.");
+      }
+
+      function onResize () {
+        if (vrDisplay && vrDisplay.isPresenting) {
+          var leftEye = vrDisplay.getEyeParameters("left");
+          var rightEye = vrDisplay.getEyeParameters("right");
+
+          webglCanvas.width = Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2;
+          webglCanvas.height = Math.max(leftEye.renderHeight, rightEye.renderHeight);
+        } else {
+          webglCanvas.width = window.innerWidth * 2.0 * window.devicePixelRatio;
+          webglCanvas.height = window.innerHeight * 2.0 *  window.devicePixelRatio;
+        }
+      }
+
+      function onClick() {
+        //onVRRequestPresent();
+      }
+      webglCanvas.addEventListener("click", onClick, false);
+
+      function getStandingViewMatrix (out, view) {
+        if (vrDisplay.stageParameters) {
+          mat4.invert(out, vrDisplay.stageParameters.sittingToStandingTransform);
+          mat4.multiply(out, view, out);
+        } else {
+          mat4.identity(out);
+          mat4.translate(out, out, [0, PLAYER_HEIGHT, 0]);
+          mat4.invert(out, out);
+          mat4.multiply(out, view, out);
+        }
+      }
+
+      function getPoseMatrix (out, pose, isGamepad) {
+        orientation = pose.orientation;
+        position = pose.position;
+        if (!orientation) { orientation = [0, 0, 0, 1]; }
+        if (!position) {
+          // If this is a gamepad without a pose set it out in front of us so
+          // we can see it.
+          position = isGamepad ? [0.1, -0.1, -0.5] : [0, 0, 0];
+        }
+
+
+        if (vrDisplay.stageParameters) {
+          mat4.fromRotationTranslation(out, orientation, position);
+          mat4.multiply(out, vrDisplay.stageParameters.sittingToStandingTransform, out);
+        } else {
+          vec3.add(standingPosition, position, [0, PLAYER_HEIGHT, 0]);
+          mat4.fromRotationTranslation(out, orientation, standingPosition);
+        }
+      }
+
+      function renderSceneView (projection, view, gamepads) {
+        cubeIsland.render(projection, view, stats);
+
+        debugGeom.bind(projection, view);
+
+        // Render every gamepad with a pose we found
+        for (var i = 0; i < gamepads.length; ++i) {
+          var gamepad = gamepads[i];
+
+          // Because this sample is done in standing space we need to apply
+          // the same transformation to the gamepad pose as we did the
+          // VRDisplay's pose.
+          getPoseMatrix(gamepadMat, gamepad.pose, true);
+
+          // Loop through all the gamepad's axes and scale the gamepad geometry
+          // by their value.
+          var scale = [0.1, 0.1, 0.1];
+          for (var j = 0; j < gamepad.axes.length; ++j) {
+            switch (j%3) {
+              case 0:
+                scale[0] *= 1.0 + gamepad.axes[j];
+                break;
+              case 1:
+                scale[1] *= 1.0 + gamepad.axes[j];
+                break;
+              case 2:
+                scale[2] *= 1.0 + gamepad.axes[j];
+                break;
+            }
+          }
+
+          // Scaled down to from 1 meter to be something closer to the size of
+          // a hand.
+          mat4.scale(gamepadMat, gamepadMat, scale);
+
+          // Rotate -90 deg so the point of the cone faces "forward"
+          mat4.rotateX(gamepadMat, gamepadMat, -Math.PI * 0.5);
+
+          // Show the gamepad's cube as red if any buttons are pressed, blue
+          // otherwise.
+          vec4.set(gamepadColor, 0, 0, 1, 1);
+          var buttons = gamepad.buttons;
+          for (var j = 0; j < buttons.length; ++j) {
+            if (buttons[j].pressed) {
+              vec4.set(gamepadColor, buttons[j].value || 1.0, 0, 0, 1);
+              break;
+            }
+          }
+
+          debugGeom.drawConeWithMatrix(gamepadMat, gamepadColor);
+
+          // Draw a "handle" for the gamepad
+          mat4.identity(gamepadMat2);
+          mat4.translate(gamepadMat2, gamepadMat2, [0, -0.5, -0.3]);
+          mat4.rotateX(gamepadMat2, gamepadMat2, -Math.PI * 0.2);
+          mat4.scale(gamepadMat2, gamepadMat2, [0.25, 0.25, 0.5]);
+
+          mat4.multiply(gamepadMat, gamepadMat, gamepadMat2);
+
+          debugGeom.drawBoxWithMatrix(gamepadMat, gamepadColor);
+        }
+      }
+
+      function onAnimationFrame (t) {
+        stats.begin();
+
+        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+        if (vrDisplay) {
+          vrDisplay.requestAnimationFrame(onAnimationFrame);
+
+          vrDisplay.getFrameData(frameData);
+
+          // Loop over every gamepad and if we find any that have a pose use it.
+          var vrGamepads = [];
+          var gamepads = navigator.getGamepads();
+          for (var i = 0; i < gamepads.length; ++i) {
+            var gamepad = gamepads[i];
+            // The array may contain undefined gamepads, so check for that as
+            // well as a non-null pose.
+            if (gamepad) {
+              if (gamepad.pose)
+                vrGamepads.push(gamepad);
+              if ("hapticActuators" in gamepad && gamepad.hapticActuators.length > 0) {
+                for (var j = 0; j < gamepad.buttons.length; ++j) {
+                  if (gamepad.buttons[j].pressed) {
+                    // Vibrate the gamepad using to the value of the button as
+                    // the vibration intensity.
+                    gamepad.hapticActuators[0].pulse(gamepad.buttons[j].value, 100);
+                    break;
+                  }
+                }
+              }
+            }
+          }
+
+
+          if (vrDisplay.isPresenting) {
+            gl.viewport(0, 0, webglCanvas.width * 0.5, webglCanvas.height);
+            getStandingViewMatrix(viewMat, frameData.leftViewMatrix);
+            renderSceneView(frameData.leftProjectionMatrix, viewMat, vrGamepads);
+
+            gl.viewport(webglCanvas.width * 0.5, 0, webglCanvas.width * 0.5, webglCanvas.height);
+            getStandingViewMatrix(viewMat, frameData.rightViewMatrix);
+            renderSceneView(frameData.rightProjectionMatrix, viewMat, vrGamepads);
+
+            vrDisplay.submitFrame();
+          } else {
+            gl.viewport(0, 0, webglCanvas.width, webglCanvas.height);
+            mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0);
+            getStandingViewMatrix(viewMat, frameData.leftViewMatrix);
+            renderSceneView(projectionMat, viewMat, vrGamepads);
+            stats.renderOrtho();
+          }
+        } else {
+          window.requestAnimationFrame(onAnimationFrame);
+
+          // No VRDisplay found.
+          gl.viewport(0, 0, webglCanvas.width, webglCanvas.height);
+          mat4.perspective(projectionMat, Math.PI*0.4, webglCanvas.width / webglCanvas.height, 0.1, 1024.0);
+          mat4.identity(viewMat);
+          mat4.translate(viewMat, viewMat, [0, -PLAYER_HEIGHT, 0]);
+          cubeIsland.render(projectionMat, viewMat, stats);
+
+          stats.renderOrtho();
+        }
+
+        stats.end();
+      }
+      })();
+    </script>
+  </body>
+</html>
--- a/servo/tests/unit/style/media_queries.rs
+++ b/servo/tests/unit/style/media_queries.rs
@@ -14,31 +14,35 @@ use style::servo::media_queries::*;
 use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard};
 use style::stylesheets::{Stylesheet, Origin, CssRule};
 use style::values::specified;
 use style_traits::ToCss;
 
 pub struct CSSErrorReporterTest;
 
 impl ParseErrorReporter for CSSErrorReporterTest {
-    fn report_error(&self, _input: &mut Parser, _position: SourcePosition, _message: &str,
-        _url: &ServoUrl) {
+    fn report_error(&self,
+                    _input: &mut Parser,
+                    _position: SourcePosition,
+                    _message: &str,
+                    _url: &ServoUrl,
+                    _line_number_offset: u64) {
     }
 }
 
 fn test_media_rule<F>(css: &str, callback: F)
     where F: Fn(&MediaList, &str),
 {
     let url = ServoUrl::parse("http://localhost").unwrap();
     let css_str = css.to_owned();
     let lock = SharedRwLock::new();
     let media_list = Arc::new(lock.wrap(MediaList::empty()));
     let stylesheet = Stylesheet::from_str(
         css, url, Origin::Author, media_list, lock,
-        None, &CSSErrorReporterTest);
+        None, &CSSErrorReporterTest, 0u64);
     let mut rule_count = 0;
     let guard = stylesheet.shared_lock.read();
     media_queries(&guard, &stylesheet.rules.read_with(&guard).0, &mut |mq| {
         rule_count += 1;
         callback(mq, css);
     });
     assert!(rule_count > 0, css_str);
 }
@@ -57,17 +61,17 @@ fn media_queries<F>(guard: &SharedRwLock
 }
 
 fn media_query_test(device: &Device, css: &str, expected_rule_count: usize) {
     let url = ServoUrl::parse("http://localhost").unwrap();
     let lock = SharedRwLock::new();
     let media_list = Arc::new(lock.wrap(MediaList::empty()));
     let ss = Stylesheet::from_str(
         css, url, Origin::Author, media_list, lock,
-        None, &CSSErrorReporterTest);
+        None, &CSSErrorReporterTest, 0u64);
     let mut rule_count = 0;
     ss.effective_style_rules(device, &ss.shared_lock.read(), |_| rule_count += 1);
     assert!(rule_count == expected_rule_count, css.to_owned());
 }
 
 #[test]
 fn test_mq_empty() {
     test_media_rule("@media { }", |list, css| {
--- a/servo/tests/unit/style/rule_tree/bench.rs
+++ b/servo/tests/unit/style/rule_tree/bench.rs
@@ -11,19 +11,25 @@ use style::media_queries::MediaList;
 use style::properties::{longhands, Importance, PropertyDeclaration, PropertyDeclarationBlock};
 use style::rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
 use style::shared_lock::SharedRwLock;
 use style::stylesheets::{Origin, Stylesheet, CssRule};
 use test::{self, Bencher};
 
 struct ErrorringErrorReporter;
 impl ParseErrorReporter for ErrorringErrorReporter {
-    fn report_error(&self, _input: &mut Parser, position: SourcePosition, message: &str,
-        url: &ServoUrl) {
-        panic!("CSS error: {}\t\n{:?} {}", url.as_str(), position, message);
+    fn report_error(&self,
+                    input: &mut Parser,
+                    position: SourcePosition,
+                    message: &str,
+                    url: &ServoUrl,
+                    line_number_offset: u64) {
+        let location = input.source_location(position);
+        let line_offset = location.line + line_number_offset as usize;
+        panic!("CSS error: {}\t\n{}:{} {}", url.as_str(), line_offset, location.column, message);
     }
 }
 
 struct AutoGCRuleTree<'a>(&'a RuleTree);
 
 impl<'a> AutoGCRuleTree<'a> {
     fn new(r: &'a RuleTree) -> Self {
         AutoGCRuleTree(r)
@@ -46,17 +52,18 @@ fn parse_rules(css: &str) -> Vec<(StyleS
     let media = Arc::new(lock.wrap(MediaList::empty()));
 
     let s = Stylesheet::from_str(css,
                                  ServoUrl::parse("http://localhost").unwrap(),
                                  Origin::Author,
                                  media,
                                  lock,
                                  None,
-                                 &ErrorringErrorReporter);
+                                 &ErrorringErrorReporter,
+                                 0u64);
     let guard = s.shared_lock.read();
     let rules = s.rules.read_with(&guard);
     rules.0.iter().filter_map(|rule| {
         match *rule {
             CssRule::Style(ref style_rule) => Some(style_rule),
             _ => None,
         }
     }).cloned().map(StyleSource::Style).map(|s| {
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -60,17 +60,17 @@ fn test_parse_stylesheet() {
                 animation-name: 'foo'; /* animation properties not allowed here */
                 animation-play-state: running; /* … except animation-play-state */
             }
         }";
     let url = ServoUrl::parse("about::test").unwrap();
     let lock = SharedRwLock::new();
     let media = Arc::new(lock.wrap(MediaList::empty()));
     let stylesheet = Stylesheet::from_str(css, url.clone(), Origin::UserAgent, media, lock,
-                                          None, &CSSErrorReporterTest);
+                                          None, &CSSErrorReporterTest, 0u64);
     let mut namespaces = Namespaces::default();
     namespaces.default = Some(ns!(html));
     let expected = Stylesheet {
         origin: Origin::UserAgent,
         media: Arc::new(stylesheet.shared_lock.wrap(MediaList::empty())),
         shared_lock: stylesheet.shared_lock.clone(),
         namespaces: RwLock::new(namespaces),
         url_data: url,
@@ -288,26 +288,27 @@ impl CSSInvalidErrorReporterTest {
     }
 }
 
 impl ParseErrorReporter for CSSInvalidErrorReporterTest {
     fn report_error(&self,
                     input: &mut CssParser,
                     position: SourcePosition,
                     message: &str,
-                    url: &ServoUrl) {
+                    url: &ServoUrl,
+                    line_number_offset: u64) {
 
         let location = input.source_location(position);
+        let line_offset = location.line + line_number_offset as usize;
 
         let mut errors = self.errors.lock().unwrap();
-
         errors.push(
             CSSError{
                 url: url.clone(),
-                line: location.line,
+                line: line_offset,
                 column: location.column,
                 message: message.to_owned()
             }
         );
     }
 }
 
 
@@ -323,25 +324,25 @@ fn test_report_error_stylesheet() {
     let url = ServoUrl::parse("about::test").unwrap();
     let error_reporter = CSSInvalidErrorReporterTest::new();
 
     let errors = error_reporter.errors.clone();
 
     let lock = SharedRwLock::new();
     let media = Arc::new(lock.wrap(MediaList::empty()));
     Stylesheet::from_str(css, url.clone(), Origin::UserAgent, media, lock,
-                         None, &error_reporter);
+                         None, &error_reporter, 5u64);
 
     let mut errors = errors.lock().unwrap();
 
     let error = errors.pop().unwrap();
     assert_eq!("Unsupported property declaration: 'invalid: true;'", error.message);
-    assert_eq!(5, error.line);
+    assert_eq!(10, error.line);
     assert_eq!(9, error.column);
 
     let error = errors.pop().unwrap();
     assert_eq!("Unsupported property declaration: 'display: invalid;'", error.message);
-    assert_eq!(4, error.line);
+    assert_eq!(9, error.line);
     assert_eq!(9, error.column);
 
     // testing for the url
     assert_eq!(url, error.url);
 }
--- a/servo/tests/unit/style/viewport.rs
+++ b/servo/tests/unit/style/viewport.rs
@@ -26,17 +26,18 @@ macro_rules! stylesheet {
     ($css:expr, $origin:ident, $error_reporter:expr, $shared_lock:expr) => {
         Box::new(Stylesheet::from_str(
             $css,
             ServoUrl::parse("http://localhost").unwrap(),
             Origin::$origin,
             Arc::new($shared_lock.wrap(MediaList::empty())),
             $shared_lock,
             None,
-            &$error_reporter
+            &$error_reporter,
+            0u64
         ))
     }
 }
 
 fn test_viewport_rule<F>(css: &str,
                          device: &Device,
                          callback: F)
     where F: Fn(&Vec<ViewportDescriptorDeclaration>, &str)
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -622,16 +622,38 @@ def set_test_type(config, tests):
     for test in tests:
         for test_type in ['mochitest', 'reftest']:
             if test_type in test['suite'] and 'web-platform' not in test['suite']:
                 test.setdefault('tags', {})['test-type'] = test_type
         yield test
 
 
 @transforms.add
+def parallel_stylo_tests(config, tests):
+    """Ensure that any stylo tests running with e10s enabled also test
+    parallel traversal in the style system."""
+
+    for test in tests:
+        if not test['test-platform'].startswith('linux64-stylo/'):
+            yield test
+            continue
+
+        e10s = test['e10s']
+        # We should have already handled 'both' in an earlier transform.
+        assert e10s != 'both'
+        if not e10s:
+            yield test
+            continue
+
+        test['mozharness'].setdefault('extra-options', [])\
+                          .append('--parallel-stylo-traversal')
+        yield test
+
+
+@transforms.add
 def make_job_description(config, tests):
     """Convert *test* descriptions to *job* descriptions (input to
     taskgraph.transforms.job)"""
 
     for test in tests:
         label = '{}-{}-{}'.format(config.kind, test['test-platform'], test['test-name'])
         if test['chunks'] > 1:
             label += '-{}'.format(test['this-chunk'])
--- a/testing/mozharness/mozharness/base/config.py
+++ b/testing/mozharness/mozharness/base/config.py
@@ -160,17 +160,17 @@ def parse_config_file(file_name, quiet=F
         config = local_dict[config_dict_name]
     elif file_name.endswith('.json'):
         fh = open(file_path)
         config = {}
         json_config = json.load(fh)
         config = dict(json_config)
         fh.close()
     else:
-        raise RuntimeError("Unknown config file type %s!" % file_name)
+        raise RuntimeError("Unknown config file type %s! (config files must end in .json or .py)" % file_name)
     # TODO return file_path
     return config
 
 
 def download_config_file(url, file_name):
     n = 0
     attempts = 5
     sleeptime = 60
--- a/testing/mozharness/scripts/desktop_unittest.py
+++ b/testing/mozharness/scripts/desktop_unittest.py
@@ -145,16 +145,22 @@ class DesktopUnittest(TestingMixin, Merc
             "help": "Number of this chunk"}
          ],
         [["--allow-software-gl-layers"], {
             "action": "store_true",
             "dest": "allow_software_gl_layers",
             "default": False,
             "help": "Permits a software GL implementation (such as LLVMPipe) to use the GL compositor."}
          ],
+        [["--parallel-stylo-traversal"], {
+            "action": "store_true",
+            "dest": "parallel_stylo_traversal",
+            "default": False,
+            "help": "Forcibly enable parallel traversal in Stylo with STYLO_THREADS=4"}
+         ],
     ] + copy.deepcopy(testing_config_options) + \
         copy.deepcopy(blobupload_config_options) + \
         copy.deepcopy(code_coverage_config_options)
 
     def __init__(self, require_config_file=True):
         # abs_dirs defined already in BaseScript but is here to make pylint happy
         self.abs_dirs = None
         super(DesktopUnittest, self).__init__(
@@ -699,16 +705,17 @@ class DesktopUnittest(TestingMixin, Merc
                 env['MOZ_UPLOAD_DIR'] = self.query_abs_dirs()['abs_blob_upload_dir']
                 env['MINIDUMP_SAVE_PATH'] = self.query_abs_dirs()['abs_blob_upload_dir']
                 env['RUST_BACKTRACE'] = '1'
                 if not os.path.isdir(env['MOZ_UPLOAD_DIR']):
                     self.mkdir_p(env['MOZ_UPLOAD_DIR'])
 
                 if self.config['allow_software_gl_layers']:
                     env['MOZ_LAYERS_ALLOW_SOFTWARE_GL'] = '1'
+                env['STYLO_THREADS'] = '4' if self.config['parallel_stylo_traversal'] else '1'
 
                 env = self.query_env(partial_env=env, log_level=INFO)
                 cmd_timeout = self.get_timeout_for_category(suite_category)
                 return_code = self.run_command(cmd, cwd=dirs['abs_work_dir'],
                                                output_timeout=cmd_timeout,
                                                output_parser=parser,
                                                env=env)
 
--- a/third_party/rust/cssparser/.cargo-checksum.json
+++ b/third_party/rust/cssparser/.cargo-checksum.json
@@ -1,1 +1,1 @@
-{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"e32812a8f09b0c5b0b972e2e090f8929eb5b600a37ca7aac2ed07ba10c30291e",".travis.yml":"f1fb4b65964c81bc1240544267ea334f554ca38ae7a74d57066f4d47d2b5d568","Cargo.toml":"74f601f0216f4d2e4ebcdcaddd5d6dbec5e06ef10110359f0eae60d801f676a6","LICENSE":"fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85","README.md":"9afe084d70a5d9396674a2624012d6ac749df35f81e322d2d75b042bf208f523","build.rs":"950bcc47a196f07f99f59637c28cc65e02a885130011f90a2b2608248b4724a2","build/match_byte.rs":"89e8b941af74df2c204abf808672d3ff278bdec75abc918c41a843260b924677","docs/.nojekyll":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","docs/404.html":"025861f76f8d1f6d67c20ab624c6e418f4f824385e2dd8ad8732c4ea563c6a2e","docs/index.html":"025861f76f8d1f6d67c20ab624c6e418f4f824385e2dd8ad8732c4ea563c6a2e","src/big-data-url.css":"04a8f6197ea1181123bca48bd1ebd016268e1da40f01b8f21055814e44bf62b8","src/color.rs":"1534825f7462378ad830954168b8d257d82b95709c140ba8e463a6ada05727b7","src/css-parsing-tests/An+B.json":"d24559c1dad55d3da9d1fca29383edefdfc6046988435d6388a9bc0f28850257","src/css-parsing-tests/LICENSE":"5f9019a92f4aa8917aadc8e035aa673c2c1bf08d5ca2e535a0564106599f44eb","src/css-parsing-tests/README.rst":"775c5f957dd1d46d3ce954aaad219c821d2b64b4a9fb93c42e9737a11131ca44","src/css-parsing-tests/color3.json":"008f080f6f2dbae5ee403ff46aaa40a9a16e68a2b8923446ac6374f04da9e868","src/css-parsing-tests/color3_hsl.json":"09a4a1e51fb78276cdbf2e834cc9234f5b97c35426ddc879e35b2b09990327b5","src/css-parsing-tests/color3_keywords.json":"95609bf9fe762c316878a30f371fa375a2e51c21a6fda24fa188a95cd9118f5c","src/css-parsing-tests/component_value_list.json":"dda7244eb3a4fcf6d296762e285f7031028837d987065a09e584e8d973edc7f3","src/css-parsing-tests/declaration_list.json":"0b85cc3f19e945f838432acbfb9edb003abea13debc4ea27bcdcef25d117eac5","src/css-parsing-tests/make_color3_hsl.py":"df6f4c154c098641aab81d030de53c65d75d9bde429e9d1ff7069cc5b1827031","src/css-parsing-tests/make_color3_keywords.py":"66bccab3f1dea18698fcfd854be79b1fd1cd724dd487e25b1f057b522163aad2","src/css-parsing-tests/one_component_value.json":"8798017709002e14cf11e203c9d716f82d308ce6ba0f6e64ee4eea331b8485c6","src/css-parsing-tests/one_declaration.json":"a34c9da56edfff9e2e21615f059e141b0e878e90f794dc8fa58d65b47cd193ed","src/css-parsing-tests/one_rule.json":"88f7b1b6049be88e1e2827673b75fc9261986b216e8ee6bf09621fecbe274e3c","src/css-parsing-tests/rule_list.json":"97c45e80fb83abef149a4016c5625a74f053e7ad70a2ce5a95c02fce1c195686","src/css-parsing-tests/stylesheet.json":"05f1e10fc486bfbda2c059c313a74ff78c0063c0768b99737cab41969c0c87ce","src/css-parsing-tests/stylesheet_bytes.json":"890fd856a596e61f82cf7ed77920ffe95df89209fdb5ee0afe0b26bdfdb80a42","src/css-parsing-tests/urange.json":"7ce494811fcb64f20597bd11c88dc99bd72445290582e280bf7774f5d15e1ed3","src/from_bytes.rs":"331fe63af2123ae3675b61928a69461b5ac77799fff3ce9978c55cf2c558f4ff","src/lib.rs":"517ed8dc4520a1294ecd103b0ffa249550e51f8410124166dddaa2b5488f55af","src/macros.rs":"bd492479eee8c65850bdfc2c9de100eddc224e5d6629cd583219058901cd0ca7","src/nth.rs":"0a5e68bd8a597403e184ebf34e69230ae1e955f92b16b99b3f67cf8730a180a9","src/parser.rs":"a41b1d885389d34b4d81176f844ae3c4100e621628dd50d7348c42b08cdd13ae","src/rules_and_declarations.rs":"6b66a986e411a56998546ab0e64de5285df3368d7c4018c7230a1b6cf6bcc532","src/serializer.rs":"4521b58389bd57acced55c3c6130831b7f80eff48ef873c48c5363e0eca0a15c","src/tests.rs":"bfaa3fd5892c6fd32a747d48251eb3262d514dc2b82329086e8c75febed98e95","src/tokenizer.rs":"ef1f220224365d46299160191facd2d9e0534e10ef362129cf56cd3dbb87106a","src/unicode_range.rs":"a3accaf00b8e0e93ba9af0863024507b97ddc2646e65c5f7421597a269317ac0"},"package":"35e3e110221f306501253d8d34857040de365ce2d92ac0abb2f06dedc845d9d0"}
\ No newline at end of file
+{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"e32812a8f09b0c5b0b972e2e090f8929eb5b600a37ca7aac2ed07ba10c30291e",".travis.yml":"f1fb4b65964c81bc1240544267ea334f554ca38ae7a74d57066f4d47d2b5d568","Cargo.toml":"410baf41d446e2e281eabe0c52a0b094b457dbb5d7c096fcfbd6ce4e5e01b998","LICENSE":"fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85","README.md":"9afe084d70a5d9396674a2624012d6ac749df35f81e322d2d75b042bf208f523","build.rs":"950bcc47a196f07f99f59637c28cc65e02a885130011f90a2b2608248b4724a2","build/match_byte.rs":"89e8b941af74df2c204abf808672d3ff278bdec75abc918c41a843260b924677","docs/.nojekyll":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","docs/404.html":"025861f76f8d1f6d67c20ab624c6e418f4f824385e2dd8ad8732c4ea563c6a2e","docs/index.html":"025861f76f8d1f6d67c20ab624c6e418f4f824385e2dd8ad8732c4ea563c6a2e","src/big-data-url.css":"04a8f6197ea1181123bca48bd1ebd016268e1da40f01b8f21055814e44bf62b8","src/color.rs":"09249793df09c51ce0f2a8eabae85405cfdbd8c23330bb121f8b61665df853fd","src/css-parsing-tests/An+B.json":"d24559c1dad55d3da9d1fca29383edefdfc6046988435d6388a9bc0f28850257","src/css-parsing-tests/LICENSE":"5f9019a92f4aa8917aadc8e035aa673c2c1bf08d5ca2e535a0564106599f44eb","src/css-parsing-tests/README.rst":"775c5f957dd1d46d3ce954aaad219c821d2b64b4a9fb93c42e9737a11131ca44","src/css-parsing-tests/color3.json":"5104348cc58a10d113ad4c20f05bdba846ecb4920ee606203fe5d28a6a9f0a13","src/css-parsing-tests/color3_hsl.json":"88936185b3ead36f8613372bf595024073d7787adbf659a62446789a2504b59f","src/css-parsing-tests/color3_keywords.json":"95609bf9fe762c316878a30f371fa375a2e51c21a6fda24fa188a95cd9118f5c","src/css-parsing-tests/component_value_list.json":"dda7244eb3a4fcf6d296762e285f7031028837d987065a09e584e8d973edc7f3","src/css-parsing-tests/declaration_list.json":"0b85cc3f19e945f838432acbfb9edb003abea13debc4ea27bcdcef25d117eac5","src/css-parsing-tests/make_color3_hsl.py":"6297d9fb7b23875ccf99111a56a8e971a37c4206d7d6782001e95a7194fa6182","src/css-parsing-tests/make_color3_keywords.py":"66bccab3f1dea18698fcfd854be79b1fd1cd724dd487e25b1f057b522163aad2","src/css-parsing-tests/one_component_value.json":"8798017709002e14cf11e203c9d716f82d308ce6ba0f6e64ee4eea331b8485c6","src/css-parsing-tests/one_declaration.json":"a34c9da56edfff9e2e21615f059e141b0e878e90f794dc8fa58d65b47cd193ed","src/css-parsing-tests/one_rule.json":"88f7b1b6049be88e1e2827673b75fc9261986b216e8ee6bf09621fecbe274e3c","src/css-parsing-tests/rule_list.json":"97c45e80fb83abef149a4016c5625a74f053e7ad70a2ce5a95c02fce1c195686","src/css-parsing-tests/stylesheet.json":"05f1e10fc486bfbda2c059c313a74ff78c0063c0768b99737cab41969c0c87ce","src/css-parsing-tests/stylesheet_bytes.json":"890fd856a596e61f82cf7ed77920ffe95df89209fdb5ee0afe0b26bdfdb80a42","src/css-parsing-tests/urange.json":"90a0348cb785f8761d956458880486136de75f2c7ce44ec66da233f73d9098bf","src/from_bytes.rs":"331fe63af2123ae3675b61928a69461b5ac77799fff3ce9978c55cf2c558f4ff","src/lib.rs":"ccc0f04541147d4fb90d3fe70591bacfb0c7030706c9be8fa60b80533e522bbc","src/macros.rs":"bd492479eee8c65850bdfc2c9de100eddc224e5d6629cd583219058901cd0ca7","src/nth.rs":"0a5e68bd8a597403e184ebf34e69230ae1e955f92b16b99b3f67cf8730a180a9","src/parser.rs":"a41b1d885389d34b4d81176f844ae3c4100e621628dd50d7348c42b08cdd13ae","src/rules_and_declarations.rs":"6b66a986e411a56998546ab0e64de5285df3368d7c4018c7230a1b6cf6bcc532","src/serializer.rs":"480043750508944fb75295d8638b6b33665273a2ac5791ec8261ae1fe63c0fcd","src/tests.rs":"80e4fec507258fe4e63a590f842f3213b44418cd69d755f78f938894966037db","src/tokenizer.rs":"945c29ff9f4a79d58aee956e27c8b476068859d878ead7c7fed83a9e89d2159f","src/unicode_range.rs":"fa0198eb00a68cd71d009551194a3f6d2cb0501513b77d9ea2fea22bfa45c141"},"package":"fb067f9d88368ca9053aea00581556151ef96f2591e32ad44f3312d7e6b67392"}
\ No newline at end of file
--- a/third_party/rust/cssparser/Cargo.toml
+++ b/third_party/rust/cssparser/Cargo.toml
@@ -1,27 +1,27 @@
 [package]
 
 name = "cssparser"
-version = "0.12.1"
+version = "0.12.2"
 authors = [ "Simon Sapin <simon.sapin@exyr.org>" ]
 
 description = "Rust implementation of CSS Syntax Level 3"
 documentation = "https://docs.rs/cssparser/"
 repository = "https://github.com/servo/rust-cssparser"
 readme = "README.md"
 keywords = ["css", "syntax", "parser"]
 license = "MPL-2.0"
 build = "build.rs"
 
 exclude = ["src/css-parsing-tests"]
 
 [dev-dependencies]
 rustc-serialize = "0.3"
-tempdir = "0.3"
+difference = "1.0"
 encoding_rs = "0.5"
 
 [dependencies]
 cssparser-macros = {path = "./macros", version = "0.2"}
 heapsize = {version = "0.3", optional = true}
 matches = "0.1"
 phf = "0.7"
 procedural-masquerade = {path = "./procedural-masquerade", version = "0.1"}
--- a/third_party/rust/cssparser/src/color.rs
+++ b/third_party/rust/cssparser/src/color.rs
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use std::cmp;
 use std::fmt;
+use std::f32::consts::PI;
 
 use super::{Token, Parser, ToCss};
 use tokenizer::NumericValue;
 
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 
 /// A color with red, green, blue, and alpha components, in a byte each.
@@ -393,80 +394,156 @@ fn clamp_f32(val: f32) -> u8 {
     // `256.0_f32 as u8` is undefined behavior:
     //
     // https://github.com/rust-lang/rust/issues/10184
     (val * 256.).floor().max(0.).min(255.) as u8
 }
 
 #[inline]
 fn parse_color_function(name: &str, arguments: &mut Parser) -> Result<Color, ()> {
-    let (is_rgb, has_alpha) = match_ignore_ascii_case! { name,
-        "rgba" => (true, true),
-        "rgb" => (true, false),
-        "hsl" => (false, false),
-        "hsla" => (false, true),
+    let is_rgb = match_ignore_ascii_case! { name,
+        "rgb" | "rgba" => true,
+        "hsl" | "hsla" => false,
         _ => return Err(())
     };
 
+    let (red, green, blue, uses_commas) = if is_rgb {
+        parse_rgb_components_rgb(arguments)?
+    } else {
+        parse_rgb_components_hsl(arguments)?
+    };
+
+    let alpha = if !arguments.is_exhausted() {
+        if uses_commas {
+            try!(arguments.expect_comma());
+        } else {
+            match try!(arguments.next()) {
+                Token::Delim('/') => {},
+                _ => return Err(())
+            };
+        };
+        let token = try!(arguments.next());
+        match token {
+            Token::Number(NumericValue { value: v, .. }) => {
+                clamp_f32(v)
+            }
+            Token::Percentage(ref v) => {
+                clamp_f32(v.unit_value)
+            }
+            _ => {
+                return Err(())
+            }
+        }
+    } else {
+        255
+    };
+
+    try!(arguments.expect_exhausted());
+    rgba(red, green, blue, alpha)
+}
+
+
+#[inline]
+fn parse_rgb_components_rgb(arguments: &mut Parser) -> Result<(u8, u8, u8, bool), ()> {
     let red: u8;
     let green: u8;
     let blue: u8;
-    if is_rgb {
-        // Either integers or percentages, but all the same type.
-        // https://drafts.csswg.org/css-color/#rgb-functions
-        match try!(arguments.next()) {
-            Token::Number(NumericValue { int_value: Some(v), .. }) => {
-                red = clamp_i32(v);
-                try!(arguments.expect_comma());
-                green = clamp_i32(try!(arguments.expect_integer()));
+    let mut uses_commas = false;
+
+    // Either integers or percentages, but all the same type.
+    // https://drafts.csswg.org/css-color/#rgb-functions
+    match try!(arguments.next()) {
+        Token::Number(NumericValue { value: v, .. }) => {
+            red = clamp_i32(v as i32);
+            green = clamp_i32(match try!(arguments.next()) {
+                Token::Number(NumericValue { value: v, .. }) => v,
+                Token::Comma => {
+                    uses_commas = true;
+                    try!(arguments.expect_number())
+                }
+                _ => return Err(())
+            } as i32);
+            if uses_commas {
                 try!(arguments.expect_comma());
-                blue = clamp_i32(try!(arguments.expect_integer()));
             }
-            Token::Percentage(ref v) => {
-                red = clamp_f32(v.unit_value);
+            blue = clamp_i32(try!(arguments.expect_number()) as i32);
+        }
+        Token::Percentage(ref v) => {
+            red = clamp_f32(v.unit_value);
+            green = clamp_f32(match try!(arguments.next()) {
+                Token::Percentage(ref v) => v.unit_value,
+                Token::Comma => {
+                    uses_commas = true;
+                    try!(arguments.expect_percentage())
+                }
+                _ => return Err(())
+            });
+            if uses_commas {
                 try!(arguments.expect_comma());
-                green = clamp_f32(try!(arguments.expect_percentage()));
-                try!(arguments.expect_comma());
-                blue = clamp_f32(try!(arguments.expect_percentage()));
             }
-            _ => return Err(())
-        };
-    } else {
-        let hue_degrees = try!(arguments.expect_number());
-        // Subtract an integer before rounding, to avoid some rounding errors:
-        let hue_normalized_degrees = hue_degrees - 360. * (hue_degrees / 360.).floor();
-        let hue = hue_normalized_degrees / 360.;
-        // Saturation and lightness are clamped to 0% ... 100%
-        // https://drafts.csswg.org/css-color/#the-hsl-notation
-        try!(arguments.expect_comma());
-        let saturation = try!(arguments.expect_percentage()).max(0.).min(1.);
-        try!(arguments.expect_comma());
-        let lightness = try!(arguments.expect_percentage()).max(0.).min(1.);
+            blue = clamp_f32(try!(arguments.expect_percentage()));
+        }
+        _ => return Err(())
+    };
+    return Ok((red, green, blue, uses_commas));
+}
 
-        // https://drafts.csswg.org/css-color/#hsl-color
-        // except with h pre-multiplied by 3, to avoid some rounding errors.
-        fn hue_to_rgb(m1: f32, m2: f32, mut h3: f32) -> f32 {
-            if h3 < 0. { h3 += 3. }
-            if h3 > 3. { h3 -= 3. }
+#[inline]
+fn parse_rgb_components_hsl(arguments: &mut Parser) -> Result<(u8, u8, u8, bool), ()> {
+    let mut uses_commas = false;
+    // Hue given as an angle
+    // https://drafts.csswg.org/css-values/#angles
+    let hue_degrees = match try!(arguments.next()) {
+        Token::Number(NumericValue { value: v, .. }) => v,
+        Token::Dimension(NumericValue { value: v, .. }, unit) => {
+            match_ignore_ascii_case! { &*unit,
+                "deg" => v,
+                "grad" => v * 360. / 400.,
+                "rad" => v * 360. / (2. * PI),
+                "turn" => v * 360.,
+                _ => return Err(())
+            }
+        }
+        _ => return Err(())
+    };
+    // Subtract an integer before rounding, to avoid some rounding errors:
+    let hue_normalized_degrees = hue_degrees - 360. * (hue_degrees / 360.).floor();
+    let hue = hue_normalized_degrees / 360.;
 
-            if h3 * 2. < 1. { m1 + (m2 - m1) * h3 * 2. }
-            else if h3 * 2. < 3. { m2 }
-            else if h3 < 2. { m1 + (m2 - m1) * (2. - h3) * 2. }
-            else { m1 }
+    // Saturation and lightness are clamped to 0% ... 100%
+    // https://drafts.csswg.org/css-color/#the-hsl-notation
+    let saturation = match try!(arguments.next()) {
+        Token::Percentage(ref v) => v.unit_value,
+        Token::Comma => {
+            uses_commas = true;
+            try!(arguments.expect_percentage())
         }
-        let m2 = if lightness <= 0.5 { lightness * (saturation + 1.) }
-                 else { lightness + saturation - lightness * saturation };
-        let m1 = lightness * 2. - m2;
-        let hue_times_3 = hue * 3.;
-        red = clamp_f32(hue_to_rgb(m1, m2, hue_times_3 + 1.));
-        green = clamp_f32(hue_to_rgb(m1, m2, hue_times_3));
-        blue = clamp_f32(hue_to_rgb(m1, m2, hue_times_3 - 1.));
+        _ => return Err(())
+    };
+    let saturation = saturation.max(0.).min(1.);
+
+    if uses_commas {
+        try!(arguments.expect_comma());
     }
 
-    let alpha = if has_alpha {
-        try!(arguments.expect_comma());
-        clamp_f32(try!(arguments.expect_number()))
-    } else {
-        255
-    };
-    try!(arguments.expect_exhausted());
-    rgba(red, green, blue, alpha)
+    let lightness = try!(arguments.expect_percentage());
+    let lightness = lightness.max(0.).min(1.);
+
+    // https://drafts.csswg.org/css-color/#hsl-color
+    // except with h pre-multiplied by 3, to avoid some rounding errors.
+    fn hue_to_rgb(m1: f32, m2: f32, mut h3: f32) -> f32 {
+        if h3 < 0. { h3 += 3. }
+        if h3 > 3. { h3 -= 3. }
+
+        if h3 * 2. < 1. { m1 + (m2 - m1) * h3 * 2. }
+        else if h3 * 2. < 3. { m2 }
+        else if h3 < 2. { m1 + (m2 - m1) * (2. - h3) * 2. }
+        else { m1 }
+    }
+    let m2 = if lightness <= 0.5 { lightness * (saturation + 1.) }
+             else { lightness + saturation - lightness * saturation };
+    let m1 = lightness * 2. - m2;
+    let hue_times_3 = hue * 3.;
+    let red = clamp_f32(hue_to_rgb(m1, m2, hue_times_3 + 1.));
+    let green = clamp_f32(hue_to_rgb(m1, m2, hue_times_3));
+    let blue = clamp_f32(hue_to_rgb(m1, m2, hue_times_3 - 1.));
+    return Ok((red, green, blue, uses_commas));
 }
--- a/third_party/rust/cssparser/src/css-parsing-tests/color3.json
+++ b/third_party/rust/cssparser/src/css-parsing-tests/color3.json
@@ -60,23 +60,24 @@
 "rgb(10%, 50%, 0)", null,
 "rgb(255, 50%, 0%)", null,
 "rgb(0, 0 0)", null,
 "rgb(0, 0, 0deg)", null,
 "rgb(0, 0, light)", null,
 "rgb()", null,
 "rgb(0)", null,
 "rgb(0, 0)", null,
-"rgb(0, 0, 0, 0)", null,
+"rgb(0, 0, 0, 0)", [0, 0, 0, 0],
 "rgb(0%)", null,
 "rgb(0%, 0%)", null,
-"rgb(0%, 0%, 0%, 0%)", null,
-"rgb(0%, 0%, 0%, 0)", null,
+"rgb(0%, 0%, 0%, 0%)", [0, 0, 0, 0],
+"rgb(0%, 0%, 0%, 0)", [0, 0, 0, 0],
 
 "rgba(0, 0, 0, 0)", [0, 0, 0, 0],
+ "rgba(.3, -1.4, -0.001e2, 0)", [0, 0, 0, 0],
 "rgba(204, 0, 102, 0.25)", [204, 0, 102, 64],
 "RGBA(255,255,255, 0)", [255, 255, 255, 0],
 "rgBA(0, 51,255, 1)", [0, 51, 255, 255],
 "rgba(0, 51,255, 1.1)", [0, 51, 255, 255],
 "rgba(0, 51,255, 37)", [0, 51, 255, 255],
 "rgba(0, 51,255, 0.5)", [0, 51, 255, 128],
 "rgba(0, 51,255, 0)", [0, 51, 255, 0],
 "rgba(0, 51,255, -0.1)", [0, 51, 255, 0],
@@ -87,60 +88,170 @@
 "rgBA(0%, 20%, 100%, 1)", [0, 51, 255, 255],
 "rgba(0%, 20%, 100%, 1.1)", [0, 51, 255, 255],
 "rgba(0%, 20%, 100%, 37)", [0, 51, 255, 255],
 "rgba(0%, 20%, 100%, 0.25)", [0, 51, 255, 64],
 "rgba(0%, 20%, 100%, 0)", [0, 51, 255, 0],
 "rgba(0%, 20%, 100%, -0.1)", [0, 51, 255, 0],
 "rgba(0%, 20%, 100%, -139)", [0, 51, 255, 0],
 
-"rgba(255,255,255, 0%)", null,
+"rgba(255,255,255, 0%)", [255, 255, 255, 0],
 "rgba(10%, 50%, 0, 1)", null,
 "rgba(255, 50%, 0%, 1)", null,
 "rgba(0, 0, 0 0)", null,
 "rgba(0, 0, 0, 0deg)", null,
 "rgba(0, 0, 0, light)", null,
 "rgba()", null,
 "rgba(0)", null,
-"rgba(0, 0, 0)", null,
+"rgba(0, 0, 0)", [0, 0, 0, 255],
 "rgba(0, 0, 0, 0, 0)", null,
 "rgba(0%)", null,
 "rgba(0%, 0%)", null,
-"rgba(0%, 0%, 0%)", null,
-"rgba(0%, 0%, 0%, 0%)", null,
+"rgba(0%, 0%, 0%)", [0, 0, 0, 255],
+"rgba(0%, 0%, 0%, 0%)", [0, 0, 0, 0],
 "rgba(0%, 0%, 0%, 0%, 0%)", null,
 
 "HSL(0, 0%, 0%)", [0, 0, 0, 255],
 "hsL(0, 100%, 50%)", [255, 0, 0, 255],
 "hsl(60, 100%, 37.5%)", [192, 192, 0, 255],
 "hsl(780, 100%, 37.5%)", [192, 192, 0, 255],
 "hsl(-300, 100%, 37.5%)", [192, 192, 0, 255],
 "hsl(300, 50%, 50%)", [192, 64, 192, 255],
 
 "hsl(10, 50%, 0)", null,
 "hsl(50%, 50%, 0%)", null,
 "hsl(0, 0% 0%)", null,
-"hsl(30deg, 100%, 100%)", null,
+"hsl(30deg, 100%, 100%)", [255, 255, 255, 255],
 "hsl(0, 0%, light)", null,
 "hsl()", null,
 "hsl(0)", null,
 "hsl(0, 0%)", null,
-"hsl(0, 0%, 0%, 0%)", null,
+"hsl(0, 0%, 0%, 0%)", [0, 0, 0, 0],
 
 "HSLA(-300, 100%, 37.5%, 1)", [192, 192, 0, 255],
 "hsLA(-300, 100%, 37.5%, 12)", [192, 192, 0, 255],
 "hsla(-300, 100%, 37.5%, 0.2)", [192, 192, 0, 51],
 "hsla(-300, 100%, 37.5%, 0)", [192, 192, 0, 0],
 "hsla(-300, 100%, 37.5%, -3)", [192, 192, 0, 0],
 
 "hsla(10, 50%, 0, 1)", null,
 "hsla(50%, 50%, 0%, 1)", null,
 "hsla(0, 0% 0%, 1)", null,
-"hsla(30deg, 100%, 100%, 1)", null,
+"hsla(30deg, 100%, 100%, 1)", [255, 255, 255, 255],
 "hsla(0, 0%, light, 1)", null,
 "hsla()", null,
 "hsla(0)", null,
 "hsla(0, 0%)", null,
-"hsla(0, 0%, 0%, 50%)", null,
+"hsla(0, 0%, 0%, 50%)", [0, 0, 0, 128],
 "hsla(0, 0%, 0%, 255, 0%)", null,
 
+"rgb(0 0 0 0)", null,
+"rgb(0 0 0 / 0)", [0, 0, 0, 0],
+"rgb(0%)", null,
+"rgb(0% 0%)", null,
+"rgb(0% 0% 0% / 0%)", [0, 0, 0, 0],
+"rgb(0% 0% 0% / 0)", [0, 0, 0, 0],
+
+"rgba(0%)", null,
+"rgba(0% 0%)", null,
+"rgba(0% 0% 0%)", [0, 0, 0, 255],
+"rgba(0% 0% 0% / 0%)", [0, 0, 0, 0],
+"rgba(0% 0% 0% / 0% 0%)", null,
+
+"rgb(0, 0 0 0)", null,
+"rgb(0 0, 0 0)", null,
+"rgb(0 0 0, 0)", null,
+"rgb(0, 0, 0 0)", null,
+
+"rgba(0%, 0% 0%)", null,
+"rgba(0% 0% 0%, 0%)", null,
+
+"HSL(0 0% 0%)", [0, 0, 0, 255],
+"hsL(0 100% 50%)", [255, 0, 0, 255],
+"hsl(60 100% 37.5%)", [192, 192, 0, 255],
+
+"HSLA(-300 100% 37.5% /1)", [192, 192, 0, 255],
+"hsLA(-300 100% 37.5% /12)", [192, 192, 0, 255],
+
+"hsl(0, 0 0 0)", null,
+"hsl(0 0, 0 0)", null,
+"hsl(0 0 0, 0)", null,
+"hsl(0, 0, 0 0)", null,
+
+"hsla(0%, 0% 0%)", null,
+"hsla(0% 0% 0%, 0%)", null,
+
+"hsla(120.0, 75%, 50%, 20%)", [32, 224, 32, 51],
+"hsla(120, 75%, 50%, 0.4)", [32, 224, 32, 102],
+"hsla(120 75% 50% / 60%)", [32, 224, 32, 153],
+"hsla(120.0 75% 50% / 1.0)", [32, 224, 32, 255],
+"hsla(120/* comment */75%/* comment */50%/1.0)", [32, 224, 32, 255],
+"hsla(120,/* comment */75%,/* comment */50%,100%)", [32, 224, 32, 255],
+"hsla(120.0, 75%, 50%)", [32, 224, 32, 255],
+"hsla(120 75% 50%)", [32, 224, 32, 255],
+"hsla(120/* comment */75%/* comment */50%)", [32, 224, 32, 255],
+"hsla(120/* comment */,75%,/* comment */50%)", [32, 224, 32, 255],
+"hsl(120, 75%, 50%, 0.2)", [32, 224, 32, 51],
+"hsl(120, 75%, 50%, 40%)", [32, 224, 32, 102],
+"hsl(120 75% 50% / 0.6)", [32, 224, 32, 153],
+"hsl(120 75% 50% / 80%)", [32, 224, 32, 204],
+"hsl(120/* comment */75%/* comment */50%/1.0)", [32, 224, 32, 255],
+"hsl(120/* comment */75%/* comment */50%/100%)", [32, 224, 32, 255],
+"hsl(120,/* comment */75%,/* comment */50%,1.0)", [32, 224, 32, 255],
+"hsl(120,/* comment */75%,/* comment */50%,100%)", [32, 224, 32, 255],
+"hsl(120/* comment */75%/* comment */50%)", [32, 224, 32, 255],
+"hsl(120/* comment */,75%,/* comment */50%)", [32, 224, 32, 255],
+"hsla(120, 75%, 50%, 0.2)", [32, 224, 32, 51],
+"hsl(240, 75%, 50%)", [32, 32, 224, 255],
+"hsla(120, 75%, 50%)", [32, 224, 32, 255],
+"hsla(120.0, 75%, 50%)", [32, 224, 32, 255],
+"hsla(1.2e2, 75%, 50%)", [32, 224, 32, 255],
+"hsla(1.2E2, 75%, 50%)", [32, 224, 32, 255],
+"hsla(60, 75%, 50%)", [224, 224, 32, 255],
+"hsl(120, 75%, 50%, 0.2)", [32, 224, 32, 51],
+"hsl(120.0, 75%, 50%, 0.4)", [32, 224, 32, 102],
+"hsl(1.2e2, 75%, 50%, 0.6)", [32, 224, 32, 153],
+"hsl(1.2E2, 75%, 50%, 0.8)", [32, 224, 32, 204],
+"hsl(60.0, 75%, 50%, 1.0)", [224, 224, 32, 255],
+"rgb(10%, 60%, 10%, 20%)", [25, 153, 25, 51],
+"rgb(10, 175, 10, 0.4)", [10, 175, 10, 102],
+"rgb(10 175 10 / 60%)", [10, 175, 10, 153],
+"rgb(10.0 175.0 10.0 / 0.8)", [10, 175, 10, 204],
+"rgb(10/* comment */175/* comment */10/100%)", [10, 175, 10, 255],
+"rgb(10,/* comment */150,/* comment */50)", [10, 150, 50, 255],
+"rgb(10%, 60%, 10%)", [25, 153, 25, 255],
+"rgb(10.0 100.0 100.0)", [10, 100, 100, 255],
+"rgb(10/* comment */75/* comment */125)", [10, 75, 125, 255],
+"rgb(10.0, 50.0, 150.0)", [10, 50, 150, 255],
+"rgba(10.0, 175.0, 10.0, 0.2)", [10, 175, 10, 51],
+"rgba(10, 175, 10, 40%)", [10, 175, 10, 102],
+"rgba(10% 75% 10% / 0.6)", [25, 192, 25, 153],
+"rgba(10 175 10 / 80%)", [10, 175, 10, 204],
+"rgba(10/* comment */175/* comment */10/100%)", [10, 175, 10, 255],
+"rgba(10,/* comment */150,/* comment */50)", [10, 150, 50, 255],
+"rgba(10.0, 125.0, 75.0)", [10, 125, 75, 255],
+"rgba(10%, 45%, 45%)", [25, 115, 115, 255],
+"rgba(10/* comment */75/* comment */125)", [10, 75, 125, 255],
+"rgba(10.0, 50.0, 150.0)", [10, 50, 150, 255],
+"rgb(10, 175, 10, 0.2)", [10, 175, 10, 51],
+"rgb(10, 175, 10, 0.4)", [10, 175, 10, 102],
+"rgb(10, 175, 10, 0.6)", [10, 175, 10, 153],
+"rgb(10%, 70%, 10%, 0.8)", [25, 179, 25, 204],
+"rgb(10%, 70%, 10%, 1.0)", [25, 179, 25, 255],
+"rgba(10, 150, 50)", [10, 150, 50, 255],
+"rgba(10, 125, 75)", [10, 125, 75, 255],
+"rgba(10%,40%, 40%)", [25, 102, 102, 255],
+"rgba(10%, 45%, 50%)", [25, 115, 128, 255],
+"rgba(10%, 50%, 60%)", [25, 128, 153, 255],
+
+"hsla(120deg, 75%, 50%, 0.4)", [32, 224, 32, 102],
+"hsla(120DEG, 75%, 50%, 0.4)", [32, 224, 32, 102],
+"hsla(120deG, 75%, 50%, 0.4)", [32, 224, 32, 102],
+"hsla(133.33333333grad, 75%, 50%, 0.6)", [32, 224, 32, 153],
+"hsla(2.0943951024rad, 75%, 50%, 0.8)", [32, 224, 32, 204],
+"hsla(0.3333333333turn, 75%, 50%, 1.0)", [32, 224, 32, 255],
+"hsl(600deg, 75%, 50%)", [32, 32, 224, 255],
+"hsl(1066.66666666grad, 75%, 50%)", [32, 32, 224, 255],
+"hsl(10.4719755118rad, 75%, 50%)", [32, 32, 224, 255],
+"hsl(2.6666666666turn, 75%, 50%)", [32, 32, 224, 255],
+
 "cmyk(0, 0, 0, 0)", null
 ]
--- a/third_party/rust/cssparser/src/css-parsing-tests/color3_hsl.json
+++ b/third_party/rust/cssparser/src/css-parsing-tests/color3_hsl.json
@@ -966,16 +966,3904 @@
 "hsl(120, 100%, 100%)", [255, 255, 255, 255],
 "hsl(150, 100%, 100%)", [255, 255, 255, 255],
 "hsl(180, 100%, 100%)", [255, 255, 255, 255],
 "hsl(210, 100%, 100%)", [255, 255, 255, 255],
 "hsl(240, 100%, 100%)", [255, 255, 255, 255],
 "hsl(270, 100%, 100%)", [255, 255, 255, 255],
 "hsl(300, 100%, 100%)", [255, 255, 255, 255],
 "hsl(330, 100%, 100%)", [255, 255, 255, 255],
+"hsl(0, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(30, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(60, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(90, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(120, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(150, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(180, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(210, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(240, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(270, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(300, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(330, 0%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(0, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(30, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(60, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(90, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(120, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(150, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(180, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(210, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(240, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(270, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(300, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(330, 12.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(0, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(30, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(60, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(90, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(120, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(150, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(180, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(210, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(240, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(270, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(300, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(330, 25%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(0, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(30, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(60, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(90, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(120, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(150, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(180, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(210, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(240, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(270, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(300, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(330, 37.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(0, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(30, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(60, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(90, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(120, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(150, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(180, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(210, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(240, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(270, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(300, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(330, 50%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(0, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(30, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(60, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(90, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(120, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(150, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(180, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(210, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(240, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(270, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(300, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(330, 62.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(0, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(30, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(60, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(90, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(120, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(150, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(180, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(210, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(240, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(270, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(300, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(330, 75%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(0, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(30, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(60, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(90, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(120, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(150, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(180, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(210, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(240, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(270, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(300, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(330, 87.5%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(0, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(30, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(60, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(90, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(120, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(150, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(180, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(210, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(240, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(270, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(300, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(330, 100%, 0%, 1.0)", [0, 0, 0, 255],
+"hsl(0, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(30, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(60, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(90, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(120, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(150, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(180, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(210, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(240, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(270, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(300, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(330, 0%, 12.5%, 1.0)", [32, 32, 32, 255],
+"hsl(0, 12.5%, 12.5%, 1.0)", [36, 28, 28, 255],
+"hsl(30, 12.5%, 12.5%, 1.0)", [36, 32, 28, 255],
+"hsl(60, 12.5%, 12.5%, 1.0)", [36, 36, 28, 255],
+"hsl(90, 12.5%, 12.5%, 1.0)", [32, 36, 28, 255],
+"hsl(120, 12.5%, 12.5%, 1.0)", [28, 36, 28, 255],
+"hsl(150, 12.5%, 12.5%, 1.0)", [28, 36, 32, 255],
+"hsl(180, 12.5%, 12.5%, 1.0)", [28, 36, 36, 255],
+"hsl(210, 12.5%, 12.5%, 1.0)", [28, 32, 36, 255],
+"hsl(240, 12.5%, 12.5%, 1.0)", [28, 28, 36, 255],
+"hsl(270, 12.5%, 12.5%, 1.0)", [32, 28, 36, 255],
+"hsl(300, 12.5%, 12.5%, 1.0)", [36, 28, 36, 255],
+"hsl(330, 12.5%, 12.5%, 1.0)", [36, 28, 32, 255],
+"hsl(0, 25%, 12.5%, 1.0)", [40, 24, 24, 255],
+"hsl(30, 25%, 12.5%, 1.0)", [40, 32, 24, 255],
+"hsl(60, 25%, 12.5%, 1.0)", [40, 40, 24, 255],
+"hsl(90, 25%, 12.5%, 1.0)", [32, 40, 24, 255],
+"hsl(120, 25%, 12.5%, 1.0)", [24, 40, 24, 255],
+"hsl(150, 25%, 12.5%, 1.0)", [24, 40, 32, 255],
+"hsl(180, 25%, 12.5%, 1.0)", [24, 40, 40, 255],
+"hsl(210, 25%, 12.5%, 1.0)", [24, 32, 40, 255],
+"hsl(240, 25%, 12.5%, 1.0)", [24, 24, 40, 255],
+"hsl(270, 25%, 12.5%, 1.0)", [32, 24, 40, 255],
+"hsl(300, 25%, 12.5%, 1.0)", [40, 24, 40, 255],
+"hsl(330, 25%, 12.5%, 1.0)", [40, 24, 32, 255],
+"hsl(0, 37.5%, 12.5%, 1.0)", [44, 20, 20, 255],
+"hsl(30, 37.5%, 12.5%, 1.0)", [44, 32, 20, 255],
+"hsl(60, 37.5%, 12.5%, 1.0)", [44, 44, 20, 255],
+"hsl(90, 37.5%, 12.5%, 1.0)", [32, 44, 20, 255],
+"hsl(120, 37.5%, 12.5%, 1.0)", [20, 44, 20, 255],
+"hsl(150, 37.5%, 12.5%, 1.0)", [20, 44, 32, 255],
+"hsl(180, 37.5%, 12.5%, 1.0)", [20, 44, 44, 255],
+"hsl(210, 37.5%, 12.5%, 1.0)", [20, 32, 44, 255],
+"hsl(240, 37.5%, 12.5%, 1.0)", [20, 20, 44, 255],
+"hsl(270, 37.5%, 12.5%, 1.0)", [32, 20, 44, 255],
+"hsl(300, 37.5%, 12.5%, 1.0)", [44, 20, 44, 255],
+"hsl(330, 37.5%, 12.5%, 1.0)", [44, 20, 32, 255],
+"hsl(0, 50%, 12.5%, 1.0)", [48, 16, 16, 255],
+"hsl(30, 50%, 12.5%, 1.0)", [48, 32, 16, 255],
+"hsl(60, 50%, 12.5%, 1.0)", [48, 48, 16, 255],
+"hsl(90, 50%, 12.5%, 1.0)", [32, 48, 16, 255],
+"hsl(120, 50%, 12.5%, 1.0)", [16, 48, 16, 255],
+"hsl(150, 50%, 12.5%, 1.0)", [16, 48, 32, 255],
+"hsl(180, 50%, 12.5%, 1.0)", [16, 48, 48, 255],
+"hsl(210, 50%, 12.5%, 1.0)", [16, 32, 48, 255],
+"hsl(240, 50%, 12.5%, 1.0)", [16, 16, 48, 255],
+"hsl(270, 50%, 12.5%, 1.0)", [32, 16, 48, 255],
+"hsl(300, 50%, 12.5%, 1.0)", [48, 16, 48, 255],
+"hsl(330, 50%, 12.5%, 1.0)", [48, 16, 32, 255],
+"hsl(0, 62.5%, 12.5%, 1.0)", [52, 12, 12, 255],
+"hsl(30, 62.5%, 12.5%, 1.0)", [52, 32, 12, 255],
+"hsl(60, 62.5%, 12.5%, 1.0)", [52, 52, 12, 255],
+"hsl(90, 62.5%, 12.5%, 1.0)", [32, 52, 12, 255],
+"hsl(120, 62.5%, 12.5%, 1.0)", [12, 52, 12, 255],
+"hsl(150, 62.5%, 12.5%, 1.0)", [12, 52, 32, 255],
+"hsl(180, 62.5%, 12.5%, 1.0)", [12, 52, 52, 255],
+"hsl(210, 62.5%, 12.5%, 1.0)", [12, 32, 52, 255],
+"hsl(240, 62.5%, 12.5%, 1.0)", [12, 12, 52, 255],
+"hsl(270, 62.5%, 12.5%, 1.0)", [32, 12, 52, 255],
+"hsl(300, 62.5%, 12.5%, 1.0)", [52, 12, 52, 255],
+"hsl(330, 62.5%, 12.5%, 1.0)", [52, 12, 32, 255],
+"hsl(0, 75%, 12.5%, 1.0)", [56, 8, 8, 255],
+"hsl(30, 75%, 12.5%, 1.0)", [56, 32, 8, 255],
+"hsl(60, 75%, 12.5%, 1.0)", [56, 56, 8, 255],
+"hsl(90, 75%, 12.5%, 1.0)", [32, 56, 8, 255],
+"hsl(120, 75%, 12.5%, 1.0)", [8, 56, 8, 255],
+"hsl(150, 75%, 12.5%, 1.0)", [8, 56, 32, 255],
+"hsl(180, 75%, 12.5%, 1.0)", [8, 56, 56, 255],
+"hsl(210, 75%, 12.5%, 1.0)", [8, 32, 56, 255],
+"hsl(240, 75%, 12.5%, 1.0)", [8, 8, 56, 255],
+"hsl(270, 75%, 12.5%, 1.0)", [32, 8, 56, 255],
+"hsl(300, 75%, 12.5%, 1.0)", [56, 8, 56, 255],
+"hsl(330, 75%, 12.5%, 1.0)", [56, 8, 32, 255],
+"hsl(0, 87.5%, 12.5%, 1.0)", [60, 4, 4, 255],
+"hsl(30, 87.5%, 12.5%, 1.0)", [60, 32, 4, 255],
+"hsl(60, 87.5%, 12.5%, 1.0)", [60, 60, 4, 255],
+"hsl(90, 87.5%, 12.5%, 1.0)", [32, 60, 4, 255],
+"hsl(120, 87.5%, 12.5%, 1.0)", [4, 60, 4, 255],
+"hsl(150, 87.5%, 12.5%, 1.0)", [4, 60, 32, 255],
+"hsl(180, 87.5%, 12.5%, 1.0)", [4, 60, 60, 255],
+"hsl(210, 87.5%, 12.5%, 1.0)", [4, 32, 60, 255],
+"hsl(240, 87.5%, 12.5%, 1.0)", [4, 4, 60, 255],
+"hsl(270, 87.5%, 12.5%, 1.0)", [32, 4, 60, 255],
+"hsl(300, 87.5%, 12.5%, 1.0)", [60, 4, 60, 255],
+"hsl(330, 87.5%, 12.5%, 1.0)", [60, 4, 32, 255],
+"hsl(0, 100%, 12.5%, 1.0)", [64, 0, 0, 255],
+"hsl(30, 100%, 12.5%, 1.0)", [64, 32, 0, 255],
+"hsl(60, 100%, 12.5%, 1.0)", [64, 64, 0, 255],
+"hsl(90, 100%, 12.5%, 1.0)", [32, 64, 0, 255],
+"hsl(120, 100%, 12.5%, 1.0)", [0, 64, 0, 255],
+"hsl(150, 100%, 12.5%, 1.0)", [0, 64, 32, 255],
+"hsl(180, 100%, 12.5%, 1.0)", [0, 64, 64, 255],
+"hsl(210, 100%, 12.5%, 1.0)", [0, 32, 64, 255],
+"hsl(240, 100%, 12.5%, 1.0)", [0, 0, 64, 255],
+"hsl(270, 100%, 12.5%, 1.0)", [32, 0, 64, 255],
+"hsl(300, 100%, 12.5%, 1.0)", [64, 0, 64, 255],
+"hsl(330, 100%, 12.5%, 1.0)", [64, 0, 32, 255],
+"hsl(0, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(30, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(60, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(90, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(120, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(150, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(180, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(210, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(240, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(270, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(300, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(330, 0%, 25%, 1.0)", [64, 64, 64, 255],
+"hsl(0, 12.5%, 25%, 1.0)", [72, 56, 56, 255],
+"hsl(30, 12.5%, 25%, 1.0)", [72, 64, 56, 255],
+"hsl(60, 12.5%, 25%, 1.0)", [72, 72, 56, 255],
+"hsl(90, 12.5%, 25%, 1.0)", [64, 72, 56, 255],
+"hsl(120, 12.5%, 25%, 1.0)", [56, 72, 56, 255],
+"hsl(150, 12.5%, 25%, 1.0)", [56, 72, 64, 255],
+"hsl(180, 12.5%, 25%, 1.0)", [56, 72, 72, 255],
+"hsl(210, 12.5%, 25%, 1.0)", [56, 64, 72, 255],
+"hsl(240, 12.5%, 25%, 1.0)", [56, 56, 72, 255],
+"hsl(270, 12.5%, 25%, 1.0)", [64, 56, 72, 255],
+"hsl(300, 12.5%, 25%, 1.0)", [72, 56, 72, 255],
+"hsl(330, 12.5%, 25%, 1.0)", [72, 56, 64, 255],
+"hsl(0, 25%, 25%, 1.0)", [80, 48, 48, 255],
+"hsl(30, 25%, 25%, 1.0)", [80, 64, 48, 255],
+"hsl(60, 25%, 25%, 1.0)", [80, 80, 48, 255],
+"hsl(90, 25%, 25%, 1.0)", [64, 80, 48, 255],
+"hsl(120, 25%, 25%, 1.0)", [48, 80, 48, 255],
+"hsl(150, 25%, 25%, 1.0)", [48, 80, 64, 255],
+"hsl(180, 25%, 25%, 1.0)", [48, 80, 80, 255],
+"hsl(210, 25%, 25%, 1.0)", [48, 64, 80, 255],
+"hsl(240, 25%, 25%, 1.0)", [48, 48, 80, 255],
+"hsl(270, 25%, 25%, 1.0)", [64, 48, 80, 255],
+"hsl(300, 25%, 25%, 1.0)", [80, 48, 80, 255],
+"hsl(330, 25%, 25%, 1.0)", [80, 48, 64, 255],
+"hsl(0, 37.5%, 25%, 1.0)", [88, 40, 40, 255],
+"hsl(30, 37.5%, 25%, 1.0)", [88, 64, 40, 255],
+"hsl(60, 37.5%, 25%, 1.0)", [88, 88, 40, 255],
+"hsl(90, 37.5%, 25%, 1.0)", [64, 88, 40, 255],
+"hsl(120, 37.5%, 25%, 1.0)", [40, 88, 40, 255],
+"hsl(150, 37.5%, 25%, 1.0)", [40, 88, 64, 255],
+"hsl(180, 37.5%, 25%, 1.0)", [40, 88, 88, 255],
+"hsl(210, 37.5%, 25%, 1.0)", [40, 64, 88, 255],
+"hsl(240, 37.5%, 25%, 1.0)", [40, 40, 88, 255],
+"hsl(270, 37.5%, 25%, 1.0)", [64, 40, 88, 255],
+"hsl(300, 37.5%, 25%, 1.0)", [88, 40, 88, 255],
+"hsl(330, 37.5%, 25%, 1.0)", [88, 40, 64, 255],
+"hsl(0, 50%, 25%, 1.0)", [96, 32, 32, 255],
+"hsl(30, 50%, 25%, 1.0)", [96, 64, 32, 255],
+"hsl(60, 50%, 25%, 1.0)", [96, 96, 32, 255],
+"hsl(90, 50%, 25%, 1.0)", [64, 96, 32, 255],
+"hsl(120, 50%, 25%, 1.0)", [32, 96, 32, 255],
+"hsl(150, 50%, 25%, 1.0)", [32, 96, 64, 255],
+"hsl(180, 50%, 25%, 1.0)", [32, 96, 96, 255],
+"hsl(210, 50%, 25%, 1.0)", [32, 64, 96, 255],
+"hsl(240, 50%, 25%, 1.0)", [32, 32, 96, 255],
+"hsl(270, 50%, 25%, 1.0)", [64, 32, 96, 255],
+"hsl(300, 50%, 25%, 1.0)", [96, 32, 96, 255],
+"hsl(330, 50%, 25%, 1.0)", [96, 32, 64, 255],
+"hsl(0, 62.5%, 25%, 1.0)", [104, 24, 24, 255],
+"hsl(30, 62.5%, 25%, 1.0)", [104, 64, 24, 255],
+"hsl(60, 62.5%, 25%, 1.0)", [104, 104, 24, 255],
+"hsl(90, 62.5%, 25%, 1.0)", [64, 104, 24, 255],
+"hsl(120, 62.5%, 25%, 1.0)", [24, 104, 24, 255],
+"hsl(150, 62.5%, 25%, 1.0)", [24, 104, 64, 255],
+"hsl(180, 62.5%, 25%, 1.0)", [24, 104, 104, 255],
+"hsl(210, 62.5%, 25%, 1.0)", [24, 64, 104, 255],
+"hsl(240, 62.5%, 25%, 1.0)", [24, 24, 104, 255],
+"hsl(270, 62.5%, 25%, 1.0)", [64, 24, 104, 255],
+"hsl(300, 62.5%, 25%, 1.0)", [104, 24, 104, 255],
+"hsl(330, 62.5%, 25%, 1.0)", [104, 24, 64, 255],
+"hsl(0, 75%, 25%, 1.0)", [112, 16, 16, 255],
+"hsl(30, 75%, 25%, 1.0)", [112, 64, 16, 255],
+"hsl(60, 75%, 25%, 1.0)", [112, 112, 16, 255],
+"hsl(90, 75%, 25%, 1.0)", [64, 112, 16, 255],
+"hsl(120, 75%, 25%, 1.0)", [16, 112, 16, 255],
+"hsl(150, 75%, 25%, 1.0)", [16, 112, 64, 255],
+"hsl(180, 75%, 25%, 1.0)", [16, 112, 112, 255],
+"hsl(210, 75%, 25%, 1.0)", [16, 64, 112, 255],
+"hsl(240, 75%, 25%, 1.0)", [16, 16, 112, 255],
+"hsl(270, 75%, 25%, 1.0)", [64, 16, 112, 255],
+"hsl(300, 75%, 25%, 1.0)", [112, 16, 112, 255],
+"hsl(330, 75%, 25%, 1.0)", [112, 16, 64, 255],
+"hsl(0, 87.5%, 25%, 1.0)", [120, 8, 8, 255],
+"hsl(30, 87.5%, 25%, 1.0)", [120, 64, 8, 255],
+"hsl(60, 87.5%, 25%, 1.0)", [120, 120, 8, 255],
+"hsl(90, 87.5%, 25%, 1.0)", [64, 120, 8, 255],
+"hsl(120, 87.5%, 25%, 1.0)", [8, 120, 8, 255],
+"hsl(150, 87.5%, 25%, 1.0)", [8, 120, 64, 255],
+"hsl(180, 87.5%, 25%, 1.0)", [8, 120, 120, 255],
+"hsl(210, 87.5%, 25%, 1.0)", [8, 64, 120, 255],
+"hsl(240, 87.5%, 25%, 1.0)", [8, 8, 120, 255],
+"hsl(270, 87.5%, 25%, 1.0)", [64, 8, 120, 255],
+"hsl(300, 87.5%, 25%, 1.0)", [120, 8, 120, 255],
+"hsl(330, 87.5%, 25%, 1.0)", [120, 8, 64, 255],
+"hsl(0, 100%, 25%, 1.0)", [128, 0, 0, 255],
+"hsl(30, 100%, 25%, 1.0)", [128, 64, 0, 255],
+"hsl(60, 100%, 25%, 1.0)", [128, 128, 0, 255],
+"hsl(90, 100%, 25%, 1.0)", [64, 128, 0, 255],
+"hsl(120, 100%, 25%, 1.0)", [0, 128, 0, 255],
+"hsl(150, 100%, 25%, 1.0)", [0, 128, 64, 255],
+"hsl(180, 100%, 25%, 1.0)", [0, 128, 128, 255],
+"hsl(210, 100%, 25%, 1.0)", [0, 64, 128, 255],
+"hsl(240, 100%, 25%, 1.0)", [0, 0, 128, 255],
+"hsl(270, 100%, 25%, 1.0)", [64, 0, 128, 255],
+"hsl(300, 100%, 25%, 1.0)", [128, 0, 128, 255],
+"hsl(330, 100%, 25%, 1.0)", [128, 0, 64, 255],
+"hsl(0, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(30, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(60, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(90, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(120, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(150, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(180, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(210, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(240, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(270, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(300, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(330, 0%, 37.5%, 1.0)", [96, 96, 96, 255],
+"hsl(0, 12.5%, 37.5%, 1.0)", [108, 84, 84, 255],
+"hsl(30, 12.5%, 37.5%, 1.0)", [108, 96, 84, 255],
+"hsl(60, 12.5%, 37.5%, 1.0)", [108, 108, 84, 255],
+"hsl(90, 12.5%, 37.5%, 1.0)", [96, 108, 84, 255],
+"hsl(120, 12.5%, 37.5%, 1.0)", [84, 108, 84, 255],
+"hsl(150, 12.5%, 37.5%, 1.0)", [84, 108, 96, 255],
+"hsl(180, 12.5%, 37.5%, 1.0)", [84, 108, 108, 255],
+"hsl(210, 12.5%, 37.5%, 1.0)", [84, 96, 108, 255],
+"hsl(240, 12.5%, 37.5%, 1.0)", [84, 84, 108, 255],
+"hsl(270, 12.5%, 37.5%, 1.0)", [96, 84, 108, 255],
+"hsl(300, 12.5%, 37.5%, 1.0)", [108, 84, 108, 255],
+"hsl(330, 12.5%, 37.5%, 1.0)", [108, 84, 96, 255],
+"hsl(0, 25%, 37.5%, 1.0)", [120, 72, 72, 255],
+"hsl(30, 25%, 37.5%, 1.0)", [120, 96, 72, 255],
+"hsl(60, 25%, 37.5%, 1.0)", [120, 120, 72, 255],
+"hsl(90, 25%, 37.5%, 1.0)", [96, 120, 72, 255],
+"hsl(120, 25%, 37.5%, 1.0)", [72, 120, 72, 255],
+"hsl(150, 25%, 37.5%, 1.0)", [72, 120, 96, 255],
+"hsl(180, 25%, 37.5%, 1.0)", [72, 120, 120, 255],
+"hsl(210, 25%, 37.5%, 1.0)", [72, 96, 120, 255],
+"hsl(240, 25%, 37.5%, 1.0)", [72, 72, 120, 255],
+"hsl(270, 25%, 37.5%, 1.0)", [96, 72, 120, 255],
+"hsl(300, 25%, 37.5%, 1.0)", [120, 72, 120, 255],
+"hsl(330, 25%, 37.5%, 1.0)", [120, 72, 96, 255],
+"hsl(0, 37.5%, 37.5%, 1.0)", [132, 60, 60, 255],
+"hsl(30, 37.5%, 37.5%, 1.0)", [132, 96, 60, 255],
+"hsl(60, 37.5%, 37.5%, 1.0)", [132, 132, 60, 255],
+"hsl(90, 37.5%, 37.5%, 1.0)", [96, 132, 60, 255],
+"hsl(120, 37.5%, 37.5%, 1.0)", [60, 132, 60, 255],
+"hsl(150, 37.5%, 37.5%, 1.0)", [60, 132, 96, 255],
+"hsl(180, 37.5%, 37.5%, 1.0)", [60, 132, 132, 255],
+"hsl(210, 37.5%, 37.5%, 1.0)", [60, 96, 132, 255],
+"hsl(240, 37.5%, 37.5%, 1.0)", [60, 60, 132, 255],
+"hsl(270, 37.5%, 37.5%, 1.0)", [96, 60, 132, 255],
+"hsl(300, 37.5%, 37.5%, 1.0)", [132, 60, 132, 255],
+"hsl(330, 37.5%, 37.5%, 1.0)", [132, 60, 96, 255],
+"hsl(0, 50%, 37.5%, 1.0)", [144, 48, 48, 255],
+"hsl(30, 50%, 37.5%, 1.0)", [144, 96, 48, 255],
+"hsl(60, 50%, 37.5%, 1.0)", [144, 144, 48, 255],
+"hsl(90, 50%, 37.5%, 1.0)", [96, 144, 48, 255],
+"hsl(120, 50%, 37.5%, 1.0)", [48, 144, 48, 255],
+"hsl(150, 50%, 37.5%, 1.0)", [48, 144, 96, 255],
+"hsl(180, 50%, 37.5%, 1.0)", [48, 144, 144, 255],
+"hsl(210, 50%, 37.5%, 1.0)", [48, 96, 144, 255],
+"hsl(240, 50%, 37.5%, 1.0)", [48, 48, 144, 255],
+"hsl(270, 50%, 37.5%, 1.0)", [96, 48, 144, 255],
+"hsl(300, 50%, 37.5%, 1.0)", [144, 48, 144, 255],
+"hsl(330, 50%, 37.5%, 1.0)", [144, 48, 96, 255],
+"hsl(0, 62.5%, 37.5%, 1.0)", [156, 36, 36, 255],
+"hsl(30, 62.5%, 37.5%, 1.0)", [156, 96, 36, 255],
+"hsl(60, 62.5%, 37.5%, 1.0)", [156, 156, 36, 255],
+"hsl(90, 62.5%, 37.5%, 1.0)", [96, 156, 36, 255],
+"hsl(120, 62.5%, 37.5%, 1.0)", [36, 156, 36, 255],
+"hsl(150, 62.5%, 37.5%, 1.0)", [36, 156, 96, 255],
+"hsl(180, 62.5%, 37.5%, 1.0)", [36, 156, 156, 255],
+"hsl(210, 62.5%, 37.5%, 1.0)", [36, 96, 156, 255],
+"hsl(240, 62.5%, 37.5%, 1.0)", [36, 36, 156, 255],
+"hsl(270, 62.5%, 37.5%, 1.0)", [96, 36, 156, 255],
+"hsl(300, 62.5%, 37.5%, 1.0)", [156, 36, 156, 255],
+"hsl(330, 62.5%, 37.5%, 1.0)", [156, 36, 96, 255],
+"hsl(0, 75%, 37.5%, 1.0)", [168, 24, 24, 255],
+"hsl(30, 75%, 37.5%, 1.0)", [168, 96, 24, 255],
+"hsl(60, 75%, 37.5%, 1.0)", [168, 168, 24, 255],
+"hsl(90, 75%, 37.5%, 1.0)", [96, 168, 24, 255],
+"hsl(120, 75%, 37.5%, 1.0)", [24, 168, 24, 255],
+"hsl(150, 75%, 37.5%, 1.0)", [24, 168, 96, 255],
+"hsl(180, 75%, 37.5%, 1.0)", [24, 168, 168, 255],
+"hsl(210, 75%, 37.5%, 1.0)", [24, 96, 168, 255],
+"hsl(240, 75%, 37.5%, 1.0)", [24, 24, 168, 255],
+"hsl(270, 75%, 37.5%, 1.0)", [96, 24, 168, 255],
+"hsl(300, 75%, 37.5%, 1.0)", [168, 24, 168, 255],
+"hsl(330, 75%, 37.5%, 1.0)", [168, 24, 96, 255],
+"hsl(0, 87.5%, 37.5%, 1.0)", [180, 12, 12, 255],
+"hsl(30, 87.5%, 37.5%, 1.0)", [180, 96, 12, 255],
+"hsl(60, 87.5%, 37.5%, 1.0)", [180, 180, 12, 255],
+"hsl(90, 87.5%, 37.5%, 1.0)", [96, 180, 12, 255],
+"hsl(120, 87.5%, 37.5%, 1.0)", [12, 180, 12, 255],
+"hsl(150, 87.5%, 37.5%, 1.0)", [12, 180, 96, 255],
+"hsl(180, 87.5%, 37.5%, 1.0)", [12, 180, 180, 255],
+"hsl(210, 87.5%, 37.5%, 1.0)", [12, 96, 180, 255],
+"hsl(240, 87.5%, 37.5%, 1.0)", [12, 12, 180, 255],
+"hsl(270, 87.5%, 37.5%, 1.0)", [96, 12, 180, 255],
+"hsl(300, 87.5%, 37.5%, 1.0)", [180, 12, 180, 255],
+"hsl(330, 87.5%, 37.5%, 1.0)", [180, 12, 96, 255],
+"hsl(0, 100%, 37.5%, 1.0)", [192, 0, 0, 255],
+"hsl(30, 100%, 37.5%, 1.0)", [192, 96, 0, 255],
+"hsl(60, 100%, 37.5%, 1.0)", [192, 192, 0, 255],
+"hsl(90, 100%, 37.5%, 1.0)", [96, 192, 0, 255],
+"hsl(120, 100%, 37.5%, 1.0)", [0, 192, 0, 255],
+"hsl(150, 100%, 37.5%, 1.0)", [0, 192, 96, 255],
+"hsl(180, 100%, 37.5%, 1.0)", [0, 192, 192, 255],
+"hsl(210, 100%, 37.5%, 1.0)", [0, 96, 192, 255],
+"hsl(240, 100%, 37.5%, 1.0)", [0, 0, 192, 255],
+"hsl(270, 100%, 37.5%, 1.0)", [96, 0, 192, 255],
+"hsl(300, 100%, 37.5%, 1.0)", [192, 0, 192, 255],
+"hsl(330, 100%, 37.5%, 1.0)", [192, 0, 96, 255],
+"hsl(0, 0%, 50%, 1.0)", [128, 128, 128, 255],
+"hsl(30, 0%, 50%, 1.0)", [128, 128, 128, 255],
+"hsl(60, 0%, 50%, 1.0)", [128, 128, 128, 255],
+"hsl(90, 0%, 50%, 1.0)", [128, 128, 128, 255],
+"hsl(120, 0%, 50%, 1.0)", [128, 128, 128, 255],
+"hsl(150, 0%, 50%, 1.0)", [128, 128, 128, 255],
+"hsl(180, 0%, 50%, 1.0)", [128, 128, 128, 255],
+"hsl(210, 0%, 50%, 1.0)", [128, 128, 128, 255],
+"hsl(240, 0%, 50%, 1.0)", [128, 128, 128, 255],
+"hsl(270, 0%, 50%, 1.0)", [128, 128, 128, 255],
+"hsl(300, 0%, 50%, 1.0)", [128, 128, 128, 255],
+"hsl(330, 0%, 50%, 1.0)", [128, 128, 128, 255],