1.0.4 parity draft
authorJeff Gilbert <jdashg@gmail.com>
Thu, 17 Dec 2015 16:16:52 -0800
changeset 316084 4e4963104c3eff60281b8166caae4d51cd156445
parent 316083 04cc2f3be0b7c720c273b0ada76a868a087b2051
child 316085 ceeba974fa1905d5bae78b425ed799d59148d805
push id8514
push userjgilbert@mozilla.com
push dateFri, 18 Dec 2015 00:24:33 +0000
bugs1
milestone45.0a1
1.0.4 parity
dom/canvas/TexUnpackBlob.cpp
dom/canvas/WebGL2ContextFramebuffers.cpp
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextDraw.cpp
dom/canvas/WebGLContextFramebufferOperations.cpp
dom/canvas/WebGLContextGL.cpp
dom/canvas/WebGLContextState.cpp
dom/canvas/WebGLContextUtils.cpp
dom/canvas/WebGLContextValidate.cpp
dom/canvas/WebGLExtensionColorBufferFloat.cpp
dom/canvas/WebGLExtensionDrawBuffers.cpp
dom/canvas/WebGLExtensionSRGB.cpp
dom/canvas/WebGLExtensions.h
dom/canvas/WebGLFormats.cpp
dom/canvas/WebGLFormats.h
dom/canvas/WebGLFramebuffer.cpp
dom/canvas/WebGLFramebuffer.h
dom/canvas/WebGLRenderbuffer.cpp
dom/canvas/WebGLRenderbuffer.h
dom/canvas/WebGLStrongTypes.h
dom/canvas/WebGLTexture.h
dom/canvas/WebGLTextureUpload.cpp
dom/canvas/test/webgl-conformance/conformance/resources/webgl-test-utils.js
gfx/gl/ScopedGLHelpers.cpp
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -18,120 +18,48 @@
 namespace mozilla {
 namespace webgl {
 
 static GLenum
 DoTexOrSubImage(bool isSubImage, gl::GLContext* gl, TexImageTarget target, GLint level,
                 const DriverUnpackInfo* dui, GLint xOffset, GLint yOffset, GLint zOffset,
                 GLsizei width, GLsizei height, GLsizei depth, const void* data)
 {
-    auto internalFormat = dui->internalFormat;
-    auto unpackFormat = dui->unpackFormat;
-    auto unpackType = dui->unpackType;
-
     if (isSubImage) {
         return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width, height,
-                             depth, unpackFormat, unpackType, data);
+                             depth, dui->ToPacking(), data);
     } else {
-        return DoTexImage(gl, target, level, internalFormat, width, height, depth,
-                          unpackFormat, unpackType, data);
+        return DoTexImage(gl, target, level, dui, width, height, depth, data);
     }
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 // TexUnpackBuffer
 
-static bool
-GetPackedSizeForUnpack(uint32_t bytesPerPixel, uint8_t rowByteAlignment,
-                       uint32_t maybeStridePixelsPerRow, uint32_t maybeStrideRowsPerImage,
-                       uint32_t skipPixelsPerRow, uint32_t skipRowsPerImage,
-                       uint32_t skipImages, uint32_t usedPixelsPerRow,
-                       uint32_t usedRowsPerImage, uint32_t usedImages,
-                       uint32_t* const out_packedBytes)
-{
-    MOZ_RELEASE_ASSERT(rowByteAlignment != 0);
-
-    if (!usedPixelsPerRow || !usedRowsPerImage || !usedImages) {
-        *out_packedBytes = 0;
-        return true;
-    }
-    // Now we know there's at least one image.
-
-    CheckedUint32 pixelsPerRow = CheckedUint32(skipPixelsPerRow) + usedPixelsPerRow;
-    CheckedUint32 rowsPerImage = CheckedUint32(skipRowsPerImage) + usedRowsPerImage;
-    CheckedUint32 images = CheckedUint32(skipImages) + usedImages;
-
-    MOZ_ASSERT_IF(maybeStridePixelsPerRow,
-                  (pixelsPerRow.isValid() &&
-                   maybeStridePixelsPerRow >= pixelsPerRow.value()));
-
-    MOZ_ASSERT_IF(maybeStrideRowsPerImage,
-                  (rowsPerImage.isValid() &&
-                   maybeStrideRowsPerImage >= rowsPerImage.value()));
-
-    CheckedUint32 stridePixelsPerRow = maybeStridePixelsPerRow ? maybeStridePixelsPerRow
-                                                               : pixelsPerRow;
-    CheckedUint32 strideRowsPerImage = maybeStrideRowsPerImage ? maybeStrideRowsPerImage
-                                                               : rowsPerImage;
-
-    CheckedUint32 strideBytesPerRow = bytesPerPixel * stridePixelsPerRow;
-    strideBytesPerRow = RoundUpToMultipleOf(strideBytesPerRow, rowByteAlignment);
-
-    CheckedUint32 strideBytesPerImage = strideBytesPerRow * strideRowsPerImage;
-
-    CheckedUint32 lastRowBytes = CheckedUint32(bytesPerPixel) * pixelsPerRow;
-    CheckedUint32 lastImageBytes = strideBytesPerRow * (rowsPerImage - 1) + lastRowBytes;
-
-    CheckedUint32 packedBytes = strideBytesPerImage * (images - 1) + lastImageBytes;
-
-    if (!packedBytes.isValid())
-        return false;
-
-    *out_packedBytes = packedBytes.value();
-    return true;
-}
-
-////////////////////
-
 bool
 TexUnpackBytes::ValidateUnpack(WebGLContext* webgl, const char* funcName, bool isFunc3D,
                                const webgl::PackingInfo& pi)
 {
     if (!mBytes)
         return true;
 
-    const auto bytesPerPixel           = webgl::BytesPerPixel(pi);
-    const auto rowByteAlignment        = webgl->mPixelStore_UnpackAlignment;
-    const auto maybeStridePixelsPerRow = webgl->mPixelStore_UnpackRowLength;
-    const auto maybeStrideRowsPerImage = webgl->mPixelStore_UnpackImageHeight;
-    const auto skipPixelsPerRow        = webgl->mPixelStore_UnpackSkipPixels;
-    const auto skipRowsPerImage        = webgl->mPixelStore_UnpackSkipRows;
-    const auto skipImages              = isFunc3D ? webgl->mPixelStore_UnpackSkipImages
-                                                  : 0;
-    const auto usedPixelsPerRow = mWidth;
-    const auto usedRowsPerImage = mHeight;
-    const auto usedImages       = mDepth;
-
-    uint32_t bytesNeeded;
-    if (!GetPackedSizeForUnpack(bytesPerPixel, rowByteAlignment,
-                                maybeStridePixelsPerRow, maybeStrideRowsPerImage,
-                                skipPixelsPerRow, skipRowsPerImage, skipImages,
-                                usedPixelsPerRow, usedRowsPerImage, usedImages,
-                                &bytesNeeded))
-    {
+    const auto bytesPerPixel = webgl::BytesPerPixel(pi);
+    const auto bytesNeeded = webgl->GetUnpackSize(isFunc3D, mWidth, mHeight, mDepth,
+                                                  bytesPerPixel);
+    if (!bytesNeeded.isValid()) {
         webgl->ErrorInvalidOperation("%s: Overflow while computing the needed buffer"
                                      " size.",
                                      funcName);
         return false;
     }
 
-    if (bytesNeeded > mByteCount) {
+    if (mByteCount < bytesNeeded.value()) {
         webgl->ErrorInvalidOperation("%s: Provided buffer is too small. (needs %u, has"
                                      " %u)",
-                                     funcName, bytesNeeded, mByteCount);
+                                     funcName, bytesNeeded.value(), mByteCount);
         return false;
     }
 
     return true;
 }
 
 static bool
 UnpackFormatHasAlpha(GLenum unpackFormat)
@@ -281,17 +209,16 @@ TexUnpackBytes::TexOrSubImage(bool isSub
                           dstPremultiplied))
         {
             MOZ_ASSERT(false, "ConvertImage failed unexpectedly.");
             *out_glError = LOCAL_GL_OUT_OF_MEMORY;
             return;
         }
 
         uploadBytes = tempBuffer.get();
-        break;
     } while (false);
 
     GLenum error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
                                    zOffset, mWidth, mHeight, mDepth, uploadBytes);
     *out_glError = error;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
--- a/dom/canvas/WebGL2ContextFramebuffers.cpp
+++ b/dom/canvas/WebGL2ContextFramebuffers.cpp
@@ -310,19 +310,16 @@ WebGL2Context::FramebufferTextureLayer(G
                                          "MAX_ARRAY_TEXTURE_LAYERS");
             }
             break;
 
         default:
             return ErrorInvalidOperation("framebufferTextureLayer: texture must be an "
                                          "existing 3D texture, or a 2D texture array.");
         }
-    } else {
-        return ErrorInvalidOperation("framebufferTextureLayer: texture must be an "
-                                     "existing 3D texture, or a 2D texture array.");
     }
 
     WebGLFramebuffer* fb;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         break;
@@ -345,58 +342,61 @@ WebGL2Context::FramebufferTextureLayer(G
 
 JS::Value
 WebGL2Context::GetFramebufferAttachmentParameter(JSContext* cx,
                                                  GLenum target,
                                                  GLenum attachment,
                                                  GLenum pname,
                                                  ErrorResult& out_error)
 {
+    const char funcName[] = "getFramebufferAttachmentParameter";
+
     if (IsContextLost())
         return JS::NullValue();
 
     // OpenGL ES 3.0.4 (August 27, 2014) 6.1. QUERYING GL STATE 240
     // "getFramebufferAttachmentParamter returns information about attachments of a bound
     // framebuffer object. target must be DRAW_FRAMEBUFFER, READ_FRAMEBUFFER, or
     // FRAMEBUFFER."
 
-    if (!ValidateFramebufferTarget(target, "getFramebufferAttachmentParameter"))
+    if (!ValidateFramebufferTarget(target, funcName))
         return JS::NullValue();
 
     // FRAMEBUFFER is equivalent to DRAW_FRAMEBUFFER.
     if (target == LOCAL_GL_FRAMEBUFFER)
         target = LOCAL_GL_DRAW_FRAMEBUFFER;
 
     WebGLFramebuffer* boundFB = nullptr;
     switch (target) {
     case LOCAL_GL_DRAW_FRAMEBUFFER: boundFB = mBoundDrawFramebuffer; break;
     case LOCAL_GL_READ_FRAMEBUFFER: boundFB = mBoundReadFramebuffer; break;
     }
 
     if (boundFB) {
-        return boundFB->GetAttachmentParameter(cx, target, attachment, pname, &out_error);
+        return boundFB->GetAttachmentParameter(funcName, cx, target, attachment, pname,
+                                               &out_error);
     }
 
     ////////////////
     // Handle default FB
 
     // If the default framebuffer is bound to target, then attachment must be BACK,
     // identifying the color buffer; DEPTH, identifying the depth buffer; or STENCIL,
     // identifying the stencil buffer.
     switch (attachment) {
     case LOCAL_GL_BACK:
     case LOCAL_GL_DEPTH:
     case LOCAL_GL_STENCIL:
     case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
         break;
 
     default:
-        ErrorInvalidEnum("getFramebufferAttachmentParameter: Can only query "
-                         "attachment BACK, DEPTH, or STENCIL from default "
-                         "framebuffer");
+        ErrorInvalidEnum("%s: Can only query attachment BACK, DEPTH, or STENCIL from"
+                         " default framebuffer.",
+                         funcName);
         return JS::NullValue();
     }
 
     switch (pname) {
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
         return JS::Int32Value(LOCAL_GL_FRAMEBUFFER_DEFAULT);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
@@ -610,17 +610,17 @@ WebGL2Context::InvalidateSubFramebuffer(
 
 void
 WebGL2Context::ReadBuffer(GLenum mode)
 {
     if (IsContextLost())
         return;
 
     const bool isColorAttachment = (mode >= LOCAL_GL_COLOR_ATTACHMENT0 &&
-                                    mode <= LastColorAttachment());
+                                    mode <= LastColorAttachmentEnum());
 
     if (mode != LOCAL_GL_NONE && mode != LOCAL_GL_BACK && !isColorAttachment) {
         ErrorInvalidEnum("readBuffer: `mode` must be one of NONE, BACK, or "
                          "COLOR_ATTACHMENTi. Was %s",
                          EnumName(mode));
         return;
     }
 
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1268,89 +1268,57 @@ WebGLContext::MozGetUnderlyingParamStrin
     }
 
     return NS_OK;
 }
 
 void
 WebGLContext::ClearScreen()
 {
-    bool colorAttachmentsMask[WebGLContext::kMaxColorAttachments] = {false};
-
     MakeContextCurrent();
     ScopedBindFramebuffer autoFB(gl, 0);
 
-    GLbitfield clearMask = LOCAL_GL_COLOR_BUFFER_BIT;
+    const bool changeDrawBuffers = (mDefaultFB_DrawBuffer0 != LOCAL_GL_BACK);
+    if (changeDrawBuffers) {
+        const GLenum back = LOCAL_GL_BACK;
+        gl->fDrawBuffers(1, &back);
+    }
+
+    GLbitfield bufferBits = LOCAL_GL_COLOR_BUFFER_BIT;
     if (mOptions.depth)
-        clearMask |= LOCAL_GL_DEPTH_BUFFER_BIT;
+        bufferBits |= LOCAL_GL_DEPTH_BUFFER_BIT;
     if (mOptions.stencil)
-        clearMask |= LOCAL_GL_STENCIL_BUFFER_BIT;
+        bufferBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
+
+    ForceClearFramebufferWithDefaultValues(bufferBits, mNeedsFakeNoAlpha);
 
-    colorAttachmentsMask[0] = true;
-
-    ForceClearFramebufferWithDefaultValues(mNeedsFakeNoAlpha, clearMask,
-                                           colorAttachmentsMask);
+    if (changeDrawBuffers) {
+        gl->fDrawBuffers(1, &mDefaultFB_DrawBuffer0);
+    }
 }
 
 void
-WebGLContext::ForceClearFramebufferWithDefaultValues(bool fakeNoAlpha, GLbitfield mask,
-                                                     const bool colorAttachmentsMask[kMaxColorAttachments])
+WebGLContext::ForceClearFramebufferWithDefaultValues(GLbitfield clearBits,
+                                                     bool fakeNoAlpha)
 {
     MakeContextCurrent();
 
-    bool initializeColorBuffer = 0 != (mask & LOCAL_GL_COLOR_BUFFER_BIT);
-    bool initializeDepthBuffer = 0 != (mask & LOCAL_GL_DEPTH_BUFFER_BIT);
-    bool initializeStencilBuffer = 0 != (mask & LOCAL_GL_STENCIL_BUFFER_BIT);
-    bool drawBuffersIsEnabled = IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers);
-    bool shouldOverrideDrawBuffers = false;
-    bool usingDefaultFrameBuffer = !mBoundDrawFramebuffer;
-
-    GLenum currentDrawBuffers[WebGLContext::kMaxColorAttachments];
+    const bool initializeColorBuffer = bool(clearBits & LOCAL_GL_COLOR_BUFFER_BIT);
+    const bool initializeDepthBuffer = bool(clearBits & LOCAL_GL_DEPTH_BUFFER_BIT);
+    const bool initializeStencilBuffer = bool(clearBits & LOCAL_GL_STENCIL_BUFFER_BIT);
 
     // Fun GL fact: No need to worry about the viewport here, glViewport is just
     // setting up a coordinates transformation, it doesn't affect glClear at all.
     AssertCachedState(); // Can't check cached bindings, as we could
                          // have a different FB bound temporarily.
 
     // Prepare GL state for clearing.
     gl->fDisable(LOCAL_GL_SCISSOR_TEST);
 
     if (initializeColorBuffer) {
-
-        if (drawBuffersIsEnabled) {
-
-            GLenum drawBuffersCommand[WebGLContext::kMaxColorAttachments] = { LOCAL_GL_NONE };
-
-            for (int32_t i = 0; i < mGLMaxDrawBuffers; i++) {
-                GLint temp;
-                gl->fGetIntegerv(LOCAL_GL_DRAW_BUFFER0 + i, &temp);
-                currentDrawBuffers[i] = temp;
-
-                if (colorAttachmentsMask[i]) {
-                    drawBuffersCommand[i] = LOCAL_GL_COLOR_ATTACHMENT0 + i;
-                }
-                if (currentDrawBuffers[i] != drawBuffersCommand[i])
-                    shouldOverrideDrawBuffers = true;
-            }
-
-            // When clearing the default framebuffer, we must be clearing only
-            // GL_BACK, and nothing else, or else gl may return an error. We will
-            // only use the first element of currentDrawBuffers in this case.
-            if (usingDefaultFrameBuffer) {
-                gl->Screen()->SetDrawBuffer(LOCAL_GL_BACK);
-                if (currentDrawBuffers[0] == LOCAL_GL_COLOR_ATTACHMENT0)
-                    currentDrawBuffers[0] = LOCAL_GL_BACK;
-                shouldOverrideDrawBuffers = false;
-            }
-            // calling draw buffers can cause resolves on adreno drivers so
-            // we try to avoid calling it
-            if (shouldOverrideDrawBuffers)
-                gl->fDrawBuffers(mGLMaxDrawBuffers, drawBuffersCommand);
-        }
-
         gl->fColorMask(1, 1, 1, 1);
 
         if (fakeNoAlpha) {
             gl->fClearColor(0.0f, 0.0f, 0.0f, 1.0f);
         } else {
             gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
         }
     }
@@ -1368,37 +1336,28 @@ WebGLContext::ForceClearFramebufferWithD
         gl->fClearStencil(0);
     }
 
     if (mRasterizerDiscardEnabled) {
         gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD);
     }
 
     // Do the clear!
