Bug 1300946 - Rewrite transform feedback. - r=jrmuizel a=ritu
☠☠ backed out by ca2593daaa91 ☠ ☠
authorJeff Gilbert <jgilbert@mozilla.com>
Wed, 07 Sep 2016 15:27:20 -0700
changeset 350510 428c52695888182fe1b40efc2fb00233098639e1
parent 350509 a9dc19ea4d04e04c64b1c53da7a073102d276b80
child 350511 c269b01e8c433545326369a45344ed674553d1b5
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)
reviewersjrmuizel, ritu
bugs1300946
milestone50.0
Bug 1300946 - Rewrite transform feedback. - r=jrmuizel a=ritu MozReview-Commit-ID: 866FkuUMMob
dom/canvas/WebGL1Context.h
dom/canvas/WebGL1ContextBuffers.cpp
dom/canvas/WebGL2Context.cpp
dom/canvas/WebGL2Context.h
dom/canvas/WebGL2ContextBuffers.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/WebGLContextGL.cpp
dom/canvas/WebGLContextUnchecked.cpp
dom/canvas/WebGLContextUnchecked.h
dom/canvas/WebGLContextValidate.cpp
dom/canvas/WebGLElementArrayCache.cpp
dom/canvas/WebGLElementArrayCache.h
dom/canvas/WebGLObjectModel.h
dom/canvas/WebGLProgram.cpp
dom/canvas/WebGLProgram.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
--- a/dom/canvas/WebGL1Context.h
+++ b/dom/canvas/WebGL1Context.h
@@ -31,18 +31,15 @@ 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_
deleted file mode 100644
--- a/dom/canvas/WebGL1ContextBuffers.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/* -*- 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,18 +144,17 @@ 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);
 
-    mBoundTransformFeedbackBuffers.SetLength(mGLMaxTransformFeedbackSeparateAttribs);
-    mBoundUniformBuffers.SetLength(mGLMaxUniformBufferBindings);
+    mIndexedUniformBufferBindings.resize(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
@@ -406,18 +406,15 @@ 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,230 +6,149 @@
 #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;
 
-    if (!ValidateBufferTarget(readTarget, funcName) ||
-        !ValidateBufferTarget(writeTarget, funcName))
+    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))
     {
         return;
     }
 
-    const WebGLRefPtr<WebGLBuffer>& readBufferSlot = GetBufferSlotByTarget(readTarget);
-    const WebGLRefPtr<WebGLBuffer>& writeBufferSlot = GetBufferSlotByTarget(writeTarget);
-    if (!readBufferSlot || !writeBufferSlot)
-        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 WebGLBuffer* readBuffer = readBufferSlot.get();
-    if (!readBuffer) {
-        ErrorInvalidOperation("%s: No buffer bound to readTarget.", funcName);
+    if (!fnValidateOffsetSize("read", readOffset, readBuffer) ||
+        !fnValidateOffsetSize("write", writeOffset, writeBuffer))
+    {
         return;
     }
 
-    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 &&
+    if (readBuffer == writeBuffer &&
         !ValidateDataRanges(readOffset, writeOffset, size, funcName))
     {
         return;
     }
 
-    WebGLBuffer::Kind readType = readBuffer->Content();
-    WebGLBuffer::Kind writeType = writeBuffer->Content();
-
-    if (readType != WebGLBuffer::Kind::Undefined &&
-        writeType != WebGLBuffer::Kind::Undefined &&
-        writeType != readType)
-    {
+    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) {
         ErrorInvalidOperation("%s: Can't copy %s data to %s data.",
                               funcName,
                               (readType == WebGLBuffer::Kind::OtherData) ? "other"
                                                                          : "element",
                               (writeType == WebGLBuffer::Kind::OtherData) ? "other"
                                                                           : "element");
         return;
     }
 
-    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);
-    }
+    gl->MakeCurrent();
+    gl->fCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size);
 }
 
 void
 WebGL2Context::GetBufferSubData(GLenum target, GLintptr offset,
                                 const dom::ArrayBufferView& data)
 {
     const char funcName[] = "getBufferSubData";
     if (IsContextLost())
         return;
 
-    // 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))
+    if (!ValidateNonNegative(funcName, "offset", offset))
         return;
 
-    // If offset is less than zero, an INVALID_VALUE error is
-    // generated.
-    if (offset < 0) {
-        ErrorInvalidValue("%s: Offset must be non-negative.", funcName);
+    const auto& buffer = ValidateBufferSelection(funcName, target);
+    if (!buffer)
         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();
 
-    CheckedInt<WebGLsizeiptr> neededByteLength = CheckedInt<WebGLsizeiptr>(offset) + data.LengthAllowShared();
+    const auto neededByteLength = CheckedInt<size_t>(offset) + data.LengthAllowShared();
     if (!neededByteLength.isValid()) {
         ErrorInvalidValue("%s: Integer overflow computing the needed byte length.",
                           funcName);
         return;
     }
 
-    if (neededByteLength.value() > boundBuffer->ByteLength()) {
+    if (neededByteLength.value() > buffer->ByteLength()) {
         ErrorInvalidValue("%s: Not enough data. Operation requires %d bytes, but buffer"
                           " only has %d bytes.",
-                          funcName, neededByteLength.value(), boundBuffer->ByteLength());
+                          funcName, neededByteLength.value(), buffer->ByteLength());
+        return;
+    }
+
+    ////
+
+    if (buffer->mNumActiveTFOs) {
+        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
+                              " object.",
+                              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 (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER &&
+        mBoundTransformFeedback->mIsActive)
+    {
+        ErrorInvalidOperation("%s: Currently bound transform feedback is active.",
+                              funcName);
+        return;
     }
 
-    /* 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);
+    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/WebGL2ContextState.cpp
+++ b/dom/canvas/WebGL2ContextState.cpp
@@ -28,24 +28,27 @@ 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_TRANSFORM_FEEDBACK_PAUSED:
-    case LOCAL_GL_TRANSFORM_FEEDBACK_ACTIVE: {
+    case LOCAL_GL_SAMPLE_COVERAGE: {
       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->ColorReadBuffer())
         return JS::Int32Value(LOCAL_GL_NONE);
 
@@ -144,17 +147,20 @@ 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:
-      return WebGLObjectAsJSValue(cx, mBoundTransformFeedbackBuffer.get(), rv);
+      {
+        const auto& tf = mBoundTransformFeedback;
+        return WebGLObjectAsJSValue(cx, tf->mGenericBufferBinding.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);
 
@@ -162,21 +168,24 @@ 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: {
-      WebGLTransformFeedback* tf =
-        (mBoundTransformFeedback != mDefaultTransformFeedback) ? mBoundTransformFeedback.get() : nullptr;
-      return WebGLObjectAsJSValue(cx, tf, rv);
-    }
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BINDING:
+      {
+        const WebGLTransformFeedback* tf = mBoundTransformFeedback;
+        if (tf == mDefaultTransformFeedback) {
+          tf = 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,38 +15,42 @@ namespace mozilla {
 // Transform Feedback
 
 already_AddRefed<WebGLTransformFeedback>
 WebGL2Context::CreateTransformFeedback()
 {
     if (IsContextLost())
         return nullptr;
 
+    MakeContextCurrent();
     GLuint tf = 0;
-    MakeContextCurrent();
     gl->fGenTransformFeedbacks(1, &tf);
 
-    RefPtr<WebGLTransformFeedback> globj = new WebGLTransformFeedback(this, tf);
-    return globj.forget();
+    RefPtr<WebGLTransformFeedback> ret = new WebGLTransformFeedback(this, tf);
+    return ret.forget();
 }
 
 void
 WebGL2Context::DeleteTransformFeedback(WebGLTransformFeedback* tf)
 {
+    const char funcName[] = "deleteTransformFeedback";
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowDeletedOrNull("deleteTransformFeedback", tf))
+    if (!ValidateObject(funcName, tf))
         return;
 
-    if (!tf || tf->IsDeleted())
+    if (tf->mIsActive) {
+        ErrorInvalidOperation("%s: Cannot delete active transform feedbacks.", funcName);
         return;
+    }
 
-    if (mBoundTransformFeedback == tf)
+    if (mBoundTransformFeedback == tf) {
         BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
+    }
 
     tf->RequestDelete();
 }
 
 bool
 WebGL2Context::IsTransformFeedback(WebGLTransformFeedback* tf)
 {
     if (IsContextLost())
@@ -60,139 +64,80 @@ 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("bindTransformFeedback: target must be TRANSFORM_FEEDBACK");
+        return ErrorInvalidEnum("%s: `target` must be TRANSFORM_FEEDBACK.", funcName);
 
-    WebGLRefPtr<WebGLTransformFeedback> currentTF = mBoundTransformFeedback;
-    if (currentTF && currentTF->mIsActive && !currentTF->mIsPaused) {
-        return ErrorInvalidOperation("bindTransformFeedback: Currently bound transform "
-                                     "feedback is active and not paused");
-    }
+    if (!ValidateObjectAllowDeletedOrNull(funcName, tf))
+        return;
 
     if (tf && tf->IsDeleted())
-        return ErrorInvalidOperation("bindTransformFeedback: Attempt to bind deleted id");
+        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);
 
     MakeContextCurrent();
-    gl->fBindTransformFeedback(target, tf ? tf->mGLName : 0);
-    if (tf)
-        mBoundTransformFeedback = tf;
-    else
-        mBoundTransformFeedback = mDefaultTransformFeedback;
+    gl->fBindTransformFeedback(target, mBoundTransformFeedback->mGLName);
 }
 
 void
-WebGL2Context::BeginTransformFeedback(GLenum primitiveMode)
+WebGL2Context::BeginTransformFeedback(GLenum primMode)
 {
     if (IsContextLost())
         return;
 
-    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;
+    mBoundTransformFeedback->BeginTransformFeedback(primMode);
 }
 
 void
 WebGL2Context::EndTransformFeedback()
 {
     if (IsContextLost())
         return;
 
-    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;
+    mBoundTransformFeedback->EndTransformFeedback();
 }
 
 void
 WebGL2Context::PauseTransformFeedback()
 {
     if (IsContextLost())
         return;
 
-    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;
+    mBoundTransformFeedback->PauseTransformFeedback();
 }
 
 void
 WebGL2Context::ResumeTransformFeedback()
 {
     if (IsContextLost())
         return;
 
-    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;
+    mBoundTransformFeedback->ResumeTransformFeedback();
 }
 
 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,67 +65,70 @@ 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;
 
-    GLint64 data = 0;
+    const std::vector<IndexedBufferBinding>* bindings;
+    switch (target) {
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_START:
+    case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_SIZE:
+        bindings = &(mBoundTransformFeedback->mIndexedBindings);
+        break;
 
-    MakeContextCurrent();
+    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);
+        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:
-        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();
+    case LOCAL_GL_UNIFORM_BUFFER_BINDING:
+        if (binding.mBufferBinding) {
+            retval.SetValue().SetAsWebGLBuffer() = binding.mBufferBinding;
         }
-        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;
+        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_START:
     case LOCAL_GL_UNIFORM_BUFFER_SIZE:
-        gl->fGetInteger64i_v(target, index, &data);
-        retval.SetValue().SetAsLongLong() = data;
-        return;
+        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,48 +12,50 @@
 
 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::BindTo(GLenum target)
+WebGLBuffer::SetContentAfterBind(GLenum target)
 {
+    if (mContent != Kind::Undefined)
+        return;
+
     switch (target) {
     case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
         mContent = Kind::ElementArray;
-        if (!mCache)
-            mCache = new WebGLElementArrayCache;
+        if (!mCache) {
+            mCache.reset(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:
-        if (mContent == Kind::Undefined) {
-          mContent = Kind::OtherData;
-        }
+        mContent = Kind::OtherData;
         break;
 
     default:
         MOZ_CRASH("GFX: invalid target");
     }
 }
 
 void
@@ -61,16 +63,100 @@ 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;
@@ -88,28 +174,89 @@ 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, uint32_t* const out_upperBound)
+WebGLBuffer::Validate(GLenum type, uint32_t maxAllowed, size_t first, size_t count) const
 {
-    return mCache->Validate(type, maxAllowed, first, count, out_upperBound);
+    return mCache->Validate(type, maxAllowed, first, count);
 }
 
 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,76 +3,83 @@
  * 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 "nsAutoPtr.h"
+#include "mozilla/UniquePtr.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:
-
     enum class Kind {
         Undefined,
         ElementArray,
         OtherData
     };
 
     WebGLBuffer(WebGLContext* webgl, GLuint buf);
 
-    void BindTo(GLenum target);
+    void SetContentAfterBind(GLenum target);
     Kind Content() const { return mContent; }
 
     void Delete();
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
-    WebGLsizeiptr ByteLength() const { return mByteLength; }
-    void SetByteLength(WebGLsizeiptr byteLength) { mByteLength = byteLength; }
+    size_t ByteLength() const { return mByteLength; }
 
     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,
-                  uint32_t* const out_upperBound);
+    bool Validate(GLenum type, uint32_t max_allowed, size_t first, size_t count) const;
 
     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;
-    WebGLsizeiptr mByteLength;
-    nsAutoPtr<WebGLElementArrayCache> mCache;
+    size_t mByteLength;
+    UniquePtr<WebGLElementArrayCache> mCache;
+    size_t mNumActiveTFOs;
+    bool mBoundForTF;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_BUFFER_H_
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -239,31 +239,29 @@ 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;
 
-    mBoundTransformFeedbackBuffers.Clear();
-    mBoundUniformBuffers.Clear();
+    mIndexedUniformBufferBindings.clear();
 
     //////
 
     ClearLinkedList(mBuffers);
     ClearLinkedList(mFramebuffers);
     ClearLinkedList(mPrograms);
     ClearLinkedList(mQueries);
     ClearLinkedList(mRenderbuffers);
@@ -2044,16 +2042,40 @@ 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);
@@ -2358,16 +2380,34 @@ 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,
@@ -2375,17 +2415,17 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(We
   mBound3DTextures,
   mBound2DArrayTextures,
   mBoundSamplers,
   mBoundArrayBuffer,
   mBoundCopyReadBuffer,
   mBoundCopyWriteBuffer,
   mBoundPixelPackBuffer,
   mBoundPixelUnpackBuffer,
-  mBoundTransformFeedbackBuffer,
+  mBoundTransformFeedback,
   mBoundUniformBuffer,
   mCurrentProgram,
   mBoundDrawFramebuffer,
   mBoundReadFramebuffer,
   mBoundRenderbuffer,
   mBoundVertexArray,
   mDefaultVertexArray,
   mActiveOcclusionQuery,
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -118,16 +118,17 @@ 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
 {
@@ -175,38 +176,54 @@ 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,
@@ -711,21 +728,16 @@ 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);
@@ -757,28 +769,24 @@ 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;
 
-    nsTArray<WebGLRefPtr<WebGLBuffer>> mBoundUniformBuffers;
-    nsTArray<WebGLRefPtr<WebGLBuffer>> mBoundTransformFeedbackBuffers;
+    std::vector<IndexedBufferBinding> mIndexedUniformBufferBindings;
 
     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;
 
@@ -1029,21 +1037,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(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 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 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,
@@ -1237,17 +1245,16 @@ 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);
@@ -1309,16 +1316,33 @@ protected:
                               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,
@@ -1354,30 +1378,19 @@ 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;
@@ -1817,11 +1830,21 @@ 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,240 +6,355 @@
 #include "WebGLContext.h"
 
 #include "GLContext.h"
 #include "WebGLBuffer.h"
 #include "WebGLVertexArray.h"
 
 namespace mozilla {
 
-void
-WebGLContext::UpdateBoundBuffer(GLenum target, WebGLBuffer* buffer)
+WebGLRefPtr<WebGLBuffer>*
+WebGLContext::ValidateBufferSlot(const char* funcName, GLenum target)
 {
-    WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target);
-    bufferSlot = 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;
 
-    if (!buffer)
-        return;
+        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;
 
-    buffer->BindTo(target);
+        case LOCAL_GL_UNIFORM_BUFFER:
+            slot = &mBoundUniformBuffer;
+            break;
+        }
+    }
+
+    if (!slot) {
+        ErrorInvalidEnum("%s: Bad `target`: 0x%04x", funcName, target);
+        return nullptr;
+    }
+
+    return slot;
 }
 
-void
-WebGLContext::UpdateBoundBufferIndexed(GLenum target, GLuint index, WebGLBuffer* buffer)
+WebGLBuffer*
+WebGLContext::ValidateBufferSelection(const char* funcName, GLenum target)
 {
-    UpdateBoundBuffer(target, buffer);
+    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();
+}
 
-    WebGLRefPtr<WebGLBuffer>& bufferIndexSlot =
-        GetBufferSlotByTargetIndexed(target, index);
-    bufferIndexSlot = buffer;
+IndexedBufferBinding*
+WebGLContext::ValidateIndexedBufferSlot(const char* funcName, GLenum target, GLuint index)
+{
+    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;
+
+    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];
 }
 
+////////////////////////////////////////
+
 void
 WebGLContext::BindBuffer(GLenum target, WebGLBuffer* buffer)
 {
+    const char funcName[] = "bindBuffer";
     if (IsContextLost())
         return;
 
-    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))
+    if (!ValidateObjectAllowDeletedOrNull(funcName, buffer))
         return;
 
     // silently ignore a deleted buffer
     if (buffer && buffer->IsDeleted())
         return;
 
-    // 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;
+    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);
+    }
+}
+
+////////////////////////////////////////
 
-    case LOCAL_GL_UNIFORM_BUFFER:
-        if (index >= mGLMaxUniformBufferBindings)
-            return ErrorInvalidValue("bindBufferBase: index should be less than "
-                                     "MAX_UNIFORM_BUFFER_BINDINGS");
-        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;
 
-    default:
-        return ErrorInvalidEnumInfo("bindBufferBase: target", target);
+    *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;
     }
 
-    if (!ValidateBufferForTarget(target, buffer, "bindBufferBase"))
-        return;
-
-    WebGLContextUnchecked::BindBufferBase(target, index, buffer);
-
-    UpdateBoundBufferIndexed(target, index, buffer);
+    return true;
 }
 
 void
-WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer,
-                              WebGLintptr offset, WebGLsizeiptr size)
+WebGLContext::BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer)
 {
+    const char funcName[] = "bindBufferBase";
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowDeletedOrNull("bindBufferRange", buffer))
+    if (!ValidateObjectAllowDeletedOrNull(funcName, buffer))
         return;
 
     // silently ignore a deleted buffer
     if (buffer && buffer->IsDeleted())
         return;
 
-    // ValidateBufferTarget
+    WebGLRefPtr<WebGLBuffer>* genericBinding;
+    IndexedBufferBinding* indexedBinding;
+    if (!ValidateIndexedBufferBinding(funcName, target, index, &genericBinding,
+                                      &indexedBinding))
+    {
+        return;
+    }
+
+    if (buffer && !buffer->ValidateCanBindToTarget(funcName, target))
+        return;
+
+    ////
+
+    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);
+    }
+}
+
+void
+WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer,
+                              WebGLintptr offset, WebGLsizeiptr size)
+{
+    const char funcName[] = "bindBufferRange";
+    if (IsContextLost())
+        return;
+
+    if (!ValidateObjectAllowDeletedOrNull(funcName, 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();
+
     switch (target) {
     case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
-        if (index >= mGLMaxTransformFeedbackSeparateAttribs)
-            return ErrorInvalidValue("bindBufferRange: index should be less than "
-                                     "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
+        if (offset % 4 != 0 || size % 4 != 0) {
+            ErrorInvalidValue("%s: For %s, `offset` and `size` must be multiples of 4.",
+                              funcName, "TRANSFORM_FEEDBACK_BUFFER");
+            return;
+        }
         break;
 
     case LOCAL_GL_UNIFORM_BUFFER:
-        if (index >= mGLMaxUniformBufferBindings)
-            return ErrorInvalidValue("bindBufferRange: index should be less than "
-                                     "MAX_UNIFORM_BUFFER_BINDINGS");
+        {
+            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;
+            }
+        }
         break;
-
-    default:
-        return ErrorInvalidEnumInfo("bindBufferRange: target", target);
     }
 
-    if (!ValidateBufferForTarget(target, buffer, "bindBufferRange"))
-        return;
+    ////
+
+#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);
 
-    WebGLContextUnchecked::BindBufferRange(target, index, buffer, offset, size);
+    ////
+
+    *genericBinding = buffer;
+    indexedBinding->mBufferBinding = buffer;
+    indexedBinding->mRangeStart = offset;
+    indexedBinding->mRangeSize = size;
 
-    UpdateBoundBufferIndexed(target, index, buffer);
+    if (buffer) {
+        buffer->SetContentAfterBind(target);
+    }
 }
 
+////////////////////////////////////////
+
 void
 WebGLContext::BufferData(GLenum target, WebGLsizeiptr size, GLenum usage)
 {
+    const char funcName[] = "bufferData";
     if (IsContextLost())
         return;
 
-    if (!ValidateBufferTarget(target, "bufferData"))
-        return;
-
-    WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target);
-
-    if (size < 0)
-        return ErrorInvalidValue("bufferData: negative size");
-
-    if (!ValidateBufferUsageEnum(usage, "bufferData: usage"))
+    if (!ValidateNonNegative(funcName, "size", size))
         return;
 
     // careful: WebGLsizeiptr is always 64-bit, but GLsizeiptr is like intptr_t.
     if (!CheckedInt<GLsizeiptr>(size).isValid())
-        return ErrorOutOfMemory("bufferData: bad size");
+        return ErrorOutOfMemory("%s: bad size", funcName);
 
-    WebGLBuffer* boundBuffer = bufferSlot.get();
+    const auto& buffer = ValidateBufferSelection(funcName, target);
+    if (!buffer)
+        return;
 
-    if (!boundBuffer)
-        return ErrorInvalidOperation("bufferData: no buffer bound!");
+    ////
 
     UniquePtr<uint8_t> zeroBuffer((uint8_t*)calloc(size, 1));
     if (!zeroBuffer)
-        return ErrorOutOfMemory("bufferData: out of memory");
-
-    MakeContextCurrent();
-    InvalidateBufferFetching();
-
-    GLenum error = CheckedBufferData(target, size, zeroBuffer.get(), usage);
+        return ErrorOutOfMemory("%s: Failed to allocate zeros.", funcName);
 
-    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");
-    }
+    buffer->BufferData(target, size_t(size), zeroBuffer.get(), usage);
 }
 
 // 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");
 
-    if (!ValidateBufferUsageEnum(usage, "bufferData: usage"))
+    const auto& buffer = ValidateBufferSelection(funcName, target);
+    if (!buffer)
         return;
 
-    WebGLBuffer* boundBuffer = bufferSlot.get();
-
-    if (!boundBuffer)
-        return ErrorInvalidOperation("bufferData: no buffer bound!");
-
-    MakeContextCurrent();
-    InvalidateBufferFetching();
-
     // Warning: Possibly shared memory.  See bug 1225033.
-    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");
-	}
+    buffer->BufferData(target, data.LengthAllowShared(), data.DataAllowShared(), usage);
 }
 
 void
 WebGLContext::BufferData(GLenum target,
                          const dom::SharedArrayBuffer& data,
                          GLenum usage)
 {
     BufferDataT(target, data, usage);
@@ -259,67 +374,73 @@ 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 (!ValidateBufferTarget(target, "bufferSubData"))
+    if (!ValidateNonNegative(funcName, "byteOffset", byteOffset))
+        return;
+
+    const auto& buffer = ValidateBufferSelection(funcName, target);
+    if (!buffer)
         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!");
+    if (buffer->mNumActiveTFOs) {
+        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
+                              " object.",
+                              "bufferSubData");
+        return;
+    }
 
     data.ComputeLengthAndData();
 
-    CheckedInt<WebGLsizeiptr> checked_neededByteLength =
-        CheckedInt<WebGLsizeiptr>(byteOffset) + data.LengthAllowShared();
+    const auto checked_neededByteLength =
+        CheckedInt<size_t>(byteOffset) + data.LengthAllowShared();
 
     if (!checked_neededByteLength.isValid()) {
         ErrorInvalidValue("bufferSubData: Integer overflow computing the needed"
                           " byte length.");
         return;
     }
 
-    if (checked_neededByteLength.value() > boundBuffer->ByteLength()) {
+    if (checked_neededByteLength.value() > buffer->ByteLength()) {
         ErrorInvalidValue("bufferSubData: Not enough data. Operation requires"
                           " %d bytes, but buffer only has %d bytes.",
                           checked_neededByteLength.value(),
-                          boundBuffer->ByteLength());
+                          buffer->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());
+    gl->fBufferSubData(target, byteOffset, data.LengthAllowShared(),
+                       data.DataAllowShared());
+
+    // Warning: Possibly shared memory.  See bug 1225033.
+    buffer->ElementArrayCacheBufferSubData(byteOffset, data.DataAllowShared(),
+                                           data.LengthAllowShared());
 }
 
 void
 WebGLContext::BufferSubData(GLenum target, WebGLsizeiptr byteOffset,
                             const dom::Nullable<dom::ArrayBuffer>& maybeData)
 {
     if (maybeData.IsNull()) {
         ErrorInvalidValue("BufferSubData: returnedData is null.");
@@ -337,16 +458,18 @@ 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();
@@ -363,70 +486,55 @@ 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;
-    }
+    ////
 
-    if (mBoundVertexArray->mElementArrayBuffer == buffer) {
-        WebGLContextUnchecked::BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, nullptr);
-        mBoundVertexArray->mElementArrayBuffer = nullptr;
-    }
+    const auto fnClearIfBuffer = [&](WebGLRefPtr<WebGLBuffer>& bindPoint) {
+        if (bindPoint == buffer) {
+            bindPoint = nullptr;
+        }
+    };
+
+    fnClearIfBuffer(mBoundArrayBuffer);
+    fnClearIfBuffer(mBoundVertexArray->mElementArrayBuffer);
 
     // WebGL binding points
     if (IsWebGL2()) {
-        if (mBoundCopyReadBuffer == buffer)
-            mBoundCopyReadBuffer = nullptr;
-
-        if (mBoundCopyWriteBuffer == buffer)
-            mBoundCopyWriteBuffer = nullptr;
-
-        if (mBoundPixelPackBuffer == buffer)
-            mBoundPixelPackBuffer = nullptr;
+        fnClearIfBuffer(mBoundCopyReadBuffer);
+        fnClearIfBuffer(mBoundCopyWriteBuffer);
+        fnClearIfBuffer(mBoundPixelPackBuffer);
+        fnClearIfBuffer(mBoundPixelUnpackBuffer);
+        fnClearIfBuffer(mBoundUniformBuffer);
+        fnClearIfBuffer(mBoundTransformFeedback->mGenericBufferBinding);
 
-        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;
+        if (!mBoundTransformFeedback->mIsActive) {
+            for (auto& binding : mBoundTransformFeedback->mIndexedBindings) {
+                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 (auto& binding : mIndexedUniformBufferBindings) {
+            fnClearIfBuffer(binding.mBufferBinding);
         }
     }
 
     for (int32_t i = 0; i < mGLMaxVertexAttribs; i++) {
-        if (mBoundVertexArray->HasAttrib(i) &&
-            mBoundVertexArray->mAttribs[i].buf == buffer)
-        {
-            mBoundVertexArray->mAttribs[i].buf = nullptr;
+        if (mBoundVertexArray->HasAttrib(i)) {
+            fnClearIfBuffer(mBoundVertexArray->mAttribs[i].buf);
         }
     }
 
+    ////
+
     buffer->RequestDelete();
 }
 
 bool
 WebGLContext::IsBuffer(WebGLBuffer* buffer)
 {
     if (IsContextLost())
         return false;
@@ -436,216 +544,9 @@ 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
@@ -245,151 +245,331 @@ WebGLContext::DrawInstanced_check(const 
         ErrorInvalidOperation("%s: at least one vertex attribute divisor should be 0", info);
         return false;
     }
 
     return true;
 }
 
 bool
-WebGLContext::DrawArrays_check(GLint first, GLsizei count, GLsizei primcount,
-                               const char* info)
+WebGLContext::DrawArrays_check(const char* funcName, GLenum mode, GLint first,
+                               GLsizei vertCount, GLsizei instanceCount)
 {
-    if (first < 0 || count < 0) {
-        ErrorInvalidValue("%s: negative first or count", info);
+    if (!ValidateDrawModeEnum(mode, funcName))
         return false;
-    }
 
-    if (primcount < 0) {
-        ErrorInvalidValue("%s: negative primcount", info);
+    if (!ValidateNonNegative(funcName, "first", first) ||
+        !ValidateNonNegative(funcName, "vertCount", vertCount) ||
+        !ValidateNonNegative(funcName, "instanceCount", instanceCount))
+    {
         return false;
     }
 
-    if (!ValidateStencilParamsForDrawCall()) {
+    if (!ValidateStencilParamsForDrawCall())
         return false;
-    }
 
-    // If count is 0, there's nothing to do.
-    if (count == 0 || primcount == 0) {
-        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 (!ValidateBufferFetching(info)) {
-        return false;
-    }
+    if (!vertCount || !instanceCount)
+        return false; // No error, just early out.
 
-    CheckedInt<GLsizei> checked_firstPlusCount = CheckedInt<GLsizei>(first) + count;
+    if (!ValidateBufferFetching(funcName))
+        return false;
 
+    const auto checked_firstPlusCount = CheckedInt<GLsizei>(first) + vertCount;
     if (!checked_firstPlusCount.isValid()) {
-        ErrorInvalidOperation("%s: overflow in first+count", info);
+        ErrorInvalidOperation("%s: overflow in first+vertCount", funcName);
         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", 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;
-    }
-
-    if (!DoFakeVertexAttrib0(checked_firstPlusCount.value())) {
+        ErrorInvalidOperation("%s: Bound vertex attribute buffers do not have sufficient"
+                              " size for given first and count.",
+                              funcName);
         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 count)
+WebGLContext::DrawArrays(GLenum mode, GLint first, GLsizei vertCount)
 {
     const char funcName[] = "drawArrays";
     if (IsContextLost())
         return;
 
-    if (!ValidateDrawModeEnum(mode, funcName))
-        return;
-
     MakeContextCurrent();
 
-    bool error;
+    bool error = false;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
-    if (!DrawArrays_check(first, count, 1, funcName))
+    const GLsizei instanceCount = 1;
+    if (!DrawArrays_check(funcName, mode, first, vertCount, instanceCount))
         return;
 
-    RunContextLossTimer();
+    const ScopedDrawHelper scopedHelper(this, funcName, first, vertCount, instanceCount, &error);
+    if (error)
+        return;
+
+    const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount,
+                                                   instanceCount, &error);
+    if (error)
+        return;
 
     {
         ScopedMaskWorkaround autoMask(*this);
-        gl->fDrawArrays(mode, first, count);
+        gl->fDrawArrays(mode, first, vertCount);
     }
 
     Draw_cleanup(funcName);
+    scopedTF.Advance();
 }
 
 void
-WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount)
+WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei vertCount,
+                                  GLsizei instanceCount)
 {
     const char funcName[] = "drawArraysInstanced";
     if (IsContextLost())
         return;
 
-    if (!ValidateDrawModeEnum(mode, funcName))
-        return;
-
     MakeContextCurrent();
 
-    bool error;
+    bool error = false;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
-    if (!DrawArrays_check(first, count, primcount, funcName))
+    if (!DrawArrays_check(funcName, mode, first, vertCount, instanceCount))
         return;
 
     if (!DrawInstanced_check(funcName))
         return;
 
-    RunContextLossTimer();
+    const ScopedDrawHelper scopedHelper(this, funcName, first, vertCount, instanceCount, &error);
+    if (error)
+        return;
+
+    const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount,
+                                                   instanceCount, &error);
+    if (error)
+        return;
 
     {
         ScopedMaskWorkaround autoMask(*this);
-        gl->fDrawArraysInstanced(mode, first, count, primcount);
+        gl->fDrawArraysInstanced(mode, first, vertCount, instanceCount);
     }
 
     Draw_cleanup(funcName);
+    scopedTF.Advance();
 }
 
+////////////////////////////////////////
+
 bool
-WebGLContext::DrawElements_check(GLsizei count, GLenum type,
-                                 WebGLintptr byteOffset, GLsizei primcount,
-                                 const char* info, GLuint* out_upperBound)
+WebGLContext::DrawElements_check(const char* funcName, GLenum mode, GLsizei vertCount,
+                                 GLenum type, WebGLintptr byteOffset,
+                                 GLsizei instanceCount)
 {
-    if (count < 0 || byteOffset < 0) {
-        ErrorInvalidValue("%s: negative count or offset", info);
+    if (!ValidateDrawModeEnum(mode, funcName))
+        return false;
+
+    if (mBoundTransformFeedback &&
+        mBoundTransformFeedback->mIsActive &&
+        !mBoundTransformFeedback->mIsPaused)
+    {
+        ErrorInvalidOperation("%s: DrawElements* functions are incompatible with"
+                              " transform feedback.",
+                              funcName);
         return false;
     }
 
-    if (primcount < 0) {
-        ErrorInvalidValue("%s: negative primcount", info);
+    if (!ValidateNonNegative(funcName, "vertCount", vertCount) ||
+        !ValidateNonNegative(funcName, "byteOffset", byteOffset) ||
+        !ValidateNonNegative(funcName, "instanceCount", instanceCount))
+    {
         return false;
     }
 
-    if (!ValidateStencilParamsForDrawCall()) {
+    if (!ValidateStencilParamsForDrawCall())
         return false;
-    }
 
-    // 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.
 
     uint8_t bytesPerElem = 0;
     switch (type) {
     case LOCAL_GL_UNSIGNED_BYTE:
         bytesPerElem = 1;
         break;
 
     case LOCAL_GL_UNSIGNED_SHORT:
@@ -399,170 +579,157 @@ WebGLContext::DrawElements_check(GLsizei
     case LOCAL_GL_UNSIGNED_INT:
         if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
             bytesPerElem = 4;
         }
         break;
     }
 
     if (!bytesPerElem) {
-        ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", info, type);
+        ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", funcName, type);
         return false;
     }
 
     if (byteOffset % bytesPerElem != 0) {
         ErrorInvalidOperation("%s: `byteOffset` must be a multiple of the size of `type`",
-                              info);
+                              funcName);
         return false;
     }
 
     const GLsizei first = byteOffset / bytesPerElem;
-    const CheckedUint32 checked_byteCount = bytesPerElem * CheckedUint32(count);
+    const CheckedUint32 checked_byteCount = bytesPerElem * CheckedUint32(vertCount);
 
     if (!checked_byteCount.isValid()) {
-        ErrorInvalidValue("%s: overflow in byteCount", info);
+        ErrorInvalidValue("%s: Overflow in byteCount.", funcName);
         return false;
     }
 
     if (!mBoundVertexArray->mElementArrayBuffer) {
-        ErrorInvalidOperation("%s: must have element array buffer binding", info);
+        ErrorInvalidOperation("%s: Must have element array buffer binding.", funcName);
         return false;
     }
 
     WebGLBuffer& elemArrayBuffer = *mBoundVertexArray->mElementArrayBuffer;
 
     if (!elemArrayBuffer.ByteLength()) {
-        ErrorInvalidOperation("%s: bound element array buffer doesn't have any data", info);
+        ErrorInvalidOperation("%s: Bound element array buffer doesn't have any data.",
+                              funcName);
         return false;
     }
 
     CheckedInt<GLsizei> checked_neededByteCount = checked_byteCount.toChecked<GLsizei>() + byteOffset;
 
     if (!checked_neededByteCount.isValid()) {
-        ErrorInvalidOperation("%s: overflow in byteOffset+byteCount", info);
+        ErrorInvalidOperation("%s: Overflow in byteOffset+byteCount.", funcName);
         return false;
     }
 
     if (uint32_t(checked_neededByteCount.value()) > elemArrayBuffer.ByteLength()) {
-        ErrorInvalidOperation("%s: bound element array buffer is too small for given count and offset", info);
+        ErrorInvalidOperation("%s: Bound element array buffer is too small for given"
+                              " count and offset.",
+                              funcName);
         return false;
     }
 
-    if (!ValidateBufferFetching(info))
+    if (!ValidateBufferFetching(funcName))
         return false;
 
     if (!mMaxFetchedVertices ||
-        !elemArrayBuffer.Validate(type, mMaxFetchedVertices - 1, first, count, out_upperBound))
+        !elemArrayBuffer.Validate(type, mMaxFetchedVertices - 1, first, vertCount))
     {
-        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);
+        ErrorInvalidOperation("%s: bound vertex attribute buffers do not have sufficient "
+                              "size for given indices from the bound element array",
+                              funcName);
         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.",
-                        info,
-                        WebGLContext::EnumName(type));
+                        funcName, WebGLContext::EnumName(type));
     }
 
-    if (!DoFakeVertexAttrib0(mMaxFetchedVertices))
-        return false;
-
     return true;
 }
 
 void
-WebGLContext::DrawElements(GLenum mode, GLsizei count, GLenum type,
+WebGLContext::DrawElements(GLenum mode, GLsizei vertCount, GLenum type,
                            WebGLintptr byteOffset)
 {
     const char funcName[] = "drawElements";
     if (IsContextLost())
         return;
 
-    if (!ValidateDrawModeEnum(mode, funcName))
-        return;
-
     MakeContextCurrent();
 
-    bool error;
+    bool error = false;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
-    GLuint upperBound = 0;
-    if (!DrawElements_check(count, type, byteOffset, 1, funcName, &upperBound))
+    const GLsizei instanceCount = 1;
+    if (!DrawElements_check(funcName, mode, vertCount, type, byteOffset, instanceCount))
         return;
 
-    RunContextLossTimer();
+    const ScopedDrawHelper scopedHelper(this, funcName, 0, mMaxFetchedVertices, instanceCount,
+                                        &error);
+    if (error)
+        return;
 
     {
         ScopedMaskWorkaround autoMask(*this);
-
-        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));
-        }
+        gl->fDrawElements(mode, vertCount, type,
+                          reinterpret_cast<GLvoid*>(byteOffset));
     }
 
     Draw_cleanup(funcName);
 }
 
 void
-WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type,
-                                    WebGLintptr byteOffset, GLsizei primcount)
+WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei vertCount, GLenum type,
+                                    WebGLintptr byteOffset, GLsizei instanceCount)
 {
     const char funcName[] = "drawElementsInstanced";
     if (IsContextLost())
         return;
 
-    if (!ValidateDrawModeEnum(mode, funcName))
-        return;
-
     MakeContextCurrent();
 
-    bool error;
+    bool error = false;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
-    GLuint upperBound = 0;
-    if (!DrawElements_check(count, type, byteOffset, primcount, funcName, &upperBound))
+    if (!DrawElements_check(funcName, mode, vertCount, type, byteOffset, instanceCount))
         return;
 
     if (!DrawInstanced_check(funcName))
         return;
 
-    RunContextLossTimer();
+    const ScopedDrawHelper scopedHelper(this, funcName, 0, mMaxFetchedVertices, instanceCount,
+                                        &error);
+    if (error)
+        return;
 
     {
         ScopedMaskWorkaround autoMask(*this);
-        gl->fDrawElementsInstanced(mode, count, type,
+        gl->fDrawElementsInstanced(mode, vertCount, type,
                                    reinterpret_cast<GLvoid*>(byteOffset),
-                                   primcount);
+                                   instanceCount);
     }
 
     Draw_cleanup(funcName);
 }
 
+////////////////////////////////////////
+
 void
 WebGLContext::Draw_cleanup(const char* funcName)
 {
-    UndoFakeVertexAttrib0();
-
     if (!mBoundDrawFramebuffer) {
         Invalidate();
         mShouldPresent = true;
         MOZ_ASSERT(!mBackbufferNeedsClear);
     }
 
     if (gl->WorkAroundDriverBugs()) {
         if (gl->Renderer() == gl::GLRenderer::Tegra) {
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -621,25 +621,20 @@ WebGLContext::GetAttribLocation(WebGLPro
 }
 
 JS::Value
 WebGLContext::GetBufferParameter(GLenum target, GLenum pname)
 {
     if (IsContextLost())
         return JS::NullValue();
 
-    if (!ValidateBufferTarget(target, "getBufferParameter"))
+    const auto& buffer = ValidateBufferSelection("getBufferParameter", target);
+    if (!buffer)
         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);
@@ -1494,16 +1489,23 @@ 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,70 +2,25 @@
 /* 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,23 +16,16 @@ 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,46 +117,21 @@ 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 with
- * ValidateDataOffsetSize().
+ * It is assumed that offset and size have already been validated.
  */
 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);
