Bug 1313541 - Rewrite TexImageSource glue. - r=ethlin draft
authorJeff Gilbert (:jgilbert) <jgilbert@mozilla.com>
Thu, 20 Oct 2016 18:03:40 -0700
changeset 431870 70324a90633dc5afdcf7f373b2a9f77743508e93
parent 431869 05747c01660609d0950bb4553dec66a91cff4131
child 431871 fdbcbc92c7e5fdf8256b853a328502c9ef6c1124
push id34137
push userbmo:jgilbert@mozilla.com
push dateMon, 31 Oct 2016 21:58:43 +0000
reviewersethlin
bugs1313541
milestone52.0a1
Bug 1313541 - Rewrite TexImageSource glue. - r=ethlin MozReview-Commit-ID: 4zwNrobTcUf
dom/canvas/TexUnpackBlob.cpp
dom/canvas/TexUnpackBlob.h
dom/canvas/WebGL2Context.h
dom/canvas/WebGL2ContextTextures.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextTextures.cpp
dom/canvas/WebGLTexture.h
dom/canvas/WebGLTextureUpload.cpp
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -126,16 +126,96 @@ FormatForPackingInfo(const PackingInfo& 
         break;
     }
 
     return WebGLTexelFormat::FormatNotSupportingAnyConversion;
 }
 
 ////////////////////
 
+static bool
+ValidateUnpackPixels(WebGLContext* webgl, const char* funcName, uint32_t fullRows,
+                     uint32_t tailPixels, webgl::TexUnpackBlob* blob)
+{
+    if (!blob->mWidth || !blob->mHeight || !blob->mDepth)
+        return true;
+
+    const auto usedPixelsPerRow = CheckedUint32(blob->mSkipPixels) + blob->mWidth;
+    if (!usedPixelsPerRow.isValid() || usedPixelsPerRow.value() > blob->mRowLength) {
+        webgl->ErrorInvalidOperation("%s: UNPACK_SKIP_PIXELS + width >"
+                                     " UNPACK_ROW_LENGTH.",
+                                     funcName);
+        return false;
+    }
+
+    if (blob->mHeight > blob->mImageHeight) {
+        webgl->ErrorInvalidOperation("%s: height > UNPACK_IMAGE_HEIGHT.", funcName);
+        return false;
+    }
+
+    //////
+
+    // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
+    auto skipFullRows = CheckedUint32(blob->mSkipImages) * blob->mImageHeight;
+    skipFullRows += blob->mSkipRows;
+
+    MOZ_ASSERT(blob->mDepth >= 1);
+    MOZ_ASSERT(blob->mHeight >= 1);
+    auto usedFullRows = CheckedUint32(blob->mDepth - 1) * blob->mImageHeight;
+    usedFullRows += blob->mHeight - 1; // Full rows in the final image, excluding the tail.
+
+    const auto fullRowsNeeded = skipFullRows + usedFullRows;
+    if (!fullRowsNeeded.isValid()) {
+        webgl->ErrorOutOfMemory("%s: Invalid calculation for required row count.",
+                                funcName);
+        return false;
+    }
+
+    if (fullRows > fullRowsNeeded.value())
+        return true;
+
+    if (fullRows == fullRowsNeeded.value() && tailPixels >= usedPixelsPerRow.value()) {
+        blob->mNeedsExactUpload = true;
+        return true;
+    }
+
+    webgl->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 bool
+ValidateUnpackBytes(WebGLContext* webgl, const char* funcName,
+                    const webgl::PackingInfo& pi, size_t availByteCount,
+                    webgl::TexUnpackBlob* blob)
+{
+    if (!blob->mWidth || !blob->mHeight || !blob->mDepth)
+        return true;
+
+    const auto bytesPerPixel = webgl::BytesPerPixel(pi);
+    const auto bytesPerRow = CheckedUint32(blob->mRowLength) * bytesPerPixel;
+    const auto rowStride = RoundUpToMultipleOf(bytesPerRow, blob->mAlignment);
+
+    const auto fullRows = availByteCount / rowStride;
+    if (!fullRows.isValid()) {
+        webgl->ErrorOutOfMemory("%s: Unacceptable upload size calculated.");
+        return false;
+    }
+
+    const auto bodyBytes = fullRows.value() * rowStride.value();
+    const auto tailPixels = (availByteCount - bodyBytes) / bytesPerPixel;
+
+    return ValidateUnpackPixels(webgl, funcName, fullRows.value(), tailPixels, blob);
+}
+
+////////////////////
+
 static uint32_t
 ZeroOn2D(TexImageTarget target, uint32_t val)
 {
     return (IsTarget3D(target) ? val : 0);
 }
 
 static uint32_t
 FallbackOnZero(uint32_t val, uint32_t fallback)
@@ -326,25 +406,36 @@ DoTexOrSubImage(bool isSubImage, gl::GLC
     }
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 // TexUnpackBytes
 
 TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target,
                                uint32_t width, uint32_t height, uint32_t depth,
-                               bool isClientData, const uint8_t* ptr)
+                               bool isClientData, const uint8_t* ptr, size_t availBytes)
     : TexUnpackBlob(webgl, target,
                     FallbackOnZero(webgl->mPixelStore_UnpackRowLength, width), width,
                     height, depth, false)
     , mIsClientData(isClientData)
     , mPtr(ptr)
+    , mAvailBytes(availBytes)
 { }
 
 bool
+TexUnpackBytes::Validate(WebGLContext* webgl, const char* funcName,
+                         const webgl::PackingInfo& pi)
+{
+    if (mIsClientData && !mPtr)
+        return true;
+
+    return ValidateUnpackBytes(webgl, funcName, pi, mAvailBytes, this);
+}
+
+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_error) const
 {
     WebGLContext* webgl = tex->mContext;
 
     const auto pi = dui->ToPacking();
@@ -466,16 +557,24 @@ TexUnpackImage::TexUnpackImage(const Web
                     isAlphaPremult)
     , mImage(image)
 { }
 
 TexUnpackImage::~TexUnpackImage()
 { }
 
 bool
+TexUnpackImage::Validate(WebGLContext* webgl, const char* funcName,
+                         const webgl::PackingInfo& pi)
+{
+    const auto& fullRows = mImage->GetSize().height;
+    return ValidateUnpackPixels(webgl, funcName, fullRows, 0, this);
+}
+
+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_error) const
 {
     MOZ_ASSERT_IF(needsRespec, !isSubImage);
 
     WebGLContext* webgl = tex->mContext;
@@ -621,16 +720,24 @@ GetFormatForSurf(gfx::SourceSurface* sur
     default:
         return false;
     }
 }
 
 //////////
 
 bool
+TexUnpackSurface::Validate(WebGLContext* webgl, const char* funcName,
+                           const webgl::PackingInfo& pi)
+{
+    const auto& fullRows = mSurf->GetSize().height;
+    return ValidateUnpackPixels(webgl, funcName, fullRows, 0, this);
+}
+
+bool
 TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec, const char* funcName,
                                 WebGLTexture* tex, TexImageTarget target, GLint level,
                                 const webgl::DriverUnpackInfo* dstDUI, GLint xOffset,
                                 GLint yOffset, GLint zOffset,
                                 GLenum* const out_error) const
 {
     WebGLContext* webgl = tex->mContext;
 
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -67,34 +67,40 @@ protected:
                          WebGLTexelFormat srcFormat,
                          const webgl::DriverUnpackInfo* dstDUI,
                          const uint8_t** const out_bytes,
                          UniqueBuffer* const out_anchoredBuffer) const;
 
 public:
     virtual bool HasData() const { return true; }
 
+    virtual bool Validate(WebGLContext* webgl, const char* funcName,
+                          const webgl::PackingInfo& pi) = 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;
 };
 
 class TexUnpackBytes final : public TexUnpackBlob
 {
 public:
     const bool mIsClientData;
     const uint8_t* const mPtr;
+    const size_t mAvailBytes;
 
     TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
-                   uint32_t height, uint32_t depth, bool isClientData, const uint8_t* ptr);
+                   uint32_t height, uint32_t depth, bool isClientData, const uint8_t* ptr,
+                   size_t availBytes);
 
     virtual bool HasData() const override { return !mIsClientData || bool(mPtr); }
 
+    virtual bool Validate(WebGLContext* webgl, const char* funcName,
+                          const webgl::PackingInfo& pi) 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 final : public TexUnpackBlob
@@ -103,32 +109,36 @@ public:
     const RefPtr<layers::Image> mImage;
 
     TexUnpackImage(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
                    uint32_t height, uint32_t depth, layers::Image* image,
                    bool isAlphaPremult);
 
     ~TexUnpackImage(); // Prevent needing to define layers::Image in the header.
 
+    virtual bool Validate(WebGLContext* webgl, const char* funcName,
+                          const webgl::PackingInfo& pi) 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 final : public TexUnpackBlob
 {
 public:
     const RefPtr<gfx::DataSourceSurface> mSurf;
 
     TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target, uint32_t width,
                      uint32_t height, uint32_t depth, gfx::DataSourceSurface* surf,
                      bool isAlphaPremult);
 
+    virtual bool Validate(WebGLContext* webgl, const char* funcName,
+                          const webgl::PackingInfo& pi) 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;
 };
 
 } // namespace webgl
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -83,94 +83,148 @@ public:
     void RenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat,
                                         GLsizei width, GLsizei height);
 
 
     // -------------------------------------------------------------------------
     // Texture objects - WebGL2ContextTextures.cpp
 
     void TexStorage2D(GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width,
-                      GLsizei height);
+                      GLsizei height)
+    {
+        const char funcName[] = "TexStorage2D";
+        const uint8_t funcDims = 2;
+        const GLsizei depth = 1;
+        TexStorage(funcName, funcDims, target, levels, internalFormat, width, height,
+                   depth);
+    }
+
     void TexStorage3D(GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width,
-                      GLsizei height, GLsizei depth);
+                      GLsizei height, GLsizei depth)
+    {
+        const char funcName[] = "TexStorage3D";
+        const uint8_t funcDims = 3;
+        TexStorage(funcName, funcDims, target, levels, internalFormat, width, height,
+                   depth);
+    }
+
+protected:
+    void TexStorage(const char* funcName, uint8_t funcDims, GLenum target, GLsizei levels,
+                    GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth);
+
+    ////////////////////////////////////
 
-    ////
+public:
+    template<typename T>
+    void CompressedTexImage3D(GLenum target, GLint level, GLenum internalFormat,
+                              GLsizei width, GLsizei height, GLsizei depth, GLint border,
+                              const T& anySrc, GLuint viewElemOffset = 0)
+    {
+        const char funcName[] = "compressedTexImage3D";
+        const uint8_t funcDims = 3;
+        const TexImageSourceAdapter src(anySrc, viewElemOffset);
+        CompressedTexImage(funcName, funcDims, target, level, internalFormat, width,
+                           height, depth, border, src);
+    }
 
