Bug 1281098 - Fix UNPACK_ handling. - r=jrmuizel draft
authorJeff Gilbert <jgilbert@mozilla.com>
Wed, 22 Jun 2016 10:28:11 -0700
changeset 383823 8e4eb48949479a1bba9729879f6aa095066aaa3c
parent 383783 093a343e812cca7e7d6116ff7ae08d583c23f0d6
child 383824 d44a419040c57aa81970cafa3d7576821142e7ac
push id22104
push userbmo:jgilbert@mozilla.com
push dateTue, 05 Jul 2016 05:11:09 +0000
reviewersjrmuizel
bugs1281098
milestone50.0a1
Bug 1281098 - Fix UNPACK_ handling. - r=jrmuizel MozReview-Commit-ID: Au9eiKSxquJ
dom/canvas/TexUnpackBlob.cpp
dom/canvas/TexUnpackBlob.h
dom/canvas/WebGLContext.h
dom/canvas/WebGLTexture.h
dom/canvas/WebGLTextureUpload.cpp
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -14,16 +14,34 @@
 #include "nsLayoutUtils.h"
 #include "WebGLContext.h"
 #include "WebGLTexelConversions.h"
 #include "WebGLTexture.h"
 
 namespace mozilla {
 namespace webgl {
 
+TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, uint32_t alignment,
+                             uint32_t rowLength, uint32_t imageHeight, uint32_t width,
+                             uint32_t height, uint32_t depth, bool hasData)
+    : mAlignment(alignment)
+    , mRowLength(rowLength)
+    , mImageHeight(imageHeight)
+
+    , mSkipPixels(webgl->mPixelStore_UnpackSkipPixels)
+    , mSkipRows(webgl->mPixelStore_UnpackSkipRows)
+    , mSkipImages(webgl->mPixelStore_UnpackSkipImages)
+
+    , mWidth(width)
+    , mHeight(height)
+    , mDepth(depth)
+
+    , mHasData(hasData)
+{ }
+
 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)
 {
     if (isSubImage) {
         return DoTexSubImage(gl, target, level, xOffset, yOffset, zOffset, width, height,
                              depth, dui->ToPacking(), data);
@@ -43,42 +61,30 @@ TexUnpackBlob::OriginsForDOM(WebGLContex
     // Thus y-flip would give us bottom-left.
     *out_dst = webgl->mPixelStore_FlipY ? gl::OriginPos::BottomLeft
                                         : gl::OriginPos::TopLeft;
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 // TexUnpackBytes
 
-bool
-TexUnpackBytes::ValidateUnpack(WebGLContext* webgl, const char* funcName, bool isFunc3D,
-                               const webgl::PackingInfo& pi)
+static uint32_t
+FallbackOnZero(uint32_t val, uint32_t fallback)
 {
-    if (!mBytes)
-        return true;
+    return (val ? val : fallback);
+}
 
-    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 (mByteCount < bytesNeeded.value()) {
-        webgl->ErrorInvalidOperation("%s: Provided buffer is too small. (needs %u, has"
-                                     " %u)",
-                                     funcName, bytesNeeded.value(), mByteCount);
-        return false;
-    }
-
-    return true;
-}
+TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, uint32_t width, uint32_t height,
+                               uint32_t depth, const void* bytes)
+    : TexUnpackBlob(webgl, webgl->mPixelStore_UnpackAlignment,
+                    FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width),
+                    FallbackOnZero(webgl->mPixelStore_UnpackImageHeight, height),
+                    width, height, depth, bool(bytes))
+    , mBytes(bytes)
+{ }
 
 static bool
 UnpackFormatHasAlpha(GLenum unpackFormat)
 {
     switch (unpackFormat) {
     case LOCAL_GL_ALPHA:
     case LOCAL_GL_LUMINANCE_ALPHA:
     case LOCAL_GL_RGBA:
@@ -131,21 +137,21 @@ FormatFromPacking(const webgl::PackingIn
         case LOCAL_GL_RGB:              return WebGLTexelFormat::RGB32F;
         case LOCAL_GL_RGBA:             return WebGLTexelFormat::RGBA32F;
         }
     }
 
     return WebGLTexelFormat::FormatNotSupportingAnyConversion;
 }
 
-void
+bool
 TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                               WebGLTexture* tex, TexImageTarget target, GLint level,
                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                              GLint yOffset, GLint zOffset, GLenum* const out_glError)
+                              GLint yOffset, GLint zOffset, GLenum* const out_error) const
 {
     WebGLContext* webgl = tex->mContext;
     gl::GLContext* gl = webgl->gl;
 
     const void* uploadBytes = mBytes;
     UniqueBuffer tempBuffer;
 
     do {
@@ -162,122 +168,150 @@ TexUnpackBytes::TexOrSubImage(bool isSub
 
         bool needsAlphaPremult = webgl->mPixelStore_PremultiplyAlpha;
         if (!UnpackFormatHasAlpha(pi.format))
             needsAlphaPremult = false;
 
         if (!needsYFlip && !needsAlphaPremult)
             break;
 
+        ////////////
         // This is literally the worst.
+
+        if (mSkipPixels || mSkipRows || mSkipImages ||
+            mRowLength != mWidth ||
+            mImageHeight != mHeight)
+        {
+            webgl->ErrorInvalidOperation("%s: FLIP_Y and PREMULTIPLY_ALPHA are"
+                                         " incompatible with WebGL 2's new UNPACK_*"
+                                         " settings.",
+                                         funcName);
+            return false;
+        }
+
+        if (mDepth != 1) {
+            webgl->ErrorInvalidOperation("%s: FLIP_Y and PREMULTIPLY_ALPHA are"
+                                         " incompatible with 3D textures.",
+                                         funcName);
+            return false;
+        }
+
         webgl->GenerateWarning("%s: Uploading ArrayBuffers with FLIP_Y or"
                                " PREMULTIPLY_ALPHA is slow.",
                                funcName);
 
-        tempBuffer = malloc(mByteCount);
-        if (!tempBuffer) {
-            *out_glError = LOCAL_GL_OUT_OF_MEMORY;
-            return;
+        const auto bytesPerPixel = webgl::BytesPerPixel(pi);
+
+        const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel;
+        const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
+        const auto imageStride = rowStride * mImageHeight;
+
+        if (!imageStride.isValid()) {
+            webgl->ErrorOutOfMemory("%s: Invalid calculation during"
+                                    " FLIP_Y/PREMULTIPLY_ALPHA handling.",
+                                    funcName);
+            return false;
         }
 
-        const auto bytesPerPixel           = webgl::BytesPerPixel(pi);
-        const auto rowByteAlignment        = webgl->mPixelStore_UnpackAlignment;
-
-        const size_t bytesPerRow = bytesPerPixel * mWidth;
-        const size_t rowStride = RoundUpToMultipleOf(bytesPerRow, rowByteAlignment);
+        tempBuffer = malloc(imageStride.value());
+        if (!tempBuffer) {
+            webgl->ErrorOutOfMemory("%s: OOM during FLIP_Y/PREMULTIPLY_ALPHA handling.",
+                                    funcName);
+            return false;
+        }
 
         if (!needsAlphaPremult) {
             MOZ_ASSERT(needsYFlip);
 
             const uint8_t* src = (const uint8_t*)mBytes;
-            const uint8_t* const srcEnd = src + rowStride * mHeight;
+            const uint8_t* const srcEnd = src + rowStride.value() * mHeight;
 
-            uint8_t* dst = (uint8_t*)tempBuffer.get() + rowStride * (mHeight - 1);
+            uint8_t* dst = (uint8_t*)tempBuffer.get() + rowStride.value() * (mHeight - 1);
 
             while (src != srcEnd) {
-                memcpy(dst, src, bytesPerRow);
-                src += rowStride;
-                dst -= rowStride;
+                memcpy(dst, src, bytesPerRow.value());
+                src += rowStride.value();
+                dst -= rowStride.value();
             }
 
             uploadBytes = tempBuffer.get();
             break;
         }
 
         const auto texelFormat = FormatFromPacking(pi);
         if (texelFormat == WebGLTexelFormat::FormatNotSupportingAnyConversion) {
             MOZ_ASSERT(false, "Bad texelFormat from pi.");
-            *out_glError = LOCAL_GL_OUT_OF_MEMORY;
-            return;
+            webgl->ErrorOutOfMemory("%s: FormatFromPacking failed during"
+                                    " PREMULTIPLY_ALPHA handling.",
+                                    funcName);
+            return false;
         }
 
         const auto srcOrigin = gl::OriginPos::BottomLeft;
         const auto dstOrigin = (needsYFlip ? gl::OriginPos::TopLeft
                                            : gl::OriginPos::BottomLeft);
 
         const bool srcPremultiplied = false;
         const bool dstPremultiplied = needsAlphaPremult; // Always true here.
 
         // And go!:
         MOZ_ASSERT(srcOrigin != dstOrigin || srcPremultiplied != dstPremultiplied);
         bool unused_wasTrivial;
         if (!ConvertImage(mWidth, mHeight,
-                          mBytes, rowStride, srcOrigin, texelFormat, srcPremultiplied,
-                          tempBuffer.get(), rowStride, dstOrigin, texelFormat,
+                          mBytes, rowStride.value(), srcOrigin, texelFormat,
+                          srcPremultiplied,
+                          tempBuffer.get(), rowStride.value(), dstOrigin, texelFormat,
                           dstPremultiplied, &unused_wasTrivial))
         {
             MOZ_ASSERT(false, "ConvertImage failed unexpectedly.");
-            *out_glError = LOCAL_GL_OUT_OF_MEMORY;
-            return;
+            webgl->ErrorOutOfMemory("%s: ConvertImage failed during PREMULTIPLY_ALPHA"
+                                    " handling.",
+                                    funcName);
+            return false;
         }
 
         uploadBytes = tempBuffer.get();
     } while (false);
 
-    GLenum error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
-                                   zOffset, mWidth, mHeight, mDepth, uploadBytes);
-    *out_glError = error;
+    *out_error = DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
+                                 zOffset, mWidth, mHeight, mDepth, uploadBytes);
+    return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackImage
 
-TexUnpackImage::TexUnpackImage(const RefPtr<layers::Image>& image, bool isAlphaPremult)
-    : TexUnpackBlob(image->GetSize().width, image->GetSize().height, 1, true)
+TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, uint32_t imageHeight,
+                               uint32_t width, uint32_t height, uint32_t depth,
+                               const RefPtr<layers::Image>& image, bool isAlphaPremult)
+    : TexUnpackBlob(webgl, 0, image->GetSize().width, imageHeight, width, height, depth,
+                    true)
     , mImage(image)
     , mIsAlphaPremult(isAlphaPremult)
 { }
 
