Backed out 3 changesets (bug 1303879, bug 1303878, bug 1300946) for bustage on a CLOSED TREE.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 03 Oct 2016 22:48:15 -0400
changeset 350530 ca2593daaa91f717e3ae65f049308309a31969a2
parent 350529 eb9d3e28db2a663e94b6396745d846e1b934a000
child 350531 d4f0cc4d2cce1abbb15595cf8902b5a202a9b020
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1303879, 1303878, 1300946
milestone50.0
backs out428c52695888182fe1b40efc2fb00233098639e1
a9dc19ea4d04e04c64b1c53da7a073102d276b80
379b19526d6a16356ab24c41d4d473225887b0f2
Backed out 3 changesets (bug 1303879, bug 1303878, bug 1300946) for bustage on a CLOSED TREE. Backed out changeset 428c52695888 (bug 1300946) Backed out changeset a9dc19ea4d04 (bug 1303879) Backed out changeset 379b19526d6a (bug 1303878)
dom/canvas/WebGL1Context.cpp
dom/canvas/WebGL1Context.h
dom/canvas/WebGL1ContextBuffers.cpp
dom/canvas/WebGL2Context.cpp
dom/canvas/WebGL2Context.h
dom/canvas/WebGL2ContextBuffers.cpp
dom/canvas/WebGL2ContextFramebuffers.cpp
dom/canvas/WebGL2ContextState.cpp
dom/canvas/WebGL2ContextTransformFeedback.cpp
dom/canvas/WebGL2ContextUniforms.cpp
dom/canvas/WebGLBuffer.cpp
dom/canvas/WebGLBuffer.h
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextBuffers.cpp
dom/canvas/WebGLContextDraw.cpp
dom/canvas/WebGLContextFramebufferOperations.cpp
dom/canvas/WebGLContextGL.cpp
dom/canvas/WebGLContextUnchecked.cpp
dom/canvas/WebGLContextUnchecked.h
dom/canvas/WebGLContextValidate.cpp
dom/canvas/WebGLElementArrayCache.cpp
dom/canvas/WebGLElementArrayCache.h
dom/canvas/WebGLFramebuffer.cpp
dom/canvas/WebGLFramebuffer.h
dom/canvas/WebGLObjectModel.h
dom/canvas/WebGLProgram.cpp
dom/canvas/WebGLProgram.h
dom/canvas/WebGLRenderbuffer.cpp
dom/canvas/WebGLRenderbuffer.h
dom/canvas/WebGLShader.cpp
dom/canvas/WebGLShader.h
dom/canvas/WebGLShaderValidator.h
dom/canvas/WebGLTextureUpload.cpp
dom/canvas/WebGLTransformFeedback.cpp
dom/canvas/WebGLTransformFeedback.h
dom/canvas/WebGLVertexAttribData.h
dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp
dom/canvas/moz.build
dom/canvas/test/webgl-conf/generated-mochitest.ini
dom/canvas/test/webgl-conf/mochitest-errata.ini
--- a/dom/canvas/WebGL1Context.cpp
+++ b/dom/canvas/WebGL1Context.cpp
@@ -36,16 +36,17 @@ JSObject*
 WebGL1Context::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
     return dom::WebGLRenderingContextBinding::Wrap(cx, this, givenProto);
 }
 
 bool
 WebGL1Context::ValidateQueryTarget(GLenum target, const char* info)
 {
+    // TODO: Implement this for EXT_disjoint_timer
     return false;
 }
 
 } // namespace mozilla
 
 nsresult
 NS_NewCanvasRenderingContextWebGL(nsIDOMWebGLRenderingContext** out_result)
 {
--- a/dom/canvas/WebGL1Context.h
+++ b/dom/canvas/WebGL1Context.h
@@ -31,15 +31,18 @@ public:
 
     // nsWrapperCache
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
 private:
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type,
                                            uint32_t* alignment,
                                            const char* info) override;
+    virtual bool ValidateBufferTarget(GLenum target, const char* info) override;
+    virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) override;
+    virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) override;
     virtual bool ValidateQueryTarget(GLenum target, const char* info) override;
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) override;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_1_CONTEXT_H_
new file mode 100644
--- /dev/null
+++ b/dom/canvas/WebGL1ContextBuffers.cpp
@@ -0,0 +1,51 @@
+/* -*- 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 "WebGL1Context.h"
+
+namespace mozilla {
+
+// -------------------------------------------------------------------------
+// Buffer objects
+
+/** Target validation for BindBuffer, etc */
+bool
+WebGL1Context::ValidateBufferTarget(GLenum target, const char* info)
+{
+    switch (target) {
+    case LOCAL_GL_ARRAY_BUFFER:
+    case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
+        return true;
+
+    default:
+        ErrorInvalidEnumInfo(info, target);
+        return false;
+    }
+}
+
+bool
+WebGL1Context::ValidateBufferIndexedTarget(GLenum target, const char* info)
+{
+    ErrorInvalidEnumInfo(info, target);
+    return false;
+}
+
+bool
+WebGL1Context::ValidateBufferUsageEnum(GLenum usage, const char* info)
+{
+    switch (usage) {
+    case LOCAL_GL_STREAM_DRAW:
+    case LOCAL_GL_STATIC_DRAW:
+    case LOCAL_GL_DYNAMIC_DRAW:
+        return true;
+    default:
+        break;
+    }
+
+    ErrorInvalidEnumInfo(info, usage);
+    return false;
+}
+
+} // namespace mozilla
--- a/dom/canvas/WebGL2Context.cpp
+++ b/dom/canvas/WebGL2Context.cpp
@@ -144,17 +144,18 @@ WebGLContext::InitWebGL2(FailureReason* 
     }
 
     // we initialise WebGL 2 related stuff.
     gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
                      &mGLMaxTransformFeedbackSeparateAttribs);
     gl->GetUIntegerv(LOCAL_GL_MAX_UNIFORM_BUFFER_BINDINGS,
                      &mGLMaxUniformBufferBindings);
 
-    mIndexedUniformBufferBindings.resize(mGLMaxUniformBufferBindings);
+    mBoundTransformFeedbackBuffers.SetLength(mGLMaxTransformFeedbackSeparateAttribs);
+    mBoundUniformBuffers.SetLength(mGLMaxUniformBufferBindings);
 
     mDefaultTransformFeedback = new WebGLTransformFeedback(this, 0);
     mBoundTransformFeedback = mDefaultTransformFeedback;
 
     if (!gl->IsGLES()) {
         // Desktop OpenGL requires the following to be enabled in order to
         // support sRGB operations on framebuffers.
         gl->MakeCurrent();
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -268,30 +268,31 @@ public:
     void VertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w);
     void VertexAttribI4iv(GLuint index, const dom::Sequence<GLint>& v);
     void VertexAttribI4ui(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w);
     void VertexAttribI4uiv(GLuint index, const dom::Sequence<GLuint>& v);
 
 
     // -------------------------------------------------------------------------
     // Writing to the drawing buffer
-
-    /* Implemented in WebGLContext
+    // TODO(djg): Implemented in WebGLContext
+/*
     void VertexAttribDivisor(GLuint index, GLuint divisor);
     void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount);
     void DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, GLintptr offset, GLsizei instanceCount);
-    */
+*/
     void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, GLintptr offset);
 
 
     // ------------------------------------------------------------------------
     // Multiple Render Targets - WebGL2ContextMRTs.cpp
-    /* Implemented in WebGLContext
+    // TODO(djg): Implemented in WebGLContext
+/*
     void DrawBuffers(const dom::Sequence<GLenum>& buffers);
-    */
+*/
 
     void ClearBufferiv_base(GLenum buffer, GLint drawbuffer, const GLint* value);
     void ClearBufferuiv_base(GLenum buffer, GLint drawbuffer, const GLuint* value);
     void ClearBufferfv_base(GLenum buffer, GLint drawbuffer, const GLfloat* value);
 
     void ClearBufferiv(GLenum buffer, GLint drawbuffer, const dom::Int32Array& value);
     void ClearBufferiv(GLenum buffer, GLint drawbuffer, const dom::Sequence<GLint>& value);
     void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const dom::Uint32Array& value);
@@ -406,15 +407,18 @@ private:
 
     void UpdateBoundQuery(GLenum target, WebGLQuery* query);
 
     // CreateVertexArrayImpl is assumed to be infallible.
     virtual WebGLVertexArray* CreateVertexArrayImpl() override;
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type,
                                            uint32_t* alignment,
                                            const char* info) override;
+    virtual bool ValidateBufferTarget(GLenum target, const char* info) override;
+    virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) override;
+    virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) override;
     virtual bool ValidateQueryTarget(GLenum target, const char* info) override;
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) override;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/canvas/WebGL2ContextBuffers.cpp
+++ b/dom/canvas/WebGL2ContextBuffers.cpp
@@ -6,149 +6,230 @@
 #include "WebGL2Context.h"
 
 #include "GLContext.h"
 #include "WebGLBuffer.h"
 #include "WebGLTransformFeedback.h"
 
 namespace mozilla {
 
+bool
+WebGL2Context::ValidateBufferTarget(GLenum target, const char* funcName)
+{
+    switch (target) {
+    case LOCAL_GL_ARRAY_BUFFER:
+    case LOCAL_GL_COPY_READ_BUFFER:
+    case LOCAL_GL_COPY_WRITE_BUFFER:
+    case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
+    case LOCAL_GL_PIXEL_PACK_BUFFER:
+    case LOCAL_GL_PIXEL_UNPACK_BUFFER:
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
+    case LOCAL_GL_UNIFORM_BUFFER:
+        return true;
+
+    default:
+        ErrorInvalidEnumInfo(funcName, target);
+        return false;
+    }
+}
+
+bool
+WebGL2Context::ValidateBufferIndexedTarget(GLenum target, const char* info)
+{
+    switch (target) {
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
+    case LOCAL_GL_UNIFORM_BUFFER:
+        return true;
+
+    default:
+        ErrorInvalidEnumInfo(info, target);
+        return false;
+    }
+}
+
+bool
+WebGL2Context::ValidateBufferUsageEnum(GLenum usage, const char* info)
+{
+    switch (usage) {
+    case LOCAL_GL_DYNAMIC_COPY:
+    case LOCAL_GL_DYNAMIC_DRAW:
+    case LOCAL_GL_DYNAMIC_READ:
+    case LOCAL_GL_STATIC_COPY:
+    case LOCAL_GL_STATIC_DRAW:
+    case LOCAL_GL_STATIC_READ:
+    case LOCAL_GL_STREAM_COPY:
+    case LOCAL_GL_STREAM_DRAW:
+    case LOCAL_GL_STREAM_READ:
+        return true;
+    default:
+        break;
+    }
+
+    ErrorInvalidEnumInfo(info, usage);
+    return false;
+}
+
 // -------------------------------------------------------------------------
 // Buffer objects
 
 void
 WebGL2Context::CopyBufferSubData(GLenum readTarget, GLenum writeTarget,
                                  GLintptr readOffset, GLintptr writeOffset,
                                  GLsizeiptr size)
 {
     const char funcName[] = "copyBufferSubData";
     if (IsContextLost())
         return;
 
-    const auto& readBuffer = ValidateBufferSelection(funcName, readTarget);
-    if (!readBuffer)
-        return;
-
-    const auto& writeBuffer = ValidateBufferSelection(funcName, writeTarget);
-    if (!writeBuffer)
-        return;
-
-    if (readBuffer->mNumActiveTFOs ||
-        writeBuffer->mNumActiveTFOs)
-    {
-        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                              " object.",
-                              funcName);
-        return;
-    }
-
-    if (!ValidateNonNegative(funcName, "readOffset", readOffset) ||
-        !ValidateNonNegative(funcName, "writeOffset", writeOffset) ||
-        !ValidateNonNegative(funcName, "size", size))
+    if (!ValidateBufferTarget(readTarget, funcName) ||
+        !ValidateBufferTarget(writeTarget, funcName))
     {
         return;
     }
 
-    const auto fnValidateOffsetSize = [&](const char* info, GLintptr offset,
-                                          const WebGLBuffer* buffer)
-    {
-        const auto neededBytes = CheckedInt<size_t>(offset) + size;
-        if (!neededBytes.isValid() || neededBytes.value() > buffer->ByteLength()) {
-            ErrorInvalidValue("%s: Invalid %s range.", funcName, info);
-            return false;
-        }
-        return true;
-    };
+    const WebGLRefPtr<WebGLBuffer>& readBufferSlot = GetBufferSlotByTarget(readTarget);
+    const WebGLRefPtr<WebGLBuffer>& writeBufferSlot = GetBufferSlotByTarget(writeTarget);
+    if (!readBufferSlot || !writeBufferSlot)
+        return;
 
-    if (!fnValidateOffsetSize("read", readOffset, readBuffer) ||
-        !fnValidateOffsetSize("write", writeOffset, writeBuffer))
-    {
+    const WebGLBuffer* readBuffer = readBufferSlot.get();
+    if (!readBuffer) {
+        ErrorInvalidOperation("%s: No buffer bound to readTarget.", funcName);
         return;
     }
 
-    if (readBuffer == writeBuffer &&
+    WebGLBuffer* writeBuffer = writeBufferSlot.get();
+    if (!writeBuffer) {
+        ErrorInvalidOperation("%s: No buffer bound to writeTarget.", funcName);
+        return;
+    }
+
+    if (!ValidateDataOffsetSize(readOffset, size, readBuffer->ByteLength(), funcName))
+        return;
+
+    if (!ValidateDataOffsetSize(writeOffset, size, writeBuffer->ByteLength(), funcName))
+        return;
+
+    if (readTarget == writeTarget &&
         !ValidateDataRanges(readOffset, writeOffset, size, funcName))
     {
         return;
     }
 
-    const auto& readType = readBuffer->Content();
-    const auto& writeType = writeBuffer->Content();
-    MOZ_ASSERT(readType != WebGLBuffer::Kind::Undefined);
-    MOZ_ASSERT(writeType != WebGLBuffer::Kind::Undefined);
-    if (writeType != readType) {
+    WebGLBuffer::Kind readType = readBuffer->Content();
+    WebGLBuffer::Kind writeType = writeBuffer->Content();
+
+    if (readType != WebGLBuffer::Kind::Undefined &&
+        writeType != WebGLBuffer::Kind::Undefined &&
+        writeType != readType)
+    {
         ErrorInvalidOperation("%s: Can't copy %s data to %s data.",
                               funcName,
                               (readType == WebGLBuffer::Kind::OtherData) ? "other"
                                                                          : "element",
                               (writeType == WebGLBuffer::Kind::OtherData) ? "other"
                                                                           : "element");
         return;
     }
 
-    gl->MakeCurrent();
-    gl->fCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size);
+    WebGLContextUnchecked::CopyBufferSubData(readTarget, writeTarget, readOffset,
+                                             writeOffset, size);
+
+    if (writeType == WebGLBuffer::Kind::Undefined) {
+        writeBuffer->BindTo(
+            (readType == WebGLBuffer::Kind::OtherData) ? LOCAL_GL_ARRAY_BUFFER
+                                                       : LOCAL_GL_ELEMENT_ARRAY_BUFFER);
+    }
 }
 
 void
 WebGL2Context::GetBufferSubData(GLenum target, GLintptr offset,
                                 const dom::ArrayBufferView& data)
 {
     const char funcName[] = "getBufferSubData";
     if (IsContextLost())
         return;
 
-    if (!ValidateNonNegative(funcName, "offset", offset))
+    // For the WebGLBuffer bound to the passed target, read
+    // returnedData.byteLength bytes from the buffer starting at byte
+    // offset offset and write them to returnedData.
+
+    // If zero is bound to target, an INVALID_OPERATION error is
+    // generated.
+    if (!ValidateBufferTarget(target, funcName))
         return;
 
-    const auto& buffer = ValidateBufferSelection(funcName, target);
-    if (!buffer)
+    // If offset is less than zero, an INVALID_VALUE error is
+    // generated.
+    if (offset < 0) {
+        ErrorInvalidValue("%s: Offset must be non-negative.", funcName);
         return;
+    }
 
-    ////
+    WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target);
+    WebGLBuffer* boundBuffer = bufferSlot.get();
+    if (!boundBuffer) {
+        ErrorInvalidOperation("%s: No buffer bound.", funcName);
+        return;
+    }
 
     // If offset + returnedData.byteLength would extend beyond the end
     // of the buffer an INVALID_VALUE error is generated.
     data.ComputeLengthAndData();
 
-    const auto neededByteLength = CheckedInt<size_t>(offset) + data.LengthAllowShared();
+    CheckedInt<WebGLsizeiptr> neededByteLength = CheckedInt<WebGLsizeiptr>(offset) + data.LengthAllowShared();
     if (!neededByteLength.isValid()) {
         ErrorInvalidValue("%s: Integer overflow computing the needed byte length.",
                           funcName);
         return;
     }
 
-    if (neededByteLength.value() > buffer->ByteLength()) {
+    if (neededByteLength.value() > boundBuffer->ByteLength()) {
         ErrorInvalidValue("%s: Not enough data. Operation requires %d bytes, but buffer"
                           " only has %d bytes.",
-                          funcName, neededByteLength.value(), buffer->ByteLength());
-        return;
-    }
-
-    ////
-
-    if (buffer->mNumActiveTFOs) {
-        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                              " object.",
-                              funcName);
+                          funcName, neededByteLength.value(), boundBuffer->ByteLength());
         return;
     }
 
-    if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER &&
-        mBoundTransformFeedback->mIsActive)
-    {
-        ErrorInvalidOperation("%s: Currently bound transform feedback is active.",
-                              funcName);
-        return;
+    // If target is TRANSFORM_FEEDBACK_BUFFER, and any transform
+    // feedback object is currently active, an INVALID_OPERATION error
+    // is generated.
+    WebGLTransformFeedback* currentTF = mBoundTransformFeedback;
+    if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && currentTF) {
+        if (currentTF->mIsActive) {
+            ErrorInvalidOperation("%s: Currently bound transform feedback is active.",
+                                  funcName);
+            return;
+        }
+
+        // https://github.com/NVIDIA/WebGL/commit/63aff5e58c1d79825a596f0f4aa46174b9a5f72c
+        // Performing reads and writes on a buffer that is currently
+        // bound for transform feedback causes undefined results in
+        // GLES3.0 and OpenGL 4.5. In practice results of reads and
+        // writes might be consistent as long as transform feedback
+        // objects are not active, but neither GLES3.0 nor OpenGL 4.5
+        // spec guarantees this - just being bound for transform
+        // feedback is sufficient to cause undefined results.
+
+        BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
     }
 
+    /* If the buffer is written and read sequentially by other
+     * operations and getBufferSubData, it is the responsibility of
+     * the WebGL API to ensure that data are access
+     * consistently. This applies even if the buffer is currently
+     * bound to a transform feedback binding point.
+     */
+
+    void* ptr = gl->fMapBufferRange(target, offset, data.LengthAllowShared(),
+                                    LOCAL_GL_MAP_READ_BIT);
+    // Warning: Possibly shared memory.  See bug 1225033.
+    memcpy(data.DataAllowShared(), ptr, data.LengthAllowShared());
+    gl->fUnmapBuffer(target);
+
     ////
 
-    gl->MakeCurrent();
-
-    const auto ptr = gl->fMapBufferRange(target, offset, data.LengthAllowShared(),
-                                         LOCAL_GL_MAP_READ_BIT);
-    // Warning: Possibly shared memory.  See bug 1225033.
-    memcpy(data.DataAllowShared(), ptr, data.LengthAllowShared());
-    gl->fUnmapBuffer(target);
+    if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && currentTF) {
+        BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, currentTF);
+    }
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextFramebuffers.cpp
+++ b/dom/canvas/WebGL2ContextFramebuffers.cpp
@@ -8,16 +8,81 @@
 #include "GLContext.h"
 #include "GLScreenBuffer.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFormats.h"
 #include "WebGLFramebuffer.h"
 
 namespace mozilla {
 
+static bool
+GetFBInfoForBlit(const WebGLFramebuffer* fb, const char* const fbInfo,
+                 GLsizei* const out_samples,
+                 const webgl::FormatInfo** const out_colorFormat,
+                 const webgl::FormatInfo** const out_depthFormat,
+                 const webgl::FormatInfo** const out_stencilFormat)
+{
+    *out_samples = 0;
+    *out_colorFormat = nullptr;
+    *out_depthFormat = nullptr;
+    *out_stencilFormat = nullptr;
+
+    if (fb->ColorAttachment(0).IsDefined()) {
+        const auto& attachment = fb->ColorAttachment(0);
+        *out_samples = attachment.Samples();
+        *out_colorFormat = attachment.Format()->format;
+    }
+
+    if (fb->DepthStencilAttachment().IsDefined()) {
+        const auto& attachment = fb->DepthStencilAttachment();
+        *out_samples = attachment.Samples();
+
+        *out_depthFormat = attachment.Format()->format;
+        *out_stencilFormat = *out_depthFormat;
+    } else {
+        if (fb->DepthAttachment().IsDefined()) {
+            const auto& attachment = fb->DepthAttachment();
+            *out_samples = attachment.Samples();
+            *out_depthFormat = attachment.Format()->format;
+        }
+
+        if (fb->StencilAttachment().IsDefined()) {
+            const auto& attachment = fb->StencilAttachment();
+            *out_samples = attachment.Samples();
+            *out_stencilFormat = attachment.Format()->format;
+        }
+    }
+    return true;
+}
+
+static void
+GetBackbufferFormats(const WebGLContextOptions& options,
+                     const webgl::FormatInfo** const out_color,
+                     const webgl::FormatInfo** const out_depth,
+                     const webgl::FormatInfo** const out_stencil)
+{
+    const auto effFormat = options.alpha ? webgl::EffectiveFormat::RGBA8
+                                          : webgl::EffectiveFormat::RGB8;
+    *out_color = webgl::GetFormat(effFormat);
+
+    *out_depth = nullptr;
+    *out_stencil = nullptr;
+    if (options.depth && options.stencil) {
+        *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH24_STENCIL8);
+        *out_stencil = *out_depth;
+    } else {
+        if (options.depth) {
+            *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT16);
+        }
+        if (options.stencil) {
+            *out_stencil = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8);
+        }
+    }
+}
+
 void
 WebGL2Context::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
                                GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                                GLbitfield mask, GLenum filter)
 {
     if (IsContextLost())
         return;
 
@@ -33,70 +98,269 @@ WebGL2Context::BlitFramebuffer(GLint src
     case LOCAL_GL_NEAREST:
     case LOCAL_GL_LINEAR:
         break;
     default:
         ErrorInvalidEnumInfo("blitFramebuffer: Bad `filter`:", filter);
         return;
     }
 
-    ////
+    const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT |
+                                           LOCAL_GL_STENCIL_BUFFER_BIT;
+    if (mask & depthAndStencilBits &&
+        filter != LOCAL_GL_NEAREST)
+    {
+        ErrorInvalidOperation("blitFramebuffer: DEPTH_BUFFER_BIT and"
+                              " STENCIL_BUFFER_BIT can only be used with"
+                              " NEAREST filtering.");
+        return;
+    }
 
-    const auto& readFB = mBoundReadFramebuffer;
-    if (readFB &&
-        !readFB->ValidateAndInitAttachments("blitFramebuffer's READ_FRAMEBUFFER"))
-    {
+    if (mBoundReadFramebuffer == mBoundDrawFramebuffer) {
+        // TODO: It's actually more complicated than this. We need to check that
+        // the underlying buffers are not the same, not the framebuffers
+        // themselves.
+        ErrorInvalidOperation("blitFramebuffer: Source and destination must"
+                              " differ.");
         return;
     }
 
-    const auto& drawFB = mBoundDrawFramebuffer;
-    if (drawFB &&
-        !drawFB->ValidateAndInitAttachments("blitFramebuffer's DRAW_FRAMEBUFFER"))
+    GLsizei srcSamples;
+    const webgl::FormatInfo* srcColorFormat = nullptr;
+    const webgl::FormatInfo* srcDepthFormat = nullptr;
+    const webgl::FormatInfo* srcStencilFormat = nullptr;
+
+    if (mBoundReadFramebuffer) {
+        if (!mBoundReadFramebuffer->ValidateAndInitAttachments("blitFramebuffer's READ_FRAMEBUFFER"))
+            return;
+
+        if (!GetFBInfoForBlit(mBoundReadFramebuffer, "READ_FRAMEBUFFER", &srcSamples,
+                              &srcColorFormat, &srcDepthFormat, &srcStencilFormat))
+        {
+            return;
+        }
+    } else {
+        srcSamples = 0; // Always 0.
+
+        GetBackbufferFormats(mOptions, &srcColorFormat, &srcDepthFormat,
+                             &srcStencilFormat);
+    }
+
+    GLsizei dstSamples;
+    const webgl::FormatInfo* dstColorFormat = nullptr;
+    const webgl::FormatInfo* dstDepthFormat = nullptr;
+    const webgl::FormatInfo* dstStencilFormat = nullptr;
+
+    if (mBoundDrawFramebuffer) {
+        if (!mBoundDrawFramebuffer->ValidateAndInitAttachments("blitFramebuffer's DRAW_FRAMEBUFFER"))
+            return;
+
+        if (!GetFBInfoForBlit(mBoundDrawFramebuffer, "DRAW_FRAMEBUFFER", &dstSamples,
+                              &dstColorFormat, &dstDepthFormat, &dstStencilFormat))
+        {
+            return;
+        }
+    } else {
+        dstSamples = gl->Screen()->Samples();
+
+        GetBackbufferFormats(mOptions, &dstColorFormat, &dstDepthFormat,
+                             &dstStencilFormat);
+    }
+
+    if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
+        const auto fnSignlessType = [](const webgl::FormatInfo* format)
+                                    -> webgl::ComponentType
+        {
+            if (!format)
+                return webgl::ComponentType::None;
+
+            switch (format->componentType) {
+            case webgl::ComponentType::UInt:
+                return webgl::ComponentType::Int;
+
+            case webgl::ComponentType::NormUInt:
+                return webgl::ComponentType::NormInt;
+
+            default:
+                return format->componentType;
+            }
+        };
+
+        const auto srcType = fnSignlessType(srcColorFormat);
+        const auto dstType = fnSignlessType(dstColorFormat);
+
+        if (srcType != dstType) {
+            ErrorInvalidOperation("blitFramebuffer: Color buffer format component type"
+                                  " mismatch.");
+            return;
+        }
+
+        const bool srcIsInt = (srcType == webgl::ComponentType::Int);
+        if (srcIsInt && filter != LOCAL_GL_NEAREST) {
+            ErrorInvalidOperation("blitFramebuffer: Integer read buffers can only"
+                                  " be filtered with NEAREST.");
+            return;
+        }
+    }
+
+    /* GLES 3.0.4, p199:
+     *   Calling BlitFramebuffer will result in an INVALID_OPERATION error if
+     *   mask includes DEPTH_BUFFER_BIT or STENCIL_BUFFER_BIT, and the source
+     *   and destination depth and stencil buffer formats do not match.
+     *
+     * jgilbert: The wording is such that if only DEPTH_BUFFER_BIT is specified,
+     * the stencil formats must match. This seems wrong. It could be a spec bug,
+     * or I could be missing an interaction in one of the earlier paragraphs.
+     */
+    if (mask & LOCAL_GL_DEPTH_BUFFER_BIT &&
+        dstDepthFormat != srcDepthFormat)
     {
+        ErrorInvalidOperation("blitFramebuffer: Depth buffer formats must match"
+                              " if selected.");
         return;
     }
 
-    ////
+    if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
+        dstStencilFormat != srcStencilFormat)
+    {
+        ErrorInvalidOperation("blitFramebuffer: Stencil buffer formats must"
+                              " match if selected.");
+        return;
+    }
+
+    if (dstSamples != 0) {
+        ErrorInvalidOperation("blitFramebuffer: DRAW_FRAMEBUFFER may not have"
+                              " multiple samples.");
+        return;
+    }
+
+    if (srcSamples != 0) {
+        if (mask & LOCAL_GL_COLOR_BUFFER_BIT &&
+            dstColorFormat != srcColorFormat)
+        {
+            ErrorInvalidOperation("blitFramebuffer: Color buffer formats must"
+                                  " match if selected, when reading from a"
+                                  " multisampled source.");
+            return;
+        }
 
-    WebGLFramebuffer::BlitFramebuffer(this,
-                                      readFB, srcX0, srcY0, srcX1, srcY1,
-                                      drawFB, dstX0, dstY0, dstX1, dstY1,
-                                      mask, filter);
+        if (dstX0 != srcX0 ||
+            dstX1 != srcX1 ||
+            dstY0 != srcY0 ||
+            dstY1 != srcY1)
+        {
+            ErrorInvalidOperation("blitFramebuffer: If the source is"
+                                  " multisampled, then the source and dest"
+                                  " regions must match exactly.");
+            return;
+        }
+    }
+
+    MakeContextCurrent();
+    gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1,
+                         dstX0, dstY0, dstX1, dstY1,
+                         mask, filter);
+}
+
+static bool
+ValidateTextureLayerAttachment(GLenum attachment)
+{
+    if (LOCAL_GL_COLOR_ATTACHMENT0 <= attachment &&
+        attachment <= LOCAL_GL_COLOR_ATTACHMENT15)
+    {
+        return true;
+    }
+
+    switch (attachment) {
+    case LOCAL_GL_DEPTH_ATTACHMENT:
+    case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
+    case LOCAL_GL_STENCIL_ATTACHMENT:
+        return true;
+    }
+
+    return false;
 }
 
 void
 WebGL2Context::FramebufferTextureLayer(GLenum target, GLenum attachment,
                                        WebGLTexture* texture, GLint level, GLint layer)
 {
-    const char funcName[] = "framebufferTextureLayer";
     if (IsContextLost())
         return;
 
-    if (!ValidateFramebufferTarget(target, funcName))
+    if (!ValidateFramebufferTarget(target, "framebufferTextureLayer"))
         return;
 
+    if (!ValidateTextureLayerAttachment(attachment))
+        return ErrorInvalidEnumInfo("framebufferTextureLayer: attachment:", attachment);
+
+    if (texture) {
+        if (texture->IsDeleted()) {
+            return ErrorInvalidValue("framebufferTextureLayer: texture must be a valid "
+                                     "texture object.");
+        }
+
+        if (layer < 0)
+            return ErrorInvalidValue("framebufferTextureLayer: layer must be >= 0.");
+
+        if (level < 0)
+            return ErrorInvalidValue("framebufferTextureLayer: level must be >= 0.");
+
+        switch (texture->Target().get()) {
+        case LOCAL_GL_TEXTURE_3D:
+            if (uint32_t(layer) >= mImplMax3DTextureSize) {
+                return ErrorInvalidValue("framebufferTextureLayer: layer must be < "
+                                         "MAX_3D_TEXTURE_SIZE");
+            }
+
+            if (uint32_t(level) > FloorLog2(mImplMax3DTextureSize)) {
+                return ErrorInvalidValue("framebufferTextureLayer: layer mube be <= "
+                                         "log2(MAX_3D_TEXTURE_SIZE");
+            }
+            break;
+
+        case LOCAL_GL_TEXTURE_2D_ARRAY:
+            if (uint32_t(layer) >= mImplMaxArrayTextureLayers) {
+                return ErrorInvalidValue("framebufferTextureLayer: layer must be < "
+                                         "MAX_ARRAY_TEXTURE_LAYERS");
+            }
+
+            if (uint32_t(level) > FloorLog2(mImplMaxTextureSize)) {
+                return ErrorInvalidValue("framebufferTextureLayer: layer mube be <= "
+                                         "log2(MAX_TEXTURE_SIZE");
+            }
+            break;
+
+        default:
+            return ErrorInvalidOperation("framebufferTextureLayer: texture must be an "
+                                         "existing 3D texture, or a 2D texture array.");
+        }
+    }
+
     WebGLFramebuffer* fb;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         break;
 
     case LOCAL_GL_READ_FRAMEBUFFER:
         fb = mBoundReadFramebuffer;
         break;
 
     default:
         MOZ_CRASH("GFX: Bad target.");
     }
 
-    if (!fb)
-        return ErrorInvalidOperation("%a: Xannot modify framebuffer 0.");
+    if (!fb) {
+        return ErrorInvalidOperation("framebufferTextureLayer: cannot modify"
+                                     " framebuffer 0.");
+    }
 
-    fb->FramebufferTextureLayer(funcName, attachment, texture, level, layer);
+    fb->FramebufferTextureLayer(attachment, texture, level, layer);
 }
 
 JS::Value
 WebGL2Context::GetFramebufferAttachmentParameter(JSContext* cx,
                                                  GLenum target,
                                                  GLenum attachment,
                                                  GLenum pname,
                                                  ErrorResult& out_error)