-    gl->fClear(mask);
+    gl->fClear(clearBits);
 
     // And reset!
     if (mScissorTestEnabled)
         gl->fEnable(LOCAL_GL_SCISSOR_TEST);
 
     if (mRasterizerDiscardEnabled) {
         gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
     }
 
     // Restore GL state after clearing.
     if (initializeColorBuffer) {
-
-        if (drawBuffersIsEnabled) {
-            if (usingDefaultFrameBuffer) {
-                gl->Screen()->SetDrawBuffer(currentDrawBuffers[0]);
-            } else if (shouldOverrideDrawBuffers) {
-                gl->fDrawBuffers(mGLMaxDrawBuffers, currentDrawBuffers);
-            }
-        }
-
         gl->fColorMask(mColorWriteMask[0],
                        mColorWriteMask[1],
                        mColorWriteMask[2],
                        mColorWriteMask[3]);
         gl->fClearColor(mColorClearValue[0],
                         mColorClearValue[1],
                         mColorClearValue[2],
                         mColorClearValue[3]);
@@ -1788,28 +1747,16 @@ WebGLContext::GetSurfaceSnapshot(bool* o
 void
 WebGLContext::DidRefresh()
 {
     if (gl) {
         gl->FlushIfHeavyGLCallsSinceLastFlush();
     }
 }
 
-size_t
-RoundUpToMultipleOf(size_t value, size_t multiple)
-{
-    return ((value + multiple - 1) / multiple) * multiple;
-}
-
-CheckedUint32
-RoundedToNextMultipleOf(CheckedUint32 value, CheckedUint32 multiple)
-{
-    return ((value + multiple - 1) / multiple) * multiple;
-}
-
 bool
 WebGLContext::ValidateCurFBForRead(const char* funcName,
                                    const webgl::FormatUsageInfo** const out_format,
                                    uint32_t* const out_width, uint32_t* const out_height)
 {
     if (!mBoundReadFramebuffer) {
         ClearBackbufferIfNeeded();
 
@@ -1993,19 +1940,17 @@ ZeroTextureData(WebGLContext* webgl, con
             return false;
 
         return true;
     }
 
     const auto driverUnpackInfo = usage->idealUnpack;
     MOZ_RELEASE_ASSERT(driverUnpackInfo);
 
-    const auto unpackFormat = driverUnpackInfo->unpackFormat;
-    const auto unpackType = driverUnpackInfo->unpackType;
-    const webgl::PackingInfo packing = { unpackFormat, unpackType };
+    const webgl::PackingInfo packing = driverUnpackInfo->ToPacking();
 
     const auto bytesPerPixel = webgl::BytesPerPixel(packing);
 
     CheckedUint32 checkedByteCount = bytesPerPixel;
     checkedByteCount *= width;
     checkedByteCount *= height;
     checkedByteCount *= depth;
 
@@ -2016,31 +1961,76 @@ ZeroTextureData(WebGLContext* webgl, con
 
     UniqueBuffer zeros = calloc(1, byteCount);
     if (!zeros)
         return false;
 
     GLenum error;
     if (respecifyTexture) {
         MOZ_RELEASE_ASSERT(!xOffset && !yOffset && !zOffset);
-
-        const auto internalFormat = driverUnpackInfo->internalFormat;
-        error = DoTexImage(gl, target, level, internalFormat, width, height, depth,
-                           unpackFormat, unpackType, zeros.get());
+        error = DoTexImage(gl, target, level, driverUnpackInfo, width, height, depth,
+                           zeros.get());
     } else {
         error = DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width, height,
-                              depth, unpackFormat, unpackType, zeros.get());
+                              depth, packing, zeros.get());
     }
     if (error)
         return false;
 
     return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+
+CheckedUint32
+WebGLContext::GetUnpackSize(bool isFunc3D, uint32_t width, uint32_t height,
+                            uint32_t depth, uint8_t bytesPerPixel)
+{
+    if (!width || !height || !depth)
+        return 0;
+
+    ////////////////
+
+    const auto& maybeRowLength = mPixelStore_UnpackRowLength;
+    const auto& maybeImageHeight = mPixelStore_UnpackImageHeight;
+
+    const auto usedPixelsPerRow = CheckedUint32(mPixelStore_UnpackSkipPixels) + width;
+    const auto stridePixelsPerRow = (maybeRowLength ? CheckedUint32(maybeRowLength)
+                                                    : usedPixelsPerRow);
+
+    const auto usedRowsPerImage = CheckedUint32(mPixelStore_UnpackSkipRows) + height;
+    const auto strideRowsPerImage = (maybeImageHeight ? CheckedUint32(maybeImageHeight)
+                                                      : usedRowsPerImage);
+
+    const uint32_t skipImages = (isFunc3D ? mPixelStore_UnpackSkipImages
+                                          : 0);
+    const CheckedUint32 usedImages = CheckedUint32(skipImages) + depth;
+
+    ////////////////
+
+    CheckedUint32 strideBytesPerRow = bytesPerPixel * stridePixelsPerRow;
+    strideBytesPerRow = RoundUpToMultipleOf(strideBytesPerRow,
+                                            mPixelStore_UnpackAlignment);
+
+    const CheckedUint32 strideBytesPerImage = strideBytesPerRow * strideRowsPerImage;
+
+    ////////////////
+
+    CheckedUint32 usedBytesPerRow = bytesPerPixel * usedPixelsPerRow;
+    // Don't round this to the alignment, since alignment here is really just used for
+    // establishing stride, particularly in WebGL 1, where you can't set ROW_LENGTH.
+
+    CheckedUint32 totalBytes = strideBytesPerImage * (usedImages - 1);
+    totalBytes += strideBytesPerRow * (usedRowsPerImage - 1);
+    totalBytes += usedBytesPerRow;
+
+    return totalBytes;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // XPCOM goop
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebGLContext)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLContext,
   mCanvasElement,
   mOffscreenCanvas,
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -209,16 +209,19 @@ class WebGLContext
         UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241,
         CONTEXT_LOST_WEBGL = 0x9242,
         UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243,
         BROWSER_DEFAULT_WEBGL = 0x9244,
         UNMASKED_VENDOR_WEBGL = 0x9245,
         UNMASKED_RENDERER_WEBGL = 0x9246
     };
 
+    static const uint32_t kMinMaxColorAttachments = 4;
+    static const uint32_t kMinMaxDrawBuffers = 4;
+
 public:
     WebGLContext();
 
 protected:
     virtual ~WebGLContext();
 
 public:
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLContext)
@@ -351,24 +354,21 @@ public:
     bool IsPreservingDrawingBuffer() const { return mOptions.preserveDrawingBuffer; }
 
     bool PresentScreenBuffer();
 
     // a number that increments every time we have an event that causes
     // all context resources to be lost.
     uint32_t Generation() { return mGeneration.value(); }
 
-    static const size_t kMaxColorAttachments = 16;
-
     // This is similar to GLContext::ClearSafely, but tries to minimize the
     // amount of work it does.
     // It only clears the buffers we specify, and can reset its state without
     // first having to query anything, as WebGL knows its state at all times.
-    void ForceClearFramebufferWithDefaultValues(bool fakeNoAlpha, GLbitfield mask,
-                                                const bool colorAttachmentsMask[kMaxColorAttachments]);
+    void ForceClearFramebufferWithDefaultValues(GLbitfield bufferBits, bool fakeNoAlpha);
 
     // Calls ForceClearFramebufferWithDefaultValues() for the Context's 'screen'.
     void ClearScreen();
     void ClearBackbufferIfNeeded();
 
     bool MinCapabilityMode() const { return mMinCapability; }
 
     void RunContextLossTimer();
@@ -1090,16 +1090,17 @@ protected:
     bool mShouldPresent;
     bool mBackbufferNeedsClear;
     bool mDisableFragHighP;
 
     template<typename WebGLObjectType>
     void DeleteWebGLObjectsArray(nsTArray<WebGLObjectType>& array);
 
     GLuint mActiveTexture;
+    GLenum mDefaultFB_DrawBuffer0;
 
     // glGetError sources:
     bool mEmitContextLostErrorOnce;
     GLenum mWebGLError;
     GLenum mUnderlyingGLError;
     GLenum GetAndFlushUnderlyingGLErrors();
 
     bool mBypassShaderValidation;
@@ -1109,22 +1110,34 @@ protected:
     // some GL constants
     int32_t mGLMaxVertexAttribs;
     int32_t mGLMaxTextureUnits;
     int32_t mGLMaxTextureImageUnits;
     int32_t mGLMaxVertexTextureImageUnits;
     int32_t mGLMaxVaryingVectors;
     int32_t mGLMaxFragmentUniformVectors;
     int32_t mGLMaxVertexUniformVectors;
-    int32_t mGLMaxColorAttachments;
-    int32_t mGLMaxDrawBuffers;
     uint32_t  mGLMaxTransformFeedbackSeparateAttribs;
     GLuint  mGLMaxUniformBufferBindings;
     GLsizei mGLMaxSamples;
 
+    // What is supported:
+    uint32_t mGLMaxColorAttachments;
+    uint32_t mGLMaxDrawBuffers;
+    // What we're allowing:
+    uint32_t mImplMaxColorAttachments;
+    uint32_t mImplMaxDrawBuffers;
+
+public:
+    GLenum LastColorAttachmentEnum() const {
+        return LOCAL_GL_COLOR_ATTACHMENT0 + mImplMaxColorAttachments - 1;
+    }
+
+protected:
+
     // Texture sizes are often not actually the GL values. Let's be explicit that these
     // are implementation limits.
     uint32_t mImplMaxTextureSize;
     uint32_t mImplMaxCubeMapTextureSize;
     uint32_t mImplMax3DTextureSize;
     uint32_t mImplMaxArrayTextureLayers;
     uint32_t mImplMaxRenderbufferSize;
 
@@ -1371,20 +1384,16 @@ protected:
     nsTArray<WebGLRefPtr<WebGLTexture> > mBound2DArrayTextures;
     nsTArray<WebGLRefPtr<WebGLSampler> > mBoundSamplers;
 
     void ResolveTexturesForDraw() const;
 
     WebGLRefPtr<WebGLProgram> mCurrentProgram;
     RefPtr<const webgl::LinkedProgramInfo> mActiveProgramLinkInfo;
 
-    GLenum LastColorAttachment() const {
-        return LOCAL_GL_COLOR_ATTACHMENT0 + mGLMaxColorAttachments - 1;
-    }
-
     bool ValidateFramebufferTarget(GLenum target, const char* const info);
 
     WebGLRefPtr<WebGLFramebuffer> mBoundDrawFramebuffer;
     WebGLRefPtr<WebGLFramebuffer> mBoundReadFramebuffer;
     WebGLRefPtr<WebGLRenderbuffer> mBoundRenderbuffer;
     WebGLRefPtr<WebGLTransformFeedback> mBoundTransformFeedback;
     WebGLRefPtr<WebGLVertexArray> mBoundVertexArray;
 
@@ -1411,16 +1420,19 @@ protected:
     uint32_t mPixelStore_UnpackSkipRows;
     uint32_t mPixelStore_UnpackSkipPixels;
     uint32_t mPixelStore_UnpackAlignment;
     uint32_t mPixelStore_PackRowLength;
     uint32_t mPixelStore_PackSkipRows;
     uint32_t mPixelStore_PackSkipPixels;
     uint32_t mPixelStore_PackAlignment;
 
+    CheckedUint32 GetUnpackSize(bool isFunc3D, uint32_t width, uint32_t height,
+                                uint32_t depth, uint8_t bytesPerPixel);
+
     CheckedUint32 GetPackSize(uint32_t width, uint32_t height, uint8_t bytesPerPixel,
                               CheckedUint32* const out_startOffset,
                               CheckedUint32* const out_rowStride);
 
     GLenum mPixelStore_ColorspaceConversion;
     bool mPixelStore_FlipY;
     bool mPixelStore_PremultiplyAlpha;
 
@@ -1567,16 +1579,17 @@ public:
 
     // Friend list
     friend class ScopedCopyTexImageSource;
     friend class ScopedResolveTexturesForDraw;
     friend class ScopedUnpackReset;
     friend class webgl::TexUnpackBytes;
     friend class webgl::TexUnpackSurface;
     friend class WebGLTexture;
+    friend class WebGLFBAttachPoint;
     friend class WebGLFramebuffer;
     friend class WebGLRenderbuffer;
     friend class WebGLProgram;
     friend class WebGLQuery;
     friend class WebGLBuffer;
     friend class WebGLSampler;
     friend class WebGLShader;
     friend class WebGLSync;
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -825,25 +825,33 @@ WebGLContext::FakeBlackTexture::FakeBlac
 
     mGL->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST);
     mGL->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST);
 
     // We allocate our zeros on the heap, and we overallocate (16 bytes instead of 4) to
     // minimize the risk of running into a driver bug in texImage2D, as it is a bit
     // unusual maybe to create 1x1 textures, and the stack may not have the alignment that
     // TexImage2D expects.