-TexUnpackImage::~TexUnpackImage()
-{ }
-
-void
+bool
 TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                               WebGLTexture* tex, TexImageTarget target, GLint level,
                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                              GLint yOffset, GLint zOffset, GLenum* const out_glError)
+                              GLint yOffset, GLint zOffset, GLenum* const out_error) const
 {
     MOZ_ASSERT_IF(needsRespec, !isSubImage);
-    *out_glError = 0;
 
     WebGLContext* webgl = tex->mContext;
 
     gl::GLContext* gl = webgl->GL();
     gl->MakeCurrent();
 
     if (needsRespec) {
-        GLenum error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
-                                       yOffset, zOffset, mWidth, mHeight, mDepth,
-                                       nullptr);
-        if (error) {
-            MOZ_ASSERT(!error);
-            *out_glError = LOCAL_GL_OUT_OF_MEMORY;
-            return;
-        }
+        *out_error = DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
+                                     yOffset, zOffset, mWidth, mHeight, mDepth,
+                                     nullptr);
+        return true;
     }
 
     do {
         if (dui->unpackFormat != LOCAL_GL_RGB && dui->unpackFormat != LOCAL_GL_RGBA)
             break;
 
         if (dui->unpackType != LOCAL_GL_UNSIGNED_BYTE)
             break;
@@ -304,39 +338,55 @@ TexUnpackImage::TexOrSubImage(bool isSub
 
         const gfx::IntSize destSize(mWidth, mHeight);
         if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, destSize, scopedFB.FB(),
                                                       dstOrigin))
         {
             break;
         }
 
-        return; // Blitting was successful, so we're done!
+        // Blitting was successful, so we're done!
+        *out_error = 0;
+        return true;
     } while (false);
 
     webgl->GenerateWarning("%s: Failed to hit GPU-copy fast-path. Falling back to CPU"
                            " upload.",
                            funcName);
 
     RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
     if (!surface) {
-        *out_glError = LOCAL_GL_OUT_OF_MEMORY;
-        return;
+        webgl->ErrorOutOfMemory("%s: GetAsSourceSurface failed after blit failed for"
+                                " TexUnpackImage.",
+                                funcName);
+        return false;
     }
 
-    TexUnpackSurface surfBlob(surface, mIsAlphaPremult);
+    TexUnpackSurface surfBlob(webgl, mImageHeight, mWidth, mHeight, mDepth, surface,
+                              mIsAlphaPremult);
 
-    surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level, dui,
-                           xOffset, yOffset, zOffset, out_glError);
+    return surfBlob.TexOrSubImage(isSubImage, needsRespec, funcName, tex, target, level,
+                                  dui, xOffset, yOffset, zOffset, out_error);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackSurface
 
+TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl, uint32_t imageHeight,
+                                   uint32_t width, uint32_t height, uint32_t depth,
+                                   gfx::SourceSurface* surf, bool isAlphaPremult)
+    : TexUnpackBlob(webgl, 0, surf->GetSize().width, imageHeight, width, height, depth,
+                    true)
+    , mSurf(surf)
+    , mIsAlphaPremult(isAlphaPremult)
+{ }
+
+//////////
+
 static bool
 GuessAlignment(const void* data, size_t bytesPerRow, size_t stride, size_t maxAlignment,
                size_t* const out_alignment)
 {
     size_t alignmentGuess = maxAlignment;
     while (alignmentGuess) {
         size_t guessStride = RoundUpToMultipleOf(bytesPerRow, alignmentGuess);
         if (guessStride == stride &&
@@ -740,88 +790,68 @@ TexUnpackSurface::ConvertSurface(WebGLCo
     *out_convertedAlignment = dstAlignment;
     *out_wasTrivial = wasTrivial;
     return true;
 }
 
 
 ////////////////////
 
-TexUnpackSurface::TexUnpackSurface(const RefPtr<gfx::SourceSurface>& surf,
-                                   bool isAlphaPremult)
-    : TexUnpackBlob(surf->GetSize().width, surf->GetSize().height, 1, true)
-    , mSurf(surf)
-    , mIsAlphaPremult(isAlphaPremult)
-{ }
-
-TexUnpackSurface::~TexUnpackSurface()
-{ }
-
-void
+bool
 TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                 WebGLTexture* tex, TexImageTarget target, GLint level,
                                 const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                                GLint yOffset, GLint zOffset, GLenum* const out_glError)
+                                GLint yOffset, GLint zOffset,
+                                GLenum* const out_error) const
 {
-    *out_glError = 0;
-
     WebGLContext* webgl = tex->mContext;
 
     // MakeCurrent is a big mess in here, because mapping (and presumably unmapping) on
     // OSX can lose our MakeCurrent. Therefore it's easiest to MakeCurrent just before we
     // call into GL, instead of trying to keep MakeCurrent-ed.
 
     RefPtr<gfx::DataSourceSurface> dataSurf = mSurf->GetDataSurface();
-
     if (!dataSurf) {
         // Since GetDataSurface didn't return error code, assume system
         // is out of memory
-        *out_glError = LOCAL_GL_OUT_OF_MEMORY;
-        return;
+        webgl->ErrorOutOfMemory("%s: OOM in GetDataSurface for TexUnpackSurface.",
+                                funcName);
+        return false;
     }
 
-    GLenum error;
     if (UploadDataSurface(isSubImage, webgl, target, level, dui, xOffset, yOffset,
-                          zOffset, mWidth, mHeight, dataSurf, mIsAlphaPremult, &error))
+                          zOffset, mWidth, mHeight, dataSurf, mIsAlphaPremult, out_error))
     {
-        return;
-    }
-    if (error == LOCAL_GL_OUT_OF_MEMORY) {
-        *out_glError = LOCAL_GL_OUT_OF_MEMORY;
-        return;
+        return true;
     }
 
     // CPU conversion. (++numCopies)
 
     UniqueBuffer convertedBuffer;
     uint8_t convertedAlignment;
     bool wasTrivial;
     bool outOfMemory;
     if (!ConvertSurface(webgl, dui, dataSurf, mIsAlphaPremult, &convertedBuffer,
                         &convertedAlignment, &wasTrivial, &outOfMemory))
     {
-        if (outOfMemory) {
-            *out_glError = LOCAL_GL_OUT_OF_MEMORY;
-        } else {
-            NS_ERROR("Failed to convert surface.");
-            *out_glError = LOCAL_GL_OUT_OF_MEMORY;
-        }
-        return;
+        webgl->ErrorOutOfMemory("%s: %s in ConvertSurface for TexUnpackSurface.",
+                                funcName, outOfMemory ? "OOM" : "Failure");
+        return false;
     }
 
     if (!wasTrivial) {
         webgl->GenerateWarning("%s: Chosen format/type incured an expensive reformat:"
                                " 0x%04x/0x%04x",
                                funcName, dui->unpackFormat, dui->unpackType);
     }
 
     MOZ_ALWAYS_TRUE( webgl->gl->MakeCurrent() );
     ScopedUnpackReset scopedReset(webgl);
     webgl->gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, convertedAlignment);
 
