UNPKG

r2-navigator-js

Version:

Readium 2 'navigator' for NodeJS (TypeScript)

1,150 lines 123 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.computeCFI = exports.computeProgressionData = void 0; const tslib_1 = require("tslib"); const debounce_1 = require("debounce"); const debug_ = require("debug"); const electron_1 = require("electron"); const tabbable_1 = require("tabbable"); const UrlUtils_1 = require("r2-utils-js/dist/es7-es2016/src/_utils/http/UrlUtils"); const sessions_1 = require("../../common/sessions"); const events_1 = require("../../common/events"); const readium_css_inject_1 = require("../../common/readium-css-inject"); const selection_1 = require("../../common/selection"); const styles_1 = require("../../common/styles"); const animateProperty_1 = require("../common/animateProperty"); const cssselector2_3_1 = require("../common/cssselector2-3"); const dom_text_utils_1 = require("../common/dom-text-utils"); const easings_1 = require("../common/easings"); const popup_dialog_1 = require("../common/popup-dialog"); const querystring_1 = require("../common/querystring"); const rect_utils_1 = require("../common/rect-utils"); const url_params_1 = require("../common/url-params"); const audiobook_1 = require("./audiobook"); const epubReadingSystem_1 = require("./epubReadingSystem"); const highlight_1 = require("./highlight"); const popoutImages_1 = require("./popoutImages"); const popupFootNotes_1 = require("./popupFootNotes"); const readaloud_1 = require("./readaloud"); const readium_css_1 = require("./readium-css"); const selection_2 = require("./selection"); const IS_DEV = (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev"); if (IS_DEV) { const cr = require("../common/console-redirect"); cr.consoleRedirect("r2:navigator#electron/renderer/webview/preload", process.stdout, process.stderr, true); } const debug = debug_("r2:navigator#electron/renderer/webview/preload"); const INJECTED_LINK_TXT = "__"; const win = global.window; win.READIUM2 = { DEBUG_VISUALS: false, fxlViewportHeight: 0, fxlViewportScale: 1, fxlViewportWidth: 0, fxlZoomPercent: 0, hashElement: null, isAudio: false, ignorekeyDownUpEvents: false, isClipboardIntercept: false, isFixedLayout: false, locationHashOverride: undefined, locationHashOverrideInfo: { audioPlaybackInfo: undefined, docInfo: undefined, epubPage: undefined, epubPageID: undefined, headings: undefined, href: "", locations: { cfi: undefined, cssSelector: undefined, position: undefined, progression: undefined, }, paginationInfo: undefined, secondWebViewHref: undefined, selectionInfo: undefined, selectionIsNew: undefined, title: undefined, userInteract: false, }, ttsClickEnabled: false, ttsOverlayEnabled: false, ttsPlaybackRate: 1, ttsSkippabilityEnabled: false, ttsSentenceDetectionEnabled: true, ttsVoice: null, urlQueryParams: win.location.search ? (0, querystring_1.getURLQueryParams)(win.location.search) : undefined, webViewSlot: styles_1.WebViewSlotEnum.center, }; win.alert = (...args) => { console.log.apply(win, args); }; win.confirm = (...args) => { console.log.apply(win, args); return false; }; win.prompt = (...args) => { console.log.apply(win, args); return ""; }; const CSS_PIXEL_TOLERANCE = 5; const TOUCH_SWIPE_DELTA_MIN = 80; const TOUCH_SWIPE_LONG_PRESS_MAX_TIME = 500; const TOUCH_SWIPE_MAX_TIME = 500; let touchstartEvent; let touchEventEnd; win.document.addEventListener("touchstart", (event) => { if ((0, popup_dialog_1.isPopupDialogOpen)(win.document)) { touchstartEvent = undefined; touchEventEnd = undefined; return; } if (event.changedTouches.length !== 1) { return; } touchstartEvent = event; }, true); win.document.addEventListener("touchend", (event) => { if ((0, popup_dialog_1.isPopupDialogOpen)(win.document)) { touchstartEvent = undefined; touchEventEnd = undefined; return; } if (event.changedTouches.length !== 1) { return; } if (!touchstartEvent) { return; } const startTouch = touchstartEvent.changedTouches[0]; const endTouch = event.changedTouches[0]; if (!startTouch || !endTouch) { return; } const deltaX = (startTouch.clientX - endTouch.clientX) / win.devicePixelRatio; const deltaY = (startTouch.clientY - endTouch.clientY) / win.devicePixelRatio; if (Math.abs(deltaX) < TOUCH_SWIPE_DELTA_MIN && Math.abs(deltaY) < TOUCH_SWIPE_DELTA_MIN) { if (touchEventEnd) { touchstartEvent = undefined; touchEventEnd = undefined; return; } if (event.timeStamp - touchstartEvent.timeStamp > TOUCH_SWIPE_LONG_PRESS_MAX_TIME) { touchstartEvent = undefined; touchEventEnd = undefined; return; } touchstartEvent = undefined; touchEventEnd = event; return; } touchEventEnd = undefined; if (event.timeStamp - touchstartEvent.timeStamp > TOUCH_SWIPE_MAX_TIME) { touchstartEvent = undefined; return; } const slope = (startTouch.clientY - endTouch.clientY) / (startTouch.clientX - endTouch.clientX); if (Math.abs(slope) > 0.5) { touchstartEvent = undefined; return; } if (deltaX < 0) { const payload = { direction: "LTR", go: "NEXT", nav: true, }; electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_PAGE_TURN_RES, payload); } else { const payload = { direction: "LTR", go: "PREVIOUS", nav: true, }; electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_PAGE_TURN_RES, payload); } touchstartEvent = undefined; }, true); function keyDownUpEventHandler(ev, keyDown) { if (win.READIUM2.ignorekeyDownUpEvents) { return; } const elementName = (ev.target && ev.target.nodeName) ? ev.target.nodeName : ""; const elementAttributes = {}; if (ev.target && ev.target.attributes) { for (let i = 0; i < ev.target.attributes.length; i++) { const attr = ev.target.attributes[i]; elementAttributes[attr.name] = attr.value; } } const payload = { altKey: ev.altKey, code: ev.code, ctrlKey: ev.ctrlKey, elementAttributes, elementName, key: ev.key, metaKey: ev.metaKey, shiftKey: ev.shiftKey, }; electron_1.ipcRenderer.sendToHost(keyDown ? events_1.R2_EVENT_WEBVIEW_KEYDOWN : events_1.R2_EVENT_WEBVIEW_KEYUP, payload); } win.document.addEventListener("keydown", (ev) => { keyDownUpEventHandler(ev, true); }, { capture: true, once: false, passive: false, }); win.document.addEventListener("keyup", (ev) => { keyDownUpEventHandler(ev, false); }, { capture: true, once: false, passive: false, }); win.READIUM2.isAudio = win.location.protocol === "data:"; if (win.READIUM2.urlQueryParams) { let readiumEpubReadingSystemJson; const base64EpubReadingSystem = win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_EPUBREADINGSYSTEM]; if (base64EpubReadingSystem) { try { const str = Buffer.from(base64EpubReadingSystem, "base64").toString("utf8"); readiumEpubReadingSystemJson = JSON.parse(str); } catch (err) { debug(err); } } if (readiumEpubReadingSystemJson) { (0, epubReadingSystem_1.setWindowNavigatorEpubReadingSystem)(win, readiumEpubReadingSystemJson); } win.READIUM2.DEBUG_VISUALS = win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_DEBUG_VISUALS] === "true"; win.READIUM2.isClipboardIntercept = win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_CLIPBOARD_INTERCEPT] === "true"; win.READIUM2.webViewSlot = win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_WEBVIEW_SLOT] === "left" ? styles_1.WebViewSlotEnum.left : (win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_WEBVIEW_SLOT] === "right" ? styles_1.WebViewSlotEnum.right : styles_1.WebViewSlotEnum.center); } if (IS_DEV) { electron_1.ipcRenderer.on(events_1.R2_EVENT_DEBUG_VISUALS, (_event, payload) => { win.READIUM2.DEBUG_VISUALS = payload.debugVisuals; if (!payload.debugVisuals) { const existings = win.document.querySelectorAll(`*[${styles_1.readPosCssStylesAttr1}], *[${styles_1.readPosCssStylesAttr2}], *[${styles_1.readPosCssStylesAttr3}], *[${styles_1.readPosCssStylesAttr4}]`); existings.forEach((existing) => { existing.removeAttribute(`${styles_1.readPosCssStylesAttr1}`); existing.removeAttribute(`${styles_1.readPosCssStylesAttr2}`); existing.removeAttribute(`${styles_1.readPosCssStylesAttr3}`); existing.removeAttribute(`${styles_1.readPosCssStylesAttr4}`); }); } if (payload.cssClass) { if (_blacklistIdClassForCssSelectors.indexOf(payload.cssClass) < 0) { _blacklistIdClassForCssSelectors.push(payload.cssClass.toLowerCase()); } if (payload.debugVisuals && payload.cssStyles && payload.cssStyles.length) { const idSuffix = `debug_for_class_${payload.cssClass}`; (0, readium_css_inject_1.appendCSSInline)(win.document, idSuffix, payload.cssStyles); if (payload.cssSelector) { const toHighlights = win.document.querySelectorAll(payload.cssSelector); toHighlights.forEach((toHighlight) => { const clazz = `${payload.cssClass}`; if (!toHighlight.classList.contains(clazz)) { toHighlight.classList.add(clazz); } }); } } else { const existings = win.document.querySelectorAll(`.${payload.cssClass}`); existings.forEach((existing) => { existing.classList.remove(`${payload.cssClass}`); }); } } }); } function computeVisibility_(element, domRect) { if (win.READIUM2.isFixedLayout) { return true; } else if (!win.document || !win.document.documentElement || !win.document.body) { return false; } if (element === win.document.body || element === win.document.documentElement) { return true; } const blacklisted = checkBlacklisted(element); if (blacklisted) { return false; } const elStyle = win.getComputedStyle(element); if (elStyle) { const display = elStyle.getPropertyValue("display"); if (display === "none") { if (IS_DEV) { debug("element DISPLAY NONE"); } return false; } const opacity = elStyle.getPropertyValue("opacity"); if (opacity === "0") { if (IS_DEV) { debug("element OPACITY ZERO"); } return false; } } const scrollElement = (0, readium_css_1.getScrollingElement)(win.document); if (!(0, readium_css_inject_1.isPaginated)(win.document)) { const rect = domRect || element.getBoundingClientRect(); if (rect.top >= 0 && rect.top <= win.document.documentElement.clientHeight) { return true; } return false; } if ((0, readium_css_1.isVerticalWritingMode)()) { return false; } const scrollLeftPotentiallyExcessive = getScrollOffsetIntoView(element, domRect); const extraShift = scrollElement.scrollLeftExtra; let currentOffset = scrollElement.scrollLeft; if (extraShift) { currentOffset += (((currentOffset < 0) ? -1 : 1) * extraShift); } if (scrollLeftPotentiallyExcessive >= (currentOffset - 10) && scrollLeftPotentiallyExcessive <= (currentOffset + 10)) { return true; } return false; } function computeVisibility(location) { let visible = false; if (win.READIUM2.isAudio) { visible = true; } else if (win.READIUM2.isFixedLayout) { visible = true; } else if (!win.document || !win.document.documentElement || !win.document.body) { visible = false; } else if (!location || !location.cssSelector) { visible = false; } else { let selected = null; try { selected = win.document.querySelector(location.cssSelector); } catch (err) { debug(err); } if (selected) { visible = computeVisibility_(selected, undefined); } } return visible; } electron_1.ipcRenderer.on(events_1.R2_EVENT_LOCATOR_VISIBLE, (_event, payload) => { payload.visible = computeVisibility(payload.location); electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_LOCATOR_VISIBLE, payload); }); electron_1.ipcRenderer.on(events_1.R2_EVENT_SCROLLTO, (_event, payload) => { if (win.READIUM2.isAudio) { return; } showHideContentMask(false, win.READIUM2.isFixedLayout); (0, selection_2.clearCurrentSelection)(win); (0, popup_dialog_1.closePopupDialogs)(win.document); if (!win.READIUM2.urlQueryParams) { win.READIUM2.urlQueryParams = {}; } if (payload.isSecondWebView) { win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_SECOND_WEBVIEW] = "1"; } else { win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_SECOND_WEBVIEW] = "0"; } if (payload.previous) { win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_PREVIOUS] = "true"; } else { if (typeof win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_PREVIOUS] !== "undefined") { delete win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_PREVIOUS]; } } if (payload.goto) { win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_GOTO] = payload.goto; } else { if (typeof win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_GOTO] !== "undefined") { delete win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_GOTO]; } } if (payload.gotoDomRange) { win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_GOTO_DOM_RANGE] = payload.gotoDomRange; } else { if (typeof win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_GOTO_DOM_RANGE] !== "undefined") { delete win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_GOTO_DOM_RANGE]; } } if (win.READIUM2.isFixedLayout) { win.READIUM2.locationHashOverride = win.document.body; resetLocationHashOverrideInfo(); debug("processXYRaw BODY"); processXYRaw(0, 0, false); notifyReadingLocationDebounced(); return; } let delayScrollIntoView = false; if (payload.hash) { debug(".hashElement = 1"); win.READIUM2.hashElement = win.document.getElementById(payload.hash); if (win.READIUM2.DEBUG_VISUALS) { if (win.READIUM2.hashElement) { win.READIUM2.hashElement.setAttribute(styles_1.readPosCssStylesAttr1, "R2_EVENT_SCROLLTO hashElement"); } } win.location.href = "#" + payload.hash; delayScrollIntoView = true; } else { win.location.href = "#"; win.READIUM2.hashElement = null; } win.READIUM2.locationHashOverride = undefined; resetLocationHashOverrideInfo(); if (delayScrollIntoView) { setTimeout(() => { debug("++++ scrollToHashRaw FROM DELAYED SCROLL_TO"); scrollToHashRaw(false); }, 100); } else { debug("++++ scrollToHashRaw FROM SCROLL_TO"); scrollToHashRaw(false); } }); function resetLocationHashOverrideInfo() { win.READIUM2.locationHashOverrideInfo = { audioPlaybackInfo: undefined, docInfo: undefined, epubPage: undefined, epubPageID: undefined, headings: undefined, href: "", locations: { cfi: undefined, cssSelector: undefined, position: undefined, progression: undefined, }, paginationInfo: undefined, secondWebViewHref: undefined, selectionInfo: undefined, selectionIsNew: undefined, title: undefined, userInteract: false, }; } let _lastAnimState; function elementCapturesKeyboardArrowKeys(target) { let curElement = target; while (curElement && curElement.nodeType === Node.ELEMENT_NODE) { const editable = curElement.getAttribute("contenteditable"); if (editable) { return true; } const arrayOfKeyboardCaptureElements = ["input", "textarea", "video", "audio", "select"]; if (arrayOfKeyboardCaptureElements.indexOf(curElement.tagName.toLowerCase()) >= 0) { return true; } curElement = curElement.parentNode; } return false; } function ensureTwoPageSpreadWithOddColumnsIsOffsetTempDisable() { const scrollElement = (0, readium_css_1.getScrollingElement)(win.document); const val = scrollElement.scrollLeftExtra; if (val === 0) { return 0; } scrollElement.scrollLeftExtra = 0; electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_SHIFT_VIEW_X, { offset: 0, backgroundColor: undefined }); return val; } function ensureTwoPageSpreadWithOddColumnsIsOffsetReEnable(scrollLeftExtra) { const scrollElement = (0, readium_css_1.getScrollingElement)(win.document); scrollElement.scrollLeftExtra = scrollLeftExtra; const scrollLeftExtraBackgroundColor = scrollElement.scrollLeftExtraBackgroundColor; electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_SHIFT_VIEW_X, { backgroundColor: scrollLeftExtraBackgroundColor ? scrollLeftExtraBackgroundColor : undefined, offset: ((0, readium_css_1.isRTL)() ? 1 : -1) * scrollLeftExtra, }); } function ensureTwoPageSpreadWithOddColumnsIsOffset(scrollOffset, maxScrollShift) { if (!win || !win.document || !win.document.body || !win.document.documentElement) { return; } const scrollElement = (0, readium_css_1.getScrollingElement)(win.document); let dialogPopup = (0, popup_dialog_1.isPopupDialogOpen)(win.document); if (dialogPopup) { const diagEl = win.document.getElementById(styles_1.POPUP_DIALOG_CLASS); if (diagEl) { const isCollapsed = diagEl.classList.contains(styles_1.POPUP_DIALOG_CLASS_COLLAPSE); if (isCollapsed) { dialogPopup = false; } } } const noChange = dialogPopup || !(0, readium_css_inject_1.isPaginated)(win.document) || !(0, readium_css_1.isTwoPageSpread)() || (0, readium_css_1.isVerticalWritingMode)() || maxScrollShift <= 0 || Math.abs(scrollOffset) <= maxScrollShift; if (noChange) { scrollElement.scrollLeftExtra = 0; electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_SHIFT_VIEW_X, { offset: 0, backgroundColor: undefined }); return; } const extraOffset = Math.abs(scrollOffset) - maxScrollShift; let backgroundColor; const docStyle = win.getComputedStyle(win.document.documentElement); if (docStyle) { backgroundColor = docStyle.getPropertyValue("background-color"); } if (!backgroundColor || backgroundColor === "transparent") { const bodyStyle = win.getComputedStyle(win.document.body); backgroundColor = bodyStyle.getPropertyValue("background-color"); if (backgroundColor === "transparent") { backgroundColor = undefined; } } scrollElement.scrollLeftExtra = extraOffset; scrollElement.scrollLeftExtraBackgroundColor = backgroundColor; electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_SHIFT_VIEW_X, { backgroundColor: backgroundColor ? backgroundColor : undefined, offset: ((0, readium_css_1.isRTL)() ? 1 : -1) * extraOffset, }); } function onEventPageTurn(payload) { let leftRightKeyWasUsedInsideKeyboardCapture = false; if (win.document.activeElement && elementCapturesKeyboardArrowKeys(win.document.activeElement)) { if (win.document.hasFocus()) { leftRightKeyWasUsedInsideKeyboardCapture = true; } else { const oldDate = win.document.activeElement.r2_leftrightKeyboardTimeStamp; if (oldDate) { const newDate = new Date(); const msDiff = newDate.getTime() - oldDate.getTime(); if (msDiff <= 300) { leftRightKeyWasUsedInsideKeyboardCapture = true; } } } } if (leftRightKeyWasUsedInsideKeyboardCapture) { return; } (0, selection_2.clearCurrentSelection)(win); (0, popup_dialog_1.closePopupDialogs)(win.document); if (win.READIUM2.isAudio || win.READIUM2.isFixedLayout || !win.document.body) { electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_PAGE_TURN_RES, payload); return; } if (!win.document || !win.document.documentElement) { return; } const scrollElement = (0, readium_css_1.getScrollingElement)(win.document); const reduceMotion = win.document.documentElement.classList.contains(styles_1.ROOT_CLASS_REDUCE_MOTION); const isPaged = (0, readium_css_inject_1.isPaginated)(win.document); const goPREVIOUS = payload.go === "PREVIOUS"; const animationTime = 300; if (_lastAnimState && _lastAnimState.animating) { win.cancelAnimationFrame(_lastAnimState.id); _lastAnimState.object[_lastAnimState.property] = _lastAnimState.destVal; } if (!goPREVIOUS) { const maxScrollShift = (0, readium_css_1.calculateMaxScrollShift)().maxScrollShift; const maxScrollShiftTolerated = maxScrollShift - CSS_PIXEL_TOLERANCE; if (isPaged) { const unit = (0, readium_css_1.isVerticalWritingMode)() ? win.document.documentElement.offsetHeight : win.document.documentElement.offsetWidth; let scrollElementOffset = Math.round((0, readium_css_1.isVerticalWritingMode)() ? scrollElement.scrollTop : scrollElement.scrollLeft); const isNegative = scrollElementOffset < 0; const scrollElementOffsetAbs = Math.abs(scrollElementOffset); const fractional = scrollElementOffsetAbs / unit; const integral = Math.floor(fractional); const decimal = fractional - integral; const partial = decimal * unit; if (partial <= CSS_PIXEL_TOLERANCE) { scrollElementOffset = (isNegative ? -1 : 1) * integral * unit; } else if (partial >= (unit - CSS_PIXEL_TOLERANCE)) { scrollElementOffset = (isNegative ? -1 : 1) * (integral + 1) * unit; } if ((0, readium_css_1.isVerticalWritingMode)() && (scrollElementOffsetAbs < maxScrollShiftTolerated) || !(0, readium_css_1.isVerticalWritingMode)() && (scrollElementOffsetAbs < maxScrollShiftTolerated)) { const scrollOffsetPotentiallyExcessive_ = (0, readium_css_1.isVerticalWritingMode)() ? (scrollElementOffset + unit) : (scrollElementOffset + ((0, readium_css_1.isRTL)() ? -1 : 1) * unit); const nWholes = Math.floor(scrollOffsetPotentiallyExcessive_ / unit); const scrollOffsetPotentiallyExcessive = nWholes * unit; ensureTwoPageSpreadWithOddColumnsIsOffset(scrollOffsetPotentiallyExcessive, maxScrollShift); const scrollOffset = (scrollOffsetPotentiallyExcessive < 0 ? -1 : 1) * Math.min(Math.abs(scrollOffsetPotentiallyExcessive), maxScrollShift); const targetObj = scrollElement; const targetProp = (0, readium_css_1.isVerticalWritingMode)() ? "scrollTop" : "scrollLeft"; if (reduceMotion) { _lastAnimState = undefined; targetObj[targetProp] = scrollOffset; } else { _ignoreScrollEvent = true; _lastAnimState = (0, animateProperty_1.animateProperty)(win.cancelAnimationFrame, (_cancelled) => { _ignoreScrollEvent = false; onScrollDebounced(); }, targetProp, animationTime, targetObj, scrollOffset, win.requestAnimationFrame, easings_1.easings.easeInOutQuad); } payload.go = ""; payload.direction = ""; electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_PAGE_TURN_RES, payload); return; } } else { if ((0, readium_css_1.isVerticalWritingMode)() && (Math.abs(scrollElement.scrollLeft) < maxScrollShiftTolerated) || !(0, readium_css_1.isVerticalWritingMode)() && (Math.abs(scrollElement.scrollTop) < maxScrollShiftTolerated)) { const newVal = (0, readium_css_1.isVerticalWritingMode)() ? (scrollElement.scrollLeft + ((0, readium_css_1.isRTL)() ? -1 : 1) * win.document.documentElement.clientWidth) : (scrollElement.scrollTop + win.document.documentElement.clientHeight); const targetObj = scrollElement; const targetProp = (0, readium_css_1.isVerticalWritingMode)() ? "scrollLeft" : "scrollTop"; if (reduceMotion) { _lastAnimState = undefined; targetObj[targetProp] = newVal; } else { _ignoreScrollEvent = true; _lastAnimState = (0, animateProperty_1.animateProperty)(win.cancelAnimationFrame, (_cancelled) => { _ignoreScrollEvent = false; onScrollDebounced(); }, targetProp, animationTime, targetObj, newVal, win.requestAnimationFrame, easings_1.easings.easeInOutQuad); } payload.go = ""; payload.direction = ""; electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_PAGE_TURN_RES, payload); return; } } } else if (goPREVIOUS) { if (isPaged) { const unit = (0, readium_css_1.isVerticalWritingMode)() ? win.document.documentElement.offsetHeight : win.document.documentElement.offsetWidth; let scrollElementOffset = Math.round((0, readium_css_1.isVerticalWritingMode)() ? scrollElement.scrollTop : scrollElement.scrollLeft); const isNegative = scrollElementOffset < 0; const scrollElementOffsetAbs = Math.abs(scrollElementOffset); const fractional = scrollElementOffsetAbs / unit; const integral = Math.floor(fractional); const decimal = fractional - integral; const partial = decimal * unit; if (partial <= CSS_PIXEL_TOLERANCE) { scrollElementOffset = (isNegative ? -1 : 1) * integral * unit; } else if (partial >= (unit - CSS_PIXEL_TOLERANCE)) { scrollElementOffset = (isNegative ? -1 : 1) * (integral + 1) * unit; } if ((0, readium_css_1.isVerticalWritingMode)() && (scrollElementOffsetAbs > 0) || !(0, readium_css_1.isVerticalWritingMode)() && (scrollElementOffsetAbs > 0)) { const scrollOffset_ = (0, readium_css_1.isVerticalWritingMode)() ? (scrollElementOffset - unit) : (scrollElementOffset - ((0, readium_css_1.isRTL)() ? -1 : 1) * unit); const nWholes = (0, readium_css_1.isRTL)() ? Math.floor(scrollOffset_ / unit) : Math.ceil(scrollOffset_ / unit); const scrollOffset = nWholes * unit; ensureTwoPageSpreadWithOddColumnsIsOffset(scrollOffset, 0); const targetObj = scrollElement; const targetProp = (0, readium_css_1.isVerticalWritingMode)() ? "scrollTop" : "scrollLeft"; if (reduceMotion) { _lastAnimState = undefined; targetObj[targetProp] = scrollOffset; } else { _ignoreScrollEvent = true; _lastAnimState = (0, animateProperty_1.animateProperty)(win.cancelAnimationFrame, (_cancelled) => { _ignoreScrollEvent = false; onScrollDebounced(); }, targetProp, animationTime, targetObj, scrollOffset, win.requestAnimationFrame, easings_1.easings.easeInOutQuad); } payload.go = ""; payload.direction = ""; electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_PAGE_TURN_RES, payload); return; } } else { if ((0, readium_css_1.isVerticalWritingMode)() && (Math.abs(scrollElement.scrollLeft) > 0) || !(0, readium_css_1.isVerticalWritingMode)() && (Math.abs(scrollElement.scrollTop) > 0)) { const newVal = (0, readium_css_1.isVerticalWritingMode)() ? (scrollElement.scrollLeft - ((0, readium_css_1.isRTL)() ? -1 : 1) * win.document.documentElement.clientWidth) : (scrollElement.scrollTop - win.document.documentElement.clientHeight); const targetObj = scrollElement; const targetProp = (0, readium_css_1.isVerticalWritingMode)() ? "scrollLeft" : "scrollTop"; if (reduceMotion) { _lastAnimState = undefined; targetObj[targetProp] = newVal; } else { _ignoreScrollEvent = true; _lastAnimState = (0, animateProperty_1.animateProperty)(win.cancelAnimationFrame, (_cancelled) => { _ignoreScrollEvent = false; onScrollDebounced(); }, targetProp, animationTime, targetObj, newVal, win.requestAnimationFrame, easings_1.easings.easeInOutQuad); } payload.go = ""; payload.direction = ""; electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_PAGE_TURN_RES, payload); return; } } } electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_PAGE_TURN_RES, payload); } electron_1.ipcRenderer.on(events_1.R2_EVENT_PAGE_TURN, (_event, payload) => { setTimeout(() => { onEventPageTurn(payload); }, 100); }); function focusElement(element) { if (element === win.document.body) { const attr = element.getAttribute("tabindex"); if (!attr) { element.setAttribute("tabindex", "-1"); element.classList.add(styles_1.CSS_CLASS_NO_FOCUS_OUTLINE); if (IS_DEV) { debug("tabindex -1 set BODY (focusable):"); debug(getCssSelector(element)); } } element.focus({ preventScroll: true }); } else { element.focus(); } electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_KEYBOARD_FOCUS_REQUEST, null); if (IS_DEV) { debug("KEYBOARD FOCUS REQUEST (1) ", getCssSelector(element)); } } let _lastAnimState2; const animationTime2 = 400; function scrollElementIntoView(element, doFocus, animate, domRect) { if (win.READIUM2.DEBUG_VISUALS) { const existings = win.document.querySelectorAll(`*[${styles_1.readPosCssStylesAttr3}]`); existings.forEach((existing) => { existing.removeAttribute(`${styles_1.readPosCssStylesAttr3}`); }); element.setAttribute(styles_1.readPosCssStylesAttr3, "scrollElementIntoView"); } if (win.READIUM2.isFixedLayout) { debug("scrollElementIntoView_ SKIP FXL"); return; } if (doFocus) { if (!domRect && !(0, tabbable_1.isFocusable)(element)) { const attr = element.getAttribute("tabindex"); if (!attr) { element.setAttribute("tabindex", "-1"); element.classList.add(styles_1.CSS_CLASS_NO_FOCUS_OUTLINE); if (IS_DEV) { debug("tabindex -1 set (focusable):"); debug(getCssSelector(element)); } } } const targets = win.document.querySelectorAll(`.${styles_1.LINK_TARGET_CLASS}`); targets.forEach((t) => { t.classList.remove(styles_1.LINK_TARGET_CLASS); }); element.style.animation = "none"; void element.offsetWidth; element.style.animation = ""; element.classList.add(styles_1.LINK_TARGET_CLASS); if (element._timeoutTargetClass) { clearTimeout(element._timeoutTargetClass); element._timeoutTargetClass = undefined; } element._timeoutTargetClass = setTimeout(() => { debug("ANIMATION TIMEOUT REMOVE"); element.classList.remove(styles_1.LINK_TARGET_CLASS); }, 2000); if (!domRect) { focusElement(element); } } setTimeout(() => { const isPaged = (0, readium_css_inject_1.isPaginated)(win.document); if (isPaged) { scrollIntoView(element, domRect); } else { const scrollElement = (0, readium_css_1.getScrollingElement)(win.document); const rect = domRect || element.getBoundingClientRect(); const scrollTopMax = scrollElement.scrollHeight - win.document.documentElement.clientHeight; let offset = scrollElement.scrollTop + (rect.top - (win.document.documentElement.clientHeight / 2)); if (offset > scrollTopMax) { offset = scrollTopMax; } else if (offset < 0) { offset = 0; } const diff = Math.abs(scrollElement.scrollTop - offset); if (diff < 10) { return; } if (animate) { const reduceMotion = win.document.documentElement.classList.contains(styles_1.ROOT_CLASS_REDUCE_MOTION); if (_lastAnimState2 && _lastAnimState2.animating) { win.cancelAnimationFrame(_lastAnimState2.id); _lastAnimState2.object[_lastAnimState2.property] = _lastAnimState2.destVal; } const targetObj = scrollElement; const targetProp = "scrollTop"; if (reduceMotion) { _lastAnimState2 = undefined; targetObj[targetProp] = offset; } else { _ignoreScrollEvent = true; _lastAnimState2 = (0, animateProperty_1.animateProperty)(win.cancelAnimationFrame, (_cancelled) => { _ignoreScrollEvent = false; onScrollDebounced(); }, targetProp, animationTime2, targetObj, offset, win.requestAnimationFrame, easings_1.easings.easeInOutQuad); } } else { scrollElement.scrollTop = offset; } } }, doFocus ? 100 : 0); } function getScrollOffsetIntoView(element, domRect) { if (!win.document || !win.document.documentElement || !win.document.body || !(0, readium_css_inject_1.isPaginated)(win.document) || (0, readium_css_1.isVerticalWritingMode)()) { return 0; } const scrollElement = (0, readium_css_1.getScrollingElement)(win.document); const rect = domRect || element.getBoundingClientRect(); const columnDimension = (0, readium_css_1.calculateColumnDimension)(); const isTwoPage = (0, readium_css_1.isTwoPageSpread)(); const fullOffset = ((0, readium_css_1.isRTL)() ? ((columnDimension * (isTwoPage ? 2 : 1)) - (rect.left + rect.width)) : rect.left) + (((0, readium_css_1.isRTL)() ? -1 : 1) * scrollElement.scrollLeft); const columnIndex = Math.floor(fullOffset / columnDimension); const spreadIndex = isTwoPage ? Math.floor(columnIndex / 2) : columnIndex; return ((0, readium_css_1.isRTL)() ? -1 : 1) * (spreadIndex * (columnDimension * (isTwoPage ? 2 : 1))); } function scrollIntoView(element, domRect) { if (!win.document || !win.document.documentElement || !win.document.body || !(0, readium_css_inject_1.isPaginated)(win.document)) { return; } const maxScrollShift = (0, readium_css_1.calculateMaxScrollShift)().maxScrollShift; const scrollLeftPotentiallyExcessive = getScrollOffsetIntoView(element, domRect); ensureTwoPageSpreadWithOddColumnsIsOffset(scrollLeftPotentiallyExcessive, maxScrollShift); const scrollElement = (0, readium_css_1.getScrollingElement)(win.document); const scrollOffset = (scrollLeftPotentiallyExcessive < 0 ? -1 : 1) * Math.min(Math.abs(scrollLeftPotentiallyExcessive), maxScrollShift); scrollElement.scrollLeft = scrollOffset; } const scrollToHashRaw = (animate) => { if (!win.document || !win.document.body || !win.document.documentElement) { return; } (0, highlight_1.recreateAllHighlights)(win); debug("++++ scrollToHashRaw"); const isPaged = (0, readium_css_inject_1.isPaginated)(win.document); if (win.READIUM2.locationHashOverride) { scrollElementIntoView(win.READIUM2.locationHashOverride, true, animate, undefined); notifyReadingLocationDebounced(); return; } else if (win.READIUM2.hashElement) { win.READIUM2.locationHashOverride = win.READIUM2.hashElement; scrollElementIntoView(win.READIUM2.hashElement, true, animate, undefined); notifyReadingLocationDebounced(); return; } else { const scrollElement = (0, readium_css_1.getScrollingElement)(win.document); if (win.READIUM2.urlQueryParams) { const previous = win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_PREVIOUS]; const isPreviousNavDirection = previous === "true"; if (isPreviousNavDirection) { const { maxScrollShift, maxScrollShiftAdjusted } = (0, readium_css_1.calculateMaxScrollShift)(); _ignoreScrollEvent = true; if (isPaged) { if ((0, readium_css_1.isVerticalWritingMode)()) { scrollElement.scrollLeft = 0; scrollElement.scrollTop = maxScrollShift; } else { const scrollLeftPotentiallyExcessive = ((0, readium_css_1.isRTL)() ? -1 : 1) * maxScrollShiftAdjusted; ensureTwoPageSpreadWithOddColumnsIsOffset(scrollLeftPotentiallyExcessive, maxScrollShift); const scrollLeft = ((0, readium_css_1.isRTL)() ? -1 : 1) * maxScrollShift; scrollElement.scrollLeft = scrollLeft; scrollElement.scrollTop = 0; } } else { if ((0, readium_css_1.isVerticalWritingMode)()) { scrollElement.scrollLeft = ((0, readium_css_1.isRTL)() ? -1 : 1) * maxScrollShift; scrollElement.scrollTop = 0; } else { scrollElement.scrollLeft = 0; scrollElement.scrollTop = maxScrollShift; } } win.READIUM2.locationHashOverride = undefined; resetLocationHashOverrideInfo(); setTimeout(() => { processXYRaw(0, 0, false); showHideContentMask(false, win.READIUM2.isFixedLayout); if (!win.READIUM2.locationHashOverride) { notifyReadingLocationDebounced(); } setTimeout(() => { _ignoreScrollEvent = false; }, 10); }, 60); return; } const gto = win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_GOTO]; let gotoCssSelector; let gotoProgression; if (gto) { const locStr = Buffer.from(gto, "base64").toString("utf8"); const locObj = JSON.parse(locStr); gotoCssSelector = locObj.cssSelector; gotoProgression = locObj.progression; } if (gotoCssSelector) { gotoCssSelector = gotoCssSelector.replace(/\+/g, " "); let selected = null; try { selected = win.document.querySelector(gotoCssSelector); } catch (err) { debug(err); } if (selected) { win.READIUM2.locationHashOverride = selected; debug(".hashElement = 2"); win.READIUM2.hashElement = selected; resetLocationHashOverrideInfo(); if (win.READIUM2.locationHashOverrideInfo) { win.READIUM2.locationHashOverrideInfo.locations.cssSelector = gotoCssSelector; } let domRect; const gtoDomRange = win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_GOTO_DOM_RANGE]; if (gtoDomRange) { try { const rangeInfoStr = Buffer.from(gtoDomRange, "base64").toString("utf8"); const rangeInfo = JSON.parse(rangeInfoStr); debug("rangeInfo", rangeInfo); const domRange = (0, selection_2.convertRangeInfo)(win.document, rangeInfo); if (domRange) { domRect = domRange.getBoundingClientRect(); } } catch (err) { debug("gtoDomRange", err); } } scrollElementIntoView(selected, true, animate, domRect); notifyReadingLocationDebounced(); return; } } else if (gotoProgression) { const { maxScrollShift } = (0, readium_css_1.calculateMaxScrollShift)(); if (isPaged) { const isTwoPage = (0, readium_css_1.isTwoPageSpread)(); const nColumns = (0, readium_css_1.calculateTotalColumns)(); const nUnits = isTwoPage ? Math.ceil(nColumns / 2) : nColumns; const unitIndex = Math.floor(gotoProgression * nUnits); const unit = (0, readium_css_1.isVerticalWritingMode)() ? win.document.documentElement.offsetHeight : win.document.documentElement.offsetWidth; const scrollOffsetPotentiallyExcessive = (0, readium_css_1.isVerticalWritingMode)() ? (unitIndex * unit) : (((0, readium_css_1.isRTL)() ? -1 : 1) * unitIndex * unit); ensureTwoPageSpreadWithOddColumnsIsOffset(scrollOffsetPotentiallyExcessive, maxScrollShift); const scrollOffsetPaged = (scrollOffsetPotentiallyExcessive < 0 ? -1 : 1) * Math.min(Math.abs(scrollOffsetPotentiallyExcessive), maxScrollShift); _ignoreScrollEvent = true; if ((0, readium_css_1.isVerticalWritingMode)()) { scrollElement.scrollTop = scrollOffsetPaged; } else { scrollElement.scrollLeft = scrollOffsetPaged; } setTimeout(() => { _ignoreScrollEvent = false; }, 10); win.READIUM2.locationHashOverride = win.document.body; resetLocationHashOverrideInfo(); focusElement(win.READIUM2.locationHashOverride); processXYRaw(0, 0, false); if (!win.READIUM2.locationHashOverride) { notifyReadingLocationDebounced(); } return; } const scrollOffset = gotoProgression * maxScrollShift; _ignoreScrollEvent = true; if ((0, readium_css_1.isVerticalWritingMode)()) { scrollElement.scrollLeft = scrollOffset; } else { scrollElement.scrollTop = scrollOffset; } setTimeout(() => { _ignoreScrollEvent = false; }, 10); win.READIUM2.locationHashOverride = win.document.body; resetLocationHashOverrideInfo(); focusElement(win.READIUM2.locationHashOverride); processXYRaw(0, 0, false); if (!win.READIUM2.locationHashOverride) { notifyReadingLocationDebounced(); } return; } } _ignoreScrollEvent = true; scrollElement.scrollLeft = 0; scrollElement.scrollTop = 0; setTimeout(() => { _ignoreScrollEvent = false; }, 10); win.READIUM2.locationHashOverride = win.document.body; resetLocationHashOverrideInfo(); focusElement(win.READIUM2.locationHashOverride); debug("processXYRaw BODY"); processXYRaw(0, 0, false); } notifyReadingLocationDebounced(); }; const scrollToHashDebounced = (0, debounce_1.debounce)((animate) => { debug("++++ scrollToHashRaw FROM DEBOUNCED"); scrollToHashRaw(animate); }, 100); let _ignoreScrollEvent = false; function showHideContentMask(doHide, isFixedLayout) { if (doHide) { win.document.documentElement.classList.add(styles_1.ROOT_CLASS_INVISIBLE_MASK); win.document.documentElement.classList.remove(styles_1.ROOT_CLASS_INVISIBLE_MASK_REMOVED); } else { electron_1.ipcRenderer.sendToHost(events_1.R2_EVENT_SHOW, null); if (isFixedLayout) { win.document.documentElement.classList.add(styles_1.ROOT_CLASS_INVISIBLE_MASK_REMOVED); } win.document.documentElement.classList.remove(styles_1.ROOT_CLASS_INVISIBLE_MASK); } } function focusScrollRaw(el, doFocus, animate, domRect) { scrollElementIntoView(el, doFocus, animate, domRect); if (win.READIUM2.locationHashOverride === el) { return; } const blacklisted = checkBlacklisted(el); if (blacklisted) { return; } debug(".hashElement = 3"); win.READIUM2.hashElement = doFocus ? el : win.READIUM2.hashElement; win.READIUM2.locationHashOverride = el; notifyReadingLocationDebounced(); } const focusScrollDebounced = (0, debounce_1.debounce)((el, doFocus, animate, domRect) => { focusScrollRaw(el, doFocus, animate, domRect); }, 100); const handleFocusInDebounced = (0, debounce_1.debounce)((target, tabKeyDownEvent) => { handleFocusInRaw(target, tabKeyDownEvent); }, 100); function handleFocusInRaw(target, _tabKeyDownEvent) { if (!target || !win.document.body) { return; } focusScrollRaw(target, false, false, undefined); } electron_1.ipcRenderer.on(events_1.R2_EVENT_READIUMCSS, (_event, payload) => { showHideContentMask(true, payload.isFixedLayout || win.READIUM2.isFixedLayout); (0, readium_css_1.readiumCSS)(win.document, payload); (0, highlight_1.recreateAllHighlights)(win); showHideContentMask(false, payload.isFixedLayout || win.READIUM2.isFixedLayout); }); let _docTitle; win.addEventListener("DOMContentLoaded", () => { debug("############# DOMContentLoaded"); const titleElement = win.document.documentElement.querySelector("head > title"); if (titleElement && titleElement.textContent) { _docTitle = titleElement.textContent; } if (!win.READIUM2.isAudio && win.location.hash && win.location.hash.length > 1) { debug(".hashElement = 4"); win.READIUM2.hashElement = win.document.getElementById(win.location.hash.substr(1)); if (win.READIUM2.DEBUG_VISUALS) { if (win.READIUM2.hashElement) { win.READIUM2.hashElement.setAttribute(styles_1.readPosCssStylesAttr1, "DOMContentLoaded hashElement"); } } } win.READIUM2.locationHashOverride = undefined; win.READIUM2.ttsClickEnabled = false; win.READIUM2.ttsSkippabilityEnabled = false; win.READIUM2.ttsSentenceDetectionEnabled = true; win.READIUM2.ttsOverlayEnabled = false; let readiumcssJson; if (win.READIUM2.urlQueryParams) { const base64ReadiumCSS = win.READIUM2.urlQueryParams[url_params_1.URL_PARAM_CSS]; if (base64ReadiumCSS) { let str; try { str = Buffer.from(base64ReadiumCSS, "base64").toString("utf8"); readiumcssJson = JSON.parse(str); } catch (err) { debug("################## READIUM CSS PARSE ERROR?!"); debug(base64ReadiumCSS); debug(err); debug(str); } } } if (win.READIUM2.isAudio) { (0, aud