From 12003de1fa9eeaae18952fe1c9fcb7a04dfe13ca Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Tue, 25 Apr 2023 20:07:31 -0500 Subject: [PATCH] Better scope fallback for Node/global/window Manually implemented a better fallback from PR #134 This is intended to help with running in non-browser environments like Node and Vite. Bumped dev-time packages. Also moved the ensureSandboxWindow method out one layer in preparation for upcoming PR #135 for pruning. --- README.md | 20 ++-- dist/dom-to-image-more.min.js | 4 +- dist/dom-to-image-more.min.js.map | 2 +- package-lock.json | 68 ++++++------- package.json | 15 +-- src/dom-to-image-more.js | 155 +++++++++++++++--------------- 6 files changed, 131 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index beb23f86..16507cc0 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ The 3.x release branch included some breaking changes in the vary infrequently used ability to configure some utility methods used in this internal processing of dom-to-image-more. As browsers have matured, many of the hacks we're accumulated over the years are not needed, or better ways have been found to handle some edge-cases. With the help of folks like @meche-gh, in #99 we're stripping out the following members: -- `.mimes` - was the not-very-comprehensive list of mime types used to handle inlining things -- `.parseExtension` - was a method to extract the extension from a filename, used to guess mime types -- `.mimeType` - was a method to map file extensions to mime types -- `.dataAsUrl` - was a method to reassemble a `data:` URI from a Base64 representation and mime type +- `.mimes` - was the not-very-comprehensive list of mime types used to handle inlining things +- `.parseExtension` - was a method to extract the extension from a filename, used to guess mime types +- `.mimeType` - was a method to map file extensions to mime types +- `.dataAsUrl` - was a method to reassemble a `data:` URI from a Base64 representation and mime type The 3.x release branch should also fix more node compatibility and `iframe` issues. @@ -207,8 +207,8 @@ _Safari [is not supported](https://github.com/tsayen/dom-to-image/issues/27), as Only standard lib is currently used, but make sure your browser supports: -- [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) -- SVG `` tag +- [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) +- SVG `` tag ### Tests @@ -216,7 +216,7 @@ As of this v3 branch chain, the testing jig is taking advantage of the `onclone` Most importantly, tests **only** depend on: -- [ocrad.js](https://github.com/antimatter15/ocrad.js), for the +- [ocrad.js](https://github.com/antimatter15/ocrad.js), for the parts when you can't compare images (due to the browser rendering differences) and just have to test whether the text is rendered @@ -284,16 +284,16 @@ for you, following steps are taken: ## Things to watch out for -- if the DOM node you want to render includes a `` element with something drawn on it, it should be handled fine, unless the canvas is [tainted](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image) - in this case rendering will rather not succeed. +- if the DOM node you want to render includes a `` element with something drawn on it, it should be handled fine, unless the canvas is [tainted](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image) - in this case rendering will rather not succeed. -- at the time of writing, Firefox has a problem with some external stylesheets (see issue #13). In such case, the error will be caught and logged. +- at the time of writing, Firefox has a problem with some external stylesheets (see issue #13). In such case, the error will be caught and logged. ## Authors Marc Brooks, Anatolii Saienko (original dom-to-image), Paul Bakaus (original idea), Aidas Klimas (fixes), Edgardo Di Gesto (fixes), 樊冬 Fan Dong (fixes), Shrijan Tripathi (docs), SNDST00M (optimize), Joseph White (performance CSS), Phani Rithvij (test), -David DOLCIMASCOLO (packaging), Zee (ZM) @zm-cttae (many major updates), Joshua Walsh @JoshuaWalsh (Firefox issues), Emre Coban @emrecoban (documentation) +David DOLCIMASCOLO (packaging), Zee (ZM) @zm-cttae (many major updates), Joshua Walsh @JoshuaWalsh (Firefox issues), Emre Coban @emrecoban (documentation), Nate Stuyvesant @nstuyvesant (fixes) ## License diff --git a/dist/dom-to-image-more.min.js b/dist/dom-to-image-more.min.js index ecfec473..3e2349d9 100644 --- a/dist/dom-to-image-more.min.js +++ b/dist/dom-to-image-more.min.js @@ -1,3 +1,3 @@ -/*! dom-to-image-more 16-04-2023 */ -!function(u){"use strict";const f=function(){let e=0;return{escape:function(e){return e.replace(/([.*+?^${}()|[]\/\\])/g,"\\$1")},isDataUrl:function(e){return-1!==e.search(/^(data:)/)},canvasToBlob:function(t){if(t.toBlob)return new Promise(function(e){t.toBlob(e)});return function(r){return new Promise(function(e){var t=l(r.toDataURL().split(",")[1]),n=t.length,o=new Uint8Array(n);for(let e=0;et.style.removeProperty(e)),["left","right","top","bottom"].forEach(e=>{t.style.getPropertyValue(e)&&t.style.setProperty(e,"0px")})))}e(c,l)}function t(){const s=f.uid();function t(r){const i=h(c,r),u=i.getPropertyValue("content");if(""!==u&&"none"!==u){const t=l.getAttribute("class")||"",n=(l.setAttribute("class",t+" "+s),document.createElement("style"));function e(){const e=`.${s}:`+r,t=(i.cssText?n:o)();return document.createTextNode(e+`{${t}}`);function n(){return`${i.cssText} content: ${u};`}function o(){const e=f.asArray(i).map(t).join("; ");return e+";";function t(e){const t=i.getPropertyValue(e),n=i.getPropertyPriority(e)?" !important":"";return e+": "+t+n}}}n.appendChild(e()),l.appendChild(n)}}[":before",":after"].forEach(function(e){t(e)})}function n(){f.isHTMLTextAreaElement(c)&&(l.innerHTML=c.value),f.isHTMLInputElement(c)&&l.setAttribute("value",c.value)}function o(){f.isSVGElement(l)&&(l.setAttribute("xmlns","http://www.w3.org/2000/svg"),f.isSVGRectElement(l))&&["width","height"].forEach(function(e){const t=l.getAttribute(e);t&&l.style.setProperty(e,t)})}}}(e,r,null,t)}).then(p).then(g).then(function(t){r.bgcolor&&(t.style.backgroundColor=r.bgcolor);r.width&&(t.style.width=r.width+"px");r.height&&(t.style.height=r.height+"px");r.style&&Object.keys(r.style).forEach(function(e){t.style[e]=r.style[e]});let e=null;"function"==typeof r.onclone&&(e=r.onclone(t));return Promise.resolve(e).then(function(){return t})}).then(function(e){let n=r.width||f.width(e),o=r.height||f.height(e);return Promise.resolve(e).then(function(e){return e.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),(new XMLSerializer).serializeToString(e)}).then(f.escapeXhtml).then(function(e){var t=(f.isDimensionMissing(n)?' width="100%"':` width="${n}"`)+(f.isDimensionMissing(o)?' height="100%"':` height="${o}"`);return`${e}`}).then(function(e){return"data:image/svg+xml;charset=utf-8,"+e})}).then(function(e){for(;0{w=null,E={}},2e4)}(),e})}function i(r,i){return m(r,i=i||{}).then(f.makeImage).then(function(e){var t="number"!=typeof i.scale?1:i.scale,n=function(e,t){let n=i.width||f.width(e),o=i.height||f.height(e);f.isDimensionMissing(n)&&(n=f.isDimensionMissing(o)?300:2*o);f.isDimensionMissing(o)&&(o=n/2);e=document.createElement("canvas");e.width=n*t,e.height=o*t,i.bgcolor&&((t=e.getContext("2d")).fillStyle=i.bgcolor,t.fillRect(0,0,e.width,e.height));return e}(r,t),o=n.getContext("2d");return o.msImageSmoothingEnabled=!1,o.imageSmoothingEnabled=!1,e&&(o.scale(t,t),o.drawImage(e,0,0)),n})}let d=null;function p(n){return e.resolveAll().then(function(e){var t;return""!==e&&(t=document.createElement("style"),n.appendChild(t),t.appendChild(document.createTextNode(e))),n})}function g(e){return n.inlineAll(e).then(function(){return e})}function y(e,t,i,u,n){const s=a.impl.options.copyDefaultStyles?function(t,e){var e=function(e){var t=[];do{if(e.nodeType===c){var n=e.tagName;if(t.push(n),v.includes(n))break}}while(e=e.parentNode,e);return t}(e),n=function(e){return("relaxed"!==t.styleCaching?e:e.filter((e,t,n)=>0===t||t===n.length-1)).join(">")}(e);if(E[n])return E[n];var o=function(){if(d)return d.contentWindow;var e=document.characterSet||"UTF-8",t=document.doctype,t=t?(`":"";return(d=document.createElement("iframe")).id="domtoimage-sandbox-"+f.uid(),d.style.visibility="hidden",d.style.position="fixed",document.body.appendChild(d),function(e,t,n,o){try{return e.contentWindow.document.write(t+`${o}`),e.contentWindow}catch(e){}var r=document.createElement("meta");r.setAttribute("charset",n);try{var i=document.implementation.createHTMLDocument(o),u=(i.head.appendChild(r),t+i.documentElement.outerHTML);return e.setAttribute("srcdoc",u),e.contentWindow}catch(e){}return e.contentDocument.head.appendChild(r),e.contentDocument.title=o,e.contentWindow}(d,t,e,"domtoimage-sandbox");function n(e){var t;return e?((t=document.createElement("div")).innerText=e,t.innerHTML):""}}(),e=function(e,t){let n=e.body;do{var o=t.pop(),o=e.createElement(o);n.appendChild(o),n=o}while(0t.style.removeProperty(e)),["left","right","top","bottom"].forEach(e=>{t.style.getPropertyValue(e)&&t.style.setProperty(e,"0px")})))}e(c,s)}function t(){const l=f.uid();function t(r){const i=h(c,r),u=i.getPropertyValue("content");if(""!==u&&"none"!==u){const t=s.getAttribute("class")||"",n=(s.setAttribute("class",t+" "+l),document.createElement("style"));function e(){const e=`.${l}:`+r,t=(i.cssText?n:o)();return document.createTextNode(e+`{${t}}`);function n(){return`${i.cssText} content: ${u};`}function o(){const e=f.asArray(i).map(t).join("; ");return e+";";function t(e){const t=i.getPropertyValue(e),n=i.getPropertyPriority(e)?" !important":"";return e+": "+t+n}}}n.appendChild(e()),s.appendChild(n)}}[":before",":after"].forEach(function(e){t(e)})}function n(){f.isHTMLTextAreaElement(c)&&(s.innerHTML=c.value),f.isHTMLInputElement(c)&&s.setAttribute("value",c.value)}function o(){f.isSVGElement(s)&&(s.setAttribute("xmlns","http://www.w3.org/2000/svg"),f.isSVGRectElement(s))&&["width","height"].forEach(function(e){const t=s.getAttribute(e);t&&s.style.setProperty(e,t)})}}}(e,r,null,t)}).then(p).then(g).then(function(t){r.bgcolor&&(t.style.backgroundColor=r.bgcolor);r.width&&(t.style.width=r.width+"px");r.height&&(t.style.height=r.height+"px");r.style&&Object.keys(r.style).forEach(function(e){t.style[e]=r.style[e]});let e=null;"function"==typeof r.onclone&&(e=r.onclone(t));return Promise.resolve(e).then(function(){return t})}).then(function(e){let n=r.width||f.width(e),o=r.height||f.height(e);return Promise.resolve(e).then(function(e){return e.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),(new XMLSerializer).serializeToString(e)}).then(f.escapeXhtml).then(function(e){var t=(f.isDimensionMissing(n)?' width="100%"':` width="${n}"`)+(f.isDimensionMissing(o)?' height="100%"':` height="${o}"`);return`${e}`}).then(function(e){return"data:image/svg+xml;charset=utf-8,"+e})}).then(function(e){for(;0{w=null,E={}},2e4)}(),e})}function i(r,i){return m(r,i=i||{}).then(f.makeImage).then(function(e){var t="number"!=typeof i.scale?1:i.scale,n=function(e,t){let n=i.width||f.width(e),o=i.height||f.height(e);f.isDimensionMissing(n)&&(n=f.isDimensionMissing(o)?300:2*o);f.isDimensionMissing(o)&&(o=n/2);e=document.createElement("canvas");e.width=n*t,e.height=o*t,i.bgcolor&&((t=e.getContext("2d")).fillStyle=i.bgcolor,t.fillRect(0,0,e.width,e.height));return e}(r,t),o=n.getContext("2d");return o.msImageSmoothingEnabled=!1,o.imageSmoothingEnabled=!1,e&&(o.scale(t,t),o.drawImage(e,0,0)),n})}let d=null;function p(n){return e.resolveAll().then(function(e){var t;return""!==e&&(t=document.createElement("style"),n.appendChild(t),t.appendChild(document.createTextNode(e))),n})}function g(e){return n.inlineAll(e).then(function(){return e})}function y(e,t,i,u,n){const l=a.impl.options.copyDefaultStyles?function(t,e){var e=function(e){var t=[];do{if(e.nodeType===c){var n=e.tagName;if(t.push(n),v.includes(n))break}}while(e=e.parentNode,e);return t}(e),n=function(e){return("relaxed"!==t.styleCaching?e:e.filter((e,t,n)=>0===t||t===n.length-1)).join(">")}(e);if(E[n])return E[n];var o=function(){if(d)return d.contentWindow;var e=document.characterSet||"UTF-8",t=document.doctype,t=t?(`":"";return(d=document.createElement("iframe")).id="domtoimage-sandbox-"+f.uid(),d.style.visibility="hidden",d.style.position="fixed",document.body.appendChild(d),function(e,t,n,o){try{return e.contentWindow.document.write(t+`${o}`),e.contentWindow}catch(e){}var r=document.createElement("meta");r.setAttribute("charset",n);try{var i=document.implementation.createHTMLDocument(o),u=(i.head.appendChild(r),t+i.documentElement.outerHTML);return e.setAttribute("srcdoc",u),e.contentWindow}catch(e){}return e.contentDocument.head.appendChild(r),e.contentDocument.title=o,e.contentWindow}(d,t,e,"domtoimage-sandbox");function n(e){var t;return e?((t=document.createElement("div")).innerText=e,t.innerHTML):""}}(),e=function(e,t){let n=e.body;do{var o=t.pop(),o=e.createElement(o);n.appendChild(o),n=o}while(0=16.0.0" @@ -184,9 +184,9 @@ } }, "node_modules/@types/node": { - "version": "18.15.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", - "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", + "version": "18.16.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.1.tgz", + "integrity": "sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==", "dev": true }, "node_modules/abbrev": { @@ -1051,15 +1051,15 @@ } }, "node_modules/eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", - "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", + "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", + "@eslint/js": "8.39.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -1069,7 +1069,7 @@ "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", + "eslint-scope": "^7.2.0", "eslint-visitor-keys": "^3.4.0", "espree": "^9.5.1", "esquery": "^1.4.2", @@ -2445,9 +2445,9 @@ } }, "node_modules/karma": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", - "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz", + "integrity": "sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ==", "dev": true, "dependencies": { "@colors/colors": "1.5.0", @@ -2493,9 +2493,9 @@ } }, "node_modules/karma-chrome-launcher": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz", - "integrity": "sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", "dev": true, "dependencies": { "which": "^1.2.1" @@ -3450,9 +3450,9 @@ } }, "node_modules/prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -3626,12 +3626,12 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.3.tgz", - "integrity": "sha512-P8ur/gp/AmbEzjr729bZnLjXK5Z+4P0zhIJgBgzqRih7hL7BOukHGtSTA3ACMY467GRFz3duQsi0bDZdR7DKdw==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dev": true, "dependencies": { - "is-core-module": "^2.12.0", + "is-core-module": "^2.11.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -3751,9 +3751,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", - "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" diff --git a/package.json b/package.json index 9b2f7e01..ac272752 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "dom-to-image-more", - "version": "3.1.4", + "version": "3.1.5", "description": "Generates an image from a DOM node using HTML5 canvas and SVG", "main": "dist/dom-to-image-more.min.js", "devDependencies": { "chai": "^4.3.7", - "eslint": "^8.38.0", + "eslint": "^8.39.0", "grunt": "^1.6.1", "grunt-cli": "^1.4.3", "grunt-contrib-jshint": "^3.2.0", @@ -13,15 +13,15 @@ "grunt-contrib-watch": "^1.1.0", "grunt-karma": "^4.0.2", "js-yaml": "^4.1.0", - "karma": "^6.4.1", + "karma": "^6.4.2", "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^3.1.1", + "karma-chrome-launcher": "^3.2.0", "karma-firefox-launcher": "^2.1.2", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "mocha": "^10.2.0", - "prettier": "^2.8.7", - "semver": "^7.4.0" + "prettier": "^2.8.8", + "semver": "^7.5.0" }, "scripts": { "format": "eslint src --fix && prettier --write .", @@ -60,7 +60,8 @@ "Zee @zm-cttae", "Andoni Zubimendi @AndoniZubimendi", "Joshua Walsh @JoshuaWalsh", - "Emre Coban @emrecoban" + "Emre Coban @emrecoban", + "Nate Stuyvesant @nstuyvesant" ], "license": "MIT", "bugs": { diff --git a/src/dom-to-image-more.js b/src/dom-to-image-more.js index 122135c9..c833b5b1 100644 --- a/src/dom-to-image-more.js +++ b/src/dom-to-image-more.js @@ -5,8 +5,8 @@ const inliner = newInliner(); const fontFaces = newFontFaces(); const images = newImages(); - const ELEMENT_NODE = typeof Node === 'undefined' ? 1 : Node.ELEMENT_NODE || 1; - + const ELEMENT_NODE = (Node && Node.ELEMENT_NODE) || 1; + // Default impl options const defaultOptions = { // Default is to copy default styles of elements @@ -47,8 +47,8 @@ } // support node and browsers - const getComputedStyle = global.getComputedStyle || window.getComputedStyle; - const atob = global.atob || window.atob; + const getComputedStyle = (global && global.getComputedStyle) || (window && window.getComputedStyle) || globalThis.getComputedStyle; + const atob = (global && global.atob) || (window && window.atob) || globalThis.atob; /** * @param {Node} node - The DOM Node object to render @@ -108,7 +108,6 @@ // put the original children back where the wrappers were inserted while (restorations.length > 0) { const restoration = restorations.pop(); - //console.log(`parent: ${restoration.parent}\nchild: ${restoration.child}\nwrapper: ${restoration.wrapper}`); restoration.parent.replaceChild(restoration.child, restoration.wrapper); } @@ -1262,80 +1261,6 @@ return tagHierarchy.join('>'); // it's like CSS } - function ensureSandboxWindow() { - if (sandbox) { - return sandbox.contentWindow; - } - - // figure out how this document is defined (doctype and charset) - const charsetToUse = document.characterSet || 'UTF-8'; - const docType = document.doctype; - const docTypeDeclaration = docType - ? `' - : ''; - - // Create a hidden sandbox