Merge mozilla-central to autoland
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 02 Jun 2017 14:33:40 +0200
changeset 410181 cee187e9cd8646d045ec33781027386952c9f683
parent 410180 1e4caa4e405acf1a7de8edca712c49f51eaeb31a (current diff)
parent 410117 2a8478029a0ca491fe914ffa619afc155b828b88 (diff)
child 410182 45cc52be6f7897c325b912bd0c4c55192dd991e9
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland
browser/themes/shared/panel-icons.svg
tools/profiler/gecko/ProfileGatherer.cpp
tools/profiler/gecko/ProfileGatherer.h
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -423,18 +423,18 @@ description#identity-popup-content-verif
 .identity-popup-permission-remove-button > .button-box {
   padding: 0;
 }
 
 .identity-popup-permission-remove-button > .button-box > .button-icon {
   margin: 0;
   width: 16px;
   height: 16px;
-  list-style-image: url(chrome://browser/skin/panel-icons.svg#cancel);
-  filter: url(chrome://global/skin/filters.svg#fill);
+  list-style-image: url(chrome://browser/skin/panel-icon-cancel.svg);
+  -moz-context-properties: fill;
   fill: graytext;
 }
 
 .identity-popup-permission-remove-button > .button-box > .button-text {
   display: none;
 }
 
 /* swap foreground / background colors on hover */
--- a/browser/themes/shared/downloads/allDownloadsViewOverlay.inc.css
+++ b/browser/themes/shared/downloads/allDownloadsViewOverlay.inc.css
@@ -88,17 +88,17 @@
   -moz-appearance: none;
   padding: 2px !important;
 }
 
 .downloadButton > .button-box > .button-icon {
   width: 16px;
   height: 16px;
   margin: 0;
-  filter: url("chrome://global/skin/filters.svg#fill");
+  -moz-context-properties: fill;
   fill: currentColor;
 }
 
 .downloadButton > .button-box > .button-text {
   display: none;
 }
 
 .downloadButton:hover > .button-box {
@@ -118,25 +118,25 @@
 @itemFocused@ > .downloadButtonArea > .downloadButton:hover:active > .button-box {
   background-color: -moz-field;
   color: -moz-fieldtext;
 }
 
 /*** Button icons ***/
 
 .downloadIconCancel > .button-box > .button-icon {
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#cancel");
+  list-style-image: url("chrome://browser/skin/panel-icon-cancel.svg");
 }
 
 .downloadIconShow > .button-box > .button-icon {
 %ifdef XP_MACOSX
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#magnifier");
+  list-style-image: url("chrome://browser/skin/panel-icon-magnifier.svg");
 %else
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#folder");
+  list-style-image: url("chrome://browser/skin/panel-icon-folder.svg");
 %endif
 }
 
 .downloadIconRetry > .button-box > .button-icon {
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#retry");
+  list-style-image: url("chrome://browser/skin/panel-icon-retry.svg");
 }
 
 /*** Progressmeter ***/
 %include progressmeter.inc.css
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -244,17 +244,17 @@ richlistitem[type="download"]:last-child
   padding: 0;
   color: inherit;
 }
 
 .downloadButton > .button-box > .button-icon {
   width: 16px;
   height: 16px;
   margin: 1px;
-  filter: url("chrome://global/skin/filters.svg#fill");
+  -moz-context-properties: fill;
   fill: currentColor;
 }
 
 .downloadButton > .button-box > .button-text {
   margin: 0 !important;
   padding: 0;
 }
 
@@ -282,37 +282,37 @@ richlistitem[type="download"]:last-child
 @item@[verdict="Malware"][showingsubview] {
   background-color: #aa1b08;
   color: white;
 }
 
 /*** Button icons ***/
 
 .downloadIconCancel > .button-box > .button-icon {
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#cancel");
+  list-style-image: url("chrome://browser/skin/panel-icon-cancel.svg");
 }
 
 .downloadIconShow > .button-box > .button-icon {
 %ifdef XP_MACOSX
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#magnifier");
+  list-style-image: url("chrome://browser/skin/panel-icon-magnifier.svg");
 %else
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#folder");
+  list-style-image: url("chrome://browser/skin/panel-icon-folder.svg");
 %endif
 }
 
 .downloadIconRetry > .button-box > .button-icon {
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#retry");
+  list-style-image: url("chrome://browser/skin/panel-icon-retry.svg");
 }
 
 .downloadShowBlockedInfo > .button-box > .button-icon {
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#arrow-right");
+  list-style-image: url("chrome://browser/skin/panel-icon-arrow-right.svg");
 }
 
 .downloadShowBlockedInfo > .button-box > .button-icon:-moz-locale-dir(rtl) {
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#arrow-left");
+  list-style-image: url("chrome://browser/skin/panel-icon-arrow-left.svg");
 }
 
 /*** Blocked subview ***/
 
 #downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews {
   /* When the main view is showing, the shadow on the left edge of the subview is
      barely visible on the right edge of the main view, so set it to none. */
   box-shadow: none;
@@ -320,21 +320,21 @@ richlistitem[type="download"]:last-child
 
 /* When the subview is showing, turn the download button into an arrow pointing
    back to the main view. */
 #downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadButton {
   color: HighlightText;
 }
 
 #downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadButton > .button-box > .button-icon {
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#arrow-left");
+  list-style-image: url("chrome://browser/skin/panel-icon-arrow-left.svg");
 }
 
 #downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadButton > .button-box > .button-icon:-moz-locale-dir(rtl) {
-  list-style-image: url("chrome://browser/skin/panel-icons.svg#arrow-right");
+  list-style-image: url("chrome://browser/skin/panel-icon-arrow-right.svg");
 }
 
 #downloadsPanel-blockedSubview {
   background-image: url("chrome://browser/skin/warning.svg");
   background-size: 32px 32px;
   background-position: 16px 16px;
   background-repeat: no-repeat;
 }
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -65,17 +65,25 @@
   skin/classic/browser/identity-icon-notice-hover.svg          (../shared/identity-block/identity-icon-notice-hover.svg)
   skin/classic/browser/info.svg                                (../shared/info.svg)
 * skin/classic/browser/menuPanel.svg                           (../shared/menuPanel.svg)
 * skin/classic/browser/menuPanel-small.svg                     (../shared/menuPanel-small.svg)
 * skin/classic/browser/notification-icons.svg                  (../shared/notification-icons.svg)
   skin/classic/browser/tracking-protection-16.svg              (../shared/identity-block/tracking-protection-16.svg)
   skin/classic/browser/newtab/close.png                        (../shared/newtab/close.png)
   skin/classic/browser/newtab/controls.svg                     (../shared/newtab/controls.svg)
-  skin/classic/browser/panel-icons.svg                         (../shared/panel-icons.svg)
+  skin/classic/browser/panel-icon-arrow-left.svg               (../shared/panel-icon-arrow-left.svg)
+  skin/classic/browser/panel-icon-arrow-right.svg              (../shared/panel-icon-arrow-right.svg)
+  skin/classic/browser/panel-icon-cancel.svg                   (../shared/panel-icon-cancel.svg)
+#ifndef XP_MACOSX
+  skin/classic/browser/panel-icon-folder.svg                   (../shared/panel-icon-folder.svg)
+#else
+  skin/classic/browser/panel-icon-magnifier.svg                (../shared/panel-icon-magnifier.svg)
+#endif
+  skin/classic/browser/panel-icon-retry.svg                    (../shared/panel-icon-retry.svg)
   skin/classic/browser/preferences/in-content-new/favicon.ico      (../shared/incontentprefs/favicon.ico)
   skin/classic/browser/preferences/in-content-new/icons.svg        (../shared/incontentprefs/icons.svg)
   skin/classic/browser/preferences/in-content-new/search.css       (../shared/incontentprefs/search.css)
   skin/classic/browser/preferences/in-content-new/siteDataSettings.css (../shared/incontentprefs/siteDataSettings.css)
 * skin/classic/browser/preferences/in-content-new/containers.css   (../shared/incontentprefs/containers.css)
   skin/classic/browser/preferences/in-content/favicon.ico      (../shared/incontentprefs-old/favicon.ico)
   skin/classic/browser/preferences/in-content/icons.svg        (../shared/incontentprefs-old/icons.svg)
   skin/classic/browser/preferences/in-content/search.css       (../shared/incontentprefs-old/search.css)
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/panel-icon-arrow-left.svg
@@ -0,0 +1,8 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="32" height="32" viewBox="0 0 32 32">
+  <path fill="context-fill" d="M23.5,25l-9-9l9-9l-3-3l-12,12l12,12L23.5,25z" />
+</svg>
+
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/panel-icon-arrow-right.svg
@@ -0,0 +1,8 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="32" height="32" viewBox="0 0 32 32">
+  <path fill="context-fill" d="M11.6,28l12-12l-12-12l-3,3l9,9l-9,9L11.6,28z" />
+</svg>
+
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/panel-icon-cancel.svg
@@ -0,0 +1,8 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="32" height="32" viewBox="0 0 32 32">
+  <path fill="context-fill" d="m 6,9.5 6.5,6.5 -6.5,6.5 3.5,3.5 6.5,-6.5 6.5,6.5 3.5,-3.5 -6.5,-6.5 6.5,-6.5 -3.5,-3.5 -6.5,6.5 -6.5,-6.5 z" />
+</svg>
+
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/panel-icon-folder.svg
@@ -0,0 +1,8 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="32" height="32" viewBox="0 0 32 32">
+  <path fill="context-fill" d="M17.3,9.4c0,0,1.1,0,3.7,0c1.7,0,2,0,5.6,0c0.6,0,0.6,0,1.1,0V9.2c0-1.5-0.9-2.6-2-2.6h-5.8V6.3c0-0.6-1.5-2-2.8-2h-7.1 H7.6H4.9v2.4v2.4v2.2c2.8,0,8.5,0,8.5,0C16.4,11.3,17.3,9.4,17.3,9.4z M29,13c0-0.6-0.6-1.1-1.5-1.7l0,0c-0.2,0-0.6,0-0.9,0 c-2.8,0-3,0-4.8,0c-1.9,0-3.3,0-3.3,0s-1.5,2.4-3.7,2.4c0,0-6.5,0-9.1,0H5.4C3,13.7,3,15.9,3,15.9l1.1,9.7C4.1,27.1,5,28,6.5,28 h19.1c1.5,0,2.4-0.9,2.4-2.4L29,13.7l0,0l0,0C29,13.7,29,13,29,13z" />
+</svg>
+
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/panel-icon-magnifier.svg
@@ -0,0 +1,8 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="32" height="32" viewBox="0 0 32 32">
+  <path fill="context-fill" d="M12.9,2c6,0,11,5,11,11c0,2.2-0.6,4.2-1.8,6l7.2,7c0.8,0.8,0.8,2.4,0,3.2c-0.6,0.6-1.2,0.8-1.6,0.8s-1.2-0.2-1.6-0.6l-7-7 c-1.8,1.2-3.8,1.8-6,1.8c-6,0-11-5-11-11C2.1,7.2,6.9,2,12.9,2z M12.9,20c3.8,0,7-3.2,7-7s-3.2-7-7-7s-7,3.2-7,7S9.1,20,12.9,20z" />
+</svg>
+
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/panel-icon-retry.svg
@@ -0,0 +1,8 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="32" height="32" viewBox="0 0 32 32">
+  <path fill="context-fill" d="M28,16.5v-14l-5,4.8c-1.8-1.4-4.4-2.4-7-2.4c-6.4,0-11.8,5.2-11.8,11.8c0,6.4,5.2,11.8,11.8,11.8c3.4,0,6.2-1.4,8.2-3.6 l-3.4-3.4c-1.2,1.2-3,1.8-5,1.8c-3.6,0.2-6.8-2.8-6.8-6.8c0-3.8,3-7.2,7-7.2c1.4,0,2.6,0.4,3.6,1l-6,6.2H28z"/>
+</svg>
+
deleted file mode 100644
--- a/browser/themes/shared/panel-icons.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg"
-     width="32" height="32" viewBox="0 0 32 32">
-  <style>
-    path:not(:target) {
-      display: none;
-    }
-  </style>
-  <path id="cancel" d="m 6,9.5 6.5,6.5 -6.5,6.5 3.5,3.5 6.5,-6.5 6.5,6.5 3.5,-3.5 -6.5,-6.5 6.5,-6.5 -3.5,-3.5 -6.5,6.5 -6.5,-6.5 z" />
-  <path id="folder" d="M17.3,9.4c0,0,1.1,0,3.7,0c1.7,0,2,0,5.6,0c0.6,0,0.6,0,1.1,0V9.2c0-1.5-0.9-2.6-2-2.6h-5.8V6.3c0-0.6-1.5-2-2.8-2h-7.1 H7.6H4.9v2.4v2.4v2.2c2.8,0,8.5,0,8.5,0C16.4,11.3,17.3,9.4,17.3,9.4z M29,13c0-0.6-0.6-1.1-1.5-1.7l0,0c-0.2,0-0.6,0-0.9,0 c-2.8,0-3,0-4.8,0c-1.9,0-3.3,0-3.3,0s-1.5,2.4-3.7,2.4c0,0-6.5,0-9.1,0H5.4C3,13.7,3,15.9,3,15.9l1.1,9.7C4.1,27.1,5,28,6.5,28 h19.1c1.5,0,2.4-0.9,2.4-2.4L29,13.7l0,0l0,0C29,13.7,29,13,29,13z" />
-  <path id="magnifier" d="M12.9,2c6,0,11,5,11,11c0,2.2-0.6,4.2-1.8,6l7.2,7c0.8,0.8,0.8,2.4,0,3.2c-0.6,0.6-1.2,0.8-1.6,0.8s-1.2-0.2-1.6-0.6l-7-7 c-1.8,1.2-3.8,1.8-6,1.8c-6,0-11-5-11-11C2.1,7.2,6.9,2,12.9,2z M12.9,20c3.8,0,7-3.2,7-7s-3.2-7-7-7s-7,3.2-7,7S9.1,20,12.9,20z" />
-  <path id="retry" d="M28,16.5v-14l-5,4.8c-1.8-1.4-4.4-2.4-7-2.4c-6.4,0-11.8,5.2-11.8,11.8c0,6.4,5.2,11.8,11.8,11.8c3.4,0,6.2-1.4,8.2-3.6 l-3.4-3.4c-1.2,1.2-3,1.8-5,1.8c-3.6,0.2-6.8-2.8-6.8-6.8c0-3.8,3-7.2,7-7.2c1.4,0,2.6,0.4,3.6,1l-6,6.2H28z"/>
-  <path id="arrow-left" d="M23.5,25l-9-9l9-9l-3-3l-12,12l12,12L23.5,25z" />
-  <path id="arrow-right" d="M11.6,28l12-12l-12-12l-3,3l9,9l-9,9L11.6,28z" />
-</svg>
--- a/gfx/2d/Point.h
+++ b/gfx/2d/Point.h
@@ -9,16 +9,17 @@
 #include "mozilla/Attributes.h"
 #include "Types.h"
 #include "Coord.h"
 #include "BaseCoord.h"
 #include "BasePoint.h"
 #include "BasePoint3D.h"
 #include "BasePoint4D.h"
 #include "BaseSize.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/TypeTraits.h"
 
 #include <cmath>
 
 namespace mozilla {
 
 template <typename> struct IsPixel;
 
@@ -287,16 +288,17 @@ struct IntSizeTyped :
     return IntSizeTyped<units>(aSize.width, aSize.height);
   }
 
   IntSizeTyped<UnknownUnits> ToUnknownSize() const {
     return IntSizeTyped<UnknownUnits>(this->width, this->height);
   }
 };
 typedef IntSizeTyped<UnknownUnits> IntSize;
+typedef Maybe<IntSize> MaybeIntSize;
 
 template<class units, class F = Float>
 struct SizeTyped :
   public BaseSize< F, SizeTyped<units, F> >,
   public units {
   static_assert(IsPixel<units>::value,
                 "'units' must be a coordinate system tag");
 
--- a/gfx/layers/LayersTypes.h
+++ b/gfx/layers/LayersTypes.h
@@ -5,16 +5,17 @@
 
 #ifndef GFX_LAYERSTYPES_H
 #define GFX_LAYERSTYPES_H
 
 #include <stdint.h>                     // for uint32_t
 
 #include "Units.h"
 #include "mozilla/gfx/Point.h"          // for IntPoint
+#include "mozilla/Maybe.h"
 #include "mozilla/TypedEnumBits.h"
 #include "nsRegion.h"
 
 #include <stdio.h>            // FILE
 #include "mozilla/Logging.h"            // for PR_LOG
 
 #ifndef MOZ_LAYERS_HAVE_LOG
 #  define MOZ_LAYERS_HAVE_LOG
@@ -209,16 +210,18 @@ typedef gfx::Matrix4x4Typed<LayerPixel, 
 typedef gfx::Matrix4x4Typed<ParentLayerPixel, ParentLayerPixel> AsyncTransformComponentMatrix;
 typedef gfx::Matrix4x4Typed<CSSTransformedLayerPixel, ParentLayerPixel> AsyncTransformMatrix;
 
 typedef Array<gfx::Color, 4> BorderColors;
 typedef Array<LayerSize, 4> BorderCorners;
 typedef Array<LayerCoord, 4> BorderWidths;
 typedef Array<uint8_t, 4> BorderStyles;
 
+typedef Maybe<LayerRect> MaybeLayerRect;
+
 // This is used to communicate Layers across IPC channels. The Handle is valid
 // for layers in the same PLayerTransaction. Handles are created by ClientLayerManager,
 // and are cached in LayerTransactionParent on first use.
 class LayerHandle
 {
   friend struct IPC::ParamTraits<mozilla::layers::LayerHandle>;
 public:
   LayerHandle() : mHandle(0)
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -527,26 +527,36 @@ mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvWaitOnTransactionProcessed()
 {
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvFlushRendering()
 {
+  if (gfxVars::UseWebRender()) {
+    mWrBridge->FlushRendering(/* aSync */ true);
+    return IPC_OK();
+  }
+
   if (mCompositorScheduler->NeedsComposite()) {
     CancelCurrentCompositeTask();
     ForceComposeToTarget(nullptr);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvFlushRenderingAsync()
 {
+  if (gfxVars::UseWebRender()) {
+    mWrBridge->FlushRendering(/* aSync */ false);
+    return IPC_OK();
+  }
+
   return RecvFlushRendering();
 }
 
 mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvForcePresent()
 {
   // During the shutdown sequence mLayerManager may be null
   if (mLayerManager) {
--- a/gfx/layers/ipc/PWebRenderBridge.ipdl
+++ b/gfx/layers/ipc/PWebRenderBridge.ipdl
@@ -16,18 +16,18 @@ include protocol PTexture;
 
 using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h";
 using struct mozilla::layers::TextureInfo from "mozilla/layers/CompositorTypes.h";
 using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h";
 using mozilla::wr::ByteBuffer from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::FontKey from "mozilla/webrender/WebRenderTypes.h";
+using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h";
 using WrBuiltDisplayListDescriptor from "mozilla/webrender/webrender_ffi.h";
-using WrSize from "mozilla/webrender/webrender_ffi.h";
 using mozilla::layers::WebRenderScrollData from "mozilla/layers/WebRenderScrollData.h";
 
 namespace mozilla {
 namespace layers {
 
 sync protocol PWebRenderBridge
 {
   manager PCompositorBridge;
@@ -55,17 +55,18 @@ parent:
   async DPBegin(IntSize aSize);
   async DPEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
               WrSize aContentSize, ByteBuffer aDL, WrBuiltDisplayListDescriptor aDLDesc,
               WebRenderScrollData aScrollData);
   sync DPSyncEnd(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, uint64_t transactionId,
                  WrSize aContentSize, ByteBuffer aDL, WrBuiltDisplayListDescriptor aDLDesc,
                  WebRenderScrollData aScrollData);
   sync DPGetSnapshot(PTexture texture);
-  async AddExternalImageId(ExternalImageId aImageId, CompositableHandle aHandle);
+  async AddPipelineIdForAsyncCompositable(PipelineId aImageId, CompositableHandle aHandle);
+  async RemovePipelineIdForAsyncCompositable(PipelineId aPipelineId);
   async AddExternalImageIdForCompositable(ExternalImageId aImageId, CompositableHandle aHandle);
   async RemoveExternalImageId(ExternalImageId aImageId);
   async SetLayerObserverEpoch(uint64_t layerObserverEpoch);
   async ClearCachedResources();
   // Schedule a composite if one isn't already scheduled.
   async ForceComposite();
 
   // These correspond exactly to the equivalent APIs in PLayerTransaction -
--- a/gfx/layers/ipc/WebRenderMessages.ipdlh
+++ b/gfx/layers/ipc/WebRenderMessages.ipdlh
@@ -4,18 +4,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include LayersSurfaces;
 include LayersMessages;
 include protocol PTexture;
 
+using WrSize from "mozilla/webrender/webrender_ffi.h";
+using WrImageRendering from "mozilla/webrender/webrender_ffi.h";
+using WrMixBlendMode from "mozilla/webrender/webrender_ffi.h";
+using MaybeImageMask from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h";
 using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h";
+using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h";
+using mozilla::gfx::MaybeIntSize from "mozilla/gfx/Point.h";
+using mozilla::LayerPoint from "Units.h";
+using mozilla::layers::MaybeLayerRect from "mozilla/layers/LayersTypes.h";
+using class mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace layers {
 
 union OptionalTransform {
   Matrix4x4;
   void_t;
@@ -26,28 +35,34 @@ union OptionalOpacity {
   void_t;
 };
 
 struct OpAddExternalImage {
   ExternalImageId externalImageId;
   ImageKey key;
 };
 
-struct OpAddExternalVideoImage {
-  ExternalImageId externalImageId;
-  ImageKey[] keys;
-};
-
 struct OpAddCompositorAnimations {
   CompositorAnimations data;
   OptionalTransform transform;
   OptionalOpacity opacity;
 };
 
+struct OpUpdateAsyncImagePipeline {
+  PipelineId pipelineId;
+  LayerRect scBounds;
+  Matrix4x4 scTransform;
+  MaybeIntSize scaleToSize;
+  MaybeLayerRect clipRect;
+  MaybeImageMask mask;
+  WrImageRendering filter;
+  WrMixBlendMode mixBlendMode;
+};
+
 union WebRenderParentCommand {
   OpAddExternalImage;
-  OpAddExternalVideoImage;
+  OpUpdateAsyncImagePipeline;
   CompositableOperation;
   OpAddCompositorAnimations;
 };
 
 } // namespace
 } // namespace
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -115,35 +115,38 @@ WebRenderBridgeChild::DPEnd(wr::DisplayL
                     contentSize, dlData, dl.dl_desc, aScrollData);
   }
 
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 }
 
+void
+WebRenderBridgeChild::AddPipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId,
+                                                        const CompositableHandle& aHandle)
+{
+  SendAddPipelineIdForAsyncCompositable(aPipelineId, aHandle);
+}
+
+void
+WebRenderBridgeChild::RemovePipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId)
+{
+  SendRemovePipelineIdForAsyncCompositable(aPipelineId);
+}
+
 wr::ExternalImageId
 WebRenderBridgeChild::GetNextExternalImageId()
 {
   wr::MaybeExternalImageId id = GetCompositorBridgeChild()->GetNextExternalImageId();
   MOZ_RELEASE_ASSERT(id.isSome());
   return id.value();
 }
 
 wr::ExternalImageId
-WebRenderBridgeChild::AllocExternalImageId(const CompositableHandle& aHandle)
-{
-  MOZ_ASSERT(!mDestroyed);
-
-  wr::ExternalImageId imageId = GetNextExternalImageId();
-  SendAddExternalImageId(imageId, aHandle);
-  return imageId;
-}
-
-wr::ExternalImageId
 WebRenderBridgeChild::AllocExternalImageIdForCompositable(CompositableClient* aCompositable)
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(aCompositable->IsConnected());
 
   wr::ExternalImageId imageId = GetNextExternalImageId();
   SendAddExternalImageIdForCompositable(imageId, aCompositable->GetIPCHandle());
   return imageId;
--- a/gfx/layers/wr/WebRenderBridgeChild.h
+++ b/gfx/layers/wr/WebRenderBridgeChild.h
@@ -68,17 +68,20 @@ public:
   CompositorBridgeChild* GetCompositorBridgeChild();
 
   wr::PipelineId GetPipeline() { return mPipelineId; }
 
   // KnowsCompositor
   TextureForwarder* GetTextureForwarder() override;
   LayersIPCActor* GetLayersIPCActor() override;
 
-  wr::ExternalImageId AllocExternalImageId(const CompositableHandle& aHandle);
+  void AddPipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId,
+                                         const CompositableHandle& aHandlee);
+  void RemovePipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId);
+
   wr::ExternalImageId AllocExternalImageIdForCompositable(CompositableClient* aCompositable);
   void DeallocExternalImageId(wr::ExternalImageId& aImageId);
 
   /**
    * Clean this up, finishing with SendShutDown() which will cause __delete__
    * to be sent from the parent side.
    */
   void Destroy();
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -118,26 +118,29 @@ WebRenderBridgeParent::WebRenderBridgePa
   , mCompositableHolder(aHolder)
   , mCompositorScheduler(aScheduler)
   , mChildLayerObserverEpoch(0)
   , mParentLayerObserverEpoch(0)
   , mWrEpoch(0)
   , mIdNameSpace(AllocIdNameSpace())
   , mPaused(false)
   , mDestroyed(false)
-  , mIsSnapshotting(false)
+  , mForceRendering(false)
 {
   MOZ_ASSERT(mCompositableHolder);
   mCompositableHolder->AddPipeline(mPipelineId);
   if (mWidget) {
     MOZ_ASSERT(!mCompositorScheduler);
     mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget);
   }
 }
 
+WebRenderBridgeParent::~WebRenderBridgeParent()
+{
+}
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvCreate(const gfx::IntSize& aSize)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
 
@@ -449,88 +452,56 @@ WebRenderBridgeParent::ProcessWebRenderC
         MOZ_ASSERT(!mActiveKeys.Get(wr::AsUint64(keys[0]), nullptr));
         mActiveKeys.Put(wr::AsUint64(keys[0]), keys[0]);
 
         RefPtr<WebRenderImageHost> host = mExternalImageIds.Get(wr::AsUint64(op.externalImageId()));
         if (!host) {
           NS_ERROR("CompositableHost does not exist");
           break;
         }
-        // XXX select Texture for video in CompositeToTarget().
         TextureHost* texture = host->GetAsTextureHostForComposite();
         if (!texture) {
           NS_ERROR("TextureHost does not exist");
           break;
         }
         WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost();
         if (wrTexture) {
           wrTexture->AddWRImage(mApi, keys, wrTexture->GetExternalImageKey());
           break;
         }
         RefPtr<DataSourceSurface> dSurf = host->GetAsSurface();
         if (!dSurf) {
+          NS_ERROR("TextureHost does not return DataSourceSurface");
           break;
         }
 
         DataSourceSurface::MappedSurface map;
         if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+          NS_ERROR("DataSourceSurface failed to map");
           break;
         }
 
         IntSize size = dSurf->GetSize();
         wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
         auto slice = Range<uint8_t>(map.mData, size.height * map.mStride);
         mApi->AddImage(keys[0], descriptor, slice);
 
         dSurf->Unmap();
         break;
       }