-private:
+    template<typename T>
+    void CompressedTexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
+                                 GLint zOffset, GLsizei width, GLsizei height,
+                                 GLsizei depth, GLenum unpackFormat, const T& anySrc,
+                                 GLuint viewElemOffset = 0)
+    {
+        const char funcName[] = "compressedTexSubImage3D";
+        const uint8_t funcDims = 3;
+        const TexImageSourceAdapter src(anySrc, viewElemOffset);
+        CompressedTexSubImage(funcName, funcDims, target, level, xOffset, yOffset,
+                              zOffset, width, height, depth, unpackFormat, src);
+    }
+
+    ////////////////////////////////////
+
+    void CopyTexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
+                           GLint zOffset, GLint x, GLint y, GLsizei width,
+                           GLsizei height)
+    {
+        const char funcName[] = "copyTexSubImage3D";
+        const uint8_t funcDims = 3;
+        CopyTexSubImage(funcName, funcDims, target, level, xOffset, yOffset, zOffset,
+                        x, y, width, height);
+    }
+
+    ////////////////////////////////////
+
+    template<typename T>
     void TexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width,
                     GLsizei height, GLsizei depth, GLint border, GLenum unpackFormat,
-                    GLenum unpackType, const dom::ArrayBufferView* srcView,
-                    GLuint srcElemOffset);
-
-public:
-    void TexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width,
-                    GLsizei height, GLsizei depth, GLint border, GLenum unpackFormat,
-                    GLenum unpackType,
-                    const dom::Nullable<dom::ArrayBufferView>& maybeSrc, ErrorResult&)
+                    GLenum unpackType, const T& anySrc, ErrorResult& out_error)
     {
-        const dom::ArrayBufferView* srcView = nullptr;
-        if (!maybeSrc.IsNull()) {
-            srcView = &maybeSrc.Value();
-        }
+        const TexImageSourceAdapter src(anySrc, &out_error);
         TexImage3D(target, level, internalFormat, width, height, depth, border,
-                   unpackFormat, unpackType, srcView, 0);
+                   unpackFormat, unpackType, src);
     }
 
     void TexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width,
                     GLsizei height, GLsizei depth, GLint border, GLenum unpackFormat,
-                    GLenum unpackType, const dom::ArrayBufferView& srcView,
-                    GLuint srcElemOffset, ErrorResult&)
+                    GLenum unpackType, const dom::ArrayBufferView& view,
+                    GLuint viewElemOffset, ErrorResult&)
     {
+        const TexImageSourceAdapter src(view, viewElemOffset);
         TexImage3D(target, level, internalFormat, width, height, depth, border,
-                   unpackFormat, unpackType, &srcView, srcElemOffset);
+                   unpackFormat, unpackType, src);
     }
 
-    ////
+protected:
+    void TexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width,
+                    GLsizei height, GLsizei depth, GLint border, GLenum unpackFormat,
+                    GLenum unpackType, const TexImageSource& src)
+    {
+        const char funcName[] = "texImage3D";
+        const uint8_t funcDims = 3;
+        TexImage(funcName, funcDims, target, level, internalFormat, width, height, depth,
+                 border, unpackFormat, unpackType, src);
+    }
+
+    ////////////////////////////////////
+
+public:
+    template<typename T>
+    void TexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
+                       GLint zOffset, GLsizei width, GLsizei height, GLsizei depth,
+                       GLenum unpackFormat, GLenum unpackType, const T& anySrc,
+                       ErrorResult& out_error)
+    {
+        const TexImageSourceAdapter src(anySrc, &out_error);
+        TexSubImage3D(target, level, xOffset, yOffset, zOffset, width, height, depth,
+                      unpackFormat, unpackType, src);
+    }
 
     void TexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
                        GLint zOffset, GLsizei width, GLsizei height, GLsizei depth,
                        GLenum unpackFormat, GLenum unpackType,
                        const dom::ArrayBufferView& srcView, GLuint srcElemOffset,
-                       ErrorResult&);
-    void TexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
-                       GLint zOffset, GLenum unpackFormat, GLenum unpackType,
-                       const dom::ImageData& data, ErrorResult&);
-    void TexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
-                       GLint zOffset, GLenum unpackFormat, GLenum unpackType,
-                       const dom::Element& elem, ErrorResult& out_error);
-
-    ////
-
-    void CopyTexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
-                           GLint zOffset, GLint x, GLint y, GLsizei width,
-                           GLsizei height);
-
-    ////
+                       ErrorResult&)
+    {
+        const TexImageSourceAdapter src(srcView, srcElemOffset);
+        TexSubImage3D(target, level, xOffset, yOffset, zOffset, width, height, depth,
+                      unpackFormat, unpackType, src);
+    }
 
-    void CompressedTexImage3D(GLenum target, GLint level, GLenum internalFormat,
-                              GLsizei width, GLsizei height, GLsizei depth, GLint border,
-                              const dom::ArrayBufferView& srcView, GLuint srcElemOffset);
-    void CompressedTexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
-                                 GLint zOffset, GLsizei width, GLsizei height,
-                                 GLsizei depth, GLenum sizedUnpackFormat,
-                                 const dom::ArrayBufferView& srcView,
-                                 GLuint srcElemOffset);
-
-    ////////////////
-    // Texture PBOs
-
-    void TexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width,
-                    GLsizei height, GLsizei depth, GLint border, GLenum unpackFormat,
-                    GLenum unpackType, WebGLsizeiptr offset, ErrorResult&);
-
+protected:
     void TexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
                        GLint zOffset, GLsizei width, GLsizei height, GLsizei depth,
-                       GLenum unpackFormat, GLenum unpackType, WebGLsizeiptr offset,
-                       ErrorResult&);
+                       GLenum unpackFormat, GLenum unpackType, const TexImageSource& src)
+    {
+        const char funcName[] = "texSubImage3D";
+        const uint8_t funcDims = 3;
+        TexSubImage(funcName, funcDims, target, level, xOffset, yOffset, zOffset, width,
+                    height, depth, unpackFormat, unpackType, src);
+    }
 
+public:
     // -------------------------------------------------------------------------
     // Programs and shaders - WebGL2ContextPrograms.cpp
     GLint GetFragDataLocation(WebGLProgram* program, const nsAString& name);
 
 
     // -------------------------------------------------------------------------
     // Uniforms and attributes - WebGL2ContextUniforms.cpp
     void VertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset);
--- a/dom/canvas/WebGL2ContextTextures.cpp
+++ b/dom/canvas/WebGL2ContextTextures.cpp
@@ -6,318 +6,28 @@
 #include "GLContext.h"
 #include "WebGL2Context.h"
 #include "WebGLContextUtils.h"
 #include "WebGLTexture.h"
 
 namespace mozilla {
 
 void
-WebGL2Context::TexStorage2D(GLenum rawTexTarget, GLsizei levels, GLenum internalFormat,
-                            GLsizei width, GLsizei height)
+WebGL2Context::TexStorage(const char* funcName, uint8_t funcDims, GLenum rawTarget,
+                          GLsizei levels, GLenum internalFormat, GLsizei width,
+                          GLsizei height, GLsizei depth)
 {
-    const char funcName[] = "TexStorage2D";
-    const uint8_t funcDims = 2;
-
     TexTarget target;
     WebGLTexture* tex;
-    if (!ValidateTexTarget(this, funcName, funcDims, rawTexTarget, &target, &tex))
-        return;
-
-    const GLsizei depth = 1;
-    tex->TexStorage(funcName, target, levels, internalFormat, width, height, depth);
-}
-
-void
-WebGL2Context::TexStorage3D(GLenum rawTexTarget, GLsizei levels, GLenum internalFormat,
-                            GLsizei width, GLsizei height, GLsizei depth)
-{
-    const char funcName[] = "texStorage3D";
-    const uint8_t funcDims = 3;
-
-    TexTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexTarget(this, funcName, funcDims, rawTexTarget, &target, &tex))
+    if (!ValidateTexTarget(this, funcName, funcDims, rawTarget, &target, &tex))
         return;
 
     tex->TexStorage(funcName, target, levels, internalFormat, width, height, depth);
 }
 
