E-book viewer: Speed up initialisation of WebEngine

Prevent the viewer JavaScript from being loaded multiple times on the
various iframes. Also only create the iframe used for the footnote
popups on demand.
This commit is contained in:
Kovid Goyal 2021-05-01 10:26:50 +05:30
parent 9a3a8bd271
commit b8e128271d
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 102 additions and 69 deletions

View File

@ -6,8 +6,8 @@ from elementmaker import E
from gettext import gettext as _ from gettext import gettext as _
from book_list.theme import get_color, get_color_as_rgba from book_list.theme import get_color, get_color_as_rgba
from dom import add_extra_css, build_rule, clear, ensure_id, svgicon from dom import add_extra_css, build_rule, clear, svgicon
from iframe_comm import IframeClient, IframeWrapper from iframe_comm import IframeClient, create_wrapped_iframe
from modals import create_custom_dialog from modals import create_custom_dialog
from utils import html_escape from utils import html_escape
from widgets import create_button from widgets import create_button
@ -348,17 +348,19 @@ def add_editor(editor):
class Editor: class Editor:
def __init__(self, iframe): def __init__(self, iframe_kw):
handlers = { handlers = {
'ready': self.on_iframe_ready, 'ready': self.on_iframe_ready,
'html': self.on_html_received, 'html': self.on_html_received,
'update_state': self.update_state, 'update_state': self.update_state,
} }
self.iframe_wrapper = IframeWrapper(handlers, iframe, 'book_list.comments_editor', _('Loading comments editor...')) iframe, self.iframe_wrapper = create_wrapped_iframe(
self.id = ensure_id(iframe) handlers, _('Loading comments editor...'), 'book_list.comments_editor', iframe_kw)
self.id = iframe.id
self.ready = False self.ready = False
self.pending_set_html = None self.pending_set_html = None
self.get_html_callbacks = v'[]' self.get_html_callbacks = v'[]'
self.iframe_obj = iframe
def init(self): def init(self):
self.iframe_wrapper.init() self.iframe_wrapper.init()
@ -421,8 +423,12 @@ class Editor:
def create_editor(): def create_editor():
iframe = E.iframe(sandbox='allow-scripts', seamless=True, style='flex-grow: 10; border: solid 1px currentColor', id=self.id) iframe_kw = {
editor = Editor(iframe) 'sandbox': 'allow-scripts', 'seamless': True, 'style': 'flex-grow: 10; border: solid 1px currentColor'
}
editor = Editor(iframe_kw)
iframe = editor.iframe_obj
v'delete editor.iframe_obj'
add_editor(editor) add_editor(editor)
return iframe, editor return iframe, editor

View File

@ -2,14 +2,14 @@
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals from __python__ import bound_methods, hash_literals
import traceback
from aes import GCM from aes import GCM
from gettext import gettext as _, install from elementmaker import E
import traceback
from book_list.globals import get_translations, main_js from book_list.globals import get_translations, main_js
from book_list.theme import get_font_family from book_list.theme import get_font_family
from dom import ensure_id from dom import ensure_id
from gettext import gettext as _, install
LOADING_DOC = ''' LOADING_DOC = '''
<!DOCTYPE html> <!DOCTYPE html>
@ -58,16 +58,22 @@ class Messenger:
class IframeWrapper: class IframeWrapper:
def __init__(self, handlers, iframe, entry_point, bootstrap_text, url): def __init__(self, handlers, iframe, entry_point, bootstrap_text):
self.messenger = Messenger() self.messenger = Messenger()
self.iframe_id = ensure_id(iframe, 'content-iframe') self.iframe_id = ensure_id(iframe, 'content-iframe')
self.needs_init = True if ':' in entry_point:
self.needs_init = False
self.srcdoc_created = True
self.constructor_url = entry_point
self.entry_point = None
else:
self.needs_init = True
self.srcdoc_created = False
self.constructor_url = None
self.entry_point = entry_point
self.ready = False self.ready = False
self.encrypted_communications = False self.encrypted_communications = False
self.srcdoc_created = False self.bootstrap_text = bootstrap_text or ''
self.constructor_url = url
self.entry_point = entry_point
self.bootstrap_text = bootstrap_text
self.handlers = {k: handlers[k] for k in handlers} self.handlers = {k: handlers[k] for k in handlers}
self.on_ready_handler = self.handlers.ready self.on_ready_handler = self.handlers.ready
self.handlers.ready = self.on_iframe_ready self.handlers.ready = self.on_iframe_ready
@ -164,6 +170,15 @@ class IframeWrapper:
callback() callback()
def create_wrapped_iframe(handlers, bootstrap_text, entry_point, kw):
if ':' in entry_point:
kw.src = entry_point
kw.sandbox = (kw.sandbox or '') + ' allow-same-origin'
iframe = E.iframe(**kw)
ans = IframeWrapper(handlers, iframe, entry_point, bootstrap_text)
return iframe, ans
class IframeClient: class IframeClient:
def __init__(self, handlers): def __init__(self, handlers):

View File