-      case WebRenderParentCommand::TOpAddExternalVideoImage: {
-        const OpAddExternalVideoImage& op = cmd.get_OpAddExternalVideoImage();
-        MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(op.externalImageId())).get());
-        MOZ_ASSERT(op.keys().Length() > 0);
-        Range<const wr::ImageKey> keys(&(op.keys())[0], op.keys().Length());
-        for (auto key : keys) {
-          MOZ_ASSERT(!mActiveKeys.Get(wr::AsUint64(key), nullptr));
-          mActiveKeys.Put(wr::AsUint64(key), key);
-        }
-
-        RefPtr<WebRenderImageHost> host = mExternalImageIds.Get(wr::AsUint64(op.externalImageId()));
-        if (!host) {
-          NS_ERROR("CompositableHost does not exist");
-          break;
-        }
-        // XXX select Texture for video in CompositeToTarget().
-        TextureHost* texture = host->GetAsTextureHostForComposite();
-        if (!texture) {
-          NS_ERROR("TextureHost does not exist");
-          break;
-        }
-        WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost();
-        if (wrTexture) {
-          wrTexture->AddWRImage(mApi, keys, wrTexture->GetExternalImageKey());
-          break;
-        }
-
-        MOZ_ASSERT(keys.length() == 1);
-        RefPtr<DataSourceSurface> dSurf = host->GetAsSurface();
-        if (!dSurf) {
-          break;
-        }
-        DataSourceSurface::MappedSurface map;
-        if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
-          break;
-        }
-
-        IntSize size = dSurf->GetSize();
-        wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
-        auto slice = Range<uint8_t>(map.mData, size.height * map.mStride);
-        mApi->AddImage(keys[0], descriptor, slice);
-
-        dSurf->Unmap();
+      case WebRenderParentCommand::TOpUpdateAsyncImagePipeline: {
+        const OpUpdateAsyncImagePipeline& op = cmd.get_OpUpdateAsyncImagePipeline();
+        mCompositableHolder->UpdateAsyncImagePipeline(op.pipelineId(),
+                                                      op.scBounds(),
+                                                      op.scTransform(),
+                                                      op.scaleToSize(),
+                                                      op.clipRect(),
+                                                      op.mask(),
+                                                      op.filter(),
+                                                      op.mixBlendMode());
         break;
       }
       case WebRenderParentCommand::TCompositableOperation: {
         if (!ReceiveCompositableUpdate(cmd.get_CompositableOperation())) {
           NS_ERROR("ReceiveCompositableUpdate failed");
         }
         break;
       }
@@ -613,39 +584,39 @@ WebRenderBridgeParent::RecvDPGetSnapshot
   // We only support B8G8R8A8 for now.
   MOZ_ASSERT(buffer);
   MOZ_ASSERT(bufferTexture->GetFormat() == SurfaceFormat::B8G8R8A8);
   uint32_t buffer_size = size.width * size.height * 4;
 
   // Assert the stride of the buffer is what webrender expects
   MOZ_ASSERT((uint32_t)(size.width * 4) == stride);
 
-  mIsSnapshotting = true;
+  mForceRendering = true;
 
   if (mCompositorScheduler->NeedsComposite()) {
     mCompositorScheduler->CancelCurrentCompositeTask();
     mCompositorScheduler->ForceComposeToTarget(nullptr, nullptr);
   }
 
   mApi->Readback(size, buffer, buffer_size);
 
-  mIsSnapshotting = false;
+  mForceRendering = false;
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-WebRenderBridgeParent::RecvAddExternalImageId(const ExternalImageId& aImageId,
-                                              const CompositableHandle& aHandle)
+WebRenderBridgeParent::RecvAddPipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId,
+                                                             const CompositableHandle& aHandle)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
 
-  MOZ_ASSERT(!mExternalImageIds.Get(wr::AsUint64(aImageId)).get());
+  MOZ_ASSERT(!mAsyncCompositables.Get(wr::AsUint64(aPipelineId)).get());
 
   RefPtr<ImageBridgeParent> imageBridge = ImageBridgeParent::GetInstance(OtherPid());
   if (!imageBridge) {
      return IPC_FAIL_NO_REASON(this);
   }
   RefPtr<CompositableHost> host = imageBridge->FindCompositable(aHandle);
   if (!host) {
     NS_ERROR("CompositableHost not found in the map!");
@@ -654,18 +625,36 @@ WebRenderBridgeParent::RecvAddExternalIm
   MOZ_ASSERT(host->AsWebRenderImageHost());
   WebRenderImageHost* wrHost = host->AsWebRenderImageHost();
   if (!wrHost) {
     NS_ERROR("Incompatible CompositableHost");
     return IPC_OK();
   }
 
   wrHost->SetWrBridge(this);
-  mExternalImageIds.Put(wr::AsUint64(aImageId), wrHost);
+  mAsyncCompositables.Put(wr::AsUint64(aPipelineId), wrHost);
+  mCompositableHolder->AddAsyncImagePipeline(aPipelineId, wrHost);
+
+  return IPC_OK();
+}
 
+mozilla::ipc::IPCResult
+WebRenderBridgeParent::RecvRemovePipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId)
+{
+  if (mDestroyed) {
+    return IPC_OK();
+  }
+
+  MOZ_ASSERT(mAsyncCompositables.Get(wr::AsUint64(aPipelineId)).get());
+  WebRenderImageHost* wrHost = mAsyncCompositables.Get(wr::AsUint64(aPipelineId)).get();
+  if (wrHost) {
+    wrHost->ClearWrBridge();
+    mCompositableHolder->RemoveAsyncImagePipeline(mApi, aPipelineId);
+  }
+  mAsyncCompositables.Remove(wr::AsUint64(aPipelineId));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvAddExternalImageIdForCompositable(const ExternalImageId& aImageId,
                                                              const CompositableHandle& aHandle)
 {
   if (mDestroyed) {
@@ -817,27 +806,30 @@ void
 WebRenderBridgeParent::CompositeToTarget(gfx::DrawTarget* aTarget, const gfx::IntRect* aRect)
 {
   if (mPaused) {
     return;
   }
 
   const uint32_t maxPendingFrameCount = 2;
 
-  if (!mIsSnapshotting &&
+  if (!mForceRendering &&
       wr::RenderThread::Get()->GetPendingFrameCount(mApi->GetId()) > maxPendingFrameCount) {
     // Render thread is busy, try next time.
     ScheduleComposition();
     return;
   }
 
   bool scheduleComposite = false;
   nsTArray<WrOpacityProperty> opacityArray;
   nsTArray<WrTransformProperty> transformArray;
 
+  mCompositableHolder->SetCompositionTime(TimeStamp::Now());
+  mCompositableHolder->ApplyAsyncImages(mApi);
+
   if (gfxPrefs::WebRenderOMTAEnabled()) {
     SampleAnimations(opacityArray, transformArray);
 
     if (!transformArray.IsEmpty() || !opacityArray.IsEmpty()) {
       scheduleComposite = true;
     }
   }
 
@@ -846,20 +838,19 @@ WebRenderBridgeParent::CompositeToTarget
   }
 
   if (!transformArray.IsEmpty() || !opacityArray.IsEmpty()) {
     mApi->GenerateFrame(opacityArray, transformArray);
   } else {
     mApi->GenerateFrame();
   }
 
-  // XXX Enable it when async video is supported.
-  // if (!mCompositableHolder->GetCompositeUntilTime().IsNull()) {
-  //   scheduleComposite = true;
-  // }
+  if (!mCompositableHolder->GetCompositeUntilTime().IsNull()) {
+    scheduleComposite = true;
+  }
 
   if (scheduleComposite) {
     ScheduleComposition();
   }
 }
 
 void
 WebRenderBridgeParent::HoldPendingTransactionId(uint32_t aWrEpoch, uint64_t aTransactionId)
@@ -906,20 +897,16 @@ WebRenderBridgeParent::FlushTransactionI
       mPendingTransactionIds.pop();
       break;
     }
     mPendingTransactionIds.pop();
   }
   return id;
 }
 
-WebRenderBridgeParent::~WebRenderBridgeParent()
-{
-}
-
 uint64_t
 WebRenderBridgeParent::GetLayersId() const
 {
   return wr::AsUint64(mPipelineId);
 }
 
 void
 WebRenderBridgeParent::DeleteOldImages()
@@ -934,16 +921,36 @@ void
 WebRenderBridgeParent::ScheduleComposition()
 {
   if (mCompositorScheduler) {
     mCompositorScheduler->ScheduleComposition();
   }
 }
 
 void
+WebRenderBridgeParent::FlushRendering(bool aIsSync)
+{
+  if (mDestroyed) {
+    return;
+  }
+
+  if (!mCompositorScheduler->NeedsComposite()) {
+    return;
+  }
+
+  mForceRendering = true;
+  mCompositorScheduler->CancelCurrentCompositeTask();
+  mCompositorScheduler->ForceComposeToTarget(nullptr, nullptr);
+  if (aIsSync) {
+    mApi->WaitFlushed();
+  }
+  mForceRendering = false;
+}
+
+void
 WebRenderBridgeParent::Pause()
 {
   MOZ_ASSERT(mWidget);
 #ifdef MOZ_WIDGET_ANDROID
   if (!mWidget || mDestroyed) {
     return;
   }
   mApi->Pause();
@@ -983,16 +990,25 @@ WebRenderBridgeParent::ClearResources()
     mKeysToDelete.push_back(iter.Data());
     iter.Remove();
   }
   DeleteOldImages();
   for (auto iter = mExternalImageIds.Iter(); !iter.Done(); iter.Next()) {
     iter.Data()->ClearWrBridge();
   }
   mExternalImageIds.Clear();
+  for (auto iter = mAsyncCompositables.Iter(); !iter.Done(); iter.Next()) {
+    wr::PipelineId pipelineId = wr::AsPipelineId(iter.Key());
+    RefPtr<WebRenderImageHost> host = iter.Data();
+    MOZ_ASSERT(host->GetAsyncRef());
+    host->ClearWrBridge();
+    mCompositableHolder->RemoveAsyncImagePipeline(mApi, pipelineId);
+  }
+  mAsyncCompositables.Clear();
+
   mCompositableHolder->RemovePipeline(mPipelineId, wr::NewEpoch(mWrEpoch));
 
   if (mWidget) {
     mCompositorScheduler->Destroy();
   }
   mCompositorScheduler = nullptr;
   mApi = nullptr;
   mCompositorBridge = nullptr;
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -101,18 +101,20 @@ public:
                                         const uint64_t& aFwdTransactionId,
                                         const uint64_t& aTransactionId,
                                         const WrSize& aContentSize,
                                         const ByteBuffer& dl,
                                         const WrBuiltDisplayListDescriptor& dlDesc,
                                         const WebRenderScrollData& aScrollData) override;
   mozilla::ipc::IPCResult RecvDPGetSnapshot(PTextureParent* aTexture) override;
 
-  mozilla::ipc::IPCResult RecvAddExternalImageId(const ExternalImageId& aImageId,
-                                                 const CompositableHandle& aHandle) override;
+  mozilla::ipc::IPCResult RecvAddPipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineIds,
+                                                                const CompositableHandle& aHandle) override;
+  mozilla::ipc::IPCResult RecvRemovePipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId) override;
+
   mozilla::ipc::IPCResult RecvAddExternalImageIdForCompositable(const ExternalImageId& aImageId,
                                                                 const CompositableHandle& aHandle) override;
   mozilla::ipc::IPCResult RecvRemoveExternalImageId(const ExternalImageId& aImageId) override;
   mozilla::ipc::IPCResult RecvSetLayerObserverEpoch(const uint64_t& aLayerObserverEpoch) override;
 
   mozilla::ipc::IPCResult RecvClearCachedResources() override;
   mozilla::ipc::IPCResult RecvForceComposite() override;
 
@@ -172,28 +174,31 @@ public:
 
   void UpdateAPZ();
   const WebRenderScrollData& GetScrollData() const;
 
   static uint32_t AllocIdNameSpace() {
     return ++sIdNameSpace;
   }
 
+  void FlushRendering(bool aIsSync);
+
+  void ScheduleComposition();
+
 private:
   virtual ~WebRenderBridgeParent();
 
   uint64_t GetLayersId() const;
   void DeleteOldImages();
   void ProcessWebRenderCommands(const gfx::IntSize &aSize,
                                 InfallibleTArray<WebRenderParentCommand>& commands,
                                 const wr::Epoch& aEpoch,
                                 const WrSize& aContentSize,
                                 const ByteBuffer& dl,
                                 const WrBuiltDisplayListDescriptor& dlDesc);
-  void ScheduleComposition();
   void ClearResources();
   uint64_t GetChildLayerObserverEpoch() const { return mChildLayerObserverEpoch; }
   bool ShouldParentObserveEpoch();
   void HandleDPEnd(const gfx::IntSize& aSize,
                    InfallibleTArray<WebRenderParentCommand>&& aCommands,
                    InfallibleTArray<OpDestroy>&& aToDestroy,
                    const uint64_t& aFwdTransactionId,
                    const uint64_t& aTransactionId,
@@ -231,33 +236,34 @@ private:
   wr::PipelineId mPipelineId;
   RefPtr<widget::CompositorWidget> mWidget;
   RefPtr<wr::WebRenderAPI> mApi;
   RefPtr<WebRenderCompositableHolder> mCompositableHolder;
   RefPtr<CompositorVsyncScheduler> mCompositorScheduler;
   std::vector<wr::ImageKey> mKeysToDelete;
   // XXX How to handle active keys of non-ExternalImages?
   nsDataHashtable<nsUint64HashKey, wr::ImageKey> mActiveKeys;
+  nsDataHashtable<nsUint64HashKey, RefPtr<WebRenderImageHost>> mAsyncCompositables;
   nsDataHashtable<nsUint64HashKey, RefPtr<WebRenderImageHost>> mExternalImageIds;
   nsTArray<ImageCompositeNotificationInfo> mImageCompositeNotifications;
 
   // These fields keep track of the latest layer observer epoch values in the child and the
   // parent. mChildLayerObserverEpoch is the latest epoch value received from the child.
   // mParentLayerObserverEpoch is the latest epoch value that we have told TabParent about
   // (via ObserveLayerUpdate).
   uint64_t mChildLayerObserverEpoch;
   uint64_t mParentLayerObserverEpoch;
 
   std::queue<PendingTransactionId> mPendingTransactionIds;
   uint32_t mWrEpoch;
   uint32_t mIdNameSpace;
 
   bool mPaused;
   bool mDestroyed;
-  bool mIsSnapshotting;
+  bool mForceRendering;
 
   // Can only be accessed on the compositor thread.
   WebRenderScrollData mScrollData;
 
   static uint32_t sIdNameSpace;
 };
 
 } // namespace layers
--- a/gfx/layers/wr/WebRenderCompositableHolder.cpp
+++ b/gfx/layers/wr/WebRenderCompositableHolder.cpp
@@ -1,84 +1,431 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderCompositableHolder.h"
 
 #include "CompositableHost.h"