-    error = DoTexOrSubImage(isSubImage, webgl->gl, target.get(), level, dui, xOffset,
-                            yOffset, zOffset, mWidth, mHeight, mDepth,
-                            convertedBuffer.get());
-    *out_glError = error;
+    *out_error = DoTexOrSubImage(isSubImage, webgl->gl, target.get(), level, dui, xOffset,
+                                 yOffset, zOffset, mWidth, mHeight, mDepth,
+                                 convertedBuffer.get());
+    return true;
 }
 
 } // namespace webgl
 } // namespace mozilla
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -42,110 +42,92 @@ class ImageContainer;
 namespace webgl {
 
 struct PackingInfo;
 struct DriverUnpackInfo;
 
 class TexUnpackBlob
 {
 public:
-    const GLsizei mWidth;
-    const GLsizei mHeight;
-    const GLsizei mDepth;
+    const uint32_t mAlignment;
+    const uint32_t mRowLength;
+    const uint32_t mImageHeight;
+    const uint32_t mSkipPixels;
+    const uint32_t mSkipRows;
+    const uint32_t mSkipImages;
+    const uint32_t mWidth;
+    const uint32_t mHeight;
+    const uint32_t mDepth;
     const bool mHasData;
 
 protected:
-    TexUnpackBlob(GLsizei width, GLsizei height, GLsizei depth, bool hasData)
-        : mWidth(width)
-        , mHeight(height)
-        , mDepth(depth)
-        , mHasData(hasData)
-    { }
+    TexUnpackBlob(const WebGLContext* webgl, uint32_t alignment, uint32_t rowLength,
+                  uint32_t imageHeight, uint32_t width, uint32_t height, uint32_t depth,
+                  bool hasData);
 
 public:
-    virtual ~TexUnpackBlob() {}
-
-    virtual bool ValidateUnpack(WebGLContext* webgl, const char* funcName, bool isFunc3D,
-                                const webgl::PackingInfo& pi) = 0;
+    virtual ~TexUnpackBlob() { }
 
-    virtual void TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
-                               WebGLTexture* tex, TexImageTarget target, GLint level,
-                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                               GLint yOffset, GLint zOffset,
-                               GLenum* const out_glError) = 0;
+    virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
+                                 WebGLTexture* tex, TexImageTarget target, GLint level,
+                                 const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                                 GLint yOffset, GLint zOffset,
+                                 GLenum* const out_error) const = 0;
 
     static void OriginsForDOM(WebGLContext* webgl, gl::OriginPos* const out_src,
                               gl::OriginPos* const out_dst);
 };
 
 class TexUnpackBytes : public TexUnpackBlob
 {
 public:
-    const size_t mByteCount;
     const void* const mBytes;
 
-    TexUnpackBytes(GLsizei width, GLsizei height, GLsizei depth, size_t byteCount,
-                    const void* bytes)
-        : TexUnpackBlob(width, height, depth, bool(bytes))
-        , mByteCount(byteCount)
-        , mBytes(bytes)
-    { }
+    TexUnpackBytes(const WebGLContext* webgl, uint32_t width, uint32_t height,
+                   uint32_t depth, const void* bytes);
 
-    virtual bool ValidateUnpack(WebGLContext* webgl, const char* funcName, bool isFunc3D,
-                                const webgl::PackingInfo& pi) override;
-
-    virtual void TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
-                               WebGLTexture* tex, TexImageTarget target, GLint level,
-                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                               GLint yOffset, GLint zOffset,
-                               GLenum* const out_glError) override;
+    virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
+                                 WebGLTexture* tex, TexImageTarget target, GLint level,
+                                 const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                                 GLint yOffset, GLint zOffset,
+                                 GLenum* const out_error) const override;
 };
 
 class TexUnpackImage : public TexUnpackBlob
 {
 public:
     const RefPtr<layers::Image> mImage;
     const bool mIsAlphaPremult;
 
-    TexUnpackImage(const RefPtr<layers::Image>& image, bool isAlphaPremult);
-    virtual ~TexUnpackImage() override;
+    TexUnpackImage(const WebGLContext* webgl, uint32_t imageHeight, uint32_t width,
+                   uint32_t height, uint32_t depth, const RefPtr<layers::Image>& image,
+                   bool isAlphaPremult);
 
-    virtual bool ValidateUnpack(WebGLContext* webgl, const char* funcName, bool isFunc3D,
-                                const webgl::PackingInfo& pi) override
-    {
-        return true;
-    }
-
-    virtual void TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
-                               WebGLTexture* tex, TexImageTarget target, GLint level,
-                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                               GLint yOffset, GLint zOffset,
-                               GLenum* const out_glError) override;
+    virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
+                                 WebGLTexture* tex, TexImageTarget target, GLint level,
+                                 const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                                 GLint yOffset, GLint zOffset,
+                                 GLenum* const out_error) const override;
 };
 
 class TexUnpackSurface : public TexUnpackBlob
 {
 public:
     const RefPtr<gfx::SourceSurface> mSurf;
     const bool mIsAlphaPremult;
 
-    TexUnpackSurface(const RefPtr<gfx::SourceSurface>& surf, bool isAlphaPremult);
-    virtual ~TexUnpackSurface() override;
+    TexUnpackSurface(const WebGLContext* webgl, uint32_t imageHeight, uint32_t width,
+                     uint32_t height, uint32_t depth, gfx::SourceSurface* surf,
+                     bool isAlphaPremult);
 
-    virtual bool ValidateUnpack(WebGLContext* webgl, const char* funcName, bool isFunc3D,
-                                const webgl::PackingInfo& pi) override
-    {
-        return true;
-    }
-
-    virtual void TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
-                               WebGLTexture* tex, TexImageTarget target, GLint level,
-                               const webgl::DriverUnpackInfo* dui, GLint xOffset,
-                               GLint yOffset, GLint zOffset,
-                               GLenum* const out_glError) override;
+    virtual bool TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
+                                 WebGLTexture* tex, TexImageTarget target, GLint level,
+                                 const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                                 GLint yOffset, GLint zOffset,
+                                 GLenum* const out_error) const override;
 
 protected:
     static bool ConvertSurface(WebGLContext* webgl, const webgl::DriverUnpackInfo* dui,
                                gfx::DataSourceSurface* surf, bool isSurfAlphaPremult,
                                UniqueBuffer* const out_convertedBuffer,
                                uint8_t* const out_convertedAlignment,
                                bool* const out_wasTrivial, bool* const out_outOfMemory);
     static bool UploadDataSurface(bool isSubImage, WebGLContext* webgl,
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -935,32 +935,45 @@ public:
     inline void
     TexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, GLint yOffset,
                   GLenum unpackFormat, GLenum unpackType, T& elem, ErrorResult& out_error)
     {
         TexSubImage2D(texImageTarget, level, xOffset, yOffset, unpackFormat, unpackType,
                       &elem, &out_error);
     }
 
+    //////
     // WebGLTextureUpload.cpp
+public:
+    bool ValidateUnpackPixels(const char* funcName, uint32_t fullRows,
+                              uint32_t tailPixels, const webgl::TexUnpackBlob* blob);
+
+protected:
     bool ValidateTexImageSpecification(const char* funcName, uint8_t funcDims,
                                        GLenum texImageTarget, GLint level,
                                        GLsizei width, GLsizei height, GLsizei depth,
                                        GLint border,
                                        TexImageTarget* const out_target,
                                        WebGLTexture** const out_texture,
                                        WebGLTexture::ImageInfo** const out_imageInfo);
     bool ValidateTexImageSelection(const char* funcName, uint8_t funcDims,
                                    GLenum texImageTarget, GLint level, GLint xOffset,
                                    GLint yOffset, GLint zOffset, GLsizei width,
                                    GLsizei height, GLsizei depth,
                                    TexImageTarget* const out_target,
                                    WebGLTexture** const out_texture,
                                    WebGLTexture::ImageInfo** const out_imageInfo);
 
+    bool GetUnpackValuesForImage(const char* funcName, uint32_t srcImageWidth,
+                                 uint32_t srcImageHeight, uint32_t* const out_rowLength,
+                                 uint32_t* const out_imageHeight);
+
+    bool ValidateUnpackInfo(const char* funcName, GLenum format, GLenum type,
+                            webgl::PackingInfo* const out);
+
 // -----------------------------------------------------------------------------
 // Vertices Feature (WebGLContextVertices.cpp)
 public:
     void DrawArrays(GLenum mode, GLint first, GLsizei count);
     void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count,
                              GLsizei primcount);
     void DrawElements(GLenum mode, GLsizei count, GLenum type,
                       WebGLintptr byteOffset);
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -232,41 +232,44 @@ public:
                        dom::ImageData* imageData);
 
     void TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
                        GLint level, GLenum internalFormat, GLint xOffset, GLint yOffset,
                        GLint zOffset, GLenum unpackFormat, GLenum unpackType,
                        dom::Element* elem, ErrorResult* const out_error);
 
 protected:
