From 00334e9f1b6f34e27a41a94de54828e07ab578b0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 27 May 2017 15:59:23 +0530 Subject: [PATCH] Workaround for window.innerWidth/Height being wrong inside an iframe on Safari --- src/pyj/read_book/cfi.pyj | 3 ++- src/pyj/read_book/flow_mode.pyj | 12 ++++++------ src/pyj/read_book/globals.pyj | 12 ++++++++++++ src/pyj/read_book/iframe.pyj | 33 ++++++++++++++++++++++---------- src/pyj/read_book/paged_mode.pyj | 30 ++++++++++++++--------------- src/pyj/read_book/toc.pyj | 6 +++--- src/pyj/read_book/touch.pyj | 6 +++--- src/pyj/read_book/view.pyj | 16 +++++++++++++++- src/pyj/utils.pyj | 4 ++++ 9 files changed, 83 insertions(+), 39 deletions(-) diff --git a/src/pyj/read_book/cfi.pyj b/src/pyj/read_book/cfi.pyj index 08a678f69c..9d65db752b 100644 --- a/src/pyj/read_book/cfi.pyj +++ b/src/pyj/read_book/cfi.pyj @@ -21,6 +21,7 @@ from __python__ import hash_literals # scroll_to(cfi): which scrolls the browser to a point corresponding to the # given cfi, and returns the x and y co-ordinates of the point. +from read_book.globals import window_width, window_height # CFI escaping {{{ escape_pat = /[\[\],^();~@!-]/g @@ -539,7 +540,7 @@ def at_point(ox, oy): # {{{ def at_current(): # {{{ winx, winy = window_scroll_pos() - winw, winh = window.innerWidth, window.innerHeight + winw, winh = window_width(), window_height() winw = max(winw, 400) winh = max(winh, 600) deltay = Math.floor(winh/50) diff --git a/src/pyj/read_book/flow_mode.pyj b/src/pyj/read_book/flow_mode.pyj index b074b03d7c..bdd2605750 100644 --- a/src/pyj/read_book/flow_mode.pyj +++ b/src/pyj/read_book/flow_mode.pyj @@ -3,7 +3,7 @@ from __python__ import hash_literals, bound_methods from dom import set_css -from read_book.globals import get_boss +from read_book.globals import get_boss, window_width, window_height from keycodes import get_key from utils import document_height, document_width, viewport_to_document @@ -31,14 +31,14 @@ def flow_onwheel(evt): elif evt.deltaMode is evt.DOM_DELTA_LINE: dy = 15 * evt.deltaY if evt.deltaMode is evt.DOM_DELTA_PAGE: - dy = (window.innerHeight - 30) * evt.deltaY + dy = (window_height() - 30) * evt.deltaY if evt.deltaX: if evt.deltaMode is evt.DOM_DELTA_PIXEL: dx = evt.deltaX elif evt.deltaMode is evt.DOM_DELTA_LINE: dx = 15 * evt.deltaX else: - dx = (window.innerWidth - 30) * evt.deltaX + dx = (window_width() - 30) * evt.deltaX if dx: window.scrollBy(dx, 0) elif dy: @@ -63,7 +63,7 @@ def goto_boundary(y): @check_for_scroll_end def scroll_by_page(backward): - h = window.innerHeight - 10 + h = window_height() - 10 window.scrollBy(0, -h if backward else h) def flow_onkeydown(evt): @@ -175,10 +175,10 @@ anchor_funcs = { y, x = pos if y < window.pageYOffset: return -1 - if y < window.pageYOffset + window.innerHeight: + if y < window.pageYOffset + window_height(): if x < window.pageXOffset: return -1 - if x < window.pageXOffset + window.innerWidth: + if x < window.pageXOffset + window_width(): return 0 return 1 , diff --git a/src/pyj/read_book/globals.pyj b/src/pyj/read_book/globals.pyj index 08283feb27..a6788f9682 100644 --- a/src/pyj/read_book/globals.pyj +++ b/src/pyj/read_book/globals.pyj @@ -5,6 +5,7 @@ from __python__ import hash_literals from aes import GCM, random_bytes from encodings import hexlify from gettext import gettext as _, register_callback, gettext as gt +from utils import is_ios _boss = None @@ -72,3 +73,14 @@ register_callback(def(): scheme = default_color_schemes[key] scheme.name = gt(scheme.name) ) + +if is_ios: + window_width = def (): + return window_width.from_parent or window.innerWidth + window_height = def(): + return window_height.from_parent or window.innerHeight +else: + window_width = def(): + return window.innerWidth + window_height = def(): + return window.innerHeight diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 3b589285be..70d9d6ade4 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -8,7 +8,7 @@ from gettext import install, gettext as _ from utils import html_escape from read_book.cfi import at_current, scroll_to as scroll_to_cfi -from read_book.globals import set_boss, set_current_spine_item, current_layout_mode, current_spine_item, set_layout_mode, current_book +from read_book.globals import set_boss, set_current_spine_item, current_layout_mode, current_spine_item, set_layout_mode, current_book, window_width, window_height from read_book.mathjax import apply_mathjax from read_book.toc import update_visible_toc_anchors from read_book.resources import finalize_resources, unserialize_html @@ -25,7 +25,7 @@ from read_book.paged_mode import ( ) from read_book.settings import apply_settings, opts from read_book.touch import create_handlers as create_touch_handlers -from utils import debounce +from utils import debounce, is_ios FORCE_FLOW_MODE = False CALIBRE_VERSION = '__CALIBRE_VERSION__' @@ -82,6 +82,7 @@ class IframeBoss: 'change_color_scheme': self.change_color_scheme, 'gesture_from_margin': self.gesture_from_margin, 'find': self.find, + 'window_size': self.received_window_size, } self.last_window_ypos = 0 self.length_before = None @@ -113,6 +114,7 @@ class IframeBoss: def initialize(self, data): self.gcm_from_parent, self.gcm_to_parent = GCM(data.secret.subarray(0, 32)), GCM(data.secret.subarray(32)) + window_width.from_parent, window_height.from_parent = data.width, data.height if data.translations: install(data.translations) window.onerror = self.onerror @@ -294,14 +296,25 @@ class IframeBoss: def onresize(self): if self.content_ready: - if current_layout_mode() is not 'flow': - self.do_layout() - if self.last_cfi: - cfi = self.last_cfi[len('epubcfi(/'):-1].partition('/')[2] - if cfi: - paged_jump_to_cfi('/' + cfi) - self.update_cfi() - self.update_toc_position() + if is_ios: + # On iOS window.innerWidth/Height are wrong inside the iframe + self.send_message('request_size') + return + self.onresize_stage2() + + def onresize_stage2(self): + if current_layout_mode() is not 'flow': + self.do_layout() + if self.last_cfi: + cfi = self.last_cfi[len('epubcfi(/'):-1].partition('/')[2] + if cfi: + paged_jump_to_cfi('/' + cfi) + self.update_cfi() + self.update_toc_position() + + def received_window_size(self, data): + window_width.from_parent, window_height.from_parent = data.width, data.height + self.onresize_stage2() def onwheel(self, evt): if self.content_ready: diff --git a/src/pyj/read_book/paged_mode.pyj b/src/pyj/read_book/paged_mode.pyj index 4422c87740..1b59291572 100644 --- a/src/pyj/read_book/paged_mode.pyj +++ b/src/pyj/read_book/paged_mode.pyj @@ -6,7 +6,7 @@ from dom import set_css from elementmaker import E from keycodes import get_key from read_book.cfi import scroll_to as cfi_scroll_to, at_point as cfi_at_point, at_current as cfi_at_current -from read_book.globals import get_boss +from read_book.globals import get_boss, window_width, window_height from read_book.settings import opts import traceback from utils import get_elem_data, set_elem_data, viewport_to_document, document_width @@ -114,19 +114,19 @@ def layout(is_single_page): body_style = window.getComputedStyle(document.body) first_layout = not _in_paged_mode cps = opts.columns_per_screen or {} - cps = cps.landscape if window.innerWidth > window.innerHeight else cps.portrait + cps = cps.landscape if window_width() > window_height() else cps.portrait try: cps = int(cps) except: cps = 0 if not cps: - cps = int(Math.floor(window.innerWidth / 500.0)) + cps = int(Math.floor(window_width() / 500.0)) cps = max(1, min(cps or 1, 20)) if first_layout: handle_rtl_body(body_style) # Check if the current document is a full screen layout like # cover, if so we treat it specially. - single_screen = (document.body.scrollHeight < window.innerHeight + 75) + single_screen = (document.body.scrollHeight < window_height() + 75) first_layout = True has_svg = document.getElementsByTagName('svg').length > 0 imgs = document.getElementsByTagName('img') @@ -151,7 +151,7 @@ def layout(is_single_page): n = cols_per_screen = cps # Calculate the column width so that cols_per_screen columns fit exactly in # the window width, with their separator margins - ww = col_width = screen_width = window.innerWidth + ww = col_width = screen_width = window_width() gap = 0 if n > 1: # Adjust the side margin so that the window width satisfies @@ -160,7 +160,7 @@ def layout(is_single_page): gap = sm + ((ww + sm) % n) # Ensure ww + gap is a multiple of n col_width = ((ww + gap) // n) - gap - screen_height = window.innerHeight + screen_height = window_height() col_and_gap = col_width + gap set_css(document.body, column_gap=gap + 'px', column_width=col_width + 'px', column_rule='0px inset blue', @@ -231,7 +231,7 @@ def scroll_to_column(number, animated=False, notify=False, duration=1000): if animated: animated_scroll(pos, duration=1000, notify=notify) else: - window.scrollTo(pos, 0) + window.scroll(pos, 0) def scroll_to_xpos(xpos, animated=False, notify=False, duration=1000): # Scroll so that the column containing xpos is the left most column in @@ -290,8 +290,8 @@ def column_location(elem): c = column_at(left) width = min(br.right, (c+1)*col_and_gap) - br.left if br.bottom < br.top: - br.bottom = window.innerHeight - height = min(br.bottom, window.innerHeight) - br.top + br.bottom = window_height() + height = min(br.bottom, window_height()) - br.top left -= c * col_and_gap return {'column':c, 'left':left, 'top':top, 'width':width, 'height':height} @@ -322,7 +322,7 @@ def next_screen_location(): cols_left = ncols - (current_col + cols_per_screen) if cols_left < cols_per_screen: return -1 # Only blank, dummy pages left - limit = document.body.scrollWidth - window.innerWidth + limit = document.body.scrollWidth - window_width() if limit < col_and_gap: return -1 if ans > limit: @@ -350,7 +350,7 @@ def next_col_location(): return -1 cc = current_column_location() ans = cc + col_and_gap - limit = document.body.scrollWidth - window.innerWidth + limit = document.body.scrollWidth - window_width() if ans > limit: ans = limit if window.pageXOffset < limit else -1 return ans @@ -439,9 +439,9 @@ def current_cfi(): if left < 0 or right > document.body.scrollWidth: continue deltax = col_and_gap // 25 - deltay = window.innerHeight // 25 + deltay = window_height() // 25 cury = 0 - while cury < window.innerHeight: + while cury < window_height(): curx = left while curx < right - gap: cfi = cfi_at_point(curx-window.pageXOffset, cury-window.pageYOffset) @@ -463,11 +463,11 @@ def current_cfi(): def progress_frac(frac): # The current scroll position as a fraction between 0 and 1 if in_paged_mode: - limit = document.body.scrollWidth - window.innerWidth + limit = document.body.scrollWidth - window_width() if limit <= 0: return 0.0 return window.pageXOffset / limit - limit = document.body.scrollHeight - window.innerHeight + limit = document.body.scrollHeight - window_height() if limit <= 0: return 0.0 return window.pageYOffset / limit diff --git a/src/pyj/read_book/toc.pyj b/src/pyj/read_book/toc.pyj index b0f3328d50..e40dd331d1 100644 --- a/src/pyj/read_book/toc.pyj +++ b/src/pyj/read_book/toc.pyj @@ -8,7 +8,7 @@ from elementmaker import E from gettext import gettext as _ from modals import error_dialog from widgets import create_tree, find_text_in_tree, scroll_tree_item_into_view -from read_book.globals import toc_anchor_map, set_toc_anchor_map, current_spine_item, current_layout_mode, current_book +from read_book.globals import toc_anchor_map, set_toc_anchor_map, current_spine_item, current_layout_mode, current_book, window_width, window_height def update_visible_toc_nodes(visible_anchors): @@ -137,7 +137,7 @@ def create_toc_panel(book, container, onclick): def current_toc_anchor_map(tam, anchor_funcs): current_map = toc_anchor_map() - if not (current_map and current_map.layout_mode is current_layout_mode() and current_map.width is window.innerWidth and current_map.height is window.innerHeight): + if not (current_map and current_map.layout_mode is current_layout_mode() and current_map.width is window_width() and current_map.height is window_height()): name = current_spine_item().name am = {} anchors = v'[]' @@ -154,7 +154,7 @@ def current_toc_anchor_map(tam, anchor_funcs): # stable sort by position in document anchors.sort(def (a, b): return anchor_funcs.cmp(am[a], am[b]) or (pos_map[a] - pos_map[b]);) - current_map = {'layout_mode': current_layout_mode, 'width': window.innerWidth, 'height': window.innerHeight, 'pos_map': am, 'sorted_anchors':anchors} + current_map = {'layout_mode': current_layout_mode, 'width': window_width(), 'height': window_height(), 'pos_map': am, 'sorted_anchors':anchors} set_toc_anchor_map(current_map) return current_map diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index 5a29f3cc9b..b44650b17c 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -2,7 +2,7 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal from __python__ import hash_literals, bound_methods -from read_book.globals import get_boss +from read_book.globals import get_boss, window_width, window_height from book_list.globals import get_read_ui HOLD_THRESHOLD = 750 # milliseconds @@ -240,10 +240,10 @@ class BookTouchHandler(TouchHandler): return if not gesture.active: if self.for_side_margin or not tap_on_link(gesture): - if gesture.viewport_y < min(100, window.innerHeight / 4): + if gesture.viewport_y < min(100, window_height() / 4): gesture.type = 'show-chrome' else: - if gesture.viewport_x < min(100, window.innerWidth / 4): + if gesture.viewport_x < min(100, window_width() / 4): gesture.type = 'prev-page' else: gesture.type = 'next-page' diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 439994ba52..efe496e504 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -113,6 +113,7 @@ class View: 'show_chrome': self.show_chrome, 'bump_font_size': self.bump_font_size, 'find_in_spine': self.on_find_in_spine, + 'request_size': self.on_request_size, } self.currently_showing = {} @@ -138,6 +139,18 @@ class View: def forward_gesture(self, gesture): self.send_message('gesture_from_margin', gesture=gesture) + def iframe_size(self): + w, h = window.innerWidth, window.innerHeight + iframe = self.iframe + w -= 2 * iframe.offsetLeft + h -= 2 * iframe.offsetTop + return w, h + + def on_request_size(self, data): + # On iOS/Safari window.innerWidth/Height are incorrect inside an iframe + w, h = self.iframe_size() + self.send_message('window_size', width=w, height=h) + def find(self, text, backwards): self.send_message('find', text=text, backwards=backwards, searched_in_spine=False) @@ -247,7 +260,8 @@ class View: def on_iframe_ready(self, data): messenger.reset() - self.send_message('initialize', secret=messenger.secret, translations=get_translations()) + w, h = self.iframe_size() + self.send_message('initialize', secret=messenger.secret, translations=get_translations(), width=w, height=h) self.encrypted_communications = True self.iframe_ready = True self.do_pending_load() diff --git a/src/pyj/utils.pyj b/src/pyj/utils.pyj index e95a521c0e..28ae5433d3 100644 --- a/src/pyj/utils.pyj +++ b/src/pyj/utils.pyj @@ -6,6 +6,10 @@ from ajax import encode_query from encodings import hexlify from book_list.theme import get_font_family + +is_ios = v'!!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform)' + + def debounce(func, wait, immediate=False): # Returns a function, that, as long as it continues to be invoked, will not # be triggered. The function will be called after it stops being called for