-void
-WebGL2Context::TexImage3D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat,
-                          GLsizei width, GLsizei height, GLsizei depth, GLint border,
-                          GLenum unpackFormat, GLenum unpackType,
-                          const dom::ArrayBufferView* srcView, GLuint srcElemOffset)
-{
-    const char funcName[] = "texImage3D";
-    const uint8_t funcDims = 3;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = false;
-    const GLint xOffset = 0;
-    const GLint yOffset = 0;
-    const GLint zOffset = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, width, height, depth, border, unpackFormat,
-                       unpackType, srcView, srcElemOffset);
-}
-
-void
-WebGL2Context::TexSubImage3D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
-                             GLint yOffset, GLint zOffset, GLsizei width, GLsizei height,
-                             GLsizei depth, GLenum unpackFormat, GLenum unpackType,
-                             const dom::ArrayBufferView& srcView, GLuint srcElemOffset,
-                             ErrorResult&)
-{
-    const char funcName[] = "texSubImage3D";
-    const uint8_t funcDims = 3;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = true;
-    const GLenum internalFormat = 0;
-    const GLint border = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, width, height, depth, border, unpackFormat,
-                       unpackType, &srcView, srcElemOffset);
-}
-
-void
-WebGL2Context::TexSubImage3D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
-                             GLint yOffset, GLint zOffset, GLenum unpackFormat,
-                             GLenum unpackType, const dom::ImageData& imageData,
-                             ErrorResult&)
-{
-    const char funcName[] = "texSubImage3D";
-    const uint8_t funcDims = 3;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = true;
-    const GLenum internalFormat = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, unpackFormat, unpackType, imageData);
-}
-
-void
-WebGL2Context::TexSubImage3D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
-                             GLint yOffset, GLint zOffset, GLenum unpackFormat,
-                             GLenum unpackType, const dom::Element& elem,
-                             ErrorResult& out_rv)
-{
-    const char funcName[] = "texSubImage3D";
-    const uint8_t funcDims = 3;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = true;
-    const GLenum internalFormat = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, unpackFormat, unpackType, elem, &out_rv);
-}
-
-void
-WebGL2Context::CompressedTexImage3D(GLenum rawTexImageTarget, GLint level,
-                                    GLenum internalFormat, GLsizei width, GLsizei height,
-                                    GLsizei depth, GLint border,
-                                    const dom::ArrayBufferView& srcView,
-                                    GLuint srcElemOffset)
-{
-    const char funcName[] = "compressedTexImage3D";
-    const uint8_t funcDims = 3;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    tex->CompressedTexImage(funcName, target, level, internalFormat, width, height, depth,
-                            border, srcView, srcElemOffset);
-}
-
-void
-WebGL2Context::CompressedTexSubImage3D(GLenum rawTexImageTarget, GLint level,
-                                       GLint xOffset, GLint yOffset, GLint zOffset,
-                                       GLsizei width, GLsizei height, GLsizei depth,
-                                       GLenum sizedUnpackFormat,
-                                       const dom::ArrayBufferView& srcView,
-                                       GLuint srcElemOffset)
-{
-    const char funcName[] = "compressedTexSubImage3D";
-    const uint8_t funcDims = 3;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    tex->CompressedTexSubImage(funcName, target, level, xOffset, yOffset, zOffset, width,
-                               height, depth, sizedUnpackFormat, srcView, srcElemOffset);
-}
-
-void
-WebGL2Context::CopyTexSubImage3D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
-                                 GLint yOffset, GLint zOffset, GLint x, GLint y,
-                                 GLsizei width, GLsizei height)
-{
-    const char funcName[] = "copyTexSubImage3D";
-    const uint8_t funcDims = 3;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    tex->CopyTexSubImage(funcName, target, level, xOffset, yOffset, zOffset, x, y, width,
-                         height);
-}
-
-////////////////////
-
-void
-WebGLContext::TexImage2D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat,
-                         GLsizei width, GLsizei height, GLint border,
-                         GLenum unpackFormat, GLenum unpackType, WebGLsizeiptr offset,
-                         ErrorResult&)
-{
-    const char funcName[] = "texImage2D";
-    const uint8_t funcDims = 2;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = false;
-    const GLint xOffset = 0;
-    const GLint yOffset = 0;
-    const GLint zOffset = 0;
-    const GLsizei depth = 1;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, width, height, depth, border, unpackFormat,
-                       unpackType, offset);
-}
-
-void
-WebGLContext::TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
-                            GLint yOffset, GLsizei width, GLsizei height,
-                            GLenum unpackFormat, GLenum unpackType, WebGLsizeiptr offset,
-                            ErrorResult&)
-{
-    const char funcName[] = "texSubImage2D";
-    const uint8_t funcDims = 2;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = true;
-    const GLenum internalFormat = 0;
-    const GLint zOffset = 0;
-    const GLsizei depth = 1;
-    const GLint border = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, width, height, depth, border, unpackFormat,
-                       unpackType, offset);
-}
-
-//////////
-
-void
-WebGL2Context::TexImage3D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat,
-                          GLsizei width, GLsizei height, GLsizei depth, GLint border,
-                          GLenum unpackFormat, GLenum unpackType, WebGLsizeiptr offset,
-                          ErrorResult&)
-{
-    const char funcName[] = "texImage3D";
-    const uint8_t funcDims = 3;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = false;
-    const GLint xOffset = 0;
-    const GLint yOffset = 0;
-    const GLint zOffset = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, width, height, depth, border, unpackFormat,
-                       unpackType, offset);
-}
-
-void
-WebGL2Context::TexSubImage3D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
-                             GLint yOffset, GLint zOffset, GLsizei width, GLsizei height,
-                             GLsizei depth, GLenum unpackFormat, GLenum unpackType,
-                             WebGLsizeiptr offset, ErrorResult&)
-{
-    const char funcName[] = "texSubImage3D";
-    const uint8_t funcDims = 3;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = true;
-    const GLenum internalFormat = 0;
-    const GLint border = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, width, height, depth, border, unpackFormat,
-                       unpackType, offset);
-}
-
 ////////////////////
 
 /*virtual*/ bool
 WebGL2Context::IsTexParamValid(GLenum pname) const
 {
     switch (pname) {
     case LOCAL_GL_TEXTURE_BASE_LEVEL:
     case LOCAL_GL_TEXTURE_COMPARE_FUNC:
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -190,16 +190,72 @@ struct IndexedBufferBinding
     uint64_t mRangeStart;
     uint64_t mRangeSize;
 
     IndexedBufferBinding();
 
     uint64_t ByteCount() const;
 };
 
+////////////////////////////////////
+
+struct TexImageSource
+{
+    const dom::ArrayBufferView* mView;
+    GLuint mViewElemOffset;
+
+    const WebGLsizeiptr* mPboOffset;
+
+    const dom::ImageData* mImageData;
+
+    const dom::Element* mDomElem;
+    ErrorResult* mOut_error;
+
+protected:
+    TexImageSource() {
+        memset(this, 0, sizeof(*this));
+    }
+};
+
+////
+
+struct TexImageSourceAdapter final : public TexImageSource
+{
+    TexImageSourceAdapter(const dom::Nullable<dom::ArrayBufferView>& maybeView,
+                          ErrorResult*)
+    {
+        if (!maybeView.IsNull()) {
+            mView = &(maybeView.Value());
+        }
+    }
+
+    TexImageSourceAdapter(const dom::ArrayBufferView& view, ErrorResult*) {
+        mView = &view;
+    }
+
+    TexImageSourceAdapter(const dom::ArrayBufferView& view, GLuint viewElemOffset) {
+        mView = &view;
+        mViewElemOffset = viewElemOffset;
+    }
+
+    template<typename ignoredT>
+    TexImageSourceAdapter(WebGLsizeiptr pboOffset, ignoredT) {
+        mPboOffset = &pboOffset;
+    }
+
+    TexImageSourceAdapter(const dom::ImageData& imageData, ErrorResult*) {
+        mImageData = &imageData;
+    }
+
+    TexImageSourceAdapter(const dom::Element& domElem, ErrorResult* const out_error) {
+        mDomElem = &domElem;
+        mOut_error = out_error;
+    }
+};
+
 ////////////////////////////////////////////////////////////////////////////////
 
 class WebGLContext
     : public nsIDOMWebGLRenderingContext
     , public nsICanvasRenderingContextInternal
     , public nsSupportsWeakReference
     , public WebGLContextUnchecked
     , public WebGLRectangleObject
@@ -933,170 +989,224 @@ public:
 
 protected:
     JS::Value GetTexParameter(GLenum texTarget, GLenum pname);
     void TexParameter_base(GLenum texTarget, GLenum pname, GLint* maybeIntParam,
                            GLfloat* maybeFloatParam);
 
     virtual bool IsTexParamValid(GLenum pname) const;
 
-    // Upload funcs
-public:
-    void CompressedTexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat,
-                              GLsizei width, GLsizei height, GLint border,
-                              const dom::ArrayBufferView& view, GLuint srcElemOffset = 0);
-    void CompressedTexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset,
-                                 GLint yOffset, GLsizei width, GLsizei height,
-                                 GLenum unpackFormat, const dom::ArrayBufferView& view,
-                                 GLuint srcElemOffset = 0);
-
-    void CopyTexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat,
-                        GLint x, GLint y, GLsizei width, GLsizei height, GLint border);
-    void CopyTexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset,
-                           GLint yOffset, GLint x, GLint y, GLsizei width,
-                           GLsizei height);
+    ////////////////////////////////////
 
-    ////
-
-    void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat,
-                    GLsizei width, GLsizei height, GLint border, GLenum unpackFormat,
-                    GLenum unpackType,
-                    const dom::Nullable<dom::ArrayBufferView>& maybeView,
-                    ErrorResult&)
+public:
+    template<typename T>
+    void CompressedTexImage2D(GLenum target, GLint level, GLenum internalFormat,
+                              GLsizei width, GLsizei height, GLint border,
+                              const T& anySrc, GLuint viewElemOffset = 0)
     {
-        const dom::ArrayBufferView* view = nullptr;
-        if (!maybeView.IsNull()) {
-            view = &(maybeView.Value());
-        }
-
-        TexImage2D(texImageTarget, level, internalFormat, width, height, border,
-                   unpackFormat, unpackType, view, 0);
+        const char funcName[] = "compressedTexImage2D";
+        const uint8_t funcDims = 2;
+        const GLsizei depth = 1;
+        const TexImageSourceAdapter src(anySrc, viewElemOffset);
+        CompressedTexImage(funcName, funcDims, target, level, internalFormat, width,
+                           height, depth, border, src);
     }
 
-    void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat,
-                    GLsizei width, GLsizei height, GLint border, GLenum unpackFormat,
-                    GLenum unpackType, const dom::ArrayBufferView& srcView,
-                    GLuint srcElemOffset, ErrorResult&)
+    template<typename T>
+    void CompressedTexSubImage2D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
+                                 GLsizei width, GLsizei height, GLenum unpackFormat,
+                                 const T& anySrc, GLuint viewElemOffset = 0)
     {
-        TexImage2D(texImageTarget, level, internalFormat, width, height, border,
-                   unpackFormat, unpackType, &srcView, srcElemOffset);
+        const char funcName[] = "compressedTexSubImage2D";
+        const uint8_t funcDims = 2;
+        const GLint zOffset = 0;
+        const GLsizei depth = 1;
+        const TexImageSourceAdapter src(anySrc, viewElemOffset);
+        CompressedTexSubImage(funcName, funcDims, target, level, xOffset, yOffset,
+                              zOffset, width, height, depth, unpackFormat, src);
     }
 
 protected:
-    void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat,
-                    GLsizei width, GLsizei height, GLint border, GLenum unpackFormat,
-                    GLenum unpackType, const dom::ArrayBufferView* srcView,
-                    GLuint srcElemOffset);
+    void CompressedTexImage(const char* funcName, uint8_t funcDims, GLenum target,
+                            GLint level, GLenum internalFormat, GLsizei width,
+                            GLsizei height, GLsizei depth, GLint border,
+                            const TexImageSource& src);
+
+    void CompressedTexSubImage(const char* funcName, uint8_t funcDims, GLenum target,
+                               GLint level, GLint xOffset, GLint yOffset, GLint zOffset,
+                               GLsizei width, GLsizei height, GLsizei depth,
+                               GLenum unpackFormat, const TexImageSource& src);
+
+    ////////////////////////////////////
 
 public:
