Skip to content

Commit

Permalink
Simplify loading of javascript in render iframe
Browse files Browse the repository at this point in the history
Also improves performance by in-lining javascript into index.html
  • Loading branch information
kovidgoyal committed Mar 24, 2016
1 parent 35d2b9f commit c56ef9c
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 107 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ resources/builtin_recipes.zip
resources/template-functions.json
resources/editor-functions.json
resources/user-manual-translation-stats.json
resources/content-server/main.js
resources/content-server/iframe.js
resources/content-server/index-generated.html
resources/content-server/locales.zip
resources/mozilla-ca-certs.pem
icons/icns/*.iconset
Expand Down
5 changes: 2 additions & 3 deletions resources/content-server/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@
<meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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/font-awesome/fa.css"></link>
</head>
<body>
<div id="page_load_progress">
<progress>
</progress>
<div>LOADING_MSG&hellip;<div>
<div>Loading, please wait&hellip;<div>
<style type="text/css">
#page_load_progress {
position:relative;
Expand All @@ -25,5 +23,6 @@
}
</style>
</div>
<script id="main_js">MAIN_JS</script>
</body>
</html>
51 changes: 11 additions & 40 deletions src/calibre/srv/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@

from __future__ import (unicode_literals, division, absolute_import,
print_function)
import re, hashlib, random, zipfile
from functools import partial
from threading import Lock
from json import load as load_json_file, dumps as json_dumps
import hashlib, random, zipfile
from json import load as load_json_file

from calibre import prepare_string_for_xml, as_unicode
from calibre import as_unicode
from calibre.customize.ui import available_input_formats
from calibre.db.view import sanitize_sort_field_name
from calibre.srv.ajax import search_result
Expand All @@ -22,45 +20,18 @@
from calibre.utils.localization import get_lang
from calibre.utils.search_query_parser import ParseException

html_cache = {}
cache_lock = Lock()
autoreload_js = None
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)
def index(ctx, rd):
default_library = ctx.library_info(rd)[1]
return rd.generate_static_output('/', partial(
get_html, 'content-server/index.html', getattr(rd.opts, 'auto_reload_port', 0),
ENTRY_POINT='book list',
LOADING_MSG=prepare_string_for_xml(_('Loading library, please wait')),
DEFAULT_LIBRARY=json_dumps(default_library)
))
return lopen(P('content-server/index-generated.html'), 'rb')

@endpoint('/auto-reload', auth_required=False)
def auto_reload(ctx, rd):
auto_reload_port = getattr(rd.opts, 'auto_reload_port', 0)
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):
db, library_id, library_map, default_library = get_library_data(ctx, rd)
Expand Down
16 changes: 6 additions & 10 deletions src/calibre/utils/rapydscript.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
__license__ = 'GPL v3'
__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 threading import local
from functools import partial
Expand Down Expand Up @@ -105,17 +105,13 @@ def compile_srv():
with lopen(rb, 'rb') as f:
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)
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')
with lopen(fname, 'rb') as f:
raw = compile_pyj(f.read(), fname).replace("__IFRAME_SCRIPT_HASH__", sha).replace('__RENDER_VERSION__', rv)
with lopen(os.path.join(base, 'main.js'), 'wb') as f:
f.write(raw.encode('utf-8'))
js = compile_pyj(f.read(), fname).replace('__RENDER_VERSION__', rv).encode('utf-8')
with lopen(os.path.join(base, 'index.html'), 'rb') as f:
html = f.read().replace(b'MAIN_JS', js)
with lopen(os.path.join(base, 'index-generated.html'), 'wb') as f:
f.write(html)

# }}}

Expand Down
32 changes: 4 additions & 28 deletions src/pyj/read_book/db.pyj
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>

from ajax import ajax
from gettext import gettext as _
from utils import base64encode, base64decode

Expand All @@ -24,17 +23,14 @@ def get_error_details(event):
elif 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_VERSION = 1

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.idb = idb
self.iframe_script = iframe_script
self.supports_blobs = supports_blobs
if not supports_blobs:
print('IndexedDB does not support Blob storage, using base64 encoding instead')
Expand Down Expand Up @@ -192,29 +188,9 @@ def create_db(ui, interface_data):
req = idb.transaction(['files'], 'readwrite').objectStore('files').put(blob, ':-test-blob-:')
except Exception:
print('WARNING: browser does not support blob storage, calibre falling back to base64 encoding')
create_db_stage2(idb, ui, interface_data, False)
return
return ui.db_initialized(DB(idb, ui, False))
req.onsuccess = def(event):
create_db_stage2(idb, ui, interface_data, True)
ui.db_initialized(DB(idb, ui, True))
req.onerror = def(event):
print('WARNING: browser does not support blob storage, calibre falling back to base64 encoding')
create_db_stage2(idb, ui, interface_data, 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()
ui.db_initialized(DB(idb, ui, False))
2 changes: 0 additions & 2 deletions src/pyj/read_book/ui.pyj
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ class ReadUI:

def db_initialized(self, 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:
pl, self.pending_load = self.pending_load, None
self.start_load(*pl)
Expand Down
9 changes: 6 additions & 3 deletions src/pyj/read_book/view.pyj
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ LOADING_DOC = '''
<head>
<script type="text/javascript" id="bootstrap" data-key="__KEY__">
__SCRIPT__
</script>
end_script
</head>
<body>
__BS__
</body>
</html>
'''
'''.replace('end_script', '<' + '/script>') # cannot have a closing script tag as this is embedded inside a script tag in index.html

class View:

Expand All @@ -35,13 +35,16 @@ class View:
self.src_doc = None
self.iframe_ready = False
self.pending_spine_load = None
self.create_src_doc()
window.addEventListener('message', self.handle_message.bind(self), False)

@property
def iframe(self):
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(
'__SCRIPT__', iframe_script).replace(
'__BS__', _('Bootstrapping book reader...')).replace(
Expand Down
5 changes: 0 additions & 5 deletions src/pyj/reader.pyj

This file was deleted.

45 changes: 31 additions & 14 deletions src/pyj/srv.pyj
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,22 @@ from utils import parse_url_params

from book_list.boss import Boss
from book_list.globals import set_session_data
from read_book.iframe import init

def on_library_loaded(end_type, xhr, ev):
p = document.getElementById('page_load_progress')
p.parentNode.removeChild(p)
if end_type is 'load':
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:
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)
sd.set('library_id', interface_data.library_id)
Boss(interface_data)
else:
p = E.p(style='color:red; font-weight: bold; font-size:1.5em')
Expand All @@ -36,25 +42,36 @@ def on_library_load_progress(loaded, total):
def load_book_list():
temp = UserSessionData(None, {}) # So that settings for anonymous users are preserved
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')
if library_id:
query.library_id = library_id
query.sort = temp.get_library_option(library_id, 'sort')
url_query = parse_url_params()
for key in url_query:
query[key] = url_query[key]
ajax('interface-data/init', on_library_loaded, on_library_load_progress, query=query).send()

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

# 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)
print('calibre loaded at:', Date().toString())
load_book_list()

is_running_in_iframe = False # Changed before script is loaded in the iframe

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


0 comments on commit c56ef9c

Please sign in to comment.