-    UniqueBuffer zeros = moz_xcalloc(1, 16);
+
+    const webgl::DriverUnpackInfo dui = {texFormat, texFormat, LOCAL_GL_UNSIGNED_BYTE};
+    UniqueBuffer zeros = moz_xcalloc(1, 16); // Infallible allocation.
+
     if (target == LOCAL_GL_TEXTURE_CUBE_MAP) {
         for (int i = 0; i < 6; ++i) {
-            DoTexImage(mGL, LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, texFormat, 1, 1,
-                       1, texFormat, LOCAL_GL_UNSIGNED_BYTE, zeros.get());
+            const TexImageTarget curTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;
+            const GLenum error = DoTexImage(mGL, curTarget.get(), 0, &dui, 1, 1, 1,
+                                            zeros.get());
+            if (error)
+                MOZ_CRASH("Unexpected error during FakeBlack creation.");
         }
     } else {
-        DoTexImage(mGL, target.get(), 0, texFormat, 1, 1, 1, texFormat,
-                   LOCAL_GL_UNSIGNED_BYTE, zeros.get());
+        const GLenum error = DoTexImage(mGL, target.get(), 0, &dui, 1, 1, 1,
+                                        zeros.get());
+        if (error)
+            MOZ_CRASH("Unexpected error during FakeBlack creation.");
     }
 }
 
 WebGLContext::FakeBlackTexture::~FakeBlackTexture()
 {
     mGL->MakeCurrent();
     mGL->fDeleteTextures(1, &mGLName);
 }
--- a/dom/canvas/WebGLContextFramebufferOperations.cpp
+++ b/dom/canvas/WebGLContextFramebufferOperations.cpp
@@ -132,81 +132,85 @@ WebGLContext::DepthMask(WebGLboolean b)
     MakeContextCurrent();
     mDepthWriteMask = b;
     gl->fDepthMask(b);
 }
 
 void
 WebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers)
 {
+    const char funcName[] = "drawBuffers";
     if (IsContextLost())
         return;
 
-    const size_t buffersLength = buffers.Length();
-
-    if (!buffersLength) {
-        return ErrorInvalidValue("drawBuffers: invalid <buffers> (buffers must not be empty)");
-    }
-
     if (!mBoundDrawFramebuffer) {
-        // OK: we are rendering in the default framebuffer
-
-        /* EXT_draw_buffers :
-         If the GL is bound to the default framebuffer, then <buffersLength> must be 1
-         and the constant must be BACK or NONE. When draw buffer zero is
-         BACK, color values are written into the sole buffer for single-
-         buffered contexts, or into the back buffer for double-buffered
-         contexts. If DrawBuffersEXT is supplied with a constant other than
-         BACK and NONE, the error INVALID_OPERATION is generated.
-         */
-        if (buffersLength != 1) {
-            return ErrorInvalidValue("drawBuffers: invalid <buffers> (main framebuffer: buffers.length must be 1)");
+        // GLES 3.0.4 p186:
+        // "If the GL is bound to the default framebuffer, then `n` must be 1 and the
+        //  constant must be BACK or NONE. [...] If DrawBuffers is supplied with a
+        //  constant other than BACK and NONE, or with a value of `n` other than 1, the
+        //  error INVALID_OPERATION is generated."
+        if (buffers.Length() != 1) {
+            ErrorInvalidOperation("%s: For the default framebuffer, `buffers` must have a"
+                                  " length of 1.",
+                                  funcName);
+            return;
         }
 
-        if (buffers[0] == LOCAL_GL_NONE || buffers[0] == LOCAL_GL_BACK) {
-            gl->Screen()->SetDrawBuffer(buffers[0]);
+        switch (buffers[0]) {
+        case LOCAL_GL_NONE:
+        case LOCAL_GL_BACK:
+            break;
+
+        default:
+            ErrorInvalidOperation("%s: For the default framebuffer, `buffers[0]` must be"
+                                  " BACK or NONE.",
+                                  funcName);
             return;
         }
-        return ErrorInvalidOperation("drawBuffers: invalid operation (main framebuffer: buffers[0] must be GL_NONE or GL_BACK)");
+
+        mDefaultFB_DrawBuffer0 = buffers[0];
+        gl->Screen()->SetDrawBuffer(buffers[0]);
+        return;
     }
 
-    // OK: we are rendering in a framebuffer object
+    // Framebuffer object (not default framebuffer)
 
-    if (buffersLength > size_t(mGLMaxDrawBuffers)) {
-        /* EXT_draw_buffers :
-         The maximum number of draw buffers is implementation-dependent. The
-         number of draw buffers supported can be queried by calling
-         GetIntegerv with the symbolic constant MAX_DRAW_BUFFERS_EXT. An
-         INVALID_VALUE error is generated if <buffersLength> is greater than
-         MAX_DRAW_BUFFERS_EXT.
-         */
-        return ErrorInvalidValue("drawBuffers: invalid <buffers> (buffers.length > GL_MAX_DRAW_BUFFERS)");
+    if (buffers.Length() > mImplMaxDrawBuffers) {
+        // "An INVALID_VALUE error is generated if `n` is greater than MAX_DRAW_BUFFERS."
+        ErrorInvalidValue("%s: `buffers` must have a length <= MAX_DRAW_BUFFERS.",
+                          funcName);
+        return;
     }
 
-    for (uint32_t i = 0; i < buffersLength; i++)
-    {
-        /* EXT_draw_buffers :
-         If the GL is bound to a draw framebuffer object, the <i>th buffer listed
-         in <bufs> must be COLOR_ATTACHMENT<i>_EXT or NONE. Specifying a
-         buffer out of order, BACK, or COLOR_ATTACHMENT<m>_EXT where <m> is
-         greater than or equal to the value of MAX_COLOR_ATTACHMENTS_EXT,
-         will generate the error INVALID_OPERATION.
-         */
-        /* WEBGL_draw_buffers :
-         The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter.
-         */
+    for (size_t i = 0; i < buffers.Length(); i++) {
+        // "If the GL is bound to a draw framebuffer object, the `i`th buffer listed in
+        //  bufs must be COLOR_ATTACHMENTi or NONE. Specifying a buffer out of order,
+        //  BACK, or COLOR_ATTACHMENTm where `m` is greater than or equal to the value of
+        // MAX_COLOR_ATTACHMENTS, will generate the error INVALID_OPERATION.
+
+        // WEBGL_draw_buffers:
+        // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or
+        //  equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter."
+        // This means that if buffers.Length() isn't larger than MaxDrawBuffers, it won't
+        // be larger than MaxColorAttachments.
         if (buffers[i] != LOCAL_GL_NONE &&
-            buffers[i] != GLenum(LOCAL_GL_COLOR_ATTACHMENT0 + i)) {
-            return ErrorInvalidOperation("drawBuffers: invalid operation (buffers[i] must be GL_NONE or GL_COLOR_ATTACHMENTi)");
+            buffers[i] != LOCAL_GL_COLOR_ATTACHMENT0 + i)
+        {
+            ErrorInvalidOperation("%s: `buffers[i]` must be NONE or COLOR_ATTACHMENTi.",
+                                  funcName);
+            return;
         }
     }
 
     MakeContextCurrent();
+    gl->fDrawBuffers(buffers.Length(), buffers.Elements());
 
-    gl->fDrawBuffers(buffersLength, buffers.Elements());
+    const GLenum* begin = buffers.Elements();
+    const GLenum* end = begin + buffers.Length();
+    mBoundDrawFramebuffer->mDrawBuffers.assign(begin, end);
 }
 
 void
 WebGLContext::StencilMask(GLuint mask)
 {
     if (IsContextLost())
         return;
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -703,20 +703,22 @@ JSUint32Value(uint32_t val)
 
 JS::Value
 WebGLContext::GetFramebufferAttachmentParameter(JSContext* cx,
                                                 GLenum target,
                                                 GLenum attachment,
                                                 GLenum pname,
                                                 ErrorResult& rv)
 {
+    const char funcName[] = "getFramebufferAttachmentParameter";
+
     if (IsContextLost())
         return JS::NullValue();
 
-    if (!ValidateFramebufferTarget(target, "getFramebufferAttachmentParameter"))
+    if (!ValidateFramebufferTarget(target, funcName))
         return JS::NullValue();
 
     WebGLFramebuffer* fb;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         break;
@@ -724,155 +726,83 @@ WebGLContext::GetFramebufferAttachmentPa
     case LOCAL_GL_READ_FRAMEBUFFER:
         fb = mBoundReadFramebuffer;
         break;
 
     default:
         MOZ_CRASH("Bad target.");
     }
 
-    if (!fb) {
-        // This isn't actually true. GLES 3.0.4, p240: "If the default framebuffer[...]".
-        ErrorInvalidOperation("getFramebufferAttachmentParameter: cannot query"
-                              " framebuffer 0.");
-        return JS::NullValue();
-    }
-
-    if (!ValidateFramebufferAttachment(fb, attachment,
-                                       "getFramebufferAttachmentParameter"))
-    {
+    if (fb)
+        return fb->GetAttachmentParameter(funcName, cx, target, attachment, pname, &rv);
+
+    ////////////////////////////////////
+
+    if (!IsWebGL2()) {
+        ErrorInvalidOperation("%s: Querying against the default framebuffer is not"
+                              " allowed in WebGL 1.",
+                              funcName);
         return JS::NullValue();
     }
 
-    if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers) &&
-        attachment >= LOCAL_GL_COLOR_ATTACHMENT0 &&
-        attachment <= LOCAL_GL_COLOR_ATTACHMENT15)
-    {
-        fb->EnsureColorAttachPoints(attachment - LOCAL_GL_COLOR_ATTACHMENT0);
+    switch (attachment) {
+    case LOCAL_GL_COLOR:
+    case LOCAL_GL_DEPTH:
+    case LOCAL_GL_STENCIL:
+        break;
+
+    default:
+        ErrorInvalidEnum("%s: For the default framebuffer, can only query COLOR, DEPTH,"
+                         " or STENCIL.",
+                         funcName);
+        return JS::NullValue();
     }
 
-    MakeContextCurrent();
-
-    const WebGLFBAttachPoint& fba = fb->GetAttachPoint(attachment);
-
     switch (pname) {
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
-        if (fba.Renderbuffer())
-            return JSUint32Value(LOCAL_GL_RENDERBUFFER);
-
-        if (fba.Texture())
-            return JSUint32Value(LOCAL_GL_TEXTURE);
-
-        return JSUint32Value(LOCAL_GL_NONE);
+        return JS::Int32Value(LOCAL_GL_FRAMEBUFFER_DEFAULT);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
-        if (fba.Renderbuffer())
-            return WebGLObjectAsJSValue(cx, fba.Renderbuffer(), rv);
-
-        if (fba.Texture())
-            return WebGLObjectAsJSValue(cx, fba.Texture(), rv);
-
         return JS::NullValue();
-    }
-
-
-    const bool hasAttachments = (fba.Renderbuffer() || fba.Texture());
-
-    switch (pname) {
-    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT:
-        if (!hasAttachments)
-            return MissingAttachmentCausesInvalidOp(this);
-
-        if (!IsWebGL2() && !IsExtensionEnabled(WebGLExtensionID::EXT_sRGB))
-            break;
-
-        if (!fba.IsDefined())
-            return JSUint32Value(LOCAL_GL_LINEAR);
-
-        if (fba.IsDefined()) {
-            if (fba.Format()->format->isSRGB)
-                return JSUint32Value(LOCAL_GL_SRGB_EXT);
-        }
-
-        return JSUint32Value(LOCAL_GL_LINEAR);
+
+    ////////////////
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
+        if (attachment == LOCAL_GL_COLOR)
+            return JS::Int32Value(8);
+        return JS::Int32Value(0);
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
+        if (attachment == LOCAL_GL_COLOR)
+            return JS::Int32Value(mOptions.alpha ? 8 : 0);
+        return JS::Int32Value(0);
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
+        if (attachment == LOCAL_GL_DEPTH)
+            return JS::Int32Value(mOptions.depth ? 24 : 0);
+        return JS::Int32Value(0);
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
+        if (attachment == LOCAL_GL_STENCIL)
+            return JS::Int32Value(mOptions.stencil ? 8 : 0);
+        return JS::Int32Value(0);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
-        if (!hasAttachments)
-            return MissingAttachmentCausesInvalidOp(this);
-
-        if (!IsWebGL2() &&
-            !IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float) &&
-            !IsExtensionEnabled(WebGLExtensionID::WEBGL_color_buffer_float))
-        {
-            break;
-        }
-
-        if (!fba.IsDefined())
-            return JSUint32Value(LOCAL_GL_LINEAR);
-
-        if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-            ErrorInvalidOperation("getFramebufferAttachmentParameter: Cannot get component"
-                                  " type of a depth-stencil attachment.");
-            return JS::NullValue();
-        }
-
-        switch (fba.Format()->format->componentType) {
-        case webgl::ComponentType::Int:
-            return JSUint32Value(LOCAL_GL_INT);
-
-        case webgl::ComponentType::UInt:
-            return JSUint32Value(LOCAL_GL_UNSIGNED_INT);
-
-        case webgl::ComponentType::NormInt:
-            return JSUint32Value(LOCAL_GL_SIGNED_NORMALIZED);
-
-        case webgl::ComponentType::NormUInt:
-            return JSUint32Value(LOCAL_GL_UNSIGNED_NORMALIZED);
-
-        case webgl::ComponentType::Float:
-            return JSUint32Value(LOCAL_GL_FLOAT);
-
-        case webgl::ComponentType::None:
-            return JSUint32Value(LOCAL_GL_NONE);
-        }
-        // Exhaustive switch means nothing's left.
-
-    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL:
-        if (!hasAttachments)
-            return MissingAttachmentCausesInvalidOp(this);
-
-        if (!fba.Texture())
-            break;
-
-        return JSUint32Value(fba.MipLevel());
-
-    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE:
-        if (!hasAttachments)
-            return MissingAttachmentCausesInvalidOp(this);
-
-        if (!fba.Texture())
-            break;
-
-        switch (fba.ImageTarget().get()) {
-        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
-        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
-        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
-        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
-        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
-        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
-            return JSUint32Value(fba.ImageTarget().get());
-        }
-
-        return JSUint32Value(0);
-
-    default:
-        break;
+        if (attachment == LOCAL_GL_STENCIL)
+            return JS::Int32Value(LOCAL_GL_UNSIGNED_INT);
+        else
+            return JS::Int32Value(LOCAL_GL_UNSIGNED_NORMALIZED);
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
+        return JS::Int32Value(LOCAL_GL_LINEAR);
     }
 
-    ErrorInvalidEnumInfo("getFramebufferAttachmentParameter: pname", pname);
+    ErrorInvalidEnum("%s: Invalid pname: 0x%04x", funcName, pname);
     return JS::NullValue();
 }
 
 JS::Value
 WebGLContext::GetRenderbufferParameter(GLenum target, GLenum pname)
 {
     if (IsContextLost())
         return JS::NullValue();
--- a/dom/canvas/WebGLContextState.cpp
+++ b/dom/canvas/WebGLContextState.cpp
@@ -139,23 +139,23 @@ WebGLContext::GetParameter(JSContext* cx
             default:
                 // Return the real value; we're not overriding this one
                 break;
         }
     }
 
     if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers)) {
         if (pname == LOCAL_GL_MAX_COLOR_ATTACHMENTS) {
-            return JS::Int32Value(mGLMaxColorAttachments);
+            return JS::Int32Value(mImplMaxColorAttachments);
 
         } else if (pname == LOCAL_GL_MAX_DRAW_BUFFERS) {
-            return JS::Int32Value(mGLMaxDrawBuffers);
+            return JS::Int32Value(mImplMaxDrawBuffers);
 
         } else if (pname >= LOCAL_GL_DRAW_BUFFER0 &&
-                   pname < GLenum(LOCAL_GL_DRAW_BUFFER0 + mGLMaxDrawBuffers))
+                   pname < GLenum(LOCAL_GL_DRAW_BUFFER0 + mImplMaxDrawBuffers))
         {
             GLint iv = 0;
             gl->fGetIntegerv(pname, &iv);
 
             if (mBoundDrawFramebuffer)
                 return JS::Int32Value(iv);
 
             const GLint index = (pname - LOCAL_GL_DRAW_BUFFER0);
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -736,20 +736,30 @@ WebGLContext::AssertCachedBindings()
 void
 WebGLContext::AssertCachedState()
 {
 #ifdef DEBUG
     MakeContextCurrent();
 
     GetAndFlushUnderlyingGLErrors();
 
-    // extensions
-    if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers)) {
-        AssertUintParamCorrect(gl, LOCAL_GL_MAX_COLOR_ATTACHMENTS, mGLMaxColorAttachments);
-        AssertUintParamCorrect(gl, LOCAL_GL_MAX_DRAW_BUFFERS, mGLMaxDrawBuffers);
+    ////////////////
+
+    AssertUintParamCorrect(gl, LOCAL_GL_MAX_COLOR_ATTACHMENTS, mGLMaxColorAttachments);
+    AssertUintParamCorrect(gl, LOCAL_GL_MAX_DRAW_BUFFERS, mGLMaxDrawBuffers);
+
+    if (IsWebGL2() ||
+        IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers))
+    {
+        MOZ_ASSERT(mImplMaxColorAttachments == std::min(mGLMaxColorAttachments,
+                                                        mGLMaxDrawBuffers));
+        MOZ_ASSERT(mImplMaxDrawBuffers == mGLMaxDrawBuffers);
+    } else {
+        MOZ_ASSERT(mImplMaxColorAttachments == 1);
+        MOZ_ASSERT(mImplMaxDrawBuffers == 1);
     }
 
     // Draw state
     MOZ_ASSERT(gl->fIsEnabled(LOCAL_GL_DEPTH_TEST) == mDepthTestEnabled);
     MOZ_ASSERT(gl->fIsEnabled(LOCAL_GL_DITHER) == mDitherEnabled);
     MOZ_ASSERT_IF(IsWebGL2(),
                   gl->fIsEnabled(LOCAL_GL_RASTERIZER_DISCARD) == mRasterizerDiscardEnabled);
     MOZ_ASSERT(gl->fIsEnabled(LOCAL_GL_SCISSOR_TEST) == mScissorTestEnabled);
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -255,20 +255,16 @@ WebGLContext::ValidateDrawModeEnum(GLenu
         return true;
 
     default:
         ErrorInvalidEnumInfo(info, mode);
         return false;
     }
 }
 