-    void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat,
-                    GLenum unpackFormat, GLenum unpackType,
-                    const dom::ImageData& imageData, ErrorResult& out_error);
-    void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat,
-                    GLenum unpackFormat, GLenum unpackType, const dom::Element& elem,
-                    ErrorResult& out_error);
-    void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat,
-                    GLsizei width, GLsizei height, GLint border, GLenum unpackFormat,
-                    GLenum unpackType, WebGLsizeiptr offset, ErrorResult&);
+    void CopyTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLint x,
+                        GLint y, GLsizei width, GLsizei height, GLint border);
+
+    void CopyTexSubImage2D(GLenum target, GLint level, GLint xOffset,
+                           GLint yOffset, GLint x, GLint y, GLsizei width,
+                           GLsizei height)
+    {
+        const char funcName[] = "copyTexSubImage2D";
+        const uint8_t funcDims = 2;
+        const GLint zOffset = 0;
+        CopyTexSubImage(funcName, funcDims, target, level, xOffset, yOffset, zOffset,
+                        x, y, width, height);
+    }
+
+protected:
+    void CopyTexSubImage(const char* funcName, uint8_t funcDims, GLenum target,
+                         GLint level, GLint xOffset, GLint yOffset, GLint zOffset,
+                         GLint x, GLint y, GLsizei width, GLsizei height);
+
+    ////////////////////////////////////
+    // TexImage
+
+    // Implicit width/height uploads
+
+public:
+    template<typename T>
+    void TexImage2D(GLenum target, GLint level, GLenum internalFormat,
+                    GLenum unpackFormat, GLenum unpackType, const T& src,
+                    ErrorResult& out_error)
+    {
+        GLsizei width = 0;
+        GLsizei height = 0;
+        GLint border = 0;
+        TexImage2D(target, level, internalFormat, width, height, border, unpackFormat,
+                   unpackType, src, out_error);
+    }
+
+    template<typename T>
+    void TexSubImage2D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
+                       GLenum unpackFormat, GLenum unpackType, const T& src,
+                       ErrorResult& out_error)
+    {
+        GLsizei width = 0;
+        GLsizei height = 0;
+        TexSubImage2D(target, level, xOffset, yOffset, width, height, unpackFormat,
+                      unpackType, src, out_error);
+    }
 
     ////
 
-    void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, GLint yOffset,
-                       GLenum unpackFormat, GLenum unpackType,
-                       const dom::ImageData& imageData, ErrorResult& out_error);
-    void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, GLint yOffset,
-                       GLenum unpackFormat, GLenum unpackType, const dom::Element& elem,
-                       ErrorResult& out_error);
+    template<typename T>
+    void TexImage2D(GLenum target, GLint level, GLenum internalFormat, GLsizei width,
+                    GLsizei height, GLint border, GLenum unpackFormat, GLenum unpackType,
+                    const T& anySrc, ErrorResult& out_error)
+    {
+        const TexImageSourceAdapter src(anySrc, &out_error);
+        TexImage2D(target, level, internalFormat, width, height, border, unpackFormat,
+                   unpackType, src);
+    }
 
-    void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, GLint yOffset,
-                       GLsizei width, GLsizei height, GLenum unpackFormat,
-                       GLenum unpackType, WebGLsizeiptr offset, ErrorResult&);
+    void TexImage2D(GLenum target, GLint level, GLenum internalFormat, GLsizei width,
+                    GLsizei height, GLint border, GLenum unpackFormat, GLenum unpackType,
+                    const dom::ArrayBufferView& view, GLuint viewElemOffset,
+                    ErrorResult&)
+    {
+        const TexImageSourceAdapter src(view, viewElemOffset);
+        TexImage2D(target, level, internalFormat, width, height, border, unpackFormat,
+                   unpackType, src);
+    }
 
-    ////////////////
-    // dom::ImageData
-
-    void TexImage2D(GLenum texImageTarget, GLint level, GLenum internalFormat,
-                    GLenum unpackFormat, GLenum unpackType,
-                    const dom::ImageData* imageData, ErrorResult& out_error)
+protected:
+    void TexImage2D(GLenum target, GLint level, GLenum internalFormat, GLsizei width,
+                    GLsizei height, GLint border, GLenum unpackFormat,
+                    GLenum unpackType, const TexImageSource& src)
     {
         const char funcName[] = "texImage2D";
-        if (IsContextLost())
-            return;
-
-        if (!imageData)
-            return ErrorInvalidValue("%s: `data` must not be null.", funcName);
-
-        TexImage2D(texImageTarget, level, internalFormat, unpackFormat, unpackType,
-                   *imageData, out_error);
+        const uint8_t funcDims = 2;
+        const GLsizei depth = 1;
+        TexImage(funcName, funcDims, target, level, internalFormat, width, height, depth,
+                 border, unpackFormat, unpackType, src);
     }
 
-    void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, GLint yOffset,
-                       GLenum unpackFormat, GLenum unpackType,
-                       const dom::ImageData* imageData, ErrorResult& out_error)
+    void TexImage(const char* funcName, uint8_t funcDims, GLenum target, GLint level,
+                  GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth,
+                  GLint border, GLenum unpackFormat, GLenum unpackType,
+                  const TexImageSource& src);
+
+    ////
+
+public:
+    template<typename T>
+    void TexSubImage2D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
+                       GLsizei width, GLsizei height, GLenum unpackFormat,
+                       GLenum unpackType, const T& anySrc, ErrorResult& out_error)
+    {
+        const TexImageSourceAdapter src(anySrc, &out_error);
+        TexSubImage2D(target, level, xOffset, yOffset, width, height, unpackFormat,
+                      unpackType, src);
+    }
+
+    void TexSubImage2D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
+                       GLsizei width, GLsizei height, GLenum unpackFormat,
+                       GLenum unpackType, const dom::ArrayBufferView& view,
+                       GLuint viewElemOffset, ErrorResult&)
+    {
+        const TexImageSourceAdapter src(view, viewElemOffset);
+        TexSubImage2D(target, level, xOffset, yOffset, width, height, unpackFormat,
+                      unpackType, src);
+    }
+
+protected:
+    void TexSubImage2D(GLenum target, GLint level, GLint xOffset, GLint yOffset,
+                       GLsizei width, GLsizei height, GLenum unpackFormat,
+                       GLenum unpackType, const TexImageSource& src)
     {
         const char funcName[] = "texSubImage2D";
-        if (!imageData) {
-            ErrorInvalidValue("%s: `data` must not be null.", funcName);
-            return;
-        }
-        TexSubImage2D(texImageTarget, level, xOffset, yOffset, unpackFormat, unpackType,
-                      *imageData, out_error);
+        const uint8_t funcDims = 2;
+        const GLint zOffset = 0;
+        const GLsizei depth = 1;
+        TexSubImage(funcName, funcDims, target, level, xOffset, yOffset, zOffset, width,
+                    height, depth, unpackFormat, unpackType, src);
     }
 
-    ////
-    // ArrayBufferView
-
-    void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, GLint yOffset,
-                       GLsizei width, GLsizei height, GLenum unpackFormat,
-                       GLenum unpackType,
-                       const dom::Nullable<dom::ArrayBufferView>& maybeSrc,
-                       ErrorResult& out_error)
-    {
-        const char funcName[] = "texSubImage2D";
-        if (IsContextLost())
-            return;
+    void TexSubImage(const char* funcName, uint8_t funcDims, GLenum target, GLint level,
+                     GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
+                     GLsizei height, GLsizei depth, GLenum unpackFormat,
+                     GLenum unpackType, const TexImageSource& src);
 
-        if (maybeSrc.IsNull())
-            return ErrorInvalidValue("%s: `data` must not be null.", funcName);
-
-        TexSubImage2D(texImageTarget, level, xOffset, yOffset, width, height,
-                      unpackFormat, unpackType, maybeSrc.Value(), 0, out_error);
-    }
-
-    void TexSubImage2D(GLenum texImageTarget, GLint level, GLint xOffset, GLint yOffset,
-                       GLsizei width, GLsizei height, GLenum unpackFormat,
-                       GLenum unpackType,
-                       const dom::ArrayBufferView& srcView, GLuint srcElemOffset,
-                       ErrorResult&);
-
-    //////
+    ////////////////////////////////////
     // WebGLTextureUpload.cpp
 public:
-    bool ValidateUnpackPixels(const char* funcName, uint32_t fullRows,
-                              uint32_t tailPixels, webgl::TexUnpackBlob* blob);
+    UniquePtr<webgl::TexUnpackBlob>
+    From(const char* funcName, TexImageTarget target, GLsizei rawWidth, GLsizei rawHeight,
+         GLsizei rawDepth, GLint border, const TexImageSource& src,
+         dom::Uint8ClampedArray* const scopedArr);
 
 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 ValidateUnpackInfo(const char* funcName, bool usePBOs, GLenum format,
                             GLenum type, webgl::PackingInfo* const out);
 
+    UniquePtr<webgl::TexUnpackBlob>
+    FromDomElem(const char* funcName, TexImageTarget target, uint32_t width,
+                uint32_t height, uint32_t depth, const dom::Element& elem,
+                ErrorResult* const out_error);
+
+    UniquePtr<webgl::TexUnpackBytes>
+    FromCompressed(const char* funcName, TexImageTarget target, GLsizei rawWidth,
+                   GLsizei rawHeight, GLsizei rawDepth, GLint border,
+                   const TexImageSource& src);
+
 // -----------------------------------------------------------------------------
 // Vertices Feature (WebGLContextVertices.cpp)
     GLenum mPrimRestartTypeBytes;
 
 public:
     void DrawArrays(GLenum mode, GLint first, GLsizei count);
     void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count,
                              GLsizei primcount);
@@ -1463,20 +1573,22 @@ protected:
     bool ValidateNonNegative(const char* funcName, const char* argName, int64_t val) {
         if (MOZ_UNLIKELY(val < 0)) {
             ErrorInvalidValue("%s: `%s` must be non-negative.", funcName, argName);
             return false;
         }
         return true;
     }
 
+public:
     bool ValidateArrayBufferView(const char* funcName, const dom::ArrayBufferView& view,
                                  GLuint elemOffset, GLuint elemCountOverride,
                                  uint8_t** const out_bytes, size_t* const out_byteLen);
 
+protected:
     ////
 
     void Invalidate();
     void DestroyResourcesAndContext();
 
     void MakeContextCurrent() const;
 
     // helpers
--- a/dom/canvas/WebGLContextTextures.cpp
+++ b/dom/canvas/WebGLContextTextures.cpp
@@ -312,261 +312,109 @@ WebGLContext::TexParameter_base(GLenum r
     WebGLTexture* tex;
     if (!ValidateTexTarget(this, funcName, funcDims, rawTexTarget, &texTarget, &tex))
         return;
 
     tex->TexParameter(texTarget, pname, maybeIntParam, maybeFloatParam);
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////
 // Uploads
 
-
-////////////////////
-// TexImage
-
 void
-WebGLContext::TexImage2D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat,
-                         GLsizei width, GLsizei height, GLint border, GLenum unpackFormat,
-                         GLenum unpackType, const dom::ArrayBufferView* maybeView,
-                         GLuint elemOffset)
+WebGLContext::CompressedTexImage(const char* funcName, uint8_t funcDims, GLenum rawTarget,
+                                 GLint level, GLenum internalFormat, GLsizei width,
+                                 GLsizei height, GLsizei depth, GLint border,
+                                 const TexImageSource& src)
 {
-    const char funcName[] = "texImage2D";
-    const uint8_t funcDims = 2;
-
     TexImageTarget target;
     WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
+    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
         return;
-    }
-
-    const bool isSubImage = false;
-    const GLint xOffset = 0;
-    const GLint yOffset = 0;
-    const GLint zOffset = 0;
-    const GLsizei depth = 1;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, width, height, depth, border, unpackFormat,
-                       unpackType, maybeView, elemOffset);
-}
 
