diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index b046806e..a3741da0 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -19,11 +19,8 @@ * ClipboardMonitor * DOM * Display - * Frontend - * PopupFactory * QueryParser * api - * dynamicLoader * wanakana */ @@ -63,11 +60,6 @@ class DisplaySearch extends Display { this._runtimeMessageHandlers = new Map([ ['updateSearchQuery', {async: false, handler: this._onExternalSearchUpdate.bind(this)}] ]); - - this.setOptionsContext({ - depth: 0, - url: window.location.href - }); } async prepare() { @@ -407,7 +399,11 @@ class DisplaySearch extends Display { yomichan.off('optionsUpdated', onOptionsUpdated); try { - await this._setupNestedPopups(); + await this.setupNestedPopups({ + depth: 1, + proxy: false, + isSearchPage: true + }); } catch (e) { yomichan.logError(e); } @@ -417,31 +413,4 @@ class DisplaySearch extends Display { await onOptionsUpdated(); } - - async _setupNestedPopups() { - await dynamicLoader.loadScripts([ - '/mixed/js/text-scanner.js', - '/mixed/js/frame-client.js', - '/fg/js/frame-offset-forwarder.js', - '/fg/js/popup.js', - '/fg/js/popup-factory.js', - '/fg/js/frontend.js' - ]); - - const {frameId} = await api.frameInformationGet(); - - const popupFactory = new PopupFactory(frameId); - popupFactory.prepare(); - - const frontend = new Frontend( - frameId, - popupFactory, - { - depth: 1, - proxy: false, - isSearchPage: true - } - ); - await frontend.prepare(); - } } diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 690d1b9e..f23a9b93 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -18,27 +18,15 @@ /* global * Display * FrameEndpoint - * Frontend - * PopupFactory * api - * dynamicLoader */ class DisplayFloat extends Display { constructor() { super(document.querySelector('#spinner'), document.querySelector('#definitions')); - this._autoPlayAudioTimer = null; this._nestedPopupsPrepared = false; this._ownerFrameId = null; this._frameEndpoint = new FrameEndpoint(); - this._messageHandlers = new Map([ - ['configure', {async: true, handler: this._onMessageConfigure.bind(this)}], - ['setOptionsContext', {async: false, handler: this._onMessageSetOptionsContext.bind(this)}], - ['setContent', {async: false, handler: this._onMessageSetContent.bind(this)}], - ['clearAutoPlayTimer', {async: false, handler: this._onMessageClearAutoPlayTimer.bind(this)}], - ['setCustomCss', {async: false, handler: this._onMessageSetCustomCss.bind(this)}], - ['setContentScale', {async: false, handler: this._onMessageSetContentScale.bind(this)}] - ]); this._windowMessageHandlers = new Map([ ['extensionUnloaded', {async: false, handler: this._onMessageExtensionUnloaded.bind(this)}] ]); @@ -49,13 +37,16 @@ class DisplayFloat extends Display { this.registerHotkeys([ {key: 'C', modifiers: ['ctrl'], action: 'copy-host-selection'} ]); + + this.autoPlayAudioDelay = 400; } async prepare() { await super.prepare(); - api.crossFrame.registerHandlers([ - ['popupMessage', {async: 'dynamic', handler: this._onMessage.bind(this)}] + this.registerMessageHandlers([ + ['configure', {async: true, handler: this._onMessageConfigure.bind(this)}], + ['setContentScale', {async: false, handler: this._onMessageSetContentScale.bind(this)}] ]); window.addEventListener('message', this._onWindowMessage.bind(this), false); @@ -98,29 +89,15 @@ class DisplayFloat extends Display { } } - autoPlayAudio() { - this._clearAutoPlayTimer(); - this._autoPlayAudioTimer = window.setTimeout(() => super.autoPlayAudio(), 400); - } - - // Message handling - - _onMessage(data) { + authenticateMessageData(data) { if (!this._frameEndpoint.authenticate(data)) { throw new Error('Invalid authentication'); } - - const {action, params} = data.data; - const handlerInfo = this._messageHandlers.get(action); - if (typeof handlerInfo === 'undefined') { - throw new Error(`Invalid action: ${action}`); - } - - const {async, handler} = handlerInfo; - const result = handler(params); - return {async, result}; + return data.data; } + // Message handling + _onWindowMessage(e) { const data = e.data; if (!this._frameEndpoint.authenticate(data)) { return; } @@ -148,22 +125,6 @@ class DisplayFloat extends Display { this._setContentScale(scale); } - _onMessageSetOptionsContext({optionsContext}) { - this.setOptionsContext(optionsContext); - } - - _onMessageSetContent({type, details}) { - this.setContent(type, details); - } - - _onMessageClearAutoPlayTimer() { - this._clearAutoPlayTimer.bind(this); - } - - _onMessageSetCustomCss({css}) { - this.setCustomCss(css); - } - _onMessageSetContentScale({scale}) { this._setContentScale(scale); } @@ -181,13 +142,6 @@ class DisplayFloat extends Display { return true; } - _clearAutoPlayTimer() { - if (this._autoPlayAudioTimer) { - window.clearTimeout(this._autoPlayAudioTimer); - this._autoPlayAudioTimer = null; - } - } - _setContentScale(scale) { const body = document.body; if (body === null) { return; } @@ -207,7 +161,13 @@ class DisplayFloat extends Display { yomichan.off('optionsUpdated', onOptionsUpdated); try { - await this._setupNestedPopups(id, depth, parentFrameId, url); + await this.setupNestedPopups({ + id, + depth, + parentFrameId, + url, + proxy: true + }); } catch (e) { yomichan.logError(e); } @@ -218,36 +178,6 @@ class DisplayFloat extends Display { await onOptionsUpdated(); } - async _setupNestedPopups(id, depth, parentFrameId, url) { - await dynamicLoader.loadScripts([ - '/mixed/js/text-scanner.js', - '/mixed/js/frame-client.js', - '/fg/js/popup.js', - '/fg/js/popup-proxy.js', - '/fg/js/popup-factory.js', - '/fg/js/frame-offset-forwarder.js', - '/fg/js/frontend.js' - ]); - - const {frameId} = await api.frameInformationGet(); - - const popupFactory = new PopupFactory(frameId); - popupFactory.prepare(); - - const frontend = new Frontend( - frameId, - popupFactory, - { - id, - depth, - parentFrameId, - url, - proxy: true - } - ); - await frontend.prepare(); - } - _invoke(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 82e77353..4e6794f3 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -20,11 +20,14 @@ * DOM * DisplayContext * DisplayGenerator + * Frontend * MediaLoader + * PopupFactory * WindowScroll * api * docRangeFromPoint * docSentenceExtract + * dynamicLoader */ class Display { @@ -32,7 +35,7 @@ class Display { this._spinner = spinner; this._container = container; this._definitions = []; - this._optionsContext = null; + this._optionsContext = {depth: 0, url: window.location.href}; this._options = null; this._context = null; this._index = 0; @@ -53,11 +56,14 @@ class Display { this._eventListenersActive = false; this._clickScanPrevent = false; this._setContentToken = null; + this._autoPlayAudioTimer = null; + this._autoPlayAudioDelay = 0; this._mediaLoader = new MediaLoader(); this._displayGenerator = new DisplayGenerator({mediaLoader: this._mediaLoader}); this._windowScroll = new WindowScroll(); this._hotkeys = new Map(); this._actions = new Map(); + this._messageHandlers = new Map(); this.registerActions([ ['close', () => { this.onEscape(); }], @@ -91,12 +97,29 @@ class Display { {key: 'P', modifiers: ['alt'], action: 'play-audio'}, {key: 'V', modifiers: ['alt'], action: 'view-note'} ]); + this.registerMessageHandlers([ + ['setOptionsContext', {async: false, handler: this._onMessageSetOptionsContext.bind(this)}], + ['setContent', {async: false, handler: this._onMessageSetContent.bind(this)}], + ['clearAutoPlayTimer', {async: false, handler: this._onMessageClearAutoPlayTimer.bind(this)}], + ['setCustomCss', {async: false, handler: this._onMessageSetCustomCss.bind(this)}] + ]); + } + + get autoPlayAudioDelay() { + return this._autoPlayAudioDelay; + } + + set autoPlayAudioDelay(value) { + this._autoPlayAudioDelay = value; } async prepare() { this._setInteractive(true); await this._displayGenerator.prepare(); yomichan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); + api.crossFrame.registerHandlers([ + ['popupMessage', {async: 'dynamic', handler: this._onMessage.bind(this)}] + ]); } onError(error) { @@ -155,9 +178,28 @@ class Display { } autoPlayAudio() { + this.clearAutoPlayTimer(); + if (this._definitions.length === 0) { return; } - this._audioPlay(this._definitions[0], this._getFirstExpressionIndex(), 0); + const definition = this._definitions[0]; + const expressionIndex = this._getFirstExpressionIndex(); + const callback = () => { + this._audioPlay(definition, expressionIndex, 0); + }; + + if (this._autoPlayAudioDelay > 0) { + this._autoPlayAudioTimer = setTimeout(callback, this._autoPlayAudioDelay); + } else { + callback(); + } + } + + clearAutoPlayTimer() { + if (this._autoPlayAudioTimer !== null) { + clearTimeout(this._autoPlayAudioTimer); + this._autoPlayAudioTimer = null; + } } async setContent(type, details) { @@ -228,6 +270,67 @@ class Display { } } + registerMessageHandlers(handlers) { + for (const [name, handlerInfo] of handlers) { + this._messageHandlers.set(name, handlerInfo); + } + } + + async setupNestedPopups(frontendInitializationData) { + await dynamicLoader.loadScripts([ + '/mixed/js/text-scanner.js', + '/mixed/js/frame-client.js', + '/fg/js/popup.js', + '/fg/js/popup-proxy.js', + '/fg/js/popup-factory.js', + '/fg/js/frame-offset-forwarder.js', + '/fg/js/frontend.js' + ]); + + const {frameId} = await api.frameInformationGet(); + + const popupFactory = new PopupFactory(frameId); + popupFactory.prepare(); + + const frontend = new Frontend(frameId, popupFactory, frontendInitializationData); + await frontend.prepare(); + } + + authenticateMessageData(data) { + return data; + } + + // Message handlers + + _onMessage(data) { + data = this.authenticateMessageData(data); + const {action, params} = data; + const handlerInfo = this._messageHandlers.get(action); + if (typeof handlerInfo === 'undefined') { + throw new Error(`Invalid action: ${action}`); + } + + const {async, handler} = handlerInfo; + const result = handler(params); + return {async, result}; + } + + _onMessageSetOptionsContext({optionsContext}) { + this.setOptionsContext(optionsContext); + } + + _onMessageSetContent({type, details}) { + this.setContent(type, details); + } + + _onMessageClearAutoPlayTimer() { + this.clearAutoPlayTimer(); + } + + _onMessageSetCustomCss({css}) { + this.setCustomCss(css); + } + // Private _onExtensionUnloaded() {