/* 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 "ClientWebGLExtensions.h"
#include "GLContext.h"
#include "WebGLContext.h"
#include "WebGLContextUtils.h"
#include "WebGLExtensions.h"
#include "mozilla/EnumeratedRange.h"
#include "mozilla/StaticPrefs_webgl.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/ToJSValue.h"
#include "nsString.h"

namespace mozilla {

const char* GetExtensionName(const WebGLExtensionID ext) {
  switch (ext) {
#define WEBGL_EXTENSION_IDENTIFIER(x) \
  case WebGLExtensionID::x:           \
    return #x;

    WEBGL_EXTENSION_IDENTIFIER(ANGLE_instanced_arrays)
    WEBGL_EXTENSION_IDENTIFIER(EXT_blend_minmax)
    WEBGL_EXTENSION_IDENTIFIER(EXT_color_buffer_float)
    WEBGL_EXTENSION_IDENTIFIER(EXT_color_buffer_half_float)
    WEBGL_EXTENSION_IDENTIFIER(EXT_depth_clamp)
    WEBGL_EXTENSION_IDENTIFIER(EXT_disjoint_timer_query)
    WEBGL_EXTENSION_IDENTIFIER(EXT_float_blend)
    WEBGL_EXTENSION_IDENTIFIER(EXT_frag_depth)
    WEBGL_EXTENSION_IDENTIFIER(EXT_shader_texture_lod)
    WEBGL_EXTENSION_IDENTIFIER(EXT_sRGB)
    WEBGL_EXTENSION_IDENTIFIER(EXT_texture_compression_bptc)
    WEBGL_EXTENSION_IDENTIFIER(EXT_texture_compression_rgtc)
    WEBGL_EXTENSION_IDENTIFIER(EXT_texture_filter_anisotropic)
    WEBGL_EXTENSION_IDENTIFIER(EXT_texture_norm16)
    WEBGL_EXTENSION_IDENTIFIER(MOZ_debug)
    WEBGL_EXTENSION_IDENTIFIER(OES_draw_buffers_indexed)
    WEBGL_EXTENSION_IDENTIFIER(OES_element_index_uint)
    WEBGL_EXTENSION_IDENTIFIER(OES_fbo_render_mipmap)
    WEBGL_EXTENSION_IDENTIFIER(OES_standard_derivatives)
    WEBGL_EXTENSION_IDENTIFIER(OES_texture_float)
    WEBGL_EXTENSION_IDENTIFIER(OES_texture_float_linear)
    WEBGL_EXTENSION_IDENTIFIER(OES_texture_half_float)
    WEBGL_EXTENSION_IDENTIFIER(OES_texture_half_float_linear)
    WEBGL_EXTENSION_IDENTIFIER(OES_vertex_array_object)
    WEBGL_EXTENSION_IDENTIFIER(OVR_multiview2)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_color_buffer_float)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_compressed_texture_astc)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_compressed_texture_etc)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_compressed_texture_etc1)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_compressed_texture_pvrtc)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_compressed_texture_s3tc)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_compressed_texture_s3tc_srgb)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_debug_renderer_info)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_debug_shaders)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_depth_texture)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_draw_buffers)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_explicit_present)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_lose_context)
    WEBGL_EXTENSION_IDENTIFIER(WEBGL_provoking_vertex)

#undef WEBGL_EXTENSION_IDENTIFIER

    case WebGLExtensionID::Max:
      break;
  }
  MOZ_CRASH("bad WebGLExtensionID");
}

// ----------------------------
// ClientWebGLContext

void ClientWebGLContext::GetExtension(JSContext* cx, const nsAString& wideName,
                                      JS::MutableHandle<JSObject*> retval,
                                      dom::CallerType callerType,
                                      ErrorResult& rv) {
  retval.set(nullptr);
  const FuncScope funcScope(*this, "getExtension");
  if (IsContextLost()) return;

  const auto name = NS_ConvertUTF16toUTF8(wideName);

  auto ext = WebGLExtensionID::Max;

  // step 1: figure what extension is wanted
  for (const auto extension : MakeEnumeratedRange(WebGLExtensionID::Max)) {
    const auto& curName = GetExtensionName(extension);
    if (name.Equals(curName, nsCaseInsensitiveCStringComparator)) {
      ext = extension;
      break;
    }
  }

  if (ext == WebGLExtensionID::Max) return;

  RefPtr<ClientWebGLExtensionBase> extObj;
  if (ext == WebGLExtensionID::WEBGL_lose_context) {
    extObj = mExtLoseContext;
  } else {
    extObj = GetExtension(ext, callerType);
  }
  if (!extObj) return;

  // Ugh, this would be easier returning `any` than `object`.
  JS::Rooted<JS::Value> v(cx);
  MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, extObj, &v));
  if (v.isObject()) {
    retval.set(&v.toObject());
  }
}

RefPtr<ClientWebGLExtensionBase> ClientWebGLContext::GetExtension(
    const WebGLExtensionID ext, const dom::CallerType callerType) {
  if (ext == WebGLExtensionID::WEBGL_lose_context) {
    // Always the same.
    return mExtLoseContext;
  }

  if (!mNotLost) return nullptr;

  if (!IsSupported(ext, callerType)) return nullptr;

  auto& extSlot = mNotLost->extensions[UnderlyingValue(ext)];
  if (!extSlot) [[unlikely]] {
    extSlot = [&]() -> RefPtr<ClientWebGLExtensionBase> {
      switch (ext) {
        // ANGLE_
        case WebGLExtensionID::ANGLE_instanced_arrays:
          return MakeRefPtr<ClientWebGLExtensionInstancedArrays>(*this);

        // EXT_
        case WebGLExtensionID::EXT_blend_minmax:
          return MakeRefPtr<ClientWebGLExtensionBlendMinMax>(*this);
        case WebGLExtensionID::EXT_color_buffer_float:
          return MakeRefPtr<ClientWebGLExtensionEXTColorBufferFloat>(*this);
        case WebGLExtensionID::EXT_color_buffer_half_float:
          return MakeRefPtr<ClientWebGLExtensionColorBufferHalfFloat>(*this);
        case WebGLExtensionID::EXT_depth_clamp:
          return MakeRefPtr<ClientWebGLExtensionDepthClamp>(*this);
        case WebGLExtensionID::EXT_disjoint_timer_query:
          return MakeRefPtr<ClientWebGLExtensionDisjointTimerQuery>(*this);
        case WebGLExtensionID::EXT_float_blend:
          return MakeRefPtr<ClientWebGLExtensionFloatBlend>(*this);
        case WebGLExtensionID::EXT_frag_depth:
          return MakeRefPtr<ClientWebGLExtensionFragDepth>(*this);
        case WebGLExtensionID::EXT_shader_texture_lod:
          return MakeRefPtr<ClientWebGLExtensionShaderTextureLod>(*this);
        case WebGLExtensionID::EXT_sRGB:
          return MakeRefPtr<ClientWebGLExtensionSRGB>(*this);
        case WebGLExtensionID::EXT_texture_compression_bptc:
          return MakeRefPtr<ClientWebGLExtensionCompressedTextureBPTC>(*this);
        case WebGLExtensionID::EXT_texture_compression_rgtc:
          return MakeRefPtr<ClientWebGLExtensionCompressedTextureRGTC>(*this);
        case WebGLExtensionID::EXT_texture_filter_anisotropic:
          return MakeRefPtr<ClientWebGLExtensionTextureFilterAnisotropic>(
              *this);
        case WebGLExtensionID::EXT_texture_norm16:
          return MakeRefPtr<ClientWebGLExtensionTextureNorm16>(*this);

        // MOZ_
        case WebGLExtensionID::MOZ_debug:
          return MakeRefPtr<ClientWebGLExtensionMOZDebug>(*this);

        // OES_
        case WebGLExtensionID::OES_draw_buffers_indexed:
          return MakeRefPtr<ClientWebGLExtensionDrawBuffersIndexed>(*this);
        case WebGLExtensionID::OES_element_index_uint:
          return MakeRefPtr<ClientWebGLExtensionElementIndexUint>(*this);
        case WebGLExtensionID::OES_fbo_render_mipmap:
          return MakeRefPtr<ClientWebGLExtensionFBORenderMipmap>(*this);
        case WebGLExtensionID::OES_standard_derivatives:
          return MakeRefPtr<ClientWebGLExtensionStandardDerivatives>(*this);
        case WebGLExtensionID::OES_texture_float:
          return MakeRefPtr<ClientWebGLExtensionTextureFloat>(*this);
        case WebGLExtensionID::OES_texture_float_linear:
          return MakeRefPtr<ClientWebGLExtensionTextureFloatLinear>(*this);
        case WebGLExtensionID::OES_texture_half_float:
          return MakeRefPtr<ClientWebGLExtensionTextureHalfFloat>(*this);
        case WebGLExtensionID::OES_texture_half_float_linear:
          return MakeRefPtr<ClientWebGLExtensionTextureHalfFloatLinear>(*this);
        case WebGLExtensionID::OES_vertex_array_object:
          return MakeRefPtr<ClientWebGLExtensionVertexArray>(*this);

        // OVR_
        case WebGLExtensionID::OVR_multiview2:
          return MakeRefPtr<ClientWebGLExtensionMultiview>(*this);

        // WEBGL_
        case WebGLExtensionID::WEBGL_color_buffer_float:
          return MakeRefPtr<ClientWebGLExtensionColorBufferFloat>(*this);
        case WebGLExtensionID::WEBGL_compressed_texture_astc:
          return MakeRefPtr<ClientWebGLExtensionCompressedTextureASTC>(*this);
        case WebGLExtensionID::WEBGL_compressed_texture_etc:
          return MakeRefPtr<ClientWebGLExtensionCompressedTextureES3>(*this);
        case WebGLExtensionID::WEBGL_compressed_texture_etc1:
          return MakeRefPtr<ClientWebGLExtensionCompressedTextureETC1>(*this);
        case WebGLExtensionID::WEBGL_compressed_texture_pvrtc:
          return MakeRefPtr<ClientWebGLExtensionCompressedTexturePVRTC>(*this);
        case WebGLExtensionID::WEBGL_compressed_texture_s3tc:
          return MakeRefPtr<ClientWebGLExtensionCompressedTextureS3TC>(*this);
        case WebGLExtensionID::WEBGL_compressed_texture_s3tc_srgb:
          return MakeRefPtr<ClientWebGLExtensionCompressedTextureS3TC_SRGB>(
              *this);
        case WebGLExtensionID::WEBGL_debug_renderer_info: {
          if (callerType != dom::CallerType::System) {
            JsWarning(
                "WEBGL_debug_renderer_info is deprecated in Firefox and will "
                "be removed. Please use RENDERER.");
          }
          return MakeRefPtr<ClientWebGLExtensionDebugRendererInfo>(*this);
        }
        case WebGLExtensionID::WEBGL_debug_shaders:
          return MakeRefPtr<ClientWebGLExtensionDebugShaders>(*this);
        case WebGLExtensionID::WEBGL_depth_texture:
          return MakeRefPtr<ClientWebGLExtensionDepthTexture>(*this);
        case WebGLExtensionID::WEBGL_draw_buffers:
          return MakeRefPtr<ClientWebGLExtensionDrawBuffers>(*this);
        case WebGLExtensionID::WEBGL_explicit_present:
          return MakeRefPtr<ClientWebGLExtensionExplicitPresent>(*this);
        case WebGLExtensionID::WEBGL_provoking_vertex:
          return MakeRefPtr<ClientWebGLExtensionProvokingVertex>(*this);

        case WebGLExtensionID::WEBGL_lose_context:
        case WebGLExtensionID::Max:
          break;
      }
      MOZ_CRASH("illegal extension enum");
    }();
    MOZ_ASSERT(extSlot);
    RequestExtension(ext);
  }

  return extSlot;
}

// ----------------------------
// WebGLContext

bool WebGLContext::IsExtensionSupported(WebGLExtensionID ext) const {
  switch (ext) {
    case WebGLExtensionID::MOZ_debug:
    case WebGLExtensionID::WEBGL_debug_renderer_info:
    case WebGLExtensionID::WEBGL_debug_shaders:
    case WebGLExtensionID::WEBGL_lose_context:
      // Always supported.
      return true;

    // In alphabetical order
    // ANGLE_
    case WebGLExtensionID::ANGLE_instanced_arrays:
      return WebGLExtensionInstancedArrays::IsSupported(this);

    // EXT_
    case WebGLExtensionID::EXT_blend_minmax:
      return WebGLExtensionBlendMinMax::IsSupported(this);

    case WebGLExtensionID::EXT_color_buffer_float:
      return WebGLExtensionEXTColorBufferFloat::IsSupported(this);

    case WebGLExtensionID::EXT_color_buffer_half_float:
      return WebGLExtensionColorBufferHalfFloat::IsSupported(this);

    case WebGLExtensionID::EXT_depth_clamp:
      return gl->IsSupported(gl::GLFeature::depth_clamp);

    case WebGLExtensionID::EXT_disjoint_timer_query:
      return WebGLExtensionDisjointTimerQuery::IsSupported(this);

    case WebGLExtensionID::EXT_float_blend:
      return WebGLExtensionFloatBlend::IsSupported(this);

    case WebGLExtensionID::EXT_frag_depth:
      return WebGLExtensionFragDepth::IsSupported(this);

    case WebGLExtensionID::EXT_shader_texture_lod:
      return WebGLExtensionShaderTextureLod::IsSupported(this);

    case WebGLExtensionID::EXT_sRGB:
      return WebGLExtensionSRGB::IsSupported(this);

    case WebGLExtensionID::EXT_texture_compression_bptc:
      return WebGLExtensionCompressedTextureBPTC::IsSupported(this);

    case WebGLExtensionID::EXT_texture_compression_rgtc:
      return WebGLExtensionCompressedTextureRGTC::IsSupported(this);

    case WebGLExtensionID::EXT_texture_filter_anisotropic:
      return gl->IsExtensionSupported(
          gl::GLContext::EXT_texture_filter_anisotropic);

    case WebGLExtensionID::EXT_texture_norm16:
      return WebGLExtensionTextureNorm16::IsSupported(this);

    // OES_
    case WebGLExtensionID::OES_draw_buffers_indexed:
      if (!IsWebGL2()) return false;
      return gl->IsSupported(gl::GLFeature::draw_buffers_indexed) &&
             gl->IsSupported(gl::GLFeature::get_integer_indexed);

    case WebGLExtensionID::OES_element_index_uint:
      if (IsWebGL2()) return false;
      return gl->IsSupported(gl::GLFeature::element_index_uint);

    case WebGLExtensionID::OES_fbo_render_mipmap:
      return WebGLExtensionFBORenderMipmap::IsSupported(this);

    case WebGLExtensionID::OES_standard_derivatives:
      if (IsWebGL2()) return false;
      return gl->IsSupported(gl::GLFeature::standard_derivatives);

    case WebGLExtensionID::OES_texture_float:
      return WebGLExtensionTextureFloat::IsSupported(this);

    case WebGLExtensionID::OES_texture_float_linear:
      return gl->IsSupported(gl::GLFeature::texture_float_linear);

    case WebGLExtensionID::OES_texture_half_float:
      return WebGLExtensionTextureHalfFloat::IsSupported(this);

    case WebGLExtensionID::OES_texture_half_float_linear:
      if (IsWebGL2()) return false;
      return gl->IsSupported(gl::GLFeature::texture_half_float_linear);

    case WebGLExtensionID::OES_vertex_array_object:
      return !IsWebGL2();  // Always supported in webgl1.

    // OVR_
    case WebGLExtensionID::OVR_multiview2:
      return WebGLExtensionMultiview::IsSupported(this);

    // WEBGL_
    case WebGLExtensionID::WEBGL_color_buffer_float:
      return WebGLExtensionColorBufferFloat::IsSupported(this);

    case WebGLExtensionID::WEBGL_compressed_texture_astc:
      return WebGLExtensionCompressedTextureASTC::IsSupported(this);

    case WebGLExtensionID::WEBGL_compressed_texture_etc:
      return gl->IsSupported(gl::GLFeature::ES3_compatibility) &&
             !gl->IsANGLE();

    case WebGLExtensionID::WEBGL_compressed_texture_etc1:
      return gl->IsExtensionSupported(
                 gl::GLContext::OES_compressed_ETC1_RGB8_texture) &&
             !gl->IsANGLE();

    case WebGLExtensionID::WEBGL_compressed_texture_pvrtc:
      return false;

    case WebGLExtensionID::WEBGL_compressed_texture_s3tc:
      return WebGLExtensionCompressedTextureS3TC::IsSupported(this);

    case WebGLExtensionID::WEBGL_compressed_texture_s3tc_srgb:
      return WebGLExtensionCompressedTextureS3TC_SRGB::IsSupported(this);

    case WebGLExtensionID::WEBGL_depth_texture:
      return WebGLExtensionDepthTexture::IsSupported(this);

    case WebGLExtensionID::WEBGL_draw_buffers:
      return WebGLExtensionDrawBuffers::IsSupported(this);

    case WebGLExtensionID::WEBGL_explicit_present:
      return WebGLExtensionExplicitPresent::IsSupported(this);

    case WebGLExtensionID::WEBGL_provoking_vertex:
      if (!gl->IsSupported(gl::GLFeature::provoking_vertex)) return false;

      // > Implementations SHOULD only expose this extension when
      // > FIRST_VERTEX_CONVENTION is more efficient than the default behavior
      // > of LAST_VERTEX_CONVENTION.
      if (gl->IsANGLE()) return true;  // Better on D3D.
      if (kIsMacOS) {
        // Better on Metal, so probably Mac in general.
        return true;
      }
      return false;  // Probably not better for Win+GL, Linux, or Android.

    case WebGLExtensionID::Max:
      break;
  }

  MOZ_CRASH();
}

bool WebGLContext::IsExtensionExplicit(const WebGLExtensionID ext) const {
  return mExtensions[ext] && mExtensions[ext]->IsExplicit();
}

void WebGLContext::WarnIfImplicit(const WebGLExtensionID ext) const {
  const auto& extension = mExtensions[ext];
  if (!extension || extension->IsExplicit()) return;

  GenerateWarning(
      "Using format enabled by implicitly enabled extension: %s. "
      "For maximal portability enable it explicitly.",
      GetExtensionName(ext));
}

void WebGLContext::RequestExtension(const WebGLExtensionID ext,
                                    const bool explicitly) {
  const auto& limits = Limits();
  if (!limits.supportedExtensions[ext]) return;

  auto& slot = mExtensions[ext];
  switch (ext) {
    // ANGLE_
    case WebGLExtensionID::ANGLE_instanced_arrays:
      slot = std::make_unique<WebGLExtensionInstancedArrays>(this);
      break;

    // EXT_
    case WebGLExtensionID::EXT_blend_minmax:
      slot = std::make_unique<WebGLExtensionBlendMinMax>(this);
      break;
    case WebGLExtensionID::EXT_color_buffer_float:
      slot = std::make_unique<WebGLExtensionEXTColorBufferFloat>(this);
      break;
    case WebGLExtensionID::EXT_color_buffer_half_float:
      slot = std::make_unique<WebGLExtensionColorBufferHalfFloat>(this);
      break;
    case WebGLExtensionID::EXT_depth_clamp:
      slot = std::make_unique<WebGLExtensionDepthClamp>(this);
      break;
    case WebGLExtensionID::EXT_disjoint_timer_query:
      slot = std::make_unique<WebGLExtensionDisjointTimerQuery>(this);
      break;
    case WebGLExtensionID::EXT_float_blend:
      slot = std::make_unique<WebGLExtensionFloatBlend>(this);
      break;
    case WebGLExtensionID::EXT_frag_depth:
      slot = std::make_unique<WebGLExtensionFragDepth>(this);
      break;
    case WebGLExtensionID::EXT_shader_texture_lod:
      slot = std::make_unique<WebGLExtensionShaderTextureLod>(this);
      break;
    case WebGLExtensionID::EXT_sRGB:
      slot = std::make_unique<WebGLExtensionSRGB>(this);
      break;
    case WebGLExtensionID::EXT_texture_compression_bptc:
      slot = std::make_unique<WebGLExtensionCompressedTextureBPTC>(this);
      break;
    case WebGLExtensionID::EXT_texture_compression_rgtc:
      slot = std::make_unique<WebGLExtensionCompressedTextureRGTC>(this);
      break;
    case WebGLExtensionID::EXT_texture_filter_anisotropic:
      slot = std::make_unique<WebGLExtensionTextureFilterAnisotropic>(this);
      break;
    case WebGLExtensionID::EXT_texture_norm16:
      slot = std::make_unique<WebGLExtensionTextureNorm16>(this);
      break;

    // MOZ_
    case WebGLExtensionID::MOZ_debug:
      slot = std::make_unique<WebGLExtensionMOZDebug>(this);
      break;

    // OES_
    case WebGLExtensionID::OES_draw_buffers_indexed:
      slot = std::make_unique<WebGLExtensionDrawBuffersIndexed>(this);
      break;
    case WebGLExtensionID::OES_element_index_uint:
      slot = std::make_unique<WebGLExtensionElementIndexUint>(this);
      break;
    case WebGLExtensionID::OES_fbo_render_mipmap:
      slot = std::make_unique<WebGLExtensionFBORenderMipmap>(this);
      break;
    case WebGLExtensionID::OES_standard_derivatives:
      slot = std::make_unique<WebGLExtensionStandardDerivatives>(this);
      break;
    case WebGLExtensionID::OES_texture_float:
      slot = std::make_unique<WebGLExtensionTextureFloat>(this);
      break;
    case WebGLExtensionID::OES_texture_float_linear:
      slot = std::make_unique<WebGLExtensionTextureFloatLinear>(this);
      break;
    case WebGLExtensionID::OES_texture_half_float:
      slot = std::make_unique<WebGLExtensionTextureHalfFloat>(this);
      break;
    case WebGLExtensionID::OES_texture_half_float_linear:
      slot = std::make_unique<WebGLExtensionTextureHalfFloatLinear>(this);
      break;
    case WebGLExtensionID::OES_vertex_array_object:
      slot = std::make_unique<WebGLExtensionVertexArray>(this);
      break;

    // WEBGL_
    case WebGLExtensionID::OVR_multiview2:
      slot = std::make_unique<WebGLExtensionMultiview>(this);
      break;

    // WEBGL_
    case WebGLExtensionID::WEBGL_color_buffer_float:
      slot = std::make_unique<WebGLExtensionColorBufferFloat>(this);
      break;
    case WebGLExtensionID::WEBGL_compressed_texture_astc:
      slot = std::make_unique<WebGLExtensionCompressedTextureASTC>(this);
      break;
    case WebGLExtensionID::WEBGL_compressed_texture_etc:
      slot = std::make_unique<WebGLExtensionCompressedTextureES3>(this);
      break;
    case WebGLExtensionID::WEBGL_compressed_texture_etc1:
      slot = std::make_unique<WebGLExtensionCompressedTextureETC1>(this);
      break;
    case WebGLExtensionID::WEBGL_compressed_texture_pvrtc:
      slot = std::make_unique<WebGLExtensionCompressedTexturePVRTC>(this);
      break;
    case WebGLExtensionID::WEBGL_compressed_texture_s3tc:
      slot = std::make_unique<WebGLExtensionCompressedTextureS3TC>(this);
      break;
    case WebGLExtensionID::WEBGL_compressed_texture_s3tc_srgb:
      slot = std::make_unique<WebGLExtensionCompressedTextureS3TC_SRGB>(this);
      break;
    case WebGLExtensionID::WEBGL_debug_renderer_info:
      slot = std::make_unique<WebGLExtensionDebugRendererInfo>(this);
      break;
    case WebGLExtensionID::WEBGL_debug_shaders:
      slot = std::make_unique<WebGLExtensionDebugShaders>(this);
      break;
    case WebGLExtensionID::WEBGL_depth_texture:
      slot = std::make_unique<WebGLExtensionDepthTexture>(this);
      break;
    case WebGLExtensionID::WEBGL_draw_buffers:
      slot = std::make_unique<WebGLExtensionDrawBuffers>(this);
      break;
    case WebGLExtensionID::WEBGL_explicit_present:
      slot = std::make_unique<WebGLExtensionExplicitPresent>(this);
      break;
    case WebGLExtensionID::WEBGL_lose_context:
      slot = std::make_unique<WebGLExtensionLoseContext>(this);
      break;
    case WebGLExtensionID::WEBGL_provoking_vertex:
      slot = std::make_unique<WebGLExtensionProvokingVertex>(this);
      break;

    case WebGLExtensionID::Max:
      MOZ_CRASH();
  }
  MOZ_ASSERT(slot);
  const auto& obj = slot;

  if (explicitly && !obj->IsExplicit()) {
    obj->SetExplicit();
  }

  // Also enable implied extensions.
  switch (ext) {
    case WebGLExtensionID::EXT_color_buffer_float:
      RequestExtension(WebGLExtensionID::EXT_float_blend, false);
      break;

    case WebGLExtensionID::OES_texture_float:
      RequestExtension(WebGLExtensionID::EXT_float_blend, false);
      RequestExtension(WebGLExtensionID::WEBGL_color_buffer_float, false);
      break;

    case WebGLExtensionID::OES_texture_half_float:
      RequestExtension(WebGLExtensionID::EXT_color_buffer_half_float, false);
      break;

    case WebGLExtensionID::WEBGL_color_buffer_float:
      RequestExtension(WebGLExtensionID::EXT_float_blend, false);
      break;

    default:
      break;
  }
}

}  // namespace mozilla
