Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/dom/canvas/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 214 kB image not shown  

Quelle  ClientWebGLContext.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "ClientWebGLContext.h"

#include <bitset>

#include "ClientWebGLExtensions.h"
#include "gfxCrashReporterUtils.h"
#include "HostWebGLContext.h"
#include "js/PropertyAndElement.h"  // JS_DefineElement
#include "js/ScalarType.h"          // js::Scalar::Type
#include "mozilla/dom/Document.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/WebGLContextEvent.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/EnumeratedRange.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/CanvasManagerChild.h"
#include "mozilla/ipc/Shmem.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/layers/CompositableForwarder.h"
#include "mozilla/layers/CompositorBridgeChild.h"
#include "mozilla/layers/ImageBridgeChild.h"
#include "mozilla/layers/OOPCanvasRenderer.h"
#include "mozilla/layers/TextureClientSharedSurface.h"
#include "mozilla/layers/WebRenderUserData.h"
#include "mozilla/layers/WebRenderCanvasRenderer.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_webgl.h"
#include "nsContentUtils.h"
#include "nsDisplayList.h"
#include "TexUnpackBlob.h"
#include "WebGLFormats.h"
#include "WebGLMethodDispatcher.h"
#include "WebGLChild.h"
#include "WebGLTextureUpload.h"
#include "WebGLValidateStrings.h"

namespace mozilla {

namespace webgl {
std::string SanitizeRenderer(const std::string&);
}  // namespace webgl

// -

webgl::NotLostData::NotLostData(ClientWebGLContext& _context)
    : context(_context) {}

webgl::NotLostData::~NotLostData() {
  if (outOfProcess) {
    outOfProcess->Destroy();
  }
}

// -

bool webgl::ObjectJS::ValidateForContext(
    const ClientWebGLContext& targetContext, const charconst argName) const {
  if (!IsForContext(targetContext)) {
    targetContext.EnqueueError(
        LOCAL_GL_INVALID_OPERATION,
        "`%s` is from a different (or lost) WebGL context.", argName);
    return false;
  }
  return true;
}

void webgl::ObjectJS::WarnInvalidUse(const ClientWebGLContext& targetContext,
                                     const charconst argName) const {
  if (!ValidateForContext(targetContext, argName)) return;

  const auto errEnum = ErrorOnDeleted();
  targetContext.EnqueueError(errEnum, "Object `%s` is already deleted.",
                             argName);
}

// -

WebGLBufferJS::~WebGLBufferJS() {
  const auto webgl = Context();
  if (webgl) {
    webgl->DeleteBuffer(this);
  }
}

WebGLFramebufferJS::~WebGLFramebufferJS() {
  const auto webgl = Context();
  if (webgl) {
    webgl->DeleteFramebuffer(this);
  }
}

WebGLQueryJS::~WebGLQueryJS() {
  const auto webgl = Context();
  if (webgl) {
    webgl->DeleteQuery(this);
  }
}

WebGLRenderbufferJS::~WebGLRenderbufferJS() {
  const auto webgl = Context();
  if (webgl) {
    webgl->DeleteRenderbuffer(this);
  }
}

WebGLSamplerJS::~WebGLSamplerJS() {
  const auto webgl = Context();
  if (webgl) {
    webgl->DeleteSampler(this);
  }
}

WebGLSyncJS::~WebGLSyncJS() {
  const auto webgl = Context();
  if (webgl) {
    webgl->DeleteSync(this);
  }
}

WebGLTextureJS::~WebGLTextureJS() {
  const auto webgl = Context();
  if (webgl) {
    webgl->DeleteTexture(this);
  }
}

WebGLTransformFeedbackJS::~WebGLTransformFeedbackJS() {
  const auto webgl = Context();
  if (webgl) {
    webgl->DeleteTransformFeedback(this);
  }
}

WebGLVertexArrayJS::~WebGLVertexArrayJS() {
  const auto webgl = Context();
  if (webgl) {
    webgl->DeleteVertexArray(this);
  }
}

// -

static bool GetJSScalarFromGLType(GLenum type,
                                  js::Scalar::Type* const out_scalarType) {
  switch (type) {
    case LOCAL_GL_BYTE:
      *out_scalarType = js::Scalar::Int8;
      return true;

    case LOCAL_GL_UNSIGNED_BYTE:
      *out_scalarType = js::Scalar::Uint8;
      return true;

    case LOCAL_GL_SHORT:
      *out_scalarType = js::Scalar::Int16;
      return true;

    case LOCAL_GL_HALF_FLOAT:
    case LOCAL_GL_HALF_FLOAT_OES:
    case LOCAL_GL_UNSIGNED_SHORT:
    case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
    case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
    case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
      *out_scalarType = js::Scalar::Uint16;
      return true;

    case LOCAL_GL_UNSIGNED_INT:
    case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
    case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
    case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
    case LOCAL_GL_UNSIGNED_INT_24_8:
      *out_scalarType = js::Scalar::Uint32;
      return true;
    case LOCAL_GL_INT:
      *out_scalarType = js::Scalar::Int32;
      return true;

    case LOCAL_GL_FLOAT:
      *out_scalarType = js::Scalar::Float32;
      return true;

    default:
      return false;
  }
}

ClientWebGLContext::ClientWebGLContext(const bool webgl2)
    : mIsWebGL2(webgl2),
      mExtLoseContext(new ClientWebGLExtensionLoseContext(*this)) {}

ClientWebGLContext::~ClientWebGLContext() { RemovePostRefreshObserver(); }

void ClientWebGLContext::JsWarning(const std::string& utf8) const {
  nsIGlobalObject* global = nullptr;
  if (mCanvasElement) {
    mozilla::dom::Document* doc = mCanvasElement->OwnerDoc();
    if (doc) {
      global = doc->GetScopeObject();
    }
  } else if (mOffscreenCanvas) {
    global = mOffscreenCanvas->GetOwnerGlobal();
  }

  dom::AutoJSAPI api;
  if (!api.Init(global)) {
    return;
  }
  const auto& cx = api.cx();
  JS::WarnUTF8(cx, "%s", utf8.c_str());
}

void AutoJsWarning(const std::string& utf8) {
  if (NS_IsMainThread()) {
    const AutoJSContext cx;
    JS::WarnUTF8(cx, "%s", utf8.c_str());
    return;
  }

  JSContext* cx = dom::GetCurrentWorkerThreadJSContext();
  if (NS_WARN_IF(!cx)) {
    return;
  }

  JS::WarnUTF8(cx, "%s", utf8.c_str());
}

// ---------

bool ClientWebGLContext::DispatchEvent(const nsAString& eventName) const {
  const auto kCanBubble = CanBubble::eYes;
  const auto kIsCancelable = Cancelable::eYes;
  bool useDefaultHandler = true;

  if (mCanvasElement) {
    nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
                                         mCanvasElement, eventName, kCanBubble,
                                         kIsCancelable, &useDefaultHandler);
  } else if (mOffscreenCanvas) {
    // OffscreenCanvas case
    RefPtr<dom::Event> event =
        new dom::Event(mOffscreenCanvas, nullptr, nullptr);
    event->InitEvent(eventName, kCanBubble, kIsCancelable);
    event->SetTrusted(true);
    useDefaultHandler = mOffscreenCanvas->DispatchEvent(
        *event, dom::CallerType::System, IgnoreErrors());
  }
  return useDefaultHandler;
}

// -

void ClientWebGLContext::EmulateLoseContext() const {
  const FuncScope funcScope(*this"loseContext");
  if (mLossStatus != webgl::LossStatus::Ready) {
    JsWarning("loseContext: Already lost.");
    if (!mNextError) {
      mNextError = LOCAL_GL_INVALID_OPERATION;
    }
    return;
  }
  OnContextLoss(webgl::ContextLossReason::Manual);
}

void ClientWebGLContext::OnContextLoss(
    const webgl::ContextLossReason reason) const {
  JsWarning("WebGL context was lost.");

  if (mNotLost) {
    for (const auto& ext : mNotLost->extensions) {
      if (!ext) continue;
      ext->mContext = nullptr;  // Detach.
    }
    mNotLost = {};  // Lost now!
    mNextError = LOCAL_GL_CONTEXT_LOST_WEBGL;
  }

  switch (reason) {
    case webgl::ContextLossReason::Guilty:
      mLossStatus = webgl::LossStatus::LostForever;
      break;

    case webgl::ContextLossReason::None:
      mLossStatus = webgl::LossStatus::Lost;
      break;

    case webgl::ContextLossReason::Manual:
      mLossStatus = webgl::LossStatus::LostManually;
      break;
  }

  const auto weak = WeakPtr<const ClientWebGLContext>(this);
  const auto fnRun = [weak]() {
    const auto strong = RefPtr<const ClientWebGLContext>(weak);
    if (!strong) return;
    strong->Event_webglcontextlost();
  };
  already_AddRefed<mozilla::CancelableRunnable> runnable =
      NS_NewCancelableRunnableFunction("enqueue Event_webglcontextlost", fnRun);
  NS_DispatchToCurrentThread(std::move(runnable));
}

