Merge mozilla-central to mozilla-inbound.
authorCosmin Sabou <csabou@mozilla.com>
Fri, 24 May 2019 12:56:42 +0300
changeset 475412 36ba94f116e5c033102b24d9c8b1736a94f7bdfa
parent 475411 574e6b299f2d8683fa071ddee27c32ebb61dbc80 (current diff)
parent 475326 f58ae8ec64c812739509de09659327bf7ea33494 (diff)
child 475413 932a983ac0583c9c4311ec05b9779a0257bdb8ef
push id36061
push usercbrindusan@mozilla.com
push dateFri, 24 May 2019 21:49:59 +0000
treeherdermozilla-central@5d3e1ea77693 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone69.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 mozilla-central to mozilla-inbound.
dom/base/nsIGlobalObject.cpp
dom/base/nsIGlobalObject.h
dom/webidl/WindowOrWorkerGlobalScope.webidl
layout/generic/nsGridContainerFrame.cpp
modules/libpref/init/StaticPrefList.h
testing/web-platform/meta/css/css-contain/contain-layout-baseline-005.html.ini
testing/web-platform/meta/css/css-contain/contain-layout-grid-001.html.ini
testing/web-platform/meta/css/motion/parsing/offset-rotate-computed.html.ini
testing/web-platform/meta/css/motion/parsing/offset-rotate-parsing-valid.html.ini
testing/web-platform/meta/html/browsers/the-window-object/window-properties.https.html.ini
testing/web-platform/meta/html/dom/interfaces.https.html.ini
testing/web-platform/meta/html/dom/interfaces.worker.js.ini
--- a/browser/components/tests/browser/browser_urlbar_matchBuckets_migration60.js
+++ b/browser/components/tests/browser/browser_urlbar_matchBuckets_migration60.js
@@ -235,16 +235,17 @@ function newExperimentOpts(opts = {}) {
   };
   const preferences = {};
   for (const [prefName, prefInfo] of Object.entries(opts.preferences || defaultPref)) {
     preferences[prefName] = { ...defaultPrefInfo, ...prefInfo };
   }
 
   return Object.assign({
     name: STUDY_NAME,
+    actionName: "SomeAction",
     branch: "branch",
   }, opts, {
     preferences,
   });
 }
 
 async function getNonExpiredExperiment() {
   try {
--- a/browser/config/mozconfigs/linux64/devedition
+++ b/browser/config/mozconfigs/linux64/devedition
@@ -5,14 +5,14 @@ export MOZ_PGO=1
 # Add-on signing is not required for DevEdition
 MOZ_REQUIRE_SIGNING=0
 
 ac_add_options --with-branding=browser/branding/aurora
 
 export MOZ_LTO=1
 ac_add_options --enable-profile-use
 ac_add_options --with-pgo-jarlog=/builds/worker/fetches/en-US.log
-ac_add_options --with-pgo-profile-path=/builds/worker/fetches/default.profraw
+ac_add_options --with-pgo-profile-path=/builds/worker/fetches
 
 # Enable MOZ_ALLOW_LEGACY_EXTENSIONS
 ac_add_options "MOZ_ALLOW_LEGACY_EXTENSIONS=1"
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux64/profile-use
+++ b/browser/config/mozconfigs/linux64/profile-use
@@ -1,6 +1,6 @@
 . $topsrcdir/browser/config/mozconfigs/linux64/nightly
 
 export MOZ_LTO=1
 ac_add_options --enable-profile-use
 ac_add_options --with-pgo-jarlog=/builds/worker/fetches/en-US.log
-ac_add_options --with-pgo-profile-path=/builds/worker/fetches/default.profraw
+ac_add_options --with-pgo-profile-path=/builds/worker/fetches
--- a/build/merge_profdata.py
+++ b/build/merge_profdata.py
@@ -1,11 +1,18 @@
 # 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/.
 
+import glob
 import subprocess
+import sys
 import buildconfig
 
 
-def main(_, profile_file):
+def main(_, profile_dir):
+    profraw_files = glob.glob(profile_dir + '/*.profraw')
+    if not profraw_files:
+        print('Could not find any profraw files in ' + profile_dir)
+        sys.exit(1)
+
     subprocess.check_call([buildconfig.substs['LLVM_PROFDATA'], 'merge',
-                           '-o', 'merged.profdata', profile_file])
+                           '-o', 'merged.profdata'] + profraw_files)
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -1465,34 +1465,39 @@ imply_option('MOZ_PGO',
 
 set_config('MOZ_PROFILE_GENERATE',
            depends_if('--enable-profile-generate')(lambda _: True))
 
 js_option('--enable-profile-use',
           help='Use a generated profile during the build')
 
 js_option('--with-pgo-profile-path',
-          help='Path to the (unmerged) profile path to use during the build',
+          help='Path to the directory with unmerged profile data to use during the build',
           nargs=1)
 
 imply_option('MOZ_PGO',
              depends_if('--enable-profile-use')(lambda _: True))
 
 set_config('MOZ_PROFILE_USE',
            depends_if('--enable-profile-use')(lambda _: True))
 
 
 @depends('--with-pgo-profile-path', '--enable-profile-use', 'LLVM_PROFDATA')
+@imports('os')
 def pgo_profile_path(path, pgo_use, profdata):
     if not path:
         return
     if path and not pgo_use:
         die('Pass --enable-profile-use to use --with-pgo-profile-path.')
     if path and not profdata:
         die('LLVM_PROFDATA must be set to process the pgo profile.')
+    if not os.path.isdir(path[0]):
+        die('Argument to --with-pgo-profile-path must be a directory.')
+    if not os.path.isabs(path[0]):
+        die('Argument to --with-pgo-profile-path must be an absolute path.')
     return path[0]
 
 
 set_config('PGO_PROFILE_PATH', pgo_profile_path)
 
 option('--with-pgo-jarlog',
        help='Use the provided jarlog file when packaging during a profile-use '
             'build',
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -677,17 +677,17 @@ skip-if = (os == "win" && ccov) # Bug 14
 [browser_dbg-xhr-run-to-completion.js]
 [browser_dbg-scroll-run-to-completion.js]
 [browser_dbg-sourcemapped-scopes.js]
 skip-if = ccov || debug || (verify && debug && (os == 'linux')) # Bug 1441545, 1536253 - very slow on debug
 [browser_dbg-sourcemapped-stepping.js]
 skip-if = true # original stepping is currently disabled
 [browser_dbg-sourcemapped-toggle.js]
 [browser_dbg-sourcemapped-preview.js]
-skip-if = os == "win" # Bug 1448523, Bug 1448450
+skip-if = (os == "win") || (os == "linux") || (debug && os == "mac" && os_version == "10.10") # Bug 1448523, Bug 1448450, Bug 1551871
 [browser_dbg-breaking.js]
 [browser_dbg-breaking-from-console.js]
 [browser_dbg-breakpoints.js]
 [browser_dbg-breakpoints-actions.js]
 [browser_dbg-breakpoints-columns.js]
 [browser_dbg-breakpoints-cond.js]
 [browser_dbg-breakpoints-cond-source-maps.js]
 [browser_dbg-breakpoints-duplicate-functions.js]
--- a/devtools/server/actors/animation-type-longhand.js
+++ b/devtools/server/actors/animation-type-longhand.js
@@ -205,17 +205,17 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
     "margin-inline-end",
     "margin-inline-start",
     "-moz-math-display",
     "max-block-size",
     "max-inline-size",
     "min-block-size",
     "-moz-min-font-size-ratio",
     "min-inline-size",
-    "offset-path",
+    "offset-rotate",
     "padding-block-end",
     "padding-block-start",
     "padding-inline-end",
     "padding-inline-start",
     "rotate",
     "scale",
     "-moz-script-level",
     "-moz-top-layer",
@@ -263,16 +263,17 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
     "font-stretch",
     "font-variation-settings",
     "font-weight",
     "-moz-image-region",
     "mask-position-x",
     "mask-position-y",
     "mask-size",
     "object-position",
+    "offset-path",
     "order",
     "perspective-origin",
     "shape-outside",
     "stroke-dasharray",
     "transform",
     "transform-origin",
     "-moz-window-transform",
     "-moz-window-transform-origin",
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -3085,16 +3085,17 @@ exports.CSS_PROPERTIES = {
       "animation-fill-mode",
       "animation-delay",
       "transform",
       "rotate",
       "scale",
       "translate",
       "offset-path",
       "offset-distance",
+      "offset-rotate",
       "scroll-behavior",
       "scroll-snap-align",
       "scroll-snap-type",
       "overscroll-behavior-x",
       "overscroll-behavior-y",
       "isolation",
       "break-after",
       "break-before",
@@ -8304,16 +8305,31 @@ exports.CSS_PROPERTIES = {
       "inherit",
       "initial",
       "none",
       "path",
       "revert",
       "unset"
     ]
   },
+  "offset-rotate": {
+    "isInherited": false,
+    "subproperties": [
+      "offset-rotate"
+    ],
+    "supports": [],
+    "values": [
+      "auto",
+      "inherit",
+      "initial",
+      "reverse",
+      "revert",
+      "unset"
+    ]
+  },
   "opacity": {
     "isInherited": false,
     "subproperties": [
       "opacity"
     ],
     "supports": [],
     "values": [
       "inherit",
@@ -10651,16 +10667,20 @@ exports.PREFERENCES = [
     "initial-letter",
     "layout.css.initial-letter.enabled"
   ],
   [
     "-moz-osx-font-smoothing",
     "layout.css.osx-font-smoothing.enabled"
   ],
   [
+    "offset-rotate",
+    "layout.css.motion-path.enabled"
+  ],
+  [
     "overflow-anchor",
     "layout.css.scroll-anchoring.enabled"
   ],
   [
     "scroll-snap-align",
     "layout.css.scroll-snap-v1.enabled"
   ],
   [
--- a/docshell/base/nsAboutRedirector.cpp
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -82,16 +82,21 @@ static const RedirEntry kRedirMap[] = {
      nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
          nsIAboutModule::MAKE_LINKABLE},
     {"logo", "chrome://branding/content/about.png",
      nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
          // Linkable for testing reasons.
          nsIAboutModule::MAKE_LINKABLE},
     {"memory", "chrome://global/content/aboutMemory.xhtml",
      nsIAboutModule::ALLOW_SCRIPT},
+    {"certificate", "chrome://global/content/certviewer/certviewer.html",
+     nsIAboutModule::ALLOW_SCRIPT |
+         nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+         nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
+         nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGED_CHILD},
     {"mozilla", "chrome://global/content/mozilla.xhtml",
      nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT},
     {"neterror", "chrome://global/content/netError.xhtml",
      nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
          nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
          nsIAboutModule::HIDE_FROM_ABOUTABOUT},
     {"networking", "chrome://global/content/aboutNetworking.xhtml",
      nsIAboutModule::ALLOW_SCRIPT},
--- a/docshell/build/components.conf
+++ b/docshell/build/components.conf
@@ -3,16 +3,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 about_pages = [
     'about',
     'addons',
     'buildconfig',
+    'certificate',
     'checkerboard',
     'crashcontent',
     'crashparent',
     'credits',
     'license',
     'logo',
     'memory',
     'mozilla',
--- a/editor/libeditor/EditorCommands.cpp
+++ b/editor/libeditor/EditorCommands.cpp
@@ -24,17 +24,17 @@
 class nsISupports;
 
 #define STATE_ENABLED "state_enabled"
 #define STATE_ATTRIBUTE "state_attribute"
 #define STATE_DATA "state_data"
 
 namespace mozilla {
 
-using namespace detail;
+using detail::Any;
 
 /******************************************************************************
  * mozilla::EditorCommand
  ******************************************************************************/
 
 NS_IMPL_ISUPPORTS(EditorCommand, nsIControllerCommand)
 
 NS_IMETHODIMP
--- a/gfx/wr/webrender/src/clip.rs
+++ b/gfx/wr/webrender/src/clip.rs
@@ -12,17 +12,17 @@ use crate::ellipse::Ellipse;
 use crate::gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use crate::gpu_types::{BoxShadowStretchMode};
 use crate::image::{self, Repetition};
 use crate::intern;
 use crate::prim_store::{ClipData, ImageMaskData, SpaceMapper, VisibleMaskImageTile};
 use crate::prim_store::{PointKey, SizeKey, RectangleKey};
 use crate::render_task::to_cache_size;
 use crate::resource_cache::{ImageRequest, ResourceCache};
-use std::{cmp, u32};
+use std::{cmp, ops, u32};
 use crate::util::{extract_inner_rect_safe, project_rect, ScaleOffset};
 
 /*
 
  Module Overview
 
  There are a number of data structures involved in the clip module:
 
@@ -235,30 +235,74 @@ pub struct ClipNodeInstance {
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipNodeRange {
     pub first: u32,
     pub count: u32,
 }
 
+impl ClipNodeRange {
+    fn to_range(&self) -> ops::Range<usize> {
+        let start = self.first as usize;
+        let end = start + self.count as usize;
+
+        ops::Range {
+            start,
+            end,
+        }
+    }
+}
+
 /// A helper struct for converting between coordinate systems
 /// of clip sources and primitives.
 // todo(gw): optimize:
 //  separate arrays for matrices
 //  cache and only build as needed.
 //TODO: merge with `CoordinateSpaceMapping`?
 #[derive(Debug, MallocSizeOf)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 enum ClipSpaceConversion {
     Local,
     ScaleOffset(ScaleOffset),
     Transform(LayoutToWorldTransform),
 }
 
+impl ClipSpaceConversion {
+    /// Construct a new clip space converter between two spatial nodes.
+    fn new(
+        prim_spatial_node_index: SpatialNodeIndex,
+        clip_spatial_node_index: SpatialNodeIndex,
+        clip_scroll_tree: &ClipScrollTree,
+    ) -> Self {
+        //Note: this code is different from `get_relative_transform` in a way that we only try
+        // getting the relative transform if it's Local or ScaleOffset,
+        // falling back to the world transform otherwise.
+        let clip_spatial_node = &clip_scroll_tree
+            .spatial_nodes[clip_spatial_node_index.0 as usize];
+        let prim_spatial_node = &clip_scroll_tree
+            .spatial_nodes[prim_spatial_node_index.0 as usize];
+
+        if prim_spatial_node_index == clip_spatial_node_index {
+            ClipSpaceConversion::Local
+        } else if prim_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
+            let scale_offset = prim_spatial_node.coordinate_system_relative_scale_offset
+                .inverse()
+                .accumulate(&clip_spatial_node.coordinate_system_relative_scale_offset);
+            ClipSpaceConversion::ScaleOffset(scale_offset)
+        } else {
+            ClipSpaceConversion::Transform(
+                clip_scroll_tree
+                    .get_world_transform(clip_spatial_node_index)
+                    .into_transform()
+            )
+        }
+    }
+}
+
 // Temporary information that is cached and reused
 // during building of a clip chain instance.
 #[derive(MallocSizeOf)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 struct ClipNodeInfo {
     conversion: ClipSpaceConversion,
     handle: ClipDataHandle,
     local_pos: LayoutPoint,
@@ -459,17 +503,19 @@ impl ClipNode {
 }
 
 /// The main clipping public interface that other modules access.
 #[derive(MallocSizeOf)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct ClipStore {
     pub clip_chain_nodes: Vec<ClipChainNode>,
     clip_node_instances: Vec<ClipNodeInstance>,
-    clip_node_info: Vec<ClipNodeInfo>,
+
+    active_clip_node_info: Vec<ClipNodeInfo>,
+    active_local_clip_rect: Option<LayoutRect>,
 }
 
 // A clip chain instance is what gets built for a given clip
 // chain id + local primitive region + positioning node.
 #[derive(Debug)]
 pub struct ClipChainInstance {
     pub clips_range: ClipNodeRange,
     // Combined clip rect for clips that are in the
@@ -545,17 +591,19 @@ impl ClipChainStack {
     }
 }
 
 impl ClipStore {
     pub fn new() -> Self {
         ClipStore {
             clip_chain_nodes: Vec::new(),
             clip_node_instances: Vec::new(),
-            clip_node_info: Vec::new(),
+
+            active_clip_node_info: Vec::new(),
+            active_local_clip_rect: None,
         }
     }
 
     pub fn get_clip_chain(&self, clip_chain_id: ClipChainId) -> &ClipChainNode {
         &self.clip_chain_nodes[clip_chain_id.0 as usize]
     }
 
     pub fn add_clip_chain_node(
@@ -580,78 +628,121 @@ impl ClipStore {
     pub fn get_instance_from_range(
         &self,
         node_range: &ClipNodeRange,
         index: u32,
     ) -> &ClipNodeInstance {
         &self.clip_node_instances[(node_range.first + index) as usize]
     }
 
-    // The main interface other code uses. Given a local primitive, positioning
-    // information, and a clip chain id, build an optimized clip chain instance.
-    pub fn build_clip_chain_instance(
+    /// Setup the active clip chains for building a clip chain instance.
+    pub fn set_active_clips(
         &mut self,
-        clip_chains: &[ClipChainId],
-        local_prim_rect: LayoutRect,
         local_prim_clip_rect: LayoutRect,
         spatial_node_index: SpatialNodeIndex,
-        prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>,
-        pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
+        clip_chains: &[ClipChainId],
         clip_scroll_tree: &ClipScrollTree,
-        gpu_cache: &mut GpuCache,
-        resource_cache: &mut ResourceCache,
-        device_pixel_scale: DevicePixelScale,
-        world_rect: &WorldRect,
         clip_data_store: &mut ClipDataStore,
-        request_resources: bool,
-    ) -> Option<ClipChainInstance> {
-        let mut local_clip_rect = local_prim_clip_rect;
+    ) {
+        self.active_clip_node_info.clear();
+        self.active_local_clip_rect = None;
 
-        // Walk the clip chain to build local rects, and collect the
-        // smallest possible local/device clip area.
-
-        self.clip_node_info.clear();
+        let mut local_clip_rect = local_prim_clip_rect;
 
         for clip_chain_root in clip_chains {
             let mut current_clip_chain_id = *clip_chain_root;
 
             // for each clip chain node
             while current_clip_chain_id != ClipChainId::NONE {
                 let clip_chain_node = &self.clip_chain_nodes[current_clip_chain_id.0 as usize];
 
                 if !add_clip_node_to_current_chain(
                     clip_chain_node,
                     spatial_node_index,
                     &mut local_clip_rect,
-                    &mut self.clip_node_info,
+                    &mut self.active_clip_node_info,
                     clip_data_store,
                     clip_scroll_tree,
                 ) {
-                    return None;
+                    return;
                 }
 
                 current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
             }
         }
 
+        self.active_local_clip_rect = Some(local_clip_rect);
+    }
+
+    /// Setup the active clip chains, based on an existing primitive clip chain instance.
+    pub fn set_active_clips_from_clip_chain(
+        &mut self,
+        prim_clip_chain: &ClipChainInstance,
+        prim_spatial_node_index: SpatialNodeIndex,
+        clip_scroll_tree: &ClipScrollTree,
+    ) {
+        // TODO(gw): Although this does less work than set_active_clips(), it does
+        //           still do some unnecessary work (such as the clip space conversion).
+        //           We could consider optimizing this if it ever shows up in a profile.
+
+        self.active_clip_node_info.clear();
+        self.active_local_clip_rect = Some(prim_clip_chain.local_clip_rect);
+
+        let clip_instances = &self
+            .clip_node_instances[prim_clip_chain.clips_range.to_range()];
+        for clip_instance in clip_instances {
+            let conversion = ClipSpaceConversion::new(
+                prim_spatial_node_index,
+                clip_instance.spatial_node_index,
+                clip_scroll_tree,
+            );
+            self.active_clip_node_info.push(ClipNodeInfo {
+                handle: clip_instance.handle,
+                local_pos: clip_instance.local_pos,
+                spatial_node_index: clip_instance.spatial_node_index,
+                conversion,
+            });
+        }
+    }
+
+    /// The main interface external code uses. Given a local primitive, positioning
+    /// information, and a clip chain id, build an optimized clip chain instance.
+    pub fn build_clip_chain_instance(
+        &mut self,
+        local_prim_rect: LayoutRect,
+        prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>,
+        pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
+        clip_scroll_tree: &ClipScrollTree,
+        gpu_cache: &mut GpuCache,
+        resource_cache: &mut ResourceCache,
+        device_pixel_scale: DevicePixelScale,
+        world_rect: &WorldRect,
+        clip_data_store: &mut ClipDataStore,
+        request_resources: bool,
+    ) -> Option<ClipChainInstance> {
+        let local_clip_rect = match self.active_local_clip_rect {
+            Some(rect) => rect,
+            None => return None,
+        };
+
         let local_bounding_rect = local_prim_rect.intersection(&local_clip_rect)?;
         let pic_clip_rect = prim_to_pic_mapper.map(&local_bounding_rect)?;
         let world_clip_rect = pic_to_world_mapper.map(&pic_clip_rect)?;
 
         // Now, we've collected all the clip nodes that *potentially* affect this
         // primitive region, and reduced the size of the prim region as much as possible.
 
         // Run through the clip nodes, and see which ones affect this prim region.
 
         let first_clip_node_index = self.clip_node_instances.len() as u32;
         let mut has_non_local_clips = false;
         let mut needs_mask = false;
 
         // For each potential clip node
-        for node_info in self.clip_node_info.drain(..) {
+        for node_info in self.active_clip_node_info.drain(..) {
             let node = &mut clip_data_store[node_info.handle];
 
             // See how this clip affects the prim region.
             let clip_result = match node_info.conversion {
                 ClipSpaceConversion::Local => {
                     node.item.get_clip_result(node_info.local_pos, &local_bounding_rect)
                 }
                 ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
@@ -1347,38 +1438,24 @@ fn add_clip_node_to_current_chain(
     node: &ClipChainNode,
     spatial_node_index: SpatialNodeIndex,
     local_clip_rect: &mut LayoutRect,
     clip_node_info: &mut Vec<ClipNodeInfo>,
     clip_data_store: &ClipDataStore,
     clip_scroll_tree: &ClipScrollTree,
 ) -> bool {
     let clip_node = &clip_data_store[node.handle];
-    let clip_spatial_node = &clip_scroll_tree.spatial_nodes[node.spatial_node_index.0 as usize];
-    let ref_spatial_node = &clip_scroll_tree.spatial_nodes[spatial_node_index.0 as usize];
 
     // Determine the most efficient way to convert between coordinate
     // systems of the primitive and clip node.
-    //Note: this code is different from `get_relative_transform` in a way that we only try
-    // getting the relative transform if it's Local or ScaleOffset,
-    // falling back to the world transform otherwise.
-    let conversion = if spatial_node_index == node.spatial_node_index {
-        ClipSpaceConversion::Local
-    } else if ref_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
-        let scale_offset = ref_spatial_node.coordinate_system_relative_scale_offset
-            .inverse()
-            .accumulate(&clip_spatial_node.coordinate_system_relative_scale_offset);
-        ClipSpaceConversion::ScaleOffset(scale_offset)
-    } else {
-        ClipSpaceConversion::Transform(
-            clip_scroll_tree
-                .get_world_transform(node.spatial_node_index)
-                .into_transform()
-        )
-    };
+    let conversion = ClipSpaceConversion::new(
+        spatial_node_index,
+        node.spatial_node_index,
+        clip_scroll_tree,
+    );
 
     // If we can convert spaces, try to reduce the size of the region
     // requested, and cache the conversion information for the next step.
     if let Some(clip_rect) = clip_node.item.get_local_clip_rect(node.local_pos) {
         match conversion {
             ClipSpaceConversion::Local => {
                 *local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
                     Some(rect) => rect,
--- a/gfx/wr/webrender/src/clip_scroll_tree.rs
+++ b/gfx/wr/webrender/src/clip_scroll_tree.rs
@@ -214,39 +214,16 @@ impl<Src, Dst> CoordinateSpaceMapping<Sr
             CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
                 Some(CoordinateSpaceMapping::ScaleOffset(scale_offset.inverse()))
             }
             CoordinateSpaceMapping::Transform(ref transform) => {
                 transform.inverse().map(CoordinateSpaceMapping::Transform)
             }
         }
     }
-
-    pub fn with_destination<NewDst>(self) -> CoordinateSpaceMapping<Src, NewDst> {
-        match self {
-            CoordinateSpaceMapping::Local => CoordinateSpaceMapping::Local,
-            CoordinateSpaceMapping::ScaleOffset(scale_offset) => CoordinateSpaceMapping::ScaleOffset(scale_offset),
-            CoordinateSpaceMapping::Transform(transform) => CoordinateSpaceMapping::Transform(
-                transform.with_destination::<NewDst>()
-            ),
-        }
-    }
-
-    pub fn post_mul_transform<NewDst>(
-        &self, other: &CoordinateSpaceMapping<Dst, NewDst>
-    ) -> TypedTransform3D<f32, Src, NewDst>
-    where Self: Clone
-    {
-        let matrix = self.clone().into_transform();
-        match *other {
-            CoordinateSpaceMapping::Local => matrix.with_destination::<NewDst>(),
-            CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => matrix.post_mul(&scale_offset.to_transform()),
-            CoordinateSpaceMapping::Transform(ref transform) => matrix.post_mul(transform),
-        }
-    }
 }
 
 impl ClipScrollTree {
     pub fn new() -> Self {
         ClipScrollTree {
             spatial_nodes: Vec::new(),
             coord_systems: Vec::new(),
             pending_scroll_offsets: FastHashMap::default(),
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -507,17 +507,16 @@ impl<'a> DisplayListFlattener<'a> {
             }
         );
 
         let tile_cache = TileCache::new(
             main_scroll_root,
             &prim_list.prim_instances,
             *self.pipeline_clip_chain_stack.last().unwrap(),
             &self.prim_store.pictures,
-            &self.clip_scroll_tree,
         );
 
         let pic_index = self.prim_store.pictures.alloc().init(PicturePrimitive::new_image(
             Some(PictureCompositeMode::TileCache { clear_color: ColorF::new(1.0, 1.0, 1.0, 1.0) }),
             Picture3DContext::Out,
             self.scene.root_pipeline_id.unwrap(),
             None,
             true,
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -152,17 +152,16 @@ pub struct FrameBuildingState<'a> {
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub transforms: &'a mut TransformPalette,
     pub segment_builder: SegmentBuilder,
     pub surfaces: &'a mut Vec<SurfaceInfo>,
     pub dirty_region_stack: Vec<DirtyRegion>,
-    pub clip_chain_stack: ClipChainStack,
 }
 
 impl<'a> FrameBuildingState<'a> {
     /// Retrieve the current dirty region during primitive traversal.
     pub fn current_dirty_region(&self) -> &DirtyRegion {
         self.dirty_region_stack.last().unwrap()
     }
 
@@ -420,17 +419,16 @@ impl FrameBuilder {
             profile_counters,
             clip_store: &mut self.clip_store,
             resource_cache,
             gpu_cache,
             transforms: transform_palette,
             segment_builder: SegmentBuilder::new(),
             surfaces,
             dirty_region_stack: Vec::new(),
-            clip_chain_stack: ClipChainStack::new(),
         };
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(self.output_rect),
             self.output_rect.size.to_f32(),
             self.root_pic_index,
             DeviceIntPoint::zero(),
             UvRectKind::Rect,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -698,32 +698,30 @@ fn collect_ref_prims(
 }
 
 impl TileCache {
     pub fn new(
         spatial_node_index: SpatialNodeIndex,
         prim_instances: &[PrimitiveInstance],
         root_clip_chain_id: ClipChainId,
         pictures: &[PicturePrimitive],
-        clip_scroll_tree: &ClipScrollTree,
     ) -> Self {
         // Build the list of reference primitives
         // for this picture cache.
         let reference_prims = ReferencePrimitiveList::new(
             prim_instances,
             pictures,
         );
 
         TileCache {
             spatial_node_index,
             tiles: Vec::new(),
             map_local_to_world: SpaceMapper::new(
                 ROOT_SPATIAL_NODE_INDEX,
                 WorldRect::zero(),
-                clip_scroll_tree,
             ),
             tiles_to_draw: Vec::new(),
             opacity_bindings: FastHashMap::default(),
             dirty_region: DirtyRegion::new(),
             world_origin: WorldPoint::zero(),
             world_tile_size: WorldSize::zero(),
             tile_count: TileSize::zero(),
             scroll_offset: None,
@@ -829,17 +827,16 @@ impl TileCache {
         }.unwrap_or(WorldVector2D::zero());
 
         // Assume no tiles are valid to draw by default
         self.tiles_to_draw.clear();
 
         self.map_local_to_world = SpaceMapper::new(
             ROOT_SPATIAL_NODE_INDEX,
             frame_context.screen_world_rect,
-            frame_context.clip_scroll_tree,
         );
 
         let world_mapper = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             self.spatial_node_index,
             frame_context.screen_world_rect,
             frame_context.clip_scroll_tree,
         );
@@ -1832,17 +1829,16 @@ impl SurfaceInfo {
 
         let pic_bounds = map_surface_to_world
             .unmap(&map_surface_to_world.bounds)
             .unwrap_or(PictureRect::max_rect());
 
         let map_local_to_surface = SpaceMapper::new(
             surface_spatial_node_index,
             pic_bounds,
-            clip_scroll_tree,
         );
 
         SurfaceInfo {
             rect: PictureRect::zero(),
             map_local_to_surface,
             render_tasks: None,
             raster_spatial_node_index,
             surface_spatial_node_index,
@@ -2412,17 +2408,16 @@ impl PicturePrimitive {
         );
 
         let pic_bounds = map_pic_to_world.unmap(&map_pic_to_world.bounds)
                                          .unwrap_or(PictureRect::max_rect());
 
         let map_local_to_pic = SpaceMapper::new(
             surface_spatial_node_index,
             pic_bounds,
-            frame_context.clip_scroll_tree,
         );
 
         let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
             surface_spatial_node_index,
             raster_spatial_node_index,
             frame_context.screen_world_rect,
             frame_context.clip_scroll_tree,
         );
@@ -2546,67 +2541,67 @@ impl PicturePrimitive {
                     }
                     PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
                         let mut max_std_deviation = 0.0;
                         for shadow in shadows {
                             // TODO(nical) presumably we should compute the clipped rect for each shadow
                             // and compute the union of them to determine what we need to rasterize and blur?
                             max_std_deviation = f32::max(max_std_deviation, shadow.blur_radius * device_pixel_scale.0);
                         }
-        
+
                         max_std_deviation = max_std_deviation.round();
                         let max_blur_range = (max_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
                         let mut device_rect = clipped.inflate(max_blur_range, max_blur_range)
                                 .intersection(&unclipped.to_i32())
                                 .unwrap();
                         device_rect.size = RenderTask::adjusted_blur_source_size(
                             device_rect.size,
                             DeviceSize::new(max_std_deviation, max_std_deviation),
                         );
-        
+
                         let uv_rect_kind = calculate_uv_rect_kind(
                             &pic_rect,
                             &transform,
                             &device_rect,
                             device_pixel_scale,
                             true,
                         );
-        
+
                         let mut picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, device_rect.size),
                             unclipped.size,
                             pic_index,
                             device_rect.origin,
                             uv_rect_kind,
                             raster_spatial_node_index,
                             device_pixel_scale,
                         );
                         picture_task.mark_for_saving();
-        
+
                         let picture_task_id = frame_state.render_tasks.add(picture_task);
-        
+
                         self.secondary_render_task_id = Some(picture_task_id);
-        
+
                         let mut blur_tasks = BlurTaskCache::default();
-        
+
                         self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new());
-        
+
                         let mut blur_render_task_id = picture_task_id;
                         for shadow in shadows {
                             let std_dev = f32::round(shadow.blur_radius * device_pixel_scale.0);
                             blur_render_task_id = RenderTask::new_blur(
                                 DeviceSize::new(std_dev, std_dev),
                                 picture_task_id,
                                 frame_state.render_tasks,
                                 RenderTargetKind::Color,
                                 ClearMode::Transparent,
                                 Some(&mut blur_tasks),
-                            );      
+                            );
                         }
-        
+
                         // TODO(nical) the second one should to be the blur's task id but we have several blurs now
                         (blur_render_task_id, picture_task_id)
                     }
                     PictureCompositeMode::MixBlend(..) if !frame_context.fb_config.gpu_supports_advanced_blend => {
                         let uv_rect_kind = calculate_uv_rect_kind(
                             &pic_rect,
                             &transform,
                             &clipped,
@@ -3359,17 +3354,16 @@ fn build_ref_prims(
     prim_map: &mut FastHashMap<ItemUid, WorldPoint>,
     clip_scroll_tree: &ClipScrollTree,
 ) {
     prim_map.clear();
 
     let mut map_local_to_world = SpaceMapper::new(
         ROOT_SPATIAL_NODE_INDEX,
         WorldRect::zero(),
-        clip_scroll_tree,
     );
 
     for ref_prim in ref_prims {
         map_local_to_world.set_target_spatial_node(
             ref_prim.spatial_node_index,
             clip_scroll_tree,
         );
 
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -129,49 +129,42 @@ impl PrimitiveOpacity {
     }
 }
 
 
 #[derive(Debug, Clone)]
 pub struct SpaceMapper<F, T> {
     kind: CoordinateSpaceMapping<F, T>,
     pub ref_spatial_node_index: SpatialNodeIndex,
-    ref_world_inv_transform: CoordinateSpaceMapping<WorldPixel, T>,
     pub current_target_spatial_node_index: SpatialNodeIndex,
     pub bounds: TypedRect<f32, T>,
     visible_face: VisibleFace,
 }
 
 impl<F, T> SpaceMapper<F, T> where F: fmt::Debug {
     pub fn new(
         ref_spatial_node_index: SpatialNodeIndex,
         bounds: TypedRect<f32, T>,
-        clip_scroll_tree: &ClipScrollTree,
     ) -> Self {
         SpaceMapper {
             kind: CoordinateSpaceMapping::Local,
             ref_spatial_node_index,
-            ref_world_inv_transform: clip_scroll_tree
-                .get_world_transform(ref_spatial_node_index)
-                .inverse()
-                .unwrap()
-                .with_destination::<T>(),
             current_target_spatial_node_index: ref_spatial_node_index,
             bounds,
             visible_face: VisibleFace::Front,
         }
     }
 
     pub fn new_with_target(
         ref_spatial_node_index: SpatialNodeIndex,
         target_node_index: SpatialNodeIndex,
         bounds: TypedRect<f32, T>,
         clip_scroll_tree: &ClipScrollTree,
     ) -> Self {
-        let mut mapper = SpaceMapper::new(ref_spatial_node_index, bounds, clip_scroll_tree);
+        let mut mapper = Self::new(ref_spatial_node_index, bounds);
         mapper.set_target_spatial_node(target_node_index, clip_scroll_tree);
         mapper
     }
 
     pub fn set_target_spatial_node(
         &mut self,
         target_node_index: SpatialNodeIndex,
         clip_scroll_tree: &ClipScrollTree,
@@ -187,19 +180,20 @@ impl<F, T> SpaceMapper<F, T> where F: fm
             CoordinateSpaceMapping::Local
         } else if ref_spatial_node.coordinate_system_id == target_spatial_node.coordinate_system_id {
             let scale_offset = ref_spatial_node.coordinate_system_relative_scale_offset
                 .inverse()
                 .accumulate(&target_spatial_node.coordinate_system_relative_scale_offset);
             CoordinateSpaceMapping::ScaleOffset(scale_offset)
         } else {
             let transform = clip_scroll_tree
-                .get_world_transform(target_node_index)
-                .post_mul_transform(&self.ref_world_inv_transform)
-                .with_source::<F>();
+                .get_relative_transform(target_node_index, self.ref_spatial_node_index)
+                .into_transform()
+                .with_source::<F>()
+                .with_destination::<T>();
             CoordinateSpaceMapping::Transform(transform)
         };
 
         self.visible_face = self.kind.visible_face();
         self.current_target_spatial_node_index = target_node_index;
     }
 
     pub fn get_transform(&self) -> TypedTransform3D<f32, F, T> {
@@ -1790,17 +1784,16 @@ impl PrimitiveStore {
             surface.surface_spatial_node_index,
             frame_context.screen_world_rect,
             frame_context.clip_scroll_tree,
         );
 
         let mut map_local_to_raster = SpaceMapper::new(
             surface.raster_spatial_node_index,
             RasterRect::max_rect(),
-            frame_context.clip_scroll_tree,
         );
 
         let mut surface_rect = PictureRect::zero();
 
         for prim_instance in &mut prim_list.prim_instances {
             prim_instance.reset();
 
             if prim_instance.is_chased() {
@@ -1983,23 +1976,28 @@ impl PrimitiveStore {
                         prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                         // Ensure the primitive clip is popped - perhaps we can use
                         // some kind of scope to do this automatically in future.
                         frame_state.clip_chain_stack.pop_clip();
                         continue;
                     }
                 }
 
+                frame_state.clip_store.set_active_clips(
+                    prim_instance.local_clip_rect,
+                    prim_instance.spatial_node_index,
+                    frame_state.clip_chain_stack.current_clips(),
+                    &frame_context.clip_scroll_tree,
+                    &mut frame_state.data_stores.clip,
+                );
+
                 let clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
-                        frame_state.clip_chain_stack.current_clips(),
                         local_rect,
-                        prim_instance.local_clip_rect,
-                        prim_instance.spatial_node_index,
                         &map_local_to_surface,
                         &map_surface_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         surface.device_pixel_scale,
                         &frame_context.screen_world_rect,
                         &mut frame_state.data_stores.clip,
@@ -2559,42 +2557,26 @@ impl PrimitiveStore {
                 }
             }
         };
 
         let is_passthrough = match pic_info {
             Some((pic_context_for_children, mut pic_state_for_children, mut prim_list)) => {
                 let is_passthrough = pic_context_for_children.is_passthrough;
 
-                // Similar to the logic in the visibility pass, push either the
-                // picture clip chain or a new root, depending on whether this
-                // picture is backed by a surface.
-                if pic_context_for_children.is_composite {
-                    frame_state.clip_chain_stack.push_surface();
-                } else {
-                    frame_state.clip_chain_stack.push_clip(prim_instance.clip_chain_id);
-                }
-
                 self.prepare_primitives(
                     &mut prim_list,
                     &pic_context_for_children,
                     &mut pic_state_for_children,
                     frame_context,
                     frame_state,
                     data_stores,
                     scratch,
                 );
 
-                // And now undo the clip stack logic above.
-                if pic_context_for_children.is_composite {
-                    frame_state.clip_chain_stack.pop_surface();
-                } else {
-                    frame_state.clip_chain_stack.pop_clip();
-                }
-
                 // Restore the dependencies (borrow check dance)
                 self.pictures[pic_context_for_children.pic_index.0]
                     .restore_context(
                         prim_list,
                         pic_context_for_children,
                         pic_state_for_children,
                         frame_state,
                     );
@@ -2602,33 +2584,27 @@ impl PrimitiveStore {
                 is_passthrough
             }
             None => {
                 false
             }
         };
 
         if !is_passthrough {
-            // Push the per-primitive clip chain onto the current active stack
-            frame_state.clip_chain_stack.push_clip(prim_instance.clip_chain_id);
-
             prim_instance.update_clip_task(
                 pic_context.raster_spatial_node_index,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
                 self,
                 data_stores,
                 scratch,
             );
 
-            // Pop the primitive clip chain.
-            frame_state.clip_chain_stack.pop_clip();
-
             if prim_instance.is_chased() {
                 println!("\tconsidered visible and ready with local pos {:?}", prim_instance.prim_origin);
             }
         }
 
         #[cfg(debug_assertions)]
         {
             prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
@@ -3658,26 +3634,29 @@ impl PrimitiveInstance {
             clip_mask_instances.push(clip_mask_kind);
         } else {
             let dirty_world_rect = frame_state.current_dirty_region().combined.world_rect;
 
             for segment in segments {
                 // Build a clip chain for the smaller segment rect. This will
                 // often manage to eliminate most/all clips, and sometimes
                 // clip the segment completely.
+                frame_state.clip_store.set_active_clips_from_clip_chain(
+                    &prim_info.clip_chain,
+                    self.spatial_node_index,
+                    &frame_context.clip_scroll_tree,
+                );
+
                 let segment_clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
-                        frame_state.clip_chain_stack.current_clips(),
                         segment.local_rect.translate(&LayoutVector2D::new(
                             self.prim_origin.x,
                             self.prim_origin.y,
                         )),
-                        self.local_clip_rect,
-                        self.spatial_node_index,
                         &pic_state.map_local_to_pic,
                         &pic_state.map_pic_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         device_pixel_scale,
                         &dirty_world_rect,
                         &mut data_stores.clip,
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -719,16 +719,101 @@ Chunk* ChunkPool::remove(Chunk* chunk) {
     chunk->info.next->info.prev = chunk->info.prev;
   }
   chunk->info.next = chunk->info.prev = nullptr;
   --count_;
 
   return chunk;
 }
 
+// We could keep the chunk pool sorted, but that's likely to be more expensive.
+// This sort is nlogn, but keeping it sorted is likely to be m*n, with m being
+// the number of operations (likely higher than n).
+void ChunkPool::sort() {
+  // Only sort if the list isn't already sorted.
+  if (!isSorted()) {
+    head_ = mergeSort(head(), count());
+
+    // Fixup prev pointers.
+    Chunk* prev = nullptr;
+    for (Chunk* cur = head_; cur; cur = cur->info.next) {
+      cur->info.prev = prev;
+      prev = cur;
+    }
+  }
+
+  MOZ_ASSERT(verify());
+  MOZ_ASSERT(isSorted());
+}
+
+Chunk* ChunkPool::mergeSort(Chunk* list, size_t count) {
+  MOZ_ASSERT(bool(list) == bool(count));
+
+  if (count < 2) {
+    return list;
+  }
+
+  size_t half = count / 2;
+
+  // Split;
+  Chunk* front = list;
+  Chunk* back;
+  {
+    Chunk* cur = list;
+    for (size_t i = 0; i < half - 1; i++) {
+      MOZ_ASSERT(cur);
+      cur = cur->info.next;
+    }
+    back = cur->info.next;
+    cur->info.next = nullptr;
+  }
+
+  front = mergeSort(front, half);
+  back = mergeSort(back, count - half);
+
+  // Merge
+  list = nullptr;
+  Chunk** cur = &list;
+  while (front || back) {
+    if (!front) {
+      *cur = back;
+      break;
+    }
+    if (!back) {
+      *cur = front;
+      break;
+    }
+
+    // Note that the sort is stable due to the <= here. Nothing depends on
+    // this but it could.
+    if (front->info.numArenasFree <= back->info.numArenasFree) {
+      *cur = front;
+      front = front->info.next;
+      cur = &(*cur)->info.next;
+    } else {
+      *cur = back;
+      back = back->info.next;
+      cur = &(*cur)->info.next;
+    }
+  }
+
+  return list;
+}
+
+bool ChunkPool::isSorted() const {
+  uint32_t last = 1;
+  for (Chunk* cursor = head_; cursor; cursor = cursor->info.next) {
+    if (cursor->info.numArenasFree < last) {
+      return false;
+    }
+    last = cursor->info.numArenasFree;
+  }
+  return true;
+}
+
 #ifdef DEBUG
 bool ChunkPool::contains(Chunk* chunk) const {
   verify();
   for (Chunk* cursor = head_; cursor; cursor = cursor->info.next) {
     if (cursor == chunk) {
       return true;
     }
   }
@@ -3487,16 +3572,17 @@ void GCRuntime::startDecommit() {
       MOZ_ASSERT(!chunk->info.numArenasFreeCommitted);
     }
 
     // Since we release the GC lock while doing the decommit syscall below,
     // it is dangerous to iterate the available list directly, as the active
     // thread could modify it concurrently. Instead, we build and pass an
     // explicit Vector containing the Chunks we want to visit.
     MOZ_ASSERT(availableChunks(lock).verify());
+    availableChunks(lock).sort();
     for (ChunkPool::Iter iter(availableChunks(lock)); !iter.done();
          iter.next()) {
       if (!toDecommit.append(iter.get())) {
         // The OOM handler does a full, immediate decommit.
         return onOutOfMallocMemory(lock);
       }
     }
   }
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -79,21 +79,29 @@ class ChunkPool {
   Chunk* head() {
     MOZ_ASSERT(head_);
     return head_;
   }
   Chunk* pop();
   void push(Chunk* chunk);
   Chunk* remove(Chunk* chunk);
 
+  void sort();
+
+ private:
+  Chunk* mergeSort(Chunk* list, size_t count);
+  bool isSorted() const;
+
 #ifdef DEBUG
+ public:
   bool contains(Chunk* chunk) const;
   bool verify() const;
 #endif
 
+ public:
   // Pool mutation does not invalidate an Iter unless the mutation
   // is of the Chunk currently being visited by the Iter.
   class Iter {
    public:
     explicit Iter(ChunkPool& pool) : current_(pool.head_) {}
     bool done() const { return !current_; }
     void next();
     Chunk* get() const { return current_; }
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -2511,19 +2511,18 @@ static void UpdateBackdropIfNeeded(nsIFr
   MOZ_ASSERT(backdropPlaceholder->IsPlaceholderFrame());
   nsIFrame* backdropFrame =
       nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPlaceholder);
   MOZ_ASSERT(backdropFrame->IsBackdropFrame());
   MOZ_ASSERT(backdropFrame->Style()->GetPseudoType() ==
              PseudoStyleType::backdrop);
 
   RefPtr<ComputedStyle> newStyle = aStyleSet.ResolvePseudoElementStyle(
-      aFrame->GetContent()->AsElement(), PseudoStyleType::backdrop,
-      aFrame->Style(),
-      /* aPseudoElement = */ nullptr);
+      *aFrame->GetContent()->AsElement(), PseudoStyleType::backdrop,
+      aFrame->Style());
 
   // NOTE(emilio): We can't use the changes handled for the owner of the
   // backdrop frame, since it's out of flow, and parented to the viewport or
   // canvas frame (depending on the `position` value).
   MOZ_ASSERT(backdropFrame->GetParent()->IsViewportFrame() ||
              backdropFrame->GetParent()->IsCanvasFrame());
   nsTArray<nsIFrame*> wrappersToRestyle;
   ServoRestyleState state(aStyleSet, aChangeList, wrappersToRestyle);
@@ -2556,18 +2555,17 @@ static void UpdateOneAdditionalComputedS
                                              ServoRestyleState& aRestyleState) {
   auto pseudoType = aOldContext.GetPseudoType();
   MOZ_ASSERT(pseudoType != PseudoStyleType::NotPseudo);
   MOZ_ASSERT(
       !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudoType));
 
   RefPtr<ComputedStyle> newStyle =
       aRestyleState.StyleSet().ResolvePseudoElementStyle(
-          aFrame->GetContent()->AsElement(), pseudoType, aFrame->Style(),
-          /* aPseudoElement = */ nullptr);
+          *aFrame->GetContent()->AsElement(), pseudoType, aFrame->Style());
 
   uint32_t equalStructs;  // Not used, actually.
   nsChangeHint childHint =
       aOldContext.CalcStyleDifference(*newStyle, &equalStructs);
   if (!aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
       !aFrame->IsColumnSpanInMulticolSubtree()) {
     childHint = NS_RemoveSubsumedHints(childHint,
                                        aRestyleState.ChangesHandledFor(aFrame));
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -1153,19 +1153,18 @@ void nsFrameConstructorState::ConstructB
   nsContainerFrame* frame = do_QueryFrame(aFrame);
   if (!frame) {
     NS_WARNING("Cannot create backdrop frame for non-container frame");
     return;
   }
 
   RefPtr<ComputedStyle> style =
       mPresShell->StyleSet()->ResolvePseudoElementStyle(
-          aContent->AsElement(), PseudoStyleType::backdrop,
-          /* aParentComputedStyle */ nullptr,
-          /* aPseudoElement */ nullptr);
+          *aContent->AsElement(), PseudoStyleType::backdrop,
+          /* aParentStyle */ nullptr);
   MOZ_ASSERT(style->StyleDisplay()->mTopLayer == NS_STYLE_TOP_LAYER_TOP);
   nsContainerFrame* parentFrame =
       GetGeometricParent(*style->StyleDisplay(), nullptr);
 
   nsBackdropFrame* backdropFrame =
       new (mPresShell) nsBackdropFrame(style, mPresShell->GetPresContext());
   backdropFrame->Init(aContent, parentFrame, nullptr);
 
@@ -1712,16 +1711,18 @@ void nsCSSFrameConstructor::CreateGenera
       elemName = nsGkAtoms::mozgeneratedcontentbefore;
       property = nsGkAtoms::beforePseudoProperty;
       break;
     case PseudoStyleType::after:
       elemName = nsGkAtoms::mozgeneratedcontentafter;
       property = nsGkAtoms::afterPseudoProperty;
       break;
     case PseudoStyleType::marker:
+      // We want to get a marker style even if we match no rules, but we still
+      // want to check the result of GeneratedContentPseudoExists.
       elemName = nsGkAtoms::mozgeneratedcontentmarker;
       property = nsGkAtoms::markerPseudoProperty;
       break;
     default:
       MOZ_ASSERT_UNREACHABLE("unexpected aPseudoElement");
   }
 
   // |ProbePseudoStyleFor| checked the 'display' property and the
@@ -8712,28 +8713,26 @@ bool nsCSSFrameConstructor::DestroyFrame
 //////////////////////////////////////////////////////////////////////
 
 // Block frame construction code
 
 already_AddRefed<ComputedStyle> nsCSSFrameConstructor::GetFirstLetterStyle(
     nsIContent* aContent, ComputedStyle* aComputedStyle) {
   if (aContent) {
     return mPresShell->StyleSet()->ResolvePseudoElementStyle(
-        aContent->AsElement(), PseudoStyleType::firstLetter, aComputedStyle,
-        nullptr);
+        *aContent->AsElement(), PseudoStyleType::firstLetter, aComputedStyle);
   }
   return nullptr;
 }
 
 already_AddRefed<ComputedStyle> nsCSSFrameConstructor::GetFirstLineStyle(
     nsIContent* aContent, ComputedStyle* aComputedStyle) {
   if (aContent) {
     return mPresShell->StyleSet()->ResolvePseudoElementStyle(
-        aContent->AsElement(), PseudoStyleType::firstLine, aComputedStyle,
-        nullptr);
+        *aContent->AsElement(), PseudoStyleType::firstLine, aComputedStyle);
   }
   return nullptr;
 }
 
 // Predicate to see if a given content (block element) has
 // first-letter style applied to it.
 bool nsCSSFrameConstructor::ShouldHaveFirstLetterStyle(
     nsIContent* aContent, ComputedStyle* aComputedStyle) {
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -9946,17 +9946,17 @@ ComputedStyle* nsLayoutUtils::StyleForSc
 Maybe<MotionPathData> nsLayoutUtils::ResolveMotionPath(const nsIFrame* aFrame) {
   MOZ_ASSERT(aFrame);
 
   const nsStyleDisplay* display = aFrame->StyleDisplay();
   if (display->mOffsetPath.IsNone()) {
     return Nothing();
   }
 
-  gfx::Float angle = 0.0;
+  double directionAngle = 0.0;
   Point point;
   if (display->mOffsetPath.IsPath()) {
     const Span<const StylePathCommand>& path =
         display->mOffsetPath.AsPath()._0.AsSpan();
     // Build the path and compute the point and angle for creating the
     // equivalent translate and rotate.
     // Here we only need to build a valid path for motion path, so
     // using the default values of stroke-width, stoke-linecap, and fill-rule
@@ -9994,26 +9994,32 @@ Maybe<MotionPathData> nsLayoutUtils::Res
       }
     } else {
       // Per the spec, for unclosed interval, let used offset distance be equal
       // to offset distance clamped by 0 and the total length of the path.
       usedDistance = clamped(usedDistance, 0.0f, pathLength);
     }
     Point tangent;
     point = gfxPath->ComputePointAtLength(usedDistance, &tangent);
-    // Bug 1429301 - Implement offset-rotate for motion path.
-    // After implement offset-rotate, |angle| will be adjusted more.
-    // For now, the default value of offset-rotate is "auto", so we use the
-    // directional tangent vector.
-    angle = atan2(tangent.y, tangent.x);
+    directionAngle = (double)atan2(tangent.y, tangent.x);  // In Radian.
   } else {
     // Bug 1480665: Implement ray() function.
     NS_WARNING("Unsupported offset-path value");
   }
 