@@ -263,31 +527,51 @@ WebGL2Context::InvalidateSubFramebuffer(
         gl->fInvalidateSubFramebuffer(target, attachments.Length(),
                                       attachments.Elements(), x, y, width, height);
     }
 }
 
 void
 WebGL2Context::ReadBuffer(GLenum mode)
 {
-    const char funcName[] = "readBuffer";
     if (IsContextLost())
         return;
 
+    const bool isColorAttachment = (mode >= LOCAL_GL_COLOR_ATTACHMENT0 &&
+                                    mode <= LastColorAttachmentEnum());
+
+    if (mode != LOCAL_GL_NONE && mode != LOCAL_GL_BACK && !isColorAttachment) {
+        ErrorInvalidEnum("readBuffer: `mode` must be one of NONE, BACK, or "
+                         "COLOR_ATTACHMENTi. Was %s",
+                         EnumName(mode));
+        return;
+    }
+
     if (mBoundReadFramebuffer) {
-        mBoundReadFramebuffer->ReadBuffer(funcName, mode);
+        if (mode != LOCAL_GL_NONE &&
+            !isColorAttachment)
+        {
+            ErrorInvalidOperation("readBuffer: If READ_FRAMEBUFFER is non-null, `mode` "
+                                  "must be COLOR_ATTACHMENTi or NONE. Was %s",
+                                  EnumName(mode));
+            return;
+        }
+
+        MakeContextCurrent();
+        mBoundReadFramebuffer->SetReadBufferMode(mode);
+        gl->fReadBuffer(mode);
         return;
     }
 
     // Operating on the default framebuffer.
     if (mode != LOCAL_GL_NONE &&
         mode != LOCAL_GL_BACK)
     {
-        ErrorInvalidOperation("%s: If READ_FRAMEBUFFER is null, `mode` must be BACK or"
-                              " NONE. Was %s",
-                              funcName, EnumName(mode));
+        ErrorInvalidOperation("readBuffer: If READ_FRAMEBUFFER is null, `mode`"
+                              " must be BACK or NONE. Was %s",
+                              EnumName(mode));
         return;
     }
 
     gl->Screen()->SetReadBuffer(mode);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextState.cpp
+++ b/dom/canvas/WebGL2ContextState.cpp
@@ -28,36 +28,30 @@ WebGL2Context::GetParameter(JSContext* c
     return JS::NullValue();
 
   MakeContextCurrent();
 
   switch (pname) {
     /* GLboolean */
     case LOCAL_GL_RASTERIZER_DISCARD:
     case LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE:
-    case LOCAL_GL_SAMPLE_COVERAGE: {
+    case LOCAL_GL_SAMPLE_COVERAGE:
+    case LOCAL_GL_TRANSFORM_FEEDBACK_PAUSED:
+    case LOCAL_GL_TRANSFORM_FEEDBACK_ACTIVE: {
       realGLboolean b = 0;
       gl->fGetBooleanv(pname, &b);
       return JS::BooleanValue(bool(b));
     }
 
-    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());
+      if (mBoundReadFramebuffer)
+        return JS::Int32Value(mBoundReadFramebuffer->ReadBufferMode());
 
-      if (!mBoundReadFramebuffer->ColorReadBuffer())
-        return JS::Int32Value(LOCAL_GL_NONE);
-
-      return JS::Int32Value(mBoundReadFramebuffer->ColorReadBuffer()->mAttachmentPoint);
+      return JS::Int32Value(LOCAL_GL_BACK);
     }
 
     case LOCAL_GL_FRAGMENT_SHADER_DERIVATIVE_HINT:
       /* fall through */
 
     /* GLint */
     case LOCAL_GL_MAX_COMBINED_UNIFORM_BLOCKS:
     case LOCAL_GL_MAX_ELEMENTS_INDICES:
@@ -147,20 +141,17 @@ WebGL2Context::GetParameter(JSContext* c
 
     case LOCAL_GL_PIXEL_PACK_BUFFER_BINDING:
       return WebGLObjectAsJSValue(cx, mBoundPixelPackBuffer.get(), rv);
 
     case LOCAL_GL_PIXEL_UNPACK_BUFFER_BINDING:
       return WebGLObjectAsJSValue(cx, mBoundPixelUnpackBuffer.get(), rv);
 
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
-      {
-        const auto& tf = mBoundTransformFeedback;
-        return WebGLObjectAsJSValue(cx, tf->mGenericBufferBinding.get(), rv);
-      }
+      return WebGLObjectAsJSValue(cx, mBoundTransformFeedbackBuffer.get(), rv);
 
     case LOCAL_GL_UNIFORM_BUFFER_BINDING:
       return WebGLObjectAsJSValue(cx, mBoundUniformBuffer.get(), rv);
 
     // DRAW_FRAMEBUFFER_BINDING is the same as FRAMEBUFFER_BINDING.
     case LOCAL_GL_READ_FRAMEBUFFER_BINDING:
       return WebGLObjectAsJSValue(cx, mBoundReadFramebuffer.get(), rv);
 
@@ -168,24 +159,21 @@ WebGL2Context::GetParameter(JSContext* c
       return WebGLObjectAsJSValue(cx, mBoundSamplers[mActiveTexture].get(), rv);
 
     case LOCAL_GL_TEXTURE_BINDING_2D_ARRAY:
       return WebGLObjectAsJSValue(cx, mBound2DArrayTextures[mActiveTexture].get(), rv);
 
     case LOCAL_GL_TEXTURE_BINDING_3D:
       return WebGLObjectAsJSValue(cx, mBound3DTextures[mActiveTexture].get(), rv);
 
-    case LOCAL_GL_TRANSFORM_FEEDBACK_BINDING:
-      {
-        const WebGLTransformFeedback* tf = mBoundTransformFeedback;
-        if (tf == mDefaultTransformFeedback) {
-          tf = nullptr;
-        }
-        return WebGLObjectAsJSValue(cx, tf, rv);
-      }
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BINDING: {
+      WebGLTransformFeedback* tf =
+        (mBoundTransformFeedback != mDefaultTransformFeedback) ? mBoundTransformFeedback.get() : nullptr;
+      return WebGLObjectAsJSValue(cx, tf, rv);
+    }
 
     case LOCAL_GL_VERTEX_ARRAY_BINDING: {
       WebGLVertexArray* vao =
         (mBoundVertexArray != mDefaultVertexArray) ? mBoundVertexArray.get() : nullptr;
       return WebGLObjectAsJSValue(cx, vao, rv);
     }
 
     case LOCAL_GL_VERSION:
--- a/dom/canvas/WebGL2ContextTransformFeedback.cpp
+++ b/dom/canvas/WebGL2ContextTransformFeedback.cpp
@@ -15,42 +15,38 @@ namespace mozilla {
 // Transform Feedback
 
 already_AddRefed<WebGLTransformFeedback>
 WebGL2Context::CreateTransformFeedback()
 {
     if (IsContextLost())
         return nullptr;
 
+    GLuint tf = 0;
     MakeContextCurrent();
-    GLuint tf = 0;
     gl->fGenTransformFeedbacks(1, &tf);
 
-    RefPtr<WebGLTransformFeedback> ret = new WebGLTransformFeedback(this, tf);
-    return ret.forget();
+    RefPtr<WebGLTransformFeedback> globj = new WebGLTransformFeedback(this, tf);
+    return globj.forget();
 }
 
 void
 WebGL2Context::DeleteTransformFeedback(WebGLTransformFeedback* tf)
 {
-    const char funcName[] = "deleteTransformFeedback";
     if (IsContextLost())
         return;
 
-    if (!ValidateObject(funcName, tf))
+    if (!ValidateObjectAllowDeletedOrNull("deleteTransformFeedback", tf))
         return;
 
-    if (tf->mIsActive) {
-        ErrorInvalidOperation("%s: Cannot delete active transform feedbacks.", funcName);
+    if (!tf || tf->IsDeleted())
         return;
-    }
 
-    if (mBoundTransformFeedback == tf) {
+    if (mBoundTransformFeedback == tf)
         BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
-    }
 
     tf->RequestDelete();
 }
 
 bool
 WebGL2Context::IsTransformFeedback(WebGLTransformFeedback* tf)
 {
     if (IsContextLost())
@@ -64,80 +60,139 @@ WebGL2Context::IsTransformFeedback(WebGL
 
     MakeContextCurrent();
     return gl->fIsTransformFeedback(tf->mGLName);
 }
 
 void
 WebGL2Context::BindTransformFeedback(GLenum target, WebGLTransformFeedback* tf)
 {
-    const char funcName[] = "bindTransformFeedback";
     if (IsContextLost())
         return;
 
+    if (!ValidateObjectAllowDeletedOrNull("bindTransformFeedback", tf))
+        return;
+
     if (target != LOCAL_GL_TRANSFORM_FEEDBACK)
-        return ErrorInvalidEnum("%s: `target` must be TRANSFORM_FEEDBACK.", funcName);
+        return ErrorInvalidEnum("bindTransformFeedback: target must be TRANSFORM_FEEDBACK");
 
-    if (!ValidateObjectAllowDeletedOrNull(funcName, tf))
-        return;
+    WebGLRefPtr<WebGLTransformFeedback> currentTF = mBoundTransformFeedback;
+    if (currentTF && currentTF->mIsActive && !currentTF->mIsPaused) {
+        return ErrorInvalidOperation("bindTransformFeedback: Currently bound transform "
+                                     "feedback is active and not paused");
+    }
 
     if (tf && tf->IsDeleted())
-        return ErrorInvalidOperation("%s: TFO already deleted.", funcName);
-
-    if (mBoundTransformFeedback->mIsActive &&
-        !mBoundTransformFeedback->mIsPaused)
-    {
-        ErrorInvalidOperation("%s: Currently bound transform feedback is active and not"
-                              " paused.",
-                              funcName);
-        return;
-    }
-
-    ////
-
-    mBoundTransformFeedback = (tf ? tf : mDefaultTransformFeedback);
+        return ErrorInvalidOperation("bindTransformFeedback: Attempt to bind deleted id");
 
     MakeContextCurrent();
-    gl->fBindTransformFeedback(target, mBoundTransformFeedback->mGLName);
+    gl->fBindTransformFeedback(target, tf ? tf->mGLName : 0);
+    if (tf)
+        mBoundTransformFeedback = tf;
+    else
+        mBoundTransformFeedback = mDefaultTransformFeedback;
 }
 
 void
-WebGL2Context::BeginTransformFeedback(GLenum primMode)
+WebGL2Context::BeginTransformFeedback(GLenum primitiveMode)
 {
     if (IsContextLost())
         return;
 
-    mBoundTransformFeedback->BeginTransformFeedback(primMode);
+    WebGLTransformFeedback* tf = mBoundTransformFeedback;
+    MOZ_ASSERT(tf);
+    if (!tf)
+        return;
+
+    if (tf->mIsActive)
+        return ErrorInvalidOperation("beginTransformFeedback: transform feedback is active");
+
+    const GLenum mode = tf->mMode;
+    if (mode != LOCAL_GL_POINTS && mode != LOCAL_GL_LINES && mode != LOCAL_GL_TRIANGLES)
+        return ErrorInvalidEnum("beginTransformFeedback: primitive must be one of POINTS, LINES, or TRIANGLES");
+
+    // TODO:
+    // GL_INVALID_OPERATION is generated by glBeginTransformFeedback
+    // if any binding point used in transform feedback mode does not
+    // have a buffer object bound. In interleaved mode, only the first
+    // buffer object binding point is ever written to.
+
+    // GL_INVALID_OPERATION is generated by glBeginTransformFeedback
+    // if no binding points would be used, either because no program
+    // object is active of because the active program object has
+    // specified no varying variables to record.
+    if (!mCurrentProgram)
+        return ErrorInvalidOperation("beginTransformFeedback: no program is active");
+
+    MakeContextCurrent();
+    gl->fBeginTransformFeedback(primitiveMode);
+    tf->mIsActive = true;
+    tf->mIsPaused = false;
 }
 
 void
 WebGL2Context::EndTransformFeedback()
 {
     if (IsContextLost())
         return;
 
-    mBoundTransformFeedback->EndTransformFeedback();
+    WebGLTransformFeedback* tf = mBoundTransformFeedback;
+    MOZ_ASSERT(tf);
+
+    if (!tf)
+        return;
+
+    if (!tf->mIsActive)
+        return ErrorInvalidOperation("%s: transform feedback in not active",
+                                     "endTransformFeedback");
+
+    MakeContextCurrent();
+    gl->fEndTransformFeedback();
+    tf->mIsActive = false;
+    tf->mIsPaused = false;
 }
 
 void
 WebGL2Context::PauseTransformFeedback()
 {
     if (IsContextLost())
         return;
 
-    mBoundTransformFeedback->PauseTransformFeedback();
+    WebGLTransformFeedback* tf = mBoundTransformFeedback;
+    MOZ_ASSERT(tf);
+    if (!tf)
+        return;
+
+    if (!tf->mIsActive || tf->mIsPaused) {
+        return ErrorInvalidOperation("%s: transform feedback is not active or is paused",
+                                     "pauseTransformFeedback");
+    }
+
+    MakeContextCurrent();
+    gl->fPauseTransformFeedback();
+    tf->mIsPaused = true;
 }
 
 void
 WebGL2Context::ResumeTransformFeedback()
 {
     if (IsContextLost())
         return;
 
-    mBoundTransformFeedback->ResumeTransformFeedback();
+    WebGLTransformFeedback* tf = mBoundTransformFeedback;
+    MOZ_ASSERT(tf);
+    if (!tf)
+        return;
+
+    if (!tf->mIsActive || !tf->mIsPaused)
+        return ErrorInvalidOperation("resumeTransformFeedback: transform feedback is not active or is not paused");
+
+    MakeContextCurrent();
+    gl->fResumeTransformFeedback();
+    tf->mIsPaused = false;
 }
 
 void
 WebGL2Context::TransformFeedbackVaryings(WebGLProgram* program,
                                          const dom::Sequence<nsString>& varyings,
                                          GLenum bufferMode)
 {
     if (IsContextLost())
--- a/dom/canvas/WebGL2ContextUniforms.cpp
+++ b/dom/canvas/WebGL2ContextUniforms.cpp
@@ -65,70 +65,67 @@ WebGL2Context::Uniform4ui(WebGLUniformLo
 
     MakeContextCurrent();
     gl->fUniform4ui(loc->mLoc, v0, v1, v2, v3);
 }
 
 
 // -------------------------------------------------------------------------
 // Uniform Buffer Objects and Transform Feedback Buffers
+// TODO(djg): Implemented in WebGLContext
+/*
+    void BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer);
+    void BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer,
+                         GLintptr offset, GLsizeiptr size);
+*/
 
+/* This doesn't belong here. It's part of state querying */
 void
 WebGL2Context::GetIndexedParameter(GLenum target, GLuint index,
                                    dom::Nullable<dom::OwningWebGLBufferOrLongLong>& retval)
 {
-    const char funcName[] = "getIndexedParameter";
     retval.SetNull();
     if (IsContextLost())
         return;
 
-    const std::vector<IndexedBufferBinding>* bindings;
+    GLint64 data = 0;
+
+    MakeContextCurrent();
+
     switch (target) {
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
+        if (index >= mGLMaxTransformFeedbackSeparateAttribs)
+            return ErrorInvalidValue("getIndexedParameter: index should be less than "
+                                     "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
+
+        if (mBoundTransformFeedbackBuffers[index].get()) {
+            retval.SetValue().SetAsWebGLBuffer() =
+                mBoundTransformFeedbackBuffers[index].get();
+        }
+        return;
+
+    case LOCAL_GL_UNIFORM_BUFFER_BINDING:
+        if (index >= mGLMaxUniformBufferBindings)
+            return ErrorInvalidValue("getIndexedParameter: index should be than "
+                                     "MAX_UNIFORM_BUFFER_BINDINGS");
+
+        if (mBoundUniformBuffers[index].get())
+            retval.SetValue().SetAsWebGLBuffer() = mBoundUniformBuffers[index].get();
+        return;
+
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_START:
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_SIZE:
-        bindings = &(mBoundTransformFeedback->mIndexedBindings);
-        break;
-
-    case LOCAL_GL_UNIFORM_BUFFER_BINDING:
     case LOCAL_GL_UNIFORM_BUFFER_START:
     case LOCAL_GL_UNIFORM_BUFFER_SIZE:
-        bindings = &mIndexedUniformBufferBindings;
-        break;
-
-    default:
-        ErrorInvalidEnumInfo("getIndexedParameter: target", target);
+        gl->fGetInteger64i_v(target, index, &data);
+        retval.SetValue().SetAsLongLong() = data;
         return;
     }
 
-    if (index >= bindings->size()) {
-        ErrorInvalidValue("%s: `index` must be < %s.", funcName,
-                          "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
-        return;
-    }
-    const auto& binding = (*bindings)[index];
-
-    switch (target) {
-    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
-    case LOCAL_GL_UNIFORM_BUFFER_BINDING:
-        if (binding.mBufferBinding) {
-            retval.SetValue().SetAsWebGLBuffer() = binding.mBufferBinding;
-        }
-        break;
-
-    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_START:
-    case LOCAL_GL_UNIFORM_BUFFER_START:
-        retval.SetValue().SetAsLongLong() = binding.mRangeStart;
-        break;
-
-    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_SIZE:
-    case LOCAL_GL_UNIFORM_BUFFER_SIZE:
-        retval.SetValue().SetAsLongLong() = binding.mRangeSize;
-        break;
-    }
+    ErrorInvalidEnumInfo("getIndexedParameter: target", target);
 }
 
 void
 WebGL2Context::GetUniformIndices(WebGLProgram* program,
                                  const dom::Sequence<nsString>& uniformNames,
                                  dom::Nullable< nsTArray<GLuint> >& retval)
 {
     retval.SetNull();
--- a/dom/canvas/WebGLBuffer.cpp
+++ b/dom/canvas/WebGLBuffer.cpp
@@ -12,50 +12,48 @@
 
 namespace mozilla {
 
 WebGLBuffer::WebGLBuffer(WebGLContext* webgl, GLuint buf)
     : WebGLContextBoundObject(webgl)
     , mGLName(buf)
     , mContent(Kind::Undefined)
     , mByteLength(0)
-    , mNumActiveTFOs(0)
-    , mBoundForTF(false)
 {
     mContext->mBuffers.insertBack(this);
 }
 
 WebGLBuffer::~WebGLBuffer()
 {
-    MOZ_ASSERT(!mNumActiveTFOs);
     DeleteOnce();
 }
 
 void
-WebGLBuffer::SetContentAfterBind(GLenum target)
+WebGLBuffer::BindTo(GLenum target)
 {
-    if (mContent != Kind::Undefined)
-        return;
-
     switch (target) {
     case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
         mContent = Kind::ElementArray;
-        if (!mCache) {
-            mCache.reset(new WebGLElementArrayCache);
-        }
+        if (!mCache)
+            mCache = new WebGLElementArrayCache;
         break;
 
     case LOCAL_GL_ARRAY_BUFFER:
     case LOCAL_GL_PIXEL_PACK_BUFFER:
     case LOCAL_GL_PIXEL_UNPACK_BUFFER:
     case LOCAL_GL_UNIFORM_BUFFER:
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
+        mContent = Kind::OtherData;
+        break;
+
     case LOCAL_GL_COPY_READ_BUFFER:
     case LOCAL_GL_COPY_WRITE_BUFFER:
-        mContent = Kind::OtherData;
+        if (mContent == Kind::Undefined) {
+          mContent = Kind::OtherData;
+        }
         break;
 
     default:
         MOZ_CRASH("GFX: invalid target");
     }
 }
 
 void
@@ -63,100 +61,16 @@ WebGLBuffer::Delete()
 {
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteBuffers(1, &mGLName);
     mByteLength = 0;
     mCache = nullptr;
     LinkedListElement<WebGLBuffer>::remove(); // remove from mContext->mBuffers
 }
 
-////////////////////////////////////////
-
-static bool
-ValidateBufferUsageEnum(WebGLContext* webgl, const char* funcName, GLenum usage)
-{
-    switch (usage) {
-    case LOCAL_GL_STREAM_DRAW:
-    case LOCAL_GL_STATIC_DRAW:
-    case LOCAL_GL_DYNAMIC_DRAW:
-        return true;
-
-    case LOCAL_GL_DYNAMIC_COPY:
-    case LOCAL_GL_DYNAMIC_READ:
-    case LOCAL_GL_STATIC_COPY:
-    case LOCAL_GL_STATIC_READ:
-    case LOCAL_GL_STREAM_COPY:
-    case LOCAL_GL_STREAM_READ:
-        if (MOZ_LIKELY(webgl->IsWebGL2()))
-            return true;
-        break;
-
-    default:
-        break;
-    }
-
-    webgl->ErrorInvalidEnum("%s: Invalid `usage`: 0x%04x", funcName, usage);
-    return false;
-}
-
-void
-WebGLBuffer::BufferData(GLenum target, size_t size, const void* data, GLenum usage)
-{
-    const char funcName[] = "bufferData";
-
-    if (!ValidateBufferUsageEnum(mContext, funcName, usage))
-        return;
-
-    if (mNumActiveTFOs) {
-        mContext->ErrorInvalidOperation("%s: Buffer is bound to an active transform"
-                                        " feedback object.",
-                                        funcName);
-        return;
-    }
-
-    const auto& gl = mContext->gl;
-    gl->MakeCurrent();
-    mContext->InvalidateBufferFetching();
-
-#ifdef XP_MACOSX
-    // bug 790879
-    if (gl->WorkAroundDriverBugs() &&
-        size > INT32_MAX)
-    {
-        mContext->ErrorOutOfMemory("%s: Allocation size too large.", funcName);
-        return;
-    }
-#endif
-
-    const bool sizeChanges = (size != ByteLength());
-    if (sizeChanges) {
-        gl::GLContext::LocalErrorScope errorScope(*gl);
-        gl->fBufferData(target, size, data, usage);
-        const auto error = errorScope.GetError();
-
-        if (error) {
-            MOZ_ASSERT(error == LOCAL_GL_OUT_OF_MEMORY);
-            mContext->ErrorOutOfMemory("%s: Error from driver: 0x%04x", funcName, error);
-            return;
-        }
-    } else {
-        gl->fBufferData(target, size, data, usage);
-    }
-
-    mByteLength = size;
-
-    // Warning: Possibly shared memory.  See bug 1225033.
-    if (!ElementArrayCacheBufferData(data, size)) {
-        mByteLength = 0;
-        mContext->ErrorOutOfMemory("%s: Failed update index buffer cache.", funcName);
-    }
-}
-
-////////////////////////////////////////
-
 bool
 WebGLBuffer::ElementArrayCacheBufferData(const void* ptr,
                                          size_t bufferSizeInBytes)
 {
     if (mContent == Kind::ElementArray)
         return mCache->BufferData(ptr, bufferSizeInBytes);
 
     return true;
@@ -174,89 +88,28 @@ size_t
 WebGLBuffer::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
 {
     size_t sizeOfCache = mCache ? mCache->SizeOfIncludingThis(mallocSizeOf)
                                 : 0;
     return mallocSizeOf(this) + sizeOfCache;
 }
 
 bool
-WebGLBuffer::Validate(GLenum type, uint32_t maxAllowed, size_t first, size_t count) const
+WebGLBuffer::Validate(GLenum type, uint32_t maxAllowed, size_t first,
+                      size_t count, uint32_t* const out_upperBound)
 {
-    return mCache->Validate(type, maxAllowed, first, count);
+    return mCache->Validate(type, maxAllowed, first, count, out_upperBound);
 }
 
 bool
 WebGLBuffer::IsElementArrayUsedWithMultipleTypes() const
 {
     return mCache->BeenUsedWithMultipleTypes();
 }
 
-bool
-WebGLBuffer::ValidateCanBindToTarget(const char* funcName, GLenum target)
-{
-    const bool wouldBeTF = (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER);
-    if (mWebGLRefCnt && wouldBeTF != mBoundForTF) {
-        mContext->ErrorInvalidOperation("%s: Buffers cannot be simultaneously bound to "
-                                        " transform feedback and bound elsewhere.",
-                                        funcName);
-        return false;
-    }
-    mBoundForTF = wouldBeTF;
-
-    /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1
-     *
-     * In the WebGL 2 API, buffers have their WebGL buffer type
-     * initially set to undefined. Calling bindBuffer, bindBufferRange
-     * or bindBufferBase with the target argument set to any buffer
-     * binding point except COPY_READ_BUFFER or COPY_WRITE_BUFFER will
-     * then set the WebGL buffer type of the buffer being bound
-     * according to the table above.
-     *
-     * Any call to one of these functions which attempts to bind a
-     * WebGLBuffer that has the element array WebGL buffer type to a
-     * binding point that falls under other data, or bind a
-     * WebGLBuffer which has the other data WebGL buffer type to
-     * ELEMENT_ARRAY_BUFFER will generate an INVALID_OPERATION error,
-     * and the state of the binding point will remain untouched.
-     */
-
-    if (mContent == WebGLBuffer::Kind::Undefined)
-        return true;
-
-    switch (target) {
-    case LOCAL_GL_COPY_READ_BUFFER:
-    case LOCAL_GL_COPY_WRITE_BUFFER:
-        return true;
-
-    case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
-        if (mContent == WebGLBuffer::Kind::ElementArray)
-            return true;
-        break;
-
-    case LOCAL_GL_ARRAY_BUFFER:
-    case LOCAL_GL_PIXEL_PACK_BUFFER:
-    case LOCAL_GL_PIXEL_UNPACK_BUFFER:
-    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
-    case LOCAL_GL_UNIFORM_BUFFER:
-        if (mContent == WebGLBuffer::Kind::OtherData)
-            return true;
-        break;
-
-    default:
-        MOZ_CRASH();
-    }
-
-    const auto dataType = (mContent == WebGLBuffer::Kind::OtherData) ? "other"
-                                                                     : "element";
-    mContext->ErrorInvalidOperation("%s: Buffer already contains %s data.", funcName,
-                                    dataType);
-    return false;
-}
-
 JSObject*
 WebGLBuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
     return dom::WebGLBufferBinding::Wrap(cx, this, givenProto);
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLBuffer)
 
--- a/dom/canvas/WebGLBuffer.h
+++ b/dom/canvas/WebGLBuffer.h
@@ -3,83 +3,76 @@
  * 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 WEBGL_BUFFER_H_
 #define WEBGL_BUFFER_H_
 
 #include "GLDefs.h"
 #include "mozilla/LinkedList.h"
-#include "mozilla/UniquePtr.h"
+#include "nsAutoPtr.h"
 #include "nsWrapperCache.h"
 
 #include "WebGLObjectModel.h"
 #include "WebGLTypes.h"
 
 namespace mozilla {
 
 class WebGLElementArrayCache;
 
 class WebGLBuffer final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLBuffer>
     , public LinkedListElement<WebGLBuffer>
     , public WebGLContextBoundObject
 {
-    friend class WebGLContext;
-    friend class WebGL2Context;
-    friend class WebGLTexture;
-    friend class WebGLTransformFeedback;
+public:
 
-public:
     enum class Kind {
         Undefined,
         ElementArray,
         OtherData
     };
 
     WebGLBuffer(WebGLContext* webgl, GLuint buf);
 
-    void SetContentAfterBind(GLenum target);
+    void BindTo(GLenum target);
     Kind Content() const { return mContent; }
 
     void Delete();
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
-    size_t ByteLength() const { return mByteLength; }
+    WebGLsizeiptr ByteLength() const { return mByteLength; }
+    void SetByteLength(WebGLsizeiptr byteLength) { mByteLength = byteLength; }
 
     bool ElementArrayCacheBufferData(const void* ptr, size_t bufferSizeInBytes);
 
     void ElementArrayCacheBufferSubData(size_t pos, const void* ptr,
                                         size_t updateSizeInBytes);
 
-    bool Validate(GLenum type, uint32_t max_allowed, size_t first, size_t count) const;
+    bool Validate(GLenum type, uint32_t max_allowed, size_t first, size_t count,
+                  uint32_t* const out_upperBound);
 
     bool IsElementArrayUsedWithMultipleTypes() const;
 
     WebGLContext* GetParentObject() const {
         return mContext;
     }
 
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
-    bool ValidateCanBindToTarget(const char* funcName, GLenum target);
-    void BufferData(GLenum target, size_t size, const void* data, GLenum usage);
-
     const GLenum mGLName;
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLBuffer)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLBuffer)
 
 protected:
     ~WebGLBuffer();
 
     Kind mContent;
-    size_t mByteLength;
-    UniquePtr<WebGLElementArrayCache> mCache;
-    size_t mNumActiveTFOs;
-    bool mBoundForTF;
+    WebGLsizeiptr mByteLength;
+    nsAutoPtr<WebGLElementArrayCache> mCache;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_BUFFER_H_
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -239,29 +239,31 @@ WebGLContext::DestroyResourcesAndContext
     mBound3DTextures.Clear();
     mBound2DArrayTextures.Clear();
     mBoundSamplers.Clear();
     mBoundArrayBuffer = nullptr;
     mBoundCopyReadBuffer = nullptr;
     mBoundCopyWriteBuffer = nullptr;
     mBoundPixelPackBuffer = nullptr;
     mBoundPixelUnpackBuffer = nullptr;
+    mBoundTransformFeedbackBuffer = nullptr;
     mBoundUniformBuffer = nullptr;
     mCurrentProgram = nullptr;
     mActiveProgramLinkInfo = nullptr;
     mBoundDrawFramebuffer = nullptr;
     mBoundReadFramebuffer = nullptr;
     mActiveOcclusionQuery = nullptr;
     mBoundRenderbuffer = nullptr;
     mBoundVertexArray = nullptr;
     mDefaultVertexArray = nullptr;
     mBoundTransformFeedback = nullptr;
     mDefaultTransformFeedback = nullptr;
 
-    mIndexedUniformBufferBindings.clear();
+    mBoundTransformFeedbackBuffers.Clear();
+    mBoundUniformBuffers.Clear();
 
     //////
 
     ClearLinkedList(mBuffers);
     ClearLinkedList(mFramebuffers);
     ClearLinkedList(mPrograms);
     ClearLinkedList(mQueries);
     ClearLinkedList(mRenderbuffers);
@@ -1605,21 +1607,28 @@ WebGLContext::PresentScreenBuffer()
 }
 
 void
 WebGLContext::DummyReadFramebufferOperation(const char* funcName)
 {
     if (!mBoundReadFramebuffer)
         return; // Infallible.
 
-    const auto status = mBoundReadFramebuffer->CheckFramebufferStatus(funcName);
+    nsCString fbStatusInfo;
+    const auto status = mBoundReadFramebuffer->CheckFramebufferStatus(&fbStatusInfo);
 
     if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
-        ErrorInvalidFramebufferOperation("%s: Framebuffer must be complete.",
-                                         funcName);
+        nsCString errorText("Incomplete framebuffer");
+
+        if (fbStatusInfo.Length()) {
+            errorText += ": ";
+            errorText += fbStatusInfo;
+        }
+
+        ErrorInvalidFramebufferOperation("%s: %s.", funcName, errorText.BeginReading());
     }
 }
 
 static bool
 CheckContextLost(GLContext* gl, bool* const out_isGuilty)
 {
     MOZ_ASSERT(gl);
     MOZ_ASSERT(out_isGuilty);
@@ -1905,28 +1914,18 @@ WebGLContext::GetSurfaceSnapshot(bool* o
     if (NS_WARN_IF(!surf)) {
         return nullptr;
     }
 
     gl->MakeCurrent();
     {
         ScopedBindFramebuffer autoFB(gl, 0);
         ClearBackbufferIfNeeded();
-
-        // Save, override, then restore glReadBuffer.
-        const GLenum readBufferMode = gl->Screen()->GetReadBufferMode();
-
-        if (readBufferMode != LOCAL_GL_BACK) {
-            gl->fReadBuffer(LOCAL_GL_BACK);
-        }
+        // TODO: Save, override, then restore glReadBuffer if present.
         ReadPixelsIntoDataSurface(gl, surf);
-
-        if (readBufferMode != LOCAL_GL_BACK) {
-            gl->fReadBuffer(readBufferMode);
-        }
     }
 
     if (out_premultAlpha) {
         *out_premultAlpha = true;
     }
     bool srcPremultAlpha = mOptions.premultipliedAlpha;
     if (!srcPremultAlpha) {
         if (out_premultAlpha) {
@@ -2042,40 +2041,16 @@ WebGLContext::ScopedMaskWorkaround::HasD
 {
     const auto& depth = fb->DepthAttachment();
     const auto& stencil = fb->StencilAttachment();
     return depth.IsDefined() && !stencil.IsDefined();
 }
 
 ////////////////////////////////////////
 
-IndexedBufferBinding::IndexedBufferBinding()
-    : mRangeStart(0)
-    , mRangeSize(0)
-{ }
-
-uint64_t
-IndexedBufferBinding::ByteCount() const
-{
-    if (!mBufferBinding)
-        return 0;
-
-    uint64_t bufferSize = mBufferBinding->ByteLength();
-    if (!mRangeSize) // BindBufferBase
-        return bufferSize;
-
-    if (mRangeStart >= bufferSize)
-        return 0;
-    bufferSize -= mRangeStart;
-
-    return std::min(bufferSize, mRangeSize);
-}
-
-////////////////////////////////////////
-
 ScopedUnpackReset::ScopedUnpackReset(WebGLContext* webgl)
     : ScopedGLWrapper<ScopedUnpackReset>(webgl->gl)
     , mWebGL(webgl)
 {
     if (mWebGL->mPixelStore_UnpackAlignment != 4) mGL->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
 
     if (mWebGL->IsWebGL2()) {
         if (mWebGL->mPixelStore_UnpackRowLength   != 0) mGL->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH  , 0);
@@ -2104,34 +2079,16 @@ ScopedUnpackReset::UnwrapImpl()
         if (mWebGL->mBoundPixelUnpackBuffer) {
             pbo = mWebGL->mBoundPixelUnpackBuffer->mGLName;
         }
 
         mGL->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, pbo);
     }
 }
 
-////////////////////
-
-void
-ScopedFBRebinder::UnwrapImpl()
-{
-    const auto fnName = [&](WebGLFramebuffer* fb) {
-        return fb ? fb->mGLName : 0;
-    };
-
-    if (mWebGL->IsWebGL2()) {
-        mGL->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fnName(mWebGL->mBoundDrawFramebuffer));
-        mGL->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fnName(mWebGL->mBoundReadFramebuffer));
-    } else {
-        MOZ_ASSERT(mWebGL->mBoundDrawFramebuffer == mWebGL->mBoundReadFramebuffer);
-        mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fnName(mWebGL->mBoundDrawFramebuffer));
-    }
-}
-
 ////////////////////////////////////////
 
 void
 Intersect(uint32_t srcSize, int32_t dstStartInSrc, uint32_t dstSize,
           uint32_t* const out_intStartInSrc, uint32_t* const out_intStartInDst,
           uint32_t* const out_intSize)
 {
     // Only >0 if dstStartInSrc is >0:
@@ -2380,34 +2337,16 @@ WebGLContext::GetUnpackSize(bool isFunc3
     totalBytes += usedBytesPerRow;
 
     return totalBytes;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // XPCOM goop
 
-void
-ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
-                            const std::vector<IndexedBufferBinding>& field,
-                            const char* name, uint32_t flags)
-{
-    for (const auto& cur : field) {
-        ImplCycleCollectionTraverse(callback, cur.mBufferBinding, name, flags);
-    }
-}
-
-void
-ImplCycleCollectionUnlink(std::vector<IndexedBufferBinding>& field)
-{
-    field.clear();
-}
-
-////
-
 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebGLContext)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLContext,
   mCanvasElement,
   mOffscreenCanvas,
   mExtensions,
   mBound2DTextures,
@@ -2415,17 +2354,17 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(We
   mBound3DTextures,
   mBound2DArrayTextures,
   mBoundSamplers,
   mBoundArrayBuffer,
   mBoundCopyReadBuffer,
   mBoundCopyWriteBuffer,
   mBoundPixelPackBuffer,
   mBoundPixelUnpackBuffer,
-  mBoundTransformFeedback,
+  mBoundTransformFeedbackBuffer,
   mBoundUniformBuffer,
   mCurrentProgram,
   mBoundDrawFramebuffer,
   mBoundReadFramebuffer,
   mBoundRenderbuffer,
   mBoundVertexArray,
   mDefaultVertexArray,
   mActiveOcclusionQuery,
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -118,17 +118,16 @@ namespace gfx {
 class SourceSurface;
 } // namespace gfx
 
 namespace webgl {
 struct LinkedProgramInfo;
 class ShaderValidator;
 class TexUnpackBlob;
 struct UniformInfo;
-struct UniformBlockInfo;
 } // namespace webgl
 
 WebGLTexelFormat GetWebGLTexelFormat(TexInternalFormat format);
 
 void AssertUintParamCorrect(gl::GLContext* gl, GLenum pname, GLuint shadow);
 
 struct WebGLContextOptions
 {
@@ -176,54 +175,37 @@ class WebGLIntOrFloat {
 public:
     explicit WebGLIntOrFloat(GLint i) : mType(Int) { mValue.i = i; }
     explicit WebGLIntOrFloat(GLfloat f) : mType(Float) { mValue.f = f; }
 
     GLint AsInt() const { return (mType == Int) ? mValue.i : NS_lroundf(mValue.f); }
     GLfloat AsFloat() const { return (mType == Float) ? mValue.f : GLfloat(mValue.i); }
 };
 
-struct IndexedBufferBinding
-{
-    WebGLRefPtr<WebGLBuffer> mBufferBinding;
-    uint64_t mRangeStart;
-    uint64_t mRangeSize;
-
-    IndexedBufferBinding();
-
-    uint64_t ByteCount() const;
-};
-
-////////////////////////////////////////////////////////////////////////////////
-
 class WebGLContext
     : public nsIDOMWebGLRenderingContext
     , public nsICanvasRenderingContextInternal
     , public nsSupportsWeakReference
     , public WebGLContextUnchecked
     , public WebGLRectangleObject
     , public nsWrapperCache
 {
-    friend class ScopedDrawHelper;
-    friend class ScopedDrawWithTransformFeedback;
-    friend class ScopedFBRebinder;
     friend class WebGL2Context;
     friend class WebGLContextUserData;
     friend class WebGLExtensionCompressedTextureATC;
     friend class WebGLExtensionCompressedTextureES3;
     friend class WebGLExtensionCompressedTextureETC1;
     friend class WebGLExtensionCompressedTexturePVRTC;
     friend class WebGLExtensionCompressedTextureS3TC;
     friend class WebGLExtensionDepthTexture;
     friend class WebGLExtensionDisjointTimerQuery;
     friend class WebGLExtensionDrawBuffers;
     friend class WebGLExtensionLoseContext;
     friend class WebGLExtensionVertexArray;
     friend class WebGLMemoryTracker;
-    friend struct webgl::UniformBlockInfo;
 
     enum {
         UNPACK_FLIP_Y_WEBGL = 0x9240,
         UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241,
         CONTEXT_LOST_WEBGL = 0x9242,
         UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243,
         BROWSER_DEFAULT_WEBGL = 0x9244,
         UNMASKED_VENDOR_WEBGL = 0x9245,
@@ -728,16 +710,21 @@ public:
 // -----------------------------------------------------------------------------
 // WEBGL_lose_context
 public:
     void LoseContext();
     void RestoreContext();
 
 // -----------------------------------------------------------------------------
 // Buffer Objects (WebGLContextBuffers.cpp)
+private:
+    void UpdateBoundBuffer(GLenum target, WebGLBuffer* buffer);
+    void UpdateBoundBufferIndexed(GLenum target, GLuint index, WebGLBuffer* buffer);
+
+public:
     void BindBuffer(GLenum target, WebGLBuffer* buffer);
     void BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buf);
     void BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buf,
                          WebGLintptr offset, WebGLsizeiptr size);
 
 private:
     template<typename BufferT>
     void BufferDataT(GLenum target, const BufferT& data, GLenum usage);
@@ -769,24 +756,28 @@ public:
 
 protected:
     // bound buffer state
     WebGLRefPtr<WebGLBuffer> mBoundArrayBuffer;
     WebGLRefPtr<WebGLBuffer> mBoundCopyReadBuffer;
     WebGLRefPtr<WebGLBuffer> mBoundCopyWriteBuffer;
     WebGLRefPtr<WebGLBuffer> mBoundPixelPackBuffer;
     WebGLRefPtr<WebGLBuffer> mBoundPixelUnpackBuffer;
+    WebGLRefPtr<WebGLBuffer> mBoundTransformFeedbackBuffer;
     WebGLRefPtr<WebGLBuffer> mBoundUniformBuffer;
 
-    std::vector<IndexedBufferBinding> mIndexedUniformBufferBindings;
+    nsTArray<WebGLRefPtr<WebGLBuffer>> mBoundUniformBuffers;
+    nsTArray<WebGLRefPtr<WebGLBuffer>> mBoundTransformFeedbackBuffers;
 
     WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTarget(GLenum target);
     WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTargetIndexed(GLenum target,
                                                            GLuint index);
 
+    GLenum GetCurrentBinding(WebGLBuffer* buffer) const;
+
 // -----------------------------------------------------------------------------
 // Queries (WebGL2ContextQueries.cpp)
 protected:
     WebGLRefPtr<WebGLQuery>& GetQuerySlotByTarget(GLenum target);
 
     WebGLRefPtr<WebGLQuery> mActiveOcclusionQuery;
     WebGLRefPtr<WebGLQuery> mActiveTransformFeedbackQuery;
 
@@ -1037,21 +1028,21 @@ private:
     // Cache the max number of vertices and instances that can be read from
     // bound VBOs (result of ValidateBuffers).
     bool mBufferFetchingIsVerified;
     bool mBufferFetchingHasPerVertex;
     uint32_t mMaxFetchedVertices;
     uint32_t mMaxFetchedInstances;
     bool mBufferFetch_IsAttrib0Active;
 
-    bool DrawArrays_check(const char* funcName, GLenum mode, GLint first,
-                          GLsizei vertCount, GLsizei instanceCount);
-    bool DrawElements_check(const char* funcName, GLenum mode, GLsizei vertCount,
-                            GLenum type, WebGLintptr byteOffset,
-                            GLsizei instanceCount);
+    bool DrawArrays_check(GLint first, GLsizei count, GLsizei primcount,
+                          const char* info);
+    bool DrawElements_check(GLsizei count, GLenum type, WebGLintptr byteOffset,
+                            GLsizei primcount, const char* info,
+                            GLuint* out_upperBound);
     bool DrawInstanced_check(const char* info);
     void Draw_cleanup(const char* funcName);
 
     void VertexAttrib1fv_base(GLuint index, uint32_t arrayLength,
                               const GLfloat* ptr);
     void VertexAttrib2fv_base(GLuint index, uint32_t arrayLength,
                               const GLfloat* ptr);
     void VertexAttrib3fv_base(GLuint index, uint32_t arrayLength,
@@ -1129,18 +1120,16 @@ protected:
     uint32_t mImplMaxColorAttachments;
     uint32_t mImplMaxDrawBuffers;
 
 public:
     GLenum LastColorAttachmentEnum() const {
         return LOCAL_GL_COLOR_ATTACHMENT0 + mImplMaxColorAttachments - 1;
     }
 
-    const decltype(mOptions)& Options() const { return mOptions; }
-
 protected:
 
     // Texture sizes are often not actually the GL values. Let's be explicit that these
     // are implementation limits.
     uint32_t mImplMaxTextureSize;
     uint32_t mImplMaxCubeMapTextureSize;
     uint32_t mImplMax3DTextureSize;
     uint32_t mImplMaxArrayTextureLayers;
@@ -1245,16 +1234,17 @@ protected:
     // Validation functions (implemented in WebGLContextValidate.cpp)
     bool InitAndValidateGL(FailureReason* const out_failReason);
 
     bool ValidateBlendEquationEnum(GLenum cap, const char* info);
     bool ValidateBlendFuncDstEnum(GLenum mode, const char* info);
     bool ValidateBlendFuncSrcEnum(GLenum mode, const char* info);
     bool ValidateBlendFuncEnumsCompatibility(GLenum sfactor, GLenum dfactor,
                                              const char* info);
+    bool ValidateDataOffsetSize(WebGLintptr offset, WebGLsizeiptr size, WebGLsizeiptr bufferSize, const char* info);
     bool ValidateDataRanges(WebGLintptr readOffset, WebGLintptr writeOffset, WebGLsizeiptr size, const char* info);
     bool ValidateTextureTargetEnum(GLenum target, const char* info);
     bool ValidateComparisonEnum(GLenum target, const char* info);
     bool ValidateStencilOpEnum(GLenum action, const char* info);
     bool ValidateFaceEnum(GLenum face, const char* info);
     bool ValidateTexInputData(GLenum type, js::Scalar::Type jsArrayType,
                               WebGLTexImageFunc func, WebGLTexDimensions dims);
     bool ValidateDrawModeEnum(GLenum mode, const char* info);
@@ -1311,38 +1301,16 @@ protected:
     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);
-    WebGLBuffer* ValidateBufferSelection(const char* funcName, GLenum target);
-    IndexedBufferBinding* ValidateIndexedBufferSlot(const char* funcName, GLenum target,
-                                                    GLuint index);
-
-    bool ValidateIndexedBufferBinding(const char* funcName, GLenum target, GLuint index,
-                                      WebGLRefPtr<WebGLBuffer>** const out_genericBinding,
-                                      IndexedBufferBinding** const out_indexedBinding);
-
-    bool ValidateNonNegative(const char* funcName, const char* argName, int64_t val) {
-        if (MOZ_UNLIKELY(val < 0)) {
-            ErrorInvalidValue("%s: `%s` must be non-negative.", funcName, argName);
-            return false;
-        }
-        return true;
-    }
-
     void Invalidate();
     void DestroyResourcesAndContext();
 
     void MakeContextCurrent() const;
 
     // helpers
 
     bool ConvertImage(size_t width, size_t height, size_t srcStride,
@@ -1378,19 +1346,30 @@ private:
     bool ValidateObjectAssumeNonNull(const char* info, ObjectType* object);
 
 private:
     // -------------------------------------------------------------------------
     // Context customization points
     virtual WebGLVertexArray* CreateVertexArrayImpl();
 
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, uint32_t* alignment, const char* info) = 0;
+    virtual bool ValidateBufferTarget(GLenum target, const char* info) = 0;
+    virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) = 0;
+    virtual bool ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer, const char* info);
+    virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) = 0;
     virtual bool ValidateQueryTarget(GLenum usage, const char* info) = 0;
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) = 0;
 
+protected:
+    /** Like glBufferData, but if the call may change the buffer size, checks
+     *  any GL error generated by this glBufferData call and returns it.
+     */
+    GLenum CheckedBufferData(GLenum target, GLsizeiptr size, const GLvoid* data,
+                             GLenum usage);
+
 public:
     void ForceLoseContext(bool simulateLoss = false);
 
 protected:
     void ForceRestoreContext();
 
     nsTArray<WebGLRefPtr<WebGLTexture> > mBound2DTextures;
     nsTArray<WebGLRefPtr<WebGLTexture> > mBoundCubeMapTextures;
@@ -1781,46 +1760,28 @@ public:
     explicit operator bool() const { return bool(mBuffer); }
 
     void* get() const { return mBuffer; }
 
     UniqueBuffer(const UniqueBuffer& other) = delete; // construct using Move()!
     void operator =(const UniqueBuffer& other) = delete; // assign using Move()!
 };
 
-class ScopedUnpackReset final
+class ScopedUnpackReset
     : public gl::ScopedGLWrapper<ScopedUnpackReset>
 {
     friend struct gl::ScopedGLWrapper<ScopedUnpackReset>;
 
-private:
+protected:
     WebGLContext* const mWebGL;
 
 public:
     explicit ScopedUnpackReset(WebGLContext* webgl);
 
-private:
-    void UnwrapImpl();
-};
-
-class ScopedFBRebinder final
-    : public gl::ScopedGLWrapper<ScopedFBRebinder>
-{
-    friend struct gl::ScopedGLWrapper<ScopedFBRebinder>;
-
-private:
-    WebGLContext* const mWebGL;
-
-public:
-    explicit ScopedFBRebinder(WebGLContext* webgl)
-        : ScopedGLWrapper<ScopedFBRebinder>(webgl->gl)
-        , mWebGL(webgl)
-    { }
-
-private:
+protected:
     void UnwrapImpl();
 };
 
 void
 ComputeLengthAndData(const dom::ArrayBufferViewOrSharedArrayBufferView& view,
                      void** const out_data, size_t* const out_length,
                      js::Scalar::Type* const out_type);
 
@@ -1830,21 +1791,11 @@ Intersect(uint32_t srcSize, int32_t dstS
           uint32_t* const out_intSize);
 
 bool
 ZeroTextureData(WebGLContext* webgl, const char* funcName, bool respecifyTexture,
                 GLuint tex, TexImageTarget target, uint32_t level,
                 const webgl::FormatUsageInfo* usage, uint32_t xOffset, uint32_t yOffset,
                 uint32_t zOffset, uint32_t width, uint32_t height, uint32_t depth);
 
-////
-
-void
-ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
-                            const std::vector<IndexedBufferBinding>& field,
-                            const char* name, uint32_t flags = 0);
-
-void
-ImplCycleCollectionUnlink(std::vector<IndexedBufferBinding>& field);
-
 } // namespace mozilla
 
 #endif
--- a/dom/canvas/WebGLContextBuffers.cpp
+++ b/dom/canvas/WebGLContextBuffers.cpp
@@ -6,355 +6,240 @@
 #include "WebGLContext.h"
 
 #include "GLContext.h"
 #include "WebGLBuffer.h"
 #include "WebGLVertexArray.h"
 
 namespace mozilla {
 
-WebGLRefPtr<WebGLBuffer>*
-WebGLContext::ValidateBufferSlot(const char* funcName, GLenum target)
+void
+WebGLContext::UpdateBoundBuffer(GLenum target, WebGLBuffer* buffer)
 {
-    WebGLRefPtr<WebGLBuffer>* slot = nullptr;
-
-    switch (target) {
-    case LOCAL_GL_ARRAY_BUFFER:
-        slot = &mBoundArrayBuffer;
-        break;
-
-    case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
-        slot = &(mBoundVertexArray->mElementArrayBuffer);
-        break;
-    }
-
-    if (IsWebGL2()) {
-        switch (target) {
-        case LOCAL_GL_COPY_READ_BUFFER:
-            slot = &mBoundCopyReadBuffer;
-            break;
-
-        case LOCAL_GL_COPY_WRITE_BUFFER:
-            slot = &mBoundCopyWriteBuffer;
-            break;
+    WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target);
+    bufferSlot = buffer;
 
-        case LOCAL_GL_PIXEL_PACK_BUFFER:
-            slot = &mBoundPixelPackBuffer;
-            break;
-
-        case LOCAL_GL_PIXEL_UNPACK_BUFFER:
-            slot = &mBoundPixelUnpackBuffer;
-            break;
-
-        case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
-            slot = &(mBoundTransformFeedback->mGenericBufferBinding);
-            break;
+    if (!buffer)
+        return;
 
-        case LOCAL_GL_UNIFORM_BUFFER:
-            slot = &mBoundUniformBuffer;
-            break;
-        }
-    }
-
-    if (!slot) {
-        ErrorInvalidEnum("%s: Bad `target`: 0x%04x", funcName, target);
-        return nullptr;
-    }
-
-    return slot;
+    buffer->BindTo(target);
 }
 
-WebGLBuffer*
-WebGLContext::ValidateBufferSelection(const char* funcName, GLenum target)
-{
-    const auto& slot = ValidateBufferSlot(funcName, target);
-    if (!slot)
-        return nullptr;
-    const auto& buffer = *slot;
-
-    if (!buffer) {
-        ErrorInvalidOperation("%s: Buffer for `target` is null.", funcName);
-        return nullptr;
-    }
-
-    return buffer.get();
-}
-
-IndexedBufferBinding*
-WebGLContext::ValidateIndexedBufferSlot(const char* funcName, GLenum target, GLuint index)
+void
+WebGLContext::UpdateBoundBufferIndexed(GLenum target, GLuint index, WebGLBuffer* buffer)
 {
-    decltype(mIndexedUniformBufferBindings)* bindings;
-    const char* maxIndexEnum;
-    switch (target) {
-    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
-        bindings = &(mBoundTransformFeedback->mIndexedBindings);
-        maxIndexEnum = "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS";
-        break;
-
-    case LOCAL_GL_UNIFORM_BUFFER:
-        bindings = &mIndexedUniformBufferBindings;
-        maxIndexEnum = "MAX_UNIFORM_BUFFER_BINDINGS";
-        break;
+    UpdateBoundBuffer(target, buffer);
 
-    default:
-        ErrorInvalidEnum("%s: Bad `target`: 0x%04x", funcName, target);
-        return nullptr;
-    }
-
-    if (index >= bindings->size()) {
-        ErrorInvalidOperation("%s: `index` >= %s.", funcName, maxIndexEnum);
-        return nullptr;
-    }
-
-    return &(*bindings)[index];
+    WebGLRefPtr<WebGLBuffer>& bufferIndexSlot =
+        GetBufferSlotByTargetIndexed(target, index);
+    bufferIndexSlot = buffer;
 }
 
-////////////////////////////////////////
-
 void
 WebGLContext::BindBuffer(GLenum target, WebGLBuffer* buffer)
 {
-    const char funcName[] = "bindBuffer";
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowDeletedOrNull(funcName, buffer))
+    if (!ValidateObjectAllowDeletedOrNull("bindBuffer", buffer))
+        return;
+
+    // silently ignore a deleted buffer
+    if (buffer && buffer->IsDeleted())
+        return;
+
+    if (!ValidateBufferTarget(target, "bindBuffer"))
+        return;
+
+    if (!ValidateBufferForTarget(target, buffer, "bindBuffer"))
+        return;
+
+    WebGLContextUnchecked::BindBuffer(target, buffer);
+
+    UpdateBoundBuffer(target, buffer);
+}
+
+void
+WebGLContext::BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer)
+{
+    if (IsContextLost())
+        return;
+
+    if (!ValidateObjectAllowDeletedOrNull("bindBufferBase", buffer))
         return;
 
     // silently ignore a deleted buffer
     if (buffer && buffer->IsDeleted())
         return;
 
-    const auto& slot = ValidateBufferSlot(funcName, target);
-    if (!slot)
-        return;
-
-    if (buffer && !buffer->ValidateCanBindToTarget(funcName, target))
-        return;
-
-    gl->MakeCurrent();
-    gl->fBindBuffer(target, buffer ? buffer->mGLName : 0);
-
-    *slot = buffer;
-    if (buffer) {
-        buffer->SetContentAfterBind(target);
-    }
-}
-
-////////////////////////////////////////
+    // ValidateBufferTarget
+    switch (target) {
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
+        if (index >= mGLMaxTransformFeedbackSeparateAttribs)
+            return ErrorInvalidValue("bindBufferBase: index should be less than "
+                                     "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
+        break;
 
-bool
-WebGLContext::ValidateIndexedBufferBinding(const char* funcName, GLenum target,
-                                           GLuint index,
-                                           WebGLRefPtr<WebGLBuffer>** const out_genericBinding,
-                                           IndexedBufferBinding** const out_indexedBinding)
-{
-    *out_genericBinding = ValidateBufferSlot(funcName, target);
-    if (!*out_genericBinding)
-        return false;
+    case LOCAL_GL_UNIFORM_BUFFER:
+        if (index >= mGLMaxUniformBufferBindings)
+            return ErrorInvalidValue("bindBufferBase: index should be less than "
+                                     "MAX_UNIFORM_BUFFER_BINDINGS");
+        break;
 
-    *out_indexedBinding = ValidateIndexedBufferSlot(funcName, target, index);
-    if (!*out_indexedBinding)
-        return false;
-
-    if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER &&
-        mBoundTransformFeedback->mIsActive)
-    {
-        ErrorInvalidOperation("%s: Cannot update indexed buffer bindings on active"
-                              " transform feedback objects.",
-                              funcName);
-        return false;
+    default:
+        return ErrorInvalidEnumInfo("bindBufferBase: target", target);
     }
 
-    return true;
-}
-
-void
-WebGLContext::BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer)
-{
-    const char funcName[] = "bindBufferBase";
-    if (IsContextLost())
-        return;
-
-    if (!ValidateObjectAllowDeletedOrNull(funcName, buffer))
-        return;
-
-    // silently ignore a deleted buffer
-    if (buffer && buffer->IsDeleted())
+    if (!ValidateBufferForTarget(target, buffer, "bindBufferBase"))
         return;
 
-    WebGLRefPtr<WebGLBuffer>* genericBinding;
-    IndexedBufferBinding* indexedBinding;
-    if (!ValidateIndexedBufferBinding(funcName, target, index, &genericBinding,
-                                      &indexedBinding))
-    {
-        return;
-    }
-
-    if (buffer && !buffer->ValidateCanBindToTarget(funcName, target))
-        return;
-
-    ////
+    WebGLContextUnchecked::BindBufferBase(target, index, buffer);
 
-    gl->MakeCurrent();
-    gl->fBindBufferBase(target, index, buffer ? buffer->mGLName : 0);
-
-    ////
-
-    *genericBinding = buffer;
-    indexedBinding->mBufferBinding = buffer;
-    indexedBinding->mRangeStart = 0;
-    indexedBinding->mRangeSize = 0;
-
-    if (buffer) {
-        buffer->SetContentAfterBind(target);
-    }
+    UpdateBoundBufferIndexed(target, index, buffer);
 }
 
 void
 WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer,
                               WebGLintptr offset, WebGLsizeiptr size)
 {
-    const char funcName[] = "bindBufferRange";
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowDeletedOrNull(funcName, buffer))
+    if (!ValidateObjectAllowDeletedOrNull("bindBufferRange", buffer))
         return;
 
     // silently ignore a deleted buffer
     if (buffer && buffer->IsDeleted())
         return;
 
