2024-04-21 14:56:22 +02:00

299 lines
9.7 KiB
JavaScript

const iFrameId = 'zundler-iframe';
var set_favicon = function(href) {
if (!href) {return;}
var favicon = document.createElement("link");
favicon.setAttribute('rel', 'shortcut icon');
href = normalize_path(href);
const file = window.global_context.file_tree[href];
if (!file) {return;}
if (file.mime_type == 'image/svg+xml') {
favicon.setAttribute('href', 'data:' + file.mime_type + ';charset=utf-8;base64,' + btoa(file.data));
favicon.setAttribute('type', file.mime_type);
} else {
if (file.base64encoded) {
favicon.setAttribute('href', 'data:' + file.mime_type + ';base64,' + file.data);
}
}
document.head.appendChild(favicon);
};
var createIframe = function() {
var iframe = document.getElementById(iFrameId);
if (iframe) { iframe.remove() };
iframe = document.createElement("iframe");
window.document.body.prepend(iframe);
iframe.setAttribute('src', '#');
iframe.setAttribute('name', iFrameId);
iframe.setAttribute('id', iFrameId);
// iframe.style.display = 'none';
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;
}
};
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).data + ' \n//# sourceURL=' + 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' && link.getAttribute("href")) {
const style = doc.createElement("style");
var href = link.getAttribute('href');
let [path, get_parameters, anchor] = split_url(href);
path = normalize_path(path);
style.textContent = retrieve_file(path).data;
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 = 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 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
// return False if loading indicator should be removed right away
const file = window.global_context.file_tree[path];
var iframe = createIframe();
if (!file) {
console.error("File not found:", path, get_params, anchor);
return false;
}
const data = file.data;
window.global_context.get_parameters = get_params;
if (file.mime_type == 'text/html') {
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}, '', '#');
return true;
} else {
let blob = new Blob([data], {type: file.mime_type})
var url = URL.createObjectURL(blob)
var myWindow = window.open(url, "_blank");
return false;
}
});
var normalize_path = function(path) {
// TODO remove redundant definition of this function (in inject.js)
// make relative paths absolute
var result = window.global_context.current_path;
result = result.split('/');
result.pop();
result = result.concat(path.split('/'));
// resolve relative directories
var array = [];
Array.from(result).forEach( component => {
if (component == '..') {
if (array) {
array.pop();
}
} else if (component == '.') {
} else {
if (component) { array.push(component); }
}
});
result = array.join('/');
// console.log(`Normalized path: ${path} -> ${result} (@${window.global_context.current_path})`);
return result;
};
window.onload = function() {
// Set up message listener
window.addEventListener("message", (evnt) => {
console.log("Received message in parent", evnt);
if (evnt.data.action == 'set_title') {
// iframe has finished loading and sent us its title
// parent sets the title and responds with the global_context object
window.document.title = evnt.data.argument.title;
set_favicon(evnt.data.argument.favicon);
var iframe = document.getElementById(iFrameId);
iframe.contentWindow.postMessage({
action: "set_data",
argument: window.global_context,
}, "*");
} else if (evnt.data.action == 'virtual_click') {
// user has clicked on a link in the iframe
show_loading_indictator();
var loaded = load_virtual_page(
evnt.data.argument.path,
evnt.data.argument.get_parameters,
evnt.data.argument.anchor,
);
if (!loaded) {
hide_loading_indictator();
}
} else if (evnt.data.action == 'show_iframe') {
// iframe finished fixing the document and is ready to be shown;
hide_loading_indictator();
var iframe = document.getElementById(iFrameId);
iframe.contentWindow.postMessage({
action: "scroll_to_anchor",
}, "*");
}
}, false);
// Set up history event listener
window.addEventListener("popstate", (evnt) => {
load_virtual_page(evnt.state.path, evnt.state.get_params, evnt.state.anchor);
});
// Load first page
load_virtual_page(window.global_context.current_path, "", "");
}
var show_loading_indictator = function() {
var iframe = document.getElementById(iFrameId);
iframe.remove()
var loading = document.getElementById('loading-indicator');
loading.style.display = '';
}
var hide_loading_indictator = function() {
var iframe = document.getElementById(iFrameId);
iframe.style.display = '';
var loading = document.getElementById('loading-indicator');
loading.style.display = 'none';
}