void ClientWebGLContext::Event_webglcontextlost() const {
  const bool useDefaultHandler = DispatchEvent(u"webglcontextlost"_ns);
  if (useDefaultHandler) {
    mLossStatus = webgl::LossStatus::LostForever;
  }

  if (mLossStatus == webgl::LossStatus::Lost) {
    RestoreContext(webgl::LossStatus::Lost);
  }
}

void ClientWebGLContext::RestoreContext(
    const webgl::LossStatus requiredStatus) const {
  if (requiredStatus != mLossStatus) {
    JsWarning(
        "restoreContext: Only valid iff context lost with loseContext().");
    if (!mNextError) {
      mNextError = LOCAL_GL_INVALID_OPERATION;
    }
    return;
  }
  MOZ_RELEASE_ASSERT(mLossStatus == webgl::LossStatus::Lost ||
                     mLossStatus == webgl::LossStatus::LostManually);

  if (mAwaitingRestore) return;
  mAwaitingRestore = true;

  const auto weak = WeakPtr<const ClientWebGLContext>(this);
  const auto fnRun = [weak]() {
    const auto strong = RefPtr<const ClientWebGLContext>(weak);
    if (!strong) return;
    strong->Event_webglcontextrestored();
  };
  already_AddRefed<mozilla::CancelableRunnable> runnable =
      NS_NewCancelableRunnableFunction("enqueue Event_webglcontextrestored",
                                       fnRun);
  NS_DispatchToCurrentThread(std::move(runnable));
}

void ClientWebGLContext::Event_webglcontextrestored() const {
  mAwaitingRestore = false;
  mLossStatus = webgl::LossStatus::Ready;
  mNextError = 0;

  uvec2 requestSize;
  if (mCanvasElement) {
    requestSize = {mCanvasElement->Width(), mCanvasElement->Height()};
  } else if (mOffscreenCanvas) {
    requestSize = {mOffscreenCanvas->Width(), mOffscreenCanvas->Height()};
  } else {
    MOZ_ASSERT_UNREACHABLE("no HTMLCanvasElement or OffscreenCanvas!");
    return;
  }

  if (!requestSize.x) {
    requestSize.x = 1;
  }
  if (!requestSize.y) {
    requestSize.y = 1;
  }

  const auto mutThis = const_cast<ClientWebGLContext*>(
      this);  // TODO: Make context loss non-mutable.
  if (!mutThis->CreateHostContext(requestSize)) {
    mLossStatus = webgl::LossStatus::LostForever;
    return;
  }

  mResetLayer = true;

  (void)DispatchEvent(u"webglcontextrestored"_ns);
}

// ---------

void ClientWebGLContext::ThrowEvent_WebGLContextCreationError(
    const std::string& text) const {
  nsCString msg;
  msg.AppendPrintf("Failed to create WebGL context: %s", text.c_str());
  JsWarning(msg.BeginReading());

  RefPtr<dom::EventTarget> target = mCanvasElement;
  if (!target && mOffscreenCanvas) {
    target = mOffscreenCanvas;
  } else if (!target) {
    return;
  }

  const auto kEventName = u"webglcontextcreationerror"_ns;

  dom::WebGLContextEventInit eventInit;
  // eventInit.mCancelable = true; // The spec says this, but it's silly.
  eventInit.mStatusMessage = NS_ConvertASCIItoUTF16(text.c_str());

  const RefPtr<dom::WebGLContextEvent> event =
      dom::WebGLContextEvent::Constructor(target, kEventName, eventInit);
  event->SetTrusted(true);

  target->DispatchEvent(*event);
}

// -------------------------------------------------------------------------
// Client-side helper methods.  Dispatch to a Host method.
// -------------------------------------------------------------------------

// If we are running WebGL in this process then call the HostWebGLContext
// method directly.  Otherwise, dispatch over IPC.
template <typename MethodT, typename... Args>
void ClientWebGLContext::Run_WithDestArgTypes(
    std::optional<JS::AutoCheckCannotGC>&& noGc, const MethodT method,
    const size_t id, const Args&... args) const {
  const auto notLost =
      mNotLost;  // Hold a strong-ref to prevent LoseContext=>UAF.

  // `AutoCheckCannotGC` must be reset after the GC data is done being used but
  // *before* the `notLost` destructor runs, since the latter can GC.
  const auto cleanup = MakeScopeExit([&]() { noGc.reset(); });

  if (IsContextLost()) {
    return;
  }

  const auto& inProcess = notLost->inProcess;
  if (inProcess) {
    (inProcess.get()->*method)(args...);
    return;
  }

  const auto& child = notLost->outOfProcess;

  const auto info = webgl::SerializationInfo(id, args...);
  const auto maybeDest = child->AllocPendingCmdBytes(info.requiredByteCount,
                                                     info.alignmentOverhead);
  if (!maybeDest) {
    noGc.reset();  // Reset early, as GC data will not be used, but JsWarning
                   // can GC.
    JsWarning("Failed to allocate internal command buffer.");
    OnContextLoss(webgl::ContextLossReason::None);
    return;
  }
  const auto& destBytes = *maybeDest;
  webgl::Serialize(destBytes, id, args...);
}

// -

#define RPROC(_METHOD) \
  decltype(&HostWebGLContext::_METHOD), &HostWebGLContext::_METHOD

// ------------------------- Composition, etc -------------------------

void ClientWebGLContext::OnBeforePaintTransaction() { Present(nullptr); }

void ClientWebGLContext::EndComposition() {
  // Mark ourselves as no longer invalidated.
  MarkContextClean();
}

// -

layers::TextureType ClientWebGLContext::GetTexTypeForSwapChain() const {
  const RefPtr<layers::ImageBridgeChild> imageBridge =
      layers::ImageBridgeChild::GetSingleton();
  const bool isOutOfProcess = mNotLost && mNotLost->outOfProcess != nullptr;
  return layers::TexTypeForWebgl(imageBridge, isOutOfProcess);
}

void ClientWebGLContext::Present(WebGLFramebufferJS* const xrFb,
                                 const bool webvr,
                                 const webgl::SwapChainOptions& options) {
  const auto texType = GetTexTypeForSwapChain();
  Present(xrFb, texType, webvr, options);
}

// Fill in remote texture ids to SwapChainOptions if async present is enabled.
webgl::SwapChainOptions ClientWebGLContext::PrepareAsyncSwapChainOptions(
    WebGLFramebufferJS* fb, bool webvr,
    const webgl::SwapChainOptions& options) {
  // Currently remote texture ids should only be set internally.
  MOZ_ASSERT(!options.remoteTextureOwnerId.IsValid() &&
             !options.remoteTextureId.IsValid());
  // Async present only works when out-of-process. It is not supported in WebVR.
  // Allow it if it is either forced or if the pref is set.
  if (fb || webvr) {
    return options;
  }
  if (!IsContextLost() && !mNotLost->inProcess &&
      (options.forceAsyncPresent ||
       StaticPrefs::webgl_out_of_process_async_present())) {
    if (!mRemoteTextureOwnerId) {
      mRemoteTextureOwnerId = Some(layers::RemoteTextureOwnerId::GetNext());
    }
    mLastRemoteTextureId = Some(layers::RemoteTextureId::GetNext());
    webgl::SwapChainOptions asyncOptions = options;
    asyncOptions.remoteTextureOwnerId = *mRemoteTextureOwnerId;
    asyncOptions.remoteTextureId = *mLastRemoteTextureId;
    return asyncOptions;
  }
  // Clear the current remote texture id so that we disable async.
  mRemoteTextureOwnerId = Nothing();
  return options;
}

void ClientWebGLContext::Present(WebGLFramebufferJS* const xrFb,
                                 const layers::TextureType type,
                                 const bool webvr,
                                 const webgl::SwapChainOptions& options) {
  if (!mIsCanvasDirty && !xrFb) return;
  if (!xrFb) {
    mIsCanvasDirty = false;
  }
  CancelAutoFlush();
  webgl::SwapChainOptions asyncOptions =
      PrepareAsyncSwapChainOptions(xrFb, webvr, options);
  Run<RPROC(Present)>(xrFb ? xrFb->mId : 0, type, webvr, asyncOptions);
}

void ClientWebGLContext::CopyToSwapChain(
    WebGLFramebufferJS* const fb, const webgl::SwapChainOptions& options) {
  CancelAutoFlush();
  const auto texType = GetTexTypeForSwapChain();
  webgl::SwapChainOptions asyncOptions =
      PrepareAsyncSwapChainOptions(fb, false, options);
  Run<RPROC(CopyToSwapChain)>(fb ? fb->mId : 0, texType, asyncOptions);
}