-    void TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
-                       GLint level, GLenum internalFormat, GLint xOffset, GLint yOffset,
-                       GLint zOffset, GLint border, GLenum unpackFormat,
-                       GLenum unpackType, webgl::TexUnpackBlob* blob);
+    void TexOrSubImageBlob(bool isSubImage, const char* funcName, TexImageTarget target,
+                           GLint level, GLenum internalFormat, GLint xOffset,
+                           GLint yOffset, GLint zOffset,
+                           const webgl::PackingInfo& pi,
+                           const webgl::TexUnpackBlob* blob);
 
     bool ValidateTexImageSpecification(const char* funcName, TexImageTarget target,
-                                       GLint level, GLsizei width, GLsizei height,
-                                       GLsizei depth, GLint border,
+                                       GLint level, uint32_t width, uint32_t height,
+                                       uint32_t depth,
                                        WebGLTexture::ImageInfo** const out_imageInfo);
     bool ValidateTexImageSelection(const char* funcName, TexImageTarget target,
                                    GLint level, GLint xOffset, GLint yOffset,
-                                   GLint zOffset, GLsizei width, GLsizei height,
-                                   GLsizei depth,
+                                   GLint zOffset, uint32_t width, uint32_t height,
+                                   uint32_t depth,
                                    WebGLTexture::ImageInfo** const out_imageInfo);
 
