Exported HTML can retain executable attributes and active embedded content
Source references: 4The clone routine explicitly removes only comments and SCRIPT elements. Other elements retain their attributes through cloneNode(false), and their children are recursively copied. It does not remove event attributes such as onload/onclick or exclude active elements such as iframe, object, embed, or form before writing a standalone HTML document.
If an artboard contains malicious or contaminated HTML, opening the download could execute JavaScript, load external pages, or submit data. Such code could also read content included in the exported page and send it over the network.
The source supports this risk, but it occurs when the user chooses “Download HTML” and later opens the file. The cloner excludes only comments and `SCRIPT`; `cloneNode(false)` retains attributes on other elements. The result is written into a standalone HTML document without active-content filtering. If an artboard contains untrusted event attributes, iframes, objects, or similar content, the export may preserve their behavior. Users can ask for tag/attribute allowlisting and open exports in an isolated environment.
const cloneStyled = (src) => { if (src.nodeType === 8 || (src.nodeType === 1 && src.tagName === 'SCRIPT')) return document.createTextNode(''); const dst = src.cloneNode(false); if (src.nodeType === 1) { const cs = getComputedStyle(src); let txt = ''; for (let i = 0; i < cs.length; i++) txt += cs[i] + ':' + cs.getPropertyValue(cs[i]) + ';'; dst.setAttribute('style', txt + 'animation:none;transition:none;'); if (src.tagName === 'CANVAS') try { const im = document.createElement('img'); im.src = src.toDataURL(); im.setAttribute('style', txt); return im; } catch {} } for (let c = src.firstChild; c; c = c.nextSibling) dst.appendChild(cloneStyled(c)); return dst; };Show 3 other places
const xml = new XMLSerializer().serializeToString(clone); const save = (blob, ext) => { if (!blob) return; const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = name + '.' + ext; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 1000); }; if (kind === 'html') { const html = '<!doctype html><html><head><meta charset="utf-8"><title>' + name + '</title>' + (fontCss ? '<style>' + fontCss + '</style>' : '') + '</head><body style="margin:0">' + xml + '</body></html>'; return save(new Blob([html], { type: 'text/html' }), 'html'); } if (kind === 'html') { const html = '<!doctype html><html><head><meta charset="utf-8"><title>' + name + '</title>' + (fontCss ? '<style>' + fontCss + '</style>' : '') + '</head><body style="margin:0">' + xml + '</body></html>'; return save(new Blob([html], { type: 'text/html' }), 'html'); } </button> {menuOpen && ( <div className="dc-menu" onPointerDown={(e) => e.stopPropagation()}> <button onClick={() => doExport('png')}>下载 PNG</button> <button onClick={() => doExport('html')}>下载 HTML</button> <button className="dc-danger"