-/**
- * Return true if the framebuffer attachment is valid. Attachment must
- * be one of depth/stencil/depth_stencil/color attachment.
- */
 bool
 WebGLContext::ValidateFramebufferAttachment(const WebGLFramebuffer* fb, GLenum attachment,
                                             const char* funcName)
 {
     if (!fb) {
         switch (attachment) {
         case LOCAL_GL_COLOR:
         case LOCAL_GL_DEPTH:
@@ -284,18 +280,21 @@ WebGLContext::ValidateFramebufferAttachm
 
     if (attachment == LOCAL_GL_DEPTH_ATTACHMENT ||
         attachment == LOCAL_GL_STENCIL_ATTACHMENT ||
         attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
     {
         return true;
     }
 
-    if (attachment >= LOCAL_GL_COLOR_ATTACHMENT0 && attachment <= LastColorAttachment())
+    if (attachment >= LOCAL_GL_COLOR_ATTACHMENT0 &&
+        attachment <= LastColorAttachmentEnum())
+    {
         return true;
+    }
 
     ErrorInvalidEnum("%s: attachment: invalid enum value 0x%x.", funcName,
                      attachment);
     return false;
 }
 
 /**
  * Return true if pname is valid for GetSamplerParameter calls.
@@ -700,16 +699,18 @@ WebGLContext::InitAndValidateGL()
     AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_WRITEMASK,  mStencilWriteMaskBack);
 
     mDitherEnabled = true;
     mRasterizerDiscardEnabled = false;
     mScissorTestEnabled = false;
 
     // Bindings, etc.
     mActiveTexture = 0;
+    mDefaultFB_DrawBuffer0 = LOCAL_GL_BACK;
+
     mEmitContextLostErrorOnce = true;
     mWebGLError = LOCAL_GL_NO_ERROR;
     mUnderlyingGLError = LOCAL_GL_NO_ERROR;
 
     mBound2DTextures.Clear();
     mBoundCubeMapTextures.Clear();
     mBound3DTextures.Clear();
     mBound2DArrayTextures.Clear();
@@ -786,33 +787,54 @@ WebGLContext::InitAndValidateGL()
 
         gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS, &mGLMaxTextureImageUnits);
         gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mGLMaxVertexTextureImageUnits);
 
         if (!gl->GetPotentialInteger(LOCAL_GL_MAX_SAMPLES, (GLint*)&mGLMaxSamples))
             mGLMaxSamples = 1;
     }
 
-    const auto fnFloor = [](uint32_t& val) {
+    // If we don't support a target, its max size is 0. We should only floor-to-POT if the
+    // value if it's non-zero. (NB log2(0) is -Inf, so zero isn't an integer power-of-two)
+    const auto fnFloorPOTIfSupported = [](uint32_t& val) {
         if (val) {
             val = FloorPOT(val);
         }
     };
 
-    fnFloor(mImplMaxTextureSize);
-    fnFloor(mImplMaxCubeMapTextureSize);
-    fnFloor(mImplMaxRenderbufferSize);
+    fnFloorPOTIfSupported(mImplMaxTextureSize);
+    fnFloorPOTIfSupported(mImplMaxCubeMapTextureSize);
+    fnFloorPOTIfSupported(mImplMaxRenderbufferSize);
 
-    fnFloor(mImplMax3DTextureSize);
-    fnFloor(mImplMaxArrayTextureLayers);
+    fnFloorPOTIfSupported(mImplMax3DTextureSize);
+    fnFloorPOTIfSupported(mImplMaxArrayTextureLayers);
 
     ////////////////
 
     mGLMaxColorAttachments = 1;
     mGLMaxDrawBuffers = 1;
+    gl->GetPotentialInteger(LOCAL_GL_MAX_COLOR_ATTACHMENTS,
+                            (GLint*)&mGLMaxColorAttachments);
+    gl->GetPotentialInteger(LOCAL_GL_MAX_DRAW_BUFFERS, (GLint*)&mGLMaxDrawBuffers);
+
+    if (MinCapabilityMode()) {
+        mGLMaxColorAttachments = std::min(mGLMaxColorAttachments,
+                                          kMinMaxColorAttachments);
+        mGLMaxDrawBuffers = std::min(mGLMaxDrawBuffers, kMinMaxDrawBuffers);
+    }
+
+    if (IsWebGL2()) {
+        mImplMaxColorAttachments = mGLMaxColorAttachments;
+        mImplMaxDrawBuffers = std::min(mGLMaxDrawBuffers, mImplMaxColorAttachments);
+    } else {
+        mImplMaxColorAttachments = 1;
+        mImplMaxDrawBuffers = 1;
+    }
+
+    ////////////////
 
     if (MinCapabilityMode()) {
         mGLMaxFragmentUniformVectors = MINVALUE_GL_MAX_FRAGMENT_UNIFORM_VECTORS;
         mGLMaxVertexUniformVectors = MINVALUE_GL_MAX_VERTEX_UNIFORM_VECTORS;
         mGLMaxVaryingVectors = MINVALUE_GL_MAX_VARYING_VECTORS;
     } else {
         if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
             gl->fGetIntegerv(LOCAL_GL_MAX_FRAGMENT_UNIFORM_VECTORS, &mGLMaxFragmentUniformVectors);
--- a/dom/canvas/WebGLExtensionColorBufferFloat.cpp
+++ b/dom/canvas/WebGLExtensionColorBufferFloat.cpp
@@ -26,18 +26,18 @@ WebGLExtensionColorBufferFloat::WebGLExt
         auto usage = fua->EditUsage(effFormat);
         usage->isRenderable = true;
 
         fua->AddRBFormat(sizedFormat, usage);
     };
 
 #define FOO(x) fnUpdateUsage(LOCAL_GL_ ## x, webgl::EffectiveFormat::x)
 
+    // The extension doesn't actually add RGB32F; only RGBA32F.
     FOO(RGBA32F);
-    FOO(RGB32F);
 
 #undef FOO
 }
 
 WebGLExtensionColorBufferFloat::~WebGLExtensionColorBufferFloat()
 {
 }
 
--- a/dom/canvas/WebGLExtensionDrawBuffers.cpp
+++ b/dom/canvas/WebGLExtensionDrawBuffers.cpp
@@ -15,36 +15,22 @@
 
 namespace mozilla {
 
 WebGLExtensionDrawBuffers::WebGLExtensionDrawBuffers(WebGLContext* webgl)
     : WebGLExtensionBase(webgl)
 {
     MOZ_ASSERT(IsSupported(webgl), "Don't construct extension if unsupported.");
 
-    GLint maxColorAttachments = 0;
-    GLint maxDrawBuffers = 0;
-
-    webgl->MakeContextCurrent();
-    gl::GLContext* gl = webgl->GL();
-
-    gl->fGetIntegerv(LOCAL_GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments);
-    gl->fGetIntegerv(LOCAL_GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
-
-    // WEBGL_draw_buffers specifications don't give a maximal value reachable by MAX_COLOR_ATTACHMENTS.
-    maxColorAttachments = std::min(maxColorAttachments, GLint(WebGLContext::kMaxColorAttachments));
-
-    if (webgl->MinCapabilityMode())
-        maxColorAttachments = std::min(maxColorAttachments, GLint(kMinColorAttachments));
-
-    // WEBGL_draw_buffers specifications request MAX_COLOR_ATTACHMENTS >= MAX_DRAW_BUFFERS.
-    maxDrawBuffers = std::min(maxDrawBuffers, GLint(maxColorAttachments));
-
-    webgl->mGLMaxColorAttachments = maxColorAttachments;
-    webgl->mGLMaxDrawBuffers = maxDrawBuffers;
+    // WEBGL_draw_buffers:
+    // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or
+    //  equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter."
+    webgl->mImplMaxColorAttachments = webgl->mGLMaxColorAttachments;
+    webgl->mImplMaxDrawBuffers = std::min(webgl->mGLMaxDrawBuffers,
+                                          webgl->mImplMaxColorAttachments);
 }
 
 WebGLExtensionDrawBuffers::~WebGLExtensionDrawBuffers()
 {
 }
 
 void
 WebGLExtensionDrawBuffers::DrawBuffersWEBGL(const dom::Sequence<GLenum>& buffers)
@@ -60,29 +46,21 @@ WebGLExtensionDrawBuffers::DrawBuffersWE
 bool
 WebGLExtensionDrawBuffers::IsSupported(const WebGLContext* webgl)
 {
     gl::GLContext* gl = webgl->GL();
 
     if (!gl->IsSupported(gl::GLFeature::draw_buffers))
         return false;
 
-    GLint supportedColorAttachments = 0;
-    GLint supportedDrawBuffers = 0;
-
-    webgl->MakeContextCurrent();
-
-    gl->fGetIntegerv(LOCAL_GL_MAX_COLOR_ATTACHMENTS, &supportedColorAttachments);
-    gl->fGetIntegerv(LOCAL_GL_MAX_COLOR_ATTACHMENTS, &supportedDrawBuffers);
-
     // WEBGL_draw_buffers requires at least 4 color attachments.
-    if (size_t(supportedColorAttachments) < kMinColorAttachments)
+    if (webgl->mGLMaxDrawBuffers < webgl->kMinMaxDrawBuffers ||
+        webgl->mGLMaxColorAttachments < webgl->kMinMaxColorAttachments)
+    {
         return false;
-
-    if (size_t(supportedDrawBuffers) < kMinDrawBuffers)
-        return false;
+    }
 
     return true;
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionDrawBuffers, WEBGL_draw_buffers)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLExtensionSRGB.cpp
+++ b/dom/canvas/WebGLExtensionSRGB.cpp
@@ -22,36 +22,33 @@ WebGLExtensionSRGB::WebGLExtensionSRGB(W
         // Desktop OpenGL requires the following to be enabled in order to
         // support sRGB operations on framebuffers.
         gl->MakeCurrent();
         gl->fEnable(LOCAL_GL_FRAMEBUFFER_SRGB_EXT);
     }
 
     auto& fua = webgl->mFormatUsage;
 
-    webgl::FormatUsageInfo* usage;
-    webgl::PackingInfo pi;
-    webgl::DriverUnpackInfo dui;
+    const auto fnAdd = [&fua](webgl::EffectiveFormat effFormat, GLenum unpackFormat)
+    {
+        auto usage = fua->EditUsage(webgl::EffectiveFormat::SRGB8);
+        usage->isFilterable = true;
 
-    usage = fua->EditUsage(webgl::EffectiveFormat::SRGB8);
-    usage->isRenderable = false;
-    usage->isFilterable = true;
-    pi = {LOCAL_GL_SRGB, LOCAL_GL_UNSIGNED_BYTE};
-    dui = {LOCAL_GL_SRGB, LOCAL_GL_SRGB, LOCAL_GL_UNSIGNED_BYTE};
-    fua->AddUnsizedTexFormat(pi, usage);
-    usage->AddUnpack(pi, dui);
+        const webgl::DriverUnpackInfo dui = {unpackFormat, unpackFormat,
+                                             LOCAL_GL_UNSIGNED_BYTE};
+        const auto pi = dui.ToPacking();
+        fua->AddUnsizedTexFormat(pi, usage);
+        usage->AddUnpack(pi, dui);
+    };
 
-    usage = fua->EditUsage(webgl::EffectiveFormat::SRGB8_ALPHA8);
+    fnAdd(webgl::EffectiveFormat::SRGB8, LOCAL_GL_SRGB);
+    fnAdd(webgl::EffectiveFormat::SRGB8_ALPHA8, LOCAL_GL_SRGB_ALPHA);
+
+    auto usage = fua->EditUsage(webgl::EffectiveFormat::SRGB8_ALPHA8);
     usage->isRenderable = true;
-    usage->isFilterable = true;
-    pi = {LOCAL_GL_SRGB_ALPHA, LOCAL_GL_UNSIGNED_BYTE};
-    dui = {LOCAL_GL_SRGB_ALPHA, LOCAL_GL_SRGB_ALPHA, LOCAL_GL_UNSIGNED_BYTE};
-    fua->AddUnsizedTexFormat(pi, usage);
-    usage->AddUnpack(pi, dui);
-
     fua->AddRBFormat(LOCAL_GL_SRGB8_ALPHA8, usage);
 }
 
 WebGLExtensionSRGB::~WebGLExtensionSRGB()
 {
 }
 
 bool
--- a/dom/canvas/WebGLExtensions.h
+++ b/dom/canvas/WebGLExtensions.h
@@ -284,24 +284,16 @@ class WebGLExtensionDrawBuffers
 public:
     explicit WebGLExtensionDrawBuffers(WebGLContext*);
     virtual ~WebGLExtensionDrawBuffers();
 
     void DrawBuffersWEBGL(const dom::Sequence<GLenum>& buffers);
 
     static bool IsSupported(const WebGLContext*);
 
-    static const size_t kMinColorAttachments = 4;
-    static const size_t kMinDrawBuffers = 4;
-    /*
-     WEBGL_draw_buffers does not give a minal value for GL_MAX_DRAW_BUFFERS. But, we request
-     for GL_MAX_DRAW_BUFFERS = 4 at least to be able to use all requested color attachments.
-     See DrawBuffersWEBGL in WebGLExtensionDrawBuffers.cpp inner comments for more informations.
-     */
-
     DECL_WEBGL_EXTENSION_GOOP
 };
 
 class WebGLExtensionVertexArray
     : public WebGLExtensionBase
 {
 public:
     explicit WebGLExtensionVertexArray(WebGLContext* webgl);
--- a/dom/canvas/WebGLFormats.cpp
+++ b/dom/canvas/WebGLFormats.cpp
@@ -235,18 +235,18 @@ InitFormatInfo()
     AddFormatInfo(FOO(RGBA16UI      ),  8, UnsizedFormat::RGBA, false, ComponentType::UInt    );
     AddFormatInfo(FOO(RGBA32I       ), 16, UnsizedFormat::RGBA, false, ComponentType::Int     );
     AddFormatInfo(FOO(RGBA32UI      ), 16, UnsizedFormat::RGBA, false, ComponentType::UInt    );
 
     // GLES 3.0.4, p133, table 3.14
     AddFormatInfo(FOO(DEPTH_COMPONENT16 ), 2, UnsizedFormat::D , false, ComponentType::NormUInt);
     AddFormatInfo(FOO(DEPTH_COMPONENT24 ), 3, UnsizedFormat::D , false, ComponentType::NormUInt);
     AddFormatInfo(FOO(DEPTH_COMPONENT32F), 4, UnsizedFormat::D , false, ComponentType::Float);
-    AddFormatInfo(FOO(DEPTH24_STENCIL8  ), 4, UnsizedFormat::DS, false, ComponentType::None);
-    AddFormatInfo(FOO(DEPTH32F_STENCIL8 ), 5, UnsizedFormat::DS, false, ComponentType::None);
+    AddFormatInfo(FOO(DEPTH24_STENCIL8  ), 4, UnsizedFormat::DS, false, ComponentType::Special);
+    AddFormatInfo(FOO(DEPTH32F_STENCIL8 ), 5, UnsizedFormat::DS, false, ComponentType::Special);
 
     // GLES 3.0.4, p205-206, "Required Renderbuffer Formats"
     AddFormatInfo(FOO(STENCIL_INDEX8), 1, UnsizedFormat::S, false, ComponentType::UInt);
 
     // GLES 3.0.4, p147, table 3.19
     // GLES 3.0.4  p286+  $C.1 "ETC Compressed Texture Image Formats"
     AddFormatInfo(FOO(COMPRESSED_RGB8_ETC2                     ), 0, UnsizedFormat::RGB , false, ComponentType::NormUInt);
     AddFormatInfo(FOO(COMPRESSED_SRGB8_ETC2                    ), 0, UnsizedFormat::RGB , true , ComponentType::NormUInt);
@@ -285,24 +285,24 @@ InitFormatInfo()
 #define FOO(x) EffectiveFormat::x, #x, 0
 
     // GLES 3.0.4, p128, table 3.12.
     AddFormatInfo(FOO(Luminance8Alpha8), 2, UnsizedFormat::LA, false, ComponentType::NormUInt);
     AddFormatInfo(FOO(Luminance8      ), 1, UnsizedFormat::L , false, ComponentType::NormUInt);
     AddFormatInfo(FOO(Alpha8          ), 1, UnsizedFormat::A , false, ComponentType::NormUInt);
 
     // OES_texture_float
-    AddFormatInfo(FOO(Luminance32FAlpha32F), 2, UnsizedFormat::LA, false, ComponentType::Float);
-    AddFormatInfo(FOO(Luminance32F        ), 1, UnsizedFormat::L , false, ComponentType::Float);
-    AddFormatInfo(FOO(Alpha32F            ), 1, UnsizedFormat::A , false, ComponentType::Float);
+    AddFormatInfo(FOO(Luminance32FAlpha32F), 8, UnsizedFormat::LA, false, ComponentType::Float);
+    AddFormatInfo(FOO(Luminance32F        ), 4, UnsizedFormat::L , false, ComponentType::Float);
+    AddFormatInfo(FOO(Alpha32F            ), 4, UnsizedFormat::A , false, ComponentType::Float);
 
     // OES_texture_half_float
-    AddFormatInfo(FOO(Luminance16FAlpha16F), 2, UnsizedFormat::LA, false, ComponentType::Float);
-    AddFormatInfo(FOO(Luminance16F        ), 1, UnsizedFormat::L , false, ComponentType::Float);
-    AddFormatInfo(FOO(Alpha16F            ), 1, UnsizedFormat::A , false, ComponentType::Float);
+    AddFormatInfo(FOO(Luminance16FAlpha16F), 4, UnsizedFormat::LA, false, ComponentType::Float);
+    AddFormatInfo(FOO(Luminance16F        ), 2, UnsizedFormat::L , false, ComponentType::Float);
+    AddFormatInfo(FOO(Alpha16F            ), 2, UnsizedFormat::A , false, ComponentType::Float);
 
 #undef FOO
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 
 bool gAreFormatTablesInitialized = false;
 
--- a/dom/canvas/WebGLFormats.h
+++ b/dom/canvas/WebGLFormats.h
@@ -158,22 +158,23 @@ enum class UnsizedFormat : uint8_t {
     A,
     D,
     S,
     DS,
 };
 
 // GLES 3.0.4 p114 Table 3.4, p240
 enum class ComponentType : uint8_t {
-    None,         // DEPTH24_STENCIL8
+    None,
     Int,          // RGBA32I
     UInt,         // RGBA32UI, STENCIL_INDEX8
     NormInt,      // RGBA8_SNORM
     NormUInt,     // RGBA8, DEPTH_COMPONENT16
     Float,        // RGBA32F
+    Special,      // DEPTH24_STENCIL8
 };
 
 enum class CompressionFamily : uint8_t {
     ETC1,
     ES3, // ETC2 or EAC
     ATC,
     S3TC,
     PVRTC,
@@ -221,16 +222,20 @@ struct PackingInfo
     }
 };
 
 struct DriverUnpackInfo
 {
     GLenum internalFormat;
     GLenum unpackFormat;
     GLenum unpackType;
+
+    PackingInfo ToPacking() const {
+        return {unpackFormat, unpackType};
+    }
 };
 
 //////////////////////////////////////////////////////////////////////////////////////////
 
 const FormatInfo* GetFormat(EffectiveFormat format);
 uint8_t BytesPerPixel(const PackingInfo& packing);
 /*
 GLint ComponentSize(const FormatInfo* format, GLenum component);
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -10,18 +10,17 @@
 #include "WebGLContext.h"
 #include "WebGLContextUtils.h"
 #include "WebGLExtensions.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
 
 namespace mozilla {
 
-WebGLFBAttachPoint::WebGLFBAttachPoint(WebGLFramebuffer* fb,
-                                       FBAttachment attachmentPoint)
+WebGLFBAttachPoint::WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPoint)
     : mFB(fb)
     , mAttachmentPoint(attachmentPoint)
     , mTexImageTarget(LOCAL_GL_NONE)
 { }
 
 WebGLFBAttachPoint::~WebGLFBAttachPoint()
 {
     MOZ_ASSERT(!mRenderbufferPtr);
@@ -229,48 +228,41 @@ WebGLFBAttachPoint::IsComplete() const
         return false;
 
     auto formatUsage = Format();
     if (!formatUsage->isRenderable)
         return false;
 
     auto format = formatUsage->format;
 
-    if (mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0 &&
-        mAttachmentPoint <= FBAttachment(LOCAL_GL_COLOR_ATTACHMENT0 - 1 +
-                                         WebGLContext::kMaxColorAttachments))
-    {
-        return format->isColorFormat;
-    }
-
     if (mAttachmentPoint == LOCAL_GL_DEPTH_ATTACHMENT)
         return format->hasDepth && !format->hasStencil;
 
     if (mAttachmentPoint == LOCAL_GL_STENCIL_ATTACHMENT)
         return !format->hasDepth && format->hasStencil;
 
     if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
         return format->hasDepth && format->hasStencil;
 
-    MOZ_CRASH("Invalid WebGL attachment point?");
+    MOZ_ASSERT(mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0);
+    return format->isColorFormat;
 }
 
 void
-WebGLFBAttachPoint::FinalizeAttachment(gl::GLContext* gl,
-                                       FBAttachment attachmentLoc) const
+WebGLFBAttachPoint::FinalizeAttachment(gl::GLContext* gl, GLenum attachment) const
 {
     if (!HasImage()) {
-        switch (attachmentLoc.get()) {
+        switch (attachment) {
         case LOCAL_GL_DEPTH_ATTACHMENT:
         case LOCAL_GL_STENCIL_ATTACHMENT:
         case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
             break;
 
         default:
-            gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachmentLoc.get(),
+            gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachment,
                                          LOCAL_GL_RENDERBUFFER, 0);
             break;
         }
 
         return;
     }
     MOZ_ASSERT(HasImage());
 
@@ -285,115 +277,146 @@ WebGLFBAttachPoint::FinalizeAttachment(g
         switch (imageTarget) {
         case LOCAL_GL_TEXTURE_2D:
         case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
         case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
         case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
         case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
         case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
         case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
-            if (attachmentLoc == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+            if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
                 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
                                           imageTarget, glName, mipLevel);
-                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT,
-                                          imageTarget, glName, mipLevel);
+                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
+                                          LOCAL_GL_STENCIL_ATTACHMENT, imageTarget,
+                                          glName, mipLevel);
             } else {
-                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachmentLoc.get(),
-                                          imageTarget, glName, mipLevel);
+                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachment, imageTarget,
+                                          glName, mipLevel);
             }
             break;
 
         case LOCAL_GL_TEXTURE_2D_ARRAY:
         case LOCAL_GL_TEXTURE_3D:
-            if (attachmentLoc == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+            if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
                 gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER,
-                                             LOCAL_GL_DEPTH_ATTACHMENT,
-                                             glName, mipLevel, layer);
+                                             LOCAL_GL_DEPTH_ATTACHMENT, glName, mipLevel,
+                                             layer);
                 gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER,
-                                             LOCAL_GL_STENCIL_ATTACHMENT,
-                                             glName, mipLevel, layer);
+                                             LOCAL_GL_STENCIL_ATTACHMENT, glName,
+                                             mipLevel, layer);
             } else {
-                gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, attachmentLoc.get(),
-                                             glName, mipLevel, layer);
+                gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, attachment, glName,
+                                             mipLevel, layer);
             }
             break;
         }
         return ;
     }
 
     if (Renderbuffer()) {
-        Renderbuffer()->FramebufferRenderbuffer(attachmentLoc);
+        Renderbuffer()->FramebufferRenderbuffer(attachment);
         return;
     }
 
     MOZ_CRASH();
 }
 
 JS::Value
-WebGLFBAttachPoint::GetParameter(WebGLContext* context, GLenum target, GLenum attachment,
-                                 GLenum pname)
+WebGLFBAttachPoint::GetParameter(const char* funcName, WebGLContext* webgl, JSContext* cx,
+                                 GLenum target, GLenum attachment, GLenum pname,
+                                 ErrorResult* const out_error)
 {
-    WebGLTexture* tex = Texture();
+    const bool hasAttachment = (mTexturePtr || mRenderbufferPtr);
+    if (!hasAttachment) {
+        switch (pname) {
+        case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
+            return JS::Int32Value(LOCAL_GL_NONE);
+
+        case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
+            return JS::NullValue();
+
+        default:
+            webgl->ErrorInvalidOperation("%s: No attachment at %s.", funcName,
+                                         webgl->EnumName(attachment));
+            return JS::NullValue();
+        }
+    }
 
     bool isPNameValid = false;
     switch (pname) {
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
+        return JS::Int32Value(mTexturePtr ? LOCAL_GL_TEXTURE
+                                          : LOCAL_GL_RENDERBUFFER);
+
+    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
+        return (mTexturePtr ? webgl->WebGLObjectAsJSValue(cx, mTexturePtr.get(),
+                                                          *out_error)
+                            : webgl->WebGLObjectAsJSValue(cx, mRenderbufferPtr.get(),
+                                                          *out_error));
+    ////////////////
+
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
+        isPNameValid = true;
+        break;
+
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
+        MOZ_ASSERT(attachment != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
         isPNameValid = true;
         break;
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
-        if (tex->mContext->IsWebGL2() ||
-            tex->mContext->IsExtensionEnabled(WebGLExtensionID::EXT_sRGB))
+        if (webgl->IsWebGL2() ||
+            webgl->IsExtensionEnabled(WebGLExtensionID::EXT_sRGB))
         {
             isPNameValid = true;
         }
         break;
 
+    ////////////////
+
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL:
-        if (tex) {
+        if (mTexturePtr)
             return JS::Int32Value(MipLevel());
-        }
         break;
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE:
-        if (tex) {
-            int32_t face = 0;
-            if (tex->Target() == LOCAL_GL_TEXTURE_CUBE_MAP) {
+        if (mTexturePtr) {
+            GLenum face = 0;
+            if (mTexturePtr->Target() == LOCAL_GL_TEXTURE_CUBE_MAP) {
                 face = ImageTarget().get();
             }
             return JS::Int32Value(face);
         }
         break;
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER:
-        if (tex) {
+        if (mTexturePtr) {
             int32_t layer = 0;
-            if (tex->Target() == LOCAL_GL_TEXTURE_2D_ARRAY ||
-                tex->Target() == LOCAL_GL_TEXTURE_3D)
+            if (ImageTarget() == LOCAL_GL_TEXTURE_2D_ARRAY ||
+                ImageTarget() == LOCAL_GL_TEXTURE_3D)
             {
                 layer = Layer();
             }
             return JS::Int32Value(layer);
         }
         break;
     }
 
     if (!isPNameValid) {
-        context->ErrorInvalidEnum("getFramebufferParameter: Invalid combination of "
-                                  "attachment and pname.");
+        webgl->ErrorInvalidEnum("%s: Invalid pname: 0x%04x", funcName, pname);
         return JS::NullValue();
     }
 
-    gl::GLContext* gl = tex->mContext->GL();
+    gl::GLContext* gl = webgl->GL();
     gl->MakeCurrent();
 
     GLint ret = 0;
     gl->fGetFramebufferAttachmentParameteriv(target, attachment, pname, &ret);
     return JS::Int32Value(ret);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -403,67 +426,77 @@ WebGLFramebuffer::WebGLFramebuffer(WebGL
     : WebGLContextBoundObject(webgl)
     , mGLName(fbo)
     , mIsKnownFBComplete(false)
     , mReadBufferMode(LOCAL_GL_COLOR_ATTACHMENT0)
     , mColorAttachment0(this, LOCAL_GL_COLOR_ATTACHMENT0)
     , mDepthAttachment(this, LOCAL_GL_DEPTH_ATTACHMENT)
     , mStencilAttachment(this, LOCAL_GL_STENCIL_ATTACHMENT)
     , mDepthStencilAttachment(this, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
+    , mDrawBuffers(1, LOCAL_GL_COLOR_ATTACHMENT0)
 #ifdef ANDROID
     , mIsFB(false)
 #endif
-
 {
     mContext->mFramebuffers.insertBack(this);
 }
 
 void
 WebGLFramebuffer::Delete()
 {
     mColorAttachment0.Clear();
     mDepthAttachment.Clear();
     mStencilAttachment.Clear();
     mDepthStencilAttachment.Clear();
 
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        mMoreColorAttachments[i].Clear();
+    for (auto& cur : mMoreColorAttachments) {
+        cur.Clear();
     }
 
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteFramebuffers(1, &mGLName);
+
     LinkedListElement<WebGLFramebuffer>::removeFrom(mContext->mFramebuffers);
 
 #ifdef ANDROID
     mIsFB = false;
 #endif
 }
 
 void
-WebGLFramebuffer::FramebufferRenderbuffer(FBAttachment attachPointEnum,
-                                          RBTarget rbtarget,
+WebGLFramebuffer::FramebufferRenderbuffer(GLenum attachment, RBTarget rbtarget,
                                           WebGLRenderbuffer* rb)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     if (!mContext->ValidateObjectAllowNull("framebufferRenderbuffer: renderbuffer", rb))
         return;
 
-    // `attachPoint` is validated by ValidateFramebufferAttachment().
-    WebGLFBAttachPoint& attachPoint = GetAttachPoint(attachPointEnum);
-    attachPoint.SetRenderbuffer(rb);
+    // `attachPointEnum` is validated by ValidateFramebufferAttachment().
+
+    const auto fnAttach = [this, rb](GLenum attachment) {
+        const auto attachPoint = this->GetAttachPoint(attachment);
+        MOZ_ASSERT(attachPoint);
+
+        attachPoint->SetRenderbuffer(rb);
+    };
+
+    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
+        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
+    } else {
+        fnAttach(attachment);
+    }
 
     InvalidateFramebufferStatus();
 }
 
 void
-WebGLFramebuffer::FramebufferTexture2D(FBAttachment attachPointEnum,
-                                       TexImageTarget texImageTarget,
+WebGLFramebuffer::FramebufferTexture2D(GLenum attachment, TexImageTarget texImageTarget,
                                        WebGLTexture* tex, GLint level)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", tex))
         return;
 
@@ -472,66 +505,97 @@ WebGLFramebuffer::FramebufferTexture2D(F
         bool isTexTarget2D = texImageTarget == LOCAL_GL_TEXTURE_2D;
         if (isTexture2D != isTexTarget2D) {
             mContext->ErrorInvalidOperation("framebufferTexture2D: Mismatched"
                                             " texture and texture target.");
             return;
         }
     }
 
-    WebGLFBAttachPoint& attachPoint = GetAttachPoint(attachPointEnum);
-    attachPoint.SetTexImage(tex, texImageTarget, level);
+    const auto fnAttach = [this, tex, texImageTarget, level](GLenum attachment) {
+        const auto attachPoint = this->GetAttachPoint(attachment);
+        MOZ_ASSERT(attachPoint);
+
+        attachPoint->SetTexImage(tex, texImageTarget, level);
+    };
+
+    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
+        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
+    } else {
+        fnAttach(attachment);
+    }
 
     InvalidateFramebufferStatus();
 }
 
 void
-WebGLFramebuffer::FramebufferTextureLayer(FBAttachment attachment, WebGLTexture* tex,
+WebGLFramebuffer::FramebufferTextureLayer(GLenum attachment, WebGLTexture* tex,
                                           GLint level, GLint layer)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
-    MOZ_ASSERT(tex);
+
+    const TexImageTarget texImageTarget = (tex ? tex->Target().get()
+                                               : LOCAL_GL_TEXTURE_2D);
+
+    const auto fnAttach = [this, tex, texImageTarget, level, layer](GLenum attachment) {
+        const auto attachPoint = this->GetAttachPoint(attachment);
+        MOZ_ASSERT(attachPoint);
 
-    WebGLFBAttachPoint& attachPoint = GetAttachPoint(attachment);
-    TexImageTarget texImageTarget = tex->Target().get();
-    attachPoint.SetTexImageLayer(tex, texImageTarget, level, layer);
+        attachPoint->SetTexImageLayer(tex, texImageTarget, level, layer);
+    };
+
+    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
+        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
+    } else {
+        fnAttach(attachment);
+    }
 
     InvalidateFramebufferStatus();
 }
 
-WebGLFBAttachPoint&
-WebGLFramebuffer::GetAttachPoint(FBAttachment attachPoint)
+WebGLFBAttachPoint*
+WebGLFramebuffer::GetAttachPoint(GLenum attachPoint)
 {
-    switch (attachPoint.get()) {
+    switch (attachPoint) {
     case LOCAL_GL_COLOR_ATTACHMENT0:
-        return mColorAttachment0;
+        return &mColorAttachment0;
 
     case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
-        return mDepthStencilAttachment;
+        return &mDepthStencilAttachment;
 
     case LOCAL_GL_DEPTH_ATTACHMENT:
-        return mDepthAttachment;
+        return &mDepthAttachment;
 
     case LOCAL_GL_STENCIL_ATTACHMENT:
-        return mStencilAttachment;
+        return &mStencilAttachment;
 
     default:
         break;
     }
 
-    if (attachPoint >= LOCAL_GL_COLOR_ATTACHMENT1) {
-        size_t colorAttachmentId = attachPoint.get() - LOCAL_GL_COLOR_ATTACHMENT0;
-        if (colorAttachmentId < (size_t)mContext->mGLMaxColorAttachments) {
-            EnsureColorAttachPoints(colorAttachmentId);
-            return mMoreColorAttachments[colorAttachmentId - 1];
+    const auto lastCAEnum = mContext->LastColorAttachmentEnum();
+    if (attachPoint < LOCAL_GL_COLOR_ATTACHMENT1 ||
+        attachPoint > lastCAEnum)
+    {
+        return nullptr;
+    }
+
+    if (!mMoreColorAttachments.size()) {
+        for (GLenum cur = LOCAL_GL_COLOR_ATTACHMENT1; cur <= lastCAEnum; cur++) {
+            mMoreColorAttachments.push_back(WebGLFBAttachPoint(this, cur));
         }
     }
+    MOZ_ASSERT(LOCAL_GL_COLOR_ATTACHMENT0 + mMoreColorAttachments.size() == lastCAEnum);
 
-    MOZ_CRASH("bad `attachPoint` validation");
+    const size_t offset = attachPoint - LOCAL_GL_COLOR_ATTACHMENT1;
+    MOZ_ASSERT(offset <= mMoreColorAttachments.size());
+    return &mMoreColorAttachments[offset];
 }
 
 void
 WebGLFramebuffer::DetachTexture(const WebGLTexture* tex)
 {
     if (mColorAttachment0.Texture() == tex)
         mColorAttachment0.Clear();
 
@@ -539,20 +603,19 @@ WebGLFramebuffer::DetachTexture(const We
         mDepthAttachment.Clear();
 
     if (mStencilAttachment.Texture() == tex)
         mStencilAttachment.Clear();
 
     if (mDepthStencilAttachment.Texture() == tex)
         mDepthStencilAttachment.Clear();
 
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        if (mMoreColorAttachments[i].Texture() == tex)
-            mMoreColorAttachments[i].Clear();
+    for (auto& cur : mMoreColorAttachments) {
+        if (cur.Texture() == tex)
+            cur.Clear();
     }
 }
 
 void
 WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb)
 {
     if (mColorAttachment0.Renderbuffer() == rb)
         mColorAttachment0.Clear();
@@ -561,36 +624,34 @@ WebGLFramebuffer::DetachRenderbuffer(con
         mDepthAttachment.Clear();
 
     if (mStencilAttachment.Renderbuffer() == rb)
         mStencilAttachment.Clear();
 
     if (mDepthStencilAttachment.Renderbuffer() == rb)
         mDepthStencilAttachment.Clear();
 
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        if (mMoreColorAttachments[i].Renderbuffer() == rb)
-            mMoreColorAttachments[i].Clear();
+    for (auto& cur : mMoreColorAttachments) {
+        if (cur.Renderbuffer() == rb)
+            cur.Clear();
     }
 }
 
 bool
 WebGLFramebuffer::HasDefinedAttachments() const
 {
     bool hasAttachments = false;
 
     hasAttachments |= mColorAttachment0.IsDefined();
     hasAttachments |= mDepthAttachment.IsDefined();
     hasAttachments |= mStencilAttachment.IsDefined();
     hasAttachments |= mDepthStencilAttachment.IsDefined();
 
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        hasAttachments |= mMoreColorAttachments[i].IsDefined();
+    for (const auto& cur : mMoreColorAttachments) {
+        hasAttachments |= cur.IsDefined();
     }
 
     return hasAttachments;
 }
 
 static bool
 IsIncomplete(const WebGLFBAttachPoint& cur)
 {
@@ -602,19 +663,18 @@ WebGLFramebuffer::HasIncompleteAttachmen
 {
     bool hasIncomplete = false;
 
     hasIncomplete |= IsIncomplete(mColorAttachment0);
     hasIncomplete |= IsIncomplete(mDepthAttachment);
     hasIncomplete |= IsIncomplete(mStencilAttachment);
     hasIncomplete |= IsIncomplete(mDepthStencilAttachment);
 
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        hasIncomplete |= IsIncomplete(mMoreColorAttachments[i]);
+    for (const auto& cur : mMoreColorAttachments) {
+        hasIncomplete |= IsIncomplete(cur);
     }
 
     return hasIncomplete;
 }
 
 static bool
 MatchOrReplaceSize(const WebGLFBAttachPoint& cur, uint32_t* const out_width,
                    uint32_t* const out_height)
@@ -647,19 +707,18 @@ WebGLFramebuffer::AllImageRectsMatch() c
     uint32_t height = 0;
     bool imageRectsMatch = true;
 
     imageRectsMatch &= MatchOrReplaceSize(mColorAttachment0,       &width, &height);
     imageRectsMatch &= MatchOrReplaceSize(mDepthAttachment,        &width, &height);
     imageRectsMatch &= MatchOrReplaceSize(mStencilAttachment,      &width, &height);
     imageRectsMatch &= MatchOrReplaceSize(mDepthStencilAttachment, &width, &height);
 
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        imageRectsMatch &= MatchOrReplaceSize(mMoreColorAttachments[i], &width, &height);
+    for (const auto& cur : mMoreColorAttachments) {
+        imageRectsMatch &= MatchOrReplaceSize(cur, &width, &height);
     }
 
     return imageRectsMatch;
 }
 
 FBStatus
 WebGLFramebuffer::PrecheckFramebufferStatus() const
 {
@@ -737,107 +796,106 @@ WebGLFramebuffer::CheckAndInitializeAtta
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     if (CheckFramebufferStatus() != LOCAL_GL_FRAMEBUFFER_COMPLETE)
         return false;
 
     // Cool! We've checked out ok. Just need to initialize.
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
 
     // Check if we need to initialize anything
     {
         bool hasUninitializedAttachments = false;
 
-        if (mColorAttachment0.HasImage())
+        if (mColorAttachment0.HasImage() && IsDrawBuffer(0))
             hasUninitializedAttachments |= mColorAttachment0.HasUninitializedImageData();
+
+        size_t i = 1;
+        for (const auto& cur : mMoreColorAttachments) {
+            if (cur.HasImage() && IsDrawBuffer(i))
+                hasUninitializedAttachments |= cur.HasUninitializedImageData();
+
+            ++i;
+        }
+
         if (mDepthAttachment.HasImage())
             hasUninitializedAttachments |= mDepthAttachment.HasUninitializedImageData();
         if (mStencilAttachment.HasImage())
             hasUninitializedAttachments |= mStencilAttachment.HasUninitializedImageData();
         if (mDepthStencilAttachment.HasImage())
             hasUninitializedAttachments |= mDepthStencilAttachment.HasUninitializedImageData();
 
-        for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-            if (mMoreColorAttachments[i].HasImage())
-                hasUninitializedAttachments |= mMoreColorAttachments[i].HasUninitializedImageData();
-        }
-
         if (!hasUninitializedAttachments)
             return true;
     }
 
     // Get buffer-bit-mask and color-attachment-mask-list
-    uint32_t mask = 0;
-    bool colorAttachmentsMask[WebGLContext::kMaxColorAttachments] = { false };
-    MOZ_ASSERT(1 + moreColorAttachmentCount <= WebGLContext::kMaxColorAttachments);
+    uint32_t clearBits = 0;
+    std::vector<GLenum> tempDrawBuffers(1 + mMoreColorAttachments.size(), LOCAL_GL_NONE);
+
+    if (mColorAttachment0.HasUninitializedImageData() && IsDrawBuffer(0)) {
+        clearBits |= LOCAL_GL_COLOR_BUFFER_BIT;
+        tempDrawBuffers[0] = LOCAL_GL_COLOR_ATTACHMENT0;
+    }
 
-    if (mColorAttachment0.HasUninitializedImageData()) {
-        colorAttachmentsMask[0] = true;
-        mask |= LOCAL_GL_COLOR_BUFFER_BIT;
+    size_t i = 1;
+    for (const auto& cur : mMoreColorAttachments) {
+        if (cur.HasUninitializedImageData() && IsDrawBuffer(i)) {
+            clearBits |= LOCAL_GL_COLOR_BUFFER_BIT;
+            tempDrawBuffers[i] = LOCAL_GL_COLOR_ATTACHMENT0 + i;
+        }
+
+        ++i;
     }
 
     if (mDepthAttachment.HasUninitializedImageData() ||
         mDepthStencilAttachment.HasUninitializedImageData())
     {
-        mask |= LOCAL_GL_DEPTH_BUFFER_BIT;
+        clearBits |= LOCAL_GL_DEPTH_BUFFER_BIT;
     }
 
     if (mStencilAttachment.HasUninitializedImageData() ||
         mDepthStencilAttachment.HasUninitializedImageData())
     {
-        mask |= LOCAL_GL_STENCIL_BUFFER_BIT;
+        clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
     }
 
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        if (mMoreColorAttachments[i].HasUninitializedImageData()) {
-          colorAttachmentsMask[1 + i] = true;
-          mask |= LOCAL_GL_COLOR_BUFFER_BIT;
-        }
-    }
+    mContext->MakeContextCurrent();
+
+    mContext->gl->fDrawBuffers(tempDrawBuffers.size(), tempDrawBuffers.data());
 
     // Clear!
-    mContext->ForceClearFramebufferWithDefaultValues(false, mask, colorAttachmentsMask);
+    mContext->ForceClearFramebufferWithDefaultValues(clearBits, false);
+
+    mContext->gl->fDrawBuffers(mDrawBuffers.size(), mDrawBuffers.data());
 
     // Mark all the uninitialized images as initialized.
-    if (mColorAttachment0.HasUninitializedImageData())
-        mColorAttachment0.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     if (mDepthAttachment.HasUninitializedImageData())
         mDepthAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     if (mStencilAttachment.HasUninitializedImageData())
         mStencilAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     if (mDepthStencilAttachment.HasUninitializedImageData())
         mDepthStencilAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
 
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        if (mMoreColorAttachments[i].HasUninitializedImageData())
-            mMoreColorAttachments[i].SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
+    if (mColorAttachment0.HasUninitializedImageData() && IsDrawBuffer(0)) {
+        mColorAttachment0.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
+    }
+
+    i = 1;
+    for (auto& cur : mMoreColorAttachments) {
+        if (cur.HasUninitializedImageData() && IsDrawBuffer(i))
+            cur.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
+
+        ++i;
     }
 
     return true;
 }
 
-void WebGLFramebuffer::EnsureColorAttachPoints(size_t colorAttachmentId)
-{
-    size_t maxColorAttachments = mContext->mGLMaxColorAttachments;
-
-    MOZ_ASSERT(colorAttachmentId < maxColorAttachments);
-
-    if (colorAttachmentId < ColorAttachmentCount())
-        return;
-
-    while (ColorAttachmentCount() < maxColorAttachments) {
-        GLenum nextAttachPoint = LOCAL_GL_COLOR_ATTACHMENT0 + ColorAttachmentCount();
-        mMoreColorAttachments.AppendElement(WebGLFBAttachPoint(this, nextAttachPoint));
-    }
-
-    MOZ_ASSERT(ColorAttachmentCount() == maxColorAttachments);
-}
-
 static void
 FinalizeDrawAndReadBuffers(gl::GLContext* gl, bool isColorBufferDefined)
 {
     MOZ_ASSERT(gl, "Expected a valid GLContext ptr.");
     // GLES don't support DrawBuffer()/ReadBuffer.
     // According to http://www.opengl.org/wiki/Framebuffer_Object
     //
     // Each draw buffers must either specify color attachment points that have images
@@ -877,19 +935,18 @@ WebGLFramebuffer::FinalizeAttachments() 
                                  LOCAL_GL_RENDERBUFFER, 0);
 
     // Call finalize.
     mColorAttachment0.FinalizeAttachment(gl, LOCAL_GL_COLOR_ATTACHMENT0);
     mDepthAttachment.FinalizeAttachment(gl, LOCAL_GL_DEPTH_ATTACHMENT);
     mStencilAttachment.FinalizeAttachment(gl, LOCAL_GL_STENCIL_ATTACHMENT);
     mDepthStencilAttachment.FinalizeAttachment(gl, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
 
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        GLenum attachPoint = LOCAL_GL_COLOR_ATTACHMENT0 + 1 + i;
+    for (size_t i = 0; i < mMoreColorAttachments.size(); i++) {
+        GLenum attachPoint = LOCAL_GL_COLOR_ATTACHMENT1 + i;
         mMoreColorAttachments[i].FinalizeAttachment(gl, attachPoint);
     }
 
     FinalizeDrawAndReadBuffers(gl, mColorAttachment0.IsDefined());
 }
 
 bool
 WebGLFramebuffer::ValidateForRead(const char* funcName,
@@ -903,26 +960,25 @@ WebGLFramebuffer::ValidateForRead(const 
     }
 
     if (mReadBufferMode == LOCAL_GL_NONE) {
         mContext->ErrorInvalidOperation("%s: Read buffer mode must not be"
                                         " NONE.", funcName);
         return false;
     }
 
-    const auto& attachPoint = GetAttachPoint(mReadBufferMode);
-
-    if (!attachPoint.IsDefined()) {
-        mContext->ErrorInvalidOperation("%s: THe attachment specified for reading is"
+    const auto attachPoint = GetAttachPoint(mReadBufferMode);
+    if (!attachPoint || !attachPoint->IsDefined()) {
+        mContext->ErrorInvalidOperation("%s: The attachment specified for reading is"
                                         " null.", funcName);
         return false;
     }
 
-    *out_format = attachPoint.Format();
-    attachPoint.Size(out_width, out_height);
+    *out_format = attachPoint->Format();
+    attachPoint->Size(out_width, out_height);
     return true;
 }
 
 static bool
 AttachmentsDontMatch(const WebGLFBAttachPoint& a, const WebGLFBAttachPoint& b)
 {
     if (a.Texture()) {
         return (a.Texture() != b.Texture());
@@ -931,101 +987,53 @@ AttachmentsDontMatch(const WebGLFBAttach
     if (a.Renderbuffer()) {
         return (a.Renderbuffer() != b.Renderbuffer());
     }
 
     return false;
 }
 
 JS::Value
-WebGLFramebuffer::GetAttachmentParameter(JSContext* cx, GLenum target, GLenum attachment,
+WebGLFramebuffer::GetAttachmentParameter(const char* funcName, JSContext* cx,
+                                         GLenum target, GLenum attachment,
                                          GLenum pname, ErrorResult* const out_error)
 {
-    // "If a framebuffer object is bound to target, then attachment must be one of the
-    // attachment points of the framebuffer listed in table 4.6."
-    switch (attachment) {
-    case LOCAL_GL_DEPTH_ATTACHMENT:
-    case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
-        break;
-
-    case LOCAL_GL_STENCIL_ATTACHMENT:
-        // "If attachment is DEPTH_STENCIL_ATTACHMENT, and different objects are bound to
-        //  the depth and stencil attachment points of target, the query will fail and
-        //  generate an INVALID_OPERATION error. If the same object is bound to both
-        //  attachment points, information about that object will be returned."
-
-        // Does this mean it has to be the same level or layer? Because the queries are
-        // independent of level or layer.
-        if (AttachmentsDontMatch(DepthAttachment(), StencilAttachment())) {
-            mContext->ErrorInvalidOperation("getFramebufferAttachmentParameter: "
-                                            "DEPTH_ATTACHMENT and STENCIL_ATTACHMENT "
-                                            "have different objects bound.");
-            return JS::NullValue();
-        }
-        break;
-
-    default:
-        if (attachment < LOCAL_GL_COLOR_ATTACHMENT0 ||
-            attachment > mContext->LastColorAttachment())
-        {
-            mContext->ErrorInvalidEnum("getFramebufferAttachmentParameter: Can only "
-                                       "query COLOR_ATTACHMENTi, DEPTH_ATTACHMENT, "
-                                       "DEPTH_STENCIL_ATTACHMENT, or STENCIL_ATTACHMENT "
-                                       "on framebuffer.");
-            return JS::NullValue();
-        }
-    }
-
-    if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT &&
-        pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE)
-    {
-        mContext->ErrorInvalidOperation("getFramebufferAttachmentParameter: Querying "
-                                        "FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE against "
-                                        "DEPTH_STENCIL_ATTACHMENT is an error.");
+    auto attachPoint = GetAttachPoint(attachment);
+    if (!attachPoint) {
+        mContext->ErrorInvalidEnum("%s: Can only query COLOR_ATTACHMENTi,"
+                                   " DEPTH_ATTACHMENT, DEPTH_STENCIL_ATTACHMENT, or"
+                                   " STENCIL_ATTACHMENT for a framebuffer.",
+                                   funcName);
         return JS::NullValue();
     }
 
-    GLenum objectType = LOCAL_GL_NONE;
-    auto& fba = GetAttachPoint(attachment);
-    if (fba.Texture()) {
-        objectType = LOCAL_GL_TEXTURE;
-    } else if (fba.Renderbuffer()) {
-        objectType = LOCAL_GL_RENDERBUFFER;
-    }
+    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        // There are a couple special rules for this one.
 
-    switch (pname) {
-    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
-        return JS::Int32Value(objectType);
-
-    case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
-        if (objectType == LOCAL_GL_NONE) {
+        if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE) {
+            mContext->ErrorInvalidOperation("%s: Querying"
+                                            " FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE"
+                                            " against DEPTH_STENCIL_ATTACHMENT is an"
+                                            " error.",
+                                            funcName);
             return JS::NullValue();
         }
 
-        if (objectType == LOCAL_GL_RENDERBUFFER) {
-            const WebGLRenderbuffer* rb = fba.Renderbuffer();
-            return mContext->WebGLObjectAsJSValue(cx, rb, *out_error);
+        if (AttachmentsDontMatch(DepthAttachment(), StencilAttachment())) {
+            mContext->ErrorInvalidOperation("%s: DEPTH_ATTACHMENT and STENCIL_ATTACHMENT"
+                                            " have different objects bound.",
+                                            funcName);
+            return JS::NullValue();
         }
 
-        /* If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is TEXTURE, then */
-        if (objectType == LOCAL_GL_TEXTURE) {
-            const WebGLTexture* tex = fba.Texture();
-            return mContext->WebGLObjectAsJSValue(cx, tex, *out_error);
-        }
-        break;
+        attachPoint = GetAttachPoint(LOCAL_GL_DEPTH_ATTACHMENT);
     }
 
-    if (objectType == LOCAL_GL_NONE) {
-        mContext->ErrorInvalidOperation("getFramebufferAttachmentParameter: No "
-                                        "attachment at %s",
-                                        mContext->EnumName(attachment));
-        return JS::NullValue();
-    }
-
-    return fba.GetParameter(mContext, target, attachment, pname);
+    return attachPoint->GetParameter(funcName, mContext, cx, target, attachment, pname,
+                                     out_error);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Goop.
 
 JSObject*
 WebGLFramebuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
@@ -1043,16 +1051,37 @@ ImplCycleCollectionTraverse(nsCycleColle
                             mozilla::WebGLFBAttachPoint& field,
                             const char* name,
                             uint32_t flags = 0)
 {
     CycleCollectionNoteChild(callback, field.Texture(), name, flags);
     CycleCollectionNoteChild(callback, field.Renderbuffer(), name, flags);
 }
 
+template<typename T>
+inline void
+ImplCycleCollectionUnlink(std::vector<T>& field)
+{
+    for (auto& cur : field) {
+        cur.Unlink();
+    }
+}
+
+template<typename T>
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
+                            std::vector<T>& field,
+                            const char* name,
+                            uint32_t flags = 0)
+{
+    for (auto& cur : field) {
+        ImplCycleCollectionTraverse(callback, cur, name, flags);
+    }
+}
+
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLFramebuffer,
                                       mColorAttachment0,
                                       mDepthAttachment,
                                       mStencilAttachment,
                                       mDepthStencilAttachment,
                                       mMoreColorAttachments)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLFramebuffer, AddRef)
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -24,26 +24,26 @@ class WebGLTexture;
 namespace gl {
     class GLContext;
 } // namespace gl
 
 class WebGLFBAttachPoint
 {
 public:
     WebGLFramebuffer* const mFB;
+    const GLenum mAttachmentPoint;
 private:
     WebGLRefPtr<WebGLTexture> mTexturePtr;
     WebGLRefPtr<WebGLRenderbuffer> mRenderbufferPtr;
-    FBAttachment mAttachmentPoint;
     TexImageTarget mTexImageTarget;
     GLint mTexImageLayer;
     GLint mTexImageLevel;
 
 public:
-    WebGLFBAttachPoint(WebGLFramebuffer* fb, FBAttachment attachmentPoint);
+    WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPoint);
     ~WebGLFBAttachPoint();
 
     void Unlink();
 
     bool IsDefined() const;
     bool IsDeleteRequested() const;
 
     const webgl::FormatUsageInfo* Format() const;
