Bug 1607940 - IPC TexImage. r=lsalzman,nika,handyman
authorJeff Gilbert <jgilbert@mozilla.com>
Tue, 21 Jul 2020 22:57:01 +0000
changeset 541522 978c7a6ddb55e53f9230112f5455a4970ee5d7b4
parent 541521 9049a0173c604b87f7d4d1b4e1d7a3ef7dd2c762
child 541523 daeda032926d1fb85aca0d9117d9e77ea677d2b4
push id37625
push usercsabou@mozilla.com
push dateWed, 22 Jul 2020 04:32:41 +0000
treeherdermozilla-central@6dfc866efa7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslsalzman, nika, handyman
bugs1607940
milestone80.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1607940 - IPC TexImage. r=lsalzman,nika,handyman Differential Revision: https://phabricator.services.mozilla.com/D83291
dom/canvas/ClientWebGLContext.cpp
dom/canvas/ClientWebGLContext.h
dom/canvas/HostWebGLContext.h
dom/canvas/PWebGL.ipdl
dom/canvas/QueueParamTraits.h
dom/canvas/TexUnpackBlob.cpp
dom/canvas/TexUnpackBlob.h
dom/canvas/WebGL2ContextState.cpp
dom/canvas/WebGL2ContextSync.cpp
dom/canvas/WebGLChild.cpp
dom/canvas/WebGLChild.h
dom/canvas/WebGLCommandQueue.h
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextGL.cpp
dom/canvas/WebGLContextState.cpp
dom/canvas/WebGLContextTextures.cpp
dom/canvas/WebGLContextUtils.cpp
dom/canvas/WebGLContextValidate.cpp
dom/canvas/WebGLIpdl.h
dom/canvas/WebGLMethodDispatcher.h
dom/canvas/WebGLParent.cpp
dom/canvas/WebGLParent.h
dom/canvas/WebGLQuery.cpp
dom/canvas/WebGLQueueParamTraits.h
dom/canvas/WebGLTexture.cpp
dom/canvas/WebGLTexture.h
dom/canvas/WebGLTextureUpload.cpp
dom/canvas/WebGLTypes.h
dom/canvas/test/webgl-conf/generated-mochitest.ini
dom/canvas/test/webgl-conf/mochitest-errata.ini
--- a/dom/canvas/ClientWebGLContext.cpp
+++ b/dom/canvas/ClientWebGLContext.cpp
@@ -877,19 +877,20 @@ RefPtr<gfx::SourceSurface> ClientWebGLCo
       return surf;
     }
     const auto& child = mNotLost->outOfProcess->mWebGLChild;
     child->FlushPendingCmds();
     webgl::FrontBufferSnapshotIpc res;
     if (!child->SendGetFrontBufferSnapshot(&res)) {
       res = {};
     }
-    if (!res.shmem) return nullptr;
     const auto& surfSize = res.surfSize;