+#include "mozilla/gfx/gfxVars.h"
 #include "mozilla/layers/WebRenderImageHost.h"
 #include "mozilla/layers/WebRenderTextureHost.h"
 #include "mozilla/webrender/WebRenderAPI.h"
 
 namespace mozilla {
 
 using namespace gfx;
 
 namespace layers {
 
+WebRenderCompositableHolder::AsyncImagePipelineHolder::AsyncImagePipelineHolder()
+ : mInitialised(false)
+ , mIsChanged(false)
+ , mFilter(WrImageRendering::Auto)
+ , mMixBlendMode(WrMixBlendMode::Normal)
+{}
+
 WebRenderCompositableHolder::WebRenderCompositableHolder(uint32_t aIdNamespace)
  : mIdNamespace(aIdNamespace)
  , mResourceId(0)
+ , mAsyncImageEpoch(0)
+ , mDestroyed(false)
 {
   MOZ_COUNT_CTOR(WebRenderCompositableHolder);
 }
 
 WebRenderCompositableHolder::~WebRenderCompositableHolder()
 {
   MOZ_COUNT_DTOR(WebRenderCompositableHolder);
 }
 
 void
+WebRenderCompositableHolder::Destroy(wr::WebRenderAPI* aApi)
+{
+  DeleteOldAsyncImages(aApi);
+  mDestroyed = true;
+}
+
+bool
+WebRenderCompositableHolder::HasKeysToDelete()
+{
+  return !mKeysToDelete.IsEmpty();
+}
+
+void
+WebRenderCompositableHolder::DeleteOldAsyncImages(wr::WebRenderAPI* aApi)
+{
+  for (wr::ImageKey key : mKeysToDelete) {
+    aApi->DeleteImage(key);
+  }
+  mKeysToDelete.Clear();
+}
+
+void
 WebRenderCompositableHolder::AddPipeline(const wr::PipelineId& aPipelineId)
 {
+  if (mDestroyed) {
+    return;
+  }
   uint64_t id = wr::AsUint64(aPipelineId);
 
   MOZ_ASSERT(!mPipelineTexturesHolders.Get(id));
   PipelineTexturesHolder* holder = new PipelineTexturesHolder();
   mPipelineTexturesHolders.Put(id, holder);
 }
 
 void
 WebRenderCompositableHolder::RemovePipeline(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch)
 {
+  if (mDestroyed) {
+    return;
+  }
+
   PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
   MOZ_ASSERT(holder);
   if (!holder) {
     return;
   }
   MOZ_ASSERT(holder->mDestroyedEpoch.isNothing());
   holder->mDestroyedEpoch = Some(aEpoch);
 }
 
 void
+WebRenderCompositableHolder::AddAsyncImagePipeline(const wr::PipelineId& aPipelineId, WebRenderImageHost* aImageHost)
+{
+  if (mDestroyed) {
+    return;
+  }
+  MOZ_ASSERT(aImageHost);
+  uint64_t id = wr::AsUint64(aPipelineId);
+
+  MOZ_ASSERT(!mAsyncImagePipelineHolders.Get(id));
+  AsyncImagePipelineHolder* holder = new AsyncImagePipelineHolder();
+  holder->mImageHost = aImageHost;
+  mAsyncImagePipelineHolders.Put(id, holder);
+  AddPipeline(aPipelineId);
+}
+
+void
+WebRenderCompositableHolder::RemoveAsyncImagePipeline(wr::WebRenderAPI* aApi, const wr::PipelineId& aPipelineId)
+{
+  if (mDestroyed) {
+    return;
+  }
+
+  uint64_t id = wr::AsUint64(aPipelineId);
+  AsyncImagePipelineHolder* holder = mAsyncImagePipelineHolders.Get(id);
+  if (!holder) {
+    return;
+  }
+
+  ++mAsyncImageEpoch; // Update webrender epoch
+  aApi->ClearRootDisplayList(wr::NewEpoch(mAsyncImageEpoch), aPipelineId);
+  for (wr::ImageKey key : holder->mKeys) {
+    aApi->DeleteImage(key);
+  }
+  mAsyncImagePipelineHolders.Remove(id);
+  RemovePipeline(aPipelineId, wr::NewEpoch(mAsyncImageEpoch));
+}
+
+void
+WebRenderCompositableHolder::UpdateAsyncImagePipeline(const wr::PipelineId& aPipelineId,
+                                                      const LayerRect& aScBounds,
+                                                      const gfx::Matrix4x4& aScTransform,
+                                                      const gfx::MaybeIntSize& aScaleToSize,
+                                                      const MaybeLayerRect& aClipRect,
+                                                      const MaybeImageMask& aMask,
+                                                      const WrImageRendering& aFilter,
+                                                      const WrMixBlendMode& aMixBlendMode)
+{
+  if (mDestroyed) {
+    return;
+  }
+  AsyncImagePipelineHolder* holder = mAsyncImagePipelineHolders.Get(wr::AsUint64(aPipelineId));
+  MOZ_ASSERT(holder);
+  if (!holder) {
+    return;
+  }
+  holder->mInitialised = true;
+  holder->mIsChanged = true;
+  holder->mScBounds = aScBounds;
+  holder->mScTransform = aScTransform;
+  holder->mScaleToSize = aScaleToSize;
+  holder->mClipRect = aClipRect;
+  holder->mMask = aMask;
+  holder->mFilter = aFilter;
+  holder->mMixBlendMode = aMixBlendMode;
+}
+
+void
+WebRenderCompositableHolder::GetImageKeys(nsTArray<wr::ImageKey>& aKeys, size_t aChannelNumber)
+{
+  MOZ_ASSERT(aChannelNumber > 0);
+  for (size_t i = 0; i < aChannelNumber; ++i) {
+    wr::ImageKey key = GetImageKey();
+    aKeys.AppendElement(key);
+  }
+}
+
+void
+WebRenderCompositableHolder::GetImageKeysForExternalImage(nsTArray<wr::ImageKey>& aKeys)
+{
+  MOZ_ASSERT(aKeys.IsEmpty());
+
+  // XXX (Jerry): Remove the hardcode image format setting.
+#if defined(XP_WIN)
+  // Use libyuv to convert the buffer to rgba format. So, use 1 image key here.
+  GetImageKeys(aKeys, 1);
+#elif defined(XP_MACOSX)
+  if (gfxVars::CanUseHardwareVideoDecoding()) {
+    // Use the hardware MacIOSurface with YCbCr interleaved format. It uses 1
+    // image key.
+    GetImageKeys(aKeys, 1);
+  } else {
+    // Use libyuv.
+    GetImageKeys(aKeys, 1);
+  }
+#elif defined(MOZ_WIDGET_GTK)
+  // Use libyuv.
+  GetImageKeys(aKeys, 1);
+#elif defined(ANDROID)
+  // Use libyuv.
+  GetImageKeys(aKeys, 1);
+#endif
+
+  MOZ_ASSERT(!aKeys.IsEmpty());
+}
+
+bool
+WebRenderCompositableHolder::GetImageKeyForTextureHost(wr::WebRenderAPI* aApi, TextureHost* aTexture, nsTArray<wr::ImageKey>& aKeys)
+{
+  MOZ_ASSERT(aKeys.IsEmpty());
+  MOZ_ASSERT(aTexture);
+
+  WebRenderTextureHost* wrTexture = aTexture->AsWebRenderTextureHost();
+
+  if (wrTexture) {
+    GetImageKeysForExternalImage(aKeys);
+    MOZ_ASSERT(!aKeys.IsEmpty());
+    Range<const wr::ImageKey> keys(&aKeys[0], aKeys.Length());
+    wrTexture->AddWRImage(aApi, keys, wrTexture->GetExternalImageKey());
+    return true;
+  } else {
+    RefPtr<DataSourceSurface> dSurf = aTexture->GetAsSurface();
+    if (!dSurf) {
+      NS_ERROR("TextureHost does not return DataSourceSurface");
+      return false;
+    }
+    DataSourceSurface::MappedSurface map;
+    if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
+      NS_ERROR("DataSourceSurface failed to map");
+      return false;
+    }
+    IntSize size = dSurf->GetSize();
+    wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat());
+    auto slice = Range<uint8_t>(map.mData, size.height * map.mStride);
+
+    wr::ImageKey key = GetImageKey();
+    aKeys.AppendElement(key);
+    aApi->AddImage(key, descriptor, slice);
+    dSurf->Unmap();
+  }
+  return false;
+}
+
+void
+WebRenderCompositableHolder::PushExternalImage(wr::DisplayListBuilder& aBuilder,
+                                               const WrRect& aBounds,
+                                               const WrClipRegionToken aClip,
+                                               wr::ImageRendering aFilter,
+                                               nsTArray<wr::ImageKey>& aKeys)
+{
+  // XXX (Jerry): Remove the hardcode image format setting. The format of
+  // textureClient could change from time to time. So, we just set the most
+  // usable format here.
+#if defined(XP_WIN)
+  // Use libyuv to convert the buffer to rgba format.
+  MOZ_ASSERT(aKeys.Length() == 1);
+  aBuilder.PushImage(aBounds, aClip, aFilter, aKeys[0]);
+#elif defined(XP_MACOSX)
+  if (gfx::gfxVars::CanUseHardwareVideoDecoding()) {
+    // Use the hardware MacIOSurface with YCbCr interleaved format.
+    MOZ_ASSERT(aKeys.Length() == 1);
+    aBuilder.PushYCbCrInterleavedImage(aBounds, aClip, aKeys[0], WrYuvColorSpace::Rec601, aFilter);
+  } else {
+    // Use libyuv to convert the buffer to rgba format.
+    MOZ_ASSERT(aKeys.Length() == 1);
+    aBuilder.PushImage(aBounds, aClip, aFilter, aKeys[0]);
+  }
+#elif defined(MOZ_WIDGET_GTK)
+  // Use libyuv to convert the buffer to rgba format.
+  MOZ_ASSERT(aKeys.Length() == 1);
+  aBuilder.PushImage(aBounds, aClip, aFilter, aKeys[0]);
+#elif defined(ANDROID)
+  // Use libyuv to convert the buffer to rgba format.
+  MOZ_ASSERT(aKeys.Length() == 1);
+  aBuilder.PushImage(aBounds, aClip, aFilter, aKeys[0]);
+#endif
+}
+
+bool
+WebRenderCompositableHolder::UpdateImageKeys(wr::WebRenderAPI* aApi,
+                                             bool& aUseExternalImage,
+                                             AsyncImagePipelineHolder* aHolder,
+                                             nsTArray<wr::ImageKey>& aKeys,
+                                             nsTArray<wr::ImageKey>& aKeysToDelete)
+{
+  MOZ_ASSERT(aKeys.IsEmpty());
+  MOZ_ASSERT(aHolder);
+  TextureHost* texture = aHolder->mImageHost->GetAsTextureHostForComposite();
+
+  if (!aHolder->mInitialised) {
+    return false;
+  }
+
+  // No change
+  if (!aHolder->mIsChanged && texture == aHolder->mCurrentTexture) {
+    // No need to update DisplayList.
+    return false;
+  }
+
+  aHolder->mIsChanged = false;
+
+  if (texture == aHolder->mCurrentTexture) {
+    // Reuse previous ImageKeys.
+    aKeys.AppendElements(aHolder->mKeys);
+    return true;
+  }
+
+  // Delete old ImageKeys
+  aKeysToDelete.AppendElements(aHolder->mKeys);
+  aHolder->mKeys.Clear();
+  aHolder->mCurrentTexture = nullptr;
+
+  // No txture to render
+  if (!texture) {
+    return true;
+  }
+
+  aUseExternalImage = GetImageKeyForTextureHost(aApi, texture, aKeys);
+  MOZ_ASSERT(!aKeys.IsEmpty());
+  aHolder->mKeys.AppendElements(aKeys);
+  aHolder->mCurrentTexture = texture;
+  return true;
+}
+
+void
+WebRenderCompositableHolder::ApplyAsyncImages(wr::WebRenderAPI* aApi)
+{
+  if (mDestroyed || mAsyncImagePipelineHolders.Count() == 0) {
+    return;
+  }
+
+  ++mAsyncImageEpoch; // Update webrender epoch
+  wr::Epoch epoch = wr::NewEpoch(mAsyncImageEpoch);
+  nsTArray<wr::ImageKey> keysToDelete;
+
+  for (auto iter = mAsyncImagePipelineHolders.Iter(); !iter.Done(); iter.Next()) {
+    wr::PipelineId pipelineId = wr::AsPipelineId(iter.Key());
+    AsyncImagePipelineHolder* holder = iter.Data();
+
+    nsTArray<wr::ImageKey> keys;
+    bool useExternalImage = false;
+    bool updateDisplayList = UpdateImageKeys(aApi,
+                                             useExternalImage,
+                                             holder,
+                                             keys,
+                                             keysToDelete);
+    if (!updateDisplayList) {
+      continue;
+    }
+
+    WrSize contentSize { holder->mScBounds.width, holder->mScBounds.height };
+    wr::DisplayListBuilder builder(pipelineId, contentSize);
+
+    if (!keys.IsEmpty()) {
+      MOZ_ASSERT(holder->mCurrentTexture.get());
+
+      float opacity = 1.0f;
+      builder.PushStackingContext(wr::ToWrRect(holder->mScBounds),
+                                  0,
+                                  &opacity,
+                                  holder->mScTransform.IsIdentity() ? nullptr : &holder->mScTransform,
+                                  holder->mMixBlendMode);
+
+      LayerRect rect(0, 0, holder->mCurrentTexture->GetSize().width, holder->mCurrentTexture->GetSize().height);
+      if (holder->mScaleToSize.isSome()) {
+        rect = LayerRect(0, 0, holder->mScaleToSize.value().width, holder->mScaleToSize.value().height);
+      }
+      LayerRect clipRect = holder->mClipRect.valueOr(rect);
+      WrClipRegionToken clip = builder.PushClipRegion(
+        wr::ToWrRect(clipRect),
+        holder->mMask.ptrOr(nullptr));
+
+      if (useExternalImage) {
+        MOZ_ASSERT(holder->mCurrentTexture->AsWebRenderTextureHost());
+        PushExternalImage(builder,
+                          wr::ToWrRect(rect),
+                          clip,
+                          holder->mFilter,
+                          keys);
+        HoldExternalImage(pipelineId, epoch, holder->mCurrentTexture->AsWebRenderTextureHost());
+      } else {
+        MOZ_ASSERT(keys.Length() == 1);
+        builder.PushImage(wr::ToWrRect(rect),
+                          clip,
+                          holder->mFilter,
+                          keys[0]);
+      }
+      builder.PopStackingContext();
+    }
+
+    wr::BuiltDisplayList dl;
+    WrSize builderContentSize;
+    builder.Finalize(builderContentSize, dl);
+    aApi->SetRootDisplayList(gfx::Color(0.f, 0.f, 0.f, 0.f), epoch, LayerSize(holder->mScBounds.width, holder->mScBounds.height),
+                             pipelineId, builderContentSize,
+                             dl.dl_desc, dl.dl.inner.data, dl.dl.inner.length);
+  }
+  DeleteOldAsyncImages(aApi);
+  mKeysToDelete.SwapElements(keysToDelete);
+}
+
+void
 WebRenderCompositableHolder::HoldExternalImage(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, WebRenderTextureHost* aTexture)
 {
+  if (mDestroyed) {
+    return;
+  }
   MOZ_ASSERT(aTexture);
 
   PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
   MOZ_ASSERT(holder);
   if (!holder) {
     return;
   }
   // Hold WebRenderTextureHost until end of its usage on RenderThread
   holder->mTextureHosts.push(ForwardingTextureHost(aEpoch, aTexture));
 }
 
 void
 WebRenderCompositableHolder::Update(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch)
 {
+  if (mDestroyed) {
+    return;
+  }
   PipelineTexturesHolder* holder = mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId));
   if (!holder) {
     return;
   }
 
   // Remove Pipeline
   if (holder->mDestroyedEpoch.isSome() && holder->mDestroyedEpoch.ref() <= aEpoch) {
+
     mPipelineTexturesHolders.Remove(wr::AsUint64(aPipelineId));
     return;
   }
 
   // Release TextureHosts based on Epoch
   while (!holder->mTextureHosts.empty()) {
     if (aEpoch <= holder->mTextureHosts.front().mEpoch) {
       break;
--- a/gfx/layers/wr/WebRenderCompositableHolder.h
+++ b/gfx/layers/wr/WebRenderCompositableHolder.h
@@ -3,45 +3,53 @@
  * 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/. */
 
 #ifndef MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H
 #define MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H
 
 #include <queue>
 
+#include "mozilla/gfx/Point.h"
 #include "mozilla/layers/TextureHost.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/webrender/WebRenderTypes.h"
 #include "nsClassHashtable.h"
 
 namespace mozilla {
 
 namespace wr {
+class DisplayListBuilder;
 class WebRenderAPI;
 }
 
 namespace layers {
 
 class CompositableHost;
+class CompositorVsyncScheduler;
+class WebRenderImageHost;
 class WebRenderTextureHost;
 
 class WebRenderCompositableHolder final
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderCompositableHolder)
 
   explicit WebRenderCompositableHolder(uint32_t aIdNamespace);
 
 protected:
   ~WebRenderCompositableHolder();
 
 public:
+  void Destroy(wr::WebRenderAPI* aApi);
+  bool HasKeysToDelete();
+
   void AddPipeline(const wr::PipelineId& aPipelineId);
   void RemovePipeline(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch);
+
   void HoldExternalImage(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, WebRenderTextureHost* aTexture);
   void Update(const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch);
 
   TimeStamp GetCompositionTime() const {
     return mCompositionTime;
   }
   void SetCompositionTime(TimeStamp aTimeStamp) {
     mCompositionTime = aTimeStamp;
@@ -55,46 +63,96 @@ public:
         mCompositeUntilTime < aTimeStamp) {
       mCompositeUntilTime = aTimeStamp;
     }
   }
   TimeStamp GetCompositeUntilTime() const {
     return mCompositeUntilTime;
   }
 
+  void AddAsyncImagePipeline(const wr::PipelineId& aPipelineId, WebRenderImageHost* aImageHost);
+  void RemoveAsyncImagePipeline(wr::WebRenderAPI* aApi, const wr::PipelineId& aPipelineId);
+
+  void UpdateAsyncImagePipeline(const wr::PipelineId& aPipelineId,
+                                const LayerRect& aScBounds,
+                                const gfx::Matrix4x4& aScTransform,
+                                const gfx::MaybeIntSize& aScaleToSize,
+                                const MaybeLayerRect& aClipRect,
+                                const MaybeImageMask& aMask,
+                                const WrImageRendering& aFilter,
+                                const WrMixBlendMode& aMixBlendMode);
+  void ApplyAsyncImages(wr::WebRenderAPI* aApi);
+
+private:
+  void DeleteOldAsyncImages(wr::WebRenderAPI* aApi);
+
   uint32_t GetNextResourceId() { return ++mResourceId; }
   uint32_t GetNamespace() { return mIdNamespace; }
   wr::ImageKey GetImageKey()
   {
     wr::ImageKey key;
     key.mNamespace = GetNamespace();
     key.mHandle = GetNextResourceId();
     return key;
   }
-
-private:
+  void GetImageKeys(nsTArray<wr::ImageKey>& aKeys, size_t aChannelNumber);
+  void GetImageKeysForExternalImage(nsTArray<wr::ImageKey>& aKeys);
+  bool GetImageKeyForTextureHost(wr::WebRenderAPI* aApi, TextureHost* aTexture, nsTArray<wr::ImageKey>& aKeys);
+  void PushExternalImage(wr::DisplayListBuilder& aBuilder,
+                         const WrRect& aBounds,
+                         const WrClipRegionToken aClip,
+                         wr::ImageRendering aFilter,
+                         nsTArray<wr::ImageKey>& aKeys);
 
   struct ForwardingTextureHost {
     ForwardingTextureHost(const wr::Epoch& aEpoch, TextureHost* aTexture)
       : mEpoch(aEpoch)
       , mTexture(aTexture)
     {}
     wr::Epoch mEpoch;
     CompositableTextureHostRef mTexture;
   };
 
   struct PipelineTexturesHolder {
     // Holds forwarding WebRenderTextureHosts.
     std::queue<ForwardingTextureHost> mTextureHosts;
     Maybe<wr::Epoch> mDestroyedEpoch;
   };
 
+  struct AsyncImagePipelineHolder {
+    AsyncImagePipelineHolder();
+
+    bool mInitialised;
+    bool mIsChanged;
+    LayerRect mScBounds;
+    gfx::Matrix4x4 mScTransform;
+    gfx::MaybeIntSize mScaleToSize;
+    MaybeLayerRect mClipRect;
+    MaybeImageMask mMask;
+    WrImageRendering mFilter;
+    WrMixBlendMode mMixBlendMode;
+    RefPtr<WebRenderImageHost> mImageHost;
+    CompositableTextureHostRef mCurrentTexture;
+    nsTArray<wr::ImageKey> mKeys;
+  };
+
+  bool UpdateImageKeys(wr::WebRenderAPI* aApi,
+                       bool& aUseExternalImage,
+                       AsyncImagePipelineHolder* aHolder,
+                       nsTArray<wr::ImageKey>& aKeys,
+                       nsTArray<wr::ImageKey>& aKeysToDelete);
+
   uint32_t mIdNamespace;
   uint32_t mResourceId;
+
   nsClassHashtable<nsUint64HashKey, PipelineTexturesHolder> mPipelineTexturesHolders;
+  nsClassHashtable<nsUint64HashKey, AsyncImagePipelineHolder> mAsyncImagePipelineHolders;
+  uint32_t mAsyncImageEpoch;
+  nsTArray<wr::ImageKey> mKeysToDelete;
+  bool mDestroyed;
 
   // Render time for the current composition.
   TimeStamp mCompositionTime;
 
   // When nonnull, during rendering, some compositable indicated that it will
   // change its rendering at this time. In order not to miss it, we composite
   // on every vsync until this time occurs (this is the latest such time).
   TimeStamp mCompositeUntilTime;
--- a/gfx/layers/wr/WebRenderImageHost.cpp
+++ b/gfx/layers/wr/WebRenderImageHost.cpp
@@ -63,16 +63,20 @@ WebRenderImageHost::UseTextureHost(const
     img.mProducerID = t.mProducerID;
     img.mTextureHost->SetCropRect(img.mPictureRect);
     img.mTextureHost->Updated();
   }
 
   mImages.SwapElements(newImages);
   newImages.Clear();
 
+  if (mWrBridge && GetAsyncRef()) {
+    mWrBridge->ScheduleComposition();
+  }
+
   // Video producers generally send replacement images with the same frameID but
   // slightly different timestamps in order to sync with the audio clock. This
   // means that any CompositeUntil() call we made in Composite() may no longer
   // guarantee that we'll composite until the next frame is ready. Fix that here.
   if (mWrBridge && mLastFrameID >= 0) {
     MOZ_ASSERT(mWrBridge->CompositableHolder());
     for (size_t i = 0; i < mImages.Length(); ++i) {
       bool frameComesAfter = mImages[i].mFrameID > mLastFrameID ||
--- a/gfx/layers/wr/WebRenderImageHost.h
+++ b/gfx/layers/wr/WebRenderImageHost.h
@@ -68,16 +68,18 @@ public:
   virtual WebRenderImageHost* AsWebRenderImageHost() override { return this; }
 
   TextureHost* GetAsTextureHostForComposite();
 
   void SetWrBridge(WebRenderBridgeParent* aWrBridge);
 
   void ClearWrBridge();
 
+  TextureHost* GetCurrentTextureHost() { return mCurrentTextureHost; }
+
 protected:
   // ImageComposite
   virtual TimeStamp GetCompositionTime() const override;
 
   void SetCurrentTextureHost(TextureHost* aTexture);
 
   WebRenderBridgeParent* MOZ_NON_OWNING_REF mWrBridge;
 
--- a/gfx/layers/wr/WebRenderImageLayer.cpp
+++ b/gfx/layers/wr/WebRenderImageLayer.cpp
@@ -28,26 +28,26 @@ WebRenderImageLayer::WebRenderImageLayer
 {
   MOZ_COUNT_CTOR(WebRenderImageLayer);
 }
 
 WebRenderImageLayer::~WebRenderImageLayer()
 {
   MOZ_COUNT_DTOR(WebRenderImageLayer);
 
-  for (auto key : mVideoKeys) {
-    WrManager()->AddImageKeyForDiscard(key);
-  }
   if (mKey.isSome()) {
     WrManager()->AddImageKeyForDiscard(mKey.value());
   }
 
   if (mExternalImageId.isSome()) {
     WrBridge()->DeallocExternalImageId(mExternalImageId.ref());
   }
+  if (mPipelineId.isSome()) {
+    WrBridge()->RemovePipelineIdForAsyncCompositable(mPipelineId.ref());
+  }
 }
 
 CompositableType
 WebRenderImageLayer::GetImageClientType()
 {
   if (mImageClientTypeContainer != CompositableType::UNKNOWN) {
     return mImageClientTypeContainer;
   }
@@ -86,27 +86,16 @@ void
 WebRenderImageLayer::ClearCachedResources()
 {
   if (mImageClient) {
     mImageClient->ClearCachedResources();
   }
 }
 
 void
-WebRenderImageLayer::AddWRVideoImage(size_t aChannelNumber)
-{
-  for (size_t i = 0; i < aChannelNumber; ++i) {
-    WrImageKey key = GetImageKey();
-    WrManager()->AddImageKeyForDiscard(key);
-    mVideoKeys.AppendElement(key);
-  }
-  WrBridge()->AddWebRenderParentCommand(OpAddExternalVideoImage(mExternalImageId.value(), mVideoKeys));
-}
-
-void
 WebRenderImageLayer::RenderLayer(wr::DisplayListBuilder& aBuilder,
                                  const StackingContextHelper& aSc)
 {
   if (!mContainer) {
      return;
   }
 
   CompositableType type = GetImageClientType();
@@ -121,72 +110,99 @@ WebRenderImageLayer::RenderLayer(wr::Dis
                                                   WrBridge(),
                                                   TextureFlags::DEFAULT);
     if (!mImageClient) {
       return;
     }
     mImageClient->Connect();
   }
 
-  if (mExternalImageId.isNothing()) {
-    if (GetImageClientType() == CompositableType::IMAGE_BRIDGE) {
-      MOZ_ASSERT(!mImageClient);
-      mExternalImageId = Some(WrBridge()->AllocExternalImageId(mContainer->GetAsyncContainerHandle()));
-      // Alloc async image pipeline id.
-      mPipelineId = Some(WrBridge()->GetCompositorBridgeChild()->GetNextPipelineId());
-    } else {
-      // Handle CompositableType::IMAGE case
-      MOZ_ASSERT(mImageClient);
-      mExternalImageId = Some(WrBridge()->AllocExternalImageIdForCompositable(mImageClient));
+  if (GetImageClientType() == CompositableType::IMAGE_BRIDGE && mPipelineId.isNothing()) {
+    MOZ_ASSERT(!mImageClient);
+    // Alloc async image pipeline id.
+    mPipelineId = Some(WrBridge()->GetCompositorBridgeChild()->GetNextPipelineId());
+    WrBridge()->AddPipelineIdForAsyncCompositable(mPipelineId.ref(),
+                                                  mContainer->GetAsyncContainerHandle());
+  } else if (GetImageClientType() == CompositableType::IMAGE && mExternalImageId.isNothing())  {
+    MOZ_ASSERT(mImageClient);
+    mExternalImageId = Some(WrBridge()->AllocExternalImageIdForCompositable(mImageClient));
+    MOZ_ASSERT(mExternalImageId.isSome());
+  }
+
+  if (GetImageClientType() == CompositableType::IMAGE_BRIDGE) {
+    MOZ_ASSERT(!mImageClient);
+    MOZ_ASSERT(mExternalImageId.isNothing());
+
+    // Push IFrame for async image pipeline.
+
+    ParentLayerRect bounds = GetLocalTransformTyped().TransformBounds(Bounds());
+
+    // As with WebRenderTextLayer, because we don't push a stacking context for
+    // this async image pipeline, WR doesn't know about the transform on this layer.
+    // Therefore we need to apply that transform to the bounds before we pass it on to WR.
+    // The conversion from ParentLayerPixel to LayerPixel below is a result of
+    // changing the reference layer from "this layer" to the "the layer that
+    // created aSc".
+    LayerRect rect = ViewAs<LayerPixel>(bounds,
+        PixelCastJustification::MovingDownToChildren);
+    DumpLayerInfo("Image Layer async", rect);
+
+    // XXX Remove IFrame for async image pipeline when partial display list update is supported.
+    WrClipRegionToken clipRegion = aBuilder.PushClipRegion(aSc.ToRelativeWrRect(rect));
+    aBuilder.PushIFrame(aSc.ToRelativeWrRect(rect), clipRegion, mPipelineId.ref());
+
+    // Prepare data that are necessary for async image pipelin.
+    // They are used within WebRenderCompositableHolder
+
+    gfx::Matrix4x4 scTransform = GetTransform();
+    // Translate is applied as part of PushIFrame()
+    scTransform.PostTranslate(-rect.x, -rect.y, 0);
+    // Adjust transform as to apply origin
+    LayerPoint scOrigin = Bounds().TopLeft();
+    scTransform.PreTranslate(-scOrigin.x, -scOrigin.y, 0);
+
+    MaybeIntSize scaleToSize;
+    if (mScaleMode != ScaleMode::SCALE_NONE) {
+      NS_ASSERTION(mScaleMode == ScaleMode::STRETCH,
+                   "No other scalemodes than stretch and none supported yet.");
+      scaleToSize = Some(mScaleToSize);
     }
+    LayerRect scBounds = BoundsForStackingContext();
+    wr::ImageRendering filter = wr::ToImageRendering(mSamplingFilter);
+    wr::MixBlendMode mixBlendMode = wr::ToWrMixBlendMode(GetMixBlendMode());
+
+    StackingContextHelper sc(aSc, aBuilder, this);
+    Maybe<WrImageMask> mask = BuildWrMaskLayer(&sc);
+
+    WrBridge()->AddWebRenderParentCommand(OpUpdateAsyncImagePipeline(mPipelineId.value(),
+                                                                     scBounds,
+                                                                     scTransform,
+                                                                     scaleToSize,
+                                                                     ClipRect(),
+                                                                     mask,
+                                                                     filter,
+                                                                     mixBlendMode));
+    return;
   }
-  MOZ_ASSERT(mExternalImageId.isSome());
 
-  // XXX Not good for async ImageContainer case.
+  MOZ_ASSERT(GetImageClientType() == CompositableType::IMAGE);
+  MOZ_ASSERT(mImageClient->AsImageClientSingle());
+
   AutoLockImage autoLock(mContainer);
   Image* image = autoLock.GetImage();
   if (!image) {
     return;
   }
   gfx::IntSize size = image->GetSize();
-
-  if (GetImageClientType() != CompositableType::IMAGE_BRIDGE) {
-    // Handle CompositableType::IMAGE case
-    MOZ_ASSERT(mImageClient->AsImageClientSingle());
-    mKey = UpdateImageKey(mImageClient->AsImageClientSingle(),
-                          mContainer,
-                          mKey,
-                          mExternalImageId.ref());
-    if (mKey.isNothing()) {
-      return;
-    }
-  } else {
-    // Always allocate key.
-    mVideoKeys.Clear();
-
-    // XXX (Jerry): Remove the hardcode image format setting.
-#if defined(XP_WIN)
-    // Use libyuv to convert the buffer to rgba format. So, use 1 image key here.
-    AddWRVideoImage(1);
-#elif defined(XP_MACOSX)
-    if (gfx::gfxVars::CanUseHardwareVideoDecoding()) {
-      // Use the hardware MacIOSurface with YCbCr interleaved format. It uses 1
-      // image key.
-      AddWRVideoImage(1);
-    } else {
-      // Use libyuv.
-      AddWRVideoImage(1);
-    }
-#elif defined(MOZ_WIDGET_GTK)
-    // Use libyuv.
-    AddWRVideoImage(1);
-#elif defined(ANDROID)
-    // Use libyuv.
-    AddWRVideoImage(1);
-#endif
+  mKey = UpdateImageKey(mImageClient->AsImageClientSingle(),
+                        mContainer,
+                        mKey,
+                        mExternalImageId.ref());
+  if (mKey.isNothing()) {
+    return;
   }
 
   ScrollingLayersHelper scroller(this, aBuilder, aSc);
   StackingContextHelper sc(aSc, aBuilder, this);
 
   LayerRect rect(0, 0, size.width, size.height);
   if (mScaleMode != ScaleMode::SCALE_NONE) {
     NS_ASSERTION(mScaleMode == ScaleMode::STRETCH,
@@ -203,47 +219,17 @@ WebRenderImageLayer::RenderLayer(wr::Dis
   wr::ImageRendering filter = wr::ToImageRendering(mSamplingFilter);
 
   DumpLayerInfo("Image Layer", rect);
   if (gfxPrefs::LayersDump()) {
     printf_stderr("ImageLayer %p texture-filter=%s \n",
                   GetLayer(),
                   Stringify(filter).c_str());
   }
-
-  if (GetImageClientType() != CompositableType::IMAGE_BRIDGE) {
-    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mKey.value());
-  } else {
-    // XXX (Jerry): Remove the hardcode image format setting. The format of
-    // textureClient could change from time to time. So, we just set the most
-    // usable format here.
-#if defined(XP_WIN)
-    // Use libyuv to convert the buffer to rgba format.
-    MOZ_ASSERT(mVideoKeys.Length() == 1);
-    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
-#elif defined(XP_MACOSX)
-    if (gfx::gfxVars::CanUseHardwareVideoDecoding()) {
-      // Use the hardware MacIOSurface with YCbCr interleaved format.
-      MOZ_ASSERT(mVideoKeys.Length() == 1);
-      aBuilder.PushYCbCrInterleavedImage(sc.ToRelativeWrRect(rect), clip, mVideoKeys[0], WrYuvColorSpace::Rec601, filter);
-    } else {
-      // Use libyuv to convert the buffer to rgba format.
-      MOZ_ASSERT(mVideoKeys.Length() == 1);
-      aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
-    }
-#elif defined(MOZ_WIDGET_GTK)
-    // Use libyuv to convert the buffer to rgba format.
-    MOZ_ASSERT(mVideoKeys.Length() == 1);
-    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
-#elif defined(ANDROID)
-    // Use libyuv to convert the buffer to rgba format.
-    MOZ_ASSERT(mVideoKeys.Length() == 1);
-    aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mVideoKeys[0]);
-#endif
-  }
+  aBuilder.PushImage(sc.ToRelativeWrRect(rect), clip, filter, mKey.value());
 }
 
 Maybe<WrImageMask>
 WebRenderImageLayer::RenderMaskLayer(const gfx::Matrix4x4& aTransform)
 {
   if (!mContainer) {
      return Nothing();
   }
--- a/gfx/layers/wr/WebRenderImageLayer.h
+++ b/gfx/layers/wr/WebRenderImageLayer.h
@@ -34,19 +34,16 @@ public:
   Maybe<WrImageMask> RenderMaskLayer(const gfx::Matrix4x4& aTransform) override;
 
 protected:
   CompositableType GetImageClientType();
 
   void AddWRVideoImage(size_t aChannelNumber);
 
   wr::MaybeExternalImageId mExternalImageId;
-  // Some video image format contains multiple channel data.
-  nsTArray<wr::ImageKey> mVideoKeys;
-  // The regular single channel image.
   Maybe<wr::ImageKey> mKey;
   RefPtr<ImageClient> mImageClient;
   CompositableType mImageClientTypeContainer;
   Maybe<wr::PipelineId> mPipelineId;
 };
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -459,19 +459,26 @@ void
 WebRenderLayerManager::RemoveDidCompositeObserver(DidCompositeObserver* aObserver)
 {
   mDidCompositeObservers.RemoveElement(aObserver);
 }
 
 void
 WebRenderLayerManager::FlushRendering()
 {
-  CompositorBridgeChild* bridge = GetCompositorBridgeChild();
-  if (bridge) {
-    bridge->SendFlushRendering();
+  CompositorBridgeChild* cBridge = GetCompositorBridgeChild();
+  if (!cBridge) {
+    return;
+  }
+  MOZ_ASSERT(mWidget);
+
+  if (mWidget->SynchronouslyRepaintOnResize() || gfxPrefs::LayersForceSynchronousResize()) {
+    cBridge->SendFlushRendering();
+  } else {
+    cBridge->SendFlushRenderingAsync();
   }
 }
 
 void
 WebRenderLayerManager::WaitOnTransactionProcessed()
 {
   CompositorBridgeChild* bridge = GetCompositorBridgeChild();
   if (bridge) {
--- a/gfx/layers/wr/WebRenderMessageUtils.h
+++ b/gfx/layers/wr/WebRenderMessageUtils.h
@@ -168,16 +168,54 @@ struct ParamTraits<WrPoint>
   Read(const Message* aMsg, PickleIterator* aIter, WrPoint* aResult)
   {
     return ReadParam(aMsg, aIter, &aResult->x) &&
            ReadParam(aMsg, aIter, &aResult->y);
   }
 };
 
 template<>
+struct ParamTraits<WrImageMask>
+{
+  static void
+  Write(Message* aMsg, const WrImageMask& aParam)
+  {
+    WriteParam(aMsg, aParam.image);
+    WriteParam(aMsg, aParam.rect);
+    WriteParam(aMsg, aParam.repeat);
+  }
+
+  static bool
+  Read(const Message* aMsg, PickleIterator* aIter, WrImageMask* aResult)
+  {
+    return ReadParam(aMsg, aIter, &aResult->image)
+        && ReadParam(aMsg, aIter, &aResult->rect)
+        && ReadParam(aMsg, aIter, &aResult->repeat);
+  }
+};
+
+template<>
+struct ParamTraits<WrImageRendering>
+  : public ContiguousEnumSerializer<
+        WrImageRendering,
+        WrImageRendering::Auto,
+        WrImageRendering::Sentinel>
+{
+};
+
+template<>
+struct ParamTraits<WrMixBlendMode>
+  : public ContiguousEnumSerializer<
+        WrMixBlendMode,
+        WrMixBlendMode::Normal,
+        WrMixBlendMode::Sentinel>
+{
+};
+
+template<>
 struct ParamTraits<WrBuiltDisplayListDescriptor>
 {
   static void
   Write(Message* aMsg, const WrBuiltDisplayListDescriptor& aParam)
   {
     WriteParam(aMsg, aParam.display_list_items_size);
     WriteParam(aMsg, aParam.builder_start_time);
     WriteParam(aMsg, aParam.builder_finish_time);
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -497,17 +497,17 @@ private:
   DECL_GFX_PREF(Live, "layers.advanced.basic-layer.enabled",          LayersAdvancedBasicLayerEnabled, bool, false);
   DECL_OVERRIDE_PREF(Live, "layers.advanced.border-layers",           LayersAllowBorderLayers, gfxPrefs::OverrideBase_WebRendest());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.boxshadow-inset-layers",  LayersAllowInsetBoxShadow, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.boxshadow-outer-layers",  LayersAllowOuterBoxShadow, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.bullet-layers",           LayersAllowBulletLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.button-foreground-layers", LayersAllowButtonForegroundLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.canvas-background-color", LayersAllowCanvasBackgroundColorLayers, gfxPrefs::OverrideBase_WebRendest());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.caret-layers",            LayersAllowCaretLayers, gfxPrefs::OverrideBase_WebRender());
-  DECL_OVERRIDE_PREF(Live, "layers.advanced.columnRule-layers",       LayersAllowColumnRuleLayers, gfxPrefs::OverrideBase_WebRendest());
+  DECL_OVERRIDE_PREF(Live, "layers.advanced.columnRule-layers",       LayersAllowColumnRuleLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.displaybuttonborder-layers", LayersAllowDisplayButtonBorder, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.image-layers",            LayersAllowImageLayers, gfxPrefs::OverrideBase_WebRendest());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.outline-layers",          LayersAllowOutlineLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.solid-color",             LayersAllowSolidColorLayers, gfxPrefs::OverrideBase_WebRender());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.table",                   LayersAllowTable, gfxPrefs::OverrideBase_WebRendest());
   DECL_OVERRIDE_PREF(Live, "layers.advanced.text-layers",             LayersAllowTextLayers, gfxPrefs::OverrideBase_WebRendest());
   DECL_GFX_PREF(Once, "layers.amd-switchable-gfx.enabled",     LayersAMDSwitchableGfxEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.async-pan-zoom.enabled",         AsyncPanZoomEnabledDoNotUseDirectly, bool, true);
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -18,16 +18,17 @@
 namespace mozilla {
 
 namespace widget {
 class CompositorWidget;
 }
 
 namespace layers {
 class CompositorBridgeParentBase;
+class WebRenderBridgeParent;
 }
 
 namespace wr {
 
 class DisplayListBuilder;
 class RendererOGL;
 class RendererEvent;
 
@@ -120,16 +121,17 @@ protected:
   void WaitFlushed();
 
   WrAPI* mWrApi;
   wr::WindowId mId;
   GLint mMaxTextureSize;
   bool mUseANGLE;
 
   friend class DisplayListBuilder;
+  friend class layers::WebRenderBridgeParent;
 };
 
 /// This is a simple C++ wrapper around WrState defined in the rust bindings.
 /// We may want to turn this into a direct wrapper on top of WebRenderFrameBuilder
 /// instead, so the interface may change a bit.
 class DisplayListBuilder {
 public:
   explicit DisplayListBuilder(wr::PipelineId aId,
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1167,42 +1167,42 @@ impl WebRenderFrameBuilder {
 pub struct WrState {
     pipeline_id: WrPipelineId,
     frame_builder: WebRenderFrameBuilder,
 }
 
 #[no_mangle]
 pub extern "C" fn wr_state_new(pipeline_id: WrPipelineId,
                                content_size: WrSize) -> *mut WrState {
-    assert!(unsafe { is_in_main_thread() });
+    assert!(unsafe { !is_in_render_thread() });
 
     let state = Box::new(WrState {
                              pipeline_id: pipeline_id,
                              frame_builder: WebRenderFrameBuilder::new(pipeline_id,
                                                                        content_size),
                          });
 
     Box::into_raw(state)
 }
 
 /// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
 #[no_mangle]
 pub extern "C" fn wr_state_delete(state: *mut WrState) {
-    assert!(unsafe { is_in_main_thread() });
+    assert!(unsafe { !is_in_render_thread() });
 
     unsafe {
         Box::from_raw(state);
     }
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_begin(state: &mut WrState,
                               width: u32,
                               height: u32) {
-    assert!(unsafe { is_in_main_thread() });
+    assert!(unsafe { !is_in_render_thread() });
     state.frame_builder.dl_builder.data.clear();
 
     let bounds = LayoutRect::new(LayoutPoint::new(0.0, 0.0),
                                  LayoutSize::new(width as f32, height as f32));
 
     state.frame_builder
          .dl_builder
          .push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
@@ -1211,28 +1211,28 @@ pub extern "C" fn wr_dp_begin(state: &mu
                                 TransformStyle::Flat,
                                 None,
                                 MixBlendMode::Normal,
                                 Vec::new());
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_end(state: &mut WrState) {
-    assert!(unsafe { is_in_main_thread() });
+    assert!(unsafe { !is_in_render_thread() });
     state.frame_builder.dl_builder.pop_stacking_context();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_clip_region(state: &mut WrState,
                                          main: WrRect,
                                          complex: *const WrComplexClipRegion,
                                          complex_count: usize,
                                          image_mask: *const WrImageMask)
                                          -> WrClipRegionToken {
-    assert!(unsafe { is_in_main_thread() });
+    assert!(unsafe { !is_in_render_thread() });
 
     let main = main.into();
     let complex_slice = make_slice(complex, complex_count);
     let complex_iter = complex_slice.iter().map(|x| x.into());
     let mask = unsafe { image_mask.as_ref() }.map(|x| x.into());
 
     let clip_region = state.frame_builder.dl_builder.push_clip_region(&main, complex_iter, mask);
 
@@ -1241,17 +1241,17 @@ pub extern "C" fn wr_dp_push_clip_region
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_stacking_context(state: &mut WrState,
                                               bounds: WrRect,
                                               animation_id: u64,
                                               opacity: *const f32,
                                               transform: *const WrMatrix,
                                               mix_blend_mode: WrMixBlendMode) {
-    assert!(unsafe { is_in_main_thread() });
+    assert!(unsafe { !is_in_render_thread() });
 
     let bounds = bounds.into();
 
     let mut filters: Vec<FilterOp> = Vec::new();
     let opacity = unsafe { opacity.as_ref() };
     if let Some(opacity) = opacity {
         if *opacity < 1.0 {
             filters.push(FilterOp::Opacity(PropertyBinding::Value(*opacity)));
@@ -1277,34 +1277,34 @@ pub extern "C" fn wr_dp_push_stacking_co
                                 TransformStyle::Flat,
                                 None,
                                 mix_blend_mode,
                                 filters);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_stacking_context(state: &mut WrState) {
-    assert!(unsafe { is_in_main_thread() });
+    assert!(unsafe { !is_in_render_thread() });
     state.frame_builder.dl_builder.pop_stacking_context();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_clip(state: &mut WrState,
                                   clip_rect: WrRect,
                                   mask: *const WrImageMask) {
     assert!(unsafe { is_in_main_thread() });
     let clip_rect = clip_rect.into();
     let mask = unsafe { mask.as_ref() }.map(|x| x.into());
     let clip_region = state.frame_builder.dl_builder.push_clip_region(&clip_rect, vec![], mask);
     state.frame_builder.dl_builder.push_clip_node(clip_rect, clip_region, None);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_pop_clip(state: &mut WrState) {
-    assert!(unsafe { is_in_main_thread() });
+    assert!(unsafe { !is_in_render_thread() });
     state.frame_builder.dl_builder.pop_clip_node();
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_scroll_layer(state: &mut WrState,
                                           scroll_id: u64,
                                           content_rect: WrRect,
                                           clip_rect: WrRect) {
@@ -1348,30 +1348,30 @@ pub extern "C" fn wr_dp_push_iframe(stat
     state.frame_builder.dl_builder.push_iframe(rect.into(), clip.into(), pipeline_id);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_rect(state: &mut WrState,
                                   rect: WrRect,
                                   clip: WrClipRegionToken,
                                   color: WrColor) {
-    assert!(unsafe { is_in_main_thread() });
+    assert!(unsafe { !is_in_render_thread() });
 
     state.frame_builder.dl_builder.push_rect(rect.into(), clip.into(), color.into());
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_image(state: &mut WrState,
                                    bounds: WrRect,
                                    clip: WrClipRegionToken,
                                    stretch_size: WrSize,
                                    tile_spacing: WrSize,
                                    image_rendering: WrImageRendering,
                                    key: WrImageKey) {
-    assert!(unsafe { is_in_main_thread() });
+    assert!(unsafe { !is_in_render_thread() });
 
     state.frame_builder
          .dl_builder
          .push_image(bounds.into(),
                      clip.into(),
                      stretch_size.into(),
                      tile_spacing.into(),
                      image_rendering,
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -502,18 +502,17 @@ js::obj_toString(JSContext* cx, unsigned
             break;
         }
     }
     // Step 14.
     // Currently omitted for non-standard fallback.
 
     // Step 15.
     RootedValue tag(cx);
-    RootedId toStringTagId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().toStringTag));
-    if (!GetProperty(cx, obj, obj, toStringTagId, &tag))
+    if (!GetInterestingSymbolProperty(cx, obj, cx->wellKnownSymbols().toStringTag, &tag))
         return false;
 
     // Step 16.
     if (!tag.isString()) {
         // Non-standard (bug 1277801): Use ClassName as a fallback in the interim
         if (!builtinTag) {
             const char* className = GetObjectClassName(cx, obj);
             // "[object Object]" is by far the most common case at this point,
--- a/js/src/jit-test/tests/basic/bug1355573.js
+++ b/js/src/jit-test/tests/basic/bug1355573.js
@@ -1,6 +1,6 @@
-// |jit-test| error:overflow
+// |jit-test| error:overflow; allow-oom
 if (getBuildConfiguration().debug === true)
     throw "overflow";
 function f(){};
 Object.defineProperty(f, "name", {value: "a".repeat((1<<28)-1)});
 len = f.bind().name.length;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3195,19 +3195,18 @@ js::ToPrimitiveSlow(JSContext* cx, JSTyp
     // Step numbers refer to the first algorithm listed in ES6 draft rev 36
     // (2015 Mar 17) 7.1.1 ToPrimitive.
     MOZ_ASSERT(preferredType == JSTYPE_UNDEFINED ||
                preferredType == JSTYPE_STRING ||
                preferredType == JSTYPE_NUMBER);
     RootedObject obj(cx, &vp.toObject());
 
     // Steps 4-5.
-    RootedId id(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().toPrimitive));
     RootedValue method(cx);
-    if (!GetProperty(cx, obj, obj, id, &method))
+    if (!GetInterestingSymbolProperty(cx, obj, cx->wellKnownSymbols().toPrimitive, &method))
         return false;
 
     // Step 6.
     if (!method.isUndefined()) {
         // Step 6 of GetMethod. js::Call() below would do this check and throw a
         // TypeError anyway, but this produces a better error message.
         if (!IsCallable(method))
             return ReportCantConvert(cx, JSMSG_TOPRIMITIVE_NOT_CALLABLE, obj, preferredType);
@@ -3559,16 +3558,17 @@ JSObject::dump(FILE* fp) const
         const ObjectGroup* group = obj->group();
         fprintf(fp, "group %p\n", (const void*)group);
     }
 
     fprintf(fp, "flags:");
     if (obj->isDelegate()) fprintf(fp, " delegate");
     if (!obj->is<ProxyObject>() && !obj->nonProxyIsExtensible()) fprintf(fp, " not_extensible");
     if (obj->isIndexed()) fprintf(fp, " indexed");
+    if (obj->maybeHasInterestingSymbolProperty()) fprintf(fp, " maybe_has_interesting_symbol");
     if (obj->isBoundFunction()) fprintf(fp, " bound_function");
     if (obj->isQualifiedVarObj()) fprintf(fp, " varobj");
     if (obj->isUnqualifiedVarObj()) fprintf(fp, " unqualified_varobj");
     if (obj->watched()) fprintf(fp, " watched");
     if (obj->isIteratedSingleton()) fprintf(fp, " iterated_singleton");
     if (obj->isNewGroupUnknown()) fprintf(fp, " new_type_unknown");
     if (obj->hasUncacheableProto()) fprintf(fp, " has_uncacheable_proto");
     if (obj->hadElementsAccess()) fprintf(fp, " had_elements_access");
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -270,16 +270,23 @@ class JSObject : public js::gc::Cell
 
     /*
      * Whether there may be indexed properties on this object, excluding any in
      * the object's elements.
      */
     inline bool isIndexed() const;
 
     /*
+     * Whether there may be "interesting symbol" properties on this object. An
+     * interesting symbol is a symbol for which symbol->isInterestingSymbol()
+     * returns true.
+     */
+    MOZ_ALWAYS_INLINE bool maybeHasInterestingSymbolProperty() const;
+
+    /*
      * If this object was instantiated with `new Ctor`, return the constructor's
      * display atom. Otherwise, return nullptr.
      */
     static bool constructorDisplayAtom(JSContext* cx, js::HandleObject obj,
                                        js::MutableHandleAtom name);
 
     /*
      * The same as constructorDisplayAtom above, however if this object has a
@@ -892,16 +899,28 @@ GetPropertyNoGC(JSContext* cx, JSObject*
 }
 
 inline bool
 GetElementNoGC(JSContext* cx, JSObject* obj, const Value& receiver, uint32_t index, Value* vp);
 
 inline bool
 GetElementNoGC(JSContext* cx, JSObject* obj, JSObject* receiver, uint32_t index, Value* vp);
 
+// Returns whether |obj| or an object on its proto chain may have an interesting
+// symbol property (see JSObject::hasInterestingSymbolProperty). If it returns
+// true, *holder is set to the object that may have this property.
+MOZ_ALWAYS_INLINE bool
+MaybeHasInterestingSymbolProperty(JSContext* cx, JSObject* obj, Symbol* symbol,
+                                  JSObject** holder = nullptr);
+
+// Like GetProperty but optimized for interesting symbol properties like
+// @@toStringTag.
+MOZ_ALWAYS_INLINE bool
+GetInterestingSymbolProperty(JSContext* cx, HandleObject obj, Symbol* sym, MutableHandleValue vp);
+
 /*
  * ES6 [[Set]]. Carry out the assignment `obj[id] = v`.
  *
  * The `receiver` argument has to do with how [[Set]] interacts with the
  * prototype chain and proxies. It's hard to explain and ES6 doesn't really
  * try. Long story short, if you just want bog-standard assignment, pass
  * `ObjectValue(*obj)` as receiver. Or better, use one of the signatures that
  * doesn't have a receiver parameter.
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -40,16 +40,38 @@ MaybeConvertUnboxedObjectToNative(JSCont
 {
     if (obj->is<UnboxedPlainObject>())
         return UnboxedPlainObject::convertToNative(cx, obj);
     if (obj->is<UnboxedArrayObject>())
         return UnboxedArrayObject::convertToNative(cx, obj);
     return true;
 }
 
+static MOZ_ALWAYS_INLINE bool
+ClassMayResolveId(const JSAtomState& names, const Class* clasp, jsid id, JSObject* maybeObj)
+{
+    MOZ_ASSERT_IF(maybeObj, maybeObj->getClass() == clasp);
+
+    if (!clasp->getResolve()) {
+        // Sanity check: we should only have a mayResolve hook if we have a
+        // resolve hook.
+        MOZ_ASSERT(!clasp->getMayResolve(), "Class with mayResolve hook but no resolve hook");
+        return false;
+    }
+
+    if (JSMayResolveOp mayResolve = clasp->getMayResolve()) {
+        // Tell the analysis our mayResolve hooks won't trigger GC.
+        JS::AutoSuppressGCAnalysis nogc;
+        if (!mayResolve(names, id, maybeObj))
+            return false;
+    }
+
+    return true;
+}
+
 } // namespace js
 
 inline js::Shape*
 JSObject::maybeShape() const
 {
     if (!is<js::ShapedObject>())
         return nullptr;
 
@@ -237,16 +259,60 @@ inline bool
 js::DeleteElement(JSContext* cx, HandleObject obj, uint32_t index, ObjectOpResult& result)
 {
     RootedId id(cx);
     if (!IndexToId(cx, index, &id))
         return false;
     return DeleteProperty(cx, obj, id, result);
 }
 
+MOZ_ALWAYS_INLINE bool
+js::MaybeHasInterestingSymbolProperty(JSContext* cx, JSObject* obj, Symbol* symbol,
+                                      JSObject** holder)
+{
+    MOZ_ASSERT(symbol->isInterestingSymbol());
+
+    jsid id = SYMBOL_TO_JSID(symbol);
+    do {
+        if (obj->maybeHasInterestingSymbolProperty() ||
+            obj->hasDynamicPrototype() ||
+            MOZ_UNLIKELY(ClassMayResolveId(cx->names(), obj->getClass(), id, obj) ||
+                         obj->getClass()->getGetProperty()))
+        {
+            if (holder)
+                *holder = obj;
+            return true;
+        }
+        obj = obj->staticPrototype();
+    } while (obj);
+
+    return false;
+}
+
+MOZ_ALWAYS_INLINE bool
+js::GetInterestingSymbolProperty(JSContext* cx, HandleObject obj, Symbol* sym, MutableHandleValue vp)
+{
+    JSObject* holder;
+    if (!MaybeHasInterestingSymbolProperty(cx, obj, sym, &holder)) {
+#ifdef DEBUG
+        RootedValue receiver(cx, ObjectValue(*obj));
+        RootedId id(cx, SYMBOL_TO_JSID(sym));
+        if (!GetProperty(cx, obj, receiver, id, vp))
+            return false;
+        MOZ_ASSERT(vp.isUndefined());
+#endif
+        vp.setUndefined();
+        return true;
+    }
+
+    RootedObject holderRoot(cx, holder);
+    RootedValue receiver(cx, ObjectValue(*obj));
+    RootedId id(cx, SYMBOL_TO_JSID(sym));
+    return GetProperty(cx, holderRoot, receiver, id, vp);
+}
 
 /* * */
 
 inline bool
 JSObject::isQualifiedVarObj() const
 {
     if (is<js::DebugEnvironmentProxy>())
         return as<js::DebugEnvironmentProxy>().environment().isQualifiedVarObj();
@@ -406,16 +472,33 @@ JSObject::hadElementsAccess() const
 }
 
 inline bool
 JSObject::isIndexed() const
 {
     return hasAllFlags(js::BaseShape::INDEXED);
 }
 
+MOZ_ALWAYS_INLINE bool
+JSObject::maybeHasInterestingSymbolProperty() const
+{
+    const js::NativeObject* nobj;
+    if (isNative()) {
+        nobj = &as<js::NativeObject>();
+    } else if (is<js::UnboxedPlainObject>()) {
+        nobj = as<js::UnboxedPlainObject>().maybeExpando();
+        if (!nobj)
+            return false;
+    } else {
+        return true;
+    }
+
+    return nobj->hasAllFlags(js::BaseShape::HAS_INTERESTING_SYMBOL);
+}
+
 inline bool
 JSObject::staticPrototypeIsImmutable() const
 {
     MOZ_ASSERT(hasStaticPrototype());
     return hasAllFlags(js::BaseShape::IMMUTABLE_PROTOTYPE);
 }
 
 inline bool
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -667,38 +667,16 @@ CallResolveOp(JSContext* cx, HandleNativ
     if (shape)
         propp.setNativeProperty(shape);
     else
         propp.setNotFound();
 
     return true;
 }
 
-static MOZ_ALWAYS_INLINE bool
-ClassMayResolveId(const JSAtomState& names, const Class* clasp, jsid id, JSObject* maybeObj)
-{
-    MOZ_ASSERT_IF(maybeObj, maybeObj->getClass() == clasp);
-
-    if (!clasp->getResolve()) {
-        // Sanity check: we should only have a mayResolve hook if we have a
-        // resolve hook.
-        MOZ_ASSERT(!clasp->getMayResolve(), "Class with mayResolve hook but no resolve hook");
-        return false;
-    }
-
-    if (JSMayResolveOp mayResolve = clasp->getMayResolve()) {
-        // Tell the analysis our mayResolve hooks won't trigger GC.
-        JS::AutoSuppressGCAnalysis nogc;
-        if (!mayResolve(names, id, maybeObj))
-            return false;
-    }
-
-    return true;
-}
-
 template <AllowGC allowGC>
 static MOZ_ALWAYS_INLINE bool
 LookupOwnPropertyInline(JSContext* cx,
                         typename MaybeRooted<NativeObject*, allowGC>::HandleType obj,
                         typename MaybeRooted<jsid, allowGC>::HandleType id,
                         typename MaybeRooted<PropertyResult, allowGC>::MutableHandleType propp,
                         bool* donep)
 {
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -440,16 +440,34 @@ ShouldConvertToDictionary(NativeObject* 
      * Use a lower limit if this object is likely a hashmap (SETELEM was used
      * to set properties).
      */
     if (obj->hadElementsAccess())
         return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT_WITH_ELEMENTS_ACCESS;
     return obj->lastProperty()->entryCount() >= PropertyTree::MAX_HEIGHT;
 }
 
+static MOZ_ALWAYS_INLINE UnownedBaseShape*
+GetBaseShapeForNewShape(JSContext* cx, HandleShape last, HandleId id)
+{
+    uint32_t index;
+    bool indexed = IdIsIndex(id, &index);
+    bool interestingSymbol = JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id)->isInterestingSymbol();
+
+    if (MOZ_LIKELY(!indexed && !interestingSymbol))
+        return last->base()->unowned();
+
+    StackBaseShape base(last->base());
+    if (indexed)
+        base.flags |= BaseShape::INDEXED;
+    else if (interestingSymbol)
+        base.flags |= BaseShape::HAS_INTERESTING_SYMBOL;
+    return BaseShape::getUnowned(cx, base);
+}
+
 /* static */ Shape*
 NativeObject::addPropertyInternal(JSContext* cx,
                                   HandleNativeObject obj, HandleId id,
                                   GetterOp getter, SetterOp setter,
                                   uint32_t slot, unsigned attrs,
                                   unsigned flags, ShapeTable::Entry* entry,
                                   bool allowDictionary, const AutoKeepShapeTables& keep)
 {
@@ -492,30 +510,19 @@ NativeObject::addPropertyInternal(JSCont
     }
 
     MOZ_ASSERT(!!table == !!entry);
 
     /* Find or create a property tree node labeled by our arguments. */
     RootedShape shape(cx);
     {
         RootedShape last(cx, obj->lastProperty());
-
-        uint32_t index;
-        bool indexed = IdIsIndex(id, &index);
-
-        Rooted<UnownedBaseShape*> nbase(cx);
-        if (!indexed) {
-            nbase = last->base()->unowned();
-        } else {
-            StackBaseShape base(last->base());
-            base.flags |= BaseShape::INDEXED;
-            nbase = BaseShape::getUnowned(cx, base);
-            if (!nbase)
-                return nullptr;
-        }
+        Rooted<UnownedBaseShape*> nbase(cx, GetBaseShapeForNewShape(cx, last, id));
+        if (!nbase)
+            return nullptr;
 
         Rooted<StackShape> child(cx, StackShape(nbase, id, slot, attrs, flags));
         child.updateGetterSetter(getter, setter);
         shape = getChildProperty(cx, obj, last, &child);
     }
 
     if (shape) {
         MOZ_ASSERT(shape == obj->lastProperty());
@@ -564,27 +571,19 @@ js::ReshapeForAllocKind(JSContext* cx, S
     RootedShape newShape(cx, EmptyShape::getInitialShape(cx, shape->getObjectClass(),
                                                          proto, nfixed, shape->getObjectFlags()));
     if (!newShape)
         return nullptr;
 
     for (unsigned i = 0; i < ids.length(); i++) {
         id = ids[i];
 
-        uint32_t index;
-        bool indexed = IdIsIndex(id, &index);
-
-        Rooted<UnownedBaseShape*> nbase(cx, newShape->base()->unowned());
-        if (indexed) {
-            StackBaseShape base(nbase);
-            base.flags |= BaseShape::INDEXED;
-            nbase = BaseShape::getUnowned(cx, base);
-            if (!nbase)
-                return nullptr;
-        }
+        Rooted<UnownedBaseShape*> nbase(cx, GetBaseShapeForNewShape(cx, newShape, id));
+        if (!nbase)
+            return nullptr;
 
         Rooted<StackShape> child(cx, StackShape(nbase, id, i, JSPROP_ENUMERATE, 0));
         newShape = cx->zone()->propertyTree().getChild(cx, newShape, child);
         if (!newShape)
             return nullptr;
     }
 
     return newShape;
@@ -679,22 +678,18 @@ NativeObject::putProperty(JSContext* cx,
      */
     bool hadSlot = shape->hasSlot();
     uint32_t oldSlot = shape->maybeSlot();
     if (!(attrs & JSPROP_SHARED) && slot == SHAPE_INVALID_SLOT && hadSlot)
         slot = oldSlot;
 
     Rooted<UnownedBaseShape*> nbase(cx);
     {
-        uint32_t index;
-        bool indexed = IdIsIndex(id, &index);
-        StackBaseShape base(obj->lastProperty()->base());
-        if (indexed)
-            base.flags |= BaseShape::INDEXED;
-        nbase = BaseShape::getUnowned(cx, base);
+        RootedShape shape(cx, obj->lastProperty());
+        nbase = GetBaseShapeForNewShape(cx, shape, id);
         if (!nbase)
             return nullptr;
     }
 
     /*
      * Now that we've possibly preserved slot, check whether all members match.
      * If so, this is a redundant "put" and we can return without more work.
      */
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -384,17 +384,17 @@ class BaseShape : public gc::TenuredCell
          *
          * If you add a new flag here, please add appropriate code to
          * JSObject::dump to dump it as part of object representation.
          */
 
         DELEGATE            =    0x8,
         NOT_EXTENSIBLE      =   0x10,
         INDEXED             =   0x20,
-        /* (0x40 is unused) */
+        HAS_INTERESTING_SYMBOL = 0x40,
         HAD_ELEMENTS_ACCESS =   0x80,
         WATCHED             =  0x100,
         ITERATED_SINGLETON  =  0x200,
         NEW_GROUP_UNKNOWN   =  0x400,
         UNCACHEABLE_PROTO   =  0x800,
         IMMUTABLE_PROTOTYPE = 0x1000,
 
         // See JSObject::isQualifiedVarObj().
--- a/js/src/vm/Symbol.h
+++ b/js/src/vm/Symbol.h
@@ -63,16 +63,25 @@ class Symbol : public js::gc::TenuredCel
     static Symbol* for_(JSContext* cx, js::HandleString description);
 
     JSAtom* description() const { return description_; }
     SymbolCode code() const { return code_; }
     js::HashNumber hash() const { return hash_; }
 
     bool isWellKnownSymbol() const { return uint32_t(code_) < WellKnownSymbolLimit; }
 
+    // An "interesting symbol" is a well-known symbol, like @@toStringTag,
+    // that's often looked up on random objects but is usually not present. We
+    // optimize this by setting a flag on the object's BaseShape when such
+    // symbol properties are added, so we can optimize lookups on objects that
+    // don't have the BaseShape flag.
+    bool isInterestingSymbol() const {
+        return code_ == SymbolCode::toStringTag || code_ == SymbolCode::toPrimitive;
+    }
+
     static const JS::TraceKind TraceKind = JS::TraceKind::Symbol;
     inline void traceChildren(JSTracer* trc) {
         if (description_)
             js::TraceManuallyBarrieredEdge(trc, &description_, "description");
     }
     inline void finalize(js::FreeOp*) {}
 
     static MOZ_ALWAYS_INLINE void writeBarrierPre(Symbol* thing) {
--- a/layout/base/tests/chrome/test_bug533845.xul
+++ b/layout/base/tests/chrome/test_bug533845.xul
@@ -5,40 +5,49 @@
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=533845
 -->
 <window title="Mozilla Bug 533845"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         onload="doTest()">
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-<panel id="panel" width="50" height="50" onpopupshown="continueTest()">
+<panel id="panel" width="50" height="50">
   <iframe type="content" id="contentFrame" src="data:text/html,&lt;html&gt;&lt;body onclick='document.body.textContent=1'&gt;This is a panel!&lt;/body&gt;&lt;/html&gt;" width="500" height="500"/>
 </panel>
 <body xmlns="http://www.w3.org/1999/xhtml">
 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=533845"
    target="_blank">Mozilla Bug 533845</a>
 </body>
   <!-- test code goes here -->
 <script type="application/javascript">
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
 
 function doTest() {
-  document.getElementById('panel').showPopup();
+  let panel = document.getElementById("panel");
+  panel.addEventListener("popupshown", function onpopupshown() {
+    panel.removeEventListener("popupshown", onpopupshown);
+    continueTest();
+    panel.addEventListener("popuphidden", function onpopuphidden() {
+      panel.removeEventListener("popuphidden", onpopuphidden);
+      SimpleTest.finish();
+    });
+    panel.hidePopup();
+  });
+  panel.openPopup();
 }
 
 function continueTest() {
   var ifrwindow = document.getElementById("contentFrame").contentWindow;
   ifrwindow.focus();
   var utils = ifrwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                        .getInterface(Components.interfaces.nsIDOMWindowUtils);
   var rect = ifrwindow.document.body.getBoundingClientRect();
   var x = rect.left + (rect.width/2);
   var y = rect.top + (rect.height/2);
   utils.sendMouseEvent("mousedown", x, y, 0, 1, 0);
   utils.sendMouseEvent("mouseup", x, y, 0, 1, 0);
   is(ifrwindow.document.body.textContent, "1", "Should have got a click event!");
-  SimpleTest.finish();
 }
 
 ]]></script>
 </window>
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -178,16 +178,31 @@ public:
 #endif
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
                            bool* aSnap) override {
     *aSnap = false;
     nsRect shadowRect =
       nsLayoutUtils::GetTextShadowRectsUnion(mRect, mFrame);
     return mRect.Union(shadowRect);
   }
+
+  virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
+  {
+    if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
+      // On OS X, web authors can turn off subpixel text rendering using the
+      // CSS property -moz-osx-font-smoothing. If they do that, we don't need
+      // to use component alpha layers for the affected text.
+      if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
+        return nsRect();
+      }
+    }
+    bool snap;
+    return GetBounds(aBuilder, &snap);
+  }
+
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      nsRenderingContext* aCtx) override;
 
   virtual uint32_t GetPerFrameKey() override { 
     return (mIndex << nsDisplayItem::TYPE_BITS) | nsDisplayItem::GetPerFrameKey(); 
   }
   void PaintTextToContext(nsRenderingContext* aCtx,
                           nsPoint aOffsetFromRect);
--- a/layout/generic/nsColumnSetFrame.cpp
+++ b/layout/generic/nsColumnSetFrame.cpp
@@ -82,16 +82,22 @@ nsDisplayColumnRule::GetLayerState(nsDis
 
   static_cast<nsColumnSetFrame*>(mFrame)->
     CreateBorderRenderers(mBorderRenderers, &ctx, mVisibleRect, ToReferenceFrame());
 
   if (mBorderRenderers.IsEmpty()) {
     return LAYER_NONE;
   }
 
+  for (auto iter = mBorderRenderers.begin(); iter != mBorderRenderers.end(); iter++) {
+    if (!iter->CanCreateWebRenderCommands()) {
+      return LAYER_NONE;
+    }
+  }
+
   return LAYER_ACTIVE;
 }
 
 already_AddRefed<Layer>
 nsDisplayColumnRule::BuildLayer(nsDisplayListBuilder* aBuilder,
                                 LayerManager* aManager,
                                 const ContainerLayerParameters& aContainerParameters)
 {
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -4899,19 +4899,17 @@ public:
   NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
 
   virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override
   {
     if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
       // On OS X, web authors can turn off subpixel text rendering using the
       // CSS property -moz-osx-font-smoothing. If they do that, we don't need
       // to use component alpha layers for the affected text.
-      nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
-      const nsStyleFont* fontStyle = f->StyleFont();
-      if (fontStyle->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
+      if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
         return nsRect();
       }
     }
     bool snap;
     return GetBounds(aBuilder, &snap);
   }
 
   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
--- a/layout/mathml/nsMathMLFrame.cpp
+++ b/layout/mathml/nsMathMLFrame.cpp
@@ -271,28 +271,16 @@ nsMathMLFrame::ParseNumericValue(const n
     return;
   }
   
   // Absolute units.
   *aLengthValue = CalcLength(aPresContext, aStyleContext, cssValue,
                              aFontSizeInflation);
 }
 
-// ================
-// Utils to map attributes into CSS rules (work-around to bug 69409 which
-// is not scheduled to be fixed anytime soon)
-//
-
-struct
-nsCSSMapping {
-  int32_t        compatibility;
-  const nsIAtom* attrAtom;
-  const char*    cssProperty;
-};
-
 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
 class nsDisplayMathMLBoundingMetrics : public nsDisplayItem {
 public:
   nsDisplayMathMLBoundingMetrics(nsDisplayListBuilder* aBuilder,
                                  nsIFrame* aFrame, const nsRect& aRect)
     : nsDisplayItem(aBuilder, aFrame), mRect(aRect) {
     MOZ_COUNT_CTOR(nsDisplayMathMLBoundingMetrics);
   }
--- a/layout/reftests/columns/reftest.list
+++ b/layout/reftests/columns/reftest.list
@@ -1,16 +1,16 @@
 == basic-1.html basic-ref.html
 == pref-width-1a.html pref-width-1-ref.html
 == pref-width-1b.html pref-width-1-ref.html
 == pref-width-1c.html pref-width-1-ref.html
 == min-width-1a.html pref-width-1-ref.html
 == min-width-1b.html min-width-1-ref.html
 == min-width-1c.html min-width-1-ref.html
-== min-width-2.html min-width-2-ref.html
+fuzzy-if(webrender,255,2) == min-width-2.html min-width-2-ref.html
 == column-balancing-overflow-000.html column-balancing-overflow-000.ref.html
 == column-balancing-overflow-001.html column-balancing-overflow-000.ref.html
 == column-balancing-overflow-002.html column-balancing-overflow-002.ref.html
 == column-balancing-overflow-003.html column-balancing-overflow-003.ref.html
 == column-balancing-overflow-004.html column-balancing-overflow-004.ref.html
 fuzzy-if(webrender,126,364) == column-balancing-overflow-005.html column-balancing-overflow-005.ref.html
 == column-balancing-000.html column-balancing-000.ref.html
 == column-balancing-001.html column-balancing-000.ref.html
--- a/layout/reftests/w3c-css/received/reftest.list
+++ b/layout/reftests/w3c-css/received/reftest.list
@@ -126,33 +126,41 @@ fails-if(OSX||winWidget) == css-multicol
 == css-multicol-1/multicol-rule-color-inherit-001.xht css-multicol-1/multicol-rule-color-inherit-001-ref.xht
 == css-multicol-1/multicol-rule-color-inherit-002.xht css-multicol-1/multicol-rule-color-inherit-001-ref.xht
 fuzzy(106,354) == css-multicol-1/multicol-rule-dashed-000.xht css-multicol-1/multicol-rule-dashed-000-ref.xht
 fuzzy(106,354) == css-multicol-1/multicol-rule-dotted-000.xht css-multicol-1/multicol-rule-dotted-000-ref.xht
 fuzzy(106,354) == css-multicol-1/multicol-rule-double-000.xht css-multicol-1/multicol-rule-double-000-ref.xht
 fails-if(OSX||winWidget) == css-multicol-1/multicol-rule-fraction-001.xht css-multicol-1/multicol-rule-fraction-001-ref.xht
 fails-if(OSX||winWidget) == css-multicol-1/multicol-rule-fraction-002.xht css-multicol-1/multicol-rule-fraction-002-ref.xht
 fails-if(!styloVsGecko) == css-multicol-1/multicol-rule-fraction-003.xht css-multicol-1/multicol-rule-fraction-3-ref.xht
-fuzzy(106,354) == css-multicol-1/multicol-rule-groove-000.xht css-multicol-1/multicol-rule-groove-000-ref.xht
+fuzzy(106,354) fails-if(webrender) == css-multicol-1/multicol-rule-groove-000.xht css-multicol-1/multicol-rule-groove-000-ref.xht
+skip-if(!webrender) pref(layers.advanced.border-layers,1) == css-multicol-1/multicol-rule-groove-000.xht css-multicol-1/multicol-rule-groove-000-ref.xht
 fuzzy(94,256) == css-multicol-1/multicol-rule-hidden-000.xht css-multicol-1/multicol-rule-hidden-000-ref.xht
-fuzzy(106,354) == css-multicol-1/multicol-rule-inset-000.xht css-multicol-1/multicol-rule-ridge-000-ref.xht
+fuzzy(106,354) fails-if(webrender) == css-multicol-1/multicol-rule-inset-000.xht css-multicol-1/multicol-rule-ridge-000-ref.xht
+skip-if(!webrender) pref(layers.advanced.border-layers,1) == css-multicol-1/multicol-rule-inset-000.xht css-multicol-1/multicol-rule-ridge-000-ref.xht
 fuzzy(255,2808) == css-multicol-1/multicol-rule-large-001.xht css-multicol-1/multicol-rule-large-001-ref.xht
 fuzzy(94,256) == css-multicol-1/multicol-rule-none-000.xht css-multicol-1/multicol-rule-hidden-000-ref.xht
-fuzzy(106,354) == css-multicol-1/multicol-rule-outset-000.xht css-multicol-1/multicol-rule-groove-000-ref.xht
+fuzzy(106,354) fails-if(webrender) == css-multicol-1/multicol-rule-outset-000.xht css-multicol-1/multicol-rule-groove-000-ref.xht
+skip-if(!webrender) pref(layers.advanced.border-layers,1) == css-multicol-1/multicol-rule-outset-000.xht css-multicol-1/multicol-rule-groove-000-ref.xht
 == css-multicol-1/multicol-rule-percent-001.xht css-multicol-1/multicol-containing-002-ref.xht
 fails-if(!styloVsGecko) == css-multicol-1/multicol-rule-px-001.xht css-multicol-1/multicol-rule-ref.xht
-fuzzy(106,354) == css-multicol-1/multicol-rule-ridge-000.xht css-multicol-1/multicol-rule-ridge-000-ref.xht
+fuzzy(106,354) fails-if(webrender) == css-multicol-1/multicol-rule-ridge-000.xht css-multicol-1/multicol-rule-ridge-000-ref.xht
+skip-if(!webrender) pref(layers.advanced.border-layers,1) == css-multicol-1/multicol-rule-ridge-000.xht css-multicol-1/multicol-rule-ridge-000-ref.xht
 == css-multicol-1/multicol-rule-samelength-001.xht css-multicol-1/multicol-rule-samelength-001-ref.xht
 == css-multicol-1/multicol-rule-shorthand-001.xht css-multicol-1/multicol-rule-samelength-001-ref.xht
 fuzzy(106,354) == css-multicol-1/multicol-rule-solid-000.xht css-multicol-1/multicol-rule-solid-000-ref.xht
 fails-if(!styloVsGecko) == css-multicol-1/multicol-rule-stacking-001.xht css-multicol-1/multicol-rule-stacking-ref.xht
-== css-multicol-1/multicol-rule-style-groove-001.xht css-multicol-1/multicol-rule-style-groove-001-ref.xht
-== css-multicol-1/multicol-rule-style-inset-001.xht css-multicol-1/multicol-rule-style-ridge-001-ref.xht
-== css-multicol-1/multicol-rule-style-outset-001.xht css-multicol-1/multicol-rule-style-groove-001-ref.xht
-== css-multicol-1/multicol-rule-style-ridge-001.xht css-multicol-1/multicol-rule-style-ridge-001-ref.xht
+fails-if(webrender) == css-multicol-1/multicol-rule-style-groove-001.xht css-multicol-1/multicol-rule-style-groove-001-ref.xht
+skip-if(!webrender) pref(layers.advanced.table,1) == css-multicol-1/multicol-rule-style-groove-001.xht css-multicol-1/multicol-rule-style-groove-001-ref.xht
+fails-if(webrender) == css-multicol-1/multicol-rule-style-inset-001.xht css-multicol-1/multicol-rule-style-ridge-001-ref.xht
+skip-if(!webrender) pref(layers.advanced.table,1) == css-multicol-1/multicol-rule-style-inset-001.xht css-multicol-1/multicol-rule-style-ridge-001-ref.xht
+fails-if(webrender) == css-multicol-1/multicol-rule-style-outset-001.xht css-multicol-1/multicol-rule-style-groove-001-ref.xht
+skip-if(!webrender) pref(layers.advanced.table,1) == css-multicol-1/multicol-rule-style-outset-001.xht css-multicol-1/multicol-rule-style-groove-001-ref.xht
+fails-if(webrender) == css-multicol-1/multicol-rule-style-ridge-001.xht css-multicol-1/multicol-rule-style-ridge-001-ref.xht
+skip-if(!webrender) pref(layers.advanced.table,1) == css-multicol-1/multicol-rule-style-ridge-001.xht css-multicol-1/multicol-rule-style-ridge-001-ref.xht
 fails-if(!styloVsGecko) == css-multicol-1/multicol-shorthand-001.xht css-multicol-1/multicol-rule-ref.xht
 fails-if(!styloVsGecko) == css-multicol-1/multicol-span-000.xht css-multicol-1/multicol-span-000-ref.xht
 fails-if(!styloVsGecko) == css-multicol-1/multicol-span-all-001.xht css-multicol-1/multicol-span-all-001-ref.xht
 fails-if(!styloVsGecko) == css-multicol-1/multicol-span-all-002.xht css-multicol-1/multicol-span-all-002-ref.xht
 fails-if(!styloVsGecko) == css-multicol-1/multicol-span-all-003.xht css-multicol-1/multicol-count-002-ref.xht
 fails-if(!styloVsGecko) == css-multicol-1/multicol-span-all-block-sibling-003.xht css-multicol-1/multicol-span-all-block-sibling-3-ref.xht
 fails-if(!styloVsGecko) == css-multicol-1/multicol-span-all-child-001.xht css-multicol-1/multicol-span-all-child-001-ref.xht
 fails-if(OSX||winWidget) == css-multicol-1/multicol-span-all-child-002.xht css-multicol-1/multicol-span-all-child-002-ref.xht
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -1719,17 +1719,17 @@ CSS_PROP_SVG(
     offsetof(nsStyleSVG, mFill),
     eStyleAnimType_PaintServer)
 CSS_PROP_SVG(
     fill-opacity,
     fill_opacity,
     FillOpacity,
     CSS_PROPERTY_PARSE_VALUE,
     "",
-    VARIANT_HN | VARIANT_OPENTYPE_SVG_KEYWORD,
+    VARIANT_HN | VARIANT_KEYWORD,
     kContextOpacityKTable,
     offsetof(nsStyleSVG, mFillOpacity),
     eStyleAnimType_float)
 CSS_PROP_SVG(
     fill-rule,
     fill_rule,
     FillRule,
     CSS_PROPERTY_PARSE_VALUE,
@@ -3864,17 +3864,17 @@ CSS_PROP_SVG(
     offsetof(nsStyleSVG, mStrokeMiterlimit),
     eStyleAnimType_float)
 CSS_PROP_SVG(
     stroke-opacity,
     stroke_opacity,
     StrokeOpacity,
     CSS_PROPERTY_PARSE_VALUE,
     "",
-    VARIANT_HN | VARIANT_OPENTYPE_SVG_KEYWORD,
+    VARIANT_HN | VARIANT_KEYWORD,
     kContextOpacityKTable,
     offsetof(nsStyleSVG, mStrokeOpacity),
     eStyleAnimType_float)
 CSS_PROP_SVG(
     stroke-width,
     stroke_width,
     StrokeWidth,
     CSS_PROPERTY_PARSE_VALUE |
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -9644,16 +9644,20 @@ nsRuleNode::ComputeSVGData(void* aStartS
          item; item = item->mNext)
     {
       nsIAtom* atom = item->mValue.GetAtomValue();
       svg->mContextProps.AppendElement(atom);
       if (atom == nsGkAtoms::fill) {
         svg->mContextPropsBits |= NS_STYLE_CONTEXT_PROPERTY_FILL;
       } else if (atom == nsGkAtoms::stroke) {
         svg->mContextPropsBits |= NS_STYLE_CONTEXT_PROPERTY_STROKE;
+      } else if (atom == nsGkAtoms::fill_opacity) {
+        svg->mContextPropsBits |= NS_STYLE_CONTEXT_PROPERTY_FILL_OPACITY;
+      } else if (atom == nsGkAtoms::stroke_opacity) {
+        svg->mContextPropsBits |= NS_STYLE_CONTEXT_PROPERTY_STROKE_OPACITY;
       }
     }
     break;
   }
 
   case eCSSUnit_Inherit:
   case eCSSUnit_Unset:
     svg->mContextProps = parentSVG->mContextProps;
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -1057,16 +1057,18 @@ enum class StyleWhiteSpace : uint8_t {
 #define NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER  3
 #define NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE    4
 
 // See nsStyleSVG
 
 // -moz-context-properties
 #define NS_STYLE_CONTEXT_PROPERTY_FILL          (1 << 0)
 #define NS_STYLE_CONTEXT_PROPERTY_STROKE        (1 << 1)
+#define NS_STYLE_CONTEXT_PROPERTY_FILL_OPACITY   (1 << 2)
+#define NS_STYLE_CONTEXT_PROPERTY_STROKE_OPACITY (1 << 3)
 
 /*
  * -moz-window-shadow
  * Also used in widget code
  */
 
 #define NS_STYLE_WINDOW_SHADOW_NONE             0
 #define NS_STYLE_WINDOW_SHADOW_DEFAULT          1
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -75,17 +75,17 @@ to mochitest command.
     * test_value_storage.html `-moz-linear-gradient` [322]
     * ... `-moz-radial-gradient` [309]
     * ... `-moz-repeating-` [298]
   * -webkit-{flex,inline-flex} for display servo/servo#15400
     * test_webkit_flex_display.html [4]
 * Unsupported values
   * SVG-in-OpenType values not supported servo/servo#15211 bug 1355412
     * test_value_storage.html `context-` [7]
-    * test_bug798843_pref.html [5]
+    * test_bug798843_pref.html [3]
 * Incorrect parsing
   * different parsing bug 1364260
     * test_supports_rules.html [6]
     * test_condition_text.html [1]
 * Incorrect serialization
   * color value not canonicalized servo/servo#15397
     * test_shorthand_property_getters.html `should condense to canonical case` [2]
   * place-{content,items,self} shorthands bug 1363971
--- a/layout/style/test/test_bug798843_pref.html
+++ b/layout/style/test/test_bug798843_pref.html
@@ -8,18 +8,16 @@
   <title>Test that SVG glyph context-* values can be pref'ed off</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
 </head>
 <body>
 
 <script>
 
 var props = {
-  "fillOpacity" : "context-stroke-opacity",
-  "strokeOpacity" : "context-fill-opacity",
   "strokeDasharray" : "context-value",
   "strokeDashoffset" : "context-value",
   "strokeWidth" : "context-value"
 };
 
 function testDisabled() {
   for (var p in props) {
     document.body.style[p] = props[p];
--- a/layout/svg/SVGContextPaint.cpp
+++ b/layout/svg/SVGContextPaint.cpp
@@ -390,12 +390,20 @@ SVGEmbeddingContextPaint::Hash() const
     // another context has stroke set to.
     hash = 1;
   }
 
   if (mStroke) {
     hash = HashGeneric(hash, mStroke->ToABGR());
   }
 
+  if (mFillOpacity != 1.0f) {
+    hash = HashGeneric(hash, mFillOpacity);
+  }
+
+  if (mStrokeOpacity != 1.0f) {
+    hash = HashGeneric(hash, mStrokeOpacity);
+  }
+
   return hash;
 }
 
 } // namespace mozilla
--- a/layout/svg/SVGContextPaint.h
+++ b/layout/svg/SVGContextPaint.h
@@ -246,25 +246,31 @@ public:
  * it from a CSS property such as 'background-image').  In this case we only
  * support context colors and not paint servers.
  */
 class SVGEmbeddingContextPaint : public SVGContextPaint
 {
   typedef gfx::Color Color;
 
 public:
-  SVGEmbeddingContextPaint() {}
+  SVGEmbeddingContextPaint()
+    : mFillOpacity(1.0f)
+    , mStrokeOpacity(1.0f)
+  {}
 
   bool operator==(const SVGEmbeddingContextPaint& aOther) const {
     MOZ_ASSERT(GetStrokeWidth() == aOther.GetStrokeWidth() &&
                GetStrokeDashOffset() == aOther.GetStrokeDashOffset() &&
                GetStrokeDashArray() == aOther.GetStrokeDashArray(),
                "We don't currently include these in the context information "
                "from an embedding element");
-    return mFill == aOther.mFill && mStroke == aOther.mStroke;
+    return mFill == aOther.mFill &&
+           mStroke == aOther.mStroke &&
+           mFillOpacity == aOther.mFillOpacity &&
+           mStrokeOpacity == aOther.mStrokeOpacity;
   }
 
   void SetFill(nscolor aFill) {
     mFill.emplace(gfx::ToDeviceColor(aFill));
   }
   void SetStroke(nscolor aStroke) {
     mStroke.emplace(gfx::ToDeviceColor(aStroke));
   }
@@ -278,29 +284,35 @@ public:
 
   /**
    * Returns a pattern of type PatternType::COLOR, or else nullptr.
    */
   already_AddRefed<gfxPattern>
   GetStrokePattern(const DrawTarget* aDrawTarget, float aStrokeOpacity,
                    const gfxMatrix& aCTM, imgDrawingParams& aImgParams) override;
 
+  void SetFillOpacity(float aOpacity) {
+    mFillOpacity = aOpacity;
+  }
   float GetFillOpacity() const override {
-    // Always 1.0f since we don't currently allow 'context-fill-opacity'
-    return 1.0f;
+    return mFillOpacity;
   };
 
+  void SetStrokeOpacity(float aOpacity) {
+    mStrokeOpacity = aOpacity;
+  }
   float GetStrokeOpacity() const override {
-    // Always 1.0f since we don't currently allow 'context-stroke-opacity'
-    return 1.0f;
+    return mStrokeOpacity;
   };
 
   uint32_t Hash() const override;
 
 private:
   Maybe<Color> mFill;
   Maybe<Color> mStroke;
+  float mFillOpacity;
+  float mStrokeOpacity;
 };
 
 } // namespace mozilla
 
 #endif // MOZILLA_SVGCONTEXTPAINT_H_
 
--- a/layout/svg/SVGImageContext.cpp
+++ b/layout/svg/SVGImageContext.cpp
@@ -44,16 +44,24 @@ SVGImageContext::MaybeStoreContextPaint(
     haveContextPaint = true;
     contextPaint->SetFill(style->mFill.GetColor());
   }
   if ((style->mContextPropsBits & NS_STYLE_CONTEXT_PROPERTY_STROKE) &&
       style->mStroke.Type() == eStyleSVGPaintType_Color) {
     haveContextPaint = true;
     contextPaint->SetStroke(style->mStroke.GetColor());
   }
+  if (style->mContextPropsBits & NS_STYLE_CONTEXT_PROPERTY_FILL_OPACITY) {
+    haveContextPaint = true;
+    contextPaint->SetFillOpacity(style->mFillOpacity);
+  }
+  if (style->mContextPropsBits & NS_STYLE_CONTEXT_PROPERTY_STROKE_OPACITY) {
+    haveContextPaint = true;
+    contextPaint->SetStrokeOpacity(style->mStrokeOpacity);
+  }
 
   if (haveContextPaint) {
     if (!aContext) {
       aContext.emplace();
     }
     aContext->mContextPaint = contextPaint.forget();
   }
 }
--- a/netwerk/base/nsStreamTransportService.cpp
+++ b/netwerk/base/nsStreamTransportService.cpp
@@ -562,16 +562,18 @@ nsStreamTransportService::Observe(nsISup
 class AvailableEvent final : public Runnable
 {
     public:
     AvailableEvent(nsIInputStream *stream,
                    nsIInputAvailableCallback *callback)
         : mStream(stream)
         , mCallback(callback)
         , mDoingCallback(false)
+        , mSize(0)
+        , mResultForCallback(NS_OK)
     {
         mCallbackTarget = NS_GetCurrentThread();
     }
 
     NS_IMETHOD Run() override
     {
         if (mDoingCallback) {
             // pong
--- a/netwerk/cache2/moz.build
+++ b/netwerk/cache2/moz.build
@@ -19,35 +19,39 @@ XPIDL_SOURCES += [
 
 XPIDL_MODULE = 'necko_cache2'
 
 EXPORTS += [
     'CacheObserver.h',
     'CacheStorageService.h',
 ]
 
+SOURCES += [
+    'AppCacheStorage.cpp',
+    'CacheStorage.cpp',
+]
+
+
 UNIFIED_SOURCES += [
-    'AppCacheStorage.cpp',
     'CacheEntry.cpp',
     'CacheFile.cpp',
     'CacheFileChunk.cpp',
     'CacheFileContextEvictor.cpp',
     'CacheFileInputStream.cpp',
     'CacheFileIOManager.cpp',
     'CacheFileMetadata.cpp',
     'CacheFileOutputStream.cpp',
     'CacheFileUtils.cpp',
     'CacheHashUtils.cpp',
     'CacheIndex.cpp',
     'CacheIndexContextIterator.cpp',
     'CacheIndexIterator.cpp',
     'CacheIOThread.cpp',
     'CacheLog.cpp',
     'CacheObserver.cpp',
-    'CacheStorage.cpp',
     'CacheStorageService.cpp',
     'OldWrappers.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/netwerk/base',
     '/netwerk/cache',
 ]
--- a/python/mozboot/mozboot/bootstrap.py
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -330,19 +330,20 @@ class Bootstrapper(object):
             if wants_stylo and not state_dir_available:
                 print(STYLO_DIRECTORY_MESSAGE.format(statedir=state_dir))
                 sys.exit(1)
 
             if wants_stylo and not have_clone:
                 print(STYLO_REQUIRES_CLONE)
                 sys.exit(1)
 
-            self.instance.stylo = True
-            self.instance.state_dir = state_dir
-            self.instance.ensure_stylo_packages(state_dir, checkout_root)
+            self.instance.stylo = wants_stylo
+            if wants_stylo:
+                self.instance.state_dir = state_dir
+                self.instance.ensure_stylo_packages(state_dir, checkout_root)
 
         print(self.finished % name)
         if not (self.instance.which('rustc') and self.instance._parse_version('rustc') >= MODERN_RUST_VERSION):
             print("To build %s, please restart the shell (Start a new terminal window)" % name)
 
         # Like 'suggest_browser_mozconfig' or 'suggest_mobile_android_mozconfig'.
         getattr(self.instance, 'suggest_%s_mozconfig' % application)()
 
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -489,16 +489,62 @@ uint32_t ActivePS::sNextGeneration = 0;
 
 #undef PS_GET
 #undef PS_GET_LOCKLESS
 #undef PS_GET_AND_SET
 
 // The mutex that guards accesses to CorePS and ActivePS.
 static PSMutex gPSMutex;
 
+// The preferred way to check profiler activeness and features is via
+// ActivePS(). However, that requires locking gPSMutex. There are some hot
+// operations where absolute precision isn't required, so we duplicate the
+// activeness/feature state in a lock-free manner in this class.
+class RacyFeatures
+{
+public:
+  static void SetActive(uint32_t aFeatures)
+  {
+    sActiveAndFeatures = Active | aFeatures;
+  }
+
+  static void SetInactive() { sActiveAndFeatures = 0; }
+
+  static bool IsActive() { return uint32_t(sActiveAndFeatures) & Active; }
+
+  static bool IsActiveWithFeature(uint32_t aFeature)
+  {
+    uint32_t af = sActiveAndFeatures;  // copy it first
+    return (af & Active) && (af & aFeature);
+  }
+
+  static bool IsActiveWithoutPrivacy()
+  {
+    uint32_t af = sActiveAndFeatures;  // copy it first
+    return (af & Active) && !(af & ProfilerFeature::Privacy);
+  }
+
+private:
+  static const uint32_t Active = 1u << 31;
+
+  // Ensure Active doesn't overlap with any of the feature bits.
+  #define NO_OVERLAP(n_, str_, Name_) \
+    static_assert(ProfilerFeature::Name_ != Active, "bad Active value");
+
+  PROFILER_FOR_EACH_FEATURE(NO_OVERLAP);
+
+  #undef NO_OVERLAP
+
+  // We combine the active bit with the feature bits so they can be read or
+  // written in a single atomic operation.
+  static Atomic<uint32_t> sActiveAndFeatures;
+};
+
+Atomic<uint32_t> RacyFeatures::sActiveAndFeatures(0);
+
 // Each live thread has a ThreadInfo, and we store a reference to it in TLS.
 // This class encapsulates that TLS.
 class TLSInfo
 {
 public:
   static bool Init(PSLockRef)
   {
     bool ok1 = sThreadInfo.init();
@@ -2405,16 +2451,19 @@ locked_profiler_start(PSLockRef aLock, i
     int javaInterval = interval;
     // Java sampling doesn't accurately keep up with 1ms sampling.
     if (javaInterval < 10) {
       javaInterval = 10;
     }
     mozilla::java::GeckoJavaSampler::Start(javaInterval, 1000);
   }
 #endif
+
+  // At the very end, set up RacyFeatures.
+  RacyFeatures::SetActive(ActivePS::Features(aLock));
 }
 
 void
 profiler_start(int aEntries, double aInterval, uint32_t aFeatures,
                const char** aFilters, uint32_t aFilterCount)
 {
   LOG("profiler_start");
 
@@ -2450,16 +2499,19 @@ profiler_start(int aEntries, double aInt
 
 static MOZ_MUST_USE SamplerThread*
 locked_profiler_stop(PSLockRef aLock)
 {
   LOG("locked_profiler_stop");
 
   MOZ_RELEASE_ASSERT(CorePS::Exists() && ActivePS::Exists(aLock));
 
+  // At the very start, clear RacyFeatures.
+  RacyFeatures::SetInactive();
+
 #ifdef MOZ_TASK_TRACER
   if (ActivePS::FeatureTaskTracer(aLock)) {
     mozilla::tasktracer::StopLogging();
   }
 #endif
 
   // Stop sampling live threads.
   Thread::tid_t tid = Thread::GetCurrentId();
@@ -2593,35 +2645,29 @@ profiler_resume()
 
 bool
 profiler_feature_active(uint32_t aFeature)
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
-  PSAutoLock lock(gPSMutex);
-
-  if (!ActivePS::Exists(lock)) {
-    return false;
-  }
-
-  return !!(ActivePS::Features(lock) & aFeature);
+  // This function is hot enough that we use RacyFeatures, not ActivePS.
+  return RacyFeatures::IsActiveWithFeature(aFeature);
 }
 
 bool
 profiler_is_active()
 {
   // This function runs both on and off the main thread.
 
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
-  PSAutoLock lock(gPSMutex);
-
-  return ActivePS::Exists(lock);
+  // This function is hot enough that we use RacyFeatures, notActivePS.
+  return RacyFeatures::IsActive();
 }
 
 void
 profiler_register_thread(const char* aName, void* aGuessStackTop)
 {
   DEBUG_LOG("profiler_register_thread(%s)", aName);
 
   MOZ_RELEASE_ASSERT(!NS_IsMainThread());
@@ -2846,26 +2892,31 @@ profiler_get_backtrace_noalloc(char *out
       output += labelLength;
     }
     *output++ = '\0';
     *output = '\0';
   }
 }
 
 static void
-locked_profiler_add_marker(PSLockRef aLock, const char* aMarkerName,
-                           ProfilerMarkerPayload* aPayload)
+racy_profiler_add_marker(const char* aMarkerName,
+                         ProfilerMarkerPayload* aPayload)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
-  MOZ_RELEASE_ASSERT(ActivePS::Exists(aLock) &&
-                     !ActivePS::FeaturePrivacy(aLock));
 
   // aPayload must be freed if we return early.
   mozilla::UniquePtr<ProfilerMarkerPayload> payload(aPayload);
 
+  // We don't assert that RacyFeatures::IsActiveWithoutPrivacy() is true here,
+  // because it's possible that the result has changed since we tested it in
+  // the caller.
+  //
+  // Because of this imprecision it's possible to miss a marker or record one
+  // we shouldn't. Either way is not a big deal.
+
   RacyThreadInfo* racyInfo = TLSInfo::RacyInfo();
   if (!racyInfo) {
     return;
   }
 
   mozilla::TimeStamp origin = (payload && !payload->GetStartTime().IsNull())
                             ? payload->GetStartTime()
                             : mozilla::TimeStamp::Now();
@@ -2874,59 +2925,56 @@ locked_profiler_add_marker(PSLockRef aLo
                              delta.ToMilliseconds());
 }
 
 void
 profiler_add_marker(const char* aMarkerName, ProfilerMarkerPayload* aPayload)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
-  PSAutoLock lock(gPSMutex);
-
   // aPayload must be freed if we return early.
   mozilla::UniquePtr<ProfilerMarkerPayload> payload(aPayload);
 
-  if (!ActivePS::Exists(lock) || ActivePS::FeaturePrivacy(lock)) {
+  // This function is hot enough that we use RacyFeatures, notActivePS.
+  if (!RacyFeatures::IsActiveWithoutPrivacy()) {
     return;
   }
 
-  locked_profiler_add_marker(lock, aMarkerName, payload.release());
+  racy_profiler_add_marker(aMarkerName, payload.release());
 }
 
 void
 profiler_tracing(const char* aCategory, const char* aMarkerName,
                  TracingKind aKind)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
-  PSAutoLock lock(gPSMutex);
-
-  if (!ActivePS::Exists(lock) || ActivePS::FeaturePrivacy(lock)) {
+  // This function is hot enough that we use RacyFeatures, notActivePS.
+  if (!RacyFeatures::IsActiveWithoutPrivacy()) {
     return;
   }
 
   auto payload = new ProfilerMarkerTracing(aCategory, aKind);
-  locked_profiler_add_marker(lock, aMarkerName, payload);
+  racy_profiler_add_marker(aMarkerName, payload);
 }
 
 void
 profiler_tracing(const char* aCategory, const char* aMarkerName,
                  UniqueProfilerBacktrace aCause, TracingKind aKind)
 {
   MOZ_RELEASE_ASSERT(CorePS::Exists());
 
-  PSAutoLock lock(gPSMutex);
-
-  if (!ActivePS::Exists(lock) || ActivePS::FeaturePrivacy(lock)) {
+  // This function is hot enough that we use RacyFeatures, notActivePS.
+  if (!RacyFeatures::IsActiveWithoutPrivacy()) {
     return;
   }
 
   auto payload =
     new ProfilerMarkerTracing(aCategory, aKind, mozilla::Move(aCause));
-  locked_profiler_add_marker(lock, aMarkerName, payload);
+  racy_profiler_add_marker(aMarkerName, payload);
 }
 
 void
 profiler_log(const char* aStr)
 {
   profiler_tracing("log", aStr);
 }
 
deleted file mode 100644
--- a/tools/profiler/gecko/ProfileGatherer.cpp
+++ /dev/null
@@ -1,187 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* 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/. */
-
-#include "ProfileGatherer.h"
-
-#include "mozilla/Services.h"
-#include "nsIObserverService.h"
-
-namespace mozilla {
-
-/**
- * When a subprocess exits before we've gathered profiles, we'll
- * store profiles for those processes until gathering starts. We'll
- * only store up to MAX_SUBPROCESS_EXIT_PROFILES. The buffer is
- * circular, so as soon as we receive another exit profile, we'll
- * bump the oldest one out of the buffer.
- */
-static const uint32_t MAX_SUBPROCESS_EXIT_PROFILES = 5;
-
-NS_IMPL_ISUPPORTS0(ProfileGatherer)
-
-ProfileGatherer::ProfileGatherer()
-  : mPendingProfiles(0)
-  , mGathering(false)
-{
-}
-
-ProfileGatherer::~ProfileGatherer()
-{
-  Cancel();
-}
-
-void
-ProfileGatherer::GatheredOOPProfile(const nsACString& aProfile)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-
-  if (!mGathering) {
-    // If we're not actively gathering, then we don't actually
-    // care that we gathered a profile here. This can happen for
-    // processes that exit while profiling.
-    return;
-  }
-
-  MOZ_RELEASE_ASSERT(mWriter.isSome(), "Should always have a writer if mGathering is true");
-
-  if (!aProfile.IsEmpty()) {
-    mWriter->Splice(PromiseFlatCString(aProfile).get());
-  }
-
-  mPendingProfiles--;
-
-  if (mPendingProfiles == 0) {
-    // We've got all of the async profiles now. Let's
-    // finish off the profile and resolve the Promise.
-    Finish();
-  }
-}
-
-RefPtr<ProfileGatherer::ProfileGatherPromise>
-ProfileGatherer::Start(double aSinceTime)
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-
-  if (mGathering) {
-    // If we're already gathering, return a rejected promise - this isn't
-    // going to end well.
-    return ProfileGatherPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
-  }
-
-  mGathering = true;
-
-  // Request profiles from the other processes. This will trigger
-  // asynchronous calls to ProfileGatherer::GatheredOOPProfile as the
-  // profiles arrive.
-  // Do this before the call to profiler_stream_json_for_this_process because
-  // that call is slow and we want to let the other processes grab their
-  // profiles as soon as possible.
-  nsTArray<RefPtr<ProfilerParent::SingleProcessProfilePromise>> profiles =
-    ProfilerParent::GatherProfiles();
-
-  mWriter.emplace();
-
-  // Start building up the JSON result and grab the profile from this process.
-  mWriter->Start(SpliceableJSONWriter::SingleLineStyle);
-  if (!profiler_stream_json_for_this_process(*mWriter, aSinceTime)) {
-    // The profiler is inactive. This either means that it was inactive even
-    // at the time that ProfileGatherer::Start() was called, or that it was
-    // stopped on a different thread since that call. Either way, we need to
-    // reject the promise and stop gathering.
-    return ProfileGatherPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
-  }
-
-  mWriter->StartArrayProperty("processes");
-
-  // If we have any process exit profiles, add them immediately, and clear
-  // mExitProfiles.
-  for (size_t i = 0; i < mExitProfiles.Length(); ++i) {
-    if (!mExitProfiles[i].IsEmpty()) {
-      mWriter->Splice(mExitProfiles[i].get());
-    }
-  }
-  mExitProfiles.Clear();
-
-  mPromiseHolder.emplace();
-  RefPtr<ProfileGatherPromise> promise = mPromiseHolder->Ensure(__func__);
-
-  // Keep the array property "processes" and the root object in mWriter open
-  // until Finish() is called. As profiles from the other processes come in,
-  // they will be inserted and end up in the right spot. Finish() will close
-  // the array and the root object.
-
-  mPendingProfiles = profiles.Length();
-  RefPtr<ProfileGatherer> self = this;
-  for (auto profile : profiles) {
-    profile->Then(AbstractThread::MainThread(), __func__,
-      [self](const nsCString& aResult) {
-        self->GatheredOOPProfile(aResult);
-      },
-      [self](PromiseRejectReason aReason) {
-        self->GatheredOOPProfile(NS_LITERAL_CSTRING(""));
-      });
-  }
-  if (!mPendingProfiles) {
-    Finish();
-  }
-
-  return promise;
-}
-
-void
-ProfileGatherer::Finish()
-{
-  MOZ_RELEASE_ASSERT(NS_IsMainThread());
-  MOZ_RELEASE_ASSERT(mWriter.isSome());
-  MOZ_RELEASE_ASSERT(mPromiseHolder.isSome());
-
-  // Close the "processes" array property.
-  mWriter->EndArray();
-
-  // Close the root object of the generated JSON.
-  mWriter->End();
-
-  UniquePtr<char[]> buf = mWriter->WriteFunc()->CopyData();
-  nsCString result(buf.get());
-  mPromiseHolder->Resolve(result, __func__);
-
-  Reset();
-}
-
-void
-ProfileGatherer::Reset()
-{
-  mPromiseHolder.reset();
-  mPendingProfiles = 0;
-  mGathering = false;
-  mWriter.reset();
-}
-
-void
-ProfileGatherer::Cancel()
-{
-  // If we have a Promise in flight, we should reject it.
-  if (mPromiseHolder.isSome()) {
-    mPromiseHolder->RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
-  }
-  Reset();
-}
-
-void
-ProfileGatherer::OOPExitProfile(const nsACString& aProfile)
-{
-  // Append the exit profile to mExitProfiles so that it can be picked up the
-  // next time a profile is requested. If we're currently gathering a profile,
-  // do not add this exit profile to it; chances are that we already have a
-  // profile from the exiting process and we don't want another one.
-  // We only keep around at most MAX_SUBPROCESS_EXIT_PROFILES exit profiles.
-  if (mExitProfiles.Length() >= MAX_SUBPROCESS_EXIT_PROFILES) {
-    mExitProfiles.RemoveElementAt(0);
-  }
-  mExitProfiles.AppendElement(aProfile);
-}
-
-} // namespace mozilla
deleted file mode 100644
--- a/tools/profiler/gecko/ProfileGatherer.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef MOZ_PROFILE_GATHERER_H
-#define MOZ_PROFILE_GATHERER_H
-
-#include "nsIFile.h"
-#include "ProfileJSONWriter.h"
-#include "mozilla/MozPromise.h"
-
-namespace mozilla {
-
-// This class holds the state for an async profile-gathering request.
-class ProfileGatherer final : public nsISupports
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  typedef MozPromise<nsCString, nsresult, false> ProfileGatherPromise;
-
-  explicit ProfileGatherer();
-  void GatheredOOPProfile(const nsACString& aProfile);
-  RefPtr<ProfileGatherPromise> Start(double aSinceTime);
-  void OOPExitProfile(const nsACString& aProfile);
-
-private:
-  ~ProfileGatherer();
-  void Cancel();
-  void Finish();
-  void Reset();
-
-  nsTArray<nsCString> mExitProfiles;
-  Maybe<MozPromiseHolder<ProfileGatherPromise>> mPromiseHolder;
-  Maybe<SpliceableChunkedJSONWriter> mWriter;
-  uint32_t mPendingProfiles;
-  bool mGathering;
-};
-
-} // namespace mozilla
-
-#endif
--- a/tools/profiler/gecko/nsProfiler.cpp
+++ b/tools/profiler/gecko/nsProfiler.cpp
@@ -18,37 +18,33 @@
 #include "nsILoadContext.h"
 #include "nsIWebNavigation.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "shared-libraries.h"
 #include "js/Value.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/TypedArray.h"
-#include "ProfileGatherer.h"
 #include "nsLocalFile.h"
+#include "ProfilerParent.h"
 #include "platform.h"
 
 using namespace mozilla;
 
 using dom::AutoJSAPI;
 using dom::Promise;
 using std::string;
 
 NS_IMPL_ISUPPORTS(nsProfiler, nsIProfiler)
 
 nsProfiler::nsProfiler()
   : mLockedForPrivateBrowsing(false)
+  , mPendingProfiles(0)
+  , mGathering(false)
 {
-  // If MOZ_PROFILER_STARTUP is set, the profiler will already be running. We
-  // need to create a ProfileGatherer in that case.
-  // XXX: this is probably not the best approach. See bug 1356694 for details.
-  if (profiler_is_active()) {
-    mGatherer = new ProfileGatherer();
-  }
 }
 
 nsProfiler::~nsProfiler()
 {
   nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
   if (observerService) {
     observerService->RemoveObserver(this, "chrome-document-global-created");
     observerService->RemoveObserver(this, "last-pb-context-exited");
@@ -120,29 +116,27 @@ nsProfiler::StartProfiler(uint32_t aEntr
     }
 
   // Convert the array of strings to a bitfield.
   uint32_t features = 0;
   PROFILER_FOR_EACH_FEATURE(ADD_FEATURE_BIT)
 
   #undef ADD_FEATURE_BIT
 
-  profiler_start(aEntries, aInterval, features, aFilters, aFilterCount);
+  ResetGathering();
 
-  // Do this after profiler_start().
-  mGatherer = new ProfileGatherer();
+  profiler_start(aEntries, aInterval, features, aFilters, aFilterCount);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsProfiler::StopProfiler()
 {
-  // Do this before profiler_stop().
-  mGatherer = nullptr;
+  CancelGathering();
 
   profiler_stop();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsProfiler::IsPaused(bool *aIsPaused)
@@ -248,17 +242,17 @@ nsProfiler::GetProfileData(double aSince
 }
 
 NS_IMETHODIMP
 nsProfiler::GetProfileDataAsync(double aSinceTime, JSContext* aCx,
                                 nsISupports** aPromise)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mGatherer) {
+  if (!profiler_is_active()) {
     return NS_ERROR_FAILURE;
   }
 
   if (NS_WARN_IF(!aCx)) {
     return NS_ERROR_FAILURE;
   }
 
   nsIGlobalObject* globalObject =
@@ -269,17 +263,17 @@ nsProfiler::GetProfileDataAsync(double a
   }
 
   ErrorResult result;
   RefPtr<Promise> promise = Promise::Create(globalObject, result);
   if (NS_WARN_IF(result.Failed())) {
     return result.StealNSResult();
   }
 
-  mGatherer->Start(aSinceTime)->Then(
+  StartGathering(aSinceTime)->Then(
     AbstractThread::MainThread(), __func__,
     [promise](nsCString aResult) {
       AutoJSAPI jsapi;
       if (NS_WARN_IF(!jsapi.Init(promise->GlobalJSObject()))) {
         // We're really hosed if we can't get a JS context for some reason.
         promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
         return;
       }
@@ -316,17 +310,17 @@ nsProfiler::GetProfileDataAsync(double a
 }
 
 NS_IMETHODIMP
 nsProfiler::GetProfileDataAsArrayBuffer(double aSinceTime, JSContext* aCx,
                                         nsISupports** aPromise)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mGatherer) {
+  if (!profiler_is_active()) {
     return NS_ERROR_FAILURE;
   }
 
   if (NS_WARN_IF(!aCx)) {
     return NS_ERROR_FAILURE;
   }
 
   nsIGlobalObject* globalObject =
@@ -337,17 +331,17 @@ nsProfiler::GetProfileDataAsArrayBuffer(
   }
 
   ErrorResult result;
   RefPtr<Promise> promise = Promise::Create(globalObject, result);
   if (NS_WARN_IF(result.Failed())) {
     return result.StealNSResult();
   }
 
-  mGatherer->Start(aSinceTime)->Then(
+  StartGathering(aSinceTime)->Then(
     AbstractThread::MainThread(), __func__,
     [promise](nsCString aResult) {
       AutoJSAPI jsapi;
       if (NS_WARN_IF(!jsapi.Init(promise->GlobalJSObject()))) {
         // We're really hosed if we can't get a JS context for some reason.
         promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
         return;
       }
@@ -372,23 +366,23 @@ nsProfiler::GetProfileDataAsArrayBuffer(
 }
 
 NS_IMETHODIMP
 nsProfiler::DumpProfileToFileAsync(const nsACString& aFilename,
                                    double aSinceTime)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mGatherer) {
+  if (!profiler_is_active()) {
     return NS_ERROR_FAILURE;
   }
 
   nsCString filename(aFilename);
 
-  mGatherer->Start(aSinceTime)->Then(
+  StartGathering(aSinceTime)->Then(
     AbstractThread::MainThread(), __func__,
     [filename](const nsCString& aResult) {
       nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
       nsresult rv = file->InitWithNativePath(filename);
       if (NS_FAILED(rv)) {
         MOZ_CRASH();
       }
       nsCOMPtr<nsIFileOutputStream> of =
@@ -491,26 +485,171 @@ nsProfiler::GetBufferInfo(uint32_t* aCur
   return NS_OK;
 }
 
 void
 nsProfiler::GatheredOOPProfile(const nsACString& aProfile)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
-  if (!mGatherer) {
+  if (!profiler_is_active()) {
+    return;
+  }
+
+  if (!mGathering) {
+    // If we're not actively gathering, then we don't actually care that we
+    // gathered a profile here. This can happen for processes that exit while
+    // profiling.
     return;
   }
 
-  mGatherer->GatheredOOPProfile(aProfile);
+  MOZ_RELEASE_ASSERT(mWriter.isSome(),
+                     "Should always have a writer if mGathering is true");
+
+  if (!aProfile.IsEmpty()) {
+    mWriter->Splice(PromiseFlatCString(aProfile).get());
+  }
+
+  mPendingProfiles--;
+
+  if (mPendingProfiles == 0) {
+    // We've got all of the async profiles now. Let's
+    // finish off the profile and resolve the Promise.
+    FinishGathering();
+  }
 }
 
+// When a subprocess exits before we've gathered profiles, we'll store profiles
+// for those processes until gathering starts. We'll only store up to
+// MAX_SUBPROCESS_EXIT_PROFILES. The buffer is circular, so as soon as we
+// receive another exit profile, we'll bump the oldest one out of the buffer.
+static const uint32_t MAX_SUBPROCESS_EXIT_PROFILES = 5;
+
 void
 nsProfiler::ReceiveShutdownProfile(const nsCString& aProfile)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
-  if (!mGatherer) {
+  if (!profiler_is_active()) {
     return;
   }
 
-  mGatherer->OOPExitProfile(aProfile);
+  // Append the exit profile to mExitProfiles so that it can be picked up the
+  // next time a profile is requested. If we're currently gathering a profile,
+  // do not add this exit profile to it; chances are that we already have a
+  // profile from the exiting process and we don't want another one.
+  // We only keep around at most MAX_SUBPROCESS_EXIT_PROFILES exit profiles.
+  if (mExitProfiles.Length() >= MAX_SUBPROCESS_EXIT_PROFILES) {
+    mExitProfiles.RemoveElementAt(0);
+  }
+  mExitProfiles.AppendElement(aProfile);
 }
+
+RefPtr<nsProfiler::GatheringPromise>
+nsProfiler::StartGathering(double aSinceTime)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  if (mGathering) {
+    // If we're already gathering, return a rejected promise - this isn't
+    // going to end well.
+    return GatheringPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
+  }
+
+  mGathering = true;
+
+  // Request profiles from the other processes. This will trigger asynchronous
+  // calls to ProfileGatherer::GatheredOOPProfile as the profiles arrive.
+  //
+  // Do this before the call to profiler_stream_json_for_this_process() because
+  // that call is slow and we want to let the other processes grab their
+  // profiles as soon as possible.
+  nsTArray<RefPtr<ProfilerParent::SingleProcessProfilePromise>> profiles =
+    ProfilerParent::GatherProfiles();
+
+  mWriter.emplace();
+
+  // Start building up the JSON result and grab the profile from this process.
+  mWriter->Start(SpliceableJSONWriter::SingleLineStyle);
+  if (!profiler_stream_json_for_this_process(*mWriter, aSinceTime)) {
+    // The profiler is inactive. This either means that it was inactive even
+    // at the time that ProfileGatherer::Start() was called, or that it was
+    // stopped on a different thread since that call. Either way, we need to
+    // reject the promise and stop gathering.
+    return GatheringPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
+  }
+
+  mWriter->StartArrayProperty("processes");
+
+  // If we have any process exit profiles, add them immediately, and clear
+  // mExitProfiles.
+  for (size_t i = 0; i < mExitProfiles.Length(); ++i) {
+    if (!mExitProfiles[i].IsEmpty()) {
+      mWriter->Splice(mExitProfiles[i].get());
+    }
+  }
+  mExitProfiles.Clear();
+
+  mPromiseHolder.emplace();
+  RefPtr<GatheringPromise> promise = mPromiseHolder->Ensure(__func__);
+
+  // Keep the array property "processes" and the root object in mWriter open
+  // until FinishGathering() is called. As profiles from the other processes
+  // come in, they will be inserted and end up in the right spot.
+  // FinishGathering() will close the array and the root object.
+
+  mPendingProfiles = profiles.Length();
+  RefPtr<nsProfiler> self = this;
+  for (auto profile : profiles) {
+    profile->Then(AbstractThread::MainThread(), __func__,
+      [self](const nsCString& aResult) {
+        self->GatheredOOPProfile(aResult);
+      },
+      [self](ipc::PromiseRejectReason aReason) {
+        self->GatheredOOPProfile(NS_LITERAL_CSTRING(""));
+      });
+  }
+  if (!mPendingProfiles) {
+    FinishGathering();
+  }
+
+  return promise;
+}
+
+void
+nsProfiler::CancelGathering()
+{
+  // If we have a Promise in flight, we should reject it.
+  if (mPromiseHolder.isSome()) {
+    mPromiseHolder->RejectIfExists(NS_ERROR_DOM_ABORT_ERR, __func__);
+  }
+  ResetGathering();
+}
+
+void
+nsProfiler::FinishGathering()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(mWriter.isSome());
+  MOZ_RELEASE_ASSERT(mPromiseHolder.isSome());
+
+  // Close the "processes" array property.
+  mWriter->EndArray();
+
+  // Close the root object of the generated JSON.
+  mWriter->End();
+
+  UniquePtr<char[]> buf = mWriter->WriteFunc()->CopyData();
+  nsCString result(buf.get());
+  mPromiseHolder->Resolve(result, __func__);
+
+  ResetGathering();
+}
+
+void
+nsProfiler::ResetGathering()
+{
+  mPromiseHolder.reset();
+  mPendingProfiles = 0;
+  mGathering = false;
+  mWriter.reset();
+}
+
--- a/tools/profiler/gecko/nsProfiler.h
+++ b/tools/profiler/gecko/nsProfiler.h
@@ -1,46 +1,59 @@
-/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
-#ifndef _NSPROFILER_H_
-#define _NSPROFILER_H_
+#ifndef nsProfiler_h
+#define nsProfiler_h
 
 #include "nsIProfiler.h"
 #include "nsIObserver.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
 #include "nsServiceManagerUtils.h"
-
-namespace mozilla {
-class ProfileGatherer;
-}
+#include "ProfileJSONWriter.h"
 
 class nsProfiler final : public nsIProfiler, public nsIObserver
 {
 public:
-    nsProfiler();
+  nsProfiler();
 
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIOBSERVER
-    NS_DECL_NSIPROFILER
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSIPROFILER
 
-    nsresult Init();
+  nsresult Init();
 
-    static nsProfiler* GetOrCreate()
-    {
-	nsCOMPtr<nsIProfiler> iprofiler =
-	    do_GetService("@mozilla.org/tools/profiler;1");
-	return static_cast<nsProfiler*>(iprofiler.get());
-    }
+  static nsProfiler* GetOrCreate()
+  {
+    nsCOMPtr<nsIProfiler> iprofiler =
+      do_GetService("@mozilla.org/tools/profiler;1");
+    return static_cast<nsProfiler*>(iprofiler.get());
+  }
 
-    void GatheredOOPProfile(const nsACString& aProfile);
+  void GatheredOOPProfile(const nsACString& aProfile);
 
 private:
-    ~nsProfiler();
+  ~nsProfiler();
+
+  typedef mozilla::MozPromise<nsCString, nsresult, false> GatheringPromise;
+
+  RefPtr<GatheringPromise> StartGathering(double aSinceTime);
+  void CancelGathering();
+  void FinishGathering();
+  void ResetGathering();
 
-    RefPtr<mozilla::ProfileGatherer> mGatherer;
-    bool mLockedForPrivateBrowsing;
+  bool mLockedForPrivateBrowsing;
+
+  // These fields are all related to profile gathering.
+  nsTArray<nsCString> mExitProfiles;
+  mozilla::Maybe<mozilla::MozPromiseHolder<GatheringPromise>> mPromiseHolder;
+  mozilla::Maybe<SpliceableChunkedJSONWriter> mWriter;
+  uint32_t mPendingProfiles;
+  bool mGathering;
 };
 
-#endif /* _NSPROFILER_H_ */
+#endif // nsProfiler_h
 
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -23,17 +23,16 @@ if CONFIG['MOZ_GECKO_PROFILER']:
         'core/ProfileJSONWriter.cpp',
         'core/ProfilerBacktrace.cpp',
         'core/ProfilerMarkerPayload.cpp',
         'core/StackTop.cpp',
         'core/ThreadInfo.cpp',
         'gecko/ChildProfilerController.cpp',
         'gecko/nsProfilerFactory.cpp',
         'gecko/nsProfilerStartParams.cpp',
-        'gecko/ProfileGatherer.cpp',
         'gecko/ProfilerChild.cpp',
         'gecko/ProfilerIOInterposeObserver.cpp',
         'gecko/ProfilerParent.cpp',
         'gecko/ThreadResponsiveness.cpp',
     ]
     if CONFIG['OS_TARGET'] == 'Darwin':
         # This file cannot be built in unified mode because it includes
         # "nsLocalFile.h", which pulls in a system header which uses a type
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -285,17 +285,19 @@ ProfilerBacktraceDestructor::operator()(
 // us calling CreateExpensiveData() unnecessarily in most cases, but the
 // expensive data will end up being created but not used if another thread
 // stops the profiler between the CreateExpensiveData() and PROFILER_OPERATION
 // calls.
 //
 PROFILER_FUNC(bool profiler_is_active(), false)
 
 // Check if a profiler feature (specified via the ProfilerFeature type) is
-// active. Returns false if the profiler is inactive.
+// active. Returns false if the profiler is inactive. Note: the return value
+// can become immediately out-of-date, much like the return value of
+// profiler_is_active().
 PROFILER_FUNC(bool profiler_feature_active(uint32_t aFeature), false)
 
 // Get the profile encoded as a JSON string. A no-op (returning nullptr) if the
 // profiler is inactive.
 PROFILER_FUNC(mozilla::UniquePtr<char[]> profiler_get_profile(double aSinceTime = 0),
               nullptr)
 
 // Write the profile for this process (excluding subprocesses) into aWriter.
--- a/tools/profiler/tests/gtest/GeckoProfiler.cpp
+++ b/tools/profiler/tests/gtest/GeckoProfiler.cpp
@@ -25,17 +25,17 @@
 // Note: profiler_init() has already been called in XRE_main(), so we can't
 // test it here. Likewise for profiler_shutdown(), and GeckoProfilerInitRAII
 // (which is just an RAII wrapper for profiler_init() and profiler_shutdown()).
 
 using namespace mozilla;
 
 typedef Vector<const char*> StrVec;
 
-void
+static void
 InactiveFeaturesAndParamsCheck()
 {
   int entries;
   double interval;
   uint32_t features;
   StrVec filters;
 
   ASSERT_TRUE(!profiler_is_active());
@@ -46,17 +46,17 @@ InactiveFeaturesAndParamsCheck()
   profiler_get_start_params(&entries, &interval, &features, &filters);
 
   ASSERT_TRUE(entries == 0);
   ASSERT_TRUE(interval == 0);
   ASSERT_TRUE(features == 0);
   ASSERT_TRUE(filters.empty());
 }
 
-void
+static void
 ActiveParamsCheck(int aEntries, double aInterval, uint32_t aFeatures,
                   const char** aFilters, size_t aFiltersLen)
 {
   int entries;
   double interval;
   uint32_t features;
   StrVec filters;
 
@@ -315,16 +315,55 @@ TEST(GeckoProfiler, Pause)
 
   ASSERT_TRUE(!profiler_is_paused());
 
   profiler_stop();
 
   ASSERT_TRUE(!profiler_is_paused());
 }
 
+// A class that keeps track of how many instances have been created, streamed,
+// and destroyed.
+class GTestPayload : public ProfilerMarkerPayload
+{
+public:
+  explicit GTestPayload(int aN)
+    : mN(aN)
+  {
+    sNumCreated++;
+  }
+
+  virtual ~GTestPayload() { sNumDestroyed++; }
+
+  virtual void StreamPayload(SpliceableJSONWriter& aWriter,
+                             const mozilla::TimeStamp& aStartTime,
+                             UniqueStacks& aUniqueStacks) override
+  {
+    streamCommonProps("gtest", aWriter, aStartTime, aUniqueStacks);
+    char buf[64];
+    SprintfLiteral(buf, "gtest-%d", mN);
+    aWriter.IntProperty(buf, mN);
+    sNumStreamed++;
+  }
+
+private:
+  int mN;
+
+public:
+  // The number of GTestPayload instances that have been created, streamed, and
+  // destroyed.
+  static int sNumCreated;
+  static int sNumStreamed;
+  static int sNumDestroyed;
+};
+
+int GTestPayload::sNumCreated = 0;
+int GTestPayload::sNumStreamed = 0;
+int GTestPayload::sNumDestroyed = 0;
+
 TEST(GeckoProfiler, Markers)
 {
   uint32_t features = ProfilerFeature::StackWalk;
   const char* filters[] = { "GeckoMain" };
 
   profiler_start(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
                  features, filters, MOZ_ARRAY_LENGTH(filters));
 
@@ -344,17 +383,60 @@ TEST(GeckoProfiler, Markers)
   profiler_add_marker("M1");
   profiler_add_marker(
     "M2", new ProfilerMarkerTracing("C", TRACING_EVENT));
   PROFILER_MARKER("M3");
   PROFILER_MARKER_PAYLOAD(
     "M4", new ProfilerMarkerTracing("C", TRACING_EVENT,
                                     profiler_get_backtrace()));
 
+  for (int i = 0; i < 10; i++) {
+    PROFILER_MARKER_PAYLOAD("M5", new GTestPayload(i));
+  }
+
+  // Sleep briefly to ensure a sample is taken and the pending markers are
+  // processed.
+  PR_Sleep(PR_MillisecondsToInterval(500));
+
+  SpliceableChunkedJSONWriter w;
+  ASSERT_TRUE(profiler_stream_json_for_this_process(w));
+
+  UniquePtr<char[]> profile = w.WriteFunc()->CopyData();
+
+  // The GTestPayloads should have been created and streamed, but not yet
+  // destroyed.
+  ASSERT_TRUE(GTestPayload::sNumCreated == 10);
+  ASSERT_TRUE(GTestPayload::sNumStreamed == 10);
+  ASSERT_TRUE(GTestPayload::sNumDestroyed == 0);
+  for (int i = 0; i < 10; i++) {
+    char buf[64];
+    SprintfLiteral(buf, "\"gtest-%d\"", i);
+    ASSERT_TRUE(strstr(profile.get(), buf));
+  }
+
   profiler_stop();
+
+  // The GTestPayloads should have been destroyed.
+  ASSERT_TRUE(GTestPayload::sNumDestroyed == 10);
+
+  for (int i = 0; i < 10; i++) {
+    PROFILER_MARKER_PAYLOAD("M5", new GTestPayload(i));
+  }
+
+  profiler_start(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
+                 features, filters, MOZ_ARRAY_LENGTH(filters));
+
+  ASSERT_TRUE(profiler_stream_json_for_this_process(w));
+
+  profiler_stop();
+
+  // The second set of GTestPayloads should not have been streamed.
+  ASSERT_TRUE(GTestPayload::sNumCreated == 20);
+  ASSERT_TRUE(GTestPayload::sNumStreamed == 10);
+  ASSERT_TRUE(GTestPayload::sNumDestroyed == 20);
 }
 
 TEST(GeckoProfiler, Time)
 {
   uint32_t features = ProfilerFeature::StackWalk;
   const char* filters[] = { "GeckoMain" };
 
   double t1 = profiler_time();
@@ -389,33 +471,57 @@ TEST(GeckoProfiler, GetProfile)
   UniquePtr<char[]> profile = profiler_get_profile();
   ASSERT_TRUE(profile && profile[0] == '{');
 
   profiler_stop();
 
   ASSERT_TRUE(!profiler_get_profile());
 }
 
+static void
+JSONOutputCheck(const char* aOutput)
+{
+  // Check that various expected strings are in the JSON.
+
+  ASSERT_TRUE(aOutput);
+  ASSERT_TRUE(aOutput[0] == '{');
+
+  ASSERT_TRUE(strstr(aOutput, "\"libs\""));
+
+  ASSERT_TRUE(strstr(aOutput, "\"meta\""));
+  ASSERT_TRUE(strstr(aOutput, "\"version\""));
+  ASSERT_TRUE(strstr(aOutput, "\"startTime\""));
+
+  ASSERT_TRUE(strstr(aOutput, "\"threads\""));
+  ASSERT_TRUE(strstr(aOutput, "\"GeckoMain\""));
+  ASSERT_TRUE(strstr(aOutput, "\"samples\""));
+  ASSERT_TRUE(strstr(aOutput, "\"markers\""));
+  ASSERT_TRUE(strstr(aOutput, "\"stackTable\""));
+  ASSERT_TRUE(strstr(aOutput, "\"frameTable\""));
+  ASSERT_TRUE(strstr(aOutput, "\"stringTable\""));
+}
+
 TEST(GeckoProfiler, StreamJSONForThisProcess)
 {
   uint32_t features = ProfilerFeature::StackWalk;
   const char* filters[] = { "GeckoMain" };
 
   SpliceableChunkedJSONWriter w;
   ASSERT_TRUE(!profiler_stream_json_for_this_process(w));
 
   profiler_start(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
                  features, filters, MOZ_ARRAY_LENGTH(filters));
 
   w.Start(SpliceableJSONWriter::SingleLineStyle);
   ASSERT_TRUE(profiler_stream_json_for_this_process(w));
   w.End();
 
   UniquePtr<char[]> profile = w.WriteFunc()->CopyData();
-  ASSERT_TRUE(profile && profile[0] == '{');
+
+  JSONOutputCheck(profile.get());
 
   profiler_stop();
 
   ASSERT_TRUE(!profiler_stream_json_for_this_process(w));
 }
 
 TEST(GeckoProfiler, StreamJSONForThisProcessThreaded)
 {
@@ -437,17 +543,18 @@ TEST(GeckoProfiler, StreamJSONForThisPro
   // Call profiler_stream_json_for_this_process on a background thread.
   thread->Dispatch(NS_NewRunnableFunction([&]() {
     w.Start(SpliceableJSONWriter::SingleLineStyle);
     ASSERT_TRUE(profiler_stream_json_for_this_process(w));
     w.End();
   }), NS_DISPATCH_SYNC);
 
   UniquePtr<char[]> profile = w.WriteFunc()->CopyData();
-  ASSERT_TRUE(profile && profile[0] == '{');
+
+  JSONOutputCheck(profile.get());
 
   // Stop the profiler and call profiler_stream_json_for_this_process on a
   // background thread.
   thread->Dispatch(NS_NewRunnableFunction([&]() {
     profiler_stop();
     ASSERT_TRUE(!profiler_stream_json_for_this_process(w));
   }), NS_DISPATCH_SYNC);
   thread->Shutdown();
--- a/widget/android/moz.build
+++ b/widget/android/moz.build
@@ -2,16 +2,19 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "Widget: Android")
 
+with Files("*CompositorWidget*"):
+    BUG_COMPONENT = ("Core", "Graphics")
+
 DIRS += [
     'bindings',
     'fennec',
     'jni',
 ]
 
 XPIDL_SOURCES += [
     'nsIAndroidBridge.idl',
--- a/widget/gtk/moz.build
+++ b/widget/gtk/moz.build
@@ -2,16 +2,22 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "Widget: Gtk")
 
+with Files("*CompositorWidget*"):
+    BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("*WindowSurface*"):
+    BUG_COMPONENT = ("Core", "Graphics")
+
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3':
     DIRS += ['mozgtk']
 
 EXPORTS += [
     'mozcontainer.h',
     'nsGTKToolkit.h',
     'nsIImageToPixbuf.h',
 ]
--- a/widget/moz.build
+++ b/widget/moz.build
@@ -20,16 +20,19 @@ with Files("reftests/**"):
     BUG_COMPONENT = ("Core", "Widget: Cocoa")
 
 with Files("reftests/*fallback*"):
     BUG_COMPONENT = ("Core", "Layout: Form Controls")
 
 with Files("*CompositorWidget*"):
     BUG_COMPONENT = ("Core", "Graphics")
 
+with Files("*WindowSurface*"):
+    BUG_COMPONENT = ("Core", "Graphics")
+
 with Files("*FontRange*"):
     BUG_COMPONENT = ("Core", "Widget: Cocoa")
 
 toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
 
 if toolkit in ('cocoa', 'android', 'uikit'):
     DIRS += [toolkit]
 if toolkit in ('android', 'cocoa', 'gtk2', 'gtk3'):
--- a/widget/windows/moz.build
+++ b/widget/windows/moz.build
@@ -2,16 +2,19 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "Widget: Win32")
 
+with Files("*CompositorWidget*"):
+    BUG_COMPONENT = ("Core", "Graphics")
+
 TEST_DIRS += ['tests']
 
 EXPORTS += [
     'nsdefs.h',
     'WindowHook.h',
     'WinUtils.h',
 ]