+    bool ValidateUnpack(const char* funcName, const webgl::TexUnpackBlob* blob,
+                        bool isFunc3D, const webgl::PackingInfo& srcPI) const;
 public:
     void TexStorage(const char* funcName, TexTarget target, GLsizei levels,
                     GLenum sizedFormat, GLsizei width, GLsizei height, GLsizei depth);
 protected:
     void TexImage(const char* funcName, TexImageTarget target, GLint level,
-                  GLenum internalFormat, GLint border, GLenum unpackFormat,
-                  GLenum unpackType, webgl::TexUnpackBlob* blob);
+                  GLenum internalFormat, const webgl::PackingInfo& pi,
+                  const webgl::TexUnpackBlob* blob);
     void TexSubImage(const char* funcName, TexImageTarget target, GLint level,
-                     GLint xOffset, GLint yOffset, GLint zOffset, GLenum unpackFormat,
-                     GLenum unpackType, webgl::TexUnpackBlob* blob);
+                     GLint xOffset, GLint yOffset, GLint zOffset,
+                     const webgl::PackingInfo& pi, const webgl::TexUnpackBlob* blob);
 public:
     void CompressedTexImage(const char* funcName, TexImageTarget target, GLint level,
                             GLenum internalFormat, GLsizei width, GLsizei height,
                             GLsizei depth, GLint border,
                             const dom::ArrayBufferView& view);
     void CompressedTexSubImage(const char* funcName, TexImageTarget target, GLint level,
                                GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
                                GLsizei height, GLsizei depth, GLenum sizedUnpackFormat,
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -50,16 +50,43 @@ namespace mozilla {
  * CopyTexImage2D(texImageTarget, level, internalFormat, x, y, width, height, border)
  * CopyTexImage3D - "Because the framebuffer is inhererntly two-dimensional, there is no
  *                   CopyTexImage3D command."
  * CopyTexSubImage2D(texImageTarget, level, xOffset, yOffset, x, y, width, height)
  * CopyTexSubImage3D(texImageTarget, level, xOffset, yOffset, zOffset, x, y, width,
  *                   height)
  */
 
+static bool
+ValidateExtents(WebGLContext* webgl, const char* funcName, GLsizei width, GLsizei height,
+                GLsizei depth, GLint border, uint32_t* const out_width,
+                uint32_t* const out_height, uint32_t* const out_depth)
+{
+    // Check border
+    if (border != 0) {
+        webgl->ErrorInvalidValue("%s: `border` must be 0.", funcName);
+        return false;
+    }
+
+    if (width < 0 || height < 0 || depth < 0) {
+        /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
+         *   "If wt and ht are the specified image width and height,
+         *   and if either wt or ht are less than zero, then the error
+         *   INVALID_VALUE is generated."
+         */
+        webgl->ErrorInvalidValue("%s: `width`/`height`/`depth` must be >= 0.", funcName);
+        return false;
+    }
+
+    *out_width = width;
+    *out_height = height;
+    *out_depth = depth;
+    return true;
+}
+
 ////////////////////////////////////////
 // ArrayBufferView?
 
 static inline bool
 DoesJSTypeMatchUnpackType(GLenum unpackType, js::Scalar::Type jsType)
 {
     switch (unpackType) {
     case LOCAL_GL_BYTE:
@@ -93,224 +120,401 @@ DoesJSTypeMatchUnpackType(GLenum unpackT
     case LOCAL_GL_FLOAT:
         return jsType == js::Scalar::Type::Float32;
 
     default:
         return false;
     }
 }
 
-static bool
-ValidateUnpackArrayType(WebGLContext* webgl, const char* funcName, GLenum unpackType,
-                        js::Scalar::Type jsType)
+bool
+WebGLContext::ValidateUnpackPixels(const char* funcName, uint32_t fullRows,
+                                   uint32_t tailPixels, const webgl::TexUnpackBlob* blob)
 {
-    if (DoesJSTypeMatchUnpackType(unpackType, jsType))
-        return true;
+    const auto usedPixelsPerRow = CheckedUint32(blob->mSkipPixels) + blob->mWidth;
+    const auto usedRowsPerImage = CheckedUint32(blob->mSkipRows) + blob->mHeight;
+    const auto usedImages = CheckedUint32(blob->mSkipImages) + blob->mDepth;
 
-    const auto& fua = webgl->mFormatUsage;
-    const GLenum fakeUnpackFormat = LOCAL_GL_RGBA;
-    if (!fua->AreUnpackEnumsValid(fakeUnpackFormat, unpackType)) {
-        webgl->ErrorInvalidEnum("%s: Invalid unpack type: 0x%04x", funcName, unpackType);
+    if (!usedPixelsPerRow.isValid() ||
+        !usedRowsPerImage.isValid() ||
+        !usedImages.isValid())
+    {
+        ErrorOutOfMemory("%s: Invalid calculation for e.g. UNPACK_SKIP_PIXELS + width.",
+                         funcName);
         return false;
     }
 
-    webgl->ErrorInvalidOperation("%s: `pixels` be compatible with unpack `type`.",
-                                 funcName);
+    //////
+
+    if (usedPixelsPerRow.value() > blob->mRowLength ||
+        usedRowsPerImage.value() > blob->mImageHeight)
+    {
+        ErrorInvalidOperation("%s: UNPACK_ROW_LENGTH or UNPACK_IMAGE_HEIGHT too small.",
+                              funcName);
+        return false;
+    }
+
+    //////
+
+    auto fullRowsNeeded = (usedImages - 1) * blob->mImageHeight;
+    fullRowsNeeded += usedRowsPerImage - 1;
+
+    if (!fullRowsNeeded.isValid()) {
+        ErrorOutOfMemory("%s: Invalid calculation for required row count.",
+                         funcName);
+        return false;
+    }
+
+    if (fullRows > fullRowsNeeded.value())
+        return true;
+
+    if (fullRows == fullRowsNeeded.value() && tailPixels >= usedPixelsPerRow.value())
+        return true;
+
+    ErrorInvalidOperation("%s: Desired upload requires more data than is available: (%u"
+                          " rows plus %u pixels needed, %u rows plus %u pixels"
+                          " available)",
+                          funcName, fullRowsNeeded.value(), usedPixelsPerRow.value(),
+                          fullRows, tailPixels);
     return false;
 }
 
 static UniquePtr<webgl::TexUnpackBlob>
-UnpackBlobFromMaybeView(WebGLContext* webgl, const char* funcName, GLsizei width,
-                        GLsizei height, GLsizei depth, GLenum unpackType,
-                        const dom::Nullable<dom::ArrayBufferView>& maybeView)
+BlobFromView(WebGLContext* webgl, const char* funcName, uint32_t width, uint32_t height,
+             uint32_t depth, const webgl::PackingInfo& pi,
+             const dom::Nullable<dom::ArrayBufferView>& maybeView)
 {
-    size_t dataSize;
-    const void* data;
-    if (maybeView.IsNull()) {
-        dataSize = 0;
-        data = nullptr;
-    } else {
+    const void* bytes = nullptr;
+    uint32_t byteCount = 0;
+
+    if (!maybeView.IsNull()) {
         const auto& view = maybeView.Value();
-        view.ComputeLengthAndData();
-        data = view.DataAllowShared();
-        dataSize = view.LengthAllowShared();
-        js::Scalar::Type jsType = JS_GetArrayBufferViewType(view.Obj());
-        if (!ValidateUnpackArrayType(webgl, funcName, unpackType, jsType))
+
+        const auto jsType = JS_GetArrayBufferViewType(view.Obj());
+        if (!DoesJSTypeMatchUnpackType(pi.type, jsType)) {
+            webgl->ErrorInvalidOperation("%s: `pixels` must be compatible with `type`.",
+                                         funcName);
             return nullptr;
+        }
+
+        if (width && height && depth) {
+            view.ComputeLengthAndData();
+
+            bytes = view.DataAllowShared();
+            byteCount = view.LengthAllowShared();
+        }
     }
 
-    UniquePtr<webgl::TexUnpackBlob> ret;
-    // Warning: Possibly shared memory.  See bug 1225033.
-    ret.reset(new webgl::TexUnpackBytes(width, height, depth, dataSize, data));
-    return Move(ret);
+    UniquePtr<webgl::TexUnpackBlob> blob(new webgl::TexUnpackBytes(webgl, width, height,
+                                                                   depth, bytes));
+
+    //////
+
+    if (bytes) {
+        const auto bytesPerPixel = webgl::BytesPerPixel(pi);
+        const auto bytesPerRow = CheckedUint32(blob->mRowLength) * bytesPerPixel;
+        const auto rowStride = RoundUpToMultipleOf(bytesPerRow, blob->mAlignment);
+
+        const auto fullRows = byteCount / rowStride;
+        if (!fullRows.isValid()) {
+            webgl->ErrorOutOfMemory("%s: Unacceptable upload size calculated.");
+            return nullptr;
+        }
+
+        const auto bodyBytes = fullRows.value() * rowStride.value();
+        const auto tailPixels = (byteCount - bodyBytes) / bytesPerPixel;
+
+        if (!webgl->ValidateUnpackPixels(funcName, fullRows.value(), tailPixels,
+                                         blob.get()))
+        {
+            return nullptr;
+        }
+    }
+
+    //////
+
+    return Move(blob);
+}
+
+bool
+WebGLContext::ValidateUnpackInfo(const char* funcName, GLenum format, GLenum type,
+                                 webgl::PackingInfo* const out)
+{
+    if (!mFormatUsage->AreUnpackEnumsValid(format, type)) {
+        ErrorInvalidEnum("%s: Invalid unpack format/type: 0x%04x/0x%04x", funcName,
+                         format, type);
+        return false;
+    }
+
+    out->format = format;
+    out->type = type;
+    return true;
 }
 
 void
 WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
                             GLint level, GLenum internalFormat, GLint xOffset,
-                            GLint yOffset, GLint zOffset, GLsizei width, GLsizei height,
-                            GLsizei depth, GLint border, GLenum unpackFormat,
-                            GLenum unpackType,
+                            GLint yOffset, GLint zOffset, GLsizei rawWidth,
+                            GLsizei rawHeight, GLsizei rawDepth, GLint border,
+                            GLenum unpackFormat, GLenum unpackType,
                             const dom::Nullable<dom::ArrayBufferView>& maybeView)
 {
-    UniquePtr<webgl::TexUnpackBlob> blob;
-    blob = UnpackBlobFromMaybeView(mContext, funcName, width, height, depth, unpackType,
+    uint32_t width, height, depth;
+    if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, rawDepth, border,
+                         &width, &height, &depth))
+    {
+        return;
+    }
+
+    webgl::PackingInfo pi;
+    if (!mContext->ValidateUnpackInfo(funcName, unpackFormat, unpackType, &pi))
+        return;
+
+    const auto blob = BlobFromView(mContext, funcName, width, height, depth, pi,
                                    maybeView);
     if (!blob)
         return;
 
-    TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset, yOffset,
-                  zOffset, border, unpackFormat, unpackType, blob.get());
+    TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
+                      yOffset, zOffset, pi, blob.get());
 }
 
 ////////////////////////////////////////
 // ImageData
 
-static UniquePtr<webgl::TexUnpackBlob>
-UnpackBlobFromImageData(WebGLContext* webgl, const char* funcName, GLenum unpackType,
-                        dom::ImageData* imageData, dom::Uint8ClampedArray* scopedArr)
+static already_AddRefed<gfx::SourceSurface>
+FromImageData(WebGLContext* webgl, const char* funcName, GLenum unpackType,
+              dom::ImageData* imageData, dom::Uint8ClampedArray* scopedArr)
 {
-    if (!imageData) {
-        // Spec says to generate an INVALID_VALUE error
-        webgl->ErrorInvalidValue("%s: null ImageData", funcName);
-        return nullptr;
-    }
-
     DebugOnly<bool> inited = scopedArr->Init(imageData->GetDataObject());
     MOZ_ASSERT(inited);
 
     scopedArr->ComputeLengthAndData();
     const DebugOnly<size_t> dataSize = scopedArr->Length();
     const void* const data = scopedArr->Data();
 
     const gfx::IntSize size(imageData->Width(), imageData->Height());
     const size_t stride = size.width * 4;
     const gfx::SurfaceFormat surfFormat = gfx::SurfaceFormat::R8G8B8A8;
 
     MOZ_ASSERT(dataSize == stride * size.height);
 
     uint8_t* wrappableData = (uint8_t*)data;
 
-    const RefPtr<gfx::SourceSurface> surf =
+    RefPtr<gfx::SourceSurface> surf =
         gfx::Factory::CreateWrappingDataSourceSurface(wrappableData,
                                                       stride,
                                                       size,
                                                       surfFormat);
+    if (!surf) {
+        webgl->ErrorOutOfMemory("%s: OOM in FromImageData.", funcName);
+        return nullptr;
+    }
 
-    // WhatWG "HTML Living Standard" (30 October 2015):
-    // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
-    //  non-premultiplied alpha values."
-    const bool surfIsAlphaPremult = false;
+    return surf.forget();
+}
 
-    UniquePtr<webgl::TexUnpackBlob> ret;
-    ret.reset(new webgl::TexUnpackSurface(surf, surfIsAlphaPremult));
-    return Move(ret);
+bool
+WebGLContext::GetUnpackValuesForImage(const char* funcName, uint32_t srcImageWidth,
+                                      uint32_t srcImageHeight,
+                                      uint32_t* const out_rowLength,
+                                      uint32_t* const out_imageHeight)
+{
+    uint32_t rowLength = mPixelStore_UnpackRowLength;
+    if (!rowLength) {
+        rowLength = srcImageWidth;
+    } else if (rowLength != srcImageWidth) {
+        ErrorInvalidOperation("%s: UNPACK_ROW_LENGTH, if set, must be == width of"
+                              " object.");
+        return false;
+    }
+
+    uint32_t imageHeight = mPixelStore_UnpackImageHeight;
+    if (!imageHeight) {
+        imageHeight = srcImageHeight;
+    } else if (imageHeight > srcImageHeight) {
+        ErrorInvalidOperation("%s: UNPACK_IMAGE_HEIGHT, if set, must be <= height of"
+                              " object");
+        return false;
+    }
+
+    *out_rowLength = rowLength;
+    *out_imageHeight = imageHeight;
+    return true;
 }
 
 void
 WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
                             GLint level, GLenum internalFormat, GLint xOffset,
                             GLint yOffset, GLint zOffset, GLenum unpackFormat,
                             GLenum unpackType, dom::ImageData* imageData)
 {
+    webgl::PackingInfo pi;
+    if (!mContext->ValidateUnpackInfo(funcName, unpackFormat, unpackType, &pi))
+        return;
+
+    if (!imageData) {
+        // Spec says to generate an INVALID_VALUE error
+        mContext->ErrorInvalidValue("%s: Null ImageData.", funcName);
+        return;
+    }
+
+    // Eventually, these will be args.
+    const uint32_t width = imageData->Width();
+    const uint32_t height = imageData->Height();
+    const uint32_t depth = 1;
+
+    uint32_t rowLength, imageHeight;
+    if (!mContext->GetUnpackValuesForImage(funcName, imageData->Width(),
+                                           imageData->Height(), &rowLength, &imageHeight))
+    {
+        return;
+    }
+
     dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(
       nsContentUtils::RootingCxForThread());
-
-    UniquePtr<webgl::TexUnpackBlob> blob;
-    blob = UnpackBlobFromImageData(mContext, funcName, unpackType, imageData, &scopedArr);
-    if (!blob)
+    const RefPtr<gfx::SourceSurface> surf = FromImageData(mContext, funcName, unpackType,
+                                                          imageData, &scopedArr);
+    if (!surf)
         return;
 
-    const GLint border = 0;
-    TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset, yOffset,
-                  zOffset, border, unpackFormat, unpackType, blob.get());
+    // WhatWG "HTML Living Standard" (30 October 2015):
+    // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
+    //  non-premultiplied alpha values."
+    const bool surfIsAlphaPremult = false;
+
+    const webgl::TexUnpackSurface blob(mContext, imageHeight, width, height, depth, surf,
+                                       surfIsAlphaPremult);
+
+    const uint32_t fullRows = imageData->Height();
+    const uint32_t tailPixels = 0;
+    if (!mContext->ValidateUnpackPixels(funcName, fullRows, tailPixels, &blob))
+        return;
+
+    TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
+                      yOffset, zOffset, pi, &blob);
 }
 
 ////////////////////////////////////////
 // dom::Element
 
 void
 WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
                             GLint level, GLenum internalFormat, GLint xOffset,
                             GLint yOffset, GLint zOffset, GLenum unpackFormat,
                             GLenum unpackType, dom::Element* elem,
                             ErrorResult* const out_error)
 {
+    webgl::PackingInfo pi;
+    if (!mContext->ValidateUnpackInfo(funcName, unpackFormat, unpackType, &pi))
+        return;
+
     auto sfer = mContext->SurfaceFromElement(elem);
 
+    uint32_t elemWidth = 0;
+    uint32_t elemHeight = 0;
+    layers::Image* layersImage = nullptr;
+    if (!gfxPrefs::WebGLDisableDOMBlitUploads() && sfer.mLayersImage) {
+        layersImage = sfer.mLayersImage;
+        elemWidth = layersImage->GetSize().width;
+        elemHeight = layersImage->GetSize().height;
+    }
+
+    gfx::SourceSurface* surf = nullptr;
+    if (!layersImage && sfer.GetSourceSurface()) {
+        surf = sfer.GetSourceSurface();
+        elemWidth = surf->GetSize().width;
+        elemHeight = surf->GetSize().height;
+    }
+
+    // Eventually, these will be args.
+    const uint32_t width = elemWidth;
+    const uint32_t height = elemHeight;
+    const uint32_t depth = 1;
+
     // While it's counter-intuitive, the shape of the SFEResult API means that we should
     // try to pull out a surface first, and then, if we do pull out a surface, check
     // CORS/write-only/etc..
-    UniquePtr<webgl::TexUnpackBlob> blob;
-    const bool isAlphaPremult = sfer.mIsPremultiplied;
-
-    const auto& layersImage = sfer.mLayersImage;
-    if (layersImage && !gfxPrefs::WebGLDisableDOMBlitUploads()) {
-        blob.reset(new webgl::TexUnpackImage(layersImage, isAlphaPremult));
-    } else if (sfer.GetSourceSurface()) {
-        blob.reset(new webgl::TexUnpackSurface(sfer.GetSourceSurface(), isAlphaPremult));
+    if (!layersImage && !surf) {
+        webgl::TexUnpackBytes blob(mContext, width, height, depth, nullptr);
+        TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
+                          yOffset, zOffset, pi, &blob);
+        return;
     }
 
-    if (blob) {
-        if (!sfer.mCORSUsed) {
-            auto& srcPrincipal = sfer.mPrincipal;
-            nsIPrincipal* dstPrincipal = mContext->GetCanvas()->NodePrincipal();
+    //////
 
-            if (!dstPrincipal->Subsumes(srcPrincipal)) {
-                mContext->GenerateWarning("%s: Cross-origin elements require CORS.",
-                                          funcName);
-                out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
-                return;
-            }
-        }
+    if (!sfer.mCORSUsed) {
+        auto& srcPrincipal = sfer.mPrincipal;
+        nsIPrincipal* dstPrincipal = mContext->GetCanvas()->NodePrincipal();
 
-        if (sfer.mIsWriteOnly) {
-            // mIsWriteOnly defaults to true, and so will be true even if SFE merely
-            // failed. Thus we must test mIsWriteOnly after successfully retrieving an
-            // Image or SourceSurface.
-            mContext->GenerateWarning("%s: Element is write-only, thus cannot be"
-                                      " uploaded.",
+        if (!dstPrincipal->Subsumes(srcPrincipal)) {
+            mContext->GenerateWarning("%s: Cross-origin elements require CORS.",
                                       funcName);
             out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
             return;
         }
-    } else {
-        mContext->GenerateWarning("%s: Failed to get data from DOM element. Implicit"
-                                  " width and height for this upload will be zero.",
+    }
+
+    if (sfer.mIsWriteOnly) {
+        // mIsWriteOnly defaults to true, and so will be true even if SFE merely
+        // failed. Thus we must test mIsWriteOnly after successfully retrieving an
+        // Image or SourceSurface.
+        mContext->GenerateWarning("%s: Element is write-only, thus cannot be"
+                                  " uploaded.",
                                   funcName);
+        out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
+        return;
+    }
+
+    //////
+    // Ok, we're good!
 
-        const uint32_t width = 0;
-        const uint32_t height = 0;
-        const uint32_t depth = 1; // Implicit depth for DOM uploads is always 1.
-        const size_t byteCount = 0;
-        blob.reset(new webgl::TexUnpackBytes(width, height, depth, byteCount, nullptr));
+    uint32_t rowLength, imageHeight;
+    if (!mContext->GetUnpackValuesForImage(funcName, elemWidth, elemHeight, &rowLength,
+                                           &imageHeight))
+    {
+        return;
     }
-    MOZ_ASSERT(blob);
+
+    UniquePtr<const webgl::TexUnpackBlob> blob;
+    const bool isAlphaPremult = sfer.mIsPremultiplied;
 
-    const GLint border = 0;
-    TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset, yOffset,
-                  zOffset, border, unpackFormat, unpackType, blob.get());
+    if (layersImage) {
+        blob.reset(new webgl::TexUnpackImage(mContext, imageHeight, width, height, depth,
+                                             layersImage, isAlphaPremult));
+    } else {
+        MOZ_ASSERT(surf);
+        blob.reset(new webgl::TexUnpackSurface(mContext, imageHeight, width, height,
+                                               depth, surf, isAlphaPremult));
+    }
+
+    const uint32_t fullRows = elemHeight;
+    const uint32_t tailPixels = 0;
+    if (!mContext->ValidateUnpackPixels(funcName, fullRows, tailPixels, blob.get()))
+        return;
+
+    TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
+                      yOffset, zOffset, pi, blob.get());
 }
 
 
 //////////////////////////////////////////////////////////////////////////////////////////
 
 void
-WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
-                            GLint level, GLenum internalFormat, GLint xOffset,
-                            GLint yOffset, GLint zOffset, GLint border,
-                            GLenum unpackFormat, GLenum unpackType,
-                            webgl::TexUnpackBlob* blob)
+WebGLTexture::TexOrSubImageBlob(bool isSubImage, const char* funcName,
+                                TexImageTarget target, GLint level, GLenum internalFormat,
+                                GLint xOffset, GLint yOffset, GLint zOffset,
+                                const webgl::PackingInfo& pi,
+                                const webgl::TexUnpackBlob* blob)
 {
     if (isSubImage) {
-        TexSubImage(funcName, target, level, xOffset, yOffset, zOffset, unpackFormat,
-                    unpackType, blob);
+        TexSubImage(funcName, target, level, xOffset, yOffset, zOffset, pi, blob);
     } else {
-        TexImage(funcName, target, level, internalFormat, border, unpackFormat,
-                 unpackType, blob);
+        TexImage(funcName, target, level, internalFormat, pi, blob);
     }
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////
 
 static bool
 ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture, const char* funcName,
@@ -329,51 +533,33 @@ ValidateTexImage(WebGLContext* webgl, We
     }
 
     WebGLTexture::ImageInfo& imageInfo = texture->ImageInfoAt(target, level);
 
     *out_imageInfo = &imageInfo;
     return true;
 }
 
-
 // For *TexImage*
 bool
 WebGLTexture::ValidateTexImageSpecification(const char* funcName, TexImageTarget target,
-                                            GLint level, GLsizei width, GLsizei height,
-                                            GLsizei depth, GLint border,
+                                            GLint level, uint32_t width, uint32_t height,
+                                            uint32_t depth,
                                             WebGLTexture::ImageInfo** const out_imageInfo)
 {
     if (mImmutable) {
         mContext->ErrorInvalidOperation("%s: Specified texture is immutable.", funcName);
         return false;
     }
 
     // Do this early to validate `level`.
     WebGLTexture::ImageInfo* imageInfo;
     if (!ValidateTexImage(mContext, this, funcName, target, level, &imageInfo))
         return false;
 
-    // Check border
-    if (border != 0) {
-        mContext->ErrorInvalidValue("%s: `border` must be 0.", funcName);
-        return false;
-    }
-
-    if (width < 0 || height < 0 || depth < 0) {
-        /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
-         *   "If wt and ht are the specified image width and height,
-         *   and if either wt or ht are less than zero, then the error
-         *   INVALID_VALUE is generated."
-         */
-        mContext->ErrorInvalidValue("%s: `width`/`height`/`depth` must be >= 0.",
-                                    funcName);
-        return false;
-    }
-
     if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP &&
         width != height)
     {
         mContext->ErrorInvalidValue("%s: Cube map images must be square.", funcName);
         return false;
     }
 
     /* GLES 3.0.4, p133-134:
@@ -414,19 +600,19 @@ WebGLTexture::ValidateTexImageSpecificat
 
     default: // cube maps
         MOZ_ASSERT(IsCubeMap());
         maxWidthHeight = mContext->mImplMaxCubeMapTextureSize >> level;
         maxDepth = 1;
         break;
     }
 
-    if (uint32_t(width) > maxWidthHeight ||
-        uint32_t(height) > maxWidthHeight ||
-        uint32_t(depth) > maxDepth)
+    if (width > maxWidthHeight ||
+        height > maxWidthHeight ||
+        depth > maxDepth)
     {
         mContext->ErrorInvalidValue("%s: Requested size at this level is unsupported.",
                                     funcName);
         return false;
     }
 
     {
         /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
@@ -434,42 +620,40 @@ WebGLTexture::ValidateTexImageSpecificat
          *   height is not a power-of-two, the error INVALID_VALUE is
          *   generated."
          *
          * This restriction does not apply to GL ES Version 3.0+.
          */
         bool requirePOT = (!mContext->IsWebGL2() && level != 0);
 
         if (requirePOT) {
-            if (!IsPowerOfTwo(uint32_t(width)) || !IsPowerOfTwo(uint32_t(height))) {
+            if (!IsPowerOfTwo(width) || !IsPowerOfTwo(height)) {
                 mContext->ErrorInvalidValue("%s: For level > 0, width and height must be"
                                             " powers of two.",
                                             funcName);
                 return false;
             }
         }
     }
 
     *out_imageInfo = imageInfo;
     return true;
 }
 
 // For *TexSubImage*
 bool
 WebGLTexture::ValidateTexImageSelection(const char* funcName, TexImageTarget target,
                                         GLint level, GLint xOffset, GLint yOffset,
-                                        GLint zOffset, GLsizei width, GLsizei height,
-                                        GLsizei depth,
+                                        GLint zOffset, uint32_t width, uint32_t height,
+                                        uint32_t depth,
                                         WebGLTexture::ImageInfo** const out_imageInfo)
 {
     // The conformance test wants bad arg checks before imageInfo checks.
-    if (xOffset < 0 || yOffset < 0 || zOffset < 0 ||
-        width < 0 || height < 0 || depth < 0)
-    {
-        mContext->ErrorInvalidValue("%s: Offsets and dimensions must be >=0.", funcName);
+    if (xOffset < 0 || yOffset < 0 || zOffset < 0) {
+        mContext->ErrorInvalidValue("%s: Offsets must be >=0.", funcName);
         return false;
     }
 
     WebGLTexture::ImageInfo* imageInfo;
     if (!ValidateTexImage(mContext, this, funcName, target, level, &imageInfo))
         return false;
 
     if (!imageInfo->IsDefined()) {
@@ -713,18 +897,20 @@ DoTexSubImage(gl::GLContext* gl, TexImag
     }
 
     return errorScope.GetError();
 }
 
 static inline GLenum
 DoCompressedTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
                      GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth,
-                     GLint border, GLsizei dataSize, const void* data)
+                     GLsizei dataSize, const void* data)
 {
+    const GLint border = 0;
+
     gl::GLContext::LocalErrorScope errorScope(*gl);
 
     if (Is3D(target)) {
         gl->fCompressedTexImage3D(target.get(), level, internalFormat, width, height,
                                   depth, border, dataSize, data);
     } else {
         MOZ_ASSERT(depth == 1);
         gl->fCompressedTexImage2D(target.get(), level, internalFormat, width, height,
@@ -753,19 +939,20 @@ DoCompressedTexSubImage(gl::GLContext* g
                                      height, sizedUnpackFormat, dataSize, data);
     }
 
     return errorScope.GetError();
 }
 
 static inline GLenum
 DoCopyTexImage2D(gl::GLContext* gl, TexImageTarget target, GLint level,
-                 GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height,
-                 GLint border)
+                 GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height)
 {
+    const GLint border = 0;
+
     gl::GLContext::LocalErrorScope errorScope(*gl);
 
     MOZ_ASSERT(!Is3D(target));
     gl->fCopyTexImage2D(target.get(), level, internalFormat, x, y, width, height,
                         border);
 
     return errorScope.GetError();
 }
