summaryrefslogtreecommitdiff
path: root/ext/web
diff options
context:
space:
mode:
authorLeo Kettmeir <crowlkats@toaxl.com>2024-01-22 12:08:01 +0100
committerGitHub <noreply@github.com>2024-01-22 12:08:01 +0100
commit8f767627938ef10802864419061e58a8a75db567 (patch)
tree81f61ba0f8c14fb820a72500840eb0c619d54362 /ext/web
parentb4990d1aa233db662cf22d7f872d45b3a947e0f6 (diff)
feat(web): ImageBitmap (#21898)
Diffstat (limited to 'ext/web')
-rw-r--r--ext/web/01_mimesniff.js197
-rw-r--r--ext/web/16_image_data.js216
-rw-r--r--ext/web/internal.d.ts4
-rw-r--r--ext/web/lib.deno_web.d.ts28
-rw-r--r--ext/web/lib.rs1
5 files changed, 196 insertions, 250 deletions
diff --git a/ext/web/01_mimesniff.js b/ext/web/01_mimesniff.js
index 2978a0762..6fde35b56 100644
--- a/ext/web/01_mimesniff.js
+++ b/ext/web/01_mimesniff.js
@@ -18,9 +18,14 @@ const {
SafeMapIterator,
StringPrototypeReplaceAll,
StringPrototypeToLowerCase,
+ StringPrototypeEndsWith,
+ Uint8Array,
+ TypedArrayPrototypeGetLength,
+ TypedArrayPrototypeIncludes,
} = primordials;
import {
+ assert,
collectHttpQuotedString,
collectSequenceOfCodepoints,
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
@@ -251,4 +256,194 @@ function extractMimeType(headerValues) {
return mimeType;
}
-export { essence, extractMimeType, parseMimeType, serializeMimeType };
+/**
+ * Ref: https://mimesniff.spec.whatwg.org/#xml-mime-type
+ * @param {MimeType} mimeType
+ * @returns {boolean}
+ */
+function isXML(mimeType) {
+ return StringPrototypeEndsWith(mimeType.subtype, "+xml") ||
+ essence(mimeType) === "text/xml" || essence(mimeType) === "application/xml";
+}
+
+/**
+ * Ref: https://mimesniff.spec.whatwg.org/#pattern-matching-algorithm
+ * @param {Uint8Array} input
+ * @param {Uint8Array} pattern
+ * @param {Uint8Array} mask
+ * @param {Uint8Array} ignored
+ * @returns {boolean}
+ */
+function patternMatchingAlgorithm(input, pattern, mask, ignored) {
+ assert(
+ TypedArrayPrototypeGetLength(pattern) ===
+ TypedArrayPrototypeGetLength(mask),
+ );
+
+ if (
+ TypedArrayPrototypeGetLength(input) < TypedArrayPrototypeGetLength(pattern)
+ ) {
+ return false;
+ }
+
+ let s = 0;
+ for (; s < TypedArrayPrototypeGetLength(input); s++) {
+ if (!TypedArrayPrototypeIncludes(ignored, input[s])) {
+ break;
+ }
+ }
+
+ let p = 0;
+ for (; p < TypedArrayPrototypeGetLength(pattern); p++, s++) {
+ const maskedData = input[s] & mask[p];
+ if (maskedData !== pattern[p]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+const ImageTypePatternTable = [
+ // A Windows Icon signature.
+ [
+ new Uint8Array([0x00, 0x00, 0x01, 0x00]),
+ new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF]),
+ new Uint8Array(),
+ "image/x-icon",
+ ],
+ // A Windows Cursor signature.
+ [
+ new Uint8Array([0x00, 0x00, 0x02, 0x00]),
+ new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF]),
+ new Uint8Array(),
+ "image/x-icon",
+ ],
+ // The string "BM", a BMP signature.
+ [
+ new Uint8Array([0x42, 0x4D]),
+ new Uint8Array([0xFF, 0xFF]),
+ new Uint8Array(),
+ "image/bmp",
+ ],
+ // The string "GIF87a", a GIF signature.
+ [
+ new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x37, 0x61]),
+ new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
+ new Uint8Array(),
+ "image/gif",
+ ],
+ // The string "GIF89a", a GIF signature.
+ [
+ new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61]),
+ new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
+ new Uint8Array(),
+ "image/gif",
+ ],
+ // The string "RIFF" followed by four bytes followed by the string "WEBPVP".
+ [
+ new Uint8Array([
+ 0x52,
+ 0x49,
+ 0x46,
+ 0x46,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x57,
+ 0x45,
+ 0x42,
+ 0x50,
+ 0x56,
+ 0x50,
+ ]),
+ new Uint8Array([
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ 0xFF,
+ ]),
+ new Uint8Array(),
+ "image/webp",
+ ],
+ // An error-checking byte followed by the string "PNG" followed by CR LF SUB LF, the PNG signature.
+ [
+ new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]),
+ new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
+ new Uint8Array(),
+ "image/png",
+ ],
+ // The JPEG Start of Image marker followed by the indicator byte of another marker.
+ [
+ new Uint8Array([0xFF, 0xD8, 0xFF]),
+ new Uint8Array([0xFF, 0xFF, 0xFF]),
+ new Uint8Array(),
+ "image/jpeg",
+ ],
+];
+
+/**
+ * Ref: https://mimesniff.spec.whatwg.org/#image-type-pattern-matching-algorithm
+ * @param {Uint8Array} input
+ * @returns {string | undefined}
+ */
+function imageTypePatternMatchingAlgorithm(input) {
+ for (let i = 0; i < ImageTypePatternTable.length; i++) {
+ const row = ImageTypePatternTable[i];
+ const patternMatched = patternMatchingAlgorithm(
+ input,
+ row[0],
+ row[1],
+ row[2],
+ );
+ if (patternMatched) {
+ return row[3];
+ }
+ }
+
+ return undefined;
+}
+
+/**
+ * Ref: https://mimesniff.spec.whatwg.org/#rules-for-sniffing-images-specifically
+ * @param {string} mimeTypeString
+ * @returns {string}
+ */
+function sniffImage(mimeTypeString) {
+ const mimeType = parseMimeType(mimeTypeString);
+ if (mimeType === null) {
+ return mimeTypeString;
+ }
+
+ if (isXML(mimeType)) {
+ return mimeTypeString;
+ }
+
+ const imageTypeMatched = imageTypePatternMatchingAlgorithm(
+ new TextEncoder().encode(mimeTypeString),
+ );
+ if (imageTypeMatched !== undefined) {
+ return imageTypeMatched;
+ }
+
+ return mimeTypeString;
+}
+
+export {
+ essence,
+ extractMimeType,
+ parseMimeType,
+ serializeMimeType,
+ sniffImage,
+};
diff --git a/ext/web/16_image_data.js b/ext/web/16_image_data.js
deleted file mode 100644
index 3dc6a46da..000000000
--- a/ext/web/16_image_data.js
+++ /dev/null
@@ -1,216 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-
-import { primordials } from "ext:core/mod.js";
-const {
- ObjectPrototypeIsPrototypeOf,
- SymbolFor,
- TypedArrayPrototypeGetLength,
- TypedArrayPrototypeGetSymbolToStringTag,
- Uint8ClampedArray,
-} = primordials;
-
-import * as webidl from "ext:deno_webidl/00_webidl.js";
-import { DOMException } from "ext:deno_web/01_dom_exception.js";
-import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
-
-webidl.converters["PredefinedColorSpace"] = webidl.createEnumConverter(
- "PredefinedColorSpace",
- [
- "srgb",
- "display-p3",
- ],
-);
-
-webidl.converters["ImageDataSettings"] = webidl.createDictionaryConverter(
- "ImageDataSettings",
- [
- { key: "colorSpace", converter: webidl.converters["PredefinedColorSpace"] },
- ],
-);
-
-class ImageData {
- /** @type {number} */
- #width;
- /** @type {height} */
- #height;
- /** @type {Uint8Array} */
- #data;
- /** @type {'srgb' | 'display-p3'} */
- #colorSpace;
-
- constructor(arg0, arg1, arg2 = undefined, arg3 = undefined) {
- webidl.requiredArguments(
- arguments.length,
- 2,
- 'Failed to construct "ImageData"',
- );
- this[webidl.brand] = webidl.brand;
-
- let sourceWidth;
- let sourceHeight;
- let data;
- let settings;
- const prefix = "Failed to construct 'ImageData'";
-
- // Overload: new ImageData(data, sw [, sh [, settings ] ])
- if (
- arguments.length > 3 ||
- TypedArrayPrototypeGetSymbolToStringTag(arg0) === "Uint8ClampedArray"
- ) {
- data = webidl.converters.Uint8ClampedArray(arg0, prefix, "Argument 1");
- sourceWidth = webidl.converters["unsigned long"](
- arg1,
- prefix,
- "Argument 2",
- );
- const dataLength = TypedArrayPrototypeGetLength(data);
-
- if (webidl.type(arg2) !== "Undefined") {
- sourceHeight = webidl.converters["unsigned long"](
- arg2,
- prefix,
- "Argument 3",
- );
- }
-
- settings = webidl.converters["ImageDataSettings"](
- arg3,
- prefix,
- "Argument 4",
- );
-
- if (dataLength === 0) {
- throw new DOMException(
- "Failed to construct 'ImageData': The input data has zero elements.",
- "InvalidStateError",
- );
- }
-
- if (dataLength % 4 !== 0) {
- throw new DOMException(
- "Failed to construct 'ImageData': The input data length is not a multiple of 4.",
- "InvalidStateError",
- );
- }
-
- if (sourceWidth < 1) {
- throw new DOMException(
- "Failed to construct 'ImageData': The source width is zero or not a number.",
- "IndexSizeError",
- );
- }
-
- if (webidl.type(sourceHeight) !== "Undefined" && sourceHeight < 1) {
- throw new DOMException(
- "Failed to construct 'ImageData': The source height is zero or not a number.",
- "IndexSizeError",
- );
- }
-
- if (dataLength / 4 % sourceWidth !== 0) {
- throw new DOMException(
- "Failed to construct 'ImageData': The input data length is not a multiple of (4 * width).",
- "IndexSizeError",
- );
- }
-
- if (
- webidl.type(sourceHeight) !== "Undefined" &&
- (sourceWidth * sourceHeight * 4 !== dataLength)
- ) {
- throw new DOMException(
- "Failed to construct 'ImageData': The input data length is not equal to (4 * width * height).",
- "IndexSizeError",
- );
- }
-
- if (webidl.type(sourceHeight) === "Undefined") {
- this.#height = dataLength / 4 / sourceWidth;
- } else {
- this.#height = sourceHeight;
- }
-
- this.#colorSpace = settings.colorSpace ?? "srgb";
- this.#width = sourceWidth;
- this.#data = data;
- return;
- }
-
- // Overload: new ImageData(sw, sh [, settings])
- sourceWidth = webidl.converters["unsigned long"](
- arg0,
- prefix,
- "Argument 1",
- );
- sourceHeight = webidl.converters["unsigned long"](
- arg1,
- prefix,
- "Argument 2",
- );
-
- settings = webidl.converters["ImageDataSettings"](
- arg2,
- prefix,
- "Argument 3",
- );
-
- if (sourceWidth < 1) {
- throw new DOMException(
- "Failed to construct 'ImageData': The source width is zero or not a number.",
- "IndexSizeError",
- );
- }
-
- if (sourceHeight < 1) {
- throw new DOMException(
- "Failed to construct 'ImageData': The source height is zero or not a number.",
- "IndexSizeError",
- );
- }
-
- this.#colorSpace = settings.colorSpace ?? "srgb";
- this.#width = sourceWidth;
- this.#height = sourceHeight;
- this.#data = new Uint8ClampedArray(sourceWidth * sourceHeight * 4);
- }
-
- get width() {
- webidl.assertBranded(this, ImageDataPrototype);
- return this.#width;
- }
-
- get height() {
- webidl.assertBranded(this, ImageDataPrototype);
- return this.#height;
- }
-
- get data() {
- webidl.assertBranded(this, ImageDataPrototype);
- return this.#data;
- }
-
- get colorSpace() {
- webidl.assertBranded(this, ImageDataPrototype);
- return this.#colorSpace;
- }
-
- [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
- return inspect(
- createFilteredInspectProxy({
- object: this,
- evaluate: ObjectPrototypeIsPrototypeOf(ImageDataPrototype, this),
- keys: [
- "data",
- "width",
- "height",
- "colorSpace",
- ],
- }),
- inspectOptions,
- );
- }
-}
-
-const ImageDataPrototype = ImageData.prototype;
-
-export { ImageData };
diff --git a/ext/web/internal.d.ts b/ext/web/internal.d.ts
index c980ddcee..4af04b071 100644
--- a/ext/web/internal.d.ts
+++ b/ext/web/internal.d.ts
@@ -111,7 +111,3 @@ declare module "ext:deno_web/13_message_port.js" {
transferables: Transferable[];
}
}
-
-declare module "ext:deno_web/16_image_data.js" {
- const ImageData: typeof ImageData;
-}
diff --git a/ext/web/lib.deno_web.d.ts b/ext/web/lib.deno_web.d.ts
index 67d1d10c9..55048e14e 100644
--- a/ext/web/lib.deno_web.d.ts
+++ b/ext/web/lib.deno_web.d.ts
@@ -1237,31 +1237,3 @@ declare var DecompressionStream: {
declare function reportError(
error: any,
): void;
-
-/** @category Web APIs */
-type PredefinedColorSpace = "srgb" | "display-p3";
-
-/** @category Web APIs */
-interface ImageDataSettings {
- readonly colorSpace?: PredefinedColorSpace;
-}
-
-/** @category Web APIs */
-interface ImageData {
- readonly colorSpace: PredefinedColorSpace;
- readonly data: Uint8ClampedArray;
- readonly height: number;
- readonly width: number;
-}
-
-/** @category Web APIs */
-declare var ImageData: {
- prototype: ImageData;
- new (sw: number, sh: number, settings?: ImageDataSettings): ImageData;
- new (
- data: Uint8ClampedArray,
- sw: number,
- sh?: number,
- settings?: ImageDataSettings,
- ): ImageData;
-};
diff --git a/ext/web/lib.rs b/ext/web/lib.rs
index acac78f56..2792212ae 100644
--- a/ext/web/lib.rs
+++ b/ext/web/lib.rs
@@ -117,7 +117,6 @@ deno_core::extension!(deno_web,
"13_message_port.js",
"14_compression.js",
"15_performance.js",
- "16_image_data.js",
],
options = {
blob_store: Arc<BlobStore>,