From 48e2916874a4298108fed6800cc0bf2599a67183 Mon Sep 17 00:00:00 2001 From: 01zulfi <85733202+01zulfi@users.noreply.github.com> Date: Thu, 12 Jun 2025 11:51:20 +0500 Subject: [PATCH] clipper: update filtering in cloned node Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> --- packages/clipper/src/clone.ts | 161 +++++------------------------ packages/clipper/src/domtoimage.ts | 11 +- packages/clipper/src/utils.ts | 87 +++++++++++++++- 3 files changed, 122 insertions(+), 137 deletions(-) diff --git a/packages/clipper/src/clone.ts b/packages/clipper/src/clone.ts index b906b51ed..c7aa017d0 100644 --- a/packages/clipper/src/clone.ts +++ b/packages/clipper/src/clone.ts @@ -17,85 +17,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -const SVGElements = [ - "altGlyph", - "altGlyphDef", - "altGlyphItem", - "animate", - "animateColor", - "animateMotion", - "animateTransform", - "circle", - "clipPath", - "color-profile", - "cursor", - "defs", - "desc", - "ellipse", - "feBlend", - "feColorMatrix", - "feComponentTransfer", - "feComposite", - "feConvolveMatrix", - "feDiffuseLighting", - "feDisplacementMap", - "feDistantLight", - "feFlood", - "feFuncA", - "feFuncB", - "feFuncG", - "feFuncR", - "feGaussianBlur", - "feImage", - "feMerge", - "feMergeNode", - "feMorphology", - "feOffset", - "fePointLight", - "feSpecularLighting", - "feSpotLight", - "feTile", - "feTurbulence", - "filter", - "font-face", - "font-face-format", - "font-face-name", - "font-face-src", - "font-face-uri", - "foreignObject", - "g", - "glyph", - "glyphRef", - "hkern", - "image", - "line", - "linearGradient", - "marker", - "mask", - "metadata", - "missing-glyph", - "mpath", - "path", - "pattern", - "polygon", - "polyline", - "radialGradient", - "rect", - "set", - "stop", - "svg", - "switch", - "symbol", - "text", - "textPath", - "title", - "tref", - "tspan", - "use", - "view", - "vkern" -].map((a) => a.toLowerCase()); - const INVALID_ELEMENTS = ["script"].map((a) => a.toLowerCase()); type CloneNodeOptions = { @@ -111,53 +32,38 @@ export function cloneNode(node: HTMLElement, options: CloneNodeOptions) { function processNode(node: HTMLElement, options: CloneNodeOptions) { try { - if (!options.images && node instanceof HTMLImageElement) { - node.remove(); - return; - } + const tagNames = [ + "img", + "button", + "form", + "select", + "input", + "textarea" + ].concat(INVALID_ELEMENTS); + const elements = node.querySelectorAll(tagNames.join(",")); - if ( - !options.styles && - (node instanceof HTMLButtonElement || - node instanceof HTMLFormElement || - node instanceof HTMLSelectElement || - node instanceof HTMLInputElement || - node instanceof HTMLTextAreaElement) - ) { - node.remove(); - return; - } - - if (node.nodeType === Node.COMMENT_NODE) { - node.remove(); - return; - } - - if (isInvalidElement(node)) { - node.remove(); - return; - } - - if (node.nodeType !== Node.TEXT_NODE && !isSVGElement(node)) { - const { display, width, height } = window.getComputedStyle(node); - if (display === "none" || (width === "0px" && height === "0px")) { - node.remove(); - return; + for (const element of elements) { + if (!options.images && element instanceof HTMLImageElement) { + element.remove(); + continue; } - if (isCustomElement(node)) { - const isInline = display.includes("inline"); - const element = document.createElement(isInline ? "span" : "div"); - for (const attribute of node.attributes) { - element.setAttribute(attribute.name, attribute.value); - } - node.replaceWith(element); + if ( + !options.styles && + (element instanceof HTMLButtonElement || + element instanceof HTMLFormElement || + element instanceof HTMLSelectElement || + element instanceof HTMLInputElement || + element instanceof HTMLTextAreaElement) + ) { + element.remove(); + continue; + } + + if (isInvalidElement(element as HTMLElement)) { + element.remove(); } } - - node.childNodes.forEach((child) => - processNode(child as HTMLElement, options) - ); } catch (e) { console.error("Failed to process node", e); return null; @@ -168,16 +74,3 @@ function isInvalidElement(element: HTMLElement) { if (!element || !element.tagName) return false; return INVALID_ELEMENTS.includes(element.tagName.toLowerCase()); } - -export function isSVGElement(element: HTMLElement) { - if (!element || !element.tagName) return false; - return SVGElements.includes(element.tagName.toLowerCase()); -} - -function isCustomElement(element: HTMLElement) { - if (!element || !element.tagName) return false; - return ( - !SVGElements.includes(element.tagName.toLowerCase()) && - element.tagName.includes("-") - ); -} diff --git a/packages/clipper/src/domtoimage.ts b/packages/clipper/src/domtoimage.ts index 5ee98a83d..370bf63a8 100644 --- a/packages/clipper/src/domtoimage.ts +++ b/packages/clipper/src/domtoimage.ts @@ -20,9 +20,16 @@ import { createImage, FetchOptions } from "./fetch.js"; import { resolveAll } from "./fontfaces.js"; import { inlineAllImages } from "./images.js"; import { Options } from "./types.js"; -import { canvasToBlob, delay, escapeXhtml, height, width } from "./utils.js"; +import { + canvasToBlob, + delay, + escapeXhtml, + height, + width, + isSVGElement +} from "./utils.js"; import { inlineStylesheets } from "./styles.js"; -import { cloneNode, isSVGElement } from "./clone.js"; +import { cloneNode } from "./clone.js"; // Default impl options const defaultOptions: Options = { diff --git a/packages/clipper/src/utils.ts b/packages/clipper/src/utils.ts index 24978f9f4..7de940b63 100644 --- a/packages/clipper/src/utils.ts +++ b/packages/clipper/src/utils.ts @@ -24,6 +24,85 @@ along with this program. If not, see . const WOFF = "application/font-woff"; const JPEG = "image/jpeg"; +const SVGElements = [ + "altGlyph", + "altGlyphDef", + "altGlyphItem", + "animate", + "animateColor", + "animateMotion", + "animateTransform", + "circle", + "clipPath", + "color-profile", + "cursor", + "defs", + "desc", + "ellipse", + "feBlend", + "feColorMatrix", + "feComponentTransfer", + "feComposite", + "feConvolveMatrix", + "feDiffuseLighting", + "feDisplacementMap", + "feDistantLight", + "feFlood", + "feFuncA", + "feFuncB", + "feFuncG", + "feFuncR", + "feGaussianBlur", + "feImage", + "feMerge", + "feMergeNode", + "feMorphology", + "feOffset", + "fePointLight", + "feSpecularLighting", + "feSpotLight", + "feTile", + "feTurbulence", + "filter", + "font-face", + "font-face-format", + "font-face-name", + "font-face-src", + "font-face-uri", + "foreignObject", + "g", + "glyph", + "glyphRef", + "hkern", + "image", + "line", + "linearGradient", + "marker", + "mask", + "metadata", + "missing-glyph", + "mpath", + "path", + "pattern", + "polygon", + "polyline", + "radialGradient", + "rect", + "set", + "stop", + "svg", + "switch", + "symbol", + "text", + "textPath", + "title", + "tref", + "tspan", + "use", + "view", + "vkern" +].map((a) => a.toLowerCase()); + const mimes = { woff: WOFF, woff2: WOFF, @@ -165,6 +244,11 @@ function getRootStylesheet() { return null; } +function isSVGElement(element: HTMLElement) { + if (!element || !element.tagName) return false; + return SVGElements.includes(element.tagName.toLowerCase()); +} + export { injectCss, escape, @@ -179,5 +263,6 @@ export { asArray, escapeXhtml, width, - height + height, + isSVGElement };