@@ -733,17 +708,16 @@ 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();
 
@@ -975,17 +949,16 @@ 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,26 +10,16 @@
 #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.
  *
@@ -240,53 +230,49 @@ 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,
-                  uint32_t* const out_upperBound)
+    bool Validate(T maxAllowed, size_t firstLeaf, size_t lastLeaf)
     {
         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
@@ -509,28 +495,23 @@ 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,
-                                 uint32_t* const out_upperBound)
+                                 size_t countElements)
 {
-    *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) {
-        UpdateUpperBound(out_upperBound, maxTSize);
+    if (maxAllowed >= 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)
@@ -551,70 +532,62 @@ 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) {
-        UpdateUpperBound(out_upperBound, globalMax);
+    if (globalMax <= maxAllowedT)
         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), out_upperBound);
+                          tree->LeafForElement(lastElement));
 }
 
 bool
 WebGLElementArrayCache::Validate(GLenum type, uint32_t maxAllowed,
-                                 size_t firstElement, size_t countElements,
-                                 uint32_t* const out_upperBound)
+                                 size_t firstElement, size_t countElements)
 {
     if (type == LOCAL_GL_UNSIGNED_BYTE)
-        return Validate<uint8_t>(maxAllowed, firstElement, countElements,
-                                 out_upperBound);
+        return Validate<uint8_t>(maxAllowed, firstElement, countElements);
     if (type == LOCAL_GL_UNSIGNED_SHORT)
-        return Validate<uint16_t>(maxAllowed, firstElement, countElements,
-                                  out_upperBound);
+        return Validate<uint16_t>(maxAllowed, firstElement, countElements);
     if (type == LOCAL_GL_UNSIGNED_INT)
-        return Validate<uint32_t>(maxAllowed, firstElement, countElements,
-                                  out_upperBound);
+        return Validate<uint32_t>(maxAllowed, firstElement, countElements);
 
     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,18 +32,17 @@ 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,
-                  uint32_t* const out_upperBound);
+    bool Validate(GLenum type, uint32_t maxAllowed, size_t first, size_t count);
 
     template<typename T>
     T Element(size_t i) const { return Elements<T>()[i]; }
 
     WebGLElementArrayCache();
     ~WebGLElementArrayCache();
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
@@ -69,18 +68,17 @@ 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,
-                  uint32_t* const out_upperBound);
+    bool Validate(uint32_t maxAllowed, size_t first, size_t count);
 
     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/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,