-void
-WebGLContext::TexImage2D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat,
-                         GLenum unpackFormat, GLenum unpackType,
-                         const dom::ImageData& imageData, ErrorResult&)
-{
-    const char funcName[] = "texImage2D";
-    const uint8_t funcDims = 2;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = false;
-    const GLint xOffset = 0;
-    const GLint yOffset = 0;
-    const GLint zOffset = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, unpackFormat, unpackType, imageData);
+    tex->CompressedTexImage(funcName, target, level, internalFormat, width, height, depth,
+                            border, src);
 }
 
 void
-WebGLContext::TexImage2D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat,
-                         GLenum unpackFormat, GLenum unpackType, const dom::Element& elem,
-                         ErrorResult& out_error)
+WebGLContext::CompressedTexSubImage(const char* funcName, uint8_t funcDims,
+                                    GLenum rawTarget, GLint level, GLint xOffset,
+                                    GLint yOffset, GLint zOffset, GLsizei width,
+                                    GLsizei height, GLsizei depth, GLenum unpackFormat,
+                                    const TexImageSource& src)
 {
-    const char funcName[] = "texImage2D";
-    const uint8_t funcDims = 2;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = false;
-    const GLint xOffset = 0;
-    const GLint yOffset = 0;
-    const GLint zOffset = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, unpackFormat, unpackType, elem, &out_error);
-}
-
-////////////////////////////////////////
-// TexSubImage
-
-void
-WebGLContext::TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
-                            GLint yOffset, GLsizei width, GLsizei height,
-                            GLenum unpackFormat, GLenum unpackType,
-                            const dom::ArrayBufferView& view, GLuint elemOffset,
-                            ErrorResult&)
-{
-    const char funcName[] = "texSubImage2D";
-    const uint8_t funcDims = 2;
-
     TexImageTarget target;
     WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
+    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
         return;
-    }
-
-    const bool isSubImage = true;
-    const GLenum internalFormat = 0;
-    const GLint zOffset = 0;
-    const GLsizei depth = 1;
-    const GLint border = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, width, height, depth, border, unpackFormat,
-                       unpackType, &view, elemOffset);
-}
 
-void
-WebGLContext::TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
-                            GLint yOffset, GLenum unpackFormat, GLenum unpackType,
-                            const dom::ImageData& imageData, ErrorResult&)
-{
-    const char funcName[] = "texSubImage2D";
-    const uint8_t funcDims = 2;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = true;
-    const GLenum internalFormat = 0;
-    const GLint zOffset = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, unpackFormat, unpackType, imageData);
+    tex->CompressedTexSubImage(funcName, target, level, xOffset, yOffset, zOffset, width,
+                               height, depth, unpackFormat, src);
 }
 
-void
-WebGLContext::TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
-                            GLint yOffset, GLenum unpackFormat, GLenum unpackType,
-                            const dom::Element& elem, ErrorResult& out_error)
-{
-    const char funcName[] = "texSubImage2D";
-    const uint8_t funcDims = 2;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const bool isSubImage = true;
-    const GLenum internalFormat = 0;
-    const GLint zOffset = 0;
-    tex->TexOrSubImage(isSubImage, funcName, target, level, internalFormat, xOffset,
-                       yOffset, zOffset, unpackFormat, unpackType, elem, &out_error);
-}
-
-////////////////////////////////////////
-// CompressedTex(Sub)Image
+////
 
 void
-WebGLContext::CompressedTexImage2D(GLenum rawTexImageTarget, GLint level,
-                                   GLenum internalFormat, GLsizei width, GLsizei height,
-                                   GLint border, const dom::ArrayBufferView& srcView,
-                                   GLuint srcElemOffset)
-{
-    const char funcName[] = "compressedTexImage2D";
-    const uint8_t funcDims = 2;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const GLsizei depth = 1;
-    tex->CompressedTexImage(funcName, target, level, internalFormat, width, height, depth,
-                            border, srcView, srcElemOffset);
-}
-
-void
-WebGLContext::CompressedTexSubImage2D(GLenum rawTexImageTarget, GLint level,
-                                      GLint xOffset, GLint yOffset, GLsizei width,
-                                      GLsizei height, GLenum sizedUnpackFormat,
-                                      const dom::ArrayBufferView& srcView,
-                                      GLuint srcElemOffset)
-{
-    const char funcName[] = "compressedTexSubImage2D";
-    const uint8_t funcDims = 2;
-
-    TexImageTarget target;
-    WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
-        return;
-    }
-
-    const GLint zOffset = 0;
-    const GLsizei depth = 1;
-    tex->CompressedTexSubImage(funcName, target, level, xOffset, yOffset, zOffset, width,
-                               height, depth, sizedUnpackFormat, srcView, srcElemOffset);
-}
-
-////////////////////////////////////////
-// CopyTex(Sub)Image
-
-void
-WebGLContext::CopyTexImage2D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat,
+WebGLContext::CopyTexImage2D(GLenum rawTarget, GLint level, GLenum internalFormat,
                              GLint x, GLint y, GLsizei width, GLsizei height,
                              GLint border)
 {
     const char funcName[] = "copyTexImage2D";
     const uint8_t funcDims = 2;
 
     TexImageTarget target;
     WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
+    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
         return;
-    }
 
     tex->CopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
 }
 
 void
-WebGLContext::CopyTexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
-                                GLint yOffset, GLint x, GLint y, GLsizei width,
-                                GLsizei height)
+WebGLContext::CopyTexSubImage(const char* funcName, uint8_t funcDims, GLenum rawTarget,
+                              GLint level, GLint xOffset, GLint yOffset, GLint zOffset,
+                              GLint x, GLint y, GLsizei width, GLsizei height)
 {
-    const char funcName[] = "copyTexSubImage2D";
-    const uint8_t funcDims = 2;
-
     TexImageTarget target;
     WebGLTexture* tex;
-    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTexImageTarget, &target,
-                                &tex))
-    {
+    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
         return;
-    }
-
-    const GLint zOffset = 0;
 
     tex->CopyTexSubImage(funcName, target, level, xOffset, yOffset, zOffset, x, y, width,
                          height);
 }
 
+////
+
+void
+WebGLContext::TexImage(const char* funcName, uint8_t funcDims, GLenum rawTarget,
+                       GLint level, GLenum internalFormat, GLsizei width, GLsizei height,
+                       GLsizei depth, GLint border, GLenum unpackFormat,
+                       GLenum unpackType, const TexImageSource& src)
+{
+    TexImageTarget target;
+    WebGLTexture* tex;
+    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
+        return;
+
+    const webgl::PackingInfo pi = {unpackFormat, unpackType};
+    tex->TexImage(funcName, target, level, internalFormat, width, height, depth, border,
+                  pi, src);
+}
+
+void
+WebGLContext::TexSubImage(const char* funcName, uint8_t funcDims, GLenum rawTarget,
+                          GLint level, GLint xOffset, GLint yOffset, GLint zOffset,
+                          GLsizei width, GLsizei height, GLsizei depth,
+                          GLenum unpackFormat, GLenum unpackType,
+                          const TexImageSource& src)
+{
+    TexImageTarget target;
+    WebGLTexture* tex;
+    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
+        return;
+
+    const webgl::PackingInfo pi = {unpackFormat, unpackType};
+    tex->TexSubImage(funcName, target, level, xOffset, yOffset, zOffset, width, height,
+                     depth, pi, src);
+}
+
 } // namespace mozilla
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -20,16 +20,17 @@
 #include "WebGLFramebufferAttachable.h"
 #include "WebGLObjectModel.h"
 #include "WebGLStrongTypes.h"
 #include "WebGLTypes.h"
 
 namespace mozilla {
 class ErrorResult;
 class WebGLContext;
+struct TexImageSource;
 
 namespace dom {
 class Element;
 class HTMLVideoElement;
 class ImageData;
 class ArrayBufferViewOrSharedArrayBufferView;
 } // namespace dom
 
@@ -227,38 +228,16 @@ public:
     JS::Value GetTexParameter(TexTarget texTarget, GLenum pname);
     bool IsTexture() const;
     void TexParameter(TexTarget texTarget, GLenum pname, GLint* maybeIntParam,
                       GLfloat* maybeFloatParam);
 
     ////////////////////////////////////
     // WebGLTextureUpload.cpp
 
-    void 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,
-                       const dom::ArrayBufferView* srcView, GLuint srcElemOffset);
-
-    void TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
-                       GLint level, GLenum internalFormat, GLint xOffset, GLint yOffset,
-                       GLint zOffset, GLenum unpackFormat, GLenum unpackType,
-                       const 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,
-                       const dom::Element& elem, ErrorResult* const out_error);
-
-    void 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,
-                       WebGLsizeiptr offset);
-
 protected:
     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,
@@ -272,32 +251,39 @@ protected:
                                    WebGLTexture::ImageInfo** const out_imageInfo);
     bool ValidateCopyTexImageForFeedback(const char* funcName, uint32_t level) const;
 
     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);
+    void TexImage(const char* funcName, TexImageTarget target, GLint level,
+                  GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth,
+                  GLint border, const webgl::PackingInfo& pi, const TexImageSource& src);
+    void TexSubImage(const char* funcName, TexImageTarget target, GLint level,
+                     GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
+                     GLsizei height, GLsizei depth, const webgl::PackingInfo& pi,
+                     const TexImageSource& src);
 protected:
     void TexImage(const char* funcName, TexImageTarget target, GLint level,
                   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,
                      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& srcView, GLuint srcElemOffset);
