From b93825eb632a8f532050166e01a50bfce62bb903 Mon Sep 17 00:00:00 2001 From: Shachar <34343793+ShaMan123@users.noreply.github.com> Date: Mon, 17 May 2021 15:09:55 +0300 Subject: [PATCH] [Enhancement] Support Cloning Custom Elements (#129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(custom-elements): ✨ obtain children of shadow node for custom elements Inspired by https://github.com/tsayen/dom-to-image/pull/370 If element has `shadowRoot` property it is custom (and the shadow node is open) so fetch it's children. Closes #128 * fix: 🐛 add safety checks to support special/custom elements Introduced by 021f124 Access to shadow node exposes the code to custom elements that break the current code. Was breaking because of elements without a style node. This commit fixes these bugs with simple, NON-BREAKING safety checks. * test(htmltoimage): 🚨 Added tests covering custom elements renders mathfield custom element https://github.com/arnog/mathlive --- src/cloneNode.ts | 6 ++- src/embedImages.ts | 2 +- test/spec/index.spec.ts | 40 ++++++++++++++++++++ test/spec/resources/custom-element/image | 1 + test/spec/resources/custom-element/node.html | 9 +++++ test/spec/resources/custom-element/style.css | 0 6 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 test/spec/resources/custom-element/image create mode 100644 test/spec/resources/custom-element/node.html create mode 100644 test/spec/resources/custom-element/style.css diff --git a/src/cloneNode.ts b/src/cloneNode.ts index 510e6f51..90d8b39e 100644 --- a/src/cloneNode.ts +++ b/src/cloneNode.ts @@ -27,7 +27,9 @@ async function cloneChildren( clonedNode: HTMLElement, filter?: Function, ): Promise { - const children = toArray(nativeNode.childNodes) + const children = toArray( + (nativeNode.shadowRoot ?? nativeNode).childNodes, + ) if (children.length === 0) { return Promise.resolve(clonedNode) } @@ -65,7 +67,7 @@ async function decorate( function cloneCssStyle(nativeNode: HTMLElement, clonedNode: HTMLElement) { const source = window.getComputedStyle(nativeNode) const target = clonedNode.style - + if (!target) return if (source.cssText) { target.cssText = source.cssText } else { diff --git a/src/embedImages.ts b/src/embedImages.ts index e7ff8f7d..79d55e3a 100644 --- a/src/embedImages.ts +++ b/src/embedImages.ts @@ -21,7 +21,7 @@ async function embedBackground( clonedNode: HTMLElement, options: Options, ): Promise { - const background = clonedNode.style.getPropertyValue('background') + const background = clonedNode.style?.getPropertyValue('background') if (!background) { return Promise.resolve(clonedNode) } diff --git a/test/spec/index.spec.ts b/test/spec/index.spec.ts index 0d845e10..7ab4a90a 100644 --- a/test/spec/index.spec.ts +++ b/test/spec/index.spec.ts @@ -229,6 +229,46 @@ describe('html to image', () => { .then(done) .catch(done) }) + + describe('custom element', () => { + let link: HTMLLinkElement + beforeAll(() => { + const script = document.createElement('script') + script.src = 'https://unpkg.com/mathlive/dist/mathlive.min.js' + link = document.createElement('link') + link.rel = 'stylesheet' + link.href = 'https://unpkg.com/mathlive/dist/mathlive-fonts.css' + const tasks = [ + new Promise((resolve, reject) => { + script.onload = resolve + script.onerror = reject + }), + new Promise((resolve, reject) => { + link.onload = resolve + link.onerror = reject + }), + ] + document.head.append(script, link) + return Promise.all(tasks) + }) + + afterAll(() => { + link.remove() + }) + + it('should render content from shadow node of custom element', (done) => { + Helper.bootstrap( + 'custom-element/node.html', + 'custom-element/style.css', + 'custom-element/image', + ) + .then(util.delay(1000)) + .then(Helper.renderAndCheck) + .then(util.delay(1000)) + .then(done) + .catch(done) + }) + }) }) describe('work with svg', () => { diff --git a/test/spec/resources/custom-element/image b/test/spec/resources/custom-element/image new file mode 100644 index 00000000..1122c49b --- /dev/null +++ b/test/spec/resources/custom-element/image @@ -0,0 +1 @@ +data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA+sAAAAnCAYAAAB3wtkbAAAHq0lEQVR4Xu3dPagcVRgG4DcgiFiIqFhYRAQRK0XERi1MI6bQGJEUamURRQkEtBKNGqzFEAIi2iQpBBVtgj9g4Q/YKGqjdgERBP/QwiAIyidzYBx3191LNndmeRYC2Xt3Zs55ztzi3XPONzviRYAAAQIECBAgQIAAAQIECIxKYMeoWqMxBAgQIECAAAECBAgQIECAQIR1NwEBAgQIECBAgAABAgQIEBiZgLA+sgHRHAIECBAgQIAAAQIECBAgsClh/YIkzyfZn+T+JCe3MLTtHB9u8fjhJS9J8lySJ5L8NKc91yTZ3bV9C012CAECBAgQIECAAAECBAhsosCmhPU2NgeTnEryzTYPVoXwI0kOLNGWm5M8meS+BaF+m7vj8gQIECBAgAABAgQIECBwLgU2KawvM5N9LmyrHUeTPL1EUG/tqcD+QJL6suHMuWikaxAgQIAAAQIECBAgQIDAeAU2KaxX4N2V5PA2c9cMeb1WWYrfluAfT/LxNrff5QkQIECAAAECBAgQIEBgmwWmFtYrCJ9I8kWSY0lu6M1G11LyL5M8nOT2JC/OmamuJeqvJvm+C/aXJnkrSc2IV8CusF+Bub2vAF2vdt19C2bMh+eo41oQv7Jb6v57t0d9uDe++nar2fVt/otweQIECBAgQIAAAQIECIxAYCphvQXe77ow3Q/SFbDb+yKt0FsBvAL5I4OZ6jrPM0leTvJjF84rjL/d/b9C/i1Jvu69r3NW0bo3upDd2jBr+GbtVa+f1XUv7A74bE5Yt3d9BH8QmkCAAAECBAgQIECAAIExCEwlrNeseb3aEvfhsvHhEvhZM9x1/DD098dg0cx6W9I+bMdwDBcF7hbaP+9mz4eF8FYpSjeGe0cbCBAgQIAAAQIECBAgQGBNAlMI67MCcAXbKuD2aFdBvUL0+71Z9EXBt873Uef5Tq8K+7kK698m2ZvkpcGYCutrusmdlgABAgQIECBAgAABAlMTmEJYnzer3l8SP3ye+f/NgNc49feu94vCzdqzvuzM+qLAXV8G1L+dSWop/PDZ65bBT+2vR3sJECBAgAABAgQIECCwJoGphfU2+117y/ck+SrJZYMq8BW86zFos55bXsvgH+uK01VY7s/QF3GF8trD3t8H397X7//vS4B5y+/r2PpdzahXgblZleIVmFvTTe60BAgQIECAAAECBAgQmJrAMKy3Pd37u460YmtVdf29berccNn6gSRHura0QF4h+tnuZ08teHxbC+v3JLlu0McK0PUlQL0eSnJ3730VmLuqd415lebr2GpTfXb4CLn6YmB3V1xuSOnRbdt0c7ksAQIECBAgQIAAAQIExigwDOvDmeMWgiu0e/73ciNYM+hHuz3133SH9KvQt5/1z1ZfSNRqgINJzix3GZ8iQIAAAQIECBAgQIAAgU0V6If1eSGzv2x8Ux3Odr+Ge9cXLXG3V/1s6zsfAQIECBAgQIAAAQIEJi7QD+vzHmt2V5J3zfiuPNJtyf0r3V732vs+XJ1Qof7BJIf4ruzrAAIECBAgQIAAAQIECGyswHAZfNsfvmjf96oYrep62yM+63jL7FdV9XkCBAgQIECAAAECBAgQ2FiBWdXgW7j+xB7qjR13HSNAgAABAgQIECBAgACBEQvMe3TbMoH9oiS/Jflrzf1b9/nX3Pwtn34Kj9XbcuccSIAAAQIECBAgQIAAAQLzBVogrOXv9ervqa6iaI8n2ZdkWMH84iTPJXlhxu+GV7MM3h1IgAABAgQIECBAgAABAgRWEGhhvYL56UFYX1SlvIrO/Zzk2iRVQO3PFa7powQIECBAgAABAgQIECBAgMACgRbW63nqFc4rtP+UZF5l+DrVFUluSvJmktuS/JrkU8oECBAgQIAAAQIECBAgQIDA2RGosF7PV9+b5I0kJ5Pc3p16VkX4+vyeJB8l+SHJ+UnuTfK6R4+dnQFxFgIECBAgQIAAAQIECBAgsGoRs6uTXN6F9aZXS+Er8FeA34RX9aX/pYXHym3CqOoDAQIECBAgQIAAAQIEJiSwSlg/L8kdSd5N8kevj3WOO5N8kOSXCfV9VlNr+f9jSY512wFqW8CJJAL7xAdW8wkQIECAAAECBAgQIDAlgVXC+o1JXkuyc04H7+9mpKfU/2Fbr++W87fq94v27k+5n9pOgAABAgQIECBAgAABAiMWWCWsj7gba21aFd+r1+G1XsXJCRAgQIAAAQIECBAgQIBAJyCsL74V2sz68cFj7dxABAgQIECAAAECBAgQIEBgbQLC+mLaa5I8mOSQavdruwedmAABAgQIECBAgAABAgQGAsL64lviYJJTSdoedjcQAQIECBAgQIAAAQIECBBYu4CwPp+4KsGftvx97fegCxAgQIAAAQIECBAgQICAmfWl7oGbk1y5AdXtl+qsDxEgQIAAAQIECBAgQIDAuATMrP93PCqo7xpUf78rydeWw4/r5tUaAgQIECBAgAABAgQIbKqAsP7vka2l7ydmDPaLSWr/+plNvRH0iwABAgQIECBAgAABAgTGIyCsj2cstIQAAQIECBAgQIAAAQIECPwjIKy7EQgQIECAAAECBAgQIECAwMgEhPWRDYjmECBAgAABAgQIECBAgACBvwE3lBQ3hOt8rQAAAABJRU5ErkJggg== \ No newline at end of file diff --git a/test/spec/resources/custom-element/node.html b/test/spec/resources/custom-element/node.html new file mode 100644 index 00000000..e0a4aab6 --- /dev/null +++ b/test/spec/resources/custom-element/node.html @@ -0,0 +1,9 @@ +S_{\triangle }=\frac{ab\sin \left(\gamma \right)}{2} + diff --git a/test/spec/resources/custom-element/style.css b/test/spec/resources/custom-element/style.css new file mode 100644 index 00000000..e69de29b