-                            mozilla::WebGLRefPtr<T>& field,
+                            const 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,16 +9,17 @@
 #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"
@@ -322,25 +323,33 @@ 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
 
-            const auto* block = new webgl::UniformBlockInfo(baseUserName, baseMappedName);
+            auto* block = new webgl::UniformBlockInfo(webgl, baseUserName, baseMappedName,
+                                                      dataSize);
             info->uniformBlocks.push_back(block);
         }
     }
 
     // Transform feedback varyings
 
     if (gl->IsSupported(gl::GLFeature::transform_feedback2)) {
         GLuint numTransformFeedbackVaryings = 0;
@@ -427,17 +436,18 @@ CreateProgram(gl::GLContext* gl)
 {
     gl->MakeCurrent();
     return gl->fCreateProgram();
 }
 
 WebGLProgram::WebGLProgram(WebGLContext* webgl)
     : WebGLContextBoundObject(webgl)
     , mGLName(CreateProgram(webgl->GL()))
-    , mTransformFeedbackBufferMode(LOCAL_GL_NONE)
+    , mNumActiveTFOs(0)
+    , mNextLink_TransformFeedbackBufferMode(LOCAL_GL_SEPARATE_ATTRIBS)
 {
     mContext->mPrograms.insertBack(this);
 }
 
 WebGLProgram::~WebGLProgram()
 {
     DeleteOnce();
 }
@@ -508,17 +518,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 = mBoundAttribLocs.insert(std::pair<nsCString, GLuint>(asciiName, loc));
+    auto res = mNextLink_BoundAttribLocs.insert({asciiName, loc});
 
     const bool wasInserted = res.second;
     if (!wasInserted) {
         auto itr = res.first;
         itr->second = loc;
     }
 }
 