-    if (!ValidateNonNegative(funcName, "offset", offset) ||
-        !ValidateNonNegative(funcName, "size", size))
-    {
-        return;
-    }
-
-    WebGLRefPtr<WebGLBuffer>* genericBinding;
-    IndexedBufferBinding* indexedBinding;
-    if (!ValidateIndexedBufferBinding(funcName, target, index, &genericBinding,
-                                      &indexedBinding))
-    {
-        return;
-    }
-
-    if (buffer && !buffer->ValidateCanBindToTarget(funcName, target))
-        return;
-
-    ////
-
-    gl->MakeCurrent();
-
+    // ValidateBufferTarget
     switch (target) {
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
-        if (offset % 4 != 0 || size % 4 != 0) {
-            ErrorInvalidValue("%s: For %s, `offset` and `size` must be multiples of 4.",
-                              funcName, "TRANSFORM_FEEDBACK_BUFFER");
-            return;
-        }
+        if (index >= mGLMaxTransformFeedbackSeparateAttribs)
+            return ErrorInvalidValue("bindBufferRange: index should be less than "
+                                     "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
         break;
 
     case LOCAL_GL_UNIFORM_BUFFER:
-        {
-            GLuint offsetAlignment = 0;
-            gl->GetUIntegerv(LOCAL_GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &offsetAlignment);
-            if (offset % offsetAlignment != 0) {
-                ErrorInvalidValue("%s: For %s, `offset` must be a multiple of %s.",
-                                  funcName, "UNIFORM_BUFFER",
-                                  "UNIFORM_BUFFER_OFFSET_ALIGNMENT");
-                return;
-            }
-        }
+        if (index >= mGLMaxUniformBufferBindings)
+            return ErrorInvalidValue("bindBufferRange: index should be less than "
+                                     "MAX_UNIFORM_BUFFER_BINDINGS");
         break;
+
+    default:
+        return ErrorInvalidEnumInfo("bindBufferRange: target", target);
     }
 
-    ////
-
-#ifdef XP_MACOSX
-    if (buffer && buffer->Content() == WebGLBuffer::Kind::Undefined &&
-        gl->WorkAroundDriverBugs())
-    {
-        // BindBufferRange will fail if the buffer's contents is undefined.
-        // Bind so driver initializes the buffer.
-        gl->fBindBuffer(target, buffer->mGLName);
-    }
-#endif
-
-    gl->fBindBufferRange(target, index, buffer ? buffer->mGLName : 0, offset, size);
+    if (!ValidateBufferForTarget(target, buffer, "bindBufferRange"))
+        return;
 
-    ////
-
-    *genericBinding = buffer;
-    indexedBinding->mBufferBinding = buffer;
-    indexedBinding->mRangeStart = offset;
-    indexedBinding->mRangeSize = size;
+    WebGLContextUnchecked::BindBufferRange(target, index, buffer, offset, size);
 
-    if (buffer) {
-        buffer->SetContentAfterBind(target);
-    }
+    UpdateBoundBufferIndexed(target, index, buffer);
 }
 
-////////////////////////////////////////
-
 void
 WebGLContext::BufferData(GLenum target, WebGLsizeiptr size, GLenum usage)
 {
-    const char funcName[] = "bufferData";
     if (IsContextLost())
         return;
 
-    if (!ValidateNonNegative(funcName, "size", size))
+    if (!ValidateBufferTarget(target, "bufferData"))
+        return;
+
+    WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target);
+
+    if (size < 0)
+        return ErrorInvalidValue("bufferData: negative size");
+
+    if (!ValidateBufferUsageEnum(usage, "bufferData: usage"))
         return;
 
     // careful: WebGLsizeiptr is always 64-bit, but GLsizeiptr is like intptr_t.
     if (!CheckedInt<GLsizeiptr>(size).isValid())
-        return ErrorOutOfMemory("%s: bad size", funcName);
+        return ErrorOutOfMemory("bufferData: bad size");
 
-    const auto& buffer = ValidateBufferSelection(funcName, target);
-    if (!buffer)
-        return;
+    WebGLBuffer* boundBuffer = bufferSlot.get();
 
-    ////
+    if (!boundBuffer)
+        return ErrorInvalidOperation("bufferData: no buffer bound!");
 
     UniquePtr<uint8_t> zeroBuffer((uint8_t*)calloc(size, 1));
     if (!zeroBuffer)
-        return ErrorOutOfMemory("%s: Failed to allocate zeros.", funcName);
+        return ErrorOutOfMemory("bufferData: out of memory");
+
+    MakeContextCurrent();
+    InvalidateBufferFetching();
+
+    GLenum error = CheckedBufferData(target, size, zeroBuffer.get(), usage);
 
-    buffer->BufferData(target, size_t(size), zeroBuffer.get(), usage);
+    if (error) {
+        GenerateWarning("bufferData generated error %s", ErrorName(error));
+        return;
+    }
+
+    boundBuffer->SetByteLength(size);
+
+    if (!boundBuffer->ElementArrayCacheBufferData(nullptr, size)) {
+        boundBuffer->SetByteLength(0);
+        return ErrorOutOfMemory("bufferData: out of memory");
+    }
 }
 
 // BufferT may be one of
 // const dom::ArrayBuffer&
 // const dom::SharedArrayBuffer&
 // const dom::ArrayBufferView&
 template<typename BufferT>
 void
 WebGLContext::BufferDataT(GLenum target,
                           const BufferT& data,
                           GLenum usage)
 {
-    const char funcName[] = "bufferData";
     if (IsContextLost())
         return;
 
+    if (!ValidateBufferTarget(target, "bufferData"))
+        return;
+
+    const WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target);
+
     data.ComputeLengthAndData();
 
     // Careful: data.Length() could conceivably be any uint32_t, but GLsizeiptr
     // is like intptr_t.
     if (!CheckedInt<GLsizeiptr>(data.LengthAllowShared()).isValid())
         return ErrorOutOfMemory("bufferData: bad size");
 
-    const auto& buffer = ValidateBufferSelection(funcName, target);
-    if (!buffer)
+    if (!ValidateBufferUsageEnum(usage, "bufferData: usage"))
         return;
 
+    WebGLBuffer* boundBuffer = bufferSlot.get();
+
+    if (!boundBuffer)
+        return ErrorInvalidOperation("bufferData: no buffer bound!");
+
+    MakeContextCurrent();
+    InvalidateBufferFetching();
+
     // Warning: Possibly shared memory.  See bug 1225033.
-    buffer->BufferData(target, data.LengthAllowShared(), data.DataAllowShared(), usage);
+    GLenum error = CheckedBufferData(target, data.LengthAllowShared(), data.DataAllowShared(), usage);
+
+    if (error) {
+        GenerateWarning("bufferData generated error %s", ErrorName(error));
+        return;
+    }
+
+    boundBuffer->SetByteLength(data.LengthAllowShared());
+
+    // Warning: Possibly shared memory.  See bug 1225033.
+    if (!boundBuffer->ElementArrayCacheBufferData(data.DataAllowShared(), data.LengthAllowShared())) {
+        boundBuffer->SetByteLength(0);
+        return ErrorOutOfMemory("bufferData: out of memory");
+	}
 }
 
 void
 WebGLContext::BufferData(GLenum target,
                          const dom::SharedArrayBuffer& data,
                          GLenum usage)
 {
     BufferDataT(target, data, usage);
@@ -374,73 +259,67 @@ WebGLContext::BufferData(GLenum target,
 
 void
 WebGLContext::BufferData(GLenum target, const dom::ArrayBufferView& data,
                          GLenum usage)
 {
     BufferDataT(target, data, usage);
 }
 
-////////////////////////////////////////
-
 // BufferT may be one of
 // const dom::ArrayBuffer&
 // const dom::SharedArrayBuffer&
 // const dom::ArrayBufferView&
 template<typename BufferT>
 void
 WebGLContext::BufferSubDataT(GLenum target,
                              WebGLsizeiptr byteOffset,
                              const BufferT& data)
 {
-    const char funcName[] = "bufferSubData";
     if (IsContextLost())
         return;
 
-    if (!ValidateNonNegative(funcName, "byteOffset", byteOffset))
-        return;
-
-    const auto& buffer = ValidateBufferSelection(funcName, target);
-    if (!buffer)
+    if (!ValidateBufferTarget(target, "bufferSubData"))
         return;
 
-    if (buffer->mNumActiveTFOs) {
-        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                              " object.",
-                              "bufferSubData");
-        return;
-    }
+    WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target);
+
+    if (byteOffset < 0)
+        return ErrorInvalidValue("bufferSubData: negative offset");
+
+    WebGLBuffer* boundBuffer = bufferSlot.get();
+    if (!boundBuffer)
+        return ErrorInvalidOperation("bufferData: no buffer bound!");
 
     data.ComputeLengthAndData();
 
-    const auto checked_neededByteLength =
-        CheckedInt<size_t>(byteOffset) + data.LengthAllowShared();
+    CheckedInt<WebGLsizeiptr> checked_neededByteLength =
+        CheckedInt<WebGLsizeiptr>(byteOffset) + data.LengthAllowShared();
 
     if (!checked_neededByteLength.isValid()) {
         ErrorInvalidValue("bufferSubData: Integer overflow computing the needed"
                           " byte length.");
         return;
     }
 
-    if (checked_neededByteLength.value() > buffer->ByteLength()) {
+    if (checked_neededByteLength.value() > boundBuffer->ByteLength()) {
         ErrorInvalidValue("bufferSubData: Not enough data. Operation requires"
                           " %d bytes, but buffer only has %d bytes.",
                           checked_neededByteLength.value(),
-                          buffer->ByteLength());
+                          boundBuffer->ByteLength());
         return;
     }
 
+    // Warning: Possibly shared memory.  See bug 1225033.
+    boundBuffer->ElementArrayCacheBufferSubData(byteOffset, data.DataAllowShared(),
+                                                data.LengthAllowShared());
+
     MakeContextCurrent();
     // Warning: Possibly shared memory.  See bug 1225033.
-    gl->fBufferSubData(target, byteOffset, data.LengthAllowShared(),
-                       data.DataAllowShared());
-
-    // Warning: Possibly shared memory.  See bug 1225033.
-    buffer->ElementArrayCacheBufferSubData(byteOffset, data.DataAllowShared(),
-                                           data.LengthAllowShared());
+    gl->fBufferSubData(target, byteOffset, data.LengthAllowShared(), data.DataAllowShared());
 }
 
 void
 WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset,
                             const dom::Nullable<dom::ArrayBuffer>& maybeData)
 {
     if (maybeData.IsNull()) {
         ErrorInvalidValue("BufferSubData: returnedData is null.");
@@ -458,18 +337,16 @@ WebGLContext::BufferSubData(GLenum targe
 
 void
 WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset,
                             const dom::ArrayBufferView& data)
 {
     BufferSubDataT(target, byteOffset, data);
 }
 
-////////////////////////////////////////
-
 already_AddRefed<WebGLBuffer>
 WebGLContext::CreateBuffer()
 {
     if (IsContextLost())
         return nullptr;
 
     GLuint buf = 0;
     MakeContextCurrent();
@@ -486,55 +363,70 @@ WebGLContext::DeleteBuffer(WebGLBuffer* 
         return;
 
     if (!ValidateObjectAllowDeletedOrNull("deleteBuffer", buffer))
         return;
 
     if (!buffer || buffer->IsDeleted())
         return;
 
-    ////
+    // TODO: Extract this into a helper function?
+    if (mBoundArrayBuffer == buffer) {
+        WebGLContextUnchecked::BindBuffer(LOCAL_GL_ARRAY_BUFFER, nullptr);
+        mBoundArrayBuffer = nullptr;
+    }
 
-    const auto fnClearIfBuffer = [&](WebGLRefPtr<WebGLBuffer>& bindPoint) {
-        if (bindPoint == buffer) {
-            bindPoint = nullptr;
-        }
-    };
-
-    fnClearIfBuffer(mBoundArrayBuffer);
-    fnClearIfBuffer(mBoundVertexArray->mElementArrayBuffer);
+    if (mBoundVertexArray->mElementArrayBuffer == buffer) {
+        WebGLContextUnchecked::BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, nullptr);
+        mBoundVertexArray->mElementArrayBuffer = nullptr;
+    }
 
     // WebGL binding points
     if (IsWebGL2()) {
-        fnClearIfBuffer(mBoundCopyReadBuffer);
-        fnClearIfBuffer(mBoundCopyWriteBuffer);
-        fnClearIfBuffer(mBoundPixelPackBuffer);
-        fnClearIfBuffer(mBoundPixelUnpackBuffer);
-        fnClearIfBuffer(mBoundUniformBuffer);
-        fnClearIfBuffer(mBoundTransformFeedback->mGenericBufferBinding);
+        if (mBoundCopyReadBuffer == buffer)
+            mBoundCopyReadBuffer = nullptr;
+
+        if (mBoundCopyWriteBuffer == buffer)
+            mBoundCopyWriteBuffer = nullptr;
+
+        if (mBoundPixelPackBuffer == buffer)
+            mBoundPixelPackBuffer = nullptr;
 
-        if (!mBoundTransformFeedback->mIsActive) {
-            for (auto& binding : mBoundTransformFeedback->mIndexedBindings) {
-                fnClearIfBuffer(binding.mBufferBinding);
+        if (mBoundPixelUnpackBuffer == buffer)
+            mBoundPixelUnpackBuffer = nullptr;
+
+        if (mBoundTransformFeedbackBuffer == buffer)
+            mBoundTransformFeedbackBuffer = nullptr;
+
+        if (mBoundUniformBuffer == buffer)
+            mBoundUniformBuffer = nullptr;
+
+        const size_t xfBufferCount = mBoundTransformFeedbackBuffers.Length();
+        for (size_t n = 0; n < xfBufferCount; n++) {
+            if (mBoundTransformFeedbackBuffers[n] == buffer) {
+                mBoundTransformFeedbackBuffers[n] = nullptr;
             }
         }
 
-        for (auto& binding : mIndexedUniformBufferBindings) {
-            fnClearIfBuffer(binding.mBufferBinding);
+        const size_t uniformBufferCount = mBoundUniformBuffers.Length();
+        for (size_t n = 0; n < uniformBufferCount; n++) {
+            if (mBoundUniformBuffers[n] == buffer) {
+                mBoundUniformBuffers[n] = nullptr;
+            }
         }
     }
 
     for (int32_t i = 0; i < mGLMaxVertexAttribs; i++) {
-        if (mBoundVertexArray->HasAttrib(i)) {
-            fnClearIfBuffer(mBoundVertexArray->mAttribs[i].buf);
+        if (mBoundVertexArray->HasAttrib(i) &&
+            mBoundVertexArray->mAttribs[i].buf == buffer)
+        {
+            mBoundVertexArray->mAttribs[i].buf = nullptr;
         }
     }
 
-    ////
-
     buffer->RequestDelete();
 }
 
 bool
 WebGLContext::IsBuffer(WebGLBuffer* buffer)
 {
     if (IsContextLost())
         return false;
@@ -544,9 +436,216 @@ WebGLContext::IsBuffer(WebGLBuffer* buff
 
     if (buffer->IsDeleted())
         return false;
 
     MakeContextCurrent();
     return gl->fIsBuffer(buffer->mGLName);
 }
 
+bool
+WebGLContext::ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer,
+                                      const char* info)
+{
+    if (!buffer)
+        return true;
+
+    /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1
+     *
+     * In the WebGL 2 API, buffers have their WebGL buffer type
+     * initially set to undefined. Calling bindBuffer, bindBufferRange
+     * or bindBufferBase with the target argument set to any buffer
+     * binding point except COPY_READ_BUFFER or COPY_WRITE_BUFFER will
+     * then set the WebGL buffer type of the buffer being bound
+     * according to the table above.
+     *
+     * Any call to one of these functions which attempts to bind a
+     * WebGLBuffer that has the element array WebGL buffer type to a
+     * binding point that falls under other data, or bind a
+     * WebGLBuffer which has the other data WebGL buffer type to
+     * ELEMENT_ARRAY_BUFFER will generate an INVALID_OPERATION error,
+     * and the state of the binding point will remain untouched.
+     */
+
+    GLenum boundTo = GetCurrentBinding(buffer);
+    if (boundTo != LOCAL_GL_NONE) {
+        if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER &&
+            boundTo != LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER)
+        {
+            ErrorInvalidOperation("Can't bind buffer to TRANSFORM_FEEDBACK_BUFFER as the "
+                                  "buffer is already bound to another bind point.");
+            return false;
+        }
+        else if (target != LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER &&
+                 boundTo == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER)
+        {
+            ErrorInvalidOperation("Can't bind buffer to bind point as it is currently "
+                                  "bound to TRANSFORM_FEEDBACK_BUFFER.");
+            return false;
+        }
+    }
+
+    WebGLBuffer::Kind content = buffer->Content();
+    if (content == WebGLBuffer::Kind::Undefined)
+        return true;
+
+    switch (target) {
+    case LOCAL_GL_COPY_READ_BUFFER:
+    case LOCAL_GL_COPY_WRITE_BUFFER:
+        return true;
+
+    case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
+        if (content == WebGLBuffer::Kind::ElementArray)
+            return true;
+        break;
+
+    case LOCAL_GL_ARRAY_BUFFER:
+    case LOCAL_GL_PIXEL_PACK_BUFFER:
+    case LOCAL_GL_PIXEL_UNPACK_BUFFER:
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
+    case LOCAL_GL_UNIFORM_BUFFER:
+        if (content == WebGLBuffer::Kind::OtherData)
+            return true;
+        break;
+
+    default:
+        MOZ_CRASH();
+    }
+
+    ErrorInvalidOperation("%s: buffer already contains %s data.", info,
+                          content == WebGLBuffer::Kind::OtherData ? "other" : "element");
+
+    return false;
+}
+
+bool
+WebGLContext::ValidateBufferUsageEnum(GLenum target, const char* info)
+{
+    switch (target) {
+    case LOCAL_GL_STREAM_DRAW:
+    case LOCAL_GL_STATIC_DRAW:
+    case LOCAL_GL_DYNAMIC_DRAW:
+        return true;
+    default:
+        break;
+    }
+
+    ErrorInvalidEnumInfo(info, target);
+    return false;
+}
+
+WebGLRefPtr<WebGLBuffer>&
+WebGLContext::GetBufferSlotByTarget(GLenum target)
+{
+    /* This function assumes that target has been validated for either
+     * WebGL1 or WebGL2.
+     */
+    switch (target) {
+    case LOCAL_GL_ARRAY_BUFFER:
+        return mBoundArrayBuffer;
+
+    case LOCAL_GL_COPY_READ_BUFFER:
+        return mBoundCopyReadBuffer;
+
+    case LOCAL_GL_COPY_WRITE_BUFFER:
+        return mBoundCopyWriteBuffer;
+
+    case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
+        return mBoundVertexArray->mElementArrayBuffer;
+
+    case LOCAL_GL_PIXEL_PACK_BUFFER:
+        return mBoundPixelPackBuffer;
+
+    case LOCAL_GL_PIXEL_UNPACK_BUFFER:
+        return mBoundPixelUnpackBuffer;
+
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
+        return mBoundTransformFeedbackBuffer;
+
+    case LOCAL_GL_UNIFORM_BUFFER:
+        return mBoundUniformBuffer;
+
+    default:
+        MOZ_CRASH("GFX: Should not get here.");
+    }
+}
+
+WebGLRefPtr<WebGLBuffer>&
+WebGLContext::GetBufferSlotByTargetIndexed(GLenum target, GLuint index)
+{
+    /* This function assumes that target has been validated for either WebGL1 or WebGL. */
+    switch (target) {
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
+        MOZ_ASSERT(index < mGLMaxTransformFeedbackSeparateAttribs);
+        return mBoundTransformFeedbackBuffers[index];
+
+    case LOCAL_GL_UNIFORM_BUFFER:
+        MOZ_ASSERT(index < mGLMaxUniformBufferBindings);
+        return mBoundUniformBuffers[index];
+
+    default:
+        MOZ_CRASH("GFX: Should not get here.");
+    }
+}
+
+GLenum
+WebGLContext::GetCurrentBinding(WebGLBuffer* buffer) const
+{
+    if (mBoundArrayBuffer == buffer)
+        return LOCAL_GL_ARRAY_BUFFER;
+
+    if (mBoundCopyReadBuffer == buffer)
+        return LOCAL_GL_COPY_READ_BUFFER;
+
+    if (mBoundCopyWriteBuffer == buffer)
+        return LOCAL_GL_COPY_WRITE_BUFFER;
+
+    if (mBoundPixelPackBuffer == buffer)
+        return LOCAL_GL_PIXEL_PACK_BUFFER;
+
+    if (mBoundPixelUnpackBuffer == buffer)
+        return LOCAL_GL_PIXEL_UNPACK_BUFFER;
+
+    if (mBoundTransformFeedbackBuffer == buffer ||
+        mBoundTransformFeedbackBuffers.Contains(buffer)) {
+        return LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER;
+    }
+
+    if (mBoundUniformBuffer == buffer ||
+        mBoundUniformBuffers.Contains(buffer)) {
+        return LOCAL_GL_UNIFORM_BUFFER;
+    }
+
+    return LOCAL_GL_NONE;
+}
+
+GLenum
+WebGLContext::CheckedBufferData(GLenum target, GLsizeiptr size,
+                                const GLvoid* data, GLenum usage)
+{
+#ifdef XP_MACOSX
+    // bug 790879
+    if (gl->WorkAroundDriverBugs() &&
+        int64_t(size) > INT32_MAX) // cast avoids a potential always-true warning on 32bit
+    {
+        GenerateWarning("Rejecting valid bufferData call with size %lu to avoid"
+                        " a Mac bug", size);
+        return LOCAL_GL_INVALID_VALUE;
+    }
+#endif
+
+    WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target);
+    WebGLBuffer* boundBuffer = bufferSlot.get();
+    MOZ_ASSERT(boundBuffer, "No buffer bound for this target.");
+
+    bool sizeChanges = uint32_t(size) != boundBuffer->ByteLength();
+    if (sizeChanges) {
+        GetAndFlushUnderlyingGLErrors();
+        gl->fBufferData(target, size, data, usage);
+        GLenum error = GetAndFlushUnderlyingGLErrors();
+        return error;
+    } else {
+        gl->fBufferData(target, size, data, usage);
+        return LOCAL_GL_NO_ERROR;
+    }
+}
+
 } // namespace mozilla
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -96,45 +96,36 @@ ScopedResolveTexturesForDraw::ScopedReso
     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();
+    std::vector<const WebGLFBAttachPoint*> fbAttachments;
+    if (mWebGL->mBoundDrawFramebuffer) {
+        const auto& fb = mWebGL->mBoundDrawFramebuffer;
+        fb->GatherAttachments(&fbAttachments);
     }
 
     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) {
             if (texUnit >= texList.Length())
                 continue;
 
             const auto& tex = texList[texUnit];
             if (!tex)
                 continue;
 
-            if (attachList &&
-                tex->IsFeedback(mWebGL, funcName, texUnit, *attachList))
-            {
+            if (tex->IsFeedback(mWebGL, funcName, texUnit, fbAttachments)) {
                 *out_error = true;
                 return;
             }
 
             FakeBlackType fakeBlack;
             if (!tex->ResolveForDraw(funcName, texUnit, &fakeBlack)) {
                 mWebGL->ErrorOutOfMemory("%s: Failed to resolve textures for draw.",
                                          funcName);
@@ -245,331 +236,160 @@ WebGLContext::DrawInstanced_check(const 
         ErrorInvalidOperation("%s: at least one vertex attribute divisor should be 0", info);
         return false;
     }
 
     return true;
 }
 
 bool
-WebGLContext::DrawArrays_check(const char* funcName, GLenum mode, GLint first,
-                               GLsizei vertCount, GLsizei instanceCount)
+WebGLContext::DrawArrays_check(GLint first, GLsizei count, GLsizei primcount,
+                               const char* info)
 {
-    if (!ValidateDrawModeEnum(mode, funcName))
+    if (first < 0 || count < 0) {
+        ErrorInvalidValue("%s: negative first or count", info);
         return false;
+    }
 
-    if (!ValidateNonNegative(funcName, "first", first) ||
-        !ValidateNonNegative(funcName, "vertCount", vertCount) ||
-        !ValidateNonNegative(funcName, "instanceCount", instanceCount))
-    {
+    if (primcount < 0) {
+        ErrorInvalidValue("%s: negative primcount", info);
         return false;
     }
 
-    if (!ValidateStencilParamsForDrawCall())
+    if (!ValidateStencilParamsForDrawCall()) {
         return false;
+    }
 
-    if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
-        MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
-        if (mPrimRestartTypeBytes != 4) {
-            mPrimRestartTypeBytes = 4;
-
-            // OSX has issues leaving this as 0.
-            gl->fPrimitiveRestartIndex(UINT32_MAX);
-        }
+    // If count is 0, there's nothing to do.
+    if (count == 0 || primcount == 0) {
+        return false;
     }
 
-    if (!vertCount || !instanceCount)
-        return false; // No error, just early out.
-
-    if (!ValidateBufferFetching(funcName))
+    if (!ValidateBufferFetching(info)) {
         return false;
+    }
 
-    const auto checked_firstPlusCount = CheckedInt<GLsizei>(first) + vertCount;
+    CheckedInt<GLsizei> checked_firstPlusCount = CheckedInt<GLsizei>(first) + count;
+
     if (!checked_firstPlusCount.isValid()) {
-        ErrorInvalidOperation("%s: overflow in first+vertCount", funcName);
+        ErrorInvalidOperation("%s: overflow in first+count", info);
         return false;
     }
 
     if (uint32_t(checked_firstPlusCount.value()) > mMaxFetchedVertices) {
-        ErrorInvalidOperation("%s: Bound vertex attribute buffers do not have sufficient"
-                              " size for given first and count.",
-                              funcName);
+        ErrorInvalidOperation("%s: bound vertex attribute buffers do not have sufficient size for given first and count", info);
+        return false;
+    }
+
+    if (uint32_t(primcount) > mMaxFetchedInstances) {
+        ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info);
+        return false;
+    }
+
+    MOZ_ASSERT(gl->IsCurrent());
+
+    if (mBoundDrawFramebuffer) {
+        if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(info))
+            return false;
+    } else {
+        ClearBackbufferIfNeeded();
+    }
+
+    if (!DoFakeVertexAttrib0(checked_firstPlusCount.value())) {
         return false;
     }
 
     return true;
 }
 
-////////////////////////////////////////
-
-class ScopedDrawHelper final
-{
-    WebGLContext* const mWebGL;
-    bool mDidFake;
-
-public:
-    ScopedDrawHelper(WebGLContext* webgl, const char* funcName, uint32_t firstVertex,
-                     uint32_t vertCount, uint32_t instanceCount, bool* const out_error)
-        : mWebGL(webgl)
-        , mDidFake(false)
-    {
-        if (instanceCount > mWebGL->mMaxFetchedInstances) {
-            mWebGL->ErrorInvalidOperation("%s: Bound instance attribute buffers do not"
-                                          " have sufficient size for given"
-                                          " `instanceCount`.",
-                                          funcName);
-            *out_error = true;
-            return;
-        }
-
-        MOZ_ASSERT(mWebGL->gl->IsCurrent());
-
-        if (mWebGL->mBoundDrawFramebuffer) {
-            if (!mWebGL->mBoundDrawFramebuffer->ValidateAndInitAttachments(funcName)) {
-                *out_error = true;
-                return;
-            }
-        } else {
-            mWebGL->ClearBackbufferIfNeeded();
-        }
-
-        ////
-
-        const size_t requiredVerts = firstVertex + vertCount;
-        if (!mWebGL->DoFakeVertexAttrib0(requiredVerts)) {
-            *out_error = true;
-            return;
-        }
-        mDidFake = true;
-
-        ////
-        // Check UBO sizes.
-
-        const auto& linkInfo = webgl->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;
-            }
-
-            const auto availByteCount = binding->ByteCount();
-            if (dataSize > availByteCount) {
-                mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is smaller"
-                                              " than UNIFORM_BLOCK_DATA_SIZE.",
-                                              funcName);
-                *out_error = true;
-                return;
-            }
-        }
-
-        ////
-
-        mWebGL->RunContextLossTimer();
-    }
-
-    ~ScopedDrawHelper() {
-        if (mDidFake) {
-            mWebGL->UndoFakeVertexAttrib0();
-        }
-    }
-};
-
-////////////////////////////////////////
-
-static uint32_t
-UsedVertsForTFDraw(GLenum mode, uint32_t vertCount)
-{
-    uint8_t vertsPerPrim;
-
-    switch (mode) {
-    case LOCAL_GL_POINTS:
-        vertsPerPrim = 1;
-        break;
-    case LOCAL_GL_LINES:
-        vertsPerPrim = 2;
-        break;
-    case LOCAL_GL_TRIANGLES:
-        vertsPerPrim = 3;
-        break;
-    default:
-        MOZ_CRASH("`mode`");
-    }
-
-    return vertCount / vertsPerPrim * vertsPerPrim;
-}
-
-class ScopedDrawWithTransformFeedback final
-{
-    WebGLContext* const mWebGL;
-    WebGLTransformFeedback* const mTFO;
-    const bool mWithTF;
-    uint32_t mUsedVerts;
-
-public:
-    ScopedDrawWithTransformFeedback(WebGLContext* webgl, const char* funcName,
-                                    GLenum mode, uint32_t vertCount,
-                                    uint32_t instanceCount, bool* const out_error)
-        : mWebGL(webgl)
-        , mTFO(mWebGL->mBoundTransformFeedback)
-        , mWithTF(mTFO &&
-                  mTFO->mIsActive &&
-                  !mTFO->mIsPaused)
-        , mUsedVerts(0)
-    {
-        *out_error = false;
-        if (!mWithTF)
-            return;
-
-        if (mode != mTFO->mActive_PrimMode) {
-            mWebGL->ErrorInvalidOperation("%s: Drawing with transform feedback requires"
-                                          " `mode` to match BeginTransformFeedback's"
-                                          " `primitiveMode`.",
-                                          funcName);
-            *out_error = true;
-            return;
-        }
-
-        const auto usedVertsPerInstance = UsedVertsForTFDraw(mode, vertCount);
-        const auto usedVerts = CheckedInt<uint32_t>(usedVertsPerInstance) * instanceCount;
-
-        const auto remainingCapacity = mTFO->mActive_VertCapacity - mTFO->mActive_VertPosition;
-        if (!usedVerts.isValid() ||
-            usedVerts.value() > remainingCapacity)
-        {
-            mWebGL->ErrorInvalidOperation("%s: Insufficient buffer capacity remaining for"
-                                          " transform feedback.",
-                                          funcName);
-            *out_error = true;
-            return;
-        }
-
-        mUsedVerts = usedVerts.value();
-    }
-
-    void Advance() const {
-        if (!mWithTF)
-            return;
-
-        mTFO->mActive_VertPosition += mUsedVerts;
-    }
-};
-
-////////////////////////////////////////
-
 void
