/* 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 https://mozilla.org/MPL/2.0/. */

use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextMethods;
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes;
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods;
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
use crate::dom::bindings::codegen::UnionTypes::Float32ArrayOrUnrestrictedFloatSequence;
use crate::dom::bindings::codegen::UnionTypes::ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement;
use crate::dom::bindings::codegen::UnionTypes::Int32ArrayOrLongSequence;
use crate::dom::bindings::codegen::UnionTypes::Uint32ArrayOrUnsignedLongSequence;
use crate::dom::bindings::error::{ErrorResult, Fallible};
use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutCanvasRenderingContextHelpers};
use crate::dom::webgl_validations::tex_image_2d::{
    TexImage2DValidator, TexImage2DValidatorResult, TexStorageValidator, TexStorageValidatorResult,
};
use crate::dom::webgl_validations::WebGLValidator;
use crate::dom::webglactiveinfo::WebGLActiveInfo;
use crate::dom::webglbuffer::WebGLBuffer;
use crate::dom::webglframebuffer::{WebGLFramebuffer, WebGLFramebufferAttachmentRoot};
use crate::dom::webglprogram::WebGLProgram;
use crate::dom::webglquery::WebGLQuery;
use crate::dom::webglrenderbuffer::WebGLRenderbuffer;
use crate::dom::webglrenderingcontext::{
    uniform_get, uniform_typed, Operation, TexPixels, TexSource, VertexAttrib,
    WebGLRenderingContext,
};
use crate::dom::webglsampler::{WebGLSampler, WebGLSamplerValue};
use crate::dom::webglshader::WebGLShader;
use crate::dom::webglshaderprecisionformat::WebGLShaderPrecisionFormat;
use crate::dom::webglsync::WebGLSync;
use crate::dom::webgltexture::WebGLTexture;
use crate::dom::webgltransformfeedback::WebGLTransformFeedback;
use crate::dom::webgluniformlocation::WebGLUniformLocation;
use crate::dom::webglvertexarrayobject::WebGLVertexArrayObject;
use crate::dom::window::Window;
use crate::js::conversions::ToJSValConvertible;
use crate::script_runtime::JSContext;
use canvas_traits::webgl::WebGLError::*;
use canvas_traits::webgl::{
    webgl_channel, GLContextAttributes, InternalFormatParameter, WebGLCommand, WebGLResult,
    WebGLVersion,
};
use dom_struct::dom_struct;
use euclid::default::{Point2D, Rect, Size2D};
use ipc_channel::ipc::{self, IpcSharedMemory};
use js::jsapi::{JSObject, Type};
use js::jsval::{BooleanValue, DoubleValue, Int32Value, UInt32Value};
use js::jsval::{JSVal, NullValue, ObjectValue, UndefinedValue};
use js::rust::{CustomAutoRooterGuard, HandleObject};
use js::typedarray::{ArrayBufferView, CreateWith, Float32, Int32Array, Uint32, Uint32Array};
use script_layout_interface::HTMLCanvasDataSource;
use servo_config::pref;
use std::cell::Cell;
use std::cmp;
use std::ptr::{self, NonNull};
use url::Host;

#[unrooted_must_root_lint::must_root]
#[derive(JSTraceable, MallocSizeOf)]
struct IndexedBinding {
    buffer: MutNullableDom<WebGLBuffer>,
    start: Cell<i64>,
    size: Cell<i64>,
}

impl IndexedBinding {
    fn new() -> IndexedBinding {
        IndexedBinding {
            buffer: MutNullableDom::new(None),
            start: Cell::new(0),
            size: Cell::new(0),
        }
    }
}

#[dom_struct]
pub struct WebGL2RenderingContext {
    reflector_: Reflector,
    base: Dom<WebGLRenderingContext>,
    occlusion_query: MutNullableDom<WebGLQuery>,
    primitives_query: MutNullableDom<WebGLQuery>,
    samplers: Box<[MutNullableDom<WebGLSampler>]>,
    bound_copy_read_buffer: MutNullableDom<WebGLBuffer>,
    bound_copy_write_buffer: MutNullableDom<WebGLBuffer>,
    bound_pixel_pack_buffer: MutNullableDom<WebGLBuffer>,
    bound_pixel_unpack_buffer: MutNullableDom<WebGLBuffer>,
    bound_transform_feedback_buffer: MutNullableDom<WebGLBuffer>,
    bound_uniform_buffer: MutNullableDom<WebGLBuffer>,
    indexed_uniform_buffer_bindings: Box<[IndexedBinding]>,
    indexed_transform_feedback_buffer_bindings: Box<[IndexedBinding]>,
    current_transform_feedback: MutNullableDom<WebGLTransformFeedback>,
    texture_pack_row_length: Cell<usize>,
    texture_pack_skip_pixels: Cell<usize>,
    texture_pack_skip_rows: Cell<usize>,
    enable_rasterizer_discard: Cell<bool>,
    default_fb_readbuffer: Cell<u32>,
    default_fb_drawbuffer: Cell<u32>,
}

fn typedarray_elem_size(typeid: Type) -> usize {
    match typeid {
        Type::Int8 | Type::Uint8 | Type::Uint8Clamped => 1,
        Type::Int16 | Type::Uint16 => 2,
        Type::Int32 | Type::Uint32 | Type::Float32 => 4,
        Type::Int64 | Type::Float64 => 8,
        Type::BigInt64 | Type::BigUint64 => 8,
        Type::Simd128 | Type::MaxTypedArrayViewType => unreachable!(),
    }
}

struct ReadPixelsAllowedFormats<'a> {
    array_types: &'a [Type],
    channels: usize,
}

struct ReadPixelsSizes {
    row_stride: usize,
    skipped_bytes: usize,
    size: usize,
}

impl WebGL2RenderingContext {
    fn new_inherited(
        window: &Window,
        canvas: &HTMLCanvasElement,
        size: Size2D<u32>,
        attrs: GLContextAttributes,
    ) -> Option<WebGL2RenderingContext> {
        let base = WebGLRenderingContext::new(window, canvas, WebGLVersion::WebGL2, size, attrs)?;

        let samplers = (0..base.limits().max_combined_texture_image_units)
            .map(|_| Default::default())
            .collect::<Vec<_>>()
            .into();
        let indexed_uniform_buffer_bindings = (0..base.limits().max_uniform_buffer_bindings)
            .map(|_| IndexedBinding::new())
            .collect::<Vec<_>>()
            .into();
        let indexed_transform_feedback_buffer_bindings =
            (0..base.limits().max_transform_feedback_separate_attribs)
                .map(|_| IndexedBinding::new())
                .collect::<Vec<_>>()
                .into();

        Some(WebGL2RenderingContext {
            reflector_: Reflector::new(),
            base: Dom::from_ref(&*base),
            occlusion_query: MutNullableDom::new(None),
            primitives_query: MutNullableDom::new(None),
            samplers: samplers,
            bound_copy_read_buffer: MutNullableDom::new(None),
            bound_copy_write_buffer: MutNullableDom::new(None),
            bound_pixel_pack_buffer: MutNullableDom::new(None),
            bound_pixel_unpack_buffer: MutNullableDom::new(None),
            bound_transform_feedback_buffer: MutNullableDom::new(None),
            bound_uniform_buffer: MutNullableDom::new(None),
            indexed_uniform_buffer_bindings,
            indexed_transform_feedback_buffer_bindings,
            current_transform_feedback: MutNullableDom::new(None),
            texture_pack_row_length: Cell::new(0),
            texture_pack_skip_pixels: Cell::new(0),
            texture_pack_skip_rows: Cell::new(0),
            enable_rasterizer_discard: Cell::new(false),
            default_fb_readbuffer: Cell::new(constants::BACK),
            default_fb_drawbuffer: Cell::new(constants::BACK),
        })
    }

    #[allow(unrooted_must_root)]
    pub fn new(
        window: &Window,
        canvas: &HTMLCanvasElement,
        size: Size2D<u32>,
        attrs: GLContextAttributes,
    ) -> Option<DomRoot<WebGL2RenderingContext>> {
        WebGL2RenderingContext::new_inherited(window, canvas, size, attrs)
            .map(|ctx| reflect_dom_object(Box::new(ctx), window))
    }

    #[allow(unsafe_code)]
    pub fn is_webgl2_enabled(_cx: JSContext, global: HandleObject) -> bool {
        if pref!(dom.webgl2.enabled) {
            return true;
        }

        let global = unsafe { GlobalScope::from_object(global.get()) };
        let origin = global.origin();
        let host = origin.host();
        WEBGL2_ORIGINS
            .iter()
            .any(|origin| host == Host::parse(origin).ok().as_ref())
    }
}

/// List of domains for which WebGL 2 is enabled automatically, regardless
/// of the status of the dom.webgl2.enabled preference.
static WEBGL2_ORIGINS: &[&str] = &["www.servoexperiments.com"];

impl WebGL2RenderingContext {
    pub fn recreate(&self, size: Size2D<u32>) {
        self.base.recreate(size)
    }

    pub fn current_vao(&self) -> DomRoot<WebGLVertexArrayObject> {
        self.base.current_vao_webgl2()
    }

    pub fn validate_uniform_block_for_draw(&self) {
        let program = match self.base.current_program() {
            Some(program) => program,
            None => return,
        };
        for uniform_block in program.active_uniform_blocks().iter() {
            let data_size = uniform_block.size as usize;
            for block in program.active_uniforms().iter() {
                let index = match block.bind_index {
                    Some(index) => index,
                    None => continue,
                };
                let indexed = &self.indexed_uniform_buffer_bindings[index as usize];
                let buffer = match indexed.buffer.get() {
                    Some(buffer) => buffer,
                    None => {
                        self.base.webgl_error(InvalidOperation);
                        return;
                    },
                };
                if indexed.size.get() == 0 {
                    if data_size > buffer.capacity() {
                        self.base.webgl_error(InvalidOperation);
                        return;
                    }
                } else {
                    let start = indexed.start.get() as usize;
                    let mut size = indexed.size.get() as usize;
                    if start >= size {
                        self.base.webgl_error(InvalidOperation);
                        return;
                    }
                    size -= start;
                    if data_size > size {
                        self.base.webgl_error(InvalidOperation);
                        return;
                    }
                }
            }
        }
    }

    fn validate_vertex_attribs_for_draw(&self) {
        let program = match self.base.current_program() {
            Some(program) => program,
            None => return,
        };
        let groups = [
            [
                constants::INT,
                constants::INT_VEC2,
                constants::INT_VEC3,
                constants::INT_VEC4,
            ],
            [
                constants::UNSIGNED_INT,
                constants::UNSIGNED_INT_VEC2,
                constants::UNSIGNED_INT_VEC3,
                constants::UNSIGNED_INT_VEC4,
            ],
            [
                constants::FLOAT,
                constants::FLOAT_VEC2,
                constants::FLOAT_VEC3,
                constants::FLOAT_VEC4,
            ],
        ];
        let vao = self.current_vao();
        for prog_attrib in program.active_attribs().iter() {
            let attrib = handle_potential_webgl_error!(
                self.base,
                vao.get_vertex_attrib(prog_attrib.location as u32)
                    .ok_or(InvalidOperation),
                return
            );

            let current_vertex_attrib =
                self.base.current_vertex_attribs()[prog_attrib.location as usize];
            let attrib_data_base_type = if !attrib.enabled_as_array {
                match current_vertex_attrib {
                    VertexAttrib::Int(_, _, _, _) => constants::INT,
                    VertexAttrib::Uint(_, _, _, _) => constants::UNSIGNED_INT,
                    VertexAttrib::Float(_, _, _, _) => constants::FLOAT,
                }
            } else {
                attrib.type_
            };

            let contains = groups
                .iter()
                .find(|g| g.contains(&attrib_data_base_type) && g.contains(&prog_attrib.type_));
            if contains.is_none() {
                self.base.webgl_error(InvalidOperation);
                return;
            }
        }
    }

    pub fn base_context(&self) -> DomRoot<WebGLRenderingContext> {
        DomRoot::from_ref(&*self.base)
    }

    fn bound_buffer(&self, target: u32) -> WebGLResult<Option<DomRoot<WebGLBuffer>>> {
        match target {
            constants::COPY_READ_BUFFER => Ok(self.bound_copy_read_buffer.get()),
            constants::COPY_WRITE_BUFFER => Ok(self.bound_copy_write_buffer.get()),
            constants::PIXEL_PACK_BUFFER => Ok(self.bound_pixel_pack_buffer.get()),
            constants::PIXEL_UNPACK_BUFFER => Ok(self.bound_pixel_unpack_buffer.get()),
            constants::TRANSFORM_FEEDBACK_BUFFER => Ok(self.bound_transform_feedback_buffer.get()),
            constants::UNIFORM_BUFFER => Ok(self.bound_uniform_buffer.get()),
            constants::ELEMENT_ARRAY_BUFFER => Ok(self.current_vao().element_array_buffer().get()),
            _ => self.base.bound_buffer(target),
        }
    }

    pub fn buffer_usage(&self, usage: u32) -> WebGLResult<u32> {
        match usage {
            constants::STATIC_READ |
            constants::DYNAMIC_READ |
            constants::STREAM_READ |
            constants::STATIC_COPY |
            constants::DYNAMIC_COPY |
            constants::STREAM_COPY => Ok(usage),
            _ => self.base.buffer_usage(usage),
        }
    }

    fn unbind_from(&self, slot: &MutNullableDom<WebGLBuffer>, buffer: &WebGLBuffer) {
        if slot.get().map_or(false, |b| buffer == &*b) {
            buffer.decrement_attached_counter(Operation::Infallible);
            slot.set(None);
        }
    }

    fn calc_read_pixel_formats(
        &self,
        pixel_type: u32,
        format: u32,
    ) -> WebGLResult<ReadPixelsAllowedFormats> {
        let array_types = match pixel_type {
            constants::BYTE => &[Type::Int8][..],
            constants::SHORT => &[Type::Int16][..],
            constants::INT => &[Type::Int32][..],
            constants::UNSIGNED_BYTE => &[Type::Uint8, Type::Uint8Clamped][..],
            constants::UNSIGNED_SHORT |
            constants::UNSIGNED_SHORT_4_4_4_4 |
            constants::UNSIGNED_SHORT_5_5_5_1 |
            constants::UNSIGNED_SHORT_5_6_5 => &[Type::Uint16][..],
            constants::UNSIGNED_INT |
            constants::UNSIGNED_INT_2_10_10_10_REV |
            constants::UNSIGNED_INT_10F_11F_11F_REV |
            constants::UNSIGNED_INT_5_9_9_9_REV => &[Type::Uint32][..],
            constants::FLOAT => &[Type::Float32][..],
            constants::HALF_FLOAT => &[Type::Uint16][..],
            _ => return Err(InvalidEnum),
        };
        let channels = match format {
            constants::ALPHA | constants::RED | constants::RED_INTEGER => 1,
            constants::RG | constants::RG_INTEGER => 2,
            constants::RGB | constants::RGB_INTEGER => 3,
            constants::RGBA | constants::RGBA_INTEGER => 4,
            _ => return Err(InvalidEnum),
        };
        Ok(ReadPixelsAllowedFormats {
            array_types,
            channels,
        })
    }

