Merge float into display (#1053)

* Update audio play delay

* Move frame endpoint to Display

* Move _invokeOwner and close implementation

* Move browser info assignment

* Move window message handler setup

* Move copy implementation

* Move document title function

* Move extension unload handler

* Move close handler

* Move history event handlers

* Remove DisplayFloat

* Remove unused

* Organize

* Move event listeners into prepare
This commit is contained in:
toasted-nutbread 2020-11-22 15:29:51 -05:00 committed by GitHub
parent 7234cce4ae
commit 2971f262f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 211 additions and 252 deletions

View File

@ -56,6 +56,7 @@ class DisplaySearch extends Display {
['AltGraph', new Set()], ['AltGraph', new Set()],
['Shift', new Set()] ['Shift', new Set()]
]); ]);
this.autoPlayAudioDelay = 0;
} }
async prepare() { async prepare() {

View File

@ -96,7 +96,6 @@
<script src="/bg/js/template-renderer-proxy.js"></script> <script src="/bg/js/template-renderer-proxy.js"></script>
<script src="/bg/js/query-parser.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> <script src="/fg/js/float-main.js"></script>

View File

@ -16,7 +16,7 @@
*/ */
/* global /* global
* DisplayFloat * Display
* api * api
*/ */
@ -25,8 +25,9 @@
api.forwardLogsToBackend(); api.forwardLogsToBackend();
await yomichan.backendReady(); await yomichan.backendReady();
const display = new DisplayFloat(); const display = new Display('popup');
await display.prepare(); await display.prepare();
display.initializeState();
yomichan.ready(); yomichan.ready();
} catch (e) { } catch (e) {

View File

@ -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);
}
}

View File