-WebGLContext::DrawArrays(GLenum mode, GLint first, GLsizei vertCount)
+WebGLContext::DrawArrays(GLenum mode, GLint first, GLsizei count)
 {
     const char funcName[] = "drawArrays";
     if (IsContextLost())
         return;
 
+    if (!ValidateDrawModeEnum(mode, funcName))
+        return;
+
     MakeContextCurrent();
 
-    bool error = false;
+    bool error;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
-    const GLsizei instanceCount = 1;
-    if (!DrawArrays_check(funcName, mode, first, vertCount, instanceCount))
+    if (!DrawArrays_check(first, count, 1, funcName))
         return;
 
-    const ScopedDrawHelper scopedHelper(this, funcName, first, vertCount, instanceCount, &error);
-    if (error)
-        return;
-
-    const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount,
-                                                   instanceCount, &error);
-    if (error)
-        return;
+    RunContextLossTimer();
 
     {
         ScopedMaskWorkaround autoMask(*this);
-        gl->fDrawArrays(mode, first, vertCount);
+        gl->fDrawArrays(mode, first, count);
     }
 
     Draw_cleanup(funcName);
-    scopedTF.Advance();
 }
 
 void
-WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei vertCount,
-                                  GLsizei instanceCount)
+WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount)
 {
     const char funcName[] = "drawArraysInstanced";
     if (IsContextLost())
         return;
 
+    if (!ValidateDrawModeEnum(mode, funcName))
+        return;
+
     MakeContextCurrent();
 
-    bool error = false;
+    bool error;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
-    if (!DrawArrays_check(funcName, mode, first, vertCount, instanceCount))
+    if (!DrawArrays_check(first, count, primcount, funcName))
         return;
 
     if (!DrawInstanced_check(funcName))
         return;
 
-    const ScopedDrawHelper scopedHelper(this, funcName, first, vertCount, instanceCount, &error);
-    if (error)
-        return;
-
-    const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount,
-                                                   instanceCount, &error);
-    if (error)
-        return;
+    RunContextLossTimer();
 
     {
         ScopedMaskWorkaround autoMask(*this);
-        gl->fDrawArraysInstanced(mode, first, vertCount, instanceCount);
+        gl->fDrawArraysInstanced(mode, first, count, primcount);
     }
 
     Draw_cleanup(funcName);
-    scopedTF.Advance();
 }
 
-////////////////////////////////////////
-
 bool
-WebGLContext::DrawElements_check(const char* funcName, GLenum mode, GLsizei vertCount,
-                                 GLenum type, WebGLintptr byteOffset,
-                                 GLsizei instanceCount)
+WebGLContext::DrawElements_check(GLsizei count, GLenum type,
+                                 WebGLintptr byteOffset, GLsizei primcount,
+                                 const char* info, GLuint* out_upperBound)
 {
-    if (!ValidateDrawModeEnum(mode, funcName))
-        return false;
-
-    if (mBoundTransformFeedback &&
-        mBoundTransformFeedback->mIsActive &&
-        !mBoundTransformFeedback->mIsPaused)
-    {
-        ErrorInvalidOperation("%s: DrawElements* functions are incompatible with"
-                              " transform feedback.",
-                              funcName);
+    if (count < 0 || byteOffset < 0) {
+        ErrorInvalidValue("%s: negative count or offset", info);
         return false;
     }
 
-    if (!ValidateNonNegative(funcName, "vertCount", vertCount) ||
-        !ValidateNonNegative(funcName, "byteOffset", byteOffset) ||
-        !ValidateNonNegative(funcName, "instanceCount", instanceCount))
-    {
+    if (primcount < 0) {
+        ErrorInvalidValue("%s: negative primcount", info);
         return false;
     }
 
-    if (!ValidateStencilParamsForDrawCall())
+    if (!ValidateStencilParamsForDrawCall()) {
         return false;
+    }
 
-    if (!vertCount || !instanceCount)
-        return false; // No error, just early out.
+    // If count is 0, there's nothing to do.
+    if (count == 0 || primcount == 0)
+        return false;
 
     uint8_t bytesPerElem = 0;
     switch (type) {
     case LOCAL_GL_UNSIGNED_BYTE:
         bytesPerElem = 1;
         break;
 
     case LOCAL_GL_UNSIGNED_SHORT:
@@ -579,157 +399,179 @@ WebGLContext::DrawElements_check(const c
     case LOCAL_GL_UNSIGNED_INT:
         if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
             bytesPerElem = 4;
         }
         break;
     }
 
     if (!bytesPerElem) {
-        ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", funcName, type);
+        ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", info, type);
         return false;
     }
 
     if (byteOffset % bytesPerElem != 0) {
         ErrorInvalidOperation("%s: `byteOffset` must be a multiple of the size of `type`",
-                              funcName);
+                              info);
         return false;
     }
 
     const GLsizei first = byteOffset / bytesPerElem;
-    const CheckedUint32 checked_byteCount = bytesPerElem * CheckedUint32(vertCount);
+    const CheckedUint32 checked_byteCount = bytesPerElem * CheckedUint32(count);
 
     if (!checked_byteCount.isValid()) {
-        ErrorInvalidValue("%s: Overflow in byteCount.", funcName);
+        ErrorInvalidValue("%s: overflow in byteCount", info);
         return false;
     }
 
     if (!mBoundVertexArray->mElementArrayBuffer) {
-        ErrorInvalidOperation("%s: Must have element array buffer binding.", funcName);
+        ErrorInvalidOperation("%s: must have element array buffer binding", info);
         return false;
     }
 
     WebGLBuffer& elemArrayBuffer = *mBoundVertexArray->mElementArrayBuffer;
 
     if (!elemArrayBuffer.ByteLength()) {
-        ErrorInvalidOperation("%s: Bound element array buffer doesn't have any data.",
-                              funcName);
+        ErrorInvalidOperation("%s: bound element array buffer doesn't have any data", info);
         return false;
     }
 
     CheckedInt<GLsizei> checked_neededByteCount = checked_byteCount.toChecked<GLsizei>() + byteOffset;
 
     if (!checked_neededByteCount.isValid()) {
-        ErrorInvalidOperation("%s: Overflow in byteOffset+byteCount.", funcName);
+        ErrorInvalidOperation("%s: overflow in byteOffset+byteCount", info);
         return false;
     }
 
     if (uint32_t(checked_neededByteCount.value()) > elemArrayBuffer.ByteLength()) {
-        ErrorInvalidOperation("%s: Bound element array buffer is too small for given"
-                              " count and offset.",
-                              funcName);
+        ErrorInvalidOperation("%s: bound element array buffer is too small for given count and offset", info);
         return false;
     }
 
-    if (!ValidateBufferFetching(funcName))
+    if (!ValidateBufferFetching(info))
         return false;
 
     if (!mMaxFetchedVertices ||
-        !elemArrayBuffer.Validate(type, mMaxFetchedVertices - 1, first, vertCount))
+        !elemArrayBuffer.Validate(type, mMaxFetchedVertices - 1, first, count, out_upperBound))
     {
-        ErrorInvalidOperation("%s: bound vertex attribute buffers do not have sufficient "
-                              "size for given indices from the bound element array",
-                              funcName);
+        ErrorInvalidOperation(
+                              "%s: bound vertex attribute buffers do not have sufficient "
+                              "size for given indices from the bound element array", info);
+        return false;
+    }
+
+    if (uint32_t(primcount) > mMaxFetchedInstances) {
+        ErrorInvalidOperation("%s: bound instance attribute buffers do not have sufficient size for given primcount", info);
         return false;
     }
 
     // Bug 1008310 - Check if buffer has been used with a different previous type
     if (elemArrayBuffer.IsElementArrayUsedWithMultipleTypes()) {
         GenerateWarning("%s: bound element array buffer previously used with a type other than "
                         "%s, this will affect performance.",
-                        funcName, WebGLContext::EnumName(type));
+                        info,
+                        WebGLContext::EnumName(type));
+    }
+
+    MOZ_ASSERT(gl->IsCurrent());
+
+    if (mBoundDrawFramebuffer) {
+        if (!mBoundDrawFramebuffer->ValidateAndInitAttachments(info))
+            return false;
+    } else {
+        ClearBackbufferIfNeeded();
+    }
+
+    if (!DoFakeVertexAttrib0(mMaxFetchedVertices)) {
+        return false;
     }
 
     return true;
 }
 
 void
-WebGLContext::DrawElements(GLenum mode, GLsizei vertCount, GLenum type,
+WebGLContext::DrawElements(GLenum mode, GLsizei count, GLenum type,
                            WebGLintptr byteOffset)
 {
     const char funcName[] = "drawElements";
     if (IsContextLost())
         return;
 
+    if (!ValidateDrawModeEnum(mode, funcName))
+        return;
+
     MakeContextCurrent();
 
-    bool error = false;
+    bool error;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
-    const GLsizei instanceCount = 1;
-    if (!DrawElements_check(funcName, mode, vertCount, type, byteOffset, instanceCount))
+    GLuint upperBound = 0;
+    if (!DrawElements_check(count, type, byteOffset, 1, funcName, &upperBound))
         return;
 
-    const ScopedDrawHelper scopedHelper(this, funcName, 0, mMaxFetchedVertices, instanceCount,
-                                        &error);
-    if (error)
-        return;
+    RunContextLossTimer();
 
     {
         ScopedMaskWorkaround autoMask(*this);
-        gl->fDrawElements(mode, vertCount, type,
-                          reinterpret_cast<GLvoid*>(byteOffset));
+
+        if (gl->IsSupported(gl::GLFeature::draw_range_elements)) {
+            gl->fDrawRangeElements(mode, 0, upperBound, count, type,
+                                   reinterpret_cast<GLvoid*>(byteOffset));
+        } else {
+            gl->fDrawElements(mode, count, type,
+                              reinterpret_cast<GLvoid*>(byteOffset));
+        }
     }
 
     Draw_cleanup(funcName);
 }
 
 void
-WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei vertCount, GLenum type,
-                                    WebGLintptr byteOffset, GLsizei instanceCount)
+WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type,
+                                    WebGLintptr byteOffset, GLsizei primcount)
 {
     const char funcName[] = "drawElementsInstanced";
     if (IsContextLost())
         return;
 
+    if (!ValidateDrawModeEnum(mode, funcName))
+        return;
+
     MakeContextCurrent();
 
-    bool error = false;
+    bool error;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
-    if (!DrawElements_check(funcName, mode, vertCount, type, byteOffset, instanceCount))
+    GLuint upperBound = 0;
+    if (!DrawElements_check(count, type, byteOffset, primcount, funcName, &upperBound))
         return;
 
     if (!DrawInstanced_check(funcName))
         return;
 
-    const ScopedDrawHelper scopedHelper(this, funcName, 0, mMaxFetchedVertices, instanceCount,
-                                        &error);
-    if (error)
-        return;
+    RunContextLossTimer();
 
     {
         ScopedMaskWorkaround autoMask(*this);
-        gl->fDrawElementsInstanced(mode, vertCount, type,
+        gl->fDrawElementsInstanced(mode, count, type,
                                    reinterpret_cast<GLvoid*>(byteOffset),
-                                   instanceCount);
+                                   primcount);
     }
 
     Draw_cleanup(funcName);
 }
 