@@ -84,21 +84,21 @@ public:
     void SetImageDataStatus(WebGLImageDataStatus x);
 
     void Size(uint32_t* const out_width, uint32_t* const out_height) const;
     //const WebGLRectangleObject& RectangleObject() const;
 
     bool HasImage() const;
     bool IsComplete() const;
 
-    void FinalizeAttachment(gl::GLContext* gl,
-                            FBAttachment attachmentLoc) const;
+    void FinalizeAttachment(gl::GLContext* gl, GLenum attachmentLoc) const;
 
-    JS::Value GetParameter(WebGLContext* context, GLenum target, GLenum attachment,
-                           GLenum pname);
+    JS::Value GetParameter(const char* funcName, WebGLContext* webgl, JSContext* cx,
+                           GLenum target, GLenum attachment, GLenum pname,
+                           ErrorResult* const out_error);
 
     void OnBackingStoreRespecified() const;
 };
 
 class WebGLFramebuffer final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLFramebuffer>
     , public LinkedListElement<WebGLFramebuffer>
@@ -117,17 +117,26 @@ private:
 
     GLenum mReadBufferMode;
 
     // No need to chase pointers for the oft-used color0.
     WebGLFBAttachPoint mColorAttachment0;
     WebGLFBAttachPoint mDepthAttachment;
     WebGLFBAttachPoint mStencilAttachment;
     WebGLFBAttachPoint mDepthStencilAttachment;