void ClientWebGLContext::EndOfFrame() {
  CancelAutoFlush();
  Run<RPROC(EndOfFrame)>();
}

Maybe<layers::SurfaceDescriptor> ClientWebGLContext::GetFrontBuffer(
    WebGLFramebufferJS* const fb, bool vr) {
  const FuncScope funcScope(*this"");
  if (IsContextLost()) return {};

  const auto& inProcess = mNotLost->inProcess;
  if (inProcess) {
    return inProcess->GetFrontBuffer(fb ? fb->mId : 0, vr);
  }

  const auto& child = mNotLost->outOfProcess;
  child->FlushPendingCmds();

  // Always synchronously get the front buffer if not using a remote texture.
  bool needsSync = true;
  Maybe<layers::SurfaceDescriptor> syncDesc;
  Maybe<layers::SurfaceDescriptor> remoteDesc;
  auto& info = child->GetFlushedCmdInfo();

  // If valid remote texture data was set for async present, then use it.
  if (!fb && !vr && mRemoteTextureOwnerId && mLastRemoteTextureId) {
    const auto tooManyFlushes = 10;
    // If there are many flushed cmds, force synchronous IPC to avoid too many
    // pending ipc messages. Otherwise don't sync for other cases to avoid any
    // performance penalty.
    needsSync = XRE_IsParentProcess() ||
                gfx::gfxVars::WebglOopAsyncPresentForceSync() ||
                info.flushesSinceLastCongestionCheck > tooManyFlushes;

    // Only send over a remote texture descriptor if the WebGLChild actor is
    // alive to ensure the remote texture id is valid.
    if (child->CanSend()) {
      remoteDesc = Some(layers::SurfaceDescriptorRemoteTexture(
          *mLastRemoteTextureId, *mRemoteTextureOwnerId));
    }
  }

  if (needsSync &&
      !child->SendGetFrontBuffer(fb ? fb->mId : 0, vr, &syncDesc)) {
    return {};
  }

  // Reset flushesSinceLastCongestionCheck
  info.flushesSinceLastCongestionCheck = 0;
  info.congestionCheckGeneration++;

  // If there is a remote texture descriptor, use that preferentially, as the
  // sync front buffer descriptor was only created to force a sync first.
  return remoteDesc ? remoteDesc : syncDesc;
}

Maybe<layers::SurfaceDescriptor> ClientWebGLContext::PresentFrontBuffer(
    WebGLFramebufferJS* const fb, bool webvr) {
  const auto texType = GetTexTypeForSwapChain();
  Present(fb, texType, webvr);
  return GetFrontBuffer(fb, webvr);
}

already_AddRefed<layers::FwdTransactionTracker>
ClientWebGLContext::UseCompositableForwarder(
    layers::CompositableForwarder* aForwarder) {
  if (mRemoteTextureOwnerId) {
    return layers::FwdTransactionTracker::GetOrCreate(mFwdTransactionTracker);
  }
  return nullptr;
}

void ClientWebGLContext::OnDestroyChild(dom::WebGLChild* aChild) {
  // Since NotLostData may be destructing at this point, the RefPtr to
  // WebGLChild may be unreliable. Instead, it must be explicitly passed in.
  if (mRemoteTextureOwnerId && mFwdTransactionTracker &&
      mFwdTransactionTracker->IsUsed()) {
    (void)aChild->SendWaitForTxn(
        *mRemoteTextureOwnerId,
        layers::ToRemoteTextureTxnType(mFwdTransactionTracker),
        layers::ToRemoteTextureTxnId(mFwdTransactionTracker));
  }
}

void ClientWebGLContext::ClearVRSwapChain() { Run<RPROC(ClearVRSwapChain)>(); }

// -

bool ClientWebGLContext::UpdateWebRenderCanvasData(
    nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
  CanvasRenderer* renderer = aCanvasData->GetCanvasRenderer();

  if (!IsContextLost() && !mResetLayer && renderer) {
    return true;
  }

  const auto& size = DrawingBufferSize();

  if (!IsContextLost() && !renderer && mNotLost->mCanvasRenderer &&
      mNotLost->mCanvasRenderer->GetSize() == gfx::IntSize(size.x, size.y) &&
      aCanvasData->SetCanvasRenderer(mNotLost->mCanvasRenderer)) {
    mNotLost->mCanvasRenderer->SetDirty();
    mResetLayer = false;
    return true;
  }

  renderer = aCanvasData->CreateCanvasRenderer();
  if (!InitializeCanvasRenderer(aBuilder, renderer)) {
    // Clear CanvasRenderer of WebRenderCanvasData
    aCanvasData->ClearCanvasRenderer();
    return false;
  }

  mNotLost->mCanvasRenderer = renderer;

  MOZ_ASSERT(renderer);
  mResetLayer = false;

  return true;
}

bool ClientWebGLContext::InitializeCanvasRenderer(
    nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) {
  const FuncScope funcScope(*this"");
  if (IsContextLost()) return false;

  layers::CanvasRendererData data;
  data.mContext = this;
  data.mOriginPos = gl::OriginPos::BottomLeft;

  const auto& options = *mInitialOptions;
  const auto& size = DrawingBufferSize();

  if (IsContextLost()) return false;

  data.mIsOpaque = !options.alpha;
  data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
  data.mSize = {size.x, size.y};

  if (aBuilder->IsPaintingToWindow() && mCanvasElement) {
    data.mDoPaintCallbacks = true;
  }

  aRenderer->Initialize(data);
  aRenderer->SetDirty();
  return true;
}

void ClientWebGLContext::UpdateCanvasParameters() {
  if (!mOffscreenCanvas) {
    return;
  }

  const auto& options = *mInitialOptions;
  const auto& size = DrawingBufferSize();

  mozilla::dom::OffscreenCanvasDisplayData data;
  data.mOriginPos = gl::OriginPos::BottomLeft;
  data.mIsOpaque = !options.alpha;
  data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
  data.mSize = {size.x, size.y};
  data.mDoPaintCallbacks = false;

  mOffscreenCanvas->UpdateDisplayData(data);
}

layers::LayersBackend ClientWebGLContext::GetCompositorBackendType() const {
  if (mCanvasElement) {
    return mCanvasElement->GetCompositorBackendType();
  } else if (mOffscreenCanvas) {
    return mOffscreenCanvas->GetCompositorBackendType();
  }

  return layers::LayersBackend::LAYERS_NONE;
}

mozilla::dom::Document* ClientWebGLContext::GetOwnerDoc() const {
  MOZ_ASSERT(mCanvasElement);
  if (!mCanvasElement) {
    return nullptr;
  }
  return mCanvasElement->OwnerDoc();
}

void ClientWebGLContext::Commit() {
  if (mOffscreenCanvas) {
    mOffscreenCanvas->CommitFrameToCompositor();
  }
}

void ClientWebGLContext::GetCanvas(
    dom::Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& retval) {
  if (mCanvasElement) {
    MOZ_RELEASE_ASSERT(!mOffscreenCanvas, "GFX: Canvas is offscreen.");

    if (mCanvasElement->IsInNativeAnonymousSubtree()) {
      retval.SetNull();
    } else {
      retval.SetValue().SetAsHTMLCanvasElement() = mCanvasElement;
    }
  } else if (mOffscreenCanvas) {
    retval.SetValue().SetAsOffscreenCanvas() = mOffscreenCanvas;
  } else {
    retval.SetNull();
  }
}

void ClientWebGLContext::SetDrawingBufferColorSpace(
    const dom::PredefinedColorSpace val) {
  mDrawingBufferColorSpace = val;
  Run<RPROC(SetDrawingBufferColorSpace)>(*mDrawingBufferColorSpace);
}

void ClientWebGLContext::SetUnpackColorSpace(
    const dom::PredefinedColorSpace val) {
  mUnpackColorSpace = val;
  Run<RPROC(SetUnpackColorSpace)>(*mUnpackColorSpace);
}

void ClientWebGLContext::GetContextAttributes(
    dom::Nullable<dom::WebGLContextAttributes>& retval) {
  retval.SetNull();
  const FuncScope funcScope(*this"getContextAttributes");
  if (IsContextLost()) return;

  dom::WebGLContextAttributes& result = retval.SetValue();

  const auto& options = mNotLost->info.options;

  result.mAlpha.Construct(options.alpha);
  result.mDepth = options.depth;
  result.mStencil = options.stencil;
  result.mAntialias.Construct(options.antialias);
  result.mPremultipliedAlpha = options.premultipliedAlpha;
  result.mPreserveDrawingBuffer = options.preserveDrawingBuffer;
  result.mFailIfMajorPerformanceCaveat = options.failIfMajorPerformanceCaveat;
  result.mPowerPreference = options.powerPreference;
  result.mForceSoftwareRendering = options.forceSoftwareRendering;
}

// -----------------------