    fn calc_read_pixel_sizes(
        &self,
        width: i32,
        height: i32,
        bytes_per_pixel: usize,
    ) -> WebGLResult<ReadPixelsSizes> {
        if width < 0 || height < 0 {
            return Err(InvalidValue);
        }

        // See also https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.36
        let pixels_per_row = if self.texture_pack_row_length.get() > 0 {
            self.texture_pack_row_length.get()
        } else {
            width as usize
        };
        if self.texture_pack_skip_pixels.get() + width as usize > pixels_per_row {
            return Err(InvalidOperation);
        }

        let bytes_per_row = pixels_per_row
            .checked_mul(bytes_per_pixel)
            .ok_or(InvalidOperation)?;
        let row_padding_bytes = {
            let pack_alignment = self.base.get_texture_packing_alignment() as usize;
            match bytes_per_row % pack_alignment {
                0 => 0,
                remainder => pack_alignment - remainder,
            }
        };
        let row_stride = bytes_per_row + row_padding_bytes;
        let size = if width == 0 || height == 0 {
            0
        } else {
            let full_row_bytes = row_stride
                .checked_mul(height as usize - 1)
                .ok_or(InvalidOperation)?;
            let last_row_bytes = bytes_per_pixel
                .checked_mul(width as usize)
                .ok_or(InvalidOperation)?;
            let result = full_row_bytes
                .checked_add(last_row_bytes)
                .ok_or(InvalidOperation)?;
            result
        };
        let skipped_bytes = {
            let skipped_row_bytes = self
                .texture_pack_skip_rows
                .get()
                .checked_mul(row_stride)
                .ok_or(InvalidOperation)?;
            let skipped_pixel_bytes = self
                .texture_pack_skip_pixels
                .get()
                .checked_mul(bytes_per_pixel)
                .ok_or(InvalidOperation)?;
            let result = skipped_row_bytes
                .checked_add(skipped_pixel_bytes)
                .ok_or(InvalidOperation)?;
            result
        };
        Ok(ReadPixelsSizes {
            row_stride,
            skipped_bytes,
            size,
        })
    }

    #[allow(unsafe_code)]
    fn read_pixels_into(
        &self,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
        format: u32,
        pixel_type: u32,
        dst: &mut ArrayBufferView,
        dst_elem_offset: u32,
    ) {
        handle_potential_webgl_error!(self.base, self.base.validate_framebuffer(), return);

        if self.bound_pixel_pack_buffer.get().is_some() {
            return self.base.webgl_error(InvalidOperation);
        }

        let fb_slot = self.base.get_draw_framebuffer_slot();
        let fb_readbuffer_valid = match fb_slot.get() {
            Some(fb) => fb.attachment(fb.read_buffer()).is_some(),
            None => self.default_fb_readbuffer.get() != constants::NONE,
        };
        if !fb_readbuffer_valid {
            return self.base.webgl_error(InvalidOperation);
        }

        let dst_byte_offset = {
            let dst_elem_size = typedarray_elem_size(dst.get_array_type());
            dst_elem_offset as usize * dst_elem_size
        };
        if dst_byte_offset > dst.len() {
            return self.base.webgl_error(InvalidValue);
        }

        let dst_array_type = dst.get_array_type();
        let ReadPixelsAllowedFormats {
            array_types: allowed_array_types,
            channels,
        } = match self.calc_read_pixel_formats(pixel_type, format) {
            Ok(result) => result,
            Err(error) => return self.base.webgl_error(error),
        };
        if !allowed_array_types.contains(&dst_array_type) {
            return self.base.webgl_error(InvalidOperation);
        }
        if format != constants::RGBA || pixel_type != constants::UNSIGNED_BYTE {
            return self.base.webgl_error(InvalidOperation);
        }

        let bytes_per_pixel = typedarray_elem_size(dst_array_type) * channels;
        let ReadPixelsSizes {
            row_stride,
            skipped_bytes,
            size,
        } = match self.calc_read_pixel_sizes(width, height, bytes_per_pixel) {
            Ok(result) => result,
            Err(error) => return self.base.webgl_error(error),
        };
        let dst_end = dst_byte_offset + skipped_bytes + size;
        let dst_pixels = unsafe { dst.as_mut_slice() };
        if dst_pixels.len() < dst_end {
            return self.base.webgl_error(InvalidOperation);
        }

        let dst_byte_offset = {
            let margin_left = cmp::max(0, -x) as usize;
            let margin_top = cmp::max(0, -y) as usize;
            dst_byte_offset +
                skipped_bytes +
                margin_left * bytes_per_pixel +
                margin_top * row_stride
        };
        let src_rect = {
            let (fb_width, fb_height) = handle_potential_webgl_error!(
                self.base,
                self.base
                    .get_current_framebuffer_size()
                    .ok_or(InvalidOperation),
                return
            );
            let src_origin = Point2D::new(x, y);
            let src_size = Size2D::new(width as u32, height as u32);
            let fb_size = Size2D::new(fb_width as u32, fb_height as u32);
            match pixels::clip(src_origin, src_size.to_u64(), fb_size.to_u64()) {
                Some(rect) => rect.to_u32(),
                None => return,
            }
        };
        let src_row_bytes = handle_potential_webgl_error!(
            self.base,
            src_rect
                .size
                .width
                .checked_mul(bytes_per_pixel as u32)
                .ok_or(InvalidOperation),
            return
        );

        let (sender, receiver) = ipc::bytes_channel().unwrap();
        self.base.send_command(WebGLCommand::ReadPixels(
            src_rect, format, pixel_type, sender,
        ));
        let src = receiver.recv().unwrap();

        for i in 0..src_rect.size.height as usize {
            let src_start = i * src_row_bytes as usize;
            let dst_start = dst_byte_offset + i * row_stride;
            dst_pixels[dst_start..dst_start + src_row_bytes as usize]
                .copy_from_slice(&src[src_start..src_start + src_row_bytes as usize]);
        }
    }

    fn uniform_vec_section_uint(
        &self,
        vec: Uint32ArrayOrUnsignedLongSequence,
        offset: u32,
        length: u32,
        uniform_size: usize,
        uniform_location: &WebGLUniformLocation,
    ) -> WebGLResult<Vec<u32>> {
        let vec = match vec {
            Uint32ArrayOrUnsignedLongSequence::Uint32Array(v) => v.to_vec(),
            Uint32ArrayOrUnsignedLongSequence::UnsignedLongSequence(v) => v,
        };
        self.base
            .uniform_vec_section::<u32>(vec, offset, length, uniform_size, uniform_location)
    }

    #[allow(unsafe_code)]
    fn get_default_fb_attachment_param(&self, attachment: u32, pname: u32) -> WebGLResult<JSVal> {
        match attachment {
            constants::BACK | constants::DEPTH | constants::STENCIL => {},
            _ => return Err(InvalidEnum),
        }

        if pname == constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME {
            return Ok(NullValue());
        }

        let attrs = self
            .GetContextAttributes()
            .unwrap_or_else(WebGLContextAttributes::empty);

        let intval = match pname {
            constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE => match attachment {
                constants::DEPTH if !attrs.depth => constants::NONE as _,
                constants::STENCIL if !attrs.stencil => constants::NONE as _,
                _ => constants::FRAMEBUFFER_DEFAULT as _,
            },
            constants::FRAMEBUFFER_ATTACHMENT_RED_SIZE |
            constants::FRAMEBUFFER_ATTACHMENT_GREEN_SIZE |
            constants::FRAMEBUFFER_ATTACHMENT_BLUE_SIZE => match attachment {
                constants::BACK => 8,
                _ => 0,
            },
            constants::FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE => match attachment {
                constants::BACK if attrs.alpha => 8,
                constants::BACK => return Err(InvalidOperation),
                _ => 0,
            },
            constants::FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE => match attachment {
                constants::DEPTH if attrs.depth => 24,
                constants::DEPTH => return Err(InvalidOperation),
                _ => 0,
            },
            constants::FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE => match attachment {
                constants::STENCIL if attrs.stencil => 8,
                constants::STENCIL => return Err(InvalidOperation),
                _ => 0,
            },
            constants::FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE => match attachment {
                constants::DEPTH if attrs.depth => constants::UNSIGNED_NORMALIZED as _,
                constants::STENCIL if attrs.stencil => constants::UNSIGNED_INT as _,
                constants::DEPTH => return Err(InvalidOperation),
                constants::STENCIL => return Err(InvalidOperation),
                _ => constants::UNSIGNED_NORMALIZED as _,
            },
            constants::FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING => match attachment {
                constants::DEPTH if !attrs.depth => return Err(InvalidOperation),
                constants::STENCIL if !attrs.stencil => return Err(InvalidOperation),
                _ => constants::LINEAR as _,
            },
            _ => return Err(InvalidEnum),
        };
        Ok(Int32Value(intval))
    }

    #[allow(unsafe_code)]
    fn get_specific_fb_attachment_param(
        &self,
        cx: JSContext,
        fb: &WebGLFramebuffer,
        target: u32,
        attachment: u32,
        pname: u32,
    ) -> WebGLResult<JSVal> {
        use crate::dom::webglframebuffer::WebGLFramebufferAttachmentRoot::{Renderbuffer, Texture};

        match attachment {
            constants::DEPTH_ATTACHMENT | constants::STENCIL_ATTACHMENT => {},
            constants::DEPTH_STENCIL_ATTACHMENT => {
                if pname == constants::FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE {
                    return Err(InvalidOperation);
                }

                let a = fb.attachment(constants::DEPTH_ATTACHMENT);
                let b = fb.attachment(constants::STENCIL_ATTACHMENT);
                match (a, b) {
                    (Some(Renderbuffer(ref a)), Some(Renderbuffer(ref b))) if a.id() == b.id() => {
                    },
                    (Some(Texture(ref a)), Some(Texture(ref b))) if a.id() == b.id() => {},
                    _ => return Err(InvalidOperation),
                }
            },
            constants::COLOR_ATTACHMENT0..=constants::COLOR_ATTACHMENT15 => {
                let last_slot =
                    constants::COLOR_ATTACHMENT0 + self.base.limits().max_color_attachments - 1;
                if last_slot < attachment {
                    return Err(InvalidEnum);
                }
            },
            _ => return Err(InvalidEnum),
        }

        let attachment = match attachment {
            constants::DEPTH_STENCIL_ATTACHMENT => constants::DEPTH_ATTACHMENT,
            _ => attachment,
        };

        if pname == constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME {
            rooted!(in(*cx) let mut rval = NullValue());
            match fb.attachment(attachment) {
                Some(Renderbuffer(rb)) => unsafe {
                    rb.to_jsval(*cx, rval.handle_mut());
                },
                Some(Texture(texture)) => unsafe {
                    texture.to_jsval(*cx, rval.handle_mut());
                },
                _ => {},
            }
            return Ok(rval.get());
        }

        match pname {
            constants::FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE => {},
            _ => match fb.attachment(attachment) {
                Some(webgl_attachment) => match pname {
                    constants::FRAMEBUFFER_ATTACHMENT_RED_SIZE |
                    constants::FRAMEBUFFER_ATTACHMENT_GREEN_SIZE |
                    constants::FRAMEBUFFER_ATTACHMENT_BLUE_SIZE |
                    constants::FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE |
                    constants::FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE |
                    constants::FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE |
                    constants::FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE |
                    constants::FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING => {},
                    _ => match webgl_attachment {
                        WebGLFramebufferAttachmentRoot::Renderbuffer(_) => return Err(InvalidEnum),
                        WebGLFramebufferAttachmentRoot::Texture(_) => match pname {
                            constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL |
                            constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE |
                            constants::FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER => {},
                            _ => return Err(InvalidEnum),
                        },
                    },
                },
                None => return Err(InvalidOperation),
            },
        }

        let (sender, receiver) = webgl_channel().unwrap();
        self.base
            .send_command(WebGLCommand::GetFramebufferAttachmentParameter(
                target, attachment, pname, sender,
            ));

        let retval = receiver.recv().unwrap();
        Ok(Int32Value(retval))
    }

    fn clearbuffer_array_size(&self, buffer: u32, draw_buffer: i32) -> WebGLResult<usize> {
        match buffer {
            constants::COLOR => {
                if draw_buffer < 0 || draw_buffer as u32 >= self.base.limits().max_draw_buffers {
                    return Err(InvalidValue);
                }
                Ok(4)
            },
            constants::DEPTH | constants::STENCIL | constants::DEPTH_STENCIL => {
                if draw_buffer != 0 {
                    return Err(InvalidValue);
                }
                Ok(1)
            },
            _ => unreachable!(),
        }
    }

    fn clear_buffer<T: Clone>(
        &self,
        buffer: u32,
        draw_buffer: i32,
        valid_buffers: &[u32],
        src_offset: u32,
        array: Vec<T>,
        msg: fn(u32, i32, Vec<T>) -> WebGLCommand,
    ) {
        if !valid_buffers.contains(&buffer) {
            return self.base.webgl_error(InvalidEnum);
        }

        let array_size = handle_potential_webgl_error!(
            self.base,
            self.clearbuffer_array_size(buffer, draw_buffer),
            return
        );
        let src_offset = src_offset as usize;

        if array.len() < src_offset + array_size {
            return self.base.webgl_error(InvalidValue);
        }
        let array = array[src_offset..src_offset + array_size].to_vec();

        self.base.send_command(msg(buffer, draw_buffer, array));
    }

    fn valid_fb_attachment_values(&self, target: u32, attachments: &[u32]) -> bool {
        let fb_slot = match target {
            constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => {
                self.base.get_draw_framebuffer_slot()
            },
            constants::READ_FRAMEBUFFER => self.base.get_read_framebuffer_slot(),
            _ => {
                self.base.webgl_error(InvalidEnum);
                return false;
            },
        };

        if let Some(fb) = fb_slot.get() {
            if fb.check_status() != constants::FRAMEBUFFER_COMPLETE {
                return false;
            }

            for &attachment in attachments {
                match attachment {
                    constants::DEPTH_ATTACHMENT |
                    constants::STENCIL_ATTACHMENT |
                    constants::DEPTH_STENCIL_ATTACHMENT => {},
                    constants::COLOR_ATTACHMENT0..=constants::COLOR_ATTACHMENT15 => {
                        let last_slot = constants::COLOR_ATTACHMENT0 +
                            self.base.limits().max_color_attachments -
                            1;
                        if last_slot < attachment {
                            return false;
                        }
                    },
                    _ => return false,
                }
            }
        } else {
            for &attachment in attachments {
                match attachment {
                    constants::COLOR | constants::DEPTH | constants::STENCIL => {},
                    _ => return false,
                }
            }
        }

        true
    }

    fn vertex_attrib_i(&self, index: u32, x: i32, y: i32, z: i32, w: i32) {
        if index >= self.base.limits().max_vertex_attribs {
            return self.base.webgl_error(InvalidValue);
        }
        self.base.current_vertex_attribs()[index as usize] = VertexAttrib::Int(x, y, z, w);
        self.current_vao()
            .set_vertex_attrib_type(index, constants::INT);
        self.base
            .send_command(WebGLCommand::VertexAttribI(index, x, y, z, w));
    }

    fn vertex_attrib_u(&self, index: u32, x: u32, y: u32, z: u32, w: u32) {
        if index >= self.base.limits().max_vertex_attribs {
            return self.base.webgl_error(InvalidValue);
        }
        self.base.current_vertex_attribs()[index as usize] = VertexAttrib::Uint(x, y, z, w);
        self.current_vao()
            .set_vertex_attrib_type(index, constants::UNSIGNED_INT);
        self.base
            .send_command(WebGLCommand::VertexAttribU(index, x, y, z, w));
    }

    fn tex_storage(
        &self,
        dimensions: u8,
        target: u32,
        levels: i32,
        internal_format: u32,
        width: i32,
        height: i32,
        depth: i32,
    ) {
        let expected_dimensions = match target {
            constants::TEXTURE_2D | constants::TEXTURE_CUBE_MAP => 2,
            constants::TEXTURE_3D | constants::TEXTURE_2D_ARRAY => 3,
            _ => return self.base.webgl_error(InvalidEnum),
        };
        if dimensions != expected_dimensions {
            return self.base.webgl_error(InvalidEnum);
        }

        let validator = TexStorageValidator::new(
            &self.base,
            dimensions,
            target,
            levels,
            internal_format,
            width,
            height,
            depth,
        );
        let TexStorageValidatorResult {
            texture,
            target,
            levels,
            internal_format,
            width,
            height,
            depth,
        } = match validator.validate() {
            Ok(result) => result,
            Err(_) => return, // NB: The validator sets the correct error for us.
        };

        handle_potential_webgl_error!(
            self.base,
            texture.storage(target, levels, internal_format, width, height, depth),
            return
        );
    }
}

impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1
    fn Canvas(&self) -> DomRoot<HTMLCanvasElement> {
        self.base.Canvas()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11
    fn Flush(&self) {
        self.base.Flush()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11
    fn Finish(&self) {
        self.base.Finish()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1
    fn DrawingBufferWidth(&self) -> i32 {
        self.base.DrawingBufferWidth()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.1
    fn DrawingBufferHeight(&self) -> i32 {
        self.base.DrawingBufferHeight()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
    fn GetBufferParameter(&self, _cx: JSContext, target: u32, parameter: u32) -> JSVal {
        let buffer =
            handle_potential_webgl_error!(self.base, self.bound_buffer(target), return NullValue());
        self.base.get_buffer_param(buffer, parameter)
    }

    #[allow(unsafe_code)]
    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn GetParameter(&self, cx: JSContext, parameter: u32) -> JSVal {
        match parameter {
            constants::VERSION => unsafe {
                rooted!(in(*cx) let mut rval = UndefinedValue());
                "WebGL 2.0".to_jsval(*cx, rval.handle_mut());
                return rval.get();
            },
            constants::SHADING_LANGUAGE_VERSION => unsafe {
                rooted!(in(*cx) let mut rval = UndefinedValue());
                "WebGL GLSL ES 3.00".to_jsval(*cx, rval.handle_mut());
                return rval.get();
            },
            constants::MAX_CLIENT_WAIT_TIMEOUT_WEBGL => {
                return DoubleValue(
                    self.base.limits().max_client_wait_timeout_webgl.as_nanos() as f64
                );
            },
            constants::MAX_SERVER_WAIT_TIMEOUT => {
                return DoubleValue(self.base.limits().max_server_wait_timeout.as_nanos() as f64);
            },
            constants::SAMPLER_BINDING => unsafe {
                let idx = (self.base.textures().active_unit_enum() - constants::TEXTURE0) as usize;
                assert!(idx < self.samplers.len());
                let sampler = self.samplers[idx].get();
                return optional_root_object_to_js_or_null!(*cx, sampler);
            },
            constants::COPY_READ_BUFFER_BINDING => unsafe {
                return optional_root_object_to_js_or_null!(
                    *cx,
                    &self.bound_copy_read_buffer.get()
                );
            },
            constants::COPY_WRITE_BUFFER_BINDING => unsafe {
                return optional_root_object_to_js_or_null!(
                    *cx,
                    &self.bound_copy_write_buffer.get()
                );
            },
            constants::PIXEL_PACK_BUFFER_BINDING => unsafe {
                return optional_root_object_to_js_or_null!(
                    *cx,
                    &self.bound_pixel_pack_buffer.get()
                );
            },
            constants::PIXEL_UNPACK_BUFFER_BINDING => unsafe {
                return optional_root_object_to_js_or_null!(
                    *cx,
                    &self.bound_pixel_unpack_buffer.get()
                );
            },
            constants::TRANSFORM_FEEDBACK_BUFFER_BINDING => unsafe {
                return optional_root_object_to_js_or_null!(
                    *cx,
                    &self.bound_transform_feedback_buffer.get()
                );
            },
            constants::UNIFORM_BUFFER_BINDING => unsafe {
                return optional_root_object_to_js_or_null!(*cx, &self.bound_uniform_buffer.get());
            },
            constants::TRANSFORM_FEEDBACK_BINDING => unsafe {
                return optional_root_object_to_js_or_null!(
                    *cx,
                    self.current_transform_feedback.get()
                );
            },
            constants::ELEMENT_ARRAY_BUFFER_BINDING => unsafe {
                let buffer = self.current_vao().element_array_buffer().get();
                return optional_root_object_to_js_or_null!(*cx, buffer);
            },
            constants::VERTEX_ARRAY_BINDING => unsafe {
                let vao = self.current_vao();
                let vao = vao.id().map(|_| &*vao);
                return optional_root_object_to_js_or_null!(*cx, vao);
            },
            // NOTE: DRAW_FRAMEBUFFER_BINDING is the same as FRAMEBUFFER_BINDING, handled on the WebGL1 side
            constants::READ_FRAMEBUFFER_BINDING => unsafe {
                return optional_root_object_to_js_or_null!(
                    *cx,
                    &self.base.get_read_framebuffer_slot().get()
                );
            },
            constants::READ_BUFFER => {
                let buffer = match self.base.get_read_framebuffer_slot().get() {
                    Some(fb) => fb.read_buffer(),
                    None => self.default_fb_readbuffer.get(),
                };
                return UInt32Value(buffer);
            },
            constants::DRAW_BUFFER0..=constants::DRAW_BUFFER15 => {
                let buffer = match self.base.get_read_framebuffer_slot().get() {
                    Some(fb) => {
                        let idx = parameter - constants::DRAW_BUFFER0;
                        fb.draw_buffer_i(idx as usize)
                    },
                    None if parameter == constants::DRAW_BUFFER0 => {
                        self.default_fb_readbuffer.get()
                    },
                    None => constants::NONE,
                };
                return UInt32Value(buffer);
            },
            constants::MAX_TEXTURE_LOD_BIAS => {
                return DoubleValue(self.base.limits().max_texture_lod_bias as f64)
            },
            constants::MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS => {
                return DoubleValue(
                    self.base.limits().max_combined_fragment_uniform_components as f64,
                )
            },
            constants::MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS => {
                return DoubleValue(
                    self.base.limits().max_combined_vertex_uniform_components as f64,
                )
            },
            constants::MAX_ELEMENT_INDEX => {
                return DoubleValue(self.base.limits().max_element_index as f64)
            },
            constants::MAX_UNIFORM_BLOCK_SIZE => {
                return DoubleValue(self.base.limits().max_uniform_block_size as f64)
            },
            constants::MIN_PROGRAM_TEXEL_OFFSET => {
                return Int32Value(self.base.limits().min_program_texel_offset)
            },
            _ => {},
        }

        let limit = match parameter {
            constants::MAX_3D_TEXTURE_SIZE => Some(self.base.limits().max_3d_texture_size),
            constants::MAX_ARRAY_TEXTURE_LAYERS => {
                Some(self.base.limits().max_array_texture_layers)
            },
            constants::MAX_COLOR_ATTACHMENTS => Some(self.base.limits().max_color_attachments),
            constants::MAX_COMBINED_UNIFORM_BLOCKS => {
                Some(self.base.limits().max_combined_uniform_blocks)
            },
            constants::MAX_DRAW_BUFFERS => Some(self.base.limits().max_draw_buffers),
            constants::MAX_ELEMENTS_INDICES => Some(self.base.limits().max_elements_indices),
            constants::MAX_ELEMENTS_VERTICES => Some(self.base.limits().max_elements_vertices),
            constants::MAX_FRAGMENT_INPUT_COMPONENTS => {
                Some(self.base.limits().max_fragment_input_components)
            },
            constants::MAX_FRAGMENT_UNIFORM_BLOCKS => {
                Some(self.base.limits().max_fragment_uniform_blocks)
            },
            constants::MAX_FRAGMENT_UNIFORM_COMPONENTS => {
                Some(self.base.limits().max_fragment_uniform_components)
            },
            constants::MAX_PROGRAM_TEXEL_OFFSET => {
                Some(self.base.limits().max_program_texel_offset)
            },
            constants::MAX_SAMPLES => Some(self.base.limits().max_samples),
            constants::MAX_UNIFORM_BUFFER_BINDINGS => {
                Some(self.base.limits().max_uniform_buffer_bindings)
            },
            constants::MAX_VARYING_COMPONENTS => Some(self.base.limits().max_varying_components),
            constants::MAX_VERTEX_OUTPUT_COMPONENTS => {
                Some(self.base.limits().max_vertex_output_components)
            },
            constants::MAX_VERTEX_UNIFORM_BLOCKS => {
                Some(self.base.limits().max_vertex_uniform_blocks)
            },
            constants::MAX_VERTEX_UNIFORM_COMPONENTS => {
                Some(self.base.limits().max_vertex_uniform_components)
            },
            constants::UNIFORM_BUFFER_OFFSET_ALIGNMENT => {
                Some(self.base.limits().uniform_buffer_offset_alignment)
            },
            _ => None,
        };
        if let Some(limit) = limit {
            return UInt32Value(limit);
        }

        self.base.GetParameter(cx, parameter)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn GetTexParameter(&self, cx: JSContext, target: u32, pname: u32) -> JSVal {
        self.base.GetTexParameter(cx, target, pname)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn GetError(&self) -> u32 {
        self.base.GetError()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.2
    fn GetContextAttributes(&self) -> Option<WebGLContextAttributes> {
        self.base.GetContextAttributes()
    }

    // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.13
    fn IsContextLost(&self) -> bool {
        self.base.IsContextLost()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.14
    fn GetSupportedExtensions(&self) -> Option<Vec<DOMString>> {
        self.base.GetSupportedExtensions()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.14
    fn GetExtension(&self, cx: JSContext, name: DOMString) -> Option<NonNull<JSObject>> {
        self.base.GetExtension(cx, name)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.4
    fn GetFramebufferAttachmentParameter(
        &self,
        cx: JSContext,
        target: u32,
        attachment: u32,
        pname: u32,
    ) -> JSVal {
        let fb_slot = match target {
            constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => {
                self.base.get_draw_framebuffer_slot()
            },
            constants::READ_FRAMEBUFFER => &self.base.get_read_framebuffer_slot(),
            _ => {
                self.base.webgl_error(InvalidEnum);
                return NullValue();
            },
        };

        if let Some(fb) = fb_slot.get() {
            // A selected framebuffer is bound to the target
            handle_potential_webgl_error!(self.base, fb.validate_transparent(), return NullValue());
            handle_potential_webgl_error!(
                self.base,
                self.get_specific_fb_attachment_param(cx, &fb, target, attachment, pname),
                return NullValue()
            )
        } else {
            // The default framebuffer is bound to the target
            handle_potential_webgl_error!(
                self.base,
                self.get_default_fb_attachment_param(attachment, pname),
                return NullValue()
            )
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7
    fn GetRenderbufferParameter(&self, cx: JSContext, target: u32, pname: u32) -> JSVal {
        self.base.GetRenderbufferParameter(cx, target, pname)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn ActiveTexture(&self, texture: u32) {
        self.base.ActiveTexture(texture)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn BlendColor(&self, r: f32, g: f32, b: f32, a: f32) {
        self.base.BlendColor(r, g, b, a)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn BlendEquation(&self, mode: u32) {
        self.base.BlendEquation(mode)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn BlendEquationSeparate(&self, mode_rgb: u32, mode_alpha: u32) {
        self.base.BlendEquationSeparate(mode_rgb, mode_alpha)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn BlendFunc(&self, src_factor: u32, dest_factor: u32) {
        self.base.BlendFunc(src_factor, dest_factor)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn BlendFuncSeparate(&self, src_rgb: u32, dest_rgb: u32, src_alpha: u32, dest_alpha: u32) {
        self.base
            .BlendFuncSeparate(src_rgb, dest_rgb, src_alpha, dest_alpha)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn AttachShader(&self, program: &WebGLProgram, shader: &WebGLShader) {
        self.base.AttachShader(program, shader)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn DetachShader(&self, program: &WebGLProgram, shader: &WebGLShader) {
        self.base.DetachShader(program, shader)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn BindAttribLocation(&self, program: &WebGLProgram, index: u32, name: DOMString) {
        self.base.BindAttribLocation(program, index, name)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.2
    fn BindBuffer(&self, target: u32, buffer: Option<&WebGLBuffer>) {
        let current_vao;
        let slot = match target {
            constants::COPY_READ_BUFFER => &self.bound_copy_read_buffer,
            constants::COPY_WRITE_BUFFER => &self.bound_copy_write_buffer,
            constants::PIXEL_PACK_BUFFER => &self.bound_pixel_pack_buffer,
            constants::PIXEL_UNPACK_BUFFER => &self.bound_pixel_unpack_buffer,
            constants::TRANSFORM_FEEDBACK_BUFFER => &self.bound_transform_feedback_buffer,
            constants::UNIFORM_BUFFER => &self.bound_uniform_buffer,
            constants::ELEMENT_ARRAY_BUFFER => {
                current_vao = self.current_vao();
                current_vao.element_array_buffer()
            },
            _ => return self.base.BindBuffer(target, buffer),
        };
        self.base.bind_buffer_maybe(&slot, target, buffer);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
    fn BindFramebuffer(&self, target: u32, framebuffer: Option<&WebGLFramebuffer>) {
        handle_potential_webgl_error!(
            self.base,
            self.base.validate_new_framebuffer_binding(framebuffer),
            return
        );

        let (bind_read, bind_draw) = match target {
            constants::FRAMEBUFFER => (true, true),
            constants::READ_FRAMEBUFFER => (true, false),
            constants::DRAW_FRAMEBUFFER => (false, true),
            _ => return self.base.webgl_error(InvalidEnum),
        };
        if bind_read {
            self.base.bind_framebuffer_to(
                target,
                framebuffer,
                &self.base.get_read_framebuffer_slot(),
            );
        }
        if bind_draw {
            self.base.bind_framebuffer_to(
                target,
                framebuffer,
                &self.base.get_draw_framebuffer_slot(),
            );
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7
    fn BindRenderbuffer(&self, target: u32, renderbuffer: Option<&WebGLRenderbuffer>) {
        self.base.BindRenderbuffer(target, renderbuffer)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn BindTexture(&self, target: u32, texture: Option<&WebGLTexture>) {
        self.base.BindTexture(target, texture)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn GenerateMipmap(&self, target: u32) {
        self.base.GenerateMipmap(target)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
    fn BufferData_(&self, target: u32, data: Option<ArrayBufferViewOrArrayBuffer>, usage: u32) {
        let usage = handle_potential_webgl_error!(self.base, self.buffer_usage(usage), return);
        let bound_buffer =
            handle_potential_webgl_error!(self.base, self.bound_buffer(target), return);
        self.base.buffer_data(target, data, usage, bound_buffer)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
    fn BufferData(&self, target: u32, size: i64, usage: u32) {
        let usage = handle_potential_webgl_error!(self.base, self.buffer_usage(usage), return);
        let bound_buffer =
            handle_potential_webgl_error!(self.base, self.bound_buffer(target), return);
        self.base.buffer_data_(target, size, usage, bound_buffer)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.3
    #[allow(unsafe_code)]
    fn BufferData__(
        &self,
        target: u32,
        data: CustomAutoRooterGuard<ArrayBufferView>,
        usage: u32,
        elem_offset: u32,
        length: u32,
    ) {
        let usage = handle_potential_webgl_error!(self.base, self.buffer_usage(usage), return);
        let bound_buffer =
            handle_potential_webgl_error!(self.base, self.bound_buffer(target), return);
        let bound_buffer =
            handle_potential_webgl_error!(self.base, bound_buffer.ok_or(InvalidOperation), return);

        let elem_size = typedarray_elem_size(data.get_array_type());
        let elem_count = data.len() / elem_size;
        let elem_offset = elem_offset as usize;
        let byte_offset = elem_offset * elem_size;

        if byte_offset > data.len() {
            return self.base.webgl_error(InvalidValue);
        }

        let copy_count = if length == 0 {
            elem_count - elem_offset
        } else {
            length as usize
        };
        if copy_count == 0 {
            return;
        }
        let copy_bytes = copy_count * elem_size;

        if byte_offset + copy_bytes > data.len() {
            return self.base.webgl_error(InvalidValue);
        }

        let data_end = byte_offset + copy_bytes;
        let data: &[u8] = unsafe { &data.as_slice()[byte_offset..data_end] };
        handle_potential_webgl_error!(self.base, bound_buffer.buffer_data(target, &data, usage));
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
    fn BufferSubData(&self, target: u32, offset: i64, data: ArrayBufferViewOrArrayBuffer) {
        let bound_buffer =
            handle_potential_webgl_error!(self.base, self.bound_buffer(target), return);
        self.base
            .buffer_sub_data(target, offset, data, bound_buffer)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.3
    #[allow(unsafe_code)]
    fn BufferSubData_(
        &self,
        target: u32,
        dst_byte_offset: i64,
        src_data: CustomAutoRooterGuard<ArrayBufferView>,
        src_elem_offset: u32,
        length: u32,
    ) {
        let bound_buffer =
            handle_potential_webgl_error!(self.base, self.bound_buffer(target), return);
        let bound_buffer =
            handle_potential_webgl_error!(self.base, bound_buffer.ok_or(InvalidOperation), return);

        let src_elem_size = typedarray_elem_size(src_data.get_array_type());
        let src_elem_count = src_data.len() / src_elem_size;
        let src_elem_offset = src_elem_offset as usize;
        let src_byte_offset = src_elem_offset * src_elem_size;

        if dst_byte_offset < 0 || src_byte_offset > src_data.len() {
            return self.base.webgl_error(InvalidValue);
        }

        let copy_count = if length == 0 {
            src_elem_count - src_elem_offset
        } else {
            length as usize
        };
        if copy_count == 0 {
            return;
        }
        let copy_bytes = copy_count * src_elem_size;

        let dst_byte_offset = dst_byte_offset as usize;
        if dst_byte_offset + copy_bytes > bound_buffer.capacity() ||
            src_byte_offset + copy_bytes > src_data.len()
        {
            return self.base.webgl_error(InvalidValue);
        }

        let (sender, receiver) = ipc::bytes_channel().unwrap();
        self.base.send_command(WebGLCommand::BufferSubData(
            target,
            dst_byte_offset as isize,
            receiver,
        ));
        let src_end = src_byte_offset + copy_bytes;
        let data: &[u8] = unsafe { &src_data.as_slice()[src_byte_offset..src_end] };
        sender.send(data).unwrap();
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.3
    fn CopyBufferSubData(
        &self,
        read_target: u32,
        write_target: u32,
        read_offset: i64,
        write_offset: i64,
        size: i64,
    ) {
        if read_offset < 0 || write_offset < 0 || size < 0 {
            return self.base.webgl_error(InvalidValue);
        }

        let read_buffer =
            handle_potential_webgl_error!(self.base, self.bound_buffer(read_target), return);
        let read_buffer =
            handle_potential_webgl_error!(self.base, read_buffer.ok_or(InvalidOperation), return);

        let write_buffer =
            handle_potential_webgl_error!(self.base, self.bound_buffer(write_target), return);
        let write_buffer =
            handle_potential_webgl_error!(self.base, write_buffer.ok_or(InvalidOperation), return);

        let read_until = read_offset + size;
        let write_until = write_offset + size;
        if read_until as usize > read_buffer.capacity() ||
            write_until as usize > write_buffer.capacity()
        {
            return self.base.webgl_error(InvalidValue);
        }

        if read_target == write_target {
            let is_separate = read_until <= write_offset || write_until <= read_offset;
            if !is_separate {
                return self.base.webgl_error(InvalidValue);
            }
        }
        let src_is_elemarray = read_buffer
            .target()
            .map_or(false, |t| t == constants::ELEMENT_ARRAY_BUFFER);
        let dst_is_elemarray = write_buffer
            .target()
            .map_or(false, |t| t == constants::ELEMENT_ARRAY_BUFFER);
        if src_is_elemarray != dst_is_elemarray {
            return self.base.webgl_error(InvalidOperation);
        }

        self.base.send_command(WebGLCommand::CopyBufferSubData(
            read_target,
            write_target,
            read_offset,
            write_offset,
            size,
        ));
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.3
    #[allow(unsafe_code)]
    fn GetBufferSubData(
        &self,
        target: u32,
        src_byte_offset: i64,
        mut dst_buffer: CustomAutoRooterGuard<ArrayBufferView>,
        dst_elem_offset: u32,
        length: u32,
    ) {
        let bound_buffer =
            handle_potential_webgl_error!(self.base, self.bound_buffer(target), return);
        let bound_buffer =
            handle_potential_webgl_error!(self.base, bound_buffer.ok_or(InvalidOperation), return);

        let dst_elem_size = typedarray_elem_size(dst_buffer.get_array_type());
        let dst_elem_count = dst_buffer.len() / dst_elem_size;
        let dst_elem_offset = dst_elem_offset as usize;
        let dst_byte_offset = dst_elem_offset * dst_elem_size;

        if src_byte_offset < 0 || dst_byte_offset > dst_buffer.len() {
            return self.base.webgl_error(InvalidValue);
        }

        let copy_count = if length == 0 {
            dst_elem_count - dst_elem_offset
        } else {
            length as usize
        };
        if copy_count == 0 {
            return;
        }
        let copy_bytes = copy_count * dst_elem_size;

        // TODO(mmatyas): Transform Feedback

        let src_byte_offset = src_byte_offset as usize;
        if src_byte_offset + copy_bytes > bound_buffer.capacity() ||
            dst_byte_offset + copy_bytes > dst_buffer.len()
        {
            return self.base.webgl_error(InvalidValue);
        }

        let (sender, receiver) = ipc::bytes_channel().unwrap();
        self.base.send_command(WebGLCommand::GetBufferSubData(
            target,
            src_byte_offset,
            copy_bytes,
            sender,
        ));
        let data = receiver.recv().unwrap();
        let dst_end = dst_byte_offset + copy_bytes;
        unsafe {
            dst_buffer.as_mut_slice()[dst_byte_offset..dst_end].copy_from_slice(&data);
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6
    #[allow(unsafe_code)]
    fn CompressedTexImage2D(
        &self,
        target: u32,
        level: i32,
        internal_format: u32,
        width: i32,
        height: i32,
        border: i32,
        pixels: CustomAutoRooterGuard<ArrayBufferView>,
        src_offset: u32,
        src_length_override: u32,
    ) {
        let mut data = unsafe { pixels.as_slice() };
        let start = src_offset as usize;
        let end = (src_offset + src_length_override) as usize;
        if start > data.len() || end > data.len() {
            self.base.webgl_error(InvalidValue);
            return;
        }
        if src_length_override != 0 {
            data = &data[start..end];
        }
        self.base.compressed_tex_image_2d(
            target,
            level,
            internal_format,
            width,
            height,
            border,
            data,
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    #[allow(unsafe_code)]
    fn CompressedTexSubImage2D(
        &self,
        target: u32,
        level: i32,
        xoffset: i32,
        yoffset: i32,
        width: i32,
        height: i32,
        format: u32,
        pixels: CustomAutoRooterGuard<ArrayBufferView>,
        src_offset: u32,
        src_length_override: u32,
    ) {
        let mut data = unsafe { pixels.as_slice() };
        let start = src_offset as usize;
        let end = (src_offset + src_length_override) as usize;
        if start > data.len() || end > data.len() {
            self.base.webgl_error(InvalidValue);
            return;
        }
        if src_length_override != 0 {
            data = &data[start..end];
        }
        self.base.compressed_tex_sub_image_2d(
            target, level, xoffset, yoffset, width, height, format, data,
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn CopyTexImage2D(
        &self,
        target: u32,
        level: i32,
        internal_format: u32,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
        border: i32,
    ) {
        self.base
            .CopyTexImage2D(target, level, internal_format, x, y, width, height, border)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn CopyTexSubImage2D(
        &self,
        target: u32,
        level: i32,
        xoffset: i32,
        yoffset: i32,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
    ) {
        self.base
            .CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11
    fn Clear(&self, mask: u32) {
        self.base.Clear(mask)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn ClearColor(&self, red: f32, green: f32, blue: f32, alpha: f32) {
        self.base.ClearColor(red, green, blue, alpha)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn ClearDepth(&self, depth: f32) {
        self.base.ClearDepth(depth)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn ClearStencil(&self, stencil: i32) {
        self.base.ClearStencil(stencil)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn ColorMask(&self, r: bool, g: bool, b: bool, a: bool) {
        self.base.ColorMask(r, g, b, a)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn CullFace(&self, mode: u32) {
        self.base.CullFace(mode)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn FrontFace(&self, mode: u32) {
        self.base.FrontFace(mode)
    }
    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn DepthFunc(&self, func: u32) {
        self.base.DepthFunc(func)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn DepthMask(&self, flag: bool) {
        self.base.DepthMask(flag)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn DepthRange(&self, near: f32, far: f32) {
        self.base.DepthRange(near, far)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn Enable(&self, cap: u32) {
        match cap {
            constants::RASTERIZER_DISCARD => {
                self.enable_rasterizer_discard.set(true);
                self.base.send_command(WebGLCommand::Enable(cap));
            },
            _ => self.base.Enable(cap),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn Disable(&self, cap: u32) {
        match cap {
            constants::RASTERIZER_DISCARD => {
                self.enable_rasterizer_discard.set(false);
                self.base.send_command(WebGLCommand::Disable(cap));
            },
            _ => self.base.Disable(cap),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn CompileShader(&self, shader: &WebGLShader) {
        self.base.CompileShader(shader)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
    fn CreateBuffer(&self) -> Option<DomRoot<WebGLBuffer>> {
        self.base.CreateBuffer()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
    fn CreateFramebuffer(&self) -> Option<DomRoot<WebGLFramebuffer>> {
        self.base.CreateFramebuffer()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7
    fn CreateRenderbuffer(&self) -> Option<DomRoot<WebGLRenderbuffer>> {
        self.base.CreateRenderbuffer()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn CreateTexture(&self) -> Option<DomRoot<WebGLTexture>> {
        self.base.CreateTexture()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn CreateProgram(&self) -> Option<DomRoot<WebGLProgram>> {
        self.base.CreateProgram()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn CreateShader(&self, shader_type: u32) -> Option<DomRoot<WebGLShader>> {
        self.base.CreateShader(shader_type)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.17
    fn CreateVertexArray(&self) -> Option<DomRoot<WebGLVertexArrayObject>> {
        self.base.create_vertex_array_webgl2()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
    fn DeleteBuffer(&self, buffer: Option<&WebGLBuffer>) {
        let buffer = match buffer {
            Some(buffer) => buffer,
            None => return,
        };
        handle_potential_webgl_error!(self.base, self.base.validate_ownership(buffer), return);
        if buffer.is_marked_for_deletion() {
            return;
        }
        self.current_vao().unbind_buffer(buffer);
        self.unbind_from(&self.base.array_buffer_slot(), &buffer);
        self.unbind_from(&self.bound_copy_read_buffer, &buffer);
        self.unbind_from(&self.bound_copy_write_buffer, &buffer);
        self.unbind_from(&self.bound_pixel_pack_buffer, &buffer);
        self.unbind_from(&self.bound_pixel_unpack_buffer, &buffer);
        self.unbind_from(&self.bound_transform_feedback_buffer, &buffer);
        self.unbind_from(&self.bound_uniform_buffer, &buffer);

        for binding in self.indexed_uniform_buffer_bindings.iter() {
            self.unbind_from(&binding.buffer, &buffer);
        }
        for binding in self.indexed_transform_feedback_buffer_bindings.iter() {
            self.unbind_from(&binding.buffer, &buffer);
        }

        buffer.mark_for_deletion(Operation::Infallible);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
    fn DeleteFramebuffer(&self, framebuffer: Option<&WebGLFramebuffer>) {
        self.base.DeleteFramebuffer(framebuffer)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7
    fn DeleteRenderbuffer(&self, renderbuffer: Option<&WebGLRenderbuffer>) {
        self.base.DeleteRenderbuffer(renderbuffer)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn DeleteTexture(&self, texture: Option<&WebGLTexture>) {
        self.base.DeleteTexture(texture)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn DeleteProgram(&self, program: Option<&WebGLProgram>) {
        self.base.DeleteProgram(program)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn DeleteShader(&self, shader: Option<&WebGLShader>) {
        self.base.DeleteShader(shader)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.17
    fn DeleteVertexArray(&self, vertex_array: Option<&WebGLVertexArrayObject>) {
        self.base.delete_vertex_array_webgl2(vertex_array);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11
    fn DrawArrays(&self, mode: u32, first: i32, count: i32) {
        self.validate_uniform_block_for_draw();
        self.validate_vertex_attribs_for_draw();
        self.base.DrawArrays(mode, first, count)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11
    fn DrawElements(&self, mode: u32, count: i32, type_: u32, offset: i64) {
        self.validate_uniform_block_for_draw();
        self.validate_vertex_attribs_for_draw();
        self.base.DrawElements(mode, count, type_, offset)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn EnableVertexAttribArray(&self, attrib_id: u32) {
        self.base.EnableVertexAttribArray(attrib_id)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn DisableVertexAttribArray(&self, attrib_id: u32) {
        self.base.DisableVertexAttribArray(attrib_id)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn GetActiveUniform(
        &self,
        program: &WebGLProgram,
        index: u32,
    ) -> Option<DomRoot<WebGLActiveInfo>> {
        self.base.GetActiveUniform(program, index)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn GetActiveAttrib(
        &self,
        program: &WebGLProgram,
        index: u32,
    ) -> Option<DomRoot<WebGLActiveInfo>> {
        self.base.GetActiveAttrib(program, index)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn GetAttribLocation(&self, program: &WebGLProgram, name: DOMString) -> i32 {
        self.base.GetAttribLocation(program, name)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.7
    fn GetFragDataLocation(&self, program: &WebGLProgram, name: DOMString) -> i32 {
        handle_potential_webgl_error!(self.base, self.base.validate_ownership(program), return -1);
        handle_potential_webgl_error!(self.base, program.get_frag_data_location(name), -1)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn GetProgramInfoLog(&self, program: &WebGLProgram) -> Option<DOMString> {
        self.base.GetProgramInfoLog(program)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn GetProgramParameter(&self, cx: JSContext, program: &WebGLProgram, param_id: u32) -> JSVal {
        handle_potential_webgl_error!(
            self.base,
            self.base.validate_ownership(program),
            return NullValue()
        );
        if program.is_deleted() {
            self.base.webgl_error(InvalidOperation);
            return NullValue();
        }
        match param_id {
            constants::TRANSFORM_FEEDBACK_VARYINGS => {
                Int32Value(program.transform_feedback_varyings_length())
            },
            constants::TRANSFORM_FEEDBACK_BUFFER_MODE => {
                Int32Value(program.transform_feedback_buffer_mode())
            },
            _ => self.base.GetProgramParameter(cx, program, param_id),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn GetShaderInfoLog(&self, shader: &WebGLShader) -> Option<DOMString> {
        self.base.GetShaderInfoLog(shader)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn GetShaderParameter(&self, cx: JSContext, shader: &WebGLShader, param_id: u32) -> JSVal {
        self.base.GetShaderParameter(cx, shader, param_id)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn GetShaderPrecisionFormat(
        &self,
        shader_type: u32,
        precision_type: u32,
    ) -> Option<DomRoot<WebGLShaderPrecisionFormat>> {
        self.base
            .GetShaderPrecisionFormat(shader_type, precision_type)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.2
    #[allow(unsafe_code)]
    fn GetIndexedParameter(&self, cx: JSContext, target: u32, index: u32) -> JSVal {
        let bindings = match target {
            constants::TRANSFORM_FEEDBACK_BUFFER_BINDING |
            constants::TRANSFORM_FEEDBACK_BUFFER_SIZE |
            constants::TRANSFORM_FEEDBACK_BUFFER_START => {
                &self.indexed_transform_feedback_buffer_bindings
            },
            constants::UNIFORM_BUFFER_BINDING |
            constants::UNIFORM_BUFFER_SIZE |
            constants::UNIFORM_BUFFER_START => &self.indexed_uniform_buffer_bindings,
            _ => {
                self.base.webgl_error(InvalidEnum);
                return NullValue();
            },
        };

        let binding = match bindings.get(index as usize) {
            Some(binding) => binding,
            None => {
                self.base.webgl_error(InvalidValue);
                return NullValue();
            },
        };

        match target {
            constants::TRANSFORM_FEEDBACK_BUFFER_BINDING | constants::UNIFORM_BUFFER_BINDING => unsafe {
                optional_root_object_to_js_or_null!(*cx, binding.buffer.get())
            },
            constants::TRANSFORM_FEEDBACK_BUFFER_START | constants::UNIFORM_BUFFER_START => {
                Int32Value(binding.start.get() as _)
            },
            constants::TRANSFORM_FEEDBACK_BUFFER_SIZE | constants::UNIFORM_BUFFER_SIZE => {
                Int32Value(binding.size.get() as _)
            },
            _ => unreachable!(),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn GetUniformLocation(
        &self,
        program: &WebGLProgram,
        name: DOMString,
    ) -> Option<DomRoot<WebGLUniformLocation>> {
        self.base.GetUniformLocation(program, name)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn GetVertexAttrib(&self, cx: JSContext, index: u32, pname: u32) -> JSVal {
        self.base.GetVertexAttrib(cx, index, pname)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn GetVertexAttribOffset(&self, index: u32, pname: u32) -> i64 {
        self.base.GetVertexAttribOffset(index, pname)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn Hint(&self, target: u32, mode: u32) {
        self.base.Hint(target, mode)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
    fn IsBuffer(&self, buffer: Option<&WebGLBuffer>) -> bool {
        self.base.IsBuffer(buffer)
    }

    // TODO: We could write this without IPC, recording the calls to `enable` and `disable`.
    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.2
    fn IsEnabled(&self, cap: u32) -> bool {
        match cap {
            constants::RASTERIZER_DISCARD => self.enable_rasterizer_discard.get(),
            _ => self.base.IsEnabled(cap),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
    fn IsFramebuffer(&self, frame_buffer: Option<&WebGLFramebuffer>) -> bool {
        self.base.IsFramebuffer(frame_buffer)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn IsProgram(&self, program: Option<&WebGLProgram>) -> bool {
        self.base.IsProgram(program)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7
    fn IsRenderbuffer(&self, render_buffer: Option<&WebGLRenderbuffer>) -> bool {
        self.base.IsRenderbuffer(render_buffer)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn IsShader(&self, shader: Option<&WebGLShader>) -> bool {
        self.base.IsShader(shader)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn IsTexture(&self, texture: Option<&WebGLTexture>) -> bool {
        self.base.IsTexture(texture)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.17
    fn IsVertexArray(&self, vertex_array: Option<&WebGLVertexArrayObject>) -> bool {
        self.base.is_vertex_array_webgl2(vertex_array)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn LineWidth(&self, width: f32) {
        self.base.LineWidth(width)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.2
    fn PixelStorei(&self, param_name: u32, param_value: i32) {
        if param_value < 0 {
            return self.base.webgl_error(InvalidValue);
        }

        match param_name {
            constants::PACK_ROW_LENGTH => self.texture_pack_row_length.set(param_value as _),
            constants::PACK_SKIP_PIXELS => self.texture_pack_skip_pixels.set(param_value as _),
            constants::PACK_SKIP_ROWS => self.texture_pack_skip_rows.set(param_value as _),
            _ => self.base.PixelStorei(param_name, param_value),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn PolygonOffset(&self, factor: f32, units: f32) {
        self.base.PolygonOffset(factor, units)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.12
    fn ReadPixels(
        &self,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
        format: u32,
        pixel_type: u32,
        mut pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>,
    ) {
        let pixels =
            handle_potential_webgl_error!(self.base, pixels.as_mut().ok_or(InvalidValue), return);

        self.read_pixels_into(x, y, width, height, format, pixel_type, pixels, 0)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.10
    fn ReadPixels_(
        &self,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
        format: u32,
        pixel_type: u32,
        dst_byte_offset: i64,
    ) {
        handle_potential_webgl_error!(self.base, self.base.validate_framebuffer(), return);

        let dst = match self.bound_pixel_pack_buffer.get() {
            Some(buffer) => buffer,
            None => return self.base.webgl_error(InvalidOperation),
        };

        if dst_byte_offset < 0 {
            return self.base.webgl_error(InvalidValue);
        }
        let dst_byte_offset = dst_byte_offset as usize;
        if dst_byte_offset > dst.capacity() {
            return self.base.webgl_error(InvalidOperation);
        }

        let ReadPixelsAllowedFormats {
            array_types: _,
            channels: bytes_per_pixel,
        } = match self.calc_read_pixel_formats(pixel_type, format) {
            Ok(result) => result,
            Err(error) => return self.base.webgl_error(error),
        };
        if format != constants::RGBA || pixel_type != constants::UNSIGNED_BYTE {
            return self.base.webgl_error(InvalidOperation);
        }

        let ReadPixelsSizes {
            row_stride: _,
            skipped_bytes,
            size,
        } = match self.calc_read_pixel_sizes(width, height, bytes_per_pixel) {
            Ok(result) => result,
            Err(error) => return self.base.webgl_error(error),
        };
        let dst_end = dst_byte_offset + skipped_bytes + size;
        if dst.capacity() < dst_end {
            return self.base.webgl_error(InvalidOperation);
        }

        {
            let (fb_width, fb_height) = handle_potential_webgl_error!(
                self.base,
                self.base
                    .get_current_framebuffer_size()
                    .ok_or(InvalidOperation),
                return
            );
            let src_origin = Point2D::new(x, y);
            let src_size = Size2D::new(width as u32, height as u32);
            let fb_size = Size2D::new(fb_width as u32, fb_height as u32);
            if pixels::clip(src_origin, src_size.to_u64(), fb_size.to_u64()).is_none() {
                return;
            }
        }
        let src_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height));

        self.base.send_command(WebGLCommand::PixelStorei(
            constants::PACK_ALIGNMENT,
            self.base.get_texture_packing_alignment() as _,
        ));
        self.base.send_command(WebGLCommand::PixelStorei(
            constants::PACK_ROW_LENGTH,
            self.texture_pack_row_length.get() as _,
        ));
        self.base.send_command(WebGLCommand::PixelStorei(
            constants::PACK_SKIP_ROWS,
            self.texture_pack_skip_rows.get() as _,
        ));
        self.base.send_command(WebGLCommand::PixelStorei(
            constants::PACK_SKIP_PIXELS,
            self.texture_pack_skip_pixels.get() as _,
        ));
        self.base.send_command(WebGLCommand::ReadPixelsPP(
            src_rect,
            format,
            pixel_type,
            dst_byte_offset,
        ));
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.10
    #[allow(unsafe_code)]
    fn ReadPixels__(
        &self,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
        format: u32,
        pixel_type: u32,
        mut dst: CustomAutoRooterGuard<ArrayBufferView>,
        dst_elem_offset: u32,
    ) {
        self.read_pixels_into(
            x,
            y,
            width,
            height,
            format,
            pixel_type,
            &mut dst,
            dst_elem_offset,
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn SampleCoverage(&self, value: f32, invert: bool) {
        self.base.SampleCoverage(value, invert)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.4
    fn Scissor(&self, x: i32, y: i32, width: i32, height: i32) {
        self.base.Scissor(x, y, width, height)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn StencilFunc(&self, func: u32, ref_: i32, mask: u32) {
        self.base.StencilFunc(func, ref_, mask)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn StencilFuncSeparate(&self, face: u32, func: u32, ref_: i32, mask: u32) {
        self.base.StencilFuncSeparate(face, func, ref_, mask)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn StencilMask(&self, mask: u32) {
        self.base.StencilMask(mask)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn StencilMaskSeparate(&self, face: u32, mask: u32) {
        self.base.StencilMaskSeparate(face, mask)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn StencilOp(&self, fail: u32, zfail: u32, zpass: u32) {
        self.base.StencilOp(fail, zfail, zpass)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
    fn StencilOpSeparate(&self, face: u32, fail: u32, zfail: u32, zpass: u32) {
        self.base.StencilOpSeparate(face, fail, zfail, zpass)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn LinkProgram(&self, program: &WebGLProgram) {
        self.base.LinkProgram(program)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn ShaderSource(&self, shader: &WebGLShader, source: DOMString) {
        self.base.ShaderSource(shader, source)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn GetShaderSource(&self, shader: &WebGLShader) -> Option<DOMString> {
        self.base.GetShaderSource(shader)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform1f(&self, location: Option<&WebGLUniformLocation>, val: f32) {
        self.base.Uniform1f(location, val)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform1i(&self, location: Option<&WebGLUniformLocation>, val: i32) {
        self.base.Uniform1i(location, val)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform1iv(
        &self,
        location: Option<&WebGLUniformLocation>,
        v: Int32ArrayOrLongSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.uniform1iv(location, v, src_offset, src_length)
    }

    // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn Uniform1ui(&self, location: Option<&WebGLUniformLocation>, val: u32) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::BOOL | constants::UNSIGNED_INT => (),
                _ => return Err(InvalidOperation),
            }
            self.base
                .send_command(WebGLCommand::Uniform1ui(location.id(), val));
            Ok(())
        });
    }

    // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn Uniform1uiv(
        &self,
        location: Option<&WebGLUniformLocation>,
        val: Uint32ArrayOrUnsignedLongSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::BOOL |
                constants::UNSIGNED_INT |
                constants::SAMPLER_2D |
                constants::SAMPLER_CUBE => {},
                _ => return Err(InvalidOperation),
            }

            let val = self.uniform_vec_section_uint(val, src_offset, src_length, 1, location)?;

            match location.type_() {
                constants::SAMPLER_2D | constants::SAMPLER_CUBE => {
                    for &v in val
                        .iter()
                        .take(cmp::min(location.size().unwrap_or(1) as usize, val.len()))
                    {
                        if v >= self.base.limits().max_combined_texture_image_units {
                            return Err(InvalidValue);
                        }
                    }
                },
                _ => {},
            }
            self.base
                .send_command(WebGLCommand::Uniform1uiv(location.id(), val));
            Ok(())
        });
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform1fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        v: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.uniform1fv(location, v, src_offset, src_length);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform2f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32) {
        self.base.Uniform2f(location, x, y)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform2fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        v: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.uniform2fv(location, v, src_offset, src_length);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform2i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32) {
        self.base.Uniform2i(location, x, y)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform2iv(
        &self,
        location: Option<&WebGLUniformLocation>,
        v: Int32ArrayOrLongSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.uniform2iv(location, v, src_offset, src_length)
    }

    // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn Uniform2ui(&self, location: Option<&WebGLUniformLocation>, x: u32, y: u32) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::BOOL_VEC2 | constants::UNSIGNED_INT_VEC2 => {},
                _ => return Err(InvalidOperation),
            }
            self.base
                .send_command(WebGLCommand::Uniform2ui(location.id(), x, y));
            Ok(())
        });
    }

    // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn Uniform2uiv(
        &self,
        location: Option<&WebGLUniformLocation>,
        val: Uint32ArrayOrUnsignedLongSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::BOOL_VEC2 | constants::UNSIGNED_INT_VEC2 => {},
                _ => return Err(InvalidOperation),
            }
            let val = self.uniform_vec_section_uint(val, src_offset, src_length, 2, location)?;
            self.base
                .send_command(WebGLCommand::Uniform2uiv(location.id(), val));
            Ok(())
        });
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform3f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32, z: f32) {
        self.base.Uniform3f(location, x, y, z)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform3fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        v: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.uniform3fv(location, v, src_offset, src_length);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform3i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32, z: i32) {
        self.base.Uniform3i(location, x, y, z)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform3iv(
        &self,
        location: Option<&WebGLUniformLocation>,
        v: Int32ArrayOrLongSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.uniform3iv(location, v, src_offset, src_length)
    }

    // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn Uniform3ui(&self, location: Option<&WebGLUniformLocation>, x: u32, y: u32, z: u32) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::BOOL_VEC3 | constants::UNSIGNED_INT_VEC3 => {},
                _ => return Err(InvalidOperation),
            }
            self.base
                .send_command(WebGLCommand::Uniform3ui(location.id(), x, y, z));
            Ok(())
        });
    }

    // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn Uniform3uiv(
        &self,
        location: Option<&WebGLUniformLocation>,
        val: Uint32ArrayOrUnsignedLongSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::BOOL_VEC3 | constants::UNSIGNED_INT_VEC3 => {},
                _ => return Err(InvalidOperation),
            }
            let val = self.uniform_vec_section_uint(val, src_offset, src_length, 3, location)?;
            self.base
                .send_command(WebGLCommand::Uniform3uiv(location.id(), val));
            Ok(())
        });
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform4i(&self, location: Option<&WebGLUniformLocation>, x: i32, y: i32, z: i32, w: i32) {
        self.base.Uniform4i(location, x, y, z, w)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform4iv(
        &self,
        location: Option<&WebGLUniformLocation>,
        v: Int32ArrayOrLongSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.uniform4iv(location, v, src_offset, src_length)
    }

    // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn Uniform4ui(&self, location: Option<&WebGLUniformLocation>, x: u32, y: u32, z: u32, w: u32) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::BOOL_VEC4 | constants::UNSIGNED_INT_VEC4 => {},
                _ => return Err(InvalidOperation),
            }
            self.base
                .send_command(WebGLCommand::Uniform4ui(location.id(), x, y, z, w));
            Ok(())
        });
    }

    // https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn Uniform4uiv(
        &self,
        location: Option<&WebGLUniformLocation>,
        val: Uint32ArrayOrUnsignedLongSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::BOOL_VEC4 | constants::UNSIGNED_INT_VEC4 => {},
                _ => return Err(InvalidOperation),
            }
            let val = self.uniform_vec_section_uint(val, src_offset, src_length, 4, location)?;
            self.base
                .send_command(WebGLCommand::Uniform4uiv(location.id(), val));
            Ok(())
        });
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform4f(&self, location: Option<&WebGLUniformLocation>, x: f32, y: f32, z: f32, w: f32) {
        self.base.Uniform4f(location, x, y, z, w)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn Uniform4fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        v: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.uniform4fv(location, v, src_offset, src_length);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn UniformMatrix2fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        transpose: bool,
        v: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base
            .uniform_matrix_2fv(location, transpose, v, src_offset, src_length)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn UniformMatrix3fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        transpose: bool,
        v: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base
            .uniform_matrix_3fv(location, transpose, v, src_offset, src_length)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn UniformMatrix4fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        transpose: bool,
        v: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base
            .uniform_matrix_4fv(location, transpose, v, src_offset, src_length)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn UniformMatrix3x2fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        transpose: bool,
        val: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::FLOAT_MAT3x2 => {},
                _ => return Err(InvalidOperation),
            }
            let val = self.base.uniform_matrix_section(
                val,
                src_offset,
                src_length,
                transpose,
                3 * 2,
                location,
            )?;
            self.base
                .send_command(WebGLCommand::UniformMatrix3x2fv(location.id(), val));
            Ok(())
        });
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn UniformMatrix4x2fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        transpose: bool,
        val: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::FLOAT_MAT4x2 => {},
                _ => return Err(InvalidOperation),
            }
            let val = self.base.uniform_matrix_section(
                val,
                src_offset,
                src_length,
                transpose,
                4 * 2,
                location,
            )?;
            self.base
                .send_command(WebGLCommand::UniformMatrix4x2fv(location.id(), val));
            Ok(())
        });
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn UniformMatrix2x3fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        transpose: bool,
        val: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::FLOAT_MAT2x3 => {},
                _ => return Err(InvalidOperation),
            }
            let val = self.base.uniform_matrix_section(
                val,
                src_offset,
                src_length,
                transpose,
                2 * 3,
                location,
            )?;
            self.base
                .send_command(WebGLCommand::UniformMatrix2x3fv(location.id(), val));
            Ok(())
        });
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn UniformMatrix4x3fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        transpose: bool,
        val: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::FLOAT_MAT4x3 => {},
                _ => return Err(InvalidOperation),
            }
            let val = self.base.uniform_matrix_section(
                val,
                src_offset,
                src_length,
                transpose,
                4 * 3,
                location,
            )?;
            self.base
                .send_command(WebGLCommand::UniformMatrix4x3fv(location.id(), val));
            Ok(())
        });
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn UniformMatrix2x4fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        transpose: bool,
        val: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::FLOAT_MAT2x4 => {},
                _ => return Err(InvalidOperation),
            }
            let val = self.base.uniform_matrix_section(
                val,
                src_offset,
                src_length,
                transpose,
                2 * 4,
                location,
            )?;
            self.base
                .send_command(WebGLCommand::UniformMatrix2x4fv(location.id(), val));
            Ok(())
        });
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn UniformMatrix3x4fv(
        &self,
        location: Option<&WebGLUniformLocation>,
        transpose: bool,
        val: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
        src_length: u32,
    ) {
        self.base.with_location(location, |location| {
            match location.type_() {
                constants::FLOAT_MAT3x4 => {},
                _ => return Err(InvalidOperation),
            }
            let val = self.base.uniform_matrix_section(
                val,
                src_offset,
                src_length,
                transpose,
                3 * 4,
                location,
            )?;
            self.base
                .send_command(WebGLCommand::UniformMatrix3x4fv(location.id(), val));
            Ok(())
        });
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    #[allow(unsafe_code)]
    fn GetUniform(
        &self,
        cx: JSContext,
        program: &WebGLProgram,
        location: &WebGLUniformLocation,
    ) -> JSVal {
        handle_potential_webgl_error!(
            self.base,
            self.base.uniform_check_program(program, location),
            return NullValue()
        );

        let triple = (&*self.base, program.id(), location.id());

        match location.type_() {
            constants::UNSIGNED_INT => {
                UInt32Value(uniform_get(triple, WebGLCommand::GetUniformUint))
            },
            constants::UNSIGNED_INT_VEC2 => unsafe {
                uniform_typed::<Uint32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformUint2))
            },
            constants::UNSIGNED_INT_VEC3 => unsafe {
                uniform_typed::<Uint32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformUint3))
            },
            constants::UNSIGNED_INT_VEC4 => unsafe {
                uniform_typed::<Uint32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformUint4))
            },
            constants::FLOAT_MAT2x3 => unsafe {
                uniform_typed::<Float32>(
                    *cx,
                    &uniform_get(triple, WebGLCommand::GetUniformFloat2x3),
                )
            },
            constants::FLOAT_MAT2x4 => unsafe {
                uniform_typed::<Float32>(
                    *cx,
                    &uniform_get(triple, WebGLCommand::GetUniformFloat2x4),
                )
            },
            constants::FLOAT_MAT3x2 => unsafe {
                uniform_typed::<Float32>(
                    *cx,
                    &uniform_get(triple, WebGLCommand::GetUniformFloat3x2),
                )
            },
            constants::FLOAT_MAT3x4 => unsafe {
                uniform_typed::<Float32>(
                    *cx,
                    &uniform_get(triple, WebGLCommand::GetUniformFloat3x4),
                )
            },
            constants::FLOAT_MAT4x2 => unsafe {
                uniform_typed::<Float32>(
                    *cx,
                    &uniform_get(triple, WebGLCommand::GetUniformFloat4x2),
                )
            },
            constants::FLOAT_MAT4x3 => unsafe {
                uniform_typed::<Float32>(
                    *cx,
                    &uniform_get(triple, WebGLCommand::GetUniformFloat4x3),
                )
            },
            constants::SAMPLER_3D | constants::SAMPLER_2D_ARRAY => {
                Int32Value(uniform_get(triple, WebGLCommand::GetUniformInt))
            },
            _ => self.base.GetUniform(cx, program, location),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn UseProgram(&self, program: Option<&WebGLProgram>) {
        self.base.UseProgram(program)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn ValidateProgram(&self, program: &WebGLProgram) {
        self.base.ValidateProgram(program)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn VertexAttrib1f(&self, indx: u32, x: f32) {
        self.base.VertexAttrib1f(indx, x)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn VertexAttrib1fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) {
        self.base.VertexAttrib1fv(indx, v)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn VertexAttrib2f(&self, indx: u32, x: f32, y: f32) {
        self.base.VertexAttrib2f(indx, x, y)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn VertexAttrib2fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) {
        self.base.VertexAttrib2fv(indx, v)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn VertexAttrib3f(&self, indx: u32, x: f32, y: f32, z: f32) {
        self.base.VertexAttrib3f(indx, x, y, z)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn VertexAttrib3fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) {
        self.base.VertexAttrib3fv(indx, v)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn VertexAttrib4f(&self, indx: u32, x: f32, y: f32, z: f32, w: f32) {
        self.base.VertexAttrib4f(indx, x, y, z, w)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn VertexAttrib4fv(&self, indx: u32, v: Float32ArrayOrUnrestrictedFloatSequence) {
        self.base.VertexAttrib4fv(indx, v)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn VertexAttribI4i(&self, index: u32, x: i32, y: i32, z: i32, w: i32) {
        self.vertex_attrib_i(index, x, y, z, w)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn VertexAttribI4iv(&self, index: u32, v: Int32ArrayOrLongSequence) {
        let values = match v {
            Int32ArrayOrLongSequence::Int32Array(v) => v.to_vec(),
            Int32ArrayOrLongSequence::LongSequence(v) => v,
        };
        if values.len() < 4 {
            return self.base.webgl_error(InvalidValue);
        }
        self.vertex_attrib_i(index, values[0], values[1], values[2], values[3]);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn VertexAttribI4ui(&self, index: u32, x: u32, y: u32, z: u32, w: u32) {
        self.vertex_attrib_u(index, x, y, z, w)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn VertexAttribI4uiv(&self, index: u32, v: Uint32ArrayOrUnsignedLongSequence) {
        let values = match v {
            Uint32ArrayOrUnsignedLongSequence::Uint32Array(v) => v.to_vec(),
            Uint32ArrayOrUnsignedLongSequence::UnsignedLongSequence(v) => v,
        };
        if values.len() < 4 {
            return self.base.webgl_error(InvalidValue);
        }
        self.vertex_attrib_u(index, values[0], values[1], values[2], values[3]);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10
    fn VertexAttribPointer(
        &self,
        attrib_id: u32,
        size: i32,
        data_type: u32,
        normalized: bool,
        stride: i32,
        offset: i64,
    ) {
        self.base
            .VertexAttribPointer(attrib_id, size, data_type, normalized, stride, offset)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.8
    fn VertexAttribIPointer(&self, index: u32, size: i32, type_: u32, stride: i32, offset: i64) {
        match type_ {
            constants::BYTE |
            constants::UNSIGNED_BYTE |
            constants::SHORT |
            constants::UNSIGNED_SHORT |
            constants::INT |
            constants::UNSIGNED_INT => {},
            _ => return self.base.webgl_error(InvalidEnum),
        };
        self.base
            .VertexAttribPointer(index, size, type_, false, stride, offset)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.4
    fn Viewport(&self, x: i32, y: i32, width: i32, height: i32) {
        self.base.Viewport(x, y, width, height)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn TexImage2D(
        &self,
        target: u32,
        level: i32,
        internal_format: i32,
        width: i32,
        height: i32,
        border: i32,
        format: u32,
        data_type: u32,
        pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>,
    ) -> Fallible<()> {
        self.base.TexImage2D(
            target,
            level,
            internal_format,
            width,
            height,
            border,
            format,
            data_type,
            pixels,
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn TexImage2D_(
        &self,
        target: u32,
        level: i32,
        internal_format: i32,
        format: u32,
        data_type: u32,
        source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement,
    ) -> ErrorResult {
        self.base
            .TexImage2D_(target, level, internal_format, format, data_type, source)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6
    fn TexImage2D__(
        &self,
        target: u32,
        level: i32,
        internalformat: i32,
        width: i32,
        height: i32,
        border: i32,
        format: u32,
        type_: u32,
        pbo_offset: i64,
    ) -> Fallible<()> {
        let pixel_unpack_buffer = match self.bound_pixel_unpack_buffer.get() {
            Some(pixel_unpack_buffer) => pixel_unpack_buffer,
            None => return Ok(self.base.webgl_error(InvalidOperation)),
        };

        if let Some(tf_buffer) = self.bound_transform_feedback_buffer.get() {
            if pixel_unpack_buffer == tf_buffer {
                return Ok(self.base.webgl_error(InvalidOperation));
            }
        }

        if pbo_offset < 0 || pbo_offset as usize > pixel_unpack_buffer.capacity() {
            return Ok(self.base.webgl_error(InvalidValue));
        }

        let unpacking_alignment = self.base.texture_unpacking_alignment();

        let validator = TexImage2DValidator::new(
            &self.base,
            target,
            level,
            internalformat as u32,
            width,
            height,
            border,
            format,
            type_,
        );

        let TexImage2DValidatorResult {
            texture,
            target,
            width,
            height,
            level,
            border,
            internal_format,
            format,
            data_type,
        } = match validator.validate() {
            Ok(result) => result,
            Err(_) => return Ok(()),
        };

        self.base.tex_image_2d(
            &texture,
            target,
            data_type,
            internal_format,
            format,
            level,
            border,
            unpacking_alignment,
            Size2D::new(width, height),
            TexSource::BufferOffset(pbo_offset),
        );

        Ok(())
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6
    fn TexImage2D___(
        &self,
        target: u32,
        level: i32,
        internalformat: i32,
        width: i32,
        height: i32,
        border: i32,
        format: u32,
        type_: u32,
        source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement,
    ) -> Fallible<()> {
        if self.bound_pixel_unpack_buffer.get().is_some() {
            return Ok(self.base.webgl_error(InvalidOperation));
        }

        let validator = TexImage2DValidator::new(
            &self.base,
            target,
            level,
            internalformat as u32,
            width,
            height,
            border,
            format,
            type_,
        );

        let TexImage2DValidatorResult {
            texture,
            target,
            width: _,
            height: _,
            level,
            border,
            internal_format,
            format,
            data_type,
        } = match validator.validate() {
            Ok(result) => result,
            Err(_) => return Ok(()),
        };

        let unpacking_alignment = self.base.texture_unpacking_alignment();

        let pixels = match self.base.get_image_pixels(source)? {
            Some(pixels) => pixels,
            None => return Ok(()),
        };

        self.base.tex_image_2d(
            &texture,
            target,
            data_type,
            internal_format,
            format,
            level,
            border,
            unpacking_alignment,
            pixels.size(),
            TexSource::Pixels(pixels),
        );

        Ok(())
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6
    #[allow(unsafe_code)]
    fn TexImage2D____(
        &self,
        target: u32,
        level: i32,
        internalformat: i32,
        width: i32,
        height: i32,
        border: i32,
        format: u32,
        type_: u32,
        src_data: CustomAutoRooterGuard<ArrayBufferView>,
        src_offset: u32,
    ) -> Fallible<()> {
        if self.bound_pixel_unpack_buffer.get().is_some() {
            return Ok(self.base.webgl_error(InvalidOperation));
        }

        if type_ == constants::FLOAT_32_UNSIGNED_INT_24_8_REV {
            return Ok(self.base.webgl_error(InvalidOperation));
        }

        let validator = TexImage2DValidator::new(
            &self.base,
            target,
            level,
            internalformat as u32,
            width,
            height,
            border,
            format,
            type_,
        );

        let TexImage2DValidatorResult {
            texture,
            target,
            width,
            height,
            level,
            border,
            internal_format,
            format,
            data_type,
        } = match validator.validate() {
            Ok(result) => result,
            Err(_) => return Ok(()),
        };

        let unpacking_alignment = self.base.texture_unpacking_alignment();

        let src_elem_size = typedarray_elem_size(src_data.get_array_type());
        let src_byte_offset = src_offset as usize * src_elem_size;

        if src_data.len() <= src_byte_offset {
            return Ok(self.base.webgl_error(InvalidOperation));
        }

        let buff = IpcSharedMemory::from_bytes(unsafe { &src_data.as_slice()[src_byte_offset..] });

        let expected_byte_length = match {
            self.base.validate_tex_image_2d_data(
                width,
                height,
                format,
                data_type,
                unpacking_alignment,
                Some(&*src_data),
            )
        } {
            Ok(byte_length) => byte_length,
            Err(()) => return Ok(()),
        };

        if expected_byte_length as usize > buff.len() {
            return Ok(self.base.webgl_error(InvalidOperation));
        }

        let size = Size2D::new(width, height);

        self.base.tex_image_2d(
            &texture,
            target,
            data_type,
            internal_format,
            format,
            level,
            border,
            unpacking_alignment,
            size,
            TexSource::Pixels(TexPixels::from_array(buff, size)),
        );

        Ok(())
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn TexSubImage2D(
        &self,
        target: u32,
        level: i32,
        xoffset: i32,
        yoffset: i32,
        width: i32,
        height: i32,
        format: u32,
        data_type: u32,
        pixels: CustomAutoRooterGuard<Option<ArrayBufferView>>,
    ) -> Fallible<()> {
        self.base.TexSubImage2D(
            target, level, xoffset, yoffset, width, height, format, data_type, pixels,
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn TexSubImage2D_(
        &self,
        target: u32,
        level: i32,
        xoffset: i32,
        yoffset: i32,
        format: u32,
        data_type: u32,
        source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement,
    ) -> ErrorResult {
        self.base
            .TexSubImage2D_(target, level, xoffset, yoffset, format, data_type, source)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn TexParameterf(&self, target: u32, name: u32, value: f32) {
        self.base.TexParameterf(target, name, value)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
    fn TexParameteri(&self, target: u32, name: u32, value: i32) {
        self.base.TexParameteri(target, name, value)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
    fn CheckFramebufferStatus(&self, target: u32) -> u32 {
        let fb_slot = match target {
            constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => {
                self.base.get_draw_framebuffer_slot()
            },
            constants::READ_FRAMEBUFFER => &self.base.get_read_framebuffer_slot(),
            _ => {
                self.base.webgl_error(InvalidEnum);
                return 0;
            },
        };
        match fb_slot.get() {
            Some(fb) => fb.check_status(),
            None => constants::FRAMEBUFFER_COMPLETE,
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7
    fn RenderbufferStorage(&self, target: u32, internal_format: u32, width: i32, height: i32) {
        self.base
            .RenderbufferStorage(target, internal_format, width, height)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
    fn FramebufferRenderbuffer(
        &self,
        target: u32,
        attachment: u32,
        renderbuffertarget: u32,
        rb: Option<&WebGLRenderbuffer>,
    ) {
        if let Some(rb) = rb {
            handle_potential_webgl_error!(self.base, self.base.validate_ownership(rb), return);
        }

        let fb_slot = match target {
            constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => {
                self.base.get_draw_framebuffer_slot()
            },
            constants::READ_FRAMEBUFFER => &self.base.get_read_framebuffer_slot(),
            _ => return self.base.webgl_error(InvalidEnum),
        };

        if renderbuffertarget != constants::RENDERBUFFER {
            return self.base.webgl_error(InvalidEnum);
        }

        match fb_slot.get() {
            Some(fb) => match attachment {
                constants::DEPTH_STENCIL_ATTACHMENT => {
                    handle_potential_webgl_error!(
                        self.base,
                        fb.renderbuffer(constants::DEPTH_ATTACHMENT, rb)
                    );
                    handle_potential_webgl_error!(
                        self.base,
                        fb.renderbuffer(constants::STENCIL_ATTACHMENT, rb)
                    );
                },
                _ => handle_potential_webgl_error!(self.base, fb.renderbuffer(attachment, rb)),
            },
            None => self.base.webgl_error(InvalidOperation),
        };
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.6
    fn FramebufferTexture2D(
        &self,
        target: u32,
        attachment: u32,
        textarget: u32,
        texture: Option<&WebGLTexture>,
        level: i32,
    ) {
        if let Some(texture) = texture {
            handle_potential_webgl_error!(self.base, self.base.validate_ownership(texture), return);
        }

        let fb_slot = match target {
            constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => {
                self.base.get_draw_framebuffer_slot()
            },
            constants::READ_FRAMEBUFFER => self.base.get_read_framebuffer_slot(),
            _ => return self.base.webgl_error(InvalidEnum),
        };
        match fb_slot.get() {
            Some(fb) => handle_potential_webgl_error!(
                self.base,
                fb.texture2d(attachment, textarget, texture, level)
            ),
            None => self.base.webgl_error(InvalidOperation),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
    fn GetAttachedShaders(&self, program: &WebGLProgram) -> Option<Vec<DomRoot<WebGLShader>>> {
        self.base.GetAttachedShaders(program)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.9
    fn DrawArraysInstanced(&self, mode: u32, first: i32, count: i32, primcount: i32) {
        self.validate_uniform_block_for_draw();
        self.validate_vertex_attribs_for_draw();
        handle_potential_webgl_error!(
            self.base,
            self.base
                .draw_arrays_instanced(mode, first, count, primcount)
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.9
    fn DrawElementsInstanced(
        &self,
        mode: u32,
        count: i32,
        type_: u32,
        offset: i64,
        primcount: i32,
    ) {
        self.validate_uniform_block_for_draw();
        self.validate_vertex_attribs_for_draw();
        handle_potential_webgl_error!(
            self.base,
            self.base
                .draw_elements_instanced(mode, count, type_, offset, primcount)
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.9
    fn DrawRangeElements(
        &self,
        mode: u32,
        start: u32,
        end: u32,
        count: i32,
        type_: u32,
        offset: i64,
    ) {
        if end < start {
            self.base.webgl_error(InvalidValue);
            return;
        }
        self.validate_uniform_block_for_draw();
        self.validate_vertex_attribs_for_draw();
        handle_potential_webgl_error!(
            self.base,
            self.base
                .draw_elements_instanced(mode, count, type_, offset, 1)
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.9
    fn VertexAttribDivisor(&self, index: u32, divisor: u32) {
        self.base.vertex_attrib_divisor(index, divisor);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12
    fn CreateQuery(&self) -> Option<DomRoot<WebGLQuery>> {
        Some(WebGLQuery::new(&self.base))
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12
    #[cfg_attr(rustfmt, rustfmt_skip)]
    fn DeleteQuery(&self, query: Option<&WebGLQuery>) {
        if let Some(query) = query {
            handle_potential_webgl_error!(self.base, self.base.validate_ownership(query), return);

            if let Some(query_target) = query.target() {
                let slot = match query_target {
                    constants::ANY_SAMPLES_PASSED |
                    constants::ANY_SAMPLES_PASSED_CONSERVATIVE => {
                        &self.occlusion_query
                    },
                    constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => {
                        &self.primitives_query
                    },
                    _ => unreachable!(),
                };
                if let Some(stored_query) = slot.get() {
                    if stored_query.target() == query.target() {
                        slot.set(None);
                    }
                }
            }

            query.delete(Operation::Infallible);
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12
    fn IsQuery(&self, query: Option<&WebGLQuery>) -> bool {
        match query {
            Some(query) => self.base.validate_ownership(query).is_ok() && query.is_valid(),
            None => false,
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13
    fn CreateSampler(&self) -> Option<DomRoot<WebGLSampler>> {
        Some(WebGLSampler::new(&self.base))
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13
    fn DeleteSampler(&self, sampler: Option<&WebGLSampler>) {
        if let Some(sampler) = sampler {
            handle_potential_webgl_error!(self.base, self.base.validate_ownership(sampler), return);
            for slot in self.samplers.iter() {
                if slot.get().map_or(false, |s| sampler == &*s) {
                    slot.set(None);
                }
            }
            sampler.delete(Operation::Infallible);
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13
    fn IsSampler(&self, sampler: Option<&WebGLSampler>) -> bool {
        match sampler {
            Some(sampler) => self.base.validate_ownership(sampler).is_ok() && sampler.is_valid(),
            None => false,
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12
    #[cfg_attr(rustfmt, rustfmt_skip)]
    fn BeginQuery(&self, target: u32, query: &WebGLQuery) {
        handle_potential_webgl_error!(self.base, self.base.validate_ownership(query), return);

        let active_query = match target {
            constants::ANY_SAMPLES_PASSED |
            constants::ANY_SAMPLES_PASSED_CONSERVATIVE => {
                &self.occlusion_query
            },
            constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => {
                &self.primitives_query
            },
            _ => {
                self.base.webgl_error(InvalidEnum);
                return;
            },
        };
        if active_query.get().is_some() {
            self.base.webgl_error(InvalidOperation);
            return;
        }
        let result = query.begin(&self.base, target);
        match result {
            Ok(_) => active_query.set(Some(query)),
            Err(error) => self.base.webgl_error(error),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12
    #[cfg_attr(rustfmt, rustfmt_skip)]
    fn EndQuery(&self, target: u32) {
        let active_query = match target {
            constants::ANY_SAMPLES_PASSED |
            constants::ANY_SAMPLES_PASSED_CONSERVATIVE => {
                self.occlusion_query.take()
            },
            constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => {
                self.primitives_query.take()
            },
            _ => {
                self.base.webgl_error(InvalidEnum);
                return;
            },
        };
        match active_query {
            None => self.base.webgl_error(InvalidOperation),
            Some(query) => {
                let result = query.end(&self.base, target);
                if let Err(error) = result {
                    self.base.webgl_error(error);
                }
            },
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12
    #[cfg_attr(rustfmt, rustfmt_skip)]
    fn GetQuery(&self, target: u32, pname: u32) -> Option<DomRoot<WebGLQuery>> {
        if pname != constants::CURRENT_QUERY {
            self.base.webgl_error(InvalidEnum);
            return None;
        }
        let active_query = match target {
            constants::ANY_SAMPLES_PASSED |
            constants::ANY_SAMPLES_PASSED_CONSERVATIVE => {
                self.occlusion_query.get()
            },
            constants::TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN => {
                self.primitives_query.get()
            },
            _ => {
                self.base.webgl_error(InvalidEnum);
                None
            },
        };
        if let Some(query) = active_query.as_ref() {
            if query.target() != Some(target) {
                return None;
            }
        }
        active_query
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12
    #[cfg_attr(rustfmt, rustfmt_skip)]
    fn GetQueryParameter(&self, _cx: JSContext, query: &WebGLQuery, pname: u32) -> JSVal {
        handle_potential_webgl_error!(
            self.base,
            self.base.validate_ownership(query),
            return NullValue()
        );
        match query.get_parameter(&self.base, pname) {
            Ok(value) => match pname {
                constants::QUERY_RESULT => UInt32Value(value),
                constants::QUERY_RESULT_AVAILABLE => BooleanValue(value != 0),
                _ => unreachable!(),
            },
            Err(error) => {
                self.base.webgl_error(error);
                NullValue()
            },
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14
    fn FenceSync(&self, condition: u32, flags: u32) -> Option<DomRoot<WebGLSync>> {
        if flags != 0 {
            self.base.webgl_error(InvalidValue);
            return None;
        }
        if condition != constants::SYNC_GPU_COMMANDS_COMPLETE {
            self.base.webgl_error(InvalidEnum);
            return None;
        }

        Some(WebGLSync::new(&self.base))
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14
    fn IsSync(&self, sync: Option<&WebGLSync>) -> bool {
        match sync {
            Some(sync) => {
                if !sync.is_valid() {
                    return false;
                }
                handle_potential_webgl_error!(
                    self.base,
                    self.base.validate_ownership(sync),
                    return false
                );
                let (sender, receiver) = webgl_channel().unwrap();
                self.base
                    .send_command(WebGLCommand::IsSync(sync.id(), sender));
                receiver.recv().unwrap()
            },
            None => false,
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14
    fn ClientWaitSync(&self, sync: &WebGLSync, flags: u32, timeout: u64) -> u32 {
        if !sync.is_valid() {
            self.base.webgl_error(InvalidOperation);
            return constants::WAIT_FAILED;
        }
        handle_potential_webgl_error!(
            self.base,
            self.base.validate_ownership(sync),
            return constants::WAIT_FAILED
        );
        if flags != 0 && flags != constants::SYNC_FLUSH_COMMANDS_BIT {
            self.base.webgl_error(InvalidValue);
            return constants::WAIT_FAILED;
        }
        if timeout > self.base.limits().max_client_wait_timeout_webgl.as_nanos() as u64 {
            self.base.webgl_error(InvalidOperation);
            return constants::WAIT_FAILED;
        }

        match sync.client_wait_sync(&self.base, flags, timeout) {
            Some(status) => status,
            None => constants::WAIT_FAILED,
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14
    fn WaitSync(&self, sync: &WebGLSync, flags: u32, timeout: i64) {
        if !sync.is_valid() {
            self.base.webgl_error(InvalidOperation);
            return;
        }
        handle_potential_webgl_error!(self.base, self.base.validate_ownership(sync), return);
        if flags != 0 {
            self.base.webgl_error(InvalidValue);
            return;
        }
        if timeout != constants::TIMEOUT_IGNORED {
            self.base.webgl_error(InvalidValue);
            return;
        }

        self.base
            .send_command(WebGLCommand::WaitSync(sync.id(), flags, timeout));
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14
    fn GetSyncParameter(&self, _cx: JSContext, sync: &WebGLSync, pname: u32) -> JSVal {
        if !sync.is_valid() {
            self.base.webgl_error(InvalidOperation);
            return NullValue();
        }
        handle_potential_webgl_error!(
            self.base,
            self.base.validate_ownership(sync),
            return NullValue()
        );
        match pname {
            constants::OBJECT_TYPE | constants::SYNC_CONDITION | constants::SYNC_FLAGS => {
                let (sender, receiver) = webgl_channel().unwrap();
                self.base
                    .send_command(WebGLCommand::GetSyncParameter(sync.id(), pname, sender));
                UInt32Value(receiver.recv().unwrap())
            },
            constants::SYNC_STATUS => match sync.get_sync_status(pname, &self.base) {
                Some(status) => UInt32Value(status),
                None => UInt32Value(constants::UNSIGNALED),
            },
            _ => {
                self.base.webgl_error(InvalidEnum);
                NullValue()
            },
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14
    fn DeleteSync(&self, sync: Option<&WebGLSync>) {
        if let Some(sync) = sync {
            handle_potential_webgl_error!(self.base, self.base.validate_ownership(sync), return);
            sync.delete(Operation::Infallible);
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13
    fn BindSampler(&self, unit: u32, sampler: Option<&WebGLSampler>) {
        if let Some(sampler) = sampler {
            handle_potential_webgl_error!(self.base, self.base.validate_ownership(sampler), return);

            if unit as usize >= self.samplers.len() {
                self.base.webgl_error(InvalidValue);
                return;
            }

            let result = sampler.bind(&self.base, unit);
            match result {
                Ok(_) => self.samplers[unit as usize].set(Some(sampler)),
                Err(error) => self.base.webgl_error(error),
            }
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.17
    fn BindVertexArray(&self, array: Option<&WebGLVertexArrayObject>) {
        self.base.bind_vertex_array_webgl2(array);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13
    fn SamplerParameteri(&self, sampler: &WebGLSampler, pname: u32, param: i32) {
        handle_potential_webgl_error!(self.base, self.base.validate_ownership(sampler), return);
        let param = WebGLSamplerValue::GLenum(param as u32);
        let result = sampler.set_parameter(&self.base, pname, param);
        if let Err(error) = result {
            self.base.webgl_error(error);
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13
    fn SamplerParameterf(&self, sampler: &WebGLSampler, pname: u32, param: f32) {
        handle_potential_webgl_error!(self.base, self.base.validate_ownership(sampler), return);
        let param = WebGLSamplerValue::Float(param);
        let result = sampler.set_parameter(&self.base, pname, param);
        if let Err(error) = result {
            self.base.webgl_error(error);
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13
    fn GetSamplerParameter(&self, _cx: JSContext, sampler: &WebGLSampler, pname: u32) -> JSVal {
        handle_potential_webgl_error!(
            self.base,
            self.base.validate_ownership(sampler),
            return NullValue()
        );
        match sampler.get_parameter(&self.base, pname) {
            Ok(value) => match value {
                WebGLSamplerValue::GLenum(value) => UInt32Value(value),
                WebGLSamplerValue::Float(value) => DoubleValue(value as f64),
            },
            Err(error) => {
                self.base.webgl_error(error);
                NullValue()
            },
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15
    fn CreateTransformFeedback(&self) -> Option<DomRoot<WebGLTransformFeedback>> {
        Some(WebGLTransformFeedback::new(&self.base))
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15
    fn DeleteTransformFeedback(&self, tf: Option<&WebGLTransformFeedback>) {
        if let Some(tf) = tf {
            handle_potential_webgl_error!(self.base, self.base.validate_ownership(tf), return);
            if tf.is_active() {
                self.base.webgl_error(InvalidOperation);
                return;
            }
            tf.delete(Operation::Infallible);
            self.current_transform_feedback.set(None);
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15
    fn IsTransformFeedback(&self, tf: Option<&WebGLTransformFeedback>) -> bool {
        match tf {
            Some(tf) => {
                if !tf.is_valid() {
                    return false;
                }
                handle_potential_webgl_error!(
                    self.base,
                    self.base.validate_ownership(tf),
                    return false
                );
                let (sender, receiver) = webgl_channel().unwrap();
                self.base
                    .send_command(WebGLCommand::IsTransformFeedback(tf.id(), sender));
                receiver.recv().unwrap()
            },
            None => false,
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15
    fn BindTransformFeedback(&self, target: u32, tf: Option<&WebGLTransformFeedback>) {
        if target != constants::TRANSFORM_FEEDBACK {
            self.base.webgl_error(InvalidEnum);
            return;
        }
        match tf {
            Some(transform_feedback) => {
                handle_potential_webgl_error!(
                    self.base,
                    self.base.validate_ownership(transform_feedback),
                    return
                );
                if !transform_feedback.is_valid() {
                    self.base.webgl_error(InvalidOperation);
                    return;
                }
                if let Some(current_tf) = self.current_transform_feedback.get() {
                    if current_tf.is_active() && !current_tf.is_paused() {
                        self.base.webgl_error(InvalidOperation);
                        return;
                    }
                }
                transform_feedback.bind(&self.base, target);
                self.current_transform_feedback
                    .set(Some(transform_feedback));
            },
            None => self
                .base
                .send_command(WebGLCommand::BindTransformFeedback(target, 0)),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15
    #[allow(non_snake_case)]
    fn BeginTransformFeedback(&self, primitiveMode: u32) {
        match primitiveMode {
            constants::POINTS | constants::LINES | constants::TRIANGLES => {},
            _ => {
                self.base.webgl_error(InvalidEnum);
                return;
            },
        };
        let current_tf = match self.current_transform_feedback.get() {
            Some(current_tf) => current_tf,
            None => {
                self.base.webgl_error(InvalidOperation);
                return;
            },
        };
        if current_tf.is_active() {
            self.base.webgl_error(InvalidOperation);
            return;
        };
        let program = match self.base.current_program() {
            Some(program) => program,
            None => {
                self.base.webgl_error(InvalidOperation);
                return;
            },
        };
        if !program.is_linked() || program.transform_feedback_varyings_length() == 0 {
            self.base.webgl_error(InvalidOperation);
            return;
        };
        current_tf.begin(&self.base, primitiveMode);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15
    fn EndTransformFeedback(&self) {
        if let Some(current_tf) = self.current_transform_feedback.get() {
            if !current_tf.is_active() {
                self.base.webgl_error(InvalidOperation);
                return;
            }
            current_tf.end(&self.base);
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15
    fn ResumeTransformFeedback(&self) {
        if let Some(current_tf) = self.current_transform_feedback.get() {
            if !current_tf.is_active() || !current_tf.is_paused() {
                self.base.webgl_error(InvalidOperation);
                return;
            }
            current_tf.resume(&self.base);
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15
    fn PauseTransformFeedback(&self) {
        if let Some(current_tf) = self.current_transform_feedback.get() {
            if !current_tf.is_active() || current_tf.is_paused() {
                self.base.webgl_error(InvalidOperation);
                return;
            }
            current_tf.pause(&self.base);
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15
    #[allow(non_snake_case)]
    fn TransformFeedbackVaryings(
        &self,
        program: &WebGLProgram,
        varyings: Vec<DOMString>,
        bufferMode: u32,
    ) {
        handle_potential_webgl_error!(self.base, program.validate(), return);
        let strs = varyings
            .iter()
            .map(|name| String::from(name.to_owned()))
            .collect::<Vec<String>>();
        match bufferMode {
            constants::INTERLEAVED_ATTRIBS => {
                self.base
                    .send_command(WebGLCommand::TransformFeedbackVaryings(
                        program.id(),
                        strs,
                        bufferMode,
                    ));
            },
            constants::SEPARATE_ATTRIBS => {
                let max_tf_sp_att =
                    self.base.limits().max_transform_feedback_separate_attribs as usize;
                if strs.len() >= max_tf_sp_att {
                    self.base.webgl_error(InvalidValue);
                    return;
                }
                self.base
                    .send_command(WebGLCommand::TransformFeedbackVaryings(
                        program.id(),
                        strs,
                        bufferMode,
                    ));
            },
            _ => self.base.webgl_error(InvalidEnum),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.15
    fn GetTransformFeedbackVarying(
        &self,
        program: &WebGLProgram,
        index: u32,
    ) -> Option<DomRoot<WebGLActiveInfo>> {
        handle_potential_webgl_error!(self.base, program.validate(), return None);
        if index >= program.transform_feedback_varyings_length() as u32 {
            self.base.webgl_error(InvalidValue);
            return None;
        }

        let (sender, receiver) = webgl_channel().unwrap();
        self.base
            .send_command(WebGLCommand::GetTransformFeedbackVarying(
                program.id(),
                index,
                sender,
            ));
        let (size, ty, name) = receiver.recv().unwrap();
        Some(WebGLActiveInfo::new(
            self.base.global().as_window(),
            size,
            ty,
            DOMString::from(name),
        ))
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16
    fn BindBufferBase(&self, target: u32, index: u32, buffer: Option<&WebGLBuffer>) {
        let (generic_slot, indexed_bindings) = match target {
            constants::TRANSFORM_FEEDBACK_BUFFER => (
                &self.bound_transform_feedback_buffer,
                &self.indexed_transform_feedback_buffer_bindings,
            ),
            constants::UNIFORM_BUFFER => (
                &self.bound_uniform_buffer,
                &self.indexed_uniform_buffer_bindings,
            ),
            _ => return self.base.webgl_error(InvalidEnum),
        };
        let indexed_binding = match indexed_bindings.get(index as usize) {
            Some(slot) => slot,
            None => return self.base.webgl_error(InvalidValue),
        };

        if let Some(buffer) = buffer {
            handle_potential_webgl_error!(self.base, self.base.validate_ownership(buffer), return);

            if buffer.is_marked_for_deletion() {
                return self.base.webgl_error(InvalidOperation);
            }
            handle_potential_webgl_error!(self.base, buffer.set_target_maybe(target), return);

            // for both the generic and the indexed bindings
            buffer.increment_attached_counter();
            buffer.increment_attached_counter();
        }

        self.base.send_command(WebGLCommand::BindBufferBase(
            target,
            index,
            buffer.map(|b| b.id()),
        ));

        for slot in &[&generic_slot, &indexed_binding.buffer] {
            if let Some(old) = slot.get() {
                old.decrement_attached_counter(Operation::Infallible);
            }
            slot.set(buffer);
        }
        indexed_binding.start.set(0);
        indexed_binding.size.set(0);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16
    fn BindBufferRange(
        &self,
        target: u32,
        index: u32,
        buffer: Option<&WebGLBuffer>,
        offset: i64,
        size: i64,
    ) {
        let (generic_slot, indexed_bindings) = match target {
            constants::TRANSFORM_FEEDBACK_BUFFER => (
                &self.bound_transform_feedback_buffer,
                &self.indexed_transform_feedback_buffer_bindings,
            ),
            constants::UNIFORM_BUFFER => (
                &self.bound_uniform_buffer,
                &self.indexed_uniform_buffer_bindings,
            ),
            _ => return self.base.webgl_error(InvalidEnum),
        };
        let indexed_binding = match indexed_bindings.get(index as usize) {
            Some(slot) => slot,
            None => return self.base.webgl_error(InvalidValue),
        };

        if offset < 0 || size < 0 {
            return self.base.webgl_error(InvalidValue);
        }
        if buffer.is_some() && size == 0 {
            return self.base.webgl_error(InvalidValue);
        }

        match target {
            constants::TRANSFORM_FEEDBACK_BUFFER => {
                if size % 4 != 0 && offset % 4 != 0 {
                    return self.base.webgl_error(InvalidValue);
                }
            },
            constants::UNIFORM_BUFFER => {
                let offset_alignment = self.base.limits().uniform_buffer_offset_alignment;
                if offset % offset_alignment as i64 != 0 {
                    return self.base.webgl_error(InvalidValue);
                }
            },
            _ => unreachable!(),
        }

        if let Some(buffer) = buffer {
            handle_potential_webgl_error!(self.base, self.base.validate_ownership(buffer), return);

            if buffer.is_marked_for_deletion() {
                return self.base.webgl_error(InvalidOperation);
            }
            handle_potential_webgl_error!(self.base, buffer.set_target_maybe(target), return);

            // for both the generic and the indexed bindings
            buffer.increment_attached_counter();
            buffer.increment_attached_counter();
        }

        self.base.send_command(WebGLCommand::BindBufferRange(
            target,
            index,
            buffer.map(|b| b.id()),
            offset,
            size,
        ));

        for slot in &[&generic_slot, &indexed_binding.buffer] {
            if let Some(old) = slot.get() {
                old.decrement_attached_counter(Operation::Infallible);
            }
            slot.set(buffer);
        }
        indexed_binding.start.set(offset);
        indexed_binding.size.set(size);
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16
    fn GetUniformIndices(&self, program: &WebGLProgram, names: Vec<DOMString>) -> Option<Vec<u32>> {
        handle_potential_webgl_error!(
            self.base,
            self.base.validate_ownership(program),
            return None
        );
        let indices = handle_potential_webgl_error!(
            self.base,
            program.get_uniform_indices(names),
            return None
        );
        Some(indices)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16
    #[allow(unsafe_code)]
    fn GetActiveUniforms(
        &self,
        cx: JSContext,
        program: &WebGLProgram,
        indices: Vec<u32>,
        pname: u32,
    ) -> JSVal {
        handle_potential_webgl_error!(
            self.base,
            self.base.validate_ownership(program),
            return NullValue()
        );
        let values = handle_potential_webgl_error!(
            self.base,
            program.get_active_uniforms(indices, pname),
            return NullValue()
        );

        rooted!(in(*cx) let mut rval = UndefinedValue());
        match pname {
            constants::UNIFORM_SIZE |
            constants::UNIFORM_TYPE |
            constants::UNIFORM_BLOCK_INDEX |
            constants::UNIFORM_OFFSET |
            constants::UNIFORM_ARRAY_STRIDE |
            constants::UNIFORM_MATRIX_STRIDE => unsafe {
                values.to_jsval(*cx, rval.handle_mut());
            },
            constants::UNIFORM_IS_ROW_MAJOR => unsafe {
                let values = values.iter().map(|&v| v != 0).collect::<Vec<_>>();
                values.to_jsval(*cx, rval.handle_mut());
            },
            _ => unreachable!(),
        }
        rval.get()
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16
    fn GetUniformBlockIndex(&self, program: &WebGLProgram, block_name: DOMString) -> u32 {
        handle_potential_webgl_error!(
            self.base,
            self.base.validate_ownership(program),
            return constants::INVALID_INDEX
        );
        let index = handle_potential_webgl_error!(
            self.base,
            program.get_uniform_block_index(block_name),
            return constants::INVALID_INDEX
        );
        index
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16
    #[allow(unsafe_code)]
    fn GetActiveUniformBlockParameter(
        &self,
        cx: JSContext,
        program: &WebGLProgram,
        block_index: u32,
        pname: u32,
    ) -> JSVal {
        handle_potential_webgl_error!(
            self.base,
            self.base.validate_ownership(program),
            return NullValue()
        );
        let values = handle_potential_webgl_error!(
            self.base,
            program.get_active_uniform_block_parameter(block_index, pname),
            return NullValue()
        );
        match pname {
            constants::UNIFORM_BLOCK_BINDING |
            constants::UNIFORM_BLOCK_DATA_SIZE |
            constants::UNIFORM_BLOCK_ACTIVE_UNIFORMS => {
                assert!(values.len() == 1);
                UInt32Value(values[0] as u32)
            },
            constants::UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES => unsafe {
                let values = values.iter().map(|&v| v as u32).collect::<Vec<_>>();
                rooted!(in(*cx) let mut result = ptr::null_mut::<JSObject>());
                let _ = Uint32Array::create(*cx, CreateWith::Slice(&values), result.handle_mut())
                    .unwrap();
                ObjectValue(result.get())
            },
            constants::UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER |
            constants::UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER => {
                assert!(values.len() == 1);
                BooleanValue(values[0] != 0)
            },
            _ => unreachable!(),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16
    fn GetActiveUniformBlockName(
        &self,
        program: &WebGLProgram,
        block_index: u32,
    ) -> Option<DOMString> {
        handle_potential_webgl_error!(
            self.base,
            self.base.validate_ownership(program),
            return None
        );
        let name = handle_potential_webgl_error!(
            self.base,
            program.get_active_uniform_block_name(block_index),
            return None
        );
        Some(DOMString::from(name))
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16
    fn UniformBlockBinding(&self, program: &WebGLProgram, block_index: u32, block_binding: u32) {
        handle_potential_webgl_error!(self.base, self.base.validate_ownership(program), return);

        if block_binding >= self.base.limits().max_uniform_buffer_bindings {
            return self.base.webgl_error(InvalidValue);
        }

        handle_potential_webgl_error!(
            self.base,
            program.bind_uniform_block(block_index, block_binding),
            return
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.11
    fn ClearBufferfv(
        &self,
        buffer: u32,
        draw_buffer: i32,
        values: Float32ArrayOrUnrestrictedFloatSequence,
        src_offset: u32,
    ) {
        let array = match values {
            Float32ArrayOrUnrestrictedFloatSequence::Float32Array(v) => v.to_vec(),
            Float32ArrayOrUnrestrictedFloatSequence::UnrestrictedFloatSequence(v) => v,
        };
        self.clear_buffer::<f32>(
            buffer,
            draw_buffer,
            &[constants::COLOR, constants::DEPTH],
            src_offset,
            array,
            WebGLCommand::ClearBufferfv,
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.11
    fn ClearBufferiv(
        &self,
        buffer: u32,
        draw_buffer: i32,
        values: Int32ArrayOrLongSequence,
        src_offset: u32,
    ) {
        let array = match values {
            Int32ArrayOrLongSequence::Int32Array(v) => v.to_vec(),
            Int32ArrayOrLongSequence::LongSequence(v) => v,
        };
        self.clear_buffer::<i32>(
            buffer,
            draw_buffer,
            &[constants::COLOR, constants::STENCIL],
            src_offset,
            array,
            WebGLCommand::ClearBufferiv,
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.11
    fn ClearBufferuiv(
        &self,
        buffer: u32,
        draw_buffer: i32,
        values: Uint32ArrayOrUnsignedLongSequence,
        src_offset: u32,
    ) {
        let array = match values {
            Uint32ArrayOrUnsignedLongSequence::Uint32Array(v) => v.to_vec(),
            Uint32ArrayOrUnsignedLongSequence::UnsignedLongSequence(v) => v,
        };
        self.clear_buffer::<u32>(
            buffer,
            draw_buffer,
            &[constants::COLOR],
            src_offset,
            array,
            WebGLCommand::ClearBufferuiv,
        )
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.11
    fn ClearBufferfi(&self, buffer: u32, draw_buffer: i32, depth: f32, stencil: i32) {
        if buffer != constants::DEPTH_STENCIL {
            return self.base.webgl_error(InvalidEnum);
        }

        handle_potential_webgl_error!(
            self.base,
            self.clearbuffer_array_size(buffer, draw_buffer),
            return
        );

        self.base.send_command(WebGLCommand::ClearBufferfi(
            buffer,
            draw_buffer,
            depth,
            stencil,
        ));
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.4
    fn InvalidateFramebuffer(&self, target: u32, attachments: Vec<u32>) {
        if !self.valid_fb_attachment_values(target, &attachments) {
            return;
        }

        self.base
            .send_command(WebGLCommand::InvalidateFramebuffer(target, attachments))
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.4
    fn InvalidateSubFramebuffer(
        &self,
        target: u32,
        attachments: Vec<u32>,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
    ) {
        if !self.valid_fb_attachment_values(target, &attachments) {
            return;
        }

        if width < 0 || height < 0 {
            return self.base.webgl_error(InvalidValue);
        }

        self.base
            .send_command(WebGLCommand::InvalidateSubFramebuffer(
                target,
                attachments,
                x,
                y,
                width,
                height,
            ))
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.4
    fn FramebufferTextureLayer(
        &self,
        target: u32,
        attachment: u32,
        texture: Option<&WebGLTexture>,
        level: i32,
        layer: i32,
    ) {
        if let Some(tex) = texture {
            handle_potential_webgl_error!(self.base, self.base.validate_ownership(tex), return);
        }

        let fb_slot = match target {
            constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => {
                self.base.get_draw_framebuffer_slot()
            },
            constants::READ_FRAMEBUFFER => self.base.get_read_framebuffer_slot(),
            _ => return self.base.webgl_error(InvalidEnum),
        };

        match fb_slot.get() {
            Some(fb) => handle_potential_webgl_error!(
                self.base,
                fb.texture_layer(attachment, texture, level, layer)
            ),
            None => self.base.webgl_error(InvalidOperation),
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.5
    #[allow(unsafe_code)]
    fn GetInternalformatParameter(
        &self,
        cx: JSContext,
        target: u32,
        internal_format: u32,
        pname: u32,
    ) -> JSVal {
        if target != constants::RENDERBUFFER {
            self.base.webgl_error(InvalidEnum);
            return NullValue();
        }

        match handle_potential_webgl_error!(
            self.base,
            InternalFormatParameter::from_u32(pname),
            return NullValue()
        ) {
            InternalFormatParameter::IntVec(param) => unsafe {
                let (sender, receiver) = webgl_channel().unwrap();
                self.base
                    .send_command(WebGLCommand::GetInternalFormatIntVec(
                        target,
                        internal_format,
                        param,
                        sender,
                    ));

                rooted!(in(*cx) let mut rval = ptr::null_mut::<JSObject>());
                let _ = Int32Array::create(
                    *cx,
                    CreateWith::Slice(&receiver.recv().unwrap()),
                    rval.handle_mut(),
                )
                .unwrap();
                ObjectValue(rval.get())
            },
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.5
    fn RenderbufferStorageMultisample(
        &self,
        target: u32,
        samples: i32,
        internal_format: u32,
        width: i32,
        height: i32,
    ) {
        self.base
            .renderbuffer_storage(target, samples, internal_format, width, height)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.4
    fn ReadBuffer(&self, src: u32) {
        match src {
            constants::BACK | constants::NONE => {},
            _ if self.base.valid_color_attachment_enum(src) => {},
            _ => return self.base.webgl_error(InvalidEnum),
        }

        if let Some(fb) = self.base.get_read_framebuffer_slot().get() {
            handle_potential_webgl_error!(self.base, fb.set_read_buffer(src), return)
        } else {
            match src {
                constants::NONE | constants::BACK => {},
                _ => return self.base.webgl_error(InvalidOperation),
            }

            self.default_fb_readbuffer.set(src);
            self.base.send_command(WebGLCommand::ReadBuffer(src));
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.11
    fn DrawBuffers(&self, buffers: Vec<u32>) {
        if let Some(fb) = self.base.get_draw_framebuffer_slot().get() {
            handle_potential_webgl_error!(self.base, fb.set_draw_buffers(buffers), return)
        } else {
            if buffers.len() != 1 {
                return self.base.webgl_error(InvalidOperation);
            }

            match buffers[0] {
                constants::NONE | constants::BACK => {},
                _ => return self.base.webgl_error(InvalidOperation),
            }

            self.default_fb_drawbuffer.set(buffers[0]);
            self.base.send_command(WebGLCommand::DrawBuffers(buffers));
        }
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6
    fn TexStorage2D(
        &self,
        target: u32,
        levels: i32,
        internal_format: u32,
        width: i32,
        height: i32,
    ) {
        self.tex_storage(2, target, levels, internal_format, width, height, 1)
    }

    /// https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.6
    fn TexStorage3D(
        &self,
        target: u32,
        levels: i32,
        internal_format: u32,
        width: i32,
        height: i32,
        depth: i32,
    ) {
        self.tex_storage(3, target, levels, internal_format, width, height, depth)
    }
}

impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, WebGL2RenderingContext> {
    #[allow(unsafe_code)]
    unsafe fn canvas_data_source(self) -> HTMLCanvasDataSource {
        let this = &*self.unsafe_get();
        (*this.base.to_layout().unsafe_get()).layout_handle()
    }
}