@@ -666,21 +676,23 @@ 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(mTransformFeedbackVaryings.size());
+            return JS::Int32Value(mNextLink_TransformFeedbackVaryings.size());
+
+        case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
+            return JS::Int32Value(mNextLink_TransformFeedbackBufferMode);
        }
     }
 
     switch (pname) {
     case LOCAL_GL_ATTACHED_SHADERS:
     case LOCAL_GL_ACTIVE_UNIFORMS:
     case LOCAL_GL_ACTIVE_ATTRIBUTES:
         return JS::Int32Value(GetProgramiv(gl, mGLName, pname));
@@ -927,106 +939,143 @@ 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("getActiveUniformBlockName: `program` must be linked.");
+        mContext->ErrorInvalidOperation("%s: `program` must be linked.", funcName);
         return;
     }
 
-    const webgl::LinkedProgramInfo* linkInfo = LinkInfo();
-    if (uniformBlockIndex >= linkInfo->uniformBlocks.size()) {
-        mContext->ErrorInvalidValue("getActiveUniformBlockName: index %u invalid.", uniformBlockIndex);
+    const auto& uniformBlocks = LinkInfo()->uniformBlocks;
+    if (uniformBlockIndex >= uniformBlocks.size()) {
+        mContext->ErrorInvalidValue("%s: Index %u invalid.", funcName, uniformBlockIndex);
         return;
     }
+    const auto& uniformBlock = uniformBlocks[uniformBlockIndex];
 
-    if (uniformBlockBinding > mContext->mGLMaxUniformBufferBindings) {
-        mContext->ErrorInvalidEnum("getActiveUniformBlockName: binding %u invalid.", uniformBlockBinding);
+    const auto& indexedBindings = mContext->mIndexedUniformBufferBindings;
+    if (uniformBlockBinding >= indexedBindings.size()) {
+        mContext->ErrorInvalidValue("%s: Binding %u invalid.", funcName,
+                                    uniformBlockBinding);
         return;
     }
+    const auto& indexedBinding = indexedBindings[uniformBlockBinding];
+
+    ////
 
     gl::GLContext* gl = mContext->GL();
     gl->MakeCurrent();
     gl->fUniformBlockBinding(mGLName, uniformBlockIndex, uniformBlockBinding);
+
+    ////
+
+    uniformBlock->mBinding = &indexedBinding;
 }
 
-void
-WebGLProgram::LinkProgram()
+bool
+WebGLProgram::ValidateForLink()
 {
-    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.");
-        mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
-        return;
+        return false;
     }
 
     if (!mFragShader || !mFragShader->IsCompiled()) {
         mLinkLog.AssignLiteral("Must have an compiled fragment shader attached.");
-        mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
-        return;
+        return false;
     }
 
-    if (!mFragShader->CanLinkTo(mVertShader, &mLinkLog)) {
-        mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
-        return;
-    }
+    if (!mFragShader->CanLinkTo(mVertShader, &mLinkLog))
+        return false;
 
-    gl::GLContext* gl = mContext->gl;
-    gl->MakeCurrent();
+    const auto& gl = mContext->gl;
 
     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.");
-            mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
-            return;
+            return false;
         }
 
         // 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.");