-    nsTArray<WebGLFBAttachPoint> mMoreColorAttachments;
+    std::vector<WebGLFBAttachPoint> mMoreColorAttachments;
+
+    std::vector<GLenum> mDrawBuffers;
+
+    bool IsDrawBuffer(size_t n) const {
+        if (n < mDrawBuffers.size())
+            return bool(mDrawBuffers[n]);
+
+        return false;
+    }
 
 #ifdef ANDROID
     // Bug 1140459: Some drivers (including our test slaves!) don't
     // give reasonable answers for IsRenderbuffer, maybe others.
     // This shows up on Android 2.3 emulator.
     //
     // So we track the `is a Framebuffer` state ourselves.
     bool mIsFB;
@@ -141,24 +150,21 @@ private:
         DeleteOnce();
     }
 
     const WebGLRectangleObject& GetAnyRectObject() const;
 
 public:
     void Delete();
 
-    void FramebufferRenderbuffer(FBAttachment attachment, RBTarget rbtarget,
+    void FramebufferRenderbuffer(GLenum attachment, RBTarget rbtarget,
                                  WebGLRenderbuffer* rb);
-
-    void FramebufferTexture2D(FBAttachment attachment,
-                              TexImageTarget texImageTarget, WebGLTexture* tex,
-                              GLint level);
-
-    void FramebufferTextureLayer(FBAttachment attachment, WebGLTexture* tex, GLint level,
+    void FramebufferTexture2D(GLenum attachment, TexImageTarget texImageTarget,
+                              WebGLTexture* tex, GLint level);
+    void FramebufferTextureLayer(GLenum attachment, WebGLTexture* tex, GLint level,
                                  GLint layer);
 
     bool HasDefinedAttachments() const;
     bool HasIncompleteAttachments() const;
     bool AllImageRectsMatch() const;
     FBStatus PrecheckFramebufferStatus() const;
     FBStatus CheckFramebufferStatus() const;
 
@@ -166,39 +172,38 @@ public:
     GetFormatForAttachment(const WebGLFBAttachPoint& attachment) const;
 
     bool HasDepthStencilConflict() const {
         return int(mDepthAttachment.IsDefined()) +
                int(mStencilAttachment.IsDefined()) +
                int(mDepthStencilAttachment.IsDefined()) >= 2;
     }
 
-    size_t ColorAttachmentCount() const {
-        return 1 + mMoreColorAttachments.Length();
-    }
     const WebGLFBAttachPoint& ColorAttachment(size_t colorAttachmentId) const {
-        MOZ_ASSERT(colorAttachmentId < ColorAttachmentCount());
+        MOZ_ASSERT(colorAttachmentId < 1 + mMoreColorAttachments.size());
         return colorAttachmentId ? mMoreColorAttachments[colorAttachmentId - 1]
                                  : mColorAttachment0;
     }
 
     const WebGLFBAttachPoint& DepthAttachment() const {
         return mDepthAttachment;
     }
 
     const WebGLFBAttachPoint& StencilAttachment() const {
         return mStencilAttachment;
     }
 
     const WebGLFBAttachPoint& DepthStencilAttachment() const {
         return mDepthStencilAttachment;
     }
 
-    WebGLFBAttachPoint& GetAttachPoint(FBAttachment attachPointEnum);
+protected:
+    WebGLFBAttachPoint* GetAttachPoint(GLenum attachment); // Fallible
 
+public:
     void DetachTexture(const WebGLTexture* tex);
 
     void DetachRenderbuffer(const WebGLRenderbuffer* rb);
 
     WebGLContext* GetParentObject() const {
         return mContext;
     }
 
@@ -209,28 +214,24 @@ public:
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLFramebuffer)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLFramebuffer)
 
     // mask mirrors glClear.
     bool HasCompletePlanes(GLbitfield mask);
 
     bool CheckAndInitializeAttachments();
 