NS_IMETHODIMP
ClientWebGLContext::SetDimensions(const int32_t signedWidth,
                                  const int32_t signedHeight) {
  const FuncScope funcScope(*this"");
  MOZ_ASSERT(mInitialOptions);

  if (mLossStatus != webgl::LossStatus::Ready) {
    // Attempted resize of a lost context.
    return NS_OK;
  }

  uvec2 size = {static_cast<uint32_t>(signedWidth),
                static_cast<uint32_t>(signedHeight)};
  if (!size.x) {
    size.x = 1;
  }
  if (!size.y) {
    size.y = 1;
  }
  const auto prevRequestedSize = mRequestedSize;
  mRequestedSize = size;

  mResetLayer = true;  // Always treat this as resize.

  if (mNotLost) {
    auto& state = State();

    auto curSize = prevRequestedSize;
    if (state.mDrawingBufferSize) {
      curSize = *state.mDrawingBufferSize;
    }
    if (size == curSize) return NS_OK;  // MUST skip no-op resize

    state.mDrawingBufferSize = Nothing();
    Run<RPROC(Resize)>(size);

    UpdateCanvasParameters();
    MarkCanvasDirty();
    return NS_OK;
  }

  // -
  // Context (re-)creation

  if (!CreateHostContext(size)) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

void ClientWebGLContext::ResetBitmap() {
  const auto size = DrawingBufferSize();
  Run<RPROC(Resize)>(size);  // No-change resize still clears/resets everything.
}

static bool IsWebglOutOfProcessEnabled() {
  if (StaticPrefs::webgl_out_of_process_force()) {
    return true;
  }
  if (!gfx::gfxVars::AllowWebglOop()) {
    return false;
  }
  if (!NS_IsMainThread()) {
    return StaticPrefs::webgl_out_of_process_worker();
  }
  return StaticPrefs::webgl_out_of_process();
}

bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
  const auto pNotLost = std::make_shared<webgl::NotLostData>(*this);
  auto& notLost = *pNotLost;

  auto res = [&]() -> Result<Ok, std::string> {
    auto options = *mInitialOptions;
    if (StaticPrefs::webgl_disable_fail_if_major_performance_caveat()) {
      options.failIfMajorPerformanceCaveat = false;
    }

    if (options.failIfMajorPerformanceCaveat) {
      const auto backend = GetCompositorBackendType();
      bool isCompositorSlow = false;
      isCompositorSlow |= (backend == layers::LayersBackend::LAYERS_WR &&
                           gfx::gfxVars::UseSoftwareWebRender());

      if (isCompositorSlow) {
        return Err(
            "failIfMajorPerformanceCaveat: Compositor is not"
            " hardware-accelerated.");
      }
    }

    const bool resistFingerprinting =
        ShouldResistFingerprinting(RFPTarget::WebGLRenderCapability);
    const auto principalKey = GetPrincipalHashValue();
    const auto initDesc = webgl::InitContextDesc{
        .isWebgl2 = mIsWebGL2,
        .resistFingerprinting = resistFingerprinting,
        .principalKey = principalKey,
        .size = requestedSize,
        .options = options,
    };

    // -

    auto useOop = IsWebglOutOfProcessEnabled();
    if (XRE_IsParentProcess()) {
      useOop = false;
    }

    if (!useOop) {
      notLost.inProcess =
          HostWebGLContext::Create({this, nullptr}, initDesc, ¬Lost.info);
      return Ok();
    }

    // -

    ScopedGfxFeatureReporter reporter("IpcWebGL");

    autoconst cm = gfx::CanvasManagerChild::Get();
    if (NS_WARN_IF(!cm)) {
      return Err("!CanvasManagerChild::Get()");
    }

    RefPtr<dom::WebGLChild> outOfProcess = new dom::WebGLChild(*this);
    outOfProcess =
        static_cast<dom::WebGLChild*>(cm->SendPWebGLConstructor(outOfProcess));
    if (!outOfProcess) {
      return Err("SendPWebGLConstructor failed");
    }

    // Clear RemoteTextureOwnerId. HostWebGLContext is going to be replaced in
    // WebGLParent.
    if (mRemoteTextureOwnerId.isSome()) {
      mRemoteTextureOwnerId = Nothing();
      mFwdTransactionTracker = nullptr;
    }

    if (!outOfProcess->SendInitialize(initDesc, ¬Lost.info)) {
      return Err("WebGL actor Initialize failed");
    }

    notLost.outOfProcess = outOfProcess;
    reporter.SetSuccessful();
    return Ok();
  }();
  if (!res.isOk()) {
    auto str = res.unwrapErr();
    if (StartsWith(str, "failIfMajorPerformanceCaveat")) {
      str +=
          " (about:config override available:"
          " webgl.disable-fail-if-major-performance-caveat)";
    }
    notLost.info.error = str;
  }
  if (!notLost.info.error->empty()) {
    ThrowEvent_WebGLContextCreationError(notLost.info.error);
    return false;
  }
  mNotLost = pNotLost;
  UpdateCanvasParameters();
  MarkCanvasDirty();

  // Init state
  const auto& limits = Limits();
  auto& state = State();
  state.mIsEnabledMap = webgl::MakeIsEnabledMap(mIsWebGL2);

  state.mDefaultTfo = new WebGLTransformFeedbackJS(*this);
  state.mDefaultVao = new WebGLVertexArrayJS(this);

  state.mBoundTfo = state.mDefaultTfo;
  state.mBoundVao = state.mDefaultVao;

  (void)state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER];

  state.mTexUnits.resize(limits.maxTexUnits);
  state.mBoundUbos.resize(limits.maxUniformBufferBindings);

  {
    webgl::TypedQuad initVal;
    const float fData[4] = {0, 0, 0, 1};
    memcpy(initVal.data.data(), fData, initVal.data.size());
    state.mGenericVertexAttribs.resize(limits.maxVertexAttribs, initVal);
  }

  const auto& size = DrawingBufferSize();
  state.mViewport = {0, 0, static_cast<int32_t>(size.x),
                     static_cast<int32_t>(size.y)};
  state.mScissor = state.mViewport;

  if (mIsWebGL2) {
    // Insert keys to enable slots:
    (void)state.mBoundBufferByTarget[LOCAL_GL_COPY_READ_BUFFER];
    (void)state.mBoundBufferByTarget[LOCAL_GL_COPY_WRITE_BUFFER];
    (void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_PACK_BUFFER];
    (void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_UNPACK_BUFFER];
    (void)state.mBoundBufferByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER];
    (void)state.mBoundBufferByTarget[LOCAL_GL_UNIFORM_BUFFER];

    (void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED];
    //(void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE];
    //// Same slot as ANY_SAMPLES_PASSED.
    (void)state
        .mCurrentQueryByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN];
  }

  return true;
}

std::unordered_map<GLenum, bool> webgl::MakeIsEnabledMap(const bool webgl2) {
  auto ret = std::unordered_map<GLenum, bool>{};

  ret[LOCAL_GL_BLEND] = false;
  ret[LOCAL_GL_CULL_FACE] = false;
  ret[LOCAL_GL_DEPTH_TEST] = false;
  ret[LOCAL_GL_DITHER] = true;
  ret[LOCAL_GL_POLYGON_OFFSET_FILL] = false;
  ret[LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE] = false;
  ret[LOCAL_GL_SAMPLE_COVERAGE] = false;
  ret[LOCAL_GL_SCISSOR_TEST] = false;
  ret[LOCAL_GL_STENCIL_TEST] = false;

  if (webgl2) {
    ret[LOCAL_GL_RASTERIZER_DISCARD] = false;
  }

  return ret;
}

// -------

uvec2 ClientWebGLContext::DrawingBufferSize() {
  if (IsContextLost()) return {};
  const auto notLost =
      mNotLost;  // Hold a strong-ref to prevent LoseContext=>UAF.
  auto& state = State();
  auto& size = state.mDrawingBufferSize;

  if (!size) {
    const auto& inProcess = mNotLost->inProcess;
    if (inProcess) {
      size = Some(inProcess->DrawingBufferSize());
    } else {
      const auto& child = mNotLost->outOfProcess;
      child->FlushPendingCmds();
      uvec2 actual = {};
      if (!child->SendDrawingBufferSize(&actual)) return {};
      size = Some(actual);
    }
  }

  return *size;
}

void ClientWebGLContext::OnMemoryPressure() {
  if (IsContextLost()) return;

  const auto& inProcess = mNotLost->inProcess;
  if (inProcess) {
    return inProcess->OnMemoryPressure();
  }
  const auto& child = mNotLost->outOfProcess;
  (void)child->SendOnMemoryPressure();
}

