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
};