+  const StyleOffsetRotate& rotate = display->mOffsetRotate;
+  // If |rotate.auto_| is true, the element should be rotated by the angle of
+  // the direction (i.e. directional tangent vector) of the offset-path, and the
+  // computed value of <angle> is added to this.
+  // Otherwise, the element has a constant clockwise rotation transformation
+  // applied to it by the specified rotation angle. (i.e. Don't need to
+  // consider the direction of the path.)
+  gfx::Float angle = static_cast<gfx::Float>(
+      (rotate.auto_ ? directionAngle : 0.0) + rotate.angle.ToRadians());
+
   // Compute the offset for motion path translate.
   // We need to resolve transform-origin here to calculate the correct path
   // translate. (i.e. Center transform-origin on the path.)
   TransformReferenceBox refBox(aFrame);
   auto& transformOrigin = display->mTransformOrigin;
   CSSPoint origin = nsStyleTransformMatrix::Convert2DPosition(
       transformOrigin.horizontal, transformOrigin.vertical, refBox);
   // Bug 1186329: the translate parameters will be adjusted more after we
--- a/layout/forms/nsButtonFrameRenderer.cpp
+++ b/layout/forms/nsButtonFrameRenderer.cpp
@@ -503,23 +503,22 @@ ImgDrawResult nsButtonFrameRenderer::Pai
   return result;
 }
 
 /**
  * Call this when styles change
  */
 void nsButtonFrameRenderer::ReResolveStyles(nsPresContext* aPresContext) {
   // get all the styles
-  ComputedStyle* context = mFrame->Style();
   ServoStyleSet* styleSet = aPresContext->StyleSet();
 
   // get styles assigned to -moz-focus-inner (ie dotted border on Windows)
   mInnerFocusStyle = styleSet->ProbePseudoElementStyle(
       *mFrame->GetContent()->AsElement(), PseudoStyleType::mozFocusInner,
-      context);
+      mFrame->Style());
 }
 
 ComputedStyle* nsButtonFrameRenderer::GetComputedStyle(int32_t aIndex) const {
   switch (aIndex) {
     case NS_BUTTON_RENDERER_FOCUS_INNER_CONTEXT_INDEX:
       return mInnerFocusStyle;
     default:
       return nullptr;
--- a/layout/forms/nsDateTimeControlFrame.cpp
+++ b/layout/forms/nsDateTimeControlFrame.cpp
@@ -180,21 +180,25 @@ void nsDateTimeControlFrame::Reflow(nsPr
     // Needed in FinishReflowChild, for logical-to-physical conversion:
     nsSize borderBoxSize =
         LogicalSize(myWM, borderBoxISize, borderBoxBSize).GetPhysicalSize(myWM);
 
     // Place the child
     FinishReflowChild(inputAreaFrame, aPresContext, childDesiredSize,
                       &childReflowOuput, myWM, childOffset, borderBoxSize, 0);
 
-    nsSize contentBoxSize = LogicalSize(myWM, contentBoxISize, contentBoxBSize)
-                                .GetPhysicalSize(myWM);
-    aDesiredSize.SetBlockStartAscent(
-        childDesiredSize.BlockStartAscent() +
-        inputAreaFrame->BStart(aReflowInput.GetWritingMode(), contentBoxSize));
+    if (!aReflowInput.mStyleDisplay->IsContainLayout()) {
+      nsSize contentBoxSize =
+          LogicalSize(myWM, contentBoxISize, contentBoxBSize)
+              .GetPhysicalSize(myWM);
+      aDesiredSize.SetBlockStartAscent(
+          childDesiredSize.BlockStartAscent() +
+          inputAreaFrame->BStart(aReflowInput.GetWritingMode(),
+                                 contentBoxSize));
+    }  // else: we're layout-contained, and so we have no baseline.
   }
 
   LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
   aDesiredSize.SetSize(myWM, logicalDesiredSize);
 
   aDesiredSize.SetOverflowAreasToDesiredBounds();
 
   if (inputAreaFrame) {
--- a/layout/forms/nsHTMLButtonControlFrame.cpp
+++ b/layout/forms/nsHTMLButtonControlFrame.cpp
@@ -306,34 +306,24 @@ void nsHTMLButtonControlFrame::ReflowBut
       wm,
       LogicalSize(wm, aButtonReflowInput.ComputedISize() + clbp.IStartEnd(wm),
                   buttonContentBox.BSize(wm) + clbp.BStartEnd(wm)));
 
   //  * Button's ascent is its child's ascent, plus the child's block-offset
   // within our frame... unless it's orthogonal, in which case we'll use the
   // contents inline-size as an approximation for now.
   // XXX is there a better strategy? should we include border-padding?
-  if (aButtonReflowInput.mStyleDisplay->IsContainLayout()) {
-    // If we're layout-contained, then for the purposes of computing the
-    // ascent, we should pretend our button-contents frame had 0 height. In
-    // other words, we use the <button> content-rect's central block-axis
-    // position as our baseline.
-    // NOTE: This should be the same ascent that we'd get from the final 'else'
-    // clause here, if we had no DOM children. In that no-children scenario,
-    // the final 'else' clause's BlockStartAscent() term would be 0, and its
-    // childPos.B(wm) term would be equal to the same central offset that we're
-    // independently calculating here.
-    nscoord containAscent = (buttonContentBox.BSize(wm) / 2) + clbp.BStart(wm);
-    aButtonDesiredSize.SetBlockStartAscent(containAscent);
-  } else if (aButtonDesiredSize.GetWritingMode().IsOrthogonalTo(wm)) {
-    aButtonDesiredSize.SetBlockStartAscent(contentsDesiredSize.ISize(wm));
-  } else {
-    aButtonDesiredSize.SetBlockStartAscent(
-        contentsDesiredSize.BlockStartAscent() + childPos.B(wm));
-  }
+  if (!aButtonReflowInput.mStyleDisplay->IsContainLayout()) {
+    if (aButtonDesiredSize.GetWritingMode().IsOrthogonalTo(wm)) {
+      aButtonDesiredSize.SetBlockStartAscent(contentsDesiredSize.ISize(wm));
+    } else {
+      aButtonDesiredSize.SetBlockStartAscent(
+          contentsDesiredSize.BlockStartAscent() + childPos.B(wm));
+    }
+  }  // else: we're layout-contained, and so we have no baseline.
 
   aButtonDesiredSize.SetOverflowAreasToDesiredBounds();
 }
 
 bool nsHTMLButtonControlFrame::GetVerticalAlignBaseline(
     mozilla::WritingMode aWM, nscoord* aBaseline) const {
   nsIFrame* inner = mFrames.FirstChild();
   if (MOZ_UNLIKELY(inner->GetWritingMode().IsOrthogonalTo(aWM))) {
--- a/layout/forms/nsNumberControlFrame.cpp
+++ b/layout/forms/nsNumberControlFrame.cpp
@@ -208,22 +208,25 @@ void nsNumberControlFrame::Reflow(nsPres
     nsSize borderBoxSize =
         LogicalSize(myWM, borderBoxISize, borderBoxBSize).GetPhysicalSize(myWM);
 
     // Place the child
     FinishReflowChild(outerWrapperFrame, aPresContext, wrappersDesiredSize,
                       &wrapperReflowInput, myWM, wrapperOffset, borderBoxSize,
                       0);
 
-    nsSize contentBoxSize = LogicalSize(myWM, contentBoxISize, contentBoxBSize)
-                                .GetPhysicalSize(myWM);
-    aDesiredSize.SetBlockStartAscent(
-        wrappersDesiredSize.BlockStartAscent() +
-        outerWrapperFrame->BStart(aReflowInput.GetWritingMode(),
-                                  contentBoxSize));
+    if (!aReflowInput.mStyleDisplay->IsContainLayout()) {
+      nsSize contentBoxSize =
+          LogicalSize(myWM, contentBoxISize, contentBoxBSize)
+              .GetPhysicalSize(myWM);
+      aDesiredSize.SetBlockStartAscent(
+          wrappersDesiredSize.BlockStartAscent() +
+          outerWrapperFrame->BStart(aReflowInput.GetWritingMode(),
+                                    contentBoxSize));
+    }  // else: we're layout-contained, and so we have no baseline.
   }
 
   LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
   aDesiredSize.SetSize(myWM, logicalDesiredSize);
 
   aDesiredSize.SetOverflowAreasToDesiredBounds();
 
   if (outerWrapperFrame) {
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -576,29 +576,31 @@ void nsTextControlFrame::Reflow(nsPresCo
   LogicalSize finalSize(
       wm,
       aReflowInput.ComputedISize() +
           aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm),
       aReflowInput.ComputedBSize() +
           aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm));
   aDesiredSize.SetSize(wm, finalSize);
 
-  // Calculate the baseline and store it in mFirstBaseline.
-  nscoord lineHeight = aReflowInput.ComputedBSize();
-  float inflation = nsLayoutUtils::FontSizeInflationFor(this);
-  if (!IsSingleLineTextControl()) {
-    lineHeight = ReflowInput::CalcLineHeight(
-        GetContent(), Style(), PresContext(), NS_AUTOHEIGHT, inflation);
-  }
-  RefPtr<nsFontMetrics> fontMet =
-      nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
-  mFirstBaseline = nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight,
-                                                          wm.IsLineInverted()) +
-                   aReflowInput.ComputedLogicalBorderPadding().BStart(wm);
-  aDesiredSize.SetBlockStartAscent(mFirstBaseline);
+  if (!aReflowInput.mStyleDisplay->IsContainLayout()) {
+    // Calculate the baseline and store it in mFirstBaseline.
+    nscoord lineHeight = aReflowInput.ComputedBSize();
+    float inflation = nsLayoutUtils::FontSizeInflationFor(this);
+    if (!IsSingleLineTextControl()) {
+      lineHeight = ReflowInput::CalcLineHeight(
+          GetContent(), Style(), PresContext(), NS_AUTOHEIGHT, inflation);
+    }
+    RefPtr<nsFontMetrics> fontMet =
+        nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
+    mFirstBaseline = nsLayoutUtils::GetCenteredFontBaseline(
+                         fontMet, lineHeight, wm.IsLineInverted()) +
+                     aReflowInput.ComputedLogicalBorderPadding().BStart(wm);
+    aDesiredSize.SetBlockStartAscent(mFirstBaseline);
+  }  // else: we're layout-contained, and so we have no baseline.
 
   // overflow handling
   aDesiredSize.SetOverflowAreasToDesiredBounds();
   // perform reflow on all kids
   nsIFrame* kid = mFrames.FirstChild();
   while (kid) {
     ReflowTextControlChild(kid, aPresContext, aReflowInput, aStatus,
                            aDesiredSize);
--- a/layout/forms/nsTextControlFrame.h
+++ b/layout/forms/nsTextControlFrame.h
@@ -64,17 +64,17 @@ class nsTextControlFrame final : public 
                                 nscoord* aBaseline) const override {
     return GetNaturalBaselineBOffset(aWM, BaselineSharingGroup::First,
                                      aBaseline);
   }
 
   bool GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
                                  BaselineSharingGroup aBaselineGroup,
                                  nscoord* aBaseline) const override {
-    if (!IsSingleLineTextControl()) {
+    if (StyleDisplay()->IsContainLayout() || !IsSingleLineTextControl()) {
       return false;
     }
     NS_ASSERTION(mFirstBaseline != NS_INTRINSIC_ISIZE_UNKNOWN,
                  "please call Reflow before asking for the baseline");
     if (aBaselineGroup == BaselineSharingGroup::First) {
       *aBaseline = mFirstBaseline;
     } else {
       *aBaseline = BSize(aWM) - mFirstBaseline;
@@ -332,17 +332,17 @@ class nsTextControlFrame final : public 
   // Otherwise, it's cached when setting specific value or getting value from
   // TextEditor.  Additionally, when contents in the anonymous <div> element
   // is modified, this is cleared.
   //
   // FIXME(bug 1402545): Consider using an nsAutoString here.
   nsString mCachedValue;
 
   // Our first baseline, or NS_INTRINSIC_ISIZE_UNKNOWN if we have a pending
-  // Reflow.
+  // Reflow (or if we're contain:layout, which means we have no baseline).
   nscoord mFirstBaseline;
 
   // these packed bools could instead use the high order bits on mState, saving
   // 4 bytes
   bool mEditorHasBeenInitialized;
   bool mIsProcessing;
 
 #ifdef DEBUG
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -147,17 +147,17 @@ static bool IsFrameDescendantOfAny(
   }
   return false;
 }
 
 class nsDisplayTextOverflowMarker final : public nsPaintedDisplayItem {
  public:
   nsDisplayTextOverflowMarker(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                               const nsRect& aRect, nscoord aAscent,
-                              nsStyleTextOverflowSide aStyle,
+                              const StyleTextOverflowSide& aStyle,
                               uint32_t aLineNumber, uint16_t aIndex)
       : nsPaintedDisplayItem(aBuilder, aFrame),
         mRect(aRect),
         mStyle(aStyle),
         mAscent(aAscent),
         mIndex((aLineNumber << 1) + aIndex) {
     MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker);
   }
@@ -198,17 +198,17 @@ class nsDisplayTextOverflowMarker final 
       mozilla::wr::IpcResourceUpdateQueue& aResources,
       const StackingContextHelper& aSc,
       layers::RenderRootStateManager* aManager,
       nsDisplayListBuilder* aDisplayListBuilder) override;
 
   NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW)
  private:
   nsRect mRect;  // in reference frame coordinates
-  const nsStyleTextOverflowSide mStyle;
+  const StyleTextOverflowSide mStyle;
   nscoord mAscent;  // baseline for the marker text in mRect
   uint16_t mIndex;
 };
 
 static void PaintTextShadowCallback(gfxContext* aCtx, nsPoint aShadowOffset,
                                     const nscolor& aShadowColor, void* aData) {
   reinterpret_cast<nsDisplayTextOverflowMarker*>(aData)->PaintTextToContext(
       aCtx, aShadowOffset);
@@ -243,30 +243,31 @@ void nsDisplayTextOverflowMarker::PaintT
           mFrame, aCtx, pt.x + mRect.width, -mAscent));
     }
   } else {
     pt.y = NSToCoordFloor(
         nsLayoutUtils::GetSnappedBaselineY(mFrame, aCtx, pt.y, mAscent));
   }
   pt += aOffsetFromRect;
 
-  if (mStyle.mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
+  if (mStyle.IsEllipsis()) {
     gfxTextRun* textRun = GetEllipsisTextRun(mFrame);
     if (textRun) {
       NS_ASSERTION(!textRun->IsRightToLeft(),
                    "Ellipsis textruns should always be LTR!");
       gfx::Point gfxPt(pt.x, pt.y);
       textRun->Draw(gfxTextRun::Range(textRun), gfxPt,
                     gfxTextRun::DrawParams(aCtx));
     }
   } else {
     RefPtr<nsFontMetrics> fm =
         nsLayoutUtils::GetInflatedFontMetricsForFrame(mFrame);
-    nsLayoutUtils::DrawString(mFrame, *fm, aCtx, mStyle.mString.get(),
-                              mStyle.mString.Length(), pt);
+    NS_ConvertUTF8toUTF16 str16{mStyle.AsString().AsString()};
+    nsLayoutUtils::DrawString(mFrame, *fm, aCtx, str16.get(), str16.Length(),
+                              pt);
   }
 }
 
 bool nsDisplayTextOverflowMarker::CreateWebRenderCommands(
     mozilla::wr::DisplayListBuilder& aBuilder,
     mozilla::wr::IpcResourceUpdateQueue& aResources,
     const StackingContextHelper& aSc, layers::RenderRootStateManager* aManager,
     nsDisplayListBuilder* aDisplayListBuilder) {
@@ -325,22 +326,31 @@ TextOverflow::TextOverflow(nsDisplayList
     // Use a null containerSize to convert a vector from logical to physical.
     const nsSize nullContainerSize;
     mContentArea.MoveBy(
         mBlockWM, LogicalPoint(mBlockWM, mScrollableFrame->GetScrollPosition(),
                                nullContainerSize));
   }
   uint8_t direction = aBlockFrame->StyleVisibility()->mDirection;
   const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
+
+  const auto& textOverflow = style->mTextOverflow;
+  bool shouldToggleDirection =
+      textOverflow.sides_are_logical && (direction == NS_STYLE_DIRECTION_RTL);
+  const auto& leftSide =
+      shouldToggleDirection ? textOverflow.second : textOverflow.first;
+  const auto& rightSide =
+      shouldToggleDirection ? textOverflow.first : textOverflow.second;
+
   if (mBlockWM.IsBidiLTR()) {
-    mIStart.Init(style->mTextOverflow.GetLeft(direction));
-    mIEnd.Init(style->mTextOverflow.GetRight(direction));
+    mIStart.Init(leftSide);
+    mIEnd.Init(rightSide);
   } else {
-    mIStart.Init(style->mTextOverflow.GetRight(direction));
-    mIEnd.Init(style->mTextOverflow.GetLeft(direction));
+    mIStart.Init(rightSide);
+    mIEnd.Init(leftSide);
   }
   // The left/right marker string is setup in ExamineLineFrames when a line
   // has overflow on that side.
 }
 
 /* static */
 Maybe<TextOverflow> TextOverflow::WillProcessLines(
     nsDisplayListBuilder* aBuilder, nsIFrame* aBlockFrame) {
@@ -687,28 +697,26 @@ LogicalRect TextOverflow::ExamineLineFra
   if (!iendWantsMarker || !mIEnd.mActive) {
     mIEnd.Reset();
   }
   return nonSnappedContentArea;
 }
 
 void TextOverflow::ProcessLine(const nsDisplayListSet& aLists, nsLineBox* aLine,
                                uint32_t aLineNumber) {
-  if (mIStart.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP &&
-      mIEnd.mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP &&
+  if (mIStart.mStyle->IsClip() && mIEnd.mStyle->IsClip() &&
       !aLine->HasLineClampEllipsis()) {
     return;
   }
 
   mIStart.Reset();
-  mIStart.mActive = mIStart.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP;
+  mIStart.mActive = !mIStart.mStyle->IsClip();
   mIEnd.Reset();
   mIEnd.mHasBlockEllipsis = aLine->HasLineClampEllipsis();
-  mIEnd.mActive = mIEnd.mStyle->mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
-                  aLine->HasLineClampEllipsis();
+  mIEnd.mActive = !mIEnd.mStyle->IsClip() || aLine->HasLineClampEllipsis();
 
   FrameHashtable framesToHide(64);
   AlignmentEdges alignmentEdges;
   const LogicalRect contentArea =
       ExamineLineFrames(aLine, &framesToHide, &alignmentEdges);
   bool needIStart = mIStart.IsNeeded();
   bool needIEnd = mIEnd.IsNeeded();
   if (!needIStart && !needIEnd) {
@@ -811,18 +819,18 @@ void TextOverflow::PruneDisplayListConte
     saved.AppendToTop(item);
   }
   aList->AppendToTop(&saved);
 }
 
 /* static */
 bool TextOverflow::HasClippedTextOverflow(nsIFrame* aBlockFrame) {
   const nsStyleTextReset* style = aBlockFrame->StyleTextReset();
-  return style->mTextOverflow.mLeft.mType == NS_STYLE_TEXT_OVERFLOW_CLIP &&
-         style->mTextOverflow.mRight.mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
+  return style->mTextOverflow.first.IsClip() &&
+         style->mTextOverflow.second.IsClip();
 }
 
 /* static */
 bool TextOverflow::HasBlockEllipsis(nsIFrame* aBlockFrame) {
   nsBlockFrame* f = do_QueryFrame(aBlockFrame);
   return f && f->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
 }
 
@@ -891,46 +899,45 @@ void TextOverflow::CreateMarkers(const n
                                   aLine->BSize());
     nsPoint offset = mBuilder->ToReferenceFrame(mBlock);
     nsRect markerRect =
         markerLogicalRect.GetPhysicalRect(mBlockWM, mBlockSize) + offset;
     ClipMarker(aContentArea.GetPhysicalRect(mBlockWM, mBlockSize) + offset,
                markerRect, clipState);
     mMarkerList.AppendNewToTop<nsDisplayTextOverflowMarker>(
         mBuilder, mBlock, markerRect, aLine->GetLogicalAscent(),
-        mIEnd.mHasBlockEllipsis ? nsStyleTextOverflowSide::Ellipsis()
+        mIEnd.mHasBlockEllipsis ? StyleTextOverflowSide::Ellipsis()
                                 : *mIEnd.mStyle,
         aLineNumber, 1);
   }
 }
 
 void TextOverflow::Marker::SetupString(nsIFrame* aFrame) {
   if (mInitialized) {
     return;
   }
 
   // A limitation here is that at the IEnd of a line, we only ever render one of
   // a text-overflow marker and a -webkit-line-clamp block ellipsis.  Since we
   // don't track the block ellipsis string and the text-overflow marker string
   // separately, if both apply to the element, we will always use "…" as the
   // string for text-overflow.
-  if (HasBlockEllipsis(aFrame) ||
-      mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
+  if (HasBlockEllipsis(aFrame) || mStyle->IsEllipsis()) {
     gfxTextRun* textRun = GetEllipsisTextRun(aFrame);
     if (textRun) {
       mISize = textRun->GetAdvanceWidth();
     } else {
       mISize = 0;
     }
   } else {
     RefPtr<gfxContext> rc =
         aFrame->PresShell()->CreateReferenceRenderingContext();
     RefPtr<nsFontMetrics> fm =
         nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
-    mISize = nsLayoutUtils::AppUnitWidthOfStringBidi(mStyle->mString, aFrame,
-                                                     *fm, *rc);
+    mISize = nsLayoutUtils::AppUnitWidthOfStringBidi(
+        NS_ConvertUTF8toUTF16(mStyle->AsString().AsString()), aFrame, *fm, *rc);
   }
   mIntrinsicISize = mISize;
   mInitialized = true;
 }
 
 }  // namespace css
 }  // namespace mozilla