NS_IMETHODIMP
ClientWebGLContext::SetContextOptions(JSContext* cx,
                                      JS::Handle<JS::Value> options,
                                      ErrorResult& aRvForDictionaryInit) {
  if (mInitialOptions && options.isNullOrUndefined()) return NS_OK;

  dom::WebGLContextAttributes attributes;
  if (!attributes.Init(cx, options)) {
    aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED);
    return NS_ERROR_UNEXPECTED;
  }

  WebGLContextOptions newOpts;

  newOpts.stencil = attributes.mStencil;
  newOpts.depth = attributes.mDepth;
  newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha;
  newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer;
  newOpts.failIfMajorPerformanceCaveat =
      attributes.mFailIfMajorPerformanceCaveat;
  newOpts.xrCompatible = attributes.mXrCompatible;
  newOpts.powerPreference = attributes.mPowerPreference;
  newOpts.forceSoftwareRendering = attributes.mForceSoftwareRendering;
  newOpts.enableDebugRendererInfo =
      StaticPrefs::webgl_enable_debug_renderer_info();
  MOZ_ASSERT(mCanvasElement || mOffscreenCanvas);
  newOpts.shouldResistFingerprinting =
      ShouldResistFingerprinting(RFPTarget::WebGLRenderCapability);

  if (attributes.mAlpha.WasPassed()) {
    newOpts.alpha = attributes.mAlpha.Value();
  }
  if (attributes.mAntialias.WasPassed()) {
    newOpts.antialias = attributes.mAntialias.Value();
  }

  // Don't do antialiasing if we've disabled MSAA.
  if (!StaticPrefs::webgl_msaa_samples()) {
    newOpts.antialias = false;
  }

  // -

  if (mInitialOptions && *mInitialOptions != newOpts) {
    // Err if the options asked for aren't the same as what they were
    // originally.
    return NS_ERROR_FAILURE;
  }

  mXRCompatible = attributes.mXrCompatible;

  mInitialOptions.emplace(newOpts);
  return NS_OK;
}

void ClientWebGLContext::DidRefresh() { Run<RPROC(DidRefresh)>(); }

already_AddRefed<gfx::SourceSurface> ClientWebGLContext::GetSurfaceSnapshot(
    gfxAlphaType* const out_alphaType) {
  const FuncScope funcScope(*this"");
  if (IsContextLost()) return nullptr;
  const auto notLost =
      mNotLost;  // Hold a strong-ref to prevent LoseContext=>UAF.

  auto ret = BackBufferSnapshot();
  if (!ret) return nullptr;

  // -

  const auto& options = mNotLost->info.options;

  auto srcAlphaType = gfxAlphaType::Opaque;
  if (options.alpha) {
    if (options.premultipliedAlpha) {
      srcAlphaType = gfxAlphaType::Premult;
    } else {
      srcAlphaType = gfxAlphaType::NonPremult;
    }
  }

  if (out_alphaType) {
    *out_alphaType = srcAlphaType;
  } else {
    // Expects Opaque or Premult
    if (srcAlphaType == gfxAlphaType::NonPremult) {
      const gfx::DataSourceSurface::ScopedMap map(
          ret, gfx::DataSourceSurface::READ_WRITE);
      MOZ_RELEASE_ASSERT(map.IsMapped(), "Failed to map snapshot surface!");

      const auto& size = ret->GetSize();
      const auto format = ret->GetFormat();
      bool rv =
          gfx::PremultiplyData(map.GetData(), map.GetStride(), format,
                               map.GetData(), map.GetStride(), format, size);
      MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
    }
  }

  return ret.forget();
}

RefPtr<gfx::SourceSurface> ClientWebGLContext::GetFrontBufferSnapshot(
    const bool requireAlphaPremult) {
  const FuncScope funcScope(*this"");
  if (IsContextLost()) return nullptr;
  const auto notLost =
      mNotLost;  // Hold a strong-ref to prevent LoseContext=>UAF.

  const auto& options = mNotLost->info.options;

  const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
                                        : gfx::SurfaceFormat::B8G8R8X8;

  const auto fnNewSurf = [&](const uvec2 size) {
    const auto stride = size.x * 4;
    return RefPtr<gfx::DataSourceSurface>(
        gfx::Factory::CreateDataSourceSurfaceWithStride({size.x, size.y},
                                                        surfFormat, stride,
                                                        /*zero=*/true));
  };

  const auto& inProcess = mNotLost->inProcess;
  if (inProcess) {
    const auto maybeSize = inProcess->FrontBufferSnapshotInto({});
    if (!maybeSize) return nullptr;
    const auto& surfSize = *maybeSize;
    const auto stride = surfSize.x * 4;
    const auto byteSize = stride * surfSize.y;
    const auto surf = fnNewSurf(surfSize);
    if (!surf) return nullptr;
    {
      const gfx::DataSourceSurface::ScopedMap map(
          surf, gfx::DataSourceSurface::READ_WRITE);
      if (!map.IsMapped()) {
        MOZ_ASSERT(false);
        return nullptr;
      }
      MOZ_RELEASE_ASSERT(map.GetStride() == static_cast<int64_t>(stride));
      auto range = Range<uint8_t>{map.GetData(), byteSize};
      if (!inProcess->FrontBufferSnapshotInto(Some(range))) {
        gfxCriticalNote << "ClientWebGLContext::GetFrontBufferSnapshot: "
                           "FrontBufferSnapshotInto(some) failed after "
                           "FrontBufferSnapshotInto(none)";
        return nullptr;
      }
      if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
        bool rv = gfx::PremultiplyData(
            map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
            map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
            surf->GetSize());
        MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
      } else {
        bool rv = gfx::SwizzleData(
            map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
            map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
            surf->GetSize());
        MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
      }
    }
    return surf;
  }
  const auto& child = mNotLost->outOfProcess;
  child->FlushPendingCmds();
  webgl::FrontBufferSnapshotIpc res;
  if (!child->SendGetFrontBufferSnapshot(&res)) {
    res = {};
  }
  if (!res.shmem) return nullptr;

  const auto& surfSize = res.surfSize;
  const webgl::RaiiShmem shmem{child, res.shmem.ref()};
  if (!shmem) return nullptr;
  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;

  {
    const gfx::DataSourceSurface::ScopedMap map(
        surf, gfx::DataSourceSurface::READ_WRITE);
    if (!map.IsMapped()) {
      MOZ_ASSERT(false);
      return nullptr;
    }
    MOZ_RELEASE_ASSERT(shmemBytes.length() == byteSize);
    if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
      bool rv = gfx::PremultiplyData(
          shmemBytes.begin().get(), stride, gfx::SurfaceFormat::R8G8B8A8,
          map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
          surf->GetSize());
      MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
    } else {
      bool rv = gfx::SwizzleData(shmemBytes.begin().get(), stride,
                                 gfx::SurfaceFormat::R8G8B8A8, map.GetData(),
                                 map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
                                 surf->GetSize());
      MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
    }
  }
  return surf;
}

RefPtr<gfx::DataSourceSurface> ClientWebGLContext::BackBufferSnapshot() {
  if (IsContextLost()) return nullptr;
  const auto notLost =
      mNotLost;  // Hold a strong-ref to prevent LoseContext=>UAF.

  const auto& options = mNotLost->info.options;
  const auto& state = State();

  const auto drawFbWas = state.mBoundDrawFb;
  const auto readFbWas = state.mBoundReadFb;
  const auto pboWas =
      Find(state.mBoundBufferByTarget, LOCAL_GL_PIXEL_PACK_BUFFER);

  const auto size = DrawingBufferSize();

  // -

  BindFramebuffer(LOCAL_GL_FRAMEBUFFER, nullptr);
  if (pboWas) {
    BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
  }

  auto reset = MakeScopeExit([&] {
    if (drawFbWas == readFbWas) {
      BindFramebuffer(LOCAL_GL_FRAMEBUFFER, drawFbWas);
    } else {
      BindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, drawFbWas);
      BindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, readFbWas);
    }
    if (pboWas) {
      BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
    }
  });

  const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
                                        : gfx::SurfaceFormat::B8G8R8X8;
  const auto stride = size.x * 4;
  RefPtr<gfx::DataSourceSurface> surf =
      gfx::Factory::CreateDataSourceSurfaceWithStride(
          {size.x, size.y}, surfFormat, stride, /*zero=*/true);
  if (NS_WARN_IF(!surf)) {
    // Was this an OOM or alloc-limit? (500MB is our default resource size
    // limit)
    surf = gfx::Factory::CreateDataSourceSurfaceWithStride({1, 1}, surfFormat,
                                                           4, /*zero=*/true);
    if (!surf) {
      // Still failed for a 1x1 size.
      gfxCriticalError() << "CreateDataSourceSurfaceWithStride(surfFormat="
                         << surfFormat << ") failed.";
    }
    return nullptr;
  }

  {
    const gfx::DataSourceSurface::ScopedMap map(
        surf, gfx::DataSourceSurface::READ_WRITE);
    if (!map.IsMapped()) {
      MOZ_ASSERT(false);
      return nullptr;
    }
    MOZ_ASSERT(static_cast<uint32_t>(map.GetStride()) == stride);

    const auto desc = webgl::ReadPixelsDesc{{0, 0}, size};
    const auto pixels = Span<uint8_t>(map.GetData(), stride * size.y);
    if (!DoReadPixels(desc, pixels)) return nullptr;

    // RGBA->BGRA and flip-y.
    MOZ_RELEASE_ASSERT(gfx::SwizzleYFlipData(
        pixels.data(), stride, gfx::SurfaceFormat::R8G8B8A8, pixels.data(),
        stride, gfx::SurfaceFormat::B8G8R8A8, {size.x, size.y}));
  }

  return surf;
}

