Merge inbound to mozilla-central r=merge a=merge
authorAndreea Pavel <apavel@mozilla.com>
Sat, 06 Jan 2018 11:50:13 +0200
changeset 398106 ece8a96dfaa436c9bcf53105877b3923a264ae31
parent 398105 a4b21febc58d3fcf47e5bc39a921b5d5fcb82b3e (current diff)
parent 398076 0894fbaddc88e7f6b9cd6bb5f9cad6cf81254ecc (diff)
child 398107 573b7c01c1618d17f3ed66f4315378c8f2aef9f8
child 398124 2cae0b7e4b1c579febf31f5034a5d37442e8947b
child 398140 0ebe9bab9a67d580d9ed19abe4d0dd32f85c36fd
push id98671
push userapavel@mozilla.com
push dateSat, 06 Jan 2018 09:57:46 +0000
treeherdermozilla-inbound@573b7c01c161 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone59.0a1
first release with
nightly linux32
ece8a96dfaa4 / 59.0a1 / 20180106100308 / files
nightly linux64
ece8a96dfaa4 / 59.0a1 / 20180106100308 / files
nightly mac
ece8a96dfaa4 / 59.0a1 / 20180106100308 / files
nightly win32
ece8a96dfaa4 / 59.0a1 / 20180106100308 / files
nightly win64
ece8a96dfaa4 / 59.0a1 / 20180106100308 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central r=merge a=merge
taskcluster/taskgraph/transforms/job/__init__.py
taskcluster/taskgraph/transforms/task.py
toolkit/content/widgets/expander.xml
toolkit/themes/windows/global/expander.css
--- a/build/unix/build-gcc/build-gcc.sh
+++ b/build/unix/build-gcc/build-gcc.sh
@@ -92,32 +92,32 @@ apply_patch() {
 }
 
 build_binutils() {
   # if binutils_configure_flags is not set at all, give it the default value
   if [ -z "${binutils_configure_flags+xxx}" ];
   then
     # gold is disabled because we don't use it on automation, and also we ran into
     # some issues with it using this script in build-clang.py.
-    binutils_configure_flags="--disable-gold --enable-plugins --disable-nls"
+    binutils_configure_flags="--disable-gold --enable-plugins --disable-nls --with-sysroot=/"
   fi
 
   mkdir $root_dir/binutils-objdir
   pushd $root_dir/binutils-objdir
   ../binutils-$binutils_version/configure --prefix=${prefix-/tools/gcc}/ $binutils_configure_flags
   make $make_flags
   make install $make_flags DESTDIR=$root_dir
   export PATH=$root_dir/${prefix-/tools/gcc}/bin:$PATH
   popd
 }
 
 build_gcc() {
   mkdir $root_dir/gcc-objdir
   pushd $root_dir/gcc-objdir
-  ../gcc-$gcc_version/configure --prefix=${prefix-/tools/gcc} --enable-languages=c,c++  --disable-nls --disable-gnu-unique-object --enable-__cxa_atexit --with-arch-32=pentiumpro --disable-initfini-array
+  ../gcc-$gcc_version/configure --prefix=${prefix-/tools/gcc} --enable-languages=c,c++  --disable-nls --disable-gnu-unique-object --enable-__cxa_atexit --with-arch-32=pentiumpro --disable-initfini-array --with-sysroot=/
   make $make_flags
   make $make_flags install DESTDIR=$root_dir
 
   cd $root_dir/tools
   ln -s gcc gcc/bin/cc
   tar caf $root_dir/gcc.tar.xz gcc/
   popd
 }
--- a/build/unix/mozconfig.stdcxx
+++ b/build/unix/mozconfig.stdcxx
@@ -1,15 +1,19 @@
 # Avoid dependency on libstdc++ 4.7
 export MOZ_STDCXX_COMPAT=1
 
 TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
 
-if [ -f "$TOOLTOOL_DIR/gcc/lib/libstdc++.so" ]; then
+# Depending whether GCC was built on a RedHat-based or a Debian-based system,
+# the directory containing 32-bits libraries can be either (respectively)
+# lib or lib32. The directory for 64-bits libraries is always lib64.
+if [ -f "$TOOLTOOL_DIR/gcc/lib64/libstdc++.so" ]; then
   # We put both 32-bits and 64-bits library path in LD_LIBRARY_PATH: ld.so
   # will prefer the files in the 32-bits path when loading 32-bits executables,
   # and the files in the 64-bits path when loading 64-bits executables.
-  LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOOLTOOL_DIR/gcc/lib64:$TOOLTOOL_DIR/gcc/lib
+  # We also put both possible 32-bits library paths.
+  LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOOLTOOL_DIR/gcc/lib64:$TOOLTOOL_DIR/gcc/lib32:$TOOLTOOL_DIR/gcc/lib
 elif [ -f "$TOOLTOOL_DIR/clang/lib/libstdc++.so" ]; then
   LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOOLTOOL_DIR/clang/lib
 fi
 
 mk_add_options "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH"
--- a/dom/base/DocumentOrShadowRoot.cpp
+++ b/dom/base/DocumentOrShadowRoot.cpp
@@ -1,17 +1,16 @@
 /* -*- 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 "DocumentOrShadowRoot.h"
 #include "mozilla/dom/StyleSheetList.h"
-#include "XULDocument.h"
 
 namespace mozilla {
 namespace dom {
 
 DocumentOrShadowRoot::DocumentOrShadowRoot(mozilla::dom::ShadowRoot& aShadowRoot)
   : mAsNode(aShadowRoot)
   , mKind(Kind::ShadowRoot)
 {}
@@ -39,21 +38,16 @@ DocumentOrShadowRoot::GetElementById(con
   }
 
   if (nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(aElementId)) {
     if (Element* el = entry->GetIdElement()) {
       return el;
     }
   }
 
-  if (MOZ_UNLIKELY(mKind == Kind::Document &&
-      static_cast<nsIDocument&>(AsNode()).IsXULDocument())) {
-    return static_cast<XULDocument&>(AsNode()).GetRefById(aElementId);
-  }
-
   return nullptr;
 }
 
 already_AddRefed<nsContentList>
 DocumentOrShadowRoot::GetElementsByTagNameNS(const nsAString& aNamespaceURI,
                                    const nsAString& aLocalName)
 {
   ErrorResult rv;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -440,24 +440,16 @@ nsIdentifierMapEntry::GetIdElement()
 
 Element*
 nsIdentifierMapEntry::GetImageIdElement()
 {
   return mImageElement ? mImageElement.get() : GetIdElement();
 }
 
 void
-nsIdentifierMapEntry::AppendAllIdContent(nsCOMArray<Element>* aElements)
-{
-  for (Element* element : mIdContentList) {
-    aElements->AppendObject(element);
-  }
-}
-
-void
 nsIdentifierMapEntry::AddContentChangeCallback(nsIDocument::IDTargetObserver aCallback,
                                                void* aData, bool aForImage)
 {
   if (!mChangeCallbacks) {
     mChangeCallbacks = new nsTHashtable<ChangeCallbackEntry>;
   }
 
   ChangeCallback cc = { aCallback, aData, aForImage };
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1154,17 +1154,16 @@ GK_ATOM(query, "query")
 GK_ATOM(queryset, "queryset")
 GK_ATOM(querytype, "querytype")
 GK_ATOM(radio, "radio")
 GK_ATOM(radiogroup, "radiogroup")
 GK_ATOM(range, "range")
 GK_ATOM(readonly, "readonly")
 GK_ATOM(rect, "rect")
 GK_ATOM(rectangle, "rectangle")
-GK_ATOM(ref, "ref")
 GK_ATOM(refresh, "refresh")
 GK_ATOM(rel, "rel")
 GK_ATOM(onreloadpage, "onreloadpage")
 GK_ATOM(rem, "rem")
 GK_ATOM(remote, "remote")
 GK_ATOM(removeelement, "removeelement")
 GK_ATOM(renderingobserverlist, "renderingobserverlist")
 GK_ATOM(repeat, "repeat")
--- a/dom/base/nsIdentifierMapEntry.h
+++ b/dom/base/nsIdentifierMapEntry.h
@@ -12,17 +12,16 @@
 #define nsIdentifierMapEntry_h
 
 #include "PLDHashTable.h"
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 #include "mozilla/net/ReferrerPolicy.h"
 
-#include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsAtom.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 
 class nsIContent;
 class nsContentList;
 class nsBaseContentList;
@@ -135,20 +134,16 @@ public:
     return mIdContentList;
   }
   /**
    * If this entry has a non-null image element set (using SetImageElement),
    * the image element will be returned, otherwise the same as GetIdElement().
    */
   Element* GetImageIdElement();
   /**
-   * Append all the elements with this id to aElements
-   */
-  void AppendAllIdContent(nsCOMArray<Element>* aElements);
-  /**
    * This can fire ID change callbacks.
    * @return true if the content could be added, false if we failed due
    * to OOM.
    */
   bool AddIdElement(Element* aElement);
   /**
    * This can fire ID change callbacks.
    */
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1942,28 +1942,16 @@ CanvasRenderingContext2D::TryBasicTarget
   if (!aOutDT) {
     return false;
   }
 
   aOutProvider = new PersistentBufferProviderBasic(aOutDT);
   return true;
 }
 
-int32_t
-CanvasRenderingContext2D::GetWidth() const
-{
-  return mWidth;
-}
-
-int32_t
-CanvasRenderingContext2D::GetHeight() const
-{
-  return mHeight;
-}
-
 NS_IMETHODIMP
 CanvasRenderingContext2D::SetDimensions(int32_t aWidth, int32_t aHeight)
 {
   ClearTarget();
 
   // Zero sized surfaces can cause problems.
   mZero = false;
   if (aHeight == 0) {
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -421,19 +421,19 @@ public:
 
   bool SwitchRenderingMode(RenderingMode aRenderingMode);
 
   // Eventually this should be deprecated. Keeping for now to keep the binding functional.
   void Demote();
 
   nsresult Redraw();
 
-  virtual int32_t GetWidth() const override;
-  virtual int32_t GetHeight() const override;
   gfx::IntSize GetSize() const { return gfx::IntSize(mWidth, mHeight); }
+  virtual int32_t GetWidth() override { return GetSize().width; }
+  virtual int32_t GetHeight() override { return GetSize().height; }
 
   // nsICanvasRenderingContextInternal
   /**
     * Gets the pres shell from either the canvas element or the doc shell
     */
   virtual nsIPresShell *GetPresShell() override {
     if (mCanvasElement) {
       return mCanvasElement->OwnerDoc()->GetShell();
--- a/dom/canvas/ImageBitmapRenderingContext.cpp
+++ b/dom/canvas/ImageBitmapRenderingContext.cpp
@@ -65,28 +65,16 @@ ImageBitmapRenderingContext::TransferFro
 
   if (!mImage) {
     return;
   }
 
   Redraw(gfxRect(0, 0, mWidth, mHeight));
 }
 
-int32_t
-ImageBitmapRenderingContext::GetWidth() const
-{
-  return mWidth;
-}
-
-int32_t
-ImageBitmapRenderingContext::GetHeight() const
-{
-  return mHeight;
-}
-
 NS_IMETHODIMP
 ImageBitmapRenderingContext::SetDimensions(int32_t aWidth, int32_t aHeight)
 {
   mWidth = aWidth;
   mHeight = aHeight;
   return NS_OK;
 }
 
--- a/dom/canvas/ImageBitmapRenderingContext.h
+++ b/dom/canvas/ImageBitmapRenderingContext.h
@@ -45,18 +45,18 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ImageBitmapRenderingContext)
 
   void TransferImageBitmap(ImageBitmap& aImageBitmap);
   void TransferFromImageBitmap(ImageBitmap& aImageBitmap);
 
   // nsICanvasRenderingContextInternal
-  virtual int32_t GetWidth() const override;
-  virtual int32_t GetHeight() const override;
+  virtual int32_t GetWidth() override { return mWidth; }
+  virtual int32_t GetHeight() override { return mHeight; }
 
   NS_IMETHOD SetDimensions(int32_t aWidth, int32_t aHeight) override;
 
   NS_IMETHOD InitializeWithDrawTarget(nsIDocShell* aDocShell,
                                       NotNull<gfx::DrawTarget*> aTarget) override;
 
   virtual mozilla::UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aFormat) override;
   NS_IMETHOD GetInputStream(const char* aMimeType,
--- a/dom/canvas/OffscreenCanvas.cpp
+++ b/dom/canvas/OffscreenCanvas.cpp
@@ -220,24 +220,17 @@ already_AddRefed<ImageBitmap>
 OffscreenCanvas::TransferToImageBitmap(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject();
   RefPtr<ImageBitmap> result = ImageBitmap::CreateFromOffscreenCanvas(globalObject, *this, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  // Clear the content.
-  if ((mCurrentContextType == CanvasContextType::WebGL1 ||
-       mCurrentContextType == CanvasContextType::WebGL2))
-  {
-    WebGLContext* webGL = static_cast<WebGLContext*>(mCurrentContext.get());
-    webGL->ClearScreen();
-  }
-
+  // TODO: Clear the content?
   return result.forget();
 }
 
 already_AddRefed<Promise>
 OffscreenCanvas::ToBlob(JSContext* aCx,
                         const nsAString& aType,
                         JS::Handle<JS::Value> aParams,
                         ErrorResult& aRv)
--- a/dom/canvas/WebGL2ContextFramebuffers.cpp
+++ b/dom/canvas/WebGL2ContextFramebuffers.cpp
@@ -35,39 +35,28 @@ WebGL2Context::BlitFramebuffer(GLint src
         break;
     default:
         ErrorInvalidEnumInfo("blitFramebuffer: Bad `filter`:", filter);
         return;
     }
 
     ////
 
-    const auto& readFB = mBoundReadFramebuffer;
-    if (readFB &&
-        !readFB->ValidateAndInitAttachments("blitFramebuffer's READ_FRAMEBUFFER"))
+    if (!ValidateAndInitFB("blitFramebuffer: READ_FRAMEBUFFER", mBoundReadFramebuffer) ||
+        !ValidateAndInitFB("blitFramebuffer: DRAW_FRAMEBUFFER", mBoundDrawFramebuffer))
     {
         return;
     }
 
-    const auto& drawFB = mBoundDrawFramebuffer;
-    if (drawFB &&
-        !drawFB->ValidateAndInitAttachments("blitFramebuffer's DRAW_FRAMEBUFFER"))
-    {
-        return;
-    }
-
-    ////
-
-    if (!mBoundReadFramebuffer) {
-        ClearBackbufferIfNeeded();
-    }
+    DoBindFB(mBoundReadFramebuffer, LOCAL_GL_READ_FRAMEBUFFER);
+    DoBindFB(mBoundDrawFramebuffer, LOCAL_GL_DRAW_FRAMEBUFFER);
 
     WebGLFramebuffer::BlitFramebuffer(this,
-                                      readFB, srcX0, srcY0, srcX1, srcY1,
-                                      drawFB, dstX0, dstY0, dstX1, dstY1,
+                                      srcX0, srcY0, srcX1, srcY1,
+                                      dstX0, dstY0, dstX1, dstY1,
                                       mask, filter);
 }
 
 void
 WebGL2Context::FramebufferTextureLayer(GLenum target, GLenum attachment,
                                        WebGLTexture* texture, GLint level, GLint layer)
 {
     const char funcName[] = "framebufferTextureLayer";
@@ -165,33 +154,41 @@ WebGLContext::ValidateInvalidateFramebuf
 {
     if (IsContextLost())
         return false;
 
     if (!ValidateFramebufferTarget(target, funcName))
         return false;
 
     const WebGLFramebuffer* fb;
-    bool isDefaultFB;
+    bool isDefaultFB = false;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
-        isDefaultFB = gl->Screen()->IsDrawFramebufferDefault();
         break;
 
     case LOCAL_GL_READ_FRAMEBUFFER:
         fb = mBoundReadFramebuffer;
-        isDefaultFB = gl->Screen()->IsReadFramebufferDefault();
         break;
 
     default:
         MOZ_CRASH("GFX: Bad target.");
     }
 
+    if (fb) {
+        const auto fbStatus = fb->CheckFramebufferStatus(funcName);
+        if (fbStatus != LOCAL_GL_FRAMEBUFFER_COMPLETE)
+            return false; // Not an error, but don't run forward to driver either.
+    } else {
+        if (!EnsureDefaultFB(funcName))
+            return false;
+    }
+    DoBindFB(fb, target);
+
     *out_glNumAttachments = attachments.Length();
     *out_glAttachments = attachments.Elements();
 
     if (fb) {
         for (const auto& attachment : attachments) {
             if (!ValidateFramebufferAttachmentEnum(this, funcName, attachment))
                 return false;
         }
@@ -225,23 +222,19 @@ WebGLContext::ValidateInvalidateFramebuf
             *out_glNumAttachments = scopedVector->size();
             *out_glAttachments = scopedVector->data();
         }
     }
 
     ////
 
     if (!fb) {
-        ClearBackbufferIfNeeded();
-
-        // Don't do more validation after these.
-        Invalidate();
-        mShouldPresent = true;
+        mDefaultFB_IsInvalid = true;
+        mResolvedDefaultFB = nullptr;
     }
-
     return true;
 }
 
 void
 WebGL2Context::InvalidateFramebuffer(GLenum target,
                                      const dom::Sequence<GLenum>& attachments,
                                      ErrorResult& rv)
 {
@@ -326,12 +319,12 @@ WebGL2Context::ReadBuffer(GLenum mode)
         nsCString enumName;
         EnumName(mode, &enumName);
         ErrorInvalidOperation("%s: If READ_FRAMEBUFFER is null, `mode` must be BACK or"
                               " NONE. Was %s.",
                               funcName, enumName.BeginReading());
         return;
     }
 
-    gl->Screen()->SetReadBuffer(mode);
+    mDefaultFB_ReadBuffer = mode;
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextMRTs.cpp
+++ b/dom/canvas/WebGL2ContextMRTs.cpp
@@ -57,21 +57,21 @@ WebGL2Context::ValidateClearBuffer(const
     if (availElemCount < requiredElements) {
         ErrorInvalidValue("%s: Not enough elements. Require %zu. Given %zu.",
                           funcName, requiredElements, availElemCount);
         return false;
     }
 
     ////
 
+    if (!BindCurFBForDraw(funcName))
+        return false;
+
     const auto& fb = mBoundDrawFramebuffer;
     if (fb) {
-        if (!fb->ValidateAndInitAttachments(funcName))
-            return false;
-
         if (!fb->ValidateClearBufferType(funcName, buffer, drawBuffer, funcType))
             return false;
     } else if (buffer == LOCAL_GL_COLOR) {
         if (drawBuffer != 0)
             return true;
 
         if (mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE)
             return true;
@@ -105,16 +105,23 @@ WebGL2Context::ClearBufferfv(GLenum buff
     }
 
     if (!ValidateClearBuffer(funcName, buffer, drawBuffer, src.elemCount, srcElemOffset,
                              LOCAL_GL_FLOAT))
     {
         return;
     }
 
+    if (!mBoundDrawFramebuffer &&
+        buffer == LOCAL_GL_DEPTH &&
+        mNeedsFakeNoDepth)
+    {
+        return;
+    }
+
     ScopedDrawCallWrapper wrapper(*this);
     const auto ptr = src.elemBytes + srcElemOffset;
     gl->fClearBufferfv(buffer, drawBuffer, ptr);
 }
 
 void
 WebGL2Context::ClearBufferiv(GLenum buffer, GLint drawBuffer, const Int32Arr& src,
                              GLuint srcElemOffset)
@@ -131,16 +138,23 @@ WebGL2Context::ClearBufferiv(GLenum buff
     }
 
     if (!ValidateClearBuffer(funcName, buffer, drawBuffer, src.elemCount, srcElemOffset,
                              LOCAL_GL_INT))
     {
         return;
     }
 
+    if (!mBoundDrawFramebuffer &&
+        buffer == LOCAL_GL_STENCIL &&
+        mNeedsFakeNoStencil)
+    {
+        return;
+    }
+
     ScopedDrawCallWrapper wrapper(*this);
     const auto ptr = src.elemBytes + srcElemOffset;
     gl->fClearBufferiv(buffer, drawBuffer, ptr);
 }
 
 void
 WebGL2Context::ClearBufferuiv(GLenum buffer, GLint drawBuffer, const Uint32Arr& src,
                               GLuint srcElemOffset)
@@ -174,13 +188,23 @@ WebGL2Context::ClearBufferfi(GLenum buff
         return;
 
     if (buffer != LOCAL_GL_DEPTH_STENCIL)
         return ErrorInvalidEnum("%s: buffer must be DEPTH_STENCIL.", funcName);
 
     if (!ValidateClearBuffer(funcName, buffer, drawBuffer, 2, 0, 0))
         return;
 
+    auto driverDepth = depth;
+    auto driverStencil = stencil;
+    if (!mBoundDrawFramebuffer) {
+        if (mNeedsFakeNoDepth) {
+            driverDepth = 1.0f;
+        } else if (mNeedsFakeNoStencil) {
+            driverStencil = 0;
+        }
+    }
+
     ScopedDrawCallWrapper wrapper(*this);
-    gl->fClearBufferfi(buffer, drawBuffer, depth, stencil);
+    gl->fClearBufferfi(buffer, drawBuffer, driverDepth, driverStencil);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextState.cpp
+++ b/dom/canvas/WebGL2ContextState.cpp
@@ -40,17 +40,17 @@ WebGL2Context::GetParameter(JSContext* c
     case LOCAL_GL_TRANSFORM_FEEDBACK_ACTIVE:
       return JS::BooleanValue(mBoundTransformFeedback->mIsActive);
     case LOCAL_GL_TRANSFORM_FEEDBACK_PAUSED:
       return JS::BooleanValue(mBoundTransformFeedback->mIsPaused);
 
     /* GLenum */
     case LOCAL_GL_READ_BUFFER: {
       if (!mBoundReadFramebuffer)
-        return JS::Int32Value(gl->Screen()->GetReadBufferMode());
+        return JS::Int32Value(mDefaultFB_ReadBuffer);
 
       if (!mBoundReadFramebuffer->ColorReadBuffer())
         return JS::Int32Value(LOCAL_GL_NONE);
 
       return JS::Int32Value(mBoundReadFramebuffer->ColorReadBuffer()->mAttachmentPoint);
     }
 
     case LOCAL_GL_FRAGMENT_SHADER_DERIVATIVE_HINT:
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -9,16 +9,17 @@
 #include <queue>
 
 #include "AccessCheck.h"
 #include "gfxContext.h"
 #include "gfxCrashReporterUtils.h"
 #include "gfxPattern.h"
 #include "gfxPrefs.h"
 #include "gfxUtils.h"
+#include "gfx/gl/MozFramebuffer.h"
 #include "GLBlitHelper.h"
 #include "GLContext.h"
 #include "GLContextProvider.h"
 #include "GLReadTexImageHelper.h"
 #include "GLScreenBuffer.h"
 #include "ImageContainer.h"
 #include "ImageEncoder.h"
 #include "Layers.h"
@@ -114,42 +115,31 @@ WebGLContext::WebGLContext()
     , mMaxAcceptableFBStatusInvals(gfxPrefs::WebGLMaxAcceptableFBStatusInvals())
     , mDataAllocGLCallCount(0)
     , mBypassShaderValidation(false)
     , mEmptyTFO(0)
     , mContextLossHandler(this)
     , mNeedsFakeNoAlpha(false)
     , mNeedsFakeNoDepth(false)
     , mNeedsFakeNoStencil(false)
-    , mNeedsEmulatedLoneDepthStencil(false)
     , mAllowFBInvalidation(gfxPrefs::WebGLFBInvalidation())
+    , mMsaaSamples(gfxPrefs::WebGLMsaaSamples())
 {
     mGeneration = 0;
     mInvalidated = false;
     mCapturedFrameInvalidated = false;
     mShouldPresent = true;
     mResetLayer = true;
     mOptionsFrozen = false;
     mDisableExtensions = false;
     mIsMesa = false;
     mEmitContextLostErrorOnce = false;
     mWebGLError = 0;
     mUnderlyingGLError = 0;
 
-    mActiveTexture = 0;
-
-    mStencilRefFront = 0;
-    mStencilRefBack = 0;
-    mStencilValueMaskFront = 0;
-    mStencilValueMaskBack = 0;
-    mStencilWriteMaskFront = 0;
-    mStencilWriteMaskBack = 0;
-    mDepthWriteMask = 0;
-    mStencilClearValue = 0;
-    mDepthClearValue = 0;
     mContextLostErrorSet = false;
 
     mViewportX = 0;
     mViewportY = 0;
     mViewportWidth = 0;
     mViewportHeight = 0;
 
     mDitherEnabled = 1;
@@ -207,16 +197,19 @@ ClearLinkedList(LinkedList<T>& list)
 }
 
 void
 WebGLContext::DestroyResourcesAndContext()
 {
     if (!gl)
         return;
 
+    mDefaultFB = nullptr;
+    mResolvedDefaultFB = nullptr;
+
     mBound2DTextures.Clear();
     mBoundCubeMapTextures.Clear();
     mBound3DTextures.Clear();
     mBound2DArrayTextures.Clear();
     mBoundSamplers.Clear();
     mBoundArrayBuffer = nullptr;
     mBoundCopyReadBuffer = nullptr;
     mBoundCopyWriteBuffer = nullptr;
@@ -290,16 +283,17 @@ WebGLContext::DestroyResourcesAndContext
 
     // We just got rid of everything, so the context had better
     // have been going away.
     if (GLContext::ShouldSpew()) {
         printf_stderr("--- WebGL context destroyed: %p\n", gl.get());
     }
 
     MOZ_ASSERT(gl);
+    gl->MarkDestroyed();
     mGL_OnlyClearInDestroyResourcesAndContext = nullptr;
     MOZ_ASSERT(!gl);
 }
 
 void
 WebGLContext::Invalidate()
 {
     if (!mCanvasElement)
@@ -393,28 +387,16 @@ WebGLContext::SetContextOptions(JSContex
         // aren't the same as what they were originally.
         return NS_ERROR_FAILURE;
     }
 
     mOptions = newOpts;
     return NS_OK;
 }
 
-int32_t
-WebGLContext::GetWidth() const
-{
-    return mWidth;
-}
-
-int32_t
-WebGLContext::GetHeight() const
-{
-    return mHeight;
-}
-
 /* So there are a number of points of failure here. We might fail based
  * on EGL vs. WGL, or we might fail to alloc a too-large size, or we
  * might not be able to create a context with a certain combo of context
  * creation attribs.
  *
  * We don't want to test the complete fallback matrix. (for now, at
  * least) Instead, attempt creation in this order:
  * 1. By platform API. (e.g. EGL vs. WGL)
@@ -481,64 +463,34 @@ HasAcceleratedLayers(const nsCOMPtr<nsIG
     return false;
 }
 
 static void
 PopulateCapFallbackQueue(const gl::SurfaceCaps& baseCaps,
                          std::queue<gl::SurfaceCaps>* out_fallbackCaps)
 {
     out_fallbackCaps->push(baseCaps);
-
-    // Dropping antialias drops our quality, but not our correctness.
-    // The user basically doesn't have to handle if this fails, they
-    // just get reduced quality.
-    if (baseCaps.antialias) {
-        gl::SurfaceCaps nextCaps(baseCaps);
-        nextCaps.antialias = false;
-        PopulateCapFallbackQueue(nextCaps, out_fallbackCaps);
-    }
-
-    // If we have to drop one of depth or stencil, we'd prefer to keep
-    // depth. However, the client app will need to handle if this
-    // doesn't work.
-    if (baseCaps.stencil) {
-        gl::SurfaceCaps nextCaps(baseCaps);
-        nextCaps.stencil = false;
-        PopulateCapFallbackQueue(nextCaps, out_fallbackCaps);
-    }
-
-    if (baseCaps.depth) {
-        gl::SurfaceCaps nextCaps(baseCaps);
-        nextCaps.depth = false;
-        PopulateCapFallbackQueue(nextCaps, out_fallbackCaps);
-    }
 }
 
 static gl::SurfaceCaps
 BaseCaps(const WebGLContextOptions& options, WebGLContext* webgl)
 {
     gl::SurfaceCaps baseCaps;
 
     baseCaps.color = true;
-    baseCaps.alpha = options.alpha;
-    baseCaps.antialias = options.antialias;
-    baseCaps.depth = options.depth;
+    baseCaps.alpha = true;
+    baseCaps.antialias = false;
+    baseCaps.depth = false;
+    baseCaps.stencil = false;
     baseCaps.premultAlpha = options.premultipliedAlpha;
     baseCaps.preserve = options.preserveDrawingBuffer;
-    baseCaps.stencil = options.stencil;
-
-    if (!baseCaps.alpha)
+
+    if (!baseCaps.alpha) {
         baseCaps.premultAlpha = true;
-
-    // we should really have this behind a
-    // |gfxPlatform::GetPlatform()->GetScreenDepth() == 16| check, but
-    // for now it's just behind a pref for testing/evaluation.
-    baseCaps.bpp16 = gfxPrefs::WebGLPrefer16bpp();
-
-    // Done with baseCaps construction.
+    }
 
     if (!gfxPrefs::WebGLForceMSAA()) {
         const nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
 
         nsCString blocklistId;
         if (IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_MSAA, &blocklistId)) {
             webgl->GenerateWarning("Disallowing antialiased backbuffers due"
                                    " to blacklisting.");
@@ -767,68 +719,83 @@ WebGLContext::CreateAndInitGL(bool force
 
         if (CreateAndInitGLWith(CreateGLWithDefault, baseCaps, flags, out_failReasons))
             return true;
     }
 
     //////
 
     if (tryANGLE) {
-        // Force enable alpha channel to make sure ANGLE use correct framebuffer formart
-        gl::SurfaceCaps& angleCaps = const_cast<gl::SurfaceCaps&>(baseCaps);
+        // Force enable alpha channel to make sure ANGLE use correct framebuffer format
+        auto angleCaps = baseCaps;
         angleCaps.alpha = true;
         return CreateAndInitGLWith(CreateGLWithANGLE, angleCaps, flags, out_failReasons);
     }
 
     //////
 
     out_failReasons->push_back(FailureReason("FEATURE_FAILURE_WEBGL_EXHAUSTED_DRIVERS",
                                              "Exhausted GL driver options."));
     return false;
 }
 
 // Fallback for resizes:
+
 bool
-WebGLContext::ResizeBackbuffer(uint32_t requestedWidth,
-                               uint32_t requestedHeight)
+WebGLContext::EnsureDefaultFB(const char* const funcName)
 {
-    uint32_t width = requestedWidth;
-    uint32_t height = requestedHeight;
-
-    bool resized = false;
-    while (width || height) {
-      width = width ? width : 1;
-      height = height ? height : 1;
-
-      gfx::IntSize curSize(width, height);
-      if (gl->ResizeOffscreen(curSize)) {
-          resized = true;
-          break;
-      }
-
-      width /= 2;
-      height /= 2;
+    if (mDefaultFB) {
+        MOZ_ASSERT(mDefaultFB->mSize == mRequestedSize);
+        return true;
     }
 
-    if (!resized)
+    const bool depthStencil = mOptions.depth || mOptions.stencil;
+    auto attemptSize = mRequestedSize;
+
+    while (attemptSize.width || attemptSize.height) {
+        attemptSize.width = std::max(attemptSize.width, 1);
+        attemptSize.height = std::max(attemptSize.height, 1);
+
+        [&]() {
+            if (mOptions.antialias) {
+                MOZ_ASSERT(!mDefaultFB);
+                mDefaultFB = MozFramebuffer::Create(gl, attemptSize, mMsaaSamples,
+                                                    depthStencil);
+                if (mDefaultFB)
+                    return;
+                if (mOptionsFrozen)
+                    return;
+            }
+
+            MOZ_ASSERT(!mDefaultFB);
+            mDefaultFB = MozFramebuffer::Create(gl, attemptSize, 0, depthStencil);
+        }();
+
+        if (mDefaultFB)
+            break;
+
+        attemptSize.width /= 2;
+        attemptSize.height /= 2;
+    }
+
+    if (!mDefaultFB) {
+        GenerateWarning("%s: Backbuffer resize failed. Losing context.", funcName);
+        ForceLoseContext();
         return false;
-
-    mWidth = gl->OffscreenSize().width;
-    mHeight = gl->OffscreenSize().height;
-    MOZ_ASSERT((uint32_t)mWidth == width);
-    MOZ_ASSERT((uint32_t)mHeight == height);
-
-    if (width != requestedWidth ||
-        height != requestedHeight)
-    {
+    }
+
+    mDefaultFB_IsInvalid = true;
+
+    if (mDefaultFB->mSize != mRequestedSize) {
         GenerateWarning("Requested size %dx%d was too large, but resize"
                           " to %dx%d succeeded.",
-                        requestedWidth, requestedHeight,
-                        width, height);
+                        mRequestedSize.width, mRequestedSize.height,
+                        mDefaultFB->mSize.width, mDefaultFB->mSize.height);
     }
+    mRequestedSize = mDefaultFB->mSize;
     return true;
 }
 
 void
 WebGLContext::ThrowEvent_WebGLContextCreationError(const nsACString& text)
 {
     RefPtr<EventTarget> target = mCanvasElement;
     if (!target && mOffscreenCanvas) {
@@ -882,46 +849,38 @@ WebGLContext::SetDimensions(int32_t sign
     if (width == 0)
         width = 1;
 
     if (height == 0)
         height = 1;
 
     // If we already have a gl context, then we just need to resize it
     if (gl) {
-        if ((uint32_t)mWidth == width &&
-            (uint32_t)mHeight == height)
+        if (uint32_t(mRequestedSize.width) == width &&
+            uint32_t(mRequestedSize.height) == height)
         {
             return NS_OK;
         }
 
         if (IsContextLost())
             return NS_OK;
 
         // If we've already drawn, we should commit the current buffer.
         PresentScreenBuffer();
 
         if (IsContextLost()) {
             GenerateWarning("WebGL context was lost due to swap failure.");
             return NS_OK;
         }
 
-        // ResizeOffscreen scraps the current prod buffer before making a new one.
-        if (!ResizeBackbuffer(width, height)) {
-            GenerateWarning("WebGL context failed to resize.");
-            ForceLoseContext();
-            return NS_OK;
-        }
-
-        // everything's good, we're done here
+        // Kill our current default fb(s), for later lazy allocation.
+        mRequestedSize = {width, height};
+        mDefaultFB = nullptr;
+
         mResetLayer = true;
-        mBackbufferNeedsClear = true;
-
-        gl->ResetSyncCallCount("Existing WebGLContext resized.");
-
         return NS_OK;
     }
 
     nsCString failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_UNKOWN");
     auto autoTelemetry = mozilla::MakeScopeExit([&] {
         Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID,
                               failureId);
     });
@@ -1009,17 +968,16 @@ WebGLContext::SetDimensions(int32_t sign
             text.AppendASCII("\n* ");
             text.Append(cur.info);
         }
         failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_REASON");
         ThrowEvent_WebGLContextCreationError(text);
         return NS_ERROR_FAILURE;
     }
     MOZ_ASSERT(gl);
-    MOZ_ASSERT_IF(mOptions.alpha, gl->Caps().alpha);
 
     if (mOptions.failIfMajorPerformanceCaveat) {
         if (gl->IsWARP()) {
             DestroyResourcesAndContext();
             MOZ_ASSERT(!gl);
 
             failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_PERF_WARP");
             const nsLiteralCString text("failIfMajorPerformanceCaveat: Driver is not"
@@ -1038,121 +996,94 @@ WebGLContext::SetDimensions(int32_t sign
             failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_DXGL_INTEROP2");
             const nsLiteralCString text("Caveat: WGL without DXGLInterop2.");
             ThrowEvent_WebGLContextCreationError(text);
             return NS_ERROR_FAILURE;
         }
 #endif
     }
 
-    if (!ResizeBackbuffer(width, height)) {
+    MOZ_ASSERT(!mDefaultFB);
+    mRequestedSize = {width, height};
+    if (!EnsureDefaultFB("context initialization")) {
+        MOZ_ASSERT(!gl);
+
         failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_BACKBUFFER");
         const nsLiteralCString text("Initializing WebGL backbuffer failed.");
         ThrowEvent_WebGLContextCreationError(text);
         return NS_ERROR_FAILURE;
     }
 
     if (GLContext::ShouldSpew()) {
         printf_stderr("--- WebGL context created: %p\n", gl.get());
     }
 
+    // Update our internal stuff:
+    mOptions.antialias = bool(mDefaultFB->mSamples);
+
+    if (!mOptions.alpha) {
+        // We always have alpha.
+        mNeedsFakeNoAlpha = true;
+    }
+
+    if (mOptions.depth || mOptions.stencil) {
+        // We always have depth+stencil if we have either.
+        if (!mOptions.depth) {
+            mNeedsFakeNoDepth = true;
+        }
+        if (!mOptions.stencil) {
+            mNeedsFakeNoStencil = true;
+        }
+    }
+
+    mNeedsFakeNoStencil_UserFBs = false;
+#ifdef MOZ_WIDGET_COCOA
+    if (!nsCocoaFeatures::IsAtLeastVersion(10, 12) &&
+        gl->Vendor() == GLVendor::Intel)
+    {
+        mNeedsFakeNoStencil_UserFBs = true;
+    }
+#endif
+
     mResetLayer = true;
     mOptionsFrozen = true;
 
-    // Update our internal stuff:
-    if (gl->WorkAroundDriverBugs()) {
-        if (!mOptions.alpha && gl->Caps().alpha)
-            mNeedsFakeNoAlpha = true;
-
-        if (!mOptions.depth && gl->Caps().depth)
-            mNeedsFakeNoDepth = true;
-
-        if (!mOptions.stencil && gl->Caps().stencil)
-            mNeedsFakeNoStencil = true;
-
-#ifdef MOZ_WIDGET_COCOA
-        if (!nsCocoaFeatures::IsAtLeastVersion(10, 12) &&
-            gl->Vendor() == GLVendor::Intel)
-        {
-            mNeedsEmulatedLoneDepthStencil = true;
-        }
-#endif
-    }
-
-    // Update mOptions.
-    if (!gl->Caps().depth)
-        mOptions.depth = false;
-
-    if (!gl->Caps().stencil)
-        mOptions.stencil = false;
-
-    mOptions.antialias = gl->Caps().antialias;
-
     //////
     // Initial setup.
 
     gl->mImplicitMakeCurrent = true;
 
-    gl->fViewport(0, 0, mWidth, mHeight);
+    const auto& size = mDefaultFB->mSize;
+
     mViewportX = mViewportY = 0;
-    mViewportWidth = mWidth;
-    mViewportHeight = mHeight;
-
-    gl->fScissor(0, 0, mWidth, mHeight);
-    gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
+    mViewportWidth = size.width;
+    mViewportHeight = size.height;
+    gl->fViewport(mViewportX, mViewportY, mViewportWidth, mViewportHeight);
+
+    gl->fScissor(0, 0, size.width, size.height);
 
     //////
     // Check everything
 
     AssertCachedBindings();
     AssertCachedGlobalState();
 
-    MOZ_ASSERT(gl->Caps().color);
-
-    MOZ_ASSERT_IF(!mNeedsFakeNoAlpha, gl->Caps().alpha == mOptions.alpha);
-    MOZ_ASSERT_IF(mNeedsFakeNoAlpha, !mOptions.alpha && gl->Caps().alpha);
-
-    MOZ_ASSERT_IF(!mNeedsFakeNoDepth, gl->Caps().depth == mOptions.depth);
-    MOZ_ASSERT_IF(mNeedsFakeNoDepth, !mOptions.depth && gl->Caps().depth);
-
-    MOZ_ASSERT_IF(!mNeedsFakeNoStencil, gl->Caps().stencil == mOptions.stencil);
-    MOZ_ASSERT_IF(mNeedsFakeNoStencil, !mOptions.stencil && gl->Caps().stencil);
-
-    MOZ_ASSERT(gl->Caps().antialias == mOptions.antialias);
-    MOZ_ASSERT(gl->Caps().preserve == mOptions.preserveDrawingBuffer);
-
-    //////
-    // Clear immediately, because we need to present the cleared initial buffer
-    mBackbufferNeedsClear = true;
-    ClearBackbufferIfNeeded();
-
     mShouldPresent = true;
 
     //////
 
     reporter.SetSuccessful();
 
     failureId = NS_LITERAL_CSTRING("SUCCESS");
 
     gl->ResetSyncCallCount("WebGLContext Initialization");
     return NS_OK;
 }
 
 void
-WebGLContext::ClearBackbufferIfNeeded()
-{
-    if (!mBackbufferNeedsClear)
-        return;
-
-    ClearScreen();
-
-    mBackbufferNeedsClear = false;
-}
-
-void
 WebGLContext::LoseOldestWebGLContextIfLimitExceeded()
 {
     const auto maxWebGLContexts = gfxPrefs::WebGLMaxContexts();
     auto maxWebGLContextsPerPrincipal = gfxPrefs::WebGLMaxContextsPerPrincipal();
 
     // maxWebGLContextsPerPrincipal must be less than maxWebGLContexts
     MOZ_ASSERT(maxWebGLContextsPerPrincipal <= maxWebGLContexts);
     maxWebGLContextsPerPrincipal = std::min(maxWebGLContextsPerPrincipal, maxWebGLContexts);
@@ -1398,18 +1329,18 @@ WebGLContext::InitializeCanvasRenderer(n
         // the invalidation state to indicate that the canvas is up to date.
         data.mPreTransCallback = WebGLContextUserData::PreTransactionCallback;
         data.mPreTransCallbackData = this;
         data.mDidTransCallback = WebGLContextUserData::DidTransactionCallback;
         data.mDidTransCallbackData = this;
     }
 
     data.mGLContext = gl;
-    data.mSize = nsIntSize(mWidth, mHeight);
-    data.mHasAlpha = gl->Caps().alpha;
+    data.mSize = DrawingBufferSize("InitializeCanvasRenderer");
+    data.mHasAlpha = mOptions.alpha;
     data.mIsGLAlphaPremult = IsPremultAlpha() || !data.mHasAlpha;
 
     aRenderer->Initialize(data);
     aRenderer->SetDirty();
     return true;
 }
 
 layers::LayersBackend
@@ -1501,55 +1432,34 @@ WebGLContext::MozGetUnderlyingParamStrin
     default:
         return NS_ERROR_INVALID_ARG;
     }
 
     return NS_OK;
 }
 
 void
-WebGLContext::ClearScreen()
-{
-    ScopedBindFramebuffer autoFB(gl, 0);
-
-    const bool changeDrawBuffers = (mDefaultFB_DrawBuffer0 != LOCAL_GL_BACK);
-    if (changeDrawBuffers) {
-        gl->Screen()->SetDrawBuffer(LOCAL_GL_BACK);
-    }
-
-    GLbitfield bufferBits = LOCAL_GL_COLOR_BUFFER_BIT;
-    if (mOptions.depth)
-        bufferBits |= LOCAL_GL_DEPTH_BUFFER_BIT;
-    if (mOptions.stencil)
-        bufferBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
-
-    ForceClearFramebufferWithDefaultValues(bufferBits, mNeedsFakeNoAlpha);
-
-    if (changeDrawBuffers) {
-        gl->Screen()->SetDrawBuffer(mDefaultFB_DrawBuffer0);
-    }
-}
-
-void
-WebGLContext::ForceClearFramebufferWithDefaultValues(GLbitfield clearBits,
-                                                     bool fakeNoAlpha)
+WebGLContext::ForceClearFramebufferWithDefaultValues(const GLbitfield clearBits,
+                                                     const bool fakeNoAlpha) const
 {
     const bool initializeColorBuffer = bool(clearBits & LOCAL_GL_COLOR_BUFFER_BIT);
     const bool initializeDepthBuffer = bool(clearBits & LOCAL_GL_DEPTH_BUFFER_BIT);
     const bool initializeStencilBuffer = bool(clearBits & LOCAL_GL_STENCIL_BUFFER_BIT);
 
     // Fun GL fact: No need to worry about the viewport here, glViewport is just
     // setting up a coordinates transformation, it doesn't affect glClear at all.
     AssertCachedGlobalState();
 
     // Prepare GL state for clearing.
-    gl->fDisable(LOCAL_GL_SCISSOR_TEST);
+    if (mScissorTestEnabled) {
+        gl->fDisable(LOCAL_GL_SCISSOR_TEST);
+    }
 
     if (initializeColorBuffer) {
-        gl->fColorMask(1, 1, 1, 1);
+        DoColorMask(0x0f);
 
         if (fakeNoAlpha) {
             gl->fClearColor(0.0f, 0.0f, 0.0f, 1.0f);
         } else {
             gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
         }
     }
 
@@ -1569,29 +1479,26 @@ WebGLContext::ForceClearFramebufferWithD
     if (mRasterizerDiscardEnabled) {
         gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD);
     }
 
     // Do the clear!
     gl->fClear(clearBits);
 
     // And reset!
-    if (mScissorTestEnabled)
+    if (mScissorTestEnabled) {
         gl->fEnable(LOCAL_GL_SCISSOR_TEST);
+    }
 
     if (mRasterizerDiscardEnabled) {
         gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
     }
 
     // Restore GL state after clearing.
     if (initializeColorBuffer) {
-        gl->fColorMask(mColorWriteMask[0],
-                       mColorWriteMask[1],
-                       mColorWriteMask[2],
-                       mColorWriteMask[3]);
         gl->fClearColor(mColorClearValue[0],
                         mColorClearValue[1],
                         mColorClearValue[2],
                         mColorClearValue[3]);
     }
 
     if (initializeDepthBuffer) {
         gl->fDepthMask(mDepthWriteMask);
@@ -1611,41 +1518,102 @@ WebGLContext::OnEndOfFrame() const
    if (gfxPrefs::WebGLSpewFrameAllocs()) {
       GeneratePerfWarning("[webgl.perf.spew-frame-allocs] %" PRIu64 " data allocations this frame.",
                            mDataAllocGLCallCount);
    }
    mDataAllocGLCallCount = 0;
    gl->ResetSyncCallCount("WebGLContext PresentScreenBuffer");
 }
 
+void
+WebGLContext::BlitBackbufferToCurDriverFB() const
+{
+    DoColorMask(0x0f);
+
+    if (mScissorTestEnabled) {
+        gl->fDisable(LOCAL_GL_SCISSOR_TEST);
+    }
+
+    [&]() {
+        const auto& size = mDefaultFB->mSize;
+
+        if (gl->IsSupported(GLFeature::framebuffer_blit)) {
+            gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB);
+            gl->fBlitFramebuffer(0, 0, size.width, size.height,
+                                 0, 0, size.width, size.height,
+                                 LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_NEAREST);
+            return;
+        }
+        if (mDefaultFB->mSamples &&
+            gl->IsExtensionSupported(GLContext::APPLE_framebuffer_multisample))
+        {
+            gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB);
+            gl->fResolveMultisampleFramebufferAPPLE();
+            return;
+        }
+
+        gl->BlitHelper()->DrawBlitTextureToFramebuffer(mDefaultFB->ColorTex(), size,
+                                                       size);
+    }();
+
+    if (mScissorTestEnabled) {
+        gl->fEnable(LOCAL_GL_SCISSOR_TEST);
+    }
+}
+
 // For an overview of how WebGL compositing works, see:
 // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
 bool
 WebGLContext::PresentScreenBuffer()
 {
-    if (IsContextLost()) {
+    if (IsContextLost())
+        return false;
+
+    if (!mShouldPresent)
+        return false;
+
+    if (!ValidateAndInitFB("Present", nullptr))
         return false;
-    }
-
-    if (!mShouldPresent) {
+
+    const auto& screen = gl->Screen();
+    if (screen->Size() != mDefaultFB->mSize &&
+        !screen->Resize(mDefaultFB->mSize))
+    {
+        GenerateWarning("screen->Resize failed. Losing context.");
+        ForceLoseContext();
         return false;
     }
-    MOZ_ASSERT(!mBackbufferNeedsClear);
-
-    GLScreenBuffer* screen = gl->Screen();
-    MOZ_ASSERT(screen);
+
+    gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
+    BlitBackbufferToCurDriverFB();
+
+#ifdef DEBUG
+    if (!mOptions.alpha) {
+        gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
+        uint32_t pixel = 3;
+        gl->fReadPixels(0, 0, 1, 1, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, &pixel);
+        MOZ_ASSERT((pixel & 0xff000000) == 0xff000000);
+    }
+#endif
 
     if (!screen->PublishFrame(screen->Size())) {
+        GenerateWarning("PublishFrame failed. Losing context.");
         ForceLoseContext();
         return false;
     }
 
     if (!mOptions.preserveDrawingBuffer) {
-        mBackbufferNeedsClear = true;
+        if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
+            gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
+            const GLenum attachments[] = { LOCAL_GL_COLOR_ATTACHMENT0 };
+            gl->fInvalidateFramebuffer(LOCAL_GL_FRAMEBUFFER, 1, attachments);
+        }
+        mDefaultFB_IsInvalid = true;
     }
+    mResolvedDefaultFB = nullptr;
 
     mShouldPresent = false;
     OnEndOfFrame();
 
     return true;
 }
 
 // Prepare the context for capture before compositing
@@ -1742,17 +1710,17 @@ CheckContextLost(GLContext* gl, bool* co
 
     *out_isGuilty = isGuilty;
     return true;
 }
 
 bool
 WebGLContext::TryToRestoreContext()
 {
-    if (NS_FAILED(SetDimensions(mWidth, mHeight)))
+    if (NS_FAILED(SetDimensions(mRequestedSize.width, mRequestedSize.height)))
         return false;
 
     return true;
 }
 
 void
 WebGLContext::RunContextLossTimer()
 {
@@ -1954,41 +1922,28 @@ WebGLContext::ForceRestoreContext()
 }
 
 already_AddRefed<mozilla::gfx::SourceSurface>
 WebGLContext::GetSurfaceSnapshot(gfxAlphaType* const out_alphaType)
 {
     if (!gl)
         return nullptr;
 
+    if (!BindDefaultFBForRead("GetSurfaceSnapshot"))
+        return nullptr;
+
     const auto surfFormat = mOptions.alpha ? SurfaceFormat::B8G8R8A8
                                            : SurfaceFormat::B8G8R8X8;
+    const auto& size = mDefaultFB->mSize;
     RefPtr<DataSourceSurface> surf;
-    surf = Factory::CreateDataSourceSurfaceWithStride(IntSize(mWidth, mHeight),
-                                                      surfFormat,
-                                                      mWidth * 4);
+    surf = Factory::CreateDataSourceSurfaceWithStride(size, surfFormat, size.width * 4);
     if (NS_WARN_IF(!surf))
         return nullptr;
 
-    {
-        ScopedBindFramebuffer autoFB(gl, 0);
-        ClearBackbufferIfNeeded();
-
-        // Save, override, then restore glReadBuffer.
-        const GLenum readBufferMode = gl->Screen()->GetReadBufferMode();
-
-        if (readBufferMode != LOCAL_GL_BACK) {
-            gl->Screen()->SetReadBuffer(LOCAL_GL_BACK);
-        }
-        ReadPixelsIntoDataSurface(gl, surf);
-
-        if (readBufferMode != LOCAL_GL_BACK) {
-            gl->Screen()->SetReadBuffer(readBufferMode);
-        }
-    }
+    ReadPixelsIntoDataSurface(gl, surf);
 
     gfxAlphaType alphaType;
     if (!mOptions.alpha) {
         alphaType = gfxAlphaType::Opaque;
     } else if (mOptions.premultipliedAlpha) {
         alphaType = gfxAlphaType::Premult;
     } else {
         alphaType = gfxAlphaType::NonPremult;
@@ -2000,138 +1955,222 @@ WebGLContext::GetSurfaceSnapshot(gfxAlph
         // Expects Opaque or Premult
         if (alphaType == gfxAlphaType::NonPremult) {
             gfxUtils::PremultiplyDataSurface(surf, surf);
         }
     }
 
     RefPtr<DrawTarget> dt =
         Factory::CreateDrawTarget(gfxPlatform::GetPlatform()->GetSoftwareBackend(),
-                                  IntSize(mWidth, mHeight),
-                                  SurfaceFormat::B8G8R8A8);
+                                  size, SurfaceFormat::B8G8R8A8);
     if (!dt)
         return nullptr;
 
-    dt->SetTransform(Matrix::Translation(0.0, mHeight).PreScale(1.0, -1.0));
-
-    dt->DrawSurface(surf,
-                    Rect(0, 0, mWidth, mHeight),
-                    Rect(0, 0, mWidth, mHeight),
-                    DrawSurfaceOptions(),
+    dt->SetTransform(Matrix::Translation(0.0, size.height).PreScale(1.0, -1.0));
+
+    const gfx::Rect rect{0, 0, float(size.width), float(size.height)};
+    dt->DrawSurface(surf, rect, rect, DrawSurfaceOptions(),
                     DrawOptions(1.0f, CompositionOp::OP_SOURCE));
 
     return dt->Snapshot();
 }
 
 void
 WebGLContext::DidRefresh()
 {
     if (gl) {
         gl->FlushIfHeavyGLCallsSinceLastFlush();
     }
 }
 
+////////////////////////////////////////////////////////////////////////////////
+
+gfx::IntSize
+WebGLContext::DrawingBufferSize(const char* const funcName)
+{
+    const gfx::IntSize zeros{0, 0};
+    if (IsContextLost())
+        return zeros;
+
+    if (!EnsureDefaultFB(funcName))
+        return zeros;
+
+    return mDefaultFB->mSize;
+}
+
 bool
-WebGLContext::ValidateCurFBForRead(const char* funcName,
-                                   const webgl::FormatUsageInfo** const out_format,
-                                   uint32_t* const out_width, uint32_t* const out_height)
+WebGLContext::ValidateAndInitFB(const char* const funcName,
+                                const WebGLFramebuffer* const fb)
+{
+    if (fb)
+        return fb->ValidateAndInitAttachments(funcName);
+
+    if (!EnsureDefaultFB(funcName))
+        return false;
+
+    if (mDefaultFB_IsInvalid) {
+        gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
+        const GLbitfield bits = LOCAL_GL_COLOR_BUFFER_BIT |
+                                LOCAL_GL_DEPTH_BUFFER_BIT |
+                                LOCAL_GL_STENCIL_BUFFER_BIT;
+        const bool fakeNoAlpha = !mOptions.alpha;
+        ForceClearFramebufferWithDefaultValues(bits, fakeNoAlpha);
+        mDefaultFB_IsInvalid = false;
+    }
+    return true;
+}
+
+void
+WebGLContext::DoBindFB(const WebGLFramebuffer* const fb, const GLenum target) const
+{
+    const GLenum driverFB = fb ? fb->mGLName : mDefaultFB->mFB;
+    gl->fBindFramebuffer(target, driverFB);
+}
+
+bool
+WebGLContext::BindCurFBForDraw(const char* const funcName)
 {
-    if (!mBoundReadFramebuffer) {
-        const GLenum readBufferMode = gl->Screen()->GetReadBufferMode();
-        if (readBufferMode == LOCAL_GL_NONE) {
-            ErrorInvalidOperation("%s: Can't read from backbuffer when readBuffer mode is"
-                                  " NONE.",
-                                  funcName);
+    const auto& fb = mBoundDrawFramebuffer;
+    if (!ValidateAndInitFB(funcName, fb))
+        return false;
+
+    DoBindFB(fb);
+    return true;
+}
+
+bool
+WebGLContext::BindCurFBForColorRead(const char* const funcName,
+                                    const webgl::FormatUsageInfo** const out_format,
+                                    uint32_t* const out_width,
+                                    uint32_t* const out_height)
+{
+    const auto& fb = mBoundReadFramebuffer;
+
+    if (fb) {
+        if (!ValidateAndInitFB(funcName, fb))
+            return false;
+        if (!fb->ValidateForColorRead(funcName, out_format, out_width, out_height))
+            return false;
+
+        gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb->mGLName);
+        return true;
+    }
+
+    if (!BindDefaultFBForRead(funcName))
+        return false;
+
+    if (mDefaultFB_ReadBuffer == LOCAL_GL_NONE) {
+        ErrorInvalidOperation("%s: Can't read from backbuffer when readBuffer mode is"
+                              " NONE.",
+                              funcName);
+        return false;
+    }
+
+    auto effFormat = mOptions.alpha ? webgl::EffectiveFormat::RGBA8
+                                    : webgl::EffectiveFormat::RGB8;
+
+    *out_format = mFormatUsage->GetUsage(effFormat);
+    MOZ_ASSERT(*out_format);
+
+    *out_width = mDefaultFB->mSize.width;
+    *out_height = mDefaultFB->mSize.height;
+    return true;
+}
+
+bool
+WebGLContext::BindDefaultFBForRead(const char* const funcName)
+{
+    if (!ValidateAndInitFB(funcName, nullptr))
+        return false;
+
+    if (!mDefaultFB->mSamples) {
+        gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
+        return true;
+    }
+
+    if (!mResolvedDefaultFB) {
+        mResolvedDefaultFB = MozFramebuffer::Create(gl, mDefaultFB->mSize, 0, false);
+        if (!mResolvedDefaultFB) {
+            gfxCriticalNote << funcName << ": Failed to create mResolvedDefaultFB.";
             return false;
         }
-
-        ClearBackbufferIfNeeded();
-
-        // FIXME - here we're assuming that the default framebuffer is backed by
-        // UNSIGNED_BYTE that might not always be true, say if we had a 16bpp default
-        // framebuffer.
-        auto effFormat = mOptions.alpha ? webgl::EffectiveFormat::RGBA8
-                                        : webgl::EffectiveFormat::RGB8;
-
-        *out_format = mFormatUsage->GetUsage(effFormat);
-        MOZ_ASSERT(*out_format);
-
-        *out_width = mWidth;
-        *out_height = mHeight;
-        return true;
     }
 
-    return mBoundReadFramebuffer->ValidateForRead(funcName, out_format, out_width,
-                                                  out_height);
+    gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
+    BlitBackbufferToCurDriverFB();
+
+    gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
+    return true;
+}
+
+void
+WebGLContext::DoColorMask(const uint8_t bitmask) const
+{
+    if (mDriverColorMask != bitmask) {
+        mDriverColorMask = bitmask;
+        gl->fColorMask(bool(mDriverColorMask & (1 << 0)),
+                       bool(mDriverColorMask & (1 << 1)),
+                       bool(mDriverColorMask & (1 << 2)),
+                       bool(mDriverColorMask & (1 << 3)));
+    }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
-WebGLContext::ScopedDrawCallWrapper::ScopedDrawCallWrapper(WebGLContext& webgl)
+ScopedDrawCallWrapper::ScopedDrawCallWrapper(WebGLContext& webgl)
     : mWebGL(webgl)
-    , mFakeNoAlpha(ShouldFakeNoAlpha(webgl))
-    , mFakeNoDepth(ShouldFakeNoDepth(webgl))
-    , mFakeNoStencil(ShouldFakeNoStencil(webgl))
 {
-    if (!mWebGL.mBoundDrawFramebuffer) {
-        mWebGL.ClearBackbufferIfNeeded();
+    uint8_t driverColorMask = mWebGL.mColorWriteMask;
+    bool driverDepthTest    = mWebGL.mDepthTestEnabled;
+    bool driverStencilTest  = mWebGL.mStencilTestEnabled;
+    const auto& fb = mWebGL.mBoundDrawFramebuffer;
+    if (!fb) {
+        if (mWebGL.mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE) {
+            driverColorMask = 0; // Is this well-optimized enough for depth-first
+                                 // rendering?
+        } else {
+            driverColorMask &= ~(uint8_t(mWebGL.mNeedsFakeNoAlpha) << 3);
+        }
+        driverDepthTest   &= !mWebGL.mNeedsFakeNoDepth;
+        driverStencilTest &= !mWebGL.mNeedsFakeNoStencil;
+    } else {
+        if (mWebGL.mNeedsFakeNoStencil_UserFBs &&
+            fb->DepthAttachment().IsDefined() &&
+            !fb->StencilAttachment().IsDefined())
+        {
+            driverStencilTest = false;
+        }
     }
 
-    if (mFakeNoAlpha) {
-        mWebGL.gl->fColorMask(mWebGL.mColorWriteMask[0],
-                              mWebGL.mColorWriteMask[1],
-                              mWebGL.mColorWriteMask[2],
-                              false);
+    const auto& gl = mWebGL.gl;
+    mWebGL.DoColorMask(driverColorMask);
+    if (mWebGL.mDriverDepthTest != driverDepthTest) {
+        // "When disabled, the depth comparison and subsequent possible updates to the
+        //  depth buffer value are bypassed and the fragment is passed to the next
+        //  operation." [GLES 3.0.5, p177]
+        mWebGL.mDriverDepthTest = driverDepthTest;
+        gl->SetEnabled(LOCAL_GL_DEPTH_TEST, mWebGL.mDriverDepthTest);
     }
-    if (mFakeNoDepth) {
-        mWebGL.gl->fDisable(LOCAL_GL_DEPTH_TEST);
-    }
-    if (mFakeNoStencil) {
-        mWebGL.gl->fDisable(LOCAL_GL_STENCIL_TEST);
+    if (mWebGL.mDriverStencilTest != driverStencilTest) {
+        // "When disabled, the stencil test and associated modifications are not made, and
+        //  the fragment is always passed." [GLES 3.0.5, p175]
+        mWebGL.mDriverStencilTest = driverStencilTest;
+        gl->SetEnabled(LOCAL_GL_STENCIL_TEST, mWebGL.mDriverStencilTest);
     }
 }
 
-WebGLContext::ScopedDrawCallWrapper::~ScopedDrawCallWrapper()
+ScopedDrawCallWrapper::~ScopedDrawCallWrapper()
 {
-    if (mFakeNoAlpha) {
-        mWebGL.gl->fColorMask(mWebGL.mColorWriteMask[0],
-                              mWebGL.mColorWriteMask[1],
-                              mWebGL.mColorWriteMask[2],
-                              mWebGL.mColorWriteMask[3]);
-    }
-    if (mFakeNoDepth) {
-        mWebGL.gl->fEnable(LOCAL_GL_DEPTH_TEST);
-    }
-    if (mFakeNoStencil) {
-        MOZ_ASSERT(mWebGL.mStencilTestEnabled);
-        mWebGL.gl->fEnable(LOCAL_GL_STENCIL_TEST);
-    }
-
-    if (!mWebGL.mBoundDrawFramebuffer) {
-        mWebGL.Invalidate();
-        mWebGL.mShouldPresent = true;
-    }
-}
-
-/*static*/ bool
-WebGLContext::ScopedDrawCallWrapper::HasDepthButNoStencil(const WebGLFramebuffer* fb)
-{
-    const auto& depth = fb->DepthAttachment();
-    const auto& stencil = fb->StencilAttachment();
-    return depth.IsDefined() && !stencil.IsDefined();
-}
-
-////
-
-void
-WebGLContext::OnBeforeReadCall()
-{
-    if (!mBoundReadFramebuffer) {
-        ClearBackbufferIfNeeded();
-    }
+    if (mWebGL.mBoundDrawFramebuffer)
+        return;
+
+    mWebGL.mResolvedDefaultFB = nullptr;
+
+    mWebGL.Invalidate();
+    mWebGL.mShouldPresent = true;
 }
 
 ////////////////////////////////////////
 
 IndexedBufferBinding::IndexedBufferBinding()
     : mRangeStart(0)
     , mRangeSize(0)
 { }
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -62,16 +62,17 @@ class nsIDocShell;
 #define LOCAL_GL_CONTEXT_LOST_WEBGL                          0x9242
 #define LOCAL_GL_MAX_CLIENT_WAIT_TIMEOUT_WEBGL               0x9247
 #define LOCAL_GL_UNPACK_COLORSPACE_CONVERSION_WEBGL          0x9243
 #define LOCAL_GL_UNPACK_FLIP_Y_WEBGL                         0x9240
 #define LOCAL_GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL              0x9241
 
 namespace mozilla {
 class ScopedCopyTexImageSource;
+class ScopedDrawCallWrapper;
 class ScopedResolveTexturesForDraw;
 class ScopedUnpackReset;
 class WebGLActiveInfo;
 class WebGLBuffer;
 class WebGLExtensionBase;
 class WebGLFramebuffer;
 class WebGLProgram;
 class WebGLQuery;
@@ -93,16 +94,20 @@ struct WebGLContextAttributes;
 template<typename> struct Nullable;
 } // namespace dom
 
 namespace gfx {
 class SourceSurface;
 class VRLayerChild;
 } // namespace gfx
 
+namespace gl {
+class MozFramebuffer;
+} // namespace gl
+
 namespace webgl {
 struct LinkedProgramInfo;
 class ShaderValidator;
 class TexUnpackBlob;
 struct UniformInfo;
 struct UniformBlockInfo;
 } // namespace webgl
 
@@ -268,19 +273,19 @@ struct TexImageSourceAdapter final : pub
 
 ////////////////////////////////////////////////////////////////////////////////
 
 class WebGLContext
     : public nsIDOMWebGLRenderingContext
     , public nsICanvasRenderingContextInternal
     , public nsSupportsWeakReference
     , public WebGLContextUnchecked
-    , public WebGLRectangleObject
     , public nsWrapperCache
 {
+    friend class ScopedDrawCallWrapper;
     friend class ScopedDrawHelper;
     friend class ScopedDrawWithTransformFeedback;
     friend class ScopedFBRebinder;
     friend class WebGL2Context;
     friend class WebGLContextUserData;
     friend class WebGLExtensionCompressedTextureASTC;
     friend class WebGLExtensionCompressedTextureATC;
     friend class WebGLExtensionCompressedTextureES3;
@@ -332,18 +337,18 @@ public:
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override = 0;
 
     NS_DECL_NSIDOMWEBGLRENDERINGCONTEXT
 
     virtual void OnVisibilityChange() override;
     virtual void OnMemoryPressure() override;
 
     // nsICanvasRenderingContextInternal
-    virtual int32_t GetWidth() const override;
-    virtual int32_t GetHeight() const override;
+    virtual int32_t GetWidth() override { return DrawingBufferWidth("get width"); }
+    virtual int32_t GetHeight() override { return DrawingBufferHeight("get height"); }
 
     NS_IMETHOD SetDimensions(int32_t width, int32_t height) override;
     NS_IMETHOD InitializeWithDrawTarget(nsIDocShell*,
                                         NotNull<gfx::DrawTarget*>) override
     {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 
@@ -356,17 +361,17 @@ public:
     NS_IMETHOD GetInputStream(const char* mimeType,
                               const char16_t* encoderOptions,
                               nsIInputStream** out_stream) override;
 
     virtual already_AddRefed<mozilla::gfx::SourceSurface>
     GetSurfaceSnapshot(gfxAlphaType* out_alphaType) override;
 
     virtual void SetIsOpaque(bool) override {};
-    bool GetIsOpaque() override { return false; }
+    bool GetIsOpaque() override { return !mOptions.alpha; }
     NS_IMETHOD SetContextOptions(JSContext* cx,
                                  JS::Handle<JS::Value> options,
                                  ErrorResult& aRvForDictionaryInit) override;
 
     NS_IMETHOD SetIsIPC(bool) override {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 
@@ -376,32 +381,32 @@ public:
      * before it is destroyed.
      */
     virtual void DidRefresh() override;
 
     NS_IMETHOD Redraw(const gfxRect&) override {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 
-    void SynthesizeGLError(GLenum err);
-    void SynthesizeGLError(GLenum err, const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4);
+    void SynthesizeGLError(GLenum err) const;
+    void SynthesizeGLError(GLenum err, const char* fmt, ...) const MOZ_FORMAT_PRINTF(3, 4);
 
-    void ErrorInvalidEnum(const char* fmt = 0, ...) MOZ_FORMAT_PRINTF(2, 3);
-    void ErrorInvalidOperation(const char* fmt = 0, ...) MOZ_FORMAT_PRINTF(2, 3);
-    void ErrorInvalidValue(const char* fmt = 0, ...) MOZ_FORMAT_PRINTF(2, 3);
-    void ErrorInvalidFramebufferOperation(const char* fmt = 0, ...) MOZ_FORMAT_PRINTF(2, 3);
-    void ErrorInvalidEnumInfo(const char* info, GLenum enumValue);
+    void ErrorInvalidEnum(const char* fmt = 0, ...) const MOZ_FORMAT_PRINTF(2, 3);
+    void ErrorInvalidOperation(const char* fmt = 0, ...) const MOZ_FORMAT_PRINTF(2, 3);
+    void ErrorInvalidValue(const char* fmt = 0, ...) const MOZ_FORMAT_PRINTF(2, 3);
+    void ErrorInvalidFramebufferOperation(const char* fmt = 0, ...) const MOZ_FORMAT_PRINTF(2, 3);
+    void ErrorInvalidEnumInfo(const char* info, GLenum enumValue) const;
     void ErrorInvalidEnumInfo(const char* info, const char* funcName,
-                              GLenum enumValue);
-    void ErrorOutOfMemory(const char* fmt = 0, ...) MOZ_FORMAT_PRINTF(2, 3);
-    void ErrorImplementationBug(const char* fmt = 0, ...) MOZ_FORMAT_PRINTF(2, 3);
+                              GLenum enumValue) const;
+    void ErrorOutOfMemory(const char* fmt = 0, ...) const MOZ_FORMAT_PRINTF(2, 3);
+    void ErrorImplementationBug(const char* fmt = 0, ...) const MOZ_FORMAT_PRINTF(2, 3);
 
-    void ErrorInvalidEnumArg(const char* funcName, const char* argName, GLenum val);
+    void ErrorInvalidEnumArg(const char* funcName, const char* argName, GLenum val) const;
 
-    const char* ErrorName(GLenum error);
+    static const char* ErrorName(GLenum error);
 
     /**
      * Return displayable name for GLenum.
      * This version is like gl::GLenumToStr but with out the GL_ prefix to
      * keep consistency with how errors are reported from WebGL.
      * Returns hex formatted version of glenum if glenum is unknown.
      */
     static void EnumName(GLenum val, nsCString* out_name);
@@ -472,40 +477,42 @@ public:
     // a number that increments every time we have an event that causes
     // all context resources to be lost.
     uint32_t Generation() const { return mGeneration.value(); }
 
     // This is similar to GLContext::ClearSafely, but tries to minimize the
     // amount of work it does.
     // It only clears the buffers we specify, and can reset its state without
     // first having to query anything, as WebGL knows its state at all times.
-    void ForceClearFramebufferWithDefaultValues(GLbitfield bufferBits, bool fakeNoAlpha);
-
-    // Calls ForceClearFramebufferWithDefaultValues() for the Context's 'screen'.
-    void ClearScreen();
-    void ClearBackbufferIfNeeded();
+    void ForceClearFramebufferWithDefaultValues(GLbitfield bufferBits,
+                                                bool fakeNoAlpha) const;
 
     void RunContextLossTimer();
     void UpdateContextLossStatus();
     void EnqueueUpdateContextLossStatus();
 
     bool TryToRestoreContext();
 
-    void AssertCachedBindings();
-    void AssertCachedGlobalState();
+    void AssertCachedBindings() const;
+    void AssertCachedGlobalState() const;
 
     dom::HTMLCanvasElement* GetCanvas() const { return mCanvasElement; }
     nsIDocument* GetOwnerDoc() const;
 
     // WebIDL WebGLRenderingContext API
     void Commit();
     void GetCanvas(Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& retval);
-    GLsizei DrawingBufferWidth() const { return IsContextLost() ? 0 : mWidth; }
-    GLsizei DrawingBufferHeight() const {
-        return IsContextLost() ? 0 : mHeight;
+private:
+    gfx::IntSize DrawingBufferSize(const char* funcName);
+public:
+    GLsizei DrawingBufferWidth(const char* const funcName = "drawingBufferWidth") {
+        return DrawingBufferSize(funcName).width;
+    }
+    GLsizei DrawingBufferHeight(const char* const funcName = "drawingBufferHeight") {
+        return DrawingBufferSize(funcName).height;
     }
 
     layers::LayersBackend GetCompositorBackendType() const;
 
     void
     GetContextAttributes(dom::Nullable<dom::WebGLContextAttributes>& retval);
 
     bool IsContextLost() const { return mContextStatus != ContextNotLost; }
@@ -978,20 +985,22 @@ public:
     void GetQuery(JSContext* cx, GLenum target, GLenum pname,
                   JS::MutableHandleValue retval, const char* funcName = nullptr);
     void GetQueryParameter(JSContext* cx, const WebGLQuery& query, GLenum pname,
                            JS::MutableHandleValue retval, const char* funcName = nullptr);
 
 
 // -----------------------------------------------------------------------------
 // State and State Requests (WebGLContextState.cpp)
+private:
+    void SetEnabled(const char* funcName, GLenum cap, bool enabled);
 public:
-    void Disable(GLenum cap);
-    void Enable(GLenum cap);
-    bool GetStencilBits(GLint* const out_stencilBits);
+    void Disable(GLenum cap) { SetEnabled("disabled", cap, false); }
+    void Enable(GLenum cap) { SetEnabled("enabled", cap, true); }
+    bool GetStencilBits(GLint* const out_stencilBits) const;
     bool GetChannelBits(const char* funcName, GLenum pname, GLint* const out_val);
     virtual JS::Value GetParameter(JSContext* cx, GLenum pname, ErrorResult& rv);
 
     void GetParameter(JSContext* cx, GLenum pname,
                       JS::MutableHandle<JS::Value> retval, ErrorResult& rv)
     {
         retval.set(GetParameter(cx, pname, rv));
     }
@@ -1438,30 +1447,30 @@ protected:
     bool mResetLayer;
     bool mOptionsFrozen;
     bool mDisableExtensions;
     bool mIsMesa;
     bool mLoseContextOnMemoryPressure;
     bool mCanLoseContextInForeground;
     bool mRestoreWhenVisible;
     bool mShouldPresent;
-    bool mBackbufferNeedsClear;
     bool mDisableFragHighP;
 
     template<typename WebGLObjectType>
     void DeleteWebGLObjectsArray(nsTArray<WebGLObjectType>& array);
 
     GLuint mActiveTexture;
     GLenum mDefaultFB_DrawBuffer0;
+    GLenum mDefaultFB_ReadBuffer;
 
     // glGetError sources:
     bool mEmitContextLostErrorOnce;
-    GLenum mWebGLError;
-    GLenum mUnderlyingGLError;
-    GLenum GetAndFlushUnderlyingGLErrors();
+    mutable GLenum mWebGLError;
+    mutable GLenum mUnderlyingGLError;
+    GLenum GetAndFlushUnderlyingGLErrors() const;
 
     bool mBypassShaderValidation;
 
     webgl::ShaderValidator* CreateShaderValidator(GLenum shaderType) const;
 
     // some GL constants
     uint32_t mGLMaxTextureUnits;
 
@@ -1580,18 +1589,16 @@ public:
         { }
     };
 protected:
     bool InitWebGL2(FailureReason* const out_failReason);
 
     bool CreateAndInitGL(bool forceEnabled,
                          std::vector<FailureReason>* const out_failReasons);
 
-    bool ResizeBackbuffer(uint32_t width, uint32_t height);
-
     typedef already_AddRefed<gl::GLContext> FnCreateGL_T(const gl::SurfaceCaps& caps,
                                                          gl::CreateContextFlags flags,
                                                          WebGLContext* webgl,
                                                          std::vector<FailureReason>* const out_failReasons);
 
     bool CreateAndInitGLWith(FnCreateGL_T fnCreateGL, const gl::SurfaceCaps& baseCaps,
                              gl::CreateContextFlags flags,
                              std::vector<FailureReason>* const out_failReasons);
@@ -1657,20 +1664,16 @@ protected:
                                       uint32_t byteLength,
                                       WebGLTexImageFunc func,
                                       WebGLTexDimensions dims);
 
     bool ValidateUniformLocationForProgram(WebGLUniformLocation* location,
                                            WebGLProgram* program,
                                            const char* funcName);
 
-    bool ValidateCurFBForRead(const char* funcName,
-                              const webgl::FormatUsageInfo** const out_format,
-                              uint32_t* const out_width, uint32_t* const out_height);
-
     bool HasDrawBuffers() const {
         return IsWebGL2() ||
                IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers);
     }
 
     WebGLRefPtr<WebGLBuffer>* ValidateBufferSlot(const char* funcName, GLenum target);
 public:
     WebGLBuffer* ValidateBufferSelection(const char* funcName, GLenum target);
@@ -1933,17 +1936,17 @@ protected:
     JSObject* GetVertexAttribUint32Array(JSContext* cx, GLuint index);
 
     GLint mStencilRefFront;
     GLint mStencilRefBack;
     GLuint mStencilValueMaskFront;
     GLuint mStencilValueMaskBack;
     GLuint mStencilWriteMaskFront;
     GLuint mStencilWriteMaskBack;
-    realGLboolean mColorWriteMask[4];
+    uint8_t mColorWriteMask; // bitmask
     realGLboolean mDepthWriteMask;
     GLfloat mColorClearValue[4];
     GLint mStencilClearValue;
     GLfloat mDepthClearValue;
 
     GLint mViewportX;
     GLint mViewportY;
     GLsizei mViewportWidth;
@@ -1957,96 +1960,68 @@ protected:
     bool mLastLossWasSimulated;
     ContextStatus mContextStatus;
     bool mContextLostErrorSet;
 
     // Used for some hardware (particularly Tegra 2 and 4) that likes to
     // be Flushed while doing hundreds of draw calls.
     int mDrawCallsSinceLastFlush;
 
-    int mAlreadyGeneratedWarnings;
+    mutable int mAlreadyGeneratedWarnings;
     int mMaxWarnings;
     bool mAlreadyWarnedAboutFakeVertexAttrib0;
 
     bool ShouldGenerateWarnings() const;
 
     bool ShouldGeneratePerfWarnings() const {
         return mNumPerfWarnings < mMaxPerfWarnings;
     }
 
     uint64_t mLastUseIndex;
 
     bool mNeedsFakeNoAlpha;
     bool mNeedsFakeNoDepth;
     bool mNeedsFakeNoStencil;
-    bool mNeedsEmulatedLoneDepthStencil;
+    bool mNeedsFakeNoStencil_UserFBs;
+
+    mutable uint8_t mDriverColorMask;
+    bool mDriverDepthTest;
+    bool mDriverStencilTest;
 
     bool mNeedsIndexValidation;
 
     const bool mAllowFBInvalidation;
 
     bool Has64BitTimestamps() const;
 
-    struct ScopedDrawCallWrapper final {
-        WebGLContext& mWebGL;
-        const bool mFakeNoAlpha;
-        const bool mFakeNoDepth;
-        const bool mFakeNoStencil;
+    // --
 
-        static bool ShouldFakeNoAlpha(WebGLContext& webgl) {
-            // We should only be doing this if we're about to draw to the backbuffer, but
-            // the backbuffer needs to have this fake-no-alpha workaround.
-            return !webgl.mBoundDrawFramebuffer &&
-                   webgl.mNeedsFakeNoAlpha &&
-                   webgl.mColorWriteMask[3] != false;
-        }
+    const uint8_t mMsaaSamples;
+    mutable gfx::IntSize mRequestedSize;
+    mutable UniquePtr<gl::MozFramebuffer> mDefaultFB;
+    mutable bool mDefaultFB_IsInvalid;
+    mutable UniquePtr<gl::MozFramebuffer> mResolvedDefaultFB;
 
-        static bool ShouldFakeNoDepth(WebGLContext& webgl) {
-            // We should only be doing this if we're about to draw to the backbuffer.
-            return !webgl.mBoundDrawFramebuffer &&
-                   webgl.mNeedsFakeNoDepth &&
-                   webgl.mDepthTestEnabled;
-        }
-
-        static bool HasDepthButNoStencil(const WebGLFramebuffer* fb);
-
-        static bool ShouldFakeNoStencil(WebGLContext& webgl) {
-            if (!webgl.mStencilTestEnabled)
-                return false;
+    // --
 
-            if (!webgl.mBoundDrawFramebuffer) {
-                if (webgl.mNeedsFakeNoStencil)
-                    return true;
-
-                if (webgl.mNeedsEmulatedLoneDepthStencil &&
-                    webgl.mOptions.depth && !webgl.mOptions.stencil)
-                {
-                    return true;
-                }
-
-                return false;
-            }
+    bool EnsureDefaultFB(const char* funcName);
+    bool ValidateAndInitFB(const char* funcName, const WebGLFramebuffer* fb);
+    void DoBindFB(const WebGLFramebuffer* fb, GLenum target = LOCAL_GL_FRAMEBUFFER) const;
 
-            if (webgl.mNeedsEmulatedLoneDepthStencil &&
-                HasDepthButNoStencil(webgl.mBoundDrawFramebuffer))
-            {
-                return true;
-            }
-
-            return false;
-        }
+    bool BindCurFBForDraw(const char* funcName);
+    bool BindCurFBForColorRead(const char* funcName,
+                               const webgl::FormatUsageInfo** out_format,
+                               uint32_t* out_width, uint32_t* out_height);
+    void DoColorMask(uint8_t bitmask) const;
+    void BlitBackbufferToCurDriverFB() const;
+    bool BindDefaultFBForRead(const char* funcName);
 
-        ////
+    // --
 
-        explicit ScopedDrawCallWrapper(WebGLContext& webgl);
-        ~ScopedDrawCallWrapper();
-    };
-
-    void OnBeforeReadCall();
-
+public:
     void LoseOldestWebGLContextIfLimitExceeded();
     void UpdateLastUseIndex();
 
     template <typename WebGLObjectType>
     JS::Value WebGLObjectAsJSValue(JSContext* cx, const WebGLObjectType*,
                                    ErrorResult& rv) const;
     template <typename WebGLObjectType>
     JSObject* WebGLObjectAsJSObject(JSContext* cx, const WebGLObjectType*,
@@ -2058,18 +2033,18 @@ protected:
     // these objects at high frequency. Having WebGLContext's hold one such object seems fine,
     // because WebGLContext objects only go away during GC, which shouldn't happen too frequently.
     // If in the future GC becomes much more frequent, we may have to revisit then (maybe use a timer).
     ForceDiscreteGPUHelperCGL mForceDiscreteGPUHelper;
 #endif
 
 public:
     // console logging helpers
-    void GenerateWarning(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
-    void GenerateWarning(const char* fmt, va_list ap) MOZ_FORMAT_PRINTF(2, 0);
+    void GenerateWarning(const char* fmt, ...) const MOZ_FORMAT_PRINTF(2, 3);
+    void GenerateWarning(const char* fmt, va_list ap) const MOZ_FORMAT_PRINTF(2, 0);
 
     void GeneratePerfWarning(const char* fmt, ...) const MOZ_FORMAT_PRINTF(2, 3);
 
 public:
     UniquePtr<webgl::FormatUsageAuthority> mFormatUsage;
 
     virtual UniquePtr<webgl::FormatUsageAuthority>
     CreateFormatUsage(gl::GLContext* gl) const = 0;
@@ -2185,16 +2160,27 @@ Intersect(int32_t srcSize, int32_t read0
           int32_t* out_intWrite0, int32_t* out_intSize);
 
 uint64_t
 AvailGroups(uint64_t totalAvailItems, uint64_t firstItemOffset, uint32_t groupSize,
             uint32_t groupStride);
 
 ////
 
+class ScopedDrawCallWrapper final
+{
+public:
+    WebGLContext& mWebGL;
+
+    explicit ScopedDrawCallWrapper(WebGLContext& webgl);
+    ~ScopedDrawCallWrapper();
+};
+
+////
+
 void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
                             const std::vector<IndexedBufferBinding>& field,
                             const char* name, uint32_t flags = 0);
 
 void
 ImplCycleCollectionUnlink(std::vector<IndexedBufferBinding>& field);
 
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 "WebGLContext.h"
 
 #include "GeckoProfiler.h"
+#include "gfx/gl/MozFramebuffer.h"
 #include "GLContext.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "nsPrintfCString.h"
 #include "WebGLBuffer.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLProgram.h"
@@ -94,33 +95,20 @@ WebGLTexture::IsFeedback(WebGLContext* w
 
 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(WebGLContext* webgl,
                                                            const char* funcName,
                                                            bool* const out_error)
     : mWebGL(webgl)
 {
     MOZ_ASSERT(mWebGL->gl->IsCurrent());
 
-    if (!mWebGL->mActiveProgramLinkInfo) {
-        mWebGL->ErrorInvalidOperation("%s: The current program is not linked.", funcName);
-        *out_error = true;
-        return;
-    }
-
     const std::vector<const WebGLFBAttachPoint*>* attachList = nullptr;
     const auto& fb = mWebGL->mBoundDrawFramebuffer;
     if (fb) {
-        if (!fb->ValidateAndInitAttachments(funcName)) {
-            *out_error = true;
-            return;
-        }
-
         attachList = &(fb->ResolvedCompleteData()->texDrawBuffers);
-    } else {
-        webgl->ClearBackbufferIfNeeded();
     }
 
     MOZ_ASSERT(mWebGL->mActiveProgramLinkInfo);
     const auto& uniformSamplers = mWebGL->mActiveProgramLinkInfo->uniformSamplers;
     for (const auto& uniform : uniformSamplers) {
         const auto& texList = *(uniform->mSamplerTexList);
 
         for (const auto& texUnit : uniform->mSamplerValues) {
@@ -250,42 +238,41 @@ public:
     ScopedDrawHelper(WebGLContext* const webgl, const char* const funcName,
                      const GLenum mode, const Maybe<uint32_t>& lastRequiredVertex,
                      const uint32_t instanceCount, bool* const out_error)
         : mWebGL(webgl)
         , mDidFake(false)
     {
         MOZ_ASSERT(mWebGL->gl->IsCurrent());
 
+        if (!mWebGL->BindCurFBForDraw(funcName)) {
+            *out_error = true;
+            return;
+        }
+
         if (!mWebGL->ValidateDrawModeEnum(mode, funcName)) {
             *out_error = true;
             return;
         }
 
         if (!mWebGL->ValidateStencilParamsForDrawCall()) {
             *out_error = true;
             return;
         }
 
-        ////
-
-        if (mWebGL->mBoundDrawFramebuffer) {
-            if (!mWebGL->mBoundDrawFramebuffer->ValidateAndInitAttachments(funcName)) {
-                *out_error = true;
-                return;
-            }
-        } else {
-            mWebGL->ClearBackbufferIfNeeded();
+        if (!mWebGL->mActiveProgramLinkInfo) {
+            mWebGL->ErrorInvalidOperation("%s: The current program is not linked.", funcName);
+            *out_error = true;
+            return;
         }
+        const auto& linkInfo = mWebGL->mActiveProgramLinkInfo;
 
         ////
         // Check UBO sizes.
 
-        const auto& linkInfo = mWebGL->mActiveProgramLinkInfo;
-
         for (const auto& cur : linkInfo->uniformBlocks) {
             const auto& dataSize = cur->mDataSize;
             const auto& binding = cur->mBinding;
             if (!binding) {
                 mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is null.",
                                               funcName);
                 *out_error = true;
                 return;
@@ -520,30 +507,30 @@ WebGLContext::DrawArrays_check(const cha
 void
 WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei vertCount,
                                   GLsizei instanceCount, const char* const funcName)
 {
     AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
     if (IsContextLost())
         return;
 
-    bool error = false;
-    ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
-    if (error)
-        return;
-
     Maybe<uint32_t> lastVert;
     if (!DrawArrays_check(funcName, first, vertCount, instanceCount, &lastVert))
         return;
 
+    bool error = false;
     const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount,
                                         &error);
     if (error)
         return;
 
+    const ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
+    if (error)
+        return;
+
     const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount,
                                                    instanceCount, &error);
     if (error)
         return;
 
     {
         ScopedDrawCallWrapper wrapper(*this);
         if (vertCount && instanceCount) {
@@ -676,33 +663,33 @@ void
 WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei indexCount, GLenum type,
                                     WebGLintptr byteOffset, GLsizei instanceCount,
                                     const char* const funcName)
 {
     AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
     if (IsContextLost())
         return;
 
-    bool error = false;
-    ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
-    if (error)
-        return;
-
     Maybe<uint32_t> lastVert;
     if (!DrawElements_check(funcName, indexCount, type, byteOffset, instanceCount,
                             &lastVert))
     {
         return;
     }
 
+    bool error = false;
     const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount,
                                         &error);
     if (error)
         return;
 
+    const ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
+    if (error)
+        return;
+
     {
         ScopedDrawCallWrapper wrapper(*this);
         {
             UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
 
             if (gl->IsANGLE()) {
                 errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
             }
@@ -754,18 +741,18 @@ WebGLContext::Draw_cleanup(const char* f
         const auto& drawBuffers = mBoundDrawFramebuffer->ColorDrawBuffers();
         for (const auto& cur : drawBuffers) {
             if (!cur->IsDefined())
                 continue;
             cur->Size(&destWidth, &destHeight);
             break;
         }
     } else {
-        destWidth = mWidth;
-        destHeight = mHeight;
+        destWidth = mDefaultFB->mSize.width;
+        destHeight = mDefaultFB->mSize.height;
     }
 
     if (mViewportWidth > int32_t(destWidth) ||
         mViewportHeight > int32_t(destHeight))
     {
         if (!mAlreadyWarnedAboutViewportLargerThanDest) {
             GenerateWarning("%s: Drawing to a destination rect smaller than the viewport"
                             " rect. (This warning will only be given once)",
--- a/dom/canvas/WebGLContextFramebufferOperations.cpp
+++ b/dom/canvas/WebGLContextFramebufferOperations.cpp
@@ -25,20 +25,17 @@ WebGLContext::Clear(GLbitfield mask)
         return ErrorInvalidValue("%s: invalid mask bits", funcName);
 
     if (mask == 0) {
         GenerateWarning("Calling gl.clear(0) has no effect.");
     } else if (mRasterizerDiscardEnabled) {
         GenerateWarning("Calling gl.clear() with RASTERIZER_DISCARD enabled has no effects.");
     }
 
-    if (mBoundDrawFramebuffer) {
-        if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(funcName))
-            return;
-
+    if (mask & LOCAL_GL_COLOR_BUFFER_BIT && mBoundDrawFramebuffer) {
         if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
             for (const auto& cur : mBoundDrawFramebuffer->ColorDrawBuffers()) {
                 if (!cur->IsDefined())
                     continue;
 
                 switch (cur->Format()->format->componentType) {
                 case webgl::ComponentType::Float:
                 case webgl::ComponentType::NormInt:
@@ -50,18 +47,31 @@ WebGLContext::Clear(GLbitfield mask)
                                           " or fixed-point. (normalized (u)ints)",
                                           funcName);
                     return;
                 }
             }
         }
     }
 
-    ScopedDrawCallWrapper wrapper(*this);
-    gl->fClear(mask);
+    if (!BindCurFBForDraw(funcName))
+        return;
+
+    auto driverMask = mask;
+    if (!mBoundDrawFramebuffer) {
+        if (mNeedsFakeNoDepth) {
+            driverMask &= ~LOCAL_GL_DEPTH_BUFFER_BIT;
+        }
+        if (mNeedsFakeNoStencil) {
+            driverMask &= ~LOCAL_GL_STENCIL_BUFFER_BIT;
+        }
+    }
+
+    const ScopedDrawCallWrapper wrapper(*this);
+    gl->fClear(driverMask);
 }
 
 static GLfloat
 GLClampFloat(GLfloat val)
 {
     if (val < 0.0)
         return 0.0;
 
@@ -116,21 +126,20 @@ WebGLContext::ClearStencil(GLint v)
 }
 
 void
 WebGLContext::ColorMask(WebGLboolean r, WebGLboolean g, WebGLboolean b, WebGLboolean a)
 {
     if (IsContextLost())
         return;
 
-    mColorWriteMask[0] = r;
-    mColorWriteMask[1] = g;
-    mColorWriteMask[2] = b;
-    mColorWriteMask[3] = a;
-    gl->fColorMask(r, g, b, a);
+    mColorWriteMask = uint8_t(bool(r)) << 0 |
+                      uint8_t(bool(g)) << 1 |
+                      uint8_t(bool(b)) << 2 |
+                      uint8_t(bool(a)) << 3;
 }
 
 void
 WebGLContext::DepthMask(WebGLboolean b)
 {
     if (IsContextLost())
         return;
 
@@ -170,17 +179,17 @@ WebGLContext::DrawBuffers(const dom::Seq
     default:
         ErrorInvalidOperation("%s: For the default framebuffer, `buffers[0]` must be"
                               " BACK or NONE.",
                               funcName);
         return;
     }
 
     mDefaultFB_DrawBuffer0 = buffers[0];
-    gl->Screen()->SetDrawBuffer(buffers[0]);
+    // Don't actually set it.
 }
 
 void
 WebGLContext::StencilMask(GLuint mask)
 {
     if (IsContextLost())
         return;
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -1556,17 +1556,17 @@ WebGLContext::ReadPixelsImpl(GLint x, GL
     const uint32_t width(rawWidth);
     const uint32_t height(rawHeight);
 
     //////
 
     const webgl::FormatUsageInfo* srcFormat;
     uint32_t srcWidth;
     uint32_t srcHeight;
-    if (!ValidateCurFBForRead("readPixels", &srcFormat, &srcWidth, &srcHeight))
+    if (!BindCurFBForColorRead("readPixels", &srcFormat, &srcWidth, &srcHeight))
         return;
 
     //////
 
     const webgl::PackingInfo pi = {packFormat, packType};
     if (!ValidateReadPixelsFormatAndType(srcFormat, pi, gl, this))
         return;
 
@@ -1601,18 +1601,16 @@ WebGLContext::ReadPixelsImpl(GLint x, GL
     {
         ErrorOutOfMemory("readPixels: Bad subrect selection.");
         return;
     }
 
     ////////////////
     // Now that the errors are out of the way, on to actually reading!
 
-    OnBeforeReadCall();
-
     if (!rwWidth || !rwHeight) {
         // Disjoint rects, so we're done already.
         DummyReadFramebufferOperation("readPixels");
         return;
     }
 
     if (uint32_t(rwWidth) == width &&
         uint32_t(rwHeight) == height)
--- a/dom/canvas/WebGLContextState.cpp
+++ b/dom/canvas/WebGLContextState.cpp
@@ -3,69 +3,58 @@
  * 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 "WebGLContext.h"
 
 #include "GLContext.h"
 #include "GLScreenBuffer.h"
 #include "mozilla/dom/ToJSValue.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/Preferences.h"
 #include "nsString.h"
 #include "WebGLBuffer.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLProgram.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLShader.h"
 #include "WebGLTexture.h"
 #include "WebGLVertexArray.h"
 
 namespace mozilla {
 
 void
-WebGLContext::Disable(GLenum cap)
+WebGLContext::SetEnabled(const char* const funcName, const GLenum cap, const bool enabled)
 {
     if (IsContextLost())
         return;
 
-    if (!ValidateCapabilityEnum(cap, "disable"))
+    if (!ValidateCapabilityEnum(cap, funcName))
         return;
 
-    realGLboolean* trackingSlot = GetStateTrackingSlot(cap);
-
-    if (trackingSlot)
-    {
-        *trackingSlot = 0;
+    const auto& slot = GetStateTrackingSlot(cap);
+    if (slot) {
+        *slot = enabled;
     }
 
-    gl->fDisable(cap);
-}
-
-void
-WebGLContext::Enable(GLenum cap)
-{
-    if (IsContextLost())
-        return;
+    switch (cap) {
+    case LOCAL_GL_DEPTH_TEST:
+    case LOCAL_GL_STENCIL_TEST:
+        break; // Lazily applied, so don't tell GL yet or we will desync.
 
-    if (!ValidateCapabilityEnum(cap, "enable"))
-        return;
-
-    realGLboolean* trackingSlot = GetStateTrackingSlot(cap);
-
-    if (trackingSlot)
-    {
-        *trackingSlot = 1;
+    default:
+        // Non-lazy caps.
+        gl->SetEnabled(cap, enabled);
+        break;
     }
-
-    gl->fEnable(cap);
 }
 
 bool
-WebGLContext::GetStencilBits(GLint* const out_stencilBits)
+WebGLContext::GetStencilBits(GLint* const out_stencilBits) const
 {
     *out_stencilBits = 0;
     if (mBoundDrawFramebuffer) {
         if (mBoundDrawFramebuffer->StencilAttachment().IsDefined() &&
             mBoundDrawFramebuffer->DepthStencilAttachment().IsDefined())
         {
             // Error, we don't know which stencil buffer's bits to use
             ErrorInvalidFramebufferOperation("getParameter: framebuffer has two stencil buffers bound");
@@ -100,21 +89,17 @@ WebGLContext::GetChannelBits(const char*
             *out_val = 8;
             break;
 
         case LOCAL_GL_ALPHA_BITS:
             *out_val = (mOptions.alpha ? 8 : 0);
             break;
 
         case LOCAL_GL_DEPTH_BITS:
-            if (mOptions.depth) {
-                *out_val = gl->Screen()->DepthBits();
-            } else {
-                *out_val = 0;
-            }
+            *out_val = (mOptions.depth ? 24 : 0);
             break;
 
         case LOCAL_GL_STENCIL_BITS:
             *out_val = (mOptions.stencil ? 8 : 0);
             break;
 
         default:
             MOZ_CRASH("GFX: bad pname");
@@ -185,17 +170,17 @@ WebGLContext::GetParameter(JSContext* cx
             return JS::Int32Value(mGLMaxDrawBuffers);
 
         } else if (pname >= LOCAL_GL_DRAW_BUFFER0 &&
                    pname < GLenum(LOCAL_GL_DRAW_BUFFER0 + mGLMaxDrawBuffers))
         {
             GLint ret = LOCAL_GL_NONE;
             if (!mBoundDrawFramebuffer) {
                 if (pname == LOCAL_GL_DRAW_BUFFER0) {
-                    ret = gl->Screen()->GetDrawBufferMode();
+                    ret = mDefaultFB_DrawBuffer0;
                 }
             } else {
                 gl->fGetIntegerv(pname, &ret);
             }
             return JS::Int32Value(ret);
         }
     }
 
@@ -337,17 +322,17 @@ WebGLContext::GetParameter(JSContext* cx
 
         case LOCAL_GL_GENERATE_MIPMAP_HINT:
             return JS::NumberValue(mGenerateMipmapHint);
 
         case LOCAL_GL_IMPLEMENTATION_COLOR_READ_FORMAT:
         case LOCAL_GL_IMPLEMENTATION_COLOR_READ_TYPE: {
             const webgl::FormatUsageInfo* usage;
             uint32_t width, height;
-            if (!ValidateCurFBForRead(funcName, &usage, &width, &height))
+            if (!BindCurFBForColorRead(funcName, &usage, &width, &height))
                 return JS::NullValue();
 
             const auto implPI = ValidImplementationColorReadPI(usage);
 
             GLenum ret;
             if (pname == LOCAL_GL_IMPLEMENTATION_COLOR_READ_FORMAT) {
                 ret = implPI.format;
             } else {
@@ -367,22 +352,44 @@ WebGLContext::GetParameter(JSContext* cx
             const GLint stencilMask = (1 << stencilBits) - 1;
 
             GLint refValue = 0;
             gl->fGetIntegerv(pname, &refValue);
 
             return JS::Int32Value(refValue & stencilMask);
         }
 
+        case LOCAL_GL_SAMPLE_BUFFERS:
+        case LOCAL_GL_SAMPLES: {
+            const auto& fb = mBoundDrawFramebuffer;
+            auto samples = [&]() -> Maybe<uint32_t> {
+                if (!fb) {
+                    if (!EnsureDefaultFB(funcName))
+                        return Nothing();
+                    return Some(mDefaultFB->mSamples);
+                }
+
+                if (!fb->IsCheckFramebufferStatusComplete(funcName))
+                    return Some(0);
+
+                DoBindFB(fb, LOCAL_GL_FRAMEBUFFER);
+                return Some(gl->GetIntAs<uint32_t>(LOCAL_GL_SAMPLES));
+            }();
+            if (samples && pname == LOCAL_GL_SAMPLE_BUFFERS) {
+                samples = Some(uint32_t(bool(samples.value())));
+            }
+            if (!samples)
+                return JS::NullValue();
+            return JS::NumberValue(samples.value());
+        }
+
         case LOCAL_GL_STENCIL_CLEAR_VALUE:
         case LOCAL_GL_UNPACK_ALIGNMENT:
         case LOCAL_GL_PACK_ALIGNMENT:
-        case LOCAL_GL_SUBPIXEL_BITS:
-        case LOCAL_GL_SAMPLE_BUFFERS:
-        case LOCAL_GL_SAMPLES: {
+        case LOCAL_GL_SUBPIXEL_BITS: {
             GLint i = 0;
             gl->fGetIntegerv(pname, &i);
             return JS::Int32Value(i);
         }
 
         case LOCAL_GL_RED_BITS:
         case LOCAL_GL_GREEN_BITS:
         case LOCAL_GL_BLUE_BITS:
@@ -460,19 +467,22 @@ WebGLContext::GetParameter(JSContext* cx
         case LOCAL_GL_POLYGON_OFFSET_UNITS:
         case LOCAL_GL_SAMPLE_COVERAGE_VALUE: {
             GLfloat f = 0.f;
             gl->fGetFloatv(pname, &f);
             return JS::DoubleValue(f);
         }
 
         // bool
+        case LOCAL_GL_DEPTH_TEST:
+            return JS::BooleanValue(mDepthTestEnabled);
+        case LOCAL_GL_STENCIL_TEST:
+            return JS::BooleanValue(mStencilTestEnabled);
+
         case LOCAL_GL_BLEND:
-        case LOCAL_GL_DEPTH_TEST:
-        case LOCAL_GL_STENCIL_TEST:
         case LOCAL_GL_CULL_FACE:
         case LOCAL_GL_DITHER:
         case LOCAL_GL_POLYGON_OFFSET_FILL:
         case LOCAL_GL_SCISSOR_TEST:
         case LOCAL_GL_SAMPLE_COVERAGE_INVERT:
         case LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE:
         case LOCAL_GL_SAMPLE_COVERAGE:
         case LOCAL_GL_DEPTH_WRITEMASK: {
@@ -551,20 +561,20 @@ WebGLContext::GetParameter(JSContext* cx
             if (!obj) {
                 rv = NS_ERROR_OUT_OF_MEMORY;
             }
             return JS::ObjectOrNullValue(obj);
         }
 
         // 4 bools
         case LOCAL_GL_COLOR_WRITEMASK: {
-            realGLboolean gl_bv[4] = { 0 };
-            gl->fGetBooleanv(pname, gl_bv);
-            bool vals[4] = { bool(gl_bv[0]), bool(gl_bv[1]),
-                             bool(gl_bv[2]), bool(gl_bv[3]) };
+            const bool vals[4] = { bool(mColorWriteMask & (1 << 0)),
+                                   bool(mColorWriteMask & (1 << 1)),
+                                   bool(mColorWriteMask & (1 << 2)),
+                                   bool(mColorWriteMask & (1 << 3)) };
             JS::Rooted<JS::Value> arr(cx);
             if (!dom::ToJSValue(cx, vals, &arr)) {
                 rv = NS_ERROR_OUT_OF_MEMORY;
             }
             return arr;
         }
 
         case LOCAL_GL_ARRAY_BUFFER_BINDING: {
@@ -637,16 +647,20 @@ bool
 WebGLContext::IsEnabled(GLenum cap)
 {
     if (IsContextLost())
         return false;
 
     if (!ValidateCapabilityEnum(cap, "isEnabled"))
         return false;
 
+    const auto& slot = GetStateTrackingSlot(cap);
+    if (slot)
+        return *slot;
+
     return gl->fIsEnabled(cap);
 }
 
 bool
 WebGLContext::ValidateCapabilityEnum(GLenum cap, const char* info)
 {
     switch (cap) {
         case LOCAL_GL_BLEND:
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLContextUtils.h"
 #include "WebGLContext.h"
 
 #include "GLContext.h"
 #include "jsapi.h"
 #include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/gfx/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Sprintf.h"
 #include "nsIDOMEvent.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIVariant.h"
 #include "nsPrintfCString.h"
 #include "nsServiceManagerUtils.h"
 #include <stdarg.h>
@@ -51,28 +52,28 @@ StringValue(JSContext* cx, const char* c
         rv.Throw(NS_ERROR_OUT_OF_MEMORY);
         return JS::NullValue();
     }
 
     return JS::StringValue(str);
 }
 
 void
-WebGLContext::GenerateWarning(const char* fmt, ...)
+WebGLContext::GenerateWarning(const char* fmt, ...) const
 {
     va_list ap;
     va_start(ap, fmt);
 
     GenerateWarning(fmt, ap);
 
     va_end(ap);
 }
 
 void
-WebGLContext::GenerateWarning(const char* fmt, va_list ap)
+WebGLContext::GenerateWarning(const char* fmt, va_list ap) const
 {
     if (!ShouldGenerateWarnings())
         return;
 
     mAlreadyGeneratedWarnings++;
 
     char buf[1024];
     VsprintfLiteral(buf, fmt, ap);
@@ -141,131 +142,131 @@ WebGLContext::GeneratePerfWarning(const 
         JS_ReportWarningASCII(cx,
                               "WebGL: After reporting %u, no further perf warnings will"
                               " be reported for this WebGL context.",
                               uint32_t(mNumPerfWarnings));
     }
 }
 
 void
-WebGLContext::SynthesizeGLError(GLenum err)
+WebGLContext::SynthesizeGLError(GLenum err) const
 {
     /* ES2 section 2.5 "GL Errors" states that implementations can have
      * multiple 'flags', as errors might be caught in different parts of
      * a distributed implementation.
      * We're signing up as a distributed implementation here, with
      * separate flags for WebGL and the underlying GLContext.
      */
     if (!mWebGLError)
         mWebGLError = err;
 }
 
 void
-WebGLContext::SynthesizeGLError(GLenum err, const char* fmt, ...)
+WebGLContext::SynthesizeGLError(GLenum err, const char* fmt, ...) const
 {
     va_list va;
     va_start(va, fmt);
     GenerateWarning(fmt, va);
     va_end(va);
 
     return SynthesizeGLError(err);
 }
 
 void
-WebGLContext::ErrorInvalidEnum(const char* fmt, ...)
+WebGLContext::ErrorInvalidEnum(const char* fmt, ...) const
 {
     va_list va;
     va_start(va, fmt);
     GenerateWarning(fmt, va);
     va_end(va);
 
     return SynthesizeGLError(LOCAL_GL_INVALID_ENUM);
 }
 
 void
-WebGLContext::ErrorInvalidEnumInfo(const char* info, GLenum enumValue)
+WebGLContext::ErrorInvalidEnumInfo(const char* info, GLenum enumValue) const
 {
     nsCString name;
     EnumName(enumValue, &name);
 
     return ErrorInvalidEnum("%s: invalid enum value %s", info, name.BeginReading());
 }
 
 void
 WebGLContext::ErrorInvalidEnumInfo(const char* info, const char* funcName,
-                                   GLenum enumValue)
+                                   GLenum enumValue) const
 {
     nsCString name;
     EnumName(enumValue, &name);
 
     ErrorInvalidEnum("%s: %s: Invalid enum: 0x%04x (%s).", funcName, info,
                      enumValue, name.BeginReading());
 }
 
 void
-WebGLContext::ErrorInvalidOperation(const char* fmt, ...)
+WebGLContext::ErrorInvalidOperation(const char* fmt, ...) const
 {
     va_list va;
     va_start(va, fmt);
     GenerateWarning(fmt, va);
     va_end(va);
 
     return SynthesizeGLError(LOCAL_GL_INVALID_OPERATION);
 }
 
 void
-WebGLContext::ErrorInvalidValue(const char* fmt, ...)
+WebGLContext::ErrorInvalidValue(const char* fmt, ...) const
 {
     va_list va;
     va_start(va, fmt);
     GenerateWarning(fmt, va);
     va_end(va);
 
     return SynthesizeGLError(LOCAL_GL_INVALID_VALUE);
 }
 
 void
-WebGLContext::ErrorInvalidFramebufferOperation(const char* fmt, ...)
+WebGLContext::ErrorInvalidFramebufferOperation(const char* fmt, ...) const
 {
     va_list va;
     va_start(va, fmt);
     GenerateWarning(fmt, va);
     va_end(va);
 
     return SynthesizeGLError(LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION);
 }
 
 void
-WebGLContext::ErrorOutOfMemory(const char* fmt, ...)
+WebGLContext::ErrorOutOfMemory(const char* fmt, ...) const
 {
     va_list va;
     va_start(va, fmt);
     GenerateWarning(fmt, va);
     va_end(va);
 
     return SynthesizeGLError(LOCAL_GL_OUT_OF_MEMORY);
 }
 
 void
-WebGLContext::ErrorImplementationBug(const char* fmt, ...)
+WebGLContext::ErrorImplementationBug(const char* fmt, ...) const
 {
     const nsPrintfCString warning("Implementation bug, please file at %s! %s",
                                   "https://bugzilla.mozilla.org/", fmt);
 
     va_list va;
     va_start(va, fmt);
     GenerateWarning(warning.BeginReading(), va);
     va_end(va);
 
     MOZ_ASSERT(false, "WebGLContext::ErrorImplementationBug");
     NS_ERROR("WebGLContext::ErrorImplementationBug");
     return SynthesizeGLError(LOCAL_GL_OUT_OF_MEMORY);
 }
 
-const char*
+/*static*/ const char*
 WebGLContext::ErrorName(GLenum error)
 {
     switch(error) {
     case LOCAL_GL_INVALID_ENUM:
         return "INVALID_ENUM";
     case LOCAL_GL_INVALID_OPERATION:
         return "INVALID_OPERATION";
     case LOCAL_GL_INVALID_VALUE:
@@ -620,17 +621,18 @@ WebGLContext::EnumName(GLenum val, nsCSt
         *out_name = name;
         return;
     }
 
     *out_name = nsPrintfCString("<enum 0x%04x>", val);
 }
 
 void
-WebGLContext::ErrorInvalidEnumArg(const char* funcName, const char* argName, GLenum val)
+WebGLContext::ErrorInvalidEnumArg(const char* funcName, const char* argName,
+                                  GLenum val) const
 {
     nsCString enumName;
     EnumName(val, &enumName);
     ErrorInvalidEnum("%s: Bad `%s`: %s", funcName, argName, enumName.BeginReading());
 }
 
 bool
 IsCompressedTextureFormat(GLenum format)
@@ -671,17 +673,17 @@ IsCompressedTextureFormat(GLenum format)
 
 bool
 IsTextureFormatCompressed(TexInternalFormat format)
 {
     return IsCompressedTextureFormat(format.get());
 }
 
 GLenum
-WebGLContext::GetAndFlushUnderlyingGLErrors()
+WebGLContext::GetAndFlushUnderlyingGLErrors() const
 {
     // Get and clear GL error in ALL cases.
     GLenum error = gl->fGetError();
 
     // Only store in mUnderlyingGLError if is hasn't already recorded an
     // error.
     if (!mUnderlyingGLError)
         mUnderlyingGLError = error;
@@ -734,41 +736,26 @@ AssertMaskedUintParamCorrect(gl::GLConte
 #else
 void
 AssertUintParamCorrect(gl::GLContext*, GLenum, GLuint)
 {
 }
 #endif
 
 void
-WebGLContext::AssertCachedBindings()
+WebGLContext::AssertCachedBindings() const
 {
 #ifdef DEBUG
     GetAndFlushUnderlyingGLErrors();
 
     if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object)) {
         GLuint bound = mBoundVertexArray ? mBoundVertexArray->GLName() : 0;
         AssertUintParamCorrect(gl, LOCAL_GL_VERTEX_ARRAY_BINDING, bound);
     }
 
-    // Framebuffers
-    if (IsWebGL2()) {
-        GLuint bound = mBoundDrawFramebuffer ? mBoundDrawFramebuffer->mGLName
-                                             : 0;
-        AssertUintParamCorrect(gl, LOCAL_GL_DRAW_FRAMEBUFFER_BINDING, bound);
-
-        bound = mBoundReadFramebuffer ? mBoundReadFramebuffer->mGLName : 0;
-        AssertUintParamCorrect(gl, LOCAL_GL_READ_FRAMEBUFFER_BINDING, bound);
-    } else {
-        MOZ_ASSERT(mBoundDrawFramebuffer == mBoundReadFramebuffer);
-        GLuint bound = mBoundDrawFramebuffer ? mBoundDrawFramebuffer->mGLName
-                                             : 0;
-        AssertUintParamCorrect(gl, LOCAL_GL_FRAMEBUFFER_BINDING, bound);
-    }
-
     GLint stencilBits = 0;
     if (GetStencilBits(&stencilBits)) { // Depends on current draw framebuffer.
         const GLuint stencilRefMask = (1 << stencilBits) - 1;
 
         AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_REF,      stencilRefMask, mStencilRefFront);
         AssertMaskedUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_REF, stencilRefMask, mStencilRefBack);
     }
 
@@ -799,44 +786,42 @@ WebGLContext::AssertCachedBindings()
 
     MOZ_ASSERT(!GetAndFlushUnderlyingGLErrors());
 #endif
 
     // We do not check the renderbuffer binding, because we never rely on it matching.
 }
 
 void
-WebGLContext::AssertCachedGlobalState()
+WebGLContext::AssertCachedGlobalState() const
 {
 #ifdef DEBUG
     GetAndFlushUnderlyingGLErrors();
 
     ////////////////
 
     // Draw state
-    MOZ_ASSERT(gl->fIsEnabled(LOCAL_GL_DEPTH_TEST) == mDepthTestEnabled);
     MOZ_ASSERT(gl->fIsEnabled(LOCAL_GL_DITHER) == mDitherEnabled);
     MOZ_ASSERT_IF(IsWebGL2(),
                   gl->fIsEnabled(LOCAL_GL_RASTERIZER_DISCARD) == mRasterizerDiscardEnabled);
     MOZ_ASSERT(gl->fIsEnabled(LOCAL_GL_SCISSOR_TEST) == mScissorTestEnabled);
-    MOZ_ASSERT(gl->fIsEnabled(LOCAL_GL_STENCIL_TEST) == mStencilTestEnabled);
-
-    realGLboolean colorWriteMask[4] = {0, 0, 0, 0};
-    gl->fGetBooleanv(LOCAL_GL_COLOR_WRITEMASK, colorWriteMask);
-    MOZ_ASSERT(colorWriteMask[0] == mColorWriteMask[0] &&
-               colorWriteMask[1] == mColorWriteMask[1] &&
-               colorWriteMask[2] == mColorWriteMask[2] &&
-               colorWriteMask[3] == mColorWriteMask[3]);
 
     GLfloat colorClearValue[4] = {0.0f, 0.0f, 0.0f, 0.0f};
     gl->fGetFloatv(LOCAL_GL_COLOR_CLEAR_VALUE, colorClearValue);
-    MOZ_ASSERT(IsCacheCorrect(mColorClearValue[0], colorClearValue[0]) &&
+    const bool ok = IsCacheCorrect(mColorClearValue[0], colorClearValue[0]) &&
                IsCacheCorrect(mColorClearValue[1], colorClearValue[1]) &&
                IsCacheCorrect(mColorClearValue[2], colorClearValue[2]) &&
-               IsCacheCorrect(mColorClearValue[3], colorClearValue[3]));
+               IsCacheCorrect(mColorClearValue[3], colorClearValue[3]);
+    if (!ok) {
+        gfxCriticalNote << mColorClearValue[0] << " - " << colorClearValue[0] << " = " << (mColorClearValue[0] - colorClearValue[0]) << "\n"
+                        << mColorClearValue[1] << " - " << colorClearValue[1] << " = " << (mColorClearValue[1] - colorClearValue[1]) << "\n"
+                        << mColorClearValue[2] << " - " << colorClearValue[2] << " = " << (mColorClearValue[2] - colorClearValue[2]) << "\n"
+                        << mColorClearValue[3] << " - " << colorClearValue[3] << " = " << (mColorClearValue[3] - colorClearValue[3]);
+    }
+    MOZ_ASSERT(ok);
 
     realGLboolean depthWriteMask = 0;
     gl->fGetBooleanv(LOCAL_GL_DEPTH_WRITEMASK, &depthWriteMask);
     MOZ_ASSERT(depthWriteMask == mDepthWriteMask);
 
     GLfloat depthClearValue = 0.0f;
     gl->fGetFloatv(LOCAL_GL_DEPTH_CLEAR_VALUE, &depthClearValue);
     MOZ_ASSERT(IsCacheCorrect(mDepthClearValue, depthClearValue));
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -415,25 +415,23 @@ WebGLContext::InitAndValidateGL(FailureR
 
     mDisableExtensions = gfxPrefs::WebGLDisableExtensions();
     mLoseContextOnMemoryPressure = gfxPrefs::WebGLLoseContextOnMemoryPressure();
     mCanLoseContextInForeground = gfxPrefs::WebGLCanLoseContextInForeground();
     mRestoreWhenVisible = gfxPrefs::WebGLRestoreWhenVisible();
 
     // These are the default values, see 6.2 State tables in the
     // OpenGL ES 2.0.25 spec.
-    mColorWriteMask[0] = 1;
-    mColorWriteMask[1] = 1;
-    mColorWriteMask[2] = 1;
-    mColorWriteMask[3] = 1;
-    mDepthWriteMask = 1;
+    mColorWriteMask = 0x0f;
+    mDriverColorMask = mColorWriteMask;
     mColorClearValue[0] = 0.f;
     mColorClearValue[1] = 0.f;
     mColorClearValue[2] = 0.f;
     mColorClearValue[3] = 0.f;
+    mDepthWriteMask = true;
     mDepthClearValue = 1.f;
     mStencilClearValue = 0;
     mStencilRefFront = 0;
     mStencilRefBack = 0;
 
     mLineWidth = 1.0;
 
     /*
@@ -456,23 +454,28 @@ WebGLContext::InitAndValidateGL(FailureR
     AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_VALUE_MASK,      mStencilValueMaskFront);
     AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_VALUE_MASK, mStencilValueMaskBack);
     AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_WRITEMASK,       mStencilWriteMaskFront);
     AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_WRITEMASK,  mStencilWriteMaskBack);
 
     mDitherEnabled = true;
     mRasterizerDiscardEnabled = false;
     mScissorTestEnabled = false;
+
     mDepthTestEnabled = 0;
+    mDriverDepthTest = false;
     mStencilTestEnabled = 0;
+    mDriverStencilTest = false;
+
     mGenerateMipmapHint = LOCAL_GL_DONT_CARE;
 
     // Bindings, etc.
     mActiveTexture = 0;
     mDefaultFB_DrawBuffer0 = LOCAL_GL_BACK;
+    mDefaultFB_ReadBuffer = LOCAL_GL_BACK;
 
     mEmitContextLostErrorOnce = true;
     mWebGLError = LOCAL_GL_NO_ERROR;
     mUnderlyingGLError = LOCAL_GL_NO_ERROR;
 
     mBound2DTextures.Clear();
     mBoundCubeMapTextures.Clear();
     mBound3DTextures.Clear();
--- a/dom/canvas/WebGLExtensionColorBufferFloat.cpp
+++ b/dom/canvas/WebGLExtensionColorBufferFloat.cpp
@@ -29,29 +29,54 @@ WebGLExtensionColorBufferFloat::WebGLExt
     };
 
 #define FOO(x) fnUpdateUsage(LOCAL_GL_ ## x, webgl::EffectiveFormat::x)
 
     // The extension doesn't actually add RGB32F; only RGBA32F.
     FOO(RGBA32F);
 
 #undef FOO
+
+#ifdef DEBUG
+    const auto gl = webgl->gl;
+    float was[4] = {};
+    gl->fGetFloatv(LOCAL_GL_COLOR_CLEAR_VALUE, was);
+
+    const float test[4] = {-1.0, 0, 2.0, 255.0};
+    gl->fClearColor(test[0], test[1], test[2], test[3]);
+
+    float now[4] = {};
+    gl->fGetFloatv(LOCAL_GL_COLOR_CLEAR_VALUE, now);
+    const bool ok = now[0] == test[0] && now[1] == test[1] &&
+                    now[2] == test[2] && now[3] == test[3];
+    if (!ok) {
+        printf_stderr("COLOR_CLEAR_VALUE: now{%f,%f,%f,%f} != test{%f,%f,%f,%f}\n",
+                      test[0], test[1], test[2], test[3],
+                      now[0], now[1], now[2], now[3]);
+        MOZ_ASSERT(false);
+    }
+    gl->fClearColor(was[0], was[1], was[2], was[3]);
+#endif
 }
 
 WebGLExtensionColorBufferFloat::~WebGLExtensionColorBufferFloat()
 {
 }
 
 bool
 WebGLExtensionColorBufferFloat::IsSupported(const WebGLContext* webgl)
 {
-    gl::GLContext* gl = webgl->GL();
+    const auto& gl = webgl->gl;
+    if (gl->IsANGLE()) {
+        // ANGLE supports this, but doesn't have a way to advertize its support,
+        // since it's compliant with WEBGL_color_buffer_float's clamping, but not
+        // EXT_color_buffer_float.
+        // TODO: This probably isn't necessary anymore.
+        return true;
+    }
 
-    // ANGLE supports this, but doesn't have a way to advertize its support,
-    // since it's compliant with WEBGL_color_buffer_float's clamping, but not
-    // EXT_color_buffer_float.
-    return gl->IsSupported(gl::GLFeature::renderbuffer_color_float) ||
-           gl->IsANGLE();
+    return gl->IsSupported(gl::GLFeature::renderbuffer_color_float) &&
+           gl->IsSupported(gl::GLFeature::frag_color_float);
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionColorBufferFloat, WEBGL_color_buffer_float)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLExtensionColorBufferHalfFloat.cpp
+++ b/dom/canvas/WebGLExtensionColorBufferHalfFloat.cpp
@@ -38,14 +38,16 @@ WebGLExtensionColorBufferHalfFloat::WebG
 
 WebGLExtensionColorBufferHalfFloat::~WebGLExtensionColorBufferHalfFloat()
 {
 }
 
 bool
 WebGLExtensionColorBufferHalfFloat::IsSupported(const WebGLContext* webgl)
 {
-    return webgl->GL()->IsSupported(gl::GLFeature::renderbuffer_color_half_float);
+    const auto& gl = webgl->gl;
+    return gl->IsSupported(gl::GLFeature::renderbuffer_color_half_float) &&
+           gl->IsSupported(gl::GLFeature::frag_color_float);
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionColorBufferHalfFloat, EXT_color_buffer_half_float)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -884,17 +884,17 @@ WebGLFramebuffer::PrecheckFramebufferSta
 
     return LOCAL_GL_FRAMEBUFFER_COMPLETE;
 }
 
 ////////////////////////////////////////
 // Validation
 
 bool
-WebGLFramebuffer::ValidateAndInitAttachments(const char* funcName)
+WebGLFramebuffer::ValidateAndInitAttachments(const char* funcName) const
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     const auto fbStatus = CheckFramebufferStatus(funcName);
     if (fbStatus == LOCAL_GL_FRAMEBUFFER_COMPLETE)
         return true;
 
@@ -937,23 +937,21 @@ WebGLFramebuffer::ValidateClearBufferTyp
                                         funcName, attachType, funcType);
         return false;
     }
 
     return true;
 }
 
 bool
-WebGLFramebuffer::ValidateForRead(const char* funcName,
-                                  const webgl::FormatUsageInfo** const out_format,
-                                  uint32_t* const out_width, uint32_t* const out_height)
+WebGLFramebuffer::ValidateForColorRead(const char* funcName,
+                                       const webgl::FormatUsageInfo** const out_format,
+                                       uint32_t* const out_width,
+                                       uint32_t* const out_height) const
 {
-    if (!ValidateAndInitAttachments(funcName))
-        return false;
-
     if (!mColorReadBuffer) {
         mContext->ErrorInvalidOperation("%s: READ_BUFFER must not be NONE.", funcName);
         return false;
     }
 
     if (!mColorReadBuffer->IsDefined()) {
         mContext->ErrorInvalidOperation("%s: The READ_BUFFER attachment is not defined.",
                                         funcName);
@@ -1180,17 +1178,17 @@ WebGLFramebuffer::RefreshResolvedData()
         mResolvedCompleteData.reset(new ResolvedData(*this));
     }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Entrypoints
 
 FBStatus
-WebGLFramebuffer::CheckFramebufferStatus(const char* funcName)
+WebGLFramebuffer::CheckFramebufferStatus(const char* const funcName) const
 {
     if (IsResolvedComplete())
         return LOCAL_GL_FRAMEBUFFER_COMPLETE;
 
     // Ok, let's try to resolve it!
 
     nsCString statusInfo;
     FBStatus ret = PrecheckFramebufferStatus(&statusInfo);
@@ -1646,25 +1644,26 @@ GetBackbufferFormats(const WebGLContext*
         if (options.stencil) {
             *out_stencil = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8);
         }
     }
 }
 
 /*static*/ void
 WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl,
-                                  const WebGLFramebuffer* srcFB, GLint srcX0, GLint srcY0,
-                                  GLint srcX1, GLint srcY1,
-                                  const WebGLFramebuffer* dstFB, GLint dstX0, GLint dstY0,
-                                  GLint dstX1, GLint dstY1,
+                                  GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
+                                  GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                                   GLbitfield mask, GLenum filter)
 {
     const char funcName[] = "blitFramebuffer";
     const auto& gl = webgl->gl;
 
+    const auto& srcFB = webgl->mBoundReadFramebuffer;
+    const auto& dstFB = webgl->mBoundDrawFramebuffer;
+
     ////
     // Collect data
 
     const auto fnGetDepthAndStencilAttach = [](const WebGLFramebuffer* fb,
                                                const WebGLFBAttachPoint** const out_depth,
                                                const WebGLFBAttachPoint** const out_stencil)
     {
         *out_depth = nullptr;
@@ -1923,18 +1922,17 @@ WebGLFramebuffer::BlitFramebuffer(WebGLC
         }
     } else if (!srcFB && !dstFB) {
         webgl->ErrorInvalidOperation("%s: Feedback with default framebuffer.", funcName);
         return;
     }
 
     ////
 
-    webgl->OnBeforeReadCall();
-    WebGLContext::ScopedDrawCallWrapper wrapper(*webgl);
+    const ScopedDrawCallWrapper wrapper(*webgl);
     gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1,
                          dstX0, dstY0, dstX1, dstY1,
                          mask, filter);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Goop.
 
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -190,17 +190,17 @@ protected:
         // IsFeedback
         std::vector<const WebGLFBAttachPoint*> texDrawBuffers; // Non-null
         std::set<WebGLFBAttachPoint::Ordered> drawSet;
         std::set<WebGLFBAttachPoint::Ordered> readSet;
 
         explicit ResolvedData(const WebGLFramebuffer& parent);
     };
 
-    UniquePtr<const ResolvedData> mResolvedCompleteData;
+    mutable UniquePtr<const ResolvedData> mResolvedCompleteData;
 
     ////
 
 public:
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLFramebuffer)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLFramebuffer)
 
     WebGLFramebuffer(WebGLContext* webgl, GLuint fbo);
@@ -231,23 +231,23 @@ protected:
     void ResolveAttachments() const;
     void RefreshDrawBuffers() const;
     void RefreshReadBuffer() const;
     bool ResolveAttachmentData(const char* funcName) const;
 
 public:
     void DetachTexture(const char* funcName, const WebGLTexture* tex);
     void DetachRenderbuffer(const char* funcName, const WebGLRenderbuffer* rb);
-    bool ValidateAndInitAttachments(const char* funcName);
+    bool ValidateAndInitAttachments(const char* funcName) const;
     bool ValidateClearBufferType(const char* funcName, GLenum buffer, uint32_t drawBuffer,
                                  GLenum funcType) const;
 
-    bool ValidateForRead(const char* info,
-                         const webgl::FormatUsageInfo** const out_format,
-                         uint32_t* const out_width, uint32_t* const out_height);
+    bool ValidateForColorRead(const char* funcName,
+                              const webgl::FormatUsageInfo** out_format,
+                              uint32_t* out_width, uint32_t* out_height) const;
 
     ////////////////
     // Getters
 
 #define GETTER(X) const decltype(m##X)& X() const { return m##X; }
 
     GETTER(DepthAttachment)
     GETTER(StencilAttachment)
@@ -263,33 +263,35 @@ public:
 
     bool IsResolvedComplete() const { return bool(mResolvedCompleteData); }
     void InvalidateFramebufferStatus(const char* funcName);
     void RefreshResolvedData();
 
     ////////////////
     // WebGL funcs
 
-    FBStatus CheckFramebufferStatus(const char* funcName);
+    bool IsCheckFramebufferStatusComplete(const char* const funcName) const {
+        return CheckFramebufferStatus(funcName) == LOCAL_GL_FRAMEBUFFER_COMPLETE;
+    }
+
+    FBStatus CheckFramebufferStatus(const char* funcName) const;
     void FramebufferRenderbuffer(const char* funcName, GLenum attachment, GLenum rbtarget,
                                  WebGLRenderbuffer* rb);
     void FramebufferTexture2D(const char* funcName, GLenum attachment,
                               GLenum texImageTarget, WebGLTexture* tex, GLint level);
     void FramebufferTextureLayer(const char* funcName, GLenum attachment,
                                  WebGLTexture* tex, GLint level, GLint layer);
     void DrawBuffers(const char* funcName, const dom::Sequence<GLenum>& buffers);
     void ReadBuffer(const char* funcName, GLenum attachPoint);
 
     JS::Value GetAttachmentParameter(const char* funcName, JSContext* cx, GLenum target,
                                      GLenum attachment, GLenum pname,
                                      ErrorResult* const out_error);
 
     static void BlitFramebuffer(WebGLContext* webgl,
-                                const WebGLFramebuffer* src, GLint srcX0, GLint srcY0,
-                                GLint srcX1, GLint srcY1,
-                                const WebGLFramebuffer* dst, GLint dstX0, GLint dstY0,
-                                GLint dstX1, GLint dstY1,
+                                GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
+                                GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                                 GLbitfield mask, GLenum filter);
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_FRAMEBUFFER_H_
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -2116,18 +2116,18 @@ WebGLTexture::CopyTexImage2D(TexImageTar
     MOZ_ASSERT(imageInfo);
 
     ////////////////////////////////////
     // Get source info
 
     const webgl::FormatUsageInfo* srcUsage;
     uint32_t srcTotalWidth;
     uint32_t srcTotalHeight;
-    if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcTotalWidth,
-                                        &srcTotalHeight))
+    if (!mContext->BindCurFBForColorRead(funcName, &srcUsage, &srcTotalWidth,
+                                         &srcTotalHeight))
     {
         return;
     }
 
     if (!ValidateCopyTexImageForFeedback(funcName, level))
         return;
 
     ////////////////////////////////////
@@ -2150,18 +2150,16 @@ WebGLTexture::CopyTexImage2D(TexImageTar
     }
 
     if (!ValidateCopyTexImageFormats(mContext, funcName, srcFormat, dstFormat))
         return;
 
     ////////////////////////////////////
     // Do the thing!
 
-    mContext->OnBeforeReadCall();
-
     const bool isSubImage = false;
     if (!DoCopyTexOrSubImage(mContext, funcName, isSubImage, this, target, level, x, y,
                              srcTotalWidth, srcTotalHeight, srcUsage, 0, 0, 0, width,
                              height, dstUsage))
     {
         return;
     }
 
@@ -2210,18 +2208,18 @@ WebGLTexture::CopyTexSubImage(const char
     }
 
     ////////////////////////////////////
     // Get source info
 
     const webgl::FormatUsageInfo* srcUsage;
     uint32_t srcTotalWidth;
     uint32_t srcTotalHeight;
-    if (!mContext->ValidateCurFBForRead(funcName, &srcUsage, &srcTotalWidth,
-                                        &srcTotalHeight))
+    if (!mContext->BindCurFBForColorRead(funcName, &srcUsage, &srcTotalWidth,
+                                         &srcTotalHeight))
     {
         return;
     }
 
     if (!ValidateCopyTexImageForFeedback(funcName, level, zOffset))
         return;
 
     ////////////////////////////////////
@@ -2229,18 +2227,16 @@ WebGLTexture::CopyTexSubImage(const char
 
     auto srcFormat = srcUsage->format;
     if (!ValidateCopyTexImageFormats(mContext, funcName, srcFormat, dstFormat))
         return;
 
     ////////////////////////////////////
     // Do the thing!
 
-    mContext->OnBeforeReadCall();
-
     bool uploadWillInitialize;
     if (!EnsureImageDataInitializedForUpload(this, funcName, target, level, xOffset,
                                              yOffset, zOffset, width, height, depth,
                                              imageInfo, &uploadWillInitialize))
     {
         return;
     }
 
--- a/dom/canvas/WebGLTransformFeedback.cpp
+++ b/dom/canvas/WebGLTransformFeedback.cpp
@@ -118,16 +118,28 @@ WebGLTransformFeedback::EndTransformFeed
     if (!mIsActive)
         return mContext->ErrorInvalidOperation("%s: Not active.", funcName);
 
     ////
 
     const auto& gl = mContext->gl;
     gl->fEndTransformFeedback();
 
+    if (gl->WorkAroundDriverBugs()) {
+#ifdef XP_MACOSX
+        // Multi-threaded GL on mac will generate INVALID_OP in some cases for at least
+        // BindBufferBase after an EndTransformFeedback if there is not a flush between
+        // the two.
+        // Single-threaded GL does not have this issue.
+        // This is likely due to not synchronizing client/server state, and erroring in
+        // BindBufferBase because the client thinks we're still in transform feedback.
+        gl->fFlush();
+#endif
+    }
+
     ////
 
     mIsActive = false;
     mIsPaused = false;
 
     ////
 
     mActive_Program->mNumActiveTFOs--;
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -196,16 +196,17 @@ LOCAL_INCLUDES += [
     '/js/xpconnect/wrappers',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '../workers',
+    '/', # Allow including relpaths from root.
     '/dom/base',
     '/dom/html',
     '/dom/svg',
     '/dom/workers',
     '/dom/xul',
     '/gfx/gl',
     '/image',
     '/js/xpconnect/src',
--- a/dom/canvas/nsICanvasRenderingContextInternal.h
+++ b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -88,18 +88,18 @@ public:
   }
 
   void SetOffscreenCanvas(mozilla::dom::OffscreenCanvas* aOffscreenCanvas)
   {
     mOffscreenCanvas = aOffscreenCanvas;
   }
 
   // Dimensions of the canvas, in pixels.
-  virtual int32_t GetWidth() const = 0;
-  virtual int32_t GetHeight() const = 0;
+  virtual int32_t GetWidth() = 0;
+  virtual int32_t GetHeight() = 0;
 
   // Sets the dimensions of the canvas, in pixels.  Called
   // whenever the size of the element changes.
   NS_IMETHOD SetDimensions(int32_t width, int32_t height) = 0;
 
   // Initializes with an nsIDocShell and DrawTarget. The size is taken from the
   // DrawTarget.
   NS_IMETHOD InitializeWithDrawTarget(nsIDocShell *aDocShell,
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -7591,17 +7591,17 @@ fail-if = (os == 'android')
 [generated/test_conformance__extensions__oes-texture-half-float-with-image-data.html]
 skip-if = (os == 'win' && os_version == '6.1')
 fail-if = (os == 'android')
 [generated/test_conformance__extensions__oes-texture-half-float-with-image.html]
 fail-if = (os == 'android')
 [generated/test_conformance__extensions__oes-texture-half-float-with-video.html]
 fail-if = (os == 'mac') || (os == 'android') || (os == 'linux') || (os == 'win')
 [generated/test_conformance__extensions__oes-texture-half-float.html]
-fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
+fail-if = (os == 'mac') || (os == 'win') || (os == 'android')
 [generated/test_conformance__extensions__oes-vertex-array-object-bufferData.html]
 [generated/test_conformance__extensions__oes-vertex-array-object.html]
 skip-if = (os == 'mac' && os_version == '10.6')
 fail-if = (os == 'mac') || (os == 'linux') || (os == 'win')
 [generated/test_conformance__extensions__webgl-compressed-texture-atc.html]
 [generated/test_conformance__extensions__webgl-compressed-texture-etc.html]
 [generated/test_conformance__extensions__webgl-compressed-texture-pvrtc.html]
 [generated/test_conformance__extensions__webgl-compressed-texture-s3tc-srgb.html]
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -127,17 +127,17 @@ fail-if = (os == 'mac') || (os == 'linux
 skip-if = (os == 'mac' && os_version == '10.6')
 [generated/test_conformance__textures__misc__texture-size.html]
 # application crashed [@ mozilla::gl::GLContext::AfterGLCall]
 skip-if = (os == 'android') || (os == 'win')
 
 [generated/test_2_conformance2__vertex_arrays__vertex-array-object.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_conformance__extensions__oes-texture-half-float.html]
-fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
+fail-if = (os == 'mac') || (os == 'win') || (os == 'android')
 [generated/test_conformance__attribs__gl-vertexattribpointer.html]
 fail-if = (os == 'android')
 [generated/test_conformance__ogles__GL__biuDepthRange__biuDepthRange_001_to_002.html]
 fail-if = (os == 'android')
 [generated/test_conformance__ogles__GL__gl_FragCoord__gl_FragCoord_001_to_003.html]
 fail-if = (os == 'android')
 
 [generated/test_conformance__textures__misc__texture-size-limit.html]
--- a/dom/canvas/test/webgl-mochitest/mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest/mochitest.ini
@@ -12,31 +12,31 @@ support-files =
   red-green.webmvp8.webm
   red-green.webmvp9.webm
 
 [ensure-exts/test_ANGLE_instanced_arrays.html]
 fail-if = (os == 'android') || (os == 'mac' && os_version == '10.6')
 [ensure-exts/test_EXT_blend_minmax.html]
 fail-if = (os == 'android')
 [ensure-exts/test_EXT_color_buffer_half_float.html]
-fail-if = (os == 'android')
+fail-if = (os == 'android') || (os == 'linux')
 [ensure-exts/test_EXT_disjoint_timer_query.html]
 fail-if = (os == 'android') || (os == 'win' && os_version == '5.1')
 [ensure-exts/test_EXT_frag_depth.html]
 fail-if = (os == 'android')
 [ensure-exts/test_EXT_sRGB.html]
 fail-if = (os == 'android') || (os == 'mac' && os_version == '10.6')
 [ensure-exts/test_EXT_shader_texture_lod.html]
 fail-if = (os == 'android')
 [ensure-exts/test_EXT_texture_filter_anisotropic.html]
 fail-if = (os == 'android') || (os == 'linux')
 [ensure-exts/test_OES_standard_derivatives.html]
 fail-if = (os == 'android')
 [ensure-exts/test_WEBGL_color_buffer_float.html]
-fail-if = (os == 'android')
+fail-if = (os == 'android') || (os == 'linux')
 [ensure-exts/test_WEBGL_compressed_texture_atc.html]
 fail-if = (os == 'android') || (os == 'linux') || (os == 'mac') || (os == 'win')
 [ensure-exts/test_WEBGL_compressed_texture_es3.html]
 fail-if = (os == 'android') || (os == 'mac') || (os == 'win')
 [ensure-exts/test_WEBGL_compressed_texture_etc1.html]
 fail-if = (os == 'linux') || (os == 'mac') || (os == 'win')
 [ensure-exts/test_WEBGL_compressed_texture_pvrtc.html]
 fail-if = (os == 'android') || (os == 'linux') || (os == 'mac') || (os == 'win')
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -1082,35 +1082,47 @@ FetchDriver::OnDataAvailable(nsIRequest*
           mMainThreadEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
         }
       }
     }
   }
 
-  uint32_t aRead;
+  // Needs to be initialized to 0 because in some cases nsStringInputStream may
+  // not write to aRead.
+  uint32_t aRead = 0;
   MOZ_ASSERT(mResponse);
   MOZ_ASSERT(mPipeOutputStream);
 
   // From "Main Fetch" step 19: SRI-part2.
   // Note: Avoid checking the hidden opaque body.
+  nsresult rv;
   if (mResponse->Type() != ResponseType::Opaque &&
       ShouldCheckSRI(mRequest, mResponse)) {
     MOZ_ASSERT(mSRIDataVerifier);
 
     SRIVerifierAndOutputHolder holder(mSRIDataVerifier, mPipeOutputStream);
-    nsresult rv = aInputStream->ReadSegments(CopySegmentToStreamAndSRI,
-                                             &holder, aCount, &aRead);
-    return rv;
+    rv = aInputStream->ReadSegments(CopySegmentToStreamAndSRI,
+                                    &holder, aCount, &aRead);
+  } else {
+    rv = aInputStream->ReadSegments(NS_CopySegmentToStream,
+                                    mPipeOutputStream,
+                                    aCount, &aRead);
   }
 
-  nsresult rv = aInputStream->ReadSegments(NS_CopySegmentToStream,
-                                           mPipeOutputStream,
-                                           aCount, &aRead);
+  // If no data was read, it's possible the output stream is closed but the
+  // ReadSegments call followed its contract of returning NS_OK despite write
+  // errors.  Unfortunately, nsIOutputStream has an ill-conceived contract when
+  // taken together with ReadSegments' contract, because the pipe will just
+  // NS_OK if we try and invoke its Write* functions ourselves with a 0 count.
+  // So we must just assume the pipe is broken.
+  if (aRead == 0 && aCount != 0) {
+    return NS_BASE_STREAM_CLOSED;
+  }
   return rv;
 }
 
 NS_IMETHODIMP
 FetchDriver::OnStopRequest(nsIRequest* aRequest,
                            nsISupports* aContext,
                            nsresult aStatusCode)
 {
--- a/dom/fetch/FetchStreamReader.cpp
+++ b/dom/fetch/FetchStreamReader.cpp
@@ -29,17 +29,20 @@ public:
     , mReader(aReader)
     , mWasNotified(false)
   {}
 
   bool Notify(Status aStatus) override
   {
     if (!mWasNotified) {
       mWasNotified = true;
-      mReader->CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
+      // The WorkerPrivate does have a context available, and we could pass it
+      // here to trigger cancellation of the reader, but the author of this
+      // comment chickened out.
+      mReader->CloseAndRelease(nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
     }
 
     return true;
   }
 
 private:
   RefPtr<FetchStreamReader> mReader;
   bool mWasNotified;
@@ -119,31 +122,53 @@ FetchStreamReader::FetchStreamReader(nsI
   , mBufferOffset(0)
   , mStreamClosed(false)
 {
   MOZ_ASSERT(aGlobal);
 }
 
 FetchStreamReader::~FetchStreamReader()
 {
-  CloseAndRelease(NS_BASE_STREAM_CLOSED);
+  CloseAndRelease(nullptr, NS_BASE_STREAM_CLOSED);
 }
 
+// If a context is provided, an attempt will be made to cancel the reader.  The
+// only situation where we don't expect to have a context is when closure is
+// being triggered from the destructor or the WorkerHolder is notifying.  If
+// we're at the destructor, it's far too late to cancel anything.  And if the
+// WorkerHolder is being notified, the global is going away, so there's also
+// no need to do further JS work.
 void
-FetchStreamReader::CloseAndRelease(nsresult aStatus)
+FetchStreamReader::CloseAndRelease(JSContext* aCx, nsresult aStatus)
 {
   NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
 
   if (mStreamClosed) {
     // Already closed.
     return;
   }
 
   RefPtr<FetchStreamReader> kungFuDeathGrip = this;
 
+  if (aCx) {
+    MOZ_ASSERT(mReader);
+
+    RefPtr<DOMException> error = DOMException::Create(aStatus);
+
+    JS::Rooted<JS::Value> errorValue(aCx);
+    if (ToJSValue(aCx, error, &errorValue)) {
+      JS::Rooted<JSObject*> reader(aCx, mReader);
+      // It's currently safe to cancel an already closed reader because, per the
+      // comments in ReadableStream::cancel() conveying the spec, step 2 of
+      // 3.4.3 that specified ReadableStreamCancel is: If stream.[[state]] is
+      // "closed", return a new promise resolved with undefined.
+      JS::ReadableStreamReaderCancel(aCx, reader, errorValue);
+    }
+  }
+
   mStreamClosed = true;
 
   mGlobal = nullptr;
 
   mPipeOut->CloseWithStatus(aStatus);
   mPipeOut = nullptr;
 
   mWorkerHolder = nullptr;
@@ -161,17 +186,17 @@ FetchStreamReader::StartConsuming(JSCont
   MOZ_DIAGNOSTIC_ASSERT(!mReader);
   MOZ_DIAGNOSTIC_ASSERT(aStream);
 
   JS::Rooted<JSObject*> reader(aCx,
                                JS::ReadableStreamGetReader(aCx, aStream,
                                                            JS::ReadableStreamReaderMode::Default));
   if (!reader) {
     aRv.StealExceptionFromJSContext(aCx);
-    CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
+    CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   mReader = reader;
   aReader.set(reader);
 
   aRv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
   if (NS_WARN_IF(aRv.Failed())) {
@@ -201,24 +226,24 @@ FetchStreamReader::OnOutputStreamReady(n
   AutoEntryScript aes(mGlobal, "ReadableStreamReader.read", !mWorkerHolder);
 
   JS::Rooted<JSObject*> reader(aes.cx(), mReader);
   JS::Rooted<JSObject*> promise(aes.cx(),
                                 JS::ReadableStreamDefaultReaderRead(aes.cx(),
                                                                     reader));
   if (NS_WARN_IF(!promise)) {
     // Let's close the stream.
-    CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
+    CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<Promise> domPromise = Promise::CreateFromExisting(mGlobal, promise);
   if (NS_WARN_IF(!domPromise)) {
     // Let's close the stream.
-    CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
+    CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
     return NS_ERROR_FAILURE;
   }
 
   // Let's wait.
   domPromise->AppendNativeHandler(this);
   return NS_OK;
 }
 
@@ -235,31 +260,31 @@ FetchStreamReader::ResolvedCallback(JSCo
 
   // We don't want to play with JS api, let's WebIDL bindings doing it for us.
   // FetchReadableStreamReadDataDone is a dictionary with just a boolean, if the
   // parsing succeeded, we can proceed with the parsing of the "value", which it
   // must be a Uint8Array.
   FetchReadableStreamReadDataDone valueDone;
   if (!valueDone.Init(aCx, aValue)) {
     JS_ClearPendingException(aCx);
-    CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
+    CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   if (valueDone.mDone) {
     // Stream is completed.
-    CloseAndRelease(NS_BASE_STREAM_CLOSED);
+    CloseAndRelease(aCx, NS_BASE_STREAM_CLOSED);
     return;
   }
 
   UniquePtr<FetchReadableStreamReadDataArray> value(
     new FetchReadableStreamReadDataArray);
   if (!value->Init(aCx, aValue) || !value->mValue.WasPassed()) {
     JS_ClearPendingException(aCx);
-    CloseAndRelease(NS_ERROR_DOM_INVALID_STATE_ERR);
+    CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   Uint8Array& array = value->mValue.Value();
   array.ComputeLengthAndData();
   uint32_t len = array.Length();
 
   if (len == 0) {
@@ -269,17 +294,22 @@ FetchStreamReader::ResolvedCallback(JSCo
   }
 
   MOZ_DIAGNOSTIC_ASSERT(!mBuffer);
   mBuffer = Move(value);
 
   mBufferOffset = 0;
   mBufferRemaining = len;
 
-  WriteBuffer();
+  nsresult rv = WriteBuffer();
+  if (NS_FAILED(rv)) {
+    // DOMException only understands errors from domerr.msg, so we normalize to
+    // identifying an abort if the write fails.
+    CloseAndRelease(aCx, NS_ERROR_DOM_ABORT_ERR);
+  }
 }
 
 nsresult
 FetchStreamReader::WriteBuffer()
 {
   MOZ_ASSERT(mBuffer);
   MOZ_ASSERT(mBuffer->mValue.WasPassed());
 
@@ -291,45 +321,43 @@ FetchStreamReader::WriteBuffer()
     nsresult rv =
       mPipeOut->Write(data + mBufferOffset, mBufferRemaining, &written);
 
     if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
       break;
     }
 
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      CloseAndRelease(rv);
       return rv;
     }
 
     MOZ_ASSERT(written <= mBufferRemaining);
     mBufferRemaining -= written;
     mBufferOffset += written;
 
     if (mBufferRemaining == 0) {
       mBuffer = nullptr;
       break;
     }
   }
 
   nsresult rv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    CloseAndRelease(rv);
     return rv;
   }
 
   return NS_OK;
 }
 
 void
 FetchStreamReader::RejectedCallback(JSContext* aCx,
                                     JS::Handle<JS::Value> aValue)
 {
   ReportErrorToConsole(aCx, aValue);
-  CloseAndRelease(NS_ERROR_FAILURE);
+  CloseAndRelease(aCx, NS_ERROR_FAILURE);
 }
 
 void
 FetchStreamReader::ReportErrorToConsole(JSContext* aCx,
                                         JS::Handle<JS::Value> aValue)
 {
   nsCString sourceSpec;
   uint32_t line = 0;
--- a/dom/fetch/FetchStreamReader.h
+++ b/dom/fetch/FetchStreamReader.h
@@ -35,18 +35,22 @@ public:
          nsIInputStream** aInputStream);
 
   void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void
   RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
+  // Idempotently close the output stream and null out all state. If aCx is
+  // provided, the reader will also be canceled.  aStatus must be a DOM error
+  // as understood by DOMException because it will be provided as the
+  // cancellation reason.
   void
-  CloseAndRelease(nsresult aStatus);
+  CloseAndRelease(JSContext* aCx, nsresult aStatus);
 
   void
   StartConsuming(JSContext* aCx,
                  JS::HandleObject aStream,
                  JS::MutableHandle<JSObject*> aReader,
                  ErrorResult& aRv);
 
 private:
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -1878,16 +1878,17 @@ ServiceWorkerPrivate::SpawnWorkerIfNeede
   // CSP values.  The principal on the registration may be polluted with CSP
   // from the registering page or other places the principal is passed.  If
   // bug 965637 is ever fixed this can be removed.
   info.mPrincipal =
     BasePrincipal::CreateCodebasePrincipal(uri, mInfo->GetOriginAttributes());
   if (NS_WARN_IF(!info.mPrincipal)) {
     return NS_ERROR_FAILURE;
   }
+  info.mLoadingPrincipal = info.mPrincipal;
 
   nsContentUtils::StorageAccess access =
     nsContentUtils::StorageAllowedForPrincipal(info.mPrincipal);
   info.mStorageAllowed = access > nsContentUtils::StorageAccess::ePrivateBrowsing;
   info.mOriginAttributes = mInfo->GetOriginAttributes();
 
   // Verify that we don't have any CSP on pristine principal.
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -5035,18 +5035,17 @@ WorkerPrivate::GetLoadInfo(JSContext* aC
 }
 
 // static
 void
 WorkerPrivate::OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo,
                                          nsIPrincipal* aPrincipal)
 {
   MOZ_ASSERT(!aLoadInfo.mInterfaceRequestor);
-  MOZ_ASSERT(aLoadInfo.mPrincipal == aPrincipal /* service workers */ ||
-             aLoadInfo.mLoadingPrincipal == aPrincipal /* any other worker type */);
+  MOZ_ASSERT(aLoadInfo.mLoadingPrincipal == aPrincipal);
 
   aLoadInfo.mInterfaceRequestor =
     new WorkerLoadInfo::InterfaceRequestor(aPrincipal, aLoadInfo.mLoadGroup);
   aLoadInfo.mInterfaceRequestor->MaybeAddTabChild(aLoadInfo.mLoadGroup);
 
   // NOTE: this defaults the load context to:
   //  - private browsing = false
   //  - content = true
--- a/dom/workers/test/serviceworkers/browser.ini
+++ b/dom/workers/test/serviceworkers/browser.ini
@@ -1,20 +1,25 @@
 [DEFAULT]
 support-files =
   browser_base_force_refresh.html
   browser_cached_force_refresh.html
   download/window.html
   download/worker.js
+  download_canceled/page_download_canceled.html
+  download_canceled/server-stream-download.sjs
+  download_canceled/sw_download_canceled.js
   fetch.js
   file_multie10s_update.html
   file_userContextId_openWindow.js
   force_refresh_browser_worker.js
   empty.html
   server_multie10s_update.sjs
+  utils.js
 
 [browser_devtools_serviceworker_interception.js]
 [browser_force_refresh.js]
 [browser_download.js]
+[browser_download_canceled.js]
 [browser_multie10s_update.js]
 skip-if = !e10s || os != "win" # Bug 1404914
 [browser_userContextId_openWindow.js]
 skip-if = !e10s
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/browser_download_canceled.js
@@ -0,0 +1,166 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test cancellation of a download in order to test edge-cases related to
+ * channel diversion.  Channel diversion occurs in cases of file (and PSM cert)
+ * downloads where we realize in the child that we really want to consume the
+ * channel data in the parent.  For data "sourced" by the parent, like network
+ * data, data streaming to the child is suspended and the parent waits for the
+ * child to send back the data it already received, then the channel is resumed.
+ * For data generated by the child, such as (the current, to be mooted by
+ * parent-intercept) child-side intercept, the data (currently) stream is
+ * continually pumped up to the parent.
+ *
+ * In particular, we want to reproduce the circumstances of Bug 1418795 where
+ * the child-side input-stream pump attempts to send data to the parent process
+ * but the parent has canceled the channel and so the IPC Actor has been torn
+ * down.  Diversion begins once the nsURILoader receives the OnStartRequest
+ * notification with the headers, so there are two ways to produce
+ */
+
+Cu.import('resource://gre/modules/Services.jsm');
+const { Downloads } = Cu.import("resource://gre/modules/Downloads.jsm", {});
+
+/**
+ * Clear the downloads list so other tests don't see our byproducts.
+ */
+async function clearDownloads() {
+  const downloads = await Downloads.getList(Downloads.ALL);
+  downloads.removeFinished();
+}
+
+/**
+ * Returns a Promise that will be resolved once the download dialog shows up and
+ * we have clicked the given button.
+ *
+ * Derived from browser/components/downloads/test/browser/head.js's
+ * self-contained promiseAlertDialogOpen helper, but modified to work on the
+ * download dialog instead of commonDialog.xul.
+ */
+function promiseClickDownloadDialogButton(buttonAction) {
+  return new Promise(resolve => {
+    Services.ww.registerNotification(function onOpen(win, topic, data) {
+      if (topic === "domwindowopened" && win instanceof Ci.nsIDOMWindow) {
+        // The test listens for the "load" event which guarantees that the alert
+        // class has already been added (it is added when "DOMContentLoaded" is
+        // fired).
+        win.addEventListener("load", function() {
+          info(`found window of type: ${win.document.documentURI}`);
+          if (win.document.documentURI ===
+                "chrome://mozapps/content/downloads/unknownContentType.xul") {
+            Services.ww.unregisterNotification(onOpen);
+
+            // nsHelperAppDlg.js currently uses an eval-based setTimeout(0) to
+            // invoke its postShowCallback that results in a misleading error to
+            // the console if we close the dialog before it gets a chance to
+            // run.  Just a setTimeout is not sufficient because it appears we
+            // get our "load" listener before the document's, so we use
+            // executeSoon to defer until after its load handler runs, then
+            // use setTimeout(0) to end up after its eval.
+            executeSoon(function() {
+              setTimeout(function() {
+                const button = win.document.documentElement.getButton(buttonAction);
+                button.disabled = false;
+                info(`clicking ${buttonAction} button`);
+                button.click();
+                resolve();
+              }, 0);
+            });
+          }
+        }, {once: true});
+      }
+    });
+  });
+}
+
+async function performCanceledDownload(tab, path) {
+  // Start waiting for the download dialog before triggering the download.
+  info("watching for download popup");
+  const cancelDownload = promiseClickDownloadDialogButton("cancel");
+
+  // Trigger the download.
+  info(`triggering download of "${path}"`);
+  await ContentTask.spawn(
+    tab.linkedBrowser,
+    path,
+    function(path) {
+      // Put a Promise in place that we can wait on for stream closure.
+      content.wrappedJSObject.trackStreamClosure(path);
+      // Create the link and trigger the download.
+      const link = content.document.createElement('a');
+      link.href = path;
+      link.download = path;
+      content.document.body.appendChild(link);
+      link.click();
+    });
+
+  // Wait for the cancelation to have been triggered.
+  info("waiting for download popup");
+  await cancelDownload;
+  ok(true, "canceled download");
+
+  // Wait for confirmation that the stream stopped.
+  info(`wait for the ${path} stream to close.`);
+  const why = await ContentTask.spawn(
+    tab.linkedBrowser,
+    path,
+    function(path) {
+      return content.wrappedJSObject.streamClosed[path].promise;
+    });
+  is(why.why, "canceled", "Ensure the stream canceled instead of timing out.");
+  // Note that for the "sw-stream-download" case, we end up with a bogus
+  // reason of "'close' may only be called on a stream in the 'readable' state."
+  // Since we aren't actually invoking close(), I'm assuming this is an
+  // implementation bug that will be corrected in the web platform tests.
+  info(`Cancellation reason: ${why.message} after ${why.ticks} ticks`);
+}
+
+const gTestRoot = getRootDirectory(gTestPath)
+  .replace("chrome://mochitests/content/", "http://mochi.test:8888/");
+
+
+const PAGE_URL = `${gTestRoot}download_canceled/page_download_canceled.html`;
+
+add_task(async function interruptedDownloads() {
+  await SpecialPowers.pushPrefEnv({'set': [
+    ['dom.serviceWorkers.enabled', true],
+    ['dom.serviceWorkers.exemptFromPerDomainMax', true],
+    ['dom.serviceWorkers.testing.enabled', true],
+    ["javascript.options.streams", true],
+    ["dom.streams.enabled", true],
+  ]});
+
+  // Open the tab
+  const tab = await BrowserTestUtils.openNewForegroundTab({
+    gBrowser,
+    opening: PAGE_URL
+  });
+
+  // Wait for it to become controlled.  Check that it was a promise that
+  // resolved as expected rather than undefined by checking the return value.
+  const controlled = await ContentTask.spawn(
+    tab.linkedBrowser,
+    null,
+    function() {
+      // This is a promise set up by the page during load, and we are post-load.
+      return content.wrappedJSObject.controlled;
+    });
+  is(controlled, "controlled", "page became controlled");
+
+  // Download a pass-through fetch stream.
+  await performCanceledDownload(tab, "sw-passthrough-download");
+
+  // Download a SW-generated stream
+  await performCanceledDownload(tab, "sw-stream-download");
+
+  // Cleanup
+  await ContentTask.spawn(
+    tab.linkedBrowser,
+    null,
+    function() {
+      return content.wrappedJSObject.registration.unregister();
+    });
+  await BrowserTestUtils.removeTab(tab);
+  await clearDownloads();
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/download_canceled/page_download_canceled.html
@@ -0,0 +1,58 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+</head>
+<body>
+
+<script src="../utils.js"></script>
+<script type="text/javascript">
+function wait_until_controlled() {
+  return new Promise(function(resolve) {
+    if (navigator.serviceWorker.controller) {
+      return resolve('controlled');
+    }
+    navigator.serviceWorker.addEventListener('controllerchange', function onController() {
+      if (navigator.serviceWorker.controller) {
+        navigator.serviceWorker.removeEventListener('controllerchange', onController);
+        return resolve('controlled');
+      }
+    });
+  });
+}
+addEventListener('load', async function(event) {
+  window.controlled = wait_until_controlled();
+  window.registration =
+    await navigator.serviceWorker.register('sw_download_canceled.js');
+  let sw = registration.installing || registration.waiting ||
+           registration.active;
+  await waitForState(sw, 'activated');
+  sw.postMessage('claim');
+});
+
+// Place to hold promises for stream closures reported by the SW.
+window.streamClosed = {};
+
+// The ServiceWorker will postMessage to this BroadcastChannel when the streams
+// are closed.  (Alternately, the SW could have used the clients API to post at
+// us, but the mechanism by which that operates would be different when this
+// test is uplifted, and it's desirable to avoid timing changes.)
+//
+// The browser test will use this promise to wait on stream shutdown.
+window.swStreamChannel = new BroadcastChannel("stream-closed");
+function trackStreamClosure(path) {
+  let resolve;
+  const promise = new Promise(r => { resolve = r });
+  window.streamClosed[path] = { promise, resolve };
+}
+window.swStreamChannel.onmessage = ({ data }) => {
+  window.streamClosed[data.what].resolve(data);
+};
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/download_canceled/server-stream-download.sjs
@@ -0,0 +1,123 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+// stolen from file_blocked_script.sjs
+function setGlobalState(data, key)
+{
+  x = { data: data, QueryInterface: function(iid) { return this } };
+  x.wrappedJSObject = x;
+  setObjectState(key, x);
+}
+
+function getGlobalState(key)
+{
+  var data;
+  getObjectState(key, function(x) {
+    data = x && x.wrappedJSObject.data;
+  });
+  return data;
+}
+
+/*
+ * We want to let the sw_download_canceled.js service worker know when the
+ * stream was canceled.  To this end, we let it issue a monitor request which we
+ * fulfill when the stream has been canceled.  In order to coordinate between
+ * multiple requests, we use the getObjectState/setObjectState mechanism that
+ * httpd.js exposes to let data be shared and/or persist between requests.  We
+ * handle both possible orderings of the requests because we currently don't
+ * try and impose an ordering between the two requests as issued by the SW, and
+ * file_blocked_script.sjs encourages us to do this, but we probably could order
+ * them.
+ */
+const MONITOR_KEY = "stream-monitor";
+function completeMonitorResponse(response, data) {
+  response.write(JSON.stringify(data));
+  response.finish();
+}
+function handleMonitorRequest(request, response) {
+  response.setHeader("Content-Type", "application/json");
+  response.setStatusLine(request.httpVersion, 200, "Found");
+
+  response.processAsync();
+  // Necessary to cause the headers to be flushed; that or touching the
+  // bodyOutputStream getter.
+  response.write("");
+  dump("server-stream-download.js: monitor headers issued\n");
+
+  const alreadyCompleted = getGlobalState(MONITOR_KEY);
+  if (alreadyCompleted) {
+    completeMonitorResponse(response, alreadyCompleted);
+    setGlobalState(null, MONITOR_KEY);
+  } else {
+    setGlobalState(response, MONITOR_KEY);
+  }
+}
+
+const MAX_TICK_COUNT = 3000;
+const TICK_INTERVAL = 2;
+function handleStreamRequest(request, response) {
+  const name = "server-stream-download";
+
+  // Create some payload to send.
+  let strChunk =
+    'Static routes are the future of ServiceWorkers! So say we all!\n';
+  while (strChunk.length < 1024) {
+    strChunk += strChunk;
+  }
+
+  response.setHeader("Content-Disposition", `attachment; filename="${name}"`);
+  response.setHeader("Content-Type", `application/octet-stream; name="${name}"`);
+  response.setHeader("Content-Length", `${strChunk.length * MAX_TICK_COUNT}`);
+  response.setStatusLine(request.httpVersion, 200, "Found");
+
+  response.processAsync();
+  response.write(strChunk);
+  dump("server-stream-download.js: stream headers + first payload issued\n");
+
+  let count = 0;
+  let intervalId;
+  function closeStream(why, message) {
+    dump("server-stream-download.js: closing stream: " + why + "\n");
+    clearInterval(intervalId);
+    response.finish();
+
+    const data = { why, message };
+    const monitorResponse = getGlobalState(MONITOR_KEY);
+    if (monitorResponse) {
+      completeMonitorResponse(monitorResponse, data);
+      setGlobalState(null, MONITOR_KEY);
+    } else {
+      setGlobalState(data, MONITOR_KEY);
+    }
+  }
+  function tick() {
+    try {
+      // bound worst-case behavior.
+      if (count++ > MAX_TICK_COUNT) {
+        closeStream("timeout", "timeout");
+        return;
+      }
+      response.write(strChunk);
+    } catch(e) {
+      closeStream("canceled", e.message);
+    }
+  }
+  intervalId = setInterval(tick, TICK_INTERVAL);
+}
+
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+function handleRequest(request, response) {
+  dump("server-stream-download.js: processing request for " + request.path +
+       "?" + request.queryString + "\n");
+  const query = new URLSearchParams(request.queryString);
+  if (query.has("monitor")) {
+    handleMonitorRequest(request, response);
+  } else {
+    handleStreamRequest(request, response);
+  }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/download_canceled/sw_download_canceled.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This file is derived from :bkelly's https://glitch.com/edit/#!/html-sw-stream
+
+addEventListener("install", evt => {
+  evt.waitUntil(self.skipWaiting());
+});
+
+// Create a BroadcastChannel to notify when we have closed our streams.
+const channel = new BroadcastChannel("stream-closed");
+
+const MAX_TICK_COUNT = 3000;
+const TICK_INTERVAL = 4;
+/**
+ * Generate a continuous stream of data at a sufficiently high frequency that a
+ * there"s a good chance of racing channel cancellation.
+ */
+function handleStream(evt, filename) {
+  // Create some payload to send.
+  const encoder = new TextEncoder();
+  let strChunk =
+    "Static routes are the future of ServiceWorkers! So say we all!\n";
+  while (strChunk.length < 1024) {
+    strChunk += strChunk;
+  }
+  const dataChunk = encoder.encode(strChunk);
+
+  evt.waitUntil(new Promise(resolve => {
+    let body = new ReadableStream({
+      start: controller => {
+        const closeStream = (why) => {
+          console.log("closing stream: " + JSON.stringify(why) + "\n");
+          clearInterval(intervalId);
+          resolve();
+          // In event of error, the controller will automatically have closed.
+          if (why.why != "canceled") {
+            try {
+              controller.close();
+            } catch(ex) {
+              // If we thought we should cancel but experienced a problem,
+              // that's a different kind of failure and we need to report it.
+              // (If we didn't catch the exception here, we'd end up erroneously
+              // in the tick() method's canceled handler.)
+              channel.postMessage({
+                what: filename,
+                why: "close-failure",
+                message: ex.message,
+                ticks: why.ticks
+              });
+              return;
+            }
+          }
+          // Post prior to performing any attempt to close...
+          channel.postMessage(why);
+        };
+
+        controller.enqueue(dataChunk);
+        let count = 0;
+        let intervalId;
+        function tick() {
+          try {
+            // bound worst-case behavior.
+            if (count++ > MAX_TICK_COUNT) {
+              closeStream({
+                what: filename, why: "timeout", message: "timeout", ticks: count
+              });
+              return;
+            }
+            controller.enqueue(dataChunk);
+          } catch(e) {
+            closeStream({
+              what: filename, why: "canceled", message: e.message, ticks: count
+            });
+          }
+        }
+        // Alternately, streams' pull mechanism could be used here, but this
+        // test doesn't so much want to saturate the stream as to make sure the
+        // data is at least flowing a little bit.  (Also, the author had some
+        // concern about slowing down the test by overwhelming the event loop
+        // and concern that we might not have sufficent back-pressure plumbed
+        // through and an infinite pipe might make bad things happen.)
+        intervalId = setInterval(tick, TICK_INTERVAL);
+        tick();
+      },
+    });
+    evt.respondWith(new Response(body, {
+      headers: {
+        "Content-Disposition": `attachment; filename="${filename}"`,
+        "Content-Type": "application/octet-stream"
+      }
+    }));
+  }));
+}
+
+/**
+ * Use an .sjs to generate a similar stream of data to the above, passing the
+ * response through directly.  Because we're handing off the response but also
+ * want to be able to report when cancellation occurs, we create a second,
+ * overlapping long-poll style fetch that will not finish resolving until the
+ * .sjs experiences closure of its socket and terminates the payload stream.
+ */
+function handlePassThrough(evt, filename) {
+  evt.waitUntil((async () => {
+    console.log("issuing monitor fetch request");
+    const response = await fetch("server-stream-download.sjs?monitor");
+    console.log("monitor headers received, awaiting body");
+    const data = await response.json();
+    console.log("passthrough monitor fetch completed, notifying.");
+    channel.postMessage({
+      what: filename,
+      why: data.why,
+      message: data.message
+    });
+  })());
+  evt.respondWith(fetch("server-stream-download.sjs").then(response => {
+    console.log("server-stream-download.sjs Response received, propagating");
+    return response;
+  }));
+}
+
+addEventListener("fetch", evt => {
+  console.log(`SW processing fetch of ${evt.request.url}`);
+  if (evt.request.url.indexOf("sw-stream-download") >= 0) {
+    return handleStream(evt, "sw-stream-download");
+  }
+  if (evt.request.url.indexOf("sw-passthrough-download") >= 0) {
+    return handlePassThrough(evt, "sw-passthrough-download");
+  }
+})
+
+addEventListener("message", evt => {
+  if (evt.data === "claim") {
+    evt.waitUntil(clients.claim());
+  }
+});
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -144,46 +144,16 @@ struct BroadcastListener {
 };
 
 struct BroadcasterMapEntry : public PLDHashEntryHdr
 {
     Element* mBroadcaster;  // [WEAK]
     nsTArray<BroadcastListener*> mListeners;  // [OWNING] of BroadcastListener objects
 };
 
-Element*
-nsRefMapEntry::GetFirstElement()
-{
-    return mRefContentList.SafeElementAt(0);
-}
-
-void
-nsRefMapEntry::AppendAll(nsCOMArray<Element>* aElements)
-{
-    for (size_t i = 0; i < mRefContentList.Length(); ++i) {
-        aElements->AppendObject(mRefContentList[i]);
-    }
-}
-
-bool
-nsRefMapEntry::AddElement(Element* aElement)
-{
-    if (mRefContentList.Contains(aElement)) {
-        return true;
-    }
-    return mRefContentList.AppendElement(aElement);
-}
-
-bool
-nsRefMapEntry::RemoveElement(Element* aElement)
-{
-    mRefContentList.RemoveElement(aElement);
-    return mRefContentList.IsEmpty();
-}
-
 //----------------------------------------------------------------------
 //
 // ctors & dtors
 //
 
 namespace mozilla {
 namespace dom {
 
@@ -606,21 +576,20 @@ ClearBroadcasterMapEntry(PLDHashTable* a
     // N.B. that we need to manually run the dtor because we
     // constructed the nsTArray object in-place.
     entry->mListeners.~nsTArray<BroadcastListener*>();
 }
 
 static bool
 CanBroadcast(int32_t aNameSpaceID, nsAtom* aAttribute)
 {
-    // Don't push changes to the |id|, |ref|, |persist|, |command| or
+    // Don't push changes to the |id|, |persist|, |command| or
     // |observes| attribute.
     if (aNameSpaceID == kNameSpaceID_None) {
         if ((aAttribute == nsGkAtoms::id) ||
-            (aAttribute == nsGkAtoms::ref) ||
             (aAttribute == nsGkAtoms::persist) ||
             (aAttribute == nsGkAtoms::command) ||
             (aAttribute == nsGkAtoms::observes)) {
             return false;
         }
     }
     return true;
 }
@@ -886,34 +855,16 @@ XULDocument::ExecuteOnBroadcastHandlerFo
             EventDispatcher::Dispatch(child, aPresContext, &event, nullptr,
                                       &status);
         }
     }
 
     return NS_OK;
 }
 
-void
-XULDocument::AttributeWillChange(nsIDocument* aDocument,
-                                 Element* aElement, int32_t aNameSpaceID,
-                                 nsAtom* aAttribute, int32_t aModType,
-                                 const nsAttrValue* aNewValue)
-{
-    MOZ_ASSERT(aElement, "Null content!");
-    NS_PRECONDITION(aAttribute, "Must have an attribute that's changing!");
-
-    // XXXbz check aNameSpaceID, dammit!
-    // See if we need to update our ref map.
-    if (aAttribute == nsGkAtoms::ref) {
-        // Might not need this, but be safe for now.
-        nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
-        RemoveElementFromRefMap(aElement);
-    }
-}
-
 static bool
 ShouldPersistAttribute(Element* aElement, nsAtom* aAttribute)
 {
     if (aElement->IsXULElement(nsGkAtoms::window)) {
         // This is not an element of the top document, its owner is
         // not an nsXULWindow. Persist it.
         if (aElement->OwnerDoc()->GetParentDocument()) {
             return true;
@@ -937,22 +888,16 @@ XULDocument::AttributeChanged(nsIDocumen
                               nsAtom* aAttribute, int32_t aModType,
                               const nsAttrValue* aOldValue)
 {
     NS_ASSERTION(aDocument == this, "unexpected doc");
 
     // Might not need this, but be safe for now.
     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
 
-    // XXXbz check aNameSpaceID, dammit!
-    // See if we need to update our ref map.
-    if (aAttribute == nsGkAtoms::ref) {
-        AddElementToRefMap(aElement);
-    }
-
     // Synchronize broadcast listeners
     if (mBroadcasterMap &&
         CanBroadcast(aNameSpaceID, aAttribute)) {
         auto entry = static_cast<BroadcasterMapEntry*>
                                 (mBroadcasterMap->Search(aElement));
 
         if (entry) {
             // We've got listeners: push the value.
@@ -1070,32 +1015,16 @@ XULDocument::ContentRemoved(nsIDocument*
     RemoveSubtreeFromDocument(aChild);
 }
 
 //----------------------------------------------------------------------
 //
 // nsIXULDocument interface
 //
 
-void
-XULDocument::GetElementsForID(const nsAString& aID,
-                              nsCOMArray<Element>& aElements)
-{
-    aElements.Clear();
-
-    nsIdentifierMapEntry *entry = mIdentifierMap.GetEntry(aID);
-    if (entry) {
-        entry->AppendAllIdContent(&aElements);
-    }
-    nsRefMapEntry *refEntry = mRefMap.GetEntry(aID);
-    if (refEntry) {
-        refEntry->AppendAll(&aElements);
-    }
-}
-
 nsresult
 XULDocument::AddForwardReference(nsForwardReference* aRef)
 {
     if (mResolutionPhase < aRef->GetPhase()) {
         if (!mForwardReferences.AppendElement(aRef)) {
             delete aRef;
             return NS_ERROR_OUT_OF_MEMORY;
         }
@@ -1572,45 +1501,31 @@ XULDocument::SetTooltipNode(nsIDOMNode* 
 NS_IMETHODIMP
 XULDocument::GetCommandDispatcher(nsIDOMXULCommandDispatcher** aTracker)
 {
     *aTracker = mCommandDispatcher;
     NS_IF_ADDREF(*aTracker);
     return NS_OK;
 }
 
-Element*
-XULDocument::GetRefById(const nsAString& aID)
-{
-    if (nsRefMapEntry* refEntry = mRefMap.GetEntry(aID)) {
-        MOZ_ASSERT(refEntry->GetFirstElement());
-        return refEntry->GetFirstElement();
-    }
-
-    return nullptr;
-}
-
 nsresult
 XULDocument::AddElementToDocumentPre(Element* aElement)
 {
     // Do a bunch of work that's necessary when an element gets added
     // to the XUL Document.
     nsresult rv;
 
-    // 1. Add the element to the resource-to-element map. Also add it to
-    // the id map, since it seems this can be called when creating
-    // elements from prototypes.
+    // 1. Add the element to the id map, since it seems this can be
+    // called when creating elements from prototypes.
     nsAtom* id = aElement->GetID();
     if (id) {
         // FIXME: Shouldn't BindToTree take care of this?
         nsAutoScriptBlocker scriptBlocker;
         AddToIdTable(aElement, id);
     }
-    rv = AddElementToRefMap(aElement);
-    if (NS_FAILED(rv)) return rv;
 
     // 2. If the element is a 'command updater' (i.e., has a
     // "commandupdater='true'" attribute), then add the element to the
     // document's command dispatcher
     if (aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::commandupdater,
                               nsGkAtoms::_true, eCaseMatters)) {
         rv = nsXULContentUtils::SetCommandUpdater(this, aElement);
         if (NS_FAILED(rv)) return rv;
@@ -1701,20 +1616,18 @@ XULDocument::RemoveSubtreeFromDocument(n
          child;
          child = child->GetPreviousSibling()) {
 
         rv = RemoveSubtreeFromDocument(child);
         if (NS_FAILED(rv))
             return rv;
     }
 
-    // 2. Remove the element from the resource-to-element map.
-    // Also remove it from the id map, since we added it in
+    // Remove the element from the id map, since we added it in
     // AddElementToDocumentPre().
-    RemoveElementFromRefMap(aElement);
     nsAtom* id = aElement->GetID();
     if (id) {
         // FIXME: Shouldn't UnbindFromTree take care of this?
         nsAutoScriptBlocker scriptBlocker;
         RemoveFromIdTable(aElement, id);
     }
 
     // 3. If the element is a 'command updater', then remove the
@@ -1738,56 +1651,16 @@ XULDocument::RemoveSubtreeFromDocument(n
                          broadcasterID, attribute, getter_AddRefs(broadcaster));
     if (rv == NS_FINDBROADCASTER_FOUND) {
         RemoveBroadcastListenerFor(*broadcaster, *listener, attribute);
     }
 
     return NS_OK;
 }
 
-static void
-GetRefMapAttribute(Element* aElement, nsAutoString* aValue)
-{
-    aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::ref, *aValue);
-}
-
-nsresult
-XULDocument::AddElementToRefMap(Element* aElement)
-{
-    // Look at the element's 'ref' attribute, and if set,
-    // add an entry in the resource-to-element map to the element.
-    nsAutoString value;
-    GetRefMapAttribute(aElement, &value);
-    if (!value.IsEmpty()) {
-        nsRefMapEntry *entry = mRefMap.PutEntry(value);
-        if (!entry)
-            return NS_ERROR_OUT_OF_MEMORY;
-        if (!entry->AddElement(aElement))
-            return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    return NS_OK;
-}
-
-void
-XULDocument::RemoveElementFromRefMap(Element* aElement)
-{
-    // Remove the element from the resource-to-element map.
-    nsAutoString value;
-    GetRefMapAttribute(aElement, &value);
-    if (!value.IsEmpty()) {
-        nsRefMapEntry *entry = mRefMap.GetEntry(value);
-        if (!entry)
-            return;
-        if (entry->RemoveElement(aElement)) {
-            mRefMap.RemoveEntry(entry);
-        }
-    }
-}
-
 //----------------------------------------------------------------------
 //
 // nsIDOMNode interface
 //
 
 nsresult
 XULDocument::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                    bool aPreallocateChildren) const
@@ -2002,19 +1875,29 @@ XULDocument::ApplyPersistentAttributesIn
 
         nsAutoString id;
         ids->GetNext(id);
 
         if (mRestrictPersistence && !mPersistenceIds.Contains(id)) {
             continue;
         }
 
-        // This will clear the array if there are no elements.
-        GetElementsForID(id, elements);
-        if (!elements.Count()) {
+        nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(id);
+        if (!entry) {
+            continue;
+        }
+
+        // We want to hold strong refs to the elements while applying
+        // persistent attributes, just in case.
+        elements.Clear();
+        elements.SetCapacity(entry->GetIdElements().Length());
+        for (Element* element : entry->GetIdElements()) {
+            elements.AppendObject(element);
+        }
+        if (elements.IsEmpty()) {
             continue;
         }
 
         rv = ApplyPersistentAttributesToElements(id, elements);
         if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
         }
     }
@@ -2233,19 +2116,16 @@ XULDocument::PrepareToWalk()
     if (mState == eState_Master) {
         // Add the root element
         rv = CreateElementFromPrototype(proto, getter_AddRefs(root), true);
         if (NS_FAILED(rv)) return rv;
 
         rv = AppendChildTo(root, false);
         if (NS_FAILED(rv)) return rv;
 
-        rv = AddElementToRefMap(root);
-        if (NS_FAILED(rv)) return rv;
-
         // Block onload until we've finished building the complete
         // document content model.
         BlockOnload();
     }
 
     // There'd better not be anything on the context stack at this
     // point! This is the basis case for our "induction" in
     // ResumeWalk(), below, which'll assume that there's always a
--- a/dom/xul/XULDocument.h
+++ b/dom/xul/XULDocument.h
@@ -41,49 +41,16 @@ class nsIObjectOutputStream;
 #else
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsXULElement.h"
 #endif
 #include "nsURIHashKey.h"
 #include "nsInterfaceHashtable.h"
 
-class nsRefMapEntry : public nsStringHashKey
-{
-public:
-  explicit nsRefMapEntry(const nsAString& aKey) :
-    nsStringHashKey(&aKey)
-  {
-  }
-  explicit nsRefMapEntry(const nsAString* aKey) :
-    nsStringHashKey(aKey)
-  {
-  }
-  nsRefMapEntry(const nsRefMapEntry& aOther) :
-    nsStringHashKey(&aOther.GetKey())
-  {
-    NS_ERROR("Should never be called");
-  }
-
-  mozilla::dom::Element* GetFirstElement();
-  void AppendAll(nsCOMArray<mozilla::dom::Element>* aElements);
-  /**
-   * @return true if aElement was added, false if we failed due to OOM
-   */
-  bool AddElement(mozilla::dom::Element* aElement);
-  /**
-   * @return true if aElement was removed and it was the last content for
-   * this ref, so this entry should be removed from the map
-   */
-  bool RemoveElement(mozilla::dom::Element* aElement);
-
-private:
-  nsTArray<mozilla::dom::Element*> mRefContentList;
-};
-
 /**
  * The XUL document class
  */
 
 namespace mozilla {
 namespace dom {
 
 class XULDocument final : public XMLDocument,
@@ -117,22 +84,18 @@ public:
 
     virtual void EndLoad() override;
 
     // nsIMutationObserver interface
     NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
     NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
     NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
     NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
-    NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
 
     // nsIXULDocument interface
-    virtual void GetElementsForID(const nsAString& aID,
-                                  nsCOMArray<mozilla::dom::Element>& aElements) override;
-
     NS_IMETHOD AddSubtreeToDocument(nsIContent* aContent) override;
     NS_IMETHOD RemoveSubtreeFromDocument(nsIContent* aContent) override;
     NS_IMETHOD OnPrototypeLoadDone(bool aResumeWalk) override;
     bool OnDocumentParserError() override;
 
     // nsINode interface overrides
     virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                            bool aPreallocateChildren) const override;
@@ -148,19 +111,16 @@ public:
     using mozilla::dom::DocumentOrShadowRoot::GetElementById;
     using nsDocument::GetImplementation;
     using nsDocument::GetTitle;
     using nsDocument::SetTitle;
     using nsDocument::GetLastStyleSheetSet;
     using nsDocument::MozSetImageElement;
     using nsIDocument::GetLocation;
 
-    // Helper for StyleScope::GetElementById.
-    Element* GetRefById(const nsAString & elementId);
-
     // nsIDOMXULDocument interface
     NS_DECL_NSIDOMXULDOCUMENT
 
     // nsICSSLoaderObserver
     NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet,
                                 bool aWasAlternate,
                                 nsresult aStatus) override;
 
@@ -228,21 +188,16 @@ protected:
 
     // Implementation methods
     friend nsresult
     (::NS_NewXULDocument(nsIXULDocument** aResult));
 
     nsresult Init(void) override;
     nsresult StartLayout(void);
 
-    nsresult
-    AddElementToRefMap(Element* aElement);
-    void
-    RemoveElementFromRefMap(Element* aElement);
-
     nsresult GetViewportSize(int32_t* aWidth, int32_t* aHeight);
 
     nsresult PrepareToLoad(nsISupports* aContainer,
                            const char* aCommand,
                            nsIChannel* aChannel,
                            nsILoadGroup* aLoadGroup,
                            nsIParser** aResult);
 
@@ -313,19 +268,16 @@ protected:
     // for owning pointers and raw COM interface pointers for weak
     // (ie, non owning) references. If you add any members to this
     // class, please make the ownership explicit (pinkerton, scc).
     // NOTE, THIS IS STILL IN PROGRESS, TALK TO PINK OR SCC BEFORE
     // CHANGING
 
     XULDocument*             mNextSrcLoadWaiter;  // [OWNER] but not COMPtr
 
-    // Tracks elements with a 'ref' attribute, or an 'id' attribute where
-    // the element's namespace has no registered ID attribute name.
-    nsTHashtable<nsRefMapEntry> mRefMap;
     nsCOMPtr<nsIXULStore>       mLocalStore;
     bool                        mApplyingPersistedAttrs;
     bool                        mIsWritingFastLoad;
     bool                        mDocumentLoaded;
     /**
      * Since ResumeWalk is interruptible, it's possible that last
      * stylesheet finishes loading while the PD walk is still in
      * progress (waiting for an overlay to finish loading).
--- a/dom/xul/nsIXULDocument.h
+++ b/dom/xul/nsIXULDocument.h
@@ -31,24 +31,16 @@ class Element;
  * method for constructing the children of a node, etc.
  */
 class nsIXULDocument : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXULDOCUMENT_IID)
 
   /**
-   * Get the elements for a particular resource --- all elements whose 'id'
-   * or 'ref' is aID. The nsCOMArray will be truncated and filled in with
-   * nsIContent pointers.
-   */
-  virtual void GetElementsForID(const nsAString& aID,
-                                nsCOMArray<mozilla::dom::Element>& aElements) = 0;
-
-  /**
    * Notify the XUL document that a subtree has been added
    */
   NS_IMETHOD AddSubtreeToDocument(nsIContent* aElement) = 0;
 
   /**
    * Notify the XUL document that a subtree has been removed
    */
   NS_IMETHOD RemoveSubtreeFromDocument(nsIContent* aElement) = 0;
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -618,24 +618,16 @@ public:
     void GetTop(DOMString& aValue) const
     {
         GetXULAttr(nsGkAtoms::top, aValue);
     }
     void SetTop(const nsAString& aValue, mozilla::ErrorResult& rv)
     {
         SetXULAttr(nsGkAtoms::top, aValue, rv);
     }
-    void GetRef(DOMString& aValue) const
-    {
-        GetXULAttr(nsGkAtoms::ref, aValue);
-    }
-    void SetRef(const nsAString& aValue, mozilla::ErrorResult& rv)
-    {
-        SetXULAttr(nsGkAtoms::ref, aValue, rv);
-    }
     void GetTooltipText(DOMString& aValue) const
     {
         GetXULAttr(nsGkAtoms::tooltiptext, aValue);
     }
     void SetTooltipText(const nsAString& aValue, mozilla::ErrorResult& rv)
     {
         SetXULAttr(nsGkAtoms::tooltiptext, aValue, rv);
     }
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -749,16 +749,40 @@ GLContext::InitWithPrefixImpl(const char
         // this has been fixed in Mac OS X 10.9. See 907946
         // and it also works in 10.8.3 and higher.  See 1094338.
         if (Vendor() == gl::GLVendor::NVIDIA &&
             !nsCocoaFeatures::IsAtLeastVersion(10,8,3))
         {
             MarkUnsupported(GLFeature::depth_texture);
         }
 #endif
+        if (IsSupported(GLFeature::frag_color_float)) {
+            float was[4] = {};
+            fGetFloatv(LOCAL_GL_COLOR_CLEAR_VALUE, was);
+
+            const float test[4] = {-1.0, 0, 2.0, 255.0};
+            fClearColor(test[0], test[1], test[2], test[3]);
+
+            float now[4] = {};
+            fGetFloatv(LOCAL_GL_COLOR_CLEAR_VALUE, now);
+
+            fClearColor(was[0], was[1], was[2], was[3]);
+
+            const bool unclamped = now[0] == test[0] && now[1] == test[1] &&
+                                   now[2] == test[2] && now[3] == test[3];
+            if (!unclamped) {
+                printf_stderr("COLOR_CLEAR_VALUE: now{%f,%f,%f,%f} != test{%f,%f,%f,%f}\n",
+                              test[0], test[1], test[2], test[3],
+                              now[0], now[1], now[2], now[3]);
+                gfxCriticalNote << "GLFeature::frag_color_float failed support probe,"
+                                << " disabling. (RENDERER: "
+                                << (const char*)fGetString(LOCAL_GL_RENDERER) << ")";
+                MarkUnsupported(GLFeature::frag_color_float);
+            }
+        }
     }
 
     if (IsExtensionSupported(GLContext::ARB_pixel_buffer_object)) {
         MOZ_ASSERT((mSymbols.fMapBuffer && mSymbols.fUnmapBuffer),
                    "ARB_pixel_buffer_object supported without glMapBuffer/UnmapBuffer"
                    " being available!");
     }
 
@@ -953,16 +977,18 @@ GLContext::InitWithPrefixImpl(const char
 #endif
 
     mMaxTextureImageSize = mMaxTextureSize;
 
     if (IsSupported(GLFeature::framebuffer_multisample)) {
         fGetIntegerv(LOCAL_GL_MAX_SAMPLES, (GLint*)&mMaxSamples);
     }
 
+    mMaxTexOrRbSize = std::min(mMaxTextureSize, mMaxRenderbufferSize);
+
     ////////////////////////////////////////////////////////////////////////////
 
     // We're ready for final setup.
     fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
 
     // TODO: Remove SurfaceCaps::any.
     if (mCaps.any) {
         mCaps.any = false;
@@ -2515,18 +2541,16 @@ SplitByChar(const nsACString& str, const
     out->push_back(nsCString(substr));
 }
 
 bool
 GLContext::Readback(SharedSurface* src, gfx::DataSourceSurface* dest)
 {
     MOZ_ASSERT(src && dest);
     MOZ_ASSERT(dest->GetSize() == src->mSize);
-    MOZ_ASSERT(dest->GetFormat() == (src->mHasAlpha ? SurfaceFormat::B8G8R8A8
-                                                    : SurfaceFormat::B8G8R8X8));
 
     if (!MakeCurrent()) {
         return false;
     }
 
     SharedSurface* prev = GetLockedSurface();
 
     const bool needsSwap = src != prev;
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -1538,17 +1538,16 @@ public:
 
     void fReadBuffer(GLenum mode) {
         BEFORE_GL_CALL;
         mSymbols.fReadBuffer(mode);
         AFTER_GL_CALL;
     }
 
     void raw_fReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels) {
-        ASSERT_NOT_PASSING_STACK_BUFFER_TO_GL(pixels);
         BEFORE_GL_CALL;
         mSymbols.fReadPixels(x, y, width, height, format, type, pixels);
         OnSyncCall();
         AFTER_GL_CALL;
         mHeavyGLCallsSinceLastFlush = true;
     }
 
     void fReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
@@ -3585,16 +3584,17 @@ private:
                             GLFeature feature);
 
 protected:
     void InitExtensions();
 
     GLint mViewportRect[4];
     GLint mScissorRect[4];
 
+    uint32_t mMaxTexOrRbSize = 0;
     GLint mMaxTextureSize;
     GLint mMaxCubeMapTextureSize;
     GLint mMaxTextureImageSize;
     GLint mMaxRenderbufferSize;
     GLint mMaxViewportDims[2];
     GLsizei mMaxSamples;
     bool mNeedsTextureSizeChecks;
     bool mNeedsFlushBeforeDeleteFB;
@@ -3616,21 +3616,21 @@ protected:
                                 target <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z)
                               ? mMaxCubeMapTextureSize
                               : mMaxTextureSize;
             return width <= maxSize && height <= maxSize;
         }
         return true;
     }
 
-
 public:
-    GLsizei MaxSamples() const {
-        return mMaxSamples;
-    }
+    auto MaxSamples() const { return uint32_t(mMaxSamples); }
+    auto MaxTextureSize() const { return uint32_t(mMaxTextureSize); }
+    auto MaxRenderbufferSize() const { return uint32_t(mMaxRenderbufferSize); }
+    auto MaxTexOrRbSize() const { return mMaxTexOrRbSize; }
 
 #ifdef MOZ_GL_DEBUG
     void CreatedProgram(GLContext* aOrigin, GLuint aName);
     void CreatedShader(GLContext* aOrigin, GLuint aName);
     void CreatedBuffers(GLContext* aOrigin, GLsizei aCount, GLuint* aNames);
     void CreatedQueries(GLContext* aOrigin, GLsizei aCount, GLuint* aNames);
     void CreatedTextures(GLContext* aOrigin, GLsizei aCount, GLuint* aNames);
     void CreatedFramebuffers(GLContext* aOrigin, GLsizei aCount, GLuint* aNames);
@@ -3688,24 +3688,52 @@ protected:
     bool mHeavyGLCallsSinceLastFlush;
 
 public:
     void FlushIfHeavyGLCallsSinceLastFlush();
     static bool ShouldSpew();
     static bool ShouldDumpExts();
     bool Readback(SharedSurface* src, gfx::DataSourceSurface* dest);
 
-    ////
+    // --
 
     void TexParams_SetClampNoMips(GLenum target = LOCAL_GL_TEXTURE_2D) {
         fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
         fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
         fTexParameteri(target, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST);
         fTexParameteri(target, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST);
     }
+
+    // --
+
+    GLuint CreateFramebuffer() {
+        GLuint x = 0;
+        fGenFramebuffers(1, &x);
+        return x;
+    }
+    GLuint CreateRenderbuffer() {
+        GLuint x = 0;
+        fGenRenderbuffers(1, &x);
+        return x;
+    }
+    GLuint CreateTexture() {
+        GLuint x = 0;
+        fGenTextures(1, &x);
+        return x;
+    }
+
+    void DeleteFramebuffer(const GLuint x) {
+        fDeleteFramebuffers(1, &x);
+    }
+    void DeleteRenderbuffer(const GLuint x) {
+        fDeleteRenderbuffers(1, &x);
+    }
+    void DeleteTexture(const GLuint x) {
+        fDeleteTextures(1, &x);
+    }
 };
 
 bool DoesStringMatch(const char* aString, const char* aWantedString);
 
 void SplitByChar(const nsACString& str, const char delim,
                  std::vector<nsCString>* const out);
 
 template<size_t N>
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -120,16 +120,17 @@ is_power_of_two(int v)
         return true;
 
     return (v & (v-1)) == 0;
 }
 
 static void
 DestroySurface(EGLSurface oldSurface) {
     if (oldSurface != EGL_NO_SURFACE) {
+        // TODO: This breaks TLS MakeCurrent caching.
         sEGLLibrary.fMakeCurrent(EGL_DISPLAY(),
                                  EGL_NO_SURFACE, EGL_NO_SURFACE,
                                  EGL_NO_CONTEXT);
         sEGLLibrary.fDestroySurface(EGL_DISPLAY(), oldSurface);
     }
 }
 
 static EGLSurface
@@ -222,17 +223,17 @@ GLContextEGLFactory::Create(EGLNativeWin
         sEGLLibrary.fSwapInterval(EGL_DISPLAY(), 0);
     }
     return gl.forget();
 }
 
 GLContextEGL::GLContextEGL(CreateContextFlags flags, const SurfaceCaps& caps,
                            bool isOffscreen, EGLConfig config, EGLSurface surface,
                            EGLContext context)
-    : GLContext(flags, caps, nullptr, isOffscreen, sEGLLibrary.IsANGLE())
+    : GLContext(flags, caps, nullptr, isOffscreen, false)
     , mConfig(config)
     , mSurface(surface)
     , mContext(context)
     , mSurfaceOverride(EGL_NO_SURFACE)
     , mThebesSurface(nullptr)
     , mBound(false)
     , mIsPBuffer(false)
     , mIsDoubleBuffered(false)
--- a/gfx/gl/GLReadTexImageHelper.cpp
+++ b/gfx/gl/GLReadTexImageHelper.cpp
@@ -627,21 +627,16 @@ GLReadTexImageHelper::ReadTexImage(DataS
         ScopedVertexAttribPointer autoAttrib1(mGL, 1, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, 0, texCoordArray);
 
         /* Bind the texture */
         if (aTextureId) {
             mGL->fBindTexture(aTextureTarget, aTextureId);
             CLEANUP_IF_GLERROR_OCCURRED("when binding texture");
         }
 
-        /* Draw quad */
-        mGL->fClearColor(1.0f, 0.0f, 1.0f, 1.0f);
-        mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
-        CLEANUP_IF_GLERROR_OCCURRED("when clearing color buffer");
-
         mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
         CLEANUP_IF_GLERROR_OCCURRED("when drawing texture");
 
         /* Read-back draw results */
         ReadPixelsIntoDataSurface(mGL, aDest);
         CLEANUP_IF_GLERROR_OCCURRED("when reading pixels into surface");
     } while (false);
 
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -812,17 +812,17 @@ DrawBuffer::Create(GLContext* const gl,
         // Nothing is needed.
         return true;
     }
 
     if (caps.antialias) {
         if (formats.samples == 0)
             return false; // Can't create it.
 
-        MOZ_ASSERT(formats.samples <= gl->MaxSamples());
+        MOZ_ASSERT(uint32_t(formats.samples) <= gl->MaxSamples());
     }
 
     GLuint colorMSRB = 0;
     GLuint depthRB   = 0;
     GLuint stencilRB = 0;
 
     GLuint* pColorMSRB = caps.antialias ? &colorMSRB : nullptr;
     GLuint* pDepthRB   = caps.depth     ? &depthRB   : nullptr;
new file mode 100644
--- /dev/null
+++ b/gfx/gl/MozFramebuffer.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */
+/* 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 "MozFramebuffer.h"
+
+#include "GLContext.h"
+#include "mozilla/gfx/Logging.h"
+#include "ScopedGLHelpers.h"
+
+namespace mozilla {
+namespace gl {
+
+static void
+DeleteByTarget(GLContext* const gl, const GLenum target, const GLuint name)
+{
+    if (target == LOCAL_GL_RENDERBUFFER) {
+        gl->DeleteRenderbuffer(name);
+    } else {
+        gl->DeleteTexture(name);
+    }
+}
+
+UniquePtr<MozFramebuffer>
+MozFramebuffer::Create(GLContext* const gl, const gfx::IntSize& size,
+                       const uint32_t samples, const bool depthStencil)
+{
+    if (samples && !gl->IsSupported(GLFeature::framebuffer_multisample))
+        return nullptr;
+
+    if (uint32_t(size.width) > gl->MaxTexOrRbSize() ||
+        uint32_t(size.height) > gl->MaxTexOrRbSize() ||
+        samples > gl->MaxSamples())
+    {
+        return nullptr;
+    }
+
+    gl->MakeCurrent();
+
+    GLContext::LocalErrorScope errorScope(*gl);
+
+    GLenum colorTarget;
+    GLuint colorName;
+    if (samples) {
+        colorTarget = LOCAL_GL_RENDERBUFFER;
+        colorName = gl->CreateRenderbuffer();
+        const ScopedBindRenderbuffer bindRB(gl, colorName);
+        gl->fRenderbufferStorageMultisample(colorTarget, samples, LOCAL_GL_RGBA8,
+                                            size.width, size.height);
+    } else {
+        colorTarget = LOCAL_GL_TEXTURE_2D;
+        colorName = gl->CreateTexture();
+        const ScopedBindTexture bindTex(gl, colorName);
+        gl->TexParams_SetClampNoMips();
+        gl->fTexImage2D(colorTarget, 0, LOCAL_GL_RGBA,
+                        size.width, size.height, 0,
+                        LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, nullptr);
+    }
+
+    const auto err = errorScope.GetError();
+    if (err) {
+        if (err != LOCAL_GL_OUT_OF_MEMORY) {
+            gfxCriticalNote << "Unexpected error: " << gfx::hexa(err) << ": "
+                            << GLContext::GLErrorToString(err);
+        }
+        DeleteByTarget(gl, colorTarget, colorName);
+        return nullptr;
+    }
+
+    return CreateWith(gl, size, samples, depthStencil, colorTarget, colorName);
+}
+
+UniquePtr<MozFramebuffer>
+MozFramebuffer::CreateWith(GLContext* const gl, const gfx::IntSize& size,
+                           const uint32_t samples, const bool depthStencil,
+                           const GLenum colorTarget, const GLuint colorName)
+{
+    UniquePtr<MozFramebuffer> mozFB(new MozFramebuffer(gl, size, samples, depthStencil,
+                                                       colorTarget, colorName));
+
+    const ScopedBindFramebuffer bindFB(gl, mozFB->mFB);
+
+    if (colorTarget == LOCAL_GL_RENDERBUFFER) {
+        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
+                                     colorTarget, colorName);
+    } else {
+        gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
+                                  colorTarget, colorName, 0);
+    }
+
+    const auto fnAllocRB = [&](GLuint rb, GLenum format) {
+        const ScopedBindRenderbuffer bindRB(gl, rb);
+        if (samples) {
+            gl->fRenderbufferStorageMultisample(LOCAL_GL_RENDERBUFFER, samples, format,
+                                                size.width, size.height);
+        } else {
+            gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, format, size.width,
+                                     size.height);
+        }
+        return rb;
+    };
+
+    if (depthStencil) {
+        GLuint depthRB, stencilRB;
+
+        {
+            GLContext::LocalErrorScope errorScope(*gl);
+
+            if (gl->IsSupported(GLFeature::packed_depth_stencil)) {
+                depthRB = fnAllocRB(mozFB->mDepthRB, LOCAL_GL_DEPTH24_STENCIL8);
+                stencilRB = depthRB; // Ignore unused mStencilRB.
+            } else {
+                depthRB   = fnAllocRB(mozFB->mDepthRB  , LOCAL_GL_DEPTH_COMPONENT24);
+                stencilRB = fnAllocRB(mozFB->mStencilRB, LOCAL_GL_STENCIL_INDEX8);
+            }
+
+            const auto err = errorScope.GetError();
+            if (err) {
+                MOZ_ASSERT(err == LOCAL_GL_OUT_OF_MEMORY);
+                return nullptr;
+            }
+        }
+
+        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
+                                     LOCAL_GL_RENDERBUFFER, depthRB);
+        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT,
+                                     LOCAL_GL_RENDERBUFFER, stencilRB);
+    }
+
+    const auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
+    if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
+        MOZ_ASSERT(false);
+        return nullptr;
+    }
+
+    return Move(mozFB);
+}
+
+////////////////////
+
+MozFramebuffer::MozFramebuffer(GLContext* const gl, const gfx::IntSize& size,
+                               const uint32_t samples, const bool depthStencil,
+                               const GLenum colorTarget, const GLuint colorName)
+    : mWeakGL(gl)
+    , mSize(size)
+    , mSamples(samples)
+    , mFB(gl->CreateFramebuffer())
+    , mColorTarget(colorTarget)
+    , mColorName(colorName)
+    , mDepthRB(depthStencil ? gl->CreateRenderbuffer() : 0)
+    , mStencilRB(depthStencil ? gl->CreateRenderbuffer() : 0)
+{
+    MOZ_ASSERT(mColorTarget);
+    MOZ_ASSERT(mColorName);
+}
+
+MozFramebuffer::~MozFramebuffer()
+{
+    GLContext* const gl = mWeakGL;
+    if (!gl || !gl->MakeCurrent())
+        return;
+
+    gl->DeleteFramebuffer(mFB);
+    gl->DeleteRenderbuffer(mDepthRB);
+    gl->DeleteRenderbuffer(mStencilRB);
+
+    DeleteByTarget(gl, mColorTarget, mColorName);
+}
+
+} // namespace gl
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/gl/MozFramebuffer.h
@@ -0,0 +1,54 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */
+/* 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_FRAMEBUFFER_H_
+#define MOZ_FRAMEBUFFER_H_
+
+#include "gfx2DGlue.h"
+#include "GLConsts.h"
+#include "GLContextTypes.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+
+namespace mozilla {
+namespace gl {
+
+class MozFramebuffer final
+{
+    const WeakPtr<GLContext> mWeakGL;
+public:
+    const gfx::IntSize mSize;
+    const uint32_t mSamples;
+    const GLuint mFB;
+    const GLenum mColorTarget;
+private:
+    const GLuint mColorName;
+    const GLuint mDepthRB;
+    const GLuint mStencilRB;
+
+public:
+    static UniquePtr<MozFramebuffer> Create(GLContext* gl, const gfx::IntSize& size,
+                                            uint32_t samples, bool depthStencil);
+
+    static UniquePtr<MozFramebuffer> CreateWith(GLContext* gl, const gfx::IntSize& size,
+                                                uint32_t samples, bool depthStencil,
+                                                GLenum colorTarget, GLuint colorName);
+private:
+    MozFramebuffer(GLContext* gl, const gfx::IntSize& size, uint32_t samples,
+                   bool depthStencil, GLenum colorTarget, GLuint colorName);
+public:
+    ~MozFramebuffer();
+
+    GLuint ColorTex() const {
+        if (mColorTarget == LOCAL_GL_RENDERBUFFER)
+            return 0;
+        return mColorName;
+    }
+};
+
+} // namespace gl
+} // namespace mozilla
+
+#endif  // MOZ_FRAMEBUFFER_H_
--- a/gfx/gl/moz.build
+++ b/gfx/gl/moz.build
@@ -131,16 +131,17 @@ UNIFIED_SOURCES += [
     'GLContextTypes.cpp',
     'GLDebugUtils.cpp',
     'GLLibraryEGL.cpp',
     'GLLibraryLoader.cpp',
     'GLReadTexImageHelper.cpp',
     'GLScreenBuffer.cpp',
     'GLTextureImage.cpp',
     'GLUploadHelpers.cpp',
+    'MozFramebuffer.cpp',
     'ScopedGLHelpers.cpp',
     'SharedSurface.cpp',
     'SharedSurfaceEGL.cpp',
     'SharedSurfaceGL.cpp',
     'SurfaceTypes.cpp',
     'TextureImageEGL.cpp',
 ]
 
--- a/gfx/layers/CopyableCanvasRenderer.cpp
+++ b/gfx/layers/CopyableCanvasRenderer.cpp
@@ -150,18 +150,18 @@ CopyableCanvasRenderer::ReadbackSurface(
   }
 
   if (!frontbuffer) {
     NS_WARNING("Null frame received.");
     return nullptr;
   }
 
   IntSize readSize(frontbuffer->mSize);
-  SurfaceFormat format =
-    mOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
+  SurfaceFormat format = frontbuffer->mHasAlpha ? SurfaceFormat::B8G8R8A8
+                                                : SurfaceFormat::B8G8R8X8;
   bool needsPremult = frontbuffer->mHasAlpha && !mIsAlphaPremultiplied;
 
   RefPtr<DataSourceSurface> resultSurf = GetTempSurface(readSize, format);
   // There will already be a warning from inside of GetTempSurface, but
   // it doesn't hurt to complain:
   if (NS_WARN_IF(!resultSurf)) {
     return nullptr;
   }
--- a/gfx/layers/ShareableCanvasRenderer.cpp
+++ b/gfx/layers/ShareableCanvasRenderer.cpp
@@ -46,17 +46,16 @@ ShareableCanvasRenderer::Initialize(cons
   if (mGLFrontbuffer) {
     // The screen caps are irrelevant if we're using a separate frontbuffer.
     caps = mGLFrontbuffer->mHasAlpha ? gl::SurfaceCaps::ForRGBA()
                                      : gl::SurfaceCaps::ForRGB();
   } else {
     MOZ_ASSERT(screen);
     caps = screen->mCaps;
   }
-  MOZ_ASSERT(caps.alpha == aData.mHasAlpha);
 
   auto forwarder = GetForwarder();
 
   mFlags = TextureFlags::ORIGIN_BOTTOM_LEFT;
   if (!aData.mIsGLAlphaPremult) {
     mFlags |= TextureFlags::NON_PREMULTIPLIED;
   }
 
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -750,16 +750,17 @@ private:
   DECL_GFX_PREF(Once, "webgl.force-layers-readback",           WebGLForceLayersReadback, bool, false);
   DECL_GFX_PREF(Live, "webgl.force-index-validation",          WebGLForceIndexValidation, int32_t, 0);
   DECL_GFX_PREF(Live, "webgl.lose-context-on-memory-pressure", WebGLLoseContextOnMemoryPressure, bool, false);
   DECL_GFX_PREF(Live, "webgl.max-contexts",                    WebGLMaxContexts, uint32_t, 32);
   DECL_GFX_PREF(Live, "webgl.max-contexts-per-principal",      WebGLMaxContextsPerPrincipal, uint32_t, 16);
   DECL_GFX_PREF(Live, "webgl.max-warnings-per-context",        WebGLMaxWarningsPerContext, uint32_t, 32);
   DECL_GFX_PREF(Live, "webgl.min_capability_mode",             WebGLMinCapabilityMode, bool, false);
   DECL_GFX_PREF(Live, "webgl.msaa-force",                      WebGLForceMSAA, bool, false);
+  DECL_GFX_PREF(Live, "webgl.msaa-samples",                    WebGLMsaaSamples, uint32_t, 4);
   DECL_GFX_PREF(Live, "webgl.prefer-16bpp",                    WebGLPrefer16bpp, bool, false);
   DECL_GFX_PREF(Live, "webgl.restore-context-when-visible",    WebGLRestoreWhenVisible, bool, true);
   DECL_GFX_PREF(Live, "webgl.allow-immediate-queries",         WebGLImmediateQueries, bool, false);
   DECL_GFX_PREF(Live, "webgl.allow-fb-invalidation",           WebGLFBInvalidation, bool, false);
 
   DECL_GFX_PREF(Live, "webgl.perf.max-warnings",                    WebGLMaxPerfWarnings, int32_t, 0);
   DECL_GFX_PREF(Live, "webgl.perf.max-acceptable-fb-status-invals", WebGLMaxAcceptableFBStatusInvals, int32_t, 0);
   DECL_GFX_PREF(Live, "webgl.perf.spew-frame-allocs",          WebGLSpewFrameAllocs, bool, true);
--- a/layout/xul/tree/nsTreeContentView.cpp
+++ b/layout/xul/tree/nsTreeContentView.cpp
@@ -1068,18 +1068,17 @@ nsTreeContentView::AttributeChanged(nsID
         int32_t index = FindContent(parent);
         if (index >= 0 && mBoxObject) {
           mBoxObject->InvalidateRow(index);
         }
       }
     }
   }
   else if (aElement->IsXULElement(nsGkAtoms::treecell)) {
-    if (aAttribute == nsGkAtoms::ref ||
-        aAttribute == nsGkAtoms::properties ||
+    if (aAttribute == nsGkAtoms::properties ||
         aAttribute == nsGkAtoms::mode ||
         aAttribute == nsGkAtoms::src ||
         aAttribute == nsGkAtoms::value ||
         aAttribute == nsGkAtoms::label) {
       nsIContent* parent = aElement->GetParent();
       if (parent) {
         nsCOMPtr<nsIContent> grandParent = parent->GetParent();
         if (grandParent && grandParent->IsXULElement()) {
@@ -1563,35 +1562,28 @@ nsTreeContentView::UpdateParentIndexes(i
       row->mParentIndex += aCount;
     }
   }
 }
 
 Element*
 nsTreeContentView::GetCell(nsIContent* aContainer, nsTreeColumn& aCol)
 {
-  RefPtr<nsAtom> colAtom(aCol.GetAtom());
   int32_t colIndex(aCol.GetIndex());
 
-  // Traverse through cells, try to find the cell by "ref" attribute or by cell
-  // index in a row. "ref" attribute has higher priority.
+  // Traverse through cells, try to find the cell by index in a row.
   Element* result = nullptr;
   int32_t j = 0;
   dom::FlattenedChildIterator iter(aContainer);
   for (nsIContent* cell = iter.GetNextChild(); cell; cell = iter.GetNextChild()) {
     if (cell->IsXULElement(nsGkAtoms::treecell)) {
-      if (colAtom &&
-          cell->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ref,
-                                         colAtom, eCaseMatters)) {
+      if (j == colIndex) {
         result = cell->AsElement();
         break;
       }
-      else if (j == colIndex) {
-        result = cell->AsElement();
-      }
       j++;
     }
   }
 
   return result;
 }
 
 bool
--- a/media/ffvpx/ffvpxcommon.mozbuild
+++ b/media/ffvpx/ffvpxcommon.mozbuild
@@ -108,23 +108,8 @@ elif CONFIG['CC_TYPE'] in ('msvc', 'clan
 DEFINES['HAVE_AV_CONFIG_H'] = True
 
 if CONFIG['MOZ_DEBUG']:
     # Enable all assertions in debug builds.
     DEFINES['ASSERT_LEVEL'] = 2
 elif not CONFIG['RELEASE_OR_BETA']:
     # Enable fast assertions in opt builds of Nightly and Aurora.
     DEFINES['ASSERT_LEVEL'] = 1
-
-# clang-cl's <intrin.h> doesn't work the same as MSVC's.  For details, see:
-#
-# http://lists.llvm.org/pipermail/cfe-dev/2016-September/050943.html
-#
-# As a temporary workaround while upstream decides how to address this,
-# we enable modules to make <intrin.h> more MSVC-compatible.
-if CONFIG['CC_TYPE'] == 'clang-cl':
-    CFLAGS += [
-        '-Xclang',
-        '-fmodules',
-        '-Xclang',
-        '-fmodules-cache-path=' + TOPOBJDIR + '/media/ffpvx',
-        '-fbuiltin-module-map',
-    ]
--- a/mobile/android/components/AddonUpdateService.js
+++ b/mobile/android/components/AddonUpdateService.js
@@ -18,23 +18,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager",
                                   "resource://gre/modules/GMPInstallManager.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher",
                                   "resource://gre/modules/Messaging.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 
-function getPref(func, preference, defaultValue) {
-  try {
-    return Services.prefs[func](preference);
-  } catch (e) {}
-  return defaultValue;
-}
-
 // -----------------------------------------------------------------------
 // Add-on auto-update management service
 // -----------------------------------------------------------------------
 
 const PREF_ADDON_UPDATE_ENABLED  = "extensions.autoupdate.enabled";
 const PREF_ADDON_UPDATE_INTERVAL = "extensions.autoupdate.interval";
 
 var gNeedsRestart = false;
@@ -43,29 +36,29 @@ function AddonUpdateService() {}
 
 AddonUpdateService.prototype = {
   classDescription: "Add-on auto-update management",
   classID: Components.ID("{93c8824c-9b87-45ae-bc90-5b82a1e4d877}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback]),
 
   notify: function aus_notify(aTimer) {
-    if (aTimer && !getPref("getBoolPref", PREF_ADDON_UPDATE_ENABLED, true))
+    if (aTimer && !Services.prefs.getBoolPref(PREF_ADDON_UPDATE_ENABLED, true))
       return;
 
     // If we already auto-upgraded and installed new versions, ignore this check
     if (gNeedsRestart)
       return;
 
     AddonManagerPrivate.backgroundUpdateCheck();
 
     let gmp = new GMPInstallManager();
     gmp.simpleCheckAndInstall().catch(() => {});
 
-    let interval = 1000 * getPref("getIntPref", PREF_ADDON_UPDATE_INTERVAL, 86400);
+    let interval = 1000 * Services.prefs.getIntPref(PREF_ADDON_UPDATE_INTERVAL, 86400);
     EventDispatcher.instance.sendRequest({
       type: "Gecko:ScheduleRun",
       action: "update-addons",
       trigger: interval,
       interval: interval,
     });
   }
 };
--- a/netwerk/base/ADivertableParentChannel.h
+++ b/netwerk/base/ADivertableParentChannel.h
@@ -32,14 +32,29 @@ public:
   virtual nsresult SuspendForDiversion() = 0;
 
   // While messages are diverted back from the child to the parent calls to
   // suspend/resume the channel must also suspend/resume the message diversion.
   // These two functions will be called by nsHttpChannel and nsFtpChannel
   // Suspend()/Resume() functions.
   virtual nsresult SuspendMessageDiversion() = 0;
   virtual nsresult ResumeMessageDiversion() = 0;
+
+  // Cancel an ongoing diversion by using IPC to invoke Cancel() in the child.
+  // This is necessary because most of the channel's state machine is suspended
+  // during diversion, so an explicit action must be taken to interrupt the
+  // diversion process so cancellation can be fully processed.
+  //
+  // Historically, diversions were assumed to be shortlived, where it was merely
+  // a question of diverting some small amount of network traffic back to the
+  // parent.  However, Service Worker child interception made it possible for
+  // the data to entirely be sourced from the child, which makes diversion
+  // potentially long-lived.  Especially when large files are involved.
+  //
+  // This mechanism is expected to be removed when ServiceWorkers move from
+  // child intercept to parent intercept (in the short to medium term).
+  virtual nsresult CancelDiversion() = 0;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif
--- a/netwerk/protocol/ftp/FTPChannelParent.cpp
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -732,16 +732,25 @@ FTPChannelParent::SuspendMessageDiversio
 nsresult
 FTPChannelParent::ResumeMessageDiversion()
 {
   // This only need to resumes message queue.
   mEventQ->Resume();
   return NS_OK;
 }
 
+nsresult
+FTPChannelParent::CancelDiversion()
+{
+  // Only HTTP channels can have child-process-sourced-data that's long-lived
+  // so this isn't currently relevant for FTP channels and there is nothing to
+  // do.
+  return NS_OK;
+}
+
 void
 FTPChannelParent::DivertTo(nsIStreamListener *aListener)
 {
   MOZ_ASSERT(aListener);
   if (NS_WARN_IF(!mDivertingFromChild)) {
     MOZ_ASSERT(mDivertingFromChild,
                "Cannot DivertTo new listener if diverting is not set!");
     return;
--- a/netwerk/protocol/ftp/FTPChannelParent.h
+++ b/netwerk/protocol/ftp/FTPChannelParent.h
@@ -49,16 +49,18 @@ public:
 
   bool Init(const FTPChannelCreationArgs& aOpenArgs);
 
   // ADivertableParentChannel functions.
   void DivertTo(nsIStreamListener *aListener) override;
   nsresult SuspendForDiversion() override;
   nsresult SuspendMessageDiversion() override;
   nsresult ResumeMessageDiversion() override;
+  nsresult CancelDiversion() override;
+
 
   // Calls OnStartRequest for "DivertTo" listener, then notifies child channel
   // that it should divert OnDataAvailable and OnStopRequest calls to this
   // parent channel.
   void StartDiversion();
 
   // Handles calling OnStart/Stop if there are errors during diversion.
   // Called asynchronously from FailDiversion.
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -610,18 +610,16 @@ HttpChannelChild::OnStartRequest(const n
   //
   // gHttpHandler->OnExamineResponse(this);
 
   mTracingEnabled = false;
 
   DoOnStartRequest(this, mListenerContext);
 }
 
-namespace {
-
 class SyntheticDiversionListener final : public nsIStreamListener
 {
   RefPtr<HttpChannelChild> mChannel;
 
   ~SyntheticDiversionListener()
   {
   }
 
@@ -638,43 +636,49 @@ public:
     MOZ_ASSERT_UNREACHABLE("SyntheticDiversionListener should never see OnStartRequest");
     return NS_OK;
   }
 
   NS_IMETHOD
   OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
                 nsresult aStatus) override
   {
-    mChannel->SendDivertOnStopRequest(aStatus);
+    if (mChannel->mIPCOpen) {
+      mChannel->SendDivertOnStopRequest(aStatus);
+      mChannel->SendDivertComplete();
+    }
     return NS_OK;
   }
 
   NS_IMETHOD
   OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
                   nsIInputStream* aInputStream, uint64_t aOffset,
                   uint32_t aCount) override
   {
+    if (!mChannel->mIPCOpen) {
+      aRequest->Cancel(NS_ERROR_ABORT);
+      return NS_ERROR_ABORT;
+    }
+
     nsAutoCString data;
     nsresult rv = NS_ConsumeStream(aInputStream, aCount, data);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       aRequest->Cancel(rv);
       return rv;
     }
 
     mChannel->SendDivertOnDataAvailable(data, aOffset, aCount);
     return NS_OK;
   }
 
   NS_DECL_ISUPPORTS
 };
 
 NS_IMPL_ISUPPORTS(SyntheticDiversionListener, nsIStreamListener);
 
-} // anonymous namespace
-
 void
 HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
   LOG(("HttpChannelChild::DoOnStartRequest [this=%p]\n", this));
 
   // In theory mListener should not be null, but in practice sometimes it is.
   MOZ_ASSERT(mListener);
   if (!mListener) {
@@ -1877,17 +1881,21 @@ HttpChannelChild::FlushedForDiversion()
   LOG(("HttpChannelChild::FlushedForDiversion [this=%p]\n", this));
   MOZ_RELEASE_ASSERT(mDivertingToParent);
 
   // Once this is set, it should not be unset before HttpChannelChild is taken
   // down. After it is set, no OnStart/OnData/OnStop callbacks should be
   // received from the parent channel, nor dequeued from the ChannelEventQueue.
   mFlushedForDiversion = true;
 
-  SendDivertComplete();
+  // If we're synthesized, it's up to the SyntheticDiversionListener to invoke
+  // SendDivertComplete after it has sent the DivertOnStopRequestMessage.
+  if (!mSynthesizedResponse) {
+    SendDivertComplete();
+  }
 }
 
 void
 HttpChannelChild::ProcessSetClassifierMatchedInfo(const nsCString& aList,
                                                   const nsCString& aProvider,
                                                   const nsCString& aFullHash)
 {
   LOG(("HttpChannelChild::ProcessSetClassifierMatchedInfo [this=%p]\n", this));
@@ -3862,16 +3870,31 @@ HttpChannelChild::RecvSetPriority(const 
 
 mozilla::ipc::IPCResult
 HttpChannelChild::RecvAttachStreamFilter(Endpoint<extensions::PStreamFilterParent>&& aEndpoint)
 {
   extensions::StreamFilterParent::Attach(this, Move(aEndpoint));
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+HttpChannelChild::RecvCancelDiversion()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // This method is a very special case for cancellation of a diverted
+  // intercepted channel.  Normally we would go through the mEventQ in order to
+  // serialize event execution in the face of sync XHR and now background
+  // channels.  However, similar to how CancelOnMainThread describes, Cancel()
+  // pre-empts everything.  (And frankly, we want this stack frame on the stack
+  // if a crash happens.)
+  Cancel(NS_ERROR_ABORT);
+  return IPC_OK();
+}
+
 void
 HttpChannelChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // OnStartRequest might be dropped if IPDL is destroyed abnormally
   // and BackgroundChild might have pending IPC messages.
   // Clean up BackgroundChild at this time to prevent memleak.
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -42,16 +42,17 @@ class nsInputStreamPump;
 class nsIInterceptedBodyCallback;
 
 namespace mozilla {
 namespace net {
 
 class HttpBackgroundChannelChild;
 class InterceptedChannelContent;
 class InterceptStreamListener;
+class SyntheticDiversionListener;
 
 class HttpChannelChild final : public PHttpChannelChild
                              , public HttpBaseChannel
                              , public HttpAsyncAborter<HttpChannelChild>
                              , public nsICacheInfoChannel
                              , public nsIProxiedChannel
                              , public nsIApplicationCacheChannel
                              , public nsIAsyncVerifyRedirectCallback
@@ -163,16 +164,18 @@ protected:
 
   mozilla::ipc::IPCResult RecvIssueDeprecationWarning(const uint32_t& warning,
                                                       const bool& asError) override;
 
   mozilla::ipc::IPCResult RecvSetPriority(const int16_t& aPriority) override;
 
   mozilla::ipc::IPCResult RecvAttachStreamFilter(Endpoint<extensions::PStreamFilterParent>&& aEndpoint) override;
 
+  mozilla::ipc::IPCResult RecvCancelDiversion() override;
+
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   MOZ_MUST_USE bool
   GetAssociatedContentSecurity(nsIAssociatedContentSecurity** res = nullptr);
   virtual void DoNotifyListenerCleanup() override;
 
   NS_IMETHOD GetResponseSynthesized(bool* aSynthesized) override;
 
@@ -467,16 +470,17 @@ private:
   friend class Redirect1Event;
   friend class Redirect3Event;
   friend class DeleteSelfEvent;
   friend class HttpFlushedForDiversionEvent;
   friend class CancelEvent;
   friend class HttpAsyncAborter<HttpChannelChild>;
   friend class InterceptStreamListener;
   friend class InterceptedChannelContent;
+  friend class SyntheticDiversionListener;
   friend class HttpBackgroundChannelChild;
   friend class NeckoTargetChannelEvent<HttpChannelChild>;
 };
 
 // A stream listener interposed between the nsInputStreamPump used for intercepted channels
 // and this channel's original listener. This is only used to ensure the original listener
 // sees the channel as the request object, and to synthesize OnStatus and OnProgress notifications.
 class InterceptStreamListener : public nsIStreamListener
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -2006,16 +2006,26 @@ nsresult
 HttpChannelParent::ResumeMessageDiversion()
 {
   LOG(("HttpChannelParent::SuspendMessageDiversion [this=%p]", this));
   // This only needs to resumes message queue.
   mEventQ->Resume();
   return NS_OK;
 }
 
+nsresult
+HttpChannelParent::CancelDiversion()
+{
+  LOG(("HttpChannelParent::CancelDiversion [this=%p]", this));
+  if (!mIPCClosed) {
+    Unused << SendCancelDiversion();
+  }
+  return NS_OK;
+}
+
 /* private, supporting function for ADivertableParentChannel */
 nsresult
 HttpChannelParent::ResumeForDiversion()
 {
   LOG(("HttpChannelParent::ResumeForDiversion [this=%p]\n", this));
   MOZ_ASSERT(mChannel);
   if (NS_WARN_IF(!mDivertingFromChild)) {
     MOZ_ASSERT(mDivertingFromChild,
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -78,16 +78,17 @@ public:
 
   MOZ_MUST_USE bool Init(const HttpChannelCreationArgs& aOpenArgs);
 
   // ADivertableParentChannel functions.
   void DivertTo(nsIStreamListener *aListener) override;
   MOZ_MUST_USE nsresult SuspendForDiversion() override;
   MOZ_MUST_USE nsresult SuspendMessageDiversion() override;
   MOZ_MUST_USE nsresult ResumeMessageDiversion() override;
+  MOZ_MUST_USE nsresult CancelDiversion() override;
 
   // Calls OnStartRequest for "DivertTo" listener, then notifies child channel
   // that it should divert OnDataAvailable and OnStopRequest calls to this
   // parent channel.
   void StartDiversion();
 
   // Handles calling OnStart/Stop if there are errors during diversion.
   // Called asynchronously from FailDiversion.
--- a/netwerk/protocol/http/InterceptedHttpChannel.cpp
+++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp
@@ -27,16 +27,17 @@ InterceptedHttpChannel::InterceptedHttpC
                                                const TimeStamp& aAsyncOpenTimestamp)
   : HttpAsyncAborter<InterceptedHttpChannel>(this)
   , mProgress(0)
   , mProgressReported(0)
   , mSynthesizedStreamLength(-1)
   , mResumeStartPos(0)
   , mSynthesizedOrReset(Invalid)
   , mCallingStatusAndProgress(false)
+  , mDiverting(false)
 {
   // Pre-set the creation and AsyncOpen times based on the original channel
   // we are intercepting.  We don't want our extra internal redirect to mask
   // any time spent processing the channel.
   mChannelCreationTime = aCreationTime;
   mChannelCreationTimestamp = aCreationTimestamp;
   mAsyncOpenTime = aAsyncOpenTimestamp;
 }
@@ -495,16 +496,25 @@ InterceptedHttpChannel::Cancel(nsresult 
   }
   mCanceled = true;
 
   MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aStatus));
   if (NS_SUCCEEDED(mStatus)) {
     mStatus = aStatus;
   }
 
+  // Everything is suspended during diversion until it completes.  Since the
+  // intercepted channel could be a long-running stream, we need to request that
+  // cancellation be triggered in the child, completing the diversion and
+  // allowing cancellation to run to completion.
+  if (mDiverting) {
+    Unused << mParentChannel->CancelDiversion();
+    // (We want the pump to be canceled as well, so don't directly return.)
+  }
+
   if (mPump) {
     return mPump->Cancel(mStatus);
   }
 
   return AsyncAbort(mStatus);
 }
 
 NS_IMETHODIMP
@@ -1108,28 +1118,30 @@ InterceptedHttpChannel::OnDataAvailable(
                                     aOffset, aCount);
 }
 
 NS_IMETHODIMP
 InterceptedHttpChannel::MessageDiversionStarted(ADivertableParentChannel* aParentChannel)
 {
   MOZ_ASSERT(!mParentChannel);
   mParentChannel = aParentChannel;
+  mDiverting = true;
   uint32_t suspendCount = mSuspendCount;
   while(suspendCount--) {
     mParentChannel->SuspendMessageDiversion();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 InterceptedHttpChannel::MessageDiversionStop()
 {
   MOZ_ASSERT(mParentChannel);
   mParentChannel = nullptr;
+  mDiverting = false;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 InterceptedHttpChannel::SuspendInternal()
 {
   ++mSuspendCount;
   if (mPump) {
--- a/netwerk/protocol/http/InterceptedHttpChannel.h
+++ b/netwerk/protocol/http/InterceptedHttpChannel.h
@@ -89,16 +89,17 @@ private:
   nsCString mResumeEntityId;
   nsString mStatusHost;
   enum {
     Invalid = 0,
     Synthesized,
     Reset
   } mSynthesizedOrReset;
   Atomic<bool> mCallingStatusAndProgress;
+  bool mDiverting;
 
   InterceptedHttpChannel(PRTime aCreationTime,
                          const TimeStamp& aCreationTimestamp,
                          const TimeStamp& aAsyncOpenTimestamp);
   ~InterceptedHttpChannel() = default;
 
   virtual void
   ReleaseListeners() override;
--- a/netwerk/protocol/http/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -145,16 +145,19 @@ child:
 
   // When CORS blocks the request in the parent process, it doesn't have the
   // correct window ID, so send the message to the child for logging to the web
   // console.
   async LogBlockedCORSRequest(nsString message);
 
   async AttachStreamFilter(Endpoint<PStreamFilterParent> aEndpoint);
 
+  // See ADivertableParentChannel::CancelDiversion
+  async CancelDiversion();
+
 both:
   // After receiving this message, the parent also calls
   // SendFinishInterceptedRedirect, and makes sure not to send any more messages
   // after that. When receiving this message, the child will call
   // Send__delete__() and complete the steps required to finish the redirect.
   async FinishInterceptedRedirect();
 
   async SetPriority(int16_t priority);
--- a/taskcluster/ci/toolchain/linux.yml
+++ b/taskcluster/ci/toolchain/linux.yml
@@ -139,17 +139,17 @@ linux64-gcc-6:
     description: "GCC 6 toolchain build"
     treeherder:
         kind: build
         platform: toolchains/opt
         symbol: TL(gcc6)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
-        docker-image: {in-tree: desktop-build}
+        docker-image: {in-tree: toolchain-build}
         max-run-time: 36000
     run:
         using: toolchain-script
         script: build-gcc-6-linux.sh
         resources:
             - 'build/unix/build-gcc/build-gcc.sh'
         toolchain-alias: linux64-gcc
         toolchain-artifact: public/build/gcc.tar.xz
--- a/taskcluster/taskgraph/__init__.py
+++ b/taskcluster/taskgraph/__init__.py
@@ -11,10 +11,10 @@ GECKO = os.path.realpath(os.path.join(__
 # Maximum number of dependencies a single task can have
 # https://docs.taskcluster.net/reference/platform/taskcluster-queue/references/api#createTask
 # specifies 100, but we also optionally add the decision task id as a dep in
 # taskgraph.create, so let's set this to 99.
 MAX_DEPENDENCIES = 99
 
 # Enable fast task generation for local debugging
 # This is normally switched on via the --fast/-F flag to `mach taskgraph`
-# Currently this skips toolchain task optimizations
+# Currently this skips toolchain task optimizations and schema validation
 fast = False
--- a/taskcluster/taskgraph/generator.py
+++ b/taskcluster/taskgraph/generator.py
@@ -91,17 +91,18 @@ def load_graph_config(root_dir):
     config_yml = os.path.join(root_dir, "config.yml")
     if not os.path.exists(config_yml):
         raise Exception("Couldn't find taskgraph configuration: {}".format(config_yml))
 
     logger.debug("loading config from `{}`".format(config_yml))
     with open(config_yml) as f:
         config = yaml.load(f)
 
-    return validate_graph_config(config)
+    validate_graph_config(config)
+    return config
 
 
 class TaskGraphGenerator(object):
     """
     The central controller for taskgraph.  This handles all phases of graph
     generation.  The task is generated from all of the kinds defined in
     subdirectories of the generator's root directory.
 
--- a/taskcluster/taskgraph/transforms/balrog.py
+++ b/taskcluster/taskgraph/transforms/balrog.py
@@ -48,19 +48,20 @@ balrog_description_schema = Schema({
     Optional('notifications'): task_description_schema['notifications'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('dependent-task', object).__dict__.get('label', '?no-label?')
-        yield validate_schema(
+        validate_schema(
             balrog_description_schema, job,
             "In balrog ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['dependent-task']
 
         treeherder = job.get('treeherder', {})
--- a/taskcluster/taskgraph/transforms/beetmover.py
+++ b/taskcluster/taskgraph/transforms/beetmover.py
@@ -335,19 +335,20 @@ beetmover_description_schema = Schema({
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('dependent-task', object).__dict__.get('label', '?no-label?')
-        yield validate_schema(
+        validate_schema(
             beetmover_description_schema, job,
             "In beetmover ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['dependent-task']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/beetmover_cdns.py
+++ b/taskcluster/taskgraph/transforms/beetmover_cdns.py
@@ -46,19 +46,20 @@ beetmover_cdns_description_schema = Sche
     Optional('notifications'): task_description_schema['notifications'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job['name']
-        yield validate_schema(
+        validate_schema(
             beetmover_cdns_description_schema, job,
             "In cdns-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_beetmover_cdns_description(config, jobs):
     for job in jobs:
         treeherder = job.get('treeherder', {})
         treeherder.setdefault('symbol', 'Rel(BM-C)')
         treeherder.setdefault('tier', 1)
--- a/taskcluster/taskgraph/transforms/beetmover_checksums.py
+++ b/taskcluster/taskgraph/transforms/beetmover_checksums.py
@@ -36,19 +36,20 @@ beetmover_checksums_description_schema =
     Optional('notifications'): task_description_schema['notifications'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('dependent-task', object).__dict__.get('label', '?no-label?')
-        yield validate_schema(
+        validate_schema(
             beetmover_checksums_description_schema, job,
             "In checksums-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_beetmover_checksums_description(config, jobs):
     for job in jobs:
         dep_job = job['dependent-task']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/beetmover_repackage.py
+++ b/taskcluster/taskgraph/transforms/beetmover_repackage.py
@@ -157,19 +157,20 @@ beetmover_description_schema = Schema({
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('dependent-task', object).__dict__.get('label', '?no-label?')
-        yield validate_schema(
+        validate_schema(
             beetmover_description_schema, job,
             "In beetmover ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['dependent-task']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/checksums_signing.py
+++ b/taskcluster/taskgraph/transforms/checksums_signing.py
@@ -33,19 +33,20 @@ checksums_signing_description_schema = S
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('dependent-task', object).__dict__.get('label', '?no-label?')
-        yield validate_schema(
+        validate_schema(
             checksums_signing_description_schema, job,
             "In checksums-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_checksums_signing_description(config, jobs):
     for job in jobs:
         dep_job = job['dependent-task']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/docker_image.py
+++ b/taskcluster/taskgraph/transforms/docker_image.py
@@ -45,19 +45,20 @@ docker_image_schema = Schema({
     # List of package tasks this docker image depends on.
     Optional('packages'): [basestring],
 })
 
 
 @transforms.add
 def validate(config, tasks):
     for task in tasks:
-        yield validate_schema(
+        validate_schema(
             docker_image_schema, task,
             "In docker image {!r}:".format(task.get('name', 'unknown')))
+        yield task
 
 
 @transforms.add
 def fill_template(config, tasks):
     available_packages = {}
     for task in config.kind_dependencies_tasks:
         if task.kind != 'packages':
             continue
--- a/taskcluster/taskgraph/transforms/job/__init__.py
+++ b/taskcluster/taskgraph/transforms/job/__init__.py
@@ -97,18 +97,19 @@ job_description_schema = Schema({
 })
 
 transforms = TransformSequence()
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
-        yield validate_schema(job_description_schema, job,
-                              "In job {!r}:".format(job.get('name', job.get('label'))))
+        validate_schema(job_description_schema, job,
+                        "In job {!r}:".format(job.get('name', job.get('label'))))
+        yield job
 
 
 @transforms.add
 def rewrite_when_to_optimization(config, jobs):
     for job in jobs:
         when = job.pop('when', {})
         if not when:
             yield job
@@ -170,30 +171,30 @@ def make_task_description(config, jobs):
         # yield only the task description, discarding the job description
         yield taskdesc
 
 
 # A registry of all functions decorated with run_job_using
 registry = {}
 
 
-def run_job_using(worker_implementation, run_using, schema=None):
+def run_job_using(worker_implementation, run_using, schema=None, defaults={}):
     """Register the decorated function as able to set up a task description for
     jobs with the given worker implementation and `run.using` property.  If
     `schema` is given, the job's run field will be verified to match it.
 
     The decorated function should have the signature `using_foo(config, job,
     taskdesc) and should modify the task description in-place.  The skeleton of
     the task description is already set up, but without a payload."""
     def wrap(func):
         for_run_using = registry.setdefault(run_using, {})
         if worker_implementation in for_run_using:
             raise Exception("run_job_using({!r}, {!r}) already exists: {!r}".format(
                 run_using, worker_implementation, for_run_using[run_using]))
-        for_run_using[worker_implementation] = (func, schema)
+        for_run_using[worker_implementation] = (func, schema, defaults)
         return func
     return wrap
 
 
 @run_job_using('always-optimized', 'always-optimized',
                Schema({'using': 'always-optimized'}))
 def always_optimized(config, job, taskdesc):
     pass
@@ -210,23 +211,25 @@ def configure_taskdesc_for_run(config, j
     run_using = job['run']['using']
     if run_using not in registry:
         raise Exception("no functions for run.using {!r}".format(run_using))
 
     if worker_implementation not in registry[run_using]:
         raise Exception("no functions for run.using {!r} on {!r}".format(
             run_using, worker_implementation))
 
-    func, schema = registry[run_using][worker_implementation]
+    func, schema, defaults = registry[run_using][worker_implementation]
+    for k, v in defaults.items():
+        job['run'].setdefault(k, v)
+
     if schema:
-        job['run'] = validate_schema(
+        validate_schema(
                 schema, job['run'],
-                "In job.run using {!r} for job {!r}:".format(
-                    job['run']['using'], job['label']))
-
+                "In job.run using {!r}/{!r} for job {!r}:".format(
+                    job['run']['using'], worker_implementation, job['label']))
     func(config, job, taskdesc)
 
 
 def import_all():
     """Import all modules that are siblings of this one, triggering the decorator
     above in the process."""
     for f in os.listdir(os.path.dirname(__file__)):
         if f.endswith('.py') and f not in ('commmon.py', '__init__.py'):
--- a/taskcluster/taskgraph/transforms/job/mach.py
+++ b/taskcluster/taskgraph/transforms/job/mach.py
@@ -14,22 +14,22 @@ from voluptuous import Required
 mach_schema = Schema({
     Required('using'): 'mach',
 
     # The mach command (omitting `./mach`) to run
     Required('mach'): basestring,
 
     # if true, perform a checkout of a comm-central based branch inside the
     # gecko checkout
-    Required('comm-checkout', default=False): bool,
+    Required('comm-checkout'): bool,
 })
 
 
-@run_job_using("docker-worker", "mach", schema=mach_schema)
-@run_job_using("native-engine", "mach", schema=mach_schema)
+@run_job_using("docker-worker", "mach", schema=mach_schema, defaults={'comm-checkout': False})
+@run_job_using("native-engine", "mach", schema=mach_schema, defaults={'comm-checkout': False})
 def docker_worker_mach(config, job, taskdesc):
     run = job['run']
 
     # defer to the run_task implementation
     run['command'] = 'cd /builds/worker/checkouts/gecko && ./mach ' + run['mach']
     run['using'] = 'run-task'
     del run['mach']
     configure_taskdesc_for_run(config, job, taskdesc, job['worker']['implementation'])
--- a/taskcluster/taskgraph/transforms/job/mozharness.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness.py
@@ -57,60 +57,74 @@ mozharness_run_schema = Schema({
     Optional('extra-config'): dict,
 
     # Extra metadata to use toward the workspace caching.
     # Only supported on docker-worker
     Optional('extra-workspace-cache-key'): basestring,
 
     # If not false, tooltool downloads will be enabled via relengAPIProxy
     # for either just public files, or all files.  Not supported on Windows
-    Required('tooltool-downloads', default=False): Any(
+    Required('tooltool-downloads'): Any(
         False,
         'public',
         'internal',
     ),
 
     # The set of secret names to which the task has access; these are prefixed
     # with `project/releng/gecko/{treeherder.kind}/level-{level}/`.  Setting
     # this will enable any worker features required and set the task's scopes
     # appropriately.  `true` here means ['*'], all secrets.  Not supported on
     # Windows
-    Required('secrets', default=False): Any(bool, [basestring]),
+    Required('secrets'): Any(bool, [basestring]),
 
     # If true, taskcluster proxy will be enabled; note that it may also be enabled
     # automatically e.g., for secrets support.  Not supported on Windows.
-    Required('taskcluster-proxy', default=False): bool,
+    Required('taskcluster-proxy'): bool,
 
     # If true, the build scripts will start Xvfb.  Not supported on Windows.
-    Required('need-xvfb', default=False): bool,
+    Required('need-xvfb'): bool,
 
     # If false, indicate that builds should skip producing artifacts.  Not
     # supported on Windows.
-    Required('keep-artifacts', default=True): bool,
+    Required('keep-artifacts'): bool,
 
     # If specified, use the in-tree job script specified.
     Optional('job-script'): basestring,
 
-    Required('requires-signed-builds', default=False): bool,
+    Required('requires-signed-builds'): bool,
 
     # If false, don't set MOZ_SIMPLE_PACKAGE_NAME
     # Only disableable on windows
-    Required('use-simple-package', default=True): bool,
+    Required('use-simple-package'): bool,
 
     # If false don't pass --branch or --skip-buildbot-actions to mozharness script
     # Only disableable on windows
-    Required('use-magic-mh-args', default=True): bool,
+    Required('use-magic-mh-args'): bool,
 
     # if true, perform a checkout of a comm-central based branch inside the
     # gecko checkout
-    Required('comm-checkout', default=False): bool,
+    Required('comm-checkout'): bool,
 })
 
 
-@run_job_using("docker-worker", "mozharness", schema=mozharness_run_schema)
+mozharness_defaults = {
+    'tooltool-downloads': False,
+    'secrets': False,
+    'taskcluster-proxy': False,
+    'need-xvfb': False,
+    'keep-artifacts': True,
+    'requires-signed-builds': False,
+    'use-simple-package': True,
+    'use-magic-mh-args': True,
+    'comm-checkout': False,
+}
+
+
+@run_job_using("docker-worker", "mozharness", schema=mozharness_run_schema,
+               defaults=mozharness_defaults)
 def mozharness_on_docker_worker_setup(config, job, taskdesc):
     run = job['run']
 
     worker = taskdesc['worker']
     worker['implementation'] = job['worker']['implementation']
 
     if not run['use-simple-package']:
         raise NotImplementedError("Simple packaging cannot be disabled via"
@@ -198,17 +212,18 @@ def mozharness_on_docker_worker_setup(co
         '/builds/worker/workspace/build/src/{}'.format(
             run.get('job-script', 'taskcluster/scripts/builder/build-linux.sh')
         ),
     ]
 
     worker['command'] = command
 
 
-@run_job_using("generic-worker", "mozharness", schema=mozharness_run_schema)
+@run_job_using("generic-worker", "mozharness", schema=mozharness_run_schema,
+               defaults=mozharness_defaults)
 def mozharness_on_generic_worker(config, job, taskdesc):
     assert job['worker']['os'] == 'windows', 'only supports windows right now'
 
     run = job['run']
 
     # fail if invalid run options are included
     invalid = []
     for prop in ['tooltool-downloads',
@@ -331,17 +346,18 @@ def mozharness_on_generic_worker(config,
         ])
 
     worker['command'].extend(hg_commands)
     worker['command'].extend([
         ' '.join(mh_command)
     ])
 
 
-@run_job_using('buildbot-bridge', 'mozharness', schema=mozharness_run_schema)
+@run_job_using('buildbot-bridge', 'mozharness', schema=mozharness_run_schema,
+               defaults=mozharness_defaults)
 def mozharness_on_buildbot_bridge(config, job, taskdesc):
     run = job['run']
     worker = taskdesc['worker']
     branch = config.params['project']
     product = run.get('index', {}).get('product', 'firefox')
 
     worker.pop('env', None)
 
--- a/taskcluster/taskgraph/transforms/job/run_task.py
+++ b/taskcluster/taskgraph/transforms/job/run_task.py
@@ -12,28 +12,28 @@ from taskgraph.util.schema import Schema
 from taskgraph.transforms.job.common import support_vcs_checkout
 from voluptuous import Required, Any
 
 run_task_schema = Schema({
     Required('using'): 'run-task',
 
     # if true, add a cache at ~worker/.cache, which is where things like pip
     # tend to hide their caches.  This cache is never added for level-1 jobs.
-    Required('cache-dotcache', default=False): bool,
+    Required('cache-dotcache'): bool,
 
     # if true (the default), perform a checkout in /builds/worker/checkouts/gecko
-    Required('checkout', default=True): bool,
+    Required('checkout'): bool,
 
     # The sparse checkout profile to use. Value is the filename relative to the
     # directory where sparse profiles are defined (build/sparse-profiles/).
-    Required('sparse-profile', default=None): basestring,
+    Required('sparse-profile'): Any(basestring, None),
 
     # if true, perform a checkout of a comm-central based branch inside the
     # gecko checkout
-    Required('comm-checkout', default=False): bool,
+    Required('comm-checkout'): bool,
 
     # The command arguments to pass to the `run-task` script, after the
     # checkout arguments.  If a list, it will be passed directly; otherwise
     # it will be included in a single argument to `bash -cx`.
     Required('command'): Any([basestring], basestring),
 })
 
 
@@ -52,17 +52,25 @@ def add_checkout_to_command(run, command
 
     command.append('--vcs-checkout=/builds/worker/checkouts/gecko')
 
     if run['sparse-profile']:
         command.append('--sparse-profile=build/sparse-profiles/%s' %
                        run['sparse-profile'])
 
 
-@run_job_using("docker-worker", "run-task", schema=run_task_schema)
+docker_defaults = {
+    'cache-dotcache': False,
+    'checkout': True,
+    'comm-checkout': False,
+    'sparse-profile': None,
+}
+
+
+@run_job_using("docker-worker", "run-task", schema=run_task_schema, defaults=docker_defaults)
 def docker_worker_run_task(config, job, taskdesc):
     run = job['run']
     worker = taskdesc['worker'] = job['worker']
     common_setup(config, job, taskdesc)
 
     if run.get('cache-dotcache'):
         worker['caches'].append({
             'type': 'persistent',
--- a/taskcluster/taskgraph/transforms/job/toolchain.py
+++ b/taskcluster/taskgraph/transforms/job/toolchain.py
@@ -35,28 +35,28 @@ toolchain_run_schema = Schema({
     # are available.
     Required('script'): basestring,
 
     # Arguments to pass to the script.
     Optional('arguments'): [basestring],
 
     # If not false, tooltool downloads will be enabled via relengAPIProxy
     # for either just public files, or all files.  Not supported on Windows
-    Required('tooltool-downloads', default=False): Any(
+    Required('tooltool-downloads'): Any(
         False,
         'public',
         'internal',
     ),
 
     # Sparse profile to give to checkout using `run-task`.  If given,
     # a filename in `build/sparse-profiles`.  Defaults to
     # "toolchain-build", i.e., to
     # `build/sparse-profiles/toolchain-build`.  If `None`, instructs
     # `run-task` to not use a sparse profile at all.
-    Required('sparse-profile', default='toolchain-build'): Any(basestring, None),
+    Required('sparse-profile'): Any(basestring, None),
 
     # Paths/patterns pointing to files that influence the outcome of a
     # toolchain build.
     Optional('resources'): [basestring],
 
     # Path to the artifact produced by the toolchain job
     Required('toolchain-artifact'): basestring,
 
@@ -101,17 +101,24 @@ def get_digest_data(config, run, taskdes
 
     # Likewise script arguments should influence the index.
     args = run.get('arguments')
     if args:
         data.extend(args)
     return data
 
 
-@run_job_using("docker-worker", "toolchain-script", schema=toolchain_run_schema)
+toolchain_defaults = {
+    'tooltool-downloads': False,
+    'sparse-profile': 'toolchain-build',
+}
+
+
+@run_job_using("docker-worker", "toolchain-script",
+               schema=toolchain_run_schema, defaults=toolchain_defaults)
 def docker_worker_toolchain(config, job, taskdesc):
     run = job['run']
     taskdesc['run-on-projects'] = ['trunk', 'try']
 
     worker = taskdesc['worker']
     worker['chain-of-trust'] = True
 
     # Allow the job to specify where artifacts come from, but add
@@ -172,17 +179,18 @@ def docker_worker_toolchain(config, job,
         add_optimization(
             config, taskdesc,
             cache_type=CACHE_TYPE,
             cache_name=name,
             digest_data=get_digest_data(config, run, taskdesc),
         )
 
 
-@run_job_using("generic-worker", "toolchain-script", schema=toolchain_run_schema)
+@run_job_using("generic-worker", "toolchain-script",
+               schema=toolchain_run_schema, defaults=toolchain_defaults)
 def windows_toolchain(config, job, taskdesc):
     run = job['run']
     taskdesc['run-on-projects'] = ['trunk', 'try']
 
     worker = taskdesc['worker']
 
     worker['artifacts'] = [{
         'path': r'public\build',
--- a/taskcluster/taskgraph/transforms/l10n.py
+++ b/taskcluster/taskgraph/transforms/l10n.py
@@ -231,18 +231,19 @@ def copy_in_useful_magic(config, jobs):
 
         attributes['build_platform'] = job['build-platform']
         yield job
 
 
 @transforms.add
 def validate_early(config, jobs):
     for job in jobs:
-        yield validate_schema(l10n_description_schema, job,
-                              "In job {!r}:".format(job.get('name', 'unknown')))
+        validate_schema(l10n_description_schema, job,
+                        "In job {!r}:".format(job.get('name', 'unknown')))
+        yield job
 
 
 @transforms.add
 def setup_nightly_dependency(config, jobs):
     """ Sets up a task dependency to the signing job this relates to """
     for job in jobs:
         if not job['attributes'].get('nightly'):
             yield job
@@ -389,18 +390,19 @@ def chain_of_trust(config, jobs):
             cot = job.setdefault('extra', {}).setdefault('chainOfTrust', {})
             cot.setdefault('inputs', {})['docker-image'] = {"task-reference": "<docker-image>"}
         yield job
 
 
 @transforms.add
 def validate_again(config, jobs):
     for job in jobs:
-        yield validate_schema(l10n_description_schema, job,
-                              "In job {!r}:".format(job.get('name', 'unknown')))
+        validate_schema(l10n_description_schema, job,
+                        "In job {!r}:".format(job.get('name', 'unknown')))
+        yield job
 
 
 @transforms.add
 def make_job_description(config, jobs):
     for job in jobs:
         job['mozharness'].update({
             'using': 'mozharness',
             'job-script': 'taskcluster/scripts/builder/build-l10n.sh',
--- a/taskcluster/taskgraph/transforms/repackage.py
+++ b/taskcluster/taskgraph/transforms/repackage.py
@@ -54,19 +54,20 @@ packaging_description_schema = Schema({
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('dependent-task', object).__dict__.get('label', '?no-label?')
-        yield validate_schema(
+        validate_schema(
             packaging_description_schema, job,
             "In packaging ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_repackage_description(config, jobs):
     for job in jobs:
         dep_job = job['dependent-task']
 
         label = job.get('label',
--- a/taskcluster/taskgraph/transforms/repackage_routes.py
+++ b/taskcluster/taskgraph/transforms/repackage_routes.py
@@ -13,19 +13,20 @@ from taskgraph.transforms.job import job
 
 transforms = TransformSequence()
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job['label']
-        yield validate_schema(
+        validate_schema(
             job_description_schema, job,
             "In repackage-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def add_indexes(config, jobs):
     for job in jobs:
         repackage_type = job['attributes'].get('repackage_type')
         if repackage_type:
             build_platform = job['attributes']['build_platform']
--- a/taskcluster/taskgraph/transforms/repackage_signing.py
+++ b/taskcluster/taskgraph/transforms/repackage_signing.py
@@ -29,19 +29,20 @@ repackage_signing_description_schema = S
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('dependent-task', object).__dict__.get('label', '?no-label?')
-        yield validate_schema(
+        validate_schema(
             repackage_signing_description_schema, job,
             "In repackage-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_repackage_signing_description(config, jobs):
     for job in jobs:
         dep_job = job['dependent-task']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/signing.py
+++ b/taskcluster/taskgraph/transforms/signing.py
@@ -42,17 +42,17 @@ signing_description_schema = Schema({
         # Paths to the artifacts to sign
         Required('paths'): [basestring],
 
         # Signing formats to use on each of the paths
         Required('formats'): [basestring],
     }],
 
     # depname is used in taskref's to identify the taskID of the unsigned things
-    Required('depname', default='build'): basestring,
+    Required('depname'): basestring,
 
     # unique label to describe this signing task, defaults to {dep.label}-signing
     Optional('label'): basestring,
 
     # treeherder is allowed here to override any defaults we use for signing.  See
     # taskcluster/taskgraph/transforms/task.py for the schema details, and the
     # below transforms for defaults of various values.
     Optional('treeherder'): task_description_schema['treeherder'],
@@ -61,22 +61,30 @@ signing_description_schema = Schema({
     Optional('routes'): [basestring],
 
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
 
 @transforms.add
+def set_defaults(config, jobs):
+    for job in jobs:
+        job.setdefault('depname', 'build')
+        yield job
+
+
+@transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('dependent-task', object).__dict__.get('label', '?no-label?')
-        yield validate_schema(
+        validate_schema(
             signing_description_schema, job,
             "In signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['dependent-task']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/source_test.py
+++ b/taskcluster/taskgraph/transforms/source_test.py
@@ -39,17 +39,17 @@ source_test_description_schema = Schema(
     # (for try selection) and treeherder metadata (for display).  If given as a list,
     # the job will be "split" into multiple tasks, one with each platform.
     Required('platform'): Any(basestring, [basestring]),
 
     # Whether the job requires a build artifact or not. If True, the task will
     # depend on a build task and the installer url will be saved to the
     # GECKO_INSTALLER_URL environment variable. Build labels are determined by the
     # `dependent-build-platforms` config in kind.yml.
-    Required('require-build', default=False): bool,
+    Required('require-build'): bool,
 
     # These fields can be keyed by "platform", and are otherwise identical to
     # job descriptions.
     Required('worker-type'): Any(
         job_description_schema['worker-type'],
         {'by-platform': {basestring: job_description_schema['worker-type']}},
     ),
     Required('worker'): Any(
@@ -57,20 +57,28 @@ source_test_description_schema = Schema(
         {'by-platform': {basestring: job_description_schema['worker']}},
     ),
 })
 
 transforms = TransformSequence()
 
 
 @transforms.add
+def set_defaults(config, jobs):
+    for job in jobs:
+        job.setdefault('require-build', False)
+        yield job
+
+
+@transforms.add
 def validate(config, jobs):
     for job in jobs:
-        yield validate_schema(source_test_description_schema, job,
-                              "In job {!r}:".format(job['name']))
+        validate_schema(source_test_description_schema, job,
+                        "In job {!r}:".format(job['name']))
+        yield job
 
 
 @transforms.add
 def set_job_name(config, jobs):
     for job in jobs:
         if 'job-from' in job and job['job-from'] != 'kind.yml':
             from_name = os.path.splitext(job['job-from'])[0]
             job['name'] = '{}-{}'.format(from_name, job['name'])
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -175,27 +175,27 @@ task_description_schema = Schema({
 
     # The `run_on_projects` attribute, defaulting to "all".  This dictates the
     # projects on which this task should be included in the target task set.
     # See the attributes documentation for details.
     Optional('run-on-projects'): [basestring],
 
     # The `shipping_phase` attribute, defaulting to None. This specifies the
     # release promotion phase that this task belongs to.
-    Required('shipping-phase', default=None): Any(
+    Required('shipping-phase'): Any(
         None,
         'build',
         'promote',
         'push',
         'ship',
     ),
 
     # The `shipping_product` attribute, defaulting to None. This specifies the
     # release promotion product that this task belongs to.
-    Required('shipping-product', default=None): Any(
+    Required('shipping-product'): Any(
         None,
         'devedition',
         'fennec',
         'firefox',
     ),
 
     # Coalescing provides the facility for tasks to be superseded by the same
     # task in a subsequent commit, if the current task backlog reaches an
@@ -218,21 +218,21 @@ task_description_schema = Schema({
         'size': int,
     },
 
     # The `always-target` attribute will cause the task to be included in the
     # target_task_graph regardless of filtering. Tasks included in this manner
     # will be candidates for optimization even when `optimize_target_tasks` is
     # False, unless the task was also explicitly chosen by the target_tasks
     # method.
-    Required('always-target', default=False): bool,
+    Required('always-target'): bool,
 
     # Optimization to perform on this task during the optimization phase.
     # Optimizations are defined in taskcluster/taskgraph/optimize.py.
-    Required('optimization', default=None): Any(
+    Required('optimization'): Any(
         # always run this task (default)
         None,
         # search the index for the given index namespaces, and replace this task if found
         # the search occurs in order, with the first match winning
         {'index-search': [basestring]},
         # consult SETA and skip this task if it is low-value
         {'seta': None},
         # skip this task if none of the given file patterns match
@@ -247,17 +247,17 @@ task_description_schema = Schema({
     ),
 
     # the provisioner-id/worker-type for the task.  The following parameters will
     # be substituted in this string:
     #  {level} -- the scm level of this push
     'worker-type': basestring,
 
     # Whether the job should use sccache compiler caching.
-    Required('needs-sccache', default=False): bool,
+    Required('needs-sccache'): bool,
 
     # Send notifications using pulse-notifier[1] service:
     #
     #     https://github.com/mozilla-releng/pulse-notify
     #
     # Notifications are send uppon task completion, failure or when exception
     # is raised.
     Optional('notifications'): {
@@ -284,68 +284,68 @@ task_description_schema = Schema({
             basestring,
             # an in-tree generated docker image (from `taskcluster/docker/<name>`)
             {'in-tree': basestring},
             # an indexed docker image
             {'indexed': basestring},
         ),
 
         # worker features that should be enabled
-        Required('relengapi-proxy', default=False): bool,
-        Required('chain-of-trust', default=False): bool,
-        Required('taskcluster-proxy', default=False): bool,
-        Required('allow-ptrace', default=False): bool,
-        Required('loopback-video', default=False): bool,
-        Required('loopback-audio', default=False): bool,
-        Required('docker-in-docker', default=False): bool,  # (aka 'dind')
+        Required('relengapi-proxy'): bool,
+        Required('chain-of-trust'): bool,
+        Required('taskcluster-proxy'): bool,
+        Required('allow-ptrace'): bool,
+        Required('loopback-video'): bool,
+        Required('loopback-audio'): bool,
+        Required('docker-in-docker'): bool,  # (aka 'dind')
 
         # Paths to Docker volumes.
         #
         # For in-tree Docker images, volumes can be parsed from Dockerfile.
         # This only works for the Dockerfile itself: if a volume is defined in
         # a base image, it will need to be declared here. Out-of-tree Docker
         # images will also require explicit volume annotation.
         #
         # Caches are often mounted to the same path as Docker volumes. In this
         # case, they take precedence over a Docker volume. But a volume still
         # needs to be declared for the path.
-        Optional('volumes', default=[]): [basestring],
+        Optional('volumes'): [basestring],
 
         # caches to set up for the task
         Optional('caches'): [{
             # only one type is supported by any of the workers right now
             'type': 'persistent',
 
             # name of the cache, allowing re-use by subsequent tasks naming the
             # same cache
             'name': basestring,
 
             # location in the task image where the cache will be mounted
             'mount-point': basestring,
 
             # Whether the cache is not used in untrusted environments
             # (like the Try repo).
-            Optional('skip-untrusted', default=False): bool,
+            Optional('skip-untrusted'): bool,
         }],
 
         # artifacts to extract from the task image after completion
         Optional('artifacts'): [{
             # type of artifact -- simple file, or recursive directory
             'type': Any('file', 'directory'),
 
             # task image path from which to read artifact
             'path': basestring,
 
             # name of the produced artifact (root of the names for
             # type=directory)
             'name': basestring,
         }],
 
         # environment variables
-        Required('env', default={}): {basestring: taskref_or_string},
+        Required('env'): {basestring: taskref_or_string},
 
         # the command to run; if not given, docker-worker will default to the
         # command in the docker image
         Optional('command'): [taskref_or_string],
 
         # the maximum time to run, in seconds
         Required('max-run-time'): int,
 
@@ -416,26 +416,26 @@ task_description_schema = Schema({
             Optional('file'): basestring,
             # Required if and only if `content` is specified and mounting a
             # directory (not a file). This should be the archive format of the
             # content (either pre-loaded cache or read-only directory).
             Optional('format'): Any('rar', 'tar.bz2', 'tar.gz', 'zip')
         }],
 
         # environment variables
-        Required('env', default={}): {basestring: taskref_or_string},
+        Required('env'): {basestring: taskref_or_string},
 
         # the maximum time to run, in seconds
         Required('max-run-time'): int,
 
         # os user groups for test task workers
-        Optional('os-groups', default=[]): [basestring],
+        Optional('os-groups'): [basestring],
 
         # optional features
-        Required('chain-of-trust', default=False): bool,
+        Required('chain-of-trust'): bool,
     }, {
         Required('implementation'): 'buildbot-bridge',
 
         # see
         # https://github.com/mozilla/buildbot-bridge/blob/master/bbb/schemas/payload.yml
         Required('buildername'): basestring,
         Required('sourcestamp'): {
             'branch': basestring,
@@ -480,17 +480,17 @@ task_description_schema = Schema({
             # name of the produced artifact (root of the names for
             # type=directory)
             Required('name'): basestring,
         }],
     }, {
         Required('implementation'): 'scriptworker-signing',
 
         # the maximum time to run, in seconds
-        Required('max-run-time', default=600): int,
+        Required('max-run-time'): int,
 
         # list of artifact URLs for the artifacts that should be signed
         Required('upstream-artifacts'): [{
             # taskId of the task with the artifact
             Required('taskId'): taskref_or_string,
 
             # type of signing task (for CoT)
             Required('taskType'): basestring,
@@ -502,17 +502,17 @@ task_description_schema = Schema({
             Required('formats'): [basestring],
         }],
     }, {
         Required('implementation'): 'binary-transparency',
     }, {
         Required('implementation'): 'beetmover',
 
         # the maximum time to run, in seconds
-        Required('max-run-time', default=600): int,
+        Required('max-run-time'): int,
 
         # locale key, if this is a locale beetmover job
         Optional('locale'): basestring,
 
         # list of artifact URLs for the artifacts that should be beetmoved
         Required('upstream-artifacts'): [{
             # taskId of the task with the artifact
             Required('taskId'): taskref_or_string,
@@ -525,17 +525,17 @@ task_description_schema = Schema({
 
             # locale is used to map upload path and allow for duplicate simple names
             Required('locale'): basestring,
         }],
     }, {
         Required('implementation'): 'beetmover-cdns',
 
         # the maximum time to run, in seconds
-        Required('max-run-time', default=600): int,
+        Required('max-run-time'): int,
         Required('product'): basestring,
     }, {
         Required('implementation'): 'balrog',
 
         # list of artifact URLs for the artifacts that should be beetmoved
         Required('upstream-artifacts'): [{
             # taskId of the task with the artifact
             Required('taskId'): taskref_or_string,
@@ -575,17 +575,17 @@ task_description_schema = Schema({
             Required('paths'): [basestring],
 
             # Artifact is optional to run the task
             Optional('optional', default=False): bool,
         }],
 
         # "Invalid" is a noop for try and other non-supported branches
         Required('google-play-track'): Any('production', 'beta', 'alpha', 'rollout', 'invalid'),
-        Required('commit', default=False): bool,
+        Required('commit'): bool,
         Optional('rollout-percentage'): int,
     }),
 })
 
 TC_TREEHERDER_SCHEMA_URL = 'https://github.com/taskcluster/taskcluster-treeherder/' \
                            'blob/master/schemas/task-treeherder-config.yml'
 
 
@@ -1125,33 +1125,73 @@ def build_buildbot_bridge_payload(config
             "project:releng:buildbot-bridge:builder-name:{}".format(worker['buildername'])
         )
 
 
 transforms = TransformSequence()
 
 
 @transforms.add
+def set_defaults(config, tasks):
+    for task in tasks:
+        task.setdefault('shipping-phase', None)
+        task.setdefault('shipping-product', None)
+        task.setdefault('always-target', False)
+        task.setdefault('optimization', None)
+        task.setdefault('needs-sccache', False)
+
+        worker = task['worker']
+        if worker['implementation'] in ('docker-worker', 'docker-engine'):
+            worker.setdefault('relengapi-proxy', False)
+            worker.setdefault('chain-of-trust', False)
+            worker.setdefault('taskcluster-proxy', False)
+            worker.setdefault('allow-ptrace', False)
+            worker.setdefault('loopback-video', False)
+            worker.setdefault('loopback-audio', False)
+            worker.setdefault('docker-in-docker', False)
+            worker.setdefault('volumes', [])
+            worker.setdefault('env', {})
+            if 'caches' in worker:
+                for c in worker['caches']:
+                    c.setdefault('skip-untrusted', False)
+        elif worker['implementation'] == 'generic-worker':
+            worker.setdefault('env', {})
+            worker.setdefault('os-groups', [])
+            worker.setdefault('chain-of-trust', False)
+        elif worker['implementation'] == 'scriptworker-signing':
+            worker.setdefault('max-run-time', 600)
+        elif worker['implementation'] == 'beetmover':
+            worker.setdefault('max-run-time', 600)
+        elif worker['implementation'] == 'beetmover-cdns':
+            worker.setdefault('max-run-time', 600)
+        elif worker['implementation'] == 'push-apk':
+            worker.setdefault('commit', False)
+
+        yield task
+
+
+@transforms.add
 def task_name_from_label(config, tasks):
     for task in tasks:
         if 'label' not in task:
             if 'name' not in task:
                 raise Exception("task has neither a name nor a label")
             task['label'] = '{}-{}'.format(config.kind, task['name'])
         if task.get('name'):
             del task['name']
         yield task
 
 
 @transforms.add
 def validate(config, tasks):
     for task in tasks:
-        yield validate_schema(
+        validate_schema(
             task_description_schema, task,
             "In task {!r}:".format(task.get('label', '?no-label?')))
+        yield task
 
 
 @index_builder('generic')
 def add_generic_index_routes(config, task):
     index = task.get('index')
     routes = task.setdefault('routes', [])
 
     verify_index(config, index)
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -184,98 +184,98 @@ test_description_schema = Schema({
 
     # The `run_on_projects` attribute, defaulting to "all".  This dictates the
     # projects on which this task should be included in the target task set.
     # See the attributes documentation for details.
     #
     # Note that the special case 'built-projects', the default, uses the parent
     # build task's run-on-projects, meaning that tests run only on platforms
     # that are built.
-    Optional('run-on-projects', default='built-projects'): optionally_keyed_by(
+    Optional('run-on-projects'): optionally_keyed_by(
         'test-platform',
         Any([basestring], 'built-projects')),
 
     # the sheriffing tier for this task (default: set based on test platform)
     Optional('tier'): optionally_keyed_by(
         'test-platform',
         Any(int, 'default')),
 
     # number of chunks to create for this task.  This can be keyed by test
     # platform by passing a dictionary in the `by-test-platform` key.  If the
     # test platform is not found, the key 'default' will be tried.
-    Required('chunks', default=1): optionally_keyed_by(
+    Required('chunks'): optionally_keyed_by(
         'test-platform',
         int),
 
     # the time (with unit) after which this task is deleted; default depends on
     # the branch (see below)
     Optional('expires-after'): basestring,
 
     # Whether to run this task with e10s (desktop-test only).  If false, run
     # without e10s; if true, run with e10s; if 'both', run one task with and
     # one task without e10s.  E10s tasks have "-e10s" appended to the test name
     # and treeherder group.
-    Required('e10s', default='true'): optionally_keyed_by(
+    Required('e10s'): optionally_keyed_by(
         'test-platform', 'project',
         Any(bool, 'both')),
 
     # Whether the task should run with WebRender enabled or not.
-    Optional('webrender', default=False): bool,
+    Optional('webrender'): bool,
 
     # The EC2 instance size to run these tests on.
-    Required('instance-size', default='default'): optionally_keyed_by(
+    Required('instance-size'): optionally_keyed_by(
         'test-platform',
         Any('default', 'large', 'xlarge')),
 
     # type of virtualization or hardware required by test.
-    Required('virtualization', default='virtual'): optionally_keyed_by(
+    Required('virtualization'): optionally_keyed_by(
         'test-platform',
         Any('virtual', 'virtual-with-gpu', 'hardware')),
 
     # Whether the task requires loopback audio or video (whatever that may mean
     # on the platform)
-    Required('loopback-audio', default=False): bool,
-    Required('loopback-video', default=False): bool,
+    Required('loopback-audio'): bool,
+    Required('loopback-video'): bool,
 
     # Whether the test can run using a software GL implementation on Linux
     # using the GL compositor. May not be used with "legacy" sized instances
     # due to poor LLVMPipe performance (bug 1296086).  Defaults to true for
     # unit tests on linux platforms and false otherwise
     Optional('allow-software-gl-layers'): bool,
 
     # For tasks that will run in docker-worker or docker-engine, this is the
     # name of the docker image or in-tree docker image to run the task in.  If
     # in-tree, then a dependency will be created automatically.  This is
     # generally `desktop-test`, or an image that acts an awful lot like it.
-    Required('docker-image', default={'in-tree': 'desktop1604-test'}): optionally_keyed_by(
+    Required('docker-image'): optionally_keyed_by(
         'test-platform',
         Any(
             # a raw Docker image path (repo/image:tag)
             basestring,
             # an in-tree generated docker image (from `taskcluster/docker/<name>`)
             {'in-tree': basestring},
             # an indexed docker image
             {'indexed': basestring},
         )
     ),
 
     # seconds of runtime after which the task will be killed.  Like 'chunks',
     # this can be keyed by test pltaform.
-    Required('max-run-time', default=3600): optionally_keyed_by(
+    Required('max-run-time'): optionally_keyed_by(
         'test-platform',
         int),
 
     # the exit status code that indicates the task should be retried
     Optional('retry-exit-status'): int,
 
     # Whether to perform a gecko checkout.
-    Required('checkout', default=False): bool,
+    Required('checkout'): bool,
 
     # Wheter to perform a machine reboot after test is done
-    Optional('reboot', default=False):
+    Optional('reboot'):
         Any(False, 'always', 'on-exception', 'on-failure'),
 
     # What to run
     Required('mozharness'): {
         # the mozharness script used to run this task
         Required('script'): optionally_keyed_by(
             'test-platform',
             basestring),
@@ -288,74 +288,74 @@ test_description_schema = Schema({
         # mochitest flavor for mochitest runs
         Optional('mochitest-flavor'): basestring,
 
         # any additional actions to pass to the mozharness command
         Optional('actions'): [basestring],
 
         # additional command-line options for mozharness, beyond those
         # automatically added
-        Required('extra-options', default=[]): optionally_keyed_by(
+        Required('extra-options'): optionally_keyed_by(
             'test-platform',
             [basestring]),
 
         # the artifact name (including path) to test on the build task; this is
         # generally set in a per-kind transformation
         Optional('build-artifact-name'): basestring,
 
         # If true, tooltool downloads will be enabled via relengAPIProxy.
-        Required('tooltool-downloads', default=False): bool,
+        Required('tooltool-downloads'): bool,
 
         # This mozharness script also runs in Buildbot and tries to read a
         # buildbot config file, so tell it not to do so in TaskCluster
-        Required('no-read-buildbot-config', default=False): bool,
+        Required('no-read-buildbot-config'): bool,
 
         # Add --blob-upload-branch=<project> mozharness parameter
         Optional('include-blob-upload-branch'): bool,
 
         # The setting for --download-symbols (if omitted, the option will not
         # be passed to mozharness)
         Optional('download-symbols'): Any(True, 'ondemand'),
 
         # If set, then MOZ_NODE_PATH=/usr/local/bin/node is included in the
         # environment.  This is more than just a helpful path setting -- it
         # causes xpcshell tests to start additional servers, and runs
         # additional tests.
-        Required('set-moz-node-path', default=False): bool,
+        Required('set-moz-node-path'): bool,
 
         # If true, include chunking information in the command even if the number
         # of chunks is 1
-        Required('chunked', default=False): optionally_keyed_by(
+        Required('chunked'): optionally_keyed_by(
             'test-platform',
             bool),
 
         # The chunking argument format to use
-        Required('chunking-args', default='this-chunk'): Any(
+        Required('chunking-args'): Any(
             # Use the usual --this-chunk/--total-chunk arguments
             'this-chunk',
             # Use --test-suite=<suite>-<chunk-suffix>; see chunk-suffix, below
             'test-suite-suffix',
         ),
 
         # the string to append to the `--test-suite` arugment when
         # chunking-args = test-suite-suffix; "<CHUNK>" in this string will
         # be replaced with the chunk number.
         Optional('chunk-suffix'): basestring,
 
-        Required('requires-signed-builds', default=False): optionally_keyed_by(
+        Required('requires-signed-builds'): optionally_keyed_by(
             'test-platform',
             bool),
     },
 
     # The current chunk; this is filled in by `all_kinds.py`
     Optional('this-chunk'): int,
 
     # os user groups for test task workers; required scopes, will be
     # added automatically
-    Optional('os-groups', default=[]): optionally_keyed_by(
+    Optional('os-groups'): optionally_keyed_by(
         'test-platform',
         [basestring]),
 
     # -- values supplied by the task-generation infrastructure
 
     # the platform of the build this task is testing
     'build-platform': basestring,
 
@@ -387,23 +387,16 @@ test_description_schema = Schema({
         'test-platform',
         Any(basestring, None),
     ),
 
 }, required=True)
 
 
 @transforms.add
-def validate(config, tests):
-    for test in tests:
-        yield validate_schema(test_description_schema, test,
-                              "In test {!r}:".format(test['test-name']))
-
-
-@transforms.add
 def handle_keyed_by_mozharness(config, tests):
     """Resolve a mozharness field if it is keyed by something"""
     for test in tests:
         resolve_keyed_by(test, 'mozharness', item_name=test['test-name'])
         yield test
 
 
 @transforms.add
@@ -417,17 +410,17 @@ def set_defaults(config, tests):
             # Android doesn't do e10s
             test['e10s'] = False
             # loopback-video is always true for Android, but false for other
             # platform phyla
             test['loopback-video'] = True
         else:
             # all non-android tests want to run the bits that require node
             test['mozharness']['set-moz-node-path'] = True
-            test.setdefault('e10s', 'true')
+            test.setdefault('e10s', True)
 
         # software-gl-layers is only meaningful on linux unittests, where it defaults to True
         if test['test-platform'].startswith('linux') and test['suite'] != 'talos':
             test.setdefault('allow-software-gl-layers', True)
         else:
             test['allow-software-gl-layers'] = False
 
         # Enable WebRender by default on the QuantumRender test platforms, since
@@ -441,18 +434,42 @@ def set_defaults(config, tests):
 
         test.setdefault('try-name', test['test-name'])
 
         test.setdefault('os-groups', [])
         test.setdefault('chunks', 1)
         test.setdefault('run-on-projects', 'built-projects')
         test.setdefault('instance-size', 'default')
         test.setdefault('max-run-time', 3600)
-        test.setdefault('reboot', True)
+        test.setdefault('reboot', False)
+        test.setdefault('virtualization', 'virtual')
+        test.setdefault('run-on-projects', 'built-projects')
+        test.setdefault('chunks', 1)
+        test.setdefault('instance-size', 'default')
+        test.setdefault('loopback-audio', False)
+        test.setdefault('loopback-video', False)
+        test.setdefault('docker-image', {'in-tree': 'desktop1604-test'})
+        test.setdefault('max-run-time', 3600)
+        test.setdefault('checkout', False)
+
         test['mozharness'].setdefault('extra-options', [])
+        test['mozharness'].setdefault('requires-signed-builds', False)
+        test['mozharness'].setdefault('tooltool-downloads', False)
+        test['mozharness'].setdefault('no-read-buildbot-config', False)
+        test['mozharness'].setdefault('set-moz-node-path', False)
+        test['mozharness'].setdefault('chunked', False)
+        test['mozharness'].setdefault('chunking-args', 'this-chunk')
+        yield test
+
+
+@transforms.add
+def validate(config, tests):
+    for test in tests:
+        validate_schema(test_description_schema, test,
+                        "In test {!r}:".format(test['test-name']))
         yield test
 
 
 @transforms.add
 def setup_talos(config, tests):
     """Add options that are specific to talos jobs (identified by suite=talos)"""
     for test in tests:
         if test['suite'] != 'talos':
--- a/taskcluster/taskgraph/util/push_apk.py
+++ b/taskcluster/taskgraph/util/push_apk.py
@@ -22,20 +22,21 @@ def fill_labels_tranform(_, jobs):
         job['label'] = job['name']
 
         yield job
 
 
 def validate_jobs_schema_transform_partial(description_schema, transform_type, config, jobs):
     for job in jobs:
         label = job.get('label', '?no-label?')
-        yield validate_schema(
+        validate_schema(
             description_schema, job,
             "In {} ({!r} kind) task for {!r}:".format(transform_type, config.kind, label)
         )
+        yield job
 
 
 def validate_dependent_tasks_transform(_, jobs):
     for job in jobs:
         check_every_architecture_is_present_in_dependent_tasks(job['dependent-tasks'])
         yield job
 
 
--- a/taskcluster/taskgraph/util/schema.py
+++ b/taskcluster/taskgraph/util/schema.py
@@ -1,31 +1,33 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import re
-import copy
 import pprint
 import collections
 import voluptuous
 
+import taskgraph
+
 from .attributes import keymatch
 
 
 def validate_schema(schema, obj, msg_prefix):
     """
     Validate that object satisfies schema.  If not, generate a useful exception
     beginning with msg_prefix.
     """
+    if taskgraph.fast:
+        return
     try:
-        # deep copy the result since it may include mutable defaults
-        return copy.deepcopy(schema(obj))
+        schema(obj)
     except voluptuous.MultipleInvalid as exc:
         msg = [msg_prefix]
         for error in exc.errors:
             msg.append(str(error))
         raise Exception('\n'.join(msg) + '\n' + pprint.pformat(obj))
 
 
 def optionally_keyed_by(*arguments):
--- a/toolkit/components/timermanager/nsUpdateTimerManager.js
+++ b/toolkit/components/timermanager/nsUpdateTimerManager.js
@@ -10,40 +10,20 @@ Cu.import("resource://gre/modules/Servic
 const PREF_APP_UPDATE_LASTUPDATETIME_FMT  = "app.update.lastUpdateTime.%ID%";
 const PREF_APP_UPDATE_TIMERMINIMUMDELAY   = "app.update.timerMinimumDelay";
 const PREF_APP_UPDATE_TIMERFIRSTINTERVAL  = "app.update.timerFirstInterval";
 const PREF_APP_UPDATE_LOG                 = "app.update.log";
 
 const CATEGORY_UPDATE_TIMER               = "update-timer";
 
 XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function tm_gLogEnabled() {
-  return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
+  return Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false);
 });
 
 /**
- *  Gets a preference value, handling the case where there is no default.
- *  @param   func
- *           The name of the preference function to call, on nsIPrefBranch
- *  @param   preference
- *           The name of the preference
- *  @param   defaultValue
- *           The default value to return in the event the preference has
- *           no setting
- *  @returns The value of the preference, or undefined if there was no
- *           user or default value.
- */
-function getPref(func, preference, defaultValue) {
-  try {
-    return Services.prefs[func](preference);
-  } catch (e) {
-  }
-  return defaultValue;
-}
-
-/**
  *  Logs a string to the error console.
  *  @param   string
  *           The string to write to the error console.
  */
 function LOG(string) {
   if (gLogEnabled) {
     dump("*** UTM:SVC " + string + "\n");
     Services.console.logStringMessage("UTM:SVC " + string);
@@ -87,23 +67,25 @@ TimerManager.prototype = {
     var minFirstInterval = 10000;
     switch (aTopic) {
       case "utm-test-init":
         // Enforce a minimum timer interval of 500 ms for tests and fall through
         // to profile-after-change to initialize the timer.
         minInterval = 500;
         minFirstInterval = 500;
       case "profile-after-change":
-        this._timerMinimumDelay = Math.max(1000 * getPref("getIntPref", PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
-                                           minInterval);
+        this._timerMinimumDelay =
+          Math.max(1000 * Services.prefs.getIntPref(PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
+                   minInterval);
         // Prevent the timer delay between notifications to other consumers from
         // being greater than 5 minutes which is 300000 milliseconds.
         this._timerMinimumDelay = Math.min(this._timerMinimumDelay, 300000);
         // Prevent the first interval from being less than the value of minFirstInterval
-        let firstInterval = Math.max(getPref("getIntPref", PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
+        let firstInterval =
+          Math.max(Services.prefs.getIntPref(PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
                                              30000), minFirstInterval);
         // Prevent the first interval from being greater than 2 minutes which is
         // 120000 milliseconds.
         firstInterval = Math.min(firstInterval, 120000);
         // Cancel the timer if it has already been initialized. This is primarily
         // for tests.
         this._canEnsureTimer = true;
         this._ensureTimer(firstInterval);
@@ -183,28 +165,28 @@ TimerManager.prototype = {
       // cid and method are validated below when calling notify.
       if (!timerID || !defaultInterval || isNaN(defaultInterval)) {
         LOG("TimerManager:notify - update-timer category registered" +
             (cid ? " for " + cid : "") + " without required parameters - " +
              "skipping");
         continue;
       }
 
-      let interval = getPref("getIntPref", prefInterval, defaultInterval);
+      let interval = Services.prefs.getIntPref(prefInterval, defaultInterval);
       // Allow the update-timer category to specify a maximum value to prevent
       // values larger than desired.
       maxInterval = parseInt(maxInterval);
       if (maxInterval && !isNaN(maxInterval)) {
         interval = Math.min(interval, maxInterval);
       }
       let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/,
                                                                       timerID);
       // Initialize the last update time to 0 when the preference isn't set so
       // the timer will be notified soon after a new profile's first use.
-      lastUpdateTime = getPref("getIntPref", prefLastUpdate, 0);
+      lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate, 0);
 
       // If the last update time is greater than the current time then reset
       // it to 0 and the timer manager will correct the value when it fires
       // next for this consumer.
       if (lastUpdateTime > now) {
         lastUpdateTime = 0;
       }
 
@@ -317,17 +299,17 @@ TimerManager.prototype = {
     }
     if (id in this._timers && callback != this._timers[id].callback) {
       LOG("TimerManager:registerTimer - Ignoring second registration for " + id);
       return;
     }
     let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
     // Initialize the last update time to 0 when the preference isn't set so
     // the timer will be notified soon after a new profile's first use.
-    let lastUpdateTime = getPref("getIntPref", prefLastUpdate, 0);
+    let lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate, 0);
     let now = Math.round(Date.now() / 1000);
     if (lastUpdateTime > now) {
       lastUpdateTime = 0;
     }
     if (lastUpdateTime == 0) {
       Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
     }
     this._timers[id] = {callback,
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -72,17 +72,16 @@ toolkit.jar:
    content/global/bindings/datekeeper.js       (widgets/datekeeper.js)
    content/global/bindings/datepicker.js       (widgets/datepicker.js)
    content/global/bindings/datetimepicker.xml  (widgets/datetimepicker.xml)
    content/global/bindings/datetimepopup.xml   (widgets/datetimepopup.xml)
    content/global/bindings/datetimebox.xml     (widgets/datetimebox.xml)
    content/global/bindings/datetimebox.css     (widgets/datetimebox.css)
 *  content/global/bindings/dialog.xml          (widgets/dialog.xml)
    content/global/bindings/editor.xml          (widgets/editor.xml)
-   content/global/bindings/expander.xml        (widgets/expander.xml)
    content/global/bindings/filefield.xml       (widgets/filefield.xml)
 *  content/global/bindings/findbar.xml         (widgets/findbar.xml)
    content/global/bindings/general.xml         (widgets/general.xml)
    content/global/bindings/groupbox.xml        (widgets/groupbox.xml)
    content/global/bindings/listbox.xml         (widgets/listbox.xml)
    content/global/bindings/menu.xml            (widgets/menu.xml)
    content/global/bindings/menulist.xml        (widgets/menulist.xml)
    content/global/bindings/notification.xml    (widgets/notification.xml)
deleted file mode 100644
--- a/toolkit/content/widgets/expander.xml
+++ /dev/null
@@ -1,86 +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/. -->
-
-<bindings id="expanderBindings"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:xbl="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-  <binding id="expander" display="xul:vbox">
-    <resources>
-      <stylesheet src="chrome://global/skin/expander.css"/>
-    </resources>
-    <content>
-      <xul:hbox align="center">
-        <xul:button type="disclosure" class="expanderButton" anonid="disclosure" xbl:inherits="disabled" mousethrough="always"/>
-        <xul:label class="header expanderButton" anonid="label" xbl:inherits="value=label,disabled" mousethrough="always" flex="1"/>
-        <xul:button anonid="clear-button" xbl:inherits="label=clearlabel,disabled=cleardisabled,hidden=clearhidden" mousethrough="always" icon="clear"/>
-      </xul:hbox>
-      <xul:vbox flex="1" anonid="settings" class="settingsContainer" collapsed="true" xbl:inherits="align">
-        <children/>
-      </xul:vbox>
-    </content>
-    <implementation>
-      <constructor><![CDATA[
-        var settings = document.getAnonymousElementByAttribute(this, "anonid", "settings");
-        var expander = document.getAnonymousElementByAttribute(this, "anonid", "disclosure");
-        var open = this.getAttribute("open") == "true";
-        settings.collapsed = !open;
-        expander.open = open;
-      ]]></constructor>
-      <property name="open">
-        <setter>
-          <![CDATA[
-            var settings = document.getAnonymousElementByAttribute(this, "anonid", "settings");
-            var expander = document.getAnonymousElementByAttribute(this, "anonid", "disclosure");
-            settings.collapsed = !val;
-            expander.open = val;
-            if (val)
-              this.setAttribute("open", "true");
-            else
-              this.setAttribute("open", "false");
-            return val;
-          ]]>
-        </setter>
-        <getter>
-          return this.getAttribute("open");
-        </getter>
-      </property>        
-      <method name="onCommand">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          var element = aEvent.originalTarget;
-          var button = element.getAttribute("anonid");
-          switch (button) {
-          case "disclosure":
-          case "label":
-            if (this.open == "true")
-              this.open = false;
-            else
-              this.open = true;
-            break;
-          case "clear-button":
-            var event = document.createEvent("Events");
-            event.initEvent("clear", true, true);
-            this.dispatchEvent(event);
-            break;
-          }
-        ]]></body>
-      </method>
-    </implementation>
-    <handlers>
-      <handler event="command"><![CDATA[
-        this.onCommand(event);
-      ]]></handler>
-      <handler event="click"><![CDATA[
-        if (event.originalTarget.localName == "label")
-          this.onCommand(event);
-      ]]></handler>
-    </handlers>
-  </binding>
-          
-</bindings>
-
-          
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -1010,24 +1010,16 @@ wizardpage {
 .wizard-header {
   -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard-header");
 }
 
 .wizard-buttons {
   -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard-buttons");
 }
 
-/********** expander ********/
-
-expander {
-  -moz-binding: url("chrome://global/content/bindings/expander.xml#expander");
-  -moz-box-orient: vertical;
-}
-
-
 /********** Rich Listbox ********/
 
 richlistbox {
   -moz-binding: url('chrome://global/content/bindings/richlistbox.xml#richlistbox');
   -moz-user-focus: normal;
   -moz-box-orient: vertical;
 }
 
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -152,36 +152,16 @@ XPCOMUtils.defineLazyGetter(this, "gCert
  */
 function LOG(string) {
   if (gLoggingEnabled) {
     dump("*** " + string + "\n");
     gConsole.logStringMessage(string);
   }
 }
 
-/**
- * Gets a preference value, handling the case where there is no default.
- * @param   func
- *          The name of the preference function to call, on nsIPrefBranch
- * @param   preference
- *          The name of the preference
- * @param   defaultValue
- *          The default value to return in the event the preference has
- *          no setting
- * @returns The value of the preference, or undefined if there was no
- *          user or default value.
- */
-function getPref(func, preference, defaultValue) {
-  try {
-    return Services.prefs[func](preference);
-  } catch (e) {
-  }
-  return defaultValue;
-}
-
 // Restarts the application checking in with observers first
 function restartApp() {
   // Notify all windows that an application quit has been requested.
   var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
                    createInstance(Ci.nsISupportsPRBool);
   Services.obs.notifyObservers(cancelQuit, "quit-application-requested");
 
   // Something aborted the quit process.
@@ -248,20 +228,20 @@ function parseRegExp(aStr) {
  * blocklist.xml and allows us to remotely disable / re-enable blocklisted
  * items managed by the Extension Manager with an item's appDisabled property.
  * It also blocklists plugins with data from blocklist.xml.
  */
 
 function Blocklist() {
   Services.obs.addObserver(this, "xpcom-shutdown");
   Services.obs.addObserver(this, "sessionstore-windows-restored");
-  gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
-  gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
-  gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
-                                     MAX_BLOCK_LEVEL);
+  gLoggingEnabled = Services.prefs.getBoolPref(PREF_EM_LOGGING_ENABLED, false);
+  gBlocklistEnabled = Services.prefs.getBoolPref(PREF_BLOCKLIST_ENABLED, true);
+  gBlocklistLevel = Math.min(Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
+                             MAX_BLOCK_LEVEL);
   Services.prefs.addObserver("extensions.blocklist.", this);
   Services.prefs.addObserver(PREF_EM_LOGGING_ENABLED, this);
   this.wrappedJSObject = this;
   // requests from child processes come in here, see receiveMessage.
   Services.ppmm.addMessageListener("Blocklist:getPluginBlocklistState", this);
   Services.ppmm.addMessageListener("Blocklist:content-blocklist-updated", this);
 }
 
@@ -296,25 +276,25 @@ Blocklist.prototype = {
   observe(aSubject, aTopic, aData) {
     switch (aTopic) {
     case "xpcom-shutdown":
       this.shutdown();
       break;
     case "nsPref:changed":
       switch (aData) {
         case PREF_EM_LOGGING_ENABLED:
-          gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
+          gLoggingEnabled = Services.prefs.getBoolPref(PREF_EM_LOGGING_ENABLED, false);
           break;
         case PREF_BLOCKLIST_ENABLED:
-          gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
+          gBlocklistEnabled = Services.prefs.getBoolPref(PREF_BLOCKLIST_ENABLED, true);
           this._loadBlocklist();
           this._blocklistUpdated(null, null);
           break;
         case PREF_BLOCKLIST_LEVEL:
-          gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
+          gBlocklistLevel = Math.min(Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
                                      MAX_BLOCK_LEVEL);
           this._blocklistUpdated(null, null);
           break;
       }
       break;
     case "sessionstore-windows-restored":
       Services.obs.removeObserver(this, "sessionstore-windows-restored");
       this._preloadBlocklist();
@@ -507,26 +487,26 @@ Blocklist.prototype = {
     try {
       var dsURI = Services.prefs.getCharPref(PREF_BLOCKLIST_URL);
     } catch (e) {
       LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" +
           " is missing!");
       return;
     }
 
-    var pingCountVersion = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTVERSION, 0);
-    var pingCountTotal = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTTOTAL, 1);
+    var pingCountVersion = Services.prefs.getIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, 0);
+    var pingCountTotal = Services.prefs.getIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, 1);
     var daysSinceLastPing = 0;
     if (pingCountVersion == 0) {
       daysSinceLastPing = "new";
     } else {
       // Seconds in one day is used because nsIUpdateTimerManager stores the
       // last update time in seconds.
       let secondsInDay = 60 * 60 * 24;
-      let lastUpdateTime = getPref("getIntPref", PREF_BLOCKLIST_LASTUPDATETIME, 0);
+      let lastUpdateTime = Services.prefs.getIntPref(PREF_BLOCKLIST_LASTUPDATETIME, 0);
       if (lastUpdateTime == 0) {
         daysSinceLastPing = "invalid";
       } else {
         let now = Math.round(Date.now() / 1000);
         daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay);
       }
 
       if (daysSinceLastPing == 0 || daysSinceLastPing == "invalid") {
@@ -1411,17 +1391,17 @@ Blocklist.prototype = {
           restartApp();
 
         this._notifyObserversBlocklistUpdated();
         Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed");
       };
 
       Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed");
 
-      if (getPref("getBoolPref", PREF_BLOCKLIST_SUPPRESSUI, false)) {
+      if (Services.prefs.getBoolPref(PREF_BLOCKLIST_SUPPRESSUI, false)) {
         applyBlocklistChanges();
         return;
       }
 
       function blocklistUnloadHandler(event) {
         if (event.target.location == URI_BLOCKLIST_DIALOG) {
           applyBlocklistChanges();
           blocklistWindow.removeEventListener("unload", blocklistUnloadHandler);
--- a/toolkit/mozapps/update/content/updates.js
+++ b/toolkit/mozapps/update/content/updates.js
@@ -78,37 +78,16 @@ function LOG(module, string) {
  * for where to open a URL.
  */
 function openUpdateURL(event) {
   if (event.button == 0)
     openURL(event.target.getAttribute("url"));
 }
 
 /**
- * Gets a preference value, handling the case where there is no default.
- * @param   func
- *          The name of the preference function to call, on nsIPrefBranch
- * @param   preference
- *          The name of the preference
- * @param   defaultValue
- *          The default value to return in the event the preference has
- *          no setting
- * @returns The value of the preference, or undefined if there was no
- *          user or default value.
- */
-function getPref(func, preference, defaultValue) {
-  try {
-    return Services.prefs[func](preference);
-  } catch (e) {
-    LOG("General", "getPref - failed to get preference: " + preference);
-  }
-  return defaultValue;
-}
-
-/**
  * A set of shared data and control functions for the wizard as a whole.
  */
 var gUpdates = {
   /**
    * The nsIUpdate object being used by this window (either for downloading,
    * notification or both).
    */
   update: null,
@@ -306,17 +285,17 @@ var gUpdates = {
   },
 
   /**
    * Called when the wizard UI is loaded.
    */
   onLoad() {
     this.wiz = document.documentElement;
 
-    gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
+    gLogEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false);
 
     this.strings = document.getElementById("updateStrings");
     var brandStrings = document.getElementById("brandStrings");
     this.brandName = brandStrings.getString("brandShortName");
 
     var pages = this.wiz.childNodes;
     for (var i = 0; i < pages.length; ++i) {
       var page = pages[i];
@@ -590,17 +569,17 @@ var gCheckingPage = {
 var gNoUpdatesPage = {
   /**
    * Initialize
    */
   onPageShow() {
     LOG("gNoUpdatesPage", "onPageShow - could not select an appropriate " +
         "update. Either there were no updates or |selectUpdate| failed");
 
-    if (getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true))
+    if (Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED, true))
       document.getElementById("noUpdatesAutoEnabled").hidden = false;
     else
       document.getElementById("noUpdatesAutoDisabled").hidden = false;
 
     gUpdates.setButtons(null, null, "okButton", true);
     gUpdates.wiz.getButton("finish").focus();
   }
 };
@@ -1290,17 +1269,17 @@ var gFinishedPage = {
       let moreElevatedLinkLabel =
         document.getElementById("finishedBackgroundMoreElevatedLinkLabel");
       let manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
       moreElevatedLinkLabel.value = manualURL;
       moreElevatedLinkLabel.setAttribute("url", manualURL);
       moreElevatedLinkLabel.setAttribute("hidden", "false");
     }
 
-    if (getPref("getBoolPref", PREF_APP_UPDATE_TEST_LOOP, false)) {
+    if (Services.prefs.getBoolPref(PREF_APP_UPDATE_TEST_LOOP, false)) {
       setTimeout(function() { gUpdates.wiz.getButton("finish").click(); },
                  UPDATE_TEST_LOOP_INTERVAL);
     }
   },
 
   /**
    * Called when the wizard finishes, i.e. the "Restart Now" button is
    * clicked.
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -203,17 +203,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                   "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
-  return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
+  return Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false);
 });
 
 XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() {
   return Services.strings.createBundle(URI_UPDATES_PROPERTIES);
 });
 
 // shared code for suppressing bad cert dialogs
 XPCOMUtils.defineLazyGetter(this, "gCertUtils", function aus_gCertUtils() {
@@ -501,17 +501,17 @@ XPCOMUtils.defineLazyGetter(this, "gCanS
 
 /**
  * Whether or not the application can stage an update.
  *
  * @return true if updates can be staged.
  */
 function getCanStageUpdates() {
   // If staging updates are disabled, then just bail out!
-  if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) {
+  if (!Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false)) {
     LOG("getCanStageUpdates - staging updates is disabled by preference " +
         PREF_APP_UPDATE_STAGING_ENABLED);
     return false;
   }
 
   if (AppConstants.platform == "win" && shouldUseService()) {
     // No need to perform directory write checks, the maintenance service will
     // be able to write to all directories.
@@ -528,17 +528,17 @@ function getCanStageUpdates() {
 
   return gCanStageUpdatesSession;
 }
 
 XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() {
   // If the administrator has disabled app update and locked the preference so
   // users can't check for updates. This preference check is ok in this lazy
   // getter since locked prefs don't change until the application is restarted.
-  var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
+  var enabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED, true);
   if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
     LOG("gCanCheckForUpdates - unable to automatically check for updates, " +
         "the preference is disabled and admistratively locked.");
     return false;
   }
 
   // If we don't know the binary platform we're updating, we can't update.
   if (!UpdateUtils.ABI) {
@@ -565,36 +565,16 @@ XPCOMUtils.defineLazyGetter(this, "gCanC
 function LOG(string) {
   if (gLogEnabled) {
     dump("*** AUS:SVC " + string + "\n");
     Services.console.logStringMessage("AUS:SVC " + string);
   }
 }
 
 /**
- * Gets a preference value, handling the case where there is no default.
- * @param   func
- *          The name of the preference function to call, on nsIPrefBranch
- * @param   preference
- *          The name of the preference
- * @param   defaultValue
- *          The default value to return in the event the preference has
- *          no setting
- * @return  The value of the preference, or undefined if there was no
- *          user or default value.
- */
-function getPref(func, preference, defaultValue) {
-  try {
-    return Services.prefs[func](preference);
-  } catch (e) {
-  }
-  return defaultValue;
-}
-
-/**
  * Convert a string containing binary values to hex.
  */
 function binaryToHex(input) {
   var result = "";
   for (var i = 0; i < input.length; ++i) {
     var hex = input.charCodeAt(i).toString(16);
     if (hex.length == 1)
       hex = "0" + hex;
@@ -751,17 +731,17 @@ function writeVersionFile(dir, version) 
  */
 function shouldUseService() {
   // This function will return true if the mantenance service should be used if
   // all of the following conditions are met:
   // 1) This build was done with the maintenance service enabled
   // 2) The maintenance service is installed
   // 3) The pref for using the service is enabled
   if (!AppConstants.MOZ_MAINTENANCE_SERVICE || !isServiceInstalled() ||
-      !getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false)) {
+      !Services.prefs.getBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false)) {
     LOG("shouldUseService - returning false");
     return false;
   }
 
   LOG("shouldUseService - returning true");
   return true;
 }
 
@@ -941,45 +921,45 @@ function handleUpdateFailure(update, err
     Cc["@mozilla.org/updates/update-prompt;1"].
       createInstance(Ci.nsIUpdatePrompt).
       showUpdateError(update);
     writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
     return true;
   }
 
   if (update.errorCode == ELEVATION_CANCELED) {
-    if (getPref("getBoolPref", PREF_APP_UPDATE_DOORHANGER, false)) {
-      let elevationAttempts = getPref("getIntPref", PREF_APP_UPDATE_ELEVATE_ATTEMPTS, 0);
+    if (Services.prefs.getBoolPref(PREF_APP_UPDATE_DOORHANGER, false)) {
+      let elevationAttempts = Services.prefs.getIntPref(PREF_APP_UPDATE_ELEVATE_ATTEMPTS, 0);
       elevationAttempts++;
       Services.prefs.setIntPref(PREF_APP_UPDATE_ELEVATE_ATTEMPTS, elevationAttempts);
-      let maxAttempts = Math.min(getPref("getIntPref", PREF_APP_UPDATE_ELEVATE_MAXATTEMPTS, 2), 10);
+      let maxAttempts = Math.min(Services.prefs.getIntPref(PREF_APP_UPDATE_ELEVATE_MAXATTEMPTS, 2), 10);
 
       if (elevationAttempts > maxAttempts) {
         LOG("handleUpdateFailure - notifying observers of error. " +
             "topic: update-error, status: elevation-attempts-exceeded");
         Services.obs.notifyObservers(update, "update-error", "elevation-attempts-exceeded");
       } else {
         LOG("handleUpdateFailure - notifying observers of error. " +
             "topic: update-error, status: elevation-attempt-failed");
         Services.obs.notifyObservers(update, "update-error", "elevation-attempt-failed");
       }
     }
 
-    let cancelations = getPref("getIntPref", PREF_APP_UPDATE_CANCELATIONS, 0);
+    let cancelations = Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS, 0);
     cancelations++;
     Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, cancelations);
     if (AppConstants.platform == "macosx") {
-      let osxCancelations = getPref("getIntPref",
-                                    PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
+      let osxCancelations =
+        Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
       osxCancelations++;
       Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX,
                                 osxCancelations);
-      let maxCancels = getPref("getIntPref",
-                               PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
-                               DEFAULT_CANCELATIONS_OSX_MAX);
+      let maxCancels =
+        Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
+                                  DEFAULT_CANCELATIONS_OSX_MAX);
       // Prevent the preference from setting a value greater than 5.
       maxCancels = Math.min(maxCancels, 5);
       if (osxCancelations >= maxCancels) {
         cleanupActiveUpdate();
       } else {
         writeStatusFile(getUpdatesDir(),
                         update.state = STATE_PENDING_ELEVATE);
       }
@@ -998,21 +978,19 @@ function handleUpdateFailure(update, err
   if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS)) {
     Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS);
   }
   if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
     Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
   }
 
   if (SERVICE_ERRORS.includes(update.errorCode)) {
-    var failCount = getPref("getIntPref",
-                            PREF_APP_UPDATE_SERVICE_ERRORS, 0);
-    var maxFail = getPref("getIntPref",
-                          PREF_APP_UPDATE_SERVICE_MAXERRORS,
-                          DEFAULT_SERVICE_MAX_ERRORS);
+    var failCount = Services.prefs.getIntPref(PREF_APP_UPDATE_SERVICE_ERRORS, 0);
+    var maxFail = Services.prefs.getIntPref(PREF_APP_UPDATE_SERVICE_MAXERRORS,
+                                            DEFAULT_SERVICE_MAX_ERRORS);
     // Prevent the preference from setting a value greater than 10.
     maxFail = Math.min(maxFail, 10);
     // As a safety, when the service reaches maximum failures, it will
     // disable itself and fallback to using the normal update mechanism
     // without the service.
     if (failCount >= maxFail) {
       Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
       Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);
@@ -1272,17 +1250,17 @@ UpdatePatch.prototype = {
  * @constructor
  */
 function Update(update) {
   this._properties = {};
   this._patches = [];
   this.isCompleteUpdate = false;
   this.unsupported = false;
   this.channel = "default";
-  this.promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
+  this.promptWaitTime = Services.prefs.getIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
 
   // Null <update>, assume this is a message container and do no
   // further initialization
   if (!update) {
     return;
   }
 
   const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
@@ -1648,17 +1626,17 @@ UpdateService.prototype = {
         // Clean up any extant updates
         this._postUpdateProcessing();
         break;
       case "network:offline-status-changed":
         this._offlineStatusChanged(data);
         break;
       case "nsPref:changed":
         if (data == PREF_APP_UPDATE_LOG) {
-          gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
+          gLogEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false);
         }
         break;
       case "quit-application":
         Services.obs.removeObserver(this, topic);
         Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this);
 
         if (AppConstants.platform == "win" && gUpdateMutexHandle) {
           // If we hold the update mutex, let it go!
@@ -1935,21 +1913,21 @@ UpdateService.prototype = {
         AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_OFFLINE);
       }
       return;
     }
 
     // Send the error code to telemetry
     AUSTLMY.pingCheckExError(this._pingSuffix, update.errorCode);
     update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES;
-    let errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
+    let errCount = Services.prefs.getIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
     errCount++;
     Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount);
     // Don't allow the preference to set a value greater than 20 for max errors.
-    let maxErrors = Math.min(getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS, 10), 20);
+    let maxErrors = Math.min(Services.prefs.getIntPref(PREF_APP_UPDATE_BACKGROUNDMAXERRORS, 10), 20);
 
     if (errCount >= maxErrors) {
       let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                      createInstance(Ci.nsIUpdatePrompt);
       LOG("UpdateService:onError - notifying observers of error. " +
           "topic: update-error, status: check-attempts-exceeded");
       Services.obs.notifyObservers(update, "update-error", "check-attempts-exceeded");
       prompter.showUpdateError(update);
@@ -2117,17 +2095,17 @@ UpdateService.prototype = {
       // foreground checks.
       if (!UpdateUtils.OSVersion) {
         AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_OS_VERSION);
       } else if (!UpdateUtils.ABI) {
         AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_OS_ABI);
       } else if (!validUpdateURL) {
         AUSTLMY.pingCheckCode(this._pingSuffix,
                               AUSTLMY.CHK_INVALID_DEFAULT_URL);
-      } else if (!getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true)) {
+      } else if (!Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED, true)) {
         AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_PREF_DISABLED);
       } else if (!hasUpdateMutex()) {
         AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_MUTEX);
       } else if (!gCanCheckForUpdates) {
         AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_UNABLE_TO_CHECK);
       } else if (!this.backgroundChecker._enabled) {
         AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_DISABLED_FOR_SESSION);
       }
@@ -2193,37 +2171,36 @@ UpdateService.prototype = {
           lastCheckCode = AUSTLMY.CHK_UPDATE_INVALID_TYPE;
           break;
       }
     });
 
     let update = minorUpdate || majorUpdate;
     if (AppConstants.platform == "macosx" && update) {
       if (getElevationRequired()) {
-        let installAttemptVersion = getPref("getCharPref",
-                                            PREF_APP_UPDATE_ELEVATE_VERSION,
-                                            null);
+        let installAttemptVersion =
+          Services.prefs.getCharPref(PREF_APP_UPDATE_ELEVATE_VERSION, null);
         if (vc.compare(installAttemptVersion, update.appVersion) != 0) {
           Services.prefs.setCharPref(PREF_APP_UPDATE_ELEVATE_VERSION,
                                      update.appVersion);
           if (Services.prefs.prefHasUserValue(
                 PREF_APP_UPDATE_CANCELATIONS_OSX)) {
             Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
           }
           if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
             Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
           }
         } else {
-          let numCancels = getPref("getIntPref",
-                                   PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
-          let rejectedVersion = getPref("getCharPref",
-                                        PREF_APP_UPDATE_ELEVATE_NEVER, "");
-          let maxCancels = getPref("getIntPref",
-                                   PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
-                                   DEFAULT_CANCELATIONS_OSX_MAX);
+          let numCancels =
+            Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
+          let rejectedVersion =
+            Services.prefs.getCharPref(PREF_APP_UPDATE_ELEVATE_NEVER, "");
+          let maxCancels =
+            Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
+                                      DEFAULT_CANCELATIONS_OSX_MAX);
           if (numCancels >= maxCancels) {
             LOG("UpdateService:selectUpdate - the user requires elevation to " +
                 "install this update, but the user has exceeded the max " +
                 "number of elevation attempts.");
             update.elevationFailure = true;
             AUSTLMY.pingCheckCode(
               this._pingSuffix,
               AUSTLMY.CHK_ELEVATION_DISABLED_FOR_VERSION);
@@ -2271,34 +2248,34 @@ UpdateService.prototype = {
     // is downloading or performed some user action to prevent notification.
     var um = Cc["@mozilla.org/updates/update-manager;1"].
              getService(Ci.nsIUpdateManager);
     if (um.activeUpdate) {
       AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_HAS_ACTIVEUPDATE);
       return;
     }
 
-    var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
+    var updateEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED, true);
     if (!updateEnabled) {
       AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_PREF_DISABLED);
       LOG("UpdateService:_selectAndInstallUpdate - not prompting because " +
           "update is disabled");
       return;
     }
 
     var update = this.selectUpdate(updates, updates.length);
     if (!update || update.elevationFailure) {
       return;
     }
 
     if (update.unsupported) {
       LOG("UpdateService:_selectAndInstallUpdate - update not supported for " +
           "this system. Notifying observers. topic: update-available, " +
           "status: unsupported");
-      if (!getPref("getBoolPref", PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, false)) {
+      if (!Services.prefs.getBoolPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, false)) {
         LOG("UpdateService:_selectAndInstallUpdate - notifying that the " +
             "update is not supported for this system");
         this._showPrompt(update);
       }
 
       Services.obs.notifyObservers(null, "update-available", "unsupported");
       AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_UNSUPPORTED);
       return;
@@ -2325,17 +2302,17 @@ UpdateService.prototype = {
      *    install is disabled and the user will be notified.
      *
      * If the update when it is first read does not have an appVersion attribute
      * the following deprecated behavior will occur:
      * Update Type   Outcome
      * Major         Notify
      * Minor         Auto Install
      */
-    if (!getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true)) {
+    if (!Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO, true)) {
       LOG("UpdateService:_selectAndInstallUpdate - prompting because silent " +
           "install is disabled. Notifying observers. topic: update-available, " +
           "status: show-prompt");
       AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_SHOWPROMPT_PREF);
 
       Services.obs.notifyObservers(update, "update-available", "show-prompt");
       this._showPrompt(update);
       return;
@@ -2823,17 +2800,17 @@ UpdateManager.prototype = {
 
     // Send an observer notification which the app update doorhanger uses to
     // display a restart notification
     LOG("UpdateManager:refreshUpdateStatus - Notifying observers that " +
         "the update was staged. topic: update-staged, status: " + update.state);
     Services.obs.notifyObservers(update, "update-staged", update.state);
 
     // Only prompt when the UI isn't already open.
-    let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
+    let windowType = Services.prefs.getCharPref(PREF_APP_UPDATE_ALTWINDOWTYPE, null);
     if (Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) ||
         windowType && Services.wm.getMostRecentWindow(windowType)) {
       return;
     }
 
     if (update.state == STATE_APPLIED ||
         update.state == STATE_APPLIED_SERVICE ||
         update.state == STATE_PENDING ||
@@ -3160,17 +3137,17 @@ Checker.prototype = {
     this._request = null;
   },
 
   /**
    * Whether or not we are allowed to do update checking.
    */
   _enabled: true,
   get enabled() {
-    return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
+    return Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED, true) &&
            gCanCheckForUpdates && hasUpdateMutex() && this._enabled;
   },
 
   /**
    * See nsIUpdateService.idl
    */
   stopChecking: function UC_stopChecking(duration) {
     // Always stop the current check
@@ -3706,22 +3683,22 @@ class ChannelDownloader extends CommonDo
   _finishDownload(request, context, status) {
     // XXX ehsan shouldShowPrompt should always be false here.
     // But what happens when there is already a UI showing?
     var state = this._patch.state;
     var shouldShowPrompt = false;
     var shouldRegisterOnlineObserver = false;
     var shouldRetrySoon = false;
     var deleteActiveUpdate = false;
-    var retryTimeout = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_RETRYTIMEOUT,
-                               DEFAULT_SOCKET_RETRYTIMEOUT);
+    var retryTimeout = Services.prefs.getIntPref(PREF_APP_UPDATE_SOCKET_RETRYTIMEOUT,
+                                                 DEFAULT_SOCKET_RETRYTIMEOUT);
     // Prevent the preference from setting a value greater than 10000.
     retryTimeout = Math.min(retryTimeout, 10000);
-    var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_MAXERRORS,
-                          DEFAULT_SOCKET_MAX_ERRORS);
+    var maxFail = Services.prefs.getIntPref(PREF_APP_UPDATE_SOCKET_MAXERRORS,
+                                            DEFAULT_SOCKET_MAX_ERRORS);
     // Prevent the preference from setting a value greater than 20.
     maxFail = Math.min(maxFail, 20);
     LOG("ChannelDownloader:finishDownload - status: " + status + ", " +
         "current fail: " + this.updateService._consecutiveSocketErrors + ", " +
         "max fail: " + maxFail + ", " +
         "retryTimeout: " + retryTimeout);
     if (Components.isSuccessCode(status)) {
       if (this._verifyDownload()) {
@@ -3853,21 +3830,21 @@ class ChannelDownloader extends CommonDo
         if (updateStatus == STATE_NONE) {
           cleanupActiveUpdate();
         } else {
           allFailed = false;
         }
       }
 
       if (allFailed) {
-        if (getPref("getBoolPref", PREF_APP_UPDATE_DOORHANGER, false)) {
-          let downloadAttempts = getPref("getIntPref", PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, 0);
+        if (Services.prefs.getBoolPref(PREF_APP_UPDATE_DOORHANGER, false)) {
+          let downloadAttempts = Services.prefs.getIntPref(PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, 0);
           downloadAttempts++;
           Services.prefs.setIntPref(PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, downloadAttempts);
-          let maxAttempts = Math.min(getPref("getIntPref", PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS, 2), 10);
+          let maxAttempts = Math.min(Services.prefs.getIntPref(PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS, 2), 10);
 
           if (downloadAttempts > maxAttempts) {
             LOG("ChannelDownloader:finishDownload - notifying observers of error. " +
                 "topic: update-error, status: download-attempts-exceeded, " +
                 "downloadAttempts: " + downloadAttempts + " " +
                 "maxAttempts: " + maxAttempts);
             Services.obs.notifyObservers(this._update, "update-error", "download-attempts-exceeded");
           } else {
@@ -3994,40 +3971,40 @@ UpdatePrompt.prototype = {
     this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
                  null, null);
   },
 
   /**
    * See nsIUpdateService.idl
    */
   showUpdateAvailable: function UP_showUpdateAvailable(update) {
-    if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
-        getPref("getBoolPref", PREF_APP_UPDATE_DOORHANGER, false) ||
+    if (Services.prefs.getBoolPref(PREF_APP_UPDATE_SILENT, false) ||
+        Services.prefs.getBoolPref(PREF_APP_UPDATE_DOORHANGER, false) ||
         this._getUpdateWindow() || this._getAltUpdateWindow()) {
       return;
     }
 
     this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
                            UPDATE_WINDOW_NAME, "updatesavailable", update);
   },
 
   /**
    * See nsIUpdateService.idl
    */
   showUpdateDownloaded: function UP_showUpdateDownloaded(update, background) {
-    if (background && getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false)) {
+    if (background && Services.prefs.getBoolPref(PREF_APP_UPDATE_SILENT, false)) {
       return;
     }
 
     // Trigger the display of the hamburger doorhanger.
     LOG("showUpdateDownloaded - Notifying observers that " +
         "an update was downloaded. topic: update-downloaded, status: " + update.state);
     Services.obs.notifyObservers(update, "update-downloaded", update.state);
 
-    if (getPref("getBoolPref", PREF_APP_UPDATE_DOORHANGER, false)) {
+    if (Services.prefs.getBoolPref(PREF_APP_UPDATE_DOORHANGER, false)) {
       return;
     }
 
     if (this._getAltUpdateWindow())
       return;
 
     if (background) {
       this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
@@ -4037,18 +4014,18 @@ UpdatePrompt.prototype = {
                    UPDATE_WINDOW_NAME, "finishedBackground", update);
     }
   },
 
   /**
    * See nsIUpdateService.idl
    */
   showUpdateError: function UP_showUpdateError(update) {
-    if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
-        getPref("getBoolPref", PREF_APP_UPDATE_DOORHANGER, false) ||
+    if (Services.prefs.getBoolPref(PREF_APP_UPDATE_SILENT, false) ||
+        Services.prefs.getBoolPref(PREF_APP_UPDATE_DOORHANGER, false) ||
         this._getAltUpdateWindow())
       return;
 
     // In some cases, we want to just show a simple alert dialog.
     if (update.state == STATE_FAILED &&
         WRITE_ERRORS.includes(update.errorCode)) {
       var title = gUpdateBundle.GetStringFromName("updaterIOErrorTitle");
       var text = gUpdateBundle.formatStringFromName("updaterIOErrorMsg",
@@ -4075,17 +4052,17 @@ UpdatePrompt.prototype = {
     this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes",
                  "Update:History", null, null);
   },
 
   /**
    * See nsIUpdateService.idl
    */
   showUpdateElevationRequired: function UP_showUpdateElevationRequired() {
-    if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
+    if (Services.prefs.getBoolPref(PREF_APP_UPDATE_SILENT, false) ||
         this._getAltUpdateWindow()) {
       return;
     }
 
     let um = Cc["@mozilla.org/updates/update-manager;1"].
              getService(Ci.nsIUpdateManager);
     this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
                  UPDATE_WINDOW_NAME, "finishedBackground", um.activeUpdate);
@@ -4099,17 +4076,17 @@ UpdatePrompt.prototype = {
   },
 
   /**
    * Returns an alternative update window if present. When a window with this
    * windowtype is open the application update service won't open the normal
    * application update user interface window.
    */
   _getAltUpdateWindow: function UP__getAltUpdateWindow() {
-    let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
+    let windowType = Services.prefs.getCharPref(PREF_APP_UPDATE_ALTWINDOWTYPE, null);
     if (!windowType)
       return null;
     return Services.wm.getMostRecentWindow(windowType);
   },
 
   /**
    * Display the update UI after the prompt wait time has elapsed.
    * @param   parent
@@ -4151,17 +4128,17 @@ UpdatePrompt.prototype = {
     };
 
     // bug 534090 - show the UI for update available notifications when the
     // the system has been idle for at least IDLE_TIME.
     if (page == "updatesavailable") {
       var idleService = Cc["@mozilla.org/widget/idleservice;1"].
                         getService(Ci.nsIIdleService);
       // Don't allow the preference to set a value greater than 600 seconds for the idle time.
-      const IDLE_TIME = Math.min(getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60), 600);
+      const IDLE_TIME = Math.min(Services.prefs.getIntPref(PREF_APP_UPDATE_IDLETIME, 60), 600);
       if (idleService.idleTime / 1000 >= IDLE_TIME) {
         this._showUI(parent, uri, features, name, page, update);
         return;
       }
     }
 
     observer.service = Services.obs;
     observer.service.addObserver(observer, "quit-application");
@@ -4196,17 +4173,17 @@ UpdatePrompt.prototype = {
    *          Can be null
    */
   _showUIWhenIdle: function UP__showUIWhenIdle(parent, uri, features, name,
                                                page, update) {
     var idleService = Cc["@mozilla.org/widget/idleservice;1"].
                       getService(Ci.nsIIdleService);
 
     // Don't allow the preference to set a value greater than 600 seconds for the idle time.
-    const IDLE_TIME = Math.min(getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60), 600);
+    const IDLE_TIME = Math.min(Services.prefs.getIntPref(PREF_APP_UPDATE_IDLETIME, 60), 600);
     if (idleService.idleTime / 1000 >= IDLE_TIME) {
       this._showUI(parent, uri, features, name, page, update);
     } else {
       var observer = {
         updatePrompt: this,
         observe(aSubject, aTopic, aData) {
           switch (aTopic) {
             case "idle":
--- a/toolkit/themes/shared/non-mac.jar.inc.mn
+++ b/toolkit/themes/shared/non-mac.jar.inc.mn
@@ -8,17 +8,16 @@
 # As a result, the source file paths are relative to the location of the
 # actual manifests.
 
 #include jar.inc.mn
 
   skin/classic/global/customizeToolbar.css                 (../../windows/global/customizeToolbar.css)
   skin/classic/global/datetimepicker.css                   (../../windows/global/datetimepicker.css)
   skin/classic/global/dialog.css                           (../../windows/global/dialog.css)
-  skin/classic/global/expander.css                         (../../windows/global/expander.css)
   skin/classic/global/filefield.css                        (../../windows/global/filefield.css)
   skin/classic/global/progressmeter.css                    (../../windows/global/progressmeter.css)
   skin/classic/global/resizer.css                          (../../windows/global/resizer.css)
   skin/classic/global/richlistbox.css                      (../../windows/global/richlistbox.css)
   skin/classic/global/scrollbars.css                       (../../windows/global/xulscrollbars.css)
   skin/classic/global/spinbuttons.css                      (../../windows/global/spinbuttons.css)
   skin/classic/global/tabprompts.css                       (../../windows/global/tabprompts.css)
   skin/classic/global/wizard.css                           (../../windows/global/wizard.css)
deleted file mode 100644
--- a/toolkit/themes/windows/global/expander.css
+++ /dev/null
@@ -1,14 +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/. */
- 
-.expanderButton {
-  cursor: pointer !important;
-}
-
-.settingsContainer {
-  padding-top: 3px;
-  padding-bottom: 5px;
-  padding-inline-start: 20px;
-  padding-inline-end: 5px;
-}