From 6e52ac2a737e68400569ea0b44504fb0e95b8adc Mon Sep 17 00:00:00 2001 From: Jake Archibald Date: Wed, 26 Aug 2020 11:41:21 +0100 Subject: [PATCH] Simpler canDecode check --- src/codecs/decoders.ts | 19 ++++++------------- src/lib/util.ts | 31 ++++++++++++++++++++++++++++--- src/{codecs => sw}/tiny.avif | Bin src/{codecs => sw}/tiny.webp | Bin src/sw/util.ts | 4 ++-- 5 files changed, 36 insertions(+), 18 deletions(-) rename src/{codecs => sw}/tiny.avif (100%) rename src/{codecs => sw}/tiny.webp (100%) diff --git a/src/codecs/decoders.ts b/src/codecs/decoders.ts index 9ac64fe9..b66be104 100644 --- a/src/codecs/decoders.ts +++ b/src/codecs/decoders.ts @@ -1,23 +1,16 @@ -import { builtinDecode, sniffMimeType, canDecodeImage } from '../lib/util'; +import { builtinDecode, sniffMimeType, canDecodeImageType } from '../lib/util'; import Processor from './processor'; -import webpDataUrl from 'url-loader!./tiny.webp'; -import avifDataUrl from 'url-loader!./tiny.avif'; - -const webPSupported = canDecodeImage(webpDataUrl); -const avifSupported = canDecodeImage(avifDataUrl); export async function decodeImage(blob: Blob, processor: Processor): Promise { const mimeType = await sniffMimeType(blob); + const canDecode = await canDecodeImageType(mimeType); try { - if (mimeType === 'image/avif' && !(await avifSupported)) { - return await processor.avifDecode(blob); + if (!canDecode) { + if (mimeType === 'image/avif') return await processor.avifDecode(blob); + if (mimeType === 'image/webp') return await processor.webpDecode(blob); + // If it's not one of those types, fall through and try built-in decoding for a laugh. } - if (mimeType === 'image/webp' && !(await webPSupported)) { - return await processor.webpDecode(blob); - } - - // Otherwise, just throw it at the browser's decoder. return await builtinDecode(blob); } catch (err) { throw Error("Couldn't decode image"); diff --git a/src/lib/util.ts b/src/lib/util.ts index 1464635e..23fc0a5b 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -78,11 +78,36 @@ async function decodeImage(url: string): Promise { return img; } +/** Caches results from canDecodeImageType */ +const canDecodeCache = new Map>(); + /** - * Attempts to load the given URL as an image. + * Tests whether the browser supports a particular image mime type. + * + * @param type Mimetype + * @example await canDecodeImageType('image/avif') */ -export function canDecodeImage(url: string): Promise { - return decodeImage(url).then(() => true, () => false); +export function canDecodeImageType(type: string): Promise { + if (!canDecodeCache.has(type)) { + const resultPromise = (async () => { + const picture = document.createElement('picture'); + const img = document.createElement('img'); + const source = document.createElement('source'); + source.srcset = 'data:,x'; + source.type = type; + picture.append(source, img); + + // Wait a single microtick just for the `img.currentSrc` to get populated. + await 0; + // At this point `img.currentSrc` will contain "data:,x" if format is supported and "" + // otherwise. + return !!img.currentSrc; + })(); + + canDecodeCache.set(type, resultPromise); + } + + return canDecodeCache.get(type)!; } export function blobToArrayBuffer(blob: Blob): Promise { diff --git a/src/codecs/tiny.avif b/src/sw/tiny.avif similarity index 100% rename from src/codecs/tiny.avif rename to src/sw/tiny.avif diff --git a/src/codecs/tiny.webp b/src/sw/tiny.webp similarity index 100% rename from src/codecs/tiny.webp rename to src/sw/tiny.webp diff --git a/src/sw/util.ts b/src/sw/util.ts index 6a5fb7cc..7cbbc651 100644 --- a/src/sw/util.ts +++ b/src/sw/util.ts @@ -1,5 +1,5 @@ -import webpDataUrl from 'url-loader!../codecs/tiny.webp'; -import avifDataUrl from 'url-loader!../codecs/tiny.avif'; +import webpDataUrl from 'url-loader!./tiny.webp'; +import avifDataUrl from 'url-loader!./tiny.avif'; // Give TypeScript the correct global. declare var self: ServiceWorkerGlobalScope;