UniquePtr<uint8_t[]> ClientWebGLContext::GetImageBuffer(
    int32_t* out_format, gfx::IntSize* out_imageSize) {
  *out_format = 0;
  *out_imageSize = {};

  // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
  gfxAlphaType any;
  RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
  if (!snapshot) return nullptr;

  RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();

  const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;
  *out_imageSize = dataSurface->GetSize();

  if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
    return gfxUtils::GetImageBufferWithRandomNoise(
        dataSurface, premultAlpha, GetCookieJarSettings(), out_format);
  }

  return gfxUtils::GetImageBuffer(dataSurface, premultAlpha, out_format);
}

NS_IMETHODIMP
ClientWebGLContext::GetInputStream(const char* mimeType,
                                   const nsAString& encoderOptions,
                                   nsIInputStream** out_stream) {
  // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
  gfxAlphaType any;
  RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
  if (!snapshot) return NS_ERROR_FAILURE;

  RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
  const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;

  if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
    return gfxUtils::GetInputStreamWithRandomNoise(
        dataSurface, premultAlpha, mimeType, encoderOptions,
        GetCookieJarSettings(), out_stream);
  }

  return gfxUtils::GetInputStream(dataSurface, premultAlpha, mimeType,
                                  encoderOptions, out_stream);
}

// ------------------------- Client WebGL Objects -------------------------
// ------------------------- Create/Destroy/Is -------------------------

template <typename T>
static already_AddRefed<T> AsAddRefed(T* ptr) {
  RefPtr<T> rp = ptr;
  return rp.forget();
}

template <typename T>
static RefPtr<T> AsRefPtr(T* ptr) {
  return {ptr};
}

already_AddRefed<WebGLBufferJS> ClientWebGLContext::CreateBuffer() const {
  const FuncScope funcScope(*this"createBuffer");

  auto ret = AsRefPtr(new WebGLBufferJS(*this));
  Run<RPROC(CreateBuffer)>(ret->mId);
  return ret.forget();
}

already_AddRefed<WebGLFramebufferJS> ClientWebGLContext::CreateFramebuffer()
    const {
  const FuncScope funcScope(*this"createFramebuffer");

  auto ret = AsRefPtr(new WebGLFramebufferJS(*this));
  Run<RPROC(CreateFramebuffer)>(ret->mId);
  return ret.forget();
}

already_AddRefed<WebGLFramebufferJS>
ClientWebGLContext::CreateOpaqueFramebuffer(
    const webgl::OpaqueFramebufferOptions& options) const {
  const FuncScope funcScope(*this"createOpaqueFramebuffer");

  auto ret = AsRefPtr(new WebGLFramebufferJS(*thistrue));

  if (mNotLost) {
    const auto& inProcess = mNotLost->inProcess;
    if (inProcess) {
      if (!inProcess->CreateOpaqueFramebuffer(ret->mId, options)) {
        ret = nullptr;
      }
      return ret.forget();
    }
    const auto& child = mNotLost->outOfProcess;
    child->FlushPendingCmds();
    bool ok = false;
    if (!child->SendCreateOpaqueFramebuffer(ret->mId, options, &ok))
      return nullptr;
    if (!ok) return nullptr;
  }

  return ret.forget();
}

already_AddRefed<WebGLProgramJS> ClientWebGLContext::CreateProgram() const {
  const FuncScope funcScope(*this"createProgram");

  auto ret = AsRefPtr(new WebGLProgramJS(*this));
  Run<RPROC(CreateProgram)>(ret->mId);
  return ret.forget();
}

already_AddRefed<WebGLQueryJS> ClientWebGLContext::CreateQuery() const {
  const FuncScope funcScope(*this"createQuery");

  auto ret = AsRefPtr(new WebGLQueryJS(this));
  Run<RPROC(CreateQuery)>(ret->mId);
  return ret.forget();
}

already_AddRefed<WebGLRenderbufferJS> ClientWebGLContext::CreateRenderbuffer()
    const {
  const FuncScope funcScope(*this"createRenderbuffer");

  auto ret = AsRefPtr(new WebGLRenderbufferJS(*this));
  Run<RPROC(CreateRenderbuffer)>(ret->mId);
  return ret.forget();
}

already_AddRefed<WebGLSamplerJS> ClientWebGLContext::CreateSampler() const {
  const FuncScope funcScope(*this"createSampler");

  auto ret = AsRefPtr(new WebGLSamplerJS(*this));
  Run<RPROC(CreateSampler)>(ret->mId);
  return ret.forget();
}

already_AddRefed<WebGLShaderJS> ClientWebGLContext::CreateShader(
    const GLenum type) const {
  const FuncScope funcScope(*this"createShader");

  switch (type) {
    case LOCAL_GL_VERTEX_SHADER:
    case LOCAL_GL_FRAGMENT_SHADER:
      break;
    default:
      EnqueueError_ArgEnum("type", type);
      return nullptr;
  }

  auto ret = AsRefPtr(new WebGLShaderJS(*this, type));
  Run<RPROC(CreateShader)>(ret->mId, ret->mType);
  return ret.forget();
}

already_AddRefed<WebGLSyncJS> ClientWebGLContext::FenceSync(
    const GLenum condition, const GLbitfield flags) const {
  const FuncScope funcScope(*this"fenceSync");

  if (condition != LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE) {
    EnqueueError_ArgEnum("condition", condition);
    return nullptr;
  }

  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;

  AutoEnqueueFlush();

  return ret.forget();
}

already_AddRefed<WebGLTextureJS> ClientWebGLContext::CreateTexture() const {
  const FuncScope funcScope(*this"createTexture");

  auto ret = AsRefPtr(new WebGLTextureJS(*this));
  Run<RPROC(CreateTexture)>(ret->mId);
  return ret.forget();
}

already_AddRefed<WebGLTransformFeedbackJS>
ClientWebGLContext::CreateTransformFeedback() const {
  const FuncScope funcScope(*this"createTransformFeedback");

  auto ret = AsRefPtr(new WebGLTransformFeedbackJS(*this));
  Run<RPROC(CreateTransformFeedback)>(ret->mId);
  return ret.forget();
}

already_AddRefed<WebGLVertexArrayJS> ClientWebGLContext::CreateVertexArray()
    const {
  const FuncScope funcScope(*this"createVertexArray");

  auto ret = AsRefPtr(new WebGLVertexArrayJS(this));
  Run<RPROC(CreateVertexArray)>(ret->mId);
  return ret.forget();
}

// -

static bool ValidateOrSkipForDelete(const ClientWebGLContext& context,
                                    const webgl::ObjectJS* const obj) {
  if (!obj) return false;
  if (!obj->ValidateForContext(context, "obj")) return false;
  if (obj->IsDeleted()) return false;
  return true;
}

void ClientWebGLContext::DeleteBuffer(WebGLBufferJS* const obj) {
  const FuncScope funcScope(*this"deleteBuffer");
  if (IsContextLost()) return;
  if (!ValidateOrSkipForDelete(*this, obj)) return;
  auto& state = State();

  // Unbind from all bind points and bound containers

  // UBOs
  for (const auto i : IntegerRange(state.mBoundUbos.size())) {
    if (state.mBoundUbos[i] == obj) {
      BindBufferBase(LOCAL_GL_UNIFORM_BUFFER, i, nullptr);
    }
  }

  // TFO only if not active
  if (!state.mBoundTfo->mActiveOrPaused) {
    const auto& buffers = state.mBoundTfo->mAttribBuffers;
    for (const auto i : IntegerRange(buffers.size())) {
      if (buffers[i] == obj) {
        BindBufferBase(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, i, nullptr);
      }
    }
  }

  // Generic/global bind points
  for (const auto& pair : state.mBoundBufferByTarget) {
    if (pair.second == obj) {
      BindBuffer(pair.first, nullptr);
    }
  }

  // VAO attachments
  if (state.mBoundVao->mIndexBuffer == obj) {
    BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, nullptr);
  }

  const auto& vaoBuffers = state.mBoundVao->mAttribBuffers;
  Maybe<WebGLBufferJS*> toRestore;
  for (const auto i : IntegerRange(vaoBuffers.size())) {
    if (vaoBuffers[i] == obj) {
      if (!toRestore) {
        toRestore =
            Some(state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER].get());
        if (*toRestore) {
          BindBuffer(LOCAL_GL_ARRAY_BUFFER, nullptr);
        }
      }
      VertexAttribPointer(i, 4, LOCAL_GL_FLOAT, false, 0, 0);
    }
  }
  if (toRestore && *toRestore) {
    BindBuffer(LOCAL_GL_ARRAY_BUFFER, *toRestore);
  }

  // -

  obj->mDeleteRequested = true;
  Run<RPROC(DeleteBuffer)>(obj->mId);
}