-            mContext->GenerateWarning("linkProgram: %s", mLinkLog.BeginReading());
-            return;
+            mLinkLog.AssignLiteral("Number of attributes exceeds Mesa's reported max"
+                                   " attribute count.");
+            return false;
         }
     }
 
+    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 (auto itr = mBoundAttribLocs.begin(); itr != mBoundAttribLocs.end(); ++itr) {
-        const nsCString& name = itr->first;
-        GLuint index = itr->second;
+    for (const auto& pair : mNextLink_BoundAttribLocs) {
+        const auto& name = pair.first;
+        const auto& index = pair.second;
 
         mVertShader->BindAttribLocation(mGLName, name, index);
     }
 
-    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);
+    // 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);
     }
 
     LinkAndUpdate();
 
     if (mMostRecentLinkInfo) {
         nsCString postLinkLog;
         if (ValidateAfterTentativeLink(&postLinkLog))
             return;
@@ -1071,20 +1120,73 @@ 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) {
@@ -1114,25 +1216,117 @@ 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("useProgram: Program has not been successfully"
-                                        " linked.");
+        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);
         return false;
     }
 
     mContext->MakeContextCurrent();
 
     mContext->InvalidateBufferFetching();
 
     mContext->gl->fUseProgram(mGLName);
@@ -1174,21 +1368,16 @@ 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.");
 }