-////////////////////////////////////////
+void WebGLContext::Draw_cleanup(const char* funcName)
+{
+    UndoFakeVertexAttrib0();
 
-void
-WebGLContext::Draw_cleanup(const char* funcName)
-{
     if (!mBoundDrawFramebuffer) {
         Invalidate();
         mShouldPresent = true;
         MOZ_ASSERT(!mBackbufferNeedsClear);
     }
 
     if (gl->WorkAroundDriverBugs()) {
         if (gl->Renderer() == gl::GLRenderer::Tegra) {
@@ -743,22 +585,19 @@ WebGLContext::Draw_cleanup(const char* f
     }
 
     // Let's check for a really common error: Viewport is larger than the actual
     // destination framebuffer.
     uint32_t destWidth = mViewportWidth;
     uint32_t destHeight = mViewportHeight;
 
     if (mBoundDrawFramebuffer) {
-        const auto& drawBuffers = mBoundDrawFramebuffer->ColorDrawBuffers();
-        for (const auto& cur : drawBuffers) {
-            if (!cur->IsDefined())
-                continue;
-            cur->Size(&destWidth, &destHeight);
-            break;
+        const auto& fba = mBoundDrawFramebuffer->ColorAttachment(0);
+        if (fba.IsDefined()) {
+            fba.Size(&destWidth, &destHeight);
         }
     } else {
         destWidth = mWidth;
         destHeight = mHeight;
     }
 
     if (mViewportWidth > int32_t(destWidth) ||
         mViewportHeight > int32_t(destHeight))
--- a/dom/canvas/WebGLContextFramebufferOperations.cpp
+++ b/dom/canvas/WebGLContextFramebufferOperations.cpp
@@ -139,47 +139,86 @@ WebGLContext::DepthMask(WebGLboolean b)
 
 void
 WebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers)
 {
     const char funcName[] = "drawBuffers";
     if (IsContextLost())
         return;
 
-    if (mBoundDrawFramebuffer) {
-        mBoundDrawFramebuffer->DrawBuffers(funcName, buffers);
+    if (!mBoundDrawFramebuffer) {
+        // GLES 3.0.4 p186:
+        // "If the GL is bound to the default framebuffer, then `n` must be 1 and the
+        //  constant must be BACK or NONE. [...] If DrawBuffers is supplied with a
+        //  constant other than BACK and NONE, or with a value of `n` other than 1, the
+        //  error INVALID_OPERATION is generated."
+        if (buffers.Length() != 1) {
+            ErrorInvalidOperation("%s: For the default framebuffer, `buffers` must have a"
+                                  " length of 1.",
+                                  funcName);
+            return;
+        }
+
+        switch (buffers[0]) {
+        case LOCAL_GL_NONE:
+        case LOCAL_GL_BACK:
+            break;
+
+        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]);
         return;
     }
 
-    // GLES 3.0.4 p186:
-    // "If the GL is bound to the default framebuffer, then `n` must be 1 and the
-    //  constant must be BACK or NONE. [...] If DrawBuffers is supplied with a
-    //  constant other than BACK and NONE, or with a value of `n` other than 1, the
-    //  error INVALID_OPERATION is generated."
-    if (buffers.Length() != 1) {
-        ErrorInvalidOperation("%s: For the default framebuffer, `buffers` must have a"
-                              " length of 1.",
-                              funcName);
+    // Framebuffer object (not default framebuffer)
+
+    if (buffers.Length() > mImplMaxDrawBuffers) {
+        // "An INVALID_VALUE error is generated if `n` is greater than MAX_DRAW_BUFFERS."
+        ErrorInvalidValue("%s: `buffers` must have a length <= MAX_DRAW_BUFFERS.",
+                          funcName);
         return;
     }
 
-    switch (buffers[0]) {
-    case LOCAL_GL_NONE:
-    case LOCAL_GL_BACK:
-        break;
+    for (size_t i = 0; i < buffers.Length(); i++) {
+        // "If the GL is bound to a draw framebuffer object, the `i`th buffer listed in
+        //  bufs must be COLOR_ATTACHMENTi or NONE. Specifying a buffer out of order,
+        //  BACK, or COLOR_ATTACHMENTm where `m` is greater than or equal to the value of
+        // MAX_COLOR_ATTACHMENTS, will generate the error INVALID_OPERATION.
 
-    default:
-        ErrorInvalidOperation("%s: For the default framebuffer, `buffers[0]` must be"
-                              " BACK or NONE.",
-                              funcName);
-        return;
+        // WEBGL_draw_buffers:
+        // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or
+        //  equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter."
+        // This means that if buffers.Length() isn't larger than MaxDrawBuffers, it won't
+        // be larger than MaxColorAttachments.
+        if (buffers[i] != LOCAL_GL_NONE &&
+            buffers[i] != LOCAL_GL_COLOR_ATTACHMENT0 + i)
+        {
+            ErrorInvalidOperation("%s: `buffers[i]` must be NONE or COLOR_ATTACHMENTi.",
+                                  funcName);
+            return;
+        }
     }
 
-    mDefaultFB_DrawBuffer0 = buffers[0];
-    gl->Screen()->SetDrawBuffer(buffers[0]);
+    MakeContextCurrent();
+
+    const GLenum* ptr = nullptr;
+    if (buffers.Length()) {
+        ptr = buffers.Elements();
+    }
+
+    gl->fDrawBuffers(buffers.Length(), ptr);
+
+    const auto end = ptr + buffers.Length();
+    mBoundDrawFramebuffer->mDrawBuffers.assign(ptr, end);
 }
 
 void
 WebGLContext::StencilMask(GLuint mask)
 {
     if (IsContextLost())
         return;
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -247,21 +247,20 @@ WebGLContext::BlendFuncSeparate(GLenum s
 
     MakeContextCurrent();
     gl->fBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
 }
 
 GLenum
 WebGLContext::CheckFramebufferStatus(GLenum target)
 {
-    const char funcName[] = "checkFramebufferStatus";
     if (IsContextLost())
         return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
 
-    if (!ValidateFramebufferTarget(target, funcName))
+    if (!ValidateFramebufferTarget(target, "invalidateFramebuffer"))
         return 0;
 
     WebGLFramebuffer* fb;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         break;
@@ -272,17 +271,18 @@ WebGLContext::CheckFramebufferStatus(GLe
 
     default:
         MOZ_CRASH("GFX: Bad target.");
     }
 
     if (!fb)
         return LOCAL_GL_FRAMEBUFFER_COMPLETE;
 
-    return fb->CheckFramebufferStatus(funcName).get();
+    nsCString fbErrorInfo;
+    return fb->CheckFramebufferStatus(&fbErrorInfo).get();
 }
 
 already_AddRefed<WebGLProgram>
 WebGLContext::CreateProgram()
 {
     if (IsContextLost())
         return nullptr;
     RefPtr<WebGLProgram> globj = new WebGLProgram(this);
@@ -479,21 +479,20 @@ WebGLContext::DepthRange(GLfloat zNear, 
     MakeContextCurrent();
     gl->fDepthRange(zNear, zFar);
 }
 
 void
 WebGLContext::FramebufferRenderbuffer(GLenum target, GLenum attachment,
                                       GLenum rbtarget, WebGLRenderbuffer* wrb)
 {
-    const char funcName[] = "framebufferRenderbuffer";
     if (IsContextLost())
         return;
 
-    if (!ValidateFramebufferTarget(target, funcName))
+    if (!ValidateFramebufferTarget(target, "framebufferRenderbuffer"))
         return;
 
     WebGLFramebuffer* fb;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         break;
@@ -501,55 +500,114 @@ WebGLContext::FramebufferRenderbuffer(GL
     case LOCAL_GL_READ_FRAMEBUFFER:
         fb = mBoundReadFramebuffer;
         break;
 
     default:
         MOZ_CRASH("GFX: Bad target.");
     }
 
-    if (!fb)
-        return ErrorInvalidOperation("%s: Cannot modify framebuffer 0.", funcName);
-
-    fb->FramebufferRenderbuffer(funcName, attachment, rbtarget, wrb);
+    if (!fb) {
+        return ErrorInvalidOperation("framebufferRenderbuffer: cannot modify"
+                                     " framebuffer 0.");
+    }
+
+    if (rbtarget != LOCAL_GL_RENDERBUFFER) {
+        return ErrorInvalidEnumInfo("framebufferRenderbuffer: rbtarget:",
+                                    rbtarget);
+    }
+
+    if (!ValidateFramebufferAttachment(fb, attachment, "framebufferRenderbuffer"))
+        return;
+
+    fb->FramebufferRenderbuffer(attachment, rbtarget, wrb);
 }
 
 void
 WebGLContext::FramebufferTexture2D(GLenum target,
                                    GLenum attachment,
                                    GLenum textarget,
                                    WebGLTexture* tobj,
                                    GLint level)
 {
-    const char funcName[] = "framebufferTexture2D";
     if (IsContextLost())
         return;
 
-    if (!ValidateFramebufferTarget(target, funcName))
+    if (!ValidateFramebufferTarget(target, "framebufferTexture2D"))
+        return;
+
+    if (level < 0) {
+        ErrorInvalidValue("framebufferTexture2D: level must not be negative.");
         return;
+    }
+
+    if (textarget != LOCAL_GL_TEXTURE_2D &&
+        (textarget < LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
+         textarget > LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z))
+    {
+        return ErrorInvalidEnumInfo("framebufferTexture2D: textarget:",
+                                    textarget);
+    }
+
+    if (IsWebGL2()) {
+        /* GLES 3.0.4 p208:
+         *   If textarget is one of TEXTURE_CUBE_MAP_POSITIVE_X,
+         *   TEXTURE_CUBE_MAP_POSITIVE_Y, TEXTURE_CUBE_MAP_POSITIVE_Z,
+         *   TEXTURE_CUBE_MAP_NEGATIVE_X, TEXTURE_CUBE_MAP_NEGATIVE_Y,
+         *   or TEXTURE_CUBE_MAP_NEGATIVE_Z, then level must be greater
+         *   than or equal to zero and less than or equal to log2 of the
+         *   value of MAX_CUBE_MAP_TEXTURE_SIZE. If textarget is TEXTURE_2D,
+         *   level must be greater than or equal to zero and no larger than
+         *   log2 of the value of MAX_TEXTURE_SIZE. Otherwise, an
+         *   INVALID_VALUE error is generated.
+         */
+
+        if (textarget == LOCAL_GL_TEXTURE_2D) {
+            if (uint32_t(level) > FloorLog2(mImplMaxTextureSize)) {
+                ErrorInvalidValue("framebufferTexture2D: level is too large.");
+                return;
+            }
+        } else {
+            MOZ_ASSERT(textarget >= LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X &&
+                       textarget <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
+
+            if (uint32_t(level) > FloorLog2(mImplMaxCubeMapTextureSize)) {
+                ErrorInvalidValue("framebufferTexture2D: level is too large.");
+                return;
+            }
+        }
+    } else if (level != 0) {
+        ErrorInvalidValue("framebufferTexture2D: level must be 0.");
+        return;
+    }
 
     WebGLFramebuffer* fb;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         break;
 
     case LOCAL_GL_READ_FRAMEBUFFER:
         fb = mBoundReadFramebuffer;
         break;
 
     default:
         MOZ_CRASH("GFX: Bad target.");
     }
 
-    if (!fb)
-        return ErrorInvalidOperation("%s: Cannot modify framebuffer 0.", funcName);
-
-    fb->FramebufferTexture2D(funcName, attachment, textarget, tobj, level);
+    if (!fb) {
+        return ErrorInvalidOperation("framebufferTexture2D: cannot modify"
+                                     " framebuffer 0.");
+    }
+
+    if (!ValidateFramebufferAttachment(fb, attachment, "framebufferTexture2D"))
+        return;
+
+    fb->FramebufferTexture2D(attachment, textarget, tobj, level);
 }
 
 void
 WebGLContext::FrontFace(GLenum mode)
 {
     if (IsContextLost())
         return;
 
@@ -621,20 +679,25 @@ WebGLContext::GetAttribLocation(WebGLPro
 }
 
 JS::Value
 WebGLContext::GetBufferParameter(GLenum target, GLenum pname)
 {
     if (IsContextLost())
         return JS::NullValue();
 
-    const auto& buffer = ValidateBufferSelection("getBufferParameter", target);
-    if (!buffer)
+    if (!ValidateBufferTarget(target, "getBufferParameter"))
         return JS::NullValue();
 
+    WebGLRefPtr<WebGLBuffer>& slot = GetBufferSlotByTarget(target);
+    if (!slot) {
+        ErrorInvalidOperation("No buffer bound to `target` (0x%4x).", target);
+        return JS::NullValue();
+    }
+
     MakeContextCurrent();
 
     switch (pname) {
         case LOCAL_GL_BUFFER_SIZE:
         case LOCAL_GL_BUFFER_USAGE:
         {
             GLint i = 0;
             gl->fGetBufferParameteriv(target, pname, &i);
@@ -1489,23 +1552,16 @@ WebGL2Context::ReadPixels(GLint x, GLint
     if (!ReadPixels_SharedPrecheck(&out_error))
         return;
 
     if (!mBoundPixelPackBuffer) {
         ErrorInvalidOperation("readPixels: PIXEL_PACK_BUFFER must not be null.");
         return;
     }
 
-    if (mBoundPixelPackBuffer->mNumActiveTFOs) {
-        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                              " object.",
-                              "readPixels");
-        return;
-    }
-
     //////
 
     if (offset < 0) {
         ErrorInvalidValue("readPixels: offset must not be negative.");
         return;
     }
 
     {
--- a/dom/canvas/WebGLContextUnchecked.cpp
+++ b/dom/canvas/WebGLContextUnchecked.cpp
@@ -2,25 +2,70 @@
 /* vim: set ts=8 sts=4 et sw=4 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 "WebGLContextUnchecked.h"
 
 #include "GLContext.h"
+#include "WebGLBuffer.h"
 #include "WebGLSampler.h"
 
 namespace mozilla {
 
 WebGLContextUnchecked::WebGLContextUnchecked(gl::GLContext* _gl)
     : mGL_OnlyClearInDestroyResourcesAndContext(_gl)
     , gl(mGL_OnlyClearInDestroyResourcesAndContext) // const reference
 { }
 
+
+// -----------------------------------------------------------------------------
+// Buffer Objects
+
+void
+WebGLContextUnchecked::BindBuffer(GLenum target, WebGLBuffer* buffer)
+{
+    gl->MakeCurrent();
+    gl->fBindBuffer(target, buffer ? buffer->mGLName : 0);
+}
+
+void
+WebGLContextUnchecked::BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer)
+{
+    gl->MakeCurrent();
+    gl->fBindBufferBase(target, index, buffer ? buffer->mGLName : 0);
+}
+
+void
+WebGLContextUnchecked::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer, WebGLintptr offset, WebGLsizeiptr size)
+{
+    gl->MakeCurrent();
+
+#ifdef XP_MACOSX
+    if (buffer && buffer->Content() == WebGLBuffer::Kind::Undefined &&
+        gl->WorkAroundDriverBugs())
+    {
+        // BindBufferRange will fail if the buffer's contents is undefined.
+        // Bind so driver initializes the buffer.
+        gl->fBindBuffer(target, buffer->mGLName);
+    }
+#endif
+
+    gl->fBindBufferRange(target, index, buffer ? buffer->mGLName : 0, offset, size);
+}
+
+void
+WebGLContextUnchecked::CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size)
+{
+    gl->MakeCurrent();
+    gl->fCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size);
+}
+
+
 // -----------------------------------------------------------------------------
 // Sampler Objects
 
 void
 WebGLContextUnchecked::BindSampler(GLuint unit, WebGLSampler* sampler)
 {
     gl->MakeCurrent();
     gl->fBindSampler(unit, sampler ? sampler->mGLName : 0);
--- a/dom/canvas/WebGLContextUnchecked.h
+++ b/dom/canvas/WebGLContextUnchecked.h
@@ -16,16 +16,23 @@ class WebGLBuffer;
 class WebGLSampler;
 
 class WebGLContextUnchecked
 {
 public:
     explicit WebGLContextUnchecked(gl::GLContext* gl);
 
     // -------------------------------------------------------------------------
+    // Buffer Objects
+    void BindBuffer(GLenum target, WebGLBuffer* buffer);
+    void BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer);
+    void BindBufferRange(GLenum taret, GLuint index, WebGLBuffer* buffer, WebGLintptr offset, WebGLsizeiptr size);
+    void CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size);
+
+    // -------------------------------------------------------------------------
     // Sampler Objects
     void BindSampler(GLuint unit, WebGLSampler* sampler);
 
     GLint   GetSamplerParameteriv(WebGLSampler* sampler, GLenum pname);
     GLfloat GetSamplerParameterfv(WebGLSampler* sampler, GLenum pname);
 
     void SamplerParameteri(WebGLSampler* sampler, GLenum pname, GLint param);
     void SamplerParameteriv(WebGLSampler* sampler, GLenum pname, const GLint* param);
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -117,21 +117,46 @@ WebGLContext::ValidateBlendFuncEnumsComp
         ErrorInvalidOperation("%s are mutually incompatible, see section 6.8 in"
                               " the WebGL 1.0 spec", info);
         return false;
     }
 
     return true;
 }
 
+bool
+WebGLContext::ValidateDataOffsetSize(WebGLintptr offset, WebGLsizeiptr size, WebGLsizeiptr bufferSize, const char* info)
+{
+    if (offset < 0) {
+        ErrorInvalidValue("%s: offset must be positive", info);
+        return false;
+    }
+
+    if (size < 0) {
+        ErrorInvalidValue("%s: size must be positive", info);
+        return false;
+    }
+
+    // *** Careful *** WebGLsizeiptr is always 64-bits but GLsizeiptr
+    // is like intptr_t. On some platforms it is 32-bits.
+    CheckedInt<GLsizeiptr> neededBytes = CheckedInt<GLsizeiptr>(offset) + size;
+    if (!neededBytes.isValid() || neededBytes.value() > bufferSize) {
+        ErrorInvalidValue("%s: invalid range", info);
+        return false;
+    }
+
+    return true;
+}
+
 /**
  * Check data ranges [readOffset, readOffset + size] and [writeOffset,
  * writeOffset + size] for overlap.
  *
- * It is assumed that offset and size have already been validated.
+ * It is assumed that offset and size have already been validated with
+ * ValidateDataOffsetSize().
  */
 bool
 WebGLContext::ValidateDataRanges(WebGLintptr readOffset, WebGLintptr writeOffset, WebGLsizeiptr size, const char* info)
 {
     MOZ_ASSERT((CheckedInt<WebGLsizeiptr>(readOffset) + size).isValid());
     MOZ_ASSERT((CheckedInt<WebGLsizeiptr>(writeOffset) + size).isValid());
 
     bool separate = (readOffset + size < writeOffset || writeOffset + size < readOffset);
@@ -708,16 +733,17 @@ WebGLContext::InitAndValidateGL(FailureR
 
     mBound2DTextures.Clear();
     mBoundCubeMapTextures.Clear();
     mBound3DTextures.Clear();
     mBound2DArrayTextures.Clear();
     mBoundSamplers.Clear();
 
     mBoundArrayBuffer = nullptr;
+    mBoundTransformFeedbackBuffer = nullptr;
     mCurrentProgram = nullptr;
 
     mBoundDrawFramebuffer = nullptr;
     mBoundReadFramebuffer = nullptr;
     mBoundRenderbuffer = nullptr;
 
     MakeContextCurrent();
 
@@ -949,16 +975,17 @@ WebGLContext::InitAndValidateGL(FailureR
     // regardless of version.
     //
     // GL Spec 4.0.0:
     // (https://www.opengl.org/registry/doc/glspec40.core.20100311.pdf)
     // in Section E.2.2 "Removed Features", pg 397: "[...] The default
     // vertex array object (the name zero) is also deprecated. [...]"
 
     if (gl->IsCoreProfile()) {
+        MakeContextCurrent();
         mDefaultVertexArray->GenVertexArray();
         mDefaultVertexArray->BindVertexArray();
     }
 
     mPixelStore_FlipY = false;
     mPixelStore_PremultiplyAlpha = false;
     mPixelStore_ColorspaceConversion = BROWSER_DEFAULT_WEBGL;
 
--- a/dom/canvas/WebGLElementArrayCache.cpp
+++ b/dom/canvas/WebGLElementArrayCache.cpp
@@ -10,16 +10,26 @@
 #include <cstring>
 #include <limits>
 #include "mozilla/Assertions.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 
+static void
+UpdateUpperBound(uint32_t* const out_upperBound, uint32_t newBound)
+{
+    MOZ_ASSERT(out_upperBound);
+    // Move *out_upperBound to a local variable to work around a false positive
+    // -Wuninitialized gcc warning about std::max() in PGO builds.
+    uint32_t upperBound = *out_upperBound;
+    *out_upperBound = std::max(upperBound, newBound);
+}
+
 /* WebGLElementArrayCacheTree contains most of the implementation of
  * WebGLElementArrayCache, which performs WebGL element array buffer validation
  * for drawElements.
  *
  * Attention: Here lie nontrivial data structures, bug-prone algorithms, and
  * non-canonical tweaks! Whence the explanatory comments, and compiled unit
  * test.
  *
@@ -230,49 +240,53 @@ public:
         return element & ~kElementsPerLeafMask;
     }
 
     static size_t NextMultipleOfElementsPerLeaf(size_t numElements) {
         MOZ_ASSERT(numElements >= 1);
         return ((numElements - 1) | kElementsPerLeafMask) + 1;
     }
 
-    bool Validate(T maxAllowed, size_t firstLeaf, size_t lastLeaf)
+    bool Validate(T maxAllowed, size_t firstLeaf, size_t lastLeaf,
+                  uint32_t* const out_upperBound)
     {
         size_t firstTreeIndex = TreeIndexForLeaf(firstLeaf);
         size_t lastTreeIndex  = TreeIndexForLeaf(lastLeaf);
 
         while (true) {
             // Given that we tweak these values in nontrivial ways, it doesn't
             // hurt to do this sanity check.
             MOZ_ASSERT(firstTreeIndex <= lastTreeIndex);
 
             // Final case where there is only one node to validate at the
             // current tree level:
             if (lastTreeIndex == firstTreeIndex) {
                 const T& curData = mTreeData[firstTreeIndex];
+                UpdateUpperBound(out_upperBound, curData);
                 return curData <= maxAllowed;
             }
 
             // If the first node at current tree level is a right node, handle
             // it individually and replace it with its right neighbor, which is
             // a left node.
             if (IsRightNode(firstTreeIndex)) {
                 const T& curData = mTreeData[firstTreeIndex];
+                UpdateUpperBound(out_upperBound, curData);
                 if (curData > maxAllowed)
                   return false;
 
                 firstTreeIndex = RightNeighborNode(firstTreeIndex);
             }
 
             // If the last node at current tree level is a left node, handle it
             // individually and replace it with its left neighbor, which is a
             // right node.
             if (IsLeftNode(lastTreeIndex)) {
                 const T& curData = mTreeData[lastTreeIndex];
+                UpdateUpperBound(out_upperBound, curData);
                 if (curData > maxAllowed)
                     return false;
 
                 lastTreeIndex = LeftNeighborNode(lastTreeIndex);
             }
 
             /* At this point it can happen that firstTreeIndex and lastTreeIndex
              * "crossed" eachother. That happens if firstTreeIndex was a right
@@ -495,23 +509,28 @@ WebGLElementArrayCache::UpdateTrees(size
     if (mUint32Tree)
         result &= mUint32Tree->Update(firstByte, lastByte);
     return result;
 }
 
 template<typename T>
 bool
 WebGLElementArrayCache::Validate(uint32_t maxAllowed, size_t firstElement,
-                                 size_t countElements)
+                                 size_t countElements,
+                                 uint32_t* const out_upperBound)
 {
+    *out_upperBound = 0;
+
     // If maxAllowed is >= the max T value, then there is no way that a T index
     // could be invalid.
     uint32_t maxTSize = std::numeric_limits<T>::max();
-    if (maxAllowed >= maxTSize)
+    if (maxAllowed >= maxTSize) {
+        UpdateUpperBound(out_upperBound, maxTSize);
         return true;
+    }
 
     T maxAllowedT(maxAllowed);
 
     // Integer overflow must have been handled earlier, so we assert that
     // maxAllowedT is exactly the max allowed value.
     MOZ_ASSERT(uint32_t(maxAllowedT) == maxAllowed);
 
     if (!mBytes.Length() || !countElements)
@@ -532,62 +551,70 @@ WebGLElementArrayCache::Validate(uint32_
         }
     }
 
     size_t lastElement = firstElement + countElements - 1;
 
     // Fast-exit path when the global maximum for the whole element array buffer
     // falls in the allowed range:
     T globalMax = tree->GlobalMaximum();
-    if (globalMax <= maxAllowedT)
+    if (globalMax <= maxAllowedT) {
+        UpdateUpperBound(out_upperBound, globalMax);
         return true;
+    }
 
     const T* elements = Elements<T>();
 
     // Before calling tree->Validate, we have to validate ourselves the
     // boundaries of the elements span, to round them to the nearest multiple of
     // kElementsPerLeaf.
     size_t firstElementAdjustmentEnd = std::min(lastElement,
                                                 tree->LastElementUnderSameLeaf(firstElement));
     while (firstElement <= firstElementAdjustmentEnd) {
         const T& curData = elements[firstElement];
+        UpdateUpperBound(out_upperBound, curData);
         if (curData > maxAllowedT)
             return false;
 
         firstElement++;
     }
     size_t lastElementAdjustmentEnd = std::max(firstElement,
                                                tree->FirstElementUnderSameLeaf(lastElement));
     while (lastElement >= lastElementAdjustmentEnd) {
         const T& curData = elements[lastElement];
+        UpdateUpperBound(out_upperBound, curData);
         if (curData > maxAllowedT)
             return false;
 
         lastElement--;
     }
 
     // at this point, for many tiny validations, we're already done.
     if (firstElement > lastElement)
         return true;
 
     // general case
     return tree->Validate(maxAllowedT, tree->LeafForElement(firstElement),
-                          tree->LeafForElement(lastElement));
+                          tree->LeafForElement(lastElement), out_upperBound);
 }
 
 bool
 WebGLElementArrayCache::Validate(GLenum type, uint32_t maxAllowed,
-                                 size_t firstElement, size_t countElements)
+                                 size_t firstElement, size_t countElements,
+                                 uint32_t* const out_upperBound)
 {
     if (type == LOCAL_GL_UNSIGNED_BYTE)
-        return Validate<uint8_t>(maxAllowed, firstElement, countElements);
+        return Validate<uint8_t>(maxAllowed, firstElement, countElements,
+                                 out_upperBound);
     if (type == LOCAL_GL_UNSIGNED_SHORT)
-        return Validate<uint16_t>(maxAllowed, firstElement, countElements);
+        return Validate<uint16_t>(maxAllowed, firstElement, countElements,
+                                  out_upperBound);
     if (type == LOCAL_GL_UNSIGNED_INT)
-        return Validate<uint32_t>(maxAllowed, firstElement, countElements);
+        return Validate<uint32_t>(maxAllowed, firstElement, countElements,
+                                  out_upperBound);
 
     MOZ_ASSERT(false, "Invalid type.");
     return false;
 }
 
 template<typename T>
 static size_t
 SizeOfNullable(mozilla::MallocSizeOf mallocSizeOf, const T& obj)
--- a/dom/canvas/WebGLElementArrayCache.h
+++ b/dom/canvas/WebGLElementArrayCache.h
@@ -32,17 +32,18 @@ struct WebGLElementArrayCacheTree;
  * Most of the implementation is hidden in the auxilary class template,
  * WebGLElementArrayCacheTree. Refer to its code for design comments.
  */
 class WebGLElementArrayCache {
 public:
     bool BufferData(const void* ptr, size_t byteLength);
     bool BufferSubData(size_t pos, const void* ptr, size_t updateByteSize);
 
-    bool Validate(GLenum type, uint32_t maxAllowed, size_t first, size_t count);
+    bool Validate(GLenum type, uint32_t maxAllowed, size_t first, size_t count,
+                  uint32_t* const out_upperBound);
 
     template<typename T>
     T Element(size_t i) const { return Elements<T>()[i]; }
 
     WebGLElementArrayCache();
     ~WebGLElementArrayCache();
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
@@ -68,17 +69,18 @@ private:
      * Output parameter:
      *   out_upperBound: Upon success, is set to the actual maximum value in the
      *                   specified range, which is then guaranteed to be less
      *                   than or equal to maxAllowed. upon failure, is set to
      *                   the first value in the specified range, that is greater
      *                   than maxAllowed.
      */
     template<typename T>
-    bool Validate(uint32_t maxAllowed, size_t first, size_t count);
+    bool Validate(uint32_t maxAllowed, size_t first, size_t count,
+                  uint32_t* const out_upperBound);
 
     template<typename T>
     const T* Elements() const {
         return reinterpret_cast<const T*>(mBytes.Elements());
     }
 
     template<typename T>
     T* Elements() { return reinterpret_cast<T*>(mBytes.Elements()); }
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -1,50 +1,36 @@
 /* -*- Mode: C++; tab-width: 20; 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 "WebGLFramebuffer.h"
 
-// You know it's going to be fun when these two show up:
-#include <algorithm>
-#include <iterator>
-
 #include "GLContext.h"
-#include "GLScreenBuffer.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "nsPrintfCString.h"
 #include "WebGLContext.h"
 #include "WebGLContextUtils.h"
 #include "WebGLExtensions.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
 
 namespace mozilla {
 
-WebGLFBAttachPoint::WebGLFBAttachPoint()
-    : mFB(nullptr)
-    , mAttachmentPoint(0)
-    , mTexImageTarget(LOCAL_GL_NONE)
-    , mTexImageLayer(0)
-    , mTexImageLevel(0)
-{ }
-
 WebGLFBAttachPoint::WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPoint)
     : mFB(fb)
     , mAttachmentPoint(attachmentPoint)
     , mTexImageTarget(LOCAL_GL_NONE)
     , mTexImageLayer(0)
     , mTexImageLevel(0)
 { }
 
 WebGLFBAttachPoint::~WebGLFBAttachPoint()
 {
-    MOZ_ASSERT(mFB, "Should have been Init'd.");
     MOZ_ASSERT(!mRenderbufferPtr);
     MOZ_ASSERT(!mTexturePtr);
 }
 
 void
 WebGLFBAttachPoint::Unlink()
 {
     Clear();
@@ -95,16 +81,25 @@ WebGLFBAttachPoint::Samples() const
 }
 
 bool
 WebGLFBAttachPoint::HasAlpha() const
 {
     return Format()->format->a;
 }
 
+const webgl::FormatUsageInfo*
+WebGLFramebuffer::GetFormatForAttachment(const WebGLFBAttachPoint& attachment) const
+{
+    MOZ_ASSERT(attachment.IsDefined());
+    MOZ_ASSERT(attachment.Texture() || attachment.Renderbuffer());
+
+    return attachment.Format();
+}
+
 bool
 WebGLFBAttachPoint::IsReadableFloat() const
 {
     auto formatUsage = Format();
     MOZ_ASSERT(formatUsage);
 
     auto format = formatUsage->format;
     if (!format->IsColorFormat())
@@ -125,18 +120,24 @@ WebGLFBAttachPoint::Clear()
 
     mTexturePtr = nullptr;
     mRenderbufferPtr = nullptr;
 
     OnBackingStoreRespecified();
 }
 
 void
-WebGLFBAttachPoint::SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level,
-                                GLint layer)
+WebGLFBAttachPoint::SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level)
+{
+    SetTexImageLayer(tex, target, level, 0);
+}
+
+void
+WebGLFBAttachPoint::SetTexImageLayer(WebGLTexture* tex, TexImageTarget target,
+                                     GLint level, GLint layer)
 {
     Clear();
 
     mTexturePtr = tex;
     mTexImageTarget = target;
     mTexImageLevel = level;
     mTexImageLayer = layer;
 
@@ -170,17 +171,17 @@ WebGLFBAttachPoint::HasUninitializedImag
 
     auto& imageInfo = mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel);
     MOZ_ASSERT(imageInfo.IsDefined());
 
     return !imageInfo.IsDataInitialized();
 }
 
 void
-WebGLFBAttachPoint::SetImageDataStatus(WebGLImageDataStatus newStatus) const
+WebGLFBAttachPoint::SetImageDataStatus(WebGLImageDataStatus newStatus)
 {
     if (!HasImage())
         return;
 
     if (mRenderbufferPtr) {
         mRenderbufferPtr->mImageDataStatus = newStatus;
         return;
     }
@@ -337,70 +338,93 @@ WebGLFBAttachPoint::IsComplete(WebGLCont
             return false;
         }
     }
 
     return true;
 }
 
 void
-WebGLFBAttachPoint::Resolve(gl::GLContext* gl) const
+WebGLFBAttachPoint::FinalizeAttachment(gl::GLContext* gl, GLenum attachment) const
 {
-    if (!HasImage())
-        return;
+    if (!HasImage()) {
+        switch (attachment) {
+        case LOCAL_GL_DEPTH_ATTACHMENT:
+        case LOCAL_GL_STENCIL_ATTACHMENT:
+        case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
+            break;
 
-    if (Renderbuffer()) {
-        Renderbuffer()->DoFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint);
+        default:
+            gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachment,
+                                         LOCAL_GL_RENDERBUFFER, 0);
+            break;
+        }
+
         return;
     }
-    MOZ_ASSERT(Texture());
+    MOZ_ASSERT(HasImage());
 
-    MOZ_ASSERT(gl == Texture()->mContext->GL());
-
-    const auto& texName = Texture()->mGLName;
+    if (Texture()) {
+        MOZ_ASSERT(gl == Texture()->mContext->GL());
 
-    ////
-
-    const auto fnAttach2D = [&](GLenum attachmentPoint) {
-        gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachmentPoint,
-                                  mTexImageTarget.get(), texName, mTexImageLevel);
-    };
-
-    ////
+        const GLenum imageTarget = ImageTarget().get();
+        const GLint mipLevel = MipLevel();
+        const GLint layer = Layer();
+        const GLuint glName = Texture()->mGLName;
 
-    switch (mTexImageTarget.get()) {
-    case LOCAL_GL_TEXTURE_2D:
-    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
-    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
-    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
-    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
-    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
-    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
-        if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-            fnAttach2D(LOCAL_GL_DEPTH_ATTACHMENT);
-            fnAttach2D(LOCAL_GL_STENCIL_ATTACHMENT);
-        } else {
-            fnAttach2D(mAttachmentPoint);
+        switch (imageTarget) {
+        case LOCAL_GL_TEXTURE_2D:
+        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
+        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
+        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
+        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
+        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
+        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
+            if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
+                                          imageTarget, glName, mipLevel);
+                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
+                                          LOCAL_GL_STENCIL_ATTACHMENT, imageTarget,
+                                          glName, mipLevel);
+            } else {
+                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachment, imageTarget,
+                                          glName, mipLevel);
+            }
+            break;
+
+        case LOCAL_GL_TEXTURE_2D_ARRAY:
+        case LOCAL_GL_TEXTURE_3D:
+            if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+                gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER,
+                                             LOCAL_GL_DEPTH_ATTACHMENT, glName, mipLevel,
+                                             layer);
+                gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER,
+                                             LOCAL_GL_STENCIL_ATTACHMENT, glName,
+                                             mipLevel, layer);
+            } else {
+                gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, attachment, glName,
+                                             mipLevel, layer);
+            }
+            break;
         }
-        break;
+        return;
+    }
 
-    case LOCAL_GL_TEXTURE_2D_ARRAY:
-    case LOCAL_GL_TEXTURE_3D:
-        // If we have fFramebufferTextureLayer, we can rely on having
-        // DEPTH_STENCIL_ATTACHMENT.
-        gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint, texName,
-                                     mTexImageLevel, mTexImageLayer);
-        break;
+    if (Renderbuffer()) {
+        Renderbuffer()->DoFramebufferRenderbuffer(attachment);
+        return;
     }
+
+    MOZ_CRASH("GFX: invalid render buffer");
 }
 
 JS::Value
 WebGLFBAttachPoint::GetParameter(const char* funcName, WebGLContext* webgl, JSContext* cx,
                                  GLenum target, GLenum attachment, GLenum pname,
-                                 ErrorResult* const out_error) const
+                                 ErrorResult* const out_error)
 {
     const bool hasAttachment = (mTexturePtr || mRenderbufferPtr);
     if (!hasAttachment) {
         // Divergent between GLES 3 and 2.
 
         // GLES 2.0.25 p127:
         // "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is NONE, then querying any
         //  other pname will generate INVALID_ENUM."
@@ -595,232 +619,368 @@ WebGLFBAttachPoint::GetParameter(const c
         MOZ_ASSERT(false, "Missing case.");
         break;
     }
 
     return JS::Int32Value(ret);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////////////
 // WebGLFramebuffer
 
 WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo)
     : WebGLContextBoundObject(webgl)
     , mGLName(fbo)
-#ifdef ANDROID
-    , mIsFB(false)
-#endif
+    , mIsKnownFBComplete(false)
+    , mReadBufferMode(LOCAL_GL_COLOR_ATTACHMENT0)
+    , mColorAttachment0(this, LOCAL_GL_COLOR_ATTACHMENT0)
     , mDepthAttachment(this, LOCAL_GL_DEPTH_ATTACHMENT)
     , mStencilAttachment(this, LOCAL_GL_STENCIL_ATTACHMENT)
     , mDepthStencilAttachment(this, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
+    , mMoreColorAttachments(webgl->mGLMaxColorAttachments)
+    , mDrawBuffers(1, LOCAL_GL_COLOR_ATTACHMENT0)
+#ifdef ANDROID
+    , mIsFB(false)
+#endif
 {
     mContext->mFramebuffers.insertBack(this);
-
-    size_t i = 0;
-    for (auto& cur : mColorAttachments) {
-        new (&cur) WebGLFBAttachPoint(this, LOCAL_GL_COLOR_ATTACHMENT0 + i);
-        i++;
-    }
-
-    mColorDrawBuffers.push_back(&mColorAttachments[0]);
-    mColorReadBuffer = &mColorAttachments[0];
 }
 
 void
 WebGLFramebuffer::Delete()
 {
-    InvalidateFramebufferStatus();
-
+    mColorAttachment0.Clear();
     mDepthAttachment.Clear();
     mStencilAttachment.Clear();
     mDepthStencilAttachment.Clear();
 
-    for (auto& cur : mColorAttachments) {
+    for (auto& cur : mMoreColorAttachments) {
         cur.Clear();
     }
 
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteFramebuffers(1, &mGLName);
 
     LinkedListElement<WebGLFramebuffer>::removeFrom(mContext->mFramebuffers);
 
 #ifdef ANDROID
     mIsFB = false;
 #endif
 }
 
-////
+void
+WebGLFramebuffer::FramebufferRenderbuffer(GLenum attachment, RBTarget rbtarget,
+                                          WebGLRenderbuffer* rb)
+{
+    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
+               mContext->mBoundReadFramebuffer == this);
 
-Maybe<WebGLFBAttachPoint*>
-WebGLFramebuffer::GetColorAttachPoint(GLenum attachPoint)
-{
-    if (attachPoint == LOCAL_GL_NONE)
-        return Some<WebGLFBAttachPoint*>(nullptr);
+    if (!mContext->ValidateObjectAllowNull("framebufferRenderbuffer: renderbuffer", rb))
+        return;
+
+    // `attachPointEnum` is validated by ValidateFramebufferAttachment().
 
-    if (attachPoint < LOCAL_GL_COLOR_ATTACHMENT0)
-        return Nothing();
+    RefPtr<WebGLRenderbuffer> rb_ = rb; // Bug 1201275
+    const auto fnAttach = [this, &rb_](GLenum attachment) {
+        const auto attachPoint = this->GetAttachPoint(attachment);
+        MOZ_ASSERT(attachPoint);
 
-    const size_t colorId = attachPoint - LOCAL_GL_COLOR_ATTACHMENT0;
+        attachPoint->SetRenderbuffer(rb_);
+    };
 
-    MOZ_ASSERT(mContext->mImplMaxColorAttachments <= kMaxColorAttachments);
-    if (colorId >= mContext->mImplMaxColorAttachments)
-        return Nothing();
+    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
+        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
+    } else {
+        fnAttach(attachment);
+    }
 
-    return Some(&mColorAttachments[colorId]);
+    InvalidateFramebufferStatus();
 }
 
-Maybe<WebGLFBAttachPoint*>
+void
+WebGLFramebuffer::FramebufferTexture2D(GLenum attachment, TexImageTarget texImageTarget,
+                                       WebGLTexture* tex, GLint level)
+{
+    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
+               mContext->mBoundReadFramebuffer == this);
+
+    if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", tex))
+        return;
+
+    if (tex) {
+        if (!tex->HasEverBeenBound()) {
+            mContext->ErrorInvalidOperation("framebufferTexture2D: the texture"
+                                            " is not the name of a texture.");
+            return;
+        }
+
+        const TexTarget destTexTarget = TexImageTargetToTexTarget(texImageTarget);
+        if (tex->Target() != destTexTarget) {
+            mContext->ErrorInvalidOperation("framebufferTexture2D: Mismatched"
+                                            " texture and texture target.");
+            return;
+        }
+    }
+
+    RefPtr<WebGLTexture> tex_ = tex; // Bug 1201275
+    const auto fnAttach = [this, &tex_, texImageTarget, level](GLenum attachment) {
+        const auto attachPoint = this->GetAttachPoint(attachment);
+        MOZ_ASSERT(attachPoint);
+
+        attachPoint->SetTexImage(tex_, texImageTarget, level);
+    };
+
+    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
+        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
+    } else {
+        fnAttach(attachment);
+    }
+
+    InvalidateFramebufferStatus();
+}
+
+void
+WebGLFramebuffer::FramebufferTextureLayer(GLenum attachment, WebGLTexture* tex,
+                                          GLint level, GLint layer)
+{
+    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
+               mContext->mBoundReadFramebuffer == this);
+
+    const TexImageTarget texImageTarget = (tex ? tex->Target().get()
+                                               : LOCAL_GL_TEXTURE_2D);
+
+    RefPtr<WebGLTexture> tex_ = tex; // Bug 1201275
+    const auto fnAttach = [this, &tex_, texImageTarget, level, layer](GLenum attachment) {
+        const auto attachPoint = this->GetAttachPoint(attachment);
+        MOZ_ASSERT(attachPoint);
+
+        attachPoint->SetTexImageLayer(tex_, texImageTarget, level, layer);
+    };
+
+    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
+        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
+    } else {
+        fnAttach(attachment);
+    }
+
+    InvalidateFramebufferStatus();
+}
+
+WebGLFBAttachPoint*
 WebGLFramebuffer::GetAttachPoint(GLenum attachPoint)
 {
     switch (attachPoint) {
+    case LOCAL_GL_COLOR_ATTACHMENT0:
+        return &mColorAttachment0;
+
     case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
-        return Some(&mDepthStencilAttachment);
+        return &mDepthStencilAttachment;
 
     case LOCAL_GL_DEPTH_ATTACHMENT:
-        return Some(&mDepthAttachment);
+        return &mDepthAttachment;
 
     case LOCAL_GL_STENCIL_ATTACHMENT:
-        return Some(&mStencilAttachment);
+        return &mStencilAttachment;
 
     default:
-        return GetColorAttachPoint(attachPoint);
+        break;
     }
-}
+
+    const auto lastCAEnum = mContext->LastColorAttachmentEnum();
+    if (attachPoint < LOCAL_GL_COLOR_ATTACHMENT1 ||
+        attachPoint > lastCAEnum)
+    {
+        return nullptr;
+    }
 
-#define FOR_EACH_ATTACHMENT(X)            \
-    X(mDepthAttachment);                  \
-    X(mStencilAttachment);                \
-    X(mDepthStencilAttachment);           \
-                                          \
-    for (auto& cur : mColorAttachments) { \
-        X(cur);                           \
+    if (!mMoreColorAttachments.Size()) {
+        for (GLenum cur = LOCAL_GL_COLOR_ATTACHMENT1; cur <= lastCAEnum; cur++) {
+            mMoreColorAttachments.AppendNew(this, cur);
+        }
     }
+    MOZ_ASSERT(LOCAL_GL_COLOR_ATTACHMENT0 + mMoreColorAttachments.Size() == lastCAEnum);
+
+    const size_t offset = attachPoint - LOCAL_GL_COLOR_ATTACHMENT1;
+    MOZ_ASSERT(offset <= mMoreColorAttachments.Size());
+    return &mMoreColorAttachments[offset];
+}
 
 void
 WebGLFramebuffer::DetachTexture(const WebGLTexture* tex)
 {
-    const auto fnDetach = [&](WebGLFBAttachPoint& attach) {
-        if (attach.Texture() == tex) {
-            attach.Clear();
-        }
-    };
+    if (mColorAttachment0.Texture() == tex)
+        mColorAttachment0.Clear();
+
+    if (mDepthAttachment.Texture() == tex)
+        mDepthAttachment.Clear();
 
-    FOR_EACH_ATTACHMENT(fnDetach)
+    if (mStencilAttachment.Texture() == tex)
+        mStencilAttachment.Clear();
+
+    if (mDepthStencilAttachment.Texture() == tex)
+        mDepthStencilAttachment.Clear();
+
+    for (auto& cur : mMoreColorAttachments) {
+        if (cur.Texture() == tex)
+            cur.Clear();
+    }
 }
 
 void
 WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb)
 {
-    const auto fnDetach = [&](WebGLFBAttachPoint& attach) {
-        if (attach.Renderbuffer() == rb) {
-            attach.Clear();
-        }
-    };
+    if (mColorAttachment0.Renderbuffer() == rb)
+        mColorAttachment0.Clear();
+
+    if (mDepthAttachment.Renderbuffer() == rb)
+        mDepthAttachment.Clear();
+
+    if (mStencilAttachment.Renderbuffer() == rb)
+        mStencilAttachment.Clear();
 
-    FOR_EACH_ATTACHMENT(fnDetach)
-}
+    if (mDepthStencilAttachment.Renderbuffer() == rb)
+        mDepthStencilAttachment.Clear();
 
-////////////////////////////////////////////////////////////////////////////////
-// Completeness
+    for (auto& cur : mMoreColorAttachments) {
+        if (cur.Renderbuffer() == rb)
+            cur.Clear();
+    }
+}
 
 bool
 WebGLFramebuffer::HasDefinedAttachments() const
 {
     bool hasAttachments = false;
-    const auto func = [&](const WebGLFBAttachPoint& attach) {
-        hasAttachments |= attach.IsDefined();
-    };
 
-    FOR_EACH_ATTACHMENT(func)
+    hasAttachments |= mColorAttachment0.IsDefined();
+    hasAttachments |= mDepthAttachment.IsDefined();
+    hasAttachments |= mStencilAttachment.IsDefined();
+    hasAttachments |= mDepthStencilAttachment.IsDefined();
+
+    for (const auto& cur : mMoreColorAttachments) {
+        hasAttachments |= cur.IsDefined();
+    }
+
     return hasAttachments;
 }
 
 bool
 WebGLFramebuffer::HasIncompleteAttachments(nsCString* const out_info) const
 {
-    bool hasIncomplete = false;
-    const auto func = [&](const WebGLFBAttachPoint& cur) {
+    const auto fnIsIncomplete = [this, out_info](const WebGLFBAttachPoint& cur) {
         if (!cur.IsDefined())
-            return; // Not defined, so can't count as incomplete.
+            return false; // Not defined, so can't count as incomplete.
 
-        hasIncomplete |= !cur.IsComplete(mContext, out_info);
+        return !cur.IsComplete(this->mContext, out_info);
     };
 
-    FOR_EACH_ATTACHMENT(func)
+    bool hasIncomplete = false;
+
+    hasIncomplete |= fnIsIncomplete(mColorAttachment0);
+    hasIncomplete |= fnIsIncomplete(mDepthAttachment);
+    hasIncomplete |= fnIsIncomplete(mStencilAttachment);
+    hasIncomplete |= fnIsIncomplete(mDepthStencilAttachment);
+
+    for (const auto& cur : mMoreColorAttachments) {
+        hasIncomplete |= fnIsIncomplete(cur);
+    }
+
     return hasIncomplete;
 }
 
 bool
 WebGLFramebuffer::AllImageRectsMatch() const
 {
     MOZ_ASSERT(HasDefinedAttachments());
     DebugOnly<nsCString> fbStatusInfo;
     MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo));
 
     bool needsInit = true;
     uint32_t width = 0;
     uint32_t height = 0;
 
-    bool hasMismatch = false;
-    const auto func = [&](const WebGLFBAttachPoint& attach) {
+    const auto fnInitializeOrMatch = [&needsInit, &width,
+                                      &height](const WebGLFBAttachPoint& attach)
+    {
         if (!attach.HasImage())
-            return;
+            return true;
 
         uint32_t curWidth;
         uint32_t curHeight;
         attach.Size(&curWidth, &curHeight);
 
         if (needsInit) {
             needsInit = false;
             width = curWidth;
             height = curHeight;
-            return;
+            return true;
         }
 
-        hasMismatch |= (curWidth != width ||
-                        curHeight != height);
+        return (curWidth == width &&
+                curHeight == height);
     };
 
-    FOR_EACH_ATTACHMENT(func)
-    return !hasMismatch;
+    bool matches = true;
+
+    matches &= fnInitializeOrMatch(mColorAttachment0      );
+    matches &= fnInitializeOrMatch(mDepthAttachment       );
+    matches &= fnInitializeOrMatch(mStencilAttachment     );
+    matches &= fnInitializeOrMatch(mDepthStencilAttachment);
+
+    for (const auto& cur : mMoreColorAttachments) {
+        matches &= fnInitializeOrMatch(cur);
+    }
+
+    return matches;
 }
 
 bool
 WebGLFramebuffer::AllImageSamplesMatch() const
 {
     MOZ_ASSERT(HasDefinedAttachments());
     DebugOnly<nsCString> fbStatusInfo;
     MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo));
 
     bool needsInit = true;
     uint32_t samples = 0;
 
-    bool hasMismatch = false;
-    const auto func = [&](const WebGLFBAttachPoint& attach) {
+    const auto fnInitializeOrMatch = [&needsInit,
+                                      &samples](const WebGLFBAttachPoint& attach)
+    {
         if (!attach.HasImage())
-          return;
+          return true;
 
         const uint32_t curSamples = attach.Samples();
 
         if (needsInit) {
             needsInit = false;
             samples = curSamples;
-            return;
+            return true;
         }
 
-        hasMismatch |= (curSamples != samples);
+        return (curSamples == samples);
     };
 
-    FOR_EACH_ATTACHMENT(func)
-    return !hasMismatch;
+    bool matches = true;
+
+    matches &= fnInitializeOrMatch(mColorAttachment0      );
+    matches &= fnInitializeOrMatch(mDepthAttachment       );
+    matches &= fnInitializeOrMatch(mStencilAttachment     );
+    matches &= fnInitializeOrMatch(mDepthStencilAttachment);
+
+    for (const auto& cur : mMoreColorAttachments) {
+        matches &= fnInitializeOrMatch(cur);
+    }
+
+    return matches;
 }
 
-#undef FOR_EACH_ATTACHMENT
-
 FBStatus
 WebGLFramebuffer::PrecheckFramebufferStatus(nsCString* const out_info) const
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     if (!HasDefinedAttachments())
         return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT; // No attachments
@@ -829,991 +989,369 @@ WebGLFramebuffer::PrecheckFramebufferSta
         return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
 
     if (!AllImageRectsMatch())
         return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS; // Inconsistent sizes
 
     if (!AllImageSamplesMatch())
         return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE; // Inconsistent samples
 
-    if (mContext->IsWebGL2()) {
-        MOZ_ASSERT(!mDepthStencilAttachment.IsDefined());
-    } else {
+    if (!mContext->IsWebGL2()) {
+        // INCOMPLETE_DIMENSIONS doesn't exist in GLES3.
         const auto depthOrStencilCount = int(mDepthAttachment.IsDefined()) +
                                          int(mStencilAttachment.IsDefined()) +
                                          int(mDepthStencilAttachment.IsDefined());
         if (depthOrStencilCount > 1)
             return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
     }
 
     return LOCAL_GL_FRAMEBUFFER_COMPLETE;
 }
 
-////////////////////////////////////////
-// Validation
+FBStatus
+WebGLFramebuffer::CheckFramebufferStatus(nsCString* const out_info) const
+{
+    if (mIsKnownFBComplete)
+        return LOCAL_GL_FRAMEBUFFER_COMPLETE;
+
+    FBStatus ret = PrecheckFramebufferStatus(out_info);
+    if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE)
+        return ret;
+
+    // Looks good on our end. Let's ask the driver.
+    mContext->MakeContextCurrent();
+
+    // Ok, attach our chosen flavor of {DEPTH, STENCIL, DEPTH_STENCIL}.
+    FinalizeAttachments();
+
+    // TODO: This should not be unconditionally GL_FRAMEBUFFER.
+    ret = mContext->gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
+
+    if (ret == LOCAL_GL_FRAMEBUFFER_COMPLETE) {
+        mIsKnownFBComplete = true;
+    } else {
+        out_info->AssignLiteral("Bad status according to the driver");
+    }
+
+    return ret;
+}
 
 bool
 WebGLFramebuffer::ValidateAndInitAttachments(const char* funcName)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
-    const auto fbStatus = CheckFramebufferStatus(funcName);
-    if (fbStatus == LOCAL_GL_FRAMEBUFFER_COMPLETE)
+    nsCString fbStatusInfo;
+    const auto fbStatus = CheckFramebufferStatus(&fbStatusInfo);
+    if (fbStatus != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
+        nsCString errorText = nsPrintfCString("Incomplete framebuffer: Status 0x%04x",
+                                              fbStatus.get());
+        if (fbStatusInfo.Length()) {
+            errorText += ": ";
+            errorText += fbStatusInfo;
+        }
+
+        mContext->ErrorInvalidFramebufferOperation("%s: %s.", funcName,
+                                                   errorText.BeginReading());
+        return false;
+    }
+
+    // Cool! We've checked out ok. Just need to initialize.
+
+    //////
+    // Check if we need to initialize anything
+    std::vector<WebGLFBAttachPoint*> tex3DToClear;
+
+    const auto fnGatherIf3D = [&](WebGLFBAttachPoint& attach) {
+        if (!attach.Texture())
+            return false;
+
+        const auto& info = attach.Texture()->ImageInfoAt(attach.ImageTarget(),
+                                                         attach.MipLevel());
+        if (info.mDepth == 1)
+            return false;
+
+        tex3DToClear.push_back(&attach);
+        return true;
+    };
+
+    //////
+
+    uint32_t clearBits = 0;
+    std::vector<GLenum> drawBuffersForClear;
+    std::vector<WebGLFBAttachPoint*> attachmentsToClear;
+
+    const auto fnGatherColor = [&](WebGLFBAttachPoint& attach, uint32_t colorAttachNum) {
+        if (!IsDrawBuffer(colorAttachNum) || !attach.HasUninitializedImageData())
+            return;
+
+        if (fnGatherIf3D(attach))
+            return;
+
+        if (attachmentsToClear.empty()) {
+            attachmentsToClear.reserve(1 + mMoreColorAttachments.Size() + 3);
+        }
+
+        if (drawBuffersForClear.empty()) {
+            drawBuffersForClear.assign(1 + mMoreColorAttachments.Size(), LOCAL_GL_NONE);
+        }
+
+        clearBits |= LOCAL_GL_COLOR_BUFFER_BIT;
+        attachmentsToClear.push_back(&attach);
+        drawBuffersForClear[colorAttachNum] = LOCAL_GL_COLOR_ATTACHMENT0 + colorAttachNum;
+    };
+
+    const auto fnGatherOther = [&](WebGLFBAttachPoint& attach, GLenum attachClearBits) {
+        if (!attach.HasUninitializedImageData())
+            return;
+
+        if (fnGatherIf3D(attach))
+            return;
+
+        if (attachmentsToClear.empty()) {
+            attachmentsToClear.reserve(1 + mMoreColorAttachments.Size() + 3);
+        }
+
+        attachmentsToClear.push_back(&attach);
+
+        clearBits |= attachClearBits;
+    };
+
+    //////
+
+    fnGatherColor(mColorAttachment0, 0);
+
+    size_t colorAttachNum = 1;
+    for (auto& cur : mMoreColorAttachments) {
+        fnGatherColor(cur, colorAttachNum);
+        ++colorAttachNum;
+    }
+
+    fnGatherOther(mDepthAttachment, LOCAL_GL_DEPTH_BUFFER_BIT);
+    fnGatherOther(mStencilAttachment, LOCAL_GL_STENCIL_BUFFER_BIT);
+    fnGatherOther(mDepthStencilAttachment,
+                  LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT);
+
+    //////
+    if (!clearBits && tex3DToClear.empty())
         return true;
 
-    mContext->ErrorInvalidFramebufferOperation("%s: Framebuffer must be"
-                                               " complete.",
-                                               funcName);
-    return false;
+    mContext->MakeContextCurrent();
+
+    if (clearBits) {
+        const auto fnDrawBuffers = [this](const std::vector<GLenum>& list) {
+            this->mContext->gl->fDrawBuffers(list.size(), list.data());
+        };
+
+        const auto drawBufferExt = WebGLExtensionID::WEBGL_draw_buffers;
+        const bool hasDrawBuffers = (mContext->IsWebGL2() ||
+                                     mContext->IsExtensionEnabled(drawBufferExt));
+
+        if (hasDrawBuffers) {
+            fnDrawBuffers(drawBuffersForClear);
+        }
+
+        ////////////
+
+        // Clear!
+        {
+            gl::ScopedBindFramebuffer autoBind(mContext->gl, mGLName);
+
+            mContext->ForceClearFramebufferWithDefaultValues(clearBits, false);
+        }
+
+        if (hasDrawBuffers) {
+            fnDrawBuffers(mDrawBuffers);
+        }
+
+        for (auto* cur : attachmentsToClear) {
+            cur->SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
+        }
+    }
+
+    //////
+
+    for (auto* attach : tex3DToClear) {
+        auto* tex = attach->Texture();
+        if (!tex->InitializeImageData(funcName, attach->ImageTarget(),
+                                      attach->MipLevel()))
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static void
+FinalizeDrawAndReadBuffers(gl::GLContext* gl, bool isColorBufferDefined)
+{
+    MOZ_ASSERT(gl, "Expected a valid GLContext ptr.");
+    // GLES don't support DrawBuffer()/ReadBuffer.
+    // According to http://www.opengl.org/wiki/Framebuffer_Object
+    //
+    // Each draw buffers must either specify color attachment points that have images
+    // attached or must be GL_NONE​. (GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER​ when false).
+    //
+    // If the read buffer is set, then it must specify an attachment point that has an
+    // image attached. (GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER​ when false).
+    //
+    // Note that this test is not performed if OpenGL 4.2 or ARB_ES2_compatibility is
+    // available.
+    if (gl->IsGLES() ||
+        gl->IsSupported(gl::GLFeature::ES2_compatibility) ||
+        gl->IsAtLeast(gl::ContextProfile::OpenGL, 420))
+    {
+        return;
+    }
+
+    // TODO(djg): Assert that fDrawBuffer/fReadBuffer is not NULL.
+    GLenum colorBufferSource = isColorBufferDefined ? LOCAL_GL_COLOR_ATTACHMENT0
+                                                    : LOCAL_GL_NONE;
+    gl->fDrawBuffer(colorBufferSource);
+    gl->fReadBuffer(colorBufferSource);
+}
+
+void
+WebGLFramebuffer::FinalizeAttachments() const
+{
+    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
+               mContext->mBoundReadFramebuffer == this);
+
+    gl::GLContext* gl = mContext->gl;
+
+    // Nuke the depth and stencil attachment points.
+    gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
+                                 LOCAL_GL_RENDERBUFFER, 0);
+    gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT,
+                                 LOCAL_GL_RENDERBUFFER, 0);
+
+    // Call finalize.
+    mColorAttachment0.FinalizeAttachment(gl, LOCAL_GL_COLOR_ATTACHMENT0);
+    mDepthAttachment.FinalizeAttachment(gl, LOCAL_GL_DEPTH_ATTACHMENT);
+    mStencilAttachment.FinalizeAttachment(gl, LOCAL_GL_STENCIL_ATTACHMENT);
+    mDepthStencilAttachment.FinalizeAttachment(gl, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
+
+    for (size_t i = 0; i < mMoreColorAttachments.Size(); i++) {
+        GLenum attachPoint = LOCAL_GL_COLOR_ATTACHMENT1 + i;
+        mMoreColorAttachments[i].FinalizeAttachment(gl, attachPoint);
+    }
+
+    FinalizeDrawAndReadBuffers(gl, mColorAttachment0.IsDefined());
 }
 
 bool
 WebGLFramebuffer::ValidateForRead(const char* funcName,
                                   const webgl::FormatUsageInfo** const out_format,
                                   uint32_t* const out_width, uint32_t* const out_height)
 {
     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.",
+    if (mReadBufferMode == LOCAL_GL_NONE) {
+        mContext->ErrorInvalidOperation("%s: Read buffer mode must not be NONE.",
                                         funcName);
         return false;
     }
 
-    *out_format = mColorReadBuffer->Format();
-    mColorReadBuffer->Size(out_width, out_height);
-    return true;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Resolution and caching
-
-void
-WebGLFramebuffer::ResolveAttachments() const
-{
-    const auto& gl = mContext->gl;
-
-    ////
-    // Nuke attachment points.
-
-    for (uint32_t i = 0; i < mContext->mImplMaxColorAttachments; i++) {
-        const GLenum attachEnum = LOCAL_GL_COLOR_ATTACHMENT0 + i;
-        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachEnum,
-                                     LOCAL_GL_RENDERBUFFER, 0);
-    }
-
-    gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
-                                 LOCAL_GL_RENDERBUFFER, 0);
-    gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT,
-                                 LOCAL_GL_RENDERBUFFER, 0);
-
-    ////
-
-    for (const auto& attach : mColorAttachments) {
-        attach.Resolve(gl);
+    const auto attachPoint = GetAttachPoint(mReadBufferMode);
+    if (!attachPoint || !attachPoint->IsDefined()) {
+        mContext->ErrorInvalidOperation("%s: The attachment specified for reading is"
+                                        " null.", funcName);
+        return false;
     }
 
-    mDepthAttachment.Resolve(gl);
-    mStencilAttachment.Resolve(gl);
-    mDepthStencilAttachment.Resolve(gl);
-}
-
-bool
-WebGLFramebuffer::ResolveAttachmentData(const char* funcName) const
-{
-    //////
-    // Check if we need to initialize anything
-
-    const auto fnIs3D = [&](const WebGLFBAttachPoint& attach) {
-        const auto& tex = attach.Texture();
-        if (!tex)
-            return false;
-
-        const auto& info = tex->ImageInfoAt(attach.ImageTarget(), attach.MipLevel());
-        if (info.mDepth == 1)
-            return false;
-
-        return true;
-    };
-
-    uint32_t clearBits = 0;
-    std::vector<const WebGLFBAttachPoint*> attachmentsToClear;
-    std::vector<const WebGLFBAttachPoint*> colorAttachmentsToClear;
-    std::vector<const WebGLFBAttachPoint*> tex3DAttachmentsToInit;
-
-    const auto fnGather = [&](const WebGLFBAttachPoint& attach, GLenum attachClearBits) {
-        if (!attach.HasUninitializedImageData())
-            return false;
-
-        if (fnIs3D(attach)) {
-            tex3DAttachmentsToInit.push_back(&attach);
-            return false;
-        }
-
-        clearBits |= attachClearBits;
-        attachmentsToClear.push_back(&attach);
-        return true;
-    };
-
-    //////
-
-    for (auto& cur : mColorDrawBuffers) {
-        if (fnGather(*cur, LOCAL_GL_COLOR_BUFFER_BIT)) {
-            colorAttachmentsToClear.push_back(cur);
-        }
-    }
-
-    fnGather(mDepthAttachment, LOCAL_GL_DEPTH_BUFFER_BIT);
-    fnGather(mStencilAttachment, LOCAL_GL_STENCIL_BUFFER_BIT);
-    fnGather(mDepthStencilAttachment, LOCAL_GL_DEPTH_BUFFER_BIT |
-                                      LOCAL_GL_STENCIL_BUFFER_BIT);
-
-    //////
-    if (!clearBits && tex3DAttachmentsToInit.empty())
-        return true;
-
-    for (const auto& attach : tex3DAttachmentsToInit) {
-        const auto& tex = attach->Texture();
-        if (!tex->InitializeImageData(funcName, attach->ImageTarget(),
-                                      attach->MipLevel()))
-        {
-            return false;
-        }
-    }
-
-    if (clearBits) {
-        const auto fnDrawBuffers = [&](const std::vector<const WebGLFBAttachPoint*>& src)
-        {
-            std::vector<GLenum> enumList;
-
-            for (const auto& cur : src) {
-                const auto& attachEnum = cur->mAttachmentPoint;
-                const GLenum attachId = attachEnum - LOCAL_GL_COLOR_ATTACHMENT0;
-
-                while (enumList.size() < attachId) {
-                    enumList.push_back(LOCAL_GL_NONE);
-                }
-                enumList.push_back(attachEnum);
-            }
-
-            mContext->gl->fDrawBuffers(enumList.size(), enumList.data());
-        };
-
-        ////
-        // Clear
-
-        mContext->MakeContextCurrent();
-
-        const bool hasDrawBuffers = mContext->HasDrawBuffers();
-        if (hasDrawBuffers) {
-            fnDrawBuffers(colorAttachmentsToClear);
-        }
-
-        {
-            gl::ScopedBindFramebuffer autoBind(mContext->gl, mGLName);
-
-            mContext->ForceClearFramebufferWithDefaultValues(clearBits, false);
-        }
-
-        if (hasDrawBuffers) {
-            RefreshDrawBuffers();
-        }
-
-        // Mark initialized.
-        for (const auto& cur : attachmentsToClear) {
-            cur->SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
-        }
-    }
-
+    *out_format = attachPoint->Format();
+    attachPoint->Size(out_width, out_height);
     return true;
 }
 
-WebGLFramebuffer::ResolvedData::ResolvedData(const WebGLFramebuffer& parent)
-    : hasSampleBuffers(false)
-    , depthBuffer(nullptr)
-    , stencilBuffer(nullptr)
-{
-    if (parent.mDepthAttachment.IsDefined()) {
-        depthBuffer = &parent.mDepthAttachment;
-    }
-    if (parent.mStencilAttachment.IsDefined()) {
-        stencilBuffer = &parent.mStencilAttachment;
-    }
-    if (parent.mDepthStencilAttachment.IsDefined()) {
-        depthBuffer = &parent.mDepthStencilAttachment;
-        stencilBuffer = &parent.mDepthStencilAttachment;
-    }
-
-    ////
-
-    texDrawBuffers.reserve(parent.mColorDrawBuffers.size() + 2); // +2 for depth+stencil.
-
-    const auto fnCommon = [&](const WebGLFBAttachPoint& attach) {
-        if (!attach.IsDefined())
-            return false;
-
-        hasSampleBuffers |= bool(attach.Samples());
-
-        if (attach.Texture()) {
-            texDrawBuffers.push_back(&attach);
-        }
-        return true;
-    };
-
-    ////
-
-    const auto fnColor = [&](const WebGLFBAttachPoint& attach,
-                             decltype(drawSet)* const out_destSet)
-    {
-        if (!fnCommon(attach))
-            return;
-
-        out_destSet->insert(WebGLFBAttachPoint::Ordered(attach));
-    };
-
-    const auto fnDepthStencil = [&](const WebGLFBAttachPoint& attach) {
-        if (!fnCommon(attach))
-            return;
-
-        drawSet.insert(WebGLFBAttachPoint::Ordered(attach));
-        readSet.insert(WebGLFBAttachPoint::Ordered(attach));
-    };
-
-    ////
-
-    fnDepthStencil(parent.mDepthAttachment);
-    fnDepthStencil(parent.mStencilAttachment);
-    fnDepthStencil(parent.mDepthStencilAttachment);
-
-    for (const auto& attach : parent.mColorDrawBuffers) {
-        fnColor(*attach, &drawSet);
-    }
-
-    if (parent.mColorReadBuffer) {
-        fnColor(*(parent.mColorReadBuffer), &readSet);
-    }
-}
-
-void
-WebGLFramebuffer::RefreshResolvedData()
-{
-    if (mResolvedCompleteData) {
-        mResolvedCompleteData.reset(new ResolvedData(*this));
-    }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Entrypoints
-
-FBStatus
-WebGLFramebuffer::CheckFramebufferStatus(const char* funcName)
+static bool
+AttachmentsDontMatch(const WebGLFBAttachPoint& a, const WebGLFBAttachPoint& b)
 {
-    if (IsResolvedComplete())
-        return LOCAL_GL_FRAMEBUFFER_COMPLETE;
-
-    // Ok, let's try to resolve it!
-
-    nsCString statusInfo;
-    FBStatus ret = PrecheckFramebufferStatus(&statusInfo);
-    do {
-        if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE)
-            break;
-
-        // Looks good on our end. Let's ask the driver.
-        gl::GLContext* const gl = mContext->gl;
-        gl->MakeCurrent();
-
-        const ScopedFBRebinder autoFB(mContext);
-        gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLName);
-
-        ////
-
-        ResolveAttachments(); // OK, attach everything properly!
-        RefreshDrawBuffers();
-        RefreshReadBuffer();
-
-        ret = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
-
-        ////
-
-        if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
-            const nsPrintfCString text("Bad status according to the driver: 0x%04x",
-                                       ret.get());
-            statusInfo = text;
-            break;
-        }
-
-        if (!ResolveAttachmentData(funcName)) {
-            ret = LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
-            statusInfo.AssignLiteral("Failed to lazily-initialize attachment data.");
-            break;
-        }
-
-        mResolvedCompleteData.reset(new ResolvedData(*this));
-        return LOCAL_GL_FRAMEBUFFER_COMPLETE;
-    } while (false);
-
-    MOZ_ASSERT(ret != LOCAL_GL_FRAMEBUFFER_COMPLETE);
-    mContext->GenerateWarning("%s: Framebuffer not complete. (status: 0x%04x) %s",
-                              funcName, ret.get(), statusInfo.BeginReading());
-    return ret;
-}
-
-////
-
-void
-WebGLFramebuffer::RefreshDrawBuffers() const
-{
-    const auto& gl = mContext->gl;
-    if (!gl->IsSupported(gl::GLFeature::draw_buffers))
-        return;
-
-    // Prior to GL4.1, having a no-image FB attachment that's selected by DrawBuffers
-    // yields a framebuffer status of FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER.
-    // We could workaround this only on affected versions, but it's easier be
-    // unconditional.
-    std::vector<GLenum> driverBuffers(mContext->mImplMaxDrawBuffers, LOCAL_GL_NONE);
-    for (const auto& attach : mColorDrawBuffers) {
-        if (attach->HasImage()) {
-            const uint32_t index = attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0;
-            driverBuffers[index] = attach->mAttachmentPoint;
-        }
-    }
-
-    gl->fDrawBuffers(driverBuffers.size(), driverBuffers.data());
-}
-
-void
-WebGLFramebuffer::RefreshReadBuffer() const
-{
-    const auto& gl = mContext->gl;
-    if (!gl->IsSupported(gl::GLFeature::read_buffer))
-        return;
-
-    // Prior to GL4.1, having a no-image FB attachment that's selected by ReadBuffer
-    // yields a framebuffer status of FRAMEBUFFER_INCOMPLETE_READ_BUFFER.
-    // We could workaround this only on affected versions, but it's easier be
-    // unconditional.
-    GLenum driverBuffer = LOCAL_GL_NONE;
-    if (mColorReadBuffer && mColorReadBuffer->HasImage()) {
-        driverBuffer = mColorReadBuffer->mAttachmentPoint;
-    }
-
-    gl->fReadBuffer(driverBuffer);
-}
-
-////
-
-void
-WebGLFramebuffer::DrawBuffers(const char* funcName, const dom::Sequence<GLenum>& buffers)
-{
-    if (buffers.Length() > mContext->mImplMaxDrawBuffers) {
-        // "An INVALID_VALUE error is generated if `n` is greater than MAX_DRAW_BUFFERS."
-        mContext->ErrorInvalidValue("%s: `buffers` must have a length <="
-                                    " MAX_DRAW_BUFFERS.", funcName);
-        return;
-    }
-
-    std::vector<const WebGLFBAttachPoint*> newColorDrawBuffers;
-    newColorDrawBuffers.reserve(buffers.Length());
-
-    for (size_t i = 0; i < buffers.Length(); i++) {
-        // "If the GL is bound to a draw framebuffer object, the `i`th buffer listed in
-        //  bufs must be COLOR_ATTACHMENTi or NONE. Specifying a buffer out of order,
-        //  BACK, or COLOR_ATTACHMENTm where `m` is greater than or equal to the value of
-        // MAX_COLOR_ATTACHMENTS, will generate the error INVALID_OPERATION.
-
-        // WEBGL_draw_buffers:
-        // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or
-        //  equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter."
-        // This means that if buffers.Length() isn't larger than MaxDrawBuffers, it won't
-        // be larger than MaxColorAttachments.
-        const auto& cur = buffers[i];
-        if (cur == LOCAL_GL_COLOR_ATTACHMENT0 + i) {
-            const auto& attach = mColorAttachments[i];
-            newColorDrawBuffers.push_back(&attach);
-        } else if (cur != LOCAL_GL_NONE) {
-            mContext->ErrorInvalidOperation("%s: `buffers[i]` must be NONE or"
-                                            " COLOR_ATTACHMENTi.",
-                                            funcName);
-            return;
-        }
+    if (a.Texture()) {
+        return (a.Texture() != b.Texture());
     }
 
-    ////
-
-    mContext->MakeContextCurrent();
-
-    mColorDrawBuffers.swap(newColorDrawBuffers);
-    RefreshDrawBuffers(); // Calls glDrawBuffers.
-    RefreshResolvedData();
-}
-
-void
-WebGLFramebuffer::ReadBuffer(const char* funcName, GLenum attachPoint)
-{
-    const auto& maybeAttach = GetColorAttachPoint(attachPoint);
-    if (!maybeAttach) {
-        const char text[] = "%s: `mode` must be a COLOR_ATTACHMENTi, for 0 <= i <"
-                            " MAX_DRAW_BUFFERS.";
-        if (attachPoint == LOCAL_GL_BACK) {
-            mContext->ErrorInvalidOperation(text, funcName);
-        } else {
-            mContext->ErrorInvalidEnum(text, funcName);
-        }
-        return;
-    }
-    const auto& attach = maybeAttach.value(); // Might be nullptr.
-
-    ////
-
-    mContext->MakeContextCurrent();
-
-    mColorReadBuffer = attach;
-    RefreshReadBuffer(); // Calls glReadBuffer.
-    RefreshResolvedData();
-}
-
-////
-
-void
-WebGLFramebuffer::FramebufferRenderbuffer(const char* funcName, GLenum attachEnum,
-                                          GLenum rbtarget, WebGLRenderbuffer* rb)
-{
-    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
-               mContext->mBoundReadFramebuffer == this);
-
-    // `attachment`
-    const auto maybeAttach = GetAttachPoint(attachEnum);
-    if (!maybeAttach) {
-        mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum);
-        return;
-    }
-    const auto& attach = maybeAttach.value();
-
-    // `rbTarget`
-    if (rbtarget != LOCAL_GL_RENDERBUFFER) {
-        mContext->ErrorInvalidEnumInfo("framebufferRenderbuffer: rbtarget:", rbtarget);
-        return;
-    }
-
-    // `rb`
-    if (!mContext->ValidateObjectAllowNull("framebufferRenderbuffer: rb", rb))
-        return;
-
-    // End of validation.
-
-    if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        mDepthAttachment.SetRenderbuffer(rb);
-        mStencilAttachment.SetRenderbuffer(rb);
-    } else {
-        attach->SetRenderbuffer(rb);
-    }
-
-    InvalidateFramebufferStatus();
-}
-
-void
-WebGLFramebuffer::FramebufferTexture2D(const char* funcName, GLenum attachEnum,
-                                       GLenum texImageTarget, WebGLTexture* tex,
-                                       GLint level)
-{
-    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
-               mContext->mBoundReadFramebuffer == this);
-
-    // `attachment`
-    const auto maybeAttach = GetAttachPoint(attachEnum);
-    if (!maybeAttach) {
-        mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum);
-        return;
-    }
-    const auto& attach = maybeAttach.value();
-
-    // `texImageTarget`
-    if (texImageTarget != LOCAL_GL_TEXTURE_2D &&
-        (texImageTarget < LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
-         texImageTarget > LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z))
-    {
-        mContext->ErrorInvalidEnumInfo("framebufferTexture2D: texImageTarget:",
-                                       texImageTarget);
-        return;
-    }
-
-    // `texture`
-    if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", tex))
-        return;
-
-    if (tex) {
-        if (!tex->HasEverBeenBound()) {
-            mContext->ErrorInvalidOperation("%s: `texture` has never been bound.",
-                                            funcName);
-            return;
-        }
-
-        const TexTarget destTexTarget = TexImageTargetToTexTarget(texImageTarget);
-        if (tex->Target() != destTexTarget) {
-            mContext->ErrorInvalidOperation("%s: Mismatched texture and texture target.",
-                                            funcName);
-            return;
-        }
+    if (a.Renderbuffer()) {
+        return (a.Renderbuffer() != b.Renderbuffer());
     }
 
-    // `level`
-    if (level < 0)
-        return mContext->ErrorInvalidValue("%s: `level` must not be negative.", funcName);
-
-    if (mContext->IsWebGL2()) {
-        /* GLES 3.0.4 p208:
-         *   If textarget is one of TEXTURE_CUBE_MAP_POSITIVE_X,
-         *   TEXTURE_CUBE_MAP_POSITIVE_Y, TEXTURE_CUBE_MAP_POSITIVE_Z,
-         *   TEXTURE_CUBE_MAP_NEGATIVE_X, TEXTURE_CUBE_MAP_NEGATIVE_Y,
-         *   or TEXTURE_CUBE_MAP_NEGATIVE_Z, then level must be greater
-         *   than or equal to zero and less than or equal to log2 of the
-         *   value of MAX_CUBE_MAP_TEXTURE_SIZE. If textarget is TEXTURE_2D,
-         *   level must be greater than or equal to zero and no larger than
-         *   log2 of the value of MAX_TEXTURE_SIZE. Otherwise, an
-         *   INVALID_VALUE error is generated.
-         */
-
-        if (texImageTarget == LOCAL_GL_TEXTURE_2D) {
-            if (uint32_t(level) > FloorLog2(mContext->mImplMaxTextureSize))
-                return mContext->ErrorInvalidValue("%s: `level` is too large.", funcName);
-        } else {
-            MOZ_ASSERT(texImageTarget >= LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X &&
-                       texImageTarget <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
-
-            if (uint32_t(level) > FloorLog2(mContext->mImplMaxCubeMapTextureSize))
-                return mContext->ErrorInvalidValue("%s: `level` is too large.", funcName);
-        }
-    } else if (level != 0) {
-        return mContext->ErrorInvalidValue("%s: `level` must be 0.", funcName);
-    }
-
-    // End of validation.
-
-    if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        mDepthAttachment.SetTexImage(tex, texImageTarget, level);
-        mStencilAttachment.SetTexImage(tex, texImageTarget, level);
-    } else {
-        attach->SetTexImage(tex, texImageTarget, level);
-    }
-
-    InvalidateFramebufferStatus();
-}
-
-void
-WebGLFramebuffer::FramebufferTextureLayer(const char* funcName, GLenum attachEnum,
-                                          WebGLTexture* tex, GLint level, GLint layer)
-{
-    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
-               mContext->mBoundReadFramebuffer == this);
-
-    // `attachment`
-    const auto maybeAttach = GetAttachPoint(attachEnum);
-    if (!maybeAttach) {
-        mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum);
-        return;
-    }
-    const auto& attach = maybeAttach.value();
-
-    // `texture`
-    if (!mContext->ValidateObjectAllowNull("framebufferTextureLayer: texture", tex))
-        return;
-
-    if (tex && !tex->HasEverBeenBound()) {
-        mContext->ErrorInvalidOperation("%s: `texture` has never been bound.", funcName);
-        return;
-    }
-
-    // `level`, `layer`
-    if (layer < 0)
-        return mContext->ErrorInvalidValue("%s: `layer` must be >= 0.", funcName);
-
-    if (level < 0)
-        return mContext->ErrorInvalidValue("%s: `level` must be >= 0.", funcName);
-
-    TexImageTarget texImageTarget = LOCAL_GL_TEXTURE_3D;
-    if (tex) {
-        texImageTarget = tex->Target().get();
-        switch (texImageTarget.get()) {
-        case LOCAL_GL_TEXTURE_3D:
-            if (uint32_t(layer) >= mContext->mImplMax3DTextureSize) {
-                mContext->ErrorInvalidValue("%s: `layer` must be < %s.", funcName,
-                                            "MAX_3D_TEXTURE_SIZE");
-                return;
-            }
-
-            if (uint32_t(level) > FloorLog2(mContext->mImplMax3DTextureSize)) {
-                mContext->ErrorInvalidValue("%s: `level` must be <= log2(%s).", funcName,
-                                            "MAX_3D_TEXTURE_SIZE");
-                return;
-            }
-            break;
-
-        case LOCAL_GL_TEXTURE_2D_ARRAY:
-            if (uint32_t(layer) >= mContext->mImplMaxArrayTextureLayers) {
-                mContext->ErrorInvalidValue("%s: `layer` must be < %s.", funcName,
-                                            "MAX_ARRAY_TEXTURE_LAYERS");
-                return;
-            }
-
-            if (uint32_t(level) > FloorLog2(mContext->mImplMaxTextureSize)) {
-                mContext->ErrorInvalidValue("%s: `level` must be <= log2(%s).", funcName,
-                                            "MAX_TEXTURE_SIZE");
-                return;
-            }
-            break;
-
-        default:
-            mContext->ErrorInvalidOperation("%s: `texture` must be a TEXTURE_3D or"
-                                            " TEXTURE_2D_ARRAY.",
-                                            funcName);
-            return;
-        }
-    }
-
-    // End of validation.
-
-    if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        mDepthAttachment.SetTexImage(tex, texImageTarget, level, layer);
-        mStencilAttachment.SetTexImage(tex, texImageTarget, level, layer);
-    } else {
-        attach->SetTexImage(tex, texImageTarget, level, layer);
-    }
-
-    InvalidateFramebufferStatus();
+    return false;
 }
 
 JS::Value
 WebGLFramebuffer::GetAttachmentParameter(const char* funcName, JSContext* cx,
-                                         GLenum target, GLenum attachEnum, GLenum pname,
-                                         ErrorResult* const out_error)
+                                         GLenum target, GLenum attachment,
+                                         GLenum pname, ErrorResult* const out_error)
 {
-    const auto maybeAttach = GetAttachPoint(attachEnum);
-    if (!maybeAttach || attachEnum == LOCAL_GL_NONE) {
+    auto attachPoint = GetAttachPoint(attachment);
+    if (!attachPoint) {
         mContext->ErrorInvalidEnum("%s: Can only query COLOR_ATTACHMENTi,"
                                    " DEPTH_ATTACHMENT, DEPTH_STENCIL_ATTACHMENT, or"
                                    " STENCIL_ATTACHMENT for a framebuffer.",
                                    funcName);
         return JS::NullValue();
     }
-    auto attach = maybeAttach.value();
 
-    if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
         // There are a couple special rules for this one.
 
         if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE) {
             mContext->ErrorInvalidOperation("%s: Querying"
                                             " FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE"
                                             " against DEPTH_STENCIL_ATTACHMENT is an"
                                             " error.",
                                             funcName);
             return JS::NullValue();
         }
 
-        if (mDepthAttachment.Renderbuffer() != mStencilAttachment.Renderbuffer() ||
-            mDepthAttachment.Texture() != mStencilAttachment.Texture())
-        {
+        if (AttachmentsDontMatch(DepthAttachment(), StencilAttachment())) {
             mContext->ErrorInvalidOperation("%s: DEPTH_ATTACHMENT and STENCIL_ATTACHMENT"
                                             " have different objects bound.",
                                             funcName);
             return JS::NullValue();
         }
 
-        attach = &mDepthAttachment;
-    }
-
-    return attach->GetParameter(funcName, mContext, cx, target, attachEnum, pname,
-                                out_error);
-}
-
-////////////////////
-
-static void
-GetBackbufferFormats(const WebGLContext* webgl,
-                     const webgl::FormatInfo** const out_color,
-                     const webgl::FormatInfo** const out_depth,
-                     const webgl::FormatInfo** const out_stencil)
-{
-    const auto& options = webgl->Options();
-
-    const auto effFormat = (options.alpha ? webgl::EffectiveFormat::RGBA8
-                                          : webgl::EffectiveFormat::RGB8);
-    *out_color = webgl::GetFormat(effFormat);
-
-    *out_depth = nullptr;
-    *out_stencil = nullptr;
-    if (options.depth && options.stencil) {
-        *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH24_STENCIL8);
-        *out_stencil = *out_depth;
-    } else {
-        if (options.depth) {
-            *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT16);
-        }
-        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,
-                                  GLbitfield mask, GLenum filter)
-{
-    const char funcName[] = "blitFramebuffer";
-    auto& gl = webgl->gl;
-
-
-    ////
-    // Collect data
-
-    const auto fnGetFormat = [](const WebGLFBAttachPoint* cur) -> const webgl::FormatInfo*
-    {
-        if (!cur)
-            return nullptr;
-
-        MOZ_ASSERT(cur->IsDefined());
-        return cur->Format()->format;
-    };
-
-    bool srcSampleBuffers;
-    const webgl::FormatInfo* srcColorFormat;
-    const webgl::FormatInfo* srcDepthFormat;
-    const webgl::FormatInfo* srcStencilFormat;
-
-    if (srcFB) {
-        srcSampleBuffers = srcFB->mResolvedCompleteData->hasSampleBuffers;
-
-        srcColorFormat = fnGetFormat(srcFB->mColorReadBuffer);
-        srcDepthFormat = fnGetFormat(srcFB->mResolvedCompleteData->depthBuffer);
-        srcStencilFormat = fnGetFormat(srcFB->mResolvedCompleteData->stencilBuffer);
-    } else {
-        srcSampleBuffers = false; // Always false.
-
-        GetBackbufferFormats(webgl, &srcColorFormat, &srcDepthFormat, &srcStencilFormat);
-    }
-
-    ////
-
-    bool dstSampleBuffers;
-    const webgl::FormatInfo* dstDepthFormat;
-    const webgl::FormatInfo* dstStencilFormat;
-    bool dstHasColor = false;
-    bool colorFormatsMatch = true;
-    bool colorTypesMatch = true;
-
-    const auto fnCheckColorFormat = [&](const webgl::FormatInfo* dstFormat) {
-        dstHasColor = true;
-        colorFormatsMatch &= (dstFormat == srcColorFormat);
-        colorTypesMatch &= (dstFormat->componentType == srcColorFormat->componentType);
-    };
-
-    if (dstFB) {
-        dstSampleBuffers = dstFB->mResolvedCompleteData->hasSampleBuffers;
-
-        dstDepthFormat = fnGetFormat(dstFB->mResolvedCompleteData->depthBuffer);
-        dstStencilFormat = fnGetFormat(dstFB->mResolvedCompleteData->stencilBuffer);
-
-        for (const auto& drawBuffer : dstFB->mColorDrawBuffers) {
-            fnCheckColorFormat(drawBuffer->Format()->format);
-        }
-    } else {
-        dstSampleBuffers = bool(gl->Screen()->Samples());
-
-        const webgl::FormatInfo* dstColorFormat;
-        GetBackbufferFormats(webgl, &dstColorFormat, &dstDepthFormat, &dstStencilFormat);
-
-        fnCheckColorFormat(dstColorFormat);
-    }
-
-    ////
-    // Clear unused buffer bits
-
-    if (mask & LOCAL_GL_COLOR_BUFFER_BIT &&
-        !srcColorFormat && !dstHasColor)
-    {
-
-        mask ^= LOCAL_GL_COLOR_BUFFER_BIT;
-    }
-
-    if (mask & LOCAL_GL_DEPTH_BUFFER_BIT &&
-        !srcDepthFormat && !dstDepthFormat)
-    {
-        mask ^= LOCAL_GL_DEPTH_BUFFER_BIT;
-    }
-
-    if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
-        !srcStencilFormat && !dstStencilFormat)
-    {
-        mask ^= LOCAL_GL_STENCIL_BUFFER_BIT;
+        attachPoint = GetAttachPoint(LOCAL_GL_DEPTH_ATTACHMENT);
     }
 
-    ////
-    // Validation
+    FinalizeAttachments();
+
+    return attachPoint->GetParameter(funcName, mContext, cx, target, attachment, pname,
+                                     out_error);
+}
+
 
-    if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
-        if (srcColorFormat && filter == LOCAL_GL_LINEAR) {
-            const auto& type = srcColorFormat->componentType;
-            if (type == webgl::ComponentType::Int ||
-                type == webgl::ComponentType::UInt)
-            {
-                webgl->ErrorInvalidOperation("%s: `filter` is LINEAR and READ_BUFFER"
-                                             " contains integer data.",
-                                             funcName);
-                return;
-            }
-        }
-
-        if (!colorTypesMatch) {
-            webgl->ErrorInvalidOperation("%s: Color component types (fixed/float/uint/"
-                                         "int) must match.",
-                                         funcName);
-            return;
-        }
-    }
-
-    const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT |
-                                           LOCAL_GL_STENCIL_BUFFER_BIT;
-    if (bool(mask & depthAndStencilBits) &&
-        filter != LOCAL_GL_NEAREST)
+void
+WebGLFramebuffer::GatherAttachments(std::vector<const WebGLFBAttachPoint*>* const out) const
+{
+    auto itr = mDrawBuffers.cbegin();
+    if (itr != mDrawBuffers.cend() &&
+        *itr != LOCAL_GL_NONE)
     {
-        webgl->ErrorInvalidOperation("%s: DEPTH_BUFFER_BIT and STENCIL_BUFFER_BIT can"
-                                     " only be used with NEAREST filtering.",
-                                     funcName);
-        return;
-    }
-
-    /* GLES 3.0.4, p199:
-     *   Calling BlitFramebuffer will result in an INVALID_OPERATION error if
-     *   mask includes DEPTH_BUFFER_BIT or STENCIL_BUFFER_BIT, and the source
-     *   and destination depth and stencil buffer formats do not match.
-     *
-     * jgilbert: The wording is such that if only DEPTH_BUFFER_BIT is specified,
-     * the stencil formats must match. This seems wrong. It could be a spec bug,
-     * or I could be missing an interaction in one of the earlier paragraphs.
-     */
-    if (mask & LOCAL_GL_DEPTH_BUFFER_BIT &&
-        dstDepthFormat != srcDepthFormat)
-    {
-        webgl->ErrorInvalidOperation("%s: Depth buffer formats must match if selected.",
-                                     funcName);
-        return;
-    }
-
-    if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
-        dstStencilFormat != srcStencilFormat)
-    {
-        webgl->ErrorInvalidOperation("%s: Stencil buffer formats must match if selected.",
-                                     funcName);
-        return;
-    }
-
-    ////
-
-    if (dstSampleBuffers) {
-        webgl->ErrorInvalidOperation("%s: DRAW_FRAMEBUFFER may not have multiple"
-                                     " samples.",
-                                     funcName);
-        return;
+        out->push_back(&mColorAttachment0);
+        ++itr;
     }
 
-    if (srcSampleBuffers) {
-        if (mask & LOCAL_GL_COLOR_BUFFER_BIT &&
-            !colorFormatsMatch)
-        {
-            webgl->ErrorInvalidOperation("%s: Color buffer formats must match if"
-                                         " selected, when reading from a multisampled"
-                                         " source.",
-                                         funcName);
-            return;
-        }
+    size_t i = 0;
+    for (; itr != mDrawBuffers.cend(); ++itr) {
+        if (i >= mMoreColorAttachments.Size())
+            break;
 
-        if (dstX0 != srcX0 ||
-            dstX1 != srcX1 ||
-            dstY0 != srcY0 ||
-            dstY1 != srcY1)
-        {
-            webgl->ErrorInvalidOperation("%s: If the source is multisampled, then the"
-                                         " source and dest regions must match exactly.",
-                                         funcName);
-            return;
+        if (*itr != LOCAL_GL_NONE) {
+            out->push_back(&mMoreColorAttachments[i]);
         }
+        ++i;
     }
 
-    ////
-    // Check for feedback
-
-    if (srcFB && dstFB) {
-        const auto fnValidateBuffers = [&](GLenum bufferBit, const char* bufferBitName,
-                                           bool srcHas, bool dstHas)
-        {
-            if (mask & bufferBit &&
-                srcHas != dstHas)
-            {
-                webgl->ErrorInvalidOperation("%s: With %s, must have a corresponding draw"
-                                             " buffer iff there's a relevent read"
-                                             " buffer.",
-                                             funcName, bufferBitName);
-                return false;
-            }
-            return true;
-        };
-
-        if (!fnValidateBuffers( LOCAL_GL_COLOR_BUFFER_BIT, "COLOR_BUFFER_BIT",
-                                bool(srcFB->mColorReadBuffer),
-                                bool(dstFB->mColorDrawBuffers.size()) ) ||
-            !fnValidateBuffers( LOCAL_GL_DEPTH_BUFFER_BIT, "DEPTH_BUFFER_BIT",
-                                bool(srcFB->mResolvedCompleteData->depthBuffer),
-                                bool(dstFB->mResolvedCompleteData->depthBuffer) ) ||
-            !fnValidateBuffers( LOCAL_GL_STENCIL_BUFFER_BIT, "STENCIL_BUFFER_BIT",
-                                bool(srcFB->mResolvedCompleteData->stencilBuffer),
-                                bool(dstFB->mResolvedCompleteData->stencilBuffer) ))
-        {
-            return;
-        }
-
-        const auto& readSet = srcFB->mResolvedCompleteData->readSet;
-        const auto& drawSet = dstFB->mResolvedCompleteData->drawSet;
-
-        std::vector<WebGLFBAttachPoint::Ordered> intersection;
-        std::set_intersection(drawSet.begin(), drawSet.end(),
-                              readSet.begin(), readSet.end(),
-                              std::back_inserter(intersection));
-
-        if (intersection.size()) {
-            // set_intersection pulls from the first range, so it records conflicts on the
-            // DRAW_FRAMEBUFFER.
-            const auto& example = intersection.cbegin()->mRef;
-            webgl->ErrorInvalidOperation("%s: Feedback detected into DRAW_FRAMEBUFFER's"
-                                         " 0x%04x attachment.",
-                                         funcName, example.mAttachmentPoint);
-            return;
-        }
-    } else if (!srcFB && !dstFB) {
-        webgl->ErrorInvalidOperation("%s: Feedback with default framebuffer.", funcName);
-        return;
-    }
-
-    ////
-
-    gl->MakeCurrent();
-    gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1,
-                         dstX0, dstY0, dstX1, dstY1,
-                         mask, filter);
+    out->push_back(&mDepthAttachment);
+    out->push_back(&mStencilAttachment);
+    out->push_back(&mDepthStencilAttachment);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Goop.
 
 JSObject*
 WebGLFramebuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
@@ -1853,17 +1391,18 @@ ImplCycleCollectionTraverse(nsCycleColle
                             uint32_t flags = 0)
 {
     for (auto& cur : field) {
         ImplCycleCollectionTraverse(callback, cur, name, flags);
     }
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLFramebuffer,
+                                      mColorAttachment0,
                                       mDepthAttachment,
                                       mStencilAttachment,
                                       mDepthStencilAttachment,
-                                      mColorAttachments)
+                                      mMoreColorAttachments)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLFramebuffer, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLFramebuffer, Release)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -22,265 +22,278 @@ namespace mozilla {
 
 class WebGLFramebuffer;
 class WebGLRenderbuffer;
 class WebGLTexture;
 
 template<typename T>
 class PlacementArray;
 
-namespace gl {
-    class GLContext;
-} // namespace gl
-
-class WebGLFBAttachPoint final
+class WebGLFBAttachPoint
 {
-    friend class WebGLFramebuffer;
 public:
     WebGLFramebuffer* const mFB;
     const GLenum mAttachmentPoint;
-
-protected:
+private:
     WebGLRefPtr<WebGLTexture> mTexturePtr;
     WebGLRefPtr<WebGLRenderbuffer> mRenderbufferPtr;
     TexImageTarget mTexImageTarget;
     GLint mTexImageLayer;
     uint32_t mTexImageLevel;
 
-    ////
+    // PlacementArray needs a default constructor.
+    template<typename T>
+    friend class PlacementArray;
 
-    WebGLFBAttachPoint();
-    WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPoint);
+    WebGLFBAttachPoint()
+        : mFB(nullptr)
+        , mAttachmentPoint(0)
+    { }
 
 public:
+    WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPoint);
     ~WebGLFBAttachPoint();
 
-    ////
-
     void Unlink();
 
     bool IsDefined() const;
     bool IsDeleteRequested() const;
 
     const webgl::FormatUsageInfo* Format() const;
     uint32_t Samples() const;
 
     bool HasAlpha() const;
     bool IsReadableFloat() const;
 
     void Clear();
 
-    void SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level,
-                     GLint layer = 0);
+    void SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level);
+    void SetTexImageLayer(WebGLTexture* tex, TexImageTarget target, GLint level,
+                          GLint layer);
     void SetRenderbuffer(WebGLRenderbuffer* rb);
 
-    WebGLTexture* Texture() const { return mTexturePtr; }
-    WebGLRenderbuffer* Renderbuffer() const { return mRenderbufferPtr; }
-
+    const WebGLTexture* Texture() const {
+        return mTexturePtr;
+    }
+    WebGLTexture* Texture() {
+        return mTexturePtr;
+    }
+    const WebGLRenderbuffer* Renderbuffer() const {
+        return mRenderbufferPtr;
+    }
+    WebGLRenderbuffer* Renderbuffer() {
+        return mRenderbufferPtr;
+    }
     TexImageTarget ImageTarget() const {
         return mTexImageTarget;
     }
     GLint Layer() const {
         return mTexImageLayer;
     }
     uint32_t MipLevel() const {
         return mTexImageLevel;
     }
     void AttachmentName(nsCString* out) const;
 
     bool HasUninitializedImageData() const;
-    void SetImageDataStatus(WebGLImageDataStatus x) const;
+    void SetImageDataStatus(WebGLImageDataStatus x);
 
     void Size(uint32_t* const out_width, uint32_t* const out_height) const;
 
     bool HasImage() const;
     bool IsComplete(WebGLContext* webgl, nsCString* const out_info) const;
 
-    void Resolve(gl::GLContext* gl) const;
+    void FinalizeAttachment(gl::GLContext* gl, GLenum attachmentLoc) const;
 
     JS::Value GetParameter(const char* funcName, WebGLContext* webgl, JSContext* cx,
                            GLenum target, GLenum attachment, GLenum pname,
-                           ErrorResult* const out_error) const;
+                           ErrorResult* const out_error);
 
     void OnBackingStoreRespecified() const;
-
-    ////
+};
 
-    struct Ordered {
-        const WebGLFBAttachPoint& mRef;
+template<typename T>
+class PlacementArray
+{
+public:
+    const size_t mCapacity;
+protected:
+    size_t mSize;
+    T* const mArray;
 
-        explicit Ordered(const WebGLFBAttachPoint& ref)
-            : mRef(ref)
-        { }
+public:
+    explicit PlacementArray(size_t capacity)
+        : mCapacity(capacity)
+        , mSize(0)
+        , mArray((T*)moz_xmalloc(sizeof(T) * capacity))
+    { }
 
-        bool operator<(const Ordered& other) const {
-            MOZ_ASSERT(mRef.IsDefined() && other.mRef.IsDefined());
+    ~PlacementArray() {
+        for (auto& cur : *this) {
+            cur.~T();
+        }
+        free(mArray);
+    }
 
-#define ORDER_BY(X) if (X != other.X) return X < other.X;
+    T* begin() const {
+        return mArray;
+    }
+
+    T* end() const {
+        return mArray + mSize;
+    }
 
-            ORDER_BY(mRef.mRenderbufferPtr)
-            ORDER_BY(mRef.mTexturePtr)
-            ORDER_BY(mRef.mTexImageTarget.get())
-            ORDER_BY(mRef.mTexImageLevel)
-            ORDER_BY(mRef.mTexImageLayer)
+    T& operator [](size_t offset) const {
+        MOZ_ASSERT(offset < mSize);
+        return mArray[offset];
+    }
+
+    const size_t& Size() const { return mSize; }
 
-#undef ORDER_BY
-            return false;
-        }
-    };
+    template<typename A, typename B>
+    void AppendNew(A a, B b) {
+        if (mSize == mCapacity)
+            MOZ_CRASH("GFX: Bad EmplaceAppend.");
+
+        // Placement `new`:
+        new (&(mArray[mSize])) T(a, b);
+        ++mSize;
+    }
 };
 
 class WebGLFramebuffer final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLFramebuffer>
     , public LinkedListElement<WebGLFramebuffer>
     , public WebGLContextBoundObject
     , public SupportsWeakPtr<WebGLFramebuffer>
 {
     friend class WebGLContext;
 
 public:
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLFramebuffer)
 
     const GLuint mGLName;
 
-protected:
+private:
+    mutable bool mIsKnownFBComplete;
+
+    GLenum mReadBufferMode;
+
+    // No need to chase pointers for the oft-used color0.
+    WebGLFBAttachPoint mColorAttachment0;
+    WebGLFBAttachPoint mDepthAttachment;
+    WebGLFBAttachPoint mStencilAttachment;
+    WebGLFBAttachPoint mDepthStencilAttachment;
+
+    PlacementArray<WebGLFBAttachPoint> mMoreColorAttachments;
+
+    std::vector<GLenum> mDrawBuffers;
+
+    bool IsDrawBuffer(size_t n) const {
+        if (n < mDrawBuffers.size())
+            return bool(mDrawBuffers[n]);
+
+        return false;
+    }
+
 #ifdef ANDROID
     // Bug 1140459: Some drivers (including our test slaves!) don't
     // give reasonable answers for IsRenderbuffer, maybe others.
     // This shows up on Android 2.3 emulator.
     //
     // So we track the `is a Framebuffer` state ourselves.
     bool mIsFB;
 #endif
 
-    ////
-
-    WebGLFBAttachPoint mDepthAttachment;
-    WebGLFBAttachPoint mStencilAttachment;
-    WebGLFBAttachPoint mDepthStencilAttachment;
-
-    // In theory, this number can be unbounded based on the driver. However, no driver
-    // appears to expose more than 8. We might as well stop there too, for now.
-    // (http://opengl.gpuinfo.org/gl_stats_caps_single.php?listreportsbycap=GL_MAX_COLOR_ATTACHMENTS)
-    static const size_t kMaxColorAttachments = 8; // jgilbert's MacBook Pro exposes 8.
-    WebGLFBAttachPoint mColorAttachments[kMaxColorAttachments];
-
-    ////
-
-    std::vector<const WebGLFBAttachPoint*> mColorDrawBuffers; // Non-null
-    const WebGLFBAttachPoint* mColorReadBuffer; // Null if NONE
-
-    ////
-
-    struct ResolvedData {
-        // BlitFramebuffer
-        bool hasSampleBuffers;
-        const WebGLFBAttachPoint* depthBuffer;
-        const WebGLFBAttachPoint* stencilBuffer;
-
-        // 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;
-
-    ////
-
 public:
-    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLFramebuffer)
-    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLFramebuffer)
-
     WebGLFramebuffer(WebGLContext* webgl, GLuint fbo);
 
-    WebGLContext* GetParentObject() const { return mContext; }
-    virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
-
 private:
     ~WebGLFramebuffer() {
         DeleteOnce();
     }
 
+    const WebGLRectangleObject& GetAnyRectObject() const;
+
 public:
     void Delete();
 
-    ////
+    void FramebufferRenderbuffer(GLenum attachment, RBTarget rbtarget,
+                                 WebGLRenderbuffer* rb);
+    void FramebufferTexture2D(GLenum attachment, TexImageTarget texImageTarget,
+                              WebGLTexture* tex, GLint level);
+    void FramebufferTextureLayer(GLenum attachment, WebGLTexture* tex, GLint level,
+                                 GLint layer);
 
     bool HasDefinedAttachments() const;
     bool HasIncompleteAttachments(nsCString* const out_info) const;
     bool AllImageRectsMatch() const;
     bool AllImageSamplesMatch() const;
     FBStatus PrecheckFramebufferStatus(nsCString* const out_info) const;
+    FBStatus CheckFramebufferStatus(nsCString* const out_info) const;
+
+    const webgl::FormatUsageInfo*
+    GetFormatForAttachment(const WebGLFBAttachPoint& attachment) const;
+
+    const WebGLFBAttachPoint& ColorAttachment(size_t colorAttachmentId) const {
+        MOZ_ASSERT(colorAttachmentId < 1 + mMoreColorAttachments.Size());
+        return colorAttachmentId ? mMoreColorAttachments[colorAttachmentId - 1]
+                                 : mColorAttachment0;
+    }
+
+    const WebGLFBAttachPoint& DepthAttachment() const {
+        return mDepthAttachment;
+    }
+
+    const WebGLFBAttachPoint& StencilAttachment() const {
+        return mStencilAttachment;
+    }
+
+    const WebGLFBAttachPoint& DepthStencilAttachment() const {
+        return mDepthStencilAttachment;
+    }
+
+    void SetReadBufferMode(GLenum readBufferMode) {
+        mReadBufferMode = readBufferMode;
+    }
+
+    GLenum ReadBufferMode() const { return mReadBufferMode; }
+
+    void GatherAttachments(std::vector<const WebGLFBAttachPoint*>* const out) const;
 
 protected:
-    Maybe<WebGLFBAttachPoint*> GetAttachPoint(GLenum attachment); // Fallible
-    Maybe<WebGLFBAttachPoint*> GetColorAttachPoint(GLenum attachment); // Fallible
-    void ResolveAttachments() const;
-    void RefreshDrawBuffers() const;
-    void RefreshReadBuffer() const;
-    bool ResolveAttachmentData(const char* funcName) const;
+    WebGLFBAttachPoint* GetAttachPoint(GLenum attachment); // Fallible
 
 public:
     void DetachTexture(const WebGLTexture* tex);
+
     void DetachRenderbuffer(const WebGLRenderbuffer* rb);
+
+    WebGLContext* GetParentObject() const {
+        return mContext;
+    }
+
+    void FinalizeAttachments() const;
+
+    virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
+
+    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLFramebuffer)
+    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLFramebuffer)
+
     bool ValidateAndInitAttachments(const char* funcName);
 