+                            GLsizei depth, GLint border, const TexImageSource& src);
     void CompressedTexSubImage(const char* funcName, TexImageTarget target, GLint level,
                                GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
                                GLsizei height, GLsizei depth, GLenum sizedUnpackFormat,
-                               const dom::ArrayBufferView& srcView, GLuint srcElemOffset);
+                               const TexImageSource& src);
+
     void CopyTexImage2D(TexImageTarget target, GLint level, GLenum internalFormat,
                         GLint x, GLint y, GLsizei width, GLsizei height, GLint border);
     void CopyTexSubImage(const char* funcName, TexImageTarget target, GLint level,
                          GLint xOffset, GLint yOffset, GLint zOffset, GLint x, GLint y,
                          GLsizei width, GLsizei height);
 
     ////////////////////////////////////
 
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -122,235 +122,95 @@ DoesJSTypeMatchUnpackType(GLenum unpackT
     case LOCAL_GL_FLOAT:
         return jsType == js::Scalar::Type::Float32;
 
     default:
         return false;
     }
 }
 
-bool
-WebGLContext::ValidateUnpackPixels(const char* funcName, uint32_t fullRows,
-                                   uint32_t tailPixels, webgl::TexUnpackBlob* blob)
+static bool
+ValidateViewType(WebGLContext* webgl, const char* funcName, GLenum unpackType,
+                 const TexImageSource& src)
 {
-    auto skipPixels = CheckedUint32(blob->mSkipPixels);
-    skipPixels += CheckedUint32(blob->mSkipRows);
-
-    const auto usedPixelsPerRow = CheckedUint32(blob->mSkipPixels) + blob->mWidth;
-    if (!usedPixelsPerRow.isValid() || usedPixelsPerRow.value() > blob->mRowLength) {
-        ErrorInvalidOperation("%s: UNPACK_SKIP_PIXELS + height > UNPACK_ROW_LENGTH.",
-                              funcName);
-        return false;
-    }
+    if (!src.mView)
+        return true;
+    const auto& view = *(src.mView);
 
-    if (blob->mHeight > blob->mImageHeight) {
-        ErrorInvalidOperation("%s: height > UNPACK_IMAGE_HEIGHT.", funcName);
-        return false;
-    }
-
-    //////
-
-    // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
-    auto skipFullRows = CheckedUint32(blob->mSkipImages) * blob->mImageHeight;
-    skipFullRows += blob->mSkipRows;
-
-    MOZ_ASSERT(blob->mDepth >= 1);
-    MOZ_ASSERT(blob->mHeight >= 1);
-    auto usedFullRows = CheckedUint32(blob->mDepth - 1) * blob->mImageHeight;
-    usedFullRows += blob->mHeight - 1; // Full rows in the final image, excluding the tail.
-
-    const auto fullRowsNeeded = skipFullRows + usedFullRows;
-    if (!fullRowsNeeded.isValid()) {
-        ErrorOutOfMemory("%s: Invalid calculation for required row count.",
-                         funcName);
+    const auto& jsType = view.Type();
+    if (!DoesJSTypeMatchUnpackType(unpackType, jsType)) {
+        webgl->ErrorInvalidOperation("%s: ArrayBufferView type not compatible with"
+                                     " `type`.",
+                                     funcName);
         return false;
     }
 
-    if (fullRows > fullRowsNeeded.value())
-        return true;
-
-    if (fullRows == fullRowsNeeded.value() && tailPixels >= usedPixelsPerRow.value()) {
-        blob->mNeedsExactUpload = true;
-        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;
+    return true;
 }
 
 static bool
-ValidateUnpackBytes(WebGLContext* webgl, const char* funcName, uint32_t width,
-                    uint32_t height, uint32_t depth, const webgl::PackingInfo& pi,
-                    size_t byteCount, webgl::TexUnpackBlob* blob)
+ValidateUnpackInfo(WebGLContext* webgl, const char* funcName,
+                   const webgl::PackingInfo& pi)
 {
-    if (!width || !height || !depth)
-        return true;
-
-    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.");
+    if (!webgl->mFormatUsage->AreUnpackEnumsValid(pi.format, pi.type)) {
+        webgl->ErrorInvalidEnum("%s: Invalid unpack format/type: 0x%04x/0x%04x", funcName,
+                                pi.format, pi.type);
         return false;
     }
 
-    const auto bodyBytes = fullRows.value() * rowStride.value();
-    const auto tailPixels = (byteCount - bodyBytes) / bytesPerPixel;
-
-    return webgl->ValidateUnpackPixels(funcName, fullRows.value(), tailPixels, blob);
-}
-
-bool
-WebGLContext::ValidateUnpackInfo(const char* funcName, bool usePBOs, GLenum format,
-                                 GLenum type, webgl::PackingInfo* const out)
-{
-    if (usePBOs != bool(mBoundPixelUnpackBuffer)) {
-        ErrorInvalidOperation("%s: PACK_BUFFER must be %s.", funcName,
-                              (usePBOs ? "non-null" : "null"));
-        return false;
-    }
-
-    if (mBoundPixelUnpackBuffer &&
-        mBoundPixelUnpackBuffer->mNumActiveTFOs)
-    {
-        ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
-                              " object.",
-                              funcName);
-        return false;
-    }
-
-    if (!mFormatUsage->AreUnpackEnumsValid(format, type)) {
-        ErrorInvalidEnum("%s: Invalid unpack format/type: 0x%04x/0x%04x", funcName,
-                         format, type);
-        return false;
-    }
-
-    out->format = format;
-    out->type = type;
     return true;
 }
 
-void
-WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
-                            GLint level, GLenum internalFormat, GLint xOffset,
-                            GLint yOffset, GLint zOffset, GLsizei rawWidth,
-                            GLsizei rawHeight, GLsizei rawDepth, GLint border,
-                            GLenum unpackFormat, GLenum unpackType,
-                            const dom::ArrayBufferView* srcView, GLuint srcElemOffset)
-{
-    uint32_t width, height, depth;
-    if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, rawDepth, border,
-                         &width, &height, &depth))
-    {
-        return;
-    }
-
-    const bool usePBOs = false;
-    webgl::PackingInfo pi;
-    if (!mContext->ValidateUnpackInfo(funcName, usePBOs, unpackFormat, unpackType, &pi))
-        return;
-
-    ////
-
-    const uint8_t* bytes = nullptr;
-    size_t byteCount = 0;
+////////////////////////////////////////////////////////////////////////////////
 