void ClientWebGLContext::DeleteFramebuffer(WebGLFramebufferJS* const obj,
                                           bool canDeleteOpaque) {
  const FuncScope funcScope(*this"deleteFramebuffer");
  if (IsContextLost()) return;
  if (!ValidateOrSkipForDelete(*this, obj)) return;
  if (!canDeleteOpaque && obj->mOpaque) {
    EnqueueError(
        LOCAL_GL_INVALID_OPERATION,
        "An opaque framebuffer's attachments cannot be inspected or changed.");
    return;
  }
  const auto& state = State();

  // Unbind
  const auto fnDetach = [&](const GLenum target,
                            const WebGLFramebufferJS* const fb) {
    if (obj == fb) {
      BindFramebuffer(target, nullptr);
    }
  };
  if (state.mBoundDrawFb == state.mBoundReadFb) {
    fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
  } else {
    fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
    fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
  }

  obj->mDeleteRequested = true;
  Run<RPROC(DeleteFramebuffer)>(obj->mId);
}

void ClientWebGLContext::DeleteProgram(WebGLProgramJS* const obj) const {
  const FuncScope funcScope(*this"deleteProgram");
  if (IsContextLost()) return;
  if (!ValidateOrSkipForDelete(*this, obj)) return;

  // Don't unbind

  obj->mKeepAlive = nullptr;
}

webgl::ProgramKeepAlive::~ProgramKeepAlive() {
  if (!mParent) return;
  const auto& context = mParent->Context();
  if (!context) return;
  context->DoDeleteProgram(*mParent);
}

void ClientWebGLContext::DoDeleteProgram(WebGLProgramJS& obj) const {
  obj.mNextLink_Shaders = {};
  Run<RPROC(DeleteProgram)>(obj.mId);
}

static GLenum QuerySlotTarget(const GLenum specificTarget);

void ClientWebGLContext::DeleteQuery(WebGLQueryJS* const obj) {
  const FuncScope funcScope(*this"deleteQuery");
  if (IsContextLost()) return;
  if (!ValidateOrSkipForDelete(*this, obj)) return;
  const auto& state = State();

  // Unbind if current

  if (obj->mTarget) {
    // Despite mTarget being set, we may not have called BeginQuery on this
    // object. QueryCounter may also set mTarget.
    const auto slotTarget = QuerySlotTarget(obj->mTarget);
    const auto curForTarget =
        MaybeFind(state.mCurrentQueryByTarget, slotTarget);

    if (curForTarget && *curForTarget == obj) {
      EndQuery(obj->mTarget);
    }
  }

  obj->mDeleteRequested = true;
  Run<RPROC(DeleteQuery)>(obj->mId);
}

void ClientWebGLContext::DeleteRenderbuffer(WebGLRenderbufferJS* const obj) {
  const FuncScope funcScope(*this"deleteRenderbuffer");
  if (IsContextLost()) return;
  if (!ValidateOrSkipForDelete(*this, obj)) return;
  const auto& state = State();

  // Unbind
  if (state.mBoundRb == obj) {
    BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nullptr);
  }

  // Unbind from bound FBs
  const auto fnDetach = [&](const GLenum target,
                            const WebGLFramebufferJS* const fb) {
    if (!fb) return;
    for (const auto& pair : fb->mAttachments) {
      if (pair.second.rb == obj) {
        FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
                                nullptr);
      }
    }
  };
  if (state.mBoundDrawFb == state.mBoundReadFb) {
    fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
  } else {
    fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
    fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
  }

  obj->mDeleteRequested = true;
  Run<RPROC(DeleteRenderbuffer)>(obj->mId);
}

void ClientWebGLContext::DeleteSampler(WebGLSamplerJS* const obj) {
  const FuncScope funcScope(*this"deleteSampler");
  if (IsContextLost()) return;
  if (!ValidateOrSkipForDelete(*this, obj)) return;
  const auto& state = State();

  // Unbind
  for (const auto i : IntegerRange(state.mTexUnits.size())) {
    if (state.mTexUnits[i].sampler == obj) {
      BindSampler(i, nullptr);
    }
  }

  obj->mDeleteRequested = true;
  Run<RPROC(DeleteSampler)>(obj->mId);
}

void ClientWebGLContext::DeleteShader(WebGLShaderJS* const obj) const {
  const FuncScope funcScope(*this"deleteShader");
  if (IsContextLost()) return;
  if (!ValidateOrSkipForDelete(*this, obj)) return;

  // Don't unbind

  obj->mKeepAlive = nullptr;
}

webgl::ShaderKeepAlive::~ShaderKeepAlive() {
  if (!mParent) return;
  const auto& context = mParent->Context();
  if (!context) return;
  context->DoDeleteShader(*mParent);
}

void ClientWebGLContext::DoDeleteShader(const WebGLShaderJS& obj) const {
  Run<RPROC(DeleteShader)>(obj.mId);
}

void ClientWebGLContext::DeleteSync(WebGLSyncJS* const obj) const {
  const FuncScope funcScope(*this"deleteSync");
  if (IsContextLost()) return;
  if (!ValidateOrSkipForDelete(*this, obj)) return;

  // Nothing to unbind

  obj->mDeleteRequested = true;
  Run<RPROC(DeleteSync)>(obj->mId);
}

void ClientWebGLContext::DeleteTexture(WebGLTextureJS* const obj) {
  const FuncScope funcScope(*this"deleteTexture");
  if (IsContextLost()) return;
  if (!ValidateOrSkipForDelete(*this, obj)) return;
  auto& state = State();

  // Unbind
  const auto& target = obj->mTarget;
  if (target) {
    // Unbind from tex units
    Maybe<uint32_t> restoreTexUnit;
    for (const auto i : IntegerRange(state.mTexUnits.size())) {
      if (state.mTexUnits[i].texByTarget[target] == obj) {
        if (!restoreTexUnit) {
          restoreTexUnit = Some(state.mActiveTexUnit);
        }
        ActiveTexture(LOCAL_GL_TEXTURE0 + i);
        BindTexture(target, nullptr);
      }
    }
    if (restoreTexUnit) {
      ActiveTexture(LOCAL_GL_TEXTURE0 + *restoreTexUnit);
    }

    // Unbind from bound FBs
    const auto fnDetach = [&](const GLenum target,
                              const WebGLFramebufferJS* const fb) {
      if (!fb) return;
      for (const auto& pair : fb->mAttachments) {
        if (pair.second.tex == obj) {
          FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
                                  nullptr);
        }
      }
    };
    if (state.mBoundDrawFb == state.mBoundReadFb) {
      fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
    } else {
      fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
      fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
    }
  }

  obj->mDeleteRequested = true;
  Run<RPROC(DeleteTexture)>(obj->mId);
}

void ClientWebGLContext::DeleteTransformFeedback(
    WebGLTransformFeedbackJS* const obj) {
  const FuncScope funcScope(*this"deleteTransformFeedback");
  if (IsContextLost()) return;
  if (!ValidateOrSkipForDelete(*this, obj)) return;
  const auto& state = State();

  if (obj->mActiveOrPaused) {
    EnqueueError(LOCAL_GL_INVALID_OPERATION,
                 "Transform Feedback object still active or paused.");
    return;
  }

  // Unbind
  if (state.mBoundTfo == obj) {
    BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
  }

  obj->mDeleteRequested = true;
  Run<RPROC(DeleteTransformFeedback)>(obj->mId);
}

void ClientWebGLContext::DeleteVertexArray(WebGLVertexArrayJS* const obj) {
  const FuncScope funcScope(*this"deleteVertexArray");
  if (IsContextLost()) return;
  if (!ValidateOrSkipForDelete(*this, obj)) return;
  const auto& state = State();

  // Unbind
  if (state.mBoundVao == obj) {
    BindVertexArray(nullptr);
  }

  obj->mDeleteRequested = true;
  Run<RPROC(DeleteVertexArray)>(obj->mId);
}

// -

bool ClientWebGLContext::IsBuffer(const WebGLBufferJS* const obj) const {
  const FuncScope funcScope(*this"isBuffer");
  if (IsContextLost()) return false;

  return obj && obj->IsUsable(*this) &&
         obj->mKind != webgl::BufferKind::Undefined;
}

bool ClientWebGLContext::IsFramebuffer(
    const WebGLFramebufferJS* const obj) const {
  const FuncScope funcScope(*this"isFramebuffer");
  if (IsContextLost()) return false;

  return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
}

