Skip to content

Commit

Permalink
fix: clone iframe nodes better (bubkoo#352)
Browse files Browse the repository at this point in the history
* fix: clone iframe nodes better

* fix: switch cloned iframes from inline to blocks

---------

Co-authored-by: 崖 <bubkoo.wy@gmail.com>
  • Loading branch information
2 people authored and istaiti committed Feb 7, 2023
1 parent 40cccc1 commit 42bf1c5
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 22 deletions.
47 changes: 34 additions & 13 deletions src/clone-node.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -49,15 +49,15 @@ async function cloneSingleNode<T extends HTMLElement>(
node: T,
options: Options,
): Promise<HTMLElement> {
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)
}

Expand All @@ -72,12 +72,23 @@ async function cloneChildren<T extends HTMLElement>(
clonedNode: T,
options: Options,
): Promise<T> {
const children =
isSlotElement(nativeNode) && nativeNode.assignedNodes
? toArray<T>(nativeNode.assignedNodes())
: toArray<T>((nativeNode.shadowRoot ?? nativeNode).childNodes)
let children: T[] = []

if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
children = toArray<T>(nativeNode.assignedNodes())
} else if (
isInstanceOfElement(nativeNode, HTMLIFrameElement) &&
nativeNode.contentDocument?.body
) {
children = toArray<T>(nativeNode.contentDocument.body.childNodes)
} else {
children = toArray<T>((nativeNode.shadowRoot ?? nativeNode).childNodes)
}

if (children.length === 0 || nativeNode instanceof HTMLVideoElement) {
if (
children.length === 0 ||
isInstanceOfElement(nativeNode, HTMLVideoElement)
) {
return clonedNode
}

Expand Down Expand Up @@ -114,9 +125,19 @@ function cloneCSSStyle<T extends HTMLElement>(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'
}

if (name === 'd' && clonedNode.getAttribute('d')) {
value = `path(${clonedNode.getAttribute('d')})`
}

targetStyle.setProperty(
name,
value,
Expand All @@ -127,17 +148,17 @@ function cloneCSSStyle<T extends HTMLElement>(nativeNode: T, clonedNode: T) {
}

function cloneInputValue<T extends HTMLElement>(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<T extends HTMLElement>(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'),
Expand All @@ -150,7 +171,7 @@ function cloneSelectValue<T extends HTMLElement>(nativeNode: T, clonedNode: T) {
}

function decorate<T extends HTMLElement>(nativeNode: T, clonedNode: T): T {
if (clonedNode instanceof Element) {
if (isInstanceOfElement(clonedNode, Element)) {
cloneCSSStyle(nativeNode, clonedNode)
clonePseudoElements(nativeNode, clonedNode)
cloneInputValue(nativeNode, clonedNode)
Expand Down
17 changes: 8 additions & 9 deletions src/embed-images.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -38,20 +38,19 @@ async function embedImageNode<T extends HTMLElement | SVGImageElement>(
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) => {
Expand All @@ -67,7 +66,7 @@ async function embedImageNode<T extends HTMLElement | SVGImageElement>(
image.loading = 'eager'
}

if (clonedNode instanceof HTMLImageElement) {
if (isImageElement) {
clonedNode.srcset = ''
clonedNode.src = dataURL
} else {
Expand All @@ -89,7 +88,7 @@ export async function embedImages<T extends HTMLElement>(
clonedNode: T,
options: Options,
) {
if (clonedNode instanceof Element) {
if (isInstanceOfElement(clonedNode, Element)) {
await embedBackground(clonedNode, options)
await embedImageNode(clonedNode, options)
await embedChildren(clonedNode, options)
Expand Down
18 changes: 18 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,21 @@ export async function nodeToDataURL(
foreignObject.appendChild(node)
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)
)
}

0 comments on commit 42bf1c5

Please sign in to comment.