diff --git a/zundler/assets/inject_pre.js b/zundler/assets/inject_pre.js index daae3e8..8cb4779 100644 --- a/zundler/assets/inject_pre.js +++ b/zundler/assets/inject_pre.js @@ -66,37 +66,21 @@ window.history.replaceState = myReplaceState; const { fetch: originalFetch } = window; -async function waitFor(predicate, timeout) { - return new Promise((resolve, reject) => { - const check = () => { - if (!predicate()) return; - clearInterval(interval); - resolve(); - }; - const interval = setInterval(check, 100); - check(); - - if (!timeout) return; - setTimeout(() => { - clearInterval(interval); - reject(); - }, timeout); - }); +function waitForParentResponse(path) { + return new Promise((resolve, reject) => { + retrieveFileFromParent(path, file => { + resolve(file); + }); + }); } -window.fetch = async (...args) => { - // wait until globalContext is ready - try { - await waitFor(() => window.hasOwnProperty("globalContext"), 10000); - } catch (err) { - throw err; - } +window.fetch = async (...args) => { let [resource, config ] = args; var path = normalizePath(resource); var response; if (isVirtual(path)) { - var file = retrieveFile(path); + var file = await waitForParentResponse(path); var data = file.data; if (file.base64encoded) { data = _base64ToArrayBuffer(data); @@ -110,6 +94,47 @@ window.fetch = async (...args) => { }; +var retrieveFileFromParent = function(path, callback) { + // Get the file into the iframe by messaging the parent document + // console.log("Retrieving file from parent: " + path); + + function messageHandler(event) { + if (event.data.action === "sendFile" && event.data.argument.path === path) { + callback(event.data.argument.file); + window.removeEventListener('message', messageHandler); + } + } + + window.addEventListener('message', messageHandler); + + window.parent.postMessage({ +action: "retrieveFile", + argument: { + path: path, + } + }, '*'); +}; + + +var embedImgFromParent = function(img) { + function setSrc(img, file) { + if (mime_type == 'image/svg+xml') { + img.setAttribute('src', "data:image/svg+xml;charset=utf-8;base64, " + btoa(file.data)); + } else { + img.setAttribute('src', `data:${file.mime_type};base64, ${file.data}`); + } + }; + + if (img.hasAttribute('src')) { + const src = img.getAttribute('src'); + if (isVirtual(src)) { + var path = normalizePath(src); + retrieveFileFromParent(path, file => setSrc(img, file)); + }; + }; +}; + + const observer = new MutationObserver((mutationList) => { // console.log("Fix mutated elements...", mutationList); mutationList.forEach((mutation) => { @@ -118,7 +143,7 @@ const observer = new MutationObserver((mutationList) => { fixLink(a); }); Array.from(mutation.target.querySelectorAll("img")).forEach( img => { - embedImg(img); + embedImgFromParent(img); }); Array.from(mutation.target.querySelectorAll("form")).forEach( form => { fixForm(form); diff --git a/zundler/assets/zundler_common.js b/zundler/assets/zundler_common.js index 0eb5fbc..671d2c7 100644 --- a/zundler/assets/zundler_common.js +++ b/zundler/assets/zundler_common.js @@ -2,19 +2,6 @@ * Functions that will be needed by several files */ -var retrieveFile = function(path) { - // console.log("Retrieving file: " + path); - var fileTree = window.globalContext.fileTree; - var file = fileTree[path]; - if (!file) { - console.warn("File not found: " + path); - return ""; - } else { - return file; - } -}; - - var isVirtual = function(url) { // Return true if the url should be retrieved from the virtual file tree var _url = url.toString().toLowerCase(); @@ -71,23 +58,6 @@ var fixForm = function(form) { }; -var embedImg = function(img) { - if (img.hasAttribute('src')) { - const src = img.getAttribute('src'); - if (isVirtual(src)) { - var path = normalizePath(src); - const file = retrieveFile(path); - const mime_type = file.mime_type; - if (mime_type == 'image/svg+xml') { - img.setAttribute('src', "data:image/svg+xml;charset=utf-8;base64, " + btoa(file.data)); - } else { - img.setAttribute('src', `data:${mime_type};base64, ${file.data}`); - } - }; - }; -}; - - var normalizePath = function(path) { // make relative paths absolute var result = window.globalContext.current_path; diff --git a/zundler/assets/zundler_main.js b/zundler/assets/zundler_main.js index c7fca18..0284e45 100644 --- a/zundler/assets/zundler_main.js +++ b/zundler/assets/zundler_main.js @@ -1,5 +1,18 @@ const iFrameId = 'zundler-iframe'; +var retrieveFile = function(path) { + // console.log("Retrieving file: " + path); + var fileTree = window.globalContext.fileTree; + var file = fileTree[path]; + if (!file) { + console.warn("File not found: " + path); + return ""; + } else { + return file; + } +}; + + var setFavicon = function(href) { if (!href) {return;} var favicon = document.createElement("link"); @@ -32,6 +45,25 @@ var createIframe = function() { } +function deepCopyExcept(obj, skipProps) { + if (obj === null || typeof obj !== 'object') { + return obj; + } + + let result = Array.isArray(obj) ? [] : {}; + + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + if (!skipProps.includes(key)) { + result[key] = deepCopyExcept(obj[key], skipProps); + } + } + } + + return result; +} + + var prepare = function(html) { function unicodeToBase64(string) { const utf8EncodedString = unescape(encodeURIComponent(string)); @@ -41,12 +73,18 @@ var prepare = function(html) { var parser = new DOMParser(); var doc = parser.parseFromString(html, "text/html"); - const gcTag = doc.createElement("script"); + // Insert the global context into the iframe's DOM, but without the file + // tree or utils. They are not necessary; the iframe will message the + // parent document to retrieve files. + // // Convert JSON object to b64 because it contain all kinds of // problematic characters: `, ", ', &, , ... // atob is insufficient, because it only deals with ASCII - we have // unicode - var serializedGC = unicodeToBase64(JSON.stringify(window.globalContext)); + const gcTag = doc.createElement("script"); + const strippedGC = deepCopyExcept(window.globalContext, ["fileTree", "utils"]); + + var serializedGC = unicodeToBase64(JSON.stringify(strippedGC)); gcTag.textContent = ` function base64ToUnicode(base64String) { @@ -82,6 +120,23 @@ var prepare = function(html) { } +var embedImg = function(img) { + if (img.hasAttribute('src')) { + const src = img.getAttribute('src'); + if (isVirtual(src)) { + var path = normalizePath(src); + const file = retrieveFile(path); + const mime_type = file.mime_type; + if (mime_type == 'image/svg+xml') { + img.setAttribute('src', "data:image/svg+xml;charset=utf-8;base64, " + btoa(file.data)); + } else { + img.setAttribute('src', `data:${mime_type};base64, ${file.data}`); + } + }; + }; +}; + + var embedJs = function(doc) { Array.from(doc.querySelectorAll("script")).forEach( oldScript => { const newScript = doc.createElement("script"); @@ -181,8 +236,20 @@ window.onload = function() { if (evnt.data.action == 'ready') { hideLoadingIndicator(); + } else if (evnt.data.action == 'retrieveFile') { + const path = evnt.data.argument.path; + const file = retrieveFile(path); + iframe.contentWindow.postMessage({ + action: "sendFile", + argument: { + path: path, + file: file, + }, + }, "*"); + } else if (evnt.data.action == 'showMenu') { showMenu(); + } else if (evnt.data.action == 'set_title') { // iframe has finished loading and sent us its title // parent sets the title and responds with the globalContext object