Merge autoland to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 02 Jun 2017 17:21:39 -0700
changeset 362090 0d23bde929e1e99c25ab40eaf67155d016f4441d
parent 362048 c67d811399a1408dd4c77a92584ecf480e3c972b (current diff)
parent 362089 0ff4ad45f99c531c6760e9540864c4b6597852d0 (diff)
child 362091 43039280fe464869428f03b047bb7c762784f44b
push id31958
push userkwierso@gmail.com
push dateSat, 03 Jun 2017 00:23:33 +0000
treeherdermozilla-central@43039280fe46 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to m-c a=merge MozReview-Commit-ID: Fjt5XIDd0p6
dom/vr/test/VRSimulationDriver.js
dom/vr/test/WebVRHelpers.js
dom/vr/test/mochitest.ini
dom/vr/test/requestPresent.js
dom/vr/test/runVRTest.js
dom/vr/test/test_vrDisplay_canvas2d.html
dom/vr/test/test_vrDisplay_exitPresent.html
dom/vr/test/test_vrDisplay_getFrameData.html
dom/vr/test/test_vrDisplay_onvrdisplaydeactivate_crosscontent.html
dom/vr/test/test_vrDisplay_requestPresent.html
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -108,17 +108,18 @@ panelview {
   transition: transform var(--panelui-subview-transition-duration);
 }
 
 panelview:not([mainview]):not([current]) {
   transition: visibility 0s linear var(--panelui-subview-transition-duration);
   visibility: collapse;
 }
 
-panelview[mainview] > .panel-header {
+panelview[mainview] > .panel-header,
+panelview:not([title]) > .panel-header {
   display: none;
 }
 
 tabbrowser {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser");
 }
 
 .tabbrowser-tabs {
--- a/browser/components/preferences/in-content-new/findInPage.js
+++ b/browser/components/preferences/in-content-new/findInPage.js
@@ -187,24 +187,17 @@ var gSearchResultsPane = {
    * Shows or hides content according to search input
    *
    * @param String event
    *    to search for filted query in
    */
   searchFunction(event) {
     let query = event.target.value.trim().toLowerCase();
     this.findSelection.removeAllRanges();
-
-    // Remove all search tooltips that were created
-    let searchTooltips = Array.from(document.querySelectorAll(".search-tooltip"));
-    for (let searchTooltip of searchTooltips) {
-      searchTooltip.parentElement.classList.remove("search-tooltip-parent");
-      searchTooltip.remove();
-    }
-    this.listSearchTooltips = [];
+    this.removeAllSearchTooltips();
 
     let srHeader = document.getElementById("header-searchResults");
 
     if (query) {
       // Showing the Search Results Tag
       gotoPref("paneSearchResults");
 
       this.searchResultsCategory.hidden = false;
@@ -365,10 +358,22 @@ var gSearchResultsPane = {
     let tooltipRect = searchTooltip.getBoundingClientRect();
     let parentRect = currentNode.parentElement.getBoundingClientRect();
 
     let offSet = (anchorRect.width / 2) - (tooltipRect.width / 2);
     let relativeOffset = anchorRect.left - parentRect.left;
     offSet += relativeOffset > 0 ? relativeOffset : 0;
 
     searchTooltip.style.setProperty("left", `${offSet}px`);
+  },
+
+  /**
+   * Remove all search tooltips that were created.
+   */
+  removeAllSearchTooltips() {
+    let searchTooltips = Array.from(document.querySelectorAll(".search-tooltip"));
+    for (let searchTooltip of searchTooltips) {
+      searchTooltip.parentElement.classList.remove("search-tooltip-parent");
+      searchTooltip.remove();
+    }
+    this.listSearchTooltips = [];
   }
 }
--- a/browser/components/preferences/in-content-new/preferences.js
+++ b/browser/components/preferences/in-content-new/preferences.js
@@ -161,16 +161,17 @@ function gotoPref(aCategory) {
   if (subcategory) {
     category = category.substring(0, breakIndex);
   }
   category = friendlyPrefCategoryNameToInternalName(category);
   if (category != "paneSearchResults") {
     gSearchResultsPane.searchInput.value = "";
     gSearchResultsPane.searchResultsCategory.hidden = true;
     gSearchResultsPane.findSelection.removeAllRanges();
+    gSearchResultsPane.removeAllSearchTooltips();
   }
 
   // Updating the hash (below) or changing the selected category
   // will re-enter gotoPref.
   if (gLastHash == category && !subcategory)
     return;
   let item = categories.querySelector(".category[value=" + category + "]");
   if (!item) {
--- a/browser/themes/shared/autocomplete.inc.css
+++ b/browser/themes/shared/autocomplete.inc.css
@@ -26,20 +26,22 @@
   padding: 0;
 }
 
 
 /* Login form autocompletion */
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="login"] > .ac-site-icon {
   display: initial;
   list-style-image: url(chrome://browser/skin/notification-icons.svg#login);
+  -moz-context-properties: fill;
+  fill: GrayText;
 }
 
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="login"] > .ac-site-icon[selected] {
-  list-style-image: url(chrome://browser/skin/notification-icons.svg#login-highlighted);
+  fill: HighlightText;
 }
 
 
 /* Insecure field warning */
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] {
   background-color: var(--arrowpanel-dimmed);
   border-bottom: 1px solid var(--panel-separator-color);
   padding-bottom: 4px;
--- a/browser/themes/shared/compacttheme.inc.css
+++ b/browser/themes/shared/compacttheme.inc.css
@@ -240,16 +240,18 @@ toolbar:-moz-lwtheme-darktext {
   color: inherit !important;
   border: 1px solid var(--chrome-nav-bar-controls-border-color) !important;
   box-shadow: none !important;
 }
 
 #identity-icon:-moz-lwtheme-brighttext,
 #tracking-protection-icon:-moz-lwtheme-brighttext,
 #connection-icon:-moz-lwtheme-brighttext,
+.notification-anchor-icon:-moz-lwtheme-brighttext,
+#blocked-permissions-container > .blocked-permission-icon:-moz-lwtheme-brighttext,
 #extension-icon:-moz-lwtheme-brighttext {
   fill: rgba(255,255,255,.7);
 }
 
 %ifndef MOZ_PHOTON_THEME
 #urlbar {
   border-inline-start: none !important;
   opacity: 1 !important;
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -378,16 +378,18 @@ description#identity-popup-content-verif
 }
 
 .identity-popup-permission-icon {
   width: 16px;
   height: 16px;
 }
 
 .identity-popup-permission-icon.in-use {
+  -moz-context-properties: fill;
+  fill: rgb(224, 41, 29);
   animation: 1.5s ease in-use-blink infinite;
 }
 
 @keyframes in-use-blink {
   50% { opacity: 0; }
 }
 
 .identity-popup-permission-label,
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -54,33 +54,39 @@
   /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
   padding-inline-start: calc(var(--backbutton-urlbar-overlap) + 5.01px);
 }
 %endif
 
 #identity-icon,
 #tracking-protection-icon,
 #connection-icon,
+.notification-anchor-icon,
+#blocked-permissions-container > .blocked-permission-icon,
 #extension-icon {
   width: 16px;
   height: 16px;
+  margin-inline-start: 2px;
   -moz-context-properties: fill;
   fill: GrayText;
 }
 
 #identity-icon:-moz-lwtheme,
 #tracking-protection-icon:-moz-lwtheme,
 #connection-icon:-moz-lwtheme,
+.notification-anchor-icon:-moz-lwtheme,
+#blocked-permissions-container > .blocked-permission-icon:-moz-lwtheme,
 #extension-icon:-moz-lwtheme {
   fill: rgba(0,0,0,.6);
 }
 
 /* MAIN IDENTITY ICON */
 
 #identity-icon {
+  margin-inline-start: 0;
   list-style-image: url(chrome://browser/skin/identity-icon.svg);
 }
 
 #identity-box:hover > #identity-icon:not(.no-hover),
 #identity-box[open=true] > #identity-icon {
   list-style-image: url(chrome://browser/skin/identity-icon-hover.svg);
 }
 
@@ -111,16 +117,18 @@
 
 /* SHARING ICON */
 
 #sharing-icon {
   width: 16px;
   height: 16px;
   margin-inline-start: -16px;
   position: relative;
+  -moz-context-properties: fill;
+  fill: rgb(224, 41, 29);
   display: none;
 }
 
 #identity-box[sharing="camera"] > #sharing-icon {
   list-style-image: url("chrome://browser/skin/notification-icons.svg#camera-sharing");
 }
 
 #identity-box[sharing="microphone"] > #sharing-icon {
@@ -150,17 +158,16 @@
     opacity: 1;
   }
 }
 
 /* TRACKING PROTECTION ICON */
 
 #tracking-protection-icon {
   list-style-image: url(chrome://browser/skin/tracking-protection-16.svg#enabled);
-  margin-inline-start: 2px;
   margin-inline-end: 0;
 }
 
 #tracking-protection-icon[state="loaded-tracking-content"] {
   list-style-image: url(chrome://browser/skin/tracking-protection-16.svg#disabled);
 }
 
 #tracking-protection-icon[animate] {
@@ -179,17 +186,16 @@
 #urlbar[pageproxystate="invalid"] > #identity-box > #tracking-protection-icon {
   visibility: collapse;
 }
 
 /* CONNECTION ICON, EXTENSION ICON */
 
 #connection-icon,
 #extension-icon {
-  margin-inline-start: 2px;
   visibility: collapse;
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedDomain > #connection-icon,
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity > #connection-icon,
 #urlbar[pageproxystate="valid"] > #identity-box.mixedActiveBlocked > #connection-icon {
   list-style-image: url(chrome://browser/skin/connection-secure.svg);
   visibility: visible;
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -61,17 +61,17 @@
   skin/classic/browser/connection-mixed-active-loaded.svg      (../shared/identity-block/connection-mixed-active-loaded.svg)
   skin/classic/browser/identity-icon.svg                       (../shared/identity-block/identity-icon.svg)
   skin/classic/browser/identity-icon-hover.svg                 (../shared/identity-block/identity-icon-hover.svg)
   skin/classic/browser/identity-icon-notice.svg                (../shared/identity-block/identity-icon-notice.svg)
   skin/classic/browser/identity-icon-notice-hover.svg          (../shared/identity-block/identity-icon-notice-hover.svg)
   skin/classic/browser/info.svg                                (../shared/info.svg)
 * skin/classic/browser/menuPanel.svg                           (../shared/menuPanel.svg)
 * skin/classic/browser/menuPanel-small.svg                     (../shared/menuPanel-small.svg)
-* skin/classic/browser/notification-icons.svg                  (../shared/notification-icons.svg)
+  skin/classic/browser/notification-icons.svg                  (../shared/notification-icons.svg)
   skin/classic/browser/tracking-protection-16.svg              (../shared/identity-block/tracking-protection-16.svg)
   skin/classic/browser/newtab/close.png                        (../shared/newtab/close.png)
   skin/classic/browser/newtab/controls.svg                     (../shared/newtab/controls.svg)
   skin/classic/browser/panel-icon-arrow-left.svg               (../shared/panel-icon-arrow-left.svg)
   skin/classic/browser/panel-icon-arrow-right.svg              (../shared/panel-icon-arrow-right.svg)
   skin/classic/browser/panel-icon-cancel.svg                   (../shared/panel-icon-cancel.svg)
 #ifndef XP_MACOSX
   skin/classic/browser/panel-icon-folder.svg                   (../shared/panel-icon-folder.svg)
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -1,41 +1,34 @@
 %if 0
 /* 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/. */
 %endif
 
+.popup-notification-icon,
+.identity-popup-permission-icon {
+  -moz-context-properties: fill;
+  fill: GrayText;
+}
+
 #notification-popup-box {
   padding: 5px 0px;
   margin: -5px 0px;
   margin-inline-end: -5px;
   padding-inline-end: 5px;
 }
 
-.notification-anchor-icon,
-#blocked-permissions-container > .blocked-permission-icon {
-  width: 16px;
-  height: 16px;
-  margin-inline-start: 2px;
-}
-
 /* This class can be used alone or in combination with the class defining the
    type of icon displayed. This rule must be defined before the others in order
    for its list-style-image to be overridden. */
 .notification-anchor-icon {
   list-style-image: url(chrome://browser/skin/notification-icons.svg#default-info);
 }
 
-.notification-anchor-icon:not(.plugin-blocked):-moz-lwtheme,
-#blocked-permissions-container > .blocked-permission-icon:-moz-lwtheme {
-  filter: url(chrome://global/skin/filters.svg#fill);
-  fill: currentColor;
-}
-
 /* INDIVIDUAL NOTIFICATIONS */
 
 .focus-tab-by-prompt-icon {
   list-style-image: url(chrome://browser/skin/notification-icons.svg#focus-tab-by-prompt);
 }
 
 .popup-notification-icon[popupid="persistent-storage"],
 .persistent-storage-icon {
@@ -247,16 +240,17 @@ html|*#webRTC-previewVideo {
 /* PLUGINS */
 
 .plugin-icon {
   list-style-image: url(chrome://browser/skin/notification-icons.svg#plugin);
 }
 
 .plugin-icon.plugin-blocked {
   list-style-image: url(chrome://browser/skin/notification-icons.svg#plugin-blocked);
+  fill: #d92215 !important;
 }
 
 #notification-popup-box[hidden] {
   /* Override display:none to make the pluginBlockedNotification animation work
      when showing the notification repeatedly. */
   display: -moz-box;
   visibility: collapse;
 }
--- a/browser/themes/shared/notification-icons.svg
+++ b/browser/themes/shared/notification-icons.svg
@@ -1,55 +1,26 @@
-<?xml version="1.0"?>
 <!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg"
-     xmlns:xlink="http://www.w3.org/1999/xlink"
-     class="fieldtext"
+<svg fill="context-fill" xmlns="http://www.w3.org/2000/svg"
      width="32" height="32" viewBox="0 0 32 32">
-#include icon-colors.inc.svg
   <style>
     :root > use:not(:target),
     :root > g:not(:target),
     #strikeout {
       display: none;
     }
     .blocked:target ~ #strikeout {
       display: block;
     }
     .blocked {
       clip-path: url(#blocked-clipPath);
     }
 
-    #login-highlighted {
-      fill: HighlightText;
-      fill-opacity: 1;
-    }
-
-    #plugin-blocked,
-    #plugin-blocked:target ~ #strikeout {
-      fill: #d92215;
-      fill-opacity: 1;
-    }
-
-    #camera-sharing,
-    #microphone-sharing,
-    #screen-sharing {
-      fill: rgb(224, 41, 29);
-      fill-opacity: 1;
-    }
-
-    #camera-indicator,
-    #microphone-indicator,
-    #screen-indicator {
-      fill: white;
-      fill-opacity: 1;
-    }
-
     #update-icon {
       stroke: #fff;
       stroke-width: 3px;
       stroke-linecap: round;
     }
   </style>
 
   <defs>
@@ -82,46 +53,45 @@
       <rect fill="black" x="14" y="14" width="4" height="10" rx="2" ry="2"/>
     </mask>
   </defs>
 
   <g id="default-info">
     <circle cx="16" cy="16" r="14" mask="url(#i-mask)"/>
   </g>
 
-  <use id="camera" xlink:href="#camera-icon" />
-  <use id="camera-sharing" xlink:href="#camera-icon"/>
-  <use id="camera-indicator" xlink:href="#camera-icon" />
-  <use id="camera-blocked" class="blocked" xlink:href="#camera-icon" />
-  <use id="desktop-notification" xlink:href="#desktop-notification-icon" />
-  <use id="desktop-notification-blocked" class="blocked" xlink:href="#desktop-notification-icon" />
-  <use id="focus-tab-by-prompt" xlink:href="#focus-tab-by-prompt-icon" />
-  <use id="geo-osx" xlink:href="#geo-osx-icon" />
-  <use id="geo-osx-blocked" class="blocked" xlink:href="#geo-osx-icon" />
-  <use id="geo-linux" xlink:href="#geo-linux-icon" />
-  <use id="geo-linux-blocked" class="blocked" xlink:href="#geo-linux-icon" />
-  <use id="geo-linux-detailed" xlink:href="#geo-linux-detailed-icon" />
-  <use id="geo-windows" xlink:href="#geo-windows-icon" />
-  <use id="geo-windows-blocked" class="blocked" xlink:href="#geo-windows-icon" />
-  <use id="geo-windows-detailed" xlink:href="#geo-windows-detailed-icon" />
-  <use id="indexedDB" xlink:href="#indexedDB-icon" />
-  <use id="indexedDB-blocked" class="blocked" xlink:href="#indexedDB-icon" />
-  <use id="login" xlink:href="#login-icon" />
-  <use id="login-highlighted" class="highlighted" xlink:href="#login-icon" />
-  <use id="login-detailed" xlink:href="#login-detailed-icon" />
-  <use id="microphone" xlink:href="#microphone-icon" />
-  <use id="microphone-sharing" xlink:href="#microphone-icon"/>
-  <use id="microphone-indicator" xlink:href="#microphone-icon"/>
-  <use id="microphone-blocked" class="blocked" xlink:href="#microphone-icon" />
-  <use id="microphone-detailed" xlink:href="#microphone-detailed-icon" />
-  <use id="persistent-storage" xlink:href="#persistent-storage-icon" />
-  <use id="persistent-storage-blocked" class="blocked" xlink:href="#persistent-storage-icon" />
-  <use id="plugin" xlink:href="#plugin-icon" />
-  <use id="plugin-blocked" class="blocked" xlink:href="#plugin-icon" />
-  <use id="popup" xlink:href="#popup-icon" />
-  <use id="screen" xlink:href="#screen-icon" />
-  <use id="screen-sharing" xlink:href="#screen-icon"/>
-  <use id="screen-indicator" xlink:href="#screen-icon"/>
-  <use id="screen-blocked" class="blocked" xlink:href="#screen-icon" />
-  <use id="update" xlink:href="#update-icon" />
+  <use id="camera" href="#camera-icon" />
+  <use id="camera-sharing" href="#camera-icon"/>
+  <use id="camera-indicator" href="#camera-icon" />
+  <use id="camera-blocked" class="blocked" href="#camera-icon" />
+  <use id="desktop-notification" href="#desktop-notification-icon" />
+  <use id="desktop-notification-blocked" class="blocked" href="#desktop-notification-icon" />
+  <use id="focus-tab-by-prompt" href="#focus-tab-by-prompt-icon" />
+  <use id="geo-osx" href="#geo-osx-icon" />
+  <use id="geo-osx-blocked" class="blocked" href="#geo-osx-icon" />
+  <use id="geo-linux" href="#geo-linux-icon" />
+  <use id="geo-linux-blocked" class="blocked" href="#geo-linux-icon" />
+  <use id="geo-linux-detailed" href="#geo-linux-detailed-icon" />
+  <use id="geo-windows" href="#geo-windows-icon" />
+  <use id="geo-windows-blocked" class="blocked" href="#geo-windows-icon" />
+  <use id="geo-windows-detailed" href="#geo-windows-detailed-icon" />
+  <use id="indexedDB" href="#indexedDB-icon" />
+  <use id="indexedDB-blocked" class="blocked" href="#indexedDB-icon" />
+  <use id="login" href="#login-icon" />
+  <use id="login-detailed" href="#login-detailed-icon" />
+  <use id="microphone" href="#microphone-icon" />
+  <use id="microphone-sharing" href="#microphone-icon"/>
+  <use id="microphone-indicator" href="#microphone-icon"/>
+  <use id="microphone-blocked" class="blocked" href="#microphone-icon" />
+  <use id="microphone-detailed" href="#microphone-detailed-icon" />
+  <use id="persistent-storage" href="#persistent-storage-icon" />
+  <use id="persistent-storage-blocked" class="blocked" href="#persistent-storage-icon" />
+  <use id="plugin" href="#plugin-icon" />
+  <use id="plugin-blocked" class="blocked" href="#plugin-icon" />
+  <use id="popup" href="#popup-icon" />
+  <use id="screen" href="#screen-icon" />
+  <use id="screen-sharing" href="#screen-icon"/>
+  <use id="screen-indicator" href="#screen-icon"/>
+  <use id="screen-blocked" class="blocked" href="#screen-icon" />
+  <use id="update" href="#update-icon" />
 
   <path id="strikeout" d="m 2,28 2,2 26,-26 -2,-2 z"/>
 </svg>
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -107,16 +107,18 @@
 .tab-icon-image[sharing]:not([selected]) {
   animation-delay: -1.5s;
 }
 
 .tab-sharing-icon-overlay {
   /* 16px of the icon + 6px of margin-inline-end of .tab-icon-image */
   margin-inline-start: -22px;
   position: relative;
+  -moz-context-properties: fill;
+  fill: rgb(224, 41, 29);
 }
 
 .tab-sharing-icon-overlay[sharing="camera"] {
   list-style-image: url("chrome://browser/skin/notification-icons.svg#camera-sharing");
 }
 
 .tab-sharing-icon-overlay[sharing="microphone"] {
   list-style-image: url("chrome://browser/skin/notification-icons.svg#microphone-sharing");
--- a/browser/themes/shared/webRTC-indicator.css
+++ b/browser/themes/shared/webRTC-indicator.css
@@ -10,16 +10,22 @@ window {
 #screenShareButton,
 #firefoxButton {
   height: 29px;
   margin: 0;
   -moz-appearance: none;
   border-style: none;
 }
 
+#audioVideoButton,
+#screenShareButton {
+  -moz-context-properties: fill;
+  fill: white;
+}
+
 #firefoxButton {
   background-image: url("chrome://branding/content/icon48.png");
   background-repeat: no-repeat;
   background-size: 22px;
   background-position: center center;
   min-width: 29px;
   background-color: white;
 }
--- a/build/subconfigure.py
+++ b/build/subconfigure.py
@@ -12,35 +12,16 @@ import os
 import re
 import subprocess
 import sys
 import pickle
 
 import mozpack.path as mozpath
 
 
-class Pool(object):
-    def __new__(cls, size):
-        try:
-            import multiprocessing
-            size = min(size, multiprocessing.cpu_count())
-            return multiprocessing.Pool(size)
-        except:
-            return super(Pool, cls).__new__(cls)
-
-    def imap_unordered(self, fn, iterable):
-        return itertools.imap(fn, iterable)
-
-    def close(self):
-        pass
-
-    def join(self):
-        pass
-
-
 class File(object):
     def __init__(self, path):
         self._path = path
         self._content = open(path, 'rb').read()
         stat = os.stat(path)
         self._times = (stat.st_atime, stat.st_mtime)
 
     @property
@@ -253,19 +234,32 @@ def prepare(srcdir, objdir, shell, args)
     with open(data_file, 'wb') as f:
         pickle.dump(data, f)
 
 
 def prefix_lines(text, prefix):
     return ''.join('%s> %s' % (prefix, line) for line in text.splitlines(True))
 
 
+def execute_and_prefix(*args, **kwargs):
+    prefix = kwargs['prefix']
+    del kwargs['prefix']
+    proc = subprocess.Popen(*args, stdout=subprocess.PIPE,
+                            stderr=subprocess.STDOUT, **kwargs)
+    while True:
+        line = proc.stdout.readline()
+        if not line:
+            break
+        print prefix_lines(line.rstrip(), prefix)
+        sys.stdout.flush()
+    return proc.wait()
+
+
 def run(objdir):
     ret = 0
-    output = ''
 
     with open(os.path.join(objdir, CONFIGURE_DATA), 'rb') as f:
         data = pickle.load(f)
 
     data['objdir'] = objdir
 
     cache_file = data['cache-file']
     cleared_cache = True
@@ -331,21 +325,20 @@ def run(objdir):
 
         # Pass --no-create to configure so that it doesn't run config.status.
         # We're going to run it ourselves.
         command += ['--no-create']
 
         print prefix_lines('configuring', relobjdir)
         print prefix_lines('running %s' % ' '.join(command[:-1]), relobjdir)
         sys.stdout.flush()
-        try:
-            output += subprocess.check_output(command,
-                stderr=subprocess.STDOUT, cwd=objdir, env=data['env'])
-        except subprocess.CalledProcessError as e:
-            return relobjdir, e.returncode, e.output
+        returncode = execute_and_prefix(command, cwd=objdir, env=data['env'],
+                                        prefix=relobjdir)
+        if returncode:
+            return returncode
 
         # Leave config.status with a new timestamp if configure is newer than
         # its original mtime.
         if config_status and os.path.getmtime(configure) <= config_status.mtime:
             config_status.update_time()
 
     # Only run config.status if one of the following is true:
     # - config.status changed or did not exist
@@ -365,28 +358,23 @@ def run(objdir):
             if not os.path.exists(t) or \
                     os.path.getmtime(f) < os.path.getmtime(t):
                 skip_config_status = False
 
     if not skip_config_status:
         if skip_configure:
             print prefix_lines('running config.status', relobjdir)
             sys.stdout.flush()
-        try:
-            output += subprocess.check_output([data['shell'], '-c',
-                './config.status'], stderr=subprocess.STDOUT, cwd=objdir,
-                env=data['env'])
-        except subprocess.CalledProcessError as e:
-            ret = e.returncode
-            output += e.output
+        ret = execute_and_prefix([data['shell'], '-c', './config.status'],
+                                 cwd=objdir, env=data['env'], prefix=relobjdir)
 
         for f in contents:
             f.update_time()
 
-    return relobjdir, ret, output
+    return ret
 
 
 def subconfigure(args):
     parser = argparse.ArgumentParser()
     parser.add_argument('--list', type=str,
         help='File containing a list of subconfigures to run')
     parser.add_argument('--skip', type=str,
         help='File containing a list of Subconfigures to skip')
@@ -399,29 +387,21 @@ def subconfigure(args):
     if args.skip:
         skips = set(open(args.skip, 'rb').read().splitlines())
         subconfigures = [s for s in subconfigures if s not in skips]
 
     if not subconfigures:
         return 0
 
     ret = 0
-    # One would think using a ThreadPool would be faster, considering
-    # everything happens in subprocesses anyways, but no, it's actually
-    # slower on Windows. (20s difference overall!)
-    pool = Pool(len(subconfigures))
-    for relobjdir, returncode, output in \
-            pool.imap_unordered(run, subconfigures):
-        print prefix_lines(output, relobjdir)
-        sys.stdout.flush()
+    for subconfigure in subconfigures:
+        returncode = run(subconfigure)
         ret = max(returncode, ret)
         if ret:
             break
-    pool.close()
-    pool.join()
     return ret
 
 
 def main(args):
     if args[0] != '--prepare':
         return subconfigure(args)
 
     topsrcdir = os.path.abspath(args[1])
--- a/devtools/client/framework/source-map-url-service.js
+++ b/devtools/client/framework/source-map-url-service.js
@@ -6,29 +6,39 @@
 /**
  * A simple service to track source actors and keep a mapping between
  * original URLs and objects holding the source actor's ID (which is
  * used as a cookie by the devtools-source-map service) and the source
  * map URL.
  *
  * @param {object} target
  *        The object the toolbox is debugging.
+ * @param {object} threadClient
+ *        The toolbox's thread client
  * @param {SourceMapService} sourceMapService
  *        The devtools-source-map functions
  */
-function SourceMapURLService(target, sourceMapService) {
+function SourceMapURLService(target, threadClient, sourceMapService) {
   this._target = target;
   this._sourceMapService = sourceMapService;
   this._urls = new Map();
 
   this._onSourceUpdated = this._onSourceUpdated.bind(this);
   this.reset = this.reset.bind(this);
 
   target.on("source-updated", this._onSourceUpdated);
   target.on("will-navigate", this.reset);
+
+  // Start fetching the sources now.
+  this._loadingPromise = new Promise(resolve => {
+    threadClient.getSources(({sources}) => {
+      // Just ignore errors.
+      resolve(sources);
+    });
+  });
 }
 
 /**
  * Reset the service.  This flushes the internal cache.
  */
 SourceMapURLService.prototype.reset = function () {
   this._sourceMapService.clearSourceMaps();
   this._urls.clear();
@@ -70,16 +80,24 @@ SourceMapURLService.prototype._onSourceU
  * @param {number} line
  *        The line number to map.
  * @param {number} column
  *        The column number to map.
  * @return Promise
  *        A promise resolving either to the original location, or null.
  */
 SourceMapURLService.prototype.originalPositionFor = async function (url, line, column) {
+  // Ensure the sources are loaded before replying.
+  await this._loadingPromise;
+
+  // Maybe we were shut down while waiting.
+  if (!this._urls) {
+    return null;
+  }
+
   const urlInfo = this._urls.get(url);
   if (!urlInfo) {
     return null;
   }
   // Call getOriginalURLs to make sure the source map has been
   // fetched.  We don't actually need the result of this though.
   await this._sourceMapService.getOriginalURLs(urlInfo);
   const location = { sourceId: urlInfo.id, line, column, sourceUrl: url };
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -8,23 +8,26 @@ support-files =
   browser_toolbox_sidebar_tool.xul
   browser_toolbox_window_title_changes_page.html
   browser_toolbox_window_title_frame_select_page.html
   code_binary_search.coffee
   code_binary_search.js
   code_binary_search.map
   code_binary_search_absolute.js
   code_binary_search_absolute.map
+  code_bundle_no_race.js
+  code_bundle_no_race.js.map
   code_bundle_reload_1.js
   code_bundle_reload_1.js.map
   code_bundle_reload_2.js
   code_bundle_reload_2.js.map
   code_inline_bundle.js
   code_inline_original.js
   code_math.js
+  code_no_race.js
   code_reload_1.js
   code_reload_2.js
   doc_empty-tab-01.html
   doc_reload.html
   head.js
   shared-head.js
   shared-redux-head.js
   helper_disable_cache.js
@@ -47,16 +50,17 @@ support-files =
 [browser_keybindings_01.js]
 [browser_keybindings_02.js]
 [browser_keybindings_03.js]
 [browser_menu_api.js]
 [browser_new_activation_workflow.js]
 [browser_source_map-01.js]
 [browser_source_map-absolute.js]
 [browser_source_map-inline.js]
+[browser_source_map-no-race.js]
 [browser_source_map-reload.js]
 [browser_target_from_url.js]
 [browser_target_events.js]
 [browser_target_remote.js]
 [browser_target_support.js]
 [browser_toolbox_custom_host.js]
 [browser_toolbox_dynamic_registration.js]
 [browser_toolbox_getpanelwhenready.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/browser_source_map-no-race.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the source map service doesn't race against source
+// reporting.
+
+"use strict";
+
+const JS_URL = URL_ROOT + "code_bundle_no_race.js";
+
+const PAGE_URL = `data:text/html,
+<!doctype html>
+
+<html>
+  <script src="${JS_URL}"></script>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Empty test page to test race case</title>
+  </head>
+
+  <body>
+  </body>
+
+</html>`;
+
+const ORIGINAL_URL = "webpack:///code_no_race.js";
+
+const GENERATED_LINE = 84;
+const ORIGINAL_LINE = 11;
+
+add_task(function* () {
+  // Start with the empty page, then navigate, so that we can properly
+  // listen for new sources arriving.
+  const toolbox = yield openNewTabAndToolbox(PAGE_URL, "webconsole");
+  const service = toolbox.sourceMapURLService;
+
+  info(`checking original location for ${JS_URL}:${GENERATED_LINE}`);
+  let newLoc = yield service.originalPositionFor(JS_URL, GENERATED_LINE);
+  is(newLoc.sourceUrl, ORIGINAL_URL, "check mapped URL");
+  is(newLoc.line, ORIGINAL_LINE, "check mapped line number");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/code_bundle_no_race.js
@@ -0,0 +1,92 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// identity function for calling harmony imports with the correct context
+/******/ 	__webpack_require__.i = function(value) { return value; };
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, {
+/******/ 				configurable: false,
+/******/ 				enumerable: true,
+/******/ 				get: getter
+/******/ 			});
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Original source code for the inline source map test.
+// The generated file was made with
+//    webpack --devtool source-map code_no_race.js code_bundle_no_race.js
+
+
+
+function f() {
+  console.log("anything will do");
+}
+
+f();
+
+
+/***/ })
+/******/ ]);
+//# sourceMappingURL=code_bundle_no_race.js.map
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/code_bundle_no_race.js.map
@@ -0,0 +1,1 @@
+{"version":3,"sources":["webpack:///webpack/bootstrap 40c4319d19d88c63024f","webpack:///./code_no_race.js"],"names":[],"mappings":";AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA,mDAA2C,cAAc;;AAEzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA;;;;;;;;AChEA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA","file":"code_bundle_no_race.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 40c4319d19d88c63024f","/* Any copyright is dedicated to the Public Domain.\n http://creativecommons.org/publicdomain/zero/1.0/ */\n\n// Original source code for the inline source map test.\n// The generated file was made with\n//    webpack --devtool source-map code_no_race.js code_bundle_no_race.js\n\n\"use strict\";\n\nfunction f() {\n  console.log(\"anything will do\");\n}\n\nf();\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./code_no_race.js\n// module id = 0\n// module chunks = 0"],"sourceRoot":""}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/code_no_race.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Original source code for the inline source map test.
+// The generated file was made with
+//    webpack --devtool source-map code_no_race.js code_bundle_no_race.js
+
+"use strict";
+
+function f() {
+  console.log("anything will do");
+}
+
+f();
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -555,17 +555,18 @@ Toolbox.prototype = {
   get sourceMapURLService() {
     if (this._sourceMapURLService) {
       return this._sourceMapURLService;
     }
     let sourceMaps = this.sourceMapService;
     if (!sourceMaps) {
       return null;
     }
-    this._sourceMapURLService = new SourceMapURLService(this._target, sourceMaps);
+    this._sourceMapURLService = new SourceMapURLService(this._target, this.threadClient,
+                                                        sourceMaps);
     return this._sourceMapURLService;
   },
 
   // Return HostType id for telemetry
   _getTelemetryHostId: function () {
     switch (this.hostType) {
       case Toolbox.HostType.BOTTOM: return 0;
       case Toolbox.HostType.SIDE: return 1;
--- a/devtools/client/webconsole/new-console-output/reducers/messages.js
+++ b/devtools/client/webconsole/new-console-output/reducers/messages.js
@@ -390,17 +390,17 @@ function matchNetworkFilters(message, fi
 function matchCssFilters(message, filters) {
   return (
     message.source != MESSAGE_SOURCE.CSS
     || filters.get("css") === true
   );
 }
 
 function matchSearchFilters(message, filters) {
-  let text = filters.text || "";
+  let text = (filters.text || "").trim();
   return (
     text === ""
     // Look for a match in parameters.
     || isTextInParameters(text, message.parameters)
     // Look for a match in location.
     || isTextInFrame(text, message.frame)
     // Look for a match in net events.
     || isTextInNetEvent(text, message.request)
--- a/devtools/client/webconsole/new-console-output/test/store/filters.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/filters.test.js
@@ -95,20 +95,30 @@ describe("Filtering", () => {
 
       store.dispatch(actions.filterToggle("net"));
       messages = getVisibleMessages(store.getState());
       expect(messages.length).toEqual(numMessages + 1);
     });
   });
 
   describe("Text filter", () => {
+    it("set the expected property on the store", () => {
+      store.dispatch(actions.filterTextSet("danger"));
+      expect(getAllFilters(store.getState()).text).toEqual("danger");
+    });
+
     it("matches on value grips", () => {
       store.dispatch(actions.filterTextSet("danger"));
       let messages = getVisibleMessages(store.getState());
       expect(messages.length - numUnfilterableMessages).toEqual(1);
+
+      // Checks that trimming works.
+      store.dispatch(actions.filterTextSet("    danger    "));
+      messages = getVisibleMessages(store.getState());
+      expect(messages.length - numUnfilterableMessages).toEqual(1);
     });
 
     it("matches unicode values", () => {
       store.dispatch(actions.filterTextSet("鼬"));
 
       let messages = getVisibleMessages(store.getState());
       expect(messages.length - numUnfilterableMessages).toEqual(1);
     });
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -2347,17 +2347,17 @@ WebGLContext::GetVRFrame()
 
     RefPtr<SharedSurfaceTextureClient> sharedSurface = screen->Front();
     if (!sharedSurface) {
         return nullptr;
     }
 
     if (sharedSurface && sharedSurface->GetAllocator() != vrmc) {
         RefPtr<SharedSurfaceTextureClient> dest =
-        screen->Factory()->NewTexClient(sharedSurface->GetSize());
+        screen->Factory()->NewTexClient(sharedSurface->GetSize(), vrmc);
         if (!dest) {
             return nullptr;
         }
         gl::SharedSurface* destSurf = dest->Surf();
         destSurf->ProducerAcquire();
         SharedSurface::ProdCopy(sharedSurface->Surf(), dest->Surf(),
                                 screen->Factory());
         destSurf->ProducerRelease();
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -123,16 +123,17 @@
 #include "nsThreadManager.h"
 #include "nsAnonymousTemporaryFile.h"
 #include "nsISpellChecker.h"
 #include "nsClipboardProxy.h"
 #include "nsDirectoryService.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsContentPermissionHelper.h"
+#include "nsPluginHost.h"
 #ifdef NS_PRINTING
 #include "nsPrintingProxy.h"
 #endif
 
 #include "IHistory.h"
 #include "nsNetUtil.h"
 
 #include "base/message_loop.h"
@@ -3369,10 +3370,20 @@ ContentChild::RecvRefreshScreens(nsTArra
 }
 
 already_AddRefed<nsIEventTarget>
 ContentChild::GetEventTargetFor(TabChild* aTabChild)
 {
   return IToplevelProtocol::GetActorEventTarget(aTabChild);
 }
 
+mozilla::ipc::IPCResult
+ContentChild::RecvSetPluginList(const uint32_t& aPluginEpoch,
+                                nsTArray<plugins::PluginTag>&& aPluginTags,
+                                nsTArray<plugins::FakePluginTag>&& aFakePluginTags)
+{
+  RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
+  host->SetPluginsInContent(aPluginEpoch, aPluginTags, aFakePluginTags);
+  return IPC_OK();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -656,16 +656,19 @@ public:
                       const Optional<int64_t>& aLastModified,
                       bool aExistenceCheck, bool aIsFromNsIFile);
 
   typedef std::function<void(PRFileDesc*)> AnonymousTemporaryFileCallback;
   nsresult AsyncOpenAnonymousTemporaryFile(const AnonymousTemporaryFileCallback& aCallback);
 
   virtual already_AddRefed<nsIEventTarget> GetEventTargetFor(TabChild* aTabChild) override;
 
+  mozilla::ipc::IPCResult
+  RecvSetPluginList(const uint32_t& aPluginEpoch, nsTArray<PluginTag>&& aPluginTags, nsTArray<FakePluginTag>&& aFakePluginTags) override;
+
 private:
   static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure);
   void StartForceKillTimer();
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   virtual void ProcessingError(Result aCode, const char* aReason) override;
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1157,27 +1157,16 @@ ContentParent::RecvGetBlocklistState(con
   }
 
   if (NS_FAILED(tag->GetBlocklistState(aState))) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult
-ContentParent::RecvFindPlugins(const uint32_t& aPluginEpoch,
-                               nsresult* aRv,
-                               nsTArray<PluginTag>* aPlugins,
-                               nsTArray<FakePluginTag>* aFakePlugins,
-                               uint32_t* aNewPluginEpoch)
-{
-  *aRv = mozilla::plugins::FindPluginsForContent(aPluginEpoch, aPlugins, aFakePlugins, aNewPluginEpoch);
-  return IPC_OK();
-}
-
 /*static*/ TabParent*
 ContentParent::CreateBrowser(const TabContext& aContext,
                              Element* aFrameElement,
                              ContentParent* aOpenerContentParent,
                              TabParent* aSameTabGroupAs,
                              uint64_t aNextTabParentId)
 {
   PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
@@ -2443,16 +2432,21 @@ ContentParent::InitInternal(ProcessPrior
 
   {
     nsTArray<BlobURLRegistrationData> registrations;
     if (nsHostObjectProtocolHandler::GetAllBlobURLEntries(registrations,
                                                           this)) {
       Unused << SendInitBlobURLs(registrations);
     }
   }
+
+  // Start up nsPluginHost and run FindPlugins to cache the plugin list.
+  // If this isn't our first content process, just send over cached list.
+  RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+  pluginHost->SendPluginsToContent();
 }
 
 bool
 ContentParent::IsAlive() const
 {
   return mIsAlive;
 }
 
@@ -5292,8 +5286,16 @@ ContentParent::CanCommunicateWith(Conten
   if (!cpm->GetParentProcessId(ChildID(), &parentId)) {
     return false;
   }
   if (IsForJSPlugin()) {
     return parentId == ContentParentId(0);
   }
   return parentId == aOtherProcess;
 }
+
+mozilla::ipc::IPCResult
+ContentParent::RecvMaybeReloadPlugins()
+{
+  RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+  pluginHost->ReloadPlugins();
+  return IPC_OK();
+}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -293,29 +293,25 @@ public:
                                                            Endpoint<PContentBridgeParent>* aEndpoint) override;
 
   virtual mozilla::ipc::IPCResult RecvCreateGMPService() override;
 
   virtual mozilla::ipc::IPCResult RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv,
                                                  uint32_t* aRunID,
                                                  Endpoint<PPluginModuleParent>* aEndpoint) override;
 
+  virtual mozilla::ipc::IPCResult RecvMaybeReloadPlugins() override;
+
   virtual mozilla::ipc::IPCResult RecvConnectPluginBridge(const uint32_t& aPluginId,
                                                           nsresult* aRv,
                                                           Endpoint<PPluginModuleParent>* aEndpoint) override;
 
   virtual mozilla::ipc::IPCResult RecvGetBlocklistState(const uint32_t& aPluginId,
                                                         uint32_t* aIsBlocklisted) override;
 
-  virtual mozilla::ipc::IPCResult RecvFindPlugins(const uint32_t& aPluginEpoch,
-                                                  nsresult* aRv,
-                                                  nsTArray<PluginTag>* aPlugins,
-                                                  nsTArray<FakePluginTag>* aFakePlugins,
-                                                  uint32_t* aNewPluginEpoch) override;
-
   virtual mozilla::ipc::IPCResult RecvUngrabPointer(const uint32_t& aTime) override;
 
   virtual mozilla::ipc::IPCResult RecvRemovePermission(const IPC::Principal& aPrincipal,
                                                        const nsCString& aPermissionType,
                                                        nsresult* aRv) override;
 
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ContentParent, nsIObserver)
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -606,16 +606,27 @@ child:
     async ProvideAnonymousTemporaryFile(uint64_t aID, FileDescOrError aFD);
 
     async SetPermissionsWithKey(nsCString aPermissionKey, Permission[] aPermissions);
 
     async RefreshScreens(ScreenDetails[] aScreens);
 
     async PIPCBlobInputStream(nsID aID, uint64_t aSize);
 
+    /**
+     * This call takes the set of plugins loaded in the chrome process, and
+     * sends them to the content process. However, in many cases this set will
+     * not have changed since the last SetPluginList message. To keep track of
+     * this, the chrome process increments an epoch number every time the set of
+     * plugins changes. The chrome process sends up the last epoch it observed.
+     * If the epoch last seen by the content process is the same, the content
+     * process ignores the update. Otherwise the content process updates its
+     * list and reloads its plugins.
+     **/
+    async SetPluginList(uint32_t pluginEpoch, PluginTag[] plugins, FakePluginTag[] fakePlugins);
 parent:
     async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
 
     sync CreateChildProcess(IPCTabContext context,
                             ProcessPriority priority,
                             TabId openerTabId,
                             TabId tabId)
         returns (ContentParentId cpId, bool isForBrowser);
@@ -641,30 +652,16 @@ parent:
     sync ConnectPluginBridge(uint32_t aPluginId)
         returns (nsresult rv, Endpoint<PPluginModuleParent> aEndpoint);
 
     /**
      * Return the current blocklist state for a particular plugin.
      */
     sync GetBlocklistState(uint32_t aPluginId) returns (uint32_t aState);
 
-    /**
-     * This call returns the set of plugins loaded in the chrome
-     * process. However, in many cases this set will not have changed since the
-     * last FindPlugins message. Consequently, the chrome process increments an
-     * epoch number every time the set of plugins changes. The content process
-     * sends up the last epoch it observed. If the epochs are the same, the
-     * chrome process returns no plugins. Otherwise it returns a complete list.
-     *
-     * |pluginEpoch| is the epoch last observed by the content
-     * process. |newPluginEpoch| is the current epoch in the chrome process. If
-     * |pluginEpoch == newPluginEpoch|, then |plugins| will be left empty.
-     */
-    sync FindPlugins(uint32_t pluginEpoch) returns (nsresult aResult, PluginTag[] plugins, FakePluginTag[] fakePlugins, uint32_t newPluginEpoch);
-
     async PJavaScript();
 
     async PRemoteSpellcheckEngine();
 
     async InitCrashReporter(Shmem shmem, NativeThreadId tid);
 
     /**
      * Is this token compatible with the provided version?
@@ -1089,16 +1086,17 @@ parent:
 
     sync GetA11yContentId() returns (uint32_t aContentId);
     async A11yHandlerControl(uint32_t aPid,
                              IHandlerControlHolder aHandlerControl);
 
     async AddMemoryReport(MemoryReport aReport);
     async FinishMemoryReport(uint32_t aGeneration);
 
+    async MaybeReloadPlugins();
 both:
      async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                         Principal aPrincipal, ClonedMessageData aData);
 
     /**
      * Notify `push-subscription-modified` observers in the parent and child.
      */
     async NotifyPushSubscriptionModifiedObservers(nsCString scope,
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -325,17 +325,17 @@ LargeAllocationNonGetRequest=A Large-All
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name. Do not translate `window.opener`.
 LargeAllocationNotOnlyToplevelInTabGroup=A Large-Allocation header was ignored due to the presence of windows which have a reference to this browsing context through the frame hierarchy or window.opener.
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name
 LargeAllocationNonE10S=A Large-Allocation header was ignored due to the document not being loaded out of process.
 GeolocationInsecureRequestIsForbidden=A Geolocation request can only be fulfilled in a secure context.
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name.
 LargeAllocationNonWin32=This page would be loaded in a new process due to a Large-Allocation header, however Large-Allocation process creation is disabled on non-Win32 platforms.
 # LOCALIZATION NOTE: Do not translate URL.createObjectURL(MediaStream).
-URLCreateObjectURL_MediaStream=URL.createObjectURL(MediaStream) is deprecated and will be removed soon.
+URLCreateObjectURL_MediaStreamWarning=URL.createObjectURL(MediaStream) is deprecated and will be removed soon.
 # LOCALIZATION NOTE: Do not translate MozAutoGainControl or autoGainControl.
 MozAutoGainControlWarning=mozAutoGainControl is deprecated. Use autoGainControl instead.
 # LOCALIZATION NOTE: Do not translate mozNoiseSuppression or noiseSuppression.
 MozNoiseSuppressionWarning=mozNoiseSuppression is deprecated. Use noiseSuppression instead.
 # LOCALIZATION NOTE: Do not translate xml:base.
 XMLBaseAttributeWarning=Use of xml:base attribute is deprecated and will be removed soon. Please remove any use of it.
 # LOCALIZATION NOTE: %S is the tag name of the element that starts the loop
 SVGReferenceLoopWarning=There is an SVG <%S> reference loop in this document, which will prevent the document rendering correctly.
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -4028,20 +4028,17 @@ MediaStreamGraph::ApplyAudioContextOpera
   graphImpl->AppendMessage(
     MakeUnique<AudioContextOperationControlMessage>(aDestinationStream, aStreams,
                                                     aOperation, aPromise));
 }
 
 bool
 MediaStreamGraph::IsNonRealtime() const
 {
-  const MediaStreamGraphImpl* impl = static_cast<const MediaStreamGraphImpl*>(this);
-  MediaStreamGraphImpl* graph;
-
-  return !gGraphs.Get(uint32_t(impl->AudioChannel()), &graph) || graph != impl;
+  return !static_cast<const MediaStreamGraphImpl*>(this)->mRealtime;
 }
 
 void
 MediaStreamGraph::StartNonRealtimeProcessing(uint32_t aTicksToProcess)
 {
   NS_ASSERTION(NS_IsMainThread(), "main thread only");
 
   MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(this);
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/crashtests/1367930_1.html
@@ -0,0 +1,39 @@
+<html>
+<body>
+  <script>
+var offer= "v=0\r\no=- 6276735615230473072 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE video\r\na=msid-semantic:WMS *\r\na=ice-ufrag:XoEUdw==\r\na=ice-pwd:hfNjAs9TU4NNuumB50mGfJwkqIEN8A==\r\na=ice-lite\r\na=setup:actpass\r\na=fingerprint:sha-256 0D:54:F2:D9:F3:10:00:2D:CD:14:C6:AC:CB:5D:E7:34:5E:6F:A8:BF:25:96:48:40:0B:C7:F9:18:6A:A6:73:3F\r\nm=video 9 UDP/TLS/RTP/SAVPF 100\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=mid:video\r\na=recvonly\r\na=rtcp-rsize\r\na=rtcp-mux\r\na=rtpmap:100 VP8/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=rtcp-fb:100 goog-remb\r\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:4 urn:3gpp:video-orientation\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=rid:hi recv\r\na=rid:mid recv\r\na=rid:lo recv\r\na=simulcast: recv rid=hi,mid,lo\r\na=candidate:1796272311 1 UDP 2130706431 192.168.10.146 55984 typ host\r\na=end-of-candidates\r\n"
+
+var data = {};
+data.sdp = offer;
+data.type = "offer";
+
+console.log('OFFER', JSON.stringify(data.sdp));
+
+var pc = new RTCPeerConnection();
+
+navigator.mediaDevices.getUserMedia({video: true, fake: true})
+  .then((stream) => {
+    return pc.addStream(stream);
+  })
+  .then(() => {
+    return pc.setRemoteDescription(new RTCSessionDescription(data));
+  })
+  .then(function() {
+      var sender = pc.getSenders()[0];
+      console.log('setting parameters');
+      return sender.setParameters({encodings: [
+          {rid: "hi", maxBitrate: 800000},
+          {rid: "mid", maxBitrate: 400000, scaleDownResolutionBy: 2},
+          {rid: "lo", maxBitrate: 150000, scaleDownResolutionBy: 4}
+      ]});
+  })
+  .then(function() {
+      return pc.createAnswer();
+  })
+  .then(function(answer) {
+      console.log('answer', JSON.stringify(answer.sdp));
+      return pc.setLocalDescription(answer);
+  });
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/crashtests/1367930_2.html
@@ -0,0 +1,25 @@
+<html>
+  <body>
+    <script>
+const sdp = {"type":"offer","sdp":"v=0\r\no=- 6276735615230473072 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE video\r\na=msid-semantic:WMS *\r\na=ice-ufrag:XpXW4g==\r\na=ice-pwd:hjagMNwFF/kMOmWoULEFptwuQXkMVQ==\r\na=ice-lite\r\na=setup:actpass\r\na=fingerprint:sha-256 DC:FC:25:56:2B:88:77:2F:E4:FA:97:4E:2E:F1:D6:34:A6:A0:11:E2:E4:38:B3:98:08:D2:F7:9D:F5:E2:C1:15\r\nm=video 9 UDP/TLS/RTP/SAVPF 100\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=mid:video\r\na=recvonly\r\na=rtcp-rsize\r\na=rtcp-mux\r\na=rtpmap:100 VP8/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=rtcp-fb:100 goog-remb\r\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:4 urn:3gpp:video-orientation\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=rid:hi recv\r\na=rid:mid recv\r\na=rid:lo recv\r\na=simulcast: recv rid=hi;mid;lo\r\na=candidate:1796272311 1 UDP 2130706431 127.0.0.1 33584 typ host\r\na=end-of-candidates\r\n"};
+
+var pc = new RTCPeerConnection();
+
+navigator.mediaDevices.getUserMedia({video: true, fake: true})
+.then(stream => pc.addStream(stream))
+.then(() => {
+            console.log('hum');
+            pc.setRemoteDescription(sdp)
+            .then(function() {
+                return pc.createAnswer();
+            })
+            .then(function(answer) {
+                console.log(answer.sdp);
+                return pc.setLocalDescription(answer);
+            })
+            .then(console.log('yay'))
+            .catch(e => console.error(e));
+})
+    </script>
+  </body>
+</html>
--- a/dom/media/tests/crashtests/crashtests.list
+++ b/dom/media/tests/crashtests/crashtests.list
@@ -10,8 +10,10 @@ load 812785.html
 load 834100.html
 load 836349.html
 load 837324.html
 load 855796.html
 load 860143.html
 load 861958.html
 load 863929.html
 load 1348381.html
+load 1367930_1.html
+load 1367930_2.html
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -46,16 +46,17 @@
 #include "nsIScriptChannel.h"
 #include "nsIBlocklistService.h"
 #include "nsVersionComparator.h"
 #include "nsIObjectLoadingContent.h"
 #include "nsIWritablePropertyBag2.h"
 #include "nsICategoryManager.h"
 #include "nsPluginStreamListenerPeer.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/FakePluginTagInitBinding.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/plugins/PluginAsyncSurrogate.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/plugins/PluginTypes.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ipc/URIUtils.h"
 
@@ -262,24 +263,16 @@ static bool UnloadPluginsASAP()
 }
 
 nsPluginHost::nsPluginHost()
   : mPluginsLoaded(false)
   , mOverrideInternalTypes(false)
   , mPluginsDisabled(false)
   , mPluginEpoch(0)
 {
-  // Bump the pluginchanged epoch on startup. This insures content gets a
-  // good plugin list the first time it requests it. Normally we'd just
-  // init this to 1, but due to the unique nature of our ctor we need to do
-  // this manually.
-  if (XRE_IsParentProcess()) {
-    IncrementChromeEpoch();
-  }
-
   // check to see if pref is set at startup to let plugins take over in
   // full page mode for certain image mime types that we handle internally
   mOverrideInternalTypes =
     Preferences::GetBool("plugin.override_internal_types", false);
 
   mPluginsDisabled = Preferences::GetBool("plugin.disable", false);
 
   Preferences::AddStrongObserver(this, "plugin.disable");
@@ -298,16 +291,23 @@ nsPluginHost::nsPluginHost()
 #ifdef PLUGIN_LOGGING
   MOZ_LOG(nsPluginLogging::gNPNLog, PLUGIN_LOG_ALWAYS,("NPN Logging Active!\n"));
   MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_ALWAYS,("General Plugin Logging Active! (nsPluginHost::ctor)\n"));
   MOZ_LOG(nsPluginLogging::gNPPLog, PLUGIN_LOG_ALWAYS,("NPP Logging Active!\n"));
 
   PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("nsPluginHost::ctor\n"));
   PR_LogFlush();
 #endif
+
+  // Load plugins on creation, as there's a good chance we'll need to send them
+  // to content processes directly after creation.
+  if (XRE_IsParentProcess())
+  {
+    LoadPlugins();
+  }
 }
 
 nsPluginHost::~nsPluginHost()
 {
   PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("nsPluginHost::dtor\n"));
 
   UnloadPlugins();
   sInst = nullptr;
@@ -355,18 +355,30 @@ bool nsPluginHost::IsRunningPlugin(nsPlu
   return false;
 }
 
 nsresult nsPluginHost::ReloadPlugins()
 {
   PLUGIN_LOG(PLUGIN_LOG_NORMAL,
   ("nsPluginHost::ReloadPlugins Begin\n"));
 
-  nsresult rv = NS_OK;
-
+  // If we're calling this from a content process, forward the reload request to
+  // the parent process. If plugins actually changed, it will notify us
+  // asynchronously later.
+  if (XRE_IsContentProcess())
+  {
+    Unused << mozilla::dom::ContentChild::GetSingleton()->SendMaybeReloadPlugins();
+    // In content processes, always signal that plugins have not changed. We
+    // will never know if they changed here unless we make slow synchronous
+    // calls. This information will hopefully only be wrong once, as if there
+    // has been a plugin update, we expect to have gotten notification from the
+    // parent process and everything should be updated by the next time this is
+    // called. See Bug 1337058 for more info.
+    return NS_ERROR_PLUGINS_PLUGINSNOTCHANGED;
+  }
   // this will create the initial plugin list out of cache
   // if it was not created yet
   if (!mPluginsLoaded)
     return LoadPlugins();
 
   // we are re-scanning plugins. New plugins may have been added, also some
   // plugins may have been removed, so we should probably shut everything down
   // but don't touch running (active and not stopped) plugins
@@ -377,16 +389,24 @@ nsresult nsPluginHost::ReloadPlugins()
   // look for possible changes
   bool pluginschanged = true;
   FindPlugins(false, &pluginschanged);
 
   // if no changed detected, return an appropriate error code
   if (!pluginschanged)
     return NS_ERROR_PLUGINS_PLUGINSNOTCHANGED;
 
+  return ActuallyReloadPlugins();
+}
+
+nsresult
+nsPluginHost::ActuallyReloadPlugins()
+{
+  nsresult rv = NS_OK;
+
   // shutdown plugins and kill the list if there are no running plugins
   RefPtr<nsPluginTag> prev;
   RefPtr<nsPluginTag> next;
 
   for (RefPtr<nsPluginTag> p = mPlugins; p != nullptr;) {
     next = p->mNext;
 
     // only remove our plugin from the list if it's not running.
@@ -410,16 +430,23 @@ nsresult nsPluginHost::ReloadPlugins()
   }
 
   // set flags
   mPluginsLoaded = false;
 
   // load them again
   rv = LoadPlugins();
 
+  if (XRE_IsParentProcess())
+  {
+    // If the plugin list changed, update content. If the plugin list changed
+    // for the content process, it will also reload plugins.
+    SendPluginsToContent();
+  }
+
   PLUGIN_LOG(PLUGIN_LOG_NORMAL,
   ("nsPluginHost::ReloadPlugins End\n"));
 
   return rv;
 }
 
 #define NS_RETURN_UASTRING_SIZE 128
 
@@ -2271,21 +2298,21 @@ WatchRegKey(uint32_t aRoot, nsCOMPtr<nsI
     return;
   }
   aKey->StartWatching(true);
 }
 #endif
 
 nsresult nsPluginHost::LoadPlugins()
 {
-#ifdef ANDROID
+  // This should only be run in the parent process. On plugin list change, we'll
+  // update observers in the content process as part of SetPluginsInContent
   if (XRE_IsContentProcess()) {
     return NS_OK;
   }
-#endif
   // do not do anything if it is already done
   // use ReloadPlugins() to enforce loading
   if (mPluginsLoaded)
     return NS_OK;
 
   if (mPluginsDisabled)
     return NS_OK;
 
@@ -2310,42 +2337,34 @@ nsresult nsPluginHost::LoadPlugins()
     if (obsService)
       obsService->NotifyObservers(nullptr, "plugins-list-updated", nullptr);
   }
 
   return NS_OK;
 }
 
 nsresult
-nsPluginHost::FindPluginsInContent(bool aCreatePluginList, bool* aPluginsChanged)
+nsPluginHost::SetPluginsInContent(uint32_t aPluginEpoch,
+                                  nsTArray<mozilla::plugins::PluginTag>& aPlugins,
+                                  nsTArray<mozilla::plugins::FakePluginTag>& aFakePlugins)
 {
   MOZ_ASSERT(XRE_IsContentProcess());
 
-  dom::ContentChild* cp = dom::ContentChild::GetSingleton();
-  nsresult rv;
   nsTArray<PluginTag> plugins;
+
   nsTArray<FakePluginTag> fakePlugins;
-  uint32_t parentEpoch;
-  if (!cp->SendFindPlugins(ChromeEpochForContent(), &rv, &plugins, &fakePlugins, &parentEpoch) ||
-      NS_FAILED(rv)) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  if (parentEpoch != ChromeEpochForContent()) {
-    *aPluginsChanged = true;
-    if (!aCreatePluginList) {
-      return NS_OK;
-    }
-
-    // Don't do this if aCreatePluginList is false. Otherwise, when we actually
-    // want to create the list, we'll come back here and do nothing.
-    SetChromeEpochForContent(parentEpoch);
-
-    for (size_t i = 0; i < plugins.Length(); i++) {
-      PluginTag& tag = plugins[i];
+
+  if (aPluginEpoch != ChromeEpochForContent()) {
+    // Since we know we're going to be repopulating the lists anyways, trigger a
+    // reload now to clear out all old entries.
+    ActuallyReloadPlugins();
+
+    SetChromeEpochForContent(aPluginEpoch);
+
+    for (auto tag : aPlugins) {
 
       // Don't add the same plugin again.
       if (nsPluginTag* existing = PluginWithId(tag.id())) {
         UpdateInMemoryPluginInfo(existing);
         continue;
       }
 
       nsPluginTag *pluginTag = new nsPluginTag(tag.id(),
@@ -2362,17 +2381,17 @@ nsPluginHost::FindPluginsInContent(bool 
                                                tag.supportsAsyncInit(),
                                                tag.supportsAsyncRender(),
                                                tag.lastModifiedTime(),
                                                tag.isFromExtension(),
                                                tag.sandboxLevel());
       AddPluginTag(pluginTag);
     }
 
-    for (const auto& tag : fakePlugins) {
+    for (const auto& tag : aFakePlugins) {
       // Don't add the same plugin again.
       for (const auto& existingTag : mFakePlugins) {
         if (existingTag->Id() == tag.id()) {
           continue;
         }
       }
 
       RefPtr<nsFakePluginTag> pluginTag =
@@ -2389,16 +2408,22 @@ nsPluginHost::FindPluginsInContent(bool 
         Preferences::GetCString(kPrefDisableFullPage);
       for (uint32_t i = 0; i < pluginTag->MimeTypes().Length(); i++) {
         if (!IsTypeInList(pluginTag->MimeTypes()[i], disableFullPage)) {
           RegisterWithCategoryManager(pluginTag->MimeTypes()[i],
                                       ePluginRegister);
         }
       }
     }
+
+    nsCOMPtr<nsIObserverService> obsService =
+      mozilla::services::GetObserverService();
+    if (obsService) {
+      obsService->NotifyObservers(nullptr, "plugins-list-updated", nullptr);
+    }
   }
 
   mPluginsLoaded = true;
   return NS_OK;
 }
 
 // if aCreatePluginList is false we will just scan for plugins
 // and see if any changes have been made to the plugins.
@@ -2406,18 +2431,20 @@ nsPluginHost::FindPluginsInContent(bool 
 nsresult nsPluginHost::FindPlugins(bool aCreatePluginList, bool * aPluginsChanged)
 {
   Telemetry::AutoTimer<Telemetry::FIND_PLUGINS> telemetry;
 
   NS_ENSURE_ARG_POINTER(aPluginsChanged);
 
   *aPluginsChanged = false;
 
+  // If plugins are found or change, the content process will be notified by the
+  // parent process. Bail out early if this is called from the content process.
   if (XRE_IsContentProcess()) {
-    return FindPluginsInContent(aCreatePluginList, aPluginsChanged);
+    return NS_OK;
   }
 
   nsresult rv;
 
   // Read cached plugins info. If the profile isn't yet available then don't
   // scan for plugins
   if (ReadPluginInfo() == NS_ERROR_NOT_AVAILABLE)
     return NS_OK;
@@ -2536,90 +2563,80 @@ nsresult nsPluginHost::FindPlugins(bool 
   // No more need for cached plugins. Clear it up.
   NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext);
   NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext);
 
   return NS_OK;
 }
 
 nsresult
-mozilla::plugins::FindPluginsForContent(uint32_t aPluginEpoch,
-                                        nsTArray<PluginTag>* aPlugins,
-                                        nsTArray<FakePluginTag>* aFakePlugins,
-                                        uint32_t* aNewPluginEpoch)
+nsPluginHost::SendPluginsToContent()
 {
   MOZ_ASSERT(XRE_IsParentProcess());
 
-  RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
-  return host->FindPluginsForContent(aPluginEpoch, aPlugins, aFakePlugins, aNewPluginEpoch);
-}
-
-nsresult
-nsPluginHost::FindPluginsForContent(uint32_t aPluginEpoch,
-                                    nsTArray<PluginTag>* aPlugins,
-                                    nsTArray<FakePluginTag>* aFakePlugins,
-                                    uint32_t* aNewPluginEpoch)
-{
-  MOZ_ASSERT(XRE_IsParentProcess());
-
+  nsTArray<PluginTag> pluginTags;
+  nsTArray<FakePluginTag> fakePluginTags;
   // Load plugins so that the epoch is correct.
   nsresult rv = LoadPlugins();
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  *aNewPluginEpoch = ChromeEpoch();
-  if (aPluginEpoch == ChromeEpoch()) {
-    return NS_OK;
-  }
+  uint32_t newPluginEpoch = ChromeEpoch();
 
   nsTArray<nsCOMPtr<nsIInternalPluginTag>> plugins;
   GetPlugins(plugins, true);
 
   for (size_t i = 0; i < plugins.Length(); i++) {
     nsCOMPtr<nsIInternalPluginTag> basetag = plugins[i];
 
     nsCOMPtr<nsIFakePluginTag> faketag = do_QueryInterface(basetag);
     if (faketag) {
       /// FIXME-jsplugins - We need to add a nsIInternalPluginTag->AsNative() to
       /// avoid this hacky static cast
       nsFakePluginTag* tag = static_cast<nsFakePluginTag*>(basetag.get());
       mozilla::ipc::URIParams handlerURI;
       SerializeURI(tag->HandlerURI(), handlerURI);
-      aFakePlugins->AppendElement(FakePluginTag(tag->Id(),
-                                                handlerURI,
-                                                tag->Name(),
-                                                tag->Description(),
-                                                tag->MimeTypes(),
-                                                tag->MimeDescriptions(),
-                                                tag->Extensions(),
-                                                tag->GetNiceFileName(),
-                                                tag->SandboxScript()));
+      fakePluginTags.AppendElement(FakePluginTag(tag->Id(),
+                                                 handlerURI,
+                                                 tag->Name(),
+                                                 tag->Description(),
+                                                 tag->MimeTypes(),
+                                                 tag->MimeDescriptions(),
+                                                 tag->Extensions(),
+                                                 tag->GetNiceFileName(),
+                                                 tag->SandboxScript()));
       continue;
     }
 
     /// FIXME-jsplugins - We need to cleanup the various plugintag classes
     /// to be more sane and avoid this dance
     nsPluginTag *tag = static_cast<nsPluginTag *>(basetag.get());
 
-    aPlugins->AppendElement(PluginTag(tag->mId,
-                                      tag->Name(),
-                                      tag->Description(),
-                                      tag->MimeTypes(),
-                                      tag->MimeDescriptions(),
-                                      tag->Extensions(),
-                                      tag->mIsJavaPlugin,
-                                      tag->mIsFlashPlugin,
-                                      tag->mSupportsAsyncInit,
-                                      tag->mSupportsAsyncRender,
-                                      tag->FileName(),
-                                      tag->Version(),
-                                      tag->mLastModifiedTime,
-                                      tag->IsFromExtension(),
-                                      tag->mSandboxLevel));
+    pluginTags.AppendElement(PluginTag(tag->mId,
+                                       tag->Name(),
+                                       tag->Description(),
+                                       tag->MimeTypes(),
+                                       tag->MimeDescriptions(),
+                                       tag->Extensions(),
+                                       tag->mIsJavaPlugin,
+                                       tag->mIsFlashPlugin,
+                                       tag->mSupportsAsyncInit,
+                                       tag->mSupportsAsyncRender,
+                                       tag->FileName(),
+                                       tag->Version(),
+                                       tag->mLastModifiedTime,
+                                       tag->IsFromExtension(),
+                                       tag->mSandboxLevel));
+  }
+  nsTArray<dom::ContentParent*> parents;
+  dom::ContentParent::GetAll(parents);
+  for (auto p : parents)
+  {
+    Unused << p->SendSetPluginList(newPluginEpoch, pluginTags, fakePluginTags);
   }
   return NS_OK;
 }
 
 void
 nsPluginHost::UpdateInMemoryPluginInfo(nsPluginTag* aPluginTag)
 {
   NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext);
--- a/dom/plugins/base/nsPluginHost.h
+++ b/dom/plugins/base/nsPluginHost.h
@@ -247,16 +247,20 @@ public:
 
   void CreateWidget(nsPluginInstanceOwner* aOwner);
 
   nsresult EnumerateSiteData(const nsACString& domain,
                              const InfallibleTArray<nsCString>& sites,
                              InfallibleTArray<nsCString>& result,
                              bool firstMatchOnly);
 
+  nsresult SendPluginsToContent();
+  nsresult SetPluginsInContent(uint32_t aPluginEpoch,
+                               nsTArray<mozilla::plugins::PluginTag>& aPlugins,
+                               nsTArray<mozilla::plugins::FakePluginTag>& aFakePlugins);
 private:
   friend class nsPluginUnloadRunnable;
 
   void DestroyRunningInstances(nsPluginTag* aPluginTag);
 
   // Writes updated plugins settings to disk and unloads the plugin
   // if it is now disabled. Should only be called by the plugin tag in question
   void UpdatePluginInfo(nsPluginTag* aPluginTag);
@@ -296,18 +300,16 @@ private:
   // be filled in with the MIME type the plugin is registered for.
   nsPluginTag* FindNativePluginForExtension(const nsACString & aExtension,
                                             /* out */ nsACString & aMimeType,
                                             bool aCheckEnabled);
 
   nsresult
   FindStoppedPluginForURL(nsIURI* aURL, nsIPluginInstanceOwner *aOwner);
 
-  nsresult FindPluginsInContent(bool aCreatePluginList, bool * aPluginsChanged);
-
   nsresult
   FindPlugins(bool aCreatePluginList, bool * aPluginsChanged);
 
   // FIXME revisit, no ns prefix
   // Registers or unregisters the given mime type with the category manager
   enum nsRegisterType { ePluginRegister,
                         ePluginUnregister,
                         // Checks if this type should still be registered first
@@ -363,16 +365,18 @@ private:
 
   // To be used by the content process to get/set the last observed epoch value
   // from the chrome process.
   uint32_t ChromeEpochForContent();
   void SetChromeEpochForContent(uint32_t aEpoch);
 
   void UpdateInMemoryPluginInfo(nsPluginTag* aPluginTag);
 
+  nsresult ActuallyReloadPlugins();
+
   RefPtr<nsPluginTag> mPlugins;
   RefPtr<nsPluginTag> mCachedPlugins;
   RefPtr<nsInvalidPluginTag> mInvalidPlugins;
 
   nsTArray< RefPtr<nsFakePluginTag> > mFakePlugins;
 
   bool mPluginsLoaded;
 
--- a/dom/vr/moz.build
+++ b/dom/vr/moz.build
@@ -1,16 +1,16 @@
 # -*- 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/.
 
 with Files("**"):
-    BUG_COMPONENT = ("Core", "DOM")
+    BUG_COMPONENT = ("Core", "WebVR")
 
 EXPORTS.mozilla.dom += [
     'VRDisplay.h',
     'VRDisplayEvent.h',
     'VREventObserver.h',
     'VRServiceTest.h'
     ]
 
@@ -23,9 +23,10 @@ UNIFIED_SOURCES = [
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base'
 ]
 
-MOCHITEST_MANIFESTS += ['test/mochitest.ini']
\ No newline at end of file
+MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
+REFTEST_MANIFESTS += ['test/reftest/reftest.list']
\ No newline at end of file
rename from dom/vr/test/VRSimulationDriver.js
rename to dom/vr/test/mochitest/VRSimulationDriver.js
rename from dom/vr/test/WebVRHelpers.js
rename to dom/vr/test/mochitest/WebVRHelpers.js
rename from dom/vr/test/mochitest.ini
rename to dom/vr/test/mochitest/mochitest.ini
rename from dom/vr/test/requestPresent.js
rename to dom/vr/test/mochitest/requestPresent.js
rename from dom/vr/test/runVRTest.js
rename to dom/vr/test/mochitest/runVRTest.js
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/mochitest/test_vrDisplay_canvas2d.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>VRDisplay Canvas2D</title>
+    <meta name="timeout" content="long"/>
+    <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="runVRTest.js"></script>
+  </head>
+  <body>
+    <script>
+      "use strict";
+      var vrDisplay;
+
+      function requestPresentTest() {
+        async_test(function (test) {
+          vrDisplay.requestAnimationFrame(callback);
+
+          function callback() {
+            vrDisplay.resetPose();
+            vrDisplay.getLayers();
+            vrDisplay.submitFrame();
+            vrDisplay.getEyeParameters("right");
+            test.done();
+          }
+        }, "Finish WebVR Canvas2D requestPresentTest.");
+      }
+
+      function startTest() {
+        promise_test((test) => {
+          var canvas = document.createElement('canvas');
+          (document.body || document.documentElement).appendChild(canvas);
+          var context = canvas.getContext('2d');
+          var img = document.createElement('img');
+          img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACw=";
+
+          return navigator.getVRDisplays().then((displays) => {
+            assert_equals(displays.length, 1, "displays.length must be one after attach.");
+            vrDisplay = displays[0];
+            var frameData = new VRFrameData();
+            return vrDisplay.requestPresent([{source: canvas}]).then(() => {
+              requestPresentTest();
+            });
+          });
+        }, "Finish running WebVR Canvas2D test.");
+      }
+
+      runVRTest(startTest);
+    </script>
+  </body>
+</html>
\ No newline at end of file
rename from dom/vr/test/test_vrDisplay_exitPresent.html
rename to dom/vr/test/mochitest/test_vrDisplay_exitPresent.html
rename from dom/vr/test/test_vrDisplay_getFrameData.html
rename to dom/vr/test/mochitest/test_vrDisplay_getFrameData.html
rename from dom/vr/test/test_vrDisplay_onvrdisplaydeactivate_crosscontent.html
rename to dom/vr/test/mochitest/test_vrDisplay_onvrdisplaydeactivate_crosscontent.html
rename from dom/vr/test/test_vrDisplay_requestPresent.html
rename to dom/vr/test/mochitest/test_vrDisplay_requestPresent.html
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/reftest/VRSimulationDriver.js
@@ -0,0 +1,56 @@
+
+var VRServiceTest;
+var vrMockDisplay;
+
+var VRSimulationDriver = (function() {
+"use strict";
+
+var AttachWebVRDisplay = function() {
+  var promise = VRServiceTest.attachVRDisplay("VRDisplayTest");
+  promise.then(function (display) {
+    vrMockDisplay = display;
+  });
+
+  return promise;
+};
+
+var SetVRDisplayPose = function(position,
+                                linearVelocity, linearAcceleration,
+                                orientation, angularVelocity,
+                                angularAcceleration) {
+  vrMockDisplay.setPose(position, linearVelocity, linearAcceleration,
+                        orientation, angularVelocity, angularAcceleration);
+};
+
+var SetEyeResolution = function(width, height) {
+  vrMockDisplay.setEyeResolution(width, height);
+}
+
+var SetEyeParameter = function(eye, offsetX, offsetY, offsetZ,
+                               upDegree, rightDegree, downDegree, leftDegree) {
+  vrMockDisplay.setEyeParameter(eye, offsetX, offsetY, offsetZ, upDegree, rightDegree,
+                                downDegree, leftDegree);
+}
+
+var SetMountState = function(isMounted) {
+  vrMockDisplay.setMountState(isMounted);
+}
+
+var UpdateVRDisplay = function() {
+  vrMockDisplay.update();
+}
+
+var API = {
+  AttachWebVRDisplay: AttachWebVRDisplay,
+  SetVRDisplayPose: SetVRDisplayPose,
+  SetEyeResolution: SetEyeResolution,
+  SetEyeParameter: SetEyeParameter,
+  SetMountState: SetMountState,
+  UpdateVRDisplay: UpdateVRDisplay,
+
+  none: false
+};
+
+return API;
+
+}());
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/reftest/draw_rect.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<meta charset='UTF-8'>
+<!-- Draw rect in WebGL and submit it to the VR device as a base64 image.
+If this fails, something is seriously wrong. -->
+<html class="reftest-wait">
+<head>
+  <script type='text/javascript' src='webgl-util.js'></script>
+  <script type='text/javascript' src="VRSimulationDriver.js"></script>
+  <script id="vs" type="x-shader/x-vertex">
+    attribute vec2 aVertCoord;
+
+    void main(void) {
+      gl_Position = vec4(aVertCoord, 0.0, 1.0);
+    }
+  </script>
+  <script id="fs" type="x-shader/x-fragment">
+    precision mediump float;
+
+    void main(void) {
+      gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+    }
+  </script>
+  <script type='text/javascript'>
+    'use strict';
+
+    var submitResult = null;
+    var vrDisplay = null;
+    var webglCanvas = null;
+    var gl = null;
+    var prog = null;
+    var img = null;
+
+    function setStatus(text) {
+      var elem = document.getElementById('status');
+      elem.innerHTML = text;
+    }
+
+    function initVRMock() {
+      VRServiceTest = navigator.requestVRServiceTest();
+      if (!VRServiceTest) {
+        setStatus('VRServiceTest get failed.');
+        return;
+      }
+
+      VRSimulationDriver.AttachWebVRDisplay().then(() => {
+        // Looking for VR displays
+        if (navigator.getVRDisplays) {
+          submitResult = new VRSubmitFrameResult();
+          navigator.getVRDisplays().then(function (displays) {
+            if (displays.length > 0) {
+              vrDisplay = displays[0];
+              vrDisplay.requestPresent([{ source: webglCanvas }]);
+              vrDisplay.requestAnimationFrame(onAnimationFrame);
+            }
+          });
+        }
+      });
+    }
+
+    function onAnimationFrame() {
+      if (!vrDisplay.isPresenting) {
+        return;
+      }
+
+      vrDisplay.requestAnimationFrame(onAnimationFrame);
+      gl.clearColor(0.0, 1.0, 0.0, 1.0);
+      gl.clear(gl.COLOR_BUFFER_BIT);
+
+      // Presenting render a stereo view.
+      gl.viewport(0, 0, webglCanvas.width * 0.5, webglCanvas.height);
+      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+
+      gl.viewport(webglCanvas.width * 0.5, 0, webglCanvas.width * 0.5, webglCanvas.height);
+      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+
+      // Indicate VRDisplay we're done rendering.
+      vrDisplay.submitFrame();
+      if (vrDisplay.getSubmitFrameResult(submitResult)) {
+        if (!img) {
+          img = document.createElement("img");
+          img.onload = function(){
+            webglCanvas.style.display = 'none';
+            vrDisplay.exitPresent();
+            setTimeout(testComplete, 0);
+          };
+          img.src = submitResult.base64Image;
+          document.body.appendChild(img);
+        } else {
+          img.src = submitResult.base64Image;
+        }
+      }
+    }
+
+    function runTest() {
+      webglCanvas = document.getElementById('canvas');
+      gl = WebGLUtil.getWebGL('canvas');
+      if (!gl) {
+        setStatus('WebGL context creation failed.');
+        return;
+      }
+      gl.disable(gl.DEPTH_TEST);
+      prog = WebGLUtil.createProgramByIds(gl, 'vs', 'fs');
+      if (!prog) {
+        setStatus('Program linking failed.');
+        return;
+      }
+      prog.aVertCoord = gl.getAttribLocation(prog, "aVertCoord");
+
+      var vertCoordArr = new Float32Array([
+        -0.5, -0.5,
+        0.5, -0.5,
+        -0.5, 0.5,
+        0.5, 0.5,
+      ]);
+      var vertCoordBuff = gl.createBuffer();
+      gl.bindBuffer(gl.ARRAY_BUFFER, vertCoordBuff);
+      gl.bufferData(gl.ARRAY_BUFFER, vertCoordArr, gl.STATIC_DRAW);
+      gl.useProgram(prog);
+      gl.enableVertexAttribArray(prog.aVertCoord);
+      gl.vertexAttribPointer(prog.aVertCoord, 2, gl.FLOAT, false, 0, 0);
+
+      initVRMock();
+    }
+
+    function testComplete() {
+      document.documentElement.removeAttribute("class");
+    }
+  </script>
+</head>
+
+<body onload='runTest();'>
+  <canvas id='canvas' width='256' height='256'></canvas>
+  <div id='status'></div>
+</body>
+
+</html>
\ No newline at end of file
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0f4d24a0d19ea26224adb3d90dab146f5a4c7a95
GIT binary patch
literal 1747
zc%17D@N?(olHy`uVBq!ia0y~yU}OMc4mKbaQ?w$Dfq`w7r;B4q#hkYnH}WzV@EqRo
z=YL5x8#{MPnextR&$N$>o-%d{orl>~3V$@H@X1d&@{oCwMU8_{JHN-H55q&!oxfuH
zU8~x9+2!mn*k4?G`TMnnt%JS8cE9`a$6qr3V%$~w>z|b${{{XFYcGGlURc#o*N``N
z-~PVK%wL#GUVr&pI!A7S{DSD^@7F*6!tj@2)TmLTMvWRZie^R)e2GaH$wWJisj`nh
THge7eR#pt2u6{1-oD!M<+goFD
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/reftest/reftest.list
@@ -0,0 +1,7 @@
+# WebVR Reftests
+default-preferences pref(dom.vr.puppet.enabled,true) pref(dom.vr.test.enabled,true) pref(dom.vr.require-gesture,false) pref(dom.vr.puppet.submitframe,1)
+
+# VR SubmitFrame is only implemented for D3D11 now.
+# We need to continue to investigate why these reftests can be run well in local,
+# but will be suspended until terminating on reftest debug build.
+skip-if(!winWidget||!layersGPUAccelerated||isDebugBuild) == draw_rect.html wrapper.html?draw_rect.png
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/reftest/webgl-util.js
@@ -0,0 +1,170 @@
+WebGLUtil = (function() {
+  // ---------------------------------------------------------------------------
+  // Error handling (for obvious failures, such as invalid element ids)
+
+  function defaultErrorFunc(str) {
+    console.log('Error: ' + str);
+  }
+
+  var gErrorFunc = defaultErrorFunc;
+  function setErrorFunc(func) {
+    gErrorFunc = func;
+  }
+
+  function error(str) {
+    gErrorFunc(str);
+  }
+
+  // ---------------------------------------------------------------------------
+  // Warning handling (for failures that may be intentional)
+
+  function defaultWarningFunc(str) {
+    console.log('Warning: ' + str);
+  }
+
+  var gWarningFunc = defaultWarningFunc;
+  function setWarningFunc(func) {
+    gWarningFunc = func;
+  }
+
+  function warning(str) {
+    gWarningFunc(str);
+  }
+
+  // ---------------------------------------------------------------------------
+  // WebGL helpers
+
+  function getWebGL(canvasId, requireConformant, attributes) {
+    // `requireConformant` will default to falsey if it is not supplied.
+
+    var canvas = document.getElementById(canvasId);
+
+    var gl = null;
+    try {
+      gl = canvas.getContext('webgl', attributes);
+    } catch(e) {}
+
+    if (!gl && !requireConformant) {
+      try {
+        gl = canvas.getContext('experimental-webgl', attributes);
+      } catch(e) {}
+    }
+
+    if (!gl) {
+      error('WebGL context could not be retrieved from \'' + canvasId + '\'.');
+      return null;
+    }
+
+    return gl;
+  }
+
+  function withWebGL2(canvasId, callback, onFinished) {
+    var run = function() {
+      var canvas = document.getElementById(canvasId);
+
+      var gl = null;
+      try {
+        gl = canvas.getContext('webgl2');
+      } catch(e) {}
+
+      if (!gl) {
+        todo(false, 'WebGL2 is not supported');
+        onFinished();
+        return;
+      }
+
+      function errorFunc(str) {
+        ok(false, 'Error: ' + str);
+      }
+      setErrorFunc(errorFunc);
+      setWarningFunc(errorFunc);
+
+      callback(gl);
+      onFinished();
+    };
+
+    try {
+      var prefArrArr = [
+        ['webgl.force-enabled', true],
+        ['webgl.enable-webgl2', true],
+      ];
+      var prefEnv = {'set': prefArrArr};
+      SpecialPowers.pushPrefEnv(prefEnv, run);
+    } catch (e) {
+      warning('No SpecialPowers, but trying WebGL2 anyway...');
+      run();
+    }
+  }
+
+  function getContentFromElem(elem) {
+    var str = "";
+    var k = elem.firstChild;
+    while (k) {
+      if (k.nodeType == 3)
+        str += k.textContent;
+
+      k = k.nextSibling;
+    }
+
+    return str;
+  }
+
+  // Returns a valid shader, or null on errors.
+  function createShaderById(gl, id) {
+    var elem = document.getElementById(id);
+    if (!elem) {
+      error('Failed to create shader from non-existent id \'' + id + '\'.');
+      return null;
+    }
+
+    var src = getContentFromElem(elem);
+
+    var shader;
+    if (elem.type == "x-shader/x-fragment") {
+      shader = gl.createShader(gl.FRAGMENT_SHADER);
+    } else if (elem.type == "x-shader/x-vertex") {
+      shader = gl.createShader(gl.VERTEX_SHADER);
+    } else {
+      error('Bad MIME type for shader \'' + id + '\': ' + elem.type + '.');
+      return null;
+    }
+
+    gl.shaderSource(shader, src);
+    gl.compileShader(shader);
+
+    return shader;
+  }
+
+  function createProgramByIds(gl, vsId, fsId) {
+    var vs = createShaderById(gl, vsId);
+    var fs = createShaderById(gl, fsId);
+    if (!vs || !fs)
+      return null;
+
+    var prog = gl.createProgram();
+    gl.attachShader(prog, vs);
+    gl.attachShader(prog, fs);
+    gl.linkProgram(prog);
+
+    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
+      var str = "Shader program linking failed:";
+      str += "\nShader program info log:\n" + gl.getProgramInfoLog(prog);
+      str += "\n\nVert shader log:\n" + gl.getShaderInfoLog(vs);
+      str += "\n\nFrag shader log:\n" + gl.getShaderInfoLog(fs);
+      warning(str);
+      return null;
+    }
+
+    return prog;
+  }
+
+  return {
+    setErrorFunc: setErrorFunc,
+    setWarningFunc: setWarningFunc,
+
+    getWebGL: getWebGL,
+    withWebGL2: withWebGL2,
+    createShaderById: createShaderById,
+    createProgramByIds: createProgramByIds,
+  };
+})();
new file mode 100644
--- /dev/null
+++ b/dom/vr/test/reftest/wrapper.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<title>Image reftest wrapper</title>
+<style type="text/css">
+  #image1 { background-color: rgb(10, 100, 250); }
+</style>
+<script>
+  // The image is loaded async after the page loads
+  // wait for it to finish loading
+  function onImageLoad() {
+    document.documentElement.removeAttribute("class");
+  };
+</script>
+</head>
+<body>
+<img id="image1">
+<script>
+  // Use as "wrapper.html?image.png"
+  var imgURL = document.location.search.substr(1);
+  document.images[0].onload = onImageLoad;
+  document.images[0].onerror = onImageLoad;
+  document.images[0].src = imgURL;
+</script>
+</body>
+</html>
+
deleted file mode 100644
--- a/dom/vr/test/test_vrDisplay_canvas2d.html
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>VRDisplay Canvas2D</title>
-    <meta name="timeout" content="long"/>
-    <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="runVRTest.js"></script>
-  </head>
-  <body>
-    <script>
-      var vrDisplay;
-
-      function requestPresentTest() {
-        async_test(function (test) {
-          vrDisplay.requestAnimationFrame(callback);
-
-          function callback() {
-            vrDisplay.resetPose();
-            vrDisplay.getLayers();
-            vrDisplay.submitFrame();
-            vrDisplay.getEyeParameters("right");
-            test.done();
-          }
-        }, "Finish WebVR Canvas2D requestPresentTest.");
-      }
-
-      function startTest() {
-        promise_test((test) => {
-          var canvas = document.createElement('canvas');
-          (document.body || document.documentElement).appendChild(canvas);
-          var context = canvas.getContext('2d');
-          var img = document.createElement('img');
-          img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACw=";
-
-          return navigator.getVRDisplays().then((displays) => {
-            assert_equals(displays.length, 1, "displays.length must be one after attach.");
-            vrDisplay = displays[0];
-            var frameData = new VRFrameData();
-            return vrDisplay.requestPresent([{source: canvas}]).then(() => {
-              requestPresentTest();
-            });
-          });
-        }, "Finish running WebVR Canvas2D test.");
-      }
-
-      runVRTest(startTest);
-    </script>
-  </body>
-</html>
\ No newline at end of file
--- a/editor/libeditor/CreateElementTransaction.cpp
+++ b/editor/libeditor/CreateElementTransaction.cpp
@@ -94,17 +94,17 @@ CreateElementTransaction::DoTransaction(
   if (!mEditorBase->GetShouldTxnSetSelection()) {
     // Do nothing - DOM range gravity will adjust selection
     return NS_OK;
   }
 
   RefPtr<Selection> selection = mEditorBase->GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
-  rv = selection->CollapseNative(mParent, mParent->IndexOf(mNewNode) + 1);
+  rv = selection->Collapse(mParent, mParent->IndexOf(mNewNode) + 1);
   NS_ASSERTION(!rv.Failed(),
                "selection could not be collapsed after insert");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CreateElementTransaction::UndoTransaction()
 {
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -1065,32 +1065,32 @@ EditorBase::BeginningOfDocument()
   // get the root element
   dom::Element* rootElement = GetRoot();
   NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER);
 
   // find first editable thingy
   nsCOMPtr<nsINode> firstNode = GetFirstEditableNode(rootElement);
   if (!firstNode) {
     // just the root node, set selection to inside the root
-    return selection->CollapseNative(rootElement, 0);
+    return selection->Collapse(rootElement, 0);
   }
 
   if (firstNode->NodeType() == nsIDOMNode::TEXT_NODE) {
     // If firstNode is text, set selection to beginning of the text node.
-    return selection->CollapseNative(firstNode, 0);
+    return selection->Collapse(firstNode, 0);
   }
 
   // Otherwise, it's a leaf node and we set the selection just in front of it.
   nsCOMPtr<nsIContent> parent = firstNode->GetParent();
   if (!parent) {
     return NS_ERROR_NULL_POINTER;
   }
 
   int32_t offsetInParent = parent->IndexOf(firstNode);
-  return selection->CollapseNative(parent, offsetInParent);
+  return selection->Collapse(parent, offsetInParent);
 }
 
 NS_IMETHODIMP
 EditorBase::EndOfDocument()
 {
   NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED);
 
   // get selection
@@ -1103,17 +1103,17 @@ EditorBase::EndOfDocument()
   nsINode* child = node->GetLastChild();
 
   while (child && IsContainer(child->AsDOMNode())) {
     node = child;
     child = node->GetLastChild();
   }
 
   uint32_t length = node->Length();
-  return selection->CollapseNative(node, int32_t(length));
+  return selection->Collapse(node, int32_t(length));
 }
 
 NS_IMETHODIMP
 EditorBase::GetDocumentModified(bool* outDocModified)
 {
   NS_ENSURE_TRUE(outDocModified, NS_ERROR_NULL_POINTER);
 
   int32_t  modCount = 0;
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -3375,22 +3375,22 @@ SetSelectionAroundHeadChildren(Selection
   // Set selection around <head> node
   nsCOMPtr<nsIDocument> doc = do_QueryReferent(aDocWeak);
   NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
 
   dom::Element* headNode = doc->GetHeadElement();
   NS_ENSURE_STATE(headNode);
 
   // Collapse selection to before first child of the head,
-  nsresult rv = aSelection->CollapseNative(headNode, 0);
+  nsresult rv = aSelection->Collapse(headNode, 0);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Then extend it to just after.
   uint32_t childCount = headNode->GetChildCount();
-  return aSelection->ExtendNative(headNode, childCount + 1);
+  return aSelection->Extend(headNode, childCount + 1);
 }
 
 NS_IMETHODIMP
 HTMLEditor::GetHeadContentsAsHTML(nsAString& aOutputString)
 {
   RefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
@@ -3712,17 +3712,17 @@ HTMLEditor::SetCaretInTableCell(nsIDOMEl
   while (node->HasChildren()) {
     node = node->GetFirstChild();
   }
 
   // Set selection at beginning of the found node
   RefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, false);
 
-  return NS_SUCCEEDED(selection->CollapseNative(node, 0));
+  return NS_SUCCEEDED(selection->Collapse(node, 0));
 }
 
 /**
  * GetEnclosingTable() finds ancestor who is a table, if any.
  */
 Element*
 HTMLEditor::GetEnclosingTable(nsINode* aNode)
 {
@@ -3815,17 +3815,17 @@ HTMLEditor::CollapseAdjacentTextNodes(ns
 }
 
 nsresult
 HTMLEditor::SetSelectionAtDocumentStart(Selection* aSelection)
 {
   dom::Element* rootElement = GetRoot();
   NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER);
 
-  return aSelection->CollapseNative(rootElement, 0);
+  return aSelection->Collapse(rootElement, 0);
 }
 
 /**
  * Remove aNode, reparenting any children into the parent of aNode.  In
  * addition, insert any br's needed to preserve identity of removed block.
  */
 nsresult
 HTMLEditor::RemoveBlockContainer(nsIContent& aNode)
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -1354,17 +1354,17 @@ TextEditRules::CreateBogusNodeIfNeeded(S
   newContent->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom,
                       kMOZEditorBogusNodeValue, false);
 
   // Put the node in the document.
   nsresult rv = mTextEditor->InsertNode(*mBogusNode, *body, 0);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Set selection.
-  aSelection->CollapseNative(body, 0);
+  aSelection->Collapse(body, 0);
   return NS_OK;
 }
 
 
 nsresult
 TextEditRules::TruncateInsertionIfNeeded(Selection* aSelection,
                                          const nsAString* aInString,
                                          nsAString* aOutString,
--- a/gfx/gl/SharedSurface.cpp
+++ b/gfx/gl/SharedSurface.cpp
@@ -12,16 +12,17 @@
 #include "GLScreenBuffer.h"
 #include "nsThreadUtils.h"
 #include "ScopedGLHelpers.h"
 #include "SharedSurfaceGL.h"
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/TextureClientSharedSurface.h"
 #include "mozilla/layers/TextureForwarder.h"
 #include "mozilla/Unused.h"
+#include "VRManagerChild.h"
 
 namespace mozilla {
 namespace gl {
 
 /*static*/ void
 SharedSurface::ProdCopy(SharedSurface* src, SharedSurface* dest,
                         SurfaceFactory* factory)
 {
@@ -312,25 +313,32 @@ SurfaceFactory::~SurfaceFactory()
     MOZ_RELEASE_ASSERT(mRecycleTotalPool.empty(),"GFX: Surface recycle pool not empty.");
 
     // If we mRecycleFreePool.clear() before StopRecycling(), we may try to recycle it,
     // fail, call StopRecycling(), then return here and call it again.
     mRecycleFreePool.clear();
 }
 
 already_AddRefed<layers::SharedSurfaceTextureClient>
-SurfaceFactory::NewTexClient(const gfx::IntSize& size)
+SurfaceFactory::NewTexClient(const gfx::IntSize& size, const layers::LayersIPCChannel* aLayersChannel)
 {
     while (!mRecycleFreePool.empty()) {
         RefPtr<layers::SharedSurfaceTextureClient> cur = mRecycleFreePool.front();
         mRecycleFreePool.pop();
 
-        if (cur->Surf()->mSize == size) {
-            cur->Surf()->WaitForBufferOwnership();
-            return cur.forget();
+        if (cur->Surf()->mSize == size){
+            // In the general case, textureClients transit textures through
+            // CompositorForwarder. But, the textureClient created by VRManagerChild
+            // has a different LayerIPCChannel, PVRManager. Therefore, textureClients
+            // need to be separated into different cases.
+            if ((aLayersChannel && aLayersChannel == cur->GetAllocator()) ||
+                (cur->GetAllocator() != gfx::VRManagerChild::Get())) {
+                cur->Surf()->WaitForBufferOwnership();
+                return cur.forget();
+            }
         }
 
         StopRecycling(cur);
     }
 
     UniquePtr<SharedSurface> surf = Move(CreateShared(size));
     if (!surf)
         return nullptr;
--- a/gfx/gl/SharedSurface.h
+++ b/gfx/gl/SharedSurface.h
@@ -301,17 +301,18 @@ protected:
 
     void StartRecycling(layers::SharedSurfaceTextureClient* tc);
     void SetRecycleCallback(layers::SharedSurfaceTextureClient* tc);
     void StopRecycling(layers::SharedSurfaceTextureClient* tc);
 
 public:
     UniquePtr<SharedSurface> NewSharedSurface(const gfx::IntSize& size);
     //already_AddRefed<ShSurfHandle> NewShSurfHandle(const gfx::IntSize& size);
-    already_AddRefed<layers::SharedSurfaceTextureClient> NewTexClient(const gfx::IntSize& size);
+    already_AddRefed<layers::SharedSurfaceTextureClient> NewTexClient(const gfx::IntSize& size,
+                                                                      const layers::LayersIPCChannel* aLayersChannel = nullptr);
 
     static void RecycleCallback(layers::TextureClient* tc, void* /*closure*/);
 
     // Auto-deletes surfs of the wrong type.
     bool Recycle(layers::SharedSurfaceTextureClient* texClient);
 };
 
 class ScopedReadbackFB
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -1053,17 +1053,17 @@ impl Renderer {
         let default_font_render_mode = match (options.enable_aa, options.enable_subpixel_aa) {
             (true, true) => FontRenderMode::Subpixel,
             (true, false) => FontRenderMode::Alpha,
             (false, _) => FontRenderMode::Mono,
         };
 
         let config = FrameBuilderConfig {
             enable_scrollbars: options.enable_scrollbars,
-            default_font_render_mode,
+            default_font_render_mode: default_font_render_mode,
             debug: options.debug,
             cache_expiry_frames: options.cache_expiry_frames,
         };
 
         let device_pixel_ratio = options.device_pixel_ratio;
         let render_target_debug = options.render_target_debug;
         let payload_tx_for_backend = payload_tx.clone();
         let recorder = options.recorder;
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -860,18 +860,16 @@ description =
 [PContent::BridgeToChildProcess]
 description =
 [PContent::LoadPlugin]
 description =
 [PContent::ConnectPluginBridge]
 description =
 [PContent::GetBlocklistState]
 description =
-[PContent::FindPlugins]
-description =
 [PContent::NSSU2FTokenIsCompatibleVersion]
 description =
 [PContent::NSSU2FTokenIsRegistered]
 description =
 [PContent::NSSU2FTokenRegister]
 description =
 [PContent::NSSU2FTokenSign]
 description =
--- a/js/src/builtin/embedjs.py
+++ b/js/src/builtin/embedjs.py
@@ -73,19 +73,19 @@ namespace %(namespace)s {
 
     uint32_t GetRawScriptsSize() {
         return %(raw_total_length)i;
     }
 } // selfhosted
 } // js
 """
 
-def embed(cxx, preprocessorOption, msgs, sources, c_out, js_out, namespace, env):
+def embed(cxx, preprocessorOption, cppflags, msgs, sources, c_out, js_out, namespace, env):
   combinedSources = '\n'.join([msgs] + ['#include "%(s)s"' % { 's': source } for source in sources])
-  args = ['-D%(k)s=%(v)s' % { 'k': k, 'v': env[k] } for k in env]
+  args = cppflags + ['-D%(k)s=%(v)s' % { 'k': k, 'v': env[k] } for k in env]
   preprocessed = preprocess(cxx, preprocessorOption, combinedSources, args)
   processed = '\n'.join([line for line in preprocessed.splitlines() if \
                          (line.strip() and not line.startswith('#'))])
 
   js_out.write(processed)
   import zlib
   compressed = zlib.compress(processed)
   data = ToCArray(compressed)
@@ -138,22 +138,24 @@ def get_config_defines(buildconfig):
     env[define] = 1
   return env
 
 def process_inputs(namespace, c_out, msg_file, inputs):
   deps = [path for path in inputs if path.endswith(".h") or path.endswith(".h.js")]
   sources = [path for path in inputs if path.endswith(".js") and not path.endswith(".h.js")]
   assert len(deps) + len(sources) == len(inputs)
   cxx = shlex.split(buildconfig.substs['CXX'])
-  cxx_option = buildconfig.substs['PREPROCESS_OPTION']
+  pp_option = buildconfig.substs['PREPROCESS_OPTION']
+  cppflags = shlex.split(buildconfig.substs['OS_CPPFLAGS'])
+  cppflags += shlex.split(buildconfig.substs['WARNINGS_AS_ERRORS'])
   env = get_config_defines(buildconfig)
   js_path = re.sub(r"\.out\.h$", "", c_out.name) + ".js"
   msgs = messages(msg_file)
   with open(js_path, 'w') as js_out:
-    embed(cxx, cxx_option, msgs, sources, c_out, js_out, namespace, env)
+    embed(cxx, pp_option, cppflags, msgs, sources, c_out, js_out, namespace, env)
 
 def generate_selfhosted(c_out, msg_file, *inputs):
   # Called from moz.build to embed selfhosted JS.
   process_inputs('selfhosted', c_out, msg_file, inputs)
 
 def generate_shellmoduleloader(c_out, msg_file, *inputs):
   # Called from moz.build to embed shell module loader JS.
   process_inputs('moduleloader', c_out, msg_file, inputs)
--- a/js/src/jit-test/jit_test.py
+++ b/js/src/jit-test/jit_test.py
@@ -230,19 +230,34 @@ def main(argv):
                 sys.stderr.write("Exception thrown trying to read test file"
                                  " '{}'\n".format(options.read_tests))
                 traceback.print_exc()
                 sys.stderr.write('---\n')
 
     if read_all:
         test_list = jittests.find_tests()
 
-    # If code coverage is enabled, exclude tests. (bug 1347245)
+    # Exclude tests when code coverage is enabled.
+    # This part is equivalent to:
+    # skip-if = coverage
     if os.getenv('GCOV_PREFIX') is not None:
-        options.exclude += ['asm.js/testSIMD.js']
+        # GCOV errors.
+        options.exclude += ['asm.js/testSIMD.js']               # Bug 1347245
+
+        # JSVM errors.
+        options.exclude += ['basic/functionnames.js']           # Bug 1369783
+        options.exclude += ['debug/Debugger-findScripts-23.js']
+        options.exclude += ['debug/bug1160182.js']
+        options.exclude += ['xdr/incremental-encoder.js']
+        options.exclude += ['xdr/bug1186973.js']                # Bug 1369785
+        options.exclude += ['basic/werror.js']
+
+        # Prevent code coverage test that expects coverage
+        # to be off when it starts.
+        options.exclude += ['debug/Script-getOffsetsCoverage-02.js']
 
     if options.exclude_from:
         with open(options.exclude_from) as fh:
             for line in fh:
                 line = line.strip()
                 if not line.startswith("#") and len(line):
                     options.exclude.append(line)
 
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -140,18 +140,17 @@ case "$target" in
     if test "$GCC" != "yes"; then
         # Check to see if we are really running in a msvc environemnt
         _WIN32_MSVC=1
 
         # Make sure compilers are valid
         CFLAGS="$CFLAGS -TC -nologo"
         CXXFLAGS="$CXXFLAGS -TP -nologo"
         if test -z "$CLANG_CL"; then
-            CFLAGS="$CFLAGS -utf-8"
-            CXXFLAGS="$CXXFLAGS -utf-8"
+            CPPFLAGS="$CPPFLAGS -utf-8"
         fi
         # MSVC warning C4800 warns when a value is implicitly cast to bool,
         # because this also forces narrowing to a single byte, which can be a
         # perf hit.  But this matters so little in practice (and often we want
         # that behavior) that it's better to turn it off.
         # _CRT_SECURE_NO_WARNINGS disables warnings about using MSVC-specific
         # secure CRT functions.
         # MSVC warning C4595 warns non-member operator new or delete functions
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -3930,16 +3930,23 @@ ContainerState::SetupMaskLayerForCSSMask
   maskLayer->SetContainer(imgContainer);
 
   if (isPaintFinished) {
     *oldUserData = Move(newUserData);
   }
   aLayer->SetMaskLayer(maskLayer);
 }
 
+static bool
+IsScrollThumbLayer(nsDisplayItem* aItem)
+{
+  return aItem->GetType() == nsDisplayItem::TYPE_OWN_LAYER &&
+         static_cast<nsDisplayOwnLayer*>(aItem)->IsScrollThumbLayer();
+}
+
 /*
  * Iterate through the non-clip items in aList and its descendants.
  * For each item we compute the effective clip rect. Each item is assigned
  * to a layer. We invalidate the areas in PaintedLayers where an item
  * has moved from one PaintedLayer to another. Also,
  * aState->mInvalidPaintedContent is invalidated in every PaintedLayer.
  * We set the clip rect for items that generated their own layer, and
  * create a mask layer to do any rounded rect clipping.
@@ -4199,18 +4206,19 @@ ContainerState::ProcessDisplayItems(nsDi
         // the whole clip chain is always just one fused clip.
         // Bug 1336516 aims to change that and to remove this workaround.
         AnimatedGeometryRoot* clipAGR = item->AnimatedGeometryRootForScrollMetadata();
         nsIntRect scrolledClipRect =
           ScaleToNearestPixels(itemClip.GetClipRect()) + mParameters.mOffset;
         mPaintedLayerDataTree.AddingOwnLayer(clipAGR,
                                              &scrolledClipRect,
                                              uniformColorPtr);
-      } else if (*animatedGeometryRoot == item->Frame() &&
-                 *animatedGeometryRoot != mBuilder->RootReferenceFrame()) {
+      } else if ((*animatedGeometryRoot == item->Frame() &&
+                  *animatedGeometryRoot != mBuilder->RootReferenceFrame()) ||
+                 (IsScrollThumbLayer(item) && mManager->IsWidgetLayerManager())) {
         // This is the case for scrollbar thumbs, for example. In that case the
         // clip we care about is the overflow:hidden clip on the scrollbar.
         mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot->mParentAGR,
                                              clipPtr,
                                              uniformColorPtr);
       } else if (prerenderedTransform) {
         mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot,
                                              clipPtr,
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -6105,16 +6105,24 @@ nsDisplayOwnLayer::nsDisplayOwnLayer(nsD
                                      bool aForceActive)
     : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot)
     , mFlags(aFlags)
     , mScrollTarget(aScrollTarget)
     , mThumbData(aThumbData)
     , mForceActive(aForceActive)
 {
   MOZ_COUNT_CTOR(nsDisplayOwnLayer);
+
+  // For scroll thumb layers, override the AGR to be the thumb's AGR rather
+  // than the AGR for mFrame (which is the slider frame).
+  if (IsScrollThumbLayer()) {
+    if (nsIFrame* thumbFrame = nsBox::GetChildXULBox(mFrame)) {
+      mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(thumbFrame);
+    }
+  }
 }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayOwnLayer::~nsDisplayOwnLayer() {
   MOZ_COUNT_DTOR(nsDisplayOwnLayer);
 }
 #endif
 
@@ -6125,16 +6133,30 @@ nsDisplayOwnLayer::GetLayerState(nsDispl
 {
   if (mForceActive) {
     return mozilla::LAYER_ACTIVE_FORCE;
   }
 
   return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, mAnimatedGeometryRoot);
 }
 
+bool
+nsDisplayOwnLayer::IsScrollThumbLayer() const
+{
+  return (mFlags & VERTICAL_SCROLLBAR) || (mFlags & HORIZONTAL_SCROLLBAR);
+}
+
+bool
+nsDisplayOwnLayer::ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder)
+{
+  // Render scroll thumb layers even if they are invisible, because async
+  // scrolling might bring them into view.
+  return IsScrollThumbLayer();
+}
+
 // nsDisplayOpacity uses layers for rendering
 already_AddRefed<Layer>
 nsDisplayOwnLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
                               LayerManager* aManager,
                               const ContainerLayerParameters& aContainerParameters)
 {
   RefPtr<ContainerLayer> layer = aManager->GetLayerBuilder()->
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -1423,16 +1423,17 @@ private:
   nsIFrame* FindAnimatedGeometryRootFrameFor(nsIFrame* aFrame);
 
   friend class nsDisplayCanvasBackgroundImage;
   friend class nsDisplayBackgroundImage;
   friend class nsDisplayFixedPosition;
   AnimatedGeometryRoot* FindAnimatedGeometryRootFor(nsDisplayItem* aItem);
 
   friend class nsDisplayItem;
+  friend class nsDisplayOwnLayer;
   AnimatedGeometryRoot* FindAnimatedGeometryRootFor(nsIFrame* aFrame);
 
   AnimatedGeometryRoot* WrapAGRForFrame(nsIFrame* aAnimatedGeometryRoot,
                                         AnimatedGeometryRoot* aParent = nullptr);
 
   nsDataHashtable<nsPtrHashKey<nsIFrame>, AnimatedGeometryRoot*> mFrameToAnimatedGeometryRootMap;
 
   /**
@@ -4074,32 +4075,33 @@ public:
                     const ActiveScrolledRoot* aActiveScrolledRoot,
                     uint32_t aFlags = 0,
                     ViewID aScrollTarget = mozilla::layers::FrameMetrics::NULL_SCROLL_ID,
                     const ScrollThumbData& aThumbData = ScrollThumbData{},
                     bool aForceActive = true);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayOwnLayer();
 #endif
-
+  virtual bool ShouldBuildLayerEvenIfInvisible(nsDisplayListBuilder* aBuilder) override;
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override;
   virtual bool TryMerge(nsDisplayItem* aItem) override
   {
     // Don't allow merging, each sublist must have its own layer
     return false;
   }
   virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
     return false;
   }
   uint32_t GetFlags() { return mFlags; }
+  bool IsScrollThumbLayer() const;
   NS_DISPLAY_DECL_NAME("OwnLayer", TYPE_OWN_LAYER)
 protected:
   uint32_t mFlags;
   ViewID mScrollTarget;
   // If this nsDisplayOwnLayer represents a scroll thumb layer, mThumbData
   // stores information about the scroll thumb. Otherwise, mThumbData will be
   // default-constructed (in particular with mDirection == ScrollDirection::NONE)
   // and can be ignored.
--- a/layout/reftests/reftest.list
+++ b/layout/reftests/reftest.list
@@ -371,16 +371,19 @@ include transform-3d/reftest.list
 # unicode/ (verify that we don't do expend effort doing unicode-aware case checks)
 include unicode/reftest.list
 
 # usercss
 include usercss/reftest.list
 
 include view-source/reftest.list
 
+# vr
+include ../../dom/vr/test/reftest/reftest.list
+
 # web-animations
 include web-animations/reftest.list
 
 # webcomponents/
 include webcomponents/reftest.list
 
 # widget/
 include ../../widget/reftests/reftest.list
--- a/layout/reftests/w3c-css/submitted/text-decor-3/reftest.list
+++ b/layout/reftests/w3c-css/submitted/text-decor-3/reftest.list
@@ -123,9 +123,9 @@ fuzzy-if(gtkWidget,3,4) == text-emphasis
 == text-emphasis-line-height-003b.html text-emphasis-line-height-003-ref.html
 == text-emphasis-line-height-003c.html text-emphasis-line-height-003-ref.html
 == text-emphasis-line-height-003d.html text-emphasis-line-height-003-ref.html
 == text-emphasis-line-height-004a.html text-emphasis-line-height-004-ref.html
 == text-emphasis-line-height-004b.html text-emphasis-line-height-004-ref.html
 == text-emphasis-line-height-004c.html text-emphasis-line-height-004-ref.html
 == text-emphasis-line-height-004d.html text-emphasis-line-height-004-ref.html
 # END tests from support/generate-text-emphasis-line-height-tests.py
-fuzzy-if(gtkWidget,3,4) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),43,1) fails-if(styloVsGecko||stylo) == text-emphasis-line-height-001z.html text-emphasis-line-height-001-ref.html
+fuzzy-if(gtkWidget,3,4) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),43,1) == text-emphasis-line-height-001z.html text-emphasis-line-height-001-ref.html
--- a/layout/tools/reftest/reftest-preferences.js
+++ b/layout/tools/reftest/reftest-preferences.js
@@ -17,16 +17,18 @@ user_pref("hangmonitor.timeout", 0);
 // Ensure autoplay is enabled for all platforms.
 user_pref("media.autoplay.enabled", true);
 // Disable updates
 user_pref("app.update.enabled", false);
 user_pref("app.update.staging.enabled", false);
 user_pref("app.update.url.android", "");
 // Ensure we can load the reftest extension
 user_pref("extensions.allow-non-mpc-extensions", true);
+user_pref("extensions.legacy.enabled", true);
+user_pref("security.turn_off_all_security_so_that_viruses_can_take_over_this_computer", true);
 // Disable addon updates and prefetching so we don't leak them
 user_pref("extensions.update.enabled", false);
 user_pref("extensions.systemAddon.update.url", "http://localhost/dummy-system-addons.xml");
 user_pref("extensions.getAddons.cache.enabled", false);
 // Disable blocklist updates so we don't have them reported as leaks
 user_pref("extensions.blocklist.enabled", false);
 // Make url-classifier updates so rare that they won't affect tests
 user_pref("urlclassifier.updateinterval", 172800);
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -320,20 +320,16 @@ class RefTest(object):
 
         addons = []
         if not self.use_marionette:
             addons.append(options.reftestExtensionPath)
 
         if options.specialPowersExtensionPath is not None:
             if not self.use_marionette:
                 addons.append(options.specialPowersExtensionPath)
-            # SpecialPowers requires insecure automation-only features that we
-            # put behind a pref.
-            prefs['security.turn_off_all_security_so_that_viruses'
-                  '_can_take_over_this_computer'] = True
 
         for pref in prefs:
             prefs[pref] = mozprofile.Preferences.cast(prefs[pref])
 
         # Install distributed extensions, if application has any.
         distExtDir = os.path.join(options.app[:options.app.rfind(os.sep)],
                                   "distribution", "extensions")
         if os.path.isdir(distExtDir):
--- a/layout/xul/nsSliderFrame.cpp
+++ b/layout/xul/nsSliderFrame.cpp
@@ -416,34 +416,51 @@ nsSliderFrame::BuildDisplayListForChildr
       gfxSize scale = nsLayoutUtils::GetTransformToAncestorScale(thumb);
       if (scale.width != 0 && scale.height != 0) {
         refSize.width /= scale.width;
         refSize.height /= scale.height;
       }
       nsRect dirty = aDirtyRect.Intersect(thumbRect);
       dirty = nsLayoutUtils::ComputePartialPrerenderArea(aDirtyRect, overflow, refSize);
 
+      // Clip the thumb layer to the slider track. This is necessary to ensure
+      // FrameLayerBuilder is able to merge content before and after the
+      // scrollframe into the same layer (otherwise it thinks the thumb could
+      // potentially move anywhere within the existing clip).
+      DisplayListClipState::AutoSaveRestore thumbClipState(aBuilder);
+      aBuilder->GetCurrentReferenceFrame();
+      thumbClipState.ClipContainingBlockDescendants(
+          GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this));
+
+      // Have the thumb's container layer capture the current clip, so
+      // it doesn't apply to the thumb's contents. This allows the contents
+      // to be fully rendered even if they're partially or fully offscreen,
+      // so async scrolling can still bring it into view.
+      DisplayListClipState::AutoSaveRestore thumbContentsClipState(aBuilder);
+      thumbContentsClipState.Clear();
+
       nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
       nsDisplayListCollection tempLists;
       nsBoxFrame::BuildDisplayListForChildren(aBuilder, dirty, tempLists);
 
       // This is a bit of a hack. Collect up all descendant display items
       // and merge them into a single Content() list.
       nsDisplayList masterList;
       masterList.AppendToTop(tempLists.BorderBackground());
       masterList.AppendToTop(tempLists.BlockBorderBackgrounds());
       masterList.AppendToTop(tempLists.Floats());
       masterList.AppendToTop(tempLists.Content());
       masterList.AppendToTop(tempLists.PositionedDescendants());
       masterList.AppendToTop(tempLists.Outlines());
 
+      // Restore the saved clip so it applies to the thumb container layer.
+      thumbContentsClipState.Restore();
+
       // Wrap the list to make it its own layer.
       const ActiveScrolledRoot* ownLayerASR = contASRTracker.GetContainerASR();
-      DisplayListClipState::AutoSaveRestore ownLayerClipState(aBuilder);
-      ownLayerClipState.ClearUpToASR(ownLayerASR);
       aLists.Content()->AppendNewToTop(new (aBuilder)
         nsDisplayOwnLayer(aBuilder, this, &masterList, ownLayerASR,
                           flags, scrollTargetId,
                           ScrollThumbData{scrollDirection,
                                           GetThumbRatio(),
                                           thumbStart,
                                           thumbLength,
                                           isAsyncDraggable,
--- a/media/webrtc/signaling/gtest/jsep_track_unittest.cpp
+++ b/media/webrtc/signaling/gtest/jsep_track_unittest.cpp
@@ -72,17 +72,18 @@ class JsepTrackTest : public ::testing::
             );
         results.push_back(ulpfec);
       }
 
       results.push_back(
           new JsepApplicationCodecDescription(
             "webrtc-datachannel",
             256,
-            5999
+            5999,
+            499
             ));
 
       // if we're doing something with red, it needs
       // to update the redundant encodings list
       if (red) {
         red->UpdateRedundantEncodings(results);
       }
 
@@ -1103,44 +1104,55 @@ TEST_F(JsepTrackTest, DataChannelDraft05
   OfferAnswer();
   CheckOffEncodingCount(1);
   CheckAnsEncodingCount(1);
 
   ASSERT_NE(std::string::npos,
             mOffer->ToString().find("a=sctpmap:5999 webrtc-datachannel 256"));
   ASSERT_NE(std::string::npos,
             mAnswer->ToString().find("a=sctpmap:5999 webrtc-datachannel 256"));
+  // Note: this is testing for a workaround, see bug 1335262 for details
+  ASSERT_NE(std::string::npos,
+            mOffer->ToString().find("a=max-message-size:499"));
+  ASSERT_NE(std::string::npos,
+            mAnswer->ToString().find("a=max-message-size:499"));
   ASSERT_EQ(std::string::npos, mOffer->ToString().find("a=sctp-port"));
   ASSERT_EQ(std::string::npos, mAnswer->ToString().find("a=sctp-port"));
 }
 
 TEST_F(JsepTrackTest, DataChannelDraft05AnswerWithDifferentPort)
 {
   mOffCodecs.values = MakeCodecs(false, false, false);
   mAnsCodecs.values = MakeCodecs(false, false, false);
 
   mOffCodecs.values.pop_back();
   mOffCodecs.values.push_back(
           new JsepApplicationCodecDescription(
             "webrtc-datachannel",
             256,
-            4555
+            4555,
+            10544
             ));
 
   InitTracks(SdpMediaSection::kApplication);
   InitSdp(SdpMediaSection::kApplication);
   OfferAnswer();
 
   CheckOffEncodingCount(1);
   CheckAnsEncodingCount(1);
 
   ASSERT_NE(std::string::npos,
             mOffer->ToString().find("a=sctpmap:4555 webrtc-datachannel 256"));
   ASSERT_NE(std::string::npos,
             mAnswer->ToString().find("a=sctpmap:5999 webrtc-datachannel 256"));
+  // Note: this is testing for a workaround, see bug 1335262 for details
+  ASSERT_NE(std::string::npos,
+            mOffer->ToString().find("a=max-message-size:10544"));
+  ASSERT_NE(std::string::npos,
+            mAnswer->ToString().find("a=max-message-size:499"));
   ASSERT_EQ(std::string::npos, mOffer->ToString().find("a=sctp-port"));
   ASSERT_EQ(std::string::npos, mAnswer->ToString().find("a=sctp-port"));
 }
 
 TEST_F(JsepTrackTest, DataChannelDraft21)
 {
   mOffCodecs.values = MakeCodecs(false, false, false);
   mAnsCodecs.values = MakeCodecs(false, false, false);
@@ -1164,16 +1176,18 @@ TEST_F(JsepTrackTest, DataChannelDraft21
       "0.0.0.0");
 
   OfferAnswer();
   CheckOffEncodingCount(1);
   CheckAnsEncodingCount(1);
 
   ASSERT_NE(std::string::npos, mOffer->ToString().find("a=sctp-port:5999"));
   ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=sctp-port:5999"));
+  ASSERT_NE(std::string::npos, mOffer->ToString().find("a=max-message-size:499"));
+  ASSERT_NE(std::string::npos, mAnswer->ToString().find("a=max-message-size:499"));
   ASSERT_EQ(std::string::npos, mOffer->ToString().find("a=sctpmap"));
   ASSERT_EQ(std::string::npos, mAnswer->ToString().find("a=sctpmap"));
 }
 
 static JsepTrack::JsConstraints
 MakeConstraints(const std::string& rid, uint32_t maxBitrate)
 {
   JsepTrack::JsConstraints constraints;
--- a/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
+++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h
@@ -4,16 +4,17 @@
 
 #ifndef _JSEPCODECDESCRIPTION_H_
 #define _JSEPCODECDESCRIPTION_H_
 
 #include <string>
 #include "signaling/src/sdp/SdpMediaSection.h"
 #include "signaling/src/sdp/SdpHelper.h"
 #include "nsCRT.h"
+#include "mozilla/net/DataChannelProtocol.h"
 
 namespace mozilla {
 
 #define JSEP_CODEC_CLONE(T)                                                    \
   virtual JsepCodecDescription* Clone() const override                         \
   {                                                                            \
     return new T(*this);                                                       \
   }
@@ -750,21 +751,24 @@ class JsepVideoCodecDescription : public
 };
 
 class JsepApplicationCodecDescription : public JsepCodecDescription {
   // This is the new draft-21 implementation
  public:
   JsepApplicationCodecDescription(const std::string& name,
                                   uint16_t channels,
                                   uint16_t localPort,
+                                  uint32_t localMaxMessageSize,
                                   bool enabled = true)
       : JsepCodecDescription(mozilla::SdpMediaSection::kApplication, "",
                              name, 0, channels, enabled),
         mLocalPort(localPort),
-        mRemotePort(0)
+        mLocalMaxMessageSize(localMaxMessageSize),
+        mRemotePort(0),
+        mRemoteMaxMessageSize(0)
   {
   }
 
   JSEP_CODEC_CLONE(JsepApplicationCodecDescription)
 
   // Override, uses sctpport or sctpmap instead of rtpmap
   virtual bool
   Matches(const std::string& fmt,
@@ -792,44 +796,55 @@ class JsepApplicationCodecDescription : 
     return false;
   }
 
   virtual void
   AddToMediaSection(SdpMediaSection& msection) const override
   {
     if (mEnabled && msection.GetMediaType() == mType) {
       if (msection.GetFormats().empty()) {
-        msection.AddDataChannel(mName, mLocalPort, mChannels);
+        msection.AddDataChannel(mName, mLocalPort, mChannels,
+                                mLocalMaxMessageSize);
       }
 
       AddParametersToMSection(msection);
     }
   }
 
   bool
   Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection) override
   {
     JsepCodecDescription::Negotiate(pt, remoteMsection);
 
+    uint32_t message_size = remoteMsection.GetMaxMessageSize();
+    if (message_size) {
+      mRemoteMaxMessageSize = message_size;
+    }
+
     int sctp_port = remoteMsection.GetSctpPort();
     if (sctp_port) {
       mRemotePort = sctp_port;
+      if (!message_size) {
+        mRemoteMaxMessageSize = WEBRTC_DATACHANELL_MAX_MESSAGE_SIZE_DEFAULT;
+      }
       return true;
     }
 
     const SdpSctpmapAttributeList::Sctpmap* sctp_map(
         remoteMsection.GetSctpmap());
     if (sctp_map) {
       mRemotePort = std::stoi(sctp_map->pt);
       return true;
     }
 
     return false;
   }
 
 
   uint16_t mLocalPort;
+  uint32_t mLocalMaxMessageSize;
   uint16_t mRemotePort;
+  uint32_t mRemoteMaxMessageSize;
 };
 
 } // namespace mozilla
 
 #endif
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -2363,17 +2363,18 @@ JsepSessionImpl::SetupDefaultCodecs()
       "ulpfec", // codec name
       90000     // clock rate (match other video codecs)
       );
   mSupportedCodecs.values.push_back(ulpfec);
 
   mSupportedCodecs.values.push_back(new JsepApplicationCodecDescription(
       "webrtc-datachannel",
       WEBRTC_DATACHANNEL_STREAMS_DEFAULT,
-      5000
+      WEBRTC_DATACHANNEL_PORT_DEFAULT,
+      WEBRTC_DATACHANELL_MAX_MESSAGE_SIZE_DEFAULT
       ));
 
   // Update the redundant encodings for the RED codec with the supported
   // codecs.  Note: only uses the video codecs.
   red->UpdateRedundantEncodings(mSupportedCodecs.values);
 }
 
 void
--- a/media/webrtc/signaling/src/jsep/JsepTrack.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp
@@ -270,19 +270,29 @@ JsepTrack::CreateEncodings(
   GetRids(remote, sdp::kRecv, &rids); // Get rids we will send
   NegotiateRids(rids, &mJsEncodeConstraints);
   if (rids.empty()) {
     // Add dummy value with an empty id to make sure we get a single unicast
     // stream.
     rids.push_back(SdpRidAttributeList::Rid());
   }
 
-  // For each rid in the remote, make sure we have an encoding, and configure
+  size_t max_streams = 1;
+
+  if (mJsEncodeConstraints.size()) {
+    max_streams = std::min(rids.size(), mJsEncodeConstraints.size());
+  }
+  // Drop SSRCs if less RIDs were offered than we have encoding constraints
+  if (mSsrcs.size() > max_streams) {
+    mSsrcs.resize(max_streams);
+  }
+
+  // For each stream make sure we have an encoding, and configure
   // that encoding appropriately.
-  for (size_t i = 0; i < rids.size(); ++i) {
+  for (size_t i = 0; i < max_streams; ++i) {
     if (i == negotiatedDetails->mEncodings.values.size()) {
       negotiatedDetails->mEncodings.values.push_back(new JsepTrackEncoding);
     }
 
     JsepTrackEncoding* encoding = negotiatedDetails->mEncodings.values[i];
 
     for (const JsepCodecDescription* codec : negotiatedCodecs) {
       if (rids[i].HasFormat(codec->mDefaultPt)) {
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1079,42 +1079,45 @@ PeerConnectionImpl::ConfigureJsepSession
   mJsepSession->SortCodecs(comparator);
   return NS_OK;
 }
 
 // Data channels won't work without a window, so in order for the C++ unit
 // tests to work (it doesn't have a window available) we ifdef the following
 // two implementations.
 NS_IMETHODIMP
-PeerConnectionImpl::EnsureDataConnection(uint16_t aNumstreams)
+PeerConnectionImpl::EnsureDataConnection(uint16_t aLocalPort,
+                                         uint16_t aNumstreams,
+                                         uint32_t aMaxMessageSize)
 {
   PC_AUTO_ENTER_API_CALL(false);
 
   if (mDataConnection) {
     CSFLogDebug(logTag,"%s DataConnection already connected",__FUNCTION__);
     // Ignore the request to connect when already connected.  This entire
     // implementation is temporary.  Ignore aNumstreams as it's merely advisory
     // and we increase the number of streams dynamically as needed.
     return NS_OK;
   }
   mDataConnection = new DataChannelConnection(this);
-  if (!mDataConnection->Init(5000, aNumstreams, true)) {
+  if (!mDataConnection->Init(aLocalPort, aNumstreams, true)) {
     CSFLogError(logTag,"%s DataConnection Init Failed",__FUNCTION__);
     return NS_ERROR_FAILURE;
   }
   CSFLogDebug(logTag,"%s DataChannelConnection %p attached to %s",
               __FUNCTION__, (void*) mDataConnection.get(), mHandle.c_str());
   return NS_OK;
 }
 
 nsresult
 PeerConnectionImpl::GetDatachannelParameters(
     uint32_t* channels,
     uint16_t* localport,
     uint16_t* remoteport,
+    uint32_t* remotemaxmessagesize,
     uint16_t* level) const {
 
   auto trackPairs = mJsepSession->GetNegotiatedTrackPairs();
   for (auto& trackPair : trackPairs) {
     bool sendDataChannel =
       trackPair.mSending &&
       trackPair.mSending->GetMediaType() == SdpMediaSection::kApplication;
     bool recvDataChannel =
@@ -1157,29 +1160,32 @@ PeerConnectionImpl::GetDatachannelParame
           *channels = codec->mChannels;
         } else {
           *channels = WEBRTC_DATACHANNEL_STREAMS_DEFAULT;
         }
         *localport =
           static_cast<const JsepApplicationCodecDescription*>(codec)->mLocalPort;
         *remoteport =
           static_cast<const JsepApplicationCodecDescription*>(codec)->mRemotePort;
+        *remotemaxmessagesize = static_cast<const JsepApplicationCodecDescription*>
+          (codec)->mRemoteMaxMessageSize;
         if (trackPair.HasBundleLevel()) {
           *level = static_cast<uint16_t>(trackPair.BundleLevel());
         } else {
           *level = static_cast<uint16_t>(trackPair.mLevel);
         }
         return NS_OK;
       }
     }
   }
 
   *channels = 0;
   *localport = 0;
   *remoteport = 0;
+  *remotemaxmessagesize = 0;
   *level = 0;
   return NS_ERROR_FAILURE;
 }
 
 /* static */
 void
 PeerConnectionImpl::DeferredAddTrackToJsepSession(
     const std::string& pcHandle,
@@ -1229,29 +1235,31 @@ nsresult
 PeerConnectionImpl::InitializeDataChannel()
 {
   PC_AUTO_ENTER_API_CALL(false);
   CSFLogDebug(logTag, "%s", __FUNCTION__);
 
   uint32_t channels = 0;
   uint16_t localport = 0;
   uint16_t remoteport = 0;
+  uint32_t remotemaxmessagesize = 0;
   uint16_t level = 0;
-  nsresult rv = GetDatachannelParameters(&channels, &localport, &remoteport, &level);
+  nsresult rv = GetDatachannelParameters(&channels, &localport, &remoteport,
+                                         &remotemaxmessagesize, &level);
 
   if (NS_FAILED(rv)) {
     CSFLogDebug(logTag, "%s: We did not negotiate datachannel", __FUNCTION__);
     return NS_OK;
   }
 
   if (channels > MAX_NUM_STREAMS) {
     channels = MAX_NUM_STREAMS;
   }
 
-  rv = EnsureDataConnection(channels);
+  rv = EnsureDataConnection(localport, channels, remotemaxmessagesize);
   if (NS_SUCCEEDED(rv)) {
     // use the specified TransportFlow
     RefPtr<TransportFlow> flow = mMedia->GetTransportFlow(level, false).get();
     CSFLogDebug(logTag, "Transportflow[%u] = %p",
                         static_cast<unsigned>(level), flow.get());
     if (flow) {
       if (mDataConnection->ConnectViaTransportFlow(flow,
                                                    localport,
@@ -1297,17 +1305,19 @@ PeerConnectionImpl::CreateDataChannel(co
 {
   PC_AUTO_ENTER_API_CALL(false);
   MOZ_ASSERT(aRetval);
 
   RefPtr<DataChannel> dataChannel;
   DataChannelConnection::Type theType =
     static_cast<DataChannelConnection::Type>(aType);
 
-  nsresult rv = EnsureDataConnection(WEBRTC_DATACHANNEL_STREAMS_DEFAULT);
+  nsresult rv = EnsureDataConnection(WEBRTC_DATACHANNEL_PORT_DEFAULT,
+                                     WEBRTC_DATACHANNEL_STREAMS_DEFAULT,
+                                     WEBRTC_DATACHANELL_MAX_MESSAGE_SIZE_DEFAULT);
   if (NS_FAILED(rv)) {
     return rv;
   }
   dataChannel = mDataConnection->Open(
     NS_ConvertUTF16toUTF8(aLabel), NS_ConvertUTF16toUTF8(aProtocol), theType,
     ordered,
     aType == DataChannelConnection::PARTIAL_RELIABLE_REXMIT ? aMaxNum :
     (aType == DataChannelConnection::PARTIAL_RELIABLE_TIMED ? aMaxTime : 0),
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -641,17 +641,18 @@ public:
 private:
   virtual ~PeerConnectionImpl();
   PeerConnectionImpl(const PeerConnectionImpl&rhs);
   PeerConnectionImpl& operator=(PeerConnectionImpl);
   nsresult CalculateFingerprint(const std::string& algorithm,
                                 std::vector<uint8_t>* fingerprint) const;
   nsresult ConfigureJsepSessionCodecs();
 
-  NS_IMETHODIMP EnsureDataConnection(uint16_t aNumstreams);
+  NS_IMETHODIMP EnsureDataConnection(uint16_t aLocalPort, uint16_t aNumstreams,
+                                     uint32_t aMaxMessageSize);
 
   nsresult CloseInt();
   nsresult CheckApiState(bool assert_ice_ready) const;
   void CheckThread() const {
     MOZ_ASSERT(CheckThreadInt(), "Wrong thread");
   }
   bool CheckThreadInt() const {
     bool on;
@@ -674,16 +675,17 @@ private:
   void SendLocalIceCandidateToContent(uint16_t level,
                                       const std::string& mid,
                                       const std::string& candidate);
 
   nsresult GetDatachannelParameters(
       uint32_t* channels,
       uint16_t* localport,
       uint16_t* remoteport,
+      uint32_t* maxmessagesize,
       uint16_t* level) const;
 
   static void DeferredAddTrackToJsepSession(const std::string& pcHandle,
                                             SdpMediaSection::MediaType type,
                                             const std::string& streamId,
                                             const std::string& trackId);
 
   nsresult AddTrackToJsepSession(SdpMediaSection::MediaType type,
--- a/media/webrtc/signaling/src/sdp/SdpAttributeList.h
+++ b/media/webrtc/signaling/src/sdp/SdpAttributeList.h
@@ -59,17 +59,18 @@ public:
   virtual const SdpImageattrAttributeList& GetImageattr() const = 0;
   virtual const SdpSimulcastAttribute& GetSimulcast() const = 0;
   virtual const SdpMsidAttributeList& GetMsid() const = 0;
   virtual const SdpMsidSemanticAttributeList& GetMsidSemantic() const = 0;
   virtual const SdpRidAttributeList& GetRid() const = 0;
   virtual const SdpRtcpFbAttributeList& GetRtcpFb() const = 0;
   virtual const SdpRtpmapAttributeList& GetRtpmap() const = 0;
   virtual const SdpSctpmapAttributeList& GetSctpmap() const = 0;
-  virtual unsigned int GetSctpPort() const = 0;
+  virtual uint32_t GetSctpPort() const = 0;
+  virtual uint32_t GetMaxMessageSize() const = 0;
   virtual const SdpSsrcAttributeList& GetSsrc() const = 0;
   virtual const SdpSsrcGroupAttributeList& GetSsrcGroup() const = 0;
 
   // These attributes are effectively simple types, so we'll make life
   // easy by just returning their value.
   virtual const std::string& GetIcePwd() const = 0;
   virtual const std::string& GetIceUfrag() const = 0;
   virtual const std::string& GetIdentity() const = 0;
--- a/media/webrtc/signaling/src/sdp/SdpHelper.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpHelper.cpp
@@ -163,17 +163,17 @@ SdpHelper::DisableMsection(Sdp* sdp, Sdp
   switch (mediaType) {
     case SdpMediaSection::kAudio:
       msection->AddCodec("0", "PCMU", 8000, 1);
       break;
     case SdpMediaSection::kVideo:
       msection->AddCodec("120", "VP8", 90000, 1);
       break;
     case SdpMediaSection::kApplication:
-      msection->AddDataChannel("rejected", 0, 0);
+      msection->AddDataChannel("rejected", 0, 0, 0);
       break;
     default:
       // We need to have something here to fit the grammar, this seems safe
       // and 19 is a reserved payload type which should not be used by anyone.
       msection->AddCodec("19", "reserved", 8000, 1);
   }
 }
 
--- a/media/webrtc/signaling/src/sdp/SdpMediaSection.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpMediaSection.cpp
@@ -94,26 +94,36 @@ SdpMediaSection::GetSctpmap() const
   const SdpSctpmapAttributeList& sctpmap = attrs.GetSctpmap();
   if (!sctpmap.mSctpmaps.size()) {
     return nullptr;
   }
 
   return &sctpmap.GetFirstEntry();
 }
 
-int
+uint32_t
 SdpMediaSection::GetSctpPort() const
 {
   auto& attrs = GetAttributeList();
   if (!attrs.HasAttribute(SdpAttribute::kSctpPortAttribute)) {
     return 0;
   }
 
-  uint32_t val = attrs.GetSctpPort();
-  return val;
+  return attrs.GetSctpPort();
+}
+
+uint32_t
+SdpMediaSection::GetMaxMessageSize() const
+{
+  auto& attrs = GetAttributeList();
+  if (!attrs.HasAttribute(SdpAttribute::kMaxMessageSizeAttribute)) {
+    return 0;
+  }
+
+  return attrs.GetMaxMessageSize();
 }
 
 bool
 SdpMediaSection::HasRtcpFb(const std::string& pt,
                            SdpRtcpFbAttributeList::Type type,
                            const std::string& subType) const
 {
   const SdpAttributeList& attrs(GetAttributeList());
--- a/media/webrtc/signaling/src/sdp/SdpMediaSection.h
+++ b/media/webrtc/signaling/src/sdp/SdpMediaSection.h
@@ -91,18 +91,18 @@ public:
   virtual SdpDirectionAttribute GetDirectionAttribute() const = 0;
 
   virtual void Serialize(std::ostream&) const = 0;
 
   virtual void AddCodec(const std::string& pt, const std::string& name,
                         uint32_t clockrate, uint16_t channels) = 0;
   virtual void ClearCodecs() = 0;
 
-  virtual void AddDataChannel(const std::string& name,
-                              uint16_t port, uint16_t streams) = 0;
+  virtual void AddDataChannel(const std::string& name, uint16_t port,
+                              uint16_t streams, uint32_t message_size) = 0;
 
   size_t
   GetLevel() const
   {
     return mLevel;
   }
 
   inline bool
@@ -152,17 +152,18 @@ public:
     GetAttributeList().SetAttribute(new SdpDirectionAttribute(direction));
   }
 
   const SdpFmtpAttributeList::Parameters* FindFmtp(const std::string& pt) const;
   void SetFmtp(const SdpFmtpAttributeList::Fmtp& fmtp);
   void RemoveFmtp(const std::string& pt);
   const SdpRtpmapAttributeList::Rtpmap* FindRtpmap(const std::string& pt) const;
   const SdpSctpmapAttributeList::Sctpmap* GetSctpmap() const;
-  int GetSctpPort() const;
+  uint32_t GetSctpPort() const;
+  uint32_t GetMaxMessageSize() const;
   bool HasRtcpFb(const std::string& pt,
                  SdpRtcpFbAttributeList::Type type,
                  const std::string& subType) const;
   SdpRtcpFbAttributeList GetRtcpFbs() const;
   void SetRtcpFbs(const SdpRtcpFbAttributeList& rtcpfbs);
   bool HasFormat(const std::string& format) const
   {
     return std::find(GetFormats().begin(), GetFormats().end(), format) !=
--- a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
+++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp
@@ -1382,16 +1382,27 @@ SipccSdpAttributeList::GetSctpPort() con
   if (!HasAttribute(SdpAttribute::kSctpPortAttribute)) {
     MOZ_CRASH();
   }
 
   const SdpAttribute* attr = GetAttribute(SdpAttribute::kSctpPortAttribute);
   return static_cast<const SdpNumberAttribute*>(attr)->mValue;
 }
 
+uint32_t
+SipccSdpAttributeList::GetMaxMessageSize() const
+{
+  if (!HasAttribute(SdpAttribute::kMaxMessageSizeAttribute)) {
+    MOZ_CRASH();
+  }
+
+  const SdpAttribute* attr = GetAttribute(SdpAttribute::kMaxMessageSizeAttribute);
+  return static_cast<const SdpNumberAttribute*>(attr)->mValue;
+}
+
 const SdpSetupAttribute&
 SipccSdpAttributeList::GetSetup() const
 {
   if (!HasAttribute(SdpAttribute::kSetupAttribute)) {
     MOZ_CRASH();
   }
   const SdpAttribute* attr = GetAttribute(SdpAttribute::kSetupAttribute);
   return *static_cast<const SdpSetupAttribute*>(attr);
--- a/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.h
+++ b/media/webrtc/signaling/src/sdp/SipccSdpAttributeList.h
@@ -60,17 +60,18 @@ public:
   const SdpSimulcastAttribute& GetSimulcast() const override;
   virtual const SdpMsidAttributeList& GetMsid() const override;
   virtual const SdpMsidSemanticAttributeList& GetMsidSemantic()
     const override;
   const SdpRidAttributeList& GetRid() const override;
   virtual const SdpRtcpFbAttributeList& GetRtcpFb() const override;
   virtual const SdpRtpmapAttributeList& GetRtpmap() const override;
   virtual const SdpSctpmapAttributeList& GetSctpmap() const override;
-  virtual unsigned int GetSctpPort() const override;
+  virtual uint32_t GetSctpPort() const override;
+  virtual uint32_t GetMaxMessageSize() const override;
 
   // These attributes are effectively simple types, so we'll make life
   // easy by just returning their value.
   virtual const std::string& GetIcePwd() const override;
   virtual const std::string& GetIceUfrag() const override;
   virtual const std::string& GetIdentity() const override;
   virtual const std::string& GetLabel() const override;
   virtual unsigned int GetMaxptime() const override;
--- a/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp
+++ b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp
@@ -393,34 +393,43 @@ SipccSdpMediaSection::ClearCodecs()
   mAttributeList.RemoveAttribute(SdpAttribute::kRtpmapAttribute);
   mAttributeList.RemoveAttribute(SdpAttribute::kFmtpAttribute);
   mAttributeList.RemoveAttribute(SdpAttribute::kSctpmapAttribute);
   mAttributeList.RemoveAttribute(SdpAttribute::kRtcpFbAttribute);
 }
 
 void
 SipccSdpMediaSection::AddDataChannel(const std::string& name, uint16_t port,
-                                     uint16_t streams)
+                                     uint16_t streams, uint32_t message_size)
 {
   // Only one allowed, for now. This may change as the specs (and deployments)
   // evolve.
   mFormats.clear();
   if ((mProtocol == kUdpDtlsSctp) ||
       (mProtocol == kTcpDtlsSctp)) {
     // new data channel format according to draft 21
     mFormats.push_back(name);
     mAttributeList.SetAttribute(new SdpNumberAttribute(
           SdpAttribute::kSctpPortAttribute, port));
+    if (message_size) {
+      mAttributeList.SetAttribute(new SdpNumberAttribute(
+            SdpAttribute::kMaxMessageSizeAttribute, message_size));
+    }
   } else {
     // old data channels format according to draft 05
     std::string port_str = std::to_string(port);
     mFormats.push_back(port_str);
     SdpSctpmapAttributeList* sctpmap = new SdpSctpmapAttributeList();
     sctpmap->PushEntry(port_str, name, streams);
     mAttributeList.SetAttribute(sctpmap);
+    if (message_size) {
+      // This is a workaround to allow detecting Firefox's w/o EOR support
+      mAttributeList.SetAttribute(new SdpNumberAttribute(
+            SdpAttribute::kMaxMessageSizeAttribute, message_size));
+    }
   }
 }
 
 void
 SipccSdpMediaSection::Serialize(std::ostream& os) const
 {
   os << "m=" << mMediaType << " " << mPort;
   if (mPortCount) {
--- a/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h
+++ b/media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h
@@ -56,18 +56,18 @@ public:
   virtual const SdpAttributeList& GetAttributeList() const override;
   virtual SdpAttributeList& GetAttributeList() override;
   virtual SdpDirectionAttribute GetDirectionAttribute() const override;
 
   virtual void AddCodec(const std::string& pt, const std::string& name,
                         uint32_t clockrate, uint16_t channels) override;
   virtual void ClearCodecs() override;
 
-  virtual void AddDataChannel(const std::string& name,
-                              uint16_t port, uint16_t streams) override;
+  virtual void AddDataChannel(const std::string& name, uint16_t port,
+                              uint16_t streams, uint32_t message_size) override;
 
   virtual void Serialize(std::ostream&) const override;
 
 private:
   SipccSdpMediaSection(size_t level, const SipccSdpAttributeList* sessionLevel)
       : SdpMediaSection(level), mAttributeList(sessionLevel)
   {
   }
--- a/netwerk/sctp/datachannel/DataChannelProtocol.h
+++ b/netwerk/sctp/datachannel/DataChannelProtocol.h
@@ -11,18 +11,19 @@
 #define SCTP_PACKED __attribute__((packed))
 #elif defined(_MSC_VER)
 #pragma pack (push, 1)
 #define SCTP_PACKED
 #else
 #error "Unsupported compiler"
 #endif
 
-// Duplicated in fsm.def
-#define WEBRTC_DATACHANNEL_STREAMS_DEFAULT 256
+#define WEBRTC_DATACHANNEL_STREAMS_DEFAULT          256
+#define WEBRTC_DATACHANNEL_PORT_DEFAULT             5000
+#define WEBRTC_DATACHANELL_MAX_MESSAGE_SIZE_DEFAULT 65536
 
 #define DATA_CHANNEL_PPID_CONTROL        50
 #define DATA_CHANNEL_PPID_BINARY         52
 #define DATA_CHANNEL_PPID_BINARY_LAST    53
 #define DATA_CHANNEL_PPID_DOMSTRING      54
 #define DATA_CHANNEL_PPID_DOMSTRING_LAST 51
 
 #define DATA_CHANNEL_MAX_BINARY_FRAGMENT 0x4000
--- a/old-configure.in
+++ b/old-configure.in
@@ -937,17 +937,17 @@ case "$target" in
 
         WIN32_CONSOLE_EXE_LDFLAGS=-mconsole
         WIN32_GUI_EXE_LDFLAGS=-mwindows
 
         # GCC/binutils can't link to a function if we try to include dllexport function
         # in the same library as dllimport caller. To work around it, we build NSPR
         # and NSS with -mnop-fun-dllimport flag. The drawback of this solution is that
         # function thunks need to be generated for cross-DLL calls.
-        MOZ_FOLD_LIBS_FLAGS=-mnop-fun-dllimport
+        MOZ_FOLD_LIBS_FLAGS="-mnop-fun-dllimport"
     else
         TARGET_COMPILER_ABI=msvc
         if test "$AS_BIN"; then
             AS="$(basename "$AS_BIN")"
         fi
         AR='lib'
         AR_FLAGS='-NOLOGO -OUT:$@'
         AR_EXTRACT=
@@ -968,18 +968,17 @@ case "$target" in
         WIN32_GUI_EXE_LDFLAGS=-SUBSYSTEM:WINDOWS,$WIN32_SUBSYSTEM_VERSION
         DSO_LDOPTS=-SUBSYSTEM:WINDOWS,$WIN32_SUBSYSTEM_VERSION
         _USE_CPP_INCLUDE_FLAG=1
         _DEFINES_CFLAGS='-FI $(topobjdir)/mozilla-config.h -DMOZILLA_CLIENT'
         _DEFINES_CXXFLAGS='-FI $(topobjdir)/mozilla-config.h -DMOZILLA_CLIENT'
         CFLAGS="$CFLAGS -W3 -Gy -Zc:inline"
         CXXFLAGS="$CXXFLAGS -W3 -Gy -Zc:inline"
         if test -z "$CLANG_CL"; then
-            CFLAGS="$CFLAGS -utf-8"
-            CXXFLAGS="$CXXFLAGS -utf-8"
+            CPPFLAGS="$CPPFLAGS -utf-8"
         fi
         if test "$CPU_ARCH" = "x86"; then
             dnl VS2012+ defaults to -arch:SSE2. We want to target nothing
             dnl more recent, so set that explicitly here unless another
             dnl target arch has already been set.
             changequote(,)
             if test -z `echo $CFLAGS | grep -i [-/]arch:`; then
               CFLAGS="$CFLAGS -arch:SSE2"
@@ -5346,17 +5345,17 @@ AC_SUBST(MOZ_CODE_COVERAGE)
 AC_SUBST(LIBJPEG_TURBO_USE_YASM)
 AC_SUBST_LIST(LIBJPEG_TURBO_ASFLAGS)
 AC_SUBST(MOZ_LIBAV_FFT)
 AC_SUBST_LIST(LIBAV_FFT_ASFLAGS)
 AC_SUBST(MOZ_DEVTOOLS)
 
 AC_SUBST(MOZ_PACKAGE_JSSHELL)
 AC_SUBST(MOZ_FOLD_LIBS)
-AC_SUBST(MOZ_FOLD_LIBS_FLAGS)
+AC_SUBST_LIST(MOZ_FOLD_LIBS_FLAGS)
 AC_SUBST(SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE)
 
 AC_SUBST(DMG_TOOL)
 
 dnl Host JavaScript runtime, if any, to use during cross compiles.
 AC_SUBST(JS_BINARY)
 
 AC_SUBST(NSS_EXTRA_SYMBOLS_FILE)
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -139,17 +139,17 @@ class BuildProgressFooter(object):
         # terminal is a blessings.Terminal.
         self._t = terminal
         self._fh = sys.stdout
         self.tiers = monitor.tiers.tier_status.viewitems()
 
     def clear(self):
         """Removes the footer from the current terminal."""
         self._fh.write(self._t.move_x(0))
-        self._fh.write(self._t.clear_eos())
+        self._fh.write(self._t.clear_eol())
 
     def draw(self):
         """Draws this footer in the terminal."""
 
         if not self.tiers:
             return
 
         # The drawn terminal looks something like:
@@ -1743,30 +1743,27 @@ class PackageFrontend(MachCommandBase):
                         for task in tasks
                     }
 
             toolchains = tasks('toolchain')
 
             for b in from_build:
                 user_value = b
 
-                if '/' not in b:
-                    b = '{}/opt'.format(b)
-
                 if not b.startswith('toolchain-'):
                     b = 'toolchain-{}'.format(b)
 
                 task = toolchains.get(b)
                 if not task:
                     self.log(logging.ERROR, 'artifact', {'build': user_value},
                              'Could not find a toolchain build named `{build}`')
                     return 1
 
-                optimized, task_id = optimize_task(task, {})
-                if not optimized:
+                task_id = optimize_task(task, {})
+                if task_id in (True, False):
                     self.log(logging.ERROR, 'artifact', {'build': user_value},
                              'Could not find artifacts for a toolchain build '
                              'named `{build}`')
                     return 1
 
                 for artifact in list_artifacts(task_id):
                     name = artifact['name']
                     if not name.startswith('public/'):
--- a/python/mozbuild/mozbuild/vendor_rust.py
+++ b/python/mozbuild/mozbuild/vendor_rust.py
@@ -81,20 +81,20 @@ Please commit or stash these changes bef
         test_paths = ['/usr/include', '/usr/local/include']
         if any([os.path.exists(os.path.join(path, 'openssl/ssl.h')) for path in test_paths]):
             # Assume we can use one of these system headers.
             return None
 
         if os.path.exists('/usr/local/opt/openssl/include/openssl/ssl.h'):
             # Found a likely homebrew install.
             self.log(logging.INFO, 'openssl', {},
-                    'Using OpenSSL in /usr/local/opt/openssl')
+                     'Using OpenSSL in /usr/local/opt/openssl')
             return {
-                 'OPENSSL_INCLUDE_DIR': '/usr/local/opt/openssl/include',
-                 'DEP_OPENSSL_INCLUDE': '/usr/local/opt/openssl/include',
+                'OPENSSL_INCLUDE_DIR': '/usr/local/opt/openssl/include',
+                'OPENSSL_LIB_DIR': '/usr/local/opt/openssl/lib',
             }
 
         self.log(logging.ERROR, 'openssl', {}, "OpenSSL not found!")
         return None
 
     def _ensure_cargo(self):
         '''
         Ensures all the necessary cargo bits are installed.
--- a/security/generate_mapfile.py
+++ b/security/generate_mapfile.py
@@ -7,43 +7,49 @@
 # This script processes NSS .def files according to the rules defined in
 # a comment at the top of each one. The files are used to define the
 # exports from NSS shared libraries, with -DEFFILE on Windows, a linker
 # script on Linux, or with -exported_symbols_list on OS X.
 #
 # The NSS build system processes them using a series of sed replacements,
 # but the Mozilla build system is already running a Python script to generate
 # the file so it's simpler to just do the replacement in Python.
+#
+# One difference between the NSS build system and Mozilla's is that
+# Mozilla's supports building on Linux for Windows using MinGW. MinGW
+# expects all lines containing ;+ removed and all ;- tokens removed.
 
 import buildconfig
 
 
 def main(output, input):
     is_darwin = buildconfig.substs['OS_ARCH'] == 'Darwin'
+    is_mingw = "WINNT" == buildconfig.substs['OS_ARCH'] and buildconfig.substs['GCC_USE_GNU_LD']
 
     with open(input, 'rb') as f:
         for line in f:
             line = line.rstrip()
-            # Remove all lines containing ';-'
-            if ';-' in line:
+            # On everything except MinGW, remove all lines containing ';-'
+            if not is_mingw and ';-' in line:
                 continue
             # On OS X, remove all lines containing ';+'
             if is_darwin and ';+' in line:
                 continue
             # Remove the string ' DATA '.
             line = line.replace(' DATA ', '')
             # Remove the string ';+'
-            line = line.replace(';+', '')
+            if not is_mingw:
+                line = line.replace(';+', '')
             # Remove the string ';;'
             line = line.replace(';;', '')
             # If a ';' is present, remove everything after it,
             # and on OS X, remove it as well.
             i = line.find(';')
             if i != -1:
-                if is_darwin:
+                if is_darwin or is_mingw:
                     line = line[:i]
                 else:
                     line = line[:i+1]
             # On OS X, symbols get an underscore in front.
             if line and is_darwin:
                 output.write('_')
             output.write(line)
             output.write('\n')
--- a/security/moz.build
+++ b/security/moz.build
@@ -88,16 +88,18 @@ else:
     gyp_vars['nspr_include_dir'] = CONFIG['NSPR_INCLUDE_DIR']
     gyp_vars['nspr_lib_dir'] = CONFIG['NSPR_LIB_DIR']
     # The Python scripts that detect clang need it to be set as CC
     # in the environment, which isn't true here. I don't know that
     # setting that would be harmful, but we already have this information
     # anyway.
     if CONFIG['CLANG_CXX'] or CONFIG['CLANG_CL']:
         gyp_vars['cc_is_clang'] = 1
+    if CONFIG['GCC_USE_GNU_LD']:
+        gyp_vars['cc_use_gnu_ld'] = 1
 
     GYP_DIRS += ['nss']
     GYP_DIRS['nss'].input = 'nss/nss.gyp'
     GYP_DIRS['nss'].variables = gyp_vars
 
     sandbox_vars = {
         # NSS explicitly exports its public symbols
         # with linker scripts.
@@ -108,16 +110,18 @@ else:
         # We could probably do so, but not without a lot of
         # careful consideration.
         'NO_PGO': True,
     }
     if CONFIG['OS_TARGET'] == 'WINNT':
         if CONFIG['CPU_ARCH'] == 'x86':
             # This should really be the default.
             sandbox_vars['ASFLAGS'] = ['-safeseh']
+        if CONFIG['MOZ_FOLD_LIBS_FLAGS']:
+            sandbox_vars['CFLAGS'] = CONFIG['MOZ_FOLD_LIBS_FLAGS']
     if CONFIG['OS_TARGET'] == 'Android':
         sandbox_vars['CFLAGS'] = [
             '-include', TOPSRCDIR + '/security/manager/android_stub.h',
             # Setting sandbox_vars['DEFINES'] is broken currently.
             '-DCHECK_FORK_GETPID',
         ]
         if CONFIG['ANDROID_VERSION']:
             sandbox_vars['CFLAGS'] += ['-DANDROID_VERSION=' + CONFIG['ANDROID_VERSION']]
--- a/servo/components/layout/text.rs
+++ b/servo/components/layout/text.rs
@@ -18,21 +18,22 @@ use gfx::text::util::{self, CompressionM
 use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFragments, LAST_FRAGMENT_OF_ELEMENT};
 use linked_list::split_off_head;
 use ordered_float::NotNaN;
 use range::Range;
 use std::borrow::ToOwned;
 use std::collections::LinkedList;
 use std::mem;
 use std::sync::Arc;
-use style::computed_values::{line_height, text_rendering, text_transform};
+use style::computed_values::{text_rendering, text_transform};
 use style::computed_values::{word_break, white_space};
 use style::logical_geometry::{LogicalSize, WritingMode};
 use style::properties::ServoComputedValues;
 use style::properties::style_structs;
+use style::values::generics::text::LineHeight;
 use unicode_bidi as bidi;
 use unicode_script::{Script, get_script};
 
 /// Returns the concatenated text of a list of unscanned text fragments.
 fn text(fragments: &LinkedList<Fragment>) -> String {
     // FIXME: Some of this work is later duplicated in split_first_fragment_at_newline_if_necessary
     // and transform_text.  This code should be refactored so that the all the scanning for
     // newlines is done in a single pass.
@@ -160,18 +161,18 @@ impl TextRunScanner {
                 compression = match in_fragment.white_space() {
                     white_space::T::normal |
                     white_space::T::nowrap => CompressionMode::CompressWhitespaceNewline,
                     white_space::T::pre |
                     white_space::T::pre_wrap => CompressionMode::CompressNone,
                     white_space::T::pre_line => CompressionMode::CompressWhitespace,
                 };
                 text_transform = inherited_text_style.text_transform;
-                letter_spacing = inherited_text_style.letter_spacing.0;
-                word_spacing = inherited_text_style.word_spacing.0
+                letter_spacing = inherited_text_style.letter_spacing;
+                word_spacing = inherited_text_style.word_spacing.value()
                                .map(|lop| lop.to_hash_key())
                                .unwrap_or((Au(0), NotNaN::new(0.0).unwrap()));
                 text_rendering = inherited_text_style.text_rendering;
                 word_break = inherited_text_style.word_break;
             }
 
             // First, transform/compress text of all the nodes.
             let (mut run_info_list, mut run_info) = (Vec::new(), RunInfo::new());
@@ -283,29 +284,29 @@ impl TextRunScanner {
             // Push the final run info.
             run_info.flush(&mut run_info_list, &mut insertion_point);
 
             // Per CSS 2.1 § 16.4, "when the resultant space between two characters is not the same
             // as the default space, user agents should not use ligatures." This ensures that, for
             // example, `finally` with a wide `letter-spacing` renders as `f i n a l l y` and not
             // `fi n a l l y`.
             let mut flags = ShapingFlags::empty();
-            match letter_spacing {
-                Some(Au(0)) | None => {}
+            match letter_spacing.value() {
+                Some(&Au(0)) | None => {}
                 Some(_) => flags.insert(IGNORE_LIGATURES_SHAPING_FLAG),
             }
             if text_rendering == text_rendering::T::optimizespeed {
                 flags.insert(IGNORE_LIGATURES_SHAPING_FLAG);
                 flags.insert(DISABLE_KERNING_SHAPING_FLAG)
             }
             if word_break == word_break::T::keep_all {
                 flags.insert(KEEP_ALL_FLAG);
             }
             let options = ShapingOptions {
-                letter_spacing: letter_spacing,
+                letter_spacing: letter_spacing.value().cloned(),
                 word_spacing: word_spacing,
                 script: Script::Common,
                 flags: flags,
             };
 
             // FIXME(https://github.com/rust-lang/rust/issues/23338)
             run_info_list.into_iter().map(|run_info| {
                 let mut options = options;
@@ -442,19 +443,19 @@ pub fn font_metrics_for_style(font_conte
     let font = fontgroup.fonts[0].borrow();
     font.metrics.clone()
 }
 
 /// Returns the line block-size needed by the given computed style and font size.
 pub fn line_height_from_style(style: &ServoComputedValues, metrics: &FontMetrics) -> Au {
     let font_size = style.get_font().font_size;
     match style.get_inheritedtext().line_height {
-        line_height::T::Normal => metrics.line_gap,
-        line_height::T::Number(l) => font_size.scale_by(l),
-        line_height::T::Length(l) => l
+        LineHeight::Normal => metrics.line_gap,
+        LineHeight::Number(l) => font_size.scale_by(l),
+        LineHeight::Length(l) => l
     }
 }
 
 fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragment>) {
     if fragments.is_empty() {
         return
     }
 
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -641,17 +641,17 @@ impl LayoutElementHelpers for LayoutJS<E
 
         let border = if let Some(this) = self.downcast::<HTMLTableElement>() {
             this.get_border()
         } else {
             None
         };
 
         if let Some(border) = border {
-            let width_value = specified::BorderWidth::from_length(specified::Length::from_px(border as f32));
+            let width_value = specified::BorderSideWidth::Length(specified::Length::from_px(border as f32));
             hints.push(from_declaration(
                 shared_lock,
                 PropertyDeclaration::BorderTopWidth(width_value.clone())));
             hints.push(from_declaration(
                 shared_lock,
                 PropertyDeclaration::BorderLeftWidth(width_value.clone())));
             hints.push(from_declaration(
                 shared_lock,
--- a/servo/components/style/parser.rs
+++ b/servo/components/style/parser.rs
@@ -2,17 +2,16 @@
  * 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/. */
 
 //! The context within which CSS code is parsed.
 
 use context::QuirksMode;
 use cssparser::{Parser, SourcePosition, UnicodeRange};
 use error_reporting::ParseErrorReporter;
-use parking_lot::RwLock;
 use style_traits::OneOrMoreCommaSeparated;
 use stylesheets::{CssRuleType, Origin, UrlExtraData, Namespaces};
 
 bitflags! {
     /// The mode to use when parsing values.
     pub flags ParsingMode: u8 {
         /// In CSS, lengths must have units, except for zero values, where the unit can be omitted.
         /// https://www.w3.org/TR/css3-values/#lengths
@@ -77,18 +76,18 @@ pub struct ParserContext<'a> {
     /// The current rule type, if any.
     pub rule_type: Option<CssRuleType>,
     /// Line number offsets for inline stylesheets
     pub line_number_offset: u64,
     /// The mode to use when parsing.
     pub parsing_mode: ParsingMode,
     /// The quirks mode of this stylesheet.
     pub quirks_mode: QuirksMode,
-    /// The list of all namespaces active in the current stylesheet
-    pub namespaces: Option<&'a RwLock<Namespaces>>,
+    /// The currently active namespaces.
+    pub namespaces: Option<&'a Namespaces>,
 }
 
 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>,
@@ -103,49 +102,52 @@ impl<'a> ParserContext<'a> {
             line_number_offset: 0u64,
             parsing_mode: parsing_mode,
             quirks_mode: quirks_mode,
             namespaces: None,
         }
     }
 
     /// 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>,
-                         parsing_mode: ParsingMode,
-                         quirks_mode: QuirksMode)
-                         -> ParserContext<'a> {
+    pub fn new_for_cssom(
+        url_data: &'a UrlExtraData,
+        error_reporter: &'a ParseErrorReporter,
+        rule_type: Option<CssRuleType>,
+        parsing_mode: ParsingMode,
+        quirks_mode: QuirksMode
+    ) -> ParserContext<'a> {
         Self::new(Origin::Author, url_data, error_reporter, rule_type, parsing_mode, quirks_mode)
     }
 
     /// 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> {
+    pub fn new_with_rule_type(
+        context: &'a ParserContext,
+        rule_type: Option<CssRuleType>
+    ) -> ParserContext<'a> {
         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,
             parsing_mode: context.parsing_mode,
             quirks_mode: context.quirks_mode,
             namespaces: context.namespaces,
         }
     }
 
     /// 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,
-                                       parsing_mode: ParsingMode,
-                                       quirks_mode: QuirksMode)
-                                       -> ParserContext<'a> {
+    pub fn new_with_line_number_offset(
+        stylesheet_origin: Origin,
+        url_data: &'a UrlExtraData,
+        error_reporter: &'a ParseErrorReporter,
+        line_number_offset: u64,
+        parsing_mode: ParsingMode,
+        quirks_mode: QuirksMode
+    ) -> ParserContext<'a> {
         ParserContext {
             stylesheet_origin: stylesheet_origin,
             url_data: url_data,
             error_reporter: error_reporter,
             rule_type: None,
             line_number_offset: line_number_offset,
             parsing_mode: parsing_mode,
             quirks_mode: quirks_mode,
--- a/servo/components/style/properties/declaration_block.rs
+++ b/servo/components/style/properties/declaration_block.rs
@@ -252,100 +252,145 @@ impl PropertyDeclarationBlock {
             Err(longhand_or_custom) => {
                 // Step 3
                 self.get(longhand_or_custom).map_or(Importance::Normal, |&(_, importance)| importance)
             }
         }
     }
 
     /// Adds or overrides the declaration for a given property in this block,
-    /// **except** if an existing declaration for the same property is more important.
+    /// **except** if an existing declaration for the same property is more
+    /// important.
+    ///
+    /// Always ensures that the property declaration is at the end.
     pub fn extend(&mut self, drain: SourcePropertyDeclarationDrain, importance: Importance) {
         self.extend_common(drain, importance, false);
     }
 
     /// Adds or overrides the declaration for a given property in this block,
-    /// **even** if an existing declaration for the same property is more important.
+    /// **even** if an existing declaration for the same property is more
+    /// important, and reuses the same position in the block.
     ///
-    /// Return whether anything changed.
+    /// Returns whether anything changed.
     pub fn extend_reset(&mut self, drain: SourcePropertyDeclarationDrain,
                         importance: Importance) -> bool {
         self.extend_common(drain, importance, true)
     }
 
-    fn extend_common(&mut self, mut drain: SourcePropertyDeclarationDrain,
-                     importance: Importance, overwrite_more_important: bool) -> bool {
+    fn extend_common(
+        &mut self,
+        mut drain: SourcePropertyDeclarationDrain,
+        importance: Importance,
+        overwrite_more_important_and_reuse_slot: bool,
+    ) -> bool {
         let all_shorthand_len = match drain.all_shorthand {
             AllShorthand::NotSet => 0,
             AllShorthand::CSSWideKeyword(_) |
             AllShorthand::WithVariables(_) => ShorthandId::All.longhands().len()
         };
         let push_calls_count = drain.declarations.len() + all_shorthand_len;
 
         // With deduplication the actual length increase may be less than this.
         self.declarations.reserve(push_calls_count);
 
         let mut changed = false;
         for decl in &mut drain.declarations {
-            changed |= self.push_common(decl, importance, overwrite_more_important);
+            changed |= self.push_common(
+                decl,
+                importance,
+                overwrite_more_important_and_reuse_slot,
+            );
         }
         match drain.all_shorthand {
             AllShorthand::NotSet => {}
             AllShorthand::CSSWideKeyword(keyword) => {
                 for &id in ShorthandId::All.longhands() {
                     let decl = PropertyDeclaration::CSSWideKeyword(id, keyword);
-                    changed |= self.push_common(decl, importance, overwrite_more_important);
+                    changed |= self.push_common(
+                        decl,
+                        importance,
+                        overwrite_more_important_and_reuse_slot,
+                    );
                 }
             }
             AllShorthand::WithVariables(unparsed) => {
                 for &id in ShorthandId::All.longhands() {
                     let decl = PropertyDeclaration::WithVariables(id, unparsed.clone());
-                    changed |= self.push_common(decl, importance, overwrite_more_important);
+                    changed |= self.push_common(
+                        decl,
+                        importance,
+                        overwrite_more_important_and_reuse_slot,
+                    );
                 }
             }
         }
         changed
     }
 
     /// Adds or overrides the declaration for a given property in this block,
-    /// **except** if an existing declaration for the same property is more important.
+    /// **except** if an existing declaration for the same property is more
+    /// important.
+    ///
+    /// Ensures that, if inserted, it's inserted at the end of the declaration
+    /// block.
     pub fn push(&mut self, declaration: PropertyDeclaration, importance: Importance) {
         self.push_common(declaration, importance, false);
     }
 
-    fn push_common(&mut self, declaration: PropertyDeclaration, importance: Importance,
-                   overwrite_more_important: bool) -> bool {
+    fn push_common(
+        &mut self,
+        declaration: PropertyDeclaration,
+        importance: Importance,
+        overwrite_more_important_and_reuse_slot: bool
+    ) -> bool {
         let definitely_new = if let PropertyDeclarationId::Longhand(id) = declaration.id() {
             !self.longhands.contains(id)
         } else {
             false  // For custom properties, always scan
         };
 
+
         if !definitely_new {
-            for slot in &mut *self.declarations {
+            let mut index_to_remove = None;
+            for (i, slot) in self.declarations.iter_mut().enumerate() {
                 if slot.0.id() == declaration.id() {
                     match (slot.1, importance) {
                         (Importance::Normal, Importance::Important) => {
                             self.important_count += 1;
                         }
                         (Importance::Important, Importance::Normal) => {
-                            if overwrite_more_important {
+                            if overwrite_more_important_and_reuse_slot {
                                 self.important_count -= 1;
                             } else {
                                 return false
                             }
                         }
                         _ => if slot.0 == declaration {
                             return false;
                         }
                     }
-                    *slot = (declaration, importance);
-                    return true
+
+                    if overwrite_more_important_and_reuse_slot {
+                        *slot = (declaration, importance);
+                        return true;
+                    }
+
+                    // NOTE(emilio): We could avoid this and just override for
+                    // properties not affected by logical props, but it's not
+                    // clear it's worth it given the `definitely_new` check.
+                    index_to_remove = Some(i);
+                    break;
                 }
             }
+
+            if let Some(index) = index_to_remove {
+                self.declarations.remove(index);
+                self.declarations.push((declaration, importance));
+                return true;
+            }
         }
 
         if let PropertyDeclarationId::Longhand(id) = declaration.id() {
             self.longhands.insert(id);
         }
         self.declarations.push((declaration, importance));
         if importance.important() {
             self.important_count += 1;
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -993,27 +993,27 @@ fn static_assert() {
     }
 
     pub fn copy_border_image_repeat_from(&mut self, other: &Self) {
         self.gecko.mBorderImageRepeatH = other.gecko.mBorderImageRepeatH;
         self.gecko.mBorderImageRepeatV = other.gecko.mBorderImageRepeatV;
     }
 
     pub fn set_border_image_width(&mut self, v: longhands::border_image_width::computed_value::T) {
-        use values::generics::border::BorderImageWidthSide;
+        use values::generics::border::BorderImageSideWidth;
 
         % for side in SIDES:
         match v.${side.index} {
-            BorderImageWidthSide::Auto => {
+            BorderImageSideWidth::Auto => {
                 self.gecko.mBorderImageWidth.data_at_mut(${side.index}).set_value(CoordDataValue::Auto)
             },
-            BorderImageWidthSide::Length(l) => {
+            BorderImageSideWidth::Length(l) => {
                 l.to_gecko_style_coord(&mut self.gecko.mBorderImageWidth.data_at_mut(${side.index}))
             },
-            BorderImageWidthSide::Number(n) => {
+            BorderImageSideWidth::Number(n) => {
                 self.gecko.mBorderImageWidth.data_at_mut(${side.index}).set_value(CoordDataValue::Factor(n))
             },
         }
         % endfor
     }
 
     pub fn copy_border_image_width_from(&mut self, other: &Self) {
         % for side in SIDES:
@@ -3632,83 +3632,82 @@ fn static_assert() {
                 color: Color::RGBA(convert_nscolor_to_rgba(shadow.mColor)),
             }
 
         }).collect();
         longhands::text_shadow::computed_value::T(buf)
     }
 
     pub fn set_line_height(&mut self, v: longhands::line_height::computed_value::T) {
-        use properties::longhands::line_height::computed_value::T;
+        use values::generics::text::LineHeight;
         // FIXME: Align binary representations and ditch |match| for cast + static_asserts
         let en = match v {
-            T::Normal => CoordDataValue::Normal,
-            T::Length(val) => CoordDataValue::Coord(val.0),
-            T::Number(val) => CoordDataValue::Factor(val),
-            T::MozBlockHeight =>
+            LineHeight::Normal => CoordDataValue::Normal,
+            LineHeight::Length(val) => CoordDataValue::Coord(val.0),
+            LineHeight::Number(val) => CoordDataValue::Factor(val),
+            LineHeight::MozBlockHeight =>
                     CoordDataValue::Enumerated(structs::NS_STYLE_LINE_HEIGHT_BLOCK_HEIGHT),
         };
         self.gecko.mLineHeight.set_value(en);
     }
 
     pub fn clone_line_height(&self) -> longhands::line_height::computed_value::T {
-        use properties::longhands::line_height::computed_value::T;
+        use values::generics::text::LineHeight;
         return match self.gecko.mLineHeight.as_value() {
-            CoordDataValue::Normal => T::Normal,
-            CoordDataValue::Coord(coord) => T::Length(Au(coord)),
-            CoordDataValue::Factor(n) => T::Number(n),
+            CoordDataValue::Normal => LineHeight::Normal,
+            CoordDataValue::Coord(coord) => LineHeight::Length(Au(coord)),
+            CoordDataValue::Factor(n) => LineHeight::Number(n),
             CoordDataValue::Enumerated(val) if val == structs::NS_STYLE_LINE_HEIGHT_BLOCK_HEIGHT =>
-                T::MozBlockHeight,
-            _ => {
-                debug_assert!(false);
-                T::MozBlockHeight
-            }
+                LineHeight::MozBlockHeight,
+            _ => panic!("this should not happen"),
         }
     }
 
     <%call expr="impl_coord_copy('line_height', 'mLineHeight')"></%call>
 
     pub fn set_letter_spacing(&mut self, v: longhands::letter_spacing::computed_value::T) {
-        match v.0 {
-            Some(au) => self.gecko.mLetterSpacing.set(au),
-            None => self.gecko.mLetterSpacing.set_value(CoordDataValue::Normal)
+        use values::generics::text::Spacing;
+        match v {
+            Spacing::Value(value) => self.gecko.mLetterSpacing.set(value),
+            Spacing::Normal => self.gecko.mLetterSpacing.set_value(CoordDataValue::Normal)
         }
     }
 
     pub fn clone_letter_spacing(&self) -> longhands::letter_spacing::computed_value::T {
-        use properties::longhands::letter_spacing::computed_value::T;
+        use values::generics::text::Spacing;
         debug_assert!(
             matches!(self.gecko.mLetterSpacing.as_value(),
                      CoordDataValue::Normal |
                      CoordDataValue::Coord(_)),
             "Unexpected computed value for letter-spacing");
-        T(Au::from_gecko_style_coord(&self.gecko.mLetterSpacing))
+        Au::from_gecko_style_coord(&self.gecko.mLetterSpacing).map_or(Spacing::Normal, Spacing::Value)
     }
 
     <%call expr="impl_coord_copy('letter_spacing', 'mLetterSpacing')"></%call>
 
     pub fn set_word_spacing(&mut self, v: longhands::word_spacing::computed_value::T) {
-        match v.0 {
-            Some(lop) => self.gecko.mWordSpacing.set(lop),
+        use values::generics::text::Spacing;
+        match v {
+            Spacing::Value(lop) => self.gecko.mWordSpacing.set(lop),
             // https://drafts.csswg.org/css-text-3/#valdef-word-spacing-normal
-            None => self.gecko.mWordSpacing.set_value(CoordDataValue::Coord(0)),
+            Spacing::Normal => self.gecko.mWordSpacing.set_value(CoordDataValue::Coord(0)),
         }
     }
 
     pub fn clone_word_spacing(&self) -> longhands::word_spacing::computed_value::T {
-        use properties::longhands::word_spacing::computed_value::T;
         use values::computed::LengthOrPercentage;
+        use values::generics::text::Spacing;
         debug_assert!(
             matches!(self.gecko.mWordSpacing.as_value(),
                      CoordDataValue::Normal |
                      CoordDataValue::Coord(_) |
                      CoordDataValue::Percent(_) |
                      CoordDataValue::Calc(_)),
             "Unexpected computed value for word-spacing");
-        T(LengthOrPercentage::from_gecko_style_coord(&self.gecko.mWordSpacing))
+        LengthOrPercentage::from_gecko_style_coord(&self.gecko.mWordSpacing).map_or(Spacing::Normal, Spacing::Value)
     }
 
     <%call expr="impl_coord_copy('word_spacing', 'mWordSpacing')"></%call>
 
     fn clear_text_emphasis_style_if_string(&mut self) {
         use nsstring::nsString;
         if self.gecko.mTextEmphasisStyle == structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING as u8 {
             self.gecko.mTextEmphasisStyleString.assign(&nsString::new());
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -13,17 +13,16 @@ use euclid::{Point2D, Size2D};
 #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
 #[cfg(feature = "gecko")] use gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI};
 #[cfg(feature = "gecko")] use gecko_string_cache::Atom;
 use properties::{CSSWideKeyword, PropertyDeclaration};
 use properties::longhands;
 use properties::longhands::background_size::computed_value::T as BackgroundSizeList;
 use properties::longhands::font_weight::computed_value::T as FontWeight;
 use properties::longhands::font_stretch::computed_value::T as FontStretch;
-use properties::longhands::line_height::computed_value::T as LineHeight;
 use properties::longhands::text_shadow::computed_value::T as TextShadowList;
 use properties::longhands::text_shadow::computed_value::TextShadow;
 use properties::longhands::box_shadow::computed_value::T as BoxShadowList;
 use properties::longhands::box_shadow::single_value::computed_value::T as BoxShadow;
 use properties::longhands::transform::computed_value::ComputedMatrix;
 use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
 use properties::longhands::transform::computed_value::T as TransformList;
 use properties::longhands::vertical_align::computed_value::T as VerticalAlign;
@@ -1295,53 +1294,16 @@ impl Animatable for MaxLength {
              MaxLength::LengthOrPercentageOrNone(ref other)) => {
                 this.compute_distance(other)
             },
             _ => Err(()),
         }
     }
 }
 
-/// https://drafts.csswg.org/css-transitions/#animtype-number
-/// https://drafts.csswg.org/css-transitions/#animtype-length
-impl Animatable for LineHeight {
-    #[inline]
-    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
-        match (*self, *other) {
-            (LineHeight::Length(ref this),
-             LineHeight::Length(ref other)) => {
-                this.add_weighted(other, self_portion, other_portion).map(LineHeight::Length)
-            }
-            (LineHeight::Number(ref this),
-             LineHeight::Number(ref other)) => {
-                this.add_weighted(other, self_portion, other_portion).map(LineHeight::Number)
-            }
-            (LineHeight::Normal, LineHeight::Normal) => {
-                Ok(LineHeight::Normal)
-            }
-            _ => Err(()),
-        }
-    }
-
-    #[inline]
-    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
-        match (*self, *other) {
-            (LineHeight::Length(ref this),
-             LineHeight::Length(ref other)) => {
-                this.compute_distance(other)
-            },
-            (LineHeight::Number(ref this),
-             LineHeight::Number(ref other)) => {
-                this.compute_distance(other)
-            },
-            _ => Err(()),
-        }
-    }
-}
-
 /// http://dev.w3.org/csswg/css-transitions/#animtype-font-weight
 impl Animatable for FontWeight {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         let a = (*self as u32) as f64;
         let b = (*other as u32) as f64;
         let weight = a * self_portion + b * other_portion;
         Ok(if weight < 150. {
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -27,17 +27,19 @@
 
     ${helpers.predefined_type("border-%s-style" % side[0], "BorderStyle",
                               "specified::BorderStyle::none",
                               need_clone=True,
                               alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-style"),
                               spec=maybe_logical_spec(side, "style"),
                               animation_value_type="none", logical=side[1])}
 
-    ${helpers.predefined_type("border-%s-width" % side[0], "BorderWidth", "Au::from_px(3)",
+    ${helpers.predefined_type("border-%s-width" % side[0],
+                              "BorderSideWidth",
+                              "Au::from_px(3)",
                               computed_type="::app_units::Au",
                               alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-width"),
                               spec=maybe_logical_spec(side, "width"),
                               animation_value_type="ComputedValue",
                               logical=side[1],
                               allow_quirks=not side[1])}
 % endfor
 
@@ -282,18 +284,18 @@
         let first = try!(RepeatKeyword::parse(input));
         let second = input.try(RepeatKeyword::parse).ok();
 
         Ok(SpecifiedValue(first, second))
     }
 </%helpers:longhand>
 
 ${helpers.predefined_type("border-image-width", "BorderImageWidth",
-    initial_value="computed::BorderImageWidthSide::one().into()",
-    initial_specified_value="specified::BorderImageWidthSide::one().into()",
+    initial_value="computed::BorderImageSideWidth::one().into()",
+    initial_specified_value="specified::BorderImageSideWidth::one().into()",
     spec="https://drafts.csswg.org/css-backgrounds/#border-image-width",
     animation_value_type="none",
     boxed=True)}
 
 ${helpers.predefined_type("border-image-slice", "BorderImageSlice",
     initial_value="computed::NumberOrPercentage::Percentage(computed::Percentage(1.)).into()",
     initial_specified_value="specified::NumberOrPercentage::Percentage(specified::Percentage(1.)).into()",
     spec="https://drafts.csswg.org/css-backgrounds/#border-image-slice",
--- a/servo/components/style/properties/longhand/column.mako.rs
+++ b/servo/components/style/properties/longhand/column.mako.rs
@@ -35,21 +35,25 @@
                           experimental=True,
                           animation_value_type="ComputedValue",
                           spec="https://drafts.csswg.org/css-multicol/#propdef-column-gap")}
 
 ${helpers.single_keyword("column-fill", "balance auto", extra_prefixes="moz",
                          products="gecko", animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-multicol/#propdef-column-fill")}
 
-${helpers.predefined_type("column-rule-width", "BorderWidth", "Au::from_px(3)",
-                          initial_specified_value="specified::BorderWidth::Medium",
-                          products="gecko", computed_type="::app_units::Au",
+${helpers.predefined_type("column-rule-width",
+                          "BorderSideWidth",
+                          "Au::from_px(3)",
+                          initial_specified_value="specified::BorderSideWidth::Medium",
+                          computed_type="::app_units::Au",
+                          products="gecko",
                           spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-width",
-                          animation_value_type="ComputedValue", extra_prefixes="moz")}
+                          animation_value_type="ComputedValue",
+                          extra_prefixes="moz")}
 
 // https://drafts.csswg.org/css-multicol-1/#crc
 ${helpers.predefined_type("column-rule-color", "CSSColor",
                           "::cssparser::Color::CurrentColor",
                           initial_specified_value="specified::CSSColor::currentcolor()",
                           products="gecko", animation_value_type="IntermediateColor", extra_prefixes="moz",
                           complex_color=True, need_clone=True,
                           ignored_when_colors_disabled=True,
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -1,158 +1,21 @@
 /* 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/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 <% from data import Keyword %>
 <% data.new_style_struct("InheritedText", inherited=True, gecko_name="Text") %>
 
-<%helpers:longhand name="line-height" animation_value_type="ComputedValue"
-                   spec="https://drafts.csswg.org/css2/visudet.html#propdef-line-height">
-    use std::fmt;
-    use style_traits::ToCss;
-
-    #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum SpecifiedValue {
-        Normal,
-        % if product == "gecko":
-            MozBlockHeight,
-        % endif
-        Number(specified::Number),
-        LengthOrPercentage(specified::LengthOrPercentage),
-    }
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                SpecifiedValue::Normal => dest.write_str("normal"),
-                % if product == "gecko":
-                    SpecifiedValue::MozBlockHeight => dest.write_str("-moz-block-height"),
-                % endif
-                SpecifiedValue::LengthOrPercentage(ref value) => value.to_css(dest),
-                SpecifiedValue::Number(number) => number.to_css(dest),
-            }
-        }
-    }
-    /// normal | <number> | <length> | <percentage>
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        use cssparser::Token;
-        use std::ascii::AsciiExt;
-
-        // We try to parse as a Number first because, for 'line-height', we want
-        // "0" to be parsed as a plain Number rather than a Length (0px); this
-        // matches the behaviour of all major browsers
-        if let Ok(number) = input.try(|i| specified::Number::parse_non_negative(context, i)) {
-            return Ok(SpecifiedValue::Number(number))
-        }
-
-        if let Ok(lop) = input.try(|i| specified::LengthOrPercentage::parse_non_negative(context, i)) {
-            return Ok(SpecifiedValue::LengthOrPercentage(lop))
-        }
-
-
-        match try!(input.next()) {
-            Token::Ident(ref value) if value.eq_ignore_ascii_case("normal") => {
-                Ok(SpecifiedValue::Normal)
-            }
-            % if product == "gecko":
-            Token::Ident(ref value) if value.eq_ignore_ascii_case("-moz-block-height") => {
-                Ok(SpecifiedValue::MozBlockHeight)
-            }
-            % endif
-            _ => Err(()),
-        }
-    }
-    pub mod computed_value {
-        use app_units::Au;
-        use values::CSSFloat;
-        #[derive(PartialEq, Copy, Clone, Debug)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub enum T {
-            Normal,
-            % if product == "gecko":
-                MozBlockHeight,
-            % endif
-            Length(Au),
-            Number(CSSFloat),
-        }
-    }
-    impl ToCss for computed_value::T {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                computed_value::T::Normal => dest.write_str("normal"),
-                % if product == "gecko":
-                    computed_value::T::MozBlockHeight => dest.write_str("-moz-block-height"),
-                % endif
-                computed_value::T::Length(length) => length.to_css(dest),
-                computed_value::T::Number(number) => write!(dest, "{}", number),
-            }
-        }
-    }
-     #[inline]
-    pub fn get_initial_value() -> computed_value::T { computed_value::T::Normal }
-
-    #[inline]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        SpecifiedValue::Normal
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            match *self {
-                SpecifiedValue::Normal => computed_value::T::Normal,
-                % if product == "gecko":
-                    SpecifiedValue::MozBlockHeight => computed_value::T::MozBlockHeight,
-                % endif
-                SpecifiedValue::Number(value) => computed_value::T::Number(value.to_computed_value(context)),
-                SpecifiedValue::LengthOrPercentage(ref value) => {
-                    match *value {
-                        specified::LengthOrPercentage::Length(ref value) =>
-                            computed_value::T::Length(value.to_computed_value(context)),
-                        specified::LengthOrPercentage::Percentage(specified::Percentage(value)) => {
-                            let fr = specified::Length::NoCalc(specified::NoCalcLength::FontRelative(
-                                specified::FontRelativeLength::Em(value)));
-                            computed_value::T::Length(fr.to_computed_value(context))
-                        },
-                        specified::LengthOrPercentage::Calc(ref calc) => {
-                            let calc = calc.to_computed_value(context);
-                            let fr = specified::FontRelativeLength::Em(calc.percentage());
-                            let fr = specified::Length::NoCalc(specified::NoCalcLength::FontRelative(fr));
-                            let length = calc.unclamped_length();
-                            computed_value::T::Length(calc.clamping_mode.clamp(length + fr.to_computed_value(context)))
-                        }
-                    }
-                }
-            }
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            match *computed {
-                computed_value::T::Normal => SpecifiedValue::Normal,
-                % if product == "gecko":
-                    computed_value::T::MozBlockHeight => SpecifiedValue::MozBlockHeight,
-                % endif
-                computed_value::T::Number(ref value) => {
-                    SpecifiedValue::Number(specified::Number::from_computed_value(value))
-                },
-                computed_value::T::Length(au) => {
-                    SpecifiedValue::LengthOrPercentage(specified::LengthOrPercentage::Length(
-                        ToComputedValue::from_computed_value(&au)
-                    ))
-                }
-            }
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type("line-height",
+                          "LineHeight",
+                          "computed::LineHeight::normal()",
+                          animation_value_type="ComputedValue",
+                          spec="https://drafts.csswg.org/css2/visudet.html#propdef-line-height")}
 
 // CSS Text Module Level 3
 
 // TODO(pcwalton): `full-width`
 ${helpers.single_keyword("text-transform",
                          "none capitalize uppercase lowercase",
                          extra_gecko_values="full-width",
                          animation_value_type="none",
@@ -390,167 +253,27 @@
         impl ComputedValueAsSpecified for SpecifiedValue {}
         pub use self::computed_value::T as SpecifiedValue;
         pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
             computed_value::T::parse(input)
         }
     % endif
 </%helpers:longhand>
 
-<%helpers:longhand name="letter-spacing" animation_value_type="ComputedValue"
-                   spec="https://drafts.csswg.org/css-text/#propdef-letter-spacing">
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::specified::AllowQuirks;
-
-    #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum SpecifiedValue {
-        Normal,
-        Specified(specified::Length),
-    }
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                SpecifiedValue::Normal => dest.write_str("normal"),
-                SpecifiedValue::Specified(ref l) => l.to_css(dest),
-            }
-        }
-    }
-
-    pub mod computed_value {
-        use app_units::Au;
-        use properties::animated_properties::Animatable;
-
-        #[derive(Debug, Clone, PartialEq)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub struct T(pub Option<Au>);
-
-        ${helpers.impl_animatable_for_option_tuple('Au(0)')}
-    }
-
-    impl ToCss for computed_value::T {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match self.0 {
-                None => dest.write_str("normal"),
-                Some(l) => l.to_css(dest),
-            }
-        }
-    }
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        computed_value::T(None)
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            match *self {
-                SpecifiedValue::Normal => computed_value::T(None),
-                SpecifiedValue::Specified(ref l) =>
-                    computed_value::T(Some(l.to_computed_value(context)))
-            }
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            computed.0.map(|ref au| {
-                SpecifiedValue::Specified(ToComputedValue::from_computed_value(au))
-            }).unwrap_or(SpecifiedValue::Normal)
-        }
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
-            Ok(SpecifiedValue::Normal)
-        } else {
-            specified::Length::parse_quirky(context, input, AllowQuirks::Yes).map(SpecifiedValue::Specified)
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type("letter-spacing",
+                          "LetterSpacing",
+                          "computed::LetterSpacing::normal()",
+                          animation_value_type="ComputedValue",
+                          spec="https://drafts.csswg.org/css-text/#propdef-letter-spacing")}
 
-<%helpers:longhand name="word-spacing" animation_value_type="ComputedValue"
-                   spec="https://drafts.csswg.org/css-text/#propdef-word-spacing">
-    use std::fmt;
-    use style_traits::ToCss;
-    use values::specified::AllowQuirks;
-
-    #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub enum SpecifiedValue {
-        Normal,
-        Specified(specified::LengthOrPercentage),
-    }
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match *self {
-                SpecifiedValue::Normal => dest.write_str("normal"),
-                SpecifiedValue::Specified(ref l) => l.to_css(dest),
-            }
-        }
-    }
-
-    pub mod computed_value {
-        use properties::animated_properties::Animatable;
-        use values::computed::LengthOrPercentage;
-        #[derive(Debug, Clone, PartialEq)]
-        #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-        pub struct T(pub Option<LengthOrPercentage>);
-
-        ${helpers.impl_animatable_for_option_tuple('LengthOrPercentage::zero()')}
-    }
-
-    impl ToCss for computed_value::T {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            match self.0 {
-                None => dest.write_str("normal"),
-                Some(l) => l.to_css(dest),
-            }
-        }
-    }
-
-    #[inline]
-    pub fn get_initial_value() -> computed_value::T {
-        computed_value::T(None)
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            match *self {
-                SpecifiedValue::Normal => computed_value::T(None),
-                SpecifiedValue::Specified(ref l) =>
-                    computed_value::T(Some(l.to_computed_value(context))),
-            }
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            computed.0.map(|ref lop| {
-                SpecifiedValue::Specified(ToComputedValue::from_computed_value(lop))
-            }).unwrap_or(SpecifiedValue::Normal)
-        }
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
-            Ok(SpecifiedValue::Normal)
-        } else {
-            specified::LengthOrPercentage::parse_quirky(context, input, AllowQuirks::Yes)
-                                          .map(SpecifiedValue::Specified)
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type("word-spacing",
+                          "WordSpacing",
+                          "computed::WordSpacing::normal()",
+                          animation_value_type="ComputedValue",
+                          spec="https://drafts.csswg.org/css-text/#propdef-word-spacing")}
 
 <%helpers:longhand name="-servo-text-decorations-in-effect"
                    derived_from="display text-decoration"
                    need_clone="True" products="servo"
                    animation_value_type="none"
                    spec="Nonstandard (Internal property used by Servo)">
     use cssparser::RGBA;
     use std::fmt;
@@ -1144,23 +867,25 @@
     "-webkit-text-stroke-color", "CSSColor",
     "CSSParserColor::CurrentColor",
     initial_specified_value="specified::CSSColor::currentcolor()",
     products="gecko", animation_value_type="IntermediateColor",
     complex_color=True, need_clone=True,
     ignored_when_colors_disabled=True,
     spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-color")}
 
-${helpers.predefined_type("-webkit-text-stroke-width", "BorderWidth", "Au::from_px(0)",
-                          initial_specified_value="specified::BorderWidth::from_length(specified::Length::zero())",
-                          computed_type="::app_units::Au", products="gecko",
+${helpers.predefined_type("-webkit-text-stroke-width",
+                          "BorderSideWidth",
+                          "Au::from_px(0)",
+                          initial_specified_value="specified::BorderSideWidth::Length(specified::Length::zero())",
+                          computed_type="::app_units::Au",
+                          products="gecko",
                           spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-width",
                           animation_value_type="none")}
 
-
 // CSS Ruby Layout Module Level 1
 // https://drafts.csswg.org/css-ruby/
 ${helpers.single_keyword("ruby-align", "space-around start center space-between",
                          products="gecko", animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-ruby/#ruby-align-property")}
 
 ${helpers.single_keyword("ruby-position", "over under",
                          products="gecko", animation_value_type="discrete",
--- a/servo/components/style/properties/longhand/outline.mako.rs
+++ b/servo/components/style/properties/longhand/outline.mako.rs
@@ -56,60 +56,23 @@
                     Err(())
                 } else {
                     Ok(result)
                 }
             })
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="outline-width" animation_value_type="ComputedValue"
-                   spec="https://drafts.csswg.org/css-ui/#propdef-outline-width">
-    use std::fmt;
-    use style_traits::ToCss;
-
-    impl ToCss for SpecifiedValue {
-        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            self.0.to_css(dest)
-        }
-    }
-
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        specified::parse_border_width(context, input).map(SpecifiedValue)
-    }
-
-    #[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-    pub struct SpecifiedValue(pub specified::Length);
-
-    pub mod computed_value {
-        use app_units::Au;
-        pub type T = Au;
-    }
-
-    pub use super::border_top_width::get_initial_value;
-    #[inline]
-    pub fn get_initial_specified_value() -> SpecifiedValue {
-        SpecifiedValue(specified::Length::NoCalc(specified::NoCalcLength::medium()))
-    }
-
-    impl ToComputedValue for SpecifiedValue {
-        type ComputedValue = computed_value::T;
-
-        #[inline]
-        fn to_computed_value(&self, context: &Context) -> computed_value::T {
-            self.0.to_computed_value(context)
-        }
-
-        #[inline]
-        fn from_computed_value(computed: &computed_value::T) -> Self {
-            SpecifiedValue(ToComputedValue::from_computed_value(computed))
-        }
-    }
-</%helpers:longhand>
+${helpers.predefined_type("outline-width",
+                          "BorderSideWidth",
+                          "Au::from_px(3)",
+                          initial_specified_value="specified::BorderSideWidth::Medium",
+                          computed_type="::app_units::Au",
+                          animation_value_type="ComputedValue",
+                          spec="https://drafts.csswg.org/css-ui/#propdef-outline-width")}
 
 // The -moz-outline-radius-* properties are non-standard and not on a standards track.
 // TODO: Should they animate?
 % for corner in ["topleft", "topright", "bottomright", "bottomleft"]:
     ${helpers.predefined_type("-moz-outline-radius-" + corner, "BorderCornerRadius",
         "computed::LengthOrPercentage::zero().into()",
         products="gecko",
         boxed=True,
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -33,18 +33,19 @@ use media_queries::Device;
 use parser::{PARSING_MODE_DEFAULT, Parse, ParserContext};
 use properties::animated_properties::TransitionProperty;
 #[cfg(feature = "gecko")] use properties::longhands::system_font::SystemFont;
 #[cfg(feature = "servo")] use servo_config::prefs::PREFS;
 use shared_lock::StylesheetGuards;
 use style_traits::{HasViewportPercentage, ToCss};
 use stylesheets::{CssRuleType, MallocSizeOf, MallocSizeOfFn, Origin, UrlExtraData};
 #[cfg(feature = "servo")] use values::Either;
+use values::generics::text::LineHeight;
+use values::computed;
 use values::specified::Color;
-use values::computed;
 use cascade_info::CascadeInfo;
 use rule_tree::{CascadeLevel, StrongRuleNode};
 use style_adjuster::StyleAdjuster;
 #[cfg(feature = "servo")] use values::specified::BorderStyle;
 
 pub use self::declaration_block::*;
 
 #[cfg(feature = "gecko")]
@@ -1257,21 +1258,19 @@ impl PropertyDeclaration {
                     prop.get_system()
                 }
             % endfor
             _ => None,
         }
     }
 
     /// Is it the default value of line-height?
-    ///
-    /// (using match because it generates less code than)
     pub fn is_default_line_height(&self) -> bool {
         match *self {
-            PropertyDeclaration::LineHeight(longhands::line_height::SpecifiedValue::Normal) => true,
+            PropertyDeclaration::LineHeight(LineHeight::Normal) => true,
             _ => false
         }
     }
 
     #[cfg(feature = "servo")]
     /// Dummy method to avoid cfg()s
     pub fn get_system(&self) -> Option<()> {
         None
--- a/servo/components/style/properties/shorthand/border.mako.rs
+++ b/servo/components/style/properties/shorthand/border.mako.rs
@@ -13,21 +13,21 @@
                                "specified::BorderStyle::parse",
                                spec="https://drafts.csswg.org/css-backgrounds/#border-style")}
 
 <%helpers:shorthand name="border-width" sub_properties="${
         ' '.join('border-%s-width' % side
                  for side in PHYSICAL_SIDES)}"
     spec="https://drafts.csswg.org/css-backgrounds/#border-width">
     use values::generics::rect::Rect;
-    use values::specified::{AllowQuirks, BorderWidth};
+    use values::specified::{AllowQuirks, BorderSideWidth};
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let rect = Rect::parse_with(context, input, |_, i| {
-            BorderWidth::parse_quirky(context, i, AllowQuirks::Yes)
+            BorderSideWidth::parse_quirky(context, i, AllowQuirks::Yes)
         })?;
         Ok(expanded! {
             border_top_width: rect.0,
             border_right_width: rect.1,
             border_bottom_width: rect.2,
             border_left_width: rect.3,
         })
     }
@@ -41,18 +41,18 @@
         }
     }
 </%helpers:shorthand>
 
 
 pub fn parse_border(context: &ParserContext, input: &mut Parser)
                  -> Result<(specified::CSSColor,
                             specified::BorderStyle,
-                            specified::BorderWidth), ()> {
-    use values::specified::{CSSColor, BorderStyle, BorderWidth};
+                            specified::BorderSideWidth), ()> {
+    use values::specified::{CSSColor, BorderStyle, BorderSideWidth};
     let _unused = context;
     let mut color = None;
     let mut style = None;
     let mut width = None;
     let mut any = false;
     loop {
         if color.is_none() {
             if let Ok(value) = input.try(|i| CSSColor::parse(context, i)) {
@@ -64,28 +64,28 @@ pub fn parse_border(context: &ParserCont
         if style.is_none() {
             if let Ok(value) = input.try(|i| BorderStyle::parse(context, i)) {
                 style = Some(value);
                 any = true;
                 continue
             }
         }
         if width.is_none() {
-            if let Ok(value) = input.try(|i| BorderWidth::parse(context, i)) {
+            if let Ok(value) = input.try(|i| BorderSideWidth::parse(context, i)) {
                 width = Some(value);
                 any = true;
                 continue
             }
         }
         break
     }
     if any {
         Ok((color.unwrap_or_else(|| CSSColor::currentcolor()),
             style.unwrap_or(BorderStyle::none),
-            width.unwrap_or(BorderWidth::Medium)))
+            width.unwrap_or(BorderSideWidth::Medium)))
     } else {
         Err(())
     }
 }
 
 % for side, logical in ALL_SIDES:
     <%
         spec = "https://drafts.csswg.org/css-backgrounds/#border-%s" % side
--- a/servo/components/style/properties/shorthand/font.mako.rs
+++ b/servo/components/style/properties/shorthand/font.mako.rs
@@ -13,20 +13,22 @@
                                     ${'font-variant-alternates' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-east-asian' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-ligatures' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-numeric' if product == 'gecko' or data.testing else ''}
                                     ${'font-variant-position' if product == 'gecko' or data.testing else ''}
                                     ${'font-language-override' if product == 'gecko' or data.testing else ''}
                                     ${'font-feature-settings' if product == 'gecko' or data.testing else ''}"
                     spec="https://drafts.csswg.org/css-fonts-3/#propdef-font">
+    use parser::Parse;
     use properties::longhands::{font_family, font_style, font_weight, font_stretch};
-    use properties::longhands::{font_size, line_height, font_variant_caps};
+    use properties::longhands::{font_size, font_variant_caps};
     #[cfg(feature = "gecko")]
     use properties::longhands::system_font::SystemFont;
+    use values::specified::text::LineHeight;
 
     <%
         gecko_sub_properties = "kerning language_override size_adjust \
                                 variant_alternates variant_east_asian \
                                 variant_ligatures variant_numeric \
                                 variant_position feature_settings".split()
     %>
     % if product == "gecko" or data.testing:
@@ -45,17 +47,17 @@
         let size;
         % if product == "gecko":
             if let Ok(sys) = input.try(SystemFont::parse) {
                 return Ok(expanded! {
                      % for name in SYSTEM_FONT_LONGHANDS:
                          ${name}: ${name}::SpecifiedValue::system_font(sys),
                      % endfor
                      // line-height is just reset to initial
-                     line_height: line_height::get_initial_specified_value(),
+                     line_height: LineHeight::normal(),
                  })
             }
         % endif
         loop {
             // Special-case 'normal' because it is valid in each of
             // font-style, font-weight, font-variant and font-stretch.
             // Leaves the values to None, 'normal' is the initial value for each of them.
             if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
@@ -93,26 +95,26 @@
         fn count<T>(opt: &Option<T>) -> u8 {
             if opt.is_some() { 1 } else { 0 }
         }
         if size.is_none() ||
            (count(&style) + count(&weight) + count(&variant_caps) + count(&stretch) + nb_normals) > 4 {
             return Err(())
         }
         let line_height = if input.try(|input| input.expect_delim('/')).is_ok() {
-            Some(try!(line_height::parse(context, input)))
+            Some(try!(LineHeight::parse(context, input)))
         } else {
             None
         };
         let family = FontFamily::parse(input)?;
         Ok(expanded! {
             % for name in "style weight stretch size variant_caps".split():
                 font_${name}: unwrap_or_initial!(font_${name}, ${name}),
             % endfor
-            line_height: unwrap_or_initial!(line_height),
+            line_height: line_height.unwrap_or(LineHeight::normal()),
             font_family: family,
             % if product == "gecko" or data.testing:
                 % for name in gecko_sub_properties:
                     font_${name}: font_${name}::get_initial_specified_value(),
                 % endfor
             % endif
         })
     }
@@ -164,22 +166,19 @@
                 if needs_this_property {
                     self.font_${name}.to_css(dest)?;
                     dest.write_str(" ")?;
                 }
             % endfor
 
             self.font_size.to_css(dest)?;
 
-            match *self.line_height {
-                line_height::SpecifiedValue::Normal => {},
-                _ => {
-                    dest.write_str("/")?;
-                    self.line_height.to_css(dest)?;
-                }
+            if *self.line_height != LineHeight::normal() {
+                dest.write_str("/")?;
+                self.line_height.to_css(dest)?;
             }
 
             dest.write_str(" ")?;
             self.font_family.to_css(dest)?;
 
             Ok(())
         }
 
@@ -192,17 +191,17 @@
                 % for prop in SYSTEM_FONT_LONGHANDS:
                     if let Some(s) = self.${prop}.get_system() {
                         debug_assert!(sys.is_none() || s == sys.unwrap());
                         sys = Some(s);
                     } else {
                         all = false;
                     }
                 % endfor
-                if self.line_height != &line_height::get_initial_specified_value() {
+                if self.line_height != &LineHeight::normal() {
                     all = false
                 }
                 if all {
                     CheckSystemResult::AllSystem(sys.unwrap())
                 } else if sys.is_some() {
                     CheckSystemResult::SomeSystem
                 } else {
                     CheckSystemResult::None
--- a/servo/components/style/stylesheets.rs
+++ b/servo/components/style/stylesheets.rs
@@ -35,17 +35,16 @@ use selectors::parser::SelectorList;
 #[cfg(feature = "servo")]
 use servo_config::prefs::PREFS;
 #[cfg(not(feature = "gecko"))]
 use servo_url::ServoUrl;
 use shared_lock::{SharedRwLock, Locked, ToCssWithGuard, SharedRwLockReadGuard};
 use smallvec::SmallVec;
 use std::{fmt, mem};
 use std::borrow::Borrow;
-use std::cell::Cell;
 use std::mem::align_of;
 use std::os::raw::c_void;
 use std::slice;
 use std::sync::atomic::{AtomicBool, Ordering};
 use str::starts_with_ignore_ascii_case;
 use style_traits::ToCss;
 use stylearc::Arc;
 use stylist::FnvHashMap;
@@ -474,49 +473,56 @@ impl CssRule {
     // Returns a parsed CSS rule and the final state of the parser
     #[allow(missing_docs)]
     pub fn parse(css: &str,
                  parent_stylesheet: &Stylesheet,
                  state: Option<State>,
                  loader: Option<&StylesheetLoader>)
                  -> Result<(Self, State), SingleRuleParseError> {
         let error_reporter = NullReporter;
-        let mut context = ParserContext::new(parent_stylesheet.origin,
-                                             &parent_stylesheet.url_data,
-                                             &error_reporter,
-                                             None,
-                                             PARSING_MODE_DEFAULT,
-                                             parent_stylesheet.quirks_mode);
-        context.namespaces = Some(&parent_stylesheet.namespaces);
+        let context = ParserContext::new(
+            parent_stylesheet.origin,
+            &parent_stylesheet.url_data,
+            &error_reporter,
+            None,
+            PARSING_MODE_DEFAULT,
+            parent_stylesheet.quirks_mode
+        );
+
         let mut input = Parser::new(css);
 
+        let mut guard = parent_stylesheet.namespaces.write();
+
         // nested rules are in the body state
         let state = state.unwrap_or(State::Body);
         let mut rule_parser = TopLevelRuleParser {
             stylesheet_origin: parent_stylesheet.origin,
             context: context,
             shared_lock: &parent_stylesheet.shared_lock,
             loader: loader,
-            state: Cell::new(state),
+            state: state,
+            namespaces: Some(&mut *guard),
         };
         match parse_one_rule(&mut input, &mut rule_parser) {
-            Ok(result) => Ok((result, rule_parser.state.get())),
+            Ok(result) => Ok((result, rule_parser.state)),
             Err(_) => {
-                if let State::Invalid = rule_parser.state.get() {
+                if let State::Invalid = rule_parser.state {
                     Err(SingleRuleParseError::Hierarchy)
                 } else {
                     Err(SingleRuleParseError::Syntax)
                 }
             }
         }
     }
 
     /// Deep clones this CssRule.
-    fn deep_clone_with_lock(&self,
-                            lock: &SharedRwLock) -> CssRule {
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock
+    ) -> CssRule {
         let guard = lock.read();
         match *self {
             CssRule::Namespace(ref arc) => {
                 let rule = arc.read_with(&guard);
                 CssRule::Namespace(Arc::new(lock.wrap(rule.clone())))
             },
             CssRule::Import(ref arc) => {
                 let rule = arc.read_with(&guard);
@@ -1210,58 +1216,77 @@ impl Stylesheet {
                            css: &str,
                            url_data: &UrlExtraData,
                            stylesheet_loader: Option<&StylesheetLoader>,
                            error_reporter: &ParseErrorReporter,
                            line_number_offset: u64) {
         let namespaces = RwLock::new(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, &namespaces,
-            &existing.shared_lock, stylesheet_loader, error_reporter,
-            existing.quirks_mode, line_number_offset);
+        let (rules, dirty_on_viewport_size_change) =
+            Stylesheet::parse_rules(
+                css,
+                url_data,
+                existing.origin,
+                &mut *namespaces.write(),
+                &existing.shared_lock,
+                stylesheet_loader,
+                error_reporter,
+                existing.quirks_mode,
+                line_number_offset
+            );
+
         mem::swap(&mut *existing.namespaces.write(), &mut *namespaces.write());
         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: &RwLock<Namespaces>,
-                   shared_lock: &SharedRwLock,
-                   stylesheet_loader: Option<&StylesheetLoader>,
-                   error_reporter: &ParseErrorReporter,
-                   quirks_mode: QuirksMode,
-                   line_number_offset: u64)
-                   -> (Vec<CssRule>, bool) {
+    fn parse_rules(
+        css: &str,
+        url_data: &UrlExtraData,
+        origin: Origin,
+        namespaces: &mut Namespaces,
+        shared_lock: &SharedRwLock,
+        stylesheet_loader: Option<&StylesheetLoader>,
+        error_reporter: &ParseErrorReporter,
+        quirks_mode: QuirksMode,
+        line_number_offset: u64
+    ) -> (Vec<CssRule>, bool) {
         let mut rules = Vec::new();
         let mut input = Parser::new(css);
-        let mut context = ParserContext::new_with_line_number_offset(origin, url_data, error_reporter,
-                                                                     line_number_offset,
-                                                                     PARSING_MODE_DEFAULT,
-                                                                     quirks_mode);
-        context.namespaces = Some(namespaces);
+
+        let context =
+            ParserContext::new_with_line_number_offset(
+                origin,
+                url_data,
+                error_reporter,
+                line_number_offset,
+                PARSING_MODE_DEFAULT,
+                quirks_mode
+            );
+
         let rule_parser = TopLevelRuleParser {
             stylesheet_origin: origin,
             shared_lock: shared_lock,
             loader: stylesheet_loader,
             context: context,
-            state: Cell::new(State::Start),
+            state: State::Start,
+            namespaces: Some(namespaces),
         };
 
         input.look_for_viewport_percentages();
 
         {
-            let mut iter = RuleListParser::new_for_stylesheet(&mut input, rule_parser);
+            let mut iter =
+                RuleListParser::new_for_stylesheet(&mut input, rule_parser);
+
             while let Some(result) = iter.next() {
                 match result {
                     Ok(rule) => rules.push(rule),
                     Err(range) => {
                         let pos = range.start;
                         let message = format!("Invalid rule: '{}'", iter.input.slice(range));
                         log_css_error(iter.input, pos, &*message, &iter.parser.context);
                     }
@@ -1284,19 +1309,27 @@ impl Stylesheet {
                     shared_lock: SharedRwLock,
                     stylesheet_loader: Option<&StylesheetLoader>,
                     error_reporter: &ParseErrorReporter,
                     quirks_mode: QuirksMode,
                     line_number_offset: u64)
                     -> Stylesheet {
         let namespaces = RwLock::new(Namespaces::default());
         let (rules, dirty_on_viewport_size_change) = Stylesheet::parse_rules(
-            css, &url_data, origin, &namespaces,
-            &shared_lock, stylesheet_loader, error_reporter, quirks_mode, line_number_offset,
+            css,
+            &url_data,
+            origin,
+            &mut *namespaces.write(),
+            &shared_lock,
+            stylesheet_loader,
+            error_reporter,
+            quirks_mode,
+            line_number_offset,
         );
+
         Stylesheet {
             origin: origin,
             url_data: url_data,
             namespaces: namespaces,
             rules: CssRules::new(rules, &shared_lock),
             media: media,
             shared_lock: shared_lock,
             dirty_on_viewport_size_change: AtomicBool::new(dirty_on_viewport_size_change),
@@ -1472,17 +1505,18 @@ impl StylesheetLoader for NoOpLoader {
 }
 
 
 struct TopLevelRuleParser<'a> {
     stylesheet_origin: Origin,
     shared_lock: &'a SharedRwLock,
     loader: Option<&'a StylesheetLoader>,
     context: ParserContext<'a>,
-    state: Cell<State>,
+    state: State,
+    namespaces: Option<&'a mut Namespaces>,
 }
 
 impl<'b> TopLevelRuleParser<'b> {
     fn nested<'a: 'b>(&'a self) -> NestedRuleParser<'a, 'b> {
         NestedRuleParser {
             stylesheet_origin: self.stylesheet_origin,
             shared_lock: self.shared_lock,
             context: &self.context,
@@ -1544,148 +1578,177 @@ fn register_namespace(ns: &Namespace) ->
 fn register_namespace(_: &Namespace) -> Result<(), ()> {
     Ok(()) // servo doesn't use namespace ids
 }
 
 impl<'a> AtRuleParser for TopLevelRuleParser<'a> {
     type Prelude = AtRulePrelude;
     type AtRule = CssRule;
 
-    fn parse_prelude(&mut self, name: &str, input: &mut Parser)
-                     -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> {
+    fn parse_prelude(
+        &mut self,
+        name: &str,
+        input: &mut Parser
+    ) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> {
         let location = get_location_with_offset(input.current_source_location(),
                                                 self.context.line_number_offset);
         match_ignore_ascii_case! { name,
             "import" => {
-                if self.state.get() <= State::Imports {
-                    self.state.set(State::Imports);
-                    let url_string = input.expect_url_or_string()?;
-                    let specified_url = SpecifiedUrl::parse_from_string(url_string, &self.context)?;
-
-                    let media = parse_media_query_list(&self.context, input);
-                    let media = Arc::new(self.shared_lock.wrap(media));
-
-                    let noop_loader = NoOpLoader;
-                    let loader = if !specified_url.is_invalid() {
-                        self.loader.expect("Expected a stylesheet loader for @import")
-                    } else {
-                        &noop_loader
-                    };
-
-                    let mut specified_url = Some(specified_url);
-                    let arc = loader.request_stylesheet(media, &mut |media| {
-                        ImportRule {
-                            url: specified_url.take().unwrap(),
-                            stylesheet: Arc::new(Stylesheet {
-                                rules: CssRules::new(Vec::new(), self.shared_lock),
-                                media: media,
-                                shared_lock: self.shared_lock.clone(),
-                                origin: self.context.stylesheet_origin,
-                                url_data: self.context.url_data.clone(),
-                                namespaces: RwLock::new(Namespaces::default()),
-                                dirty_on_viewport_size_change: AtomicBool::new(false),
-                                disabled: AtomicBool::new(false),
-                                quirks_mode: self.context.quirks_mode,
-                            }),
-                            source_location: location,
-                        }
-                    }, &mut |import_rule| {
-                        Arc::new(self.shared_lock.wrap(import_rule))
-                    });
-                    return Ok(AtRuleType::WithoutBlock(CssRule::Import(arc)))
-                } else {
-                    self.state.set(State::Invalid);
+                if self.state > State::Imports {
+                    self.state = State::Invalid;
                     return Err(())  // "@import must be before any rule but @charset"
                 }
+
+                self.state = State::Imports;
+                let url_string = input.expect_url_or_string()?;
+                let specified_url = SpecifiedUrl::parse_from_string(url_string, &self.context)?;
+
+                let media = parse_media_query_list(&self.context, input);
+                let media = Arc::new(self.shared_lock.wrap(media));
+
+                let noop_loader = NoOpLoader;
+                let loader = if !specified_url.is_invalid() {
+                    self.loader.expect("Expected a stylesheet loader for @import")
+                } else {
+                    &noop_loader
+                };
+
+                let mut specified_url = Some(specified_url);
+                let arc = loader.request_stylesheet(media, &mut |media| {
+                    ImportRule {
+                        url: specified_url.take().unwrap(),
+                        stylesheet: Arc::new(Stylesheet {
+                            rules: CssRules::new(Vec::new(), self.shared_lock),
+                            media: media,
+                            shared_lock: self.shared_lock.clone(),
+                            origin: self.context.stylesheet_origin,
+                            url_data: self.context.url_data.clone(),
+                            namespaces: RwLock::new(Namespaces::default()),
+                            dirty_on_viewport_size_change: AtomicBool::new(false),
+                            disabled: AtomicBool::new(false),
+                            quirks_mode: self.context.quirks_mode,
+                        }),
+                        source_location: location,
+                    }
+                }, &mut |import_rule| {
+                    Arc::new(self.shared_lock.wrap(import_rule))
+                });
+
+                return Ok(AtRuleType::WithoutBlock(CssRule::Import(arc)))
             },
             "namespace" => {
-                if self.state.get() <= State::Namespaces {
-                    self.state.set(State::Namespaces);
-
-                    let prefix_result = input.try(|input| input.expect_ident());
-                    let url = Namespace::from(try!(input.expect_url_or_string()));
-
-                    let id = register_namespace(&url)?;
-
-                    let opt_prefix = if let Ok(prefix) = prefix_result {
-                        let prefix = Prefix::from(prefix);
-                        self.context.namespaces.expect("namespaces must be set whilst parsing rules")
-                                               .write().prefixes.insert(prefix.clone(), (url.clone(), id));
-                        Some(prefix)
-                    } else {
-                        self.context.namespaces.expect("namespaces must be set whilst parsing rules")
-                                               .write().default = Some((url.clone(), id));
-                        None
-                    };
-
-                    return Ok(AtRuleType::WithoutBlock(CssRule::Namespace(Arc::new(
-                        self.shared_lock.wrap(NamespaceRule {
-                            prefix: opt_prefix,
-                            url: url,
-                            source_location: location,
-                        })
-                    ))))
-                } else {
-                    self.state.set(State::Invalid);
+                if self.state > State::Namespaces {
+                    self.state = State::Invalid;
                     return Err(())  // "@namespace must be before any rule but @charset and @import"
                 }
+                self.state = State::Namespaces;
+
+                let prefix_result = input.try(|input| input.expect_ident());
+                let url = Namespace::from(try!(input.expect_url_or_string()));
+
+                let id = register_namespace(&url)?;
+
+                let mut namespaces = self.namespaces.as_mut().unwrap();
+
+                let opt_prefix = if let Ok(prefix) = prefix_result {
+                    let prefix = Prefix::from(prefix);
+                    namespaces
+                        .prefixes
+                        .insert(prefix.clone(), (url.clone(), id));
+                    Some(prefix)
+                } else {
+                    namespaces.default = Some((url.clone(), id));
+                    None
+                };
+
+                return Ok(AtRuleType::WithoutBlock(CssRule::Namespace(Arc::new(
+                    self.shared_lock.wrap(NamespaceRule {
+                        prefix: opt_prefix,
+                        url: url,
+                        source_location: location,
+                    })
+                ))))
             },
             // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet
             // anything left is invalid.
             "charset" => return Err(()), // (insert appropriate error message)
             _ => {}
         }
         // Don't allow starting with an invalid state
-        if self.state.get() > State::Body {
-            self.state.set(State::Invalid);
+        if self.state > State::Body {
+            self.state = State::Invalid;
             return Err(());
         }
-        self.state.set(State::Body);
+        self.state = State::Body;
+
+        // "Freeze" the namespace map (no more namespace rules can be parsed
+        // after this point), and stick it in the context.
+        if self.namespaces.is_some() {
+            let namespaces = &*self.namespaces.take().unwrap();
+            self.context.namespaces = Some(namespaces);
+        }
         AtRuleParser::parse_prelude(&mut self.nested(), name, input)
     }
 
     #[inline]
     fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CssRule, ()> {
         AtRuleParser::parse_block(&mut self.nested(), prelude, input)
     }
 }
 
 
 impl<'a> QualifiedRuleParser for TopLevelRuleParser<'a> {
     type Prelude = SelectorList<SelectorImpl>;
     type QualifiedRule = CssRule;
 
     #[inline]
     fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> {
-        self.state.set(State::Body);
+        self.state = State::Body;
+
+        // "Freeze" the namespace map (no more namespace rules can be parsed
+        // after this point), and stick it in the context.
+        if self.namespaces.is_some() {
+            let namespaces = &*self.namespaces.take().unwrap();
+            self.context.namespaces = Some(namespaces);
+        }
+
         QualifiedRuleParser::parse_prelude(&mut self.nested(), input)
     }
 
     #[inline]
-    fn parse_block(&mut self, prelude: SelectorList<SelectorImpl>, input: &mut Parser)
-                   -> Result<CssRule, ()> {
+    fn parse_block(
+        &mut self,
+        prelude: SelectorList<SelectorImpl>,
+        input: &mut Parser
+    ) -> Result<CssRule, ()> {
         QualifiedRuleParser::parse_block(&mut self.nested(), prelude, input)
     }
 }
 
 #[derive(Clone)]  // shallow, relatively cheap .clone
 struct NestedRuleParser<'a, 'b: 'a> {
     stylesheet_origin: Origin,
     shared_lock: &'a SharedRwLock,
     context: &'a ParserContext<'b>,
 }
 
 impl<'a, 'b> NestedRuleParser<'a, 'b> {
-    fn parse_nested_rules(&self, input: &mut Parser, rule_type: CssRuleType) -> Arc<Locked<CssRules>> {
+    fn parse_nested_rules(
+        &mut self,
+        input: &mut Parser,
+        rule_type: CssRuleType
+    ) -> Arc<Locked<CssRules>> {
         let context = ParserContext::new_with_rule_type(self.context, Some(rule_type));
+
         let nested_parser = NestedRuleParser {
             stylesheet_origin: self.stylesheet_origin,
             shared_lock: self.shared_lock,
             context: &context,
         };
+
         let mut iter = RuleListParser::new_for_nested_rule(input, nested_parser);
         let mut rules = Vec::new();
         while let Some(result) = iter.next() {
             match result {
                 Ok(rule) => rules.push(rule),
                 Err(range) => {
                     let pos = range.start;
                     let message = format!("Unsupported rule: '{}'", iter.input.slice(range));
@@ -1706,20 +1769,27 @@ fn is_viewport_enabled() -> bool {
 fn is_viewport_enabled() -> bool {
     true
 }
 
 impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
     type Prelude = AtRulePrelude;
     type AtRule = CssRule;
 
-    fn parse_prelude(&mut self, name: &str, input: &mut Parser)
-                     -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> {
-        let location = get_location_with_offset(input.current_source_location(),
-                                                self.context.line_number_offset);
+    fn parse_prelude(
+        &mut self,
+        name: &str,
+        input: &mut Parser
+    ) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> {
+        let location =
+            get_location_with_offset(
+                input.current_source_location(),
+                self.context.line_number_offset
+            );
+
         match_ignore_ascii_case! { name,
             "media" => {
                 let media_queries = parse_media_query_list(self.context, input);
                 let arc = Arc::new(self.shared_lock.wrap(media_queries));
                 Ok(AtRuleType::WithBlock(AtRulePrelude::Media(arc, location)))
             },
             "supports" => {
                 let cond = SupportsCondition::parse(input)?;
@@ -1850,26 +1920,29 @@ impl<'a, 'b> AtRuleParser for NestedRule
     }
 }
 
 impl<'a, 'b> QualifiedRuleParser for NestedRuleParser<'a, 'b> {
     type Prelude = SelectorList<SelectorImpl>;
     type QualifiedRule = CssRule;
 
     fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> {
-        let ns = self.context.namespaces.expect("namespaces must be set when parsing rules").read();
         let selector_parser = SelectorParser {
             stylesheet_origin: self.stylesheet_origin,
-            namespaces: &*ns,
+            namespaces: self.context.namespaces.unwrap(),
         };
+
         SelectorList::parse(&selector_parser, input)
     }
 
-    fn parse_block(&mut self, prelude: SelectorList<SelectorImpl>, input: &mut Parser)
-                   -> Result<CssRule, ()> {
+    fn parse_block(
+        &mut self,
+        prelude: SelectorList<SelectorImpl>,
+        input: &mut Parser
+    ) -> Result<CssRule, ()> {
         let location = get_location_with_offset(input.current_source_location(),
                                                 self.context.line_number_offset);
         let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Style));
         let declarations = parse_property_declaration_list(&context, input);
         Ok(CssRule::Style(Arc::new(self.shared_lock.wrap(StyleRule {
             selectors: prelude,
             block: Arc::new(self.shared_lock.wrap(declarations)),
             source_location: location,
--- a/servo/components/style/values/computed/border.rs
+++ b/servo/components/style/values/computed/border.rs
@@ -2,35 +2,35 @@
  * 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/. */
 
 //! Computed types for CSS values related to borders.
 
 use values::computed::{Number, NumberOrPercentage};
 use values::computed::length::LengthOrPercentage;
 use values::generics::border::BorderCornerRadius as GenericBorderCornerRadius;
+use values::generics::border::BorderImageSideWidth as GenericBorderImageSideWidth;
 use values::generics::border::BorderImageSlice as GenericBorderImageSlice;
-use values::generics::border::BorderImageWidthSide as GenericBorderImageWidthSide;
 use values::generics::border::BorderRadius as GenericBorderRadius;
 use values::generics::rect::Rect;
 
 /// A computed value for the `border-image-width` property.
-pub type BorderImageWidth = Rect<BorderImageWidthSide>;
+pub type BorderImageWidth = Rect<BorderImageSideWidth>;
 
 /// A computed value for a single side of a `border-image-width` property.
-pub type BorderImageWidthSide = GenericBorderImageWidthSide<LengthOrPercentage, Number>;
+pub type BorderImageSideWidth = GenericBorderImageSideWidth<LengthOrPercentage, Number>;
 
 /// A computed value for the `border-image-slice` property.
 pub type BorderImageSlice = GenericBorderImageSlice<NumberOrPercentage>;
 
 /// A computed value for the `border-radius` property.
 pub type BorderRadius = GenericBorderRadius<LengthOrPercentage>;
 
 /// A computed value for the `border-*-radius` longhand properties.
 pub type BorderCornerRadius = GenericBorderCornerRadius<LengthOrPercentage>;
 
-impl BorderImageWidthSide {
+impl BorderImageSideWidth {
     /// Returns `1`.
     #[inline]
     pub fn one() -> Self {
-        GenericBorderImageWidthSide::Number(1.)
+        GenericBorderImageSideWidth::Number(1.)
     }
 }
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -217,16 +217,23 @@ impl ToComputedValue for specified::Calc
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub enum LengthOrPercentage {
     Length(Au),
     Percentage(CSSFloat),
     Calc(CalcLengthOrPercentage),
 }
 
+impl From<Au> for LengthOrPercentage {
+    #[inline]
+    fn from(length: Au) -> Self {
+        LengthOrPercentage::Length(length)
+    }
+}
+
 impl LengthOrPercentage {
     #[inline]
     #[allow(missing_docs)]
     pub fn zero() -> LengthOrPercentage {
         LengthOrPercentage::Length(Au(0))
     }
 
     #[inline]
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -19,39 +19,41 @@ use style_traits::ToCss;
 use super::{CSSFloat, CSSInteger, RGBA};
 use super::generics::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize};
 use super::generics::grid::TrackList as GenericTrackList;
 use super::specified;
 
 pub use app_units::Au;
 pub use cssparser::Color as CSSColor;
 pub use self::background::BackgroundSize;
-pub use self::border::{BorderImageSlice, BorderImageWidth, BorderImageWidthSide};
+pub use self::border::{BorderImageSlice, BorderImageWidth, BorderImageSideWidth};
 pub use self::border::{BorderRadius, BorderCornerRadius};
 pub use self::image::{Gradient, GradientItem, ImageLayer, LineDirection, Image, ImageRect};
 pub use self::rect::LengthOrNumberRect;
 pub use super::{Auto, Either, None_};
 #[cfg(feature = "gecko")]
 pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use super::specified::{BorderStyle, Percentage, UrlOrNone};
 pub use super::generics::grid::GridLine;
 pub use super::specified::url::SpecifiedUrl;
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrAutoOrContent, LengthOrPercentageOrNone, LengthOrNone};
 pub use self::length::{MaxLength, MozLength};
 pub use self::position::Position;
+pub use self::text::{LetterSpacing, LineHeight, WordSpacing};
 pub use self::transform::TransformOrigin;
 
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod image;
 pub mod length;
 pub mod position;
 pub mod rect;
+pub mod text;
 pub mod transform;
 
 /// A `Context` is all the data a specified value could ever need to compute
 /// itself and be transformed to a computed value.
 pub struct Context<'a> {
     /// Whether the current element is the root element.
     pub is_root_element: bool,
 
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/computed/text.rs
@@ -0,0 +1,58 @@
+/* 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/. */
+
+//! Computed types for text properties.
+
+use app_units::Au;
+use properties::animated_properties::Animatable;
+use values::CSSFloat;
+use values::computed::length::{Length, LengthOrPercentage};
+use values::generics::text::{LineHeight as GenericLineHeight, Spacing};
+
+/// A computed value for the `letter-spacing` property.
+pub type LetterSpacing = Spacing<Length>;
+
+/// A computed value for the `word-spacing` property.
+pub type WordSpacing = Spacing<LengthOrPercentage>;
+
+/// A computed value for the `line-height` property.
+pub type LineHeight = GenericLineHeight<CSSFloat, Au>;
+
+impl Animatable for LineHeight {
+    #[inline]
+    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
+        match (*self, *other) {
+            (GenericLineHeight::Length(ref this), GenericLineHeight::Length(ref other)) => {
+                this.add_weighted(other, self_portion, other_portion).map(GenericLineHeight::Length)
+            },
+            (GenericLineHeight::Number(ref this), GenericLineHeight::Number(ref other)) => {
+                this.add_weighted(other, self_portion, other_portion).map(GenericLineHeight::Number)
+            },
+            (GenericLineHeight::Normal, GenericLineHeight::Normal) => {
+                Ok(GenericLineHeight::Normal)
+            },
+            #[cfg(feature = "gecko")]
+            (GenericLineHeight::MozBlockHeight, GenericLineHeight::MozBlockHeight) => {
+                Ok(GenericLineHeight::MozBlockHeight)
+            },
+            _ => Err(()),
+        }
+    }
+
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        match (*self, *other) {
+            (GenericLineHeight::Length(ref this), GenericLineHeight::Length(ref other)) => {
+                this.compute_distance(other)
+            },
+            (GenericLineHeight::Number(ref this), GenericLineHeight::Number(ref other)) => {
+                this.compute_distance(other)
+            },
+            (GenericLineHeight::Normal, GenericLineHeight::Normal) => Ok(0.),
+            #[cfg(feature = "gecko")]
+            (GenericLineHeight::MozBlockHeight, GenericLineHeight::MozBlockHeight) => Ok(0.),
+            _ => Err(()),
+        }
+    }
+}
--- a/servo/components/style/values/generics/border.rs
+++ b/servo/components/style/values/generics/border.rs
@@ -7,17 +7,17 @@
 use euclid::Size2D;
 use std::fmt;
 use style_traits::ToCss;
 use values::generics::rect::Rect;
 
 /// A generic value for a single side of a `border-image-width` property.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
-pub enum BorderImageWidthSide<LengthOrPercentage, Number> {
+pub enum BorderImageSideWidth<LengthOrPercentage, Number> {
     /// `<length-or-percentage>`
     Length(LengthOrPercentage),
     /// `<number>`
     Number(Number),
     /// `auto`
     Auto,
 }
 
@@ -47,26 +47,26 @@ pub struct BorderRadius<LengthOrPercenta
     pub bottom_left: BorderCornerRadius<LengthOrPercentage>,
 }
 
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
 /// A generic value for `border-*-radius` longhand properties.
 pub struct BorderCornerRadius<L>(pub Size2D<L>);
 
-impl<L, N> ToCss for BorderImageWidthSide<L, N>
+impl<L, N> ToCss for BorderImageSideWidth<L, N>
     where L: ToCss, N: ToCss,
 {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
         where W: fmt::Write
     {
         match *self {
-            BorderImageWidthSide::Length(ref length) => length.to_css(dest),
-            BorderImageWidthSide::Number(ref number) => number.to_css(dest),
-            BorderImageWidthSide::Auto => dest.write_str("auto"),
+            BorderImageSideWidth::Length(ref length) => length.to_css(dest),
+            BorderImageSideWidth::Number(ref number) => number.to_css(dest),
+            BorderImageSideWidth::Auto => dest.write_str("auto"),
         }
     }
 }
 
 impl<N> From<N> for BorderImageSlice<N>
     where N: Clone,
 {
     #[inline]
--- a/servo/components/style/values/generics/mod.rs
+++ b/servo/components/style/values/generics/mod.rs
@@ -14,16 +14,17 @@ use super::CustomIdent;
 
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod grid;
 pub mod image;
 pub mod position;
 pub mod rect;
+pub mod text;
 pub mod transform;
 
 // https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type
 define_css_keyword_enum! { SymbolsType:
     "cyclic" => Cyclic,
     "numeric" => Numeric,
     "alphabetic" => Alphabetic,
     "symbolic" => Symbolic,
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/generics/text.rs
@@ -0,0 +1,129 @@
+/* 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/. */
+
+//! Generic types for text properties.
+
+use app_units::Au;
+use cssparser::Parser;
+use parser::ParserContext;
+use properties::animated_properties::Animatable;
+use std::fmt;
+use style_traits::ToCss;
+
+/// A generic spacing value for the `letter-spacing` and `word-spacing` properties.alloc
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue)]
+pub enum Spacing<Value> {
+    /// `normal`
+    Normal,
+    /// `<value>`
+    Value(Value),
+}
+
+impl<Value> Spacing<Value> {
+    /// Returns `normal`.
+    #[inline]
+    pub fn normal() -> Self {
+        Spacing::Normal
+    }
+
+    /// Parses.
+    #[inline]
+    pub fn parse_with<F>(
+        context: &ParserContext,
+        input: &mut Parser,
+        parse: F)
+        -> Result<Self, ()>
+        where F: FnOnce(&ParserContext, &mut Parser) -> Result<Value, ()>
+    {
+        if input.try(|i| i.expect_ident_matching("normal")).is_ok() {
+            return Ok(Spacing::Normal);
+        }
+        parse(context, input).map(Spacing::Value)
+    }
+
+    /// Returns the spacing value, if not `normal`.
+    #[inline]
+    pub fn value(&self) -> Option<&Value> {
+        match *self {
+            Spacing::Normal => None,
+            Spacing::Value(ref value) => Some(value),
+        }
+    }
+}
+
+impl<Value> Animatable for Spacing<Value>
+    where Value: Animatable + From<Au>,
+{
+    #[inline]
+    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
+        if let (&Spacing::Normal, &Spacing::Normal) = (self, other) {
+            return Ok(Spacing::Normal);
+        }
+        let zero = Value::from(Au(0));
+        let this = self.value().unwrap_or(&zero);
+        let other = other.value().unwrap_or(&zero);
+        this.add_weighted(other, self_portion, other_portion).map(Spacing::Value)
+    }
+
+    #[inline]
+    fn compute_distance(&self, other: &Self) -> Result<f64, ()> {
+        let zero = Value::from(Au(0));
+        let this = self.value().unwrap_or(&zero);
+        let other = other.value().unwrap_or(&zero);
+        this.compute_distance(other)
+    }
+}
+
+impl<Value> ToCss for Spacing<Value>
+    where Value: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+        where W: fmt::Write
+    {
+        match *self {
+            Spacing::Normal => dest.write_str("normal"),
+            Spacing::Value(ref value) => value.to_css(dest),
+        }
+    }
+}
+
+/// A generic value for the `line-height` property.
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq)]
+pub enum LineHeight<Number, LengthOrPercentage> {
+    /// `normal`
+    Normal,
+    /// `-moz-block-height`
+    #[cfg(feature = "gecko")]
+    MozBlockHeight,
+    /// `<number>`
+    Number(Number),
+    /// `<length-or-percentage>`
+    Length(LengthOrPercentage),
+}
+
+impl<N, L> LineHeight<N, L> {
+    /// Returns `normal`.
+    #[inline]
+    pub fn normal() -> Self {
+        LineHeight::Normal
+    }
+}
+
+impl<N, L> ToCss for LineHeight<N, L>
+    where N: ToCss, L: ToCss,
+{
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+        where W: fmt::Write,
+    {
+        match *self {
+            LineHeight::Normal => dest.write_str("normal"),
+            #[cfg(feature = "gecko")]
+            LineHeight::MozBlockHeight => dest.write_str("-moz-block-height"),
+            LineHeight::Number(ref number) => number.to_css(dest),
+            LineHeight::Length(ref value) => value.to_css(dest),
+        }
+    }
+}
--- a/servo/components/style/values/specified/border.rs
+++ b/servo/components/style/values/specified/border.rs
@@ -1,59 +1,136 @@
 /* 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/. */
 
 //! Specified types for CSS values related to borders.
 
+use app_units::Au;
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
+use std::fmt;
+use style_traits::ToCss;
+use values::computed::{Context, ToComputedValue};
 use values::generics::border::BorderCornerRadius as GenericBorderCornerRadius;
+use values::generics::border::BorderImageSideWidth as GenericBorderImageSideWidth;
 use values::generics::border::BorderImageSlice as GenericBorderImageSlice;
-use values::generics::border::BorderImageWidthSide as GenericBorderImageWidthSide;
 use values::generics::border::BorderRadius as GenericBorderRadius;
 use values::generics::rect::Rect;
-use values::specified::{Number, NumberOrPercentage};
-use values::specified::length::LengthOrPercentage;
+use values::specified::{AllowQuirks, Number, NumberOrPercentage};
+use values::specified::length::{Length, LengthOrPercentage};
+
+/// A specified value for a single side of the `border-width` property.
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
+pub enum BorderSideWidth {
+    /// `thin`
+    Thin,
+    /// `medium`
+    Medium,
+    /// `thick`
+    Thick,
+    /// `<length>`
+    Length(Length),
+}
 
 /// A specified value for the `border-image-width` property.
-pub type BorderImageWidth = Rect<BorderImageWidthSide>;
+pub type BorderImageWidth = Rect<BorderImageSideWidth>;
 
 /// A specified value for a single side of a `border-image-width` property.
-pub type BorderImageWidthSide = GenericBorderImageWidthSide<LengthOrPercentage, Number>;
+pub type BorderImageSideWidth = GenericBorderImageSideWidth<LengthOrPercentage, Number>;
 
 /// A specified value for the `border-image-slice` property.
 pub type BorderImageSlice = GenericBorderImageSlice<NumberOrPercentage>;
 
 /// A specified value for the `border-radius` property.
 pub type BorderRadius = GenericBorderRadius<LengthOrPercentage>;
 
 /// A specified value for the `border-*-radius` longhand properties.
 pub type BorderCornerRadius = GenericBorderCornerRadius<LengthOrPercentage>;
 
-impl BorderImageWidthSide {
-    /// Returns `1`.
-    #[inline]
-    pub fn one() -> Self {
-        GenericBorderImageWidthSide::Number(Number::new(1.))
+impl BorderSideWidth {
+    /// Parses, with quirks.
+    pub fn parse_quirky(
+        context: &ParserContext,
+        input: &mut Parser,
+        allow_quirks: AllowQuirks)
+        -> Result<Self, ()>
+    {
+        if let Ok(length) = input.try(|i| Length::parse_non_negative_quirky(context, i, allow_quirks)) {
+            return Ok(BorderSideWidth::Length(length));
+        }
+        match_ignore_ascii_case! { &input.expect_ident()?,
+            "thin" => Ok(BorderSideWidth::Thin),
+            "medium" => Ok(BorderSideWidth::Medium),
+            "thick" => Ok(BorderSideWidth::Thick),
+            _ => Err(())
+        }
+    }
+}
+
+impl Parse for BorderSideWidth {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        Self::parse_quirky(context, input, AllowQuirks::No)
+    }
+}
+
+impl ToCss for BorderSideWidth {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            BorderSideWidth::Thin => dest.write_str("thin"),
+            BorderSideWidth::Medium => dest.write_str("medium"),
+            BorderSideWidth::Thick => dest.write_str("thick"),
+            BorderSideWidth::Length(ref length) => length.to_css(dest)
+        }
     }
 }
 
-impl Parse for BorderImageWidthSide {
+impl ToComputedValue for BorderSideWidth {
+    type ComputedValue = Au;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        // We choose the pixel length of the keyword values the same as both spec and gecko.
+        // Spec: https://drafts.csswg.org/css-backgrounds-3/#line-width
+        // Gecko: https://bugzilla.mozilla.org/show_bug.cgi?id=1312155#c0
+        match *self {
+            BorderSideWidth::Thin => Length::from_px(1.).to_computed_value(context),
+            BorderSideWidth::Medium => Length::from_px(3.).to_computed_value(context),
+            BorderSideWidth::Thick => Length::from_px(5.).to_computed_value(context),
+            BorderSideWidth::Length(ref length) => length.to_computed_value(context)
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        BorderSideWidth::Length(ToComputedValue::from_computed_value(computed))
+    }
+}
+
+impl BorderImageSideWidth {
+    /// Returns `1`.
+    #[inline]
+    pub fn one() -> Self {
+        GenericBorderImageSideWidth::Number(Number::new(1.))
+    }
+}
+
+impl Parse for BorderImageSideWidth {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         if input.try(|i| i.expect_ident_matching("auto")).is_ok() {
-            return Ok(GenericBorderImageWidthSide::Auto);
+            return Ok(GenericBorderImageSideWidth::Auto);
         }
 
         if let Ok(len) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) {
-            return Ok(GenericBorderImageWidthSide::Length(len));
+            return Ok(GenericBorderImageSideWidth::Length(len));
         }
 
         let num = Number::parse_non_negative(context, input)?;
-        Ok(GenericBorderImageWidthSide::Number(num))
+        Ok(GenericBorderImageSideWidth::Number(num))
     }
 }
 
 impl Parse for BorderImageSlice {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         let mut fill = input.try(|i| i.expect_ident_matching("fill")).is_ok();
         let offsets = Rect::parse_with(context, input, NumberOrPercentage::parse_non_negative)?;
         if !fill {
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -2,17 +2,16 @@
  * 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/. */
 
 //! Specified values.
 //!
 //! TODO(emilio): Enhance docs.
 
 use Namespace;
-use app_units::Au;
 use context::QuirksMode;
 use cssparser::{self, Parser, Token, serialize_identifier};
 use itoa;
 use parser::{ParserContext, Parse};
 use self::grid::TrackSizeOrRepeat;
 use self::url::SpecifiedUrl;
 use std::ascii::AsciiExt;
 use std::f32;
@@ -27,42 +26,44 @@ use super::generics::grid::{TrackBreadth
 use super::generics::grid::TrackList as GenericTrackList;
 use values::computed::ComputedValueAsSpecified;
 use values::specified::calc::CalcNode;
 
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use self::background::BackgroundSize;
 pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth};
-pub use self::border::{BorderImageWidthSide, BorderRadius};
+pub use self::border::{BorderImageSideWidth, BorderRadius, BorderSideWidth};
 pub use self::color::Color;
 pub use self::rect::LengthOrNumberRect;
 pub use super::generics::grid::GridLine;
 pub use self::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use self::image::{GradientItem, GradientKind, Image, ImageRect, ImageLayer};
 pub use self::length::AbsoluteLength;
 pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
 pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength};
 pub use self::length::{MaxLength, MozLength};
 pub use self::position::{Position, PositionComponent};
+pub use self::text::{LetterSpacing, LineHeight, WordSpacing};
 pub use self::transform::TransformOrigin;
 
 #[cfg(feature = "gecko")]
 pub mod align;
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod calc;
 pub mod color;
 pub mod grid;
 pub mod image;
 pub mod length;
 pub mod position;
 pub mod rect;
+pub mod text;
 pub mod transform;
 
 /// Common handling for the specified value CSS url() values.
 pub mod url {
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use values::computed::ComputedValueAsSpecified;
 
@@ -427,102 +428,16 @@ impl Angle {
             Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
                 input.parse_nested_block(|i| CalcNode::parse_angle(context, i))
             }
             _ => Err(())
         }
     }
 }
 
-#[allow(missing_docs)]
-pub fn parse_border_width(context: &ParserContext, input: &mut Parser) -> Result<Length, ()> {
-    input.try(|i| Length::parse_non_negative(context, i)).or_else(|()| {
-        match_ignore_ascii_case! { &try!(input.expect_ident()),
-            "thin" => Ok(Length::from_px(1.)),
-            "medium" => Ok(Length::from_px(3.)),
-            "thick" => Ok(Length::from_px(5.)),
-            _ => Err(())
-        }
-    })
-}
-
-#[derive(Clone, Debug, HasViewportPercentage, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub enum BorderWidth {
-    Thin,
-    Medium,
-    Thick,
-    Width(Length),
-}
-
-impl Parse for BorderWidth {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<BorderWidth, ()> {
-        Self::parse_quirky(context, input, AllowQuirks::No)
-    }
-}
-
-impl BorderWidth {
-    /// Parses a border width, allowing quirks.
-    pub fn parse_quirky(context: &ParserContext,
-                        input: &mut Parser,
-                        allow_quirks: AllowQuirks)
-                        -> Result<BorderWidth, ()> {
-        match input.try(|i| Length::parse_non_negative_quirky(context, i, allow_quirks)) {
-            Ok(length) => Ok(BorderWidth::Width(length)),
-            Err(_) => match_ignore_ascii_case! { &try!(input.expect_ident()),
-               "thin" => Ok(BorderWidth::Thin),
-               "medium" => Ok(BorderWidth::Medium),
-               "thick" => Ok(BorderWidth::Thick),
-               _ => Err(())
-            }
-        }
-    }
-}
-
-impl BorderWidth {
-    #[allow(missing_docs)]
-    pub fn from_length(length: Length) -> Self {
-        BorderWidth::Width(length)
-    }
-}
-
-impl ToCss for BorderWidth {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        match *self {
-            BorderWidth::Thin => dest.write_str("thin"),
-            BorderWidth::Medium => dest.write_str("medium"),
-            BorderWidth::Thick => dest.write_str("thick"),
-            BorderWidth::Width(ref length) => length.to_css(dest)
-        }
-    }
-}
-
-impl ToComputedValue for BorderWidth {
-    type ComputedValue = Au;
-
-    #[inline]
-    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
-        // We choose the pixel length of the keyword values the same as both spec and gecko.
-        // Spec: https://drafts.csswg.org/css-backgrounds-3/#line-width
-        // Gecko: https://bugzilla.mozilla.org/show_bug.cgi?id=1312155#c0
-        match *self {
-            BorderWidth::Thin => Length::from_px(1.).to_computed_value(context),
-            BorderWidth::Medium => Length::from_px(3.).to_computed_value(context),
-            BorderWidth::Thick => Length::from_px(5.).to_computed_value(context),
-            BorderWidth::Width(ref length) => length.to_computed_value(context)
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
-        BorderWidth::Width(ToComputedValue::from_computed_value(computed))
-    }
-}
-
 // The integer values here correspond to the border conflict resolution rules in CSS 2.1 §
 // 17.6.2.1. Higher values override lower values.
 define_numbered_css_keyword_enum! { BorderStyle:
     "none" => none = -1,
     "solid" => solid = 6,
     "double" => double = 7,
     "dotted" => dotted = 4,
     "dashed" => dashed = 5,
@@ -1391,66 +1306,69 @@ impl Parse for Attr {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Attr, ()> {
         input.expect_function_matching("attr")?;
         input.parse_nested_block(|i| Attr::parse_function(context, i))
     }
 }
 
 #[cfg(feature = "gecko")]
 /// Get the namespace id from the namespace map
-pub fn get_id_for_namespace(namespace: &Namespace, context: &ParserContext) -> Result<NamespaceId, ()> {
-    if let Some(map) = context.namespaces {
-        if let Some(ref entry) = map.read().prefixes.get(&namespace.0) {
-            Ok(entry.1)
-        } else {
-            Err(())
+fn get_id_for_namespace(namespace: &Namespace, context: &ParserContext) -> Result<NamespaceId, ()> {
+    let namespaces_map = match context.namespaces {
+        Some(map) => map,
+        None => {
+            // If we don't have a namespace map (e.g. in inline styles)
+            // we can't parse namespaces
+            return Err(());
         }
-    } else {
-        // if we don't have a namespace map (e.g. in inline styles)
-        // we can't parse namespaces
-        Err(())
+    };
+
+    match namespaces_map.prefixes.get(&namespace.0) {
+        Some(entry) => Ok(entry.1),
+        None => Err(()),
     }
 }
 
 #[cfg(feature = "servo")]
 /// Get the namespace id from the namespace map
-pub fn get_id_for_namespace(_: &Namespace, _: &ParserContext) -> Result<NamespaceId, ()> {
+fn get_id_for_namespace(_: &Namespace, _: &ParserContext) -> Result<NamespaceId, ()> {
     Ok(())
 }
 
 impl Attr {
     /// Parse contents of attr() assuming we have already parsed `attr` and are
     /// within a parse_nested_block()
     pub fn parse_function(context: &ParserContext, input: &mut Parser) -> Result<Attr, ()> {
         // Syntax is `[namespace? `|`]? ident`
         // no spaces allowed
         let first = input.try(|i| i.expect_ident()).ok();
         if let Ok(token) = input.try(|i| i.next_including_whitespace()) {
             match token {
-                Token::Delim('|') => {
-                    // must be followed by an ident
-                    let second_token = match input.next_including_whitespace()? {
-                        Token::Ident(second) => second,
-                        _ => return Err(()),
-                    };
-                    let ns_with_id = if let Some(ns) = first {
-                        let ns: Namespace = ns.into();
-                        let id = get_id_for_namespace(&ns, context)?;
-                        Some((ns, id))
-                    } else {
-                        None
-                    };
-                    return Ok(Attr {
-                        namespace: ns_with_id,
-                        attribute: second_token.into_owned(),
-                    })
-                }
-                _ => return Err(())
+                Token::Delim('|') => {}
+                _ => return Err(()),
             }
+            // must be followed by an ident
+            let second_token = match input.next_including_whitespace()? {
+                Token::Ident(second) => second,
+                _ => return Err(()),
+            };
+
+            let ns_with_id = if let Some(ns) = first {
+                let ns: Namespace = ns.into();
+                let id = get_id_for_namespace(&ns, context)?;
+                Some((ns, id))
+            } else {
+                None
+            };
+            return Ok(Attr {
+                namespace: ns_with_id,
+                attribute: second_token.into_owned(),
+            })
         }
+
         if let Some(first) = first {
             Ok(Attr {
                 namespace: None,
                 attribute: first.into_owned(),
             })
         } else {
             Err(())
         }
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/text.rs
@@ -0,0 +1,119 @@
+/* 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/. */
+
+//! Specified types for text properties.
+
+use cssparser::Parser;
+use parser::{Parse, ParserContext};
+use std::ascii::AsciiExt;
+use values::computed::{Context, ToComputedValue};
+use values::computed::text::LineHeight as ComputedLineHeight;
+use values::generics::text::{LineHeight as GenericLineHeight, Spacing};
+use values::specified::{AllowQuirks, Number};
+use values::specified::length::{FontRelativeLength, Length, LengthOrPercentage, NoCalcLength};
+
+/// A specified value for the `letter-spacing` property.
+pub type LetterSpacing = Spacing<Length>;
+
+/// A specified value for the `word-spacing` property.
+pub type WordSpacing = Spacing<LengthOrPercentage>;
+
+/// A specified value for the `line-height` property.
+pub type LineHeight = GenericLineHeight<Number, LengthOrPercentage>;
+
+impl Parse for LetterSpacing {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        Spacing::parse_with(context, input, |c, i| {
+            Length::parse_quirky(c, i, AllowQuirks::Yes)
+        })
+    }
+}
+
+impl Parse for WordSpacing {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        Spacing::parse_with(context, input, |c, i| {
+            LengthOrPercentage::parse_quirky(c, i, AllowQuirks::Yes)
+        })
+    }
+}
+
+impl Parse for LineHeight {
+    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        if let Ok(number) = input.try(|i| Number::parse_non_negative(context, i)) {
+            return Ok(GenericLineHeight::Number(number))
+        }
+        if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) {
+            return Ok(GenericLineHeight::Length(lop))
+        }
+        match &input.expect_ident()? {
+            ident if ident.eq_ignore_ascii_case("normal") => {
+                Ok(GenericLineHeight::Normal)
+            },
+            #[cfg(feature = "gecko")]
+            ident if ident.eq_ignore_ascii_case("-moz-block-height") => {
+                Ok(GenericLineHeight::MozBlockHeight)
+            },
+            _ => Err(()),
+        }
+    }
+}
+
+impl ToComputedValue for LineHeight {
+    type ComputedValue = ComputedLineHeight;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        match *self {
+            GenericLineHeight::Normal => {
+                GenericLineHeight::Normal
+            },
+            #[cfg(feature = "gecko")]
+            GenericLineHeight::MozBlockHeight => {
+                GenericLineHeight::MozBlockHeight
+            },
+            GenericLineHeight::Number(number) => {
+                GenericLineHeight::Number(number.to_computed_value(context))
+            },
+            GenericLineHeight::Length(LengthOrPercentage::Length(ref length)) => {
+                GenericLineHeight::Length(length.to_computed_value(context))
+            },
+            GenericLineHeight::Length(LengthOrPercentage::Percentage(p)) => {
+                let font_relative_length =
+                    Length::NoCalc(NoCalcLength::FontRelative(FontRelativeLength::Em(p.0)));
+                GenericLineHeight::Length(font_relative_length.to_computed_value(context))
+            },
+            GenericLineHeight::Length(LengthOrPercentage::Calc(ref calc)) => {
+                let computed_calc = calc.to_computed_value(context);
+                let font_relative_length =
+                    Length::NoCalc(NoCalcLength::FontRelative(FontRelativeLength::Em(computed_calc.percentage())));
+                let absolute_length = computed_calc.unclamped_length();
+                let computed_length = computed_calc.clamping_mode.clamp(
+                    absolute_length + font_relative_length.to_computed_value(context)
+                );
+                GenericLineHeight::Length(computed_length)
+            },
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        match *computed {
+            GenericLineHeight::Normal => {
+                GenericLineHeight::Normal
+            },
+            #[cfg(feature = "gecko")]
+            GenericLineHeight::MozBlockHeight => {
+                GenericLineHeight::MozBlockHeight
+            },
+            GenericLineHeight::Number(ref number) => {
+                GenericLineHeight::Number(Number::from_computed_value(number))
+            },
+            GenericLineHeight::Length(ref length) => {
+                GenericLineHeight::Length(LengthOrPercentage::Length(
+                    NoCalcLength::from_computed_value(length)
+                ))
+            }
+        }
+    }
+}
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -1941,30 +1941,30 @@ pub extern "C" fn Servo_DeclarationBlock
 pub extern "C" fn Servo_DeclarationBlock_SetPixelValue(declarations:
                                                        RawServoDeclarationBlockBorrowed,
                                                        property: nsCSSPropertyID,
                                                        value: f32) {
     use style::properties::{PropertyDeclaration, LonghandId};
     use style::properties::longhands::border_spacing::SpecifiedValue as BorderSpacing;
     use style::properties::longhands::height::SpecifiedValue as Height;
     use style::properties::longhands::width::SpecifiedValue as Width;
-    use style::values::specified::BorderWidth;
+    use style::values::specified::BorderSideWidth;
     use style::values::specified::MozLength;
     use style::values::specified::length::{NoCalcLength, LengthOrPercentage};
 
     let long = get_longhand_from_id!(property);
     let nocalc = NoCalcLength::from_px(value);
 
     let prop = match_wrap_declared! { long,
         Height => Height(MozLength::LengthOrPercentageOrAuto(nocalc.into())),
         Width => Width(MozLength::LengthOrPercentageOrAuto(nocalc.into())),
-        BorderTopWidth => BorderWidth::Width(nocalc.into()),
-        BorderRightWidth => BorderWidth::Width(nocalc.into()),
-        BorderBottomWidth => BorderWidth::Width(nocalc.into()),
-        BorderLeftWidth => BorderWidth::Width(nocalc.into()),
+        BorderTopWidth => BorderSideWidth::Length(nocalc.into()),
+        BorderRightWidth => BorderSideWidth::Length(nocalc.into()),
+        BorderBottomWidth => BorderSideWidth::Length(nocalc.into()),
+        BorderLeftWidth => BorderSideWidth::Length(nocalc.into()),
         MarginTop => nocalc.into(),
         MarginRight => nocalc.into(),
         MarginBottom => nocalc.into(),
         MarginLeft => nocalc.into(),
         PaddingTop => nocalc.into(),
         PaddingRight => nocalc.into(),
         PaddingBottom => nocalc.into(),
         PaddingLeft => nocalc.into(),
--- a/servo/tests/unit/style/parsing/inherited_text.rs
+++ b/servo/tests/unit/style/parsing/inherited_text.rs
@@ -1,34 +1,34 @@
 /* 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 parsing::parse;
+use style::values::generics::text::Spacing;
 
 #[test]
 fn negative_letter_spacing_should_parse_properly() {
     use style::properties::longhands::letter_spacing;
-    use style::properties::longhands::letter_spacing::SpecifiedValue;
     use style::values::specified::length::{Length, NoCalcLength, FontRelativeLength};
 
     let negative_value = parse_longhand!(letter_spacing, "-0.5em");
-    let expected = SpecifiedValue::Specified(Length::NoCalc(NoCalcLength::FontRelative(FontRelativeLength::Em(-0.5))));
+    let expected = Spacing::Value(Length::NoCalc(NoCalcLength::FontRelative(FontRelativeLength::Em(-0.5))));
     assert_eq!(negative_value, expected);
 }
 
 #[test]
 fn negative_word_spacing_should_parse_properly() {
     use style::properties::longhands::word_spacing;
-    use style::properties::longhands::word_spacing::SpecifiedValue;
     use style::values::specified::length::{NoCalcLength, LengthOrPercentage, FontRelativeLength};
 
     let negative_value = parse_longhand!(word_spacing, "-0.5em");
-    let expected = SpecifiedValue::Specified(LengthOrPercentage::Length(NoCalcLength::FontRelative(
-                                             FontRelativeLength::Em(-0.5))));
+    let expected = Spacing::Value(LengthOrPercentage::Length(
+        NoCalcLength::FontRelative(FontRelativeLength::Em(-0.5))
+    ));
     assert_eq!(negative_value, expected);
 }
 
 #[test]
 fn text_emphasis_style_longhand_should_parse_properly() {
     use style::properties::longhands::text_emphasis_style;
     use style::properties::longhands::text_emphasis_style::{ShapeKeyword, SpecifiedValue, KeywordValue};
 
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -4,17 +4,17 @@
 
 use properties::parse;
 use style::computed_values::display::T::inline_block;
 use style::properties::{PropertyDeclaration, Importance, PropertyId};
 use style::properties::longhands::outline_color::computed_value::T as ComputedColor;
 use style::properties::parse_property_declaration_list;
 use style::values::{RGBA, Auto};
 use style::values::CustomIdent;
-use style::values::specified::{BorderStyle, BorderWidth, CSSColor, Length, LengthOrPercentage};
+use style::values::specified::{BorderStyle, BorderSideWidth, CSSColor, Length, LengthOrPercentage};
 use style::values::specified::{LengthOrPercentageOrAuto, LengthOrPercentageOrAutoOrContent};
 use style::values::specified::{NoCalcLength, PositionComponent};
 use style::values::specified::position::Y;
 use style::values::specified::url::SpecifiedUrl;
 use style_traits::ToCss;
 use stylesheets::block_from;
 
 #[test]
@@ -216,18 +216,18 @@ mod shorthand_serialization {
 
           let solid = BorderStyle::solid;
 
           properties.push(PropertyDeclaration::BorderTopStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderRightStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderBottomStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderLeftStyle(solid.clone()));
 
-          let px_30 = BorderWidth::from_length(Length::from_px(30f32));
-          let px_10 = BorderWidth::from_length(Length::from_px(10f32));
+          let px_30 = BorderSideWidth::Length(Length::from_px(30f32));
+          let px_10 = BorderSideWidth::Length(Length::from_px(10f32));
 
           properties.push(PropertyDeclaration::BorderTopWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderRightWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderBottomWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderLeftWidth(px_10.clone()));
 
           let blue = CSSColor {
               parsed: ComputedColor::RGBA(RGBA::new(0, 0, 255, 255)),
@@ -250,17 +250,17 @@ mod shorthand_serialization {
 
           let solid = BorderStyle::solid;
 
           properties.push(PropertyDeclaration::BorderTopStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderRightStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderBottomStyle(solid.clone()));
           properties.push(PropertyDeclaration::BorderLeftStyle(solid.clone()));
 
-          let px_30 = BorderWidth::from_length(Length::from_px(30f32));
+          let px_30 = BorderSideWidth::Length(Length::from_px(30f32));
 
           properties.push(PropertyDeclaration::BorderTopWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderRightWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderBottomWidth(px_30.clone()));
           properties.push(PropertyDeclaration::BorderLeftWidth(px_30.clone()));
 
           let blue = CSSColor {
               parsed: ComputedColor::RGBA(RGBA::new(0, 0, 255, 255)),
@@ -290,39 +290,39 @@ mod shorthand_serialization {
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "padding: 10px 15px;");
         }
 
         #[test]
         fn border_width_should_serialize_correctly() {
             let mut properties = Vec::new();
 
-            let top_px = BorderWidth::from_length(Length::from_px(10f32));
-            let bottom_px = BorderWidth::from_length(Length::from_px(10f32));
+            let top_px = BorderSideWidth::Length(Length::from_px(10f32));
+            let bottom_px = BorderSideWidth::Length(Length::from_px(10f32));
 
-            let right_px = BorderWidth::from_length(Length::from_px(15f32));
-            let left_px = BorderWidth::from_length(Length::from_px(15f32));
+            let right_px = BorderSideWidth::Length(Length::from_px(15f32));
+            let left_px = BorderSideWidth::Length(Length::from_px(15f32));
 
             properties.push(PropertyDeclaration::BorderTopWidth(top_px));
             properties.push(PropertyDeclaration::BorderRightWidth(right_px));
             properties.push(PropertyDeclaration::BorderBottomWidth(bottom_px));
             properties.push(PropertyDeclaration::BorderLeftWidth(left_px));
 
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "border-width: 10px 15px;");
         }
 
         #[test]
         fn border_width_with_keywords_should_serialize_correctly() {
             let mut properties = Vec::new();
 
-            let top_px = BorderWidth::Thin;
-            let right_px = BorderWidth::Medium;
-            let bottom_px = BorderWidth::Thick;
-            let left_px = BorderWidth::from_length(Length::from_px(15f32));
+            let top_px = BorderSideWidth::Thin;
+            let right_px = BorderSideWidth::Medium;
+            let bottom_px = BorderSideWidth::Thick;
+            let left_px = BorderSideWidth::Length(Length::from_px(15f32));
 
             properties.push(PropertyDeclaration::BorderTopWidth(top_px));
             properties.push(PropertyDeclaration::BorderRightWidth(right_px));
             properties.push(PropertyDeclaration::BorderBottomWidth(bottom_px));
             properties.push(PropertyDeclaration::BorderLeftWidth(left_px));
 
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "border-width: thin medium thick 15px;");
@@ -398,33 +398,33 @@ mod shorthand_serialization {
 
         // we can use border-top as a base to test out the different combinations
         // but afterwards, we only need to to one test per "directional border shorthand"
 
         #[test]
         fn directional_border_should_show_all_properties_when_values_are_set() {
             let mut properties = Vec::new();
 
-            let width = BorderWidth::from_length(Length::from_px(4f32));
+            let width = BorderSideWidth::Length(Length::from_px(4f32));
             let style = BorderStyle::solid;
             let color = CSSColor {
                 parsed: ComputedColor::RGBA(RGBA::new(255, 0, 0, 255)),
                 authored: None
             };
 
             properties.push(PropertyDeclaration::BorderTopWidth(width));
             properties.push(PropertyDeclaration::BorderTopStyle(style));
             properties.push(PropertyDeclaration::BorderTopColor(color));
 
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "border-top: 4px solid rgb(255, 0, 0);");
         }
 
-        fn get_border_property_values() -> (BorderWidth, BorderStyle, CSSColor) {
-            (BorderWidth::from_length(Length::from_px(4f32)),
+        fn get_border_property_values() -> (BorderSideWidth, BorderStyle, CSSColor) {
+            (BorderSideWidth::Length(Length::from_px(4f32)),
              BorderStyle::solid,
              CSSColor::currentcolor())
         }
 
         #[test]
         fn border_top_should_serialize_correctly() {
             let mut properties = Vec::new();
             let (width, style, color) = get_border_property_values();
@@ -518,25 +518,24 @@ mod shorthand_serialization {
             properties.push(PropertyDeclaration::ListStyleType(style_type));
 
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "list-style: inside url(\"http://servo/test.png\") disc;");
         }
     }
 
     mod outline {
-        use style::properties::longhands::outline_width::SpecifiedValue as WidthContainer;
         use style::values::Either;
         use super::*;
 
         #[test]
         fn outline_should_show_all_properties_when_set() {
             let mut properties = Vec::new();
 
-            let width = WidthContainer(Length::from_px(4f32));
+            let width = BorderSideWidth::Length(Length::from_px(4f32));
             let style = Either::Second(BorderStyle::solid);
             let color = CSSColor {
                 parsed: ComputedColor::RGBA(RGBA::new(255, 0, 0, 255)),
                 authored: None
             };
 
             properties.push(PropertyDeclaration::OutlineWidth(width));
             properties.push(PropertyDeclaration::OutlineStyle(style));
@@ -545,17 +544,17 @@ mod shorthand_serialization {
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "outline: 4px solid rgb(255, 0, 0);");
         }
 
         #[test]
         fn outline_should_serialize_correctly_when_style_is_auto() {
             let mut properties = Vec::new();
 
-            let width = WidthContainer(Length::from_px(4f32));
+            let width = BorderSideWidth::Length(Length::from_px(4f32));
             let style = Either::First(Auto);
             let color = CSSColor {
                 parsed: ComputedColor::RGBA(RGBA::new(255, 0, 0, 255)),
                 authored: None
             };
             properties.push(PropertyDeclaration::OutlineWidth(width));
             properties.push(PropertyDeclaration::OutlineStyle(style));
             properties.push(PropertyDeclaration::OutlineColor(color));
--- a/servo/tests/unit/style/properties/viewport.rs
+++ b/servo/tests/unit/style/properties/viewport.rs
@@ -1,27 +1,27 @@
 /* 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 app_units::Au;
 use style::properties::PropertyDeclaration;
-use style::properties::longhands::border_top_width;
 use style::values::specified::{AbsoluteLength, Length, NoCalcLength, ViewportPercentageLength};
+use style::values::specified::border::BorderSideWidth;
 use style_traits::HasViewportPercentage;
 
 #[test]
 fn has_viewport_percentage_for_specified_value() {
     //TODO: test all specified value with a HasViewportPercentage impl
     let pvw = PropertyDeclaration::BorderTopWidth(
-        border_top_width::SpecifiedValue::from_length(
+        BorderSideWidth::Length(
             Length::NoCalc(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(100.)))
         )
     );
     assert!(pvw.has_viewport_percentage());
 
     let pabs = PropertyDeclaration::BorderTopWidth(
-        border_top_width::SpecifiedValue::from_length(
+        BorderSideWidth::Length(
             Length::NoCalc(NoCalcLength::Absolute(AbsoluteLength::Px(Au(100).to_f32_px())))
         )
     );
     assert!(!pabs.has_viewport_percentage());
 }
--- a/taskcluster/taskgraph/transforms/job/mozharness_test.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness_test.py
@@ -314,18 +314,17 @@ def mozharness_test_on_generic_worker(co
 
 
 @run_job_using('native-engine', 'mozharness-test', schema=mozharness_test_run_schema)
 def mozharness_test_on_native_engine(config, job, taskdesc):
     test = taskdesc['run']['test']
     mozharness = test['mozharness']
     worker = taskdesc['worker']
     is_talos = test['suite'] == 'talos'
-
-    assert worker['os'] == 'macosx'
+    is_macosx = worker['os'] == 'macosx'
 
     installer_url = get_artifact_url('<build>', mozharness['build-artifact-name'])
     mozharness_url = get_artifact_url('<build>',
                                       'public/build/mozharness.zip')
 
     worker['artifacts'] = [{
         'name': prefix.rstrip('/'),
         'path': path.rstrip('/'),
@@ -354,17 +353,17 @@ def mozharness_test_on_native_engine(con
         "MOZ_HIDE_RESULTS_TABLE": '1',
         "MOZ_NODE_PATH": "/usr/local/bin/node",
         'MOZ_AUTOMATION': '1',
     }
     # talos tests don't need Xvfb
     if is_talos:
         env['NEED_XVFB'] = 'false'
 
-    script = 'test-macosx.sh' if test['test-platform'].startswith('macosx') else 'test-linux.sh'
+    script = 'test-macosx.sh' if is_macosx else 'test-linux.sh'
     worker['context'] = '{}/raw-file/{}/taskcluster/scripts/tester/{}'.format(
         config.params['head_repository'], config.params['head_rev'], script
     )
 
     command = worker['command'] = ["./{}".format(script)]
     if mozharness.get('no-read-buildbot-config'):
         command.append("--no-read-buildbot-config")
     command.extend([
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -48,17 +48,20 @@ LINUX_WORKER_TYPES = {
 
 # windows / os x worker types keyed by test-platform
 WINDOWS_WORKER_TYPES = {
     'windows7-32-vm': 'aws-provisioner-v1/gecko-t-win7-32',
     'windows7-32': 'aws-provisioner-v1/gecko-t-win7-32-gpu',
     'windows10-64-vm': 'aws-provisioner-v1/gecko-t-win10-64',
     'windows10-64': 'aws-provisioner-v1/gecko-t-win10-64-gpu',
     'windows10-64-asan': 'aws-provisioner-v1/gecko-t-win10-64-gpu',
-    'macosx64': 'scl3-puppet/os-x-10-10-gw'
+}
+
+MACOSX_WORKER_TYPES = {
+    'macosx64': 'releng-hardware/gecko-t-osx-1010',
 }
 
 logger = logging.getLogger(__name__)
 
 transforms = TransformSequence()
 
 # Schema for a test description
 #
@@ -693,17 +696,17 @@ def parallel_stylo_tests(config, tests):
 def set_worker_type(config, tests):
     """Set the worker type based on the test platform."""
     for test in tests:
         # during the taskcluuster migration, this is a bit tortured, but it
         # will get simpler eventually!
         test_platform = test['test-platform']
         if test_platform.startswith('macosx'):
             # note that some portion of these will be allocated to BBB below
-            test['worker-type'] = 'tc-worker-provisioner/gecko-t-osx-10-10'
+            test['worker-type'] = MACOSX_WORKER_TYPES['macosx64']
         elif test_platform.startswith('win'):
             if test.get('suite', '') == 'talos':
                 test['worker-type'] = 'buildbot-bridge/buildbot-bridge'
             else:
                 test['worker-type'] = WINDOWS_WORKER_TYPES[test_platform.split('/')[0]]
         elif test_platform.startswith('linux') or test_platform.startswith('android'):
             if test.get('suite', '') == 'talos':
                 if config.config['args'].taskcluster_worker:
--- a/taskcluster/taskgraph/util/workertypes.py
+++ b/taskcluster/taskgraph/util/workertypes.py
@@ -31,17 +31,17 @@ WORKER_TYPES = {
     'invalid/invalid': ('invalid', None),
     'null-provisioner/human-breakpoint': ('push-apk-breakpoint', None),
     'null-provisioner/human-breakpoint': ('push-apk-breakpoint', None),
     'releng-hardware/gecko-t-linux-talos': ('native-engine', 'linux'),
     'scriptworker-prov-v1/balrogworker-v1': ('balrog', None),
     'scriptworker-prov-v1/beetmoverworker-v1': ('beetmover', None),
     'scriptworker-prov-v1/pushapk-v1': ('push-apk', None),
     "scriptworker-prov-v1/signing-linux-v1": ('scriptworker-signing', None),
-    'tc-worker-provisioner/gecko-t-osx-10-10': ('native-engine', 'macosx'),
+    'releng-hardware/gecko-t-osx-1010': ('generic-worker', 'macosx'),
 }
 
 
 def worker_type_implementation(worker_type):
     """Get the worker implementation and OS for the given workerType, where the
     OS represents the host system, not the target OS, in the case of
     cross-compiles."""
     # assume that worker types for all levels are the same implementation
--- a/testing/mozharness/mozharness/mozilla/testing/codecoverage.py
+++ b/testing/mozharness/mozharness/mozilla/testing/codecoverage.py
@@ -29,16 +29,17 @@ code_coverage_config_options = [
 
 
 class CodeCoverageMixin(object):
     """
     Mixin for setting GCOV_PREFIX during test execution, packaging up
     the resulting .gcda files and uploading them to blobber.
     """
     gcov_dir = None
+    jsvm_dir = None
 
     @property
     def code_coverage_enabled(self):
         try:
             if self.config.get('code_coverage'):
                 return True
 
             # XXX workaround because bug 1110465 is hard
@@ -56,22 +57,26 @@ class CodeCoverageMixin(object):
             return False
 
     @PreScriptAction('run-tests')
     def _set_gcov_prefix(self, action):
         if not self.code_coverage_enabled:
             return
         self.gcov_dir = tempfile.mkdtemp()
         os.environ['GCOV_PREFIX'] = self.gcov_dir
+        # Set JSVM directory also.
+        self.jsvm_dir = tempfile.mkdtemp()
+        os.environ['JS_CODE_COVERAGE_OUTPUT_DIR'] = self.jsvm_dir
 
     @PostScriptAction('run-tests')
     def _package_coverage_data(self, action, success=None):
         if not self.code_coverage_enabled:
             return
         del os.environ['GCOV_PREFIX']
+        del os.environ['JS_CODE_COVERAGE_OUTPUT_DIR']
 
         if not self.ccov_upload_disabled:
             # TODO This is fragile, find rel_topsrcdir properly somehow
             # We need to find the path relative to the gecko topsrcdir. Use
             # some known gecko directories as a test.
             canary_dirs = ['browser', 'docshell', 'dom', 'js', 'layout', 'toolkit', 'xpcom', 'xpfe']
             rel_topsrcdir = None
             for root, dirs, files in os.walk(self.gcov_dir):
@@ -80,14 +85,24 @@ class CodeCoverageMixin(object):
                     rel_topsrcdir = root
                     break
             else:
                 # Unable to upload code coverage files. Since this is the whole
                 # point of code coverage, making this fatal.
                 self.fatal("Could not find relative topsrcdir in code coverage "
                            "data!")
 
+            # Package GCOV coverage data.
             dirs = self.query_abs_dirs()
             file_path = os.path.join(
                 dirs['abs_blob_upload_dir'], 'code-coverage-gcda.zip')
             command = ['zip', '-r', file_path, '.']
             self.run_command(command, cwd=rel_topsrcdir)
+
+            # Package JSVM coverage data.
+            dirs = self.query_abs_dirs()
+            file_path = os.path.join(
+                dirs['abs_blob_upload_dir'], 'code-coverage-jsvm.zip')
+            command = ['zip', '-r', file_path, '.']
+            self.run_command(command, cwd=self.jsvm_dir)
+
         shutil.rmtree(self.gcov_dir)
+        shutil.rmtree(self.jsvm_dir)
--- a/testing/mozharness/mozharness/mozilla/testing/firefox_ui_tests.py
+++ b/testing/mozharness/mozharness/mozilla/testing/firefox_ui_tests.py
@@ -249,19 +249,20 @@ class FirefoxUITests(TestingMixin, VCSTo
 
         # Set further environment settings
         env = env or self.query_env()
         env.update({'MINIDUMP_SAVE_PATH': dirs['abs_blob_upload_dir']})
         if self.query_minidump_stackwalk():
             env.update({'MINIDUMP_STACKWALK': self.minidump_stackwalk_path})
         env['RUST_BACKTRACE'] = '1'
 
-        # If code coverage is enabled, set GCOV_PREFIX env variable
+        # If code coverage is enabled, set GCOV_PREFIX and JS_CODE_COVERAGE_OUTPUT_DIR env variables
         if self.config.get('code_coverage'):
             env['GCOV_PREFIX'] = self.gcov_dir
+            env['JS_CODE_COVERAGE_OUTPUT_DIR'] = self.jsvm_dir
 
         if self.config['allow_software_gl_layers']:
             env['MOZ_LAYERS_ALLOW_SOFTWARE_GL'] = '1'
         if self.config['enable_webrender']:
             env['MOZ_WEBRENDER'] = '1'
 
         return_code = self.run_command(cmd,
                                        cwd=dirs['abs_work_dir'],
--- a/testing/web-platform/tests/tools/manifest/manifest.py
+++ b/testing/web-platform/tests/tools/manifest/manifest.py
@@ -46,16 +46,25 @@ class Manifest(object):
             for path, tests in sorted(iteritems(self._data[item_type])):
                 yield item_type, path, tests
 
     def iterpath(self, path):
         for type_tests in self._data.values():
             for test in type_tests.get(path, set()):
                 yield test
 
+    def iterdir(self, dir_name):
+        if not dir_name.endswith(os.path.sep):
+            dir_name = dir_name + os.path.sep
+        for type_tests in self._data.values():
+            for path, tests in type_tests.iteritems():
+                if path.startswith(dir_name):
+                    for test in tests:
+                        yield test
+
     @property
     def reftest_nodes_by_url(self):
         if self._reftest_nodes_by_url is None:
             by_url = {}
             for path, nodes in iteritems(self._data.get("reftests", {})):
                 for node in nodes:
                     by_url[node.url] = node
             self._reftest_nodes_by_url = by_url
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/manifestinclude.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/manifestinclude.py
@@ -88,17 +88,18 @@ class IncludeManifest(ManifestItem):
         paths = glob.glob(maybe_path)
 
         if paths:
             urls = []
             for path in paths:
                 for manifest, data in test_manifests.iteritems():
                     found = False
                     rel_path = os.path.relpath(path, data["tests_path"])
-                    for test in manifest.iterpath(rel_path):
+                    iterator = manifest.iterpath if os.path.isfile(path) else manifest.iterdir
+                    for test in iterator(rel_path):
                         if not hasattr(test, "url"):
                             continue
                         url = test.url
                         if query or fragment:
                             parsed = urlparse.urlparse(url)
                             if ((query and query != parsed.query) or
                                 (fragment and fragment != parsed.fragment)):
                                 continue
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -47,18 +47,18 @@ function WebRequestEventManager(context,
         requestId: data.requestId,
         url: data.url,
         originUrl: data.originUrl,
         documentUrl: data.documentUrl,
         method: data.method,
         tabId: browserData.tabId,
         type: data.type,
         timeStamp: Date.now(),
-        frameId: data.type == "main_frame" ? 0 : data.windowId,
-        parentFrameId: data.type == "main_frame" ? -1 : data.parentWindowId,
+        frameId: data.windowId,
+        parentFrameId: data.parentWindowId,
       };
 
       const maybeCached = ["onResponseStarted", "onBeforeRedirect", "onCompleted", "onErrorOccurred"];
       if (maybeCached.includes(eventName)) {
         data2.fromCache = !!data.fromCache;
       }
 
       if ("ip" in data) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/file_simple_xhr.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+
+<script>
+"use strict";
+
+let req = new XMLHttpRequest();
+req.open("GET", "http://example.org/example.txt");
+req.send();
+</script>
+<img src="file_image_good.png"/>
+<iframe src="file_simple_xhr_frame.html"/>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/file_simple_xhr_frame.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+
+<script>
+"use strict";
+
+let req = new XMLHttpRequest();
+req.open("GET", "/xhr_resource");
+req.send();
+</script>
+<img src="file_image_bad.png"/>
+<iframe src="file_simple_xhr_frame2.html"/>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/file_simple_xhr_frame2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+
+<script>
+"use strict";
+
+let req = new XMLHttpRequest();
+req.open("GET", "/xhr_resource_2");
+req.send();
+</script>
+<img src="file_image_bad.png#2"/>
+</body>
+</html>
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -31,16 +31,19 @@ support-files =
   file_style_bad.css
   file_style_redirect.css
   file_script_good.js
   file_script_bad.js
   file_script_redirect.js
   file_script_xhr.js
   file_remote_frame.html
   file_sample.html
+  file_simple_xhr.html
+  file_simple_xhr_frame.html
+  file_simple_xhr_frame2.html
   redirection.sjs
   file_privilege_escalation.html
   file_ext_test_api_injection.js
   file_permission_xhr.html
   file_teardown_test.js
   return_headers.sjs
   webrequest_worker.js
   !/toolkit/components/passwordmgr/test/authenticate.sjs
@@ -101,16 +104,17 @@ skip-if = os == 'android' # Bug 1258975 
 [test_ext_unload_frame.html]
 [test_ext_listener_proxies.html]
 [test_ext_web_accessible_resources.html]
 [test_ext_webrequest_auth.html]
 skip-if = os == 'android'
 [test_ext_webrequest_background_events.html]
 [test_ext_webrequest_basic.html]
 [test_ext_webrequest_filter.html]
+[test_ext_webrequest_frameId.html]
 [test_ext_webrequest_suspend.html]
 [test_ext_webrequest_upload.html]
 skip-if = os == 'android' # Currently fails in emulator tests
 [test_ext_webrequest_permission.html]
 [test_ext_webnavigation.html]
 [test_ext_webnavigation_filters.html]
 [test_ext_window_postMessage.html]
 [test_ext_subframes_privileges.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_frameId.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+
+<html>
+<head>
+<meta charset="utf-8">
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head_webrequest.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+"use strict";
+
+function background() {
+  browser.webRequest.onBeforeRequest.addListener(details => {
+    browser.test.sendMessage("onBeforeRequest", details);
+  }, {urls: ["<all_urls>"]}, ["blocking"]);
+
+  let tab;
+  browser.tabs.onCreated.addListener(newTab => {
+    tab = newTab;
+  });
+
+  browser.test.onMessage.addListener(msg => {
+    if (msg === "close-tab") {
+      browser.tabs.remove(tab.id);
+      browser.test.sendMessage("tab-closed");
+    }
+  });
+}
+
+let extensionData = {
+  manifest: {
+    permissions: ["webRequest", "webRequestBlocking", "<all_urls>", "tabs"],
+  },
+  background,
+};
+
+let expected = {
+  "file_simple_xhr.html": {
+    type: "main_frame",
+    toplevel: true,
+  },
+  "file_image_good.png": {
+    type: "image",
+    toplevel: true,
+  },
+  "example.txt": {
+    type: "xmlhttprequest",
+    toplevel: true,
+  },
+  "file_simple_xhr_frame.html": {
+    type: "sub_frame",
+    toplevelParent: true,
+  },
+  "file_image_bad.png": {
+    type: "image",
+  },
+  "xhr_resource": {
+    type: "xmlhttprequest",
+  },
+  "file_simple_xhr_frame2.html": {
+    type: "sub_frame",
+  },
+  "file_image_bad.png#2": {
+    type: "image",
+  },
+  "xhr_resource_2": {
+    type: "xmlhttprequest",
+  },
+};
+
+let subframeId, parentId;
+function checkDetails(details) {
+  let url = new URL(details.url);
+  let filename = url.pathname.split("/").pop();
+  let expect = expected[filename];
+  is(expect.type, details.type, `${details.type} type matches`);
+  if (expect.toplevel) {
+    is(0, details.frameId, "expect load at top level");
+    is(-1, details.parentFrameId, "expect top level frame to have no parent");
+  } else if (details.type == "sub_frame") {
+    ok(details.frameId > 0, "expect sub_frame to load into a new frame");
+    if (expect.toplevelParent) {
+      is(0, details.parentFrameId, "expect sub_frame to have top level parent");
+    } else {
+      ok(details.parentFrameId > 0, "expect sub_frame to have parent");
+    }
+    subframeId = details.frameId;
+    parentId = details.parentFrameId;
+  } else {
+    is(subframeId, details.frameId, "expect load in subframe");
+    is(parentId, details.parentFrameId, "expect subframe parent");
+  }
+}
+
+add_task(async function test_webRequest_main_frame() {
+  // Clear the image cache, since it gets in the way otherwise.
+  let imgTools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(SpecialPowers.Ci.imgITools);
+  let cache = imgTools.getImgCacheForDocument(document);
+  cache.clearCache(false);
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  await extension.startup();
+
+  let a = addLink(`file_simple_xhr.html?nocache=${Math.random()}`);
+  a.click();
+
+  for (let i = 0; i < Object.keys(expected).length; i++) {
+    checkDetails(await extension.awaitMessage("onBeforeRequest"));
+  }
+
+  let closed = extension.awaitMessage("tab-closed");
+  extension.sendMessage("close-tab");
+  await closed;
+  await extension.unload();
+});
+
+</script>
+</head>
+<body>
+<div id="test">Sample text</div>
+
+</body>
+</html>
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -497,139 +497,56 @@ var PingPicker = {
 };
 
 var GeneralData = {
   /**
    * Renders the general data
    */
   render(aPing) {
     setHasData("general-data-section", true);
-    let table = document.createElement("table");
+    let generalDataSection = document.getElementById("general-data");
+    removeAllChildNodes(generalDataSection);
 
-    let caption = document.createElement("caption");
-    let captionString = bundle.GetStringFromName("generalDataTitle");
-    caption.appendChild(document.createTextNode(captionString + "\n"));
-    table.appendChild(caption);
-
-    let headings = document.createElement("tr");
-    this.appendColumn(headings, "th", bundle.GetStringFromName("generalDataHeadingName") + "\t");
-    this.appendColumn(headings, "th", bundle.GetStringFromName("generalDataHeadingValue") + "\t");
-    table.appendChild(headings);
+    const headings = [
+      "namesHeader",
+      "valuesHeader",
+    ].map(h => bundle.GetStringFromName(h));
 
     // The payload & environment parts are handled by other renderers.
     let ignoreSections = ["payload", "environment"];
     let data = explodeObject(filterObject(aPing, ignoreSections));
 
-    for (let [path, value] of data) {
-        let row = document.createElement("tr");
-        this.appendColumn(row, "td", path + "\t");
-        this.appendColumn(row, "td", value + "\t");
-        table.appendChild(row);
-    }
-
-    let dataDiv = document.getElementById("general-data");
-    removeAllChildNodes(dataDiv);
-    dataDiv.appendChild(table);
-  },
-
-  /**
-   * Helper function for appending a column to the data table.
-   *
-   * @param aRowElement Parent row element
-   * @param aColType Column's tag name
-   * @param aColText Column contents
-   */
-  appendColumn(aRowElement, aColType, aColText) {
-    let colElement = document.createElement(aColType);
-    let colTextElement = document.createTextNode(aColText);
-    colElement.appendChild(colTextElement);
-    aRowElement.appendChild(colElement);
+    const table = GenericTable.render(data, headings);
+    generalDataSection.appendChild(table);
   },
 };
 
 var EnvironmentData = {
   /**
    * Renders the environment data
    */
   render(ping) {
     let dataDiv = document.getElementById("environment-data");
     removeAllChildNodes(dataDiv);
     const hasData = !!ping.environment;
     setHasData("environment-data-section", hasData);
     if (!hasData) {
       return;
     }
 
-    let data = sectionalizeObject(ping.environment);
-
-    for (let [section, sectionData] of data) {
-      if (section == "addons") {
-        break;
-      }
-
-      let table = document.createElement("table");
-      this.appendHeading(table);
-
-      for (let [path, value] of sectionData) {
-        let row = document.createElement("tr");
-        this.appendColumn(row, "td", path);
-        this.appendColumn(row, "td", value);
-        table.appendChild(row);
-      }
-
-      let hasData = sectionData.size > 0;
-      this.createSubsection(section, hasData, table, dataDiv);
-    }
+    let ignore = ["addons"];
+    let env = filterObject(ping.environment, ignore);
+    let sections = sectionalizeObject(env);
+    GenericSubsection.render(sections, dataDiv);
 
     // We use specialized rendering here to make the addon and plugin listings
     // more readable.
     this.createAddonSection(dataDiv, ping);
   },
 
-  createSubsection(title, hasSubdata, subSectionData, dataDiv) {
-    let dataSection = document.createElement("section");
-    dataSection.classList.add("data-subsection");
-
-    if (hasSubdata) {
-      dataSection.classList.add("has-subdata");
-    }
-
-    // Create section heading
-    let sectionName = document.createElement("h2");
-    sectionName.setAttribute("class", "section-name");
-    sectionName.appendChild(document.createTextNode(title));
-    sectionName.addEventListener("click", toggleSection);
-
-    // Create caption for toggling the subsection visibility.
-    let toggleCaption = document.createElement("span");
-    toggleCaption.setAttribute("class", "toggle-caption");
-    let toggleText = bundle.GetStringFromName("environmentDataSubsectionToggle");
-    toggleCaption.appendChild(document.createTextNode(" " + toggleText));
-    toggleCaption.addEventListener("click", toggleSection);
-
-    // Create caption for empty subsections.
-    let emptyCaption = document.createElement("span");
-    emptyCaption.setAttribute("class", "empty-caption");
-    let emptyText = bundle.GetStringFromName("environmentDataSubsectionEmpty");
-    emptyCaption.appendChild(document.createTextNode(" " + emptyText));
-
-    // Create data container
-    let data = document.createElement("div");
-    data.setAttribute("class", "subsection-data subdata");
-    data.appendChild(subSectionData);
-
-    // Append elements
-    dataSection.appendChild(sectionName);
-    dataSection.appendChild(toggleCaption);
-    dataSection.appendChild(emptyCaption);
-    dataSection.appendChild(data);
-
-    dataDiv.appendChild(dataSection);
-  },
-
   renderPersona(addonObj, addonSection, sectionTitle) {
     let table = document.createElement("table");
     table.setAttribute("id", sectionTitle);
     this.appendAddonSubsectionTitle(sectionTitle, table);
     this.appendRow(table, "persona", addonObj.persona);
     addonSection.appendChild(table);
   },
 
@@ -666,65 +583,55 @@ var EnvironmentData = {
       }
     }
 
     addonSection.appendChild(table);
   },
 
   renderKeyValueObject(addonObj, addonSection, sectionTitle) {
     let data = explodeObject(addonObj);
-    let table = document.createElement("table");
+    let table = GenericTable.render(data);
     table.setAttribute("class", sectionTitle);
     this.appendAddonSubsectionTitle(sectionTitle, table);
-    this.appendHeading(table);
-
-    for (let [key, value] of data) {
-      this.appendRow(table, key, value);
-    }
-
     addonSection.appendChild(table);
   },
 
   appendAddonID(table, addonID) {
     this.appendRow(table, "id", addonID);
   },
 
-  appendHeading(table) {
-    let headings = document.createElement("tr");
-    this.appendColumn(headings, "th", bundle.GetStringFromName("environmentDataHeadingName"));
-    this.appendColumn(headings, "th", bundle.GetStringFromName("environmentDataHeadingValue"));
-    table.appendChild(headings);
-  },
-
   appendHeadingName(table, name) {
     let headings = document.createElement("tr");
     this.appendColumn(headings, "th", name);
     headings.cells[0].colSpan = 2;
     table.appendChild(headings);
   },
 
   appendAddonSubsectionTitle(section, table) {
     let caption = document.createElement("caption");
     caption.setAttribute("class", "addon-caption");
     caption.appendChild(document.createTextNode(section));
     table.appendChild(caption);
   },
 
   createAddonSection(dataDiv, ping) {
     let addonSection = document.createElement("div");
+    addonSection.setAttribute("class", "subsection-data subdata");
     let addons = ping.environment.addons;
     this.renderAddonsObject(addons.activeAddons, addonSection, "activeAddons");
     this.renderActivePlugins(addons.activePlugins, addonSection, "activePlugins");
     this.renderKeyValueObject(addons.theme, addonSection, "theme");
     this.renderKeyValueObject(addons.activeExperiment, addonSection, "activeExperiment");
     this.renderAddonsObject(addons.activeGMPlugins, addonSection, "activeGMPlugins");
     this.renderPersona(addons, addonSection, "persona");
 
     let hasAddonData = Object.keys(ping.environment.addons).length > 0;
-    this.createSubsection("addons", hasAddonData, addonSection, dataDiv);
+    let s = GenericSubsection.renderSubsectionHeader("addons", hasAddonData);
+    s.appendChild(addonSection);
+    dataDiv.appendChild(s);
   },
 
   appendRow(table, id, value) {
     let row = document.createElement("tr");
     this.appendColumn(row, "td", id);
     this.appendColumn(row, "td", value);
     table.appendChild(row);
   },
@@ -1465,90 +1372,88 @@ function RenderObject(aObject) {
   }
   output = "{\"" + keys[0] + "\":\u00A0" + JSON.stringify(aObject[keys[0]]);
   for (let i = 1; i < keys.length; i++) {
     output += ", \"" + keys[i] + "\":\u00A0" + JSON.stringify(aObject[keys[i]]);
   }
   return output + "}";
 }
 
-var KeyValueTable = {
-  /**
-   * Returns a 2-column table with keys and values
-   * @param aMeasurements Each key in this JS object is rendered as a row in
-   *                      the table with its corresponding value
-   * @param aKeysLabel    Column header for the keys column
-   * @param aValuesLabel  Column header for the values column
-   */
-  render: function KeyValueTable_render(aMeasurements, aKeysLabel, aValuesLabel) {
-    let table = document.createElement("table");
-    this.renderHeader(table, aKeysLabel, aValuesLabel);
-    this.renderBody(table, aMeasurements);
-    return table;
+var GenericSubsection = {
+
+  render(data, dataDiv) {
+    for (let [title, sectionData] of data) {
+      let hasData = sectionData.size > 0;
+      let s = this.renderSubsectionHeader(title, hasData);
+      s.appendChild(this.renderSubsectionData(sectionData));
+      dataDiv.appendChild(s);
+    }
   },
 
-  /**
-   * Create the table header
-   * Tabs & newlines added to cells to make it easier to copy-paste.
-   *
-   * @param aTable Table element
-   * @param aKeysLabel    Column header for the keys column
-   * @param aValuesLabel  Column header for the values column
-   */
-  renderHeader: function KeyValueTable_renderHeader(aTable, aKeysLabel, aValuesLabel) {
-    let headerRow = document.createElement("tr");
-    aTable.appendChild(headerRow);
+  renderSubsectionHeader(title, hasData) {
+    let section = document.createElement("section");
+    section.classList.add("data-subsection");
+    if (hasData) {
+      section.classList.add("has-subdata");
+    }
+
+    // Create section heading
+    let sectionName = document.createElement("h2");
+    sectionName.setAttribute("class", "section-name");
+    sectionName.appendChild(document.createTextNode(title));
+    sectionName.addEventListener("click", toggleSection);
 
-    let keysColumn = document.createElement("th");
-    keysColumn.appendChild(document.createTextNode(aKeysLabel + "\t"));
-    let valuesColumn = document.createElement("th");
-    valuesColumn.appendChild(document.createTextNode(aValuesLabel + "\n"));
+    // Create caption for toggling the subsection visibility.
+    let toggleCaption = document.createElement("span");
+    toggleCaption.setAttribute("class", "toggle-caption");
+    let toggleText = bundle.GetStringFromName("environmentDataSubsectionToggle");
+    toggleCaption.appendChild(document.createTextNode(" " + toggleText));
+    toggleCaption.addEventListener("click", toggleSection);
 
-    headerRow.appendChild(keysColumn);
-    headerRow.appendChild(valuesColumn);
+    // Create caption for empty subsections.
+    let emptyCaption = document.createElement("span");
+    emptyCaption.setAttribute("class", "empty-caption");
+    let emptyText = bundle.GetStringFromName("environmentDataSubsectionEmpty");
+    emptyCaption.appendChild(document.createTextNode(" " + emptyText));
+
+    // Append elements
+    section.appendChild(sectionName);
+    section.appendChild(toggleCaption);
+    section.appendChild(emptyCaption);
+
+    return section;
   },
 
-  /**
-   * Create the table body
-   * Tabs & newlines added to cells to make it easier to copy-paste.
-   *
-   * @param aTable Table element
-   * @param aMeasurements Key/value map
-   */
-  renderBody: function KeyValueTable_renderBody(aTable, aMeasurements) {
-    for (let [key, value] of Object.entries(aMeasurements)) {
-      // use .valueOf() to unbox Number, String, etc. objects
-      if (value &&
-         (typeof value == "object") &&
-         (typeof value.valueOf() == "object")) {
-        value = RenderObject(value);
-      }
+  renderSubsectionData(data) {
+    // Create data container
+    let dataDiv = document.createElement("div");
+    dataDiv.setAttribute("class", "subsection-data subdata");
+    // Instanciate the data
+    let table = GenericTable.render(data);
+    dataDiv.appendChild(table);
 
-      let newRow = document.createElement("tr");
-      aTable.appendChild(newRow);
-
-      let keyField = document.createElement("td");
-      keyField.appendChild(document.createTextNode(key + "\t"));
-      newRow.appendChild(keyField);
+    return dataDiv;
+  },
 
-      let valueField = document.createElement("td");
-      valueField.appendChild(document.createTextNode(value + "\n"));
-      newRow.appendChild(valueField);
-    }
-  }
-};
+}
 
 var GenericTable = {
+
+  defaultHeadings: [
+    bundle.GetStringFromName("keysHeader"),
+    bundle.GetStringFromName("valuesHeader")
+  ],
+
   /**
    * Returns a n-column table.
    * @param rows An array of arrays, each containing data to render
    *             for one row.
    * @param headings The column header strings.
    */
-  render(rows, headings) {
+  render(rows, headings = this.defaultHeadings) {
     let table = document.createElement("table");
     this.renderHeader(table, headings);
     this.renderBody(table, rows);
     return table;
   },
 
   /**
    * Create the table header.
@@ -1594,17 +1499,17 @@ var GenericTable = {
 
       for (let i = 0; i < row.length; ++i) {
         let suffix = (i == (row.length - 1)) ? "\n" : "\t";
         let field = document.createElement("td");
         field.appendChild(document.createTextNode(row[i] + suffix));
         newRow.appendChild(field);
       }
     }
-  }
+  },
 };
 
 var KeyedHistogram = {
   render(parent, id, keyedHistogram) {
     let outerDiv = document.createElement("div");
     outerDiv.className = "keyed-histogram";
     outerDiv.id = id;
 
@@ -1640,21 +1545,23 @@ var AddonDetails = {
       return;
     }
 
     for (let provider in addonDetails) {
       let providerSection = document.createElement("h2");
       let titleText = bundle.formatStringFromName("addonProvider", [provider], 1);
       providerSection.appendChild(document.createTextNode(titleText));
       addonSection.appendChild(providerSection);
-      addonSection.appendChild(
-        KeyValueTable.render(addonDetails[provider],
-                             this.tableIDTitle, this.tableDetailsTitle));
+
+      let headingStrings = [this.tableIDTitle, this.tableDetailsTitle ]
+      let table = GenericTable.render(explodeObject(addonDetails[provider]),
+                                      headingStrings);
+      addonSection.appendChild(table);
     }
-  }
+  },
 };
 
 var Scalars = {
   /**
    * Render the scalar data - if present - from the payload in a simple key-value table.
    * @param aPayload A payload object to render the data from.
    */
   render(aPayload) {
@@ -1672,21 +1579,23 @@ var Scalars = {
 
     let scalars = aPayload.processes[selectedProcess].scalars;
     const hasData = scalars && Object.keys(scalars).length > 0;
     setHasData("scalars-section", hasData || processesSelect.options.length);
     if (!hasData) {
       return;
     }
 
-    const headingName = bundle.GetStringFromName("namesHeader");
-    const headingValue = bundle.GetStringFromName("valuesHeader");
-    const table = KeyValueTable.render(scalars, headingName, headingValue);
+    const headings = [
+      "namesHeader",
+      "valuesHeader",
+    ].map(h => bundle.GetStringFromName(h));
+    const table = GenericTable.render(explodeObject(scalars), headings);
     scalarsSection.appendChild(table);
-  }
+  },
 };
 
 var KeyedScalars = {
   /**
    * Render the keyed scalar data - if present - from the payload in a simple key-value table.
    * @param aPayload A payload object to render the data from.
    */
   render(aPayload) {
@@ -1704,28 +1613,30 @@ var KeyedScalars = {
 
     let keyedScalars = aPayload.processes[selectedProcess].keyedScalars;
     const hasData = keyedScalars && Object.keys(keyedScalars).length > 0;
     setHasData("keyed-scalars-section", hasData || processesSelect.options.length);
     if (!hasData) {
       return;
     }
 
-    const headingName = bundle.GetStringFromName("namesHeader");
-    const headingValue = bundle.GetStringFromName("valuesHeader");
+    const headings = [
+      "namesHeader",
+      "valuesHeader",
+    ].map(h => bundle.GetStringFromName(h));
     for (let scalar in keyedScalars) {
       // Add the name of the scalar.
       let scalarNameSection = document.createElement("h2");
       scalarNameSection.appendChild(document.createTextNode(scalar));
       scalarsSection.appendChild(scalarNameSection);
       // Populate the section with the key-value pairs from the scalar.
-      const table = KeyValueTable.render(keyedScalars[scalar], headingName, headingValue);
+      const table = GenericTable.render(explodeObject(keyedScalars[scalar]), headings);
       scalarsSection.appendChild(table);
     }
-  }
+  },
 };
 
 var Events = {
   /**
    * Render the event data - if present - from the payload in a simple table.
    * @param aPayload A payload object to render the data from.
    */
   render(aPayload) {
@@ -1758,17 +1669,17 @@ var Events = {
       "methodHeader",
       "objectHeader",
       "valuesHeader",
       "extraHeader",
     ].map(h => bundle.GetStringFromName(h));
 
     const table = GenericTable.render(events, headings);
     eventsSection.appendChild(table);
-  }
+  },
 };
 
 /**
  * Helper function for showing either the toggle element or "No data collected" message for a section
  *
  * @param aSectionID ID of the section element that needs to be changed
  * @param aHasData true (default) indicates that toggle should be displayed
  */
@@ -1938,62 +1849,186 @@ var LateWritesSingleton = {
     if (!lateWrites) {
       return;
     }
 
     let stacks = lateWrites.stacks;
     let memoryMap = lateWrites.memoryMap;
     StackRenderer.renderStacks("late-writes", stacks, memoryMap,
                                LateWritesSingleton.renderHeader);
-  }
+  },
 };
 
-/**
- * Helper function for sorting the startup milestones in the Simple Measurements
- * section into temporal order.
- *
- * @param aSimpleMeasurements Telemetry ping's "Simple Measurements" data
- * @return Sorted measurements
- */
-function sortStartupMilestones(aSimpleMeasurements) {
-  const telemetryTimestamps = TelemetryTimestamps.get();
-  let startupEvents = Services.startup.getStartupInfo();
-  delete startupEvents["process"];
+var HistogramSection = {
+  render(aPayload) {
+    let hgramDiv = document.getElementById("histograms");
+    removeAllChildNodes(hgramDiv);
+
+    let histograms = aPayload.histograms;
+
+    let hgramsSelect = document.getElementById("histograms-processes");
+    let hgramsOption = hgramsSelect.selectedOptions.item(0);
+    let hgramsProcess = hgramsOption.getAttribute("value");
+    // "parent" histograms/keyedHistograms aren't under "parent". Fix that up.
+    if (hgramsProcess === "parent") {
+      hgramsProcess = "";
+    }
+    if (hgramsProcess &&
+        "processes" in aPayload &&
+        hgramsProcess in aPayload.processes) {
+      histograms = aPayload.processes[hgramsProcess].histograms;
+    }
+
+    let hasData = Object.keys(histograms).length > 0;
+    setHasData("histograms-section", hasData || hgramsSelect.options.length);
+
+    if (hasData) {
+      for (let [name, hgram] of Object.entries(histograms)) {
+        Histogram.render(hgramDiv, name, hgram, {unpacked: true});
+      }
+
+      let filterBox = document.getElementById("histograms-filter");
+      filterBox.addEventListener("input", Histogram.histogramFilterChanged);
+      if (filterBox.value.trim() != "") { // on load, no need to filter if empty
+        Histogram.filterHistograms(hgramDiv, filterBox.value);
+      }
+
+      setHasData("histograms-section", true);
+    }
+  },
+}
 
-  function keyIsMilestone(k) {
-    return (k in startupEvents) || (k in telemetryTimestamps);
-  }
+var KeyedHistogramSection = {
+  render(aPayload) {
+    let keyedDiv = document.getElementById("keyed-histograms");
+    removeAllChildNodes(keyedDiv);
+
+    let keyedHistograms = aPayload.keyedHistograms;
 
-  let sortedKeys = Object.keys(aSimpleMeasurements);
+    let keyedHgramsSelect = document.getElementById("keyed-histograms-processes");
+    let keyedHgramsOption = keyedHgramsSelect.selectedOptions.item(0);
+    let keyedHgramsProcess = keyedHgramsOption.getAttribute("value");
+    // "parent" histograms/keyedHistograms aren't under "parent". Fix that up.
+    if (keyedHgramsProcess === "parent") {
+      keyedHgramsProcess = "";
+    }
+    if (keyedHgramsProcess &&
+        "processes" in aPayload &&
+        keyedHgramsProcess in aPayload.processes) {
+      keyedHistograms = aPayload.processes[keyedHgramsProcess].keyedHistograms;
+    }
+
+    setHasData("keyed-histograms-section", keyedHgramsSelect.options.length);
+    if (keyedHistograms) {
+      let hasData = false;
+      for (let [id, keyed] of Object.entries(keyedHistograms)) {
+        if (Object.keys(keyed).length > 0) {
+          hasData = true;
+          KeyedHistogram.render(keyedDiv, id, keyed, {unpacked: true});
+        }
+      }
+      setHasData("keyed-histograms-section", hasData || keyedHgramsSelect.options.length);
+    }
+  },
+}
+
+var AddonHistogramSection = {
+  render(aPayload) {
+    let addonDiv = document.getElementById("addon-histograms");
+    removeAllChildNodes(addonDiv);
 
-  // Sort the measurements, with startup milestones at the front + ordered by time
-  sortedKeys.sort(function keyCompare(keyA, keyB) {
-    let isKeyAMilestone = keyIsMilestone(keyA);
-    let isKeyBMilestone = keyIsMilestone(keyB);
+    let addonHistogramsRendered = false;
+    let addonData = aPayload.addonHistograms;
+    if (addonData) {
+      for (let [addon, histograms] of Object.entries(addonData)) {
+        for (let [name, hgram] of Object.entries(histograms)) {
+          addonHistogramsRendered = true;
+          Histogram.render(addonDiv, addon + ": " + name, hgram, {unpacked: true});
+        }
+      }
+    }
+
+    setHasData("addon-histograms-section", addonHistogramsRendered);
+  },
+}
+
+var SessionInformation = {
+  render(aPayload) {
+    let infoSection = document.getElementById("session-info");
+    removeAllChildNodes(infoSection);
+
+    let hasData = Object.keys(aPayload.info).length > 0;
+    setHasData("session-info-section", hasData);
+
+    if (hasData) {
+      const table = GenericTable.render(explodeObject(aPayload.info));
+      infoSection.appendChild(table);
+    }
+  },
+}
+
+var SimpleMeasurements = {
+  render(aPayload) {
+    let simpleSection = document.getElementById("simple-measurements");
+    removeAllChildNodes(simpleSection);
+
+    let simpleMeasurements = this.sortStartupMilestones(aPayload.simpleMeasurements);
+    let hasData = Object.keys(simpleMeasurements).length > 0;
+    setHasData("simple-measurements-section", hasData);
+
+    if (hasData) {
+      const table = GenericTable.render(explodeObject(simpleMeasurements));
+      simpleSection.appendChild(table);
+    }
+  },
 
-    // First order by startup vs non-startup measurement
-    if (isKeyAMilestone && !isKeyBMilestone)
-      return -1;
-    if (!isKeyAMilestone && isKeyBMilestone)
-      return 1;
-    // Don't change order of non-startup measurements
-    if (!isKeyAMilestone && !isKeyBMilestone)
-      return 0;
+  /**
+   * Helper function for sorting the startup milestones in the Simple Measurements
+   * section into temporal order.
+   *
+   * @param aSimpleMeasurements Telemetry ping's "Simple Measurements" data
+   * @return Sorted measurements
+   */
+  sortStartupMilestones(aSimpleMeasurements) {
+    const telemetryTimestamps = TelemetryTimestamps.get();
+    let startupEvents = Services.startup.getStartupInfo();
+    delete startupEvents["process"];
+
+    function keyIsMilestone(k) {
+      return (k in startupEvents) || (k in telemetryTimestamps);
+    }
+
+    let sortedKeys = Object.keys(aSimpleMeasurements);
 
-    // If both keys are startup measurements, order them by value
-    return aSimpleMeasurements[keyA] - aSimpleMeasurements[keyB];
-  });
+    // Sort the measurements, with startup milestones at the front + ordered by time
+    sortedKeys.sort(function keyCompare(keyA, keyB) {
+      let isKeyAMilestone = keyIsMilestone(keyA);
+      let isKeyBMilestone = keyIsMilestone(keyB);
 
-  // Insert measurements into a result object in sort-order
-  let result = {};
-  for (let key of sortedKeys) {
-    result[key] = aSimpleMeasurements[key];
-  }
+      // First order by startup vs non-startup measurement
+      if (isKeyAMilestone && !isKeyBMilestone)
+        return -1;
+      if (!isKeyAMilestone && isKeyBMilestone)
+        return 1;
+      // Don't change order of non-startup measurements
+      if (!isKeyAMilestone && !isKeyBMilestone)
+        return 0;
 
-  return result;
+      // If both keys are startup measurements, order them by value
+      return aSimpleMeasurements[keyA] - aSimpleMeasurements[keyB];
+    });
+
+    // Insert measurements into a result object in sort-order
+    let result = {};
+    for (let key of sortedKeys) {
+      result[key] = aSimpleMeasurements[key];
+    }
+
+    return result;
+  },
 }
 
 function renderProcessList(ping, selectEl) {
   removeAllChildNodes(selectEl);
   let option = document.createElement("option");
   option.appendChild(document.createTextNode("parent"));
   option.setAttribute("value", "parent");
   option.selected = true;
@@ -2085,20 +2120,16 @@ function displayPingData(ping, updatePay
   try {
     displayRichPingData(ping, updatePayloadList);
   } catch (err) {
     PingPicker._showRawPingData();
   }
 }
 
 function displayRichPingData(ping, updatePayloadList) {
-  // Update the structured data rendering.
-  const keysHeader = bundle.GetStringFromName("keysHeader");
-  const valuesHeader = bundle.GetStringFromName("valuesHeader");
-
   // Update the payload list and process lists
   if (updatePayloadList) {
     renderPayloadList(ping);
     renderProcessList(ping, document.getElementById("scalars-processes"));
     renderProcessList(ping, document.getElementById("keyed-scalars-processes"));
     renderProcessList(ping, document.getElementById("histograms-processes"));
     renderProcessList(ping, document.getElementById("keyed-histograms-processes"));
     renderProcessList(ping, document.getElementById("events-processes"));
@@ -2144,125 +2175,33 @@ function displayRichPingData(ping, updat
 
   // Show thread hang stats
   ThreadHangStats.render(payload);
 
   // Show captured stacks.
   CapturedStacks.render(payload);
 
   // Show simple measurements
-  let simpleMeasurements = sortStartupMilestones(payload.simpleMeasurements);
-  let hasData = Object.keys(simpleMeasurements).length > 0;
-  setHasData("simple-measurements-section", hasData);
-  let simpleSection = document.getElementById("simple-measurements");
-  removeAllChildNodes(simpleSection);
-
-  if (hasData) {
-    simpleSection.appendChild(KeyValueTable.render(simpleMeasurements,
-                                                   keysHeader, valuesHeader));
-  }
+  SimpleMeasurements.render(payload);
 
   LateWritesSingleton.renderLateWrites(payload.lateWrites);
 
   // Show basic session info gathered
-  hasData = Object.keys(ping.payload.info).length > 0;
-  setHasData("session-info-section", hasData);
-  let infoSection = document.getElementById("session-info");
-  removeAllChildNodes(infoSection);
-
-  if (hasData) {
-    infoSection.appendChild(KeyValueTable.render(ping.payload.info,
-                                                 keysHeader, valuesHeader));
-  }
+  SessionInformation.render(payload);
 
   // Show scalar data.
   Scalars.render(payload);
   KeyedScalars.render(payload);
 
   // Show histogram data
-  let hgramDiv = document.getElementById("histograms");
-  removeAllChildNodes(hgramDiv);
-
-  let histograms = payload.histograms;
-
-  let hgramsSelect = document.getElementById("histograms-processes");
-  let hgramsOption = hgramsSelect.selectedOptions.item(0);
-  let hgramsProcess = hgramsOption.getAttribute("value");
-  // "parent" histograms/keyedHistograms aren't under "parent". Fix that up.
-  if (hgramsProcess === "parent") {
-    hgramsProcess = "";
-  }
-  if (hgramsProcess &&
-      "processes" in ping.payload &&
-      hgramsProcess in ping.payload.processes) {
-    histograms = ping.payload.processes[hgramsProcess].histograms;
-  }
-
-  hasData = Object.keys(histograms).length > 0;
-  setHasData("histograms-section", hasData || hgramsSelect.options.length);
-
-  if (hasData) {
-    for (let [name, hgram] of Object.entries(histograms)) {
-      Histogram.render(hgramDiv, name, hgram, {unpacked: true});
-    }
-
-    let filterBox = document.getElementById("histograms-filter");
-    filterBox.addEventListener("input", Histogram.histogramFilterChanged);
-    if (filterBox.value.trim() != "") { // on load, no need to filter if empty
-      Histogram.filterHistograms(hgramDiv, filterBox.value);
-    }
-
-    setHasData("histograms-section", true);
-  }
+  HistogramSection.render(payload);
 
   // Show keyed histogram data
-  let keyedDiv = document.getElementById("keyed-histograms");
-  removeAllChildNodes(keyedDiv);
-
-  let keyedHistograms = payload.keyedHistograms;
-
-  let keyedHgramsSelect = document.getElementById("keyed-histograms-processes");
-  let keyedHgramsOption = keyedHgramsSelect.selectedOptions.item(0);
-  let keyedHgramsProcess = keyedHgramsOption.getAttribute("value");
-  // "parent" histograms/keyedHistograms aren't under "parent". Fix that up.
-  if (keyedHgramsProcess === "parent") {
-    keyedHgramsProcess = "";
-  }
-  if (keyedHgramsProcess &&
-      "processes" in ping.payload &&
-      keyedHgramsProcess in ping.payload.processes) {
-    keyedHistograms = ping.payload.processes[keyedHgramsProcess].keyedHistograms;
-  }
-
-  setHasData("keyed-histograms-section", keyedHgramsSelect.options.length);
-  if (keyedHistograms) {
-    let hasData = false;
-    for (let [id, keyed] of Object.entries(keyedHistograms)) {
-      if (Object.keys(keyed).length > 0) {
-        hasData = true;
-        KeyedHistogram.render(keyedDiv, id, keyed, {unpacked: true});
-      }
-    }
-    setHasData("keyed-histograms-section", hasData || keyedHgramsSelect.options.length);
-  }
+  KeyedHistogramSection.render(payload);
 
   // Show event data.
   Events.render(payload);
 
   // Show addon histogram data
-  let addonDiv = document.getElementById("addon-histograms");
-  removeAllChildNodes(addonDiv);
-
-  let addonHistogramsRendered = false;
-  let addonData = payload.addonHistograms;
-  if (addonData) {
-    for (let [addon, histograms] of Object.entries(addonData)) {
-      for (let [name, hgram] of Object.entries(histograms)) {
-        addonHistogramsRendered = true;
-        Histogram.render(addonDiv, addon + ": " + name, hgram, {unpacked: true});
-      }
-    }
-  }
-
-  setHasData("addon-histograms-section", addonHistogramsRendered);
+  AddonHistogramSection.render(payload);
 }
 
 window.addEventListener("load", onLoad);
--- a/toolkit/content/widgets/datetimebox.xml
+++ b/toolkit/content/widgets/datetimebox.xml
@@ -1219,19 +1219,21 @@
             field.setAttribute("rawValue", "");
             // Minimum digits to display, padded with leading 0s.
             field.setAttribute("mindigits", aMinDigits);
             // Maximum length for the field, will be advance to the next field
             // automatically if exceeded.
             field.setAttribute("maxlength", aMaxLength);
 
             if (this.mIsRTL) {
-              // Override the numeric field with "ltr", so that it stays in the
-              // same order even when it's empty (with placeholder).
-              field.style.unicodeBidi = "bidi-override";
+              // Force the direction to be "ltr", so that the field stays in the
+              // same order even when it's empty (with placeholder). By using
+              // "embed", the text inside the element is still displayed based
+              // on its directionality.
+              field.style.unicodeBidi = "embed";
               field.style.direction = "ltr";
             }
           }
 
           return field;
         ]]>
         </body>
       </method>
--- a/toolkit/locales/en-US/chrome/global/aboutTelemetry.properties
+++ b/toolkit/locales/en-US/chrome/global/aboutTelemetry.properties
@@ -4,24 +4,16 @@
 
 # Note to translators:
 # - %1$S will be replaced by brandFullName
 # - %2$S will be replaced with the value of the toolkit.telemetry.server_owner preference
 pageSubtitle = This page shows the information about performance, hardware, usage and customizations collected by Telemetry. This information is submitted to %1$S to help improve %2$S.
 
 generalDataTitle = General Data
 
-generalDataHeadingName = Name
-
-generalDataHeadingValue = Value
-
-environmentDataHeadingName = Name
-
-environmentDataHeadingValue = Value
-
 environmentDataSubsectionToggle = Click to toggle section
 
 environmentDataSubsectionEmpty = (No data collected)
 
 telemetryLogTitle = Telemetry Log
 
 telemetryLogHeadingId = Id
 
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -732,18 +732,19 @@ HttpObserverManager = {
 
     let data = {
       requestId: RequestId.get(channel),
       url: channel.URI.spec,
       method: channel.requestMethod,
       browser: loadContext && loadContext.topFrameElement,
       type: WebRequestCommon.typeForPolicyType(policyType),
       fromCache: getData(channel).fromCache,
+      // Defaults for a top level request
       windowId: 0,
-      parentWindowId: 0,
+      parentWindowId: -1,
     };
 
     if (loadInfo) {
       let originPrincipal = loadInfo.triggeringPrincipal;
       if (originPrincipal.URI) {
         data.originUrl = originPrincipal.URI.spec;
       }
       let docPrincipal = loadInfo.loadingPrincipal;
@@ -757,25 +758,37 @@ HttpObserverManager = {
       // request results in, only rely on that if no other principal is available.
       let {isSystemPrincipal} = Services.scriptSecurityManager;
       let isTopLevel = !loadInfo.loadingPrincipal && !!data.browser;
       data.isSystemPrincipal = !isTopLevel &&
                                isSystemPrincipal(loadInfo.loadingPrincipal ||
                                                  loadInfo.principalToInherit ||
                                                  loadInfo.triggeringPrincipal);
 
-      if (loadInfo.frameOuterWindowID) {
+      // Handle window and parent id values for sub_frame requests or requests
+      // inside a sub_frame.
+      if (loadInfo.frameOuterWindowID != 0) {
+        // This is a sub_frame.  Only frames (ie. iframe; a request with a frameloader)
+        // have a non-zero frameOuterWindowID.  For a sub_frame, outerWindowID
+        // points at the frames parent.  The parent frame is the main_frame if
+        // outerWindowID == parentOuterWindowID, in which case set parentWindowId
+        // to zero.
         Object.assign(data, {
           windowId: loadInfo.frameOuterWindowID,
-          parentWindowId: loadInfo.outerWindowID,
+          parentWindowId: loadInfo.outerWindowID == loadInfo.parentOuterWindowID ? 0 : loadInfo.outerWindowID,
         });
-      } else {
+      } else if (loadInfo.outerWindowID != loadInfo.parentOuterWindowID) {
+        // This is a non-frame (e.g. script, image, etc) request within a
+        // sub_frame.  We have to check parentOuterWindowID against the browser
+        // to see if it is the main_frame in which case the parenteWindowId
+        // available to the caller must be set to zero.
+        let parentMainFrame = data.browser && data.browser.outerWindowID == loadInfo.parentOuterWindowID;
         Object.assign(data, {
           windowId: loadInfo.outerWindowID,
-          parentWindowId: loadInfo.parentOuterWindowID,
+          parentWindowId: parentMainFrame ? 0 : loadInfo.parentOuterWindowID,
         });
       }
     }
 
     if (channel instanceof Ci.nsIHttpChannelInternal) {
       try {
         data.ip = channel.remoteAddress;
       } catch (e) {
--- a/xpcom/ds/nsTArray.h
+++ b/xpcom/ds/nsTArray.h
@@ -874,17 +874,22 @@ public:
   static const index_type NoIndex = index_type(-1);
 
   using base_type::Length;
 
   //
   // Finalization method
   //
 
-  ~nsTArray_Impl() { Clear(); }
+  ~nsTArray_Impl()
+  {
+    if (!base_type::IsEmpty()) {
+      Clear();
+    }
+  }
 
   //
   // Initialization methods
   //
 
   nsTArray_Impl() {}
 
   // Initialize this array and pre-allocate some number of elements.