@@ -923,21 +1110,20 @@ WebGLTexture::TexStorage(const char* fun
     if (!width || !height || !depth) {
         mContext->ErrorInvalidValue("%s: Dimensions must be non-zero.", funcName);
         return;
     }
 
     const TexImageTarget testTarget = IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X
                                                   : target.get();
     const GLint testLevel = 0;
-    const GLint border = 0;
 
     WebGLTexture::ImageInfo* testImageInfo;
     if (!ValidateTexImageSpecification(funcName, testTarget, testLevel, width, height,
-                                       depth, border, &testImageInfo))
+                                       depth, &testImageInfo))
     {
         return;
     }
     MOZ_ASSERT(testImageInfo);
     mozilla::Unused << testImageInfo;
 
     auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat);
     if (!dstUsage) {
@@ -1010,100 +1196,70 @@ WebGLTexture::TexStorage(const char* fun
     mImmutable = true;
 }
 
 ////////////////////////////////////////
 // Tex(Sub)Image
 
 void
 WebGLTexture::TexImage(const char* funcName, TexImageTarget target, GLint level,
-                       GLenum internalFormat, GLint border, GLenum unpackFormat,
-                       GLenum unpackType, webgl::TexUnpackBlob* blob)
+                       GLenum internalFormat, const webgl::PackingInfo& pi,
+                       const webgl::TexUnpackBlob* blob)
 {
     ////////////////////////////////////
     // Get dest info
 
     WebGLTexture::ImageInfo* imageInfo;
     if (!ValidateTexImageSpecification(funcName, target, level, blob->mWidth,
-                                       blob->mHeight, blob->mDepth, border, &imageInfo))
+                                       blob->mHeight, blob->mDepth, &imageInfo))
     {
         return;
     }
     MOZ_ASSERT(imageInfo);
 