-    bool CheckColorAttachmentNumber(FBAttachment attachment,
-                                    const char* funcName) const;
-
-    void EnsureColorAttachPoints(size_t colorAttachmentId);
-
     void InvalidateFramebufferStatus() const {
         mIsKnownFBComplete = false;
     }
 
     bool ValidateForRead(const char* info,
                          const webgl::FormatUsageInfo** const out_format,
                          uint32_t* const out_width, uint32_t* const out_height);
 
-    JS::Value GetAttachmentParameter(JSContext* cx, GLenum target, GLenum attachment,
-                                     GLenum pname, ErrorResult* const out_error);
+    JS::Value GetAttachmentParameter(const char* funcName, JSContext* cx, GLenum target,
+                                     GLenum attachment, GLenum pname,
+                                     ErrorResult* const out_error);
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_FRAMEBUFFER_H_
--- a/dom/canvas/WebGLRenderbuffer.cpp
+++ b/dom/canvas/WebGLRenderbuffer.cpp
@@ -215,21 +215,21 @@ WebGLRenderbuffer::RenderbufferStorage(G
     mHeight = height;
     mImageDataStatus = WebGLImageDataStatus::UninitializedImageData;
     mIsUsingSecondary = bool(secondaryFormat);
 
     InvalidateStatusOfAttachedFBs();
 }
 
 void
