Refactor iframe communications client side code into a separate class

This commit is contained in:
Kovid Goyal 2017-10-16 09:35:24 +05:30
parent 67f6cd6ce0
commit cad17a5785
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 83 additions and 51 deletions

View File

@ -2,9 +2,11 @@
# 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 book_list.globals import main_js, get_translations 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
@ -142,3 +144,63 @@ class IframeWrapper:
self.encrypted_communications = True self.encrypted_communications = True
if callback: if callback:
callback() callback()
class IframeClient:
def __init__(self, handlers):
self.encrypted_communications = False
self.handlers = {k: handlers[k] for k in handlers}
self.initialize_handler = handlers.initialize
self.handlers.initialize = self.initialize
self.ready_sent = False
window.addEventListener('message', self.handle_message, False)
window.addEventListener('load', def():
if not self.ready_sent:
self.send_message('ready', {})
self.ready_sent = True
)
def initialize(self, data):
nonlocal print
self.gcm_from_parent, self.gcm_to_parent = GCM(data.secret.subarray(0, 32)), GCM(data.secret.subarray(32))
if data.translations:
install(data.translations)
print = self.print_to_parent
self.encrypted_communications = True
if self.initialize_handler:
self.initialize_handler(data)
def print_to_parent(self, *args):
self.send_message('print', string=' '.join(map(str, args)))
def handle_message(self, event):
if event.source is not window.parent:
return
msg = event.data
data = msg.data
if msg.encrypted:
# We cannot use self.encrypted_communications as the 'display'
# message has to be unencrypted as it transports Blob objects
try:
data = JSON.parse(self.gcm_from_parent.decrypt(data))
except Exception as e:
print('Could not process message from parent:')
console.log(e)
return
func = self.handlers[data.action]
if func:
try:
func(data)
except Exception as e:
console.log('Error in iframe message handler:')
console.log(e)
self.send_message('error', title=_('Error in message handler'), details=traceback.format_exc(), msg=e.toString())
else:
print('Unknown action in message to iframe from parent: ' + data.action)
def send_message(self, action, data):
data.action = action
if self.encrypted_communications:
data = self.gcm_to_parent.encrypt(JSON.stringify(data))
window.parent.postMessage(data, '*')

View File