@ -6,7 +6,7 @@ from elementmaker import E
from gettext import gettext as _ from gettext import gettext as _
from dom import add_extra_css, build_rule, clear, svgicon from dom import add_extra_css, build_rule, clear, svgicon
from iframe_comm import IframeWrapper from iframe_comm import create_wrapped_iframe
from read_book.globals import runtime, ui_operations from read_book.globals import runtime, ui_operations
from read_book.resources import load_resources from read_book.resources import load_resources
@ -42,29 +42,35 @@ class ContentPopupOverlay:
self.loaded_resources = {} self.loaded_resources = {}
c = self.container c = self.container
c.classList.add(CLASS_NAME) c.classList.add(CLASS_NAME)
sandbox = 'allow-scripts' c.appendChild(E.div(E.div()))
if runtime.is_standalone_viewer:
sandbox += ' allow-same-origin'
iframe = E.iframe(seamless=True, sandbox=sandbox, style='width: 100%; max-height: 70vh')
c.appendChild(E.div(
E.div(),
iframe
))
c.addEventListener('click', self.hide) c.addEventListener('click', self.hide)
c.firstChild.addEventListener('click', def(ev): c.firstChild.addEventListener('click', def(ev):
ev.stopPropagation(), ev.preventDefault() ev.stopPropagation(), ev.preventDefault()
) )
self.pending_load = None
def reset(self):
if self.iframe_wrapper:
self.iframe_wrapper.reset()
def create_iframe(self):
handlers = { handlers = {
'ready': self.on_iframe_ready, 'ready': self.on_iframe_ready,
'error': self.view.on_iframe_error, 'error': self.view.on_iframe_error,
'content_loaded': self.on_content_loaded, 'content_loaded': self.on_content_loaded,
} }
entry_point = None if runtime.is_standalone_viewer else 'read_book.footnotes' iframe_kw = {
self.iframe_wrapper = IframeWrapper( 'seamless': True, 'sandbox': 'allow-scripts', 'style': 'width: 100%; max-height: 70vh'
handlers, iframe, entry_point, _('Loading data, please wait...'), }
f'{runtime.FAKE_PROTOCOL}://{runtime.SANDBOX_HOST}/book/__popup__') if runtime.is_standalone_viewer:
self.pending_load = None entry_point = f'{runtime.FAKE_PROTOCOL}://{runtime.SANDBOX_HOST}/book/__popup__'
else:
entry_point = 'read_book.footnotes'
iframe, self.iframe_wrapper = create_wrapped_iframe(
handlers, _('Loading data, please wait...'), entry_point, iframe_kw
)
c = self.container
c.firstChild.appendChild(iframe)
@property @property
def container(self): def container(self):
@ -117,6 +123,8 @@ class ContentPopupOverlay:
load_resources(self.view.book, name, self.loaded_resources, cb) load_resources(self.view.book, name, self.loaded_resources, cb)
def show_footnote(self, data): def show_footnote(self, data):
if not self.iframe_wrapper:
self.create_iframe()
self.current_footnote_data = data self.current_footnote_data = data
width = 100 // data.cols_per_screen width = 100 // data.cols_per_screen
c = self.container.firstChild c = self.container.firstChild

View File