-    if (srcView) {
-        const auto& jsType = srcView->Type();
-        if (!DoesJSTypeMatchUnpackType(pi.type, jsType)) {
-            mContext->ErrorInvalidOperation("%s: `pixels` not compatible with `type`.",
-                                            funcName);
-            return;
-        }
-
-        if (!mContext->ValidateArrayBufferView(funcName, *srcView, srcElemOffset, 0,
-                                               const_cast<uint8_t**>(&bytes), &byteCount))
+static UniquePtr<webgl::TexUnpackBytes>
+FromView(WebGLContext* webgl, const char* funcName, TexImageTarget target,
+         uint32_t width, uint32_t height, uint32_t depth,
+         const dom::ArrayBufferView* view, GLuint viewElemOffset)
+{
+    const bool isClientData = true;
+    const uint8_t* bytes = nullptr;
+    size_t availByteCount = 0;
+    if (view) {
+        if (!webgl->ValidateArrayBufferView(funcName, *view, viewElemOffset, 0,
+                                            const_cast<uint8_t**>(&bytes),
+                                            &availByteCount))
         {
-            return;
+            return nullptr;
         }
-    } else if (isSubImage) {
-        mContext->ErrorInvalidValue("%s: `pixels` must not be null.", funcName);
-        return;
     }
+    return MakeUnique<webgl::TexUnpackBytes>(webgl, target, width, height, depth,
+                                             isClientData, bytes, availByteCount);
+}
 
-    const bool isClientData = true;
-    webgl::TexUnpackBytes blob(mContext, target, width, height, depth, isClientData,
-                               bytes);
-
-    if (bytes &&
-        !ValidateUnpackBytes(mContext, funcName, width, height, depth, pi, byteCount,
-                             &blob))
-    {
-        return;
+static UniquePtr<webgl::TexUnpackBytes>
+FromPboOffset(WebGLContext* webgl, const char* funcName, TexImageTarget target,
+              uint32_t width, uint32_t height, uint32_t depth, WebGLsizeiptr pboOffset,
+              size_t availBufferBytes)
+{
+    if (pboOffset < 0) {
+        webgl->ErrorInvalidValue("%s: offset cannot be negative.", funcName);
+        return nullptr;
     }
 
-    TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
-                      yOffset, zOffset, pi, &blob);
+    if (size_t(pboOffset) > availBufferBytes) {
+        webgl->ErrorInvalidOperation("%s: Offset is passed end of buffer.", funcName);
+        return nullptr;
+    }
+    availBufferBytes -= pboOffset;
+
+    const bool isClientData = false;
+    const auto ptr = (const uint8_t*)pboOffset;
+    return MakeUnique<webgl::TexUnpackBytes>(webgl, target, width, height, depth,
+                                             isClientData, ptr, availBufferBytes);
 }
 
-void
-WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
-                            GLint level, GLenum internalFormat, GLint xOffset,
-                            GLint yOffset, GLint zOffset, GLsizei rawWidth,
-                            GLsizei rawHeight, GLsizei rawDepth, GLint border,
-                            GLenum unpackFormat, GLenum unpackType,
-                            WebGLsizeiptr offset)
-{
-    uint32_t width, height, depth;
-    if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, rawDepth, border,
-                         &width, &height, &depth))
-    {
-        return;
-    }
-
-    const bool usePBOs = true;
-    webgl::PackingInfo pi;
-    if (!mContext->ValidateUnpackInfo(funcName, usePBOs, unpackFormat, unpackType, &pi))
-        return;
-
-    ////
-
-    if (offset < 0) {
-        mContext->ErrorInvalidValue("%s: offset cannot be negative.", funcName);
-        return;
-    }
-
-    const bool isClientData = false;
-    const auto ptr = (const uint8_t*)offset;
-    webgl::TexUnpackBytes blob(mContext, target, width, height, depth, isClientData, ptr);
-
-    const auto& packBuffer = mContext->mBoundPixelUnpackBuffer;
-    const auto bufferByteCount = packBuffer->ByteLength();
-
-    uint32_t byteCount = 0;
-    if (bufferByteCount >= uint64_t(offset)) {
-        byteCount = bufferByteCount - offset;
-    }
-
-    if (!ValidateUnpackBytes(mContext, funcName, width, height, depth, pi, byteCount,
-                             &blob))
-    {
-        return;
-    }
-
-    TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
-                      yOffset, zOffset, pi, &blob);
-}
-
-////////////////////////////////////////
-// ImageData
-
-static already_AddRefed<gfx::DataSourceSurface>
-FromImageData(WebGLContext* webgl, const char* funcName, GLenum unpackType,
+static UniquePtr<webgl::TexUnpackBlob>
+FromImageData(WebGLContext* webgl, const char* funcName, TexImageTarget target,
+              uint32_t width, uint32_t height, uint32_t depth,
               const dom::ImageData& imageData, dom::Uint8ClampedArray* scopedArr)
 {
     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();
@@ -358,93 +218,56 @@ FromImageData(WebGLContext* webgl, const
     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;
 
-    RefPtr<gfx::DataSourceSurface> surf =
-        gfx::Factory::CreateWrappingDataSourceSurface(wrappableData,
-                                                      stride,
-                                                      size,
+    const RefPtr<gfx::DataSourceSurface> surf =
+        gfx::Factory::CreateWrappingDataSourceSurface(wrappableData, stride, size,
                                                       surfFormat);
     if (!surf) {
         webgl->ErrorOutOfMemory("%s: OOM in FromImageData.", funcName);
         return nullptr;
     }
 
-    return surf.forget();
-}
+    ////
+
+    if (!width) {
+        width = imageData.Width();
+    }
 
-void
-WebGLTexture::TexOrSubImage(bool isSubImage, const char* funcName, TexImageTarget target,
-                            GLint level, GLenum internalFormat, GLint xOffset,
-                            GLint yOffset, GLint zOffset, GLenum unpackFormat,
-                            GLenum unpackType, const dom::ImageData& imageData)
-{
-    const bool usePBOs = false;
-    webgl::PackingInfo pi;
-    if (!mContext->ValidateUnpackInfo(funcName, usePBOs, unpackFormat, unpackType, &pi))
-        return;
+    if (!height) {
+        height = imageData.Height();
+    }
 
-    // Eventually, these will be args.
-    const uint32_t width = imageData.Width();
-    const uint32_t height = imageData.Height();
-    const uint32_t depth = 1;
-
-    dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(dom::RootingCx());
-    const RefPtr<gfx::DataSourceSurface> surf = FromImageData(mContext, funcName,
-                                                              unpackType, imageData,
-                                                              &scopedArr);
-    if (!surf)
-        return;
+    ////
 
     // 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 isAlphaPremult = false;
-
-    webgl::TexUnpackSurface blob(mContext, target, width, height, depth, surf,
-                                 isAlphaPremult);
-
-    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);
+    return MakeUnique<webgl::TexUnpackSurface>(webgl, target, width, height, depth, surf,
+                                               isAlphaPremult);
 }
 
-////////////////////////////////////////
-// 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, const dom::Element& elem,
-                            ErrorResult* const out_error)
+UniquePtr<webgl::TexUnpackBlob>
+WebGLContext::FromDomElem(const char* funcName, TexImageTarget target, uint32_t width,
+                          uint32_t height, uint32_t depth, const dom::Element& elem,
+                          ErrorResult* const out_error)
 {
-    const bool usePBOs = false;
-    webgl::PackingInfo pi;
-    if (!mContext->ValidateUnpackInfo(funcName, usePBOs, unpackFormat, unpackType, &pi))
-        return;
-
-    //////
-
     uint32_t flags = nsLayoutUtils::SFE_WANT_IMAGE_SURFACE |
                      nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR;
 
-    if (mContext->mPixelStore_ColorspaceConversion == LOCAL_GL_NONE)
+    if (mPixelStore_ColorspaceConversion == LOCAL_GL_NONE)
         flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
 
-    if (!mContext->mPixelStore_PremultiplyAlpha)
+    if (!mPixelStore_PremultiplyAlpha)
         flags |= nsLayoutUtils::SFE_PREFER_NO_PREMULTIPLY_ALPHA;
 
     RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr; // Don't care for now.
     auto sfer = nsLayoutUtils::SurfaceFromElement(const_cast<dom::Element*>(&elem), flags,
                                                   idealDrawTarget);
 
     //////
 
@@ -464,98 +287,175 @@ WebGLTexture::TexOrSubImage(bool isSubIm
         elemHeight = surf->GetSize().height;
 
         // WARNING: OSX can lose our MakeCurrent here.
         dataSurf = surf->GetDataSurface();
     }
 
     //////
 
-    // Eventually, these will be args.
-    const uint32_t width = elemWidth;
-    const uint32_t height = elemHeight;
-    const uint32_t depth = 1;
+    if (!width) {
+        width = elemWidth;
+    }
+
+    if (!height) {
+        height = elemHeight;
+    }
+
+    ////
 
     if (!layersImage && !dataSurf) {
         const bool isClientData = true;
-        const webgl::TexUnpackBytes blob(mContext, target, width, height, depth,
-                                         isClientData, nullptr);
-        TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
-                          yOffset, zOffset, pi, &blob);
-        return;
+        return MakeUnique<webgl::TexUnpackBytes>(this, target, width, height, depth,
+                                                 isClientData, nullptr, 0);
     }
 
     //////
 
     // 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..
 
     if (!sfer.mCORSUsed) {
         auto& srcPrincipal = sfer.mPrincipal;
-        nsIPrincipal* dstPrincipal = mContext->GetCanvas()->NodePrincipal();
+        nsIPrincipal* dstPrincipal = GetCanvas()->NodePrincipal();
 
         if (!dstPrincipal->Subsumes(srcPrincipal)) {
-            mContext->GenerateWarning("%s: Cross-origin elements require CORS.",
-                                      funcName);
+            GenerateWarning("%s: Cross-origin elements require CORS.", funcName);
             out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
-            return;
+            return nullptr;
         }
     }
 
     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);
+        GenerateWarning("%s: Element is write-only, thus cannot be uploaded.", funcName);
         out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
-        return;
+        return nullptr;
     }
 
     //////
     // Ok, we're good!
 
-    UniquePtr<webgl::TexUnpackBlob> blob;
     const bool isAlphaPremult = sfer.mIsPremultiplied;
 
     if (layersImage) {
-        blob.reset(new webgl::TexUnpackImage(mContext, target, width, height, depth,
-                                             layersImage, isAlphaPremult));
-    } else {
-        MOZ_ASSERT(dataSurf);
-        blob.reset(new webgl::TexUnpackSurface(mContext, target, width, height, depth,
-                                               dataSurf, isAlphaPremult));
+        return MakeUnique<webgl::TexUnpackImage>(this, target, width, height, depth,
+                                                 layersImage,  isAlphaPremult);
+    }
+
+    MOZ_ASSERT(dataSurf);
+    return MakeUnique<webgl::TexUnpackSurface>(this, target, width, height, depth,
+                                               dataSurf, isAlphaPremult);
+}
+
+////////////////////////////////////////
+
+UniquePtr<webgl::TexUnpackBlob>
+WebGLContext::From(const char* funcName, TexImageTarget target, GLsizei rawWidth,
+                   GLsizei rawHeight, GLsizei rawDepth, GLint border,
+                   const TexImageSource& src, dom::Uint8ClampedArray* const scopedArr)
+{
+    uint32_t width, height, depth;
+    if (!ValidateExtents(this, funcName, rawWidth, rawHeight, rawDepth, border, &width,
+                         &height, &depth))
+    {
+        return nullptr;
+    }
+
+    if (src.mPboOffset) {
+        if (!mBoundPixelUnpackBuffer) {
+            ErrorInvalidOperation("%s: PACK_BUFFER must be non-null.", funcName);
+            return nullptr;
+        }
+
+        if (mBoundPixelUnpackBuffer->mNumActiveTFOs) {
+            ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
+                                  " object.",
+                                  funcName);
+            return nullptr;
+        }
+
+        const auto& availBytes = mBoundPixelUnpackBuffer->ByteLength();
+        return FromPboOffset(this, funcName, target, width, height, depth,
+                             *(src.mPboOffset), availBytes);
+    }
+
+    if (mBoundPixelUnpackBuffer) {
+        ErrorInvalidOperation("%s: PACK_BUFFER must be null.", funcName);
+        return nullptr;
     }
 
-    const uint32_t fullRows = elemHeight;
-    const uint32_t tailPixels = 0;
-    if (!mContext->ValidateUnpackPixels(funcName, fullRows, tailPixels, blob.get()))
+    if (src.mImageData) {
+        return FromImageData(this, funcName, target, width, height, depth,
+                             *(src.mImageData), scopedArr);
+    }
+
+    if (src.mDomElem) {
+        return FromDomElem(funcName, target, width, height, depth, *(src.mDomElem),
+                           src.mOut_error);
+    }
+
+    return FromView(this, funcName, target, width, height, depth, src.mView,
+                    src.mViewElemOffset);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static UniquePtr<webgl::TexUnpackBlob>
+ValidateTexOrSubImage(WebGLContext* webgl, const char* funcName, TexImageTarget target,
+                      GLsizei rawWidth, GLsizei rawHeight, GLsizei rawDepth,
+                      GLint border, const webgl::PackingInfo& pi,
+                      const TexImageSource& src, dom::Uint8ClampedArray* const scopedArr)
+{
+    if (!ValidateUnpackInfo(webgl, funcName, pi))
+        return nullptr;
+
+    if (!ValidateViewType(webgl, funcName, pi.type, src))
+        return nullptr;
+
+    auto blob = webgl->From(funcName, target, rawWidth, rawHeight, rawDepth, border, src,
+                            scopedArr);
+    if (!blob || !blob->Validate(webgl, funcName, pi))
+        return nullptr;
+
+    return Move(blob);
+}
+
+void
+WebGLTexture::TexImage(const char* funcName, TexImageTarget target, GLint level,
+                       GLenum internalFormat, GLsizei width, GLsizei height,
+                       GLsizei depth, GLint border, const webgl::PackingInfo& pi,
+                       const TexImageSource& src)
+{
+    dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(dom::RootingCx());
+    const auto blob = ValidateTexOrSubImage(mContext, funcName, target, width, height,
+                                            depth, border, pi, src, &scopedArr);
+    if (!blob)
         return;
 
-    TexOrSubImageBlob(isSubImage, funcName, target, level, internalFormat, xOffset,
-                      yOffset, zOffset, pi, blob.get());
+    TexImage(funcName, target, level, internalFormat, pi, blob.get());
 }
 
-
-//////////////////////////////////////////////////////////////////////////////////////////
-
 void