@ -6,6 +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 read_book.comm import IframeWrapper
CLASS_NAME = 'book-content-popup-container' CLASS_NAME = 'book-content-popup-container'
TOP_LEVEL_DISPLAY = 'flex' TOP_LEVEL_DISPLAY = 'flex'
@ -38,15 +39,21 @@ class ContentPopupOverlay:
self.view = view self.view = view
c = self.container c = self.container
c.classList.add(CLASS_NAME) c.classList.add(CLASS_NAME)
iframe = E.iframe(seamless=True, sandbox='allow-scripts')
c.appendChild(E.div( c.appendChild(E.div(
E.div(), E.div(),
E.iframe(seamless=True, sandbox='allow-scripts') 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()
) )
handlers = {
'ready': self.on_iframe_ready,
}
self.iframe_wrapper = IframeWrapper(handlers, iframe, 'popup', _('Loading data, please wait...'))
self.pending_load = None
@property @property
def container(self): def container(self):
@ -54,7 +61,7 @@ class ContentPopupOverlay:
@property @property
def iframe(self): def iframe(self):
return self.container.querySelector('iframe') return self.iframe_wrapper.iframe
@property @property
def is_visible(self): def is_visible(self):
@ -67,6 +74,9 @@ class ContentPopupOverlay:
c = self.container c = self.container
c.style.display = TOP_LEVEL_DISPLAY c.style.display = TOP_LEVEL_DISPLAY
def on_iframe_ready(self, msg):
return self.do_pending_load
def apply_color_scheme(self, bg, fg): def apply_color_scheme(self, bg, fg):
c = self.container.firstChild c = self.container.firstChild
c.style.backgroundColor = bg c.style.backgroundColor = bg

View File

@ -3,10 +3,10 @@
from __python__ import bound_methods, hash_literals from __python__ import bound_methods, hash_literals
import traceback import traceback
from aes import GCM from gettext import gettext as _
from gettext import gettext as _, install
from read_book.cfi import at_current, scroll_to as scroll_to_cfi from read_book.cfi import at_current, scroll_to as scroll_to_cfi
from read_book.comm import IframeClient
from read_book.flow_mode import ( from read_book.flow_mode import (
anchor_funcs as flow_anchor_funcs, flow_onkeydown, flow_onwheel, anchor_funcs as flow_anchor_funcs, flow_onkeydown, flow_onwheel,
flow_to_scroll_fraction, handle_gesture as flow_handle_gesture, flow_to_scroll_fraction, handle_gesture as flow_handle_gesture,
@ -66,7 +66,6 @@ class IframeBoss:
def __init__(self): def __init__(self):
window.navigator.epubReadingSystem = EPUBReadingSystem() window.navigator.epubReadingSystem = EPUBReadingSystem()
self.ready_sent = False
self.last_cfi = None self.last_cfi = None
self.replace_history_on_next_cfi_update = True self.replace_history_on_next_cfi_update = True
self.encrypted_communications = False self.encrypted_communications = False
@ -74,14 +73,8 @@ class IframeBoss:
self.resource_urls = {} self.resource_urls = {}
self.content_ready = False self.content_ready = False
self.last_window_width = self.last_window_height = -1 self.last_window_width = self.last_window_height = -1
window.addEventListener('message', self.handle_message, False)
window.addEventListener('load', def():
if not self.ready_sent:
self.send_message('ready')
self.ready_sent = True
)
set_boss(self) set_boss(self)
self.handlers = { handlers = {
'initialize':self.initialize, 'initialize':self.initialize,
'display': self.display, 'display': self.display,
'scroll_to_anchor': self.on_scroll_to_anchor, 'scroll_to_anchor': self.on_scroll_to_anchor,
@ -92,40 +85,12 @@ class IframeBoss:
'find': self.find, 'find': self.find,
'window_size': self.received_window_size, 'window_size': self.received_window_size,
} }
self.comm = IframeClient(handlers)
self.last_window_ypos = 0 self.last_window_ypos = 0
self.length_before = None self.length_before = None
def handle_message(self, event):
if event.source is not window.parent:
return
msg = event.data
data = msg.data
if msg.encrypted:
# We cannot use self.encrypted_communications as the 'display'
# message has to be unencrypted as it transports Blob objects
try:
data = JSON.parse(self.gcm_from_parent.decrypt(data))
except Exception as e:
print('Could not process message from parent:')
console.log(e)
return
func = self.handlers[data.action]
if func:
try:
func(data)
except Exception as e:
console.log('Error in iframe message handler:')
console.log(e)
self.send_message('error', title=_('Error in message handler'), details=traceback.format_exc(), msg=e.toString())
else:
print('Unknown action in message to iframe from parent: ' + data.action)
def initialize(self, data): def initialize(self, data):
nonlocal print
self.gcm_from_parent, self.gcm_to_parent = GCM(data.secret.subarray(0, 32)), GCM(data.secret.subarray(32))
scroll_viewport.update_window_size(data.width, data.height) scroll_viewport.update_window_size(data.width, data.height)
if data.translations:
install(data.translations)
window.onerror = self.onerror window.onerror = self.onerror
window.addEventListener('scroll', debounce(self.onscroll, 1000)) window.addEventListener('scroll', debounce(self.onscroll, 1000))
window.addEventListener('resize', debounce(self.onresize, 500)) window.addEventListener('resize', debounce(self.onresize, 500))
@ -133,13 +98,8 @@ class IframeBoss:
window.addEventListener('keydown', self.onkeydown) window.addEventListener('keydown', self.onkeydown)
document.documentElement.addEventListener('contextmenu', self.oncontextmenu) document.documentElement.addEventListener('contextmenu', self.oncontextmenu)
self.color_scheme = data.color_scheme self.color_scheme = data.color_scheme
self.encrypted_communications = True
print = self.print_to_parent
create_touch_handlers() create_touch_handlers()
def print_to_parent(self, *args):
self.send_message('print', string=' '.join(map(str, args)))
def onerror(self, msg, script_url, line_number, column_number, error_object): def onerror(self, msg, script_url, line_number, column_number, error_object):
if error_object is None: if error_object is None:
# This happens for cross-domain errors (probably javascript injected # This happens for cross-domain errors (probably javascript injected
@ -365,10 +325,7 @@ class IframeBoss:
self.send_message('show_chrome') self.send_message('show_chrome')
def send_message(self, action, **data): def send_message(self, action, **data):
data.action = action self.comm.send_message(action, data)
if self.encrypted_communications:
data = self.gcm_to_parent.encrypt(JSON.stringify(data))
window.parent.postMessage(data, '*')
def connect_links(self): def connect_links(self):
link_attr = 'data-' + self.book.manifest.link_uid link_attr = 'data-' + self.book.manifest.link_uid
@ -430,3 +387,5 @@ def init():
script = document.getElementById('bootstrap') script = document.getElementById('bootstrap')
script.parentNode.removeChild(script) # free up some memory script.parentNode.removeChild(script) # free up some memory
IframeBoss() IframeBoss()
# elif window.iframe_type is 'popup':
# PopupIframeBoss()

View File

@ -325,6 +325,7 @@ class View:
is_current_book = self.book and self.book.key == book.key is_current_book = self.book and self.book.key == book.key
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.book = current_book.book = book self.book = current_book.book = book
self.ui.db.update_last_read_time(book) self.ui.db.update_last_read_time(book)
self.loaded_resources = {} self.loaded_resources = {}