@@ -1228,52 +1417,53 @@ WebGLProgram::FindUniformByMappedName(co
 
     return false;
 }
 
 void
 WebGLProgram::TransformFeedbackVaryings(const dom::Sequence<nsString>& varyings,
                                         GLenum 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));
+    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);
         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;
-    }
+    ////
 
-    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);
+    mNextLink_TransformFeedbackVaryings.assign(varyings.Elements(),
+                                               varyings.Elements() + varyings.Length());
+    mNextLink_TransformFeedbackBufferMode = bufferMode;
 }
 
 already_AddRefed<WebGLActiveInfo>
-WebGLProgram::GetTransformFeedbackVarying(GLuint index)
+WebGLProgram::GetTransformFeedbackVarying(GLuint index) const
 {
     // 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,43 +56,52 @@ protected:
 public:
     explicit UniformInfo(WebGLActiveInfo* activeInfo);
 };
 
 struct UniformBlockInfo final
 {
     const nsCString mBaseUserName;
     const nsCString mBaseMappedName;
+    const uint32_t mDataSize;
 
-    UniformBlockInfo(const nsACString& baseUserName,
-                     const nsACString& baseMappedName)
+    const IndexedBufferBinding* mBinding;
+
+    UniformBlockInfo(WebGLContext* webgl, const nsACString& baseUserName,
+                     const nsACString& baseMappedName, uint32_t dataSize)
         : 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<const UniformBlockInfo*> uniformBlocks; // Owns its contents.
+    std::vector<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();
 
@@ -118,16 +127,18 @@ 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();
 
@@ -169,17 +180,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);
+    already_AddRefed<WebGLActiveInfo> GetTransformFeedbackVarying(GLuint index) const;
 
     void EnumerateFragOutputs(std::map<nsCString, const nsCString> &out_FragOutputs) const;
 
     bool IsLinked() const { return mMostRecentLinkInfo; }
 
     const webgl::LinkedProgramInfo* LinkInfo() const {
         return mMostRecentLinkInfo.get();
     }