-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)
+WebGLTexture::TexSubImage(const char* funcName, TexImageTarget target, GLint level,
+                          GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
+                          GLsizei height, GLsizei depth,
+                          const webgl::PackingInfo& pi, const TexImageSource& src)
 {
-    if (isSubImage) {
-        TexSubImage(funcName, target, level, xOffset, yOffset, zOffset, pi, blob);
-    } else {
-        TexImage(funcName, target, level, internalFormat, pi, blob);
-    }
+    const GLint border = 0;
+    dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(dom::RootingCx());
+    const auto blob = ValidateTexOrSubImage(mContext, funcName, target, width, height,
+                                            depth, border, pi, src, &scopedArr);
+    if (!blob)
+        return;
+
+    TexSubImage(funcName, target, level, xOffset, yOffset, zOffset, pi, blob.get());
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////
 
 static bool
 ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture, const char* funcName,
                  TexImageTarget target, GLint level,
@@ -1444,36 +1344,72 @@ WebGLTexture::TexSubImage(const char* fu
     if (uploadWillInitialize) {
         imageInfo->SetIsDataInitialized(true, this);
     }
 }
 
 ////////////////////////////////////////
 // CompressedTex(Sub)Image
 
+UniquePtr<webgl::TexUnpackBytes>
+WebGLContext::FromCompressed(const char* funcName, TexImageTarget target,
+                             GLsizei rawWidth, GLsizei rawHeight, GLsizei rawDepth,
+                             GLint border, const TexImageSource& src)
+{
+    uint32_t width, height, depth;
+    if (!ValidateExtents(this, funcName, rawWidth, rawHeight, rawDepth, border, &width,
+                         &height, &depth))
+    {
+        return nullptr;
+    }
+
+    if (src.mPboOffset) {
+        if (!mBoundPixelUnpackBuffer) {
+            ErrorInvalidOperation("%s: PACK_BUFFER must be non-null.", funcName);
+            return nullptr;
+        }
+
+        if (mBoundPixelUnpackBuffer->mNumActiveTFOs) {
+            ErrorInvalidOperation("%s: Buffer is bound to an active transform feedback"
+                                  " object.",
+                                  funcName);
+            return nullptr;
+        }
+
+        const auto& availBytes = mBoundPixelUnpackBuffer->ByteLength();
+        return FromPboOffset(this, funcName, target, width, height, depth,
+                             *(src.mPboOffset), availBytes);
+    }
+
+    if (mBoundPixelUnpackBuffer) {
+        ErrorInvalidOperation("%s: PACK_BUFFER must be null.", funcName);
+        return nullptr;
+    }
+
+    return FromView(this, funcName, target, width, height, depth, src.mView,
+                    src.mViewElemOffset);
+}
+
 void
 WebGLTexture::CompressedTexImage(const char* funcName, TexImageTarget target, GLint level,
                                  GLenum internalFormat, GLsizei rawWidth,
                                  GLsizei rawHeight, GLsizei rawDepth, GLint border,
-                                 const dom::ArrayBufferView& srcView,
-                                 GLuint srcElemOffset)
+                                 const TexImageSource& src)
 {
-    uint32_t width, height, depth;
-    if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, rawDepth, border,
-                         &width, &height, &depth))
-    {
+    const auto blob = mContext->FromCompressed(funcName, target, rawWidth, rawHeight,
+                                               rawDepth, border, src);
+    if (!blob)
         return;
-    }
 
     ////////////////////////////////////
     // Get dest info
 
     WebGLTexture::ImageInfo* imageInfo;
-    if (!ValidateTexImageSpecification(funcName, target, level, width, height, depth,
-                                       &imageInfo))
+    if (!ValidateTexImageSpecification(funcName, target, level, blob->mWidth,
+                                       blob->mHeight, blob->mDepth, &imageInfo))
     {
         return;
     }
     MOZ_ASSERT(imageInfo);
 
     auto usage = mContext->mFormatUsage->GetSizedTexUsage(internalFormat);
     if (!usage) {
         mContext->ErrorInvalidEnum("%s: Invalid internalFormat: 0x%04x", funcName,
@@ -1489,47 +1425,41 @@ WebGLTexture::CompressedTexImage(const c
     }
 
     if (!ValidateTargetForFormat(funcName, mContext, target, format))
         return;
 
     ////////////////////////////////////
     // Get source info
 
-    const uint8_t* bytes;
-    size_t byteLen;
-    if (!mContext->ValidateArrayBufferView(funcName, srcView, srcElemOffset, 0,
-                                           const_cast<uint8_t**>(&bytes), &byteLen))
-    {
-        return;
-    }
-
-    if (!ValidateCompressedTexUnpack(mContext, funcName, width, height, depth, format,
-                                     byteLen))
+    if (!ValidateCompressedTexUnpack(mContext, funcName, blob->mWidth, blob->mHeight,
+                                     blob->mDepth, format, blob->mAvailBytes))
     {
         return;
     }
 
     ////////////////////////////////////
     // Check that source is compatible with dest
 
     if (!ValidateCompressedTexImageRestrictions(funcName, mContext, target, level, format,
-                                                width, height, depth))
+                                                blob->mWidth, blob->mHeight,
+                                                blob->mDepth))
     {
         return;
     }
 
     ////////////////////////////////////
     // Do the thing!
 
     mContext->gl->MakeCurrent();
 
     // Warning: Possibly shared memory.  See bug 1225033.
     GLenum error = DoCompressedTexImage(mContext->gl, target, level, internalFormat,
-                                        width, height, depth, byteLen, bytes);
+                                        blob->mWidth, blob->mHeight, blob->mDepth,
+                                        blob->mAvailBytes, blob->mPtr);
     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.",
@@ -1537,17 +1467,18 @@ WebGLTexture::CompressedTexImage(const c
         mContext->ForceLoseContext();
         return;
     }
 
     ////////////////////////////////////
     // Update our specification data.
 
     const bool isDataInitialized = true;
-    const ImageInfo newImageInfo(usage, width, height, depth, isDataInitialized);
+    const ImageInfo newImageInfo(usage, blob->mWidth, blob->mHeight, blob->mDepth,
+                                 isDataInitialized);
     SetImageInfo(imageInfo, newImageInfo);
 }
 
 static inline bool
 IsSubImageBlockAligned(const webgl::CompressedFormatInfo* compression,
                        const WebGLTexture::ImageInfo* imageInfo, GLint xOffset,
                        GLint yOffset, uint32_t width, uint32_t height)
 {
@@ -1566,68 +1497,58 @@ IsSubImageBlockAligned(const webgl::Comp
     return true;
 }
 
 void
 WebGLTexture::CompressedTexSubImage(const char* funcName, TexImageTarget target,
                                     GLint level, GLint xOffset, GLint yOffset,
                                     GLint zOffset, GLsizei rawWidth, GLsizei rawHeight,
                                     GLsizei rawDepth, GLenum sizedUnpackFormat,
-                                    const dom::ArrayBufferView& srcView,
-                                    GLuint srcElemOffset)
+                                    const TexImageSource& src)
 {
-    uint32_t width, height, depth;
-    if (!ValidateExtents(mContext, funcName, rawWidth, rawHeight, rawDepth, 0, &width,
-                         &height, &depth))
-    {
+    const GLint border = 0;
+    const auto blob = mContext->FromCompressed(funcName, target, rawWidth, rawHeight,
+                                               rawDepth, border, src);
+    if (!blob)
         return;
-    }
 
     ////////////////////////////////////
     // Get dest info
 
     WebGLTexture::ImageInfo* imageInfo;
     if (!ValidateTexImageSelection(funcName, target, level, xOffset, yOffset, zOffset,
-                                   width, height, depth, &imageInfo))
+                                   blob->mWidth, blob->mHeight, blob->mDepth, &imageInfo))
     {
         return;
     }
     MOZ_ASSERT(imageInfo);
 
     auto dstUsage = imageInfo->mFormat;
     auto dstFormat = dstUsage->format;
 
     ////////////////////////////////////
     // Get source info
 
-    const uint8_t* bytes;
-    size_t byteLen;
-    if (!mContext->ValidateArrayBufferView(funcName, srcView, srcElemOffset, 0,
-                                           const_cast<uint8_t**>(&bytes), &byteLen))
-    {
-        return;
-    }
-
     auto srcUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedUnpackFormat);
     if (!srcUsage->format->compression) {
         mContext->ErrorInvalidEnum("%s: Specified format must be compressed.", funcName);
         return;
     }
 
     if (srcUsage != dstUsage) {
         mContext->ErrorInvalidOperation("%s: `format` must match the format of the"
                                         " existing texture image.",
                                         funcName);
         return;
     }
 
     auto format = srcUsage->format;
     MOZ_ASSERT(format == dstFormat);
-    if (!ValidateCompressedTexUnpack(mContext, funcName, width, height, depth, format,
-                                     byteLen))
+    if (!ValidateCompressedTexUnpack(mContext, funcName, blob->mWidth, blob->mHeight,
+                                     blob->mDepth, format, blob->mAvailBytes))
     {
         return;
     }
 
     ////////////////////////////////////
     // Check that source is compatible with dest
 
     switch (format->compression->family) {
@@ -1637,56 +1558,58 @@ WebGLTexture::CompressedTexSubImage(cons
         mContext->ErrorInvalidOperation("%s: Format does not allow sub-image"
                                         " updates.", funcName);
         return;
 
     // Block-aligned:
     case webgl::CompressionFamily::ES3:  // Yes, the ES3 formats don't match the ES3
     case webgl::CompressionFamily::S3TC: // default behavior.
         if (!IsSubImageBlockAligned(dstFormat->compression, imageInfo, xOffset, yOffset,
-                                    width, height))
+                                    blob->mWidth, blob->mHeight))
         {
             mContext->ErrorInvalidOperation("%s: Format requires block-aligned sub-image"
                                             " updates.",
                                             funcName);
             return;
         }
         break;
 
     // Full-only: (The ES3 default)
     default: // PVRTC
         if (xOffset || yOffset ||
-            uint32_t(width) != imageInfo->mWidth ||
-            uint32_t(height) != imageInfo->mHeight)
+            blob->mWidth != imageInfo->mWidth ||
+            blob->mHeight != imageInfo->mHeight)
         {
             mContext->ErrorInvalidOperation("%s: Format does not allow partial sub-image"
                                             " updates.",
                                             funcName);
             return;
         }
         break;
     }
 
     ////////////////////////////////////
     // Do the thing!
 
     mContext->gl->MakeCurrent();
 
     bool uploadWillInitialize;
     if (!EnsureImageDataInitializedForUpload(this, funcName, target, level, xOffset,
-                                             yOffset, zOffset, width, height, depth,
-                                             imageInfo, &uploadWillInitialize))
+                                             yOffset, zOffset, blob->mWidth,
+                                             blob->mHeight, blob->mDepth, imageInfo,
+                                             &uploadWillInitialize))
     {
         return;
     }
 
     // Warning: Possibly shared memory.  See bug 1225033.
     GLenum error = DoCompressedTexSubImage(mContext->gl, target, level, xOffset, yOffset,
-                                           zOffset, width, height, depth,
-                                           sizedUnpackFormat, byteLen, bytes);
+                                           zOffset, blob->mWidth, blob->mHeight,
+                                           blob->mDepth, sizedUnpackFormat,
+                                           blob->mAvailBytes, blob->mPtr);
     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.",