mirror of
https://github.com/tcsenpai/Zundler.git
synced 2025-06-07 03:55:26 +00:00
Add monkeypatching
This commit is contained in:
parent
a34cfa6027
commit
2489e8ab31
36
src/embed.py
36
src/embed.py
@ -38,6 +38,7 @@ def embed_assets(index_file):
|
|||||||
'inject.js',
|
'inject.js',
|
||||||
'init.css',
|
'init.css',
|
||||||
'init.html',
|
'init.html',
|
||||||
|
'monkeypatch.js',
|
||||||
'pako.min.js',
|
'pako.min.js',
|
||||||
]:
|
]:
|
||||||
path = os.path.join(SCRIPT_PATH, filename)
|
path = os.path.join(SCRIPT_PATH, filename)
|
||||||
@ -48,7 +49,12 @@ def embed_assets(index_file):
|
|||||||
new_base_name = 'SELF_CONTAINED_' + base_name
|
new_base_name = 'SELF_CONTAINED_' + base_name
|
||||||
result_file = os.path.join(base_dir, new_base_name)
|
result_file = os.path.join(base_dir, new_base_name)
|
||||||
|
|
||||||
file_tree = load_filetree(base_dir, init_files['inject.js'], exclude_pattern=new_base_name)
|
file_tree = load_filetree(
|
||||||
|
base_dir,
|
||||||
|
before=init_files['monkeypatch.js'],
|
||||||
|
after=init_files['inject.js'],
|
||||||
|
exclude_pattern=new_base_name,
|
||||||
|
)
|
||||||
file_tree = json.dumps(file_tree)
|
file_tree = json.dumps(file_tree)
|
||||||
logger.debug('total asset size: %d' % len(file_tree))
|
logger.debug('total asset size: %d' % len(file_tree))
|
||||||
file_tree = deflate(file_tree)
|
file_tree = deflate(file_tree)
|
||||||
@ -86,7 +92,7 @@ def embed_assets(index_file):
|
|||||||
return result_file
|
return result_file
|
||||||
|
|
||||||
|
|
||||||
def pack_file(filename, js):
|
def pack_file(filename, before, after):
|
||||||
_, ext = os.path.splitext(filename)
|
_, ext = os.path.splitext(filename)
|
||||||
ext = ext.lower()[1:]
|
ext = ext.lower()[1:]
|
||||||
data = open(filename, 'rb').read()
|
data = open(filename, 'rb').read()
|
||||||
@ -101,7 +107,12 @@ def pack_file(filename, js):
|
|||||||
data = base64.b64encode(data)
|
data = base64.b64encode(data)
|
||||||
|
|
||||||
elif ext in ['html', 'htm']:
|
elif ext in ['html', 'htm']:
|
||||||
data = embed_html_resources(data, os.path.dirname(filename), js).encode()
|
data = embed_html_resources(
|
||||||
|
data,
|
||||||
|
os.path.dirname(filename),
|
||||||
|
before,
|
||||||
|
after,
|
||||||
|
).encode()
|
||||||
|
|
||||||
if not isinstance(data, str):
|
if not isinstance(data, str):
|
||||||
try:
|
try:
|
||||||
@ -120,16 +131,22 @@ def deflate(data):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def embed_html_resources(html, base_dir, js):
|
def embed_html_resources(html, base_dir, before, after):
|
||||||
"""Embed fonts in preload links to avoid jumps when loading"""
|
"""Embed fonts in preload links to avoid jumps when loading"""
|
||||||
# This cannot be done in JavaScript, it would be too late
|
# This cannot be done in JavaScript, it would be too late
|
||||||
|
|
||||||
import bs4
|
import bs4
|
||||||
soup = bs4.BeautifulSoup(html, 'lxml')
|
soup = bs4.BeautifulSoup(html, 'lxml')
|
||||||
|
|
||||||
script = soup.new_tag("script")
|
if before:
|
||||||
script.string = js
|
script = soup.new_tag("script")
|
||||||
soup.find('body').append(script)
|
script.string = before
|
||||||
|
soup.find('body').insert(0, script)
|
||||||
|
|
||||||
|
if after:
|
||||||
|
script = soup.new_tag("script")
|
||||||
|
script.string = after
|
||||||
|
soup.find('body').append(script)
|
||||||
|
|
||||||
return str(soup)
|
return str(soup)
|
||||||
|
|
||||||
@ -202,7 +219,7 @@ def embed_css_resources(css, filename):
|
|||||||
return css
|
return css
|
||||||
|
|
||||||
|
|
||||||
def load_filetree(base_dir, js, exclude_pattern=None):
|
def load_filetree(base_dir, before=None, after=None, exclude_pattern=None):
|
||||||
"""Load entire directory in a dict"""
|
"""Load entire directory in a dict"""
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
@ -214,7 +231,8 @@ def load_filetree(base_dir, js, exclude_pattern=None):
|
|||||||
key = path.relative_to(base_dir).as_posix()
|
key = path.relative_to(base_dir).as_posix()
|
||||||
result[key] = pack_file(
|
result[key] = pack_file(
|
||||||
path.as_posix(),
|
path.as_posix(),
|
||||||
js,
|
before,
|
||||||
|
after,
|
||||||
)
|
)
|
||||||
logger.debug('Packed file %s [%d]' % (key, len(result[key])))
|
logger.debug('Packed file %s [%d]' % (key, len(result[key])))
|
||||||
|
|
||||||
|
34
src/init.js
34
src/init.js
@ -32,13 +32,41 @@ var createIframe = function() {
|
|||||||
return iframe;
|
return iframe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// var load_blob = (function () {
|
||||||
|
// var a = document.createElement("a");
|
||||||
|
// document.body.appendChild(a);
|
||||||
|
// a.style = "display: none";
|
||||||
|
// return function (data, get_params, anchor) {
|
||||||
|
// var blob = new Blob([data], {type: "text/html"}),
|
||||||
|
// url = window.URL.createObjectURL(blob);
|
||||||
|
// if (get_params) {
|
||||||
|
// url += "?" + get_params; // This is considered a security issue
|
||||||
|
// }
|
||||||
|
// if (anchor) {
|
||||||
|
// url += "#" + anchor;
|
||||||
|
// }
|
||||||
|
// a.href = url;
|
||||||
|
// a.target = 'main';
|
||||||
|
// a.click();
|
||||||
|
// window.URL.revokeObjectURL(url);
|
||||||
|
// };
|
||||||
|
// }());
|
||||||
|
//
|
||||||
|
// var load_virtual_page = (function (path, get_params, anchor) {
|
||||||
|
// const data = window.data.file_tree[path];
|
||||||
|
// var iframe = createIframe();
|
||||||
|
// load_blob(data, get_params, anchor);
|
||||||
|
// window.data.current_path = path;
|
||||||
|
// });
|
||||||
|
|
||||||
var load_virtual_page = (function (path, get_params, anchor) {
|
var load_virtual_page = (function (path, get_params, anchor) {
|
||||||
const data = window.data.file_tree[path];
|
const data = window.data.file_tree[path];
|
||||||
var iframe = createIframe();
|
var iframe = createIframe();
|
||||||
if (get_params) { iframe.src = path + '?' + get_params; }
|
window.data.get_parameters = get_params;
|
||||||
else { iframe.src = path; }
|
|
||||||
iframe.contentDocument.write(data);
|
iframe.contentDocument.write(data);
|
||||||
if (anchor) { iframe.contentDocument.location.hash = anchor; }
|
if (anchor) {
|
||||||
|
iframe.contentDocument.location.hash = anchor;
|
||||||
|
}
|
||||||
window.data.current_path = path;
|
window.data.current_path = path;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -51,8 +51,17 @@ var split_url = function(url) {
|
|||||||
var virtual_click = function(evnt) {
|
var virtual_click = function(evnt) {
|
||||||
// Handle GET parameters and anchors
|
// Handle GET parameters and anchors
|
||||||
console.log("Virtual click", evnt);
|
console.log("Virtual click", evnt);
|
||||||
var a = evnt.currentTarget;
|
var el = evnt.currentTarget;
|
||||||
let [path, get_parameters, anchor] = split_url(a.getAttribute('href'));
|
var name = el.tagName.toLowerCase();
|
||||||
|
if (name == 'a') {
|
||||||
|
var [path, get_parameters, anchor] = split_url(el.getAttribute('href'));
|
||||||
|
} else if (name == 'form') {
|
||||||
|
var [path, get_parameters, anchor] = split_url(el.getAttribute('action'));
|
||||||
|
const formData = new FormData(el);
|
||||||
|
get_parameters = new URLSearchParams(formData).toString();
|
||||||
|
} else {
|
||||||
|
console.error("Invalid element", el);
|
||||||
|
}
|
||||||
path = normalize_path(path);
|
path = normalize_path(path);
|
||||||
|
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
@ -79,14 +88,8 @@ var fix_links = function(origin) {
|
|||||||
var fix_forms = function(origin) {
|
var fix_forms = function(origin) {
|
||||||
Array.from(document.querySelectorAll("form")).forEach( form => {
|
Array.from(document.querySelectorAll("form")).forEach( form => {
|
||||||
var href = form.getAttribute('action');
|
var href = form.getAttribute('action');
|
||||||
if (is_virtual(href)) {
|
if (is_virtual(href) && form.getAttribute('method').toLowerCase() == 'get') {
|
||||||
// TODO test this
|
form.addEventListener('submit', virtual_click);
|
||||||
// let [path, get_parameters, anchor] = split_url(href);
|
|
||||||
// path = normalize_path(path);
|
|
||||||
// var new_href = to_blob(retrieve_file(path), 'text/html');
|
|
||||||
// if (get_parameters) { new_href += '?' + get_parameters; }
|
|
||||||
// if (anchor) { new_href += '?' + anchor; }
|
|
||||||
// form.action = new_href;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -164,11 +167,17 @@ window.addEventListener("message", (evnt) => {
|
|||||||
console.log("Received data from parent", window.data);
|
console.log("Received data from parent", window.data);
|
||||||
// dynamically fix elements on this page
|
// dynamically fix elements on this page
|
||||||
try {
|
try {
|
||||||
|
embed_js(); // This might change the DOM, so do this first
|
||||||
embed_css();
|
embed_css();
|
||||||
fix_links();
|
fix_links();
|
||||||
fix_forms();
|
fix_forms();
|
||||||
embed_img();
|
embed_img();
|
||||||
embed_js();
|
// Trigger DOMContentLoaded again, some scripts that have just
|
||||||
|
// been executed expect it.
|
||||||
|
window.document.dispatchEvent(new Event("DOMContentLoaded", {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true
|
||||||
|
}));
|
||||||
} finally {
|
} finally {
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
action: "show_iframe",
|
action: "show_iframe",
|
||||||
@ -178,6 +187,7 @@ window.addEventListener("message", (evnt) => {
|
|||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
|
||||||
// Set parent window title
|
// Set parent window title
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
action: "set_title",
|
action: "set_title",
|
||||||
|
54
src/monkeypatch.js
Normal file
54
src/monkeypatch.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Monkeypatch URLSearchParams
|
||||||
|
*
|
||||||
|
* Sphinx documents that use `searchtool.js` rely on passing information via
|
||||||
|
* GET parameters (aka search parameters). Unfortunately, this doesn't work
|
||||||
|
* in our approach due to the same origin policy, so we have to get ...
|
||||||
|
* creative.
|
||||||
|
*
|
||||||
|
* Here, we patch the `URLSearchParams` class so it returns the information
|
||||||
|
* stored in `window.data.get_parameters`.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const originalGet = URLSearchParams.prototype.get;
|
||||||
|
|
||||||
|
var myGet = function (arg) {
|
||||||
|
const originalResult = originalGet.apply(this, [arg]);
|
||||||
|
// If searchtools.js of sphinx is used
|
||||||
|
if (
|
||||||
|
(arg == "q" || arg == "highlight") &&
|
||||||
|
window.DOCUMENTATION_OPTIONS &&
|
||||||
|
window.Scorer &&
|
||||||
|
window.data.get_parameters &&
|
||||||
|
(! originalResult)
|
||||||
|
) {
|
||||||
|
const params = new URLSearchParams('?' + window.data.get_parameters);
|
||||||
|
const result = params.get("q");
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return originalResult;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
URLSearchParams.prototype.get = myGet;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Monkeypatch fetch
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { fetch: originalFetch } = window;
|
||||||
|
|
||||||
|
window.fetch = async (...args) => {
|
||||||
|
let [resource, config ] = args;
|
||||||
|
var path = normalize_path(resource);
|
||||||
|
var response;
|
||||||
|
if (is_virtual(path)) {
|
||||||
|
var data = retrieve_file(path);
|
||||||
|
response = new Response(data);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
response = await originalFetch(resource, config);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user