diff options
| author | Leo Kettmeir <crowlkats@toaxl.com> | 2024-01-22 12:08:01 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-01-22 12:08:01 +0100 |
| commit | 8f767627938ef10802864419061e58a8a75db567 (patch) | |
| tree | 81f61ba0f8c14fb820a72500840eb0c619d54362 /ext/web | |
| parent | b4990d1aa233db662cf22d7f872d45b3a947e0f6 (diff) | |
feat(web): ImageBitmap (#21898)
Diffstat (limited to 'ext/web')
| -rw-r--r-- | ext/web/01_mimesniff.js | 197 | ||||
| -rw-r--r-- | ext/web/16_image_data.js | 216 | ||||
| -rw-r--r-- | ext/web/internal.d.ts | 4 | ||||
| -rw-r--r-- | ext/web/lib.deno_web.d.ts | 28 | ||||
| -rw-r--r-- | ext/web/lib.rs | 1 |
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>, |
