Simplify loading of javascript in render iframe

Also improves performance by in-lining javascript into index.html
This commit is contained in:
Kovid Goyal 2016-03-24 23:41:57 +05:30
parent 35d2b9fda9
commit c56ef9c087
9 changed files with 60 additions and 106 deletions

3
.gitignore vendored
View File

@ -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

View File

@ -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&hellip;<div> <div>Loading, please wait&hellip;<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>

View File

@ -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)

View File

@ -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)
# }}} # }}}

View File

@ -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()

View File

@ -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)

View File

@ -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(

View File

@ -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()

View File

@ -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
v'delete window.calibre_entry_point'
if ep is 'book list':
print('calibre loaded at:', Date().toString()) print('calibre loaded at:', Date().toString())
load_book_list() load_book_list()
# We wait for all page elements to load, since this is a single page app is_running_in_iframe = False # Changed before script is loaded in the iframe
# with a largely empty starting document, we can use this to preload any resources
# we know are going to be needed immediately. if is_running_in_iframe:
window.addEventListener('load', on_load) 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()