bool ClientWebGLContext::IsProgram(const WebGLProgramJS* const obj) const {
  const FuncScope funcScope(*this"isProgram");
  if (IsContextLost()) return false;

  return obj && obj->IsUsable(*this);
}

bool ClientWebGLContext::IsQuery(const WebGLQueryJS* const obj) const {
  const FuncScope funcScope(*this"isQuery");
  if (IsContextLost()) return false;

  return obj && obj->IsUsable(*this) && obj->mTarget;
}

bool ClientWebGLContext::IsRenderbuffer(
    const WebGLRenderbufferJS* const obj) const {
  const FuncScope funcScope(*this"isRenderbuffer");
  if (IsContextLost()) return false;

  return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
}

bool ClientWebGLContext::IsSampler(const WebGLSamplerJS* const obj) const {
  const FuncScope funcScope(*this"isSampler");
  if (IsContextLost()) return false;

  return obj && obj->IsUsable(*this);
}

bool ClientWebGLContext::IsShader(const WebGLShaderJS* const obj) const {
  const FuncScope funcScope(*this"isShader");
  if (IsContextLost()) return false;

  return obj && obj->IsUsable(*this);
}

bool ClientWebGLContext::IsSync(const WebGLSyncJS* const obj) const {
  const FuncScope funcScope(*this"isSync");
  if (IsContextLost()) return false;

  return obj && obj->IsUsable(*this);
}

bool ClientWebGLContext::IsTexture(const WebGLTextureJS* const obj) const {
  const FuncScope funcScope(*this"isTexture");
  if (IsContextLost()) return false;

  return obj && obj->IsUsable(*this) && obj->mTarget;
}

bool ClientWebGLContext::IsTransformFeedback(
    const WebGLTransformFeedbackJS* const obj) const {
  const FuncScope funcScope(*this"isTransformFeedback");
  if (IsContextLost()) return false;

  return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
}

bool ClientWebGLContext::IsVertexArray(
    const WebGLVertexArrayJS* const obj) const {
  const FuncScope funcScope(*this"isVertexArray");
  if (IsContextLost()) return false;

  return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
}

// ------------------------- GL State -------------------------

void ClientWebGLContext::SetEnabledI(const GLenum cap, const Maybe<GLuint> i,
                                     const bool val) const {
  const FuncScope funcScope(*this"enable/disable");
  if (IsContextLost()) return;

  auto& map = mNotLost->state.mIsEnabledMap;
  auto slot = MaybeFind(map, cap);
  if (i && cap != LOCAL_GL_BLEND) {
    slot = nullptr;
  }
  if (!slot) {
    EnqueueError_ArgEnum("cap", cap);
    return;
  }

  Run<RPROC(SetEnabled)>(cap, i, val);

  if (!i || *i == 0) {
    *slot = val;
  }
}

bool ClientWebGLContext::IsEnabled(const GLenum cap) const {
  const FuncScope funcScope(*this"isEnabled");
  if (IsContextLost()) return false;

  const auto& map = mNotLost->state.mIsEnabledMap;
  const auto slot = MaybeFind(map, cap);
  if (!slot) {
    EnqueueError_ArgEnum("cap", cap);
    return false;
  }

  return *slot;
}

template <typename T, typename S>
static JS::Value Create(JSContext* cx, nsWrapperCache* creator, const S& src,
                        ErrorResult& rv) {
  return JS::ObjectOrNullValue(T::Create(cx, creator, src, rv));
}

void ClientWebGLContext::GetInternalformatParameter(
    JSContext* cx, GLenum target, GLenum internalformat, GLenum pname,
    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<int32_t>> maybe;
  if (inProcessContext) {
    maybe = inProcessContext->GetInternalformatParameter(target, internalformat,
                                                         pname);
  } else {
    const auto& child = notLost->outOfProcess;
    child->FlushPendingCmds();
    if (!child->SendGetInternalformatParameter(target, internalformat, pname,
                                               &maybe)) {
      return;
    }
  }

  if (!maybe) {
    return;
  }

  retval.set(Create<dom::Int32Array>(cx, this, *maybe, rv));
}

static JS::Value StringValue(JSContext* cx, const std::string& str,
                             ErrorResult& er) {
  JSString* jsStr = JS_NewStringCopyN(cx, str.data(), str.size());
  if (!jsStr) {
    er.Throw(NS_ERROR_OUT_OF_MEMORY);
    return JS::NullValue();
  }

  return JS::StringValue(jsStr);
}

template <typename T>
bool ToJSValueOrNull(JSContext* const cx, const RefPtr<T>& ptr,
                     JS::MutableHandle<JS::Value> retval) {
  if (!ptr) {
    retval.set(JS::NullValue());
    return true;
  }
  return dom::ToJSValue(cx, ptr, retval);
}

Maybe<double> ClientWebGLContext::GetNumber(const GLenum pname) {
  MOZ_ASSERT(!IsContextLost());

  const auto& inProcess = mNotLost->inProcess;
  if (inProcess) {
    return inProcess->GetNumber(pname);
  }

  const auto& child = mNotLost->outOfProcess;
  child->FlushPendingCmds();

  Maybe<double> ret;
  if (!child->SendGetNumber(pname, &ret)) {
    ret.reset();
  }
  return ret;
}

Maybe<std::string> ClientWebGLContext::GetString(const GLenum pname) {
  MOZ_ASSERT(!IsContextLost());

  const auto& inProcess = mNotLost->inProcess;
  if (inProcess) {
    return inProcess->GetString(pname);
  }

  const auto& child = mNotLost->outOfProcess;
  child->FlushPendingCmds();

  Maybe<std::string> ret;
  if (!child->SendGetString(pname, &ret)) {
    ret.reset();
  }
  return ret;
}

void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
                                      JS::MutableHandle<JS::Value> retval,
                                      ErrorResult& rv, const bool debug) {
  retval.set(JS::NullValue());
  const FuncScope funcScope(*this"getParameter");
  if (IsContextLost()) return;
  const auto& limits = Limits();
  const auto& state = State();

  // -

  const auto fnSetRetval_Buffer = [&](const GLenum target) {
    const auto buffer = *MaybeFind(state.mBoundBufferByTarget, target);
    (void)ToJSValueOrNull(cx, buffer, retval);
  };
  const auto fnSetRetval_Tex = [&](const GLenum texTarget) {
    const auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
    const auto tex = Find(texUnit.texByTarget, texTarget, nullptr);
    (void)ToJSValueOrNull(cx, tex, retval);
  };

  switch (pname) {
    case LOCAL_GL_ARRAY_BUFFER_BINDING:
      fnSetRetval_Buffer(LOCAL_GL_ARRAY_BUFFER);
      return;

    case LOCAL_GL_CURRENT_PROGRAM:
      (void)ToJSValueOrNull(cx, state.mCurrentProgram, retval);
      return;

    case LOCAL_GL_ELEMENT_ARRAY_BUFFER_BINDING:
      (void)ToJSValueOrNull(cx, state.mBoundVao->mIndexBuffer, retval);
      return;

    case LOCAL_GL_FRAMEBUFFER_BINDING:
      (void)ToJSValueOrNull(cx, state.mBoundDrawFb, retval);
      return;

    case LOCAL_GL_RENDERBUFFER_BINDING:
      (void)ToJSValueOrNull(cx, state.mBoundRb, retval);
      return;

    case LOCAL_GL_TEXTURE_BINDING_2D:
      fnSetRetval_Tex(LOCAL_GL_TEXTURE_2D);
      return;

    case LOCAL_GL_TEXTURE_BINDING_CUBE_MAP:
      fnSetRetval_Tex(LOCAL_GL_TEXTURE_CUBE_MAP);
      return;

    case LOCAL_GL_VERTEX_ARRAY_BINDING: {
      if (!mIsWebGL2 &&
          !IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object))
        break;

      auto ret = state.mBoundVao;
      if (ret == state.mDefaultVao) {
        ret = nullptr;
      }
      (void)ToJSValueOrNull(cx, ret, retval);
      return;
    }

    case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
      retval.set(JS::NumberValue(limits.maxTexUnits));
      return;
    case LOCAL_GL_MAX_TEXTURE_SIZE:
      retval.set(JS::NumberValue(limits.maxTex2dSize));
      return;
    case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE:
      retval.set(JS::NumberValue(limits.maxTexCubeSize));
      return;
    case LOCAL_GL_MAX_VERTEX_ATTRIBS:
      retval.set(JS::NumberValue(limits.maxVertexAttribs));
      return;

    case LOCAL_GL_MAX_VIEWS_OVR:
      if (IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) {
        retval.set(JS::NumberValue(limits.maxMultiviewLayers));
        return;
      }
      break;

    case LOCAL_GL_PACK_ALIGNMENT:
      retval.set(JS::NumberValue(state.mPixelPackState.alignmentInTypeElems));
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=98 G=94

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.