-    const webgl::PackingInfo srcPacking = { unpackFormat, unpackType };
+    const auto& fua = mContext->mFormatUsage;
+    if (!fua->IsInternalFormatEnumValid(internalFormat)) {
+        mContext->ErrorInvalidValue("%s: Invalid internalformat: 0x%04x",
+                                    funcName, internalFormat);
+        return;
+    }
 
-    const auto& fua = mContext->mFormatUsage;
     auto dstUsage = fua->GetSizedTexUsage(internalFormat);
     if (!dstUsage) {
-        if (internalFormat != unpackFormat) {
-            if (!fua->AreUnpackEnumsValid(unpackFormat, unpackType)) {
-                mContext->ErrorInvalidEnum("%s: Invalid unpack format/type:"
-                                           " 0x%04x/0x%04x",
-                                           funcName, unpackFormat, unpackType);
-                return;
-            }
-
-            if (!fua->IsInternalFormatEnumValid(internalFormat)) {
-                mContext->ErrorInvalidValue("%s: Invalid internalformat: 0x%04x",
-                                            funcName, internalFormat);
-                return;
-            }
-
+        if (internalFormat != pi.format) {
             /* GL ES Version 3.0.4 - 3.8.3 Texture Image Specification
              *   "Specifying a combination of values for format, type, and
              *   internalformat that is not listed as a valid combination
              *   in tables 3.2 or 3.3 generates the error INVALID_OPERATION."
              */
             mContext->ErrorInvalidOperation("%s: Unsized internalFormat must match"
                                             " unpack format.",
                                             funcName);
             return;
         }
 
-        dstUsage = fua->GetUnsizedTexUsage(srcPacking);
+        dstUsage = fua->GetUnsizedTexUsage(pi);
     }
 
     if (!dstUsage) {
-        if (!fua->IsInternalFormatEnumValid(internalFormat)) {
-            mContext->ErrorInvalidValue("%s: Invalid internalformat: 0x%04x",
-                                        funcName, internalFormat);
-            return;
-        }
-
-        if (!fua->AreUnpackEnumsValid(unpackFormat, unpackType)) {
-            mContext->ErrorInvalidEnum("%s: Invalid unpack format/type:"
-                                       " 0x%04x/0x%04x",
-                                       funcName, unpackFormat, unpackType);
-            return;
-        }
-
         mContext->ErrorInvalidOperation("%s: Invalid internalformat/format/type:"
                                         " 0x%04x/0x%04x/0x%04x",
-                                        funcName, internalFormat, unpackFormat,
-                                        unpackType);
+                                        funcName, internalFormat, pi.format, pi.type);
         return;
     }
 
     const webgl::DriverUnpackInfo* driverUnpackInfo;
-    if (!dstUsage->IsUnpackValid(srcPacking, &driverUnpackInfo)) {
+    if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) {
         mContext->ErrorInvalidOperation("%s: Mismatched internalFormat and format/type:"
                                         " 0x%04x and 0x%04x/0x%04x",
-                                        funcName, internalFormat, unpackFormat,
-                                        unpackType);
+                                        funcName, internalFormat, pi.format, pi.type);
         return;
     }
 
     ////////////////////////////////////