+    void InvalidateFramebufferStatus() const {
+        mIsKnownFBComplete = false;
+    }
+
     bool ValidateForRead(const char* info,
                          const webgl::FormatUsageInfo** const out_format,
                          uint32_t* const out_width, uint32_t* const out_height);
 
-    ////////////////
-    // Getters
-
-#define GETTER(X) const decltype(m##X)& X() const { return m##X; }
-
-    GETTER(DepthAttachment)
-    GETTER(StencilAttachment)
-    GETTER(DepthStencilAttachment)
-    GETTER(ColorDrawBuffers)
-    GETTER(ColorReadBuffer)
-    GETTER(ResolvedCompleteData)
-
-#undef GETTER
-
-    ////////////////
-    // Invalidation
-
-    bool IsResolvedComplete() const { return bool(mResolvedCompleteData); }
-
-    void InvalidateFramebufferStatus() {
-        mResolvedCompleteData = nullptr;
-    }
-
-    void RefreshResolvedData();
-
-    ////////////////
-    // WebGL funcs
-
-    FBStatus CheckFramebufferStatus(const char* funcName);
-    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,
-                                GLbitfield mask, GLenum filter);
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_FRAMEBUFFER_H_
--- a/dom/canvas/WebGLObjectModel.h
+++ b/dom/canvas/WebGLObjectModel.h
@@ -326,16 +326,16 @@ inline void
 ImplCycleCollectionUnlink(mozilla::WebGLRefPtr<T>& field)
 {
     field = nullptr;
 }
 
 template <typename T>
 inline void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
