aboutsummaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/bg/js/search.js1
-rw-r--r--ext/fg/float.html1
-rw-r--r--ext/fg/js/float-main.js5
-rw-r--r--ext/fg/js/float.js184
-rw-r--r--ext/mixed/js/display.js272
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 '';
+ }
+ }
}