From 84bbe784cc16d0143169e74cf445fae4db82df8d Mon Sep 17 00:00:00 2001 From: Dustin Brett Date: Tue, 27 Dec 2022 21:58:40 -0800 Subject: [PATCH 1/2] fix: clone iframe nodes better --- src/clone-node.ts | 37 ++++++++++++++++++++++++------------- src/embed-images.ts | 17 ++++++++--------- src/util.ts | 18 ++++++++++++++++++ 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/clone-node.ts b/src/clone-node.ts index 57e2106d..99df9c3f 100644 --- a/src/clone-node.ts +++ b/src/clone-node.ts @@ -1,6 +1,6 @@ import type { Options } from './types' import { clonePseudoElements } from './clone-pseudos' -import { createImage, toArray } from './util' +import { createImage, toArray, isInstanceOfElement } from './util' import { getMimeType } from './mimes' import { resourceToDataURL } from './dataurl' @@ -49,15 +49,15 @@ async function cloneSingleNode( node: T, options: Options, ): Promise { - if (node instanceof HTMLCanvasElement) { + if (isInstanceOfElement(node, HTMLCanvasElement)) { return cloneCanvasElement(node) } - if (node instanceof HTMLVideoElement) { + if (isInstanceOfElement(node, HTMLVideoElement)) { return cloneVideoElement(node, options) } - if (node instanceof HTMLIFrameElement) { + if (isInstanceOfElement(node, HTMLIFrameElement)) { return cloneIFrameElement(node) } @@ -72,12 +72,23 @@ async function cloneChildren( clonedNode: T, options: Options, ): Promise { - const children = - isSlotElement(nativeNode) && nativeNode.assignedNodes - ? toArray(nativeNode.assignedNodes()) - : toArray((nativeNode.shadowRoot ?? nativeNode).childNodes) + let children: T[] = [] + + if (isSlotElement(nativeNode) && nativeNode.assignedNodes) { + children = toArray(nativeNode.assignedNodes()) + } else if ( + isInstanceOfElement(nativeNode, HTMLIFrameElement) && + nativeNode.contentDocument?.body + ) { + children = toArray(nativeNode.contentDocument.body.childNodes) + } else { + children = toArray((nativeNode.shadowRoot ?? nativeNode).childNodes) + } - if (children.length === 0 || nativeNode instanceof HTMLVideoElement) { + if ( + children.length === 0 || + isInstanceOfElement(nativeNode, HTMLVideoElement) + ) { return clonedNode } @@ -124,17 +135,17 @@ function cloneCSSStyle(nativeNode: T, clonedNode: T) { } function cloneInputValue(nativeNode: T, clonedNode: T) { - if (nativeNode instanceof HTMLTextAreaElement) { + if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) { clonedNode.innerHTML = nativeNode.value } - if (nativeNode instanceof HTMLInputElement) { + if (isInstanceOfElement(nativeNode, HTMLInputElement)) { clonedNode.setAttribute('value', nativeNode.value) } } function cloneSelectValue(nativeNode: T, clonedNode: T) { - if (nativeNode instanceof HTMLSelectElement) { + if (isInstanceOfElement(nativeNode, HTMLSelectElement)) { const clonedSelect = clonedNode as any as HTMLSelectElement const selectedOption = Array.from(clonedSelect.children).find( (child) => nativeNode.value === child.getAttribute('value'), @@ -147,7 +158,7 @@ function cloneSelectValue(nativeNode: T, clonedNode: T) { } function decorate(nativeNode: T, clonedNode: T): T { - if (clonedNode instanceof Element) { + if (isInstanceOfElement(clonedNode, Element)) { cloneCSSStyle(nativeNode, clonedNode) clonePseudoElements(nativeNode, clonedNode) cloneInputValue(nativeNode, clonedNode) diff --git a/src/embed-images.ts b/src/embed-images.ts index 6da82d42..76a6683a 100644 --- a/src/embed-images.ts +++ b/src/embed-images.ts @@ -1,6 +1,6 @@ import { Options } from './types' import { embedResources } from './embed-resources' -import { toArray } from './util' +import { toArray, isInstanceOfElement } from './util' import { isDataUrl, resourceToDataURL } from './dataurl' import { getMimeType } from './mimes' @@ -23,20 +23,19 @@ async function embedImageNode( clonedNode: T, options: Options, ) { + const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement) + if ( - !(clonedNode instanceof HTMLImageElement && !isDataUrl(clonedNode.src)) && + !(isImageElement && !isDataUrl(clonedNode.src)) && !( - clonedNode instanceof SVGImageElement && + isInstanceOfElement(clonedNode, SVGImageElement) && !isDataUrl(clonedNode.href.baseVal) ) ) { return } - const url = - clonedNode instanceof HTMLImageElement - ? clonedNode.src - : clonedNode.href.baseVal + const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal const dataURL = await resourceToDataURL(url, getMimeType(url), options) await new Promise((resolve, reject) => { @@ -48,7 +47,7 @@ async function embedImageNode( image.decode = resolve as any } - if (clonedNode instanceof HTMLImageElement) { + if (isImageElement) { clonedNode.srcset = '' clonedNode.src = dataURL } else { @@ -70,7 +69,7 @@ export async function embedImages( clonedNode: T, options: Options, ) { - if (clonedNode instanceof Element) { + if (isInstanceOfElement(clonedNode, Element)) { await embedBackground(clonedNode, options) await embedImageNode(clonedNode, options) await embedChildren(clonedNode, options) diff --git a/src/util.ts b/src/util.ts index 2e375735..667fbee1 100644 --- a/src/util.ts +++ b/src/util.ts @@ -223,3 +223,21 @@ export async function nodeToDataURL( return svgToDataURL(svg) } + +export const isInstanceOfElement = < + T extends typeof Element | typeof HTMLElement | typeof SVGImageElement, +>( + node: Element | HTMLElement | SVGImageElement, + instance: T, +): node is T['prototype'] => { + if (node instanceof instance) return true + + const nodePrototype = Object.getPrototypeOf(node) + + if (nodePrototype === null) return false + + return ( + nodePrototype.constructor.name === instance.name || + isInstanceOfElement(nodePrototype, instance) + ) +} From 728755cd95471f61c88aefad93041dc23559182e Mon Sep 17 00:00:00 2001 From: Dustin Brett Date: Wed, 28 Dec 2022 20:20:47 -0800 Subject: [PATCH 2/2] fix: switch cloned iframes from inline to blocks --- src/clone-node.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/clone-node.ts b/src/clone-node.ts index 99df9c3f..18752fe1 100644 --- a/src/clone-node.ts +++ b/src/clone-node.ts @@ -125,6 +125,13 @@ function cloneCSSStyle(nativeNode: T, clonedNode: T) { Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1 value = `${reducedFont}px` } + if ( + isInstanceOfElement(nativeNode, HTMLIFrameElement) && + name === 'display' && + value === 'inline' + ) { + value = 'block' + } targetStyle.setProperty( name, value,