/** * * @param {StyleSheetList} styleSheets */ var ForeignHtmlRenderer = function (styleSheets) { const self = this; /** * * @param {String} binStr */ const binaryStringToBase64 = function (binStr) { return new Promise(function (resolve) { const reader = new FileReader(); reader.readAsDataURL(binStr); reader.onloadend = function () { resolve(reader.result); } }); }; function prepend(extension) { return window.location.origin + extension; } function CorsProxy(url) { return prepend("/corsProxy/") + encodeURIComponent(url); } /** * * @param {String} url * @returns {Promise} */ const getResourceAsBase64 = function (webUrl, inurl) { return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); //const url = inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl; //const url = CorsProxy(inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl);// inurl.startsWith("http") ? CorsProxy(inurl) : inurl; var url = inurl; if (inurl.startsWith("/static")) { url = (new URL(webUrl).origin + inurl); } else if ((inurl.startsWith("/") && !inurl.startsWith("//"))) { url = CorsProxy(new URL(webUrl).origin + inurl); } else if (!inurl.startsWith("http") && !inurl.startsWith("//")) { url = CorsProxy(webUrl + "/" + inurl); } xhr.open("GET", url); xhr.responseType = 'blob'; xhr.onreadystatechange = async function () { if (xhr.readyState === 4 && xhr.status === 200) { const resBase64 = await binaryStringToBase64(xhr.response); resolve( { "resourceUrl": inurl, "resourceBase64": resBase64 } ); } else if (xhr.readyState === 4) { console.log("COULDN'T FIND: " + (inurl.startsWith("/") ? webUrl + inurl : inurl)); resolve( { "resourceUrl": "", "resourceBase64": inurl } ); } }; xhr.send(null); }); }; /** * * @param {String[]} urls * @returns {Promise} */ const getMultipleResourcesAsBase64 = function (webUrl, urls) { const promises = []; for (let i = 0; i < urls.length; i++) { promises.push(getResourceAsBase64(webUrl, urls[i])); } return Promise.all(promises); }; /** * * @param {String} str * @param {Number} startIndex * @param {String} prefixToken * @param {String[]} suffixTokens * * @returns {String|null} */ const parseValue = function (str, startIndex, prefixToken, suffixTokens) { const idx = str.indexOf(prefixToken, startIndex); if (idx === -1) { return null; } let val = ''; for (let i = idx + prefixToken.length; i < str.length; i++) { if (suffixTokens.indexOf(str[i]) !== -1) { break; } val += str[i]; } return { "foundAtIndex": idx, "value": val } }; /** * * @param {String} cssRuleStr * @returns {String[]} */ const getUrlsFromCssString = function (cssRuleStr, selector = "url(", delimiters = [')'], mustEndWithQuote = false) { const urlsFound = []; let searchStartIndex = 0; while (true) { const url = parseValue(cssRuleStr, searchStartIndex, selector, delimiters); if (url === null) { break; } searchStartIndex = url.foundAtIndex + url.value.length; if (mustEndWithQuote && url.value[url.value.length - 1] !== '"') continue; const unquoted = removeQuotes(url.value); if (!unquoted /* || (!unquoted.startsWith('http')&& !unquoted.startsWith("/") )*/ || unquoted === 'http://' || unquoted === 'https://') { continue; } unquoted && urlsFound.push(unquoted); } return urlsFound; }; /** * * @param {String} html * @returns {String[]} */ const getImageUrlsFromFromHtml = function (html) { return getUrlsFromCssString(html, "src=", [' ', '>', '\t'], true); }; const getSourceUrlsFromFromHtml = function (html) { return getUrlsFromCssString(html, "source=", [' ', '>', '\t'], true); }; /** * * @param {String} str * @returns {String} */ const removeQuotes = function (str) { return str.replace(/["']/g, ""); }; const escapeRegExp = function (string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string }; /** * * @param {String} contentHtml * @param {Number} width * @param {Number} height * * @returns {Promise} */ const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll, xoff) { return new Promise(async function (resolve, reject) { /* !! The problems !! * 1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with) * 2. Platform won't wait for external assets to load (fonts, images, etc.) */ // copy styles let cssStyles = ""; let urlsFoundInCss = []; for (let i = 0; i < styleSheets.length; i++) { try { const rules = styleSheets[i].cssRules for (let j = 0; j < rules.length; j++) { const cssRuleStr = rules[j].cssText; urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr)); cssStyles += cssRuleStr; } } catch (e) { } } // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss); // for (let i = 0; i < fetchedResourcesFromStylesheets.length; i++) { // const r = fetchedResourcesFromStylesheets[i]; // if (r.resourceUrl) { // cssStyles = cssStyles.replace(new RegExp(escapeRegExp(r.resourceUrl), "g"), r.resourceBase64); // } // } contentHtml = contentHtml.replace(/]*>/g, "") // tags have a which has a srcset field of image refs. instead of converting each, just use the default of the picture .replace(/noscript/g, "div").replace(/
<\/div>/g, "") // when scripting isn't available (ie, rendering web pages here),