-    const auto& shmemBytes = ByteRange(*res.shmem);
+    const webgl::RaiiShmem shmem{child, res.shmem};
+    const auto& shmemBytes = shmem.ByteRange();
+    if (!surfSize.x) return nullptr;  // Zero means failure.
 
     const auto stride = surfSize.x * 4;
     const auto byteSize = stride * surfSize.y;
 
     const auto surf = fnNewSurf(surfSize);
     if (!surf) return nullptr;
 
     {
@@ -1153,16 +1154,21 @@ already_AddRefed<WebGLSyncJS> ClientWebG
 
   if (flags) {
     EnqueueError(LOCAL_GL_INVALID_VALUE, "`flags` must be 0.");
     return nullptr;
   }
 
   auto ret = AsRefPtr(new WebGLSyncJS(*this));
   Run<RPROC(CreateSync)>(ret->mId);
+
+  auto& availRunnable = EnsureAvailabilityRunnable();
+  availRunnable.mSyncs.push_back(ret.get());
+  ret->mCanBeAvailable = false;
+
   return ret.forget();
 }
 
 already_AddRefed<WebGLTextureJS> ClientWebGLContext::CreateTexture() const {
   const FuncScope funcScope(*this, "createTexture");
   if (IsContextLost()) return nullptr;
 
   auto ret = AsRefPtr(new WebGLTextureJS(*this));
@@ -1615,17 +1621,17 @@ void ClientWebGLContext::GetInternalform
     JS::MutableHandle<JS::Value> retval, ErrorResult& rv) {
   const FuncScope funcScope(*this, "getInternalformatParameter");
   retval.set(JS::NullValue());
   const auto notLost =
       mNotLost;  // Hold a strong-ref to prevent LoseContext=>UAF.
   if (IsContextLost()) return;
 
   const auto& inProcessContext = notLost->inProcess;
-  Maybe<std::vector<int>> maybe;
+  Maybe<std::vector<int32_t>> maybe;
   if (inProcessContext) {
     maybe = inProcessContext->GetInternalformatParameter(target, internalformat,
                                                          pname);
   } else {
     const auto& child = notLost->outOfProcess->mWebGLChild;
     child->FlushPendingCmds();
     if (!child->SendGetInternalformatParameter(target, internalformat, pname,
                                                &maybe)) {
@@ -1800,16 +1806,30 @@ void ClientWebGLContext::GetParameter(JS
         retval.set(JS::NumberValue(limits.maxMultiviewLayers));
         return;
       }
       break;
 
     case LOCAL_GL_PACK_ALIGNMENT:
       retval.set(JS::NumberValue(state.mPixelPackState.alignment));
       return;
+    case LOCAL_GL_UNPACK_ALIGNMENT:
+      retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackAlignment));
+      return;
+
+    case dom::WebGLRenderingContext_Binding::UNPACK_FLIP_Y_WEBGL:
+      retval.set(JS::BooleanValue(state.mPixelUnpackState.mFlipY));
+      return;
+    case dom::WebGLRenderingContext_Binding::UNPACK_PREMULTIPLY_ALPHA_WEBGL:
+      retval.set(JS::BooleanValue(state.mPixelUnpackState.mPremultiplyAlpha));
+      return;
+    case dom::WebGLRenderingContext_Binding::UNPACK_COLORSPACE_CONVERSION_WEBGL:
+      retval.set(
+          JS::NumberValue(state.mPixelUnpackState.mColorspaceConversion));
+      return;
 
     // -
     // Array returns
 
     // 2 floats
     case LOCAL_GL_DEPTH_RANGE:
       retval.set(Create<dom::Float32Array>(cx, this, state.mDepthRange, rv));
       return;
@@ -1943,16 +1963,32 @@ void ClientWebGLContext::GetParameter(JS
         retval.set(JS::NumberValue(state.mPixelPackState.rowLength));
         return;
       case LOCAL_GL_PACK_SKIP_PIXELS:
         retval.set(JS::NumberValue(state.mPixelPackState.skipPixels));
         return;
       case LOCAL_GL_PACK_SKIP_ROWS:
         retval.set(JS::NumberValue(state.mPixelPackState.skipRows));
         return;
+
+      case LOCAL_GL_UNPACK_IMAGE_HEIGHT:
+        retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackImageHeight));
+        return;
+      case LOCAL_GL_UNPACK_ROW_LENGTH:
+        retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackRowLength));
+        return;
+      case LOCAL_GL_UNPACK_SKIP_IMAGES:
+        retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackSkipImages));
+        return;
+      case LOCAL_GL_UNPACK_SKIP_PIXELS:
+        retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackSkipPixels));
+        return;
+      case LOCAL_GL_UNPACK_SKIP_ROWS:
+        retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackSkipRows));
+        return;
     }  // switch pname
   }    // if webgl2
 
   // -
 
   if (!debug) {
     const char* ret = nullptr;
 
@@ -2070,18 +2106,16 @@ void ClientWebGLContext::GetParameter(JS
         case LOCAL_GL_DEPTH_WRITEMASK:
         case LOCAL_GL_DITHER:
         case LOCAL_GL_POLYGON_OFFSET_FILL:
         case LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE:
         case LOCAL_GL_SAMPLE_COVERAGE:
         case LOCAL_GL_SAMPLE_COVERAGE_INVERT:
         case LOCAL_GL_SCISSOR_TEST:
         case LOCAL_GL_STENCIL_TEST:
-        case LOCAL_GL_UNPACK_FLIP_Y_WEBGL:
-        case LOCAL_GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL:
         // WebGL 2:
         case LOCAL_GL_RASTERIZER_DISCARD:
         case LOCAL_GL_TRANSFORM_FEEDBACK_ACTIVE:
         case LOCAL_GL_TRANSFORM_FEEDBACK_PAUSED:
           retval.set(JS::BooleanValue(*maybe));
           break;
 
         default:
@@ -2598,17 +2632,27 @@ void ClientWebGLContext::DepthRange(GLcl
   if (IsContextLost()) return;
   auto& state = State();
 
   state.mDepthRange = {zNear, zFar};
 
   Run<RPROC(DepthRange)>(zNear, zFar);
 }
 
-void ClientWebGLContext::Flush() { Run<RPROC(Flush)>(); }
+void ClientWebGLContext::Flush() {
+  const FuncScope funcScope(*this, "flush");
+  const auto notLost = mNotLost;
+  if (IsContextLost()) return;
+
+  Run<RPROC(Flush)>();
+
+  if (notLost->inProcess) return;
+  const auto& child = mNotLost->outOfProcess->mWebGLChild;
+  child->FlushPendingCmds();
+}
 
 void ClientWebGLContext::Finish() {
   if (IsContextLost()) return;
 
   const auto& inProcess = mNotLost->inProcess;
   if (inProcess) {
     inProcess->Finish();
     return;
@@ -2645,16 +2689,20 @@ GLenum ClientWebGLContext::GetError() {
 void ClientWebGLContext::Hint(GLenum target, GLenum mode) {
   Run<RPROC(Hint)>(target, mode);
 }
 
 void ClientWebGLContext::LineWidth(GLfloat width) {
   Run<RPROC(LineWidth)>(width);
 }
 
+Maybe<webgl::ErrorInfo> SetPixelUnpack(const bool isWebgl2,
+                                       WebGLPixelStore* const unpacking,
+                                       const GLenum pname, const GLint param);
+
 void ClientWebGLContext::PixelStorei(const GLenum pname, const GLint iparam) {
   const FuncScope funcScope(*this, "pixelStorei");
   if (IsContextLost()) return;
   if (!ValidateNonNegative("param", iparam)) return;
   const auto param = static_cast<uint32_t>(iparam);
 
   auto& state = State();
   auto& packState = state.mPixelPackState;
@@ -2685,21 +2733,33 @@ void ClientWebGLContext::PixelStorei(con
       packState.skipPixels = param;
       return;
 
     case LOCAL_GL_PACK_SKIP_ROWS:
       if (!mIsWebGL2) break;
       packState.skipRows = param;
       return;
 
+    case dom::MOZ_debug_Binding::UNPACK_REQUIRE_FASTPATH:
+      if (!IsSupported(WebGLExtensionID::MOZ_debug)) {
+        EnqueueError_ArgEnum("pname", pname);
+        return;
+      }
+      break;
+
     default:
       break;
   }
 
-  Run<RPROC(PixelStorei)>(pname, param);
+  const auto err =
+      SetPixelUnpack(mIsWebGL2, &state.mPixelUnpackState, pname, iparam);
+  if (err) {
+    EnqueueError(*err);
+    return;
+  }
 }
 
 void ClientWebGLContext::PolygonOffset(GLfloat factor, GLfloat units) {
   Run<RPROC(PolygonOffset)>(factor, units);
 }
 
 void ClientWebGLContext::SampleCoverage(GLclampf value, WebGLboolean invert) {
   Run<RPROC(SampleCoverage)>(value, invert);
@@ -2987,25 +3047,32 @@ void ClientWebGLContext::GetBufferSubDat
   const auto& inProcessContext = notLost->inProcess;
   if (inProcessContext) {
     inProcessContext->GetBufferSubData(target, srcByteOffset, destView);
     return;
   }
 
   const auto& child = notLost->outOfProcess->mWebGLChild;
   child->FlushPendingCmds();
-  mozilla::ipc::Shmem shmem;
+  mozilla::ipc::Shmem rawShmem;
   if (!child->SendGetBufferSubData(target, srcByteOffset, destView.length(),
-                                   &shmem))
+                                   &rawShmem)) {
     return;
-  if (!shmem.IsReadable()) return;
-
-  const auto srcView = ByteRange(shmem);
-  MOZ_RELEASE_ASSERT(srcView.length() == destView.length());
-  Memcpy(destView.begin(), srcView.begin(), srcView.length());
+  }
+  const webgl::RaiiShmem shmem{child, rawShmem};
+
+  const auto shmemView = shmem.ByteRange();
+  MOZ_RELEASE_ASSERT(shmemView.length() == 1 + destView.length());
+
+  const auto ok = bool(*(shmemView.begin().get()));
+  const auto srcView =
+      Range<const uint8_t>{shmemView.begin() + 1, shmemView.end()};
+  if (ok) {
+    Memcpy(destView.begin(), srcView.begin(), srcView.length());
+  }
 }
 
 ////
 
 void ClientWebGLContext::BufferData(GLenum target, WebGLsizeiptr rawSize,
                                     GLenum usage) {
   const FuncScope funcScope(*this, "bufferData");
   if (!ValidateNonNegative("size", rawSize)) return;
@@ -3731,62 +3798,169 @@ void ClientWebGLContext::TexStorage(uint
   if (!IsTexTargetForDims(texTarget, mIsWebGL2, funcDims)) {
     EnqueueError_ArgEnum("texTarget", texTarget);
     return;
   }
   Run<RPROC(TexStorage)>(texTarget, static_cast<uint32_t>(levels),
                          internalFormat, CastUvec3(size));
 }
 
+namespace webgl {
+// TODO: Move these definitions into statics here.
+Maybe<webgl::TexUnpackBlobDesc> FromImageBitmap(
+    GLenum target, uvec3 size, const dom::ImageBitmap& imageBitmap,
+    ErrorResult* const out_rv);
+
+webgl::TexUnpackBlobDesc FromImageData(GLenum target, uvec3 size,
+                                       const dom::ImageData& imageData,
+                                       dom::Uint8ClampedArray* const scopedArr);
+
+Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext&,
+                                            GLenum target, uvec3 size,
+                                            const dom::Element& src,
+                                            const bool allowBlitImage,
+                                            ErrorResult* const out_error);
+}  // namespace webgl
+
 void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget,
                                   GLint level, GLenum respecFormat,
-                                  const ivec3& offset, const ivec3& size,
+                                  const ivec3& offset, const ivec3& isize,
                                   GLint border, const webgl::PackingInfo& pi,
                                   const TexImageSource& src) const {
   const FuncScope funcScope(*this, "tex(Sub)Image[23]D");
   if (IsContextLost()) return;
   if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
     EnqueueError_ArgEnum("imageTarget", imageTarget);
     return;
   }
   if (border != 0) {
     EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
     return;
   }
-
-  if (src.mView) {
-    const auto& view = *src.mView;
-    const auto& jsType = view.Type();
-    const auto err = JSTypeMatchUnpackTypeError(pi.type, jsType);
-    switch (err) {
-      case LOCAL_GL_INVALID_ENUM:
-        EnqueueError_ArgEnum("unpackType", pi.type);
-        return;
-      case LOCAL_GL_INVALID_OPERATION:
-        EnqueueError(LOCAL_GL_INVALID_OPERATION,
-                     "ArrayBufferView type %s not compatible with `type` %s.",
-                     ToString(jsType).c_str(), EnumString(pi.type).c_str());
-        return;
-      default:
-        break;
+  const auto explicitSize = CastUvec3(isize);
+
+  // -
+
+  dom::Uint8ClampedArray scopedArr;
+
+  // -
+  bool isDataUpload = false;
+  auto desc = [&]() -> Maybe<webgl::TexUnpackBlobDesc> {
+    if (src.mPboOffset) {
+      isDataUpload = true;
+      const auto offset = static_cast<uint64_t>(*src.mPboOffset);
+      return Some(webgl::TexUnpackBlobDesc{imageTarget,
+                                           explicitSize,
+                                           gfxAlphaType::NonPremult,
+                                           {},
+                                           Some(offset)});
+    }
+
+    if (src.mView) {
+      isDataUpload = true;
+      const auto& view = *src.mView;
+      const auto& jsType = view.Type();
+      const auto err = JSTypeMatchUnpackTypeError(pi.type, jsType);
+      switch (err) {
+        case LOCAL_GL_INVALID_ENUM:
+          EnqueueError_ArgEnum("unpackType", pi.type);
+          return {};
+        case LOCAL_GL_INVALID_OPERATION:
+          EnqueueError(LOCAL_GL_INVALID_OPERATION,
+                       "ArrayBufferView type %s not compatible with `type` %s.",
+                       ToString(jsType).c_str(), EnumString(pi.type).c_str());
+          return {};
+        default:
+          break;
+      }
+
+      const auto range = GetRangeFromView(view, src.mViewElemOffset,
+                                          src.mViewElemLengthOverride);
+      if (!range) {
+        EnqueueError(LOCAL_GL_INVALID_OPERATION, "`source` too small.");
+        return {};
+      }
+      return Some(webgl::TexUnpackBlobDesc{imageTarget,
+                                           explicitSize,
+                                           gfxAlphaType::NonPremult,
+                                           Some(RawBuffer<>{*range}),
+                                           {}});
+    }
+
+    if (src.mImageBitmap) {
+      return webgl::FromImageBitmap(imageTarget, explicitSize,
+                                    *(src.mImageBitmap), src.mOut_error);
+    }
+
+    if (src.mImageData) {
+      return Some(webgl::FromImageData(imageTarget, explicitSize,
+                                       *(src.mImageData), &scopedArr));
     }
-  }
-
-  const auto notLost =
-      mNotLost;  // Hold a strong-ref to prevent LoseContext=>UAF.
-  if (!notLost) return;
-  const auto& inProcessContext = notLost->inProcess;
-  Maybe<std::vector<int>> maybe;
-  if (inProcessContext) {
-    inProcessContext->TexImage(imageTarget, static_cast<uint32_t>(level),
-                               respecFormat, CastUvec3(offset), CastUvec3(size),
-                               pi, src, *GetCanvas());
-  } else {
-    MOZ_ASSERT_UNREACHABLE("TODO: Remote GetInternalformatParameter");
-  }
+
+    if (src.mDomElem) {
+      bool canUseLayerImage = true;
+      if (StaticPrefs::webgl_disable_DOM_blit_uploads()) {
+        canUseLayerImage = false;
+      }
+      if (mNotLost && mNotLost->outOfProcess) {
+        canUseLayerImage = false;
+      }
+
+      return webgl::FromDomElem(*this, imageTarget, explicitSize,
+                                *(src.mDomElem), canUseLayerImage,
+                                src.mOut_error);
+    }
+
+    return Some(webgl::TexUnpackBlobDesc{
+        imageTarget, explicitSize, gfxAlphaType::NonPremult, {}, {}});
+  }();
+  if (!desc) return;
+
+  // -
+  // Further, for uploads from TexImageSource, implied UNPACK_ROW_LENGTH and
+  // UNPACK_ALIGNMENT are not strictly defined. These restrictions ensure
+  // consistent and efficient behavior regardless of implied UNPACK_ params.
+
+  Maybe<gfx::IntSize> structuredSrcSize;
+  if (desc->surf) {
+    structuredSrcSize = Some(desc->surf->GetSize());
+  }
+  if (desc->image) {
+    structuredSrcSize = Some(desc->image->GetSize());
+  }
+  if (structuredSrcSize) {
+    auto& size = desc->size;
+    if (!size.x) {
+      size.x = structuredSrcSize->width;
+    }
+    if (!size.y) {
+      size.y = structuredSrcSize->height;
+    }
+  }
+
+  const auto& rawUnpacking = State().mPixelUnpackState;
+  const bool isSubrect =
+      (rawUnpacking.mUnpackImageHeight || rawUnpacking.mUnpackSkipImages ||
+       rawUnpacking.mUnpackRowLength || rawUnpacking.mUnpackSkipRows ||
+       rawUnpacking.mUnpackSkipPixels);
+  if (isDataUpload && isSubrect) {
+    if (rawUnpacking.mFlipY || rawUnpacking.mPremultiplyAlpha) {
+      EnqueueError(LOCAL_GL_INVALID_OPERATION,
+                   "Non-DOM-Element uploads with alpha-premult"
+                   " or y-flip do not support subrect selection.");
+      return;
+    }
+  }
+  desc->unpacking =
+      rawUnpacking.ForUseWith(desc->imageTarget, desc->size, structuredSrcSize);
+
+  // -
+
+  Run<RPROC(TexImage)>(static_cast<uint32_t>(level), respecFormat,
+                       CastUvec3(offset), pi, std::move(*desc));
 }
 
 void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims,
                                             GLenum imageTarget, GLint level,
                                             GLenum format, const ivec3& offset,
                                             const ivec3& size, GLint border,
                                             const TexImageSource& src,
                                             GLsizei pboImageSize) const {
@@ -4309,39 +4483,46 @@ void ClientWebGLContext::DoReadPixels(co
   child->FlushPendingCmds();
   webgl::ReadPixelsResultIpc res = {};
   if (!child->SendReadPixels(desc, dest.length(), &res)) {
     res = {};
   }
   if (!res.byteStride) return;
   const auto& byteStride = res.byteStride;
   const auto& subrect = res.subrect;
-  const auto& shmemBytes = ByteRange(res.shmem);
+  const webgl::RaiiShmem shmem{child, res.shmem};
+  const auto& shmemBytes = shmem.ByteRange();
 
   uint8_t bpp;
   if (!GetBytesPerPixel(desc.pi, &bpp)) {
     MOZ_ASSERT(false);
     return;
   }
 
+  const auto& packing = desc.packState;
+  auto packRect = *uvec2::From(subrect.x, subrect.y);
+  packRect.x += packing.skipPixels;
+  packRect.y += packing.skipRows;
+
   const auto xByteSize = bpp * static_cast<uint32_t>(subrect.width);
-  const ptrdiff_t byteOffset = subrect.y * byteStride + subrect.x * bpp;
+  const ptrdiff_t byteOffset = packRect.y * byteStride + packRect.x * bpp;
 
   auto srcItr = shmemBytes.begin() + byteOffset;
   auto destItr = dest.begin() + byteOffset;
 
   for (const auto i : IntegerRange(subrect.height)) {
-    Unused << i;
-
-    MOZ_RELEASE_ASSERT(srcItr + xByteSize <= shmemBytes.end());
-    MOZ_RELEASE_ASSERT(destItr + xByteSize <= dest.end());
+    if (i) {
+      // Don't trigger an assert on the last loop by pushing a RangedPtr past
+      // its bounds.
+      srcItr += byteStride;
+      destItr += byteStride;
+      MOZ_RELEASE_ASSERT(srcItr + xByteSize <= shmemBytes.end());
+      MOZ_RELEASE_ASSERT(destItr + xByteSize <= dest.end());
+    }
     Memcpy(destItr, srcItr, xByteSize);
-
-    srcItr += byteStride;
-    destItr += byteStride;
   }
 }
 
 bool ClientWebGLContext::ReadPixels_SharedPrecheck(
     CallerType aCallerType, ErrorResult& out_error) const {
   if (IsContextLost()) return false;
 
   if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
@@ -4413,40 +4594,49 @@ void ClientWebGLContext::GetQuery(JSCont
 void ClientWebGLContext::GetQueryParameter(
     JSContext*, WebGLQueryJS& query, const GLenum pname,
     JS::MutableHandle<JS::Value> retval) const {
   retval.set(JS::NullValue());
   const FuncScope funcScope(*this, "getQueryParameter");
   if (IsContextLost()) return;
   if (!query.ValidateUsable(*this, "query")) return;
 
-  const auto maybe = [&]() {
+  auto maybe = [&]() {
     const auto& inProcess = mNotLost->inProcess;
     if (inProcess) {
       return inProcess->GetQueryParameter(query.mId, pname);
     }
     const auto& child = mNotLost->outOfProcess->mWebGLChild;
     child->FlushPendingCmds();
     Maybe<double> ret;
     if (!child->SendGetQueryParameter(query.mId, pname, &ret)) {
       ret.reset();
     }
     return ret;
   }();
-
-  if (maybe) {
-    switch (pname) {
-      case LOCAL_GL_QUERY_RESULT_AVAILABLE:
-        retval.set(JS::BooleanValue(*maybe));
-        break;
-
-      default:
-        retval.set(JS::NumberValue(*maybe));
-        break;
+  if (!maybe) return;
+
+  // We must usually wait for an event loop before the query can be available.
+  const bool canBeAvailable =
+      (query.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
+  if (!canBeAvailable) {
+    if (pname != LOCAL_GL_QUERY_RESULT_AVAILABLE) {
+      return;
     }
+    maybe = Some(0.0);
+  }
+
+  switch (pname) {
+    case LOCAL_GL_QUERY_RESULT_AVAILABLE:
+      retval.set(JS::BooleanValue(*maybe));
+      break;
+
+    default:
+      retval.set(JS::NumberValue(*maybe));
+      break;
   }
 }
 
 void ClientWebGLContext::BeginQuery(const GLenum specificTarget,
                                     WebGLQueryJS& query) {
   const FuncScope funcScope(*this, "beginQuery");
   if (IsContextLost()) return;
   if (!query.ValidateUsable(*this, "query")) return;
@@ -4493,20 +4683,24 @@ void ClientWebGLContext::EndQuery(const 
     return;
   }
   auto& slot = *maybeSlot;
   if (!slot || slot->mTarget != specificTarget) {
     EnqueueError(LOCAL_GL_INVALID_OPERATION, "No Query is active for %s.",
                  EnumString(specificTarget).c_str());
     return;
   }
-
+  const auto query = slot;
   slot = nullptr;
 
   Run<RPROC(EndQuery)>(specificTarget);
+
+  auto& availRunnable = EnsureAvailabilityRunnable();
+  availRunnable.mQueries.push_back(query.get());
+  query->mCanBeAvailable = false;
 }
 
 void ClientWebGLContext::QueryCounter(WebGLQueryJS& query,
                                       const GLenum target) const {
   const FuncScope funcScope(*this, "queryCounter");
   if (IsContextLost()) return;
   if (!query.ValidateUsable(*this, "query")) return;
 
@@ -4518,16 +4712,20 @@ void ClientWebGLContext::QueryCounter(We
   if (query.mTarget && query.mTarget != target) {
     EnqueueError(LOCAL_GL_INVALID_OPERATION,
                  "`query` cannot be changed to a different target.");
     return;
   }
   query.mTarget = target;
 
   Run<RPROC(QueryCounter)>(query.mId);
+
+  auto& availRunnable = EnsureAvailabilityRunnable();
+  availRunnable.mQueries.push_back(&query);
+  query.mCanBeAvailable = false;
 }
 
 // -------------------------------- Sampler -------------------------------
 void ClientWebGLContext::GetSamplerParameter(
     JSContext* cx, const WebGLSamplerJS& sampler, const GLenum pname,
     JS::MutableHandle<JS::Value> retval) const {
   retval.set(JS::NullValue());
   const FuncScope funcScope(*this, "getSamplerParameter");
@@ -4656,16 +4854,29 @@ GLenum ClientWebGLContext::ClientWaitSyn
 
   switch (ret) {
     case LOCAL_GL_CONDITION_SATISFIED:
     case LOCAL_GL_ALREADY_SIGNALED:
       sync.mSignaled = true;
       break;
   }
 
+  // -
+
+  const bool canBeAvailable =
+      (sync.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
+  if (!canBeAvailable) {
+    if (timeout) {
+      EnqueueWarning(
+          "Sync object not yet queryable. Please wait for the event"
+          " loop.");
+    }
+    return LOCAL_GL_WAIT_FAILED;
+  }
+
   return ret;
 }
 
 void ClientWebGLContext::WaitSync(const WebGLSyncJS& sync,
                                   const GLbitfield flags,
                                   const GLint64 timeout) const {
   const FuncScope funcScope(*this, "waitSync");
   if (IsContextLost()) return;
--- a/dom/canvas/ClientWebGLContext.h
+++ b/dom/canvas/ClientWebGLContext.h
@@ -41,16 +41,17 @@ namespace mozilla {
 
 class ClientWebGLExtensionBase;
 
 namespace dom {
 class WebGLChild;
 }
 
 namespace webgl {
+class AvailabilityRunnable;
 class TexUnpackBlob;
 class TexUnpackBytes;
 }  // namespace webgl
 
 ////////////////////////////////////
 
 class WebGLActiveInfoJS final : public RefCounted<WebGLActiveInfoJS> {
  public:
@@ -170,16 +171,17 @@ class ContextGenerationInfo final {
 
   std::array<bool, 4> mColorWriteMask = {{true, true, true, true}};
   std::array<int32_t, 4> mScissor = {};
   std::array<int32_t, 4> mViewport = {};
   std::array<float, 4> mClearColor = {{0, 0, 0, 0}};
   std::array<float, 4> mBlendColor = {{0, 0, 0, 0}};
   std::array<float, 2> mDepthRange = {{0, 1}};
   webgl::PixelPackState mPixelPackState;
+  WebGLPixelStore mPixelUnpackState;
 
   std::vector<GLenum> mCompressedTextureFormats;
 
   Maybe<uvec2> mDrawingBufferSize;
 
   ObjectId NextId() { return mLastId += 1; }
 };
 
@@ -383,24 +385,29 @@ class WebGLProgramJS final : public nsWr
   bool IsDeleted() const override { return !mKeepAliveWeak.lock(); }
   GLenum ErrorOnDeleted() const override { return LOCAL_GL_INVALID_VALUE; }
 
   JSObject* WrapObject(JSContext*, JS::Handle<JSObject*>) override;
 };
 
 // -
 
-class WebGLQueryJS final : public nsWrapperCache, public webgl::ObjectJS {
+class WebGLQueryJS final : public nsWrapperCache,
+                           public webgl::ObjectJS,
+                           public SupportsWeakPtr<WebGLQueryJS> {
   friend class ClientWebGLContext;
+  friend class webgl::AvailabilityRunnable;
 
   GLenum mTarget = 0;  // !IsQuery until Bind
+  bool mCanBeAvailable = false;
 
  public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLQueryJS)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLQueryJS)
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLQueryJS)
 
   explicit WebGLQueryJS(const ClientWebGLContext& webgl)
       : webgl::ObjectJS(webgl) {}
 
  private:
   ~WebGLQueryJS() = default;
 
  public:
@@ -478,24 +485,29 @@ class WebGLShaderJS final : public nsWra
   bool IsDeleted() const override { return !mKeepAliveWeak.lock(); }
   GLenum ErrorOnDeleted() const override { return LOCAL_GL_INVALID_VALUE; }
 
   JSObject* WrapObject(JSContext*, JS::Handle<JSObject*>) override;
 };
 
 // -
 
-class WebGLSyncJS final : public nsWrapperCache, public webgl::ObjectJS {
+class WebGLSyncJS final : public nsWrapperCache,
+                          public webgl::ObjectJS,
+                          public SupportsWeakPtr<WebGLSyncJS> {
   friend class ClientWebGLContext;
-
+  friend class webgl::AvailabilityRunnable;
+
+  bool mCanBeAvailable = false;
   bool mSignaled = false;
 
  public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLSyncJS)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLSyncJS)
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLSyncJS)
 
   explicit WebGLSyncJS(const ClientWebGLContext& webgl)
       : webgl::ObjectJS(webgl) {}
 
  private:
   ~WebGLSyncJS() = default;
 
  public:
@@ -689,16 +701,17 @@ struct TexImageSourceAdapter final : pub
 };
 
 /**
  * Base class for all IDL implementations of WebGLContext
  */
 class ClientWebGLContext final : public nsICanvasRenderingContextInternal,
                                  public nsWrapperCache,
                                  public SupportsWeakPtr<ClientWebGLContext> {
+  friend class webgl::AvailabilityRunnable;
   friend class webgl::ObjectJS;
   friend class webgl::ProgramKeepAlive;
   friend class webgl::ShaderKeepAlive;
 
  public:
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(ClientWebGLContext)
 
   // ----------------------------- Lifetime and DOM ---------------------------
@@ -748,16 +761,24 @@ class ClientWebGLContext final : public 
 
   auto& State() { return mNotLost->state; }
   const auto& State() const {
     return const_cast<ClientWebGLContext*>(this)->State();
   }
 
   // -
 
+ private:
+  mutable RefPtr<webgl::AvailabilityRunnable> mAvailabilityRunnable;
+
+ public:
+  webgl::AvailabilityRunnable& EnsureAvailabilityRunnable() const;
+
+  // -
+
  public:
   void EmulateLoseContext() const;
   void OnContextLoss(webgl::ContextLossReason) const;
   void RestoreContext(webgl::LossStatus requiredStatus) const;
 
  private:
   bool DispatchEvent(const nsAString&) const;
   void Event_webglcontextlost() const;
@@ -2100,19 +2121,16 @@ class ClientWebGLContext final : public 
   template <typename MethodType, MethodType method, typename... Args>
   void Run(Args&&... aArgs) const;
 
   // -------------------------------------------------------------------------
   // Helpers for DOM operations, composition, actors, etc
   // -------------------------------------------------------------------------
 
  public:
-  WebGLPixelStore GetPixelStore() { return mPixelStore; }
-  const WebGLPixelStore GetPixelStore() const { return mPixelStore; }
-
   // https://immersive-web.github.io/webxr/#xr-compatible
   bool IsXRCompatible() const;
   already_AddRefed<dom::Promise> MakeXRCompatible(ErrorResult& aRv);
 
  protected:
   bool ShouldResistFingerprinting() const;
 
   // Prepare the context for capture before compositing
@@ -2120,17 +2138,16 @@ class ClientWebGLContext final : public 
 
   // Clean up the context after captured for compositing
   void EndComposition();
 
   mozilla::dom::Document* GetOwnerDoc() const;
 
   bool mResetLayer = true;
   Maybe<const WebGLContextOptions> mInitialOptions;
-  WebGLPixelStore mPixelStore;
   bool mXRCompatible = false;
 };
 
 // used by DOM bindings in conjunction with GetParentObject
 inline nsISupports* ToSupports(ClientWebGLContext* webgl) {
   return static_cast<nsICanvasRenderingContextInternal*>(webgl);
 }
 
--- a/dom/canvas/HostWebGLContext.h
+++ b/dom/canvas/HostWebGLContext.h
@@ -420,20 +420,16 @@ class HostWebGLContext final : public Su
   void LineWidth(GLfloat width) const { mContext->LineWidth(width); }
 
   void LinkProgram(const ObjectId id) const {
     const auto obj = ById<WebGLProgram>(id);
     if (!obj) return;
     mContext->LinkProgram(*obj);
   }
 
-  void PixelStorei(GLenum pname, uint32_t param) const {
-    mContext->PixelStorei(pname, param);
-  }
-
   void PolygonOffset(GLfloat factor, GLfloat units) const {
     mContext->PolygonOffset(factor, units);
   }
 
   void SampleCoverage(GLclampf value, bool invert) const {
     mContext->SampleCoverage(value, invert);
   }
 
@@ -565,22 +561,20 @@ class HostWebGLContext final : public Su
   void CopyTexImage(GLenum imageTarget, uint32_t level, GLenum respecFormat,
                     const uvec3& dstOffset, const ivec2& srcOffset,
                     const uvec2& size) const {
     mContext->CopyTexImage(imageTarget, level, respecFormat, dstOffset,
                            srcOffset, size);
   }
 
   // TexSubImage if `!respecFormat`
-  void TexImage(GLenum imageTarget, uint32_t level, GLenum respecFormat,
-                const uvec3& offset, const uvec3& size,
-                const webgl::PackingInfo& pi, const TexImageSource& src,
-                const dom::HTMLCanvasElement& canvas) const {
-    mContext->TexImage(imageTarget, level, respecFormat, offset, size, pi, src,
-                       canvas);
+  void TexImage(uint32_t level, GLenum respecFormat, const uvec3& offset,
+                const webgl::PackingInfo& pi,
+                const webgl::TexUnpackBlobDesc& src) const {
+    mContext->TexImage(level, respecFormat, offset, pi, src);
   }
 
   void TexStorage(GLenum texTarget, uint32_t levels, GLenum internalFormat,
                   const uvec3& size) const {
     GetWebGL2Context()->TexStorage(texTarget, levels, internalFormat, size);
   }
 
   Maybe<double> GetTexParameter(ObjectId id, GLenum pname) const {
--- a/dom/canvas/PWebGL.ipdl
+++ b/dom/canvas/PWebGL.ipdl
@@ -7,29 +7,26 @@
 
 include protocol PCompositorBridge;
 include protocol PLayerTransaction;
 
 using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::SurfaceDescriptor from "mozilla/layers/LayersTypes.h";
 using std::string from "ipc/IPCMessageUtils.h";
 using mozilla::uvec2 from "mozilla/dom/WebGLIpdl.h";
+using mozilla::webgl::Int32Vector from "mozilla/dom/WebGLIpdl.h";
 using mozilla::webgl::CompileResult from "mozilla/dom/WebGLIpdl.h";
 using mozilla::webgl::ContextLossReason from "mozilla/dom/WebGLIpdl.h";
 using mozilla::webgl::FrontBufferSnapshotIpc from "mozilla/dom/WebGLIpdl.h";
 using mozilla::webgl::GetUniformData from "mozilla/dom/WebGLIpdl.h";
 using mozilla::webgl::InitContextDesc from "mozilla/dom/WebGLIpdl.h";
 using mozilla::webgl::InitContextResult from "mozilla/dom/WebGLIpdl.h";
 using mozilla::webgl::Int32Vector from "mozilla/dom/WebGLIpdl.h";
 using mozilla::webgl::LinkResult from "mozilla/dom/WebGLIpdl.h";
-using mozilla::webgl::MaybeDouble from "mozilla/dom/WebGLIpdl.h";
-using mozilla::webgl::MaybeFrontBufferSnapshotIpc from "mozilla/dom/WebGLIpdl.h";
-using mozilla::webgl::MaybeShaderPrecisionFormat from "mozilla/dom/WebGLIpdl.h";
-using mozilla::webgl::MaybeSurfaceDescriptor from "mozilla/dom/WebGLIpdl.h";
-using mozilla::webgl::MaybeString from "mozilla/dom/WebGLIpdl.h";
+using mozilla::webgl::ShaderPrecisionFormat from "mozilla/dom/WebGLIpdl.h";
 using mozilla::webgl::OpaqueFramebufferOptions from "mozilla/dom/WebGLIpdl.h";
 using mozilla::webgl::ReadPixelsDesc from "mozilla/dom/WebGLIpdl.h";
 using mozilla::webgl::ReadPixelsResultIpc from "mozilla/dom/WebGLIpdl.h";
 using mozilla::HostWebGLCommandSinkP from "mozilla/dom/WebGLCrossProcessCommandQueue.h";
 using mozilla::HostWebGLCommandSinkI from "mozilla/dom/WebGLCrossProcessCommandQueue.h";
 using mozilla::dom::IpdlQueueBuffer from "mozilla/dom/IpdlQueue.h";
 using mozilla::dom::IpdlQueueBuffers from "mozilla/dom/IpdlQueue.h";
 
@@ -53,48 +50,48 @@ parent:
   async __delete__();
 
   // -
 
   async DispatchCommands(Shmem commands, uint64_t size);
 
   // -
 
+  sync GetBufferSubData(uint32_t target, uint64_t srcByteOffset, uint64_t byteSize) returns (Shmem ret);
   sync GetFrontBufferSnapshot() returns (FrontBufferSnapshotIpc ret);
   sync ReadPixels(ReadPixelsDesc desc, uint64_t maxBytes) returns (ReadPixelsResultIpc ret);
 
   // -
 
   sync CheckFramebufferStatus(uint32_t target) returns (uint32_t ret);
   sync ClientWaitSync(uint64_t id, uint32_t flags, uint64_t timeout) returns (uint32_t ret);
   sync CreateOpaqueFramebuffer(uint64_t id, OpaqueFramebufferOptions options) returns (bool ret);
   sync DrawingBufferSize() returns (uvec2 ret);
   sync Finish();
-  sync GetBufferParameter(uint32_t target, uint32_t pname) returns (MaybeDouble ret);
-  sync GetBufferSubData(uint32_t target, uint64_t srcByteOffset, uint64_t byteSize) returns (Shmem ret);
+  sync GetBufferParameter(uint32_t target, uint32_t pname) returns (double? ret);
   sync GetCompileResult(uint64_t id) returns (CompileResult ret);
   sync GetError() returns (uint32_t ret);
   sync GetFragDataLocation(uint64_t id, string name) returns (int32_t ret);
   sync GetFramebufferAttachmentParameter(uint64_t id,
                                                   uint32_t attachment,
-                                                  uint32_t pname) returns (MaybeDouble ret);
-  sync GetFrontBuffer(uint64_t fb, bool vr) returns (MaybeSurfaceDescriptor ret);
-  sync GetIndexedParameter(uint32_t target, uint32_t index) returns (MaybeDouble ret);
+                                                  uint32_t pname) returns (double? ret);
+  sync GetFrontBuffer(uint64_t fb, bool vr) returns (SurfaceDescriptor? ret);
+  sync GetIndexedParameter(uint32_t target, uint32_t index) returns (double? ret);
   sync GetInternalformatParameter(uint32_t target, uint32_t internalFormat, uint32_t pname) returns (Int32Vector? ret);
   sync GetLinkResult(uint64_t id) returns (LinkResult ret);
-  sync GetNumber(uint32_t pname) returns (MaybeDouble ret);
-  sync GetQueryParameter(uint64_t id, uint32_t pname) returns (MaybeDouble ret);
-  sync GetRenderbufferParameter(uint64_t id, uint32_t pname) returns (MaybeDouble ret);
-  sync GetSamplerParameter(uint64_t id, uint32_t pname) returns (MaybeDouble ret);
+  sync GetNumber(uint32_t pname) returns (double? ret);
+  sync GetQueryParameter(uint64_t id, uint32_t pname) returns (double? ret);
+  sync GetRenderbufferParameter(uint64_t id, uint32_t pname) returns (double? ret);
+  sync GetSamplerParameter(uint64_t id, uint32_t pname) returns (double? ret);
   sync GetShaderPrecisionFormat(
-      uint32_t shaderType, uint32_t precisionType) returns (MaybeShaderPrecisionFormat ret);
-  sync GetString(uint32_t pname) returns (MaybeString ret);
-  sync GetTexParameter(uint64_t id, uint32_t pname) returns (MaybeDouble ret);
+      uint32_t shaderType, uint32_t precisionType) returns (ShaderPrecisionFormat? ret);
+  sync GetString(uint32_t pname) returns (string? ret);
+  sync GetTexParameter(uint64_t id, uint32_t pname) returns (double? ret);
   sync GetUniform(uint64_t id, uint32_t loc) returns (GetUniformData ret);
-  sync GetVertexAttrib(uint32_t index, uint32_t pname) returns (MaybeDouble ret);
+  sync GetVertexAttrib(uint32_t index, uint32_t pname) returns (double? ret);
   sync IsEnabled(uint32_t cap) returns (bool ret);
   sync OnMemoryPressure();
   sync ValidateProgram(uint64_t id) returns (bool ret);
 
 child:
   async JsWarning(string text);
 
   // Tell client that this queue needs to be shut down
--- a/dom/canvas/QueueParamTraits.h
+++ b/dom/canvas/QueueParamTraits.h
@@ -3,25 +3,27 @@
  */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _QUEUEPARAMTRAITS_H_
 #define _QUEUEPARAMTRAITS_H_ 1
 
-#include "mozilla/ipc/SharedMemoryBasic.h"
+#include "mozilla/gfx/2D.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/IntegerRange.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/SharedMemoryBasic.h"
 #include "mozilla/ipc/Shmem.h"
-#include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/Logging.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TypeTraits.h"
 #include "nsString.h"
+#include "WebGLTypes.h"
 
 namespace mozilla {
 namespace webgl {
 
 enum class QueueStatus {
   // Operation was successful
   kSuccess,
   // The operation failed because the queue isn't ready for it.
@@ -163,16 +165,23 @@ class ProducerView {
   inline QueueStatus Write(const uint8_t* begin, const uint8_t* end);
 
   template <typename T>
   inline QueueStatus Write(const T* begin, const T* end) {
     return Write(reinterpret_cast<const uint8_t*>(begin),
                  reinterpret_cast<const uint8_t*>(end));
   }
 
+  template <typename T>
+  inline QueueStatus WritePod(const T& in) {
+    static_assert(std::is_trivially_copyable_v<T>);
+    const auto begin = reinterpret_cast<const uint8_t*>(&in);
+    return Write(begin, begin + sizeof(T));
+  }
+
   /**
    * Serialize aArg using Arg's QueueParamTraits.
    */
   template <typename Arg>
   QueueStatus WriteParam(const Arg& aArg) {
     return mozilla::webgl::QueueParamTraits<
         typename RemoveCVR<Arg>::Type>::Write(*this, aArg);
   }
@@ -208,16 +217,23 @@ class ConsumerView {
   inline QueueStatus Read(uint8_t* begin, uint8_t* end);
 
   template <typename T>
   inline QueueStatus Read(T* begin, T* end) {
     return Read(reinterpret_cast<uint8_t*>(begin),
                 reinterpret_cast<uint8_t*>(end));
   }
 
+  template <typename T>
+  inline QueueStatus ReadPod(T* out) {
+    static_assert(std::is_trivially_copyable_v<T>);
+    const auto begin = reinterpret_cast<uint8_t*>(out);
+    return Read(begin, begin + sizeof(T));
+  }
+
   /**
    * Deserialize aArg using Arg's QueueParamTraits.
    * If the return value is not Success then aArg is not changed.
    */
   template <typename Arg>
   QueueStatus ReadParam(Arg* aArg) {
     MOZ_ASSERT(aArg);
     return mozilla::webgl::QueueParamTraits<std::remove_cv_t<Arg>>::Read(*this,
@@ -229,29 +245,33 @@ class ConsumerView {
  private:
   Consumer* mConsumer;
   size_t* mRead;
   size_t mWrite;
   QueueStatus mStatus;
 };
 
 template <typename T>
-QueueStatus ProducerView<T>::Write(const uint8_t* begin, const uint8_t* end) {
-  MOZ_ASSERT(begin);
-  MOZ_ASSERT(begin < end);
-  if (IsSuccess(mStatus)) {
+QueueStatus ProducerView<T>::Write(const uint8_t* const begin,
+                                   const uint8_t* const end) {
+  MOZ_ASSERT(begin <= end);
+  if (!mStatus) return mStatus;
+  if (begin < end) {
+    MOZ_ASSERT(begin);
     mStatus = mProducer->WriteObject(mRead, mWrite, begin, end - begin);
   }
   return mStatus;
 }
 
 template <typename T>
-QueueStatus ConsumerView<T>::Read(uint8_t* begin, uint8_t* end) {
-  MOZ_ASSERT(begin < end);
-  if (IsSuccess(mStatus)) {
+QueueStatus ConsumerView<T>::Read(uint8_t* const begin, uint8_t* const end) {
+  MOZ_ASSERT(begin <= end);
+  if (!mStatus) return mStatus;
+  if (begin < end) {
+    MOZ_ASSERT(begin);
     mStatus = mConsumer->ReadObject(mRead, mWrite, begin, end - begin);
   }
   return mStatus;
 }
 
 // ---------------------------------------------------------------
 
 /**
@@ -332,16 +352,92 @@ struct ContiguousEnumSerializerInclusive
 template <>
 struct QueueParamTraits<QueueStatus>
     : public ContiguousEnumSerializerInclusive<
           QueueStatus, QueueStatus::kSuccess, QueueStatus::kOOMError> {};
 
 // ---------------------------------------------------------------
 
 template <>
+struct QueueParamTraits<webgl::TexUnpackBlobDesc> {
+  using ParamType = webgl::TexUnpackBlobDesc;
+
+  template <typename U>
+  static QueueStatus Write(ProducerView<U>& view, const ParamType& in) {
+    MOZ_ASSERT(!in.image);
+    const bool isSurf = bool(in.surf);
+    if (!view.WriteParam(in.imageTarget) || !view.WriteParam(in.size) ||
+        !view.WriteParam(in.srcAlphaType) || !view.WriteParam(in.unpacking) ||
+        !view.WriteParam(in.cpuData) || !view.WriteParam(in.pboOffset) ||
+        !view.WriteParam(isSurf)) {
+      return view.GetStatus();
+    }
+    if (isSurf) {
+      gfx::DataSourceSurface::ScopedMap map(in.surf,
+                                            gfx::DataSourceSurface::READ);
+      if (!map.IsMapped()) {
+        return QueueStatus::kOOMError;
+      }
+      const auto& surfSize = in.surf->GetSize();
+      const auto stride = *MaybeAs<size_t>(map.GetStride());
+      if (!view.WriteParam(surfSize) ||
+          !view.WriteParam(in.surf->GetFormat()) || !view.WriteParam(stride)) {
+        return view.GetStatus();
+      }
+
+      const size_t dataSize = stride * surfSize.height;
+      const auto& begin = map.GetData();
+      if (!view.Write(begin, begin + dataSize)) {
+        return view.GetStatus();
+      }
+    }
+    return QueueStatus::kSuccess;
+  }
+
+  template <typename U>
+  static QueueStatus Read(ConsumerView<U>& view, ParamType* const out) {
+    bool isSurf;
+    if (!view.ReadParam(&out->imageTarget) || !view.ReadParam(&out->size) ||
+        !view.ReadParam(&out->srcAlphaType) ||
+        !view.ReadParam(&out->unpacking) || !view.ReadParam(&out->cpuData) ||
+        !view.ReadParam(&out->pboOffset) || !view.ReadParam(&isSurf)) {
+      return view.GetStatus();
+    }
+    if (isSurf) {
+      gfx::IntSize surfSize;
+      gfx::SurfaceFormat format;
+      size_t stride;
+      if (!view.ReadParam(&surfSize) || !view.ReadParam(&format) ||
+          !view.ReadParam(&stride)) {
+        return view.GetStatus();
+      }
+      out->surf = gfx::Factory::CreateDataSourceSurfaceWithStride(
+          surfSize, format, stride, true);
+      if (!out->surf) {
+        return QueueStatus::kOOMError;
+      }
+
+      gfx::DataSourceSurface::ScopedMap map(out->surf,
+                                            gfx::DataSourceSurface::WRITE);
+      if (!map.IsMapped()) {
+        return QueueStatus::kOOMError;
+      }
+      const size_t dataSize = stride * surfSize.height;
+      const auto& begin = map.GetData();
+      if (!view.Read(begin, begin + dataSize)) {
+        return view.GetStatus();
+      }
+    }
+    return QueueStatus::kSuccess;
+  }
+};
+
+// ---------------------------------------------------------------
+
+template <>
 struct QueueParamTraits<nsACString> {
   using ParamType = nsACString;
 
   template <typename U>
   static QueueStatus Write(ProducerView<U>& aProducerView,
                            const ParamType& aArg) {
     if ((!aProducerView.WriteParam(aArg.IsVoid())) || aArg.IsVoid()) {
       return aProducerView.GetStatus();
--- a/dom/canvas/TexUnpackBlob.cpp
+++ b/dom/canvas/TexUnpackBlob.cpp
@@ -13,16 +13,41 @@
 #include "nsLayoutUtils.h"
 #include "WebGLBuffer.h"
 #include "WebGLContext.h"
 #include "WebGLFormats.h"
 #include "WebGLTexelConversions.h"
 #include "WebGLTexture.h"
 
 namespace mozilla {
+
+void WebGLPixelStore::Apply(gl::GLContext& gl, const bool isWebgl2,
+                            const uvec3& uploadSize) const {
+  gl.fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, mUnpackAlignment);
+  if (!isWebgl2) return;
+
+  // Re-simplify. (ANGLE seems to have an issue with imageHeight ==
+  // uploadSize.y)
+  auto rowLength = mUnpackRowLength;
+  auto imageHeight = mUnpackImageHeight;
+  if (rowLength == uploadSize.x) {
+    rowLength = 0;
+  }
+  if (imageHeight == uploadSize.y) {
+    imageHeight = 0;
+  }
+
+  gl.fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
+  gl.fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, imageHeight);
+
+  gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS, mUnpackSkipPixels);
+  gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, mUnpackSkipRows);
+  gl.fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, mUnpackSkipImages);
+}
+
 namespace webgl {
 
 static bool IsPIValidForDOM(const webgl::PackingInfo& pi) {
   // https://www.khronos.org/registry/webgl/specs/latest/2.0/#TEXTURE_TYPES_FORMATS_FROM_DOM_ELEMENTS_TABLE
 
   // Just check for invalid individual formats and types, not combinations.
   switch (pi.format) {
     case LOCAL_GL_RGB:
@@ -60,17 +85,17 @@ static bool IsPIValidForDOM(const webgl:
 
     default:
       return false;
   }
 
   return true;
 }
 
-static bool ValidatePIForDOM(WebGLContext* webgl,
+static bool ValidatePIForDOM(const WebGLContext* const webgl,
                              const webgl::PackingInfo& pi) {
   if (!IsPIValidForDOM(pi)) {
     webgl->ErrorInvalidValue("Format or type is invalid for DOM sources.");
     return false;
   }
   return true;
 }
 
@@ -173,126 +198,146 @@ static WebGLTexelFormat FormatForPacking
       break;
   }
 
   return WebGLTexelFormat::FormatNotSupportingAnyConversion;
 }
 
 ////////////////////
 
-static bool ValidateUnpackPixels(WebGLContext* webgl, uint32_t fullRows,
+static uint32_t ZeroOn2D(const GLenum target, const uint32_t val) {
+  const bool is2d = !IsTexTarget3D(target);
+  if (is2d) return 0;
+  return val;
+}
+
+static bool ValidateUnpackPixels(const WebGLContext* webgl, uint32_t fullRows,
                                  uint32_t tailPixels,
-                                 webgl::TexUnpackBlob* blob) {
-  if (!blob->mWidth || !blob->mHeight || !blob->mDepth) return true;
+                                 webgl::TexUnpackBlob* const blob) {
+  const auto& size = blob->mDesc.size;
+  if (!size.x || !size.y || !size.z) return true;
+
+  const auto& unpacking = blob->mDesc.unpacking;
 
-  const auto usedPixelsPerRow = CheckedUint32(blob->mSkipPixels) + blob->mWidth;
+  // -
+
+  const auto usedPixelsPerRow =
+      CheckedUint32(unpacking.mUnpackSkipPixels) + size.x;
   if (!usedPixelsPerRow.isValid() ||
-      usedPixelsPerRow.value() > blob->mRowLength) {
+      usedPixelsPerRow.value() > unpacking.mUnpackRowLength) {
     webgl->ErrorInvalidOperation(
         "UNPACK_SKIP_PIXELS + width >"
         " UNPACK_ROW_LENGTH.");
     return false;
   }
 
-  if (blob->mHeight > blob->mImageHeight) {
+  if (size.y > unpacking.mUnpackImageHeight) {
     webgl->ErrorInvalidOperation("height > UNPACK_IMAGE_HEIGHT.");
     return false;
   }
 
   //////
 
   // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
-  auto skipFullRows = CheckedUint32(blob->mSkipImages) * blob->mImageHeight;
-  skipFullRows += blob->mSkipRows;
+  auto skipFullRows =
+      CheckedUint32(unpacking.mUnpackSkipImages) * unpacking.mUnpackImageHeight;
+  skipFullRows += unpacking.mUnpackSkipRows;
 
-  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.
+  // Full rows in the final image, excluding the tail.
+  MOZ_ASSERT(size.y >= 1);
+  MOZ_ASSERT(size.z >= 1);
+  auto usedFullRows = CheckedUint32(size.z - 1) * unpacking.mUnpackImageHeight;
+  usedFullRows += size.y - 1;
 
   const auto fullRowsNeeded = skipFullRows + usedFullRows;
   if (!fullRowsNeeded.isValid()) {
     webgl->ErrorOutOfMemory("Invalid calculation for required row count.");
     return false;
   }
 
-  if (fullRows > fullRowsNeeded.value()) return true;
+  if (fullRows > fullRowsNeeded.value()) {
+    blob->mNeedsExactUpload = false;
+    return true;
+  }
 
   if (fullRows == fullRowsNeeded.value() &&
       tailPixels >= usedPixelsPerRow.value()) {
-    blob->mNeedsExactUpload = true;
+    MOZ_ASSERT(blob->mNeedsExactUpload);
     return true;
   }
 
   webgl->ErrorInvalidOperation(
       "Desired upload requires more data than is"
       " available: (%u rows plus %u pixels needed, %u rows"
       " plus %u pixels available)",
       fullRowsNeeded.value(), usedPixelsPerRow.value(), fullRows, tailPixels);
   return false;
 }
 
-static bool ValidateUnpackBytes(WebGLContext* webgl,
+static bool ValidateUnpackBytes(const WebGLContext* const webgl,
                                 const webgl::PackingInfo& pi,
                                 size_t availByteCount,
-                                webgl::TexUnpackBlob* blob) {
-  if (!blob->mWidth || !blob->mHeight || !blob->mDepth) return true;
+                                webgl::TexUnpackBlob* const blob) {
+  const auto& size = blob->mDesc.size;
+  if (!size.x || !size.y || !size.z) return true;
+  const auto& unpacking = blob->mDesc.unpacking;
 
   const auto bytesPerPixel = webgl::BytesPerPixel(pi);
-  const auto bytesPerRow = CheckedUint32(blob->mRowLength) * bytesPerPixel;
-  const auto rowStride = RoundUpToMultipleOf(bytesPerRow, blob->mAlignment);
+  const auto bytesPerRow =
+      CheckedUint32(unpacking.mUnpackRowLength) * bytesPerPixel;
+  const auto rowStride =
+      RoundUpToMultipleOf(bytesPerRow, unpacking.mUnpackAlignment);
 
   const auto fullRows = availByteCount / rowStride;
   if (!fullRows.isValid()) {
     webgl->ErrorOutOfMemory("Unacceptable upload size calculated.");
     return false;
   }
 
   const auto bodyBytes = fullRows.value() * rowStride.value();
   const auto tailPixels = (availByteCount - bodyBytes) / bytesPerPixel;
 
   return ValidateUnpackPixels(webgl, 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) {
-  return (val ? val : fallback);
-}
-
-TexUnpackBlob::TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target,
-                             uint32_t rowLength, uint32_t width,
-                             uint32_t height, uint32_t depth,
-                             gfxAlphaType srcAlphaType)
-    : mAlignment(webgl->mPixelStore.mUnpackAlignment),
-      mRowLength(rowLength),
-      mImageHeight(FallbackOnZero(
-          ZeroOn2D(target, webgl->mPixelStore.mUnpackImageHeight), height))
+// static
+std::unique_ptr<TexUnpackBlob> TexUnpackBlob::Create(
+    const TexUnpackBlobDesc& desc) {
+  return std::unique_ptr<TexUnpackBlob>{[&]() -> TexUnpackBlob* {
+    if (!IsTarget3D(desc.imageTarget) && desc.size.z != 1) {
+      MOZ_ASSERT(false);
+      return nullptr;
+    }
 
-      ,
-      mSkipPixels(webgl->mPixelStore.mUnpackSkipPixels),
-      mSkipRows(webgl->mPixelStore.mUnpackSkipRows),
-      mSkipImages(ZeroOn2D(target, webgl->mPixelStore.mUnpackSkipImages))
+    switch (desc.unpacking.mUnpackAlignment) {
+      case 1:
+      case 2:
+      case 4:
+      case 8:
+        break;
+      default:
+        MOZ_ASSERT(false);
+        return nullptr;
+    }
 
-      ,
-      mWidth(width),
-      mHeight(height),
-      mDepth(depth)
+    if (desc.image) {
+      return new TexUnpackImage(desc);
+    }
+    if (desc.surf) {
+      return new TexUnpackSurface(desc);
+    }
 
-      ,
-      mSrcAlphaType(srcAlphaType)
-
-      ,
-      mNeedsExactUpload(false) {
-  MOZ_ASSERT_IF(!IsTarget3D(target), mDepth == 1);
+    if (desc.srcAlphaType != gfxAlphaType::NonPremult) {
+      MOZ_ASSERT(false);
+      return nullptr;
+    }
+    return new TexUnpackBytes(desc);
+  }()};
 }
 
 static bool HasColorAndAlpha(const WebGLTexelFormat format) {
   switch (format) {
     case WebGLTexelFormat::RA8:
     case WebGLTexelFormat::RA16F:
     case WebGLTexelFormat::RA32F:
     case WebGLTexelFormat::RGBA8:
@@ -303,41 +348,43 @@ static bool HasColorAndAlpha(const WebGL
     case WebGLTexelFormat::BGRA8:
       return true;
     default:
       return false;
   }
 }
 
 bool TexUnpackBlob::ConvertIfNeeded(
-    WebGLContext* webgl, const uint32_t rowLength, const uint32_t rowCount,
-    WebGLTexelFormat srcFormat, const uint8_t* const srcBegin,
-    const ptrdiff_t srcStride, WebGLTexelFormat dstFormat,
-    const ptrdiff_t dstStride, const uint8_t** const out_begin,
+    const WebGLContext* const webgl, const uint32_t rowLength,
+    const uint32_t rowCount, WebGLTexelFormat srcFormat,
+    const uint8_t* const srcBegin, const ptrdiff_t srcStride,
+    WebGLTexelFormat dstFormat, const ptrdiff_t dstStride,
+    const uint8_t** const out_begin,
     UniqueBuffer* const out_anchoredBuffer) const {
   MOZ_ASSERT(srcFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
   MOZ_ASSERT(dstFormat != WebGLTexelFormat::FormatNotSupportingAnyConversion);
 
   *out_begin = srcBegin;
 
+  const auto& unpacking = mDesc.unpacking;
+
   if (!rowLength || !rowCount) return true;
 
-  const auto srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult);
-  const auto& dstIsPremult = webgl->mPixelStore.mPremultiplyAlpha;
+  const auto srcIsPremult = (mDesc.srcAlphaType == gfxAlphaType::Premult);
+  const auto& dstIsPremult = unpacking.mPremultiplyAlpha;
   const auto fnHasPremultMismatch = [&]() {
-    if (mSrcAlphaType == gfxAlphaType::Opaque) return false;
+    if (mDesc.srcAlphaType == gfxAlphaType::Opaque) return false;
 
     if (!HasColorAndAlpha(srcFormat)) return false;
 
     return srcIsPremult != dstIsPremult;
   };
 
   const auto srcOrigin =
-      (webgl->mPixelStore.mFlipY ? gl::OriginPos::TopLeft
-                                 : gl::OriginPos::BottomLeft);
+      (unpacking.mFlipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
   const auto dstOrigin = gl::OriginPos::BottomLeft;
 
   if (srcFormat != dstFormat) {
     webgl->GeneratePerfWarning(
         "Conversion requires pixel reformatting. (%u->%u)", uint32_t(srcFormat),
         uint32_t(dstFormat));
   } else if (fnHasPremultMismatch()) {
     webgl->GeneratePerfWarning(
@@ -394,76 +441,87 @@ static GLenum DoTexOrSubImage(bool isSub
   } else {
     return DoTexImage(gl, target, level, dui, width, height, depth, data);
   }
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 // TexUnpackBytes
 
-TexUnpackBytes::TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target,
-                               uint32_t width, uint32_t height, uint32_t depth,
-                               bool isClientData, const uint8_t* ptr,
-                               size_t availBytes)
-    : TexUnpackBlob(webgl, target,
-                    FallbackOnZero(webgl->mPixelStore.mUnpackRowLength, width),
-                    width, height, depth, gfxAlphaType::NonPremult),
-      mIsClientData(isClientData),
-      mPtr(ptr),
-      mAvailBytes(availBytes) {}
+bool TexUnpackBytes::Validate(const WebGLContext* const webgl,
+                              const webgl::PackingInfo& pi) {
+  if (!HasData()) return true;
+
+  CheckedInt<size_t> availBytes = 0;
+  if (mDesc.cpuData) {
+    const auto& range = mDesc.cpuData->Data();
+    availBytes = range.length();
+  } else if (mDesc.pboOffset) {
+    const auto& pboOffset = *mDesc.pboOffset;
 
-bool TexUnpackBytes::Validate(WebGLContext* webgl,
-                              const webgl::PackingInfo& pi) {
-  if (mIsClientData && !mPtr) return true;
+    const auto& pbo =
+        webgl->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
+    if (!pbo) return false;  // Might be invalid e.g. due to in-use by TF.
+    availBytes = pbo->ByteLength();
+    availBytes -= pboOffset;
+  } else {
+    MOZ_ASSERT(false, "Must be one of the above");
+  }
+  if (!availBytes.isValid()) {
+    webgl->ErrorInvalidOperation("Offset is passed end of buffer.");
+    return false;
+  }
 
-  return ValidateUnpackBytes(webgl, pi, mAvailBytes, this);
+  return ValidateUnpackBytes(webgl, pi, availBytes.value(), this);
 }
 
 bool TexUnpackBytes::TexOrSubImage(bool isSubImage, bool needsRespec,
-                                   WebGLTexture* tex, TexImageTarget target,
-                                   GLint level,
+                                   WebGLTexture* tex, GLint level,
                                    const webgl::DriverUnpackInfo* dui,
                                    GLint xOffset, GLint yOffset, GLint zOffset,
                                    const webgl::PackingInfo& pi,
                                    GLenum* const out_error) const {
-  WebGLContext* webgl = tex->mContext;
+  const auto& webgl = tex->mContext;
+  const auto& target = mDesc.imageTarget;
+  const auto& size = mDesc.size;
+  const auto& unpacking = mDesc.unpacking;
 
   const auto format = FormatForPackingInfo(pi);
   const auto bytesPerPixel = webgl::BytesPerPixel(pi);
 
-  const uint8_t* uploadPtr = mPtr;
+  const uint8_t dummy = 0;
+  const uint8_t* uploadPtr = nullptr;
+  if (mDesc.cpuData) {
+    const auto range = mDesc.cpuData->Data();
+    uploadPtr = range.begin().get();
+    if (!uploadPtr) {
+      MOZ_ASSERT(!range.length());
+      uploadPtr = &dummy;
+    }
+  }
+
   UniqueBuffer tempBuffer;
 
   do {
-    if (!mIsClientData || !mPtr) break;
-
-    if (!webgl->mPixelStore.mFlipY && !webgl->mPixelStore.mPremultiplyAlpha) {
-      break;
-    }
+    if (mDesc.pboOffset || !uploadPtr) break;
 
-    if (webgl->mPixelStore.mUnpackImageHeight ||
-        webgl->mPixelStore.mUnpackSkipImages ||
-        webgl->mPixelStore.mUnpackRowLength ||
-        webgl->mPixelStore.mUnpackSkipRows ||
-        webgl->mPixelStore.mUnpackSkipPixels) {
-      webgl->ErrorInvalidOperation(
-          "Non-DOM-Element uploads with alpha-premult"
-          " or y-flip do not support subrect selection.");
-      return false;
+    if (!unpacking.mFlipY && !unpacking.mPremultiplyAlpha) {
+      break;
     }
 
     webgl->GenerateWarning(
         "Alpha-premult and y-flip are deprecated for"
         " non-DOM-Element uploads.");
 
-    const uint32_t rowLength = mWidth;
-    const uint32_t rowCount = mHeight * mDepth;
-    const auto stride =
-        RoundUpToMultipleOf(rowLength * bytesPerPixel, mAlignment);
-    if (!ConvertIfNeeded(webgl, rowLength, rowCount, format, mPtr, stride,
+    const uint32_t rowLength = size.x;
+    const uint32_t rowCount = size.y * size.z;
+    const auto stride = RoundUpToMultipleOf(rowLength * bytesPerPixel,
+                                            unpacking.mUnpackAlignment);
+    const auto srcPtr = uploadPtr;
+    if (!ConvertIfNeeded(webgl, rowLength, rowCount, format, srcPtr, stride,
                          format, stride, &uploadPtr, &tempBuffer)) {
       return false;
     }
   } while (false);
 
   //////
 
   const auto& gl = webgl->gl;
@@ -483,70 +541,74 @@ bool TexUnpackBytes::TexOrSubImage(bool 
   if (!useParanoidHandling) {
     if (webgl->mBoundPixelUnpackBuffer) {
       gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER,
                       webgl->mBoundPixelUnpackBuffer->mGLName);
     }
 
     *out_error =
         DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
-                        zOffset, mWidth, mHeight, mDepth, uploadPtr);
+                        zOffset, size.x, size.y, size.z, uploadPtr);
 
     if (webgl->mBoundPixelUnpackBuffer) {
       gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
     }
     return true;
   }
 
   //////
 
   MOZ_ASSERT(webgl->mBoundPixelUnpackBuffer);
 
   if (!isSubImage) {
     // Alloc first to catch OOMs.
     AssertUintParamCorrect(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
     *out_error =
         DoTexOrSubImage(false, gl, target, level, dui, xOffset, yOffset,
-                        zOffset, mWidth, mHeight, mDepth, nullptr);
+                        zOffset, size.x, size.y, size.z, nullptr);
     if (*out_error) return true;
   }
 
   const ScopedLazyBind bindPBO(gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
                                webgl->mBoundPixelUnpackBuffer);
 
   //////
 
   // Make our sometimes-implicit values explicit. Also this keeps them constant
   // when we ask for height=mHeight-1 and such.
-  gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, mRowLength);
-  gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, mImageHeight);
+  gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, unpacking.mUnpackRowLength);
+  gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, unpacking.mUnpackImageHeight);
 
-  if (mDepth > 1) {
+  if (size.z > 1) {
     *out_error =
         DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset, zOffset,
-                        mWidth, mHeight, mDepth - 1, uploadPtr);
+                        size.x, size.y, size.z - 1, uploadPtr);
   }
 
   // Skip the images we uploaded.
-  gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, mSkipImages + mDepth - 1);
+  const auto skipImages = ZeroOn2D(target, unpacking.mUnpackSkipImages);
+  gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, skipImages + size.z - 1);
 
-  if (mHeight > 1) {
-    *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
-                                 zOffset + mDepth - 1, mWidth, mHeight - 1, 1,
-                                 uploadPtr);
+  if (size.y > 1) {
+    *out_error =
+        DoTexOrSubImage(true, gl, target, level, dui, xOffset, yOffset,
+                        zOffset + size.z - 1, size.x, size.y - 1, 1, uploadPtr);
   }
 
   const auto totalSkipRows =
-      CheckedUint32(mSkipImages) * mImageHeight + mSkipRows;
+      CheckedUint32(skipImages) * unpacking.mUnpackImageHeight +
+      unpacking.mUnpackSkipRows;
   const auto totalFullRows =
-      CheckedUint32(mDepth - 1) * mImageHeight + mHeight - 1;
+      CheckedUint32(size.z - 1) * unpacking.mUnpackImageHeight + size.y - 1;
   const auto tailOffsetRows = totalSkipRows + totalFullRows;
 
-  const auto bytesPerRow = CheckedUint32(mRowLength) * bytesPerPixel;
-  const auto rowStride = RoundUpToMultipleOf(bytesPerRow, mAlignment);
+  const auto bytesPerRow =
+      CheckedUint32(unpacking.mUnpackRowLength) * bytesPerPixel;
+  const auto rowStride =
+      RoundUpToMultipleOf(bytesPerRow, unpacking.mUnpackAlignment);
   if (!rowStride.isValid()) {
     MOZ_CRASH("Should be checked earlier.");
   }
   const auto tailOffsetBytes = tailOffsetRows * rowStride;
 
   uploadPtr += tailOffsetBytes.value();
 
   //////
@@ -554,98 +616,83 @@ bool TexUnpackBytes::TexOrSubImage(bool 
   gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);    // No stride padding.
   gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, 0);   // No padding in general.
   gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0);  // Don't skip images,
   gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS,
                    0);  // or rows.
                         // Keep skipping pixels though!
 
   *out_error = DoTexOrSubImage(true, gl, target, level, dui, xOffset,
-                               yOffset + mHeight - 1, zOffset + mDepth - 1,
-                               mWidth, 1, 1, uploadPtr);
+                               yOffset + size.y - 1, zOffset + size.z - 1,
+                               size.x, 1, 1, uploadPtr);
 
-  // Reset all our modified state.
-  gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
-                   webgl->mPixelStore.mUnpackAlignment);
-  gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT,
-                   webgl->mPixelStore.mUnpackImageHeight);
-  gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH,
-                   webgl->mPixelStore.mUnpackRowLength);
-  gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES,
-                   webgl->mPixelStore.mUnpackSkipImages);
-  gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS,
-                   webgl->mPixelStore.mUnpackSkipRows);
+  // Caller will reset all our modified PixelStorei state.
 
   return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackImage
 
-TexUnpackImage::TexUnpackImage(const WebGLContext* webgl, TexImageTarget target,
-                               uint32_t width, uint32_t height, uint32_t depth,
-                               layers::Image* image, gfxAlphaType srcAlphaType)
-    : TexUnpackBlob(webgl, target, image->GetSize().width, width, height, depth,
-                    srcAlphaType),
-      mImage(image) {}
-
 TexUnpackImage::~TexUnpackImage() = default;
 
-bool TexUnpackImage::Validate(WebGLContext* webgl,
+bool TexUnpackImage::Validate(const WebGLContext* const webgl,
                               const webgl::PackingInfo& pi) {
   if (!ValidatePIForDOM(webgl, pi)) return false;
 
-  const auto fullRows = mImage->GetSize().height;
+  const auto fullRows = mDesc.image->GetSize().height;
   return ValidateUnpackPixels(webgl, fullRows, 0, this);
 }
 
 bool TexUnpackImage::TexOrSubImage(bool isSubImage, bool needsRespec,
-                                   WebGLTexture* tex, TexImageTarget target,
-                                   GLint level,
+                                   WebGLTexture* tex, GLint level,
                                    const webgl::DriverUnpackInfo* dui,
                                    GLint xOffset, GLint yOffset, GLint zOffset,
                                    const webgl::PackingInfo& pi,
                                    GLenum* const out_error) const {
   MOZ_ASSERT_IF(needsRespec, !isSubImage);
 
-  WebGLContext* webgl = tex->mContext;
+  const auto& webgl = tex->mContext;
+  const auto& target = mDesc.imageTarget;
+  const auto& size = mDesc.size;
+  const auto& image = mDesc.image;
+  const auto& unpacking = mDesc.unpacking;
 
-  gl::GLContext* gl = webgl->GL();
+  const auto& gl = webgl->GL();
 
   if (needsRespec) {
     *out_error =
-        DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
-                        yOffset, zOffset, mWidth, mHeight, mDepth, nullptr);
+        DoTexOrSubImage(isSubImage, gl, target, level, dui, xOffset, yOffset,
+                        zOffset, size.x, size.y, size.z, nullptr);
     if (*out_error) return true;
   }
 
   const char* fallbackReason;
   do {
-    if (mDepth != 1) {
+    if (size.z != 1) {
       fallbackReason = "depth is not 1";
       break;
     }
     if (xOffset != 0 || yOffset != 0 || zOffset != 0) {
       fallbackReason = "x/y/zOffset is not 0";
       break;
     }
 
-    if (webgl->mPixelStore.mUnpackSkipPixels ||
-        webgl->mPixelStore.mUnpackSkipRows ||
-        webgl->mPixelStore.mUnpackSkipImages) {
+    if (unpacking.mUnpackSkipPixels || unpacking.mUnpackSkipRows ||
+        unpacking.mUnpackSkipImages) {
       fallbackReason = "non-zero UNPACK_SKIP_* not yet supported";
       break;
     }
 
     const auto fnHasPremultMismatch = [&]() {
-      if (mSrcAlphaType == gfxAlphaType::Opaque) return false;
+      if (mDesc.srcAlphaType == gfxAlphaType::Opaque) return false;
 
-      const bool srcIsPremult = (mSrcAlphaType == gfxAlphaType::Premult);
-      const auto& dstIsPremult = webgl->mPixelStore.mPremultiplyAlpha;
+      const bool srcIsPremult = (mDesc.srcAlphaType == gfxAlphaType::Premult);
+      const auto& dstIsPremult = unpacking.mPremultiplyAlpha;
       if (srcIsPremult == dstIsPremult) return false;
 
       if (dstIsPremult) {
         fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not true";
       } else {
         fallbackReason = "UNPACK_PREMULTIPLY_ALPHA_WEBGL is not false";
       }
       return true;
@@ -665,94 +712,86 @@ bool TexUnpackImage::TexOrSubImage(bool 
 
     gl::ScopedFramebuffer scopedFB(gl);
     gl::ScopedBindFramebuffer bindFB(gl, scopedFB.FB());
 
     {
       gl::GLContext::LocalErrorScope errorScope(*gl);
 
       gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
-                                LOCAL_GL_COLOR_ATTACHMENT0, target.get(),
+                                LOCAL_GL_COLOR_ATTACHMENT0, target,
                                 tex->mGLName, level);
 
       if (errorScope.GetError()) {
         fallbackReason = "bug: failed to attach to FB for blit";
         break;
       }
     }
 
     const GLenum status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
     if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
       fallbackReason = "bug: failed to confirm FB for blit";
       break;
     }
 
-    const gfx::IntSize dstSize(mWidth, mHeight);
     const auto dstOrigin =
-        (webgl->mPixelStore.mFlipY ? gl::OriginPos::TopLeft
-                                   : gl::OriginPos::BottomLeft);
-    if (!gl->BlitHelper()->BlitImageToFramebuffer(mImage, dstSize, dstOrigin)) {
+        (unpacking.mFlipY ? gl::OriginPos::TopLeft : gl::OriginPos::BottomLeft);
+    if (!gl->BlitHelper()->BlitImageToFramebuffer(image, {size.x, size.y},
+                                                  dstOrigin)) {
       fallbackReason = "likely bug: failed to blit";
       break;
     }
 
     // Blitting was successful, so we're done!
     *out_error = 0;
     return true;
   } while (false);
 
   const nsPrintfCString perfMsg(
       "Failed to hit GPU-copy fast-path: %s (src type %u)", fallbackReason,
-      uint32_t(mImage->GetFormat()));
+      uint32_t(image->GetFormat()));
 
-  if (webgl->mPixelStore.mRequireFastPath) {
+  if (unpacking.mRequireFastPath) {
     webgl->ErrorInvalidOperation("%s", perfMsg.BeginReading());
     return false;
   }
 
   webgl->GeneratePerfWarning("%s Falling back to CPU upload.",
                              perfMsg.BeginReading());
 
-  const RefPtr<gfx::SourceSurface> surf = mImage->GetAsSourceSurface();
+  const RefPtr<gfx::SourceSurface> surf = image->GetAsSourceSurface();
 
   RefPtr<gfx::DataSourceSurface> dataSurf;
   if (surf) {
     // WARNING: OSX can lose our MakeCurrent here.
     dataSurf = surf->GetDataSurface();
   }
   if (!dataSurf) {
     webgl->ErrorOutOfMemory(
         "GetAsSourceSurface or GetDataSurface failed after"
         " blit failed for TexUnpackImage.");
     return false;
   }
 
-  const TexUnpackSurface surfBlob(webgl, target, mWidth, mHeight, mDepth,
-                                  dataSurf, mSrcAlphaType);
-
-  return surfBlob.TexOrSubImage(isSubImage, needsRespec, tex, target, level,
-                                dui, xOffset, yOffset, zOffset, pi, out_error);
+  const TexUnpackBlobDesc newDesc = {
+      target, size, mDesc.srcAlphaType, {}, {}, {}, dataSurf, unpacking};
+  const TexUnpackSurface surfBlob(newDesc);
+  return surfBlob.TexOrSubImage(isSubImage, needsRespec, tex, level, dui,
+                                xOffset, yOffset, zOffset, pi, out_error);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // TexUnpackSurface
 
-TexUnpackSurface::TexUnpackSurface(const WebGLContext* webgl,
-                                   TexImageTarget target, uint32_t width,
-                                   uint32_t height, uint32_t depth,
-                                   gfx::DataSourceSurface* surf,
-                                   gfxAlphaType srcAlphaType)
-    : TexUnpackBlob(webgl, target, surf->GetSize().width, width, height, depth,
-                    srcAlphaType),
-      mSurf(surf) {}
+TexUnpackSurface::~TexUnpackSurface() = default;
 
 //////////
 
-static bool GetFormatForSurf(gfx::SourceSurface* surf,
+static bool GetFormatForSurf(const gfx::SourceSurface* surf,
                              WebGLTexelFormat* const out_texelFormat,
                              uint8_t* const out_bpp) {
   const auto surfFormat = surf->GetFormat();
   switch (surfFormat) {
     case gfx::SurfaceFormat::B8G8R8A8:
       *out_texelFormat = WebGLTexelFormat::BGRA8;
       *out_bpp = 4;
       return true;
@@ -791,52 +830,56 @@ static bool GetFormatForSurf(gfx::Source
 
     default:
       return false;
   }
 }
 
 //////////
 
-bool TexUnpackSurface::Validate(WebGLContext* webgl,
+bool TexUnpackSurface::Validate(const WebGLContext* const webgl,
                                 const webgl::PackingInfo& pi) {
   if (!ValidatePIForDOM(webgl, pi)) return false;
 
-  const auto fullRows = mSurf->GetSize().height;
+  const auto fullRows = mDesc.surf->GetSize().height;
   return ValidateUnpackPixels(webgl, fullRows, 0, this);
 }
 
-bool TexUnpackSurface::TexOrSubImage(
-    bool isSubImage, bool needsRespec, WebGLTexture* tex, TexImageTarget target,
-    GLint level, const webgl::DriverUnpackInfo* dui, GLint xOffset,
-    GLint yOffset, GLint zOffset, const webgl::PackingInfo& dstPI,
-    GLenum* const out_error) const {
+bool TexUnpackSurface::TexOrSubImage(bool isSubImage, bool needsRespec,
+                                     WebGLTexture* tex, GLint level,
+                                     const webgl::DriverUnpackInfo* dui,
+                                     GLint xOffset, GLint yOffset,
+                                     GLint zOffset,
+                                     const webgl::PackingInfo& dstPI,
+                                     GLenum* const out_error) const {
   const auto& webgl = tex->mContext;
+  const auto& size = mDesc.size;
+  auto& surf = *(mDesc.surf);
 
   ////
 
-  const auto rowLength = mSurf->GetSize().width;
-  const auto rowCount = mSurf->GetSize().height;
+  const auto rowLength = surf.GetSize().width;
+  const auto rowCount = surf.GetSize().height;
 
   const auto& dstBPP = webgl::BytesPerPixel(dstPI);
   const auto dstFormat = FormatForPackingInfo(dstPI);
 
   ////
 
   WebGLTexelFormat srcFormat;
   uint8_t srcBPP;
-  if (!GetFormatForSurf(mSurf, &srcFormat, &srcBPP)) {
+  if (!GetFormatForSurf(&surf, &srcFormat, &srcBPP)) {
     webgl->ErrorImplementationBug(
         "GetFormatForSurf failed for"
         " WebGLTexelFormat::%u.",
-        uint32_t(mSurf->GetFormat()));
+        uint32_t(surf.GetFormat()));
     return false;
   }
 
-  gfx::DataSourceSurface::ScopedMap map(mSurf,
+  gfx::DataSourceSurface::ScopedMap map(&surf,
                                         gfx::DataSourceSurface::MapType::READ);
   if (!map.IsMapped()) {
     webgl->ErrorOutOfMemory("Failed to map source surface for upload.");
     return false;
   }
 
   const auto& srcBegin = map.GetData();
   const auto& srcStride = map.GetStride();
@@ -877,23 +920,18 @@ bool TexUnpackSurface::TexOrSubImage(
   }
 
   gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, dstAlignment);
   if (webgl->IsWebGL2()) {
     gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH, rowLength);
   }
 
   *out_error =
-      DoTexOrSubImage(isSubImage, gl, target.get(), level, dui, xOffset,
-                      yOffset, zOffset, mWidth, mHeight, mDepth, dstBegin);
+      DoTexOrSubImage(isSubImage, gl, mDesc.imageTarget, level, dui, xOffset,
+                      yOffset, zOffset, size.x, size.y, size.z, dstBegin);
 
-  gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
-                   webgl->mPixelStore.mUnpackAlignment);
-  if (webgl->IsWebGL2()) {
-    gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH,
-                     webgl->mPixelStore.mUnpackRowLength);
-  }
+  // Caller will reset all our modified PixelStorei state.
 
   return true;
 }
 
 }  // namespace webgl
 }  // namespace mozilla
--- a/dom/canvas/TexUnpackBlob.h
+++ b/dom/canvas/TexUnpackBlob.h
@@ -27,126 +27,111 @@ namespace gfx {
 class DataSourceSurface;
 }  // namespace gfx
 
 namespace layers {
 class Image;
 class ImageContainer;
 }  // namespace layers
 
+bool IsTarget3D(TexImageTarget target);
+
 namespace webgl {
 
 struct PackingInfo;
 struct DriverUnpackInfo;
 
 class TexUnpackBlob {
  public:
-  const uint32_t mAlignment;
-  const uint32_t mRowLength;
-  const uint32_t mImageHeight;
-  const uint32_t mSkipPixels;
-  const uint32_t mSkipRows;
-  const uint32_t mSkipImages;
-  const uint32_t mWidth;
-  const uint32_t mHeight;
-  const uint32_t mDepth;
+  const TexUnpackBlobDesc& mDesc;
+  bool mNeedsExactUpload = true;
 
-  const gfxAlphaType mSrcAlphaType;
-
-  bool mNeedsExactUpload;
+  static std::unique_ptr<TexUnpackBlob> Create(const TexUnpackBlobDesc&);
 
  protected:
-  TexUnpackBlob(const WebGLContext* webgl, TexImageTarget target,
-                uint32_t rowLength, uint32_t width, uint32_t height,
-                uint32_t depth, gfxAlphaType srcAlphaType);
+  explicit TexUnpackBlob(const TexUnpackBlobDesc& desc) : mDesc(desc) {
+    MOZ_ASSERT_IF(!IsTarget3D(mDesc.imageTarget), mDesc.size.z == 1);
+  }
 
  public:
   virtual ~TexUnpackBlob() = default;
 
  protected:
-  bool ConvertIfNeeded(WebGLContext* webgl, const uint32_t rowLength,
+  bool ConvertIfNeeded(const WebGLContext*, const uint32_t rowLength,
                        const uint32_t rowCount, WebGLTexelFormat srcFormat,
                        const uint8_t* const srcBegin, const ptrdiff_t srcStride,
                        WebGLTexelFormat dstFormat, const ptrdiff_t dstStride,
 
                        const uint8_t** const out_begin,
                        UniqueBuffer* const out_anchoredBuffer) const;
 
  public:
   virtual bool HasData() const { return true; }
 
-  virtual bool Validate(WebGLContext* webgl, const webgl::PackingInfo& pi) = 0;
+  virtual bool Validate(const WebGLContext*, const webgl::PackingInfo& pi) = 0;
 
   // Returns false when we've generated a WebGL error.
   // Returns true but with a non-zero *out_error if we still need to generate a
   // WebGL error.
   virtual bool TexOrSubImage(bool isSubImage, bool needsRespec,
-                             WebGLTexture* tex, TexImageTarget target,
-                             GLint level, const webgl::DriverUnpackInfo* dui,
-                             GLint xOffset, GLint yOffset, GLint zOffset,
+                             WebGLTexture* tex, GLint level,
+                             const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                             GLint yOffset, GLint zOffset,
                              const webgl::PackingInfo& pi,
                              GLenum* const out_error) const = 0;
 };
 
 class TexUnpackBytes final : public TexUnpackBlob {
  public:
-  const bool mIsClientData;
-  const uint8_t* const mPtr;
-  const size_t mAvailBytes;
+  explicit TexUnpackBytes(const TexUnpackBlobDesc& desc) : TexUnpackBlob(desc) {
+    MOZ_ASSERT(mDesc.srcAlphaType == gfxAlphaType::NonPremult);
+  }
 
-  TexUnpackBytes(const WebGLContext* webgl, TexImageTarget target,
-                 uint32_t width, uint32_t height, uint32_t depth,
-                 bool isClientData, const uint8_t* ptr, size_t availBytes);
+  virtual bool HasData() const override {
+    return mDesc.pboOffset || mDesc.cpuData;
+  }
 
-  virtual bool HasData() const override { return !mIsClientData || bool(mPtr); }
-
-  virtual bool Validate(WebGLContext* webgl,
+  virtual bool Validate(const WebGLContext*,
                         const webgl::PackingInfo& pi) override;
   virtual bool TexOrSubImage(bool isSubImage, bool needsRespec,
-                             WebGLTexture* tex, TexImageTarget target,
-                             GLint level, const webgl::DriverUnpackInfo* dui,
-                             GLint xOffset, GLint yOffset, GLint zOffset,
+                             WebGLTexture* tex, GLint level,
+                             const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                             GLint yOffset, GLint zOffset,
                              const webgl::PackingInfo& pi,
                              GLenum* const out_error) const override;
 };
 
 class TexUnpackImage final : public TexUnpackBlob {
  public:
-  const RefPtr<layers::Image> mImage;
-
-  TexUnpackImage(const WebGLContext* webgl, TexImageTarget target,
-                 uint32_t width, uint32_t height, uint32_t depth,
-                 layers::Image* image, gfxAlphaType srcAlphaType);
-
+  explicit TexUnpackImage(const TexUnpackBlobDesc& desc)
+      : TexUnpackBlob(desc) {}
   ~TexUnpackImage();  // Prevent needing to define layers::Image in the header.
 
-  virtual bool Validate(WebGLContext* webgl,
+  virtual bool Validate(const WebGLContext*,
                         const webgl::PackingInfo& pi) override;
   virtual bool TexOrSubImage(bool isSubImage, bool needsRespec,
-                             WebGLTexture* tex, TexImageTarget target,
-                             GLint level, const webgl::DriverUnpackInfo* dui,
-                             GLint xOffset, GLint yOffset, GLint zOffset,
+                             WebGLTexture* tex, GLint level,
+                             const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                             GLint yOffset, GLint zOffset,
                              const webgl::PackingInfo& dstPI,
                              GLenum* const out_error) const override;
 };
 
 class TexUnpackSurface final : public TexUnpackBlob {
  public:
-  const RefPtr<gfx::DataSourceSurface> mSurf;
+  explicit TexUnpackSurface(const TexUnpackBlobDesc& desc)
+      : TexUnpackBlob(desc) {}
+  ~TexUnpackSurface();
 
-  TexUnpackSurface(const WebGLContext* webgl, TexImageTarget target,
-                   uint32_t width, uint32_t height, uint32_t depth,
-                   gfx::DataSourceSurface* surf, gfxAlphaType srcAlphaType);
-
-  virtual bool Validate(WebGLContext* webgl,
+  virtual bool Validate(const WebGLContext*,
                         const webgl::PackingInfo& pi) override;
   virtual bool TexOrSubImage(bool isSubImage, bool needsRespec,
-                             WebGLTexture* tex, TexImageTarget target,
-                             GLint level, const webgl::DriverUnpackInfo* dui,
-                             GLint xOffset, GLint yOffset, GLint zOffset,
+                             WebGLTexture* tex, GLint level,
+                             const webgl::DriverUnpackInfo* dui, GLint xOffset,
+                             GLint yOffset, GLint zOffset,
                              const webgl::PackingInfo& dstPI,
                              GLenum* const out_error) const override;
 };
 
 }  // namespace webgl
 }  // namespace mozilla
 
 #endif  // TEX_UNPACK_BLOB_H_
--- a/dom/canvas/WebGL2ContextState.cpp
+++ b/dom/canvas/WebGL2ContextState.cpp
@@ -62,36 +62,22 @@ Maybe<double> WebGL2Context::GetParamete
     case LOCAL_GL_MAX_PROGRAM_TEXEL_OFFSET:
     case LOCAL_GL_MAX_SAMPLES:
     case LOCAL_GL_MAX_TEXTURE_LOD_BIAS:
     case LOCAL_GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS:
     case LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS:
     case LOCAL_GL_MAX_VERTEX_OUTPUT_COMPONENTS:
     case LOCAL_GL_MAX_VERTEX_UNIFORM_BLOCKS:
     case LOCAL_GL_MAX_VERTEX_UNIFORM_COMPONENTS:
-    case LOCAL_GL_MIN_PROGRAM_TEXEL_OFFSET:
-    case LOCAL_GL_PACK_ROW_LENGTH:
-    case LOCAL_GL_PACK_SKIP_PIXELS:
-    case LOCAL_GL_PACK_SKIP_ROWS:
-    case LOCAL_GL_UNPACK_IMAGE_HEIGHT:
-    case LOCAL_GL_UNPACK_ROW_LENGTH: {
+    case LOCAL_GL_MIN_PROGRAM_TEXEL_OFFSET: {
       GLint val;
       gl->fGetIntegerv(pname, &val);
       return Some(val);
     }
 
-    case LOCAL_GL_UNPACK_SKIP_IMAGES:
-      return Some(mPixelStore.mUnpackSkipImages);
-
-    case LOCAL_GL_UNPACK_SKIP_PIXELS:
-      return Some(mPixelStore.mUnpackSkipPixels);
-
-    case LOCAL_GL_UNPACK_SKIP_ROWS:
-      return Some(mPixelStore.mUnpackSkipRows);
-
     case LOCAL_GL_MAX_VARYING_COMPONENTS: {
       // On OS X Core Profile this is buggy.  The spec says that the
       // value is 4 * GL_MAX_VARYING_VECTORS
       GLint val;
       gl->fGetIntegerv(LOCAL_GL_MAX_VARYING_VECTORS, &val);
       return Some(4 * val);
     }
 
--- a/dom/canvas/WebGL2ContextSync.cpp
+++ b/dom/canvas/WebGL2ContextSync.cpp
@@ -24,20 +24,16 @@ RefPtr<WebGLSync> WebGL2Context::FenceSy
   }
 
   if (flags != 0) {
     ErrorInvalidValue("flags must be 0");
     return nullptr;
   }
 
   RefPtr<WebGLSync> globj = new WebGLSync(this, condition, flags);
-
-  const auto& availRunnable = EnsureAvailabilityRunnable();
-  availRunnable->mSyncs.push_back(globj);
-
   return globj;
 }
 
 GLenum WebGL2Context::ClientWaitSync(const WebGLSync& sync, GLbitfield flags,
                                      GLuint64 timeout) {
   const FuncScope funcScope(*this, "clientWaitSync");
   if (IsContextLost()) return LOCAL_GL_WAIT_FAILED;
 
@@ -49,27 +45,16 @@ GLenum WebGL2Context::ClientWaitSync(con
   }
 
   if (timeout > kMaxClientWaitSyncTimeoutNS) {
     ErrorInvalidOperation("`timeout` must not exceed %s nanoseconds.",
                           "MAX_CLIENT_WAIT_TIMEOUT_WEBGL");
     return LOCAL_GL_WAIT_FAILED;
   }
 
-  const bool canBeAvailable =
-      (sync.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
-  if (!canBeAvailable) {
-    if (timeout) {
-      GenerateWarning(
-          "Sync object not yet queryable. Please wait for the event"
-          " loop.");
-    }
-    return LOCAL_GL_WAIT_FAILED;
-  }
-
   const auto ret = gl->fClientWaitSync(sync.mGLName, flags, timeout);
 
   if (ret == LOCAL_GL_CONDITION_SATISFIED || ret == LOCAL_GL_ALREADY_SIGNALED) {
     sync.MarkSignaled();
   }
 
   return ret;
 }
--- a/dom/canvas/WebGLChild.cpp
+++ b/dom/canvas/WebGLChild.cpp
@@ -6,73 +6,76 @@
 #include "WebGLChild.h"
 
 #include "ClientWebGLContext.h"
 #include "WebGLMethodDispatcher.h"
 
 namespace mozilla {
 namespace dom {
 
-WebGLChild::WebGLChild(ClientWebGLContext& context)
-    : PcqActor(this), mContext(context) {}
+WebGLChild::WebGLChild(ClientWebGLContext& context) : mContext(&context) {}
 
 WebGLChild::~WebGLChild() { (void)Send__delete__(this); }
 
 // -
 
+static constexpr size_t kDefaultCmdsShmemSize = 100 * 1000;
+
 Maybe<Range<uint8_t>> WebGLChild::AllocPendingCmdBytes(const size_t size) {
-  if (!mPendingCmds) {
-    mPendingCmds.reset(new webgl::ShmemCmdBuffer);
-    size_t capacity = 1000 * 1000;
+  if (!mPendingCmdsShmem) {
+    size_t capacity = kDefaultCmdsShmemSize;
     if (capacity < size) {
       capacity = size;
     }
 
-    if (!PWebGLChild::AllocShmem(
-            capacity, mozilla::ipc::SharedMemory::SharedMemoryType::TYPE_BASIC,
-            &(mPendingCmds->mShmem))) {
-      return {};
-    }
+    auto shmem = webgl::RaiiShmem::Alloc(
+        this, capacity,
+        mozilla::ipc::SharedMemory::SharedMemoryType::TYPE_BASIC);
+    MOZ_ASSERT(shmem);
+    if (!shmem) return {};
+    mPendingCmdsShmem = std::move(shmem);
+    mPendingCmdsPos = 0;
   }
 
-  auto remaining = mPendingCmds->Remaining();
+  const auto range = mPendingCmdsShmem.ByteRange();
+
+  const auto remaining =
+      Range<uint8_t>{range.begin() + mPendingCmdsPos, range.end()};
   if (size > remaining.length()) {
     FlushPendingCmds();
     return AllocPendingCmdBytes(size);
   }
-  mPendingCmds->mPos += size;
+  mPendingCmdsPos += size;
   return Some(Range<uint8_t>{remaining.begin(), remaining.begin() + size});
 }
 
 void WebGLChild::FlushPendingCmds() {
-  if (!mPendingCmds) return;
+  if (!mPendingCmdsShmem) return;
 
-  const auto cmdBytes = mPendingCmds->mPos;
-  SendDispatchCommands(std::move(mPendingCmds->mShmem), cmdBytes);
-  mPendingCmds = nullptr;
+  const auto byteSize = mPendingCmdsPos;
+  SendDispatchCommands(mPendingCmdsShmem.Extract(), byteSize);
 
   mFlushedCmdInfo.flushes += 1;
-  mFlushedCmdInfo.flushedCmdBytes += cmdBytes;
+  mFlushedCmdInfo.flushedCmdBytes += byteSize;
+
+  printf_stderr("[WebGLChild] Flushed %zu bytes. (%zu over %zu flushes)\n",
+                byteSize, mFlushedCmdInfo.flushedCmdBytes,
+                mFlushedCmdInfo.flushes);
 }
 
 // -
 
 mozilla::ipc::IPCResult WebGLChild::RecvJsWarning(
     const std::string& text) const {
-  mContext.JsWarning(text);
+  if (!mContext) return IPC_OK();
+  mContext->JsWarning(text);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult WebGLChild::RecvOnContextLoss(
     const webgl::ContextLossReason reason) const {
-  mContext.OnContextLoss(reason);
+  if (!mContext) return IPC_OK();
+  mContext->OnContextLoss(reason);
   return IPC_OK();
 }
 
-/* static */
-IpdlQueueProtocol WebGLChild::GetIpdlQueueProtocol(size_t aCmd, ...) {
-  bool isSync =
-      WebGLMethodDispatcher<>::SyncType(aCmd) == CommandSyncType::SYNC;
-  return isSync ? IpdlQueueProtocol::kSync : IpdlQueueProtocol::kBufferedAsync;
-}
-
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/canvas/WebGLChild.h
+++ b/dom/canvas/WebGLChild.h
@@ -28,33 +28,29 @@ class ClientWebGLContext;
 namespace dom {
 
 struct FlushedCmdInfo final {
   size_t flushes = 0;
   size_t flushedCmdBytes = 0;
 };
 
 class WebGLChild final : public PWebGLChild,
-                         public SupportsWeakPtr<WebGLChild>,
-                         public mozilla::webgl::PcqActor {
-  std::unique_ptr<webgl::ShmemCmdBuffer> mPendingCmds;
+                         public SupportsWeakPtr<WebGLChild> {
+  const WeakPtr<ClientWebGLContext> mContext;
+  webgl::RaiiShmem mPendingCmdsShmem;
+  size_t mPendingCmdsPos = 0;
   FlushedCmdInfo mFlushedCmdInfo;
 
  public:
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLChild)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebGLChild, override);
   using OtherSideActor = WebGLParent;
 
-  ClientWebGLContext& mContext;
-
   explicit WebGLChild(ClientWebGLContext&);
 
-  // For SyncProducerActor:
-  static IpdlQueueProtocol GetIpdlQueueProtocol(size_t aCmd, ...);
-
   Maybe<Range<uint8_t>> AllocPendingCmdBytes(size_t);
   void FlushPendingCmds();
 
  private:
   friend PWebGLChild;
   virtual ~WebGLChild();
 
  public:
--- a/dom/canvas/WebGLCommandQueue.h
+++ b/dom/canvas/WebGLCommandQueue.h
@@ -21,28 +21,16 @@
 #endif
 
 namespace mozilla {
 
 using webgl::QueueStatus;
 
 namespace webgl {
 
-struct ShmemCmdBuffer final {
-  mozilla::ipc::Shmem mShmem = {};
-  size_t mPos = 0;
-
-  Range<uint8_t> Remaining() const {
-    const auto range = ByteRange(mShmem);
-    return {range.begin() + mPos, range.end()};
-  }
-};
-
-// -
-
 class RangeConsumerView final : public webgl::ConsumerView<RangeConsumerView> {
   RangedPtr<const uint8_t> mSrcItr;
   const RangedPtr<const uint8_t> mSrcEnd;
 
  public:
   auto Remaining() const { return *MaybeAs<size_t>(mSrcEnd - mSrcItr); }
 
   explicit RangeConsumerView(const Range<const uint8_t> range)
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -204,20 +204,16 @@ void WebGLContext::DestroyResourcesAndCo
   mDefaultTransformFeedback = nullptr;
 
   mQuerySlot_SamplesPassed = nullptr;
   mQuerySlot_TFPrimsWritten = nullptr;
   mQuerySlot_TimeElapsed = nullptr;
 
   mIndexedUniformBufferBindings.clear();
 
-  if (mAvailabilityRunnable) {
-    mAvailabilityRunnable->Run();
-  }
-
   //////
 
   if (mEmptyTFO) {
     gl->fDeleteTransformFeedbacks(1, &mEmptyTFO);
     mEmptyTFO = 0;
   }
 
   //////
@@ -1388,58 +1384,16 @@ uint64_t IndexedBufferBinding::ByteCount
   if (mRangeStart >= bufferSize) return 0;
   bufferSize -= mRangeStart;
 
   return std::min(bufferSize, mRangeSize);
 }
 
 ////////////////////////////////////////
 
-ScopedUnpackReset::ScopedUnpackReset(const WebGLContext* const webgl)
-    : mWebGL(webgl) {
-  const auto& gl = mWebGL->gl;
-  // clang-format off
-  if (mWebGL->mPixelStore.mUnpackAlignment != 4) gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
-
-  if (mWebGL->IsWebGL2()) {
-    if (mWebGL->mPixelStore.mUnpackRowLength   != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH  , 0);
-    if (mWebGL->mPixelStore.mUnpackImageHeight != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, 0);
-    if (mWebGL->mPixelStore.mUnpackSkipPixels  != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS , 0);
-    if (mWebGL->mPixelStore.mUnpackSkipRows    != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS   , 0);
-    if (mWebGL->mPixelStore.mUnpackSkipImages  != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES , 0);
-
-    if (mWebGL->mBoundPixelUnpackBuffer) gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0);
-  }
-  // clang-format on
-}
-
-ScopedUnpackReset::~ScopedUnpackReset() {
-  const auto& gl = mWebGL->gl;
-  // clang-format off
-  gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, mWebGL->mPixelStore.mUnpackAlignment);
-
-  if (mWebGL->IsWebGL2()) {
-    gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH  , mWebGL->mPixelStore.mUnpackRowLength  );
-    gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, mWebGL->mPixelStore.mUnpackImageHeight);
-    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS , mWebGL->mPixelStore.mUnpackSkipPixels );
-    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS   , mWebGL->mPixelStore.mUnpackSkipRows   );
-    gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES , mWebGL->mPixelStore.mUnpackSkipImages );
-
-    GLuint pbo = 0;
-    if (mWebGL->mBoundPixelUnpackBuffer) {
-        pbo = mWebGL->mBoundPixelUnpackBuffer->mGLName;
-    }
-
-    gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, pbo);
-  }
-  // clang-format on
-}
-
-////////////////////
-
 ScopedFBRebinder::~ScopedFBRebinder() {
   const auto fnName = [&](WebGLFramebuffer* fb) {
     return fb ? fb->mGLName : 0;
   };
 
   const auto& gl = mWebGL->gl;
   if (mWebGL->IsWebGL2()) {
     gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
@@ -1518,64 +1472,16 @@ uint64_t AvailGroups(const uint64_t tota
   if (tailItems >= groupSize) {
     availGroups += 1;
   }
   return availGroups;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
-CheckedUint32 WebGLContext::GetUnpackSize(bool isFunc3D, uint32_t width,
-                                          uint32_t height, uint32_t depth,
-                                          uint8_t bytesPerPixel) {
-  if (!width || !height || !depth) return 0;
-
-  ////////////////
-
-  const auto& maybeRowLength = mPixelStore.mUnpackRowLength;
-  const auto& maybeImageHeight = mPixelStore.mUnpackImageHeight;
-
-  const auto usedPixelsPerRow =
-      CheckedUint32(mPixelStore.mUnpackSkipPixels) + width;
-  const auto stridePixelsPerRow =
-      (maybeRowLength ? CheckedUint32(maybeRowLength) : usedPixelsPerRow);
-
-  const auto usedRowsPerImage =
-      CheckedUint32(mPixelStore.mUnpackSkipRows) + height;
-  const auto strideRowsPerImage =
-      (maybeImageHeight ? CheckedUint32(maybeImageHeight) : usedRowsPerImage);
-
-  const uint32_t skipImages = (isFunc3D ? mPixelStore.mUnpackSkipImages : 0);
-  const CheckedUint32 usedImages = CheckedUint32(skipImages) + depth;
-
-  ////////////////
-
-  CheckedUint32 strideBytesPerRow = bytesPerPixel * stridePixelsPerRow;
-  strideBytesPerRow =
-      RoundUpToMultipleOf(strideBytesPerRow, mPixelStore.mUnpackAlignment);
-
-  const CheckedUint32 strideBytesPerImage =
-      strideBytesPerRow * strideRowsPerImage;
-
-  ////////////////
-
-  CheckedUint32 usedBytesPerRow = bytesPerPixel * usedPixelsPerRow;
-  // Don't round this to the alignment, since alignment here is really just used
-  // for establishing stride, particularly in WebGL 1, where you can't set
-  // ROW_LENGTH.
-
-  CheckedUint32 totalBytes = strideBytesPerImage * (usedImages - 1);
-  totalBytes += strideBytesPerRow * (usedRowsPerImage - 1);
-  totalBytes += usedBytesPerRow;
-
-  return totalBytes;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
 const char* WebGLContext::FuncName() const {
   const char* ret;
   if (MOZ_LIKELY(mFuncScope)) {
     ret = mFuncScope->mFuncName;
   } else {
     MOZ_ASSERT(false, "FuncScope not on stack!");
     ret = "<funcName unknown>";
   }
@@ -1637,48 +1543,51 @@ already_AddRefed<dom::Promise> ClientWeb
   // This should update `options` and lose+restore the context.
   mXRCompatible = true;
   promise->MaybeResolveWithUndefined();
   return promise.forget();
 }
 
 // --
 
-webgl::AvailabilityRunnable* WebGLContext::EnsureAvailabilityRunnable() {
+webgl::AvailabilityRunnable& ClientWebGLContext::EnsureAvailabilityRunnable()
+    const {
   if (!mAvailabilityRunnable) {
-    RefPtr<webgl::AvailabilityRunnable> runnable =
-        new webgl::AvailabilityRunnable(this);
-
-    NS_DispatchToCurrentThread(runnable.forget());
+    mAvailabilityRunnable = new webgl::AvailabilityRunnable(this);
+    auto forgettable = mAvailabilityRunnable;
+    NS_DispatchToCurrentThread(forgettable.forget());
   }
-  return mAvailabilityRunnable;
+  return *mAvailabilityRunnable;
 }
 
-webgl::AvailabilityRunnable::AvailabilityRunnable(WebGLContext* const webgl)
-    : Runnable("webgl::AvailabilityRunnable"), mWebGL(webgl) {
-  mWebGL->mAvailabilityRunnable = this;
-}
+webgl::AvailabilityRunnable::AvailabilityRunnable(
+    const ClientWebGLContext* const webgl)
+    : Runnable("webgl::AvailabilityRunnable"), mWebGL(webgl) {}
 
 webgl::AvailabilityRunnable::~AvailabilityRunnable() {
   MOZ_ASSERT(mQueries.empty());
   MOZ_ASSERT(mSyncs.empty());
 }
 
 nsresult webgl::AvailabilityRunnable::Run() {
   for (const auto& cur : mQueries) {
+    if (!cur) continue;
     cur->mCanBeAvailable = true;
   }
   mQueries.clear();
 
   for (const auto& cur : mSyncs) {
+    if (!cur) continue;
     cur->mCanBeAvailable = true;
   }
   mSyncs.clear();
 
-  mWebGL->mAvailabilityRunnable = nullptr;
+  if (mWebGL) {
+    mWebGL->mAvailabilityRunnable = nullptr;
+  }
   return NS_OK;
 }
 
 // -
 
 void WebGLContext::GenerateErrorImpl(const GLenum err,
                                      const std::string& text) const {
   if (mFuncScope && mFuncScope->mBindFailureGuard) {
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -61,17 +61,16 @@ class nsIDocShell;
 #define LOCAL_GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL     0x9241
 // clang-format on
 
 namespace mozilla {
 class HostWebGLContext;
 class ScopedCopyTexImageSource;
 class ScopedDrawCallWrapper;
 class ScopedResolveTexturesForDraw;
-class ScopedUnpackReset;
 class WebGLBuffer;
 class WebGLExtensionBase;
 class WebGLFramebuffer;
 class WebGLProgram;
 class WebGLQuery;
 class WebGLRenderbuffer;
 class WebGLSampler;
 class WebGLShader;
@@ -180,21 +179,21 @@ struct IndexedBufferBinding {
 };
 
 ////////////////////////////////////
 
 namespace webgl {
 
 class AvailabilityRunnable final : public Runnable {
  public:
-  const RefPtr<WebGLContext> mWebGL;  // Prevent CC
-  std::vector<RefPtr<WebGLQuery>> mQueries;
-  std::vector<RefPtr<WebGLSync>> mSyncs;
+  const WeakPtr<const ClientWebGLContext> mWebGL;
+  std::vector<WeakPtr<WebGLQueryJS>> mQueries;
+  std::vector<WeakPtr<WebGLSyncJS>> mSyncs;
 
-  explicit AvailabilityRunnable(WebGLContext* webgl);
+  explicit AvailabilityRunnable(const ClientWebGLContext* webgl);
   ~AvailabilityRunnable();
 
   NS_IMETHOD Run() override;
 };
 
 struct BufferAndIndex final {
   const WebGLBuffer* buffer = nullptr;
   uint32_t id = -1;
@@ -388,16 +387,20 @@ class WebGLContext : public VRefCounted,
     ~FuncScope();
   };
 
   void GenerateErrorImpl(const GLenum err, const nsACString& text) const {
     GenerateErrorImpl(err, std::string(text.BeginReading()));
   }
   void GenerateErrorImpl(const GLenum err, const std::string& text) const;
 
+  void GenerateError(const webgl::ErrorInfo& err) {
+    GenerateError(err.type, "%s", err.info.c_str());
+  }
+
   template <typename... Args>
   void GenerateError(const GLenum err, const char* const fmt,
                      const Args&... args) const {
     MOZ_ASSERT(FuncName());
 
     nsCString text;
     text.AppendPrintf("WebGL warning: %s: ", FuncName());
 
@@ -588,17 +591,16 @@ class WebGLContext : public VRefCounted,
       GLenum shadertype, GLenum precisiontype) const;
 
   webgl::GetUniformData GetUniform(const WebGLProgram&, uint32_t loc) const;
 
   void Hint(GLenum target, GLenum mode);
 
   void LineWidth(GLfloat width);
   void LinkProgram(WebGLProgram& prog);
-  void PixelStorei(GLenum pname, uint32_t param);
   void PolygonOffset(GLfloat factor, GLfloat units);
 
   ////
 
   webgl::PackingInfo ValidImplementationColorReadPI(
       const webgl::FormatUsageInfo* usage) const;
 
  protected:
@@ -774,20 +776,19 @@ class WebGLContext : public VRefCounted,
                           const Maybe<uint64_t>& pboOffset) const;
 
   // CopyTexSubImage if `!respectFormat`
   void CopyTexImage(GLenum imageTarget, uint32_t level, GLenum respecFormat,
                     uvec3 dstOffset, const ivec2& srcOffset,
                     const uvec2& size) const;
 
   // TexSubImage if `!respectFormat`
-  void TexImage(GLenum imageTarget, uint32_t level, GLenum respecFormat,
-                uvec3 offset, uvec3 size, const webgl::PackingInfo& pi,
-                const TexImageSource& src,
-                const dom::HTMLCanvasElement& canvas) const;
+  void TexImage(uint32_t level, GLenum respecFormat, uvec3 offset,
+                const webgl::PackingInfo& pi,
+                const webgl::TexUnpackBlobDesc&) const;
 
   void TexStorage(GLenum texTarget, uint32_t levels, GLenum sizedFormat,
                   uvec3 size) const;
 
   UniquePtr<webgl::TexUnpackBlob> ToTexUnpackBytes(
       const WebGLTexImageData& imageData);
 
   UniquePtr<webgl::TexUnpackBytes> ToTexUnpackBytes(WebGLTexPboOffset& aPbo);
@@ -1091,17 +1092,16 @@ class WebGLContext : public VRefCounted,
     return true;
   }
 
   ////
 
  public:
   void LoseContext(
       webgl::ContextLossReason reason = webgl::ContextLossReason::None);
-  const WebGLPixelStore GetPixelStore() const { return mPixelStore; }
 
  protected:
   nsTArray<RefPtr<WebGLTexture>> mBound2DTextures;
   nsTArray<RefPtr<WebGLTexture>> mBoundCubeMapTextures;
   nsTArray<RefPtr<WebGLTexture>> mBound3DTextures;
   nsTArray<RefPtr<WebGLTexture>> mBound2DArrayTextures;
   nsTArray<RefPtr<WebGLSampler>> mBoundSamplers;
 
@@ -1124,30 +1124,16 @@ class WebGLContext : public VRefCounted,
 
  public:
   const auto& BoundReadFb() const { return mBoundReadFramebuffer; }
 
  protected:
   RefPtr<WebGLTransformFeedback> mDefaultTransformFeedback;
   RefPtr<WebGLVertexArray> mDefaultVertexArray;
 
-  WebGLPixelStore mPixelStore;
-
-  CheckedUint32 GetUnpackSize(bool isFunc3D, uint32_t width, uint32_t height,
-                              uint32_t depth, uint8_t bytesPerPixel);
-
-  UniquePtr<webgl::TexUnpackBlob> FromDomElem(
-      const dom::HTMLCanvasElement& canvas, TexImageTarget target, uvec3 size,
-      const dom::Element& elem, ErrorResult* const out_error) const;
-
-  UniquePtr<webgl::TexUnpackBlob> From(
-      const dom::HTMLCanvasElement& canvas, TexImageTarget target,
-      const uvec3& size, const TexImageSource& src,
-      dom::Uint8ClampedArray* const scopedArr) const;
-
   ////////////////////////////////////
 
  protected:
   GLuint mEmptyTFO = 0;
 
   // Generic Vertex Attributes
   // Though CURRENT_VERTEX_ATTRIB is listed under "Vertex Shader State" in the
   // spec state tables, this isn't vertex shader /object/ state. This array is
@@ -1289,29 +1275,19 @@ class WebGLContext : public VRefCounted,
  public:
   UniquePtr<webgl::FormatUsageAuthority> mFormatUsage;
 
   virtual UniquePtr<webgl::FormatUsageAuthority> CreateFormatUsage(
       gl::GLContext* gl) const;
 
   const decltype(mBound2DTextures)* TexListForElemType(GLenum elemType) const;
 
-  // --
- private:
-  webgl::AvailabilityRunnable* mAvailabilityRunnable = nullptr;
-
- public:
-  webgl::AvailabilityRunnable* EnsureAvailabilityRunnable();
-
-  // -
-
   // Friend list
   friend class ScopedCopyTexImageSource;
   friend class ScopedResolveTexturesForDraw;
-  friend class ScopedUnpackReset;
   friend class webgl::TexUnpackBlob;
   friend class webgl::TexUnpackBytes;
   friend class webgl::TexUnpackImage;
   friend class webgl::TexUnpackSurface;
   friend struct webgl::UniformInfo;
   friend class WebGLTexture;
   friend class WebGLFBAttachPoint;
   friend class WebGLFramebuffer;
@@ -1333,25 +1309,16 @@ class WebGLContext : public VRefCounted,
 template <typename V, typename M>
 V RoundUpToMultipleOf(const V& value, const M& multiple) {
   return ((value + multiple - 1) / multiple) * multiple;
 }
 
 const char* GetEnumName(GLenum val, const char* defaultRet = "<unknown>");
 std::string EnumString(GLenum val);
 
-class ScopedUnpackReset final {
- private:
-  const WebGLContext* const mWebGL;
-
- public:
-  explicit ScopedUnpackReset(const WebGLContext* webgl);
-  ~ScopedUnpackReset();
-};
-
 class ScopedFBRebinder final {
  private:
   const WebGLContext* const mWebGL;
 
  public:
   explicit ScopedFBRebinder(const WebGLContext* const webgl) : mWebGL(webgl) {}
   ~ScopedFBRebinder();
 };
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -43,16 +43,17 @@
 // needed to check if current OS is lower than 10.7
 #if defined(MOZ_WIDGET_COCOA)
 #  include "nsCocoaFeatures.h"
 #endif
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/ImageData.h"
+#include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "mozilla/EndianUtils.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "mozilla/StaticPrefs_webgl.h"
 
 namespace mozilla {
 
 using namespace mozilla::dom;
@@ -733,99 +734,99 @@ void WebGLContext::LinkProgram(WebGLProg
     mActiveProgramLinkInfo = prog.LinkInfo();
 
     if (gl->WorkAroundDriverBugs() && gl->Vendor() == gl::GLVendor::NVIDIA) {
       gl->fUseProgram(prog.mGLName);
     }
   }
 }
 
-void WebGLContext::PixelStorei(GLenum pname, uint32_t param) {
-  const FuncScope funcScope(*this, "pixelStorei");
-  if (IsContextLost()) return;
-
-  if (IsWebGL2()) {
+Maybe<webgl::ErrorInfo> SetPixelUnpack(const bool isWebgl2,
+                                       WebGLPixelStore* const unpacking,
+                                       const GLenum pname, const GLint param) {
+  if (isWebgl2) {
     uint32_t* pValueSlot = nullptr;
     switch (pname) {
       case LOCAL_GL_UNPACK_IMAGE_HEIGHT:
-        pValueSlot = &mPixelStore.mUnpackImageHeight;
+        pValueSlot = &unpacking->mUnpackImageHeight;
         break;
 
       case LOCAL_GL_UNPACK_SKIP_IMAGES:
-        pValueSlot = &mPixelStore.mUnpackSkipImages;
+        pValueSlot = &unpacking->mUnpackSkipImages;
         break;
 
       case LOCAL_GL_UNPACK_ROW_LENGTH:
-        pValueSlot = &mPixelStore.mUnpackRowLength;
+        pValueSlot = &unpacking->mUnpackRowLength;
         break;
 
       case LOCAL_GL_UNPACK_SKIP_ROWS:
-        pValueSlot = &mPixelStore.mUnpackSkipRows;
+        pValueSlot = &unpacking->mUnpackSkipRows;
         break;
 
       case LOCAL_GL_UNPACK_SKIP_PIXELS:
-        pValueSlot = &mPixelStore.mUnpackSkipPixels;
+        pValueSlot = &unpacking->mUnpackSkipPixels;
         break;
     }
 
     if (pValueSlot) {
-      gl->fPixelStorei(pname, static_cast<int32_t>(param));
-      *pValueSlot = param;
-      return;
+      *pValueSlot = static_cast<uint32_t>(param);
+      return {};
     }
   }
 
   switch (pname) {
-    case UNPACK_FLIP_Y_WEBGL:
-      mPixelStore.mFlipY = bool(param);
-      return;
+    case dom::WebGLRenderingContext_Binding::UNPACK_FLIP_Y_WEBGL:
+      unpacking->mFlipY = bool(param);
+      return {};
 
-    case UNPACK_PREMULTIPLY_ALPHA_WEBGL:
-      mPixelStore.mPremultiplyAlpha = bool(param);
-      return;
+    case dom::WebGLRenderingContext_Binding::UNPACK_PREMULTIPLY_ALPHA_WEBGL:
+      unpacking->mPremultiplyAlpha = bool(param);
+      return {};
 
-    case UNPACK_COLORSPACE_CONVERSION_WEBGL:
+    case dom::WebGLRenderingContext_Binding::UNPACK_COLORSPACE_CONVERSION_WEBGL:
       switch (param) {
         case LOCAL_GL_NONE:
-        case BROWSER_DEFAULT_WEBGL:
-          mPixelStore.mColorspaceConversion = param;
-          return;
+        case dom::WebGLRenderingContext_Binding::BROWSER_DEFAULT_WEBGL:
+          break;
 
-        default:
-          ErrorInvalidEnumInfo("colorspace conversion parameter", param);
-          return;
+        default: {
+          const nsPrintfCString text("Bad UNPACK_COLORSPACE_CONVERSION: %s",
+                                     EnumString(param).c_str());
+          return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE, ToString(text)});
+        }
       }
+      unpacking->mColorspaceConversion = param;
+      return {};
 
-    case UNPACK_REQUIRE_FASTPATH:
-      if (IsExtensionEnabled(WebGLExtensionID::MOZ_debug)) {
-        mPixelStore.mRequireFastPath = bool(param);
-        return;
-      }
-      break;
+    case dom::MOZ_debug_Binding::UNPACK_REQUIRE_FASTPATH:
+      unpacking->mRequireFastPath = bool(param);
+      return {};
 
     case LOCAL_GL_UNPACK_ALIGNMENT:
       switch (param) {
         case 1:
         case 2:
         case 4:
         case 8:
-          mPixelStore.mUnpackAlignment = param;
-          gl->fPixelStorei(pname, static_cast<int32_t>(param));
-          return;
+          break;
 
-        default:
-          ErrorInvalidValue("Invalid pack/unpack alignment value.");
-          return;
+        default: {
+          const nsPrintfCString text(
+              "UNPACK_ALIGNMENT must be [1,2,4,8], was %i", param);
+          return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE, ToString(text)});
+        }
       }
+      unpacking->mUnpackAlignment = param;
+      return {};
 
     default:
       break;
   }
-
-  ErrorInvalidEnumInfo("pname", pname);
+  const nsPrintfCString text("Bad `pname`: %s", EnumString(pname).c_str());
+  return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_ENUM, ToString(text)});
 }
 
 bool WebGLContext::DoReadPixelsAndConvert(
     const webgl::FormatInfo* const srcFormat, const webgl::ReadPixelsDesc& desc,
     const uintptr_t dest, const uint64_t destSize, const uint32_t rowStride) {
   const auto& x = desc.srcOffset.x;
   const auto& y = desc.srcOffset.y;
   const auto size = *ivec2::From(desc.size);
--- a/dom/canvas/WebGLContextState.cpp
+++ b/dom/canvas/WebGLContextState.cpp
@@ -228,18 +228,16 @@ Maybe<double> WebGLContext::GetParameter
       if (samples && pname == LOCAL_GL_SAMPLE_BUFFERS) {
         samples = Some(uint32_t(bool(samples.value())));
       }
       if (!samples) return Nothing();
       return Some(samples.value());
     }
 
     case LOCAL_GL_STENCIL_CLEAR_VALUE:
-    case LOCAL_GL_UNPACK_ALIGNMENT:
-    case LOCAL_GL_PACK_ALIGNMENT:
     case LOCAL_GL_SUBPIXEL_BITS: {
       GLint i = 0;
       gl->fGetIntegerv(pname, &i);
       return Some(i);
     }
 
     case LOCAL_GL_RED_BITS:
     case LOCAL_GL_GREEN_BITS:
@@ -385,26 +383,16 @@ Maybe<double> WebGLContext::GetParameter
     case LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE:
     case LOCAL_GL_SAMPLE_COVERAGE:
     case LOCAL_GL_DEPTH_WRITEMASK: {
       realGLboolean b = 0;
       gl->fGetBooleanv(pname, &b);
       return Some(bool(b));
     }
 
-    // bool, WebGL-specific
-    case UNPACK_FLIP_Y_WEBGL:
-      return Some((bool)mPixelStore.mFlipY);
-    case UNPACK_PREMULTIPLY_ALPHA_WEBGL:
-      return Some((bool)mPixelStore.mPremultiplyAlpha);
-
-    // uint, WebGL-specific
-    case UNPACK_COLORSPACE_CONVERSION_WEBGL:
-      return Some(mPixelStore.mColorspaceConversion);
-
     default:
       break;
   }
 
   ErrorInvalidEnumInfo("pname", pname);
   return Nothing();
 }
 
--- a/dom/canvas/WebGLContextTextures.cpp
+++ b/dom/canvas/WebGLContextTextures.cpp
@@ -140,27 +140,16 @@ void WebGLContext::TexParameter_base(GLe
   const auto& tex = GetActiveTex(texTarget);
   if (!tex) return;
   tex->TexParameter(texTarget, pname, param);
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 // Uploads
 
-static bool IsTexTarget3D(const GLenum texTarget) {
-  switch (texTarget) {
-    case LOCAL_GL_TEXTURE_2D_ARRAY:
-    case LOCAL_GL_TEXTURE_3D:
-      return true;
-
-    default:
-      return false;
-  }
-}
-
 WebGLTexture* WebGLContext::GetActiveTex(const GLenum texTarget) const {
   const auto* list = &mBound2DTextures;
   switch (texTarget) {
     case LOCAL_GL_TEXTURE_2D:
       break;
     case LOCAL_GL_TEXTURE_CUBE_MAP:
       list = &mBoundCubeMapTextures;
       break;
@@ -192,35 +181,29 @@ void WebGLContext::TexStorage(GLenum tex
   if (!IsTexTarget3D(texTarget)) {
     size.z = 1;
   }
   const auto tex = GetActiveTex(texTarget);
   if (!tex) return;
   tex->TexStorage(texTarget, levels, internalFormat, size);
 }
 
-void WebGLContext::TexImage(GLenum imageTarget, uint32_t level,
-                            GLenum respecFormat, uvec3 offset, uvec3 size,
+void WebGLContext::TexImage(uint32_t level, GLenum respecFormat, uvec3 offset,
                             const webgl::PackingInfo& pi,
-                            const TexImageSource& src,
-                            const dom::HTMLCanvasElement& canvas) const {
+                            const webgl::TexUnpackBlobDesc& src) const {
   const WebGLContext::FuncScope funcScope(
       *this, bool(respecFormat) ? "texImage" : "texSubImage");
 
   if (respecFormat) {
     offset = {0, 0, 0};
   }
-  const auto texTarget = ImageToTexTarget(imageTarget);
-  if (!IsTexTarget3D(texTarget)) {
-    size.z = 1;
-  }
+  const auto texTarget = ImageToTexTarget(src.imageTarget);
   const auto tex = GetActiveTex(texTarget);
   if (!tex) return;
-  tex->TexImage(imageTarget, level, respecFormat, offset, size, pi, src,
-                canvas);
+  tex->TexImage(level, respecFormat, offset, pi, src);
 }
 
 void WebGLContext::CompressedTexImage(bool sub, GLenum imageTarget,
                                       uint32_t level, GLenum format,
                                       uvec3 offset, uvec3 size,
                                       const Range<const uint8_t>& src,
                                       const uint32_t pboImageSize,
                                       const Maybe<uint64_t>& pboOffset) const {
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -541,32 +541,16 @@ void WebGLContext::AssertCachedGlobalSta
                                maxStencilBitsMask, mStencilWriteMaskBack);
 
   // Viewport
   GLint int4[4] = {0, 0, 0, 0};
   gl->fGetIntegerv(LOCAL_GL_VIEWPORT, int4);
   MOZ_ASSERT(int4[0] == mViewportX && int4[1] == mViewportY &&
              int4[2] == mViewportWidth && int4[3] == mViewportHeight);
 
-  AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_ALIGNMENT,
-                         mPixelStore.mUnpackAlignment);
-
-  if (IsWebGL2()) {
-    AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_IMAGE_HEIGHT,
-                           mPixelStore.mUnpackImageHeight);
-    AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_SKIP_IMAGES,
-                           mPixelStore.mUnpackSkipImages);
-    AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_ROW_LENGTH,
-                           mPixelStore.mUnpackRowLength);
-    AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_SKIP_ROWS,
-                           mPixelStore.mUnpackSkipRows);
-    AssertUintParamCorrect(gl, LOCAL_GL_UNPACK_SKIP_PIXELS,
-                           mPixelStore.mUnpackSkipPixels);
-  }
-
   MOZ_ASSERT(!gl::GLContext::IsBadCallError(errorScope.GetError()));
 #endif
 }
 
 const char* InfoFrom(WebGLTexImageFunc func, WebGLTexDimensions dims) {
   switch (dims) {
     case WebGLTexDimensions::Tex2D:
       switch (func) {
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -570,29 +570,16 @@ bool WebGLContext::InitAndValidateGL(Fai
   //
   // GL Spec 4.0.0:
   // (https://www.opengl.org/registry/doc/glspec40.core.20100311.pdf)
   // in Section E.2.2 "Removed Features", pg 397: "[...] The default
   // vertex array object (the name zero) is also deprecated. [...]"
   mDefaultVertexArray = WebGLVertexArray::Create(this);
   mDefaultVertexArray->BindVertexArray();
 
-  mPixelStore.mFlipY = false;
-  mPixelStore.mPremultiplyAlpha = false;
-  mPixelStore.mColorspaceConversion = BROWSER_DEFAULT_WEBGL;
-  mPixelStore.mRequireFastPath = false;
-
-  // GLES 3.0.4, p259:
-  mPixelStore.mUnpackImageHeight = 0;
-  mPixelStore.mUnpackSkipImages = 0;
-  mPixelStore.mUnpackRowLength = 0;
-  mPixelStore.mUnpackSkipRows = 0;
-  mPixelStore.mUnpackSkipPixels = 0;
-  mPixelStore.mUnpackAlignment = 4;
-
   mPrimRestartTypeBytes = 0;
 
   mGenericVertexAttribTypes.assign(limits.maxVertexAttribs,
                                    webgl::AttribBaseType::Float);
   mGenericVertexAttribTypeInvalidator.InvalidateCaches();
 
   static const float kDefaultGenericVertexAttribData[4] = {0, 0, 0, 1};
   memcpy(mGenericVertexAttrib0Data, kDefaultGenericVertexAttribData,
--- a/dom/canvas/WebGLIpdl.h
+++ b/dom/canvas/WebGLIpdl.h
@@ -5,16 +5,99 @@
 
 #ifndef WEBGLIPDL_H_
 #define WEBGLIPDL_H_
 
 #include "mozilla/layers/LayersSurfaces.h"
 #include "WebGLTypes.h"
 
 namespace mozilla {
+namespace webgl {
+
+// TODO: This should probably replace Shmem, or at least this should move to ipc/glue.
+class RaiiShmem final {
+  RefPtr<mozilla::ipc::ActorLifecycleProxy> mWeakRef;
+  mozilla::ipc::Shmem mShmem = {};
+
+ public:
+  /// Returns zeroed data.
+  static RaiiShmem Alloc(mozilla::ipc::IProtocol* const allocator,
+                         const size_t size,
+                         const Shmem::SharedMemory::SharedMemoryType type) {
+    mozilla::ipc::Shmem shmem;
+    if (!allocator->AllocShmem(size, type, &shmem)) return {};
+    return {allocator, shmem};
+  }
+
+  // -
+
+  RaiiShmem() = default;
+
+  RaiiShmem(mozilla::ipc::IProtocol* const allocator,
+            const mozilla::ipc::Shmem& shmem)
+      : mWeakRef(allocator->ToplevelProtocol()->GetLifecycleProxy()),
+        mShmem(shmem) {
+    // Shmems are handled by the top-level, so use that or we might leak after
+    // the actor dies.
+    MOZ_ASSERT(mWeakRef);
+  }
+
+  void reset() {
+    if (IsShmem()) {
+      const auto& allocator = mWeakRef->Get();
+      if (allocator) {
+        allocator->DeallocShmem(mShmem);
+      }
+    }
+    mWeakRef = nullptr;
+    mShmem = {};
+  }
+
+  ~RaiiShmem() { reset(); }
+
+  // -
+
+  RaiiShmem(RaiiShmem&& rhs) { *this = std::move(rhs); }
+  RaiiShmem& operator=(RaiiShmem&& rhs) {
+    reset();
+    mWeakRef = rhs.mWeakRef;
+    mShmem = rhs.Extract();
+    return *this;
+  }
+
+  // -
+
+  bool IsShmem() const { return mShmem.IsReadable(); }
+
+  explicit operator bool() const { return IsShmem(); }
+
+  // -
+
+  const auto& Shmem() const {
+    MOZ_ASSERT(IsShmem());
+    return mShmem;
+  }
+
+  Range<uint8_t> ByteRange() const {
+    MOZ_ASSERT(IsShmem());
+    return {mShmem.get<uint8_t>(), mShmem.Size<uint8_t>()};
+  }
+
+  mozilla::ipc::Shmem Extract() {
+    auto ret = mShmem;
+    mShmem = {};
+    reset();
+    return ret;
+  }
+};
+
+using Int32Vector = std::vector<int32_t>;
+
+}  // namespace webgl
+
 namespace ipc {
 
 template <>
 struct IPDLParamTraits<mozilla::webgl::FrontBufferSnapshotIpc> final {
   using T = mozilla::webgl::FrontBufferSnapshotIpc;
 
   static void Write(IPC::Message* const msg, IProtocol* actor, T& in) {
     WriteParam(msg, in.surfSize);
@@ -471,20 +554,9 @@ struct ParamTraits<mozilla::avec3<U>> fi
                    T* const out) {
     return ReadParam(msg, itr, &out->x) && ReadParam(msg, itr, &out->y) &&
            ReadParam(msg, itr, &out->z);
   }
 };
 
 }  // namespace IPC
 
-namespace mozilla {
-namespace webgl {
-using MaybeDouble = Maybe<double>;
-using MaybeFrontBufferSnapshotIpc = Maybe<FrontBufferSnapshotIpc>;
-using MaybeSurfaceDescriptor = Maybe<layers::SurfaceDescriptor>;
-using MaybeReadPixelsResultIpc = Maybe<ReadPixelsResultIpc>;
-using MaybeShaderPrecisionFormat = Maybe<ShaderPrecisionFormat>;
-using MaybeString = Maybe<std::string>;
-}  // namespace webgl
-}  // namespace mozilla
-
 #endif
--- a/dom/canvas/WebGLMethodDispatcher.h
+++ b/dom/canvas/WebGLMethodDispatcher.h
@@ -108,17 +108,16 @@ DEFINE_ASYNC(HostWebGLContext::FrontFace
 // DEFINE_SYNC(HostWebGLContext::GetError)
 // DEFINE_SYNC(HostWebGLContext::GetFramebufferAttachmentParameter)
 // DEFINE_SYNC(HostWebGLContext::GetRenderbufferParameter)
 // DEFINE_SYNC(HostWebGLContext::GetShaderPrecisionFormat)
 // DEFINE_SYNC(HostWebGLContext::GetUniform)
 DEFINE_ASYNC(HostWebGLContext::Hint)
 DEFINE_ASYNC(HostWebGLContext::LineWidth)
 DEFINE_ASYNC(HostWebGLContext::LinkProgram)
-DEFINE_ASYNC(HostWebGLContext::PixelStorei)
 DEFINE_ASYNC(HostWebGLContext::PolygonOffset)
 DEFINE_ASYNC(HostWebGLContext::Present)
 DEFINE_ASYNC(HostWebGLContext::SampleCoverage)
 DEFINE_ASYNC(HostWebGLContext::Scissor)
 DEFINE_ASYNC(HostWebGLContext::ShaderSource)
 DEFINE_ASYNC(HostWebGLContext::StencilFuncSeparate)
 DEFINE_ASYNC(HostWebGLContext::StencilMaskSeparate)
 DEFINE_ASYNC(HostWebGLContext::StencilOpSeparate)
@@ -135,17 +134,17 @@ DEFINE_ASYNC(HostWebGLContext::Invalidat
 DEFINE_ASYNC(HostWebGLContext::ReadBuffer)
 // DEFINE_SYNC(HostWebGLContext::GetInternalformatParameter)
 DEFINE_ASYNC(HostWebGLContext::RenderbufferStorageMultisample)
 DEFINE_ASYNC(HostWebGLContext::ActiveTexture)
 DEFINE_ASYNC(HostWebGLContext::BindTexture)
 DEFINE_ASYNC(HostWebGLContext::GenerateMipmap)
 DEFINE_ASYNC(HostWebGLContext::CopyTexImage)
 DEFINE_ASYNC(HostWebGLContext::TexStorage)
-// DEFINE_ASYNC(HostWebGLContext::TexImage)
+DEFINE_ASYNC(HostWebGLContext::TexImage)
 DEFINE_ASYNC(HostWebGLContext::CompressedTexImage)
 // DEFINE_SYNC(HostWebGLContext::GetTexParameter)
 DEFINE_ASYNC(HostWebGLContext::TexParameter_base)
 DEFINE_ASYNC(HostWebGLContext::UseProgram)
 // DEFINE_SYNC(HostWebGLContext::ValidateProgram)
 DEFINE_ASYNC(HostWebGLContext::UniformData)
 DEFINE_ASYNC(HostWebGLContext::VertexAttrib4T)
 DEFINE_ASYNC(HostWebGLContext::VertexAttribDivisor)
--- a/dom/canvas/WebGLParent.cpp
+++ b/dom/canvas/WebGLParent.cpp
@@ -34,27 +34,29 @@ mozilla::ipc::IPCResult WebGLParent::Rec
 
   if (!mHost) {
     return IPC_FAIL(this, "Failed to create HostWebGLContext");
   }
 
   return IPC_OK();
 }
 
-WebGLParent::WebGLParent() : PcqActor(this) {}
+WebGLParent::WebGLParent() = default;
 WebGLParent::~WebGLParent() = default;
 
 // -
 
 using IPCResult = mozilla::ipc::IPCResult;
 
-IPCResult WebGLParent::RecvDispatchCommands(Shmem&& shmem,
+IPCResult WebGLParent::RecvDispatchCommands(Shmem&& rawShmem,
                                             const uint64_t cmdsByteSize) {
+  auto shmem = webgl::RaiiShmem(this, std::move(rawShmem));
+
   MOZ_ASSERT(cmdsByteSize);
-  const auto shmemBytes = ByteRange(shmem);
+  const auto shmemBytes = shmem.ByteRange();
   const auto byteSize = std::min<uint64_t>(shmemBytes.length(), cmdsByteSize);
   const auto cmdsBytes =
       Range<const uint8_t>{shmemBytes.begin(), shmemBytes.begin() + byteSize};
   auto view = webgl::RangeConsumerView{cmdsBytes};
 
   while (true) {
     size_t id = 0;
     const auto status = view.ReadParam(&id);
@@ -79,70 +81,75 @@ void WebGLParent::ActorDestroy(ActorDest
 
 IPCResult WebGLParent::RecvGetFrontBufferSnapshot(
     webgl::FrontBufferSnapshotIpc* const ret) {
   *ret = {};
 
   const auto surfSize = mHost->GetFrontBufferSize();
   const auto byteSize = 4 * surfSize.x * surfSize.y;
 
-  Shmem shmem;
-  if (!PWebGLParent::AllocShmem(
-          byteSize, mozilla::ipc::SharedMemory::SharedMemoryType::TYPE_BASIC,
-          &shmem)) {
+  auto shmem = webgl::RaiiShmem::Alloc(
+      this, byteSize, mozilla::ipc::SharedMemory::SharedMemoryType::TYPE_BASIC);
+  MOZ_ASSERT(shmem);
+  if (!shmem) {
     return IPC_FAIL(this, "Failed to allocate shmem for result");
   }
 
-  auto shmemBytes = ByteRange(shmem);
-  if (!mHost->FrontBufferSnapshotInto(shmemBytes)) return IPC_OK();
-  *ret = {surfSize, Some(std::move(shmem))};
+  const auto range = shmem.ByteRange();
+  auto retSize = surfSize;
+  if (!mHost->FrontBufferSnapshotInto(range)) {
+    retSize = {0, 0};  // Zero means failure.
+  }
+  *ret = {retSize, shmem.Extract()};
   return IPC_OK();
 }
 
 IPCResult WebGLParent::RecvGetBufferSubData(const GLenum target,
                                             const uint64_t srcByteOffset,
                                             const uint64_t byteSize,
                                             Shmem* const ret) {
-  Shmem shmem;
-  if (!PWebGLParent::AllocShmem(
-          byteSize, mozilla::ipc::SharedMemory::SharedMemoryType::TYPE_BASIC,
-          &shmem)) {
-    MOZ_ASSERT(false);
+  const auto allocSize = 1 + byteSize;
+  auto shmem = webgl::RaiiShmem::Alloc(
+      this, allocSize,
+      mozilla::ipc::SharedMemory::SharedMemoryType::TYPE_BASIC);
+  MOZ_ASSERT(shmem);
+  if (!shmem) {
     return IPC_FAIL(this, "Failed to allocate shmem for result");
   }
 
-  const auto range = ByteRange(shmem);
-  memset(range.begin().get(), 0,
-         range.length());  // TODO: This is usually overkill.
+  const auto shmemRange = shmem.ByteRange();
+  const auto dataRange =
+      Range<uint8_t>{shmemRange.begin() + 1, shmemRange.end()};
 
-  if (mHost->GetBufferSubData(target, srcByteOffset, range)) {
-    *ret = std::move(shmem);
-  }
+  // We need to always send the shmem:
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=1463831#c2
+  const auto ok = mHost->GetBufferSubData(target, srcByteOffset, dataRange);
+  *(shmemRange.begin().get()) = ok;
+  *ret = shmem.Extract();
   return IPC_OK();
 }
 
 IPCResult WebGLParent::RecvReadPixels(const webgl::ReadPixelsDesc& desc,
-                                      const uint64_t byteCount,
+                                      const uint64_t byteSize,
                                       webgl::ReadPixelsResultIpc* const ret) {
   *ret = {};
 
-  Shmem shmem;
-  if (!PWebGLParent::AllocShmem(
-          byteCount, mozilla::ipc::SharedMemory::SharedMemoryType::TYPE_BASIC,
-          &shmem)) {
-    MOZ_ASSERT(false);
+  const auto allocSize = std::max<uint64_t>(1, byteSize);
+  auto shmem = webgl::RaiiShmem::Alloc(
+      this, allocSize,
+      mozilla::ipc::SharedMemory::SharedMemoryType::TYPE_BASIC);
+  MOZ_ASSERT(shmem);
+  if (!shmem) {
     return IPC_FAIL(this, "Failed to allocate shmem for result");
   }
 
-  auto range = ByteRange(shmem);
-  memset(range.begin().get(), 0,
-         range.length());  // TODO: This is usually overkill.
+  const auto range = shmem.ByteRange();
 
   const auto res = mHost->ReadPixelsInto(desc, range);
-  *ret = {res, std::move(shmem)};
+  *ret = {res, shmem.Extract()};
   return IPC_OK();
 }
 
 // -
 
 IPCResult WebGLParent::RecvCheckFramebufferStatus(GLenum target,
                                                   GLenum* const ret) {
   *ret = mHost->CheckFramebufferStatus(target);
--- a/dom/canvas/WebGLParent.h
+++ b/dom/canvas/WebGLParent.h
@@ -17,19 +17,17 @@ class HostWebGLContext;
 
 namespace layers {
 class SharedSurfaceTextureClient;
 class SurfaceDescriptor;
 }
 
 namespace dom {
 
-class WebGLParent : public PWebGLParent,
-                    public SupportsWeakPtr<WebGLParent>,
-                    public mozilla::webgl::PcqActor {
+class WebGLParent : public PWebGLParent, public SupportsWeakPtr<WebGLParent> {
   friend PWebGLParent;
 
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebGLParent, override);
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLParent)
   using OtherSideActor = WebGLChild;
 
   mozilla::ipc::IPCResult RecvInitialize(
@@ -37,16 +35,18 @@ class WebGLParent : public PWebGLParent,
       UniquePtr<HostWebGLCommandSinkI>&& aSinkI, webgl::InitContextResult* out);
 
   WebGLParent();  // For IPDL
 
   using IPCResult = mozilla::ipc::IPCResult;
 
   IPCResult RecvDispatchCommands(mozilla::ipc::Shmem&&, uint64_t);
 
+  IPCResult RecvGetBufferSubData(GLenum target, uint64_t srcByteOffset,
+                                 uint64_t byteSize, mozilla::ipc::Shmem* ret);
   IPCResult RecvGetFrontBufferSnapshot(webgl::FrontBufferSnapshotIpc* ret);
   IPCResult RecvReadPixels(const webgl::ReadPixelsDesc&, uint64_t byteSize,
                            webgl::ReadPixelsResultIpc* ret);
 
   // -
 
   using ObjectId = webgl::ObjectId;
 
@@ -55,18 +55,16 @@ class WebGLParent : public PWebGLParent,
                                GLenum* ret);
   IPCResult RecvCreateOpaqueFramebuffer(ObjectId id,
                                         const OpaqueFramebufferOptions&,
                                         bool* ret);
   IPCResult RecvDrawingBufferSize(uvec2* ret);
   IPCResult RecvFinish();
   IPCResult RecvGetBufferParameter(GLenum target, GLenum pname,
                                    Maybe<double>* ret);
-  IPCResult RecvGetBufferSubData(GLenum target, uint64_t srcByteOffset,
-                                 uint64_t byteSize, mozilla::ipc::Shmem* ret);
   IPCResult RecvGetCompileResult(ObjectId id, webgl::CompileResult* ret);
   IPCResult RecvGetError(GLenum* ret);
   IPCResult RecvGetFragDataLocation(ObjectId id, const std::string& name,
                                     GLint* ret);
   IPCResult RecvGetFramebufferAttachmentParameter(ObjectId id,
                                                   GLenum attachment,
                                                   GLenum pname,
                                                   Maybe<double>* ret);
--- a/dom/canvas/WebGLQuery.cpp
+++ b/dom/canvas/WebGLQuery.cpp
@@ -71,21 +71,16 @@ void WebGLQuery::EndQuery() {
   mCanBeAvailable = false;
 
   ////
 
   const auto& gl = mContext->gl;
 
   const auto driverTarget = TargetForDriver(gl, mTarget);
   gl->fEndQuery(driverTarget);
-
-  ////
-
-  const auto& availRunnable = mContext->EnsureAvailabilityRunnable();
-  availRunnable->mQueries.push_back(this);
 }
 
 Maybe<double> WebGLQuery::GetQueryParameter(GLenum pname) const {
   switch (pname) {
     case LOCAL_GL_QUERY_RESULT_AVAILABLE:
     case LOCAL_GL_QUERY_RESULT:
       break;
 
@@ -102,26 +97,16 @@ Maybe<double> WebGLQuery::GetQueryParame
   if (mActiveSlot) {
     mContext->ErrorInvalidOperation("Query is still active.");
     return Nothing();
   }
 
   // End of validation
   ////
 
-  // We must usually wait for an event loop before the query can be available.
-  const bool canBeAvailable =
-      (mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
-  if (!canBeAvailable) {
-    if (pname == LOCAL_GL_QUERY_RESULT_AVAILABLE) {
-      return Some(false);
-    }
-    return Nothing();
-  }
-
   const auto& gl = mContext->gl;
 
   uint64_t val = 0;
   switch (pname) {
     case LOCAL_GL_QUERY_RESULT_AVAILABLE:
       gl->fGetQueryObjectuiv(mGLName, pname, (GLuint*)&val);
       return Some(static_cast<bool>(val));
 
@@ -162,14 +147,11 @@ void WebGLQuery::QueryCounter() {
     return;
   }
 
   mTarget = target;
   mCanBeAvailable = false;
 
   const auto& gl = mContext->gl;
   gl->fQueryCounter(mGLName, mTarget);
-
-  const auto& availRunnable = mContext->EnsureAvailabilityRunnable();
-  availRunnable->mQueries.push_back(this);
 }
 
 }  // namespace mozilla
--- a/dom/canvas/WebGLQueueParamTraits.h
+++ b/dom/canvas/WebGLQueueParamTraits.h
@@ -39,16 +39,19 @@ template <>
 struct IsTriviallySerializable<WebGLTexPboOffset> : std::true_type {};
 
 template <>
 struct IsTriviallySerializable<webgl::ExtensionBits> : std::true_type {};
 template <>
 struct IsTriviallySerializable<webgl::GetUniformData> : std::true_type {};
 
 template <>
+struct IsTriviallySerializable<mozilla::webgl::PackingInfo> : std::true_type {};
+
+template <>
 struct IsTriviallySerializable<ICRData> : std::true_type {};
 
 template <>
 struct IsTriviallySerializable<gfx::IntSize> : std::true_type {};
 
 template <typename T>
 struct IsTriviallySerializable<avec2<T>> : std::true_type {};
 template <typename T>
@@ -197,17 +200,16 @@ struct QueueParamTraits<std::vector<U>> 
     auto status = aProducerView.WriteParam(aArg.size());
     if (!status) return status;
     status = aProducerView.Write(aArg.data(), aArg.data() + aArg.size());
     return status;
   }
 
   template <typename V>
   static QueueStatus Read(ConsumerView<V>& aConsumerView, T* aArg) {
-    MOZ_CRASH("no way to fallibly resize vectors without exceptions");
     size_t size;
     auto status = aConsumerView.ReadParam(&size);
     if (!status) return status;
 
     aArg->resize(size);
     status = aConsumerView.Read(aArg->data(), aArg->data() + size);
     return status;
   }
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -581,19 +581,21 @@ static bool ZeroTextureData(const WebGLC
 
     if (!checkedByteCount.isValid()) return false;
 
     const size_t sliceByteCount = checkedByteCount.value();
 
     UniqueBuffer zeros = calloc(1u, sliceByteCount);
     if (!zeros) return false;
 
-    ScopedUnpackReset scopedReset(webgl);
-    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);  // Don't bother with
-                                                     // striding it well.
+    // Don't bother with striding it well.
+    // TODO: We shouldn't need to do this for CompressedTexSubImage.
+    gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
+    const auto revert = MakeScopeExit(
+        [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
 
     GLenum error = 0;
     for (const auto z : IntegerRange(depth)) {
       if ((*info.mUninitializedSlices)[z]) {
         error = DoCompressedTexSubImage(gl, target.get(), level, 0, 0, z, width,
                                         height, 1, sizedFormat, sliceByteCount,
                                         zeros.get());
         if (error) break;
@@ -623,19 +625,20 @@ static bool ZeroTextureData(const WebGLC
 
   if (!checkedByteCount.isValid()) return false;
 
   const size_t sliceByteCount = checkedByteCount.value();
 
   UniqueBuffer zeros = calloc(1u, sliceByteCount);
   if (!zeros) return false;
 
-  ScopedUnpackReset scopedReset(webgl);
-  gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
-                   1);  // Don't bother with striding it well.
+  // Don't bother with striding it well.
+  gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
+  const auto revert =
+      MakeScopeExit([&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
 
   GLenum error = 0;
   for (const auto z : IntegerRange(depth)) {
     if ((*info.mUninitializedSlices)[z]) {
       error = DoTexSubImage(gl, target, level, 0, 0, z, width, height, 1,
                             packing, zeros.get());
       if (error) break;
     }
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -196,20 +196,18 @@ class WebGLTexture final : public WebGLC
   bool ValidateUnpack(const webgl::TexUnpackBlob* blob, bool isFunc3D,
                       const webgl::PackingInfo& srcPI) const;
 
  public:
   void TexStorage(TexTarget target, uint32_t levels, GLenum sizedFormat,
                   const uvec3& size);
 
   // TexSubImage iff `!respecFormat`
-  void TexImage(GLenum imageTarget, uint32_t level, GLenum respecFormat,
-                const uvec3& offset, const uvec3& size,
-                const webgl::PackingInfo& pi, const TexImageSource& src,
-                const dom::HTMLCanvasElement& canvas);
+  void TexImage(uint32_t level, GLenum respecFormat, const uvec3& offset,
+                const webgl::PackingInfo& pi, const webgl::TexUnpackBlobDesc&);
 
   // CompressedTexSubImage iff `sub`
   void CompressedTexImage(bool sub, GLenum imageTarget, uint32_t level,
                           GLenum formatEnum, const uvec3& offset,
                           const uvec3& size, const Range<const uint8_t>& src,
                           const uint32_t pboImageSize,
                           const Maybe<uint64_t>& pboOffset);
 
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLTexture.h"
 
 #include <algorithm>
 #include <limits>
 
 #include "CanvasUtils.h"
+#include "ClientWebGLContext.h"
 #include "GLBlitHelper.h"
 #include "GLContext.h"
 #include "mozilla/Casting.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/dom/ImageData.h"
@@ -25,172 +26,123 @@
 #include "TexUnpackBlob.h"
 #include "WebGLBuffer.h"
 #include "WebGLContext.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLTexelConversions.h"
 
 namespace mozilla {
-
-static UniquePtr<webgl::TexUnpackBytes> FromView(
-    const WebGLContext* webgl, TexImageTarget target, const uvec3& size,
-    const dom::ArrayBufferView* view, GLuint viewElemOffset,
-    GLuint viewElemLengthOverride) {
-  const bool isClientData = true;
-  const uint8_t* bytes = nullptr;
-  size_t availByteCount = 0;
-  if (view) {
-    const auto range =
-        GetRangeFromView(*view, viewElemOffset, viewElemLengthOverride);
-    if (!range) {
-      webgl->GenerateError(LOCAL_GL_INVALID_OPERATION, "`source` too small.");
-      return nullptr;
-    }
-    bytes = range->begin().get();
-    availByteCount = range->length();
-  }
-  return MakeUnique<webgl::TexUnpackBytes>(webgl, target, size.x, size.y,
-                                           size.z, isClientData, bytes,
-                                           availByteCount);
-}
+namespace webgl {
 
-static UniquePtr<webgl::TexUnpackBytes> FromPboOffset(const WebGLContext* webgl,
-                                                      TexImageTarget target,
-                                                      const uvec3& size,
-                                                      WebGLintptr pboOffset) {
-  if (!webgl->ValidateNonNegative("offset", pboOffset)) return nullptr;
-
-  const auto& buffer =
-      webgl->ValidateBufferSelection(LOCAL_GL_PIXEL_UNPACK_BUFFER);
-  if (!buffer) return nullptr;
-
-  size_t availBufferBytes = buffer->ByteLength();
-  if (size_t(pboOffset) > availBufferBytes) {
-    webgl->ErrorInvalidOperation("Offset is passed end of buffer.");
-    return nullptr;
-  }
-  availBufferBytes -= pboOffset;
-  const bool isClientData = false;
-  const auto ptr = (const uint8_t*)pboOffset;
-  return MakeUnique<webgl::TexUnpackBytes>(webgl, target, size.x, size.y,
-                                           size.z, isClientData, ptr,
-                                           availBufferBytes);
-}
-
-static UniquePtr<webgl::TexUnpackBlob> FromImageBitmap(
-    const WebGLContext* webgl, TexImageTarget target, uvec3 size,
-    const dom::ImageBitmap& imageBitmap, ErrorResult* aRv) {
+Maybe<TexUnpackBlobDesc> FromImageBitmap(const GLenum target, uvec3 size,
+                                         const dom::ImageBitmap& imageBitmap,
+                                         ErrorResult* const out_rv) {
   if (imageBitmap.IsWriteOnly()) {
-    aRv->Throw(NS_ERROR_DOM_SECURITY_ERR);
-    return nullptr;
+    out_rv->Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return {};
   }
 
-  UniquePtr<dom::ImageBitmapCloneData> cloneData = imageBitmap.ToCloneData();
+  const auto cloneData = imageBitmap.ToCloneData();
   if (!cloneData) {
-    return nullptr;
+    return {};
   }
 
   const RefPtr<gfx::DataSourceSurface> surf = cloneData->mSurface;
 
   if (!size.x) {
     size.x = surf->GetSize().width;
   }
 
   if (!size.y) {
     size.y = surf->GetSize().height;
   }
 
   // WhatWG "HTML Living Standard" (30 October 2015):
   // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
   // non-premultiplied alpha values."
-  return MakeUnique<webgl::TexUnpackSurface>(
-      webgl, target, size.x, size.y, size.z, surf, cloneData->mAlphaType);
+  return Some(
+      TexUnpackBlobDesc{target, size, cloneData->mAlphaType, {}, {}, {}, surf});
 }
 
-static UniquePtr<webgl::TexUnpackBlob> FromImageData(
-    const WebGLContext* webgl, TexImageTarget target, uvec3 size,
-    const dom::ImageData& imageData, dom::Uint8ClampedArray* scopedArr) {
-  DebugOnly<bool> inited = scopedArr->Init(imageData.GetDataObject());
-  MOZ_ASSERT(inited);
-
+TexUnpackBlobDesc FromImageData(const GLenum target, uvec3 size,
+                                const dom::ImageData& imageData,
+                                dom::Uint8ClampedArray* const scopedArr) {
+  MOZ_RELEASE_ASSERT(scopedArr->Init(imageData.GetDataObject()));
   scopedArr->ComputeState();
-  const DebugOnly<size_t> dataSize = scopedArr->Length();
-  const void* const data = scopedArr->Data();
+  const size_t dataSize = scopedArr->Length();
+  const auto data = reinterpret_cast<uint8_t*>(scopedArr->Data());
 
   const gfx::IntSize imageSize(imageData.Width(), imageData.Height());
   const size_t stride = imageSize.width * 4;
   const gfx::SurfaceFormat surfFormat = gfx::SurfaceFormat::R8G8B8A8;
-
-  // WhatWG "HTML Living Standard" (30 October 2015):
-  // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
-  // non-premultiplied alpha values."
-  const auto alphaType = gfxAlphaType::NonPremult;
-
-  MOZ_ASSERT(dataSize == stride * imageSize.height);
-
-  uint8_t* wrappableData = (uint8_t*)data;
+  MOZ_ALWAYS_TRUE(dataSize == stride * imageSize.height);
 
   const RefPtr<gfx::DataSourceSurface> surf =
-      gfx::Factory::CreateWrappingDataSourceSurface(wrappableData, stride,
-                                                    imageSize, surfFormat);
-  if (!surf) {
-    webgl->ErrorOutOfMemory("OOM in FromImageData.");
-    return nullptr;
-  }
+      gfx::Factory::CreateWrappingDataSourceSurface(data, stride, imageSize,
+                                                    surfFormat);
+  MOZ_ASSERT(surf);
 
   ////
 
   if (!size.x) {
     size.x = imageData.Width();
   }
 
   if (!size.y) {
     size.y = imageData.Height();
   }
 
   ////
 
-  return MakeUnique<webgl::TexUnpackSurface>(webgl, target, size.x, size.y,
-                                             size.z, surf, alphaType);
+  // WhatWG "HTML Living Standard" (30 October 2015):
+  // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned as
+  // non-premultiplied alpha values."
+  return {target, size, gfxAlphaType::NonPremult, {}, {}, {}, surf};
 }
 
-UniquePtr<webgl::TexUnpackBlob> WebGLContext::FromDomElem(
-    const dom::HTMLCanvasElement& canvas, TexImageTarget target, uvec3 size,
-    const dom::Element& elem, ErrorResult* const out_error) const {
+Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext& webgl,
+                                            const GLenum target, uvec3 size,
+                                            const dom::Element& elem,
+                                            const bool allowBlitImage,
+                                            ErrorResult* const out_error) {
+  const auto& canvas = *webgl.GetCanvas();
+
   if (elem.IsHTMLElement(nsGkAtoms::canvas)) {
-    const dom::HTMLCanvasElement* canvas =
+    const dom::HTMLCanvasElement* srcCanvas =
         static_cast<const dom::HTMLCanvasElement*>(&elem);
-    if (canvas->IsWriteOnly()) {
+    if (srcCanvas->IsWriteOnly()) {
       out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
-      return nullptr;
+      return {};
     }
   }
 
   // The canvas spec says that drawImage should draw the first frame of
   // animated images. The webgl spec doesn't mention the issue, so we do the
   // same as drawImage.
   uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
                    nsLayoutUtils::SFE_WANT_IMAGE_SURFACE |
                    nsLayoutUtils::SFE_USE_ELEMENT_SIZE_IF_VECTOR |
                    nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
-
-  if (mPixelStore.mColorspaceConversion == LOCAL_GL_NONE)
+  const auto& unpacking = webgl.State().mPixelUnpackState;
+  if (unpacking.mColorspaceConversion == LOCAL_GL_NONE) {
     flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
+  }
 
   RefPtr<gfx::DrawTarget> idealDrawTarget = nullptr;  // Don't care for now.
   auto sfer = nsLayoutUtils::SurfaceFromElement(
       const_cast<dom::Element*>(&elem), flags, idealDrawTarget);
 
   //////
 
   uint32_t elemWidth = 0;
   uint32_t elemHeight = 0;
   layers::Image* layersImage = nullptr;
-  if (!StaticPrefs::webgl_disable_DOM_blit_uploads() && sfer.mLayersImage) {
+
+  if (sfer.mLayersImage && allowBlitImage) {
     layersImage = sfer.mLayersImage;
     elemWidth = layersImage->GetSize().width;
     elemHeight = layersImage->GetSize().height;
   }
 
   RefPtr<gfx::DataSourceSurface> dataSurf;
   if (!layersImage && sfer.GetSourceSurface()) {
     const auto surf = sfer.GetSourceSurface();
@@ -209,91 +161,59 @@ UniquePtr<webgl::TexUnpackBlob> WebGLCon
 
   if (!size.y) {
     size.y = elemHeight;
   }
 
   ////
 
   if (!layersImage && !dataSurf) {
-    const bool isClientData = true;
-    return MakeUnique<webgl::TexUnpackBytes>(this, target, size.x, size.y,
-                                             size.z, isClientData, nullptr, 0);
+    return Some(TexUnpackBlobDesc{target, size, gfxAlphaType::NonPremult});
   }
 
   //////
 
   // 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 = canvas.NodePrincipal();
 
     if (!dstPrincipal->Subsumes(srcPrincipal)) {
-      GenerateWarning("Cross-origin elements require CORS.");
+      webgl.EnqueueWarning("Cross-origin elements require CORS.");
       out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
-      return nullptr;
+      return {};
     }
   }
 
   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.
-    GenerateWarning("Element is write-only, thus cannot be uploaded.");
+    webgl.EnqueueWarning("Element is write-only, thus cannot be uploaded.");
     out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
-    return nullptr;
+    return {};
   }
 
   //////
   // Ok, we're good!
 
   if (layersImage) {
-    return MakeUnique<webgl::TexUnpackImage>(
-        this, target, size.x, size.y, size.z, layersImage, sfer.mAlphaType);
+    return Some(
+        TexUnpackBlobDesc{target, size, sfer.mAlphaType, {}, {}, layersImage});
   }
 
   MOZ_ASSERT(dataSurf);
-  return MakeUnique<webgl::TexUnpackSurface>(this, target, size.x, size.y,
-                                             size.z, dataSurf, sfer.mAlphaType);
+  return Some(
+      TexUnpackBlobDesc{target, size, sfer.mAlphaType, {}, {}, {}, dataSurf});
 }
 
-////////////////////////////////////////
-
-UniquePtr<webgl::TexUnpackBlob> WebGLContext::From(
-    const dom::HTMLCanvasElement& canvas, TexImageTarget target,
-    const uvec3& size, const TexImageSource& src,
-    dom::Uint8ClampedArray* const scopedArr) const {
-  if (src.mPboOffset) {
-    return FromPboOffset(this, target, size, *(src.mPboOffset));
-  }
-
-  if (mBoundPixelUnpackBuffer) {
-    ErrorInvalidOperation("PIXEL_UNPACK_BUFFER must be null.");
-    return nullptr;
-  }
-
-  if (src.mImageBitmap) {
-    return FromImageBitmap(this, target, size, *(src.mImageBitmap),
-                           src.mOut_error);
-  }
-
-  if (src.mImageData) {
-    return FromImageData(this, target, size, *(src.mImageData), scopedArr);
-  }
-
-  if (src.mDomElem) {
-    return FromDomElem(canvas, target, size, *(src.mDomElem), src.mOut_error);
-  }
-
-  return FromView(this, target, size, src.mView, src.mViewElemOffset,
-                  src.mViewElemLengthOverride);
-}
+}  // namespace webgl
 
 //////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////
 
 static bool ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture,
                              TexImageTarget target, uint32_t level,
                              webgl::ImageInfo** const out_imageInfo) {
   // Check level
@@ -931,31 +851,38 @@ void WebGLTexture::TexStorage(TexTarget 
   mImmutableLevelCount = AutoAssertCast(levels);
   ClampLevelBaseAndMax();
 }
 
 ////////////////////////////////////////
 // Tex(Sub)Image
 
 // TexSubImage iff `!respectFormat`
-void WebGLTexture::TexImage(GLenum imageTarget, uint32_t level,
-                            GLenum respecFormat, const uvec3& offset,
-                            const uvec3& claimedSize,
-                            const webgl::PackingInfo& pi,
-                            const TexImageSource& src,
-                            const dom::HTMLCanvasElement& canvas) {
-  dom::Uint8ClampedArray scopedArr;
-  const auto blob =
-      mContext->From(canvas, imageTarget, claimedSize, src, &scopedArr);
-  if (!blob) return;
+void WebGLTexture::TexImage(uint32_t level, GLenum respecFormat,
+                            const uvec3& offset, const webgl::PackingInfo& pi,
+                            const webgl::TexUnpackBlobDesc& src) {
+  Maybe<RawBuffer<>> cpuDataView;
+  if (src.cpuData) {
+    cpuDataView = Some(RawBuffer<>{src.cpuData->Data()});
+  }
+  const auto srcViewDesc = webgl::TexUnpackBlobDesc{
+      src.imageTarget, src.size,  src.srcAlphaType, std::move(cpuDataView),
+      src.pboOffset,   src.image, src.surf,         src.unpacking};
+  const auto blob = webgl::TexUnpackBlob::Create(srcViewDesc);
+  if (!blob) {
+    MOZ_ASSERT(false);
+    return;
+  }
 
-  // For DOM element upload entrypoints that have no size arguments, claimedSize
-  // is 0. Use blob size, not claimedSize. (blob size can also be zero, and
-  // that's valid!)
-  const auto size = uvec3{blob->mWidth, blob->mHeight, blob->mDepth};
+  const auto imageTarget = blob->mDesc.imageTarget;
+  auto size = blob->mDesc.size;
+
+  if (!IsTarget3D(imageTarget)) {
+    size.z = 1;
+  }
 
   ////////////////////////////////////
   // Get dest info
 
   const auto& fua = mContext->mFormatUsage;
   const auto fnValidateUnpackEnums = [&]() {
     if (!fua->AreUnpackEnumsValid(pi.format, pi.type)) {
       mContext->ErrorInvalidEnum("Invalid unpack format/type: %s/%s",
@@ -1050,25 +977,31 @@ void WebGLTexture::TexImage(GLenum image
     return;
   }
 
   if (!blob->Validate(mContext, pi)) return;
 
   ////////////////////////////////////
   // Do the thing!
 
+  blob->mDesc.unpacking.Apply(*mContext->gl, mContext->IsWebGL2(), size);
+  const auto revertUnpacking = MakeScopeExit([&]() {
+    const WebGLPixelStore defaultUnpacking;
+    defaultUnpacking.Apply(*mContext->gl, mContext->IsWebGL2(), size);
+  });
+
   Maybe<webgl::ImageInfo> newImageInfo;
   bool isRespec = false;
   if (respecFormat) {
     // It's tempting to do allocation first, and TexSubImage second, but this is
     // generally slower.
     newImageInfo = Some(webgl::ImageInfo{dstUsage, size.x, size.y, size.z});
     if (!blob->HasData()) {
       newImageInfo->mUninitializedSlices =
-          Some(std::vector<bool>(blob->mDepth, true));
+          Some(std::vector<bool>(size.z, true));
     }
 
     isRespec = (imageInfo->mWidth != newImageInfo->mWidth ||
                 imageInfo->mHeight != newImageInfo->mHeight ||
                 imageInfo->mDepth != newImageInfo->mDepth ||
                 imageInfo->mFormat != newImageInfo->mFormat);
   } else {
     if (!blob->HasData()) {
@@ -1078,19 +1011,18 @@ void WebGLTexture::TexImage(GLenum image
     if (!EnsureImageDataInitializedForUpload(this, imageTarget, level, offset,
                                              size, imageInfo)) {
       return;
     }
   }
 
   const bool isSubImage = !respecFormat;
   GLenum glError;
-  if (!blob->TexOrSubImage(isSubImage, isRespec, this, imageTarget, level,
-                           driverUnpackInfo, offset.x, offset.y, offset.z, pi,
-                           &glError)) {
+  if (!blob->TexOrSubImage(isSubImage, isRespec, this, level, driverUnpackInfo,
+                           offset.x, offset.y, offset.z, pi, &glError)) {
     return;
   }
 
   if (glError == LOCAL_GL_OUT_OF_MEMORY) {
     mContext->ErrorOutOfMemory("Driver ran out of memory during upload.");
     Truncate();
     return;
   }
@@ -1697,18 +1629,19 @@ static bool DoCopyTexOrSubImage(WebGLCon
 
       if (!zeros.get()) {
         webgl->ErrorOutOfMemory("Ran out of memory allocating zeros.");
         return false;
       }
     }
 
     if (!isSubImage || zeros) {
-      const ScopedUnpackReset unpackReset(webgl);
       gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
+      const auto revert = MakeScopeExit(
+          [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
       if (!isSubImage) {
         error = DoTexImage(gl, target, level, idealUnpack, dstWidth, dstHeight,
                            1, nullptr);
         if (error) {
           errorText = nsPrintfCString(
               "DoTexImage(0x%04x, %i, {0x%04x, 0x%04x, 0x%04x}, %u,%u,1) -> "
               "0x%04x",
               target.get(), level, idealUnpack->internalFormat,
--- a/dom/canvas/WebGLTypes.h
+++ b/dom/canvas/WebGLTypes.h
@@ -20,17 +20,16 @@
 #include "mozilla/gfx/Point.h"
 #include "mozilla/ipc/Shmem.h"
 #include "gfxTypes.h"
 
 #include "nsTArray.h"
 #include "nsString.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "mozilla/ipc/SharedMemoryBasic.h"
-//#include "WebGLStrongTypes.h"
 
 // Manual reflection of WebIDL typedefs that are different from their
 // OpenGL counterparts.
 typedef int64_t WebGLsizeiptr;
 typedef int64_t WebGLintptr;
 typedef bool WebGLboolean;
 
 // -
@@ -284,19 +283,19 @@ class UniqueBuffer {
     this->mBuffer = newBuffer;
     return *this;
   }
 
   explicit operator bool() const { return bool(mBuffer); }
 
   void* get() const { return mBuffer; }
 
-  UniqueBuffer(const UniqueBuffer& other) =
+  explicit UniqueBuffer(const UniqueBuffer& other) =
       delete;  // construct using std::move()!
-  void operator=(const UniqueBuffer& other) =
+  UniqueBuffer& operator=(const UniqueBuffer& other) =
       delete;  // assign using std::move()!
 };
 
 namespace webgl {
 struct FormatUsageInfo;
 
 struct SampleableInfo final {
   const char* incompleteReason = nullptr;
@@ -346,29 +345,16 @@ struct FloatOrInt final  // For TexParam
   explicit FloatOrInt(GLfloat x) : isFloat(true), f(x), i(roundf(x)) {}
 
   FloatOrInt& operator=(const FloatOrInt& x) {
     memcpy(this, &x, sizeof(x));
     return *this;
   }
 };
 
-struct WebGLPixelStore final {
-  uint32_t mUnpackImageHeight = 0;
-  uint32_t mUnpackSkipImages = 0;
-  uint32_t mUnpackRowLength = 0;
-  uint32_t mUnpackSkipRows = 0;
-  uint32_t mUnpackSkipPixels = 0;
-  uint32_t mUnpackAlignment = 0;
-  GLenum mColorspaceConversion = 0;
-  bool mFlipY = false;
-  bool mPremultiplyAlpha = false;
-  bool mRequireFastPath = false;
-};
-
 using WebGLTexUnpackVariant =
     Variant<UniquePtr<webgl::TexUnpackBytes>,
             UniquePtr<webgl::TexUnpackSurface>,
             UniquePtr<webgl::TexUnpackImage>, WebGLTexPboOffset>;
 
 using MaybeWebGLTexUnpackVariant = Maybe<WebGLTexUnpackVariant>;
 
 struct WebGLContextOptions {
@@ -687,17 +673,17 @@ struct TypedQuad final {
 /// [1-16]x32-bit primitives, with a type tag.
 struct GetUniformData final {
   alignas(alignof(float)) uint8_t data[4 * 4 * sizeof(float)] = {};
   GLenum type = 0;
 };
 
 struct FrontBufferSnapshotIpc final {
   uvec2 surfSize = {};
-  Maybe<mozilla::ipc::Shmem> shmem;
+  mozilla::ipc::Shmem shmem = {};
 };
 
 struct ReadPixelsResult {
   gfx::IntRect subrect = {};
   size_t byteStride = 0;
 };
 
 struct ReadPixelsResultIpc final : public ReadPixelsResult {
@@ -728,17 +714,17 @@ struct ICRData {
   bool isPremultAlpha;
 };
 
 /**
  * Represents a block of memory that it may or may not own.  The
  * inner data type must be trivially copyable by memcpy.
  */
 template <typename T = uint8_t>
-class RawBuffer {
+class RawBuffer final {
   const T* mBegin = nullptr;
   size_t mLen = 0;
   UniqueBuffer mOwned;
 
  public:
   using ElementType = T;
 
   /**
@@ -747,16 +733,17 @@ class RawBuffer {
   explicit RawBuffer(const Range<const T>& data, UniqueBuffer&& owned = {})
       : mBegin(data.begin().get()),
         mLen(data.length()),
         mOwned(std::move(owned)) {}
 
   ~RawBuffer() = default;
 
   Range<const T> Data() const { return {mBegin, mLen}; }
+  const auto& begin() const { return mBegin; };
 
   RawBuffer() = default;
 
   RawBuffer(const RawBuffer&) = delete;
   RawBuffer& operator=(const RawBuffer&) = delete;
 
   RawBuffer(RawBuffer&&) = default;
   RawBuffer& operator=(RawBuffer&&) = default;
@@ -865,16 +852,27 @@ inline GLenum ImageToTexTarget(const GLe
     case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
     case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
       return LOCAL_GL_TEXTURE_CUBE_MAP;
     default:
       return imageTarget;
   }
 }
 
+inline bool IsTexTarget3D(const GLenum texTarget) {
+  switch (texTarget) {
+    case LOCAL_GL_TEXTURE_2D_ARRAY:
+    case LOCAL_GL_TEXTURE_3D:
+      return true;
+
+    default:
+      return false;
+  }
+}
+
 // -
 
 namespace dom {
 class Element;
 class ImageBitmap;
 class ImageData;
 }  // namespace dom
 
@@ -887,25 +885,87 @@ struct TexImageSource {
 
   const dom::ImageBitmap* mImageBitmap = nullptr;
   const dom::ImageData* mImageData = nullptr;
 
   const dom::Element* mDomElem = nullptr;
   ErrorResult* mOut_error = nullptr;
 };
 
+struct WebGLPixelStore final {
+  uint32_t mUnpackImageHeight = 0;
+  uint32_t mUnpackSkipImages = 0;
+  uint32_t mUnpackRowLength = 0;
+  uint32_t mUnpackSkipRows = 0;
+  uint32_t mUnpackSkipPixels = 0;
+  uint32_t mUnpackAlignment = 4;
+  GLenum mColorspaceConversion =
+      dom::WebGLRenderingContext_Binding::BROWSER_DEFAULT_WEBGL;
+  bool mFlipY = false;
+  bool mPremultiplyAlpha = false;
+  bool mRequireFastPath = false;
+
+  void Apply(gl::GLContext&, bool isWebgl2, const uvec3& uploadSize) const;
+
+  WebGLPixelStore ForUseWith(
+      const GLenum target, const uvec3& uploadSize,
+      const Maybe<gfx::IntSize>& structuredSrcSize) const {
+    auto ret = *this;
+
+    if (!IsTexTarget3D(target)) {
+      ret.mUnpackSkipImages = 0;
+      ret.mUnpackImageHeight = 0;
+    }
+
+    if (structuredSrcSize) {
+      ret.mUnpackRowLength = structuredSrcSize->width;
+    }
+
+    if (!ret.mUnpackRowLength) {
+      ret.mUnpackRowLength = uploadSize.x;
+    }
+    if (!ret.mUnpackImageHeight) {
+      ret.mUnpackImageHeight = uploadSize.y;
+    }
+
+    return ret;
+  }
+};
+
+struct TexImageData final {
+  WebGLPixelStore unpackState;
+
+  Maybe<uint64_t> pboOffset;
+
+  RawBuffer<> data;
+
+  const dom::Element* domElem = nullptr;
+  ErrorResult* out_domElemError = nullptr;
+};
+
+namespace webgl {
+
+struct TexUnpackBlobDesc final {
+  GLenum imageTarget = LOCAL_GL_TEXTURE_2D;
+  uvec3 size;
+  gfxAlphaType srcAlphaType = gfxAlphaType::NonPremult;
+
+  Maybe<RawBuffer<>> cpuData;
+  Maybe<uint64_t> pboOffset;
+  RefPtr<layers::Image> image;
+  RefPtr<gfx::DataSourceSurface> surf;
+
+  WebGLPixelStore unpacking;
+};
+
+}  // namespace webgl
+
 // ---------------------------------------
 // MakeRange
 
-inline Range<uint8_t> ByteRange(const mozilla::ipc::Shmem& shmem) {
-  return {shmem.get<uint8_t>(), shmem.Size<uint8_t>()};
-}
-
-// -
-
 template <typename T, size_t N>
 inline Range<const T> MakeRange(T (&arr)[N]) {
   return {arr, N};
 }
 
 template <typename T>
 inline Range<const T> MakeRange(const dom::Sequence<T>& seq) {
   return {seq.Elements(), seq.Length()};
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -5427,16 +5427,17 @@ subsuite = webgl2-core
 subsuite = webgl2-core
 [generated/test_2_conformance2__state__gl-get-calls.html]
 subsuite = webgl2-core
 fail-if = (os == 'linux')
 [generated/test_2_conformance2__state__gl-getstring.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__state__gl-object-get-calls.html]
 subsuite = webgl2-core
+skip-if = 1
 fail-if = (os == 'linux')
 [generated/test_2_conformance2__sync__sync-webgl-specific.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__textures__canvas__tex-2d-r11f_g11f_b10f-rgb-float.html]
 subsuite = webgl2-ext
 [generated/test_2_conformance2__textures__canvas__tex-2d-r11f_g11f_b10f-rgb-half_float.html]
 subsuite = webgl2-ext
 [generated/test_2_conformance2__textures__canvas__tex-2d-r11f_g11f_b10f-rgb-unsigned_int_10f_11f_11f_rev.html]
@@ -12275,16 +12276,17 @@ subsuite = webgl1-core
 [generated/test_conformance__state__gl-geterror.html]
 subsuite = webgl1-core
 [generated/test_conformance__state__gl-getstring.html]
 subsuite = webgl1-core
 [generated/test_conformance__state__gl-initial-state.html]
 subsuite = webgl1-core
 [generated/test_conformance__state__gl-object-get-calls.html]
 subsuite = webgl1-core
+skip-if = 1
 [generated/test_conformance__state__state-uneffected-after-compositing.html]
 subsuite = webgl1-core
 [generated/test_conformance__textures__canvas__tex-2d-alpha-alpha-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__canvas__tex-2d-luminance-luminance-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__canvas__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 subsuite = webgl1-ext
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -68,16 +68,24 @@ skip-if = 1
 ####################
 # Timing out
 [generated/test_conformance__uniforms__uniform-default-values.html]
 # Timeout on Windows, crash on Android/Linux.
 skip-if = (os == 'android') || (os == 'linux') || (os == 'win')
 [generated/test_conformance__ogles__GL__mat3__mat3_001_to_006.html]
 # Timeout on D3D11
 skip-if = (os == 'win')
+[generated/test_conformance__state__gl-object-get-calls.html]
+# Really really slow on IPC mode. Update test suite to get faster/quick-mode test.
+skip-if = 1
+[generated/test_2_conformance2__state__gl-object-get-calls.html]
+# Really really slow on IPC mode. Update test suite to get faster/quick-mode test.
+skip-if = 1
+# Also fails on Linux still?
+fail-if = (os == 'linux')
 
 ########################################################################
 # Global
 
 [generated/test_2_conformance2__extensions__ovr_multiview2.html]
 skip-if = (os == 'android')
 #[task 2020-01-06T23:47:51.786Z] 23:47:51     INFO -  GPU: UNKNOWN
 #[task 2020-01-06T23:47:51.786Z] 23:47:51     INFO -  Crash reason:  SIGSEGV /SEGV_MAPERR
@@ -794,18 +802,16 @@ fail-if = (os == 'linux')
 [generated/test_2_conformance2__context__context-attributes-depth-stencil-antialias-obeyed.html]
 fail-if = (os == 'android') || (os == 'linux')
 [generated/test_2_conformance2__rendering__blitframebuffer-multisampled-readbuffer.html]
 fail-if = (os == 'linux')
 [generated/test_2_conformance2__rendering__clipping-wide-points.html]
 fail-if = (os == 'linux')
 [generated/test_2_conformance2__state__gl-get-calls.html]
 fail-if = (os == 'linux')
-[generated/test_2_conformance2__state__gl-object-get-calls.html]
-fail-if = (os == 'linux')
 [generated/test_2_conformance__state__gl-get-calls.html]
 fail-if = (os == 'linux')
 [generated/test_2_conformance__glsl__bugs__sampler-array-using-loop-index.html]
 fail-if = (os == 'linux')
 [generated/test_conformance__rendering__line-rendering-quality.html]
 # Found 0 lines, looking in the vertical direction, expected 2
 fail-if = (os == 'linux')
 [generated/test_conformance__rendering__blending.html]