From b50711a2d7f9bbe7d1baec19d6a78f2b727be8e4 Mon Sep 17 00:00:00 2001 From: wengxiangmin <157215725@qq.com> Date: Wed, 6 Nov 2024 18:12:24 +0800 Subject: [PATCH] feat: warnings are printed only when debug is enabled (close #106) --- src/clone-canvas.ts | 8 +++--- src/clone-element.ts | 4 +-- src/clone-iframe.ts | 3 +-- src/clone-video.ts | 12 ++++----- src/converts/dom-to-foreign-object-svg.ts | 3 +-- src/copy-pseudo-class.ts | 4 +-- src/create-context.ts | 5 ++-- src/create-logger.ts | 8 +++--- src/css-url.ts | 4 +-- src/destroy-context.ts | 3 +-- src/embed-web-font.ts | 8 +++--- src/fetch.ts | 4 +-- src/get-default-style.ts | 32 +++++------------------ src/image-to-canvas.ts | 6 ++--- src/sandbox.ts | 26 ++++++++++++++++++ src/utils.ts | 20 ++++++-------- 16 files changed, 76 insertions(+), 74 deletions(-) create mode 100644 src/sandbox.ts diff --git a/src/clone-canvas.ts b/src/clone-canvas.ts index b1996e2..6ed06a6 100644 --- a/src/clone-canvas.ts +++ b/src/clone-canvas.ts @@ -1,7 +1,9 @@ -import { consoleWarn, createImage } from './utils' +import type { Context } from './context' +import { createImage } from './utils' export function cloneCanvas( canvas: T, + context: Context, ): HTMLCanvasElement | HTMLImageElement { if (canvas.ownerDocument) { try { @@ -11,7 +13,7 @@ export function cloneCanvas( } } catch (error) { - consoleWarn('Failed to clone canvas', error) + context.log.warn('Failed to clone canvas', error) } } @@ -30,7 +32,7 @@ export function cloneCanvas( return cloned } catch (error) { - consoleWarn('Failed to clone canvas', error) + context.log.warn('Failed to clone canvas', error) } return cloned diff --git a/src/clone-element.ts b/src/clone-element.ts index e2aead4..d20ca02 100644 --- a/src/clone-element.ts +++ b/src/clone-element.ts @@ -15,7 +15,7 @@ export function cloneElement( context: Context, ): (HTMLElement | SVGElement) | Promise { if (isCanvasElement(node)) { - return cloneCanvas(node) + return cloneCanvas(node, context) } if (isIFrameElement(node)) { @@ -27,7 +27,7 @@ export function cloneElement( } if (isVideoElement(node)) { - return cloneVideo(node) + return cloneVideo(node, context) } return node.cloneNode(false) as T diff --git a/src/clone-iframe.ts b/src/clone-iframe.ts index 689f32a..e5dabdb 100644 --- a/src/clone-iframe.ts +++ b/src/clone-iframe.ts @@ -1,6 +1,5 @@ import type { Context } from './context' import { cloneNode } from './clone-node' -import { consoleWarn } from './utils' export function cloneIframe( iframe: T, @@ -12,7 +11,7 @@ export function cloneIframe( } } catch (error) { - consoleWarn('Failed to clone iframe', error) + context.log.warn('Failed to clone iframe', error) } return iframe.cloneNode(false) as HTMLIFrameElement diff --git a/src/clone-video.ts b/src/clone-video.ts index 3fd6ffb..ad50497 100644 --- a/src/clone-video.ts +++ b/src/clone-video.ts @@ -1,8 +1,10 @@ +import type { Context } from './context' import { cloneCanvas } from './clone-canvas' -import { consoleWarn, createImage, loadMedia } from './utils' +import { createImage, loadMedia } from './utils' export async function cloneVideo( video: T, + context: Context, ): Promise { if ( video.ownerDocument @@ -22,9 +24,7 @@ export async function cloneVideo( const ownerDocument = cloned.ownerDocument if (ownerDocument) { let canPlay = true - await loadMedia(cloned, { - onError: () => canPlay = false, - }) + await loadMedia(cloned, { onError: () => canPlay = false, onWarn: context.log.warn }) if (!canPlay) { if (video.poster) { return createImage(video.poster, video.ownerDocument) @@ -44,13 +44,13 @@ export async function cloneVideo( ctx.drawImage(cloned, 0, 0, canvas.width, canvas.height) } catch (error) { - consoleWarn('Failed to clone video', error) + context.log.warn('Failed to clone video', error) if (video.poster) { return createImage(video.poster, video.ownerDocument) } return cloned } - return cloneCanvas(canvas) + return cloneCanvas(canvas, context) } return cloned diff --git a/src/converts/dom-to-foreign-object-svg.ts b/src/converts/dom-to-foreign-object-svg.ts index 3d23d07..19392ca 100644 --- a/src/converts/dom-to-foreign-object-svg.ts +++ b/src/converts/dom-to-foreign-object-svg.ts @@ -6,7 +6,6 @@ import { destroyContext } from '../destroy-context' import { embedNode } from '../embed-node' import { embedWebFont } from '../embed-web-font' import { - consoleWarn, createSvg, isElementNode, isSVGElementNode, @@ -67,7 +66,7 @@ export async function domToForeignObjectSvg(node: any, options?: any): Promise( (cloned as any).className = [(cloned as any).className, ...klasses].join(' ') } catch (err) { - consoleWarn('Failed to copyPseudoClass', err) + context.log.warn('Failed to copyPseudoClass', err) return } diff --git a/src/create-context.ts b/src/create-context.ts index c973fee..800194f 100644 --- a/src/create-context.ts +++ b/src/create-context.ts @@ -3,7 +3,6 @@ import type { Options } from './options' import { createLogger } from './create-logger' import { getDefaultRequestInit } from './get-default-request-init' import { - consoleWarn, IN_BROWSER, isContext, isElementNode, @@ -97,7 +96,7 @@ export async function createContext(node: T, options?: Options & return worker } catch (error) { - consoleWarn('Failed to new Worker', error) + context.log.warn('Failed to new Worker', error) return null } }).filter(Boolean) as any, @@ -126,7 +125,7 @@ export async function createContext(node: T, options?: Options & } context.log.time('wait until load') - await waitUntilLoad(node, context.timeout) + await waitUntilLoad(node, { timeout: context.timeout, onWarn: context.log.warn }) context.log.timeEnd('wait until load') const { width, height } = resolveBoundingBox(node, context) diff --git a/src/create-logger.ts b/src/create-logger.ts index fb4da13..a9077a6 100644 --- a/src/create-logger.ts +++ b/src/create-logger.ts @@ -1,4 +1,4 @@ -import { consoleTime, consoleTimeEnd, consoleWarn } from './utils' +import { consoleWarn, PREFIX } from './utils' export interface Logger { time: (label: string) => void @@ -8,8 +8,10 @@ export interface Logger { export function createLogger(debug: boolean): Logger { return { - time: (label: string) => debug && consoleTime(label), - timeEnd: (label: string) => debug && consoleTimeEnd(label), + // eslint-disable-next-line no-console + time: (label: string) => debug && console.time(`${PREFIX} ${label}`), + // eslint-disable-next-line no-console + timeEnd: (label: string) => debug && console.timeEnd(`${PREFIX} ${label}`), warn: (...args: any[]) => debug && consoleWarn(...args), } } diff --git a/src/css-url.ts b/src/css-url.ts index 1818de5..603b4fd 100644 --- a/src/css-url.ts +++ b/src/css-url.ts @@ -1,6 +1,6 @@ import type { Context } from './context' import { contextFetch } from './fetch' -import { consoleWarn, isDataUrl, resolveUrl } from './utils' +import { isDataUrl, resolveUrl } from './utils' export async function replaceCssUrlToDataUrl( cssText: string, @@ -24,7 +24,7 @@ export async function replaceCssUrlToDataUrl( cssText = cssText.replace(toRE(rawUrl), `$1${dataUrl}$3`) } catch (error) { - consoleWarn('Failed to fetch css data url', rawUrl, error) + context.log.warn('Failed to fetch css data url', rawUrl, error) } } diff --git a/src/destroy-context.ts b/src/destroy-context.ts index 8a6b75d..13236e8 100644 --- a/src/destroy-context.ts +++ b/src/destroy-context.ts @@ -1,5 +1,4 @@ import type { Context } from './context' -import { consoleWarn } from './utils' export function destroyContext(context: Context): void { context.ownerDocument = undefined @@ -13,7 +12,7 @@ export function destroyContext(context: Context): void { context.sandbox.remove() } catch (err) { - consoleWarn('Failed to destroyContext', err) + context.log.warn('Failed to destroyContext', err) } context.sandbox = undefined } diff --git a/src/embed-web-font.ts b/src/embed-web-font.ts index 7c299e8..bbf02e9 100644 --- a/src/embed-web-font.ts +++ b/src/embed-web-font.ts @@ -1,7 +1,7 @@ import type { Context } from './context' import { hasCssUrl, replaceCssUrlToDataUrl, URL_RE } from './css-url' import { contextFetch } from './fetch' -import { consoleWarn, isCssFontFaceRule, isCSSImportRule, resolveUrl, splitFontFamily } from './utils' +import { isCssFontFaceRule, isCSSImportRule, resolveUrl, splitFontFamily } from './utils' export async function embedWebFont( clone: T, @@ -34,7 +34,7 @@ export async function embedWebFont( return 'cssRules' in styleSheet && Boolean(styleSheet.cssRules.length) } catch (error) { - consoleWarn(`Error while reading CSS rules from ${styleSheet.href}`, error) + context.log.warn(`Error while reading CSS rules from ${styleSheet.href}`, error) return false } }) @@ -54,7 +54,7 @@ export async function embedWebFont( }) } catch (error) { - consoleWarn(`Error fetch remote css import from ${baseUrl}`, error) + context.log.warn(`Error fetch remote css import from ${baseUrl}`, error) } const replacedCssText = cssText.replace( URL_RE, @@ -70,7 +70,7 @@ export async function embedWebFont( ) } catch (error) { - consoleWarn('Error inserting rule from remote css import', { rule, error }) + context.log.warn('Error inserting rule from remote css import', { rule, error }) } } } diff --git a/src/fetch.ts b/src/fetch.ts index a3cc084..33fcc81 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -1,5 +1,5 @@ import type { Context } from './context' -import { blobToDataUrl, consoleWarn, IN_FIREFOX, IN_SAFARI } from './utils' +import { blobToDataUrl, IN_FIREFOX, IN_SAFARI } from './utils' export type BaseFetchOptions = RequestInit & { url: string @@ -122,7 +122,7 @@ export function contextFetch(context: Context, options: ContextFetchOptions): Pr requests.delete(rawUrl) if (requestType === 'image' && placeholderImage) { - consoleWarn('Failed to fetch image base64, trying to use placeholder image', url) + context.log.warn('Failed to fetch image base64, trying to use placeholder image', url) return typeof placeholderImage === 'string' ? placeholderImage : placeholderImage(imageDom!) diff --git a/src/get-default-style.ts b/src/get-default-style.ts index 77c5259..8c6516e 100644 --- a/src/get-default-style.ts +++ b/src/get-default-style.ts @@ -1,5 +1,6 @@ import type { Context } from './context' -import { consoleWarn, isSVGElementNode, uuid, XMLNS } from './utils' +import { getSandBox } from './sandbox' +import { isSVGElementNode, XMLNS } from './utils' const ignoredStyles = [ 'width', @@ -17,7 +18,7 @@ export function getDefaultStyle( pseudoElement: string | null, context: Context, ): Map { - const { defaultComputedStyles, ownerDocument } = context + const { defaultComputedStyles } = context const nodeName = node.nodeName.toLowerCase() const isSvgNode = isSVGElementNode(node) && nodeName !== 'svg' @@ -39,32 +40,11 @@ export function getDefaultStyle( if (defaultComputedStyles.has(key)) return defaultComputedStyles.get(key)! - let sandbox = context.sandbox - if (!sandbox) { - try { - if (ownerDocument) { - sandbox = ownerDocument.createElement('iframe') - sandbox.id = `__SANDBOX__-${uuid()}` - sandbox.width = '0' - sandbox.height = '0' - sandbox.style.visibility = 'hidden' - sandbox.style.position = 'fixed' - ownerDocument.body.appendChild(sandbox) - sandbox.contentWindow?.document.write('') - context.sandbox = sandbox - } - } - catch (error) { - consoleWarn('Failed to create iframe sandbox', error) - } - } - if (!sandbox) - return new Map() - - const sandboxWindow = sandbox.contentWindow + const sandbox = getSandBox(context) + const sandboxWindow = sandbox?.contentWindow if (!sandboxWindow) return new Map() - const sandboxDocument = sandboxWindow.document + const sandboxDocument = sandboxWindow?.document let root: HTMLElement | SVGSVGElement let el: Element diff --git a/src/image-to-canvas.ts b/src/image-to-canvas.ts index ab8b858..22c44d6 100644 --- a/src/image-to-canvas.ts +++ b/src/image-to-canvas.ts @@ -1,5 +1,5 @@ import type { Context } from './context' -import { consoleWarn, loadMedia } from './utils' +import { loadMedia } from './utils' export async function imageToCanvas( image: T, @@ -13,14 +13,14 @@ export async function imageToCanvas( } = context log.time('image to canvas') - const loaded = await loadMedia(image, { timeout }) + const loaded = await loadMedia(image, { timeout, onWarn: context.log.warn }) const { canvas, context2d } = createCanvas(image.ownerDocument, context) const drawImage = (): void => { try { context2d?.drawImage(loaded, 0, 0, canvas.width, canvas.height) } catch (error) { - consoleWarn('Failed to drawImage', error) + context.log.warn('Failed to drawImage', error) } } diff --git a/src/sandbox.ts b/src/sandbox.ts new file mode 100644 index 0000000..fe3f977 --- /dev/null +++ b/src/sandbox.ts @@ -0,0 +1,26 @@ +import type { Context } from './context' +import { uuid } from './utils' + +export function getSandBox(context: Context): HTMLIFrameElement | undefined { + let sandbox = context.sandbox + if (!sandbox) { + const { ownerDocument } = context + try { + if (ownerDocument) { + sandbox = ownerDocument.createElement('iframe') + sandbox.id = `__SANDBOX__-${uuid()}` + sandbox.width = '0' + sandbox.height = '0' + sandbox.style.visibility = 'hidden' + sandbox.style.position = 'fixed' + ownerDocument.body.appendChild(sandbox) + sandbox.contentWindow?.document.write('') + context.sandbox = sandbox + } + } + catch (error) { + context.log.warn('Failed to getSandBox', error) + } + } + return sandbox +} diff --git a/src/utils.ts b/src/utils.ts index dd5f8b2..f50fb84 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -39,10 +39,6 @@ export const isIFrameElement = (node: Element): node is HTMLIFrameElement => nod // Console export const consoleWarn = (...args: any[]): void => console.warn(PREFIX, ...args) -// eslint-disable-next-line no-console -export const consoleTime = (label: string): void => console.time(`${PREFIX} ${label}`) -// eslint-disable-next-line no-console -export const consoleTimeEnd = (label: string): void => console.timeEnd(`${PREFIX} ${label}`) // Supports export function supportWebp(ownerDocument?: Document): boolean { @@ -132,7 +128,6 @@ export async function canvasToBlob(canvas: HTMLCanvasElement, type = 'image/png' } catch (error) { if (SUPPORT_ATOB) { - consoleWarn('Failed canvas to blob', { type, quality }, error) return dataUrlToBlob(canvas.toDataURL(type, quality)) } throw error @@ -184,13 +179,14 @@ interface LoadMediaOptions { ownerDocument?: Document timeout?: number onError?: (error: Error) => void + onWarn?: (...args: any[]) => void } export function loadMedia(media: T, options?: LoadMediaOptions): Promise export function loadMedia(media: string, options?: LoadMediaOptions): Promise export function loadMedia(media: any, options?: LoadMediaOptions): Promise { return new Promise((resolve) => { - const { timeout, ownerDocument, onError: userOnError } = options ?? {} + const { timeout, ownerDocument, onError: userOnError, onWarn } = options ?? {} const node: Media = typeof media === 'string' ? createImage(media, getDocument(ownerDocument)) : media @@ -220,7 +216,7 @@ export function loadMedia(media: any, options?: LoadMediaOptions): Promise } const onLoadeddata = onResolve const onError = (error: any): void => { - consoleWarn( + onWarn?.( 'Failed video load', currentSrc, error, @@ -250,7 +246,7 @@ export function loadMedia(media: any, options?: LoadMediaOptions): Promise await node.decode() } catch (error) { - consoleWarn( + onWarn?.( 'Failed to decode image, trying to render anyway', node.dataset.originalSrc || currentSrc, error, @@ -261,7 +257,7 @@ export function loadMedia(media: any, options?: LoadMediaOptions): Promise } const onError = (error: any): void => { - consoleWarn( + onWarn?.( 'Failed image load', node.dataset.originalSrc || currentSrc, error, @@ -284,16 +280,16 @@ export function loadMedia(media: any, options?: LoadMediaOptions): Promise }) } -export async function waitUntilLoad(node: Node, timeout: number): Promise { +export async function waitUntilLoad(node: Node, options?: LoadMediaOptions): Promise { if (isHTMLElementNode(node)) { if (isImageElement(node) || isVideoElement(node)) { - await loadMedia(node, { timeout }) + await loadMedia(node, options) } else { await Promise.all( ['img', 'video'].flatMap((selectors) => { return Array.from(node.querySelectorAll(selectors)) - .map(el => loadMedia(el as any, { timeout })) + .map(el => loadMedia(el as any, options)) }), ) }