-    // Get source info
-    const bool isFunc3D = Is3D(target);
-    if (!blob->ValidateUnpack(mContext, funcName, isFunc3D, srcPacking))
-        return;
-
-    ////////////////////////////////////
     // Check that source and dest info are compatible
     auto dstFormat = dstUsage->format;
 
     if (!ValidateTargetForFormat(funcName, mContext, target, dstFormat))
         return;
 
     if (!mContext->IsWebGL2() && dstFormat->hasDepth) {
         if (target != LOCAL_GL_TEXTURE_2D ||
@@ -1162,18 +1318,17 @@ WebGLTexture::TexImage(const char* funcN
     // Update our specification data.
 
     SetImageInfo(imageInfo, newImageInfo);
 }
 
 void
 WebGLTexture::TexSubImage(const char* funcName, TexImageTarget target, GLint level,
                           GLint xOffset, GLint yOffset, GLint zOffset,
-                          GLenum unpackFormat, GLenum unpackType,
-                          webgl::TexUnpackBlob* blob)
+                          const webgl::PackingInfo& pi, const webgl::TexUnpackBlob* blob)
 {
     ////////////////////////////////////
     // Get dest info
 
     WebGLTexture::ImageInfo* imageInfo;
     if (!ValidateTexImageSelection(funcName, target, level, xOffset, yOffset, zOffset,
                                    blob->mWidth, blob->mHeight, blob->mDepth, &imageInfo))
     {
@@ -1195,38 +1350,24 @@ WebGLTexture::TexSubImage(const char* fu
                                         " format %s.",
                                         funcName, dstFormat->name);
         return;
     }
 
     ////////////////////////////////////
     // Get source info
 
-    const webgl::PackingInfo srcPacking = { unpackFormat, unpackType };
     const webgl::DriverUnpackInfo* driverUnpackInfo;
-    if (!dstUsage->IsUnpackValid(srcPacking, &driverUnpackInfo)) {
-        const auto& fua = mContext->mFormatUsage;
-        if (!fua->AreUnpackEnumsValid(unpackFormat, unpackType)) {
-            mContext->ErrorInvalidEnum("%s: Invalid unpack format/type:"
-                                       " 0x%04x/0x%04x",
-                                       funcName, unpackFormat, unpackType);
-            return;
-        }
-
+    if (!dstUsage->IsUnpackValid(pi, &driverUnpackInfo)) {
         mContext->ErrorInvalidOperation("%s: Mismatched internalFormat and format/type:"
                                         " %s and 0x%04x/0x%04x",
-                                        funcName, dstFormat->name, unpackFormat,
-                                        unpackType);
+                                        funcName, dstFormat->name, pi.format, pi.type);
         return;
     }
 
-    const bool isFunc3D = Is3D(target);
-    if (!blob->ValidateUnpack(mContext, funcName, isFunc3D, srcPacking))
-        return;
-
     ////////////////////////////////////
     // Do the thing!
 
     mContext->gl->MakeCurrent();
 
     bool uploadWillInitialize;
     if (!EnsureImageDataInitializedForUpload(this, funcName, target, level, xOffset,
                                              yOffset, zOffset, blob->mWidth,
@@ -1264,26 +1405,33 @@ WebGLTexture::TexSubImage(const char* fu
     }
 }
 
 ////////////////////////////////////////
 // CompressedTex(Sub)Image
 
 void
 WebGLTexture::CompressedTexImage(const char* funcName, TexImageTarget target, GLint level,
-                                 GLenum internalFormat, GLsizei width, GLsizei height,
-                                 GLsizei depth, GLint border,
+                                 GLenum internalFormat, GLsizei rawWidth,
+                                 GLsizei rawHeight, GLsizei rawDepth, GLint border,
                                  const dom::ArrayBufferView& view)
 {
+    uint32_t width, height, depth;
+    if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, rawDepth, border,
+                         &width, &height, &depth))
+    {
+        return;
+    }
+
     ////////////////////////////////////
     // Get dest info
 
     WebGLTexture::ImageInfo* imageInfo;
     if (!ValidateTexImageSpecification(funcName, target, level, width, height, depth,
-                                       border, &imageInfo))
+                                       &imageInfo))
     {
         return;
     }
     MOZ_ASSERT(imageInfo);
 
     auto usage = mContext->mFormatUsage->GetSizedTexUsage(internalFormat);
     if (!usage) {
         mContext->ErrorInvalidEnum("%s: Invalid internalFormat: 0x%04x", funcName,
@@ -1325,17 +1473,17 @@ WebGLTexture::CompressedTexImage(const c
 
     ////////////////////////////////////
     // Do the thing!
 
     mContext->gl->MakeCurrent();
 
     // Warning: Possibly shared memory.  See bug 1225033.
     GLenum error = DoCompressedTexImage(mContext->gl, target, level, internalFormat,
-                                        width, height, depth, border, dataSize, data);
+                                        width, height, depth, dataSize, data);
     if (error == LOCAL_GL_OUT_OF_MEMORY) {
         mContext->ErrorOutOfMemory("%s: Ran out of memory during upload.", funcName);
         return;
     }
     if (error) {
         MOZ_RELEASE_ASSERT(false, "GFX: We should have caught all other errors.");
         mContext->GenerateWarning("%s: Unexpected error during texture upload. Context"
                                   " lost.",
@@ -1370,20 +1518,27 @@ IsSubImageBlockAligned(const webgl::Comp
         return false;
 
     return true;
 }
 
 void
 WebGLTexture::CompressedTexSubImage(const char* funcName, TexImageTarget target,
                                     GLint level, GLint xOffset, GLint yOffset,
-                                    GLint zOffset, GLsizei width, GLsizei height,
-                                    GLsizei depth, GLenum sizedUnpackFormat,
+                                    GLint zOffset, GLsizei rawWidth, GLsizei rawHeight,
+                                    GLsizei rawDepth, GLenum sizedUnpackFormat,
                                     const dom::ArrayBufferView& view)
 {
+    uint32_t width, height, depth;
+    if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, rawDepth, 0, &width,
+                         &height, &depth))
+    {
+        return;
+    }
+
     ////////////////////////////////////
     // Get dest info
 
     WebGLTexture::ImageInfo* imageInfo;
     if (!ValidateTexImageSelection(funcName, target, level, xOffset, yOffset, zOffset,
                                    width, height, depth, &imageInfo))
     {
         return;
@@ -1693,29 +1848,34 @@ ScopedCopyTexImageSource::~ScopedCopyTex
     gl->fDeleteRenderbuffers(1, &mRB);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 // There is no CopyTexImage3D.
 void
 WebGLTexture::CopyTexImage2D(TexImageTarget target, GLint level, GLenum internalFormat,
-                             GLint x, GLint y, GLsizei width, GLsizei height,
+                             GLint x, GLint y, GLsizei rawWidth, GLsizei rawHeight,
                              GLint border)
 {
     const char funcName[] = "CopyTexImage2D";
 
-    const uint8_t depth = 1;
-
     ////////////////////////////////////
     // Get dest info
 
+    uint32_t width, height, depth;
+    if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, 1, border, &width,
+                         &height, &depth))
+    {
+        return;
+    }
+
     WebGLTexture::ImageInfo* imageInfo;
     if (!ValidateTexImageSpecification(funcName, target, level, width, height, depth,
-                                       border, &imageInfo))
+                                       &imageInfo))
     {
         return;
     }
     MOZ_ASSERT(imageInfo);
 
     ////////////////////////////////////
     // Get source info
 
@@ -1795,18 +1955,18 @@ WebGLTexture::CopyTexImage2D(TexImageTar
     uint32_t writeX, writeY;
     uint32_t rwWidth, rwHeight;
     Intersect(srcWidth, x, width, &readX, &writeX, &rwWidth);
     Intersect(srcHeight, y, height, &readY, &writeY, &rwHeight);
 
     GLenum error;
     if (rwWidth == uint32_t(width) && rwHeight == uint32_t(height)) {
         MOZ_ASSERT(dstUsage->idealUnpack);
-        error = DoCopyTexImage2D(gl, target, level, dstUsage->idealUnpack->internalFormat, x, y, width, height,
-                                 border);
+        error = DoCopyTexImage2D(gl, target, level, dstUsage->idealUnpack->internalFormat,
+                                 x, y, width, height);
     } else {
         // 1. Zero the texture data.
         // 2. CopyTexSubImage the subrect.
 
         const bool respecifyTexture = true;
         const uint8_t zOffset = 0;
         if (!ZeroTextureData(mContext, funcName, respecifyTexture, mGLName, target, level,
                              dstUsage, 0, 0, zOffset, width, height, depth))
@@ -1846,19 +2006,24 @@ WebGLTexture::CopyTexImage2D(TexImageTar
     const bool isDataInitialized = true;
     const ImageInfo newImageInfo(dstUsage, width, height, depth, isDataInitialized);
     SetImageInfo(imageInfo, newImageInfo);
 }
 
 void
 WebGLTexture::CopyTexSubImage(const char* funcName, TexImageTarget target, GLint level,
                               GLint xOffset, GLint yOffset, GLint zOffset, GLint x,
-                              GLint y, GLsizei width, GLsizei height)
+                              GLint y, GLsizei rawWidth, GLsizei rawHeight)
 {
-    const GLsizei depth = 1;
+    uint32_t width, height, depth;
+    if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, 1, 0, &width,
+                         &height, &depth))
+    {
+        return;
+    }
 
     ////////////////////////////////////
     // Get dest info
 
     WebGLTexture::ImageInfo* imageInfo;
     if (!ValidateTexImageSelection(funcName, target, level, xOffset, yOffset, zOffset,
                                    width, height, depth, &imageInfo))
     {