--- a/layout/generic/TextOverflow.h
+++ b/layout/generic/TextOverflow.h
@@ -245,49 +245,47 @@ class TextOverflow final {
   nsDisplayList mMarkerList;
   nsSize mBlockSize;
   WritingMode mBlockWM;
   bool mCanHaveInlineAxisScrollbar;
   bool mAdjustForPixelSnapping;
 
   class Marker {
    public:
-    void Init(const nsStyleTextOverflowSide& aStyle) {
+    void Init(const StyleTextOverflowSide& aStyle) {
       mInitialized = false;
       mISize = 0;
       mStyle = &aStyle;
       mIntrinsicISize = 0;
       mHasOverflow = false;
       mHasBlockEllipsis = false;
       mActive = false;
       mEdgeAligned = false;
     }
 
     /**
      * Setup the marker string and calculate its size, if not done already.
      */
     void SetupString(nsIFrame* aFrame);
 
-    bool IsSuppressed() const {
-      return !mHasBlockEllipsis && mStyle->mType == NS_STYLE_TEXT_OVERFLOW_CLIP;
-    }
+    bool IsSuppressed() const { return !mHasBlockEllipsis && mStyle->IsClip(); }
     bool IsNeeded() const { return mHasOverflow || mHasBlockEllipsis; }
     void Reset() {
       mHasOverflow = false;
       mHasBlockEllipsis = false;
       mEdgeAligned = false;
     }
 
     // The current width of the marker, the range is [0 .. mIntrinsicISize].
     nscoord mISize;
     // The intrinsic width of the marker.
     nscoord mIntrinsicISize;
     // The text-overflow style for this side.  Ignored if we're rendering a
     // block ellipsis.
-    const nsStyleTextOverflowSide* mStyle;
+    const StyleTextOverflowSide* mStyle;
     // True if there is visible overflowing inline content on this side.
     bool mHasOverflow;
     // True if this side has a block ellipsis (from -webkit-line-clamp).
     bool mHasBlockEllipsis;
     // True if mISize and mIntrinsicISize have been setup from style.
     bool mInitialized;
     // True if the style is not text-overflow:clip on this side and the marker
     // won't cause the line to become empty.
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -2925,18 +2925,21 @@ void nsBlockFrame::ReflowDirtyLines(Bloc
         marker, aState, metrics,
         aState.mReflowInput.ComputedPhysicalBorderPadding().top);
     NS_ASSERTION(!MarkerIsEmpty() || metrics.BSize(wm) == 0,
                  "empty ::marker frame took up space");
 
     if (!MarkerIsEmpty()) {
       // There are no lines so we have to fake up some y motion so that
       // we end up with *some* height.
-
-      if (metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
+      // (Note: if we're layout-contained, we have to be sure to leave our
+      // ReflowOutput's BlockStartAscent() (i.e. the baseline) untouched,
+      // because layout-contained frames have no baseline.)
+      if (!aState.mReflowInput.mStyleDisplay->IsContainLayout() &&
+          metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
         nscoord ascent;
         WritingMode wm = aState.mReflowInput.GetWritingMode();
         if (nsLayoutUtils::GetFirstLineBaseline(wm, marker, &ascent)) {
           metrics.SetBlockStartAscent(ascent);
         } else {
           metrics.SetBlockStartAscent(metrics.BSize(wm));
         }
       }
@@ -5716,18 +5719,17 @@ void nsBlockFrame::UpdateFirstLetterStyl
   if (inFlowFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
     inFlowFrame = inFlowFrame->GetPlaceholderFrame();
   }
   nsIFrame* styleParent = CorrectStyleParentFrame(inFlowFrame->GetParent(),
                                                   PseudoStyleType::firstLetter);
   ComputedStyle* parentStyle = styleParent->Style();
   RefPtr<ComputedStyle> firstLetterStyle =
       aRestyleState.StyleSet().ResolvePseudoElementStyle(
-          mContent->AsElement(), PseudoStyleType::firstLetter, parentStyle,
-          nullptr);
+          *mContent->AsElement(), PseudoStyleType::firstLetter, parentStyle);
   // Note that we don't need to worry about changehints for the continuation
   // styles: those will be handled by the styleParent already.
   RefPtr<ComputedStyle> continuationStyle =
       aRestyleState.StyleSet().ResolveStyleForFirstLetterContinuation(
           parentStyle);
   UpdateStyleOfOwnedChildFrame(letterFrame, firstLetterStyle, aRestyleState,
                                Some(continuationStyle.get()));
 
@@ -7444,18 +7446,17 @@ void nsBlockFrame::UpdatePseudoElementSt
 
   if (nsIFrame* firstLineFrame = GetFirstLineFrame()) {
     nsIFrame* styleParent = CorrectStyleParentFrame(firstLineFrame->GetParent(),
                                                     PseudoStyleType::firstLine);
 
     ComputedStyle* parentStyle = styleParent->Style();
     RefPtr<ComputedStyle> firstLineStyle =
         aRestyleState.StyleSet().ResolvePseudoElementStyle(
-            mContent->AsElement(), PseudoStyleType::firstLine, parentStyle,
-            nullptr);
+            *mContent->AsElement(), PseudoStyleType::firstLine, parentStyle);
 
     // FIXME(bz): Can we make first-line continuations be non-inheriting anon
     // boxes?
     RefPtr<ComputedStyle> continuationStyle =
         aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
             PseudoStyleType::mozLineFrame, parentStyle);
 
     UpdateStyleOfOwnedChildFrame(firstLineFrame, firstLineStyle, aRestyleState,
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -4548,19 +4548,18 @@ void nsFlexContainerFrame::DoFlexLayout(
 
   GenerateFlexLines(aPresContext, aReflowInput, aContentBoxMainSize,
                     aAvailableBSizeForContent, aStruts, aAxisTracker,
                     aMainGapSize, aHasLineClampEllipsis, placeholderKids,
                     lines);
 
   if ((lines.getFirst()->IsEmpty() && !lines.getFirst()->getNext()) ||
       aReflowInput.mStyleDisplay->IsContainLayout()) {
-    // If have no flex items, or if we  are layout contained and
-    // want to behave as if we have none, our parent
-    // should synthesize a baseline if needed.
+    // We have no flex items, or we're layout-contained. So, we have no
+    // baseline, and our parent should synthesize a baseline if needed.
     AddStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
   } else {
     RemoveStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE);
   }
 
   // Construct our computed info if we've been asked to do so. This is
   // necessary to do now so we can capture some computed values for
   // FlexItems during layout that would not otherwise be saved (like
@@ -4914,17 +4913,18 @@ void nsFlexContainerFrame::DoFlexLayout(
       }
 
       // If we didn't perform a final reflow of the item, we still have a
       // -webkit-line-clamp ellipsis hanging around, but we shouldn't have one
       // any more, we need to clear that now.  We strictly only need to do this
       // if we didn't do a bsize measuring reflow of the item earlier (since
       // that is normally when we deal with -webkit-line-clamp ellipses) but
       // not all flex items need such a reflow.
-      if (!itemNeedsReflow && aHasLineClampEllipsis && GetLineClampValue() == 0) {
+      if (!itemNeedsReflow && aHasLineClampEllipsis &&
+          GetLineClampValue() == 0) {
         item->BlockFrame()->ClearLineClampEllipsis();
       }
 
       // If the item has auto margins, and we were tracking the UsedMargin
       // property, set the property to the computed margin values.
       if (item->HasAnyAutoMargin()) {
         nsMargin* propValue =
             item->Frame()->GetProperty(nsIFrame::UsedMarginProperty());
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -974,17 +974,18 @@ static void GetScrollableOverflowForPers
 nscoord nsHTMLScrollFrame::GetLogicalBaseline(WritingMode aWritingMode) const {
   // This function implements some of the spec text here:
   //  https://drafts.csswg.org/css-align/#baseline-export
   //
   // Specifically: if our scrolled frame is a block, we just use the inherited
   // GetLogicalBaseline() impl, which synthesizes a baseline from the
   // margin-box. Otherwise, we defer to our scrolled frame, considering it
   // to be scrolled to its initial scroll position.
-  if (mHelper.mScrolledFrame->IsBlockFrameOrSubclass()) {
+  if (mHelper.mScrolledFrame->IsBlockFrameOrSubclass() ||
+      StyleDisplay()->IsContainLayout()) {
     return nsContainerFrame::GetLogicalBaseline(aWritingMode);
   }
 
   // OK, here's where we defer to our scrolled frame. We have to add our
   // border BStart thickness to whatever it returns, to produce an offset in
   // our frame-rect's coordinate system. (We don't have to add padding,
   // because the scrolled frame handles our padding.)
   LogicalMargin border = GetLogicalUsedBorder(aWritingMode);
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -41,18 +41,18 @@ typedef nsAbsoluteContainingBlock::AbsPo
 typedef nsGridContainerFrame::TrackSize TrackSize;
 const uint32_t nsGridContainerFrame::kTranslatedMaxLine =
     uint32_t(nsStyleGridLine::kMaxLine - nsStyleGridLine::kMinLine);
 const uint32_t nsGridContainerFrame::kAutoLine = kTranslatedMaxLine + 3457U;
 typedef nsTHashtable<nsPtrHashKey<nsIFrame>> FrameHashtable;
 typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags;
 typedef nsLayoutUtils::IntrinsicISizeType IntrinsicISizeType;
 
-static const nsFrameState kIsSubgridBits = (NS_STATE_GRID_IS_COL_SUBGRID |
-                                            NS_STATE_GRID_IS_ROW_SUBGRID);
+static const nsFrameState kIsSubgridBits =
+    (NS_STATE_GRID_IS_COL_SUBGRID | NS_STATE_GRID_IS_ROW_SUBGRID);
 
 // https://drafts.csswg.org/css-sizing/#constraints
 enum class SizingConstraint {
   MinContent,   // sizing under min-content constraint
   MaxContent,   // sizing under max-content constraint
   NoConstraint  // no constraint, used during Reflow
 };
 
@@ -148,18 +148,17 @@ struct RepeatTrackSizingInput {
       : mMin(aWM, 0, 0),
         mSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
         mMax(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) {}
   RepeatTrackSizingInput(const LogicalSize& aMin, const LogicalSize& aSize,
                          const LogicalSize& aMax)
       : mMin(aMin), mSize(aSize), mMax(aMax) {}
 
   void SetDefiniteSizes(LogicalAxis aAxis, WritingMode aWM,
-                        const StyleSize& aMinCoord,
-                        const StyleSize& aSizeCoord,
+                        const StyleSize& aMinCoord, const StyleSize& aSizeCoord,
                         const StyleMaxSize& aMaxCoord) {
     nscoord& min = mMin.Size(aAxis, aWM);
     nscoord& size = mSize.Size(aAxis, aWM);
     nscoord& max = mMax.Size(aAxis, aWM);
     if (aMinCoord.ConvertsToLength()) {
       min = aMinCoord.ToLength();
     }
     if (aMaxCoord.ConvertsToLength()) {
@@ -767,17 +766,18 @@ struct nsGridContainerFrame::Subgrid {
   // The subgrid's area as a grid item, i.e. in its parent's grid space.
   GridArea mArea;
   // The (inner) grid size for the subgrid, zero-based.
   uint32_t mGridColEnd;
   uint32_t mGridRowEnd;
   // The margin+border+padding for the subgrid box in its parent grid's WM.
   // (This also includes the size of any scrollbars.)
   LogicalMargin mMarginBorderPadding;
-  // Does the subgrid frame have orthogonal writing-mode to its parent grid container?
+  // Does the subgrid frame have orthogonal writing-mode to its parent grid
+  // container?
   bool mIsOrthogonal;
 
   NS_DECLARE_FRAME_PROPERTY_DELETABLE(Prop, Subgrid)
 };
 using Subgrid = nsGridContainerFrame::Subgrid;
 
 /**
  * Track size data for use by subgrids (which don't do sizing of their own
@@ -1269,21 +1269,22 @@ struct nsGridContainerFrame::TrackSizing
 
   TrackSizingFunctions(const nsStyleGridTemplate& aGridTemplate,
                        const nsStyleCoord& aAutoMinSizing,
                        const nsStyleCoord& aAutoMaxSizing)
       // Note: if mIsSubgrid is true below then the HasRepeatAuto bit is for
       // the mLineNameList, so we suppress that so that we can use this struct
       // also when it's true.  This can happen when a specified 'subgrid' has
       // no grid parent, which will behave as 'none'.
-      : TrackSizingFunctions(aGridTemplate.mMinTrackSizingFunctions,
-                             aGridTemplate.mMaxTrackSizingFunctions,
-                             aAutoMinSizing, aAutoMaxSizing,
-                             !aGridTemplate.mIsSubgrid && aGridTemplate.HasRepeatAuto(),
-                             aGridTemplate.mRepeatAutoIndex) {}
+      : TrackSizingFunctions(
+            aGridTemplate.mMinTrackSizingFunctions,
+            aGridTemplate.mMaxTrackSizingFunctions, aAutoMinSizing,
+            aAutoMaxSizing,
+            !aGridTemplate.mIsSubgrid && aGridTemplate.HasRepeatAuto(),
+            aGridTemplate.mRepeatAutoIndex) {}
 
   /**
    * Initialize the number of auto-fill/fit tracks to use and return that.
    * (zero if no auto-fill/fit track was specified)
    */
   uint32_t InitRepeatTracks(const NonNegativeLengthPercentageOrNormal& aGridGap,
                             nscoord aMinSize, nscoord aSize, nscoord aMaxSize) {
     uint32_t repeatTracks =
@@ -1443,56 +1444,60 @@ struct nsGridContainerFrame::TrackSizing
  *
  * FIXME: this was written before there was a spec... the spec now says:
  * "If calculating the layout of a grid item in this step depends on
  *  the available space in the block axis, assume the available space
  *  that it would have if any row with a definite max track sizing
  *  function had that size and all other rows were infinite."
  * https://drafts.csswg.org/css-grid-2/#subgrid-sizing
  */
-struct MOZ_STACK_CLASS nsGridContainerFrame::SubgridFallbackTrackSizingFunctions {
-  SubgridFallbackTrackSizingFunctions(nsGridContainerFrame* aSubgridFrame,
-                                      const Subgrid* aSubgrid,
-                                      nsGridContainerFrame* aParentGridContainer,
-                                      LogicalAxis aParentAxis) {
+struct MOZ_STACK_CLASS
+    nsGridContainerFrame::SubgridFallbackTrackSizingFunctions {
+  SubgridFallbackTrackSizingFunctions(
+      nsGridContainerFrame* aSubgridFrame, const Subgrid* aSubgrid,
+      nsGridContainerFrame* aParentGridContainer, LogicalAxis aParentAxis) {
     MOZ_ASSERT(aSubgrid);
-    MOZ_ASSERT(aSubgridFrame->IsSubgrid(
-        aSubgrid->mIsOrthogonal ? GetOrthogonalAxis(aParentAxis) : aParentAxis));
+    MOZ_ASSERT(aSubgridFrame->IsSubgrid(aSubgrid->mIsOrthogonal
+                                            ? GetOrthogonalAxis(aParentAxis)
+                                            : aParentAxis));
     nsGridContainerFrame* parent = aParentGridContainer;
     auto parentAxis = aParentAxis;
     LineRange range = aSubgrid->mArea.LineRangeForAxis(parentAxis);
     // Find our nearest non-subgridded axis and use its track sizing functions.
     while (parent->IsSubgrid(parentAxis)) {
       const auto* parentSubgrid = parent->GetProperty(Subgrid::Prop());
       auto* grandParent = parent->ParentGridContainerForSubgrid();
       auto grandParentWM = grandParent->GetWritingMode();
-      bool isSameDirInAxis = parent->GetWritingMode().
-          ParallelAxisStartsOnSameSide(parentAxis, grandParentWM);
+      bool isSameDirInAxis =
+          parent->GetWritingMode().ParallelAxisStartsOnSameSide(parentAxis,
+                                                                grandParentWM);
       if (MOZ_UNLIKELY(!isSameDirInAxis)) {
         auto end = parentAxis == eLogicalAxisBlock ? parentSubgrid->mGridRowEnd
                                                    : parentSubgrid->mGridColEnd;
         range.ReverseDirection(end);
         // range is now in the same direction as the grand-parent's axis
       }
-      auto grandParentAxis = parentSubgrid->mIsOrthogonal ?
-          GetOrthogonalAxis(parentAxis) : parentAxis;
-      const auto& parentRange = parentSubgrid->mArea.LineRangeForAxis(grandParentAxis);
+      auto grandParentAxis = parentSubgrid->mIsOrthogonal
+                                 ? GetOrthogonalAxis(parentAxis)
+                                 : parentAxis;
+      const auto& parentRange =
+          parentSubgrid->mArea.LineRangeForAxis(grandParentAxis);
       range.Translate(parentRange.mStart);
       // range is now in the grand-parent's coordinates
       parentAxis = grandParentAxis;
       parent = grandParent;
     }
     const auto* pos = parent->StylePosition();
     const auto isInlineAxis = parentAxis == eLogicalAxisInline;
-    const auto& szf = isInlineAxis ? pos->GridTemplateColumns()
-                                   : pos->GridTemplateRows();
-    const auto& minAuto = isInlineAxis ? pos->mGridAutoColumnsMin
-                                       : pos->mGridAutoRowsMin;
-    const auto& maxAuto = isInlineAxis ? pos->mGridAutoColumnsMax
-                                       : pos->mGridAutoRowsMax;
+    const auto& szf =
+        isInlineAxis ? pos->GridTemplateColumns() : pos->GridTemplateRows();
+    const auto& minAuto =
+        isInlineAxis ? pos->mGridAutoColumnsMin : pos->mGridAutoRowsMin;
+    const auto& maxAuto =
+        isInlineAxis ? pos->mGridAutoColumnsMax : pos->mGridAutoRowsMax;
     TrackSizingFunctions tsf(szf, minAuto, maxAuto);
     for (auto i : range.Range()) {
       mMinSizingFunctions.AppendElement(tsf.MinSizingFor(i));
       mMaxSizingFunctions.AppendElement(tsf.MaxSizingFor(i));
     }
     mAutoMinSizing = &minAuto;
     mAutoMaxSizing = &maxAuto;
   }
@@ -2275,17 +2280,17 @@ struct MOZ_STACK_CLASS nsGridContainerFr
     mRows = mSharedGridData->mRows;
 
     if (firstInFlow->GetProperty(UsedTrackSizes::Prop())) {
       auto* prop = aGridContainerFrame->GetProperty(UsedTrackSizes::Prop());
       if (!prop) {
         prop = new UsedTrackSizes();
         aGridContainerFrame->SetProperty(UsedTrackSizes::Prop(), prop);
       }
-      prop->mCanResolveLineRangeSize = { true, true };
+      prop->mCanResolveLineRangeSize = {true, true};
       prop->mSizes[eLogicalAxisInline] = mCols.mSizes;
       prop->mSizes[eLogicalAxisBlock] = mRows.mSizes;
     }
 
     // Copy item data from each child's first-in-flow data in mSharedGridData.
     // XXX NOTE: This is O(n^2) in the number of items. (bug 1252186)
     mIter.Reset();
     for (; !mIter.AtEnd(); mIter.Next()) {
@@ -2869,20 +2874,21 @@ struct MOZ_STACK_CLASS nsGridContainerFr
 static Subgrid* SubgridComputeMarginBorderPadding(
     const GridItemInfo& aGridItem, const LogicalSize& aPercentageBasis) {
   auto* subgridFrame = aGridItem.SubgridFrame();
   auto cbWM = aGridItem.mFrame->GetParent()->GetWritingMode();
   nsMargin physicalMBP;
   {
     auto wm = subgridFrame->GetWritingMode();
     auto pmPercentageBasis = cbWM.IsOrthogonalTo(wm)
-        ? aPercentageBasis.BSize(wm) : aPercentageBasis.ISize(wm);
+                                 ? aPercentageBasis.BSize(wm)
+                                 : aPercentageBasis.ISize(wm);
     SizeComputationInput sz(subgridFrame, nullptr, cbWM, pmPercentageBasis);
-    physicalMBP = sz.ComputedPhysicalMargin() +
-                  sz.ComputedPhysicalBorderPadding();
+    physicalMBP =
+        sz.ComputedPhysicalMargin() + sz.ComputedPhysicalBorderPadding();
   }
   auto* subgrid = subgridFrame->GetProperty(Subgrid::Prop());
   subgrid->mMarginBorderPadding = LogicalMargin(cbWM, physicalMBP);
   if (aGridItem.mFrame != subgridFrame) {
     nsIScrollableFrame* scrollFrame = aGridItem.mFrame->GetScrollTargetFrame();
     if (scrollFrame) {
       nsMargin ssz = scrollFrame->GetActualScrollbarSizes();
       subgrid->mMarginBorderPadding += LogicalMargin(cbWM, ssz);
@@ -2926,25 +2932,24 @@ static void CopyUsedTrackSizes(nsTArray<
   if (parentAxis == eLogicalAxisInline) {
     // Find the subgrid's grid item frame in its parent grid container.  This
     // is usually the same as aSubgridFrame but it may also have a ScrollFrame,
     // FieldSetFrame etc.  We just loop until we see the first ancestor
     // GridContainerFrame and pick the last frame we saw before that.
     // Note that all subgrids are inside a parent (sub)grid container.
     const nsIFrame* outerGridItemFrame = aSubgridFrame;
     for (nsIFrame* parent = aSubgridFrame->GetParent();
-         parent != aUsedTrackSizesFrame;
-         parent = parent->GetParent()) {
+         parent != aUsedTrackSizesFrame; parent = parent->GetParent()) {
       MOZ_ASSERT(!parent->IsGridContainerFrame());
       outerGridItemFrame = parent;
     }
     auto sizeInAxis = range.ToLength(aUsedTrackSizes->mSizes[parentAxis]);
-    LogicalSize pmPercentageBasis = aSubgrid->mIsOrthogonal
-        ? LogicalSize(wm, nscoord(0), sizeInAxis)
-        : LogicalSize(wm, sizeInAxis, nscoord(0));
+    LogicalSize pmPercentageBasis =
+        aSubgrid->mIsOrthogonal ? LogicalSize(wm, nscoord(0), sizeInAxis)
+                                : LogicalSize(wm, sizeInAxis, nscoord(0));
     GridItemInfo info(const_cast<nsIFrame*>(outerGridItemFrame),
                       aSubgrid->mArea);
     SubgridComputeMarginBorderPadding(info, pmPercentageBasis);
   }
   const LogicalMargin& mbp = aSubgrid->mMarginBorderPadding;
   if (MOZ_LIKELY(cbwm.ParallelAxisStartsOnSameSide(parentAxis, wm))) {
     uint32_t i = range.mStart;
     nscoord startMBP = mbp.Start(parentAxis, cbwm);
@@ -2968,17 +2973,17 @@ static void CopyUsedTrackSizes(nsTArray<
     }
     aResult[0].mPosition = 0;
     aResult[0].mBase -= startMBP;
     aResult.LastElement().mBase -= mbp.Start(parentAxis, cbwm);
   }
 }
 
 void nsGridContainerFrame::UsedTrackSizes::ResolveTrackSizesForAxis(
-  nsGridContainerFrame* aFrame, LogicalAxis aAxis, gfxContext& aRC) {
+    nsGridContainerFrame* aFrame, LogicalAxis aAxis, gfxContext& aRC) {
   if (mCanResolveLineRangeSize[aAxis]) {
     return;
   }
   if (!aFrame->IsSubgrid()) {
     // We can't resolve sizes in this axis at this point. aFrame is the top grid
     // container, which will store its final track sizes later once they're
     // resolved in this axis (in GridReflowInput::CalculateTrackSizesForAxis).
     // The single caller of this method only needs track sizes for
@@ -2989,17 +2994,17 @@ void nsGridContainerFrame::UsedTrackSize
   auto* parent = aFrame->ParentGridContainerForSubgrid();
   auto* parentSizes = parent->GetUsedTrackSizes();
   if (!parentSizes) {
     parentSizes = new UsedTrackSizes();
     parent->SetProperty(UsedTrackSizes::Prop(), parentSizes);
   }
   auto* subgrid = aFrame->GetProperty(Subgrid::Prop());
   const auto parentAxis =
-    subgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
+      subgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
   parentSizes->ResolveTrackSizesForAxis(parent, parentAxis, aRC);
   if (!parentSizes->mCanResolveLineRangeSize[parentAxis]) {
     if (aFrame->IsSubgrid(aAxis)) {
       ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC,
                                       NS_UNCONSTRAINEDSIZE);
     }
     return;
   }
@@ -3115,20 +3120,20 @@ void nsGridContainerFrame::GridReflowInp
 
   // positions and sizes are now final
   tracks.mCanResolveLineRangeSize = true;
 }
 
 void nsGridContainerFrame::GridReflowInput::CalculateTrackSizes(
     const Grid& aGrid, const LogicalSize& aContentBox,
     SizingConstraint aConstraint) {
-  CalculateTrackSizesForAxis(eLogicalAxisInline, aGrid,
-                             aContentBox.ISize(mWM), aConstraint);
-  CalculateTrackSizesForAxis(eLogicalAxisBlock, aGrid,
-                             aContentBox.BSize(mWM), aConstraint);
+  CalculateTrackSizesForAxis(eLogicalAxisInline, aGrid, aContentBox.ISize(mWM),
+                             aConstraint);
+  CalculateTrackSizesForAxis(eLogicalAxisBlock, aGrid, aContentBox.BSize(mWM),
+                             aConstraint);
 }
 
 /**
  * (XXX share this utility function with nsFlexContainerFrame at some point)
  *
  * Helper for BuildDisplayList, to implement this special-case for grid
  * items from the spec:
  *   The painting order of grid items is exactly the same as inline blocks,
@@ -3854,17 +3859,17 @@ void nsGridContainerFrame::Grid::Subgrid
 void nsGridContainerFrame::Grid::PlaceGridItems(
     GridReflowInput& aState, const RepeatTrackSizingInput& aSizes) {
   MOZ_ASSERT(mCellMap.mCells.IsEmpty(), "unexpected entries in cell map");
 
   mAreas = aState.mFrame->GetImplicitNamedAreas();
 
   if (aState.mFrame->HasSubgridItems() || aState.mFrame->IsSubgrid()) {
     if (auto* uts = aState.mFrame->GetUsedTrackSizes()) {
-      uts->mCanResolveLineRangeSize = { false, false };
+      uts->mCanResolveLineRangeSize = {false, false};
       uts->mSizes[eLogicalAxisInline].ClearAndRetainStorage();
       uts->mSizes[eLogicalAxisBlock].ClearAndRetainStorage();
     }
   }
 
   // SubgridPlaceGridItems will set these if we find any subgrid items.
   aState.mFrame->RemoveStateBits(NS_STATE_GRID_HAS_COL_SUBGRID_ITEM |
                                  NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM);
@@ -4484,28 +4489,30 @@ static nscoord ContentContribution(
         extraMargin += mbp.End(aAxis, aCBWM);
       }
     }
     // It also contributes (half of) the subgrid's gap on its edges (if any)
     // subtracted by the non-subgrid ancestor grid container's gap.
     // Note that this can also be negative since it's considered a margin.
     if (itemEdgeBits != ItemState::eEdgeBits) {
       auto subgridAxis = aCBWM.IsOrthogonalTo(subgridFrame->GetWritingMode())
-                              ? GetOrthogonalAxis(aAxis) : aAxis;
-      auto& gapStyle = subgridAxis == eLogicalAxisBlock ?
-          subgridFrame->StylePosition()->mRowGap :
-          subgridFrame->StylePosition()->mColumnGap;
+                             ? GetOrthogonalAxis(aAxis)
+                             : aAxis;
+      auto& gapStyle = subgridAxis == eLogicalAxisBlock
+                           ? subgridFrame->StylePosition()->mRowGap
+                           : subgridFrame->StylePosition()->mColumnGap;
       if (!gapStyle.IsNormal()) {
-        auto subgridExtent =
-            subgridAxis == eLogicalAxisBlock ? subgrid->mGridRowEnd
-                                             : subgrid->mGridColEnd;
+        auto subgridExtent = subgridAxis == eLogicalAxisBlock
+                                 ? subgrid->mGridRowEnd
+                                 : subgrid->mGridColEnd;
         if (subgridExtent > 1) {
           nscoord subgridGap =
               nsLayoutUtils::ResolveGapToLength(gapStyle, NS_UNCONSTRAINEDSIZE);
-          auto& tracks = aAxis == eLogicalAxisBlock ? aState.mRows : aState.mCols;
+          auto& tracks =
+              aAxis == eLogicalAxisBlock ? aState.mRows : aState.mCols;
           auto gapDelta = subgridGap - tracks.mGridGap;
           if (!itemEdgeBits) {
             extraMargin += gapDelta;
           } else {
             extraMargin += gapDelta / 2;
           }
         }
       }
@@ -4527,27 +4534,28 @@ static nscoord ContentContribution(
     // The next two variables are MinSizeClamp values in the child's axes.
     nscoord iMinSizeClamp = NS_MAXSIZE;
     nscoord bMinSizeClamp = NS_MAXSIZE;
     LogicalSize cbSize(childWM, 0, NS_UNCONSTRAINEDSIZE);
     // Below, we try to resolve the child's grid-area size in its inline-axis
     // to use as the CB/Available size in the MeasuringReflow that follows.
     if (child->GetParent() != aState.mFrame) {
       // This item is a child of a subgrid descendant.
-      auto* subgridFrame = static_cast<nsGridContainerFrame*>(child->GetParent());
+      auto* subgridFrame =
+          static_cast<nsGridContainerFrame*>(child->GetParent());
       MOZ_ASSERT(subgridFrame->IsGridContainerFrame());
       auto* uts = subgridFrame->GetProperty(UsedTrackSizes::Prop());
       if (!uts) {
         uts = new UsedTrackSizes();
         subgridFrame->SetProperty(UsedTrackSizes::Prop(), uts);
       }
       // The grid-item's inline-axis as expressed in the subgrid's WM.
       auto subgridAxis = childWM.IsOrthogonalTo(subgridFrame->GetWritingMode())
-                            ? eLogicalAxisBlock
-                            : eLogicalAxisInline;
+                             ? eLogicalAxisBlock
+                             : eLogicalAxisInline;
       uts->ResolveTrackSizesForAxis(subgridFrame, subgridAxis, *aRC);
       if (uts->mCanResolveLineRangeSize[subgridAxis]) {
         auto* subgrid =
             subgridFrame->GetProperty(nsGridContainerFrame::Subgrid::Prop());
         const GridItemInfo* originalItem = nullptr;
         for (const auto& item : subgrid->mGridItems) {
           if (item.mFrame == child) {
             originalItem = &item;
@@ -5250,20 +5258,18 @@ void nsGridContainerFrame::Tracks::Resol
       auto* subgrid =
           SubgridComputeMarginBorderPadding(gridItem, percentageBasis);
       LogicalMargin mbp = SubgridAccumulatedMarginBorderPadding(
           gridItem.SubgridFrame(), subgrid, wm, mAxis);
       if (span == 1) {
         AddSubgridContribution(mSizes[lineRange.mStart],
                                mbp.StartEnd(mAxis, wm));
       } else {
-        AddSubgridContribution(mSizes[lineRange.mStart],
-                               mbp.Start(mAxis, wm));
-        AddSubgridContribution(mSizes[lineRange.mEnd - 1],
-                               mbp.End(mAxis, wm));
+        AddSubgridContribution(mSizes[lineRange.mStart], mbp.Start(mAxis, wm));
+        AddSubgridContribution(mSizes[lineRange.mEnd - 1], mbp.End(mAxis, wm));
       }
       continue;
     }
 
     if (span == 1) {
       // Step 1. Size tracks to fit non-spanning items.
       if (ResolveIntrinsicSizeStep1(aState, aFunctions, aPercentageBasis,
                                     aConstraint, lineRange, gridItem)) {
@@ -7034,18 +7040,20 @@ void nsGridContainerFrame::Reflow(nsPres
                "continuations should have same kIsSubgridBits");
   }
   GridReflowInput gridReflowInput(this, aReflowInput);
   if (gridReflowInput.mIter.ItemsAreAlreadyInOrder()) {
     AddStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER);
   } else {
     RemoveStateBits(NS_STATE_GRID_NORMAL_FLOW_CHILDREN_IN_CSS_ORDER);
   }
-  if (gridReflowInput.mIter.AtEnd()) {
-    // We have no grid items, our parent should synthesize a baseline if needed.
+  if (gridReflowInput.mIter.AtEnd() ||
+      aReflowInput.mStyleDisplay->IsContainLayout()) {
+    // We have no grid items, or we're layout-contained. So, we have no
+    // baseline, and our parent should synthesize a baseline if needed.
     AddStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE);
   } else {
     RemoveStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE);
   }
   const nscoord computedBSize = aReflowInput.ComputedBSize();
   const nscoord computedISize = aReflowInput.ComputedISize();
   const WritingMode& wm = gridReflowInput.mWM;
   const LogicalSize computedSize(wm, computedISize, computedBSize);
@@ -7113,17 +7121,17 @@ void nsGridContainerFrame::Reflow(nsPres
     const auto& rowSizes = gridReflowInput.mRows.mSizes;
     if (!IsRowSubgrid()) {
       // Apply 'align-content' to the grid.
       if (computedBSize == NS_AUTOHEIGHT &&
           stylePos->mRowGap.IsLengthPercentage() &&
           stylePos->mRowGap.AsLengthPercentage().HasPercent()) {
         // Re-resolve the row-gap now that we know our intrinsic block-size.
         gridReflowInput.mRows.mGridGap =
-          nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap, bSize);
+            nsLayoutUtils::ResolveGapToLength(stylePos->mRowGap, bSize);
       }
       gridReflowInput.mRows.AlignJustifyContent(stylePos, wm, bSize, false);
     } else {
       if (computedBSize == NS_AUTOHEIGHT) {
         bSize = gridReflowInput.mRows.GridLineEdge(rowSizes.Length(),
                                                    GridLineSide::BeforeGridGap);
         contentArea.BSize(wm) = bSize;
       }
@@ -7493,28 +7501,27 @@ void nsGridContainerFrame::Init(nsIConte
                                 nsIFrame* aPrevInFlow) {
   nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
 
   nsFrameState bits = nsFrameState(0);
   if (MOZ_LIKELY(!aPrevInFlow)) {
     bits = ComputeSelfSubgridBits();
   } else {
     bits = aPrevInFlow->GetStateBits() &
-           (kIsSubgridBits |
-            NS_STATE_GRID_HAS_COL_SUBGRID_ITEM |
+           (kIsSubgridBits | NS_STATE_GRID_HAS_COL_SUBGRID_ITEM |
             NS_STATE_GRID_HAS_ROW_SUBGRID_ITEM);
   }
   AddStateBits(bits);
 }
 
 void nsGridContainerFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
   nsContainerFrame::DidSetComputedStyle(aOldStyle);
 
   if (!aOldStyle) {
-    return; // Init() already initialized the bits.
+    return;  // Init() already initialized the bits.
   }
   UpdateSubgridFrameState();
 }
 
 nscoord nsGridContainerFrame::IntrinsicISize(gfxContext* aRenderingContext,
                                              IntrinsicISizeType aType) {
   // Calculate the sum of column sizes under intrinsic sizing.
   // http://dev.w3.org/csswg/css-grid/#intrinsic-sizes
--- a/layout/mathml/nsMathMLFrame.cpp
+++ b/layout/mathml/nsMathMLFrame.cpp
@@ -93,17 +93,17 @@ nsMathMLFrame::UpdatePresentationData(ui
 /* static */
 void nsMathMLFrame::ResolveMathMLCharStyle(nsPresContext* aPresContext,
                                            nsIContent* aContent,
                                            ComputedStyle* aParentComputedStyle,
                                            nsMathMLChar* aMathMLChar) {
   PseudoStyleType pseudoType = PseudoStyleType::mozMathAnonymous;  // savings
   RefPtr<ComputedStyle> newComputedStyle;
   newComputedStyle = aPresContext->StyleSet()->ResolvePseudoElementStyle(
-      aContent->AsElement(), pseudoType, aParentComputedStyle, nullptr);
+      *aContent->AsElement(), pseudoType, aParentComputedStyle);
 
   aMathMLChar->SetComputedStyle(newComputedStyle);
 }
 
 /* static */
 void nsMathMLFrame::GetEmbellishDataFrom(nsIFrame* aFrame,
                                          nsEmbellishData& aEmbellishData) {
   // initialize OUT params
--- a/layout/reftests/w3c-css/submitted/contain/contain-layout-suppress-baseline-002-ref.html
+++ b/layout/reftests/w3c-css/submitted/contain/contain-layout-suppress-baseline-002-ref.html
@@ -3,29 +3,28 @@
 <head>
   <meta charset="utf-8">
   <title>CSS Reftest Reference</title>
   <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
   <style>
   .flexBaselineCheck {
     display: flex;
     border: 1px solid black;
-    height: 70px;
   }
   .flexBaselineCheck > * {
     border: 2px solid teal;
-
     /* In the testcase, the (baseline-aligned) items should all have their
        bottom borders aligned with the 50px-tall canvas.  In other words, their
-       bottom borders should all be 20px away from the bottom of their flex
-       container.  Here in the reference case, we just use "flex-end" alignment
-       plus a hardcoded 20px margin-bottom to produce a precise reference
-       for what that should look like. */
+       bottom borders should all be aligned at the bottom of their flex
+       container, separated from the bottom by only by their margin-end
+       distance.  Here in the reference case, we just use "flex-end" alignment
+       (plus the same amount of margin) to produce a precise reference for what
+       that should look like. */
     align-self: flex-end;
-    margin-bottom: 20px;
+    margin: 2px;
   }
   canvas {
     background: purple;
     width: 20px;
     height: 50px;
     box-sizing: border-box;
   }
   </style>
--- a/layout/reftests/w3c-css/submitted/contain/contain-layout-suppress-baseline-002.html
+++ b/layout/reftests/w3c-css/submitted/contain/contain-layout-suppress-baseline-002.html
@@ -6,22 +6,22 @@
   <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
   <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-layout">
   <link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#valdef-align-items-baseline">
   <link rel="match" href="contain-layout-suppress-baseline-002-ref.html">
   <style>
   .flexBaselineCheck {
     display: flex;
     border: 1px solid black;
-    height: 70px;
   }
   .flexBaselineCheck > * {
     contain: layout;
     border: 2px solid teal;
     align-self: baseline;
+    margin: 2px;
   }
   canvas {
     background: purple;
     width: 20px;
     height: 50px;
     box-sizing: border-box;
   }
   </style>
--- a/layout/reftests/w3c-css/submitted/contain/reftest.list
+++ b/layout/reftests/w3c-css/submitted/contain/reftest.list
@@ -43,16 +43,16 @@ fuzzy-if(webrender&&winWidget,0-24,0-2) 
 == contain-layout-formatting-context-margin-001.html contain-layout-formatting-context-margin-001-ref.html
 == contain-layout-containing-block-fixed-001.html contain-paint-containing-block-fixed-001-ref.html
 == contain-layout-containing-block-absolute-001.html contain-paint-containing-block-absolute-001-ref.html
 == contain-layout-ignored-cases-ib-split-001.html contain-layout-ignored-cases-ib-split-001-ref.html
 == contain-layout-ignored-cases-no-principal-box-001.html contain-paint-ignored-cases-no-principal-box-001-ref.html
 == contain-layout-ignored-cases-no-principal-box-002.html contain-layout-ignored-cases-no-principal-box-002-ref.html
 == contain-layout-ignored-cases-no-principal-box-003.html contain-layout-ignored-cases-no-principal-box-003-ref.html
 == contain-layout-suppress-baseline-001.html contain-layout-suppress-baseline-001-ref.html
-fails == contain-layout-suppress-baseline-002.html contain-layout-suppress-baseline-002-ref.html # bug 1552287
+== contain-layout-suppress-baseline-002.html contain-layout-suppress-baseline-002-ref.html
 
 # The following lines are duplicates of other lines from further up in this
 # manifest. They're listed again here so we can re-run these tests with
 # column-span enabled. These lines can be removed once the pref becomes
 # default-enabled (Bug 1426010).
 pref(layout.css.column-span.enabled,true) == contain-size-multicol-002.html contain-size-multicol-002-ref.html
 pref(layout.css.column-span.enabled,true) == contain-size-multicol-003.html contain-size-multicol-003-ref.html
--- a/layout/style/ComputedStyle.cpp
+++ b/layout/style/ComputedStyle.cpp
@@ -369,18 +369,16 @@ Maybe<StyleStructID> ComputedStyle::Look
 #  undef STYLE_STRUCT
   return Nothing();
 }
 #endif  // DEBUG
 
 ComputedStyle* ComputedStyle::GetCachedLazyPseudoStyle(
     PseudoStyleType aPseudo) const {
   MOZ_ASSERT(PseudoStyle::IsPseudoElement(aPseudo));
-  MOZ_ASSERT(!IsLazilyCascadedPseudoElement(),
-             "Lazy pseudos can't inherit lazy pseudos");
 
   if (nsCSSPseudoElements::PseudoElementSupportsUserActionState(aPseudo)) {
     return nullptr;
   }
 
   return mCachedInheritingStyles.Lookup(aPseudo);
 }
 
--- a/layout/style/ComputedStyle.h
+++ b/layout/style/ComputedStyle.h
@@ -189,18 +189,16 @@ class ComputedStyle {
     mCachedInheritingStyles.Insert(aStyle);
   }
 
   ComputedStyle* GetCachedLazyPseudoStyle(PseudoStyleType aPseudo) const;
 
   void SetCachedLazyPseudoStyle(ComputedStyle* aStyle) {
     MOZ_ASSERT(aStyle->IsPseudoElement());
     MOZ_ASSERT(!GetCachedLazyPseudoStyle(aStyle->GetPseudoType()));
-    MOZ_ASSERT(!IsLazilyCascadedPseudoElement(),
-               "lazy pseudos can't inherit lazy pseudos");
     MOZ_ASSERT(aStyle->IsLazilyCascadedPseudoElement());
 
     // Since we're caching lazy pseudo styles on the ComputedValues of the
     // originating element, we can assume that we either have the same
     // originating element, or that they were at least similar enough to share
     // the same ComputedValues, which means that they would match the same
     // pseudo rules. This allows us to avoid matching selectors and checking
     // the rule node before deciding to share.
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -404,16 +404,17 @@ cbindgen-types = [
     { gecko = "StyleDisplayMode", servo = "gecko::media_features::DisplayMode" },
     { gecko = "StylePrefersColorScheme", servo = "gecko::media_features::PrefersColorScheme" },
     { gecko = "StyleExtremumLength", servo = "values::computed::length::ExtremumLength" },
     { gecko = "StyleFillRule", servo = "values::generics::basic_shape::FillRule" },
     { gecko = "StyleFontDisplay", servo = "font_face::FontDisplay" },
     { gecko = "StyleFontFaceSourceListComponent", servo = "font_face::FontFaceSourceListComponent" },
     { gecko = "StyleFontLanguageOverride", servo = "values::computed::font::FontLanguageOverride" },
     { gecko = "StyleOffsetPath", servo = "values::computed::motion::OffsetPath" },
+    { gecko = "StyleOffsetRotate", servo = "values::computed::motion::OffsetRotate" },
     { gecko = "StylePathCommand", servo = "values::specified::svg_path::PathCommand" },
     { gecko = "StyleUnicodeRange", servo = "cssparser::UnicodeRange" },
     { gecko = "StyleOverflowWrap", servo = "values::computed::OverflowWrap" },
     { gecko = "StyleWordBreak", servo = "values::computed::WordBreak" },
     { gecko = "StyleLineBreak", servo = "values::computed::LineBreak" },
     { gecko = "StyleUserSelect", servo = "values::computed::UserSelect" },
     { gecko = "StyleBreakBetween", servo = "values::computed::BreakBetween" },
     { gecko = "StyleBreakWithin", servo = "values::computed::BreakWithin" },
@@ -422,16 +423,17 @@ cbindgen-types = [
     { gecko = "StyleScrollSnapAlign", servo = "values::computed::ScrollSnapAlign" },
     { gecko = "StyleScrollSnapStrictness", servo = "values::computed::ScrollSnapStrictness" },
     { gecko = "StyleScrollSnapType", servo = "values::computed::ScrollSnapType" },
     { gecko = "StyleResize", servo = "values::computed::Resize" },
     { gecko = "StyleOverflowClipBox", servo = "values::computed::OverflowClipBox" },
     { gecko = "StyleFloat", servo = "values::computed::Float" },
     { gecko = "StyleOverscrollBehavior", servo = "values::computed::OverscrollBehavior" },
     { gecko = "StyleTextAlign", servo = "values::computed::TextAlign" },
+    { gecko = "StyleTextOverflow", servo = "values::computed::TextOverflow" },
     { gecko = "StyleOverflow", servo = "values::computed::Overflow" },
     { gecko = "StyleOverflowAnchor", servo = "values::computed::OverflowAnchor" },
     { gecko = "StyleLength", servo = "values::computed::CSSPixelLength" },
     { gecko = "StyleLengthPercentage", servo = "values::computed::LengthPercentage" },
     { gecko = "StyleNonNegativeLengthPercentage", servo = "values::computed::NonNegativeLengthPercentage" },
     { gecko = "StyleGenericLengthPercentageOrAuto", servo = "values::generics::length::LengthPercentageOrAuto" },
     { gecko = "StyleGenericLengthPercentageOrNormal", servo = "values::generics::length::LengthPercentageOrNormal" },
     { gecko = "StyleLengthPercentageOrAuto", servo = "values::computed::LengthPercentageOrAuto" },
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -431,48 +431,55 @@ static inline bool LazyPseudoIsCacheable
                                          ComputedStyle* aParentStyle) {
   return aParentStyle &&
          !nsCSSPseudoElements::IsEagerlyCascadedInServo(aType) &&
          aOriginatingElement.HasServoData() &&
          !Servo_Element_IsPrimaryStyleReusedViaRuleNode(&aOriginatingElement);
 }
 
 already_AddRefed<ComputedStyle> ServoStyleSet::ResolvePseudoElementStyle(
-    Element* aOriginatingElement, PseudoStyleType aType,
-    ComputedStyle* aParentStyle, Element* aPseudoElement) {
+    const Element& aOriginatingElement, PseudoStyleType aType,
+    ComputedStyle* aParentStyle, IsProbe aIsProbe) {
   // Runs from frame construction, this should have clean styles already, except
   // with non-lazy FC...
   UpdateStylistIfNeeded();
   MOZ_ASSERT(PseudoStyle::IsPseudoElement(aType));
 
-  RefPtr<ComputedStyle> computedValues;
+  const bool cacheable =
+      LazyPseudoIsCacheable(aType, aOriginatingElement, aParentStyle);
+  RefPtr<ComputedStyle> style =
+      cacheable ? aParentStyle->GetCachedLazyPseudoStyle(aType) : nullptr;
+
+  const bool isProbe = aIsProbe == IsProbe::Yes;
 
-  if (aPseudoElement) {
-    MOZ_ASSERT(aType == aPseudoElement->GetPseudoElementType());
-    computedValues =
-        Servo_ResolveStyle(aPseudoElement, mRawSet.get()).Consume();
-  } else {
-    bool cacheable =
-        LazyPseudoIsCacheable(aType, *aOriginatingElement, aParentStyle);
-    computedValues =
-        cacheable ? aParentStyle->GetCachedLazyPseudoStyle(aType) : nullptr;
-
-    if (!computedValues) {
-      computedValues = Servo_ResolvePseudoStyle(aOriginatingElement, aType,
-                                                /* is_probe = */ false,
-                                                aParentStyle, mRawSet.get())
-                           .Consume();
-      if (cacheable) {
-        aParentStyle->SetCachedLazyPseudoStyle(computedValues);
-      }
+  if (!style) {
+    // FIXME(emilio): Why passing null for probing as the parent style?
+    //
+    // There are callers which do pass the wrong parent style and it would
+    // assert (like ComputeSelectionStyle()). That's messy!
+    style = Servo_ResolvePseudoStyle(&aOriginatingElement, aType, isProbe,
+                                     isProbe ? nullptr : aParentStyle,
+                                     mRawSet.get())
+                .Consume();
+    if (!style) {
+      MOZ_ASSERT(isProbe);
+      return nullptr;
+    }
+    if (cacheable) {
+      aParentStyle->SetCachedLazyPseudoStyle(style);
     }
   }
 
-  MOZ_ASSERT(computedValues);
-  return computedValues.forget();
+  MOZ_ASSERT(style);
+
+  if (isProbe && !GeneratedContentPseudoExists(*aParentStyle, *style)) {
+    return nullptr;
+  }
+
+  return style.forget();
 }
 
 already_AddRefed<ComputedStyle>
 ServoStyleSet::ResolveInheritingAnonymousBoxStyle(PseudoStyleType aType,
                                                   ComputedStyle* aParentStyle) {
   MOZ_ASSERT(PseudoStyle::IsInheritingAnonBox(aType));
   MOZ_ASSERT_IF(aParentStyle, !StylistNeedsUpdate());
 
@@ -648,76 +655,45 @@ void ServoStyleSet::AddDocStyleSheet(Sty
     SetStylistStyleSheetsDirty();
   }
 
   if (mStyleRuleMap) {
     mStyleRuleMap->SheetAdded(*aSheet);
   }
 }
 
-already_AddRefed<ComputedStyle> ServoStyleSet::ProbePseudoElementStyle(
-    const Element& aOriginatingElement, PseudoStyleType aType,
-    ComputedStyle* aParentStyle) {
-  // Runs from frame construction, this should have clean styles already, except
-  // with non-lazy FC...
-  UpdateStylistIfNeeded();
-
-  // NB: We ignore aParentStyle, because in some cases
-  // (first-line/first-letter on anonymous box blocks) Gecko passes something
-  // nonsensical there.  In all other cases we want to inherit directly from
-  // aOriginatingElement's styles anyway.
-  MOZ_ASSERT(PseudoStyle::IsPseudoElement(aType));
-
-  bool cacheable =
-      LazyPseudoIsCacheable(aType, aOriginatingElement, aParentStyle);
+bool ServoStyleSet::GeneratedContentPseudoExists(
+    const ComputedStyle& aParentStyle, const ComputedStyle& aPseudoStyle) {
+  auto type = aPseudoStyle.GetPseudoType();
+  MOZ_ASSERT(type != PseudoStyleType::NotPseudo);
 
-  RefPtr<ComputedStyle> computedValues =
-      cacheable ? aParentStyle->GetCachedLazyPseudoStyle(aType) : nullptr;
-  if (!computedValues) {
-    computedValues =
-        Servo_ResolvePseudoStyle(&aOriginatingElement, aType,
-                                 /* is_probe = */ true, nullptr, mRawSet.get())
-            .Consume();
-    if (!computedValues) {
-      return nullptr;
-    }
-
-    if (cacheable) {
-      // NB: We don't need to worry about the before/after handling below
-      // because those are eager and thus not |cacheable| anyway.
-      aParentStyle->SetCachedLazyPseudoStyle(computedValues);
-    }
-  }
-
-  if (aType == PseudoStyleType::marker) {
+  if (type == PseudoStyleType::marker) {
     // ::marker only exist for list items (for now).
-    if (aParentStyle->StyleDisplay()->mDisplay != StyleDisplay::ListItem) {
-      return nullptr;
+    if (aParentStyle.StyleDisplay()->mDisplay != StyleDisplay::ListItem) {
+      return false;
     }
     // display:none is equivalent to not having the pseudo-element at all.
-    if (computedValues->StyleDisplay()->mDisplay == StyleDisplay::None) {
-      return nullptr;
+    if (aPseudoStyle.StyleDisplay()->mDisplay == StyleDisplay::None) {
+      return false;
     }
   }
 
   // For :before and :after pseudo-elements, having display: none or no
   // 'content' property is equivalent to not having the pseudo-element
   // at all.
-  bool isBeforeOrAfter =
-      aType == PseudoStyleType::before || aType == PseudoStyleType::after;
-  if (isBeforeOrAfter) {
-    const nsStyleDisplay* display = computedValues->StyleDisplay();
-    const nsStyleContent* content = computedValues->StyleContent();
-    if (display->mDisplay == StyleDisplay::None ||
-        content->ContentCount() == 0) {
-      return nullptr;
+  if (type == PseudoStyleType::before || type == PseudoStyleType::after) {
+    if (aPseudoStyle.StyleDisplay()->mDisplay == StyleDisplay::None) {
+      return false;
+    }
+    if (!aPseudoStyle.StyleContent()->ContentCount()) {
+      return false;
     }
   }
 
-  return computedValues.forget();
+  return true;
 }
 
 bool ServoStyleSet::StyleDocument(ServoTraversalFlags aFlags) {
   AUTO_PROFILER_LABEL_CATEGORY_PAIR(LAYOUT_StyleComputation);
   MOZ_ASSERT(GetPresContext(), "Styling a document without a shell?");
 
   if (!mDocument->GetServoRestyleRoot()) {
     return false;
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -164,24 +164,41 @@ class ServoStyleSet {
   // The returned ComputedStyle will have nsCSSAnonBoxes::oofPlaceholder() as
   // its pseudo.
   //
   // (Perhaps nsCSSAnonBoxes::oofPaceholder() should go away and we shouldn't
   // even create ComputedStyle for placeholders.  However, not doing any rule
   // matching for them is a first step.)
   already_AddRefed<ComputedStyle> ResolveStyleForPlaceholder();
 
-  // Get a ComputedStyle for a pseudo-element.  aParentElement must be
-  // non-null.  aPseudoID is the PseudoStyleType for the
-  // pseudo-element.  aPseudoElement must be non-null if the pseudo-element
-  // type is one that allows user action pseudo-classes after it or allows
-  // style attributes; otherwise, it is ignored.
+  // Returns whether a given pseudo-element should exist or not.
+  static bool GeneratedContentPseudoExists(const ComputedStyle& aParentStyle,
+                                           const ComputedStyle& aPseudoStyle);
+
+  enum class IsProbe {
+    No,
+    Yes,
+  };
+
+  // Get a style for a pseudo-element.
+  //
+  // If IsProbe is Yes, then no style is returned if there are no rules matching
+  // for the pseudo-element, or GeneratedContentPseudoExists returns false.
+  //
+  // If IsProbe is No, then the style is guaranteed to be non-null.
   already_AddRefed<ComputedStyle> ResolvePseudoElementStyle(
-      dom::Element* aOriginatingElement, PseudoStyleType aType,
-      ComputedStyle* aParentStyle, dom::Element* aPseudoElement);
+      const dom::Element& aOriginatingElement, PseudoStyleType,
+      ComputedStyle* aParentStyle, IsProbe = IsProbe::No);
+
+  already_AddRefed<ComputedStyle> ProbePseudoElementStyle(
+      const dom::Element& aOriginatingElement, PseudoStyleType aType,
+      ComputedStyle* aParentStyle) {
+    return ResolvePseudoElementStyle(aOriginatingElement, aType, aParentStyle,
+                                     IsProbe::Yes);
+  }
 
   // Resolves style for a (possibly-pseudo) Element without assuming that the
   // style has been resolved. If the element was unstyled and a new style
   // was resolved, it is not stored in the DOM. (That is, the element remains
   // unstyled.)
   //
   // TODO(emilio): Element argument should be `const`.
   already_AddRefed<ComputedStyle> ResolveStyleLazily(
@@ -215,21 +232,16 @@ class ServoStyleSet {
   void AppendAllNonDocumentAuthorSheets(nsTArray<StyleSheet*>& aArray) const;
 
   void RemoveDocStyleSheet(StyleSheet* aSheet) {
     RemoveStyleSheet(StyleOrigin::Author, aSheet);
   }
 
   void AddDocStyleSheet(StyleSheet* aSheet);
 
-  // check whether there is ::before/::after style for an element
-  already_AddRefed<ComputedStyle> ProbePseudoElementStyle(
-      const dom::Element& aOriginatingElement, PseudoStyleType aType,
-      ComputedStyle* aParentStyle);
-
   /**
    * Performs a Servo traversal to compute style for all dirty nodes in the
    * document.
    *
    * This will traverse all of the document's style roots (that is, its document
    * element, and the roots of the document-level native anonymous content).
    *
    * We specify |ForCSSRuleChanges| to try to update all CSS animations
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -371,21 +371,16 @@ const KTableEntry nsCSSProps::kTextDecor
 const KTableEntry nsCSSProps::kTextEmphasisStyleShapeKTable[] = {
     {eCSSKeyword_dot, NS_STYLE_TEXT_EMPHASIS_STYLE_DOT},
     {eCSSKeyword_circle, NS_STYLE_TEXT_EMPHASIS_STYLE_CIRCLE},
     {eCSSKeyword_double_circle, NS_STYLE_TEXT_EMPHASIS_STYLE_DOUBLE_CIRCLE},
     {eCSSKeyword_triangle, NS_STYLE_TEXT_EMPHASIS_STYLE_TRIANGLE},
     {eCSSKeyword_sesame, NS_STYLE_TEXT_EMPHASIS_STYLE_SESAME},
     {eCSSKeyword_UNKNOWN, -1}};
 
-const KTableEntry nsCSSProps::kTextOverflowKTable[] = {
-    {eCSSKeyword_clip, NS_STYLE_TEXT_OVERFLOW_CLIP},
-    {eCSSKeyword_ellipsis, NS_STYLE_TEXT_OVERFLOW_ELLIPSIS},
-    {eCSSKeyword_UNKNOWN, -1}};
-
 // keyword tables for SVG properties
 
 const KTableEntry nsCSSProps::kFilterFunctionKTable[] = {
     {eCSSKeyword_blur, NS_STYLE_FILTER_BLUR},
     {eCSSKeyword_brightness, NS_STYLE_FILTER_BRIGHTNESS},
     {eCSSKeyword_contrast, NS_STYLE_FILTER_CONTRAST},
     {eCSSKeyword_grayscale, NS_STYLE_FILTER_GRAYSCALE},
     {eCSSKeyword_invert, NS_STYLE_FILTER_INVERT},
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -305,17 +305,16 @@ class nsCSSProps {
   // clang-format on
   static const KTableEntry kFontSmoothingKTable[];
   static const KTableEntry kGridAutoFlowKTable[];
   static const KTableEntry kGridTrackBreadthKTable[];
   static const KTableEntry kLineHeightKTable[];
   static const KTableEntry kTextAlignKTable[];
   static const KTableEntry kTextDecorationStyleKTable[];
   static const KTableEntry kTextEmphasisStyleShapeKTable[];
-  static const KTableEntry kTextOverflowKTable[];
 };
 
 // MOZ_DBG support for nsCSSPropertyID
 
 inline std::ostream& operator<<(std::ostream& aOut, nsCSSPropertyID aProperty) {
   return aOut << nsCSSProps::GetStringValue(aProperty);
 }
 
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -546,21 +546,16 @@ enum class StyleGridTrackBreadth : uint8
   0  // not in CSS spec, mapped to -moz-none
 #define NS_STYLE_TEXT_DECORATION_STYLE_DOTTED 1
 #define NS_STYLE_TEXT_DECORATION_STYLE_DASHED 2
 #define NS_STYLE_TEXT_DECORATION_STYLE_SOLID 3
 #define NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE 4
 #define NS_STYLE_TEXT_DECORATION_STYLE_WAVY 5
 #define NS_STYLE_TEXT_DECORATION_STYLE_MAX NS_STYLE_TEXT_DECORATION_STYLE_WAVY
 
-// See nsStyleTextOverflow
-#define NS_STYLE_TEXT_OVERFLOW_CLIP 0
-#define NS_STYLE_TEXT_OVERFLOW_ELLIPSIS 1
-#define NS_STYLE_TEXT_OVERFLOW_STRING 2
-
 // See nsStyleText
 #define NS_STYLE_TEXT_TRANSFORM_NONE 0
 #define NS_STYLE_TEXT_TRANSFORM_CAPITALIZE 1
 #define NS_STYLE_TEXT_TRANSFORM_LOWERCASE 2
 #define NS_STYLE_TEXT_TRANSFORM_UPPERCASE 3
 #define NS_STYLE_TEXT_TRANSFORM_FULL_WIDTH 4
 #define NS_STYLE_TEXT_TRANSFORM_FULL_SIZE_KANA 5
 
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2951,16 +2951,17 @@ nsStyleDisplay::nsStyleDisplay(const Doc
       mScrollSnapType(
           {StyleScrollSnapAxis::Both, StyleScrollSnapStrictness::None}),
       mLineClamp(0),
       mBackfaceVisibility(NS_STYLE_BACKFACE_VISIBILITY_VISIBLE),
       mTransformStyle(NS_STYLE_TRANSFORM_STYLE_FLAT),
       mTransformBox(StyleGeometryBox::BorderBox),
       mOffsetPath(StyleOffsetPath::None()),
       mOffsetDistance(LengthPercentage::Zero()),
+      mOffsetRotate{true, StyleAngle{0.0}},
       mTransformOrigin{LengthPercentage::FromPercentage(0.5),
                        LengthPercentage::FromPercentage(0.5),
                        {0.}},
       mChildPerspective(StylePerspective::None()),
       mPerspectiveOrigin(Position::FromPercentage(0.5f)),
       mVerticalAlign(
           StyleVerticalAlign::Keyword(StyleVerticalAlignKeyword::Baseline)),
       mShapeMargin(LengthPercentage::Zero()) {
@@ -3016,16 +3017,17 @@ nsStyleDisplay::nsStyleDisplay(const nsS
       mRotate(aSource.mRotate),
       mTranslate(aSource.mTranslate),
       mScale(aSource.mScale),
       mBackfaceVisibility(aSource.mBackfaceVisibility),
       mTransformStyle(aSource.mTransformStyle),
       mTransformBox(aSource.mTransformBox),
       mOffsetPath(aSource.mOffsetPath),
       mOffsetDistance(aSource.mOffsetDistance),
+      mOffsetRotate(aSource.mOffsetRotate),
       mTransformOrigin(aSource.mTransformOrigin),
       mChildPerspective(aSource.mChildPerspective),
       mPerspectiveOrigin(aSource.mPerspectiveOrigin),
       mVerticalAlign(aSource.mVerticalAlign),
       mShapeImageThreshold(aSource.mShapeImageThreshold),
       mShapeMargin(aSource.mShapeMargin),
       mShapeOutside(aSource.mShapeOutside) {
   MOZ_COUNT_CTOR(nsStyleDisplay);
@@ -3056,35 +3058,34 @@ static inline nsChangeHint CompareTransf
       result |= nsChangeHint_UpdateOverflow;
     }
   }
 
   return result;
 }
 
 static inline nsChangeHint CompareMotionValues(
-    const StyleOffsetPath& aOffsetPath, const LengthPercentage& aOffsetDistance,
-    const StyleOffsetPath& aNewOffsetPath,
-    const LengthPercentage& aNewOffsetDistance) {
-  if (aOffsetPath == aNewOffsetPath) {
-    if (aOffsetDistance == aNewOffsetDistance) {
+    const nsStyleDisplay& aDisplay, const nsStyleDisplay& aNewDisplay) {
+  if (aDisplay.mOffsetPath == aNewDisplay.mOffsetPath) {
+    if (aDisplay.mOffsetDistance == aNewDisplay.mOffsetDistance &&
+        aDisplay.mOffsetRotate == aNewDisplay.mOffsetRotate) {
       return nsChangeHint(0);
     }
 
-    if (aOffsetPath.IsNone()) {
+    if (aDisplay.mOffsetPath.IsNone()) {
       return nsChangeHint_NeutralChange;
     }
   }
 
   // TODO: Bug 1482737: This probably doesn't need to UpdateOverflow
   // (or UpdateTransformLayer) if there's already a transform.
   // Set the same hints as what we use for transform because motion path is
   // a kind of transform and will be combined with other transforms.
   nsChangeHint result = nsChangeHint_UpdateTransformLayer;
-  if (!aOffsetPath.IsNone() && !aNewOffsetPath.IsNone()) {
+  if (!aDisplay.mOffsetPath.IsNone() && !aNewDisplay.mOffsetPath.IsNone()) {
     result |= nsChangeHint_UpdatePostTransformOverflow;
   } else {
     result |= nsChangeHint_UpdateOverflow;
   }
   return result;
 }
 
 nsChangeHint nsStyleDisplay::CalcDifference(
@@ -3203,19 +3204,17 @@ nsChangeHint nsStyleDisplay::CalcDiffere
      * nsChangeHint_NeutralChange.
      */
     nsChangeHint transformHint = nsChangeHint(0);
 
     transformHint |= CompareTransformValues(mTransform, aNewData.mTransform);
     transformHint |= CompareTransformValues(mRotate, aNewData.mRotate);
     transformHint |= CompareTransformValues(mTranslate, aNewData.mTranslate);
     transformHint |= CompareTransformValues(mScale, aNewData.mScale);
-    transformHint |=
-        CompareMotionValues(mOffsetPath, mOffsetDistance, aNewData.mOffsetPath,
-                            aNewData.mOffsetDistance);
+    transformHint |= CompareMotionValues(*this, aNewData);
 
     if (mTransformOrigin != aNewData.mTransformOrigin) {
       transformHint |= nsChangeHint_UpdateTransformLayer |
                        nsChangeHint_UpdatePostTransformOverflow;
     }
 
     if (mPerspectiveOrigin != aNewData.mPerspectiveOrigin ||
         mTransformStyle != aNewData.mTransformStyle ||
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1242,79 +1242,16 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
 
   template <typename SizeOrMaxSize>
   static bool BSizeCoordDependsOnContainer(const SizeOrMaxSize& aCoord) {
     return aCoord.IsLengthPercentage() &&
            aCoord.AsLengthPercentage().HasPercent();
   }
 };
 
-struct nsStyleTextOverflowSide {
-  nsStyleTextOverflowSide() : mType(NS_STYLE_TEXT_OVERFLOW_CLIP) {}
-
-  static nsStyleTextOverflowSide Ellipsis() {
-    nsStyleTextOverflowSide side;
-    side.mType = NS_STYLE_TEXT_OVERFLOW_ELLIPSIS;
-    return side;
-  }
-
-  bool operator==(const nsStyleTextOverflowSide& aOther) const {
-    return mType == aOther.mType && (mType != NS_STYLE_TEXT_OVERFLOW_STRING ||
-                                     mString == aOther.mString);
-  }
-  bool operator!=(const nsStyleTextOverflowSide& aOther) const {
-    return !(*this == aOther);
-  }
-
-  nsString mString;
-  uint8_t mType;
-};
-
-struct nsStyleTextOverflow {
-  nsStyleTextOverflow() : mLogicalDirections(true) {}
-  bool operator==(const nsStyleTextOverflow& aOther) const {
-    return mLeft == aOther.mLeft && mRight == aOther.mRight;
-  }
-  bool operator!=(const nsStyleTextOverflow& aOther) const {
-    return !(*this == aOther);
-  }
-
-  // Returns the value to apply on the left side.
-  const nsStyleTextOverflowSide& GetLeft(uint8_t aDirection) const {
-    NS_ASSERTION(aDirection == NS_STYLE_DIRECTION_LTR ||
-                     aDirection == NS_STYLE_DIRECTION_RTL,
-                 "bad direction");
-    return !mLogicalDirections || aDirection == NS_STYLE_DIRECTION_LTR ? mLeft
-                                                                       : mRight;
-  }
-
-  // Returns the value to apply on the right side.
-  const nsStyleTextOverflowSide& GetRight(uint8_t aDirection) const {
-    NS_ASSERTION(aDirection == NS_STYLE_DIRECTION_LTR ||
-                     aDirection == NS_STYLE_DIRECTION_RTL,
-                 "bad direction");
-    return !mLogicalDirections || aDirection == NS_STYLE_DIRECTION_LTR ? mRight
-                                                                       : mLeft;
-  }
-
-  // Returns the first value that was specified.
-  const nsStyleTextOverflowSide* GetFirstValue() const {
-    return mLogicalDirections ? &mRight : &mLeft;
-  }
-
-  // Returns the second value, or null if there was only one value specified.
-  const nsStyleTextOverflowSide* GetSecondValue() const {
-    return mLogicalDirections ? nullptr : &mRight;
-  }
-
-  nsStyleTextOverflowSide mLeft;   // start side when mLogicalDirections is true
-  nsStyleTextOverflowSide mRight;  // end side when mLogicalDirections is true
-  bool mLogicalDirections;         // true when only one value was specified
-};
-
 struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleTextReset {
   explicit nsStyleTextReset(const mozilla::dom::Document&);
   nsStyleTextReset(const nsStyleTextReset& aOther);
   ~nsStyleTextReset();
   void TriggerImageLoads(mozilla::dom::Document&, const nsStyleTextReset*) {}
   const static bool kHasTriggerImageLoads = false;
 
   // Note the difference between this and
@@ -1322,17 +1259,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   bool HasTextDecorationLines() const {
     return mTextDecorationLine != mozilla::StyleTextDecorationLine_NONE &&
            mTextDecorationLine !=
                mozilla::StyleTextDecorationLine_COLOR_OVERRIDE;
   }
 
   nsChangeHint CalcDifference(const nsStyleTextReset& aNewData) const;
 
-  nsStyleTextOverflow mTextOverflow;  // enum, string
+  mozilla::StyleTextOverflow mTextOverflow;
 
   mozilla::StyleTextDecorationLine mTextDecorationLine;
   uint8_t mTextDecorationStyle;  // NS_STYLE_TEXT_DECORATION_STYLE_*
   uint8_t mUnicodeBidi;          // NS_STYLE_UNICODE_BIDI_*
   nscoord mInitialLetterSink;    // 0 means normal
   float mInitialLetterSize;      // 0.0f means normal
   mozilla::StyleColor mTextDecorationColor;
 };
@@ -1732,16 +1669,17 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   mozilla::StyleScale mScale;
 
   uint8_t mBackfaceVisibility;
   uint8_t mTransformStyle;
   StyleGeometryBox mTransformBox;
 
   mozilla::StyleOffsetPath mOffsetPath;
   mozilla::LengthPercentage mOffsetDistance;
+  mozilla::StyleOffsetRotate mOffsetRotate;
 
   mozilla::StyleTransformOrigin mTransformOrigin;
   mozilla::StylePerspective mChildPerspective;
   mozilla::Position mPerspectiveOrigin;
 
   mozilla::StyleVerticalAlign mVerticalAlign;
 
   nsCSSPropertyID GetTransitionProperty(uint32_t aIndex) const {
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -8742,16 +8742,25 @@ if (IsCSSPropertyPrefEnabled("layout.css
   gCSSProperties["offset-distance"] = {
     domProp: "offsetDistance",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "0" ],
     other_values: [ "10px", "10%", "190%", "-280%", "calc(30px + 40%)" ],
     invalid_values: [ "none", "45deg" ]
   };
+
+  gCSSProperties["offset-rotate"] = {
+    domProp: "offsetRotate",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "auto" ],
+    other_values: [ "reverse", "0deg", "0rad reverse", "-45deg", "5turn auto" ],
+    invalid_values: [ "none", "10px", "reverse 0deg reverse", "reverse auto" ]
+  };
 }
 
 if (IsCSSPropertyPrefEnabled("layout.css.clip-path-path.enabled")) {
   gCSSProperties["clip-path"].other_values.push(
     "path(nonzero, 'M 10 10 h 100 v 100 h-100 v-100 z')",
     "path(evenodd, 'M 10 10 h 100 v 100 h-100 v-100 z')",
     "path('M10,30A20,20 0,0,1 50,30A20,20 0,0,1 90,30Q90,60 50,90Q10,60 10,30z')",
   );
--- a/layout/tables/nsTableWrapperFrame.cpp
+++ b/layout/tables/nsTableWrapperFrame.cpp
@@ -26,16 +26,22 @@
 using namespace mozilla;
 using namespace mozilla::layout;
 
 #define NO_SIDE 100
 
 /* virtual */
 nscoord nsTableWrapperFrame::GetLogicalBaseline(
     WritingMode aWritingMode) const {
+  if (StyleDisplay()->IsContainLayout()) {
+    // We have no baseline. Fall back to the inherited impl which is
+    // appropriate for this situation.
+    return nsContainerFrame::GetLogicalBaseline(aWritingMode);
+  }
+
   nsIFrame* kid = mFrames.FirstChild();
   if (!kid) {
     MOZ_ASSERT_UNREACHABLE("no inner table");
     return nsContainerFrame::GetLogicalBaseline(aWritingMode);
   }
 
   return kid->GetLogicalBaseline(aWritingMode) +
          kid->BStart(aWritingMode, mRect.Size());
--- a/layout/tables/nsTableWrapperFrame.h
+++ b/layout/tables/nsTableWrapperFrame.h
@@ -65,16 +65,19 @@ class nsTableWrapperFrame : public nsCon
                                      const nsDisplayListSet& aLists);
 
   virtual nscoord GetLogicalBaseline(
       mozilla::WritingMode aWritingMode) const override;
 
   bool GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
                                  BaselineSharingGroup aBaselineGroup,
                                  nscoord* aBaseline) const override {
+    if (StyleDisplay()->IsContainLayout()) {
+      return false;
+    }
     auto innerTable = InnerTableFrame();
     nscoord offset;
     if (innerTable->GetNaturalBaselineBOffset(aWM, aBaselineGroup, &offset)) {
       auto bStart = innerTable->BStart(aWM, mRect.Size());
       if (aBaselineGroup == BaselineSharingGroup::First) {
         *aBaseline = offset + bStart;
       } else {
         auto bEnd = bStart + innerTable->BSize(aWM);
--- a/mobile/android/config/mozconfigs/android-aarch64/profile-use
+++ b/mobile/android/config/mozconfigs/android-aarch64/profile-use
@@ -2,9 +2,9 @@
 
 export LLVM_PROFDATA="$topsrcdir/clang/bin/llvm-profdata"
 
 ac_add_options --enable-profile-use
 
 # This is disabled because jarlog re-ordering breaks apk publishing tasks,
 # see bug 1539933.
 # ac_add_options --with-pgo-jarlog=/builds/worker/fetches/en-US.log
-ac_add_options --with-pgo-profile-path=/builds/worker/fetches/default.profraw
+ac_add_options --with-pgo-profile-path=/builds/worker/fetches
--- a/mobile/android/config/mozconfigs/android-api-16/profile-use
+++ b/mobile/android/config/mozconfigs/android-api-16/profile-use
@@ -3,9 +3,9 @@
 export LLVM_PROFDATA="$topsrcdir/clang/bin/llvm-profdata"
 
 ac_add_options --enable-profile-use
 
 # This is disabled because jarlog re-ordering breaks apk publishing tasks,
 # see bug 1539933.
 # ac_add_options --with-pgo-jarlog=/builds/worker/fetches/en-US.log
 
-ac_add_options --with-pgo-profile-path=/builds/worker/fetches/default.profraw
+ac_add_options --with-pgo-profile-path=/builds/worker/fetches
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1658,17 +1658,17 @@ VARCACHE_PREF(
   RelaxedAtomicInt32, 5000
 )
 
 #if defined(XP_LINUX) && !defined(ANDROID)
 # define PREF_VALUE true
 #elif defined(XP_WIN) && !defined(_ARM64_)
 # define PREF_VALUE false
 #elif defined(XP_MACOSX)
-# define PREF_VALUE false
+# define PREF_VALUE true
 #else
 # define PREF_VALUE false
 #endif
 VARCACHE_PREF(
   "media.rdd-vorbis.enabled",
    MediaRddVorbisEnabled,
   RelaxedAtomicBool, PREF_VALUE
 )
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -398,16 +398,17 @@ pref("media.decoder-doctor.new-issue-end
 pref("media.videocontrols.picture-in-picture.enabled", true);
 pref("media.videocontrols.picture-in-picture.video-toggle.enabled", true);
 #else
 pref("media.videocontrols.picture-in-picture.enabled", false);
 pref("media.videocontrols.picture-in-picture.video-toggle.enabled", false);
 #endif
 pref("media.videocontrols.picture-in-picture.video-toggle.flyout-enabled", false);
 pref("media.videocontrols.picture-in-picture.video-toggle.flyout-wait-ms", 5000);
+pref("media.videocontrols.picture-in-picture.video-toggle.always-show", false);
 
 #ifdef MOZ_WEBRTC
 pref("media.navigator.video.enabled", true);
 pref("media.navigator.video.default_fps",30);
 pref("media.navigator.video.use_remb", true);
 pref("media.navigator.video.use_tmmbr", false);
 pref("media.navigator.audio.use_fec", true);
 pref("media.navigator.video.red_ulpfec_enabled", false);
--- a/python/mozbuild/mozbuild/vendor_rust.py
+++ b/python/mozbuild/mozbuild/vendor_rust.py
@@ -140,21 +140,21 @@ Please commit or stash these changes bef
     # solely by a build peer; any additions must be checked by somebody
     # competent to review licensing minutiae.
 
     # Licenses for code used at runtime. Please see the above comment before
     # adding anything to this list.
     RUNTIME_LICENSE_WHITELIST = [
         'Apache-2.0',
         'Apache-2.0 WITH LLVM-exception',
-        'BSD-2-Clause',
-        # BSD-3-Clause is ok, but packages using it must be added to the
-        # appropriate section of about:licenses. To encourage people to remember
-        # to do that, we do not whitelist the license itself and we require the
-        # packages to be added to RUNTIME_LICENSE_PACKAGE_WHITELIST below.
+        # BSD-2-Clause and BSD-3-Clause are ok, but packages using them
+        # must be added to the appropriate section of about:licenses.
+        # To encourage people to remember to do that, we do not whitelist
+        # the licenses themselves, and we require the packages to be added
+        # to RUNTIME_LICENSE_PACKAGE_WHITELIST below.
         'CC0-1.0',
         'ISC',
         'MIT',
         'MPL-2.0',
         'Unlicense',
     ]
 
     # Licenses for code used at build time (e.g. code generators). Please see the above
@@ -167,16 +167,20 @@ Please commit or stash these changes bef
             'fuchsia-zircon-sys',
             'fuchsia-cprng',
         ]
     }
 
     # This whitelist should only be used for packages that use an acceptable
     # license, but that also need to explicitly mentioned in about:license.
     RUNTIME_LICENSE_PACKAGE_WHITELIST = {
+        'BSD-2-Clause': [
+            'arrayref',
+            'Inflector',
+        ],
         'BSD-3-Clause': [
         ]
     }
 
     # This whitelist should only be used for packages that use a
     # license-file and for which the license-file entry has been
     # reviewed.  The table is keyed by package names and maps to the
     # sha256 hash of the license file that we reviewed.
--- a/security/manager/ssl/RemoteSecuritySettings.jsm
+++ b/security/manager/ssl/RemoteSecuritySettings.jsm
@@ -355,17 +355,17 @@ this.RemoteSecuritySettings = class Remo
     }
 
     async maybeSync(expectedTimestamp, options) {
       return this.client.maybeSync(expectedTimestamp, options);
     }
 
     async removeCerts(recordsToRemove) {
       let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
-      let hashes = recordsToRemove.map(record => record.pubKeyHash);
+      let hashes = recordsToRemove.map(record => record.derHash);
       let result = await new Promise((resolve) => {
           certStorage.removeCertsByHashes(hashes, resolve);
       }).catch((err) => err);
       if (result != Cr.NS_OK) {
         Cu.reportError(`Failed to remove some intermediate certificates`);
         Services.telemetry.getHistogramById(INTERMEDIATES_ERRORS_TELEMETRY)
           .add("failedToRemove");
       }
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -98,16 +98,37 @@ const allCertificateUsages = {
   certificateUsageSSLServer,
   certificateUsageSSLCA,
   certificateUsageEmailSigner,
   certificateUsageEmailRecipient,
 };
 
 const NO_FLAGS = 0;
 
+// Convert a string to an array of bytes consisting of the char code at each
+// index.
+function stringToArray(s) {
+  let a = [];
+  for (let i = 0; i < s.length; i++) {
+    a.push(s.charCodeAt(i));
+  }
+  return a;
+}
+
+// Converts an array of bytes to a JS string using fromCharCode on each byte.
+function arrayToString(a) {
+  let s = "";
+  for (let b of a) {
+    s += String.fromCharCode(b);
+  }
+  return s;
+}
+
+
+
 // Commonly certificates are represented as PEM. The format is roughly as
 // follows:
 //
 // -----BEGIN CERTIFICATE-----
 // [some lines of base64, each typically 64 characters long]
 // -----END CERTIFICATE-----
 //
 // However, nsIX509CertDB.constructX509FromBase64 and related functions do not
--- a/security/manager/ssl/tests/unit/test_cert_storage_direct.js
+++ b/security/manager/ssl/tests/unit/test_cert_storage_direct.js
@@ -22,32 +22,16 @@ async function addCerts(certInfos) {
 
 async function removeCertsByHashes(hashesBase64) {
   let result = await new Promise((resolve) => {
     certStorage.removeCertsByHashes(hashesBase64, resolve);
   });
   Assert.equal(result, Cr.NS_OK, "removeCertsByHashes should succeed");
 }
 
-function stringToArray(s) {
-  let a = [];
-  for (let i = 0; i < s.length; i++) {
-    a.push(s.charCodeAt(i));
-  }
-  return a;
-}
-
-function arrayToString(a) {
-  let s = "";
-  for (let b of a) {
-    s += String.fromCharCode(b);
-  }
-  return s;
-}
-
 function getLongString(uniquePart, length) {
   return String(uniquePart).padStart(length, "0");
 }
 
 class CertInfo {
   constructor(cert, subject) {
     this.cert = btoa(cert);
     this.subject = btoa(subject);
--- a/security/manager/ssl/tests/unit/test_intermediate_preloads.js
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads.js
@@ -5,16 +5,17 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 "use strict";
 do_get_profile(); // must be called before getting nsIX509CertDB
 
 const {RemoteSettings} = ChromeUtils.import("resource://services-settings/remote-settings.js");
 const {TestUtils} = ChromeUtils.import("resource://testing-common/TestUtils.jsm");
 const {TelemetryTestUtils} = ChromeUtils.import("resource://testing-common/TelemetryTestUtils.jsm");
+const {X509} = ChromeUtils.import("resource://gre/modules/psm/X509.jsm");
 
 let remoteSecSetting;
 if (AppConstants.MOZ_NEW_CERT_STORAGE) {
   const {RemoteSecuritySettings} = ChromeUtils.import("resource://gre/modules/psm/RemoteSecuritySettings.jsm");
   remoteSecSetting = new RemoteSecuritySettings();
 }
 
 let server;
@@ -51,16 +52,23 @@ function countTelemetryReports(histogram
 function clearTelemetry() {
   Services.telemetry.getHistogramById("INTERMEDIATE_PRELOADING_ERRORS")
     .clear();
   Services.telemetry.getHistogramById("INTERMEDIATE_PRELOADING_UPDATE_TIME_MS")
     .clear();
   Services.telemetry.clearScalars();
 }
 
+function getSubjectBytes(certDERString) {
+  let bytes = stringToArray(certDERString);
+  let cert = new X509.Certificate();
+  cert.parse(bytes);
+  return btoa(arrayToString(cert.tbsCertificate.subject._der._bytes));
+}
+
 /**
  * Simulate a Remote Settings synchronization by filling up the
  * local data with fake records.
  *
  * @param {*} filenames List of pem files for which we will create
  *                      records.
  * @param {*} options Options for records to generate.
  */
@@ -75,35 +83,37 @@ async function syncAndDownload(filenames
   if (clear) {
     await localDB.clear();
   }
 
   let count = 1;
   for (const filename of filenames) {
     const file = do_get_file(`test_intermediate_preloads/${filename}`);
     const certBytes = readFile(file);
+    const certDERBytes = atob(pemToBase64(certBytes));
 
     const record = {
       "details": {
         "who": "",
         "why": "",
         "name": "",
         "created": "",
       },
+      "derHash": getHashCommon(certDERBytes, true),
       "subject": "",
+      "subjectDN": getSubjectBytes(certDERBytes),
       "attachment": {
         "hash": hashFunc(certBytes),
         "size": lengthFunc(certBytes),
         "filename": `intermediate certificate #${count}.pem`,
         "location": `security-state-workspace/intermediates/${filename}`,
         "mimetype": "application/x-pem-file",
       },
       "whitelist": false,
-      // "pubKeyHash" is actually just the hash of the DER bytes of the certificate
-      "pubKeyHash": getHashCommon(atob(pemToBase64(certBytes)), true),
+      "pubKeyHash": "", // not used yet
       "crlite_enrolled": true,
     };
 
     await localDB.create(record);
     count++;
   }
   // This promise will wait for the end of downloading.
   const updatedPromise = TestUtils.topicObserved("remote-security-settings:intermediates-updated");
@@ -305,16 +315,39 @@ add_task({
 
   // Re-run
   result = await syncAndDownload([], { clear: false });
   equal(result, "success", "Preloading update should have run");
 
   equal((await locallyDownloaded()).length, 200, "There should have been 200 downloaded");
 });
 
+add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, async function test_delete() {
+  Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
+  Services.prefs.setIntPref(INTERMEDIATES_DL_PER_POLL_PREF, 100);
+
+  let syncResult = await syncAndDownload(["int.pem", "int2.pem"]);
+  equal(syncResult, "success", "Preloading update should have run");
+
+  equal((await locallyDownloaded()).length, 2, "There should have been 2 downloads");
+
+  let localDB = await remoteSecSetting.client.openCollection();
+  let { data } = await localDB.list();
+  ok(data.length > 0, "should have some entries");
+  let subject = data[0].subjectDN;
+  let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
+  let resultsBefore = certStorage.findCertsBySubject(stringToArray(atob(subject)));
+  equal(resultsBefore.length, 1, "should find the intermediate in cert storage before");
+  // simulate a sync where we deleted the entry
+  await remoteSecSetting.client.emit("sync", { "data": { deleted: [ data[0] ] } });
+  let resultsAfter = certStorage.findCertsBySubject(stringToArray(atob(subject)));
+  equal(resultsAfter.length, 0, "shouldn't find intermediate in cert storage now");
+});
 
 function run_test() {
   server = new HttpServer();
   server.start(-1);
   registerCleanupFunction(() => server.stop(() => { }));
 
   server.registerDirectory("/cdn/security-state-workspace/intermediates/", do_get_file("test_intermediate_preloads"));
 
--- a/servo/components/selectors/builder.rs
+++ b/servo/components/selectors/builder.rs
@@ -79,22 +79,16 @@ impl<Impl: SelectorImpl> SelectorBuilder
     /// Completes the current compound selector and starts a new one, delimited
     /// by the given combinator.
     #[inline(always)]
     pub fn push_combinator(&mut self, c: Combinator) {
         self.combinators.push((c, self.current_len));
         self.current_len = 0;
     }
 
-    /// Returns true if no simple selectors have ever been pushed to this builder.
-    #[inline(always)]
-    pub fn is_empty(&self) -> bool {
-        self.simple_selectors.is_empty()
-    }
-
     /// Returns true if combinators have ever been pushed to this builder.
     #[inline(always)]
     pub fn has_combinators(&self) -> bool {
         !self.combinators.is_empty()
     }
 
     /// Consumes the builder, producing a Selector.
     #[inline(always)]
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -326,21 +326,19 @@ where
         if let Some(s) = iter.next() {
             debug_assert!(
                 matches!(*s, Component::NonTSPseudoClass(..)),
                 "Someone messed up pseudo-element parsing"
             );
             return false;
         }
 
-        // Advance to the non-pseudo-element part of the selector, but let the
-        // context note that .
-        if iter.next_sequence().is_none() {
-            return true;
-        }
+        // Advance to the non-pseudo-element part of the selector.
+        let next_sequence = iter.next_sequence().unwrap();
+        debug_assert_eq!(next_sequence, Combinator::PseudoElement);
     }
 
     let result =
         matches_complex_selector_internal(iter, element, context, flags_setter, Rightmost::Yes);
 
     match result {
         SelectorMatchingResult::Matched => true,
         _ => false,
@@ -447,20 +445,16 @@ where
             if !selector.clone().is_featureless_host_selector() {
                 return None;
             }
 
             element.containing_shadow_host()
         },
         Combinator::Part => element.containing_shadow_host(),
         Combinator::SlotAssignment => {
-            debug_assert!(
-                context.current_host.is_some(),
-                "Should not be trying to match slotted rules in a non-shadow-tree context"
-            );
             debug_assert!(element
                 .assigned_slot()
                 .map_or(true, |s| s.is_html_slot_element()));
             let scope = context.current_host?;
             let mut current_slot = element.assigned_slot()?;
             while current_slot.containing_shadow_host().unwrap().opaque() != scope {
                 current_slot = current_slot.assigned_slot()?;
             }
@@ -672,17 +666,16 @@ where
     debug_assert!(context.shared.is_nested() || !context.shared.in_negation());
 
     match *selector {
         Component::Combinator(_) => unreachable!(),
         Component::Part(ref part) => element.is_part(part),
         Component::Slotted(ref selector) => {
             // <slots> are never flattened tree slottables.
             !element.is_html_slot_element() &&
-                element.assigned_slot().is_some() &&
                 context.shared.nest(|context| {
                     matches_complex_selector(selector.iter(), element, context, flags_setter)
                 })
         },
         Component::PseudoElement(ref pseudo) => {
             element.match_pseudo_element(pseudo, context.shared)
         },
         Component::LocalName(ref local_name) => matches_local_name(element, local_name),
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -1997,29 +1997,25 @@ where
             },
             SimpleSelectorParseResult::PartPseudo(part_name) => {
                 state.insert(SelectorParsingState::AFTER_PART);
                 builder.push_combinator(Combinator::Part);
                 builder.push_simple_selector(Component::Part(part_name));
             },
             SimpleSelectorParseResult::SlottedPseudo(selector) => {
                 state.insert(SelectorParsingState::AFTER_SLOTTED);
-                if !builder.is_empty() {
-                    builder.push_combinator(Combinator::SlotAssignment);
-                }
+                builder.push_combinator(Combinator::SlotAssignment);
                 builder.push_simple_selector(Component::Slotted(selector));
             },
             SimpleSelectorParseResult::PseudoElement(p) => {
                 state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
                 if !p.accepts_state_pseudo_classes() {
                     state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT);
                 }
-                if !builder.is_empty() {
-                    builder.push_combinator(Combinator::PseudoElement);
-                }
+                builder.push_combinator(Combinator::PseudoElement);
                 builder.push_simple_selector(Component::PseudoElement(p));
             },
         }
     }
     if empty {
         // An empty selector is invalid.
         Ok(None)
     } else {
@@ -2823,34 +2819,39 @@ pub mod tests {
                 }],
                 specificity(0, 1, 0),
             )]))
         );
         // https://github.com/mozilla/servo/issues/1723
         assert_eq!(
             parse("::before"),
             Ok(SelectorList::from_vec(vec![Selector::from_vec(
-                vec![Component::PseudoElement(PseudoElement::Before)],
+                vec![
+                    Component::Combinator(Combinator::PseudoElement),
+                    Component::PseudoElement(PseudoElement::Before),
+                ],
                 specificity(0, 0, 1) | HAS_PSEUDO_BIT,
             )]))
         );
         assert_eq!(
             parse("::before:hover"),
             Ok(SelectorList::from_vec(vec![Selector::from_vec(
                 vec![
+                    Component::Combinator(Combinator::PseudoElement),
                     Component::PseudoElement(PseudoElement::Before),
                     Component::NonTSPseudoClass(PseudoClass::Hover),
                 ],
                 specificity(0, 1, 1) | HAS_PSEUDO_BIT,
             )]))
         );
         assert_eq!(
             parse("::before:hover:hover"),
             Ok(SelectorList::from_vec(vec![Selector::from_vec(
                 vec![
+                    Component::Combinator(Combinator::PseudoElement),
                     Component::PseudoElement(PseudoElement::Before),
                     Component::NonTSPseudoClass(PseudoClass::Hover),
                     Component::NonTSPseudoClass(PseudoClass::Hover),
                 ],
                 specificity(0, 2, 1) | HAS_PSEUDO_BIT,
             )]))
         );
         assert!(parse("::before:hover:lang(foo)").is_err());
@@ -2953,16 +2954,17 @@ pub mod tests {
                 vec![Component::Negation(
                     vec![Component::ExplicitUniversalType]
                         .into_boxed_slice()
                         .into(),
                 )],
                 specificity(0, 0, 0),
             )]))
         );
+
         assert_eq!(
             parse_ns(":not(svg|*)", &parser),
             Ok(SelectorList::from_vec(vec![Selector::from_vec(
                 vec![Component::Negation(
                     vec![
                         Component::Namespace(DummyAtom("svg".into()), SVG.into()),
                         Component::ExplicitUniversalType,
                     ]
@@ -3027,16 +3029,18 @@ pub mod tests {
         let selector = &parse("::before").unwrap().0[0];
         assert!(selector.is_universal());
         let mut iter = selector.iter();
         assert_eq!(
             iter.next(),
             Some(&Component::PseudoElement(PseudoElement::Before))
         );
         assert_eq!(iter.next(), None);
+        assert_eq!(iter.next_sequence(), Some(Combinator::PseudoElement));
+        assert_eq!(iter.next(), None);
         assert_eq!(iter.next_sequence(), None);
     }
 
     struct TestVisitor {
         seen: Vec<String>,
     }
 
     impl SelectorVisitor for TestVisitor {
--- a/servo/components/selectors/tree.rs
+++ b/servo/components/selectors/tree.rs
@@ -42,19 +42,23 @@ pub trait Element: Sized + Clone + Debug
     /// The host of the containing shadow root, if any.
     fn containing_shadow_host(&self) -> Option<Self>;
 
     /// The parent of a given pseudo-element, after matching a pseudo-element
     /// selector.
     ///
     /// This is guaranteed to be called in a pseudo-element.
     fn pseudo_element_originating_element(&self) -> Option<Self> {
+        debug_assert!(self.is_pseudo_element());
         self.parent_element()
     }
 
+    /// Whether we're matching on a pseudo-element.
+    fn is_pseudo_element(&self) -> bool;
+
     /// Skips non-element nodes
     fn prev_sibling_element(&self) -> Option<Self>;
 
     /// Skips non-element nodes
     fn next_sibling_element(&self) -> Option<Self>;
 
     fn is_html_element_in_html_document(&self) -> bool;
 
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -774,17 +774,17 @@ pub trait TElement:
 
     /// Return the element which we can use to look up rules in the selector
     /// maps.
     ///
     /// This is always the element itself, except in the case where we are an
     /// element-backed pseudo-element, in which case we return the originating
     /// element.
     fn rule_hash_target(&self) -> Self {
-        if self.implemented_pseudo_element().is_some() {
+        if self.is_pseudo_element() {
             self.pseudo_element_originating_element()
                 .expect("Trying to collect rules for a detached pseudo-element")
         } else {
             *self
         }
     }
 
     /// Executes the callback for each applicable style rule data which isn't
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -1093,17 +1093,17 @@ impl structs::FontSizePrefs {
 }
 
 impl<'le> TElement for GeckoElement<'le> {
     type ConcreteNode = GeckoNode<'le>;
     type FontMetricsProvider = GeckoFontMetricsProvider;
     type TraversalChildrenIterator = GeckoChildrenIterator<'le>;
 
     fn inheritance_parent(&self) -> Option<Self> {
-        if self.implemented_pseudo_element().is_some() {
+        if self.is_pseudo_element() {
             return self.pseudo_element_originating_element();
         }
 
         self.as_node()
             .flattened_tree_parent()
             .and_then(|n| n.as_element())
     }
 
@@ -1466,17 +1466,17 @@ impl<'le> TElement for GeckoElement<'le>
                 true
             });
         }
     }
 
     #[inline]
     fn skip_item_display_fixup(&self) -> bool {
         debug_assert!(
-            self.implemented_pseudo_element().is_none(),
+            !self.is_pseudo_element(),
             "Just don't call me if I'm a pseudo, you should know the answer already"
         );
         self.is_root_of_native_anonymous_subtree()
     }
 
     unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
         debug_assert!(!flags.is_empty());
         self.set_flags(selector_flags_to_node_flags(flags));
@@ -1914,18 +1914,23 @@ impl<'le> ::selectors::Element for Gecko
 
     #[inline]
     fn containing_shadow_host(&self) -> Option<Self> {
         let shadow = self.containing_shadow()?;
         Some(shadow.host())
     }
 
     #[inline]
+    fn is_pseudo_element(&self) -> bool {
+        self.implemented_pseudo_element().is_some()
+    }
+
+    #[inline]
     fn pseudo_element_originating_element(&self) -> Option<Self> {
-        debug_assert!(self.implemented_pseudo_element().is_some());
+        debug_assert!(self.is_pseudo_element());
         let parent = self.closest_anon_subtree_root_parent()?;
 
         // FIXME(emilio): Special-case for <input type="number">s
         // pseudo-elements, which are nested NAC. Probably nsNumberControlFrame
         // should instead inherit from nsTextControlFrame, and then this could
         // go away.
         if let Some(PseudoElement::MozNumberText) = parent.implemented_pseudo_element() {
             debug_assert_eq!(
--- a/servo/components/style/invalidation/element/element_wrapper.rs
+++ b/servo/components/style/invalidation/element/element_wrapper.rs
@@ -361,16 +361,20 @@ where
     fn is_empty(&self) -> bool {
         self.element.is_empty()
     }
 
     fn is_root(&self) -> bool {
         self.element.is_root()
     }
 
+    fn is_pseudo_element(&self) -> bool {
+        self.element.is_pseudo_element()
+    }
+
     fn pseudo_element_originating_element(&self) -> Option<Self> {
         self.element
             .pseudo_element_originating_element()
             .map(|e| ElementWrapper::new(e, self.snapshot_map))
     }
 
     fn assigned_slot(&self) -> Option<Self> {
         self.element
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -327,16 +327,17 @@ class Longhand(object):
                 "JustifySelf",
                 "LineBreak",
                 "MozForceBrokenImageIcon",
                 "MozListReversed",
                 "MozScriptLevel",
                 "MozScriptMinSize",
                 "MozScriptSizeMultiplier",
                 "NonNegativeNumber",
+                "OffsetRotate",
                 "Opacity",
                 "OutlineStyle",
                 "Overflow",
                 "OverflowAnchor",
                 "OverflowClipBox",
                 "OverflowWrap",
                 "OverscrollBehavior",
                 "Percentage",
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -3337,93 +3337,17 @@ fn static_assert() {
         TextEmphasisStyle::Keyword(TextEmphasisKeywordValue { fill, shape })
     }
 
     ${impl_non_negative_length('_webkit_text_stroke_width',
                                'mWebkitTextStrokeWidth')}
 
 </%self:impl_trait>
 
-<%self:impl_trait style_struct_name="Text"
-                  skip_longhands="text-overflow initial-letter">
-
-    fn clear_overflow_sides_if_string(&mut self) {
-        use crate::gecko_bindings::structs::nsStyleTextOverflowSide;
-        fn clear_if_string(side: &mut nsStyleTextOverflowSide) {
-            if side.mType == structs::NS_STYLE_TEXT_OVERFLOW_STRING as u8 {
-                side.mString.truncate();
-                side.mType = structs::NS_STYLE_TEXT_OVERFLOW_CLIP as u8;
-            }
-        }
-        clear_if_string(&mut self.gecko.mTextOverflow.mLeft);
-        clear_if_string(&mut self.gecko.mTextOverflow.mRight);
-    }
-
-    pub fn set_text_overflow(&mut self, v: longhands::text_overflow::computed_value::T) {
-        use crate::gecko_bindings::structs::nsStyleTextOverflowSide;
-        use crate::values::specified::text::TextOverflowSide;
-
-        fn set(side: &mut nsStyleTextOverflowSide, value: &TextOverflowSide) {
-            let ty = match *value {
-                TextOverflowSide::Clip => structs::NS_STYLE_TEXT_OVERFLOW_CLIP,
-                TextOverflowSide::Ellipsis => structs::NS_STYLE_TEXT_OVERFLOW_ELLIPSIS,
-                TextOverflowSide::String(ref s) => {
-                    side.mString.assign_str(s);
-                    structs::NS_STYLE_TEXT_OVERFLOW_STRING
-                }
-            };
-            side.mType = ty as u8;
-        }
-
-        self.clear_overflow_sides_if_string();
-        self.gecko.mTextOverflow.mLogicalDirections = v.sides_are_logical;
-
-        set(&mut self.gecko.mTextOverflow.mLeft, &v.first);
-        set(&mut self.gecko.mTextOverflow.mRight, &v.second);
-    }
-
-    pub fn copy_text_overflow_from(&mut self, other: &Self) {
-        use crate::gecko_bindings::structs::nsStyleTextOverflowSide;
-        fn set(side: &mut nsStyleTextOverflowSide, other: &nsStyleTextOverflowSide) {
-            if other.mType == structs::NS_STYLE_TEXT_OVERFLOW_STRING as u8 {
-                side.mString.assign(&*other.mString)
-            }
-            side.mType = other.mType
-        }
-        self.clear_overflow_sides_if_string();
-        set(&mut self.gecko.mTextOverflow.mLeft, &other.gecko.mTextOverflow.mLeft);
-        set(&mut self.gecko.mTextOverflow.mRight, &other.gecko.mTextOverflow.mRight);
-        self.gecko.mTextOverflow.mLogicalDirections = other.gecko.mTextOverflow.mLogicalDirections;
-    }
-
-    pub fn reset_text_overflow(&mut self, other: &Self) {
-        self.copy_text_overflow_from(other)
-    }
-
-    pub fn clone_text_overflow(&self) -> longhands::text_overflow::computed_value::T {
-        use crate::gecko_bindings::structs::nsStyleTextOverflowSide;
-        use crate::values::specified::text::TextOverflowSide;
-
-        fn to_servo(side: &nsStyleTextOverflowSide) -> TextOverflowSide {
-            match side.mType as u32 {
-                structs::NS_STYLE_TEXT_OVERFLOW_CLIP => TextOverflowSide::Clip,
-                structs::NS_STYLE_TEXT_OVERFLOW_ELLIPSIS => TextOverflowSide::Ellipsis,
-                structs::NS_STYLE_TEXT_OVERFLOW_STRING =>
-                    TextOverflowSide::String(side.mString.to_string().into_boxed_str()),
-                _ => panic!("Found unexpected value in style struct for text_overflow property"),
-            }
-        }
-
-        longhands::text_overflow::computed_value::T {
-            first: to_servo(&self.gecko.mTextOverflow.mLeft),
-            second: to_servo(&self.gecko.mTextOverflow.mRight),
-            sides_are_logical: self.gecko.mTextOverflow.mLogicalDirections
-        }
-    }
-
+<%self:impl_trait style_struct_name="Text" skip_longhands="initial-letter">
     pub fn set_initial_letter(&mut self, v: longhands::initial_letter::computed_value::T) {
         use crate::values::generics::text::InitialLetter;
         match v {
             InitialLetter::Normal => {
                 self.gecko.mInitialLetterSize = 0.;
                 self.gecko.mInitialLetterSink = 0;
             },
             InitialLetter::Specified(size, sink) => {
--- a/servo/components/style/properties/longhands/box.mako.rs
+++ b/servo/components/style/properties/longhands/box.mako.rs
@@ -379,16 +379,28 @@
     "computed::LengthPercentage::zero()",
     products="gecko",
     animation_value_type="ComputedValue",
     gecko_pref="layout.css.motion-path.enabled",
     spec="https://drafts.fxtf.org/motion-1/#offset-distance-property",
     servo_restyle_damage="reflow_out_of_flow"
 )}
 
+// Motion Path Module Level 1
+${helpers.predefined_type(
+    "offset-rotate",
+    "OffsetRotate",
+    "computed::OffsetRotate::auto()",
+    products="gecko",
+    animation_value_type="none",
+    gecko_pref="layout.css.motion-path.enabled",
+    spec="https://drafts.fxtf.org/motion-1/#offset-rotate-property",
+    servo_restyle_damage="reflow_out_of_flow"
+)}
+
 // CSSOM View Module
 // https://www.w3.org/TR/cssom-view-1/
 ${helpers.single_keyword(
     "scroll-behavior",
     "auto smooth",
     products="gecko",
     spec="https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior",
     animation_value_type="discrete",
--- a/servo/components/style/style_adjuster.rs
+++ b/servo/components/style/style_adjuster.rs
@@ -729,20 +729,17 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
     /// When comparing to Gecko, this is similar to the work done by
     /// `ComputedStyle::ApplyStyleFixups`, plus some parts of
     /// `nsStyleSet::GetContext`.
     pub fn adjust<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>)
     where
         E: TElement,
     {
         if cfg!(debug_assertions) {
-            if element
-                .and_then(|e| e.implemented_pseudo_element())
-                .is_some()
-            {
+            if element.map_or(false, |e| e.is_pseudo_element()) {
                 // It'd be nice to assert `self.style.pseudo == Some(&pseudo)`,
                 // but we do resolve ::-moz-list pseudos on ::before / ::after
                 // content, sigh.
                 debug_assert!(self.style.pseudo.is_some(), "Someone really messed up");
             }
         }
         // FIXME(emilio): The apply_declarations callsite in Servo's
         // animation, and the font stuff for Gecko
--- a/servo/components/style/style_resolver.rs
+++ b/servo/components/style/style_resolver.rs
@@ -228,17 +228,17 @@ where
         &mut self,
         parent_style: Option<&ComputedValues>,
         layout_parent_style: Option<&ComputedValues>,
     ) -> ResolvedElementStyles {
         let primary_style = self.resolve_primary_style(parent_style, layout_parent_style);
 
         let mut pseudo_styles = EagerPseudoStyles::default();
 
-        if self.element.implemented_pseudo_element().is_none() {
+        if !self.element.is_pseudo_element() {
             let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents() {
                 layout_parent_style
             } else {
                 Some(primary_style.style())
             };
             SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
                 let pseudo_style = self.resolve_pseudo_style(
                     &pseudo,
@@ -288,20 +288,16 @@ where
 
     fn cascade_style_and_visited(
         &mut self,
         inputs: CascadeInputs,
         parent_style: Option<&ComputedValues>,
         layout_parent_style: Option<&ComputedValues>,
         pseudo: Option<&PseudoElement>,
     ) -> ResolvedStyle {
-        debug_assert!(
-            self.element.implemented_pseudo_element().is_none() || pseudo.is_none(),
-            "Pseudo-elements can't have other pseudos!"
-        );
         debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
 
         let implemented_pseudo = self.element.implemented_pseudo_element();
         let pseudo = pseudo.or(implemented_pseudo.as_ref());
 
         let mut conditions = Default::default();
         let values = self.context.shared.stylist.cascade_style_and_visited(
             Some(self.element),
@@ -472,18 +468,18 @@ where
         visited_handling: VisitedHandlingMode,
     ) -> Option<StrongRuleNode> {
         debug!(
             "Match pseudo {:?} for {:?}, visited: {:?}",
             self.element, pseudo_element, visited_handling
         );
         debug_assert!(pseudo_element.is_eager());
         debug_assert!(
-            self.element.implemented_pseudo_element().is_none(),
-            "Element pseudos can't have any other pseudo."
+            !self.element.is_pseudo_element(),
+            "Element pseudos can't have any other eager pseudo."
         );
 
         let mut applicable_declarations = ApplicableDeclarationList::new();
 
         let stylist = &self.context.shared.stylist;
 
         if !self
             .element
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -60,17 +60,17 @@ pub use self::image::{Gradient, Gradient
 pub use self::length::{CSSPixelLength, ExtremumLength, NonNegativeLength};
 pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber};
 pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size};
 pub use self::length::{NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto};
 #[cfg(feature = "gecko")]
 pub use self::list::ListStyleType;
 pub use self::list::MozListReversed;
 pub use self::list::{QuotePair, Quotes};
-pub use self::motion::OffsetPath;
+pub use self::motion::{OffsetPath, OffsetRotate};
 pub use self::outline::OutlineStyle;
 pub use self::percentage::{NonNegativePercentage, Percentage};
 pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, ZIndex};
 pub use self::rect::NonNegativeLengthOrNumberRect;
 pub use self::resolution::Resolution;
 pub use self::svg::MozContextProperties;
 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind};
 pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
--- a/servo/components/style/values/computed/motion.rs
+++ b/servo/components/style/values/computed/motion.rs
@@ -1,10 +1,44 @@
 /* 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 https://mozilla.org/MPL/2.0/. */
 
 //! Computed types for CSS values that are related to motion path.
 
+use crate::values::computed::Angle;
+use crate::Zero;
+
 /// A computed offset-path. The computed value is as specified value.
 ///
 /// https://drafts.fxtf.org/motion-1/#offset-path-property
 pub use crate::values::specified::motion::OffsetPath;
+
+#[inline]
+fn is_auto_zero_angle(auto: &bool, angle: &Angle) -> bool {
+    *auto && angle.is_zero()
+}
+
+/// A computed offset-rotate.
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue)]
+#[repr(C)]
+pub struct OffsetRotate {
+    /// If auto is false, this is a fixed angle which indicates a
+    /// constant clockwise rotation transformation applied to it by this
+    /// specified rotation angle. Otherwise, the angle will be added to
+    /// the angle of the direction in layout.
+    #[css(represents_keyword)]
+    pub auto: bool,
+    /// The angle value.
+    #[css(contextual_skip_if = "is_auto_zero_angle")]
+    pub angle: Angle,
+}
+
+impl OffsetRotate {
+    /// Returns "auto 0deg".
+    #[inline]
+    pub fn auto() -> Self {
+        OffsetRotate {
+            auto: true,
+            angle: Zero::zero(),
+        }
+    }
+}
--- a/servo/components/style/values/computed/text.rs
+++ b/servo/components/style/values/computed/text.rs
@@ -100,16 +100,17 @@ impl ToComputedValue for specified::Word
         Spacing::Value(ToComputedValue::from_computed_value(computed))
     }
 }
 
 /// A computed value for the `line-height` property.
 pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLength>;
 
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue)]
+#[repr(C)]
 /// text-overflow.
 /// When the specified value only has one side, that's the "second"
 /// side, and the sides are logical, so "second" means "end".  The
 /// start side is Clip in that case.
 ///
 /// When the specified value has two sides, those are our "first"
 /// and "second" sides, and they are physical sides ("left" and
 /// "right").
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -61,17 +61,17 @@ pub use self::length::{FontRelativeLengt
 pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
 pub use self::length::{MaxSize, Size};
 pub use self::length::{NoCalcLength, ViewportPercentageLength};
 pub use self::length::{NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto};
 #[cfg(feature = "gecko")]
 pub use self::list::ListStyleType;
 pub use self::list::MozListReversed;
 pub use self::list::{QuotePair, Quotes};
-pub use self::motion::OffsetPath;
+pub use self::motion::{OffsetPath, OffsetRotate};
 pub use self::outline::OutlineStyle;
 pub use self::percentage::Percentage;
 pub use self::position::{GridAutoFlow, GridTemplateAreas, Position};
 pub use self::position::{PositionComponent, ZIndex};
 pub use self::rect::NonNegativeLengthOrNumberRect;
 pub use self::resolution::Resolution;
 pub use self::svg::MozContextProperties;
 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind};
--- a/servo/components/style/values/specified/motion.rs
+++ b/servo/components/style/values/specified/motion.rs
@@ -1,16 +1,19 @@
 /* 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 https://mozilla.org/MPL/2.0/. */
 
 //! Specified types for CSS values that are related to motion path.
 
 use crate::parser::{Parse, ParserContext};
-use crate::values::specified::SVGPathData;
+use crate::values::computed::motion::OffsetRotate as ComputedOffsetRotate;
+use crate::values::computed::{Context, ToComputedValue};
+use crate::values::specified::{Angle, SVGPathData};
+use crate::Zero;
 use cssparser::Parser;
 use style_traits::{ParseError, StyleParseErrorKind};
 
 /// The offset-path value.
 ///
 /// https://drafts.fxtf.org/motion-1/#offset-path-property
 /// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(
@@ -70,8 +73,109 @@ impl Parse for OffsetPath {
                     Err(location.new_custom_error(
                         StyleParseErrorKind::UnexpectedFunction(function.clone())
                     ))
                 },
             }
         })
     }
 }
+
+/// The direction of offset-rotate.
+#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
+#[repr(u8)]
+pub enum OffsetRotateDirection {
+    /// Unspecified direction keyword.
+    #[css(skip)]
+    None,
+    /// 0deg offset (face forward).
+    Auto,
+    /// 180deg offset (face backward).
+    Reverse,
+}
+
+impl OffsetRotateDirection {
+    /// Returns true if it is none (i.e. the keyword is not specified).
+    #[inline]
+    fn is_none(&self) -> bool {
+        *self == OffsetRotateDirection::None
+    }
+}
+
+#[inline]
+fn direction_specified_and_angle_is_zero(direction: &OffsetRotateDirection, angle: &Angle) -> bool {
+    !direction.is_none() && angle.is_zero()
+}
+
+/// The specified offset-rotate.
+/// The syntax is: "[ auto | reverse ] || <angle>"
+///
+/// https://drafts.fxtf.org/motion-1/#offset-rotate-property
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
+pub struct OffsetRotate {
+    /// [auto | reverse].
+    #[css(skip_if = "OffsetRotateDirection::is_none")]
+    direction: OffsetRotateDirection,
+    /// <angle>.
+    /// If direction is None, this is a fixed angle which indicates a
+    /// constant clockwise rotation transformation applied to it by this
+    /// specified rotation angle. Otherwise, the angle will be added to
+    /// the angle of the direction in layout.
+    #[css(contextual_skip_if = "direction_specified_and_angle_is_zero")]
+    angle: Angle,
+}
+
+impl Parse for OffsetRotate {
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        let location = input.current_source_location();
+        let mut direction = input.try(OffsetRotateDirection::parse);
+        let angle = input.try(|i| Angle::parse(context, i));
+        if direction.is_err() {
+            // The direction and angle could be any order, so give it a change to parse
+            // direction again.
+            direction = input.try(OffsetRotateDirection::parse);
+        }
+
+        if direction.is_err() && angle.is_err() {
+            return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+        }
+
+        Ok(OffsetRotate {
+            direction: direction.unwrap_or(OffsetRotateDirection::None),
+            angle: angle.unwrap_or(Zero::zero()),
+        })
+    }
+}
+
+impl ToComputedValue for OffsetRotate {
+    type ComputedValue = ComputedOffsetRotate;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        use crate::values::computed::Angle as ComputedAngle;
+
+        ComputedOffsetRotate {
+            auto: !self.direction.is_none(),
+            angle: if self.direction == OffsetRotateDirection::Reverse {
+                // The computed value should always convert "reverse" into "auto".
+                // e.g. "reverse calc(20deg + 10deg)" => "auto 210deg"
+                self.angle.to_computed_value(context) + ComputedAngle::from_degrees(180.0)
+            } else {
+                self.angle.to_computed_value(context)
+            },
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        OffsetRotate {
+            direction: if computed.auto {
+                OffsetRotateDirection::Auto
+            } else {
+                OffsetRotateDirection::None
+            },
+            angle: ToComputedValue::from_computed_value(&computed.angle),
+        }
+    }
+}
--- a/servo/components/style/values/specified/text.rs
+++ b/servo/components/style/values/specified/text.rs
@@ -129,24 +129,26 @@ impl ToComputedValue for LineHeight {
             GenericLineHeight::Length(ref length) => {
                 GenericLineHeight::Length(NoCalcLength::from_computed_value(&length.0).into())
             },
         }
     }
 }
 
 /// A generic value for the `text-overflow` property.
+/// cbindgen:derive-tagged-enum-copy-constructor=true
 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
+#[repr(C, u8)]
 pub enum TextOverflowSide {
     /// Clip inline content.
     Clip,
     /// Render ellipsis to represent clipped inline content.
     Ellipsis,
     /// Render a given string to represent clipped inline content.
-    String(Box<str>),
+    String(crate::OwnedStr),
 }
 
 impl Parse for TextOverflowSide {
     fn parse<'i, 't>(
         _context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<TextOverflowSide, ParseError<'i>> {
         let location = input.current_source_location();
@@ -156,17 +158,17 @@ impl Parse for TextOverflowSide {
                     "clip" => Ok(TextOverflowSide::Clip),
                     "ellipsis" => Ok(TextOverflowSide::Ellipsis),
                     _ => Err(location.new_custom_error(
                         SelectorParseErrorKind::UnexpectedIdent(ident.clone())
                     ))
                 }
             },
             Token::QuotedString(ref v) => Ok(TextOverflowSide::String(
-                v.as_ref().to_owned().into_boxed_str(),
+                v.as_ref().to_owned().into(),
             )),
             ref t => Err(location.new_unexpected_token_error(t.clone())),
         }
     }
 }
 
 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
 /// text-overflow. Specifies rendering when inline content overflows its line box edge.
--- a/servo/ports/geckolib/cbindgen.toml
+++ b/servo/ports/geckolib/cbindgen.toml
@@ -65,16 +65,17 @@ include = [
   "FontDisplay",
   "FontFaceSourceListComponent",
   "FontLanguageOverride",
   "GenericFontFamily",
   "FontFamilyNameSyntax",
   "OverflowWrap",
   "TimingFunction",
   "OffsetPath",
+  "OffsetRotate",
   "UnicodeRange",
   "UserSelect",
   "Float",
   "OverscrollBehavior",
   "ScrollSnapAlign",
   "ScrollSnapAxis",
   "ScrollSnapStrictness",
   "ScrollSnapType",
@@ -109,16 +110,17 @@ include = [
   "WordBreak",
   "Contain",
   "Origin",
   "RestyleHint",
   "TouchAction",
   "WillChange",
   "TextDecorationLine",
   "TextTransform",
+  "TextOverflow",
   "MozListReversed",
   "Owned",
   "OwnedOrNull",
   "Strong",
   "ScrollbarColor",
   "Color",
   "ColorOrAuto",
   "GradientItem",
@@ -447,8 +449,24 @@ renaming_overrides_prefixing = true
 "OffsetPath" = """
  private:
   // Private default constructor without initialization so that the helper
   // constructor functions still work as expected. They take care of
   // initializing the fields properly.
   StyleOffsetPath() {}
  public:
 """
+
+"TextOverflowSide" = """
+ private:
+  // Private default constructor without initialization so that the helper
+  // constructor functions still work as expected. They take care of
+  // initializing the fields properly.
+  StyleTextOverflowSide() {}
+ public:
+"""
+
+"TextOverflow" = """
+  StyleTextOverflow()
+    : first(StyleTextOverflowSide::Clip()),
+      second(StyleTextOverflowSide::Clip()),
+      sides_are_logical(true) {}
+"""
--- a/taskcluster/ci/release-update-verify/kind.yml
+++ b/taskcluster/ci/release-update-verify/kind.yml
@@ -19,17 +19,17 @@ transforms:
 job-defaults:
     name: update-verify
     run-on-projects: []  # to make sure this never runs as part of CI
     shipping-phase: promote
     worker-type: b-linux
     worker:
         artifacts:
             - name: 'public/build/diff-summary.log'
-              path: '/builds/worker/tools/release/updates/diff-summary.log'
+              path: '/builds/worker/checkouts/gecko/diff-summary.log'
               type: file
         docker-image:
             in-tree: "update-verify"
         max-run-time: 5400
         retry-exit-status:
             - 255
     treeherder:
         symbol: UV(UV)
--- a/taskcluster/ci/test/raptor.yml
+++ b/taskcluster/ci/test/raptor.yml
@@ -2141,8 +2141,303 @@ raptor-tp6m-14-geckoview-cold:
     target: geckoview_example.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-cold-14
             - --app=geckoview
             - --binary=org.mozilla.geckoview_example
             - --activity=org.mozilla.geckoview_example.GeckoViewActivity
+
+raptor-tp6m-1-fennec64-cold:
+    description: "Raptor tp6m-1 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-1-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-1-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-1
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+
+raptor-tp6m-2-fennec64-cold:
+    description: "Raptor tp6m-2 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-2-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-2-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-2
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-3-fennec64-cold:
+    description: "Raptor tp6m-3 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-3-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-3-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-3
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-4-fennec64-cold:
+    description: "Raptor tp6m-4 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-4-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-4-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-4
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-5-fennec64-cold:
+    description: "Raptor tp6m-5 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-5-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-5-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-1
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-6-fennec64-cold:
+    description: "Raptor tp6m-6 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-6-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-6-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-6
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-7-fennec64-cold:
+    description: "Raptor tp6m-7 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-7-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-7-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-7
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-8-fennec64-cold:
+    description: "Raptor tp6m-8 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-8-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-8-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-8
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-9-fennec64-cold:
+    description: "Raptor tp6m-9 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-9-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-9-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-9
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-10-fennec64-cold:
+    description: "Raptor tp6m-10 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-10-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-10-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-10
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-11-fennec64-cold:
+    description: "Raptor tp6m-11 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-11-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-11-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-11
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-12-fennec64-cold:
+    description: "Raptor tp6m-12 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-12-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-12-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-12
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-13-fennec64-cold:
+    description: "Raptor tp6m-13 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-13-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-13-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-13
+            - --app=fennec
+            - --binary=org.mozilla.firefox
+
+raptor-tp6m-14-fennec64-cold:
+    description: "Raptor tp6m-14 cold page-load on Fennec64"
+    max-run-time: 3600
+    try-name: raptor-tp6m-14-fennec64-cold
+    treeherder-symbol: Rap(tp6m-c-14-f64)
+    target:
+        by-test-platform:
+            android-hw-p2-8-0-android-aarch64.*:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-aarch64-opt
+                name: build/target.apk
+            default:
+                index: gecko.v2.mozilla-release.revision.65621d0fe1262af0643cec37c23b2d9ec42588ad.mobile.android-api-16-opt
+                name: build/target.apk
+    run-on-projects: ['try']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-cold-14
+            - --app=fennec
+            - --binary=org.mozilla.firefox
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -529,16 +529,30 @@ android-hw-arm7-raptor:
     - raptor-tp6m-7-fennec-cold
     - raptor-tp6m-8-fennec-cold
     - raptor-tp6m-9-fennec-cold
     - raptor-tp6m-10-fennec-cold
     - raptor-tp6m-11-fennec-cold
     - raptor-tp6m-12-fennec-cold
     - raptor-tp6m-13-fennec-cold
     - raptor-tp6m-14-fennec-cold
+    - raptor-tp6m-1-fennec64-cold
+    - raptor-tp6m-2-fennec64-cold
+    - raptor-tp6m-3-fennec64-cold
+    - raptor-tp6m-4-fennec64-cold
+    - raptor-tp6m-5-fennec64-cold
+    - raptor-tp6m-6-fennec64-cold
+    - raptor-tp6m-7-fennec64-cold
+    - raptor-tp6m-8-fennec64-cold
+    - raptor-tp6m-9-fennec64-cold
+    - raptor-tp6m-10-fennec64-cold
+    - raptor-tp6m-11-fennec64-cold
+    - raptor-tp6m-12-fennec64-cold
+    - raptor-tp6m-13-fennec64-cold
+    - raptor-tp6m-14-fennec64-cold
 
 android-hw-aarch64-raptor:
     - raptor-speedometer-geckoview
     - raptor-speedometer-refbrow
     - raptor-tp6m-1-geckoview
     - raptor-tp6m-2-geckoview
     - raptor-tp6m-3-geckoview
     - raptor-tp6m-4-geckoview
@@ -595,16 +609,30 @@ android-hw-aarch64-raptor:
     - raptor-tp6m-7-fennec-cold
     - raptor-tp6m-8-fennec-cold
     - raptor-tp6m-9-fennec-cold
     - raptor-tp6m-10-fennec-cold
     - raptor-tp6m-11-fennec-cold
     - raptor-tp6m-12-fennec-cold
     - raptor-tp6m-13-fennec-cold
     - raptor-tp6m-14-fennec-cold
+    - raptor-tp6m-1-fennec64-cold
+    - raptor-tp6m-2-fennec64-cold
+    - raptor-tp6m-3-fennec64-cold
+    - raptor-tp6m-4-fennec64-cold
+    - raptor-tp6m-5-fennec64-cold
+    - raptor-tp6m-6-fennec64-cold
+    - raptor-tp6m-7-fennec64-cold
+    - raptor-tp6m-8-fennec64-cold
+    - raptor-tp6m-9-fennec64-cold
+    - raptor-tp6m-10-fennec64-cold
+    - raptor-tp6m-11-fennec64-cold
+    - raptor-tp6m-12-fennec64-cold
+    - raptor-tp6m-13-fennec64-cold
+    - raptor-tp6m-14-fennec64-cold
 
 android-hw-arm7-raptor-power:
     - raptor-speedometer-geckoview-power
     - raptor-scn-power-idle-fenix
     - raptor-scn-power-idle-fennec
     - raptor-scn-power-idle-geckoview
     - raptor-scn-power-idle-refbrow
 
--- a/taskcluster/scripts/misc/run-profileserver.sh
+++ b/taskcluster/scripts/misc/run-profileserver.sh
@@ -28,9 +28,16 @@ start_xvfb '1024x768x24' 2
 
 cd /builds/worker/checkouts/gecko
 
 # Move our fetched firefox into objdir/dist so the jarlog entries will match
 # the paths when the final PGO stage packages the build.
 mkdir -p $PGO_RUNDIR
 mv $MOZ_FETCHES_DIR/firefox $PGO_RUNDIR
 ./mach python build/pgo/profileserver.py --binary $PGO_RUNDIR/firefox/firefox
-tar -acvf $UPLOAD_PATH/profdata.tar.xz default.profraw en-US.log
+
+# Fail the build if for some reason we didn't collect any profile data.
+if test -z "$(find . -maxdepth 1 -name '*.profraw' -print -quit)"; then
+    echo "ERROR: no profile data produced"
+    exit 1
+fi
+
+tar -acvf $UPLOAD_PATH/profdata.tar.xz *.profraw en-US.log
--- a/testing/profiles/perf/user.js
+++ b/testing/profiles/perf/user.js
@@ -70,19 +70,16 @@ user_pref("media.gmp-manager.url", "http
 // Don't block old libavcodec libraries when testing, because our test systems
 // cannot easily be upgraded.
 user_pref("media.libavcodec.allow-obsolete", true);
 user_pref("media.navigator.enabled", true);
 user_pref("media.navigator.permission.disabled", true);
 user_pref("media.peerconnection.enabled", true);
 // Disable speculative connections so they aren't reported as leaking when they're hanging around.
 user_pref("network.http.speculative-parallel-limit", 0);
-user_pref("network.proxy.http", "localhost");
-user_pref("network.proxy.http_port", 80);
-user_pref("network.proxy.type", 1);
 // Set places maintenance far in the future (the maximum time possible in an
 // int32_t) to avoid it kicking in during tests. The maintenance can take a
 // relatively long time which may cause unnecessary intermittents and slow down
 // tests. This, like many things, will stop working correctly in 2038.
 user_pref("places.database.lastMaintenance", 2147483647);
 user_pref("plugin.state.flash", 0);
 user_pref("plugins.flashBlock.enabled", false);
 user_pref("privacy.reduceTimerPrecision", false); // Bug 1445243 - reduces precision of tests
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-1.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-1.ini
@@ -39,23 +39,23 @@ measure = fnbpaint, fcp, dcf, loadtime
 
 [raptor-tp6m-facebook-fenix-cold]
 apps = fenix
 test_url = https://m.facebook.com
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-facebook.manifest
 playback_recordings = android-facebook.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-cold-amazon-fennec]
+[raptor-tp6m-amazon-fennec-cold]
 apps = fennec
 test_url = https://www.amazon.com
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-amazon.manifest
 playback_recordings = android-amazon.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-cold-facebook-fennec]
+[raptor-tp6m-facebook-fennec-cold]
 apps = fennec
 test_url = https://m.facebook.com
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-facebook.manifest
 playback_recordings = android-facebook.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-10.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-10.ini
@@ -39,23 +39,23 @@ measure = fnbpaint, fcp, dcf, loadtime
 
 [raptor-tp6m-stackoverflow-geckoview-cold]
 apps = geckoview
 test_url = https://stackoverflow.com/
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-stackoverflow.manifest
 playback_recordings = mitmproxy-recordings-raptor-tp6m-stackoverflow.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-cold-bbc-fennec]
+[raptor-tp6m-bbc-fennec-cold]
 apps = fennec
 test_url = https://www.bbc.com/news/business-47245877
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-bbc.manifest
 playback_recordings = mitmproxy-recordings-raptor-tp6m-bbc.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-cold-stackoverflow-fennec]
+[raptor-tp6m-stackoverflow-fennec-cold]
 apps = fennec
 test_url = https://stackoverflow.com/
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-stackoverflow.manifest
 playback_recordings = mitmproxy-recordings-raptor-tp6m-stackoverflow.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-11.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-11.ini
@@ -39,23 +39,23 @@ measure = fnbpaint, fcp, dcf, loadtime
 
 [raptor-tp6m-jianshu-geckoview-cold]
 apps = geckoview
 test_url = https://www.jianshu.com/
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-jianshu.manifest
 playback_recordings = android-jianshu.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-cold-microsoft-support-fennec]
+[raptor-tp6m-microsoft-support-fennec-cold]
 apps = fennec
 test_url = https://support.microsoft.com/en-us
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-microsoft-support.manifest
 playback_recordings = android-microsoft-support.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-cold-jianshu-fennec]
+[raptor-tp6m-jianshu-fennec-cold]
 apps = fennec
 test_url = https://www.jianshu.com/
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-jianshu.manifest
 playback_recordings = android-jianshu.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-12.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-12.ini
@@ -39,23 +39,23 @@ measure = fnbpaint, fcp, dcf, loadtime
 
 [raptor-tp6m-allrecipes-geckoview-cold]
 apps = geckoview
 test_url = https://www.allrecipes.com/
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-allrecipes.manifest
 playback_recordings = android-allrecipes.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-cold-imdb-fennec]
+[raptor-tp6m-imdb-fennec-cold]
 apps = fennec
 test_url = https://m.imdb.com/
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-imdb.manifest
 playback_recordings = android-imdb.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-cold-allrecipes-fennec]
+[raptor-tp6m-allrecipes-fennec-cold]
 apps = fennec
 test_url = https://www.allrecipes.com/
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-allrecipes.manifest
 playback_recordings = android-allrecipes.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-13.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-13.ini
@@ -39,23 +39,23 @@ measure = fnbpaint, fcp, dcf, loadtime
 
 [raptor-tp6m-web-de-geckoview-cold]
 apps = geckoview
 test_url = https://web.de/magazine/politik/politologe-glaubt-grossen-koalition-herbst-knallen-33563566
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-web-de.manifest
 playback_recordings = android-web-de.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-cold-espn-fennec]
+[raptor-tp6m-espn-fennec-cold]
 apps = fennec
 test_url = http://www.espn.com/nba/story/_/page/allstarweekend25788027/the-comparison-lebron-james-michael-jordan-their-own-words
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-espn.manifest
 playback_recordings = android-espn.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-cold-web-de-fennec]
+[raptor-tp6m-web-de-fennec-cold]
 apps = fennec
 test_url = https://web.de/magazine/politik/politologe-glaubt-grossen-koalition-herbst-knallen-33563566
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-web-de.manifest
 playback_recordings = android-web-de.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-14.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-14.ini
@@ -39,23 +39,23 @@ measure = fnbpaint, fcp, dcf, loadtime
 
 [raptor-tp6m-aframeio-animation-geckoview-cold]
 apps = geckoview
 test_url = https://aframe.io/examples/showcase/animation
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-aframeio-animation.manifest
 playback_recordings = android-aframeio-animation.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-cold-facebook-cristiano-fennec]
+[raptor-tp6m-facebook-cristiano-fennec-cold]
 apps = fennec
 test_url = https://m.facebook.com/Cristiano
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-facebook-cristiano.manifest
 playback_recordings = android-facebook-cristiano.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-cold-aframeio-animation-fennec]
+[raptor-tp6m-aframeio-animation-fennec-cold]
 apps = fennec
 test_url = https://aframe.io/examples/showcase/animation
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-aframeio-animation.manifest
 playback_recordings = android-aframeio-animation.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-2.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-2.ini
@@ -54,8 +54,9 @@ alert_on = loadtime
 
 [raptor-tp6m-youtube-fennec-cold]
 apps = fennec
 test_url = https://www.youtube.com
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-youtube.manifest
 playback_recordings = android-youtube.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
+disabled = Intermittent fennec v64 - wont fix
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-3.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-3.ini
@@ -39,23 +39,23 @@ measure = fnbpaint, fcp, dcf, loadtime
 
 [raptor-tp6m-bing-geckoview-cold]
 apps = geckoview
 test_url = https://www.bing.com
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-bing-mobile.manifest
 playback_recordings = bing-mobile.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-instagram-fennec]
+[raptor-tp6m-instagram-fennec-cold]
 apps = fennec
 test_url = https://www.instagram.com
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-instagram-mobile.manifest
 playback_recordings = instagram-mobile.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-cold-bing-fennec]
+[raptor-tp6m-bing-fennec-cold]
 apps = fennec
 test_url = https://www.bing.com
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-bing-mobile.manifest
 playback_recordings = bing-mobile.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-4.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-4.ini
@@ -39,23 +39,23 @@ measure = fnbpaint, fcp, dcf, loadtime
 
 [raptor-tp6m-ebay-kleinanzeigen-geckoview-cold]
 apps = geckoview
 test_url = https://m.ebay-kleinanzeigen.de
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-ebay-kleinanzeigen-mobile.manifest
 playback_recordings = ebay-kleinanzeigen-mobile.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-cold-bing-restaurants-fennec]
+[raptor-tp6m-bing-restaurants-fennec-cold]
 apps = fennec
 test_url = https://www.bing.com/search?q=restaurants
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-bing-restaurants-mobile.manifest
 playback_recordings = bing-restaurants-mobile.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-ebay-kleinanzeigen-fennec]
+[raptor-tp6m-ebay-kleinanzeigen-fennec-cold]
 apps = fennec
 test_url = https://m.ebay-kleinanzeigen.de
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-ebay-kleinanzeigen-mobile.manifest
 playback_recordings = ebay-kleinanzeigen-mobile.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-5.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-5.ini
@@ -39,23 +39,23 @@ measure = fnbpaint, fcp, dcf, loadtime
 
 [raptor-tp6m-google-maps-geckoview-cold]
 apps = geckoview
 test_url = https://www.google.com/maps?force=pwa
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-google-maps.manifest
 playback_recordings = google_maps_mobile.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-ebay-kleinanzeigen-search-fennec]
+[raptor-tp6m-ebay-kleinanzeigen-search-fennec-cold]
 apps = fennec
 test_url = https://m.ebay-kleinanzeigen.de/s-anzeigen/auf-zeit-wg-berlin/zimmer/c199-l3331
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-ebay-kleinanzeigen-search-mobile.manifest
 playback_recordings = ebay-kleinanzeigen-search-mobile.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-cold-google-maps-fennec]
+[raptor-tp6m-google-maps-fennec-cold]
 apps = fennec
 test_url = https://www.google.com/maps?force=pwa
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-google-maps.manifest
 playback_recordings = google_maps_mobile.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-6.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-6.ini
@@ -41,24 +41,24 @@ disabled = Bug 1533283 Intermittent time
 
 [raptor-tp6m-amazon-search-geckoview-cold]
 apps = geckoview
 test_url = https://www.amazon.com/s/ref=nb_sb_noss_2/139-6317191-5622045?url=search-alias%3Daps&field-keywords=mobile+phone
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-amazon-search.manifest
 playback_recordings = android-amazon-search.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-cold-google-restaurants-fennec]
+[raptor-tp6m-google-restaurants-fennec-cold]
 apps = fennec
 test_url = https://www.google.com/search?q=restaurants+near+me
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-google-restaurants.manifest
 playback_recordings = google-search-restaurants-mobile.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 disabled = Bug 1541431 TEST-UNEXPECTED-FAIL: test 'raptor-tp6m-google-restaurants-fennec' timed out loading test page
 
-[raptor-tp6m-cold-amazon-search-fennec]
+[raptor-tp6m-cold-amazon-search-fennec-cold]
 apps = fennec
 test_url = https://www.amazon.com/s/ref=nb_sb_noss_2/139-6317191-5622045?url=search-alias%3Daps&field-keywords=mobile+phone
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-amazon-search.manifest
 playback_recordings = android-amazon-search.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-7.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-7.ini
@@ -39,23 +39,23 @@ measure = fnbpaint, fcp, dcf, loadtime
 
 [raptor-tp6m-youtube-watch-geckoview-cold]
 apps = geckoview
 test_url = https://www.youtube.com/watch?v=COU5T-Wafa4
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-youtube-watch.manifest
 playback_recordings = android-youtube-watch.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-cold-wikipedia-fennec]
+[raptor-tp6m-wikipedia-fennec-cold]
 apps = fennec
 test_url = https://en.m.wikipedia.org/wiki/Main_Page
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-wikipedia.manifest
 playback_recordings = android-wikipedia.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-cold-youtube-watch-fennec]
+[raptor-tp6m-youtube-watch-fennec-cold]
 apps = fennec
 test_url = https://www.youtube.com/watch?v=COU5T-Wafa4
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-youtube-watch.manifest
 playback_recordings = android-youtube-watch.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-8.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-8.ini
@@ -41,23 +41,24 @@ measure = fnbpaint, fcp, dcf, loadtime
 [raptor-tp6m-cnn-geckoview-cold]
 apps = geckoview
 test_url = https://edition.cnn.com/
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-cnn.manifest
 playback_recordings = android-cnn.mp
 measure = fnbpaint, fcp, dcf, loadtime
 disabled = Bug 1533287 Intermittent timeouts running raptor-tp6m-cnn-geckoview
 
-[raptor-tp6m-cold-booking-fennec]
+[raptor-tp6m-booking-fennec-cold]
 apps = fennec
 test_url = https://www.booking.com/
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-booking.manifest
 playback_recordings = android-booking.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-cold-cnn-fennec]
+[raptor-tp6m-cnn-fennec-cold]
 apps = fennec
 test_url = https://edition.cnn.com/
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-cnn.manifest
 playback_recordings = android-cnn.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
+disabled = Intermittent on fennec v64 - wont fix
--- a/testing/raptor/raptor/tests/raptor-tp6m-cold-9.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6m-cold-9.ini
@@ -39,23 +39,23 @@ measure = fnbpaint, fcp, dcf, loadtime
 
 [raptor-tp6m-reddit-geckoview-cold]
 apps = geckoview
 test_url = https://www.reddit.com
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-reddit.manifest
 playback_recordings = mitmproxy-recordings-raptor-tp6m-reddit.mp
 measure = fnbpaint, fcp, dcf, loadtime
 
-[raptor-tp6m-cold-cnn-ampstories-fennec]
+[raptor-tp6m-cnn-ampstories-fennec-cold]
 apps = fennec
 test_url = https://edition.cnn.com/ampstories/us/why-hurricane-michael-is-a-monster-unlike-any-other
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-cnn-ampstories.manifest
 playback_recordings = android-cnn-ampstories.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
 
-[raptor-tp6m-cold-reddit-fennec]
+[raptor-tp6m-reddit-fennec-cold]
 apps = fennec
 test_url = https://www.reddit.com
 playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-reddit.manifest
 playback_recordings = mitmproxy-recordings-raptor-tp6m-reddit.mp
 measure = fnbpaint, dcf, loadtime
 alert_on = loadtime
--- a/testing/raptor/raptor/tests/raptor-youtube-playback.ini
+++ b/testing/raptor/raptor/tests/raptor-youtube-playback.ini
@@ -17,18 +17,16 @@ test_url = http://yttest.dev.mozaws.net/
 page_cycles = 1
 # account for a page cycle duration of at maximum 45 minutes
 page_timeout = 2700000
 alert_threshold = 2.0
 lower_is_better = true
 unit = score
 subtest_lower_is_better = true
 subtest_unit = score
-# TODO: Allow the host / port option in the manifest (Bug 1547932)
-preferences = {"network.proxy.type": 0}
 
 [raptor-youtube-playback-firefox]
 apps = firefox
 
 [raptor-youtube-playback-geckoview]
 # Bug 1547717 - Cannot override autoplay preference due to GeckoRuntime Settings
 apps = geckoview
 test_url = http://yttest.dev.mozaws.net/2019/main.html?test_type=playbackperf-test&raptor=true&command=run&exclude=1,2&muted=true
--- a/testing/talos/talos/tests/dromaeo/tests/cssquery-dojo.html
+++ b/testing/talos/talos/tests/dromaeo/tests/cssquery-dojo.html
@@ -163,17 +163,17 @@ var query = dojo.query;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/cssquery-ext.html
+++ b/testing/talos/talos/tests/dromaeo/tests/cssquery-ext.html
@@ -164,17 +164,17 @@ var query = Ext.DomQuery.select;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/cssquery-jquery.html
+++ b/testing/talos/talos/tests/dromaeo/tests/cssquery-jquery.html
@@ -162,17 +162,17 @@ var html = document.body.innerHTML;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/cssquery-mootools.html
+++ b/testing/talos/talos/tests/dromaeo/tests/cssquery-mootools.html
@@ -162,17 +162,17 @@ var html = document.body.innerHTML;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/cssquery-prototype.html
+++ b/testing/talos/talos/tests/dromaeo/tests/cssquery-prototype.html
@@ -162,17 +162,17 @@ var html = document.body.innerHTML;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/cssquery-yui.html
+++ b/testing/talos/talos/tests/dromaeo/tests/cssquery-yui.html
@@ -165,17 +165,17 @@ var query = YAHOO.util.Selector.query;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/dom-attr.html
+++ b/testing/talos/talos/tests/dromaeo/tests/dom-attr.html
@@ -43,17 +43,17 @@ var num = 10240;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/dom-modify.html
+++ b/testing/talos/talos/tests/dromaeo/tests/dom-modify.html
@@ -73,17 +73,17 @@ for ( var i = 0; i < 1024; i++ )
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/dom-query.html
+++ b/testing/talos/talos/tests/dromaeo/tests/dom-query.html
@@ -100,17 +100,17 @@ var html = document.body.innerHTML;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/dom-traverse.html
+++ b/testing/talos/talos/tests/dromaeo/tests/dom-traverse.html
@@ -72,17 +72,17 @@ var html = document.body.innerHTML;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/jslib-attr-jquery.html
+++ b/testing/talos/talos/tests/dromaeo/tests/jslib-attr-jquery.html
@@ -49,17 +49,17 @@ var html = document.body.innerHTML;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/jslib-attr-prototype.html
+++ b/testing/talos/talos/tests/dromaeo/tests/jslib-attr-prototype.html
@@ -49,17 +49,17 @@ var html = document.body.innerHTML;
 	*/
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/jslib-event-jquery.html
+++ b/testing/talos/talos/tests/dromaeo/tests/jslib-event-jquery.html
@@ -36,17 +36,17 @@ function testfn(){}
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/jslib-event-prototype.html
+++ b/testing/talos/talos/tests/dromaeo/tests/jslib-event-prototype.html
@@ -36,17 +36,17 @@ function testfn(){}
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/jslib-modify-jquery.html
+++ b/testing/talos/talos/tests/dromaeo/tests/jslib-modify-jquery.html
@@ -60,17 +60,17 @@ var html = document.body.innerHTML,
 	*/
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/jslib-modify-prototype.html
+++ b/testing/talos/talos/tests/dromaeo/tests/jslib-modify-prototype.html
@@ -60,17 +60,17 @@ var html = document.body.innerHTML,
 	*/
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/jslib-style-jquery.html
+++ b/testing/talos/talos/tests/dromaeo/tests/jslib-style-jquery.html
@@ -58,17 +58,17 @@ var html = document.body.innerHTML;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/jslib-style-prototype.html
+++ b/testing/talos/talos/tests/dromaeo/tests/jslib-style-prototype.html
@@ -59,17 +59,17 @@ var html = document.body.innerHTML;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/jslib-traverse-jquery.html
+++ b/testing/talos/talos/tests/dromaeo/tests/jslib-traverse-jquery.html
@@ -60,17 +60,17 @@ var html = document.body.innerHTML;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
--- a/testing/talos/talos/tests/dromaeo/tests/jslib-traverse-prototype.html
+++ b/testing/talos/talos/tests/dromaeo/tests/jslib-traverse-prototype.html
@@ -60,17 +60,17 @@ var html = document.body.innerHTML;
 	});
 
 endTest();
 };
 </script>
 </head>
 <body>
   <div class="head">
-   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="http://www.w3.org/Icons/w3c_home" width=72></a>
+   <p><a href="http://www.w3.org/"><img height=48 alt=W3C src="w3c_home.png" width=72></a>
 
    <h1 id="title">Selectors</h1>
 
    <h2>W3C Working Draft 15 December 2005</h2>
 
    <dl>
 
     <dt>This version:
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f70c2b0847fb568cab477e880ad11bf6c074d482
GIT binary patch
literal 1936
zc$@){2XFX^P)<h;3K|Lk000e1NJLTq002k;001xu0{{R3g+k2P00093P)t-s00001
z0RaL60tE#H1_lNQ2nY-e3=R$s5)u*=6ciN|6&M&88X6iM9UUJZA0QwgCMG5*C@3r}
zEHN=LH8nLhHa0gmH#j&rJ3Bi+KR-l7L`6kKMn*<SNl8vlPESuyR#sMBU0q&YUSMEg
zVPRopWMpS&XKZY2ZEbCDZ*OpLaB*>QbaZrle0%^}oB>;$0$ZH}T%816o&{Z=240>B
zU!MtJp9^823}T@UW1$aZq7r7K6K10nXQUQrr5I|Z8f&H-Yo;D;s2^^qAaAH4Z>b}3
zsw8o$D0HkXcdasduQYtHHGHu*ez7=yvN?aUI)Jl0fwMt`wL*oqM25CUh`33KxJrt-
zON+TqjJi>dyH%0BS(Lw8l)qb*zg?EVUzfpRnZjqC#A%+zYM;expT=yU#%-a-ZlT9-
zqQ`Ng$aAF0b*9O8r^<M!%X+EHe5%ZStId9_&4Gb|goK2NiHVAeij0hmjg5_ukB^a&
zk&=><mX?;6mzS8Bn3|fJot>SZpP!(hprN6mqN1XurKP5(rl_c>s;a81tE;T6tgWrB
zudlDMv9YqUva_?Zw6wIhx3{^uxxBo*zrVkNuFiw6&xNqihOy9xvCxRJ(TlXwjJ491
zyVjSy*O<K5nZ4JWzSy9`+N8wXrN!K+$K9&P-mlBxu*~4G&Ec}m;k3@;x6tFe)8)L>
z<-XPC!NI}8!otJD!^OqL#>U3S$H&dh&Cbrw&(F`()6>}4*xA|H+uPgR+}z#W-QVBe
zz}Dx%*XP67=*8LS$J^=1+v>{Q>dW2h%--wG-s{fa>&@Tn(c$gV;_lPq?$qP%)#UHj
z<?q<#@7d<?+UD@v=<(d>@!;U#;o;%r<mBh)=jrL`>+9?7?d|UF?&0k7;_UR~?)B&I
z_UZBV>hbsM^7rlY`0n)h@bK{R^78cb^!4@i_xJbk_4)Jn`t<nw_WAtx`uzF%`TP6(
z`1<_$`~CX-{rmj={QUg={r&v?{{8;`{{H^{{{R2~|IEzHk(wu800001VoOIv0Eh)0
zNB{r;32;bRa{vGUNB{r;NB~C3Yd!z~1Sv^GK~zY`?UxBulvNbRA5=qPX;vg;h?TVS
zF))M%L4^iPC<eAn5k(=iP-GGk7|{ZWwntPbQ)WaF5k}Dlw9ulXAkn@Mgc!025fl;(
z?|AQf-{bpc7zWP4bLyPD=fL-W_q*?RzWd(0_W?PE91?ROW|h%t7@5xG!=Qf?^zBZQ
zsa<x|nM|f$<$XI0O*<6pwVK}vKjxYy85Ac;QnLIe8njUfOqMLImU#!#-CWN35!D%#
zR;A@Vn%2!g#So#FXBL7sC?LHR=yP;-oQQ{6^9ZHyIyBR?4d*VbSjRv;ItXduGaEtr
z_X$pm6(?c@oI|d>ERKAOdXS2rAI~|{vpSuO%O@0csQ?*a5O4*vr69f82W5x?o3+h>
zJk*t)^MuEM3{vNF8|ZieGQeOHQ-6hlydG7*Ga(N1va(NeBpTxME6sSO=Iz{#WL!Sw
zphIL%pFa$~XR-j);#O4IgdDXV#XCSuft7zCGxKS2j#T-4g=<mIbt%YjruJL_@;N3w
z2+8HIKhVtRv^tQtD*0fVQUTZTAd{##A(#Hiwq8R{7qheOp;jPOKDh8w6xSgj%Ta9i
z2K6fQRTIZ*F?(pgHE1wE<in1F<iienJ?_~ch|x$;D|zTs#C^gN0gDm&6!Qa9Bo;UZ
z#mgXkck{>`a<4tmR*_E}2gRa}0<jgIy_bNvQI5QiRM`WyX?Z@w7s5V;OH$x46s(UV
zY1`oCYD8<V=3BYm=^uL_x}4`zi~E$stFs6mk@)U>{*}5dILJGW+6CqFe6ZR-Dd*iF
zR;-(-G;hrF!;|cTc1bKBJ^sc$)YIWWv6E$GCHdh<S_GACo^KyCdNJnH!oTY}>;|=}
zTA}p7?MS)_REm8NJH^RHzEZe4#$<K%f?b2v1_k;4Bp|FSJyg4{o$r@HukI&cU*add
z0mpd~5UX@QNs5+Fke@||Lkj}W^K=4J-696QHHD}*b8WDQT>+kiDS(W8awF<Su8kJ4
zSApk-DS&u?4&#GRc5Z&Nh`k(KiN9FRHxG;l@f(A;A9@Sw%Pc2d1g<qtc3Joye9s1m
z-C^Zizlr)T{LVY|e{fd=x?XGYQgePf`hFXrTG4)}+@QXo^c6S=&vI7+_LASJ=VQu@
zZZ~q44bVui+<wrYK?TDl*6Isg;dOL;h8^!(N<Vha6ER(<5c04M5cZ-Xp`k&=;d2%C
z%bP#_?j`(0E#7OXN8H`dLMo3ZWWL!Js90{XXnTF6VtnpfqPl1k(KB>1IrhAq+_iHZ
z2$|hXZGn1pQOY1n*TbH5L1UaN|7M;JS8>(O`H0#C(dYt27eveAn`IoMO%MgseYPXx
zxHr=YKrei)=!O@^HlZXgB7Ef?4XVH&QcqprRjHo(AN|ws{{k}GvMGRO9-4o%PB;ii
Wc1oRMEStpu0000<MNUMnLSTZX&hGU9
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-contain/contain-layout-baseline-005.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[contain-layout-baseline-005.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-contain/contain-layout-grid-001.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[contain-layout-grid-001.html]
-  expected: FAIL
--- a/testing/web-platform/meta/css/motion/animation/offset-rotate-interpolation.html.ini
+++ b/testing/web-platform/meta/css/motion/animation/offset-rotate-interpolation.html.ini
@@ -1,12 +1,9 @@
 [offset-rotate-interpolation.html]
-  ["100deg" and "180deg" are valid offset-rotate values]
-    expected: FAIL
-
   [Animation between "100deg" and "180deg" at progress -1]
     expected: FAIL
 
   [Animation between "100deg" and "180deg" at progress 0]
     expected: FAIL
 
   [Animation between "100deg" and "180deg" at progress 0.125]
     expected: FAIL
@@ -15,19 +12,16 @@
     expected: FAIL
 
   [Animation between "100deg" and "180deg" at progress 1]
     expected: FAIL
 
   [Animation between "100deg" and "180deg" at progress 2]
     expected: FAIL
 
-  ["auto 100deg" and "reverse" are valid offset-rotate values]
-    expected: FAIL
-
   [Animation between "auto 100deg" and "reverse" at progress -1]
     expected: FAIL
 
   [Animation between "auto 100deg" and "reverse" at progress 0]
     expected: FAIL
 
   [Animation between "auto 100deg" and "reverse" at progress 0.125]
     expected: FAIL
@@ -36,19 +30,16 @@
     expected: FAIL
 
   [Animation between "auto 100deg" and "reverse" at progress 1]
     expected: FAIL
 
   [Animation between "auto 100deg" and "reverse" at progress 2]
     expected: FAIL
 
-  ["reverse 90deg" and "360deg" are valid offset-rotate values]
-    expected: FAIL
-
   [Animation between "reverse 90deg" and "360deg" at progress -1]
     expected: FAIL
 
   [Animation between "reverse 90deg" and "360deg" at progress 0]
     expected: FAIL
 
   [Animation between "reverse 90deg" and "360deg" at progress 0.125]
     expected: FAIL
@@ -57,29 +48,16 @@
     expected: FAIL
 
   [Animation between "reverse 90deg" and "360deg" at progress 1]
     expected: FAIL
 
   [Animation between "reverse 90deg" and "360deg" at progress 2]
     expected: FAIL
 
-  ["6rad" and "auto" are valid offset-rotate values]
-    expected: FAIL
-
   [Animation between "6rad" and "auto" at progress -1]
     expected: FAIL
 
   [Animation between "6rad" and "auto" at progress 0]
     expected: FAIL
 
   [Animation between "6rad" and "auto" at progress 0.125]
     expected: FAIL
-
-  [Animation between "6rad" and "auto" at progress 0.875]
-    expected: FAIL
-
-  [Animation between "6rad" and "auto" at progress 1]
-    expected: FAIL
-
-  [Animation between "6rad" and "auto" at progress 2]
-    expected: FAIL
-
--- a/testing/web-platform/meta/css/motion/offset-supports-calc.html.ini
+++ b/testing/web-platform/meta/css/motion/offset-supports-calc.html.ini
@@ -3,14 +3,11 @@
     expected: FAIL
 
   [offset-path supports calc]
     expected: FAIL
 
   [offset-distance supports calc]
     expected: FAIL
 
-  [offset-rotate supports calc]
-    expected: FAIL
-
   [offset-anchor supports calc]
     expected: FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/css/motion/parsing/offset-rotate-computed.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[offset-rotate-computed.html]
-  [Motion Path Module Level 1: getComputedValue().offsetRotate]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/css/motion/parsing/offset-rotate-parsing-valid.html.ini
+++ /dev/null
@@ -1,43 +0,0 @@
-[offset-rotate-parsing-valid.html]
-  [Serialization should round-trip after setting e.style['offset-rotate'\] = "5turn auto"]
-    expected: FAIL
-
-  [Serialization should round-trip after setting e.style['offset-rotate'\] = "0rad reverse"]
-    expected: FAIL
-
-  [e.style['offset-rotate'\] = "auto" should set the property value]
-    expected: FAIL
-
-  [Serialization should round-trip after setting e.style['offset-rotate'\] = "auto"]
-    expected: FAIL
-
-  [e.style['offset-rotate'\] = "reverse" should set the property value]
-    expected: FAIL
-
-  [Serialization should round-trip after setting e.style['offset-rotate'\] = "reverse"]
-    expected: FAIL
-
-  [e.style['offset-rotate'\] = "-400deg" should set the property value]
-    expected: FAIL
-
-  [Serialization should round-trip after setting e.style['offset-rotate'\] = "-400deg"]
-    expected: FAIL
-
-  [e.style['offset-rotate'\] = "auto 5turn" should set the property value]
-    expected: FAIL
-
-  [Serialization should round-trip after setting e.style['offset-rotate'\] = "auto 5turn"]
-    expected: FAIL
-
-  [e.style['offset-rotate'\] = "reverse 0rad" should set the property value]
-    expected: FAIL
-
-  [Serialization should round-trip after setting e.style['offset-rotate'\] = "reverse 0rad"]
-    expected: FAIL
-
-  [e.style['offset-rotate'\] = "5turn auto" should set the property value]
-    expected: FAIL
-
-  [e.style['offset-rotate'\] = "0rad reverse" should set the property value]
-    expected: FAIL
-
--- a/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-suppress-baseline-002.html.ini
+++ b/testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-suppress-baseline-002.html.ini
@@ -1,2 +1,3 @@
 [contain-layout-suppress-baseline-002.html]
-  expected: FAIL
+  expected:
+    if os == "win": FAIL
--- a/testing/web-platform/tests/css/css-contain/contain-layout-baseline-005.html
+++ b/testing/web-platform/tests/css/css-contain/contain-layout-baseline-005.html
@@ -8,16 +8,17 @@
 <style>
 .wrapper {
   height: 110px;
 }
 .wrapper > * {
   contain: layout;
   background: cyan;
   font-size: 20px;
+  vertical-align: baseline;
 }
 .wrapper > :nth-child(1) {
   background: magenta;
 }
 .inline-block {
   display: inline-block;
 }
 canvas {
@@ -44,17 +45,17 @@ fieldset, details {
   <div class="inline-block">foo</div>
   <button>foo</button>
   <select><option>foo</option></select>
   <select multiple style="height: 40px;"><option>foo</option></select>
   <textarea style="height: 40px;"></textarea>
 </div>
 <div class="wrapper">
   <canvas></canvas>
-  <input value="foo"></input>
+  <input value="foo" size="3"></input>
   <input type="file"></input>
 </div>
 <div class="wrapper">
   <canvas></canvas>
   <table style="display: inline-table;"><tr><td>foo</td></tr></table>
   <canvas></canvas>
   <fieldset></fieldset>
   <fieldset><legend>foo</legend></fieldset>
--- a/testing/web-platform/tests/css/css-contain/contain-layout-button-001.html
+++ b/testing/web-platform/tests/css/css-contain/contain-layout-button-001.html
@@ -1,20 +1,24 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>CSS Containment Test: Layout containment on button</title>
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <link rel="help" href="https://drafts.csswg.org/css-contain-1/#containment-layout">
+<link rel="help" href="https://drafts.csswg.org/css2/visudet.html#propdef-vertical-align">
 <link rel="match" href="reference/contain-layout-button-001-ref.html">
-<meta name=assert content="Layout containment does apply to buttons, thus their baseline is the same than if they don't have contents.">
+<meta name=assert content="Layout containment does apply to buttons, thus their baseline is their margin-bottom edge.">
 <style>
 button {
   border: 5px solid green;
   padding: 0;
+  /* We use a nonzero margin-bottom to be sure we're synthesizing a baseline
+     from the margin-box rather than from the border-box: */
+  margin-bottom: 2px;
   contain: layout;
   color: transparent;
   width: 0;
   height: 0;
 }
 </style>
 
-<p>This test passes if it has the same output as the reference. You see the word "before", a 10px green square at the bottom, and then the word "after".</p>
+<p>This test passes if it has the same output as the reference. You see the word "before", a 10px green square aligned 2px above the text's baseline, and then the word "after".</p>
 before<button>b</button>after
--- a/testing/web-platform/tests/css/css-contain/reference/contain-layout-baseline-005-ref.html
+++ b/testing/web-platform/tests/css/css-contain/reference/contain-layout-baseline-005-ref.html
@@ -41,17 +41,17 @@ fieldset, details {
   <div class="inline-block">foo</div>
   <button>foo</button>
   <select><option>foo</option></select>
   <select multiple style="height: 40px;"><option>foo</option></select>
   <textarea style="height: 40px;"></textarea>
 </div>
 <div class="wrapper">
   <canvas></canvas>
-  <input value="foo"></input>
+  <input value="foo" size="3"></input>
   <input type="file"></input>
 </div>
 <div class="wrapper">
   <canvas></canvas>
   <table style="display: inline-table;"><tr><td>foo</td></tr></table>
   <canvas></canvas>
   <fieldset></fieldset>
   <fieldset><legend>foo</legend></fieldset>
--- a/testing/web-platform/tests/css/css-contain/reference/contain-layout-button-001-ref.html
+++ b/testing/web-platform/tests/css/css-contain/reference/contain-layout-button-001-ref.html
@@ -1,19 +1,21 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>CSS Containment Test: Reference file</title>
 <link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
 <style>
-button {
+div.fakeButton {
+  display: inline-block;
   border: 5px solid green;
   padding: 0;
+  margin-bottom: 2px;
   color: transparent;
   width: 0;
   height: 0px;
   /* Layout containment creates a stacking context, the following lines simuluate the same in the reference file. */
   position: relative;
   z-index: 1;
 }
 </style>
 
-<p>This test passes if it has the same output as the reference. You see the word "before", a 10px green square at the bottom, and then the word "after".</p>
-before<button></button>after
+<p>This test passes if it has the same output as the reference. You see the word "before", a 10px green square aligned 2px above the text's baseline, and then the word "after".</p>
+before<div class="fakeButton"></div>after
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-lists/nested-marker-dynamic.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<title>::marker pseudo-elements generated by ::before and ::after are not addressable by selectors</title>
+<link rel="help" href="https://drafts.csswg.org/css-lists/#list-item">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1539171">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1543758">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="match" href="nested-marker-ref.html">
+<style>
+  li, ::marker {
+    color: red;
+  }
+  li::before, li::after {
+    display: list-item;
+    content: "Before";
+  }
+  li::after {
+    content: "After";
+  }
+  .tweak::marker {
+    color: blue;
+  }
+  .tweak, .tweak::before, .tweak::after {
+    color: initial;
+  }
+</style>
+<ol>
+  <li>Foo
+  <li>Bar
+<script>
+  window.onload = function() {
+    document.body.offsetTop;
+    for (let li of document.querySelectorAll("li"))
+      li.classList.add("tweak");
+  }
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-lists/nested-marker-ref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>CSS test reference</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<style>
+  li::marker {
+    color: blue;
+  }
+  div {
+    display: list-item;
+  }
+</style>
+<ol>
+  <li><div>Before</div>Foo<div>After</div>
+  <li><div>Before</div>Bar<div>After</div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-lists/nested-marker.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<title>::marker pseudo-elements generated by ::before and ::after are not addressable by global selectors</title>
+<link rel="help" href="https://drafts.csswg.org/css-lists/#list-item">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1539171">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1543758">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="match" href="nested-marker-ref.html">
+<style>
+  ::marker {
+    color: red;
+  }
+  li::marker {
+    color: blue;
+  }
+  li::before, li::after {
+    display: list-item;
+    content: "Before";
+  }
+  li::after {
+    content: "After";
+  }
+</style>
+<ol>
+  <li>Foo
+  <li>Bar
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/slotted-matches.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>CSS Test: ::slotted() should not match via the matches() API, since it's in the wrong scope</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#slotted-pseudo">
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-element-matches">
+<link rel="help" href="https://bugzil.la/1544242">
+<div id="host"><div id="slotted"></div></div>
+<script>
+test(function() {
+  let slotted = document.getElementById("slotted");
+  host.attachShadow({ mode: "open" }).innerHTML = `<slot></slot>`;
+  assert_false(slotted.matches("::slotted(div)"), "Shouldn't match ::slotted from the outer tree")
+}, "::slotted() doesn't reveal the presence of shadow DOM via matches()");
+</script>
--- a/testing/web-platform/tests/css/motion/inheritance.html
+++ b/testing/web-platform/tests/css/motion/inheritance.html
@@ -14,12 +14,13 @@
 <div id="container">
 <div id="target"></div>
 </div>
 <script>
 assert_not_inherited('offset-anchor', 'auto', '2px 3px');
 assert_not_inherited('offset-distance', '0px', '4px');
 assert_not_inherited('offset-path', 'none', 'path("M 5 6 H 7")');
 assert_not_inherited('offset-position', 'auto', '8px 9px');
-assert_not_inherited('offset-rotate', 'auto 0deg', '90deg');
+// https://github.com/w3c/fxtf-drafts/issues/340
+assert_not_inherited('offset-rotate', ['auto 0deg', 'auto'], '90deg');
 </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/motion/offset-rotate-003.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Motion Path: offset-rotate</title>
+    <link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-rotate-property">
+    <link rel="match" href="offset-rotate-ref.html">
+    <meta name="assert" content="This tests offset-rotate <angle>">
+    <style>
+      #target {
+        position: absolute;
+        left: 300px;
+        top: 100px;
+        width: 300px;
+        height: 200px;
+        background-color: lime;
+        transform-origin: 0px 0px;
+        offset-path: path('m 0 0 v -200 -200') ;
+        offset-rotate: 30deg;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="target"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/motion/offset-rotate-004.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <title>CSS Motion Path: offset-rotate</title>
+    <link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-rotate-property">
+    <link rel="match" href="offset-rotate-ref.html">
+    <meta name="assert" content="This tests offset-rotate auto with path()">
+    <style>
+      #target {
+        position: absolute;
+        left: 300px;
+        top: 100px;
+        width: 300px;
+        height: 200px;
+        background-color: lime;
+        transform-origin: 0px 0px;
+        offset-rotate: auto;
+      }
+    </style>
+    <script>
+      function test() {
+        let target = document.getElementById('target');
+        // Get a path which has the same direction as "ray(120deg ...)"
+        let verticalMove = 100 * Math.tan(30 * Math.PI / 180);
+        target.style.offsetPath = `path("m 0 0 l 100 ${verticalMove}")`;
+        window.getComputedStyle(target).offsetPath;
+
+        window.requestAnimationFrame(function() {
+          document.documentElement.removeAttribute('class');
+        });
+      }
+    </script>
+  </head>
+  <body onload='test()'>
+    <div id="target"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/motion/offset-rotate-005.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <title>CSS Motion Path: offset-rotate</title>
+    <link rel="help" href="https://drafts.fxtf.org/motion-1/#offset-rotate-property">
+    <link rel="match" href="offset-rotate-ref.html">
+    <meta name="assert" content="This tests offset-rotate reverse <angle> with path()">
+    <style>
+      #target {
+        position: absolute;
+        left: 300px;
+        top: 100px;
+        width: 300px;
+        height: 200px;
+        background-color: lime;
+        transform-origin: 0px 0px;
+        offset-rotate: reverse 60deg;
+      }
+    </style>
+    <script>
+      function test() {
+        let target = document.getElementById('target');
+        // Get a path which has the same direction as "ray(-120deg ...)"
+        let verticalMove = 100 * Math.tan(30 * Math.PI / 180);
+        target.style.offsetPath = `path("m 0 0 l -100 ${verticalMove}")`;
+        window.getComputedStyle(target).offsetPath;
+
+        window.requestAnimationFrame(function() {
+          document.documentElement.removeAttribute('class');
+        });
+      }
+    </script>
+  </head>
+  <body onload='test()'>
+    <div id="target"></div>
+  </body>
+</html>
--- a/testing/web-platform/tests/css/motion/parsing/offset-rotate-computed.html
+++ b/testing/web-platform/tests/css/motion/parsing/offset-rotate-computed.html
@@ -8,16 +8,17 @@
 <meta name="assert" content="offset-rotate reverse is auto 180deg.">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/css/support/computed-testcommon.js"></script>
 </head>
 <body>
 <div id="target"></div>
 <script>
-test_computed_value("offset-rotate", "auto", "auto 0deg");
+// https://github.com/w3c/fxtf-drafts/issues/340
+test_computed_value("offset-rotate", "auto", ["auto 0deg", "auto"]);
 test_computed_value("offset-rotate", "reverse", "auto 180deg");
 test_computed_value("offset-rotate", "calc(90deg - 0.5turn - 300grad + 0rad)", "-360deg");
 test_computed_value("offset-rotate", "auto 5turn", "auto 1800deg");
 test_computed_value("offset-rotate", "reverse -50grad", "auto 135deg");
 </script>
 </body>
 </html>
--- a/testing/web-platform/tests/css/motion/parsing/offset-rotate-parsing-valid.html
+++ b/testing/web-platform/tests/css/motion/parsing/offset-rotate-parsing-valid.html
@@ -11,14 +11,16 @@
 <script src="/css/support/parsing-testcommon.js"></script>
 </head>
 <body>
 <script>
 test_valid_value("offset-rotate", "auto");
 test_valid_value("offset-rotate", "reverse");
 test_valid_value("offset-rotate", "-400deg");
 test_valid_value("offset-rotate", "auto 5turn");
-test_valid_value("offset-rotate", "reverse 0rad");
+// https://github.com/w3c/fxtf-drafts/issues/340
+test_valid_value("offset-rotate", "reverse 0rad", ["reverse 0rad", "reverse"]);
 test_valid_value("offset-rotate", "5turn auto", "auto 5turn");
-test_valid_value("offset-rotate", "0rad reverse", "reverse 0rad");
+// https://github.com/w3c/fxtf-drafts/issues/340
+test_valid_value("offset-rotate", "0rad reverse", ["reverse 0rad", "reverse"]);
 </script>
 </body>
 </html>
--- a/testing/web-platform/tests/css/support/computed-testcommon.js
+++ b/testing/web-platform/tests/css/support/computed-testcommon.js
@@ -1,28 +1,42 @@
 'use strict';
 
 /**
  * Create test that a CSS property computes to the expected value.
  * The document element #target is used to perform the test.
  *
  * @param {string} property  The name of the CSS property being tested.
  * @param {string} specified A specified value for the property.
- * @param {string} computed  The expected computed value. If omitted,
-                             defaults to specified.
+ * @param {string|array} computed  The expected computed value,
+ *                                 or an array of permitted computed value.
+ *                                 If omitted, defaults to specified.
  */
 function test_computed_value(property, specified, computed) {
   if (!computed)
     computed = specified;
+
+  let computedDesc = "'" + computed + "'";
+  if (Array.isArray(computed))
+    computedDesc = '[' + computed.map(e => "'" + e + "'").join(' or ') + ']';
+
   test(() => {
     const target = document.getElementById('target');
     if (!getComputedStyle(target)[property])
       return;
     target.style[property] = '';
     target.style[property] = specified;
-    assert_equals(getComputedStyle(target)[property], computed);
-    if (computed !== specified) {
+
+    let readValue = getComputedStyle(target)[property];
+    if (Array.isArray(computed)) {
+      assert_in_array(readValue, computed);
+    } else {
+      assert_equals(readValue, computed);
+    }
+    if (readValue !== specified) {
       target.style[property] = '';
-      target.style[property] = computed;
-      assert_equals(getComputedStyle(target)[property], computed, 'computed value should round-trip');
+      target.style[property] = readValue;
+      assert_equals(getComputedStyle(target)[property], readValue,
+                    'computed value should round-trip');
     }
-  }, "Property " + property + " value '" + specified + "' computes to '" + computed + "'");
+  }, "Property " + property + " value '" + specified + "' computes to " +
+     computedDesc);
 }
--- a/testing/web-platform/tests/css/support/inheritance-testcommon.js
+++ b/testing/web-platform/tests/css/support/inheritance-testcommon.js
@@ -1,32 +1,42 @@
 'use strict';
 
 (function() {
 
 function assert_initial(property, initial) {
+  let initialDesc = initial;
+  if (Array.isArray(initial))
+    initialDesc = '[' + initial.map(e => "'" + e + "'").join(' or ') + ']';
+
   test(() => {
     const target = document.getElementById('target');
     if (!getComputedStyle(target)[property])
       return;
     target.style[property] = 'initial';
-    assert_equals(getComputedStyle(target)[property], initial);
+    if (Array.isArray(initial)) {
+      assert_in_array(getComputedStyle(target)[property], initial);
+    } else {
+      assert_equals(getComputedStyle(target)[property], initial);
+    }
     target.style[property] = '';
-  }, 'Property ' + property + ' has initial value ' + initial);
+  }, 'Property ' + property + ' has initial value ' + initialDesc);
 }
 
 /**
  * Create tests that a CSS property inherits and has the given initial value.
  *
  * The current document must have an element #target within element #container.
  *
- * @param {string} property  The name of the CSS property being tested.
- * @param {string} initial   The computed value for 'initial'.
- * @param {string} other     An arbitrary value for the property that round
- *                           trips and is distinct from the initial value.
+ * @param {string}        property  The name of the CSS property being tested.
+ * @param {string|array}  initial   The computed value for 'initial' or a list
+ *                                  of acceptable computed value serializations.
+ * @param {string}        other     An arbitrary value for the property that
+ *                                  round trips and is distinct from the initial
+ *                                  value.
  */
 function assert_inherited(property, initial, other) {
   assert_initial(property, initial);
 
   test(() => {
     const container = document.getElementById('container');
     const target = document.getElementById('target');
     if (!getComputedStyle(target)[property])
@@ -49,20 +59,22 @@ function assert_inherited(property, init
 }
 
 /**
  * Create tests that a CSS property does not inherit, and that it has the
  * given initial value.
  *
  * The current document must have an element #target within element #container.
  *
- * @param {string} property  The name of the CSS property being tested.
- * @param {string} initial   The computed value for 'initial'.
- * @param {string} other     An arbitrary value for the property that round
- *                           trips and is distinct from the initial value.
+ * @param {string}        property  The name of the CSS property being tested.
+ * @param {string|array}  initial   The computed value for 'initial' or a list
+ *                                  of acceptable computed value serializations.
+ * @param {string}        other     An arbitrary value for the property that
+ *                                  round trips and is distinct from the initial
+ *                                  value.
  */
 function assert_not_inherited(property, initial, other) {
   assert_initial(property, initial);
 
   test(() => {
     const container = document.getElementById('container');
     const target = document.getElementById('target');
     if (!getComputedStyle(target)[property])
--- a/toolkit/actors/UAWidgetsChild.jsm
+++ b/toolkit/actors/UAWidgetsChild.jsm
@@ -53,16 +53,17 @@ class UAWidgetsChild extends ActorChild 
     let prefKeys = [];
     switch (aElement.localName) {
       case "video":
       case "audio":
         uri = "chrome://global/content/elements/videocontrols.js";
         widgetName = "VideoControlsWidget";
         prefKeys = [
           "media.videocontrols.picture-in-picture.video-toggle.enabled",
+          "media.videocontrols.picture-in-picture.video-toggle.always-show",
         ];
         break;
       case "input":
         uri = "chrome://global/content/elements/datetimebox.js";
         widgetName = "DateTimeBoxWidget";
         break;
       case "embed":
       case "object":
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/content/certviewer.html
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width" />
+    <meta http-equiv="Content-Security-Policy" content="default-src chrome:" />
+    <title>about:certificate</title>
+  </head>
+  <body>
+    <h1>Certificate Viewer</h1>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/jar.mn
@@ -0,0 +1,6 @@
+# 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/.
+
+toolkit.jar:
+  content/global/certviewer/certviewer.html                         (content/certviewer.html)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/certviewer/moz.build
@@ -0,0 +1,10 @@
+# -*- 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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+with Files('**'):
+    BUG_COMPONENT = ("Firefox", "Security")
\ No newline at end of file
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -1381,16 +1381,17 @@ let pendingExtensions = new Map();
  * This class is the main representation of an active WebExtension
  * in the main process.
  * @extends ExtensionData
  */
 class Extension extends ExtensionData {
   constructor(addonData, startupReason) {
     super(addonData.resourceURI);
 
+    this.startupStates = new Set();
     this.state = "Not started";
 
     this.sharedDataKeys = new Set();
 
     this.uuid = UUIDMap.get(addonData.id);
     this.instanceId = getUniqueId();
 
     this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`;
@@ -1487,16 +1488,34 @@ class Extension extends ExtensionData {
       this.policy.permissions = Array.from(this.permissions);
       this.policy.allowedOrigins = this.whiteListedHosts;
 
       this.cachePermissions();
     });
     /* eslint-enable mozilla/balanced-listeners */
   }
 
+  set state(startupState) {
+    this.startupStates.clear();
+    this.startupStates.add(startupState);
+  }
+
+  get state() {
+    return `${Array.from(this.startupStates).join(", ")}`;
+  }
+
+  async addStartupStatePromise(name, fn) {
+    this.startupStates.add(name);
+    try {
+      await fn();
+    } finally {
+      this.startupStates.delete(name);
+    }
+  }
+
   get restrictSchemes() {
     return !(this.isPrivileged && this.hasPermission("mozillaAddons"));
   }
 
   // Some helpful properties added elsewhere:
   /**
    * An object used to map between extension-visible tab ids and
    * native Tab object
@@ -1738,44 +1757,27 @@ class Extension extends ExtensionData {
     this.updateContentScripts();
   }
 
   updateContentScripts() {
     this.setSharedData("contentScripts", this.registeredContentScripts);
   }
 
   runManifest(manifest) {
-    let state = new Set();
-    let updateState = () => {
-      this.state = `Startup: Run manifest: ${Array.from(state)}`;
-    };
-
     let promises = [];
-    let addPromise = (name, promise) => {
-      if (promise) {
-        promises.push(promise);
-
-        state.add(name);
-        promise.finally(() => {
-          state.delete(name);
-          updateState();
-        });
-      }
+    let addPromise = (name, fn) => {
+      promises.push(this.addStartupStatePromise(name, fn));
     };
 
     for (let directive in manifest) {
       if (manifest[directive] !== null) {
-        addPromise(`manifest_${directive}`,
-                   Management.emit(`manifest_${directive}`, directive, this, manifest));
-
         addPromise(`asyncEmitManifestEntry("${directive}")`,
-                   Management.asyncEmitManifestEntry(this, directive));
+                   () => Management.asyncEmitManifestEntry(this, directive));
       }
     }
-    updateState();
 
     activeExtensionIDs.add(this.id);
     sharedData.set("extensions/activeIDs", activeExtensionIDs);
 
     pendingExtensions.delete(this.id);
     sharedData.set("extensions/pending", pendingExtensions);
 
     Services.ppmm.sharedData.flush();
@@ -1968,27 +1970,22 @@ class Extension extends ExtensionData {
       resolveReadyPromise(this.policy);
 
       // The "startup" Management event sent on the extension instance itself
       // is emitted just before the Management "startup" event,
       // and it is used to run code that needs to be executed before
       // any of the "startup" listeners.
       this.emit("startup", this);
 
-      let state = new Set(["Emit startup", "Run manifest"]);
-      this.state = `Startup: ${Array.from(state)}`;
+      this.startupStates.clear();
       await Promise.all([
-        Management.emit("startup", this).finally(() => {
-          state.delete("Emit startup");
-          this.state = `Startup: ${Array.from(state)}`;
-        }),
-        this.runManifest(this.manifest).finally(() => {
-          state.delete("Run manifest");
-          this.state = `Startup: ${Array.from(state)}`;
-        }),
+        this.addStartupStatePromise("Startup: Emit startup",
+                                    () => Management.emit("startup", this)),
+        this.addStartupStatePromise("Startup: Run manifest",
+                                    () => this.runManifest(this.manifest)),
       ]);
       this.state = "Startup: Ran manifest";
 
       Management.emit("ready", this);
       this.emit("ready");
 
       this.state = "Startup: Complete";
     } catch (errors) {
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -18,16 +18,17 @@ DIRS += [
     'aboutperformance',
     'alerts',
     'antitracking',
     'apppicker',
     'asyncshutdown',
     'backgroundhangmonitor',
     'bitsdownload',
     'browser',
+    'certviewer',
     'cleardata',
     'clearsitedata',
     'cloudstorage',
     'commandlines',
     'contentprefs',
     'contextualidentity',
     'crashes',
     'crashmonitor',
--- a/toolkit/components/normandy/actions/PreferenceExperimentAction.jsm
+++ b/toolkit/components/normandy/actions/PreferenceExperimentAction.jsm
@@ -73,16 +73,17 @@ class PreferenceExperimentAction extends
         return;
       }
 
       // Otherwise, enroll!
       const branch = await this.chooseBranch(slug, branches);
       const experimentType = isHighPopulation ? "exp-highpop" : "exp";
       await PreferenceExperiments.start({
         name: slug,
+        actionName: this.name,
         branch: branch.slug,
         preferences: branch.preferences,
         experimentType,
       });
     } else {
       // If the experiment exists, and isn't expired, bump the lastSeen date.
       const experiment = await PreferenceExperiments.get(slug);
       if (experiment.expired) {
@@ -113,16 +114,22 @@ class PreferenceExperimentAction extends
    * End any experiments which we didn't see during this session.
    * This is the "normal" way experiments end, as they are disabled on
    * the server and so we stop seeing them.  This can also happen if
    * the user doesn't match the filter any more.
    */
   async _finalize() {
     const activeExperiments = await PreferenceExperiments.getAllActive();
     return Promise.all(activeExperiments.map(experiment => {
+      if (this.name != experiment.action) {
+        // Another action is responsible for cleaning this one
+        // up. Leave it alone.
+        return null;
+      }
+
       if (this.seenExperimentNames.includes(experiment.name)) {
         return null;
       }
 
       return PreferenceExperiments.stop(experiment.name, {
         resetValue: true,
         reason: "recipe-not-seen",
       }).catch(e => {
--- a/toolkit/components/normandy/lib/PreferenceExperiments.jsm
+++ b/toolkit/components/normandy/lib/PreferenceExperiments.jsm
@@ -120,58 +120,83 @@ function ensureStorage() {
 /**
  * Migrate storage of experiments from old format (one preference per
  * experiment) to new format.
  *
  * This function is exported for testing purposes but should not be
  * called otherwise.
  */
 function migrateStorage(storage) {
-  if (storage.data.__version == 2) {
+  if (storage.data.__version == 3) {
     return;
   }
-  const newData = {
-    __version: 2,
-    experiments: {},
-  };
-  for (let [expName, experiment] of Object.entries(storage.data)) {
-    if (expName == "__version") {
-      continue;
+
+  // v1 doesn't have a __version; it's just experiments
+  const oldVersion = storage.data.__version || 1;
+
+  if (oldVersion == 1) {
+    // Add version field
+    storage.data = {
+      __version: 2,
+      experiments: storage.data,
+    };
+
+    // Migrate storage.data to multi-preference format
+    const oldExperiments = storage.data.experiments;
+    const v2Experiments = {};
+
+    for (let [expName, experiment] of Object.entries(oldExperiments)) {
+      if (expName == "__version") {
+        continue;
+      }
+
+      const {
+        name,
+        branch,
+        expired,
+        lastSeen,
+        preferenceName,
+        preferenceValue,
+        preferenceType,
+        previousPreferenceValue,
+        preferenceBranchType,
+        experimentType,
+      } = experiment;
+      const newExperiment = {
+        name,
+        branch,
+        expired,
+        lastSeen,
+        preferences: {
+          [preferenceName]: {
+            preferenceBranchType,
+            preferenceType,
+            preferenceValue,
+            previousPreferenceValue,
+          },
+        },
+        experimentType,
+      };
+      v2Experiments[expName] = newExperiment;
+    }
+    storage.data.experiments = v2Experiments;
+  }
+  if (oldVersion <= 2) {
+    // Add "actionName" field for experiments that don't have it
+    for (const experiment of Object.values(storage.data.experiments)) {
+      if (!experiment.actionName) {
+        // Assume SinglePreferenceExperimentAction because as of this
+        // writing, no multi-pref experiment recipe has launched.
+        experiment.actionName = "SinglePreferenceExperimentAction";
+      }
     }
 
-    const {
-      name,
-      branch,
-      expired,
-      lastSeen,
-      preferenceName,
-      preferenceValue,
-      preferenceType,
-      previousPreferenceValue,
-      preferenceBranchType,
-      experimentType,
-    } = experiment;
-    const newExperiment = {
-      name,
-      branch,
-      expired,
-      lastSeen,
-      preferences: {
-        [preferenceName]: {
-          preferenceBranchType,
-          preferenceType,
-          preferenceValue,
-          previousPreferenceValue,
-        },
-      },
-      experimentType,
-    };
-    newData.experiments[expName] = newExperiment;
+    // Bump version
+    storage.data.__version = 3;
   }
-  storage.data = newData;
 }
 
 const log = LogManager.getLogger("preference-experiments");
 
 // List of active preference observers. Cleaned up on shutdown.
 let experimentObservers = new Map();
 CleanupManager.addCleanupHandler(() => PreferenceExperiments.stopAllObservers());
 
@@ -372,27 +397,31 @@ var PreferenceExperiments = {
     };
     store.saveSoon();
   },
 
   /**
    * Start a new preference experiment.
    * @param {Object} experiment
    * @param {string} experiment.name
+   * @param {string} experiment.actionName  The action who knows about this
+   *   experiment and is responsible for cleaning it up. This should
+   *   correspond to the name of some BaseAction subclass.
    * @param {string} experiment.branch
    * @param {string} experiment.preferenceName
    * @param {string|integer|boolean} experiment.preferenceValue
    * @param {PreferenceBranchType} experiment.preferenceBranchType
    * @rejects {Error}
    *   - If an experiment with the given name already exists
    *   - if an experiment for the given preference is active
    *   - If the given preferenceType does not match the existing stored preference
    */
   async start({
     name,
+    actionName,
     branch,
     preferences,
     experimentType = "exp",
     userFacingName = null,
     userFacingDescription = null,
   }) {
     log.debug(`PreferenceExperiments.start(${name}, ${branch})`);
 
@@ -457,16 +486,17 @@ var PreferenceExperiments = {
       const preferenceBranch = PreferenceBranchType[preferenceBranchType];
       setPref(preferenceBranch, preferenceName, preferenceType, preferenceValue);
     }
     PreferenceExperiments.startObserver(name, preferences);
 
     /** @type {Experiment} */
     const experiment = {
       name,
+      actionName,
       branch,
       expired: false,
       lastSeen: new Date().toJSON(),
       preferences,
       experimentType,
       userFacingName,
       userFacingDescription,
     };
--- a/toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js
+++ b/toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js
@@ -32,17 +32,17 @@ function experimentFactory(attrs) {
     expired: false,
     lastSeen: new Date().toJSON(),
     experimentType: "exp",
   }, attrs, {
     preferences,
   });
 }
 
-const mockOldData = {
+const mockV1Data = {
   "hypothetical_experiment": {
     name: "hypothetical_experiment",
     branch: "hypo_1",
     expired: false,
     lastSeen: new Date().toJSON(),
     preferenceName: "some.pref",
     preferenceValue: 2,
     preferenceType: "integer",
@@ -59,70 +59,132 @@ const mockOldData = {
     preferenceValue: true,
     preferenceType: "boolean",
     previousPreferenceValue: false,
     preferenceBranchType: "default",
     experimentType: "exp",
   },
 };
 
+const mockV2Data = {
+  __version: 2,
+  experiments: {
+    "hypothetical_experiment": {
+      name: "hypothetical_experiment",
+      branch: "hypo_1",
+      expired: false,
+      lastSeen: mockV1Data.hypothetical_experiment.lastSeen,
+      preferences: {
+        "some.pref": {
+          preferenceValue: 2,
+          preferenceType: "integer",
+          previousPreferenceValue: 1,
+          preferenceBranchType: "user",
+        },
+      },
+      experimentType: "exp",
+    },
+    "another_experiment": {
+      name: "another_experiment",
+      branch: "another_4",
+      expired: true,
+      lastSeen: mockV1Data.another_experiment.lastSeen,
+      preferences: {
+        "another.pref": {
+          preferenceValue: true,
+          preferenceType: "boolean",
+          previousPreferenceValue: false,
+          preferenceBranchType: "default",
+        },
+      },
+      experimentType: "exp",
+    },
+  },
+};
+
+const MIGRATED_DATA = {
+  __version: 3,
+  experiments: {
+    "hypothetical_experiment": {
+      name: "hypothetical_experiment",
+      branch: "hypo_1",
+      actionName: "SinglePreferenceExperimentAction",
+      expired: false,
+      lastSeen: mockV1Data.hypothetical_experiment.lastSeen,
+      preferences: {
+        "some.pref": {
+          preferenceValue: 2,
+          preferenceType: "integer",
+          previousPreferenceValue: 1,
+          preferenceBranchType: "user",
+        },
+      },
+      experimentType: "exp",
+    },
+    "another_experiment": {
+      name: "another_experiment",
+      branch: "another_4",
+      actionName: "SinglePreferenceExperimentAction",
+      expired: true,
+      lastSeen: mockV1Data.another_experiment.lastSeen,
+      preferences: {
+        "another.pref": {
+          preferenceValue: true,
+          preferenceType: "boolean",
+          previousPreferenceValue: false,
+          preferenceBranchType: "default",
+        },
+      },
+      experimentType: "exp",
+    },
+  },
+};
+
 add_task(function migrateStorage_migrates_to_new_format() {
-  const mockJsonFile = {
+  let mockJsonFile = {
     // Deep clone the data in case migrateStorage mutates it.
-    data: JSON.parse(JSON.stringify(mockOldData)),
+    data: JSON.parse(JSON.stringify(mockV1Data)),
   };
   migrateStorage(mockJsonFile);
-  Assert.deepEqual(mockJsonFile.data, {
-    __version: 2,
-    experiments: {
-      "hypothetical_experiment": {
-        name: "hypothetical_experiment",
-        branch: "hypo_1",
-        expired: false,
-        lastSeen: mockOldData.hypothetical_experiment.lastSeen,
-        preferences: {
-          "some.pref": {
-            preferenceValue: 2,
-            preferenceType: "integer",
-            previousPreferenceValue: 1,
-            preferenceBranchType: "user",
-          },
-        },
-        experimentType: "exp",
-      },
-      "another_experiment": {
-        name: "another_experiment",
-        branch: "another_4",
-        expired: true,
-        lastSeen: mockOldData.another_experiment.lastSeen,
-        preferences: {
-          "another.pref": {
-            preferenceValue: true,
-            preferenceType: "boolean",
-            previousPreferenceValue: false,
-            preferenceBranchType: "default",
-          },
-        },
-        experimentType: "exp",
-      },
-    },
-  });
+  Assert.deepEqual(mockJsonFile.data, MIGRATED_DATA);
+
+  mockJsonFile = {
+    data: JSON.parse(JSON.stringify(mockV2Data)),
+  };
+  migrateStorage(mockJsonFile);
+  Assert.deepEqual(mockJsonFile.data, MIGRATED_DATA);
+});
+
+add_task(function migrateStorage_keeps_actionNames() {
+  let mockData = JSON.parse(JSON.stringify(mockV2Data));
+  mockData.experiments.another_experiment.actionName = "SomeOldAction";
+  const mockJsonFile = {
+    data: mockData,
+  };
+  // Output should be the same as MIGRATED_DATA, but preserving the action.
+  const migratedData = JSON.parse(JSON.stringify(MIGRATED_DATA));
+  migratedData.experiments.another_experiment.actionName = "SomeOldAction";
+
+  migrateStorage(mockJsonFile);
+  Assert.deepEqual(mockJsonFile.data, migratedData);
 });
 
 add_task(function migrateStorage_is_idempotent() {
-  const mockJsonFileOnce = {
-    data: JSON.parse(JSON.stringify(mockOldData)),
-  };
-  const mockJsonFileTwice = {
-    data: JSON.parse(JSON.stringify(mockOldData)),
-  };
-  migrateStorage(mockJsonFileOnce);
-  migrateStorage(mockJsonFileTwice);
-  migrateStorage(mockJsonFileTwice);
-  Assert.deepEqual(mockJsonFileOnce, mockJsonFileTwice);
+  for (const [name, mockOldData] of [["v1", mockV1Data], ["v2", mockV2Data]]) {
+    const mockJsonFileOnce = {
+      data: JSON.parse(JSON.stringify(mockOldData)),
+    };
+    const mockJsonFileTwice = {
+      data: JSON.parse(JSON.stringify(mockOldData)),
+    };
+    migrateStorage(mockJsonFileOnce);
+    migrateStorage(mockJsonFileTwice);
+    migrateStorage(mockJsonFileTwice);
+    Assert.deepEqual(mockJsonFileOnce, mockJsonFileTwice, "migrating data twice should be idempotent for " + name);
+  }
 });
 
 // clearAllExperimentStorage
 decorate_task(
   withMockExperiments([experimentFactory({ name: "test" })]),
   async function(experiments) {
     ok(await PreferenceExperiments.has("test"), "Mock experiment is detected.");
     await PreferenceExperiments.clearAllExperimentStorage();
@@ -136,16 +198,17 @@ decorate_task(
 // start should throw if an experiment with the given name already exists
 decorate_task(
   withMockExperiments([experimentFactory({ name: "test" })]),
   withSendEventStub,
   async function(experiments, sendEventStub) {
     await Assert.rejects(
       PreferenceExperiments.start({
         name: "test",
+        actionName: "SomeAction",
         branch: "branch",
         preferences: {
           "fake.preference": {
             preferenceValue: "value",
             preferenceType: "string",
             preferenceBranchType: "default",
           },
         },
@@ -164,16 +227,17 @@ decorate_task(
 // preferences are active
 decorate_task(
   withMockExperiments([experimentFactory({ name: "test", preferences: {"fake.preferenceinteger": {}} })]),
   withSendEventStub,
   async function(experiments, sendEventStub) {
     await Assert.rejects(
       PreferenceExperiments.start({
         name: "different",
+        actionName: "SomeAction",
         branch: "branch",
         preferences: {
           "fake.preference": {
             preferenceValue: "value",
             preferenceType: "string",
             preferenceBranchType: "default",
           },
           "fake.preferenceinteger": {
@@ -196,16 +260,17 @@ decorate_task(
 // start should throw if an invalid preferenceBranchType is given
 decorate_task(
   withMockExperiments(),
   withSendEventStub,
   async function(experiments, sendEventStub) {
     await Assert.rejects(
       PreferenceExperiments.start({
         name: "test",
+        actionName: "SomeAction",
         branch: "branch",
         preferences: {
           "fake.preference": {
             preferenceValue: "value",
             preferenceType: "string",
             preferenceBranchType: "invalid",
           },
         },
@@ -230,16 +295,17 @@ decorate_task(
   async function testStart(experiments, mockPreferences, startObserverStub, sendEventStub) {
     mockPreferences.set("fake.preference", "oldvalue", "default");
     mockPreferences.set("fake.preference", "uservalue", "user");
     mockPreferences.set("fake.preferenceinteger", 1, "default");
     mockPreferences.set("fake.preferenceinteger", 101, "user");
 
     const experiment = {
       name: "test",
+      actionName: "SomeAction",
       branch: "branch",
       preferences: {
         "fake.preference": {
           preferenceValue: "newvalue",
           preferenceBranchType: "default",
           preferenceType: "string",
         },
         "fake.preferenceinteger": {
@@ -319,16 +385,17 @@ decorate_task(
   withMockPreferences,
   withStub(PreferenceExperiments, "startObserver"),
   async function(experiments, mockPreferences, startObserver) {
     mockPreferences.set("fake.preference", "olddefaultvalue", "default");
     mockPreferences.set("fake.preference", "oldvalue", "user");
 
     const experiment = {
       name: "test",
+      actionName: "SomeAction",
       branch: "branch",
       preferences: {
         "fake.preference": {
           preferenceValue: "newvalue",
           preferenceType: "string",
           preferenceBranchType: "user",
         },
       },
@@ -373,16 +440,17 @@ decorate_task(
   withMockPreferences,
   withSendEventStub,
   async function(mockPreferences, sendEventStub) {
     mockPreferences.set("fake.type_preference", "oldvalue");
 
     await Assert.rejects(
       PreferenceExperiments.start({
         name: "test",
+        actionName: "SomeAction",
         branch: "branch",
         preferences: {
           "fake.type_preference": {
             preferenceBranchType: "user",
             preferenceValue: 12345,
             preferenceType: "integer",
           },
         },
@@ -935,16 +1003,17 @@ decorate_task(
 decorate_task(
   withMockExperiments(),
   withStub(TelemetryEnvironment, "setExperimentActive"),
   withStub(TelemetryEnvironment, "setExperimentInactive"),
   withSendEventStub,
   async function testStartAndStopTelemetry(experiments, setActiveStub, setInactiveStub, sendEventStub) {
     await PreferenceExperiments.start({
       name: "test",
+      actionName: "SomeAction",
       branch: "branch",
       preferences: {
         "fake.preference": {
           preferenceValue: "value",
           preferenceType: "string",
           preferenceBranchType: "default",
         },
       },
@@ -978,16 +1047,17 @@ decorate_task(
 decorate_task(
   withMockExperiments(),
   withStub(TelemetryEnvironment, "setExperimentActive"),
   withStub(TelemetryEnvironment, "setExperimentInactive"),
   withSendEventStub,
   async function testInitTelemetryExperimentType(experiments, setActiveStub, setInactiveStub, sendEventStub) {
     await PreferenceExperiments.start({
       name: "test",
+      actionName: "SomeAction",
       branch: "branch",
       preferences: {
         "fake.preference": {
           preferenceValue: "value",
           preferenceType: "string",
           preferenceBranchType: "default",
         },
       },
@@ -1215,16 +1285,17 @@ decorate_task(
   withStub(PreferenceExperiments, "stopObserver"),
   async function testDefaultBranchStop(mockExperiments, mockPreferences) {
     const prefName = "fake.preference";
     mockPreferences.set(prefName, "old version's value", "default");
 
     // start an experiment
     await PreferenceExperiments.start({
       name: "test",
+      actionName: "SomeAction",
       branch: "branch",
       preferences: {
         [prefName]: {
           preferenceValue: "experiment value",
           preferenceBranchType: "default",
           preferenceType: "string",
         },
       },
@@ -1266,16 +1337,17 @@ decorate_task(
   withStub(PreferenceExperiments, "stopObserver"),
   async function testDefaultBranchStop(mockExperiments, mockPreferences) {
     const prefName = "fake.preference";
     mockPreferences.set(prefName, "old version's value", "default");
 
     // start an experiment
     await PreferenceExperiments.start({
       name: "test",
+      actionName: "SomeAction",
       branch: "branch",
       preferences: {
         [prefName]: {
           preferenceValue: "experiment value",
           preferenceBranchType: "default",
           preferenceType: "string",
         },
       },
--- a/toolkit/components/normandy/test/browser/browser_actions_PreferenceExperimentAction.js
+++ b/toolkit/components/normandy/test/browser/browser_actions_PreferenceExperimentAction.js
@@ -128,16 +128,17 @@ decorate_task(
       return branches[0];
     });
 
     await action.runRecipe(recipe);
     await action.finalize();
 
     Assert.deepEqual(startStub.args, [[{
       name: "test",
+      actionName: "PreferenceExperimentAction",
       branch: "branch1",
       preferences: {
         "fake.preference": {
           preferenceValue: "branch1",
           preferenceBranchType: "user",
           preferenceType: "string",
         },
       },
@@ -191,34 +192,53 @@ decorate_task(
 
     Assert.deepEqual(startStub.args, [], "start was not called");
   }
 );
 
 decorate_task(
   withStub(PreferenceExperiments, "stop"),
   PreferenceExperiments.withMockExperiments([
-    {name: "seen", expired: false},
-    {name: "unseen", expired: false},
+    {name: "seen", expired: false, action: "PreferenceExperimentAction"},
+    {name: "unseen", expired: false, action: "PreferenceExperimentAction"},
   ]),
   async function stop_experiments_not_seen(stopStub) {
     const action = new PreferenceExperimentAction();
     const recipe = preferenceExperimentFactory({
       slug: "seen",
     });
 
     await action.runRecipe(recipe);
     await action.finalize();
 
     Assert.deepEqual(stopStub.args,
                      [["unseen", {resetValue: true, reason: "recipe-not-seen"}]]);
   }
 );
 
 decorate_task(
+  withStub(PreferenceExperiments, "stop"),
+  PreferenceExperiments.withMockExperiments([
+    {name: "seen", expired: false, action: "SinglePreferenceExperimentAction"},
+    {name: "unseen", expired: false, action: "SinglePreferenceExperimentAction"},
+  ]),
+  async function dont_stop_experiments_for_other_action(stopStub) {
+    const action = new PreferenceExperimentAction();
+    const recipe = preferenceExperimentFactory({
+      slug: "seen",
+    });
+
+    await action.runRecipe(recipe);
+    await action.finalize();
+
+    Assert.deepEqual(stopStub.args, [], "stop not called for other action's experiments");
+  }
+);
+
+decorate_task(
   withStub(PreferenceExperiments, "start"),
   withStub(Uptake, "reportRecipe"),
   PreferenceExperiments.withMockExperiments([
     {
       name: "conflict",
       preferences: {
         "conflict.pref": {},
       },
--- a/toolkit/components/pictureinpicture/tests/browser.ini
+++ b/toolkit/components/pictureinpicture/tests/browser.ini
@@ -8,16 +8,17 @@ support-files =
   test-transparent-overlay-1.html
   test-transparent-overlay-2.html
   test-video.mp4
 
 prefs =
   media.videocontrols.picture-in-picture.enabled=true
   media.videocontrols.picture-in-picture.video-toggle.enabled=true
   media.videocontrols.picture-in-picture.video-toggle.testing=true
+  media.videocontrols.picture-in-picture.video-toggle.always-show=true
 
 [browser_cannotTriggerFromContent.js]
 [browser_contextMenu.js]
 [browser_closeTab.js]
 [browser_fullscreen.js]
 [browser_mouseButtonVariation.js]
 skip-if = debug
 [browser_rerequestPiP.js]
--- a/toolkit/content/license.html
+++ b/toolkit/content/license.html
@@ -70,16 +70,17 @@
       <li><a href="about:license#apache">Apache License 2.0</a></li>
       <li><a href="about:license#apache-llvm">Apache License 2.0 with LLVM exception</a></li>
       <li><a href="about:license#apple">Apple License</a></li>
       <li><a href="about:license#apple-mozilla">Apple/Mozilla NPRuntime License</a></li>
       <li><a href="about:license#arm">ARM License</a></li>
       <li><a href="about:license#babel">Babel License</a></li>
       <li><a href="about:license#babylon">Babylon License</a></li>
       <li><a href="about:license#bincode">bincode License</a></li>
+      <li><a href="about:license#bsd2clause">BSD-2-Clause License</a></li>
       <li><a href="about:license#bspatch">bspatch License</a></li>
       <li><a href="about:license#byteorder">byteorder License</a></li>
       <li><a href="about:license#cairo">Cairo Component Licenses</a></li>
       <li><a href="about:license#chromium">Chromium License</a></li>
       <li><a href="about:license#codemirror">CodeMirror License</a></li>
       <li><a href="about:license#cubic-bezier">cubic-bezier License</a></li>
       <li><a href="about:license#d3">D3 License</a></li>
       <li><a href="about:license#dagre-d3">Dagre-D3 License</a></li>
@@ -132,16 +133,17 @@
       <li><a href="about:license#myspell">MySpell License</a></li>
       <li><a href="about:license#naturalSort">naturalSort License</a></li>
       <li><a href="about:license#nicer">nICEr License</a></li>
       <li><a href="about:license#node-md5">node-md5 License</a></li>
       <li><a href="about:license#node-properties">node-properties License</a></li>
       <li><a href="about:license#nom">nom License</a></li>
       <li><a href="about:license#nrappkit">nrappkit License</a></li>
       <li><a href="about:license#openaes">OpenAES License</a></li>
+      <li><a href="about:license#openldap">OpenLDAP Public License</a></li>
       <li><a href="about:license#openvision">OpenVision License</a></li>
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
       <li><a href="about:license#openvr">OpenVR License</a></li>
 #endif
       <li><a href="about:license#ordered-float">ordered-float License</a></li>
       <li><a href="about:license#owning_ref">owning_ref License</a></li>
       <li><a href="about:license#pbkdf2-sha256">pbkdf2_sha256 License</a></li>
       <li><a href="about:license#phf">phf, phf_codegen, phf_generator, phf_shared License</a></li>
@@ -2889,16 +2891,54 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE F
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
 </pre>
 
 
     <hr>
 
+    <h1><a id="bsd2clause"></a>BSD-2-Clause License</h1>
+
+    <p>This license applies to files in the following directories:
+    <ul>
+        <li><code>third_party/rust/arrayref</code></li>
+        <li><code>third_party/rust/Inflector</code></li>
+    </ul>
+    See the individual LICENSE files for copyright owners.</p>
+
+<pre>
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the
+   distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+    <hr>
+
     <h1><a id="bspatch"></a>bspatch License</h1>
 
     <p>This license applies to the files
     <code>toolkit/mozapps/update/updater/bspatch/bspatch.cpp</code> and
     <code>toolkit/mozapps/update/updater/bspatch/bspatch.h</code>.
     </p>
 
 <pre>
@@ -4825,16 +4865,75 @@ INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
 </pre>
 
 
     <hr>
 
+    <h1><a id="openldap"></a>OpenLDAP Public License</h1>
+
+    <p>This license applies to certain files in the directory
+    <code>third_party/rust/lmdb-rkv-sys/lmdb/libraries/liblmdb</code>.
+    </p>
+
+<pre>
+The OpenLDAP Public License
+  Version 2.8, 17 August 2003
+
+Redistribution and use of this software and associated documentation
+("Software"), with or without modification, are permitted provided
+that the following conditions are met:
+
+1. Redistributions in source form must retain copyright statements
+   and notices,
+
+2. Redistributions in binary form must reproduce applicable copyright
+   statements and notices, this list of conditions, and the following
+   disclaimer in the documentation and/or other materials provided
+   with the distribution, and
+
+3. Redistributions must contain a verbatim copy of this document.
+
+The OpenLDAP Foundation may revise this license from time to time.
+Each revision is distinguished by a version number.  You may use
+this Software under terms of this license revision or under the
+terms of any subsequent revision of the license.
+
+THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
+CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
+SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
+OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+The names of the authors and copyright holders must not be used in
+advertising or otherwise to promote the sale, use or other dealing
+in this Software without specific, written prior permission.  Title
+to copyright in this Software shall at all times remain with copyright
+holders.
+
+OpenLDAP is a registered trademark of the OpenLDAP Foundation.
+
+Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
+California, USA.  All Rights Reserved.  Permission to copy and
+distribute verbatim copies of this document is granted.
+</pre>
+
+
+    <hr>
+
     <h1><a id="openvision"></a>OpenVision License</h1>
 
     <p>This license applies to the file
     <code>extensions/auth/gssapi.h</code>.</p>
 
 <pre>
 Copyright 1993 by OpenVision Technologies, Inc.
 
--- a/toolkit/content/widgets/videocontrols.js
+++ b/toolkit/content/widgets/videocontrols.js
@@ -85,16 +85,55 @@ this.VideoControlsWidget = class {
     this.impl.destructor();
     this.shadowRoot.firstChild.remove();
     delete this.impl;
   }
 
   static isPictureInPictureVideo(someVideo) {
     return someVideo.isCloningElementVisually;
   }
+
+  /**
+   * Returns true if a <video> meets the requirements to show the Picture-in-Picture
+   * toggle. Those requirements currently are:
+   *
+   * 1. The video must be 45 seconds in length or longer.
+   * 2. Neither the width or the height of the video can be less than 160px.
+   * 3. The video must have audio.
+   *
+   * This can be overridden via the
+   * media.videocontrols.picture-in-picture.video-toggle.always-show pref, which
+   * is mostly used for testing.
+   *
+   * @param {Object} prefs The preferences set that was passed to the UAWidget.
+   * @param {Element} someVideo The <video> to test.
+   * @return {Boolean}
+   */
+  static shouldShowPictureInPictureToggle(prefs, someVideo) {
+    if (prefs["media.videocontrols.picture-in-picture.video-toggle.always-show"]) {
+      return true;
+    }
+
+    const MIN_VIDEO_LENGTH = 45; // seconds
+    if (someVideo.duration < MIN_VIDEO_LENGTH) {
+      return false;
+    }
+
+    const MIN_VIDEO_DIMENSION = 160; // pixels
+    if (someVideo.videoWidth < MIN_VIDEO_DIMENSION ||
+        someVideo.videoHeight < MIN_VIDEO_DIMENSION) {
+      return false;
+    }
+
+    if (!someVideo.mozHasAudio) {
+      return false;
+    }
+
+    return true;
+  }
 };
 
 this.VideoControlsImplWidget = class {
   constructor(shadowRoot, prefs) {
     this.shadowRoot = shadowRoot;
     this.prefs = prefs;
     this.element = shadowRoot.host;
     this.document = this.element.ownerDocument;
@@ -245,16 +284,17 @@ this.VideoControlsImplWidget = class {
         // document might be, in which case we set this attribute to
         // apply any styles for the DOM fullscreen case.
         if (this.document.fullscreenElement) {
           this.videocontrols.setAttribute("inDOMFullscreen", true);
         }
 
         if (this.isAudioOnly) {
           this.startFadeOut(this.clickToPlay, true);
+          this.pictureInPictureToggleButton.setAttribute("hidden", true);
         }
 
         // If the first frame hasn't loaded, kick off a throbber fade-in.
         if (this.video.readyState >= this.video.HAVE_CURRENT_DATA) {
           this.firstFrameShown = true;
         }
 
         // We can't determine the exact buffering status, but do know if it's
@@ -272,20 +312,23 @@ this.VideoControlsImplWidget = class {
           this.startFadeOut(this.clickToPlay, true);
           this.statusIcon.setAttribute("type", "error");
           this.updateErrorText();
           this.setupStatusFader(true);
         } else if (VideoControlsWidget.isPictureInPictureVideo(this.video)) {
           this.setShowPictureInPictureMessage(true);
         }
 
-        if (!this.pipToggleEnabled ||
-            this.isShowingPictureInPictureMessage ||
-            this.isAudioOnly) {
-          this.pictureInPictureToggleButton.setAttribute("hidden", true);
+        if (this.video.readyState >= this.video.HAVE_METADATA) {
+          // According to the spec[1], at the HAVE_METADATA (or later) state, we know
+          // the video duration and dimensions, which means we can calculate whether or
+          // not to show the Picture-in-Picture toggle now.
+          //
+          // [1]: https://www.w3.org/TR/html50/embedded-content-0.html#dom-media-have_metadata
+          this.updatePictureInPictureToggleDisplay();
         }
 
         let adjustableControls = [
           ...this.prioritizedControls,
           this.controlBar,
           this.clickToPlay,
         ];
 
@@ -385,16 +428,30 @@ this.VideoControlsImplWidget = class {
         this.adjustControlSize();
 
         // Can only update the volume controls once we've computed
         // _volumeControlWidth, since the volume slider implementation
         // depends on it.
         this.updateVolumeControls();
       },
 
+      updatePictureInPictureToggleDisplay() {
+        if (this.isAudioOnly) {
+          return;
+        }
+
+        if (this.pipToggleEnabled &&
+            !this.isShowingPictureInPictureMessage &&
+            VideoControlsWidget.shouldShowPictureInPictureToggle(this.prefs, this.video)) {
+          this.pictureInPictureToggleButton.removeAttribute("hidden");
+        } else {
+          this.pictureInPictureToggleButton.setAttribute("hidden", true);
+        }
+      },
+
       setupNewLoadState() {
         // For videos with |autoplay| set, we'll leave the controls initially hidden,
         // so that they don't get in the way of the playing video. Otherwise we'll
         // go ahead and reveal the controls now, so they're an obvious user cue.
         var shouldShow = !this.dynamicControls ||
                          (this.video.paused &&
                          !this.video.autoplay);
         // Hide the overlay if the video time is non-zero or if an error occurred to workaround bug 718107.
@@ -539,16 +596,17 @@ this.VideoControlsImplWidget = class {
               this.setFullscreenButtonState();
             }
             this.showPosition(Math.round(this.video.currentTime * 1000), Math.round(this.video.duration * 1000));
             if (!this.isAudioOnly && !this.video.mozHasAudio) {
               this.muteButton.setAttribute("noAudio", "true");
               this.muteButton.setAttribute("disabled", "true");
             }
             this.adjustControlSize();
+            this.updatePictureInPictureToggleDisplay();
             break;
           case "loadeddata":
             this.firstFrameShown = true;
             this.setupStatusFader();
             break;
           case "loadstart":
             this.maxCurrentTimeSeen = 0;
             this.controlsSpacer.removeAttribute("aria-label");
@@ -2570,16 +2628,29 @@ this.NoControlsDesktopImplWidget = class
           case "fullscreenchange": {
             if (this.document.fullscreenElement) {
               this.videocontrols.setAttribute("inDOMFullscreen", true);
             } else {
               this.videocontrols.removeAttribute("inDOMFullscreen");
             }
             break;
           }
+          case "loadedmetadata": {
+            this.updatePictureInPictureToggleDisplay();
+            break;
+          }
+        }
+      },
+
+      updatePictureInPictureToggleDisplay() {
+        if (this.pipToggleEnabled &&
+            VideoControlsWidget.shouldShowPictureInPictureToggle(this.prefs, this.video)) {
+          this.pictureInPictureToggleButton.removeAttribute("hidden");
+        } else {
+          this.pictureInPictureToggleButton.setAttribute("hidden", true);
         }
       },
 
       init(shadowRoot, prefs) {
         this.shadowRoot = shadowRoot;
         this.prefs = prefs;
         this.video = shadowRoot.host;
         this.videocontrols = shadowRoot.firstChild;
@@ -2589,29 +2660,38 @@ this.NoControlsDesktopImplWidget = class
 
         this.pictureInPictureToggleButton =
           this.shadowRoot.getElementById("pictureInPictureToggleButton");
 
         if (this.document.fullscreenElement) {
           this.videocontrols.setAttribute("inDOMFullscreen", true);
         }
 
-        if (!this.pipToggleEnabled) {
-          this.pictureInPictureToggleButton.setAttribute("hidden", true);
+        if (this.video.readyState >= this.video.HAVE_METADATA) {
+          // According to the spec[1], at the HAVE_METADATA (or later) state, we know
+          // the video duration and dimensions, which means we can calculate whether or
+          // not to show the Picture-in-Picture toggle now.
+          //
+          // [1]: https://www.w3.org/TR/html50/embedded-content-0.html#dom-media-have_metadata
+          this.updatePictureInPictureToggleDisplay();
         }
 
         this.document.addEventListener("fullscreenchange", this, {
           capture: true,
         });
+
+        this.video.addEventListener("loadedmetadata", this);
       },
 
       terminate() {
         this.document.removeEventListener("fullscreenchange", this, {
           capture: true,
         });
+
+        this.video.removeEventListener("loadedmetadata", this);
       },
 
       get pipToggleEnabled() {
         return this.prefs["media.videocontrols.picture-in-picture.video-toggle.enabled"];
       },
     };
     this.Utils.init(this.shadowRoot, this.prefs);
   }
--- a/tools/update-verify/release/updates/verify.sh
+++ b/tools/update-verify/release/updates/verify.sh
@@ -13,17 +13,17 @@ create_cache
 ftp_server_to="http://stage.mozilla.org/pub/mozilla.org"
 ftp_server_from="http://stage.mozilla.org/pub/mozilla.org"
 aus_server="https://aus4.mozilla.org"
 to=""
 to_build_id=""
 to_app_version=""
 to_display_version=""
 override_certs=""
-diff_summary_log="$PWD/diff-summary.log"
+diff_summary_log=${DIFF_SUMMARY_LOG:-"$PWD/diff-summary.log"}
 if [ -e ${diff_summary_log} ]; then
   rm ${diff_summary_log}
 fi
 touch ${diff_summary_log}
 
 pushd `dirname $0` &>/dev/null
 MY_DIR=$(pwd)
 popd &>/dev/null
--- a/tools/update-verify/scripts/chunked-verify.py
+++ b/tools/update-verify/scripts/chunked-verify.py
@@ -28,16 +28,17 @@ if __name__ == "__main__":
     parser.set_defaults(
         chunks=None,
         thisChunk=None,
     )
     parser.add_argument("--verify-config", required=True, dest="verifyConfig")
     parser.add_argument("--verify-channel", required=True, dest="verify_channel")
     parser.add_argument("--chunks", required=True, dest="chunks", type=int)
     parser.add_argument("--this-chunk", required=True, dest="thisChunk", type=int)
+    parser.add_argument("--diff-summary", required=True, type=str)
 
     options = parser.parse_args()
     assert options.chunks and options.thisChunk, \
         "chunks and this-chunk are required"
     assert path.isfile(options.verifyConfig), "Update verify config must exist!"
     verifyConfigFile = options.verifyConfig
 
     fd, configFile = mkstemp()
@@ -48,12 +49,16 @@ if __name__ == "__main__":
         myVerifyConfig = verifyConfig.getChunk(
             options.chunks, options.thisChunk)
         # override the channel if explicitly set
         if options.verify_channel:
             myVerifyConfig.channel = options.verify_channel
         myVerifyConfig.write(fh)
         fh.close()
         run_cmd(["cat", configFile])
-        run_cmd(UPDATE_VERIFY_COMMAND + [configFile], cwd=UPDATE_VERIFY_DIR)
+        run_cmd(
+            UPDATE_VERIFY_COMMAND + [configFile],
+            cwd=UPDATE_VERIFY_DIR,
+            env={"DIFF_SUMMARY_LOG": path.abspath(options.diff_summary)},
+        )
     finally:
         if path.exists(configFile):
             os.unlink(configFile)
--- a/tools/update-verify/scripts/chunked-verify.sh
+++ b/tools/update-verify/scripts/chunked-verify.sh
@@ -16,17 +16,17 @@ VERIFY_CONFIG="$MOZ_FETCHES_DIR/update-v
 
 # release promotion
 if [ -n "$CHANNEL" ]; then
   EXTRA_PARAMS="--verify-channel $CHANNEL"
 else
   EXTRA_PARAMS=""
 fi
 $PYTHON $MY_DIR/chunked-verify.py --chunks $chunks --this-chunk $thisChunk \
---verify-config $VERIFY_CONFIG $EXTRA_PARAMS \
+--verify-config $VERIFY_CONFIG --diff-summary $PWD/diff-summary.log $EXTRA_PARAMS \
 2>&1 | tee $SCRIPTS_DIR/../verify_log.txt
 
 print_failed_msg()
 {
   echo "-------------------------"
   echo "This run has failed, see the above log"
   echo
   return 1
--- a/widget/android/jni/Refs.h
+++ b/widget/android/jni/Refs.h
@@ -705,16 +705,19 @@ class StringParam : public String::Ref {
 
  private:
   // Not null if we should delete ref on destruction.
   JNIEnv* const mEnv;
 
   static jstring GetString(JNIEnv* env, const nsAString& str) {
     const jstring result = env->NewString(
         reinterpret_cast<const jchar*>(str.BeginReading()), str.Length());
+    if (!result) {
+      NS_ABORT_OOM(str.Length() * sizeof(char16_t));
+    }
     MOZ_CATCH_JNI_EXCEPTION(env);
     return result;
   }
 
  public:
   MOZ_IMPLICIT StringParam(decltype(nullptr)) : Ref(nullptr), mEnv(nullptr) {}
 
   MOZ_IMPLICIT StringParam(const Ref& ref) : Ref(ref.Get()), mEnv(nullptr) {}