@@ -189,29 +200,31 @@ 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;
-    std::map<nsCString, GLuint> mBoundAttribLocs;
-    std::vector<nsCString> mTransformFeedbackVaryings;
-    GLenum mTransformFeedbackBufferMode;
+    size_t mNumActiveTFOs;
+
+    std::map<nsCString, GLuint> mNextLink_BoundAttribLocs;
+
+    std::vector<nsString> mNextLink_TransformFeedbackVaryings;
+    GLenum mNextLink_TransformFeedbackBufferMode;
+
     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/WebGLShader.cpp
+++ b/dom/canvas/WebGLShader.cpp
@@ -403,50 +403,34 @@ WebGLShader::EnumerateFragOutputs(std::m
 
     if (!mValidator) {
         return;
     }
     mValidator->EnumerateFragOutputs(out_FragOutputs);
 }
 
 void
-WebGLShader::ApplyTransformFeedbackVaryings(GLuint prog,
-                                            const std::vector<nsCString>& varyings,
-                                            GLenum bufferMode,
-                                            std::vector<std::string>* out_mappedVaryings) const
+WebGLShader::MapTransformFeedbackVaryings(const std::vector<nsString>& varyings,
+                                          std::vector<std::string>* out_mappedVaryings) const
 {
     MOZ_ASSERT(mType == LOCAL_GL_VERTEX_SHADER);
-    MOZ_ASSERT(!varyings.empty());
     MOZ_ASSERT(out_mappedVaryings);
 
-    const size_t varyingsCount = varyings.size();
-    std::vector<std::string> mappedVaryings;
+    out_mappedVaryings->clear();
+    out_mappedVaryings->reserve(varyings.size());
 
-    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);
-
-        mappedVaryings.push_back(*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);
     }