@ -21,6 +21,7 @@
* DisplayGenerator * DisplayGenerator
* DisplayHistory * DisplayHistory
* DocumentUtil * DocumentUtil
* FrameEndpoint
* Frontend * Frontend
* MediaLoader * MediaLoader
* PopupFactory * PopupFactory
@ -47,19 +48,18 @@ class Display extends EventDispatcher {
}); });
this._styleNode = null; this._styleNode = null;
this._eventListeners = new EventListenerCollection(); this._eventListeners = new EventListenerCollection();
this._persistentEventListeners = new EventListenerCollection();
this._interactive = false;
this._eventListenersActive = false; this._eventListenersActive = false;
this._clickScanPrevent = false; this._clickScanPrevent = false;
this._setContentToken = null; this._setContentToken = null;
this._autoPlayAudioTimer = null; this._autoPlayAudioTimer = null;
this._autoPlayAudioDelay = 0; this._autoPlayAudioDelay = 400;
this._mediaLoader = new MediaLoader(); this._mediaLoader = new MediaLoader();
this._displayGenerator = new DisplayGenerator({mediaLoader: this._mediaLoader}); this._displayGenerator = new DisplayGenerator({mediaLoader: this._mediaLoader});
this._hotkeys = new Map(); this._hotkeys = new Map();
this._actions = new Map(); this._actions = new Map();
this._messageHandlers = new Map(); this._messageHandlers = new Map();
this._directMessageHandlers = new Map(); this._directMessageHandlers = new Map();
this._windowMessageHandlers = new Map();
this._history = new DisplayHistory({clearable: true, useBrowserHistory: false}); this._history = new DisplayHistory({clearable: true, useBrowserHistory: false});
this._historyChangeIgnore = false; this._historyChangeIgnore = false;
this._historyHasChanged = false; this._historyHasChanged = false;
@ -102,6 +102,9 @@ class Display extends EventDispatcher {
this._parentFrameId = null; this._parentFrameId = null;
this._ownerFrameId = null; this._ownerFrameId = null;
this._childrenSupported = true; this._childrenSupported = true;
this._frameEndpoint = (pageType === 'popup' ? new FrameEndpoint() : null);
this._browser = null;
this._copyTextarea = null;
this.registerActions([ this.registerActions([
['close', () => { this.onEscape(); }], ['close', () => { this.onEscape(); }],
@ -117,7 +120,8 @@ class Display extends EventDispatcher {
['addNoteTermKanji', () => { this._noteTryAdd('term-kanji'); }], ['addNoteTermKanji', () => { this._noteTryAdd('term-kanji'); }],
['addNoteTermKana', () => { this._noteTryAdd('term-kana'); }], ['addNoteTermKana', () => { this._noteTryAdd('term-kana'); }],
['viewNote', () => { this._noteTryView(); }], ['viewNote', () => { this._noteTryView(); }],
['playAudio', () => { this._playAudioCurrent(); }] ['playAudio', () => { this._playAudioCurrent(); }],
['copyHostSelection', () => this._copyHostSelection()]
]); ]);
this.registerHotkeys([ this.registerHotkeys([
{key: 'Escape', modifiers: [], action: 'close'}, {key: 'Escape', modifiers: [], action: 'close'},
@ -133,7 +137,8 @@ class Display extends EventDispatcher {
{key: 'E', modifiers: ['alt'], action: 'addNoteTermKanji'}, {key: 'E', modifiers: ['alt'], action: 'addNoteTermKanji'},
{key: 'R', modifiers: ['alt'], action: 'addNoteTermKana'}, {key: 'R', modifiers: ['alt'], action: 'addNoteTermKana'},
{key: 'P', modifiers: ['alt'], action: 'playAudio'}, {key: 'P', modifiers: ['alt'], action: 'playAudio'},
{key: 'V', modifiers: ['alt'], action: 'viewNote'} {key: 'V', modifiers: ['alt'], action: 'viewNote'},
{key: 'C', modifiers: ['ctrl'], action: 'copyHostSelection'}
]); ]);
this.registerMessageHandlers([ this.registerMessageHandlers([
['setMode', {async: false, handler: this._onMessageSetMode.bind(this)}] ['setMode', {async: false, handler: this._onMessageSetMode.bind(this)}]
@ -146,6 +151,9 @@ class Display extends EventDispatcher {
['setContentScale', {async: false, handler: this._onMessageSetContentScale.bind(this)}], ['setContentScale', {async: false, handler: this._onMessageSetContentScale.bind(this)}],
['configure', {async: true, handler: this._onMessageConfigure.bind(this)}] ['configure', {async: true, handler: this._onMessageConfigure.bind(this)}]
]); ]);
this.registerWindowMessageHandlers([
['extensionUnloaded', {async: false, handler: this._onMessageExtensionUnloaded.bind(this)}]
]);
} }
get autoPlayAudioDelay() { get autoPlayAudioDelay() {
@ -169,31 +177,58 @@ class Display extends EventDispatcher {
return this._mode; return this._mode;
} }
get ownerFrameId() {
return this._ownerFrameId;
}
async prepare() { async prepare() {
this._audioSystem.prepare(); // State setup
const {documentElement} = document;
this._updateMode(); this._updateMode();
this._setInteractive(true); const {browser} = await api.getEnvironmentInfo();
this._browser = browser;
// Prepare
await this._displayGenerator.prepare(); await this._displayGenerator.prepare();
this._audioSystem.prepare();
this._queryParser.prepare(); this._queryParser.prepare();
this._history.prepare(); this._history.prepare();
// Event setup
this._history.on('stateChanged', this._onStateChanged.bind(this)); this._history.on('stateChanged', this._onStateChanged.bind(this));
this._queryParser.on('searched', this._onQueryParserSearch.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)); yomichan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this));
chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); chrome.runtime.onMessage.addListener(this._onMessage.bind(this));
api.crossFrame.registerHandlers([ api.crossFrame.registerHandlers([
['popupMessage', {async: 'dynamic', handler: this._onDirectMessage.bind(this)}] ['popupMessage', {async: 'dynamic', handler: this._onDirectMessage.bind(this)}]
]); ]);
window.addEventListener('message', this._onWindowMessage.bind(this), false);
window.addEventListener('focus', this._onWindowFocus.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._updateFocusedElement();
this._progressIndicatorVisible.on('change', this._onProgressIndicatorVisibleChanged.bind(this));
} }
initializeState() { initializeState() {
this._onStateChanged(); this._onStateChanged();
if (this._frameEndpoint !== null) {
this._frameEndpoint.signal();
}
} }
setHistorySettings({clearable, useBrowserHistory}) { setHistorySettings({clearable, useBrowserHistory}) {
@ -211,7 +246,9 @@ class Display extends EventDispatcher {
} }
onEscape() { onEscape() {
throw new Error('Override me'); if (this._pageType === 'popup') {
this.close();
}
} }
onKeyDown(e) { onKeyDown(e) {
@ -340,6 +377,9 @@ class Display extends EventDispatcher {
} }
async getDocumentTitle() { async getDocumentTitle() {
if (this._pageType === 'float') {
return await this._getRootFrameDocumentTitle();
}
return document.title; return document.title;
} }
@ -372,16 +412,30 @@ class Display extends EventDispatcher {
} }
} }
registerWindowMessageHandlers(handlers) {
for (const [name, handlerInfo] of handlers) {
this._windowMessageHandlers.set(name, handlerInfo);
}
}
authenticateMessageData(data) { authenticateMessageData(data) {
if (this._frameEndpoint === null) {
return data; return data;
} }
if (!this._frameEndpoint.authenticate(data)) {
throw new Error('Invalid authentication');
}
return data.data;
}
postProcessQuery(query) { postProcessQuery(query) {
return query; return query;
} }
close() { close() {
// NOP if (this._pageType === 'popup') {
this._invokeOwner('closePopup');
}
} }
blurElement(element) { blurElement(element) {
@ -410,6 +464,21 @@ class Display extends EventDispatcher {
return {async, result}; 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}) { _onMessageSetMode({mode}) {
this._setMode(mode, true); this._setMode(mode, true);
} }
@ -444,6 +513,11 @@ class Display extends EventDispatcher {
await this.setOptionsContext(optionsContext); await this.setOptionsContext(optionsContext);
} }
_onMessageExtensionUnloaded() {
if (yomichan.isExtensionUnloaded) { return; }
yomichan.triggerExtensionUnloaded();
}
// Private // Private
async _onStateChanged() { async _onStateChanged() {
@ -751,6 +825,38 @@ class Display extends EventDispatcher {
console.log(definition); 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) { _updateDocumentOptions(options) {
const data = document.documentElement.dataset; const data = document.documentElement.dataset;
data.ankiEnabled = `${options.anki.enable}`; data.ankiEnabled = `${options.anki.enable}`;
@ -768,31 +874,8 @@ class Display extends EventDispatcher {
document.documentElement.dataset.yomichanTheme = themeName; 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) { _setEventListenersActive(active) {
active = !!active && this._interactive; active = !!active;
if (this._eventListenersActive === active) { return; } if (this._eventListenersActive === active) { return; }
this._eventListenersActive = active; this._eventListenersActive = active;
@ -1628,4 +1711,63 @@ class Display extends EventDispatcher {
this._frontend = frontend; this._frontend = frontend;
await frontend.prepare(); 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 '';
}
}
} }