mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Refactor iframe communications client side code into a separate class
This commit is contained in:
parent
67f6cd6ce0
commit
cad17a5785
@ -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, '*')
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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 = {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user