mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Simplify loading of javascript in render iframe
Also improves performance by in-lining javascript into index.html
This commit is contained in:
parent
35d2b9fda9
commit
c56ef9c087
3
.gitignore
vendored
3
.gitignore
vendored
@ -20,8 +20,7 @@ resources/builtin_recipes.zip
|
|||||||
resources/template-functions.json
|
resources/template-functions.json
|
||||||
resources/editor-functions.json
|
resources/editor-functions.json
|
||||||
resources/user-manual-translation-stats.json
|
resources/user-manual-translation-stats.json
|
||||||
resources/content-server/main.js
|
resources/content-server/index-generated.html
|
||||||
resources/content-server/iframe.js
|
|
||||||
resources/content-server/locales.zip
|
resources/content-server/locales.zip
|
||||||
resources/mozilla-ca-certs.pem
|
resources/mozilla-ca-certs.pem
|
||||||
icons/icns/*.iconset
|
icons/icns/*.iconset
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
<meta name="robots" content="noindex">
|
<meta name="robots" content="noindex">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/png" href="favicon.png">
|
<link rel="icon" type="image/png" href="favicon.png">
|
||||||
<script>window.calibre_entry_point = 'ENTRY_POINT'; window.calibre_default_library = DEFAULT_LIBRARY;</script>
|
|
||||||
<script type="text/javascript" src="static/main.js"></script>
|
|
||||||
<link rel="stylesheet" href="static/reset.css"></link>
|
<link rel="stylesheet" href="static/reset.css"></link>
|
||||||
<link rel="stylesheet" href="static/font-awesome/fa.css"></link>
|
<link rel="stylesheet" href="static/font-awesome/fa.css"></link>
|
||||||
</head>
|
</head>
|
||||||
@ -15,7 +13,7 @@
|
|||||||
<div id="page_load_progress">
|
<div id="page_load_progress">
|
||||||
<progress>
|
<progress>
|
||||||
</progress>
|
</progress>
|
||||||
<div>LOADING_MSG…<div>
|
<div>Loading, please wait…<div>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
#page_load_progress {
|
#page_load_progress {
|
||||||
position:relative;
|
position:relative;
|
||||||
@ -25,5 +23,6 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</div>
|
</div>
|
||||||
|
<script id="main_js">MAIN_JS</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -4,12 +4,10 @@
|
|||||||
|
|
||||||
from __future__ import (unicode_literals, division, absolute_import,
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
print_function)
|
print_function)
|
||||||
import re, hashlib, random, zipfile
|
import hashlib, random, zipfile
|
||||||
from functools import partial
|
from json import load as load_json_file
|
||||||
from threading import Lock
|
|
||||||
from json import load as load_json_file, dumps as json_dumps
|
|
||||||
|
|
||||||
from calibre import prepare_string_for_xml, as_unicode
|
from calibre import as_unicode
|
||||||
from calibre.customize.ui import available_input_formats
|
from calibre.customize.ui import available_input_formats
|
||||||
from calibre.db.view import sanitize_sort_field_name
|
from calibre.db.view import sanitize_sort_field_name
|
||||||
from calibre.srv.ajax import search_result
|
from calibre.srv.ajax import search_result
|
||||||
@ -22,45 +20,18 @@ from calibre.utils.icu import sort_key
|
|||||||
from calibre.utils.localization import get_lang
|
from calibre.utils.localization import get_lang
|
||||||
from calibre.utils.search_query_parser import ParseException
|
from calibre.utils.search_query_parser import ParseException
|
||||||
|
|
||||||
html_cache = {}
|
|
||||||
cache_lock = Lock()
|
|
||||||
autoreload_js = None
|
|
||||||
POSTABLE = frozenset({'GET', 'POST', 'HEAD'})
|
POSTABLE = frozenset({'GET', 'POST', 'HEAD'})
|
||||||
|
|
||||||
def get_html(name, auto_reload_port, **replacements):
|
|
||||||
global autoreload_js
|
|
||||||
key = (name, auto_reload_port, tuple(replacements.iteritems()))
|
|
||||||
with cache_lock:
|
|
||||||
try:
|
|
||||||
return html_cache[key]
|
|
||||||
except KeyError:
|
|
||||||
with lopen(P(name), 'rb') as f:
|
|
||||||
raw = f.read()
|
|
||||||
for k, val in key[-1]:
|
|
||||||
if isinstance(val, type('')):
|
|
||||||
val = val.encode('utf-8')
|
|
||||||
if isinstance(k, type('')):
|
|
||||||
k = k.encode('utf-8')
|
|
||||||
raw = raw.replace(k, val)
|
|
||||||
if auto_reload_port > 0:
|
|
||||||
if autoreload_js is None:
|
|
||||||
autoreload_js = P('content-server/autoreload.js', data=True, allow_user_override=False).replace(
|
|
||||||
b'AUTORELOAD_PORT', bytes(str(auto_reload_port)))
|
|
||||||
raw = re.sub(
|
|
||||||
br'(<\s*/\s*head\s*>)', br'<script type="text/javascript">%s</script>\1' % autoreload_js,
|
|
||||||
raw, flags=re.IGNORECASE)
|
|
||||||
html_cache[key] = raw
|
|
||||||
return raw
|
|
||||||
|
|
||||||
@endpoint('', auth_required=False)
|
@endpoint('', auth_required=False)
|
||||||
def index(ctx, rd):
|
def index(ctx, rd):
|
||||||
default_library = ctx.library_info(rd)[1]
|
return lopen(P('content-server/index-generated.html'), 'rb')
|
||||||
return rd.generate_static_output('/', partial(
|
|
||||||
get_html, 'content-server/index.html', getattr(rd.opts, 'auto_reload_port', 0),
|
@endpoint('/auto-reload', auth_required=False)
|
||||||
ENTRY_POINT='book list',
|
def auto_reload(ctx, rd):
|
||||||
LOADING_MSG=prepare_string_for_xml(_('Loading library, please wait')),
|
auto_reload_port = getattr(rd.opts, 'auto_reload_port', 0)
|
||||||
DEFAULT_LIBRARY=json_dumps(default_library)
|
if auto_reload_port > 0:
|
||||||
))
|
rd.outheaders.set('Calibre-Auto-Reload-Port', type('')(auto_reload_port), replace_all=True)
|
||||||
|
return lopen(P('content-server/autoreload.js'), 'rb')
|
||||||
|
|
||||||
def get_basic_query_data(ctx, rd):
|
def get_basic_query_data(ctx, rd):
|
||||||
db, library_id, library_map, default_library = get_library_data(ctx, rd)
|
db, library_id, library_map, default_library = get_library_data(ctx, rd)
|
||||||
|
@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import os, sys, atexit, errno, subprocess, glob, shutil, json, hashlib, re
|
import os, sys, atexit, errno, subprocess, glob, shutil, json, re
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from threading import local
|
from threading import local
|
||||||
from functools import partial
|
from functools import partial
|
||||||
@ -105,17 +105,13 @@ def compile_srv():
|
|||||||
with lopen(rb, 'rb') as f:
|
with lopen(rb, 'rb') as f:
|
||||||
rv = str(int(re.search(br'^RENDER_VERSION\s+=\s+(\d+)', f.read(), re.M).group(1)))
|
rv = str(int(re.search(br'^RENDER_VERSION\s+=\s+(\d+)', f.read(), re.M).group(1)))
|
||||||
base = P('content-server', allow_user_override=False)
|
base = P('content-server', allow_user_override=False)
|
||||||
fname = os.path.join(rapydscript_dir, 'reader.pyj')
|
|
||||||
with lopen(fname, 'rb') as f:
|
|
||||||
reader = compile_pyj(f.read(), fname)
|
|
||||||
sha = hashlib.sha1(reader).hexdigest()
|
|
||||||
with lopen(os.path.join(base, 'iframe.js'), 'wb') as f:
|
|
||||||
f.write(reader.encode('utf-8'))
|
|
||||||
fname = os.path.join(rapydscript_dir, 'srv.pyj')
|
fname = os.path.join(rapydscript_dir, 'srv.pyj')
|
||||||
with lopen(fname, 'rb') as f:
|
with lopen(fname, 'rb') as f:
|
||||||
raw = compile_pyj(f.read(), fname).replace("__IFRAME_SCRIPT_HASH__", sha).replace('__RENDER_VERSION__', rv)
|
js = compile_pyj(f.read(), fname).replace('__RENDER_VERSION__', rv).encode('utf-8')
|
||||||
with lopen(os.path.join(base, 'main.js'), 'wb') as f:
|
with lopen(os.path.join(base, 'index.html'), 'rb') as f:
|
||||||
f.write(raw.encode('utf-8'))
|
html = f.read().replace(b'MAIN_JS', js)
|
||||||
|
with lopen(os.path.join(base, 'index-generated.html'), 'wb') as f:
|
||||||
|
f.write(html)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
from ajax import ajax
|
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from utils import base64encode, base64decode
|
from utils import base64encode, base64decode
|
||||||
|
|
||||||
@ -24,17 +23,14 @@ def get_error_details(event):
|
|||||||
elif desc.errorCode:
|
elif desc.errorCode:
|
||||||
desc = desc.errorCode
|
desc = desc.errorCode
|
||||||
|
|
||||||
IFRAME_SCRIPT_HASH = "__IFRAME_SCRIPT_HASH__"
|
|
||||||
|
|
||||||
DB_NAME = 'calibre-books-db-testing' # TODO: Remove test suffix and change version back to 1
|
DB_NAME = 'calibre-books-db-testing' # TODO: Remove test suffix and change version back to 1
|
||||||
DB_VERSION = 1
|
DB_VERSION = 1
|
||||||
|
|
||||||
class DB:
|
class DB:
|
||||||
|
|
||||||
def __init__(self, idb, ui, supports_blobs, iframe_script):
|
def __init__(self, idb, ui, supports_blobs):
|
||||||
self.interface_data = ui.interface_data
|
self.interface_data = ui.interface_data
|
||||||
self.idb = idb
|
self.idb = idb
|
||||||
self.iframe_script = iframe_script
|
|
||||||
self.supports_blobs = supports_blobs
|
self.supports_blobs = supports_blobs
|
||||||
if not supports_blobs:
|
if not supports_blobs:
|
||||||
print('IndexedDB does not support Blob storage, using base64 encoding instead')
|
print('IndexedDB does not support Blob storage, using base64 encoding instead')
|
||||||
@ -192,29 +188,9 @@ def create_db(ui, interface_data):
|
|||||||
req = idb.transaction(['files'], 'readwrite').objectStore('files').put(blob, ':-test-blob-:')
|
req = idb.transaction(['files'], 'readwrite').objectStore('files').put(blob, ':-test-blob-:')
|
||||||
except Exception:
|
except Exception:
|
||||||
print('WARNING: browser does not support blob storage, calibre falling back to base64 encoding')
|
print('WARNING: browser does not support blob storage, calibre falling back to base64 encoding')
|
||||||
create_db_stage2(idb, ui, interface_data, False)
|
return ui.db_initialized(DB(idb, ui, False))
|
||||||
return
|
|
||||||
req.onsuccess = def(event):
|
req.onsuccess = def(event):
|
||||||
create_db_stage2(idb, ui, interface_data, True)
|
ui.db_initialized(DB(idb, ui, True))
|
||||||
req.onerror = def(event):
|
req.onerror = def(event):
|
||||||
print('WARNING: browser does not support blob storage, calibre falling back to base64 encoding')
|
print('WARNING: browser does not support blob storage, calibre falling back to base64 encoding')
|
||||||
create_db_stage2(idb, ui, interface_data, False)
|
ui.db_initialized(DB(idb, ui, False))
|
||||||
|
|
||||||
def create_db_stage2(idb, ui, interface_data, supports_blobs):
|
|
||||||
req = idb.transaction(['objects']).objectStore('objects').get('iframe.js')
|
|
||||||
req.onerror = def(event):
|
|
||||||
ui.db_initialized(_('Failed to initialize books database: ') + get_error_details(event))
|
|
||||||
req.onsuccess = def(event):
|
|
||||||
s = event.result
|
|
||||||
if s and s.script_hash is IFRAME_SCRIPT_HASH:
|
|
||||||
return ui.db_initialized(DB(idb, ui, supports_blobs, s.src))
|
|
||||||
ajax('static/iframe.js', def(end_type, xhr, event):
|
|
||||||
if end_type != 'load':
|
|
||||||
return ui.db_initialized('<div>' + _('Failed to load book reader script') + '</div>' + xhr.error_html)
|
|
||||||
obj = {'key':'iframe.js', 'script_hash': IFRAME_SCRIPT_HASH, 'src':xhr.responseText}
|
|
||||||
req = idb.transaction(['objects'], 'readwrite').objectStore('objects').put(obj)
|
|
||||||
req.onerror = def(event):
|
|
||||||
ui.db_initialized(_('Failed to store book reader script in database: ') + get_error_details(event))
|
|
||||||
req.onsuccess = def(event):
|
|
||||||
ui.db_initialized(DB(idb, ui, supports_blobs, obj.src))
|
|
||||||
).send()
|
|
||||||
|
@ -93,8 +93,6 @@ class ReadUI:
|
|||||||
|
|
||||||
def db_initialized(self, db):
|
def db_initialized(self, db):
|
||||||
self.db = db
|
self.db = db
|
||||||
if type(self.db) is not 'string':
|
|
||||||
self.view.create_src_doc(self.db.iframe_script)
|
|
||||||
if self.pending_load is not None:
|
if self.pending_load is not None:
|
||||||
pl, self.pending_load = self.pending_load, None
|
pl, self.pending_load = self.pending_load, None
|
||||||
self.start_load(*pl)
|
self.start_load(*pl)
|
||||||
|
@ -11,13 +11,13 @@ LOADING_DOC = '''
|
|||||||
<head>
|
<head>
|
||||||
<script type="text/javascript" id="bootstrap" data-key="__KEY__">
|
<script type="text/javascript" id="bootstrap" data-key="__KEY__">
|
||||||
__SCRIPT__
|
__SCRIPT__
|
||||||
</script>
|
end_script
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
__BS__
|
__BS__
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
'''
|
'''.replace('end_script', '<' + '/script>') # cannot have a closing script tag as this is embedded inside a script tag in index.html
|
||||||
|
|
||||||
class View:
|
class View:
|
||||||
|
|
||||||
@ -35,13 +35,16 @@ class View:
|
|||||||
self.src_doc = None
|
self.src_doc = None
|
||||||
self.iframe_ready = False
|
self.iframe_ready = False
|
||||||
self.pending_spine_load = None
|
self.pending_spine_load = None
|
||||||
|
self.create_src_doc()
|
||||||
window.addEventListener('message', self.handle_message.bind(self), False)
|
window.addEventListener('message', self.handle_message.bind(self), False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def iframe(self):
|
def iframe(self):
|
||||||
return document.getElementById(iframe_id)
|
return document.getElementById(iframe_id)
|
||||||
|
|
||||||
def create_src_doc(self, iframe_script):
|
def create_src_doc(self):
|
||||||
|
iframe_script = self.ui.interface_data.main_js.replace(/is_running_in_iframe\s*=\s*false/, 'is_running_in_iframe = true')
|
||||||
|
self.ui.interface_data.main_js = None
|
||||||
self.src_doc = self.iframe.srcdoc = LOADING_DOC.replace(
|
self.src_doc = self.iframe.srcdoc = LOADING_DOC.replace(
|
||||||
'__SCRIPT__', iframe_script).replace(
|
'__SCRIPT__', iframe_script).replace(
|
||||||
'__BS__', _('Bootstrapping book reader...')).replace(
|
'__BS__', _('Bootstrapping book reader...')).replace(
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
# vim:fileencoding=utf-8
|
|
||||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
||||||
|
|
||||||
from read_book.iframe import init
|
|
||||||
init()
|
|
@ -9,16 +9,22 @@ from utils import parse_url_params
|
|||||||
|
|
||||||
from book_list.boss import Boss
|
from book_list.boss import Boss
|
||||||
from book_list.globals import set_session_data
|
from book_list.globals import set_session_data
|
||||||
|
from read_book.iframe import init
|
||||||
|
|
||||||
def on_library_loaded(end_type, xhr, ev):
|
def on_library_loaded(end_type, xhr, ev):
|
||||||
p = document.getElementById('page_load_progress')
|
p = document.getElementById('page_load_progress')
|
||||||
p.parentNode.removeChild(p)
|
p.parentNode.removeChild(p)
|
||||||
if end_type is 'load':
|
if end_type is 'load':
|
||||||
interface_data = JSON.parse(xhr.responseText)
|
interface_data = JSON.parse(xhr.responseText)
|
||||||
|
interface_data.main_js = main_js
|
||||||
|
script = document.getElementById('main_js')
|
||||||
|
if script:
|
||||||
|
script.parentNode.removeChild(script) # Free up some memory
|
||||||
if interface_data.translations:
|
if interface_data.translations:
|
||||||
install(interface_data.translations)
|
install(interface_data.translations)
|
||||||
sd = UserSessionData(interface_data['username'], interface_data['user_session_data'])
|
sd = UserSessionData(interface_data.username, interface_data.user_session_data)
|
||||||
set_session_data(sd)
|
set_session_data(sd)
|
||||||
|
sd.set('library_id', interface_data.library_id)
|
||||||
Boss(interface_data)
|
Boss(interface_data)
|
||||||
else:
|
else:
|
||||||
p = E.p(style='color:red; font-weight: bold; font-size:1.5em')
|
p = E.p(style='color:red; font-weight: bold; font-size:1.5em')
|
||||||
@ -36,25 +42,36 @@ def on_library_load_progress(loaded, total):
|
|||||||
def load_book_list():
|
def load_book_list():
|
||||||
temp = UserSessionData(None, {}) # So that settings for anonymous users are preserved
|
temp = UserSessionData(None, {}) # So that settings for anonymous users are preserved
|
||||||
query = {}
|
query = {}
|
||||||
default_lib = window.calibre_default_library
|
|
||||||
v'delete window.calibre_default_library'
|
|
||||||
query.sort = temp.get_library_option(default_lib, 'sort')
|
|
||||||
library_id = temp.get('library_id')
|
library_id = temp.get('library_id')
|
||||||
if library_id:
|
if library_id:
|
||||||
query.library_id = library_id
|
query.library_id = library_id
|
||||||
|
query.sort = temp.get_library_option(library_id, 'sort')
|
||||||
url_query = parse_url_params()
|
url_query = parse_url_params()
|
||||||
for key in url_query:
|
for key in url_query:
|
||||||
query[key] = url_query[key]
|
query[key] = url_query[key]
|
||||||
ajax('interface-data/init', on_library_loaded, on_library_load_progress, query=query).send()
|
ajax('interface-data/init', on_library_loaded, on_library_load_progress, query=query).send()
|
||||||
|
|
||||||
def on_load():
|
def on_load():
|
||||||
ep = window.calibre_entry_point
|
print('calibre loaded at:', Date().toString())
|
||||||
v'delete window.calibre_entry_point'
|
load_book_list()
|
||||||
if ep is 'book list':
|
|
||||||
print('calibre loaded at:', Date().toString())
|
is_running_in_iframe = False # Changed before script is loaded in the iframe
|
||||||
load_book_list()
|
|
||||||
|
if is_running_in_iframe:
|
||||||
|
init()
|
||||||
|
else:
|
||||||
|
main_js = document.scripts[0].textContent
|
||||||
|
# We wait for all page elements to load, since this is a single page app
|
||||||
|
# with a largely empty starting document, we can use this to preload any resources
|
||||||
|
# we know are going to be needed immediately.
|
||||||
|
window.addEventListener('load', on_load)
|
||||||
|
|
||||||
|
ajax('auto-reload', def(end_type, xhr, event):
|
||||||
|
if end_type is 'load':
|
||||||
|
port = xhr.getResponseHeader('Calibre-Auto-Reload-Port')
|
||||||
|
if port:
|
||||||
|
src = xhr.responseText.replace('AUTORELOAD_PORT', port)
|
||||||
|
eval(src)
|
||||||
|
, bypass_cache=False).send()
|
||||||
|
|
||||||
|
|
||||||
# We wait for all page elements to load, since this is a single page app
|
|
||||||
# with a largely empty starting document, we can use this to preload any resources
|
|
||||||
# we know are going to be needed immediately.
|
|
||||||
window.addEventListener('load', on_load)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user