@ -11,7 +11,7 @@ from book_list.theme import cached_color_to_rgba, get_color, set_ui_colors
from book_list.ui import query_as_href from book_list.ui import query_as_href
from dom import add_extra_css, build_rule, clear, set_css, svgicon, unique_id from dom import add_extra_css, build_rule, clear, set_css, svgicon, unique_id
from gettext import gettext as _ from gettext import gettext as _
from iframe_comm import IframeWrapper from iframe_comm import create_wrapped_iframe
from modals import error_dialog, warning_dialog from modals import error_dialog, warning_dialog
from read_book.annotations import AnnotationsManager from read_book.annotations import AnnotationsManager
from read_book.bookmarks import create_new_bookmark from read_book.bookmarks import create_new_bookmark
@ -229,38 +229,6 @@ class View:
set_left_margin_handler(left_margin) set_left_margin_handler(left_margin)
right_margin = side_margin_elem(self, sd, 'right') right_margin = side_margin_elem(self, sd, 'right')
set_right_margin_handler(right_margin) set_right_margin_handler(right_margin)
iframe_id = unique_id('read-book-iframe')
sandbox = 'allow-popups allow-scripts allow-popups-to-escape-sandbox'
if runtime.is_standalone_viewer:
sandbox += ' allow-same-origin'
container.appendChild(
E.div(style='max-height: 100vh; width: 100vw; height: 100vh; overflow: hidden; display: flex; align-items: stretch', # container for horizontally aligned panels
oncontextmenu=def (ev):
if not default_context_menu_should_be_allowed(ev):
ev.preventDefault()
,
E.div(style='max-height: 100vh; display: flex; flex-direction: column; align-items: stretch; flex-grow:2', # container for iframe and any other panels in the same column
E.div(style='max-height: 100vh; flex-grow: 2; display:flex; align-items: stretch', # container for iframe and its overlay
left_margin,
E.div(style='flex-grow:2; display:flex; align-items:stretch; flex-direction: column', # container for top and bottom margins
margin_elem(sd, 'margin_top', 'book-top-margin', self.top_margin_clicked, self.margin_context_menu.bind(None, 'top')),
E.iframe(id=iframe_id, seamless=True, sandbox=sandbox, style='flex-grow: 2', allowfullscreen='true'),
margin_elem(sd, 'margin_bottom', 'book-bottom-margin', self.bottom_margin_clicked, self.margin_context_menu.bind(None, 'bottom')),
),
right_margin,
self.book_scrollbar.create(),
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none;', id='book-selection-bar-overlay'), # selection bar overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none;', id='book-read-aloud-overlay'), # read aloud overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none;', id='book-hints-overlay'), # hints overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; pointer-events:none; display:none', id='book-search-overlay'), # search overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='book-content-popup-overlay'), # content popup overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; overflow: auto; display:none', id='book-overlay'), # main overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='controls-help-overlay'), # controls help overlay
)
),
),
)
handlers = { handlers = {
'autoscroll_state_changed': def(data): 'autoscroll_state_changed': def(data):
self.autoscroll_active = v'!!data.running' self.autoscroll_active = v'!!data.running'
@ -304,15 +272,51 @@ class View:
ui_operations.view_image(data.calibre_src) ui_operations.view_image(data.calibre_src)
, ,
} }
entry_point = None if runtime.is_standalone_viewer else 'read_book.iframe'
iframe_id = unique_id('read-book-iframe')
if runtime.is_standalone_viewer:
entry_point = f'{runtime.FAKE_PROTOCOL}://{runtime.SANDBOX_HOST}/book/__index__'
else:
entry_point = 'read_book.iframe'
iframe_kw = {
'id': iframe_id, 'seamless': True,
'sandbox': 'allow-popups allow-scripts allow-popups-to-escape-sandbox',
'style': 'flex-grow: 2', 'allowfullscreen': 'true',
}
iframe, self.iframe_wrapper = create_wrapped_iframe(handlers, _('Bootstrapping book reader...'), entry_point, iframe_kw)
container.appendChild(
E.div(style='max-height: 100vh; width: 100vw; height: 100vh; overflow: hidden; display: flex; align-items: stretch', # container for horizontally aligned panels
oncontextmenu=def (ev):
if not default_context_menu_should_be_allowed(ev):
ev.preventDefault()
,
E.div(style='max-height: 100vh; display: flex; flex-direction: column; align-items: stretch; flex-grow:2', # container for iframe and any other panels in the same column
E.div(style='max-height: 100vh; flex-grow: 2; display:flex; align-items: stretch', # container for iframe and its overlay
left_margin,
E.div(style='flex-grow:2; display:flex; align-items:stretch; flex-direction: column', # container for top and bottom margins
margin_elem(sd, 'margin_top', 'book-top-margin', self.top_margin_clicked, self.margin_context_menu.bind(None, 'top')),
iframe,
margin_elem(sd, 'margin_bottom', 'book-bottom-margin', self.bottom_margin_clicked, self.margin_context_menu.bind(None, 'bottom')),
),
right_margin,
self.book_scrollbar.create(),
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none;', id='book-selection-bar-overlay'), # selection bar overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none;', id='book-read-aloud-overlay'), # read aloud overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none;', id='book-hints-overlay'), # hints overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; pointer-events:none; display:none', id='book-search-overlay'), # search overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='book-content-popup-overlay'), # content popup overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; overflow: auto; display:none', id='book-overlay'), # main overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='controls-help-overlay'), # controls help overlay
)
),
),
)
self.current_color_scheme = resolve_color_scheme() self.current_color_scheme = resolve_color_scheme()
if runtime.is_standalone_viewer: if runtime.is_standalone_viewer:
document.documentElement.addEventListener('keydown', self.handle_keypress, {'passive': False}) document.documentElement.addEventListener('keydown', self.handle_keypress, {'passive': False})
set_ui_colors(self.current_color_scheme.is_dark_theme) set_ui_colors(self.current_color_scheme.is_dark_theme)
is_dark_theme(self.current_color_scheme.is_dark_theme) is_dark_theme(self.current_color_scheme.is_dark_theme)
self.iframe_wrapper = IframeWrapper(
handlers, document.getElementById(iframe_id), entry_point, _('Bootstrapping book reader...'),
f'{runtime.FAKE_PROTOCOL}://{runtime.SANDBOX_HOST}/book/__index__')
self.search_overlay = SearchOverlay(self) self.search_overlay = SearchOverlay(self)
self.content_popup_overlay = ContentPopupOverlay(self) self.content_popup_overlay = ContentPopupOverlay(self)
self.overlay = Overlay(self) self.overlay = Overlay(self)
@ -914,7 +918,7 @@ class View:
self.book_load_started = True self.book_load_started = True
if not is_current_book: if not is_current_book:
self.iframe_wrapper.reset() self.iframe_wrapper.reset()
self.content_popup_overlay.iframe_wrapper.reset() self.content_popup_overlay.reset()
self.loaded_resources = {} self.loaded_resources = {}
self.content_popup_overlay.loaded_resources = {} self.content_popup_overlay.loaded_resources = {}
self.timers.start_book(book) self.timers.start_book(book)