-
-    // 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,17 +47,16 @@ 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;
@@ -66,21 +65,22 @@ public:
                                       bool* const out_isArray) const;
 
     void EnumerateFragOutputs(std::map<nsCString, const nsCString> &out_FragOutputs) const;
 
     bool IsCompiled() const {
         return mTranslationSuccessful && mCompilationSuccessful;
     }
 
-    void ApplyTransformFeedbackVaryings(GLuint prog,
-                                        const std::vector<nsCString>& varyings,
-                                        GLenum bufferMode,
-                                        std::vector<std::string>* out_mappedVaryings) const;
+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;
 
+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,14 +59,18 @@ 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,16 +14,17 @@
 #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:
@@ -208,16 +209,25 @@ 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;
@@ -310,17 +320,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 >= offset) {
+    if (bufferByteCount >= uint64_t(offset)) {
         byteCount = bufferByteCount - offset;
     }
 
     if (!ValidateUnpackBytes(mContext, funcName, width, height, depth, pi, byteCount,
                              &blob))
     {
         return;
     }
--- a/dom/canvas/WebGLTransformFeedback.cpp
+++ b/dom/canvas/WebGLTransformFeedback.cpp
@@ -6,53 +6,211 @@
 #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)
-    , mMode(LOCAL_GL_NONE)
+    , mIndexedBindings(webgl->mGLMaxTransformFeedbackSeparateAttribs)
+    , mIsPaused(false)
     , mIsActive(false)
-    , mIsPaused(false)
 {
     mContext->mTransformFeedbacks.insertBack(this);
 }
 
 WebGLTransformFeedback::~WebGLTransformFeedback()
 {
-    mMode = LOCAL_GL_NONE;
-    mIsActive = false;
-    mIsPaused = false;
     DeleteOnce();
 }
 
 void
 WebGLTransformFeedback::Delete()
 {
-    mContext->MakeContextCurrent();
-    mContext->gl->fDeleteTransformFeedbacks(1, &mGLName);
+    if (mGLName) {
+        mContext->MakeContextCurrent();
+        mContext->gl->fDeleteTransformFeedbacks(1, &mGLName);
+    }
     removeFrom(mContext->mTransformFeedbacks);
 }
 
-WebGLContext*
-WebGLTransformFeedback::GetParentObject() const
+////////////////////////////////////////
+
+void
+WebGLTransformFeedback::BeginTransformFeedback(GLenum primMode)
 {
-    return mContext;
+    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++;
 }
 
+
+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,34 +13,50 @@
 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();
 
-    void Delete();
-    WebGLContext* GetParentObject() const;
-    virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
-
-    const GLuint mGLName;
-
+public:
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLTransformFeedback)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLTransformFeedback)
 
-private:
-    ~WebGLTransformFeedback();
+    void Delete();
+    WebGLContext* GetParentObject() const { return mContext; }
+    virtual JSObject* WrapObject(JSContext*, JS::Handle<JSObject*>) override;
 
-    GLenum mMode;
-    bool mIsActive;
-    bool mIsPaused;
+    // GL Funcs
+    void BeginTransformFeedback(GLenum primMode);
+    void EndTransformFeedback();
+    void PauseTransformFeedback();
+    void ResumeTransformFeedback();
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_TRANSFORM_FEEDBACK_H_
--- a/dom/canvas/WebGLVertexAttribData.h
+++ b/dom/canvas/WebGLVertexAttribData.h
@@ -36,36 +36,29 @@ 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 sizeof(GLubyte);
+            return 1;
 
         case LOCAL_GL_SHORT:
-            return sizeof(GLshort);
-
         case LOCAL_GL_UNSIGNED_SHORT:
-            return sizeof(GLushort);
+        case LOCAL_GL_HALF_FLOAT:
+        case LOCAL_GL_HALF_FLOAT_OES:
+            return 2;
 
         case LOCAL_GL_INT:
-            return sizeof(GLint);
-
         case LOCAL_GL_UNSIGNED_INT:
-            return sizeof(GLuint);
-
-        // case LOCAL_GL_FIXED:
         case LOCAL_GL_FLOAT:
-            return sizeof(GLfloat);
+            return 4;
 
         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,24 +61,18 @@ GLType()
     return 0;
   }
 }
 
 void
 CheckValidate(bool expectSuccess, mozilla::WebGLElementArrayCache& c, GLenum type,
               uint32_t maxAllowed, size_t first, size_t count)
 {
-  uint32_t out_upperBound = 0;
-  const bool success = c.Validate(type, maxAllowed, first, count, &out_upperBound);
+  const bool success = c.Validate(type, maxAllowed, first, count);
   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,17 +66,16 @@ 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',