Process DOM of virtual HTML file before rendering

With this commit, the virtual HTML file is processed before it's fed to
the iframe and thus rendered. With the previous approach, script tags
with inline JavaScript were executed first and script tags with a
virtual `src` were executed last. This messed up the execution flow of
the document.
This commit is contained in:
Adrian Vollmer 2024-02-18 21:05:51 +01:00
parent 6aaa3841d2
commit 2b870da3cb
2 changed files with 152 additions and 108 deletions

View File

@ -1,41 +1,3 @@
var embed_css = function() {
Array.from(document.querySelectorAll("link")).forEach( link => {
if (link.getAttribute('rel') == 'stylesheet') {
const style = document.createElement("style");
var href = link.getAttribute('href');
let [path, get_parameters, anchor] = split_url(href);
path = normalize_path(path);
style.innerText = retrieve_file(path);
link.replaceWith(style);
};
});
};
var embed_js = function() {
Array.from(document.querySelectorAll("script")).forEach( oldScript => {
const newScript = document.createElement("script");
Array.from(oldScript.attributes).forEach( attr => {
newScript.setAttribute(attr.name, attr.value);
});
try {
if (newScript.hasAttribute('src') && is_virtual(newScript.getAttribute('src'))) {
var src = newScript.getAttribute('src');
let [path, get_parameters, anchor] = split_url(src);
path = normalize_path(path);
var src = retrieve_file(path) + ' //# sourceMap=' + path;
newScript.appendChild(document.createTextNode(src));
newScript.removeAttribute('src');
oldScript.parentNode.replaceChild(newScript, oldScript);
}
} catch (e) {
// Make sure all scripts are loaded
console.error("Caught error in " + oldScript.getAttribute("src"), e);
}
});
};
var split_url = function(url) {
// Return a list of three elements: path, GET parameters, anchor
var anchor = url.split('#')[1] || "";
@ -80,61 +42,6 @@ var virtual_click = function(evnt) {
return false;
};
var fix_links = function() {
Array.from(document.querySelectorAll("a")).forEach( a => {
fix_link(a);
});
};
var fix_link = function(a) {
if (is_virtual(a.getAttribute('href'))) {
a.addEventListener('click', virtual_click);
} else if (a.getAttribute('href').startsWith('#')) {
a.setAttribute('href', "about:srcdoc" + a.getAttribute('href'))
} else if (!a.getAttribute('href').startsWith('about:srcdoc')) {
// External links should open in a new tab. Browsers block links to
// sites of different origin within an iframe for security reasons.
a.setAttribute('target', "_blank");
}
};
var fix_form = function(form) {
var href = form.getAttribute('action');
if (is_virtual(href) && form.getAttribute('method').toLowerCase() == 'get') {
form.addEventListener('submit', virtual_click);
}
};
var fix_forms = function() {
Array.from(document.querySelectorAll("form")).forEach( form => {
fix_form(form);
});
};
var embed_img = function(img) {
if (img.hasAttribute('src')) {
const src = img.getAttribute('src');
if (is_virtual(src)) {
var path = normalize_path(src);
const file = retrieve_file(path);
const mime_type = window.global_context.file_tree[path].mime_type;
if (mime_type == 'image/svg+xml') {
img.setAttribute('src', "data:image/svg+xml;charset=utf-8;base64, " + btoa(file));
} else {
img.setAttribute('src', `data:${mime_type};base64, ${file}`);
}
};
};
};
var embed_imgs = function() {
Array.from(document.querySelectorAll("img")).forEach( img => {
embed_img(img);
});
};
var is_virtual = function(url) {
// Return true if the url should be retrieved from the virtual file tree
var _url = url.toString().toLowerCase();
@ -192,22 +99,11 @@ var normalize_path = function(path) {
};
var fix_document = function() {
embed_js(); // This might change the DOM, so do this first
monkey_patch();
embed_css();
embed_imgs();
fix_links();
fix_forms();
};
var on_set_data = function(argument) {
window.global_context = argument;
console.log("Received data from parent", window.global_context);
// dynamically fix elements on this page
try {
fix_document();
monkey_patch();
// Trigger DOMContentLoaded again, some scripts that have just
// been executed expect it.
window.document.dispatchEvent(new Event("DOMContentLoaded", {

View File

@ -31,6 +31,154 @@ var createIframe = function() {
return iframe;
}
var retrieve_file = function(path) {
// console.log("Retrieving file: " + path);
var file_tree = window.global_context.file_tree;
var file = file_tree[path];
if (!file) {
console.warn("File not found: " + path);
return "";
} else {
return file.data;
}
};
var is_virtual = function(url) {
// Return true if the url should be retrieved from the virtual file tree
var _url = url.toString().toLowerCase();
return (! (
_url == "" ||
_url[0] == "#" ||
_url.startsWith('https://') ||
_url.startsWith('http://') ||
_url.startsWith('data:') ||
_url.startsWith('about:srcdoc') ||
_url.startsWith('blob:')
));
};
var split_url = function(url) {
// Return a list of three elements: path, GET parameters, anchor
var anchor = url.split('#')[1] || "";
var get_parameters = url.split('#')[0].split('?')[1] || "";
var path = url.split('#')[0];
path = path.split('?')[0];
let result = [path, get_parameters, anchor];
// console.log("Split URL", url, result);
return result;
};
var prepare = function(html) {
var parser = new DOMParser();
var doc = parser.parseFromString(html, "text/html");
embed_js(doc);
embed_css(doc);
embed_imgs(doc);
fix_links(doc);
fix_forms(doc);
return doc.documentElement.outerHTML;
}
var embed_js = function(doc) {
Array.from(doc.querySelectorAll("script")).forEach( oldScript => {
const newScript = doc.createElement("script");
Array.from(oldScript.attributes).forEach( attr => {
newScript.setAttribute(attr.name, attr.value);
});
try {
if (newScript.hasAttribute('src') && is_virtual(newScript.getAttribute('src'))) {
var src = newScript.getAttribute('src');
let [path, get_parameters, anchor] = split_url(src);
path = normalize_path(path);
console.debug("Embed script: " + path);
var src = retrieve_file(path) + ' //# sourceMap=' + path;
newScript.appendChild(doc.createTextNode(src));
newScript.removeAttribute('src');
oldScript.parentNode.replaceChild(newScript, oldScript);
}
} catch (e) {
// Make sure all scripts are loaded
console.error("Caught error in " + oldScript.getAttribute("src"), e);
}
});
}
var embed_css = function(doc) {
Array.from(doc.querySelectorAll("link")).forEach( link => {
if (link.getAttribute('rel') == 'stylesheet') {
const style = doc.createElement("style");
var href = link.getAttribute('href');
let [path, get_parameters, anchor] = split_url(href);
path = normalize_path(path);
style.innerText = retrieve_file(path);
link.replaceWith(style);
};
});
};
var fix_links = function(doc) {
Array.from(doc.querySelectorAll("a")).forEach( a => {
fix_link(a);
});
};
var fix_link = function(a) {
if (is_virtual(a.getAttribute('href'))) {
// a.addEventListener('click', virtual_click);
a.setAttribute("onclick", "virtual_click(event)");
} else if (a.getAttribute('href').startsWith('#')) {
a.setAttribute('href', "about:srcdoc" + a.getAttribute('href'))
} else if (!a.getAttribute('href').startsWith('about:srcdoc')) {
// External links should open in a new tab. Browsers block links to
// sites of different origin within an iframe for security reasons.
a.setAttribute('target', "_blank");
}
};
var fix_form = function(form) {
var href = form.getAttribute('action');
if (is_virtual(href) && form.getAttribute('method').toLowerCase() == 'get') {
// form.addEventListener('submit', virtual_click);
form.setAttribute("onsubmit", "virtual_click(event)");
}
};
var fix_forms = function(doc) {
Array.from(doc.querySelectorAll("form")).forEach( form => {
fix_form(form);
});
};
var embed_img = function(img) {
if (img.hasAttribute('src')) {
const src = img.getAttribute('src');
if (is_virtual(src)) {
var path = normalize_path(src);
const file = retrieve_file(path);
const mime_type = window.global_context.file_tree[path].mime_type;
if (mime_type == 'image/svg+xml') {
img.setAttribute('src', "data:image/svg+xml;charset=utf-8;base64, " + btoa(file));
} else {
img.setAttribute('src', `data:${mime_type};base64, ${file}`);
}
};
};
};
var embed_imgs = function(doc) {
Array.from(doc.querySelectorAll("img")).forEach( img => {
embed_img(img);
});
};
var load_virtual_page = (function (path, get_params, anchor) {
// fill the iframe with the new page
// return True if it worked
@ -43,11 +191,12 @@ var load_virtual_page = (function (path, get_params, anchor) {
return false;
}
const data = file.data;
window.global_context.get_parameters = get_params;
if (file.mime_type == 'text/html') {
iframe.setAttribute("srcdoc", data);
const html = prepare(data);
iframe.setAttribute("srcdoc", html);
window.global_context.current_path = path;
window.global_context.anchor = anchor;
window.history.pushState({path, get_params, anchor}, '', '#');
@ -148,5 +297,4 @@ var hide_loading_indictator = function() {
loading.style.display = 'none';
}
//# sourceURL=main.js