diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/bg/js/search.js | 1 | ||||
-rw-r--r-- | ext/fg/float.html | 1 | ||||
-rw-r--r-- | ext/fg/js/float-main.js | 5 | ||||
-rw-r--r-- | ext/fg/js/float.js | 184 | ||||
-rw-r--r-- | ext/mixed/js/display.js | 272 |
5 files changed, 211 insertions, 252 deletions
diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index e68dccfd..498f4ade 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -56,6 +56,7 @@ class DisplaySearch extends Display { ['AltGraph', new Set()], ['Shift', new Set()] ]); + this.autoPlayAudioDelay = 0; } async prepare() { diff --git a/ext/fg/float.html b/ext/fg/float.html index 4b1f55e1..36aedb7a 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -96,7 +96,6 @@ <script src="/bg/js/template-renderer-proxy.js"></script> <script src="/bg/js/query-parser.js"></script> -<script src="/fg/js/float.js"></script> <script src="/fg/js/float-main.js"></script> diff --git a/ext/fg/js/float-main.js b/ext/fg/js/float-main.js index 85f605a2..6b4daebb 100644 --- a/ext/fg/js/float-main.js +++ b/ext/fg/js/float-main.js @@ -16,7 +16,7 @@ */ /* global - * DisplayFloat + * Display * api */ @@ -25,8 +25,9 @@ api.forwardLogsToBackend(); await yomichan.backendReady(); - const display = new DisplayFloat(); + const display = new Display('popup'); await display.prepare(); + display.initializeState(); yomichan.ready(); } catch (e) { diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js deleted file mode 100644 index 195861a3..00000000 --- a/ext/fg/js/float.js +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2016-2020 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -/* global - * Display - * FrameEndpoint - * api - */ - -class DisplayFloat extends Display { - constructor() { - super('popup'); - this._frameEndpoint = new FrameEndpoint(); - this._windowMessageHandlers = new Map([ - ['extensionUnloaded', {async: false, handler: this._onMessageExtensionUnloaded.bind(this)}] - ]); - this._browser = null; - this._copyTextarea = null; - - this.registerActions([ - ['copyHostSelection', () => this._copySelection()] - ]); - this.registerHotkeys([ - {key: 'C', modifiers: ['ctrl'], action: 'copyHostSelection'} - ]); - - this.autoPlayAudioDelay = 400; - } - - async prepare() { - await super.prepare(); - - const {browser} = await api.getEnvironmentInfo(); - this._browser = browser; - - window.addEventListener('message', this._onWindowMessage.bind(this), false); - document.documentElement.addEventListener('mouseup', this._onMouseUp.bind(this), false); - document.documentElement.addEventListener('click', this._onClick.bind(this), false); - document.documentElement.addEventListener('auxclick', this._onClick.bind(this), false); - - this.initializeState(); - - this._frameEndpoint.signal(); - } - - onEscape() { - this.close(); - } - - async getDocumentTitle() { - try { - const targetFrameId = 0; - const {title} = await api.crossFrame.invoke(targetFrameId, 'getDocumentInformation'); - return title; - } catch (e) { - return ''; - } - } - - authenticateMessageData(data) { - if (!this._frameEndpoint.authenticate(data)) { - throw new Error('Invalid authentication'); - } - return data.data; - } - - close() { - this._invokeOwner('closePopup'); - } - - // Message handling - - _onWindowMessage(e) { - const data = e.data; - if (!this._frameEndpoint.authenticate(data)) { return; } - - const {action, params} = data.data; - const messageHandler = this._windowMessageHandlers.get(action); - if (typeof messageHandler === 'undefined') { return; } - - const callback = () => {}; // NOP - yomichan.invokeMessageHandler(messageHandler, params, callback); - } - - _onMessageExtensionUnloaded() { - if (yomichan.isExtensionUnloaded) { return; } - yomichan.triggerExtensionUnloaded(); - } - - // Private - - _onMouseUp(e) { - switch (e.button) { - case 3: // Back - if (this._history.hasPrevious()) { - e.preventDefault(); - } - break; - case 4: // Forward - if (this._history.hasNext()) { - e.preventDefault(); - } - break; - } - } - - _onClick(e) { - switch (e.button) { - case 3: // Back - if (this._history.hasPrevious()) { - e.preventDefault(); - this._history.back(); - } - break; - case 4: // Forward - if (this._history.hasNext()) { - e.preventDefault(); - this._history.forward(); - } - break; - } - } - - _copySelection() { - if (window.getSelection().toString()) { return false; } - this._copyHostSelection(); - return true; - } - - async _copyHostSelection() { - switch (this._browser) { - case 'firefox': - case 'firefox-mobile': - { - let text; - try { - text = await this._invokeOwner('getSelectionText'); - } catch (e) { - break; - } - this._copyText(text); - } - break; - default: - this._invokeOwner('copySelection'); - break; - } - } - - _copyText(text) { - const parent = document.body; - if (parent === null) { return; } - - let textarea = this._copyTextarea; - if (textarea === null) { - textarea = document.createElement('textarea'); - this._copyTextarea = textarea; - } - - textarea.value = text; - parent.appendChild(textarea); - textarea.select(); - document.execCommand('copy'); - parent.removeChild(textarea); - } - - _invokeOwner(action, params={}) { - return api.crossFrame.invoke(this.ownerFrameId, action, params); - } -} diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index ba1f3758..8b755b83 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -21,6 +21,7 @@ * DisplayGenerator * DisplayHistory * DocumentUtil + * FrameEndpoint * Frontend * MediaLoader * PopupFactory @@ -47,19 +48,18 @@ class Display extends EventDispatcher { }); this._styleNode = null; this._eventListeners = new EventListenerCollection(); - this._persistentEventListeners = new EventListenerCollection(); - this._interactive = false; this._eventListenersActive = false; this._clickScanPrevent = false; this._setContentToken = null; this._autoPlayAudioTimer = null; - this._autoPlayAudioDelay = 0; + this._autoPlayAudioDelay = 400; this._mediaLoader = new MediaLoader(); this._displayGenerator = new DisplayGenerator({mediaLoader: this._mediaLoader}); this._hotkeys = new Map(); this._actions = new Map(); this._messageHandlers = new Map(); this._directMessageHandlers = new Map(); + this._windowMessageHandlers = new Map(); this._history = new DisplayHistory({clearable: true, useBrowserHistory: false}); this._historyChangeIgnore = false; this._historyHasChanged = false; @@ -102,38 +102,43 @@ class Display extends EventDispatcher { this._parentFrameId = null; this._ownerFrameId = null; this._childrenSupported = true; + this._frameEndpoint = (pageType === 'popup' ? new FrameEndpoint() : null); + this._browser = null; + this._copyTextarea = null; this.registerActions([ - ['close', () => { this.onEscape(); }], - ['nextEntry', () => { this._focusEntry(this._index + 1, true); }], - ['nextEntry3', () => { this._focusEntry(this._index + 3, true); }], - ['previousEntry', () => { this._focusEntry(this._index - 1, true); }], - ['previousEntry3', () => { this._focusEntry(this._index - 3, true); }], - ['lastEntry', () => { this._focusEntry(this._definitions.length - 1, true); }], - ['firstEntry', () => { this._focusEntry(0, true); }], - ['historyBackward', () => { this._sourceTermView(); }], - ['historyForward', () => { this._nextTermView(); }], - ['addNoteKanji', () => { this._noteTryAdd('kanji'); }], - ['addNoteTermKanji', () => { this._noteTryAdd('term-kanji'); }], - ['addNoteTermKana', () => { this._noteTryAdd('term-kana'); }], - ['viewNote', () => { this._noteTryView(); }], - ['playAudio', () => { this._playAudioCurrent(); }] + ['close', () => { this.onEscape(); }], + ['nextEntry', () => { this._focusEntry(this._index + 1, true); }], + ['nextEntry3', () => { this._focusEntry(this._index + 3, true); }], + ['previousEntry', () => { this._focusEntry(this._index - 1, true); }], + ['previousEntry3', () => { this._focusEntry(this._index - 3, true); }], + ['lastEntry', () => { this._focusEntry(this._definitions.length - 1, true); }], + ['firstEntry', () => { this._focusEntry(0, true); }], + ['historyBackward', () => { this._sourceTermView(); }], + ['historyForward', () => { this._nextTermView(); }], + ['addNoteKanji', () => { this._noteTryAdd('kanji'); }], + ['addNoteTermKanji', () => { this._noteTryAdd('term-kanji'); }], + ['addNoteTermKana', () => { this._noteTryAdd('term-kana'); }], + ['viewNote', () => { this._noteTryView(); }], + ['playAudio', () => { this._playAudioCurrent(); }], + ['copyHostSelection', () => this._copyHostSelection()] ]); this.registerHotkeys([ - {key: 'Escape', modifiers: [], action: 'close'}, - {key: 'PageUp', modifiers: ['alt'], action: 'previousEntry3'}, - {key: 'PageDown', modifiers: ['alt'], action: 'nextEntry3'}, - {key: 'End', modifiers: ['alt'], action: 'lastEntry'}, - {key: 'Home', modifiers: ['alt'], action: 'firstEntry'}, - {key: 'ArrowUp', modifiers: ['alt'], action: 'previousEntry'}, - {key: 'ArrowDown', modifiers: ['alt'], action: 'nextEntry'}, - {key: 'B', modifiers: ['alt'], action: 'historyBackward'}, - {key: 'F', modifiers: ['alt'], action: 'historyForward'}, - {key: 'K', modifiers: ['alt'], action: 'addNoteKanji'}, - {key: 'E', modifiers: ['alt'], action: 'addNoteTermKanji'}, - {key: 'R', modifiers: ['alt'], action: 'addNoteTermKana'}, - {key: 'P', modifiers: ['alt'], action: 'playAudio'}, - {key: 'V', modifiers: ['alt'], action: 'viewNote'} + {key: 'Escape', modifiers: [], action: 'close'}, + {key: 'PageUp', modifiers: ['alt'], action: 'previousEntry3'}, + {key: 'PageDown', modifiers: ['alt'], action: 'nextEntry3'}, + {key: 'End', modifiers: ['alt'], action: 'lastEntry'}, + {key: 'Home', modifiers: ['alt'], action: 'firstEntry'}, + {key: 'ArrowUp', modifiers: ['alt'], action: 'previousEntry'}, + {key: 'ArrowDown', modifiers: ['alt'], action: 'nextEntry'}, + {key: 'B', modifiers: ['alt'], action: 'historyBackward'}, + {key: 'F', modifiers: ['alt'], action: 'historyForward'}, + {key: 'K', modifiers: ['alt'], action: 'addNoteKanji'}, + {key: 'E', modifiers: ['alt'], action: 'addNoteTermKanji'}, + {key: 'R', modifiers: ['alt'], action: 'addNoteTermKana'}, + {key: 'P', modifiers: ['alt'], action: 'playAudio'}, + {key: 'V', modifiers: ['alt'], action: 'viewNote'}, + {key: 'C', modifiers: ['ctrl'], action: 'copyHostSelection'} ]); this.registerMessageHandlers([ ['setMode', {async: false, handler: this._onMessageSetMode.bind(this)}] @@ -146,6 +151,9 @@ class Display extends EventDispatcher { ['setContentScale', {async: false, handler: this._onMessageSetContentScale.bind(this)}], ['configure', {async: true, handler: this._onMessageConfigure.bind(this)}] ]); + this.registerWindowMessageHandlers([ + ['extensionUnloaded', {async: false, handler: this._onMessageExtensionUnloaded.bind(this)}] + ]); } get autoPlayAudioDelay() { @@ -169,31 +177,58 @@ class Display extends EventDispatcher { return this._mode; } - get ownerFrameId() { - return this._ownerFrameId; - } - async prepare() { - this._audioSystem.prepare(); + // State setup + const {documentElement} = document; this._updateMode(); - this._setInteractive(true); + const {browser} = await api.getEnvironmentInfo(); + this._browser = browser; + + // Prepare await this._displayGenerator.prepare(); + this._audioSystem.prepare(); this._queryParser.prepare(); this._history.prepare(); + + // Event setup this._history.on('stateChanged', this._onStateChanged.bind(this)); this._queryParser.on('searched', this._onQueryParserSearch.bind(this)); + this._progressIndicatorVisible.on('change', this._onProgressIndicatorVisibleChanged.bind(this)); yomichan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); api.crossFrame.registerHandlers([ ['popupMessage', {async: 'dynamic', handler: this._onDirectMessage.bind(this)}] ]); + window.addEventListener('message', this._onWindowMessage.bind(this), false); window.addEventListener('focus', this._onWindowFocus.bind(this), false); + + if (this._pageType === 'popup' && documentElement !== null) { + documentElement.addEventListener('mouseup', this._onDocumentElementMouseUp.bind(this), false); + documentElement.addEventListener('click', this._onDocumentElementClick.bind(this), false); + documentElement.addEventListener('auxclick', this._onDocumentElementClick.bind(this), false); + } + + document.addEventListener('keydown', this.onKeyDown.bind(this), false); + document.addEventListener('wheel', this._onWheel.bind(this), {passive: false}); + if (this._closeButton !== null) { + this._closeButton.addEventListener('click', this._onCloseButtonClick.bind(this), false); + } + if (this._navigationPreviousButton !== null) { + this._navigationPreviousButton.addEventListener('click', this._onSourceTermView.bind(this), false); + } + if (this._navigationNextButton !== null) { + this._navigationNextButton.addEventListener('click', this._onNextTermView.bind(this), false); + } + + // Final preparation this._updateFocusedElement(); - this._progressIndicatorVisible.on('change', this._onProgressIndicatorVisibleChanged.bind(this)); } initializeState() { this._onStateChanged(); + if (this._frameEndpoint !== null) { + this._frameEndpoint.signal(); + } } setHistorySettings({clearable, useBrowserHistory}) { @@ -211,7 +246,9 @@ class Display extends EventDispatcher { } onEscape() { - throw new Error('Override me'); + if (this._pageType === 'popup') { + this.close(); + } } onKeyDown(e) { @@ -340,6 +377,9 @@ class Display extends EventDispatcher { } async getDocumentTitle() { + if (this._pageType === 'float') { + return await this._getRootFrameDocumentTitle(); + } return document.title; } @@ -372,8 +412,20 @@ class Display extends EventDispatcher { } } + registerWindowMessageHandlers(handlers) { + for (const [name, handlerInfo] of handlers) { + this._windowMessageHandlers.set(name, handlerInfo); + } + } + authenticateMessageData(data) { - return data; + if (this._frameEndpoint === null) { + return data; + } + if (!this._frameEndpoint.authenticate(data)) { + throw new Error('Invalid authentication'); + } + return data.data; } postProcessQuery(query) { @@ -381,7 +433,9 @@ class Display extends EventDispatcher { } close() { - // NOP + if (this._pageType === 'popup') { + this._invokeOwner('closePopup'); + } } blurElement(element) { @@ -410,6 +464,21 @@ class Display extends EventDispatcher { return {async, result}; } + _onWindowMessage({data}) { + try { + data = this.authenticateMessageData(data); + } catch (e) { + return; + } + + const {action, params} = data; + const messageHandler = this._windowMessageHandlers.get(action); + if (typeof messageHandler === 'undefined') { return; } + + const callback = () => {}; // NOP + yomichan.invokeMessageHandler(messageHandler, params, callback); + } + _onMessageSetMode({mode}) { this._setMode(mode, true); } @@ -444,6 +513,11 @@ class Display extends EventDispatcher { await this.setOptionsContext(optionsContext); } + _onMessageExtensionUnloaded() { + if (yomichan.isExtensionUnloaded) { return; } + yomichan.triggerExtensionUnloaded(); + } + // Private async _onStateChanged() { @@ -751,6 +825,38 @@ class Display extends EventDispatcher { console.log(definition); } + _onDocumentElementMouseUp(e) { + switch (e.button) { + case 3: // Back + if (this._history.hasPrevious()) { + e.preventDefault(); + } + break; + case 4: // Forward + if (this._history.hasNext()) { + e.preventDefault(); + } + break; + } + } + + _onDocumentElementClick(e) { + switch (e.button) { + case 3: // Back + if (this._history.hasPrevious()) { + e.preventDefault(); + this._history.back(); + } + break; + case 4: // Forward + if (this._history.hasNext()) { + e.preventDefault(); + this._history.forward(); + } + break; + } + } + _updateDocumentOptions(options) { const data = document.documentElement.dataset; data.ankiEnabled = `${options.anki.enable}`; @@ -768,31 +874,8 @@ class Display extends EventDispatcher { document.documentElement.dataset.yomichanTheme = themeName; } - _setInteractive(interactive) { - interactive = !!interactive; - if (this._interactive === interactive) { return; } - this._interactive = interactive; - - if (interactive) { - this._persistentEventListeners.addEventListener(document, 'keydown', this.onKeyDown.bind(this), false); - this._persistentEventListeners.addEventListener(document, 'wheel', this._onWheel.bind(this), {passive: false}); - if (this._closeButton !== null) { - this._persistentEventListeners.addEventListener(this._closeButton, 'click', this._onCloseButtonClick.bind(this)); - } - if (this._navigationPreviousButton !== null) { - this._persistentEventListeners.addEventListener(this._navigationPreviousButton, 'click', this._onSourceTermView.bind(this)); - } - if (this._navigationNextButton !== null) { - this._persistentEventListeners.addEventListener(this._navigationNextButton, 'click', this._onNextTermView.bind(this)); - } - } else { - this._persistentEventListeners.removeAllEventListeners(); - } - this._setEventListenersActive(this._eventListenersActive); - } - _setEventListenersActive(active) { - active = !!active && this._interactive; + active = !!active; if (this._eventListenersActive === active) { return; } this._eventListenersActive = active; @@ -1628,4 +1711,63 @@ class Display extends EventDispatcher { this._frontend = frontend; await frontend.prepare(); } + + async _invokeOwner(action, params={}) { + if (this._ownerFrameId === null) { + throw new Error('No owner frame'); + } + return await api.crossFrame.invoke(this._ownerFrameId, action, params); + } + + _copyHostSelection() { + if (window.getSelection().toString()) { return false; } + this._copyHostSelectionInner(); + return true; + } + + async _copyHostSelectionInner() { + switch (this._browser) { + case 'firefox': + case 'firefox-mobile': + { + let text; + try { + text = await this._invokeOwner('getSelectionText'); + } catch (e) { + break; + } + this._copyText(text); + } + break; + default: + await this._invokeOwner('copySelection'); + break; + } + } + + _copyText(text) { + const parent = document.body; + if (parent === null) { return; } + + let textarea = this._copyTextarea; + if (textarea === null) { + textarea = document.createElement('textarea'); + this._copyTextarea = textarea; + } + + textarea.value = text; + parent.appendChild(textarea); + textarea.select(); + document.execCommand('copy'); + parent.removeChild(textarea); + } + + async _getRootFrameDocumentTitle() { + try { + const {title} = await api.crossFrame.invoke(0, 'getDocumentInformation'); + return title; + } catch (e) { + return ''; + } + } } |