-                            const mozilla::WebGLRefPtr<T>& field,
+                            mozilla::WebGLRefPtr<T>& field,
                             const char* name,
                             uint32_t flags = 0)
 {
     CycleCollectionNoteChild(callback, field.get(), name, flags);
 }
 
 #endif
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -9,17 +9,16 @@
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "mozilla/RefPtr.h"
 #include "nsPrintfCString.h"
 #include "WebGLActiveInfo.h"
 #include "WebGLContext.h"
 #include "WebGLShader.h"
-#include "WebGLTransformFeedback.h"
 #include "WebGLUniformLocation.h"
 #include "WebGLValidateStrings.h"
 
 namespace mozilla {
 
 /* If `name`: "foo[3]"
  * Then returns true, with
  *     `out_baseName`: "foo"
@@ -323,33 +322,25 @@ QueryProgramInfo(WebGLProgram* prog, gl:
 
                     GLuint loc = gl->fGetUniformBlockIndex(prog->mGLName,
                                                            mappedNameStr.c_str());
                     if (loc != LOCAL_GL_INVALID_INDEX)
                         isArray = true;
                 }
             }
 
-            ////
-
-            GLuint dataSize = 0;
-            gl->fGetActiveUniformBlockiv(prog->mGLName, i,
-                                         LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE,
-                                         (GLint*)&dataSize);
-
 #ifdef DUMP_SHADERVAR_MAPPINGS
             printf_stderr("[uniform block %i] %s/%i/%s/%s\n", i,
                           mappedName.BeginReading(), (int)isArray,
                           baseMappedName.BeginReading(), baseUserName.BeginReading());
             printf_stderr("    lengthWithoutNull: %d\n", lengthWithoutNull);
             printf_stderr("    isArray: %d\n", (int)isArray);
 #endif
 
-            auto* block = new webgl::UniformBlockInfo(webgl, baseUserName, baseMappedName,
-                                                      dataSize);
+            const auto* block = new webgl::UniformBlockInfo(baseUserName, baseMappedName);
             info->uniformBlocks.push_back(block);
         }
     }
 
     // Transform feedback varyings
 
     if (gl->IsSupported(gl::GLFeature::transform_feedback2)) {
         GLuint numTransformFeedbackVaryings = 0;
@@ -436,18 +427,17 @@ CreateProgram(gl::GLContext* gl)
 {
     gl->MakeCurrent();
     return gl->fCreateProgram();
 }
 
 WebGLProgram::WebGLProgram(WebGLContext* webgl)
     : WebGLContextBoundObject(webgl)
     , mGLName(CreateProgram(webgl->GL()))
-    , mNumActiveTFOs(0)
-    , mNextLink_TransformFeedbackBufferMode(LOCAL_GL_SEPARATE_ATTRIBS)
+    , mTransformFeedbackBufferMode(LOCAL_GL_NONE)
 {
     mContext->mPrograms.insertBack(this);
 }
 
 WebGLProgram::~WebGLProgram()
 {
     DeleteOnce();
 }
@@ -518,17 +508,17 @@ WebGLProgram::BindAttribLocation(GLuint 
     if (StringBeginsWith(name, NS_LITERAL_STRING("gl_"))) {
         mContext->ErrorInvalidOperation("bindAttribLocation: Can't set the  location of a"
                                         " name that starts with 'gl_'.");
         return;
     }
 
     NS_LossyConvertUTF16toASCII asciiName(name);
 
-    auto res = mNextLink_BoundAttribLocs.insert({asciiName, loc});
+    auto res = mBoundAttribLocs.insert(std::pair<nsCString, GLuint>(asciiName, loc));
 
     const bool wasInserted = res.second;
     if (!wasInserted) {
         auto itr = res.first;
         itr->second = loc;
     }
 }
 
@@ -676,23 +666,21 @@ JS::Value
 WebGLProgram::GetProgramParameter(GLenum pname) const
 {
     gl::GLContext* gl = mContext->gl;
     gl->MakeCurrent();
 
     if (mContext->IsWebGL2()) {
         switch (pname) {
         case LOCAL_GL_ACTIVE_UNIFORM_BLOCKS:
+        case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
             return JS::Int32Value(GetProgramiv(gl, mGLName, pname));
 
         case LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS:
-            return JS::Int32Value(mNextLink_TransformFeedbackVaryings.size());
-
-        case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
-            return JS::Int32Value(mNextLink_TransformFeedbackBufferMode);
+            return JS::Int32Value(mTransformFeedbackVaryings.size());
        }
     }
 
     switch (pname) {
     case LOCAL_GL_ATTACHED_SHADERS:
     case LOCAL_GL_ACTIVE_UNIFORMS:
     case LOCAL_GL_ACTIVE_ATTRIBUTES:
         return JS::Int32Value(GetProgramiv(gl, mGLName, pname));
@@ -939,143 +927,106 @@ WebGLProgram::GetUniformIndices(const do
         GLuint index = 0;
         gl->fGetUniformIndices(mGLName, 1, &mappedNameBytes, &index);
         arr.AppendElement(index);
     }
 }
 
 
 void
-WebGLProgram::UniformBlockBinding(GLuint uniformBlockIndex,
-                                  GLuint uniformBlockBinding) const
+WebGLProgram::UniformBlockBinding(GLuint uniformBlockIndex, GLuint uniformBlockBinding) const
 {
-    const char funcName[] = "getActiveUniformBlockName";
     if (!IsLinked()) {
-        mContext->ErrorInvalidOperation("%s: `program` must be linked.", funcName);
+        mContext->ErrorInvalidOperation("getActiveUniformBlockName: `program` must be linked.");
         return;
     }
 
-    const auto& uniformBlocks = LinkInfo()->uniformBlocks;
-    if (uniformBlockIndex >= uniformBlocks.size()) {
-        mContext->ErrorInvalidValue("%s: Index %u invalid.", funcName, uniformBlockIndex);
+    const webgl::LinkedProgramInfo* linkInfo = LinkInfo();
+    if (uniformBlockIndex >= linkInfo->uniformBlocks.size()) {
+        mContext->ErrorInvalidValue("getActiveUniformBlockName: index %u invalid.", uniformBlockIndex);
         return;
     }
-    const auto& uniformBlock = uniformBlocks[uniformBlockIndex];
 
-    const auto& indexedBindings = mContext->mIndexedUniformBufferBindings;
-    if (uniformBlockBinding >= indexedBindings.size()) {
-        mContext->ErrorInvalidValue("%s: Binding %u invalid.", funcName,
-                                    uniformBlockBinding);
+    if (uniformBlockBinding > mContext->mGLMaxUniformBufferBindings) {
+        mContext->ErrorInvalidEnum("getActiveUniformBlockName: binding %u invalid.", uniformBlockBinding);
         return;
     }
-    const auto& indexedBinding = indexedBindings[uniformBlockBinding];
-
-    ////
 
     gl::GLContext* gl = mContext->GL();
     gl->MakeCurrent();
     gl->fUniformBlockBinding(mGLName, uniformBlockIndex, uniformBlockBinding);
-
-    ////
-
-    uniformBlock->mBinding = &indexedBinding;
 }
 
-bool
-WebGLProgram::ValidateForLink()
+void
+WebGLProgram::LinkProgram()
 {
+    mContext->InvalidateBufferFetching(); // we do it early in this function
+    // as some of the validation below changes program state
+
+    mLinkLog.Truncate();
+    mMostRecentLinkInfo = nullptr;
+
     if (!mVertShader || !mVertShader->IsCompiled()) {
         mLinkLog.AssignLiteral("Must have a compiled vertex shader attached.");
-        return false;
+        mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
+        return;
     }
 
     if (!mFragShader || !mFragShader->IsCompiled()) {
         mLinkLog.AssignLiteral("Must have an compiled fragment shader attached.");
-        return false;
+        mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
+        return;
     }
 
-    if (!mFragShader->CanLinkTo(mVertShader, &mLinkLog))
-        return false;
+    if (!mFragShader->CanLinkTo(mVertShader, &mLinkLog)) {
+        mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
+        return;
+    }
 
-    const auto& gl = mContext->gl;
+    gl::GLContext* gl = mContext->gl;
+    gl->MakeCurrent();
 
     if (gl->WorkAroundDriverBugs() &&
         mContext->mIsMesa)
     {
         // Bug 777028: Mesa can't handle more than 16 samplers per program,
         // counting each array entry.
         size_t numSamplerUniforms_upperBound = mVertShader->CalcNumSamplerUniforms() +
                                                mFragShader->CalcNumSamplerUniforms();
         if (numSamplerUniforms_upperBound > 16) {
             mLinkLog.AssignLiteral("Programs with more than 16 samplers are disallowed on"
                                    " Mesa drivers to avoid crashing.");
-            return false;
+            mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
+            return;
         }
 
         // Bug 1203135: Mesa crashes internally if we exceed the reported maximum attribute count.
         if (mVertShader->NumAttributes() > mContext->MaxVertexAttribs()) {
-            mLinkLog.AssignLiteral("Number of attributes exceeds Mesa's reported max"
-                                   " attribute count.");
-            return false;
+            mLinkLog.AssignLiteral("Number of attributes exceeds Mesa's reported max attribute count.");
+            mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
+            return;
         }
     }
 
-    return true;
-}
-
-void
-WebGLProgram::LinkProgram()
-{
-    const char funcName[] = "linkProgram";
-
-    if (mNumActiveTFOs) {
-        mContext->ErrorInvalidOperation("%s: Program is in-use by one or more active"
-                                        " transform feedback objects.",
-                                        funcName);
-        return;
-    }
-
-    mContext->MakeContextCurrent();
-    mContext->InvalidateBufferFetching(); // we do it early in this function
-    // as some of the validation changes program state
-
-    mLinkLog.Truncate();
-    mMostRecentLinkInfo = nullptr;
-
-    if (!ValidateForLink()) {
-        mContext->GenerateWarning("%s: %s", funcName, mLinkLog.BeginReading());
-        return;
-    }
-
     // Bind the attrib locations.
     // This can't be done trivially, because we have to deal with mapped attrib names.
-    for (const auto& pair : mNextLink_BoundAttribLocs) {
-        const auto& name = pair.first;
-        const auto& index = pair.second;
+    for (auto itr = mBoundAttribLocs.begin(); itr != mBoundAttribLocs.end(); ++itr) {
+        const nsCString& name = itr->first;
+        GLuint index = itr->second;
 
         mVertShader->BindAttribLocation(mGLName, name, index);
     }
 
-    // Storage for transform feedback varyings before link.
-    // (Work around for bug seen on nVidia drivers.)
-    std::vector<std::string> scopedMappedTFVaryings;
-
-    if (mContext->IsWebGL2()) {
-        mVertShader->MapTransformFeedbackVaryings(mNextLink_TransformFeedbackVaryings,
-                                                  &scopedMappedTFVaryings);
-
-        std::vector<const char*> driverVaryings;
-        driverVaryings.reserve(scopedMappedTFVaryings.size());
-        for (const auto& cur : scopedMappedTFVaryings) {
-            driverVaryings.push_back(cur.c_str());
-        }
-
-        mContext->gl->fTransformFeedbackVaryings(mGLName, driverVaryings.size(),
-                                                 driverVaryings.data(),
-                                                 mNextLink_TransformFeedbackBufferMode);
+    if (!mTransformFeedbackVaryings.empty()) {
+        // Bind the transform feedback varyings.
+        // This can't be done trivially, because we have to deal with mapped names too.
+        mVertShader->ApplyTransformFeedbackVaryings(mGLName,
+                                                    mTransformFeedbackVaryings,
+                                                    mTransformFeedbackBufferMode,
+                                                    &mTempMappedVaryings);
     }
 
     LinkAndUpdate();
 
     if (mMostRecentLinkInfo) {
         nsCString postLinkLog;
         if (ValidateAfterTentativeLink(&postLinkLog))
             return;
@@ -1120,73 +1071,20 @@ NumUsedLocationsByElemType(GLenum elemTy
     case LOCAL_GL_FLOAT_MAT4:
         return 4;
 
     default:
         return 1;
     }
 }
 
-static uint8_t
-NumComponents(GLenum elemType)
-{
-    switch (elemType) {
-    case LOCAL_GL_FLOAT:
-    case LOCAL_GL_INT:
-    case LOCAL_GL_UNSIGNED_INT:
-    case LOCAL_GL_BOOL:
-        return 1;
-
-    case LOCAL_GL_FLOAT_VEC2:
-    case LOCAL_GL_INT_VEC2:
-    case LOCAL_GL_UNSIGNED_INT_VEC2:
-    case LOCAL_GL_BOOL_VEC2:
-        return 2;
-
-    case LOCAL_GL_FLOAT_VEC3:
-    case LOCAL_GL_INT_VEC3:
-    case LOCAL_GL_UNSIGNED_INT_VEC3:
-    case LOCAL_GL_BOOL_VEC3:
-        return 3;
-
-    case LOCAL_GL_FLOAT_VEC4:
-    case LOCAL_GL_INT_VEC4:
-    case LOCAL_GL_UNSIGNED_INT_VEC4:
-    case LOCAL_GL_BOOL_VEC4:
-    case LOCAL_GL_FLOAT_MAT2:
-        return 4;
-
-    case LOCAL_GL_FLOAT_MAT2x3:
-    case LOCAL_GL_FLOAT_MAT3x2:
-        return 6;
-
-    case LOCAL_GL_FLOAT_MAT2x4:
-    case LOCAL_GL_FLOAT_MAT4x2:
-        return 8;
-
-    case LOCAL_GL_FLOAT_MAT3:
-        return 9;
-
-    case LOCAL_GL_FLOAT_MAT3x4:
-    case LOCAL_GL_FLOAT_MAT4x3:
-        return 12;
-
-    case LOCAL_GL_FLOAT_MAT4:
-        return 16;
-
-    default:
-        MOZ_CRASH("`elemType`");
-    }
-}
-
 bool
 WebGLProgram::ValidateAfterTentativeLink(nsCString* const out_linkLog) const
 {
     const auto& linkInfo = mMostRecentLinkInfo;
-    const auto& gl = mContext->gl;
 
     // Check if the attrib name conflicting to uniform name
     for (const auto& attrib : linkInfo->attribs) {
         const auto& attribName = attrib.mActiveInfo->mBaseUserName;
 
         for (const auto& uniform : linkInfo->uniforms) {
             const auto& uniformName = uniform->mActiveInfo->mBaseUserName;
             if (attribName == uniformName) {
@@ -1216,117 +1114,25 @@ WebGLProgram::ValidateAfterTentativeLink
                                                " attrib \"%s\".",
                                                aliasingName.BeginReading(),
                                                existingName.BeginReading());
                 return false;
             }
         }
     }
 
-    // Forbid:
-    // * Unrecognized varying name
-    // * Duplicate varying name
-    // * Too many components for specified buffer mode
-    if (mNextLink_TransformFeedbackVaryings.size()) {
-        GLuint maxComponentsPerIndex = 0;
-        switch (mNextLink_TransformFeedbackBufferMode) {
-        case LOCAL_GL_INTERLEAVED_ATTRIBS:
-            gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS,
-                             &maxComponentsPerIndex);
-            break;
-
-        case LOCAL_GL_SEPARATE_ATTRIBS:
-            gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS,
-                             &maxComponentsPerIndex);
-            break;
-
-        default:
-            MOZ_CRASH("`bufferMode`");
-        }
-
-        std::vector<size_t> componentsPerVert;
-        std::set<const WebGLActiveInfo*> alreadyUsed;
-        for (const auto& wideUserName : mNextLink_TransformFeedbackVaryings) {
-            if (!componentsPerVert.size() ||
-                mNextLink_TransformFeedbackBufferMode == LOCAL_GL_SEPARATE_ATTRIBS)
-            {
-                componentsPerVert.push_back(0);
-            }
-
-            ////
-
-            const WebGLActiveInfo* curInfo = nullptr;
-            for (const auto& info : linkInfo->transformFeedbackVaryings) {
-                const NS_ConvertASCIItoUTF16 info_wideUserName(info->mBaseUserName);
-                if (info_wideUserName == wideUserName) {
-                    curInfo = info.get();
-                    break;
-                }
-            }
-
-            if (!curInfo) {
-                const NS_LossyConvertUTF16toASCII asciiUserName(wideUserName);
-                *out_linkLog = nsPrintfCString("Transform feedback varying \"%s\" not"
-                                               " found.",
-                                               asciiUserName.BeginReading());
-                return false;
-            }
-
-            const auto insertResPair = alreadyUsed.insert(curInfo);
-            const auto& didInsert = insertResPair.second;
-            if (!didInsert) {
-                const NS_LossyConvertUTF16toASCII asciiUserName(wideUserName);
-                *out_linkLog = nsPrintfCString("Transform feedback varying \"%s\""
-                                               " specified twice.",
-                                               asciiUserName.BeginReading());
-                return false;
-            }
-
-            ////
-
-            size_t varyingComponents = NumComponents(curInfo->mElemType);
-            varyingComponents *= curInfo->mElemCount;
-
-            auto& totalComponentsForIndex = *(componentsPerVert.rbegin());
-            totalComponentsForIndex += varyingComponents;
-
-            if (totalComponentsForIndex > maxComponentsPerIndex) {
-                const NS_LossyConvertUTF16toASCII asciiUserName(wideUserName);
-                *out_linkLog = nsPrintfCString("Transform feedback varying \"%s\""
-                                               " pushed `componentsForIndex` over the"
-                                               " limit of %u.",
-                                               asciiUserName.BeginReading(),
-                                               maxComponentsPerIndex);
-                return false;
-            }
-        }
-
-        linkInfo->componentsPerTFVert.swap(componentsPerVert);
-    }
-
     return true;
 }
 
 bool
 WebGLProgram::UseProgram() const
 {
-    const char funcName[] = "useProgram";
-
     if (!mMostRecentLinkInfo) {
-        mContext->ErrorInvalidOperation("%s: Program has not been successfully linked.",
-                                        funcName);
-        return false;
-    }
-
-    if (mContext->mBoundTransformFeedback &&
-        mContext->mBoundTransformFeedback->mIsActive &&
-        !mContext->mBoundTransformFeedback->mIsPaused)
-    {
-        mContext->ErrorInvalidOperation("%s: Transform feedback active and not paused.",
-                                        funcName);
+        mContext->ErrorInvalidOperation("useProgram: Program has not been successfully"
+                                        " linked.");
         return false;
     }
 
     mContext->MakeContextCurrent();
 
     mContext->InvalidateBufferFetching();
 
     mContext->gl->fUseProgram(mGLName);
@@ -1368,16 +1174,21 @@ WebGLProgram::LinkAndUpdate()
     gl->fGetProgramiv(mGLName, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&logLenWithNull);
     if (logLenWithNull > 1) {
         mLinkLog.SetLength(logLenWithNull - 1);
         gl->fGetProgramInfoLog(mGLName, logLenWithNull, nullptr, mLinkLog.BeginWriting());
     } else {
         mLinkLog.SetLength(0);
     }
 
+    // Post link, temporary mapped varying names for transform feedback can be discarded.
+    // The memory can only be deleted after log is queried or the link status will fail.
+    std::vector<std::string> empty;
+    empty.swap(mTempMappedVaryings);
+
     GLint ok = 0;
     gl->fGetProgramiv(mGLName, LOCAL_GL_LINK_STATUS, &ok);
     if (!ok)
         return;
 
     mMostRecentLinkInfo = QueryProgramInfo(this, gl);
     MOZ_RELEASE_ASSERT(mMostRecentLinkInfo, "GFX: most recent link info not set.");
 }
@@ -1417,53 +1228,52 @@ WebGLProgram::FindUniformByMappedName(co
 
     return false;
 }
 
 void
 WebGLProgram::TransformFeedbackVaryings(const dom::Sequence<nsString>& varyings,
                                         GLenum bufferMode)
 {
-    const char funcName[] = "transformFeedbackVaryings";
-
-    const auto& gl = mContext->gl;
-    gl->MakeCurrent();
-
-    switch (bufferMode) {
-    case LOCAL_GL_INTERLEAVED_ATTRIBS:
-        break;
-
-    case LOCAL_GL_SEPARATE_ATTRIBS:
-        {
-            GLuint maxAttribs = 0;
-            gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
-                             &maxAttribs);
-            if (varyings.Length() >= maxAttribs) {
-                mContext->ErrorInvalidValue("%s: Length of `varyings` exceeds %s.",
-                                            funcName,
-                                            "TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
-                return;
-            }
-        }
-        break;
-
-    default:
-        mContext->ErrorInvalidEnum("%s: Bad `bufferMode`: 0x%04x.", funcName, bufferMode);
+    if (bufferMode != LOCAL_GL_INTERLEAVED_ATTRIBS &&
+        bufferMode != LOCAL_GL_SEPARATE_ATTRIBS)
+    {
+        mContext->ErrorInvalidEnum("transformFeedbackVaryings: `bufferMode` %s is "
+                                   "invalid. Must be one of gl.INTERLEAVED_ATTRIBS or "
+                                   "gl.SEPARATE_ATTRIBS.",
+                                   mContext->EnumName(bufferMode));
         return;
     }
 
-    ////
+    size_t varyingsCount = varyings.Length();
+    if (bufferMode == LOCAL_GL_SEPARATE_ATTRIBS &&
+        varyingsCount >= mContext->mGLMaxTransformFeedbackSeparateAttribs)
+    {
+        mContext->ErrorInvalidValue("transformFeedbackVaryings: Number of `varyings` exc"
+                                    "eeds gl.MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS.");
+        return;
+    }
 
-    mNextLink_TransformFeedbackVaryings.assign(varyings.Elements(),
-                                               varyings.Elements() + varyings.Length());
-    mNextLink_TransformFeedbackBufferMode = bufferMode;
+    std::vector<nsCString> asciiVaryings;
+    for (size_t i = 0; i < varyingsCount; i++) {
+        if (!ValidateGLSLVariableName(varyings[i], mContext, "transformFeedbackVaryings"))
+            return;
+
+        NS_LossyConvertUTF16toASCII asciiName(varyings[i]);
+        asciiVaryings.push_back(asciiName);
+    }
+
+    // All validated. Translate the strings and store them until
+    // program linking.
+    mTransformFeedbackBufferMode = bufferMode;
+    mTransformFeedbackVaryings.swap(asciiVaryings);
 }
 
 already_AddRefed<WebGLActiveInfo>
-WebGLProgram::GetTransformFeedbackVarying(GLuint index) const
+WebGLProgram::GetTransformFeedbackVarying(GLuint index)
 {
     // No docs in the WebGL 2 spec for this function. Taking the language for
     // getActiveAttrib, which states that the function returns null on any error.
     if (!IsLinked()) {
         mContext->ErrorInvalidOperation("getTransformFeedbackVarying: `program` must be "
                                         "linked.");
         return nullptr;
     }
--- a/dom/canvas/WebGLProgram.h
+++ b/dom/canvas/WebGLProgram.h
@@ -56,52 +56,43 @@ protected:
 public:
     explicit UniformInfo(WebGLActiveInfo* activeInfo);
 };
 
 struct UniformBlockInfo final
 {
     const nsCString mBaseUserName;
     const nsCString mBaseMappedName;
-    const uint32_t mDataSize;
 
-    const IndexedBufferBinding* mBinding;
-
-    UniformBlockInfo(WebGLContext* webgl, const nsACString& baseUserName,
-                     const nsACString& baseMappedName, uint32_t dataSize)
+    UniformBlockInfo(const nsACString& baseUserName,
+                     const nsACString& baseMappedName)
         : mBaseUserName(baseUserName)
         , mBaseMappedName(baseMappedName)
-        , mDataSize(dataSize)
-        , mBinding(&webgl->mIndexedUniformBufferBindings[0])
-    { }
+    {}
 };
 
 struct LinkedProgramInfo final
     : public RefCounted<LinkedProgramInfo>
     , public SupportsWeakPtr<LinkedProgramInfo>
 {
-    friend class WebGLProgram;
-
     MOZ_DECLARE_REFCOUNTED_TYPENAME(LinkedProgramInfo)
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(LinkedProgramInfo)
 
     //////
 
     WebGLProgram* const prog;
 
     std::vector<AttribInfo> attribs;
     std::vector<UniformInfo*> uniforms; // Owns its contents.
-    std::vector<UniformBlockInfo*> uniformBlocks; // Owns its contents.
+    std::vector<const UniformBlockInfo*> uniformBlocks; // Owns its contents.
     std::vector<RefPtr<WebGLActiveInfo>> transformFeedbackVaryings;
 
     // Needed for draw call validation.
     std::vector<UniformInfo*> uniformSamplers;
 
-    mutable std::vector<size_t> componentsPerTFVert;
-
     //////
 
     // The maps for the frag data names to the translated names.
     std::map<nsCString, const nsCString> fragDataMap;
 
     explicit LinkedProgramInfo(WebGLProgram* prog);
     ~LinkedProgramInfo();
 
@@ -127,18 +118,16 @@ struct LinkedProgramInfo final
 } // namespace webgl
 
 class WebGLProgram final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLProgram>
     , public LinkedListElement<WebGLProgram>
     , public WebGLContextBoundObject
 {
-    friend class WebGLTransformFeedback;
-
 public:
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLProgram)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLProgram)
 
     explicit WebGLProgram(WebGLContext* webgl);
 
     void Delete();
 
@@ -180,17 +169,17 @@ public:
                                  nsCString* const out_userName,
                                  bool* const out_isArray) const;
     bool FindUniformBlockByMappedName(const nsACString& mappedName,
                                       nsCString* const out_userName,
                                       bool* const out_isArray) const;
 
     void TransformFeedbackVaryings(const dom::Sequence<nsString>& varyings,
                                    GLenum bufferMode);
-    already_AddRefed<WebGLActiveInfo> GetTransformFeedbackVarying(GLuint index) const;
+    already_AddRefed<WebGLActiveInfo> GetTransformFeedbackVarying(GLuint index);
 
     void EnumerateFragOutputs(std::map<nsCString, const nsCString> &out_FragOutputs) const;
 
     bool IsLinked() const { return mMostRecentLinkInfo; }
 
     const webgl::LinkedProgramInfo* LinkInfo() const {
         return mMostRecentLinkInfo.get();
     }
@@ -200,31 +189,29 @@ public:
     }
 
     virtual JSObject* WrapObject(JSContext* js, JS::Handle<JSObject*> givenProto) override;
 
 private:
     ~WebGLProgram();
 
     void LinkAndUpdate();
-    bool ValidateForLink();
     bool ValidateAfterTentativeLink(nsCString* const out_linkLog) const;
 
 public:
     const GLuint mGLName;
 
 private:
     WebGLRefPtr<WebGLShader> mVertShader;
     WebGLRefPtr<WebGLShader> mFragShader;
-    size_t mNumActiveTFOs;
-
-    std::map<nsCString, GLuint> mNextLink_BoundAttribLocs;
-
-    std::vector<nsString> mNextLink_TransformFeedbackVaryings;
-    GLenum mNextLink_TransformFeedbackBufferMode;
-
+    std::map<nsCString, GLuint> mBoundAttribLocs;
+    std::vector<nsCString> mTransformFeedbackVaryings;
+    GLenum mTransformFeedbackBufferMode;
     nsCString mLinkLog;
     RefPtr<const webgl::LinkedProgramInfo> mMostRecentLinkInfo;
+    // Storage for transform feedback varyings before link.
+    // (Work around for bug seen on nVidia drivers.)
+    std::vector<std::string> mTempMappedVaryings;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_PROGRAM_H_
--- a/dom/canvas/WebGLRenderbuffer.cpp
+++ b/dom/canvas/WebGLRenderbuffer.cpp
@@ -223,30 +223,32 @@ WebGLRenderbuffer::RenderbufferStorage(c
     mWidth = width;
     mHeight = height;
     mImageDataStatus = WebGLImageDataStatus::UninitializedImageData;
 
     InvalidateStatusOfAttachedFBs();
 }
 
 void
-WebGLRenderbuffer::DoFramebufferRenderbuffer(FBTarget target, GLenum attachment) const
+WebGLRenderbuffer::DoFramebufferRenderbuffer(GLenum attachment) const
 {
     gl::GLContext* gl = mContext->gl;
 
     if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
         const GLuint stencilRB = (mSecondaryRB ? mSecondaryRB : mPrimaryRB);
-        gl->fFramebufferRenderbuffer(target.get(), LOCAL_GL_DEPTH_ATTACHMENT,
+        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
+                                     LOCAL_GL_DEPTH_ATTACHMENT,
                                      LOCAL_GL_RENDERBUFFER, mPrimaryRB);
-        gl->fFramebufferRenderbuffer(target.get(), LOCAL_GL_STENCIL_ATTACHMENT,
+        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
+                                     LOCAL_GL_STENCIL_ATTACHMENT,
                                      LOCAL_GL_RENDERBUFFER, stencilRB);
         return;
     }
 
-    gl->fFramebufferRenderbuffer(target.get(), attachment,
+    gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachment,
                                  LOCAL_GL_RENDERBUFFER, mPrimaryRB);
 }
 
 GLint
 WebGLRenderbuffer::GetRenderbufferParameter(RBTarget target,
                                             RBParam pname) const
 {
     gl::GLContext* gl = mContext->gl;
--- a/dom/canvas/WebGLRenderbuffer.h
+++ b/dom/canvas/WebGLRenderbuffer.h
@@ -80,16 +80,16 @@ public:
 
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
 protected:
     ~WebGLRenderbuffer() {
         DeleteOnce();
     }
 
-    void DoFramebufferRenderbuffer(FBTarget target, GLenum attachment) const;
+    void DoFramebufferRenderbuffer(GLenum attachment) const;
     GLenum DoRenderbufferStorage(uint32_t samples, const webgl::FormatUsageInfo* format,
                                  uint32_t width, uint32_t height);
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_RENDERBUFFER_H_
--- a/dom/canvas/WebGLShader.cpp
+++ b/dom/canvas/WebGLShader.cpp
@@ -403,34 +403,50 @@ WebGLShader::EnumerateFragOutputs(std::m
 
     if (!mValidator) {
         return;
     }
     mValidator->EnumerateFragOutputs(out_FragOutputs);
 }
 
 void
-WebGLShader::MapTransformFeedbackVaryings(const std::vector<nsString>& varyings,
-                                          std::vector<std::string>* out_mappedVaryings) const
+WebGLShader::ApplyTransformFeedbackVaryings(GLuint prog,
+                                            const std::vector<nsCString>& varyings,
+                                            GLenum bufferMode,
+                                            std::vector<std::string>* out_mappedVaryings) const
 {
     MOZ_ASSERT(mType == LOCAL_GL_VERTEX_SHADER);
+    MOZ_ASSERT(!varyings.empty());
     MOZ_ASSERT(out_mappedVaryings);
 
-    out_mappedVaryings->clear();
-    out_mappedVaryings->reserve(varyings.size());
+    const size_t varyingsCount = varyings.size();
+    std::vector<std::string> mappedVaryings;
+
+    for (size_t i = 0; i < varyingsCount; i++) {
+        const nsCString& userName = varyings[i];
+        std::string userNameStr(userName.BeginReading());
+
+        const std::string* mappedNameStr = &userNameStr;
+        if (mValidator)
+            mValidator->FindVaryingMappedNameByUserName(userNameStr, &mappedNameStr);
 
-    for (const auto& wideUserName : varyings) {
-        const NS_LossyConvertUTF16toASCII mozUserName(wideUserName); // Don't validate here.
-        const std::string userName(mozUserName.BeginReading(), mozUserName.Length());
-        const std::string* pMappedName = &userName;
-        if (mValidator) {
-            mValidator->FindVaryingMappedNameByUserName(userName, &pMappedName);
-        }
-        out_mappedVaryings->push_back(*pMappedName);
+        mappedVaryings.push_back(*mappedNameStr);
     }
+
+    // Temporary, tight packed array of string pointers into mappedVaryings.
+    std::vector<const GLchar*> strings;
+    strings.resize(varyingsCount);
+    for (size_t i = 0; i < varyingsCount; i++) {
+        strings[i] = mappedVaryings[i].c_str();
+    }
+
+    mContext->MakeContextCurrent();
+    mContext->gl->fTransformFeedbackVaryings(prog, varyingsCount, &strings[0], bufferMode);
+
+    out_mappedVaryings->swap(mappedVaryings);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Boilerplate
 
 JSObject*
 WebGLShader::WrapObject(JSContext* js, JS::Handle<JSObject*> givenProto)
 {
--- a/dom/canvas/WebGLShader.h
+++ b/dom/canvas/WebGLShader.h
@@ -47,16 +47,17 @@ public:
     void GetShaderSource(nsAString* out) const;
     void GetShaderTranslatedSource(nsAString* out) const;
     void ShaderSource(const nsAString& source);
 
     // Util funcs
     bool CanLinkTo(const WebGLShader* prev, nsCString* const out_log) const;
     size_t CalcNumSamplerUniforms() const;
     size_t NumAttributes() const;
+    void BindAttribLocation(GLuint prog, const nsCString& userName, GLuint index) const;
     bool FindAttribUserNameByMappedName(const nsACString& mappedName,
                                         nsDependentCString* const out_userName) const;
     bool FindVaryingByMappedName(const nsACString& mappedName,
                                  nsCString* const out_userName,
                                  bool* const out_isArray) const;
     bool FindUniformByMappedName(const nsACString& mappedName,
                                  nsCString* const out_userName,
                                  bool* const out_isArray) const;
@@ -65,22 +66,21 @@ public:
                                       bool* const out_isArray) const;
 
     void EnumerateFragOutputs(std::map<nsCString, const nsCString> &out_FragOutputs) const;
 
     bool IsCompiled() const {
         return mTranslationSuccessful && mCompilationSuccessful;
     }
 
-private:
-    void BindAttribLocation(GLuint prog, const nsCString& userName, GLuint index) const;
-    void MapTransformFeedbackVaryings(const std::vector<nsString>& varyings,
-                                      std::vector<std::string>* out_mappedVaryings) const;
+    void ApplyTransformFeedbackVaryings(GLuint prog,
+                                        const std::vector<nsCString>& varyings,
+                                        GLenum bufferMode,
+                                        std::vector<std::string>* out_mappedVaryings) const;
 
-public:
     // Other funcs
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
     void Delete();
 
     WebGLContext* GetParentObject() const { return mContext; }
 
     virtual JSObject* WrapObject(JSContext* js, JS::Handle<JSObject*> givenProto) override;
 
--- a/dom/canvas/WebGLShaderValidator.h
+++ b/dom/canvas/WebGLShaderValidator.h
@@ -59,18 +59,14 @@ public:
                                  bool* const out_isArray) const;
     bool FindUniformByMappedName(const std::string& mappedName,
                                  std::string* const out_userName,
                                  bool* const out_isArray) const;
     bool FindUniformBlockByMappedName(const std::string& mappedName,
                                       std::string* const out_userName) const;
 
     void EnumerateFragOutputs(std::map<nsCString, const nsCString> &out_FragOutputs) const;
-
-    bool ValidateTransformFeedback(const std::vector<nsString>& userNames,
-                                   uint32_t maxComponents, nsCString* const out_errorText,
-                                   std::vector<std::string>* const out_mappedNames);
 };
 
 } // namespace webgl
 } // namespace mozilla
 
 #endif // WEBGL_SHADER_VALIDATOR_H_
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -14,17 +14,16 @@
 #include "mozilla/gfx/2D.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/unused.h"
 #include "ScopedGLHelpers.h"
 #include "TexUnpackBlob.h"
-#include "WebGLBuffer.h"
 #include "WebGLContext.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLTexelConversions.h"
 
 namespace mozilla {
 
 /* This file handles:
@@ -209,25 +208,16 @@ WebGLContext::ValidateUnpackInfo(const c
                                  GLenum type, webgl::PackingInfo* const out)
 {
     if (usePBOs != bool(mBoundPixelUnpackBuffer)) {
         ErrorInvalidOperation("%s: PACK_BUFFER must be %s.", funcName,
                               (usePBOs ? "non-null" : "null"));
         return false;
     }
 
-    if (mBoundPixelUnpackBuffer &&
-        mBoundPixelUnpackBuffer->mNumActiveTFOs)
-    {
-        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                              " object.",
-                              funcName);
-        return false;
-    }
-
     if (!mFormatUsage->AreUnpackEnumsValid(format, type)) {
         ErrorInvalidEnum("%s: Invalid unpack format/type: 0x%04x/0x%04x", funcName,
                          format, type);
         return false;
     }
 
     out->format = format;
     out->type = type;
@@ -320,17 +310,17 @@ WebGLTexture::TexOrSubImage(bool isSubIm
     const bool isClientData = false;
     const auto ptr = (const uint8_t*)offset;
     webgl::TexUnpackBytes blob(mContext, target, width, height, depth, isClientData, ptr);
 
     const auto& packBuffer = mContext->mBoundPixelUnpackBuffer;
     const auto bufferByteCount = packBuffer->ByteLength();
 
     uint32_t byteCount = 0;
-    if (bufferByteCount >= uint64_t(offset)) {
+    if (bufferByteCount >= offset) {
         byteCount = bufferByteCount - offset;
     }
 
     if (!ValidateUnpackBytes(mContext, funcName, width, height, depth, pi, byteCount,
                              &blob))
     {
         return;
     }
@@ -1973,28 +1963,30 @@ ValidateCopyDestUsage(const char* funcNa
     return dstUsage;
 }
 
 bool
 WebGLTexture::ValidateCopyTexImageForFeedback(const char* funcName, uint32_t level) const
 {
     const auto& fb = mContext->mBoundReadFramebuffer;
     if (fb) {
-        const auto& attach = fb->ColorReadBuffer();
-        MOZ_ASSERT(attach);
+        const auto readBuffer = fb->ReadBufferMode();
+        MOZ_ASSERT(readBuffer != LOCAL_GL_NONE);
+        const uint32_t colorAttachment = readBuffer - LOCAL_GL_COLOR_ATTACHMENT0;
+        const auto& attach = fb->ColorAttachment(colorAttachment);
 
-        if (attach->Texture() == this &&
-            uint32_t(attach->MipLevel()) == level)
+        if (attach.Texture() == this &&
+            uint32_t(attach.MipLevel()) == level)
         {
             // Note that the TexImageTargets *don't* have to match for this to be
             // undefined per GLES 3.0.4 p211, thus an INVALID_OP in WebGL.
             mContext->ErrorInvalidOperation("%s: Feedback loop detected, as this texture"
                                             " is already attached to READ_FRAMEBUFFER's"
                                             " READ_BUFFER-selected COLOR_ATTACHMENT%u.",
-                                            funcName, attach->mAttachmentPoint);
+                                            funcName, colorAttachment);
             return false;
         }
     }
     return true;
 }
 
 // There is no CopyTexImage3D.
 void
--- a/dom/canvas/WebGLTransformFeedback.cpp
+++ b/dom/canvas/WebGLTransformFeedback.cpp
@@ -6,211 +6,53 @@
 #include "WebGLTransformFeedback.h"
 
 #include "GLContext.h"
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 #include "WebGL2Context.h"
 
 namespace mozilla {
 
-WebGLTransformFeedback::WebGLTransformFeedback(WebGLContext* webgl, GLuint tf)
+WebGLTransformFeedback::WebGLTransformFeedback(WebGLContext* webgl,
+                                               GLuint tf)
     : WebGLContextBoundObject(webgl)
     , mGLName(tf)
-    , mIndexedBindings(webgl->mGLMaxTransformFeedbackSeparateAttribs)
+    , mMode(LOCAL_GL_NONE)
+    , mIsActive(false)
     , mIsPaused(false)
-    , mIsActive(false)
 {
     mContext->mTransformFeedbacks.insertBack(this);
 }
 
 WebGLTransformFeedback::~WebGLTransformFeedback()
 {
+    mMode = LOCAL_GL_NONE;
+    mIsActive = false;
+    mIsPaused = false;
     DeleteOnce();
 }
 
 void
 WebGLTransformFeedback::Delete()
 {
-    if (mGLName) {
-        mContext->MakeContextCurrent();
-        mContext->gl->fDeleteTransformFeedbacks(1, &mGLName);
-    }
+    mContext->MakeContextCurrent();
+    mContext->gl->fDeleteTransformFeedbacks(1, &mGLName);
     removeFrom(mContext->mTransformFeedbacks);
 }
 
-////////////////////////////////////////
-
-void
-WebGLTransformFeedback::BeginTransformFeedback(GLenum primMode)
+WebGLContext*
+WebGLTransformFeedback::GetParentObject() const
 {
-    const char funcName[] = "beginTransformFeedback";
-
-    if (mIsActive)
-        return mContext->ErrorInvalidOperation("%s: Already active.", funcName);
-
-    switch (primMode) {
-    case LOCAL_GL_POINTS:
-    case LOCAL_GL_LINES:
-    case LOCAL_GL_TRIANGLES:
-        break;
-    default:
-        mContext->ErrorInvalidEnum("%s: `primitiveMode` must be one of POINTS, LINES, or"
-                                   " TRIANGLES.",
-                                   funcName);
-        return;
-    }
-
-    const auto& prog = mContext->mCurrentProgram;
-    if (!prog ||
-        !prog->IsLinked() ||
-        !prog->LinkInfo()->componentsPerTFVert.size())
-    {
-        mContext->ErrorInvalidOperation("%s: Current program not valid for transform"
-                                        " feedback.",
-                                        funcName);
-        return;
-    }
-
-    const auto& linkInfo = prog->LinkInfo();
-    const auto& componentsPerTFVert = linkInfo->componentsPerTFVert;
-
-    size_t minVertCapacity = SIZE_MAX;
-    for (size_t i = 0; i < componentsPerTFVert.size(); i++) {
-        const auto& indexedBinding = mIndexedBindings[i];
-        const auto& componentsPerVert = componentsPerTFVert[i];
-
-        const auto& buffer = indexedBinding.mBufferBinding;
-        if (!buffer) {
-            mContext->ErrorInvalidOperation("%s: No buffer attached to required transform"
-                                            " feedback index %u.",
-                                            funcName, (uint32_t)i);
-            return;
-        }
-
-        const size_t vertCapacity = buffer->ByteLength() / 4 / componentsPerVert;
-        minVertCapacity = std::min(minVertCapacity, vertCapacity);
-    }
-
-    ////
-
-    const auto& gl = mContext->gl;
-    gl->MakeCurrent();
-    gl->fBeginTransformFeedback(primMode);
-
-    ////
-
-    mIsActive = true;
-    MOZ_ASSERT(!mIsPaused);
-
-    mActive_Program = prog;
-    mActive_PrimMode = primMode;
-    mActive_VertPosition = 0;
-    mActive_VertCapacity = minVertCapacity;
-
-    ////
-
-    for (const auto& cur : mIndexedBindings) {
-        const auto& buffer = cur.mBufferBinding;
-        if (buffer) {
-            buffer->mNumActiveTFOs++;
-        }
-    }
-
-    mActive_Program->mNumActiveTFOs++;
+    return mContext;
 }
 
-
-void
-WebGLTransformFeedback::EndTransformFeedback()
-{
-    const char funcName[] = "endTransformFeedback";
-
-    if (!mIsActive)
-        return mContext->ErrorInvalidOperation("%s: Not active.", funcName);
-
-    ////
-
-    const auto& gl = mContext->gl;
-    gl->MakeCurrent();
-    gl->fEndTransformFeedback();
-
-    ////
-
-    mIsActive = false;
-    mIsPaused = false;
-
-    ////
-
-    for (const auto& cur : mIndexedBindings) {
-        const auto& buffer = cur.mBufferBinding;
-        if (buffer) {
-            buffer->mNumActiveTFOs--;
-        }
-    }
-
-    mActive_Program->mNumActiveTFOs--;
-}
-
-void
-WebGLTransformFeedback::PauseTransformFeedback()
-{
-    const char funcName[] = "pauseTransformFeedback";
-
-    if (!mIsActive ||
-        mIsPaused)
-    {
-        mContext->ErrorInvalidOperation("%s: Not active or is paused.", funcName);
-        return;
-    }
-
-    ////
-
-    const auto& gl = mContext->gl;
-    gl->MakeCurrent();
-    gl->fPauseTransformFeedback();
-
-    ////
-
-    mIsPaused = true;
-}
-
-void
-WebGLTransformFeedback::ResumeTransformFeedback()
-{
-    const char funcName[] = "resumeTransformFeedback";
-
-    if (!mIsPaused)
-        return mContext->ErrorInvalidOperation("%s: Not paused.", funcName);
-
-    if (mContext->mCurrentProgram != mActive_Program) {
-        mContext->ErrorInvalidOperation("%s: Active program differs from original.",
-                                        funcName);
-        return;
-    }
-
-    ////
-
-    const auto& gl = mContext->gl;
-    gl->MakeCurrent();
-    gl->fResumeTransformFeedback();
-
-    ////
-
-    MOZ_ASSERT(mIsActive);
-    mIsPaused = false;
-}
-
-////////////////////////////////////////
-
 JSObject*
 WebGLTransformFeedback::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
     return dom::WebGLTransformFeedbackBinding::Wrap(cx, this, givenProto);
 }
 
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLTransformFeedback)
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLTransformFeedback, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLTransformFeedback, Release)
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLTransformFeedback,
-                                      mGenericBufferBinding,
-                                      mIndexedBindings,
-                                      mActive_Program)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLTransformFeedback.h
+++ b/dom/canvas/WebGLTransformFeedback.h
@@ -13,50 +13,34 @@
 namespace mozilla {
 
 class WebGLTransformFeedback final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLTransformFeedback>
     , public LinkedListElement<WebGLTransformFeedback>
     , public WebGLContextBoundObject
 {
-    friend class ScopedDrawWithTransformFeedback;
     friend class WebGLContext;
     friend class WebGL2Context;
-    friend class WebGLProgram;
-
-public:
-    const GLuint mGLName;
-private:
-    // GLES 3.0.4 p267, Table 6.24 "Transform Feedback State"
-    WebGLRefPtr<WebGLBuffer> mGenericBufferBinding;
-    std::vector<IndexedBufferBinding> mIndexedBindings;
-    bool mIsPaused;
-    bool mIsActive;
-    // Not in state tables:
-    WebGLRefPtr<WebGLProgram> mActive_Program;
-    GLenum mActive_PrimMode;
-    size_t mActive_VertPosition;
-    size_t mActive_VertCapacity;
 
 public:
     WebGLTransformFeedback(WebGLContext* webgl, GLuint tf);
-private:
-    ~WebGLTransformFeedback();
 
-public:
+    void Delete();
+    WebGLContext* GetParentObject() const;
+    virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
+
+    const GLuint mGLName;
+
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLTransformFeedback)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLTransformFeedback)
 
-    void Delete();
-    WebGLContext* GetParentObject() const { return mContext; }
-    virtual JSObject* WrapObject(JSContext*, JS::Handle<JSObject*>) override;
+private:
+    ~WebGLTransformFeedback();
 
-    // GL Funcs
-    void BeginTransformFeedback(GLenum primMode);
-    void EndTransformFeedback();
-    void PauseTransformFeedback();
-    void ResumeTransformFeedback();
+    GLenum mMode;
+    bool mIsActive;
+    bool mIsPaused;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_TRANSFORM_FEEDBACK_H_
--- a/dom/canvas/WebGLVertexAttribData.h
+++ b/dom/canvas/WebGLVertexAttribData.h
@@ -36,29 +36,36 @@ struct WebGLVertexAttribData
     GLenum type;
     bool enabled;
     bool normalized;
     bool integer;
 
     GLuint componentSize() const {
         switch(type) {
         case LOCAL_GL_BYTE:
+            return sizeof(GLbyte);
+
         case LOCAL_GL_UNSIGNED_BYTE:
-            return 1;
+            return sizeof(GLubyte);
 
         case LOCAL_GL_SHORT:
+            return sizeof(GLshort);
+
         case LOCAL_GL_UNSIGNED_SHORT:
-        case LOCAL_GL_HALF_FLOAT:
-        case LOCAL_GL_HALF_FLOAT_OES:
-            return 2;
+            return sizeof(GLushort);
 
         case LOCAL_GL_INT:
+            return sizeof(GLint);
+
         case LOCAL_GL_UNSIGNED_INT:
+            return sizeof(GLuint);
+
+        // case LOCAL_GL_FIXED:
         case LOCAL_GL_FLOAT:
-            return 4;
+            return sizeof(GLfloat);
 
         default:
             MOZ_ASSERT(false, "Should never get here!");
             return 0;
         }
     }
 
     GLuint actualStride() const {
--- a/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp
+++ b/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp
@@ -61,18 +61,24 @@ GLType()
     return 0;
   }
 }
 
 void
 CheckValidate(bool expectSuccess, mozilla::WebGLElementArrayCache& c, GLenum type,
               uint32_t maxAllowed, size_t first, size_t count)
 {
-  const bool success = c.Validate(type, maxAllowed, first, count);
+  uint32_t out_upperBound = 0;
+  const bool success = c.Validate(type, maxAllowed, first, count, &out_upperBound);
   VERIFY(success == expectSuccess);
+  if (success) {
+    VERIFY(out_upperBound <= maxAllowed);
+  } else {
+    VERIFY(out_upperBound > maxAllowed);
+  }
 }
 
 template<typename T>
 void
 CheckValidateOneTypeVariousBounds(mozilla::WebGLElementArrayCache& c, size_t firstByte,
                                   size_t countBytes)
 {
   size_t first = firstByte / sizeof(T);
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -66,16 +66,17 @@ UNIFIED_SOURCES += [
 SOURCES += [
     'ImageUtils.cpp',
 ]
 
 # WebGL Sources
 UNIFIED_SOURCES += [
     'TexUnpackBlob.cpp',
     'WebGL1Context.cpp',
+    'WebGL1ContextBuffers.cpp',
     'WebGL1ContextUniforms.cpp',
     'WebGL2Context.cpp',
     'WebGL2ContextBuffers.cpp',
     'WebGL2ContextDraw.cpp',
     'WebGL2ContextFramebuffers.cpp',
     'WebGL2ContextMRTs.cpp',
     'WebGL2ContextPrograms.cpp',
     'WebGL2ContextQueries.cpp',
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -6036,16 +6036,17 @@ fail-if = (os == 'win') || (os == 'mac')
 [generated/test_conformance__extensions__webgl-compressed-texture-size-limit.html]
 skip-if = (os == 'win')
 [generated/test_conformance__extensions__webgl-debug-renderer-info.html]
 [generated/test_conformance__extensions__webgl-debug-shaders.html]
 [generated/test_conformance__extensions__webgl-depth-texture.html]
 [generated/test_conformance__extensions__webgl-draw-buffers-max-draw-buffers.html]
 [generated/test_conformance__extensions__webgl-draw-buffers.html]
 skip-if = (os == 'linux')
+fail-if = (os == 'mac')
 [generated/test_conformance__extensions__webgl-shared-resources.html]
 [generated/test_conformance__glsl__bugs__angle-ambiguous-function-call.html]
 [generated/test_conformance__glsl__bugs__angle-constructor-invalid-parameters.html]
 [generated/test_conformance__glsl__bugs__angle-d3d11-compiler-error.html]
 [generated/test_conformance__glsl__bugs__angle-dx-variable-bug.html]
 [generated/test_conformance__glsl__bugs__array-of-struct-with-int-first-position.html]
 skip-if = (os == 'android')
 [generated/test_conformance__glsl__bugs__compare-loop-index-to-uniform.html]
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -85,16 +85,17 @@ skip-if = (os == 'win' && os_version != 
 # Complicated
 
 [generated/test_conformance__context__context-attributes-alpha-depth-stencil-antialias.html]
 fail-if = (os == 'mac' && os_version == '10.6')
 # Asserts on 'B2G ICS Emulator Debug' and linux debug. Crashes on Android.
 skip-if = (os == 'b2g') || (os == 'linux') || (os == 'android')
 
 [generated/test_conformance__extensions__webgl-draw-buffers.html]
+fail-if = (os == 'mac')
 # Crashes
 skip-if = (os == 'linux')
 
 [generated/test_conformance__glsl__constructors__glsl-construct-bvec3.html]
 # Crashes from libglsl.so
 # application crashed [@ jemalloc_crash] on Android
 skip-if = (os == 'linux') || (os == 'mac') || (os == 'android')
 [generated/test_conformance__glsl__constructors__glsl-construct-bvec4.html]