-WebGLRenderbuffer::FramebufferRenderbuffer(FBAttachment attachment) const
+WebGLRenderbuffer::FramebufferRenderbuffer(GLenum attachment) const
 {
     gl::GLContext* gl = mContext->gl;
     if (attachment != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachment.get(),
+        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachment,
                                      LOCAL_GL_RENDERBUFFER, mPrimaryRB);
         return;
     }
 
     GLuint stencilRB = mPrimaryRB;
     if (mIsUsingSecondary) {
         MOZ_ASSERT(mSecondaryRB);
         stencilRB = mSecondaryRB;
--- a/dom/canvas/WebGLRenderbuffer.h
+++ b/dom/canvas/WebGLRenderbuffer.h
@@ -54,17 +54,17 @@ public:
 
     WebGLContext* GetParentObject() const {
         return mContext;
     }
 
     void BindRenderbuffer() const;
     void RenderbufferStorage(GLsizei samples, const webgl::FormatUsageInfo* format,
                              GLsizei width, GLsizei height);
-    void FramebufferRenderbuffer(FBAttachment attachment) const;
+    void FramebufferRenderbuffer(GLenum attachment) const;
     // Only handles a subset of `pname`s.
     GLint GetRenderbufferParameter(RBTarget target, RBParam pname) const;
 
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLRenderbuffer)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLRenderbuffer)
 
--- a/dom/canvas/WebGLStrongTypes.h
+++ b/dom/canvas/WebGLStrongTypes.h
@@ -142,17 +142,16 @@ public:
         MOZ_ASSERT(IsValueLegal(mValue));
     }
 
     GLenum get() const {
         MOZ_ASSERT(mValue != NonexistantGLenum);
         return mValue;
     }
 
-
     bool operator==(const StrongGLenum& other) const {
         return get() == other.get();
     }
 
     bool operator!=(const StrongGLenum& other) const {
         return get() != other.get();
     }
 
@@ -396,38 +395,16 @@ STRONG_GLENUM_BEGIN(FBTarget)
     STRONG_GLENUM_VALUE(FRAMEBUFFER),
 STRONG_GLENUM_END(FBTarget)
 
 STRONG_GLENUM_BEGIN(RBTarget)
     STRONG_GLENUM_VALUE(NONE),
     STRONG_GLENUM_VALUE(RENDERBUFFER),
 STRONG_GLENUM_END(RBTarget)
 
-STRONG_GLENUM_BEGIN(FBAttachment)
-    STRONG_GLENUM_VALUE(DEPTH_STENCIL_ATTACHMENT),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT0),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT1),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT2),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT3),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT4),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT5),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT6),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT7),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT8),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT9),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT10),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT11),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT12),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT13),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT14),
-    STRONG_GLENUM_VALUE(COLOR_ATTACHMENT15),
-    STRONG_GLENUM_VALUE(DEPTH_ATTACHMENT),
-    STRONG_GLENUM_VALUE(STENCIL_ATTACHMENT),
-STRONG_GLENUM_END(FBAttachment)
-
 STRONG_GLENUM_BEGIN(FBStatus)
     STRONG_GLENUM_VALUE(FRAMEBUFFER_COMPLETE),
     STRONG_GLENUM_VALUE(FRAMEBUFFER_INCOMPLETE_ATTACHMENT),
     STRONG_GLENUM_VALUE(FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT),
     STRONG_GLENUM_VALUE(FRAMEBUFFER_INCOMPLETE_DIMENSIONS),
     STRONG_GLENUM_VALUE(FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER),
     STRONG_GLENUM_VALUE(FRAMEBUFFER_INCOMPLETE_READ_BUFFER),
     STRONG_GLENUM_VALUE(FRAMEBUFFER_UNSUPPORTED),
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -403,23 +403,23 @@ TexImageTargetForTargetAndFace(TexTarget
         MOZ_CRASH();
     }
 }
 
 already_AddRefed<mozilla::layers::Image>
 ImageFromVideo(dom::HTMLVideoElement* elem);
 
 GLenum
-DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLenum internalFormat,
-           GLsizei width, GLsizei height, GLsizei depth, GLenum unpackFormat,
-           GLenum unpackType, const void* data);
+DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
+           const webgl::DriverUnpackInfo* dui, GLsizei width, GLsizei height,
+           GLsizei depth, const void* data);
 GLenum
 DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLint xOffset,
               GLint yOffset, GLint zOffset, GLsizei width, GLsizei height,
-              GLsizei depth, GLenum unpackFormat, GLenum unpackType, const void* data);
+              GLsizei depth, const webgl::PackingInfo& pi, const void* data);
 GLenum
 DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level,
                         GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
                         GLsizei height, GLsizei depth, GLenum sizedUnpackFormat,
                         GLsizei dataSize, const void* data);
 
 } // namespace mozilla
 
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -666,51 +666,51 @@ Is3D(TexImageTarget target)
         return true;
 
     default:
         MOZ_CRASH("bad target");
     }
 }
 
 GLenum
-DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLenum internalFormat,
-           GLsizei width, GLsizei height, GLsizei depth, GLenum unpackFormat,
-           GLenum unpackType, const void* data)
+DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
+           const webgl::DriverUnpackInfo* dui, GLsizei width, GLsizei height,
+           GLsizei depth, const void* data)
 {
     const GLint border = 0;
 
     gl::GLContext::LocalErrorScope errorScope(*gl);
 
     if (Is3D(target)) {
-        gl->fTexImage3D(target.get(), level, internalFormat, width, height, depth,
-                        border, unpackFormat, unpackType, data);
+        gl->fTexImage3D(target.get(), level, dui->internalFormat, width, height, depth,
+                        border, dui->unpackFormat, dui->unpackType, data);
     } else {
         MOZ_ASSERT(depth == 1);
-        gl->fTexImage2D(target.get(), level, internalFormat, width, height, border,
-                        unpackFormat, unpackType, data);
+        gl->fTexImage2D(target.get(), level, dui->internalFormat, width, height, border,
+                        dui->unpackFormat, dui->unpackType, data);
     }
 
     return errorScope.GetError();
 }
 
 GLenum
 DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLint xOffset,
               GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth,
-              GLenum unpackFormat, GLenum unpackType, const void* data)
+              const webgl::PackingInfo& pi, const void* data)
 {
     gl::GLContext::LocalErrorScope errorScope(*gl);
 
     if (Is3D(target)) {
         gl->fTexSubImage3D(target.get(), level, xOffset, yOffset, zOffset, width, height,
-                           depth, unpackFormat, unpackType, data);
+                           depth, pi.format, pi.type, data);
     } else {
         MOZ_ASSERT(zOffset == 0);
         MOZ_ASSERT(depth == 1);
         gl->fTexSubImage2D(target.get(), level, xOffset, yOffset, width, height,
-                           unpackFormat, unpackType, data);
+                           pi.format, pi.type, data);
     }
 
     return errorScope.GetError();
 }
 
 static inline GLenum
 DoCompressedTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
                      GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth,
--- a/dom/canvas/test/webgl-conformance/conformance/resources/webgl-test-utils.js
+++ b/dom/canvas/test/webgl-conformance/conformance/resources/webgl-test-utils.js
@@ -630,41 +630,16 @@ function create3DContextWithWrapperThatT
   }
   wrap.getError = function() {
       return context.getError();
   };
   return wrap;
 };
 
 /**
- * Tests that an evaluated expression generates a specific GL error.
- * @param {!WebGLContext} gl The WebGLContext to use.
- * @param {number} glError The expected gl error.
- * @param {string} evalSTr The string to evaluate.
- */
-var shouldGenerateGLError = function(gl, glError, evalStr) {
-  var exception;
-  try {
-    eval(evalStr);
-  } catch (e) {
-    exception = e;
-  }
-  if (exception) {
-    testFailed(evalStr + " threw exception " + exception);
-  } else {
-    var err = gl.getError();
-    if (err != glError) {
-      testFailed(evalStr + " expected: " + getGLErrorAsString(gl, glError) + ". Was " + getGLErrorAsString(gl, err) + ".");
-    } else {
-      testPassed(evalStr + " was expected value: " + getGLErrorAsString(gl, glError) + ".");
-    }
-  }
-};
-
-/**
  * Tests that the first error GL returns is the specified error.
  * @param {!WebGLContext} gl The WebGLContext to use.
  * @param {number} glError The expected gl error.
  * @param {string} opt_msg
  */
 var glErrorShouldBe = function(gl, glError, opt_msg) {
   opt_msg = opt_msg || "";
   var err = gl.getError();
@@ -673,19 +648,19 @@ var glErrorShouldBe = function(gl, glErr
                ". Was " + getGLErrorAsString(gl, err) + " : " + opt_msg);
   } else {
     testPassed("getError was expected value: " +
                 getGLErrorAsString(gl, glError) + " : " + opt_msg);
   }
 };
 
 /**
- * Tests that the first error GL returns is the specified error.
+ * Tests that the first error GL returns is in the specified error list.
  * @param {!WebGLContext} gl The WebGLContext to use.
- * @param {number} glError The expected gl error.
+ * @param {!Array.<number>} expectedErrorList The list of expected gl errors.
  * @param {string} opt_msg
  */
 var glErrorShouldBeIn = function(gl, expectedErrorList, opt_msg) {
   opt_msg = opt_msg || "";
 
   var expectedErrorStrList = [];
   var expectedErrorSet = {};
 
@@ -703,16 +678,56 @@ var glErrorShouldBeIn = function(gl, exp
     testPassed("getError was in expected values: " + expectedErrorStr + " : " + opt_msg);
   } else {
     testFailed("getError expected: " + expectedErrorStr +
                ". Was " + getGLErrorAsString(gl, actualError) + " : " + opt_msg);
   }
 };
 
 /**
+ * Tests that an evaluated expression generates a specific GL error.
+ * @param {!WebGLContext} gl The WebGLContext to use.
+ * @param {number} glError The expected gl error.
+ * @param {string} evalSTr The string to evaluate.
+ */
+var shouldGenerateGLError = function(gl, glError, evalStr) {
+  var exception;
+  try {
+    eval(evalStr);
+  } catch (e) {
+    exception = e;
+  }
+  if (exception) {
+    testFailed(evalStr + " threw exception " + exception);
+  } else {
+    glErrorShouldBe(gl, glError, evalStr);
+  }
+};
+
+/**
+ * Tests that an evaluated expression generates a GL error from a list.
+ * @param {!WebGLContext} gl The WebGLContext to use.
+ * @param {!Array.<number>} expectedErrorList The list of expected gl errors.
+ * @param {string} evalSTr The string to evaluate.
+ */
+var shouldGenerateGLErrorIn = function(gl, expectedErrorList, evalStr) {
+  var exception;
+  try {
+    eval(evalStr);
+  } catch (e) {
+    exception = e;
+  }
+  if (exception) {
+    testFailed(evalStr + " threw exception " + exception);
+  } else {
+    glErrorShouldBeIn(gl, expectedErrorList, evalStr);
+  }
+};
+
+/**
  * Links a WebGL program, throws if there are errors.
  * @param {!WebGLContext} gl The WebGLContext to use.
  * @param {!WebGLProgram} program The WebGLProgram to link.
  * @param {function(string): void) opt_errorCallback callback for errors.
  */
 var linkProgram = function(gl, program, opt_errorCallback) {
   errFn = opt_errorCallback || testFailed;
   // Link the program
--- a/gfx/gl/ScopedGLHelpers.cpp
+++ b/gfx/gl/ScopedGLHelpers.cpp
@@ -331,51 +331,51 @@ ScopedFramebufferForRenderbuffer::Unwrap
 
 /* ScopedViewportRect *********************************************************/
 
 ScopedViewportRect::ScopedViewportRect(GLContext* aGL,
                                        GLint x, GLint y,
                                        GLsizei width, GLsizei height)
   : ScopedGLWrapper<ScopedViewportRect>(aGL)
 {
-  mGL->fGetIntegerv(LOCAL_GL_VIEWPORT, mSavedViewportRect);
-  mGL->fViewport(x, y, width, height);
+    mGL->fGetIntegerv(LOCAL_GL_VIEWPORT, mSavedViewportRect);
+    mGL->fViewport(x, y, width, height);
 }
 
 void ScopedViewportRect::UnwrapImpl()
 {
-  mGL->fViewport(mSavedViewportRect[0],
-                 mSavedViewportRect[1],
-                 mSavedViewportRect[2],
-                 mSavedViewportRect[3]);
+    mGL->fViewport(mSavedViewportRect[0],
+                   mSavedViewportRect[1],
+                   mSavedViewportRect[2],
+                   mSavedViewportRect[3]);
 }
 
 /* ScopedScissorRect **********************************************************/
 
 ScopedScissorRect::ScopedScissorRect(GLContext* aGL,
                                      GLint x, GLint y,
                                      GLsizei width, GLsizei height)
   : ScopedGLWrapper<ScopedScissorRect>(aGL)
 {
-  mGL->fGetIntegerv(LOCAL_GL_SCISSOR_BOX, mSavedScissorRect);
-  mGL->fScissor(x, y, width, height);
+    mGL->fGetIntegerv(LOCAL_GL_SCISSOR_BOX, mSavedScissorRect);
+    mGL->fScissor(x, y, width, height);
 }
 
 ScopedScissorRect::ScopedScissorRect(GLContext* aGL)
   : ScopedGLWrapper<ScopedScissorRect>(aGL)
 {
-  mGL->fGetIntegerv(LOCAL_GL_SCISSOR_BOX, mSavedScissorRect);
+    mGL->fGetIntegerv(LOCAL_GL_SCISSOR_BOX, mSavedScissorRect);
 }
 
 void ScopedScissorRect::UnwrapImpl()
 {
-  mGL->fScissor(mSavedScissorRect[0],
-                mSavedScissorRect[1],
-                mSavedScissorRect[2],
-                mSavedScissorRect[3]);
+    mGL->fScissor(mSavedScissorRect[0],
+                  mSavedScissorRect[1],
+                  mSavedScissorRect[2],
+                  mSavedScissorRect[3]);
 }
 
 /* ScopedVertexAttribPointer **************************************************/
 
 ScopedVertexAttribPointer::ScopedVertexAttribPointer(GLContext* aGL,
                                                      GLuint index,
                                                      GLint size,
                                                      GLenum type,
@@ -557,17 +557,17 @@ ScopedUnpackAlignment::ScopedUnpackAlign
                scopedVal == 4 ||
                scopedVal == 8);
 
     gl->fGetIntegerv(LOCAL_GL_UNPACK_ALIGNMENT, &mOldVal);
 
     if (scopedVal != mOldVal) {
         gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, scopedVal);
     } else {
-      // Don't try to re-set it during unwrap.
+        // Don't try to re-set it during unwrap.
         mOldVal = 0;
     }
 }
 
 void
 ScopedUnpackAlignment::UnwrapImpl() {
     // Check that we're not falling out of scope after the current context changed.
     MOZ_ASSERT(mGL->IsCurrent());