From b8e128271d3ca114dc01ace1403589db2a971296 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 1 May 2021 10:26:50 +0530 Subject: [PATCH] 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. --- src/pyj/book_list/comments_editor.pyj | 20 ++++--- src/pyj/iframe_comm.pyj | 33 ++++++++--- src/pyj/read_book/content_popup.pyj | 38 ++++++++----- src/pyj/read_book/view.pyj | 80 ++++++++++++++------------- 4 files changed, 102 insertions(+), 69 deletions(-) diff --git a/src/pyj/book_list/comments_editor.pyj b/src/pyj/book_list/comments_editor.pyj index 1f3edea406..9116e17051 100644 --- a/src/pyj/book_list/comments_editor.pyj +++ b/src/pyj/book_list/comments_editor.pyj @@ -6,8 +6,8 @@ from elementmaker import E from gettext import gettext as _ from book_list.theme import get_color, get_color_as_rgba -from dom import add_extra_css, build_rule, clear, ensure_id, svgicon -from iframe_comm import IframeClient, IframeWrapper +from dom import add_extra_css, build_rule, clear, svgicon +from iframe_comm import IframeClient, create_wrapped_iframe from modals import create_custom_dialog from utils import html_escape from widgets import create_button @@ -348,17 +348,19 @@ def add_editor(editor): class Editor: - def __init__(self, iframe): + def __init__(self, iframe_kw): handlers = { 'ready': self.on_iframe_ready, 'html': self.on_html_received, 'update_state': self.update_state, } - self.iframe_wrapper = IframeWrapper(handlers, iframe, 'book_list.comments_editor', _('Loading comments editor...')) - self.id = ensure_id(iframe) + iframe, self.iframe_wrapper = create_wrapped_iframe( + handlers, _('Loading comments editor...'), 'book_list.comments_editor', iframe_kw) + self.id = iframe.id self.ready = False self.pending_set_html = None self.get_html_callbacks = v'[]' + self.iframe_obj = iframe def init(self): self.iframe_wrapper.init() @@ -421,8 +423,12 @@ class Editor: def create_editor(): - iframe = E.iframe(sandbox='allow-scripts', seamless=True, style='flex-grow: 10; border: solid 1px currentColor', id=self.id) - editor = Editor(iframe) + iframe_kw = { + '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) return iframe, editor diff --git a/src/pyj/iframe_comm.pyj b/src/pyj/iframe_comm.pyj index eb5589658e..22b80b09cd 100644 --- a/src/pyj/iframe_comm.pyj +++ b/src/pyj/iframe_comm.pyj @@ -2,14 +2,14 @@ # License: GPL v3 Copyright: 2017, Kovid Goyal from __python__ import bound_methods, hash_literals -import traceback 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.theme import get_font_family from dom import ensure_id - +from gettext import gettext as _, install LOADING_DOC = ''' @@ -58,16 +58,22 @@ class Messenger: 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.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.encrypted_communications = False - self.srcdoc_created = False - self.constructor_url = url - self.entry_point = entry_point - self.bootstrap_text = bootstrap_text + self.bootstrap_text = bootstrap_text or '' self.handlers = {k: handlers[k] for k in handlers} self.on_ready_handler = self.handlers.ready self.handlers.ready = self.on_iframe_ready @@ -164,6 +170,15 @@ class IframeWrapper: 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: def __init__(self, handlers): diff --git a/src/pyj/read_book/content_popup.pyj b/src/pyj/read_book/content_popup.pyj index 21a78e7731..4cf568e940 100644 --- a/src/pyj/read_book/content_popup.pyj +++ b/src/pyj/read_book/content_popup.pyj @@ -6,7 +6,7 @@ from elementmaker import E from gettext import gettext as _ 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.resources import load_resources @@ -42,29 +42,35 @@ class ContentPopupOverlay: self.loaded_resources = {} c = self.container c.classList.add(CLASS_NAME) - sandbox = 'allow-scripts' - 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.appendChild(E.div(E.div())) c.addEventListener('click', self.hide) c.firstChild.addEventListener('click', def(ev): ev.stopPropagation(), ev.preventDefault() ) + self.pending_load = None + + def reset(self): + if self.iframe_wrapper: + self.iframe_wrapper.reset() + + def create_iframe(self): handlers = { 'ready': self.on_iframe_ready, 'error': self.view.on_iframe_error, 'content_loaded': self.on_content_loaded, } - entry_point = None if runtime.is_standalone_viewer else 'read_book.footnotes' - self.iframe_wrapper = IframeWrapper( - handlers, iframe, entry_point, _('Loading data, please wait...'), - f'{runtime.FAKE_PROTOCOL}://{runtime.SANDBOX_HOST}/book/__popup__') - self.pending_load = None + iframe_kw = { + 'seamless': True, 'sandbox': 'allow-scripts', 'style': 'width: 100%; max-height: 70vh' + } + if runtime.is_standalone_viewer: + 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 def container(self): @@ -117,6 +123,8 @@ class ContentPopupOverlay: load_resources(self.view.book, name, self.loaded_resources, cb) def show_footnote(self, data): + if not self.iframe_wrapper: + self.create_iframe() self.current_footnote_data = data width = 100 // data.cols_per_screen c = self.container.firstChild diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 97765cce85..be39e35791 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -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 dom import add_extra_css, build_rule, clear, set_css, svgicon, unique_id 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 read_book.annotations import AnnotationsManager from read_book.bookmarks import create_new_bookmark @@ -229,38 +229,6 @@ class View: set_left_margin_handler(left_margin) right_margin = side_margin_elem(self, sd, 'right') 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 = { 'autoscroll_state_changed': def(data): self.autoscroll_active = v'!!data.running' @@ -304,15 +272,51 @@ class View: 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() if runtime.is_standalone_viewer: document.documentElement.addEventListener('keydown', self.handle_keypress, {'passive': False}) set_ui_colors(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.content_popup_overlay = ContentPopupOverlay(self) self.overlay = Overlay(self) @@ -914,7 +918,7 @@ class View: self.book_load_started = True if not is_current_book: self.iframe_wrapper.reset() - self.content_popup_overlay.iframe_wrapper.reset() + self.content_popup_overlay.reset() self.loaded_resources = {} self.content_popup_overlay.loaded_resources = {} self.timers.start_book(book)