From 89a8494208d12b04c75724d2ea885adfa87349ef Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 11 Oct 2019 23:24:27 -0400 Subject: Add function for (de)activating event listeners in Display --- ext/mixed/js/display.js | 79 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 24 deletions(-) (limited to 'ext/mixed/js') diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 22181301..f879f5b3 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -30,14 +30,17 @@ class Display { this.audioFallback = null; this.audioCache = {}; this.optionsContext = {}; + this.eventListeners = []; + this.persistentEventListeners = []; + this.interactive = false; + this.eventListenersActive = false; this.dependencies = {}; this.windowScroll = new WindowScroll(); - document.addEventListener('keydown', this.onKeyDown.bind(this)); - document.addEventListener('wheel', this.onWheel.bind(this), {passive: false}); + this.setInteractive(true); } onError(error) { @@ -172,9 +175,48 @@ class Display { } } + setInteractive(interactive) { + interactive = !!interactive; + if (this.interactive === interactive) { return; } + this.interactive = interactive; + + if (interactive) { + Display.addEventListener(this.persistentEventListeners, document, 'keydown', this.onKeyDown.bind(this), false); + Display.addEventListener(this.persistentEventListeners, document, 'wheel', this.onWheel.bind(this), {passive: false}); + } else { + Display.clearEventListeners(this.persistentEventListeners); + } + this.setEventListenersActive(this.eventListenersActive); + } + + setEventListenersActive(active) { + active = !!active && this.interactive; + if (this.eventListenersActive === active) { return; } + this.eventListenersActive = active; + + if (active) { + this.addEventListeners('.action-add-note', 'click', this.onNoteAdd.bind(this)); + this.addEventListeners('.action-view-note', 'click', this.onNoteView.bind(this)); + this.addEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this)); + this.addEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this)); + this.addEventListeners('.source-term', 'click', this.onSourceTermView.bind(this)); + if (this.options.scanning.enablePopupSearch) { + this.addEventListeners('.glossary-item', 'click', this.onTermLookup.bind(this)); + } + } else { + Display.clearEventListeners(this.eventListeners); + } + } + + addEventListeners(selector, type, listener, options) { + this.container.querySelectorAll(selector).forEach((node) => { + Display.addEventListener(this.eventListeners, node, type, listener, options); + }); + } + async termsShow(definitions, options, context) { try { - this.clearEventListeners(); + this.setEventListenersActive(false); if (!context || context.focus !== false) { window.focus(); @@ -215,14 +257,7 @@ class Display { this.autoPlayAudio(); } - this.addEventListeners('.action-add-note', 'click', this.onNoteAdd.bind(this)); - this.addEventListeners('.action-view-note', 'click', this.onNoteView.bind(this)); - this.addEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this)); - this.addEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this)); - this.addEventListeners('.source-term', 'click', this.onSourceTermView.bind(this)); - if (this.options.scanning.enablePopupSearch) { - this.addEventListeners('.glossary-item', 'click', this.onTermLookup.bind(this)); - } + this.setEventListenersActive(true); await this.adderButtonUpdate(['term-kanji', 'term-kana'], sequence); } catch (e) { @@ -232,7 +267,7 @@ class Display { async kanjiShow(definitions, options, context) { try { - this.clearEventListeners(); + this.setEventListenersActive(false); if (!context || context.focus !== false) { window.focus(); @@ -265,9 +300,7 @@ class Display { const {index, scroll} = context || {}; this.entryScrollIntoView(index || 0, scroll); - this.addEventListeners('.action-add-note', 'click', this.onNoteAdd.bind(this)); - this.addEventListeners('.action-view-note', 'click', this.onNoteView.bind(this)); - this.addEventListeners('.source-term', 'click', this.onSourceTermView.bind(this)); + this.setEventListenersActive(true); await this.adderButtonUpdate(['kanji'], sequence); } catch (e) { @@ -544,18 +577,16 @@ class Display { return -1; } - addEventListeners(selector, type, listener, options) { - this.container.querySelectorAll(selector).forEach((node) => { - node.addEventListener(type, listener, options); - this.eventListeners.push([node, type, listener, options]); - }); + static addEventListener(eventListeners, object, type, listener, options) { + object.addEventListener(type, listener, options); + eventListeners.push([object, type, listener, options]); } - clearEventListeners() { - for (const [node, type, listener, options] of this.eventListeners) { - node.removeEventListener(type, listener, options); + static clearEventListeners(eventListeners) { + for (const [object, type, listener, options] of eventListeners) { + object.removeEventListener(type, listener, options); } - this.eventListeners = []; + eventListeners.length = 0; } static getElementTop(element) { -- cgit v1.2.3 From 3e249e19ac5f5650553c6303b87adfdb2a688536 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 12 Oct 2019 14:20:54 -0400 Subject: Update Display initialization process --- ext/bg/js/search.js | 46 ++++++++++++++++++++++++++++++++-------------- ext/fg/js/float.js | 8 +++++++- ext/fg/js/popup.js | 6 +----- ext/mixed/js/display.js | 37 +++++++++++++++++++++++++++++++------ 4 files changed, 71 insertions(+), 26 deletions(-) (limited to 'ext/mixed/js') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index ead9ba6f..7a8fdf5e 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -33,23 +33,37 @@ class DisplaySearch extends Display { this.introAnimationTimer = null; this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract}); + } - if (this.search !== null) { - this.search.addEventListener('click', (e) => this.onSearch(e), false); - } - if (this.query !== null) { - this.query.addEventListener('input', () => this.onSearchInput(), false); + static create() { + const instance = new DisplaySearch(); + instance.prepare(); + return instance; + } + + async prepare() { + try { + await this.initialize(); - const query = DisplaySearch.getSearchQueryFromLocation(window.location.href); - if (query !== null) { - this.query.value = window.wanakana.toKana(query); - this.onSearchQueryUpdated(query, false); + if (this.search !== null) { + this.search.addEventListener('click', (e) => this.onSearch(e), false); } + if (this.query !== null) { + this.query.addEventListener('input', () => this.onSearchInput(), false); - window.wanakana.bind(this.query); - } + const query = DisplaySearch.getSearchQueryFromLocation(window.location.href); + if (query !== null) { + this.query.value = window.wanakana.toKana(query); + this.onSearchQueryUpdated(query, false); + } - this.updateSearchButton(); + window.wanakana.bind(this.query); + } + + this.updateSearchButton(); + } catch (e) { + this.onError(e); + } } onError(error) { @@ -89,7 +103,7 @@ class DisplaySearch extends Display { this.updateSearchButton(); if (valid) { const {definitions} = await apiTermsFind(query, this.optionsContext); - this.termsShow(definitions, await apiOptionsGet(this.optionsContext)); + this.termsShow(definitions, this.options); } else { this.container.textContent = ''; } @@ -98,6 +112,10 @@ class DisplaySearch extends Display { } } + getOptionsContext() { + return this.optionsContext; + } + setIntroVisible(visible, animate) { if (this.introVisible === visible) { return; @@ -164,4 +182,4 @@ class DisplaySearch extends Display { } } -window.yomichan_search = new DisplaySearch(); +window.yomichan_search = DisplaySearch.create(); diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 533d98e1..d9b483d7 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -84,6 +84,10 @@ class DisplayFloat extends Display { super.onKeyDown(e); } + getOptionsContext() { + return this.optionsContext; + } + autoPlayAudio() { this.clearAutoPlayTimer(); this.autoPlayAudioTimer = window.setTimeout(() => super.autoPlayAudio(), 400); @@ -96,7 +100,9 @@ class DisplayFloat extends Display { } } - initialize(options, popupInfo, url, childrenSupported) { + async initialize(options, popupInfo, url, childrenSupported) { + await super.initialize(options); + const css = options.general.customPopupCss; if (css) { this.setStyle(css); diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 7f96fe97..0fd6a9d0 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -61,11 +61,7 @@ class Popup { const parentFrameId = (typeof this.frameId === 'number' ? this.frameId : null); this.container.addEventListener('load', () => { this.invokeApi('initialize', { - options: { - general: { - customPopupCss: options.general.customPopupCss - } - }, + options: options, popupInfo: { id: this.id, depth: this.depth, diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index f879f5b3..0d7be355 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -29,7 +29,6 @@ class Display { this.audioPlaying = null; this.audioFallback = null; this.audioCache = {}; - this.optionsContext = {}; this.eventListeners = []; this.persistentEventListeners = []; @@ -76,7 +75,7 @@ class Display { context.source.source = this.context.source; } - const kanjiDefs = await apiKanjiFind(link.textContent, this.optionsContext); + const kanjiDefs = await apiKanjiFind(link.textContent, this.getOptionsContext()); this.kanjiShow(kanjiDefs, this.options, context); } catch (e) { this.onError(e); @@ -99,7 +98,7 @@ class Display { try { textSource.setEndOffset(this.options.scanning.length); - ({definitions, length} = await apiTermsFind(textSource.text(), this.optionsContext)); + ({definitions, length} = await apiTermsFind(textSource.text(), this.getOptionsContext())); if (definitions.length === 0) { return false; } @@ -175,6 +174,28 @@ class Display { } } + onRuntimeMessage({action, params}, sender, callback) { + const handlers = Display.runtimeMessageHandlers; + if (handlers.hasOwnProperty(action)) { + const handler = handlers[action]; + handler(this, params); + callback(); + } + } + + getOptionsContext() { + throw new Error('Override me'); + } + + async initialize(options=null) { + await this.updateOptions(options); + chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); + } + + async updateOptions(options) { + this.options = options ? options : await apiOptionsGet(this.getOptionsContext()); + } + setInteractive(interactive) { interactive = !!interactive; if (this.interactive === interactive) { return; } @@ -314,7 +335,7 @@ class Display { async adderButtonUpdate(modes, sequence) { try { - const states = await apiDefinitionsAddable(this.definitions, modes, this.optionsContext); + const states = await apiDefinitionsAddable(this.definitions, modes, this.getOptionsContext()); if (!states || sequence !== this.sequence) { return; } @@ -416,7 +437,7 @@ class Display { } } - const noteId = await apiDefinitionAdd(definition, mode, context, this.optionsContext); + const noteId = await apiDefinitionAdd(definition, mode, context, this.getOptionsContext()); if (noteId) { const index = this.definitions.indexOf(definition); const adderButton = this.adderButtonFind(index, mode); @@ -446,7 +467,7 @@ class Display { } const sources = this.options.audio.sources; - let {audio, source} = await audioGetFromSources(expression, sources, this.optionsContext, true, this.audioCache); + let {audio, source} = await audioGetFromSources(expression, sources, this.getOptionsContext(), true, this.audioCache); let info; if (audio === null) { if (this.audioFallback === null) { @@ -706,3 +727,7 @@ Display.onKeyDownHandlers = { return false; } }; + +Display.runtimeMessageHandlers = { + optionsUpdate: (self) => self.updateOptions(null) +}; -- cgit v1.2.3 From a5b208fb895d46793223910451d177dc53d9463a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 12 Oct 2019 14:41:24 -0400 Subject: Check if objects are properly initialized before showing content --- ext/fg/js/popup.js | 7 +++++++ ext/mixed/js/display.js | 8 ++++++++ 2 files changed, 15 insertions(+) (limited to 'ext/mixed/js') diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 396a5be9..a9fde7b6 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -79,6 +79,10 @@ class Popup { }); } + isInitialized() { + return this.options !== null; + } + async setOptions(options) { this.options = options; } @@ -212,6 +216,7 @@ class Popup { } async showOrphaned(elementRect, writingMode, options) { + if (!this.isInitialized()) { return; } await this.show(elementRect, writingMode, options); this.invokeApi('orphaned'); } @@ -275,11 +280,13 @@ class Popup { } async termsShow(elementRect, writingMode, definitions, options, context) { + if (!this.isInitialized()) { return; } await this.show(elementRect, writingMode, options); this.invokeApi('termsShow', {definitions, options, context}); } async kanjiShow(elementRect, writingMode, definitions, options, context) { + if (!this.isInitialized()) { return; } await this.show(elementRect, writingMode, options); this.invokeApi('kanjiShow', {definitions, options, context}); } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 0d7be355..d5d055e0 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -187,6 +187,10 @@ class Display { throw new Error('Override me'); } + isInitialized() { + return this.options !== null; + } + async initialize(options=null) { await this.updateOptions(options); chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); @@ -236,6 +240,8 @@ class Display { } async termsShow(definitions, options, context) { + if (!this.isInitialized()) { return; } + try { this.setEventListenersActive(false); @@ -287,6 +293,8 @@ class Display { } async kanjiShow(definitions, options, context) { + if (!this.isInitialized()) { return; } + try { this.setEventListenersActive(false); -- cgit v1.2.3 From 6da76835524fbf6b95902f06822d77c54ccf735b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 12 Oct 2019 14:55:18 -0400 Subject: Don't pass options around for calls to termsShow, kanjiShow, etc. --- ext/bg/js/search.js | 6 +++++- ext/fg/js/float.js | 4 ++-- ext/fg/js/frontend.js | 5 +---- ext/fg/js/popup-proxy-host.js | 18 +++++++++--------- ext/fg/js/popup-proxy.js | 12 ++++++------ ext/fg/js/popup.js | 30 +++++++++++++++--------------- ext/mixed/js/display.js | 18 ++++++++++-------- 7 files changed, 48 insertions(+), 45 deletions(-) (limited to 'ext/mixed/js') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 7a8fdf5e..80519f4a 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -103,7 +103,11 @@ class DisplaySearch extends Display { this.updateSearchButton(); if (valid) { const {definitions} = await apiTermsFind(query, this.optionsContext); - this.termsShow(definitions, this.options); + this.termsShow(definitions, { + focus: false, + sentence: null, + url: window.location.href + }); } else { this.container.textContent = ''; } diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index d9b483d7..4a571466 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -143,8 +143,8 @@ DisplayFloat.onKeyDownHandlers = { }; DisplayFloat.messageHandlers = { - termsShow: (self, {definitions, options, context}) => self.termsShow(definitions, options, context), - kanjiShow: (self, {definitions, options, context}) => self.kanjiShow(definitions, options, context), + termsShow: (self, {definitions, context}) => self.termsShow(definitions, context), + kanjiShow: (self, {definitions, context}) => self.kanjiShow(definitions, context), clearAutoPlayTimer: (self) => self.clearAutoPlayTimer(), orphaned: (self) => self.onOrphaned(), initialize: (self, {options, popupInfo, url, childrenSupported}) => self.initialize(options, popupInfo, url, childrenSupported) diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index f67441af..52a23889 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -333,8 +333,7 @@ class Frontend { if (textSource && this.options.scanning.modifier !== 'none') { this.popup.showOrphaned( textSource.getRect(), - textSource.getWritingMode(), - this.options + textSource.getWritingMode() ); } } else { @@ -374,7 +373,6 @@ class Frontend { textSource.getRect(), textSource.getWritingMode(), definitions, - this.options, {sentence, url, focus} ); @@ -405,7 +403,6 @@ class Frontend { textSource.getRect(), textSource.getWritingMode(), definitions, - this.options, {sentence, url, focus} ); diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index c3acec7f..74a5153a 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -39,12 +39,12 @@ class PopupProxyHost { this.apiReceiver = new FrontendApiReceiver(`popup-proxy-host#${frameId}`, { createNestedPopup: ({parentId}) => this.createNestedPopup(parentId), setOptions: ({id, options}) => this.setOptions(id, options), - showOrphaned: ({id, elementRect, options}) => this.showOrphaned(id, elementRect, options), + showOrphaned: ({id, elementRect}) => this.showOrphaned(id, elementRect), hide: ({id, changeFocus}) => this.hide(id, changeFocus), setVisibleOverride: ({id, visible}) => this.setVisibleOverride(id, visible), containsPoint: ({id, x, y}) => this.containsPoint(id, x, y), - termsShow: ({id, elementRect, writingMode, definitions, options, context}) => this.termsShow(id, elementRect, writingMode, definitions, options, context), - kanjiShow: ({id, elementRect, writingMode, definitions, options, context}) => this.kanjiShow(id, elementRect, writingMode, definitions, options, context), + termsShow: ({id, elementRect, writingMode, definitions, context}) => this.termsShow(id, elementRect, writingMode, definitions, context), + kanjiShow: ({id, elementRect, writingMode, definitions, context}) => this.kanjiShow(id, elementRect, writingMode, definitions, context), clearAutoPlayTimer: ({id}) => this.clearAutoPlayTimer(id) }); } @@ -91,10 +91,10 @@ class PopupProxyHost { return await popup.setOptions(options); } - async showOrphaned(id, elementRect, options) { + async showOrphaned(id, elementRect) { const popup = this.getPopup(id); elementRect = this.jsonRectToDOMRect(popup, elementRect); - return await popup.showOrphaned(elementRect, options); + return await popup.showOrphaned(elementRect); } async hide(id, changeFocus) { @@ -112,18 +112,18 @@ class PopupProxyHost { return await popup.containsPoint(x, y); } - async termsShow(id, elementRect, writingMode, definitions, options, context) { + async termsShow(id, elementRect, writingMode, definitions, context) { const popup = this.getPopup(id); elementRect = this.jsonRectToDOMRect(popup, elementRect); if (!PopupProxyHost.popupCanShow(popup)) { return false; } - return await popup.termsShow(elementRect, writingMode, definitions, options, context); + return await popup.termsShow(elementRect, writingMode, definitions, context); } - async kanjiShow(id, elementRect, writingMode, definitions, options, context) { + async kanjiShow(id, elementRect, writingMode, definitions, context) { const popup = this.getPopup(id); elementRect = this.jsonRectToDOMRect(popup, elementRect); if (!PopupProxyHost.popupCanShow(popup)) { return false; } - return await popup.kanjiShow(elementRect, writingMode, definitions, options, context); + return await popup.kanjiShow(elementRect, writingMode, definitions, context); } async clearAutoPlayTimer(id) { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 96fc8890..e8d6bc98 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -51,10 +51,10 @@ class PopupProxy { return await this.invokeHostApi('setOptions', {id, options}); } - async showOrphaned(elementRect, options) { + async showOrphaned(elementRect) { const id = await this.getPopupId(); elementRect = PopupProxy.DOMRectToJson(elementRect); - return await this.invokeHostApi('showOrphaned', {id, elementRect, options}); + return await this.invokeHostApi('showOrphaned', {id, elementRect}); } async hide(changeFocus) { @@ -76,16 +76,16 @@ class PopupProxy { return await this.invokeHostApi('containsPoint', {id: this.id, x, y}); } - async termsShow(elementRect, writingMode, definitions, options, context) { + async termsShow(elementRect, writingMode, definitions, context) { const id = await this.getPopupId(); elementRect = PopupProxy.DOMRectToJson(elementRect); - return await this.invokeHostApi('termsShow', {id, elementRect, writingMode, definitions, options, context}); + return await this.invokeHostApi('termsShow', {id, elementRect, writingMode, definitions, context}); } - async kanjiShow(elementRect, writingMode, definitions, options, context) { + async kanjiShow(elementRect, writingMode, definitions, context) { const id = await this.getPopupId(); elementRect = PopupProxy.DOMRectToJson(elementRect); - return await this.invokeHostApi('kanjiShow', {id, elementRect, writingMode, definitions, options, context}); + return await this.invokeHostApi('kanjiShow', {id, elementRect, writingMode, definitions, context}); } async clearAutoPlayTimer() { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index a9fde7b6..f36bb436 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -41,14 +41,14 @@ class Popup { this.updateVisibility(); } - inject(options) { + inject() { if (this.injectPromise === null) { - this.injectPromise = this.createInjectPromise(options); + this.injectPromise = this.createInjectPromise(); } return this.injectPromise; } - async createInjectPromise(options) { + async createInjectPromise() { try { const {frameId} = await this.frameIdPromise; if (typeof frameId === 'number') { @@ -62,7 +62,7 @@ class Popup { const parentFrameId = (typeof this.frameId === 'number' ? this.frameId : null); this.container.addEventListener('load', () => { this.invokeApi('initialize', { - options: options, + options: this.options, popupInfo: { id: this.id, depth: this.depth, @@ -87,10 +87,10 @@ class Popup { this.options = options; } - async show(elementRect, writingMode, options) { - await this.inject(options); + async show(elementRect, writingMode) { + await this.inject(); - const optionsGeneral = options.general; + const optionsGeneral = this.options.general; const container = this.container; const containerRect = container.getBoundingClientRect(); const getPosition = ( @@ -215,9 +215,9 @@ class Popup { return [position, size, after]; } - async showOrphaned(elementRect, writingMode, options) { + async showOrphaned(elementRect, writingMode) { if (!this.isInitialized()) { return; } - await this.show(elementRect, writingMode, options); + await this.show(elementRect, writingMode); this.invokeApi('orphaned'); } @@ -279,16 +279,16 @@ class Popup { return false; } - async termsShow(elementRect, writingMode, definitions, options, context) { + async termsShow(elementRect, writingMode, definitions, context) { if (!this.isInitialized()) { return; } - await this.show(elementRect, writingMode, options); - this.invokeApi('termsShow', {definitions, options, context}); + await this.show(elementRect, writingMode); + this.invokeApi('termsShow', {definitions, context}); } - async kanjiShow(elementRect, writingMode, definitions, options, context) { + async kanjiShow(elementRect, writingMode, definitions, context) { if (!this.isInitialized()) { return; } - await this.show(elementRect, writingMode, options); - this.invokeApi('kanjiShow', {definitions, options, context}); + await this.show(elementRect, writingMode); + this.invokeApi('kanjiShow', {definitions, context}); } clearAutoPlayTimer() { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index d5d055e0..b3ddae72 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -76,7 +76,7 @@ class Display { } const kanjiDefs = await apiKanjiFind(link.textContent, this.getOptionsContext()); - this.kanjiShow(kanjiDefs, this.options, context); + this.kanjiShow(kanjiDefs, context); } catch (e) { this.onError(e); } @@ -125,7 +125,7 @@ class Display { context.source.source = this.context.source; } - this.termsShow(definitions, this.options, context); + this.termsShow(definitions, context); } catch (e) { this.onError(e); } @@ -239,10 +239,12 @@ class Display { }); } - async termsShow(definitions, options, context) { + async termsShow(definitions, context) { if (!this.isInitialized()) { return; } try { + const options = this.options; + this.setEventListenersActive(false); if (!context || context.focus !== false) { @@ -250,7 +252,6 @@ class Display { } this.definitions = definitions; - this.options = options; this.context = context; const sequence = ++this.sequence; @@ -280,7 +281,7 @@ class Display { const {index, scroll} = context || {}; this.entryScrollIntoView(index || 0, scroll); - if (this.options.audio.enabled && this.options.audio.autoPlay) { + if (options.audio.enabled && options.audio.autoPlay) { this.autoPlayAudio(); } @@ -292,10 +293,12 @@ class Display { } } - async kanjiShow(definitions, options, context) { + async kanjiShow(definitions, context) { if (!this.isInitialized()) { return; } try { + const options = this.options; + this.setEventListenersActive(false); if (!context || context.focus !== false) { @@ -303,7 +306,6 @@ class Display { } this.definitions = definitions; - this.options = options; this.context = context; const sequence = ++this.sequence; @@ -415,7 +417,7 @@ class Display { source: this.context.source.source }; - this.termsShow(this.context.source.definitions, this.options, context); + this.termsShow(this.context.source.definitions, context); } } -- cgit v1.2.3 From 537d2ef532aa7b7498de13ab039bd23f28d32714 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 12 Oct 2019 15:00:39 -0400 Subject: Remove Display.dependencies --- ext/bg/js/search.js | 2 -- ext/fg/js/float.js | 2 -- ext/mixed/js/display.js | 4 ---- 3 files changed, 8 deletions(-) (limited to 'ext/mixed/js') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 80519f4a..1d780d70 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -31,8 +31,6 @@ class DisplaySearch extends Display { this.intro = document.querySelector('#intro'); this.introVisible = true; this.introAnimationTimer = null; - - this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract}); } static create() { diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 4a571466..5164cd8f 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -28,8 +28,6 @@ class DisplayFloat extends Display { url: window.location.href }; - this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract}); - window.addEventListener('message', (e) => this.onMessage(e), false); } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index b3ddae72..cd9f41bd 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -35,8 +35,6 @@ class Display { this.interactive = false; this.eventListenersActive = false; - this.dependencies = {}; - this.windowScroll = new WindowScroll(); this.setInteractive(true); @@ -86,8 +84,6 @@ class Display { try { e.preventDefault(); - const {docRangeFromPoint, docSentenceExtract} = this.dependencies; - const clickedElement = e.target; const textSource = docRangeFromPoint(e.clientX, e.clientY, this.options); if (textSource === null) { -- cgit v1.2.3 From c90bc75eb89f5a731f6e3366f6388b594a27b2aa Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 12 Oct 2019 17:06:03 -0400 Subject: Create themes --- ext/bg/search.html | 2 ++ ext/fg/css/client.css | 8 ++++- ext/fg/float.html | 2 ++ ext/fg/js/popup.js | 1 + ext/mixed/css/display-dark.css | 51 +++++++++++++++++++++++++++++ ext/mixed/css/display-default.css | 51 +++++++++++++++++++++++++++++ ext/mixed/css/display.css | 68 ++++++--------------------------------- ext/mixed/js/display.js | 11 +++++++ 8 files changed, 134 insertions(+), 60 deletions(-) create mode 100644 ext/mixed/css/display-dark.css create mode 100644 ext/mixed/css/display-default.css (limited to 'ext/mixed/js') diff --git a/ext/bg/search.html b/ext/bg/search.html index e63b4ac1..12b62797 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -7,6 +7,8 @@ + +
diff --git a/ext/fg/css/client.css b/ext/fg/css/client.css index a2b06d0f..84098653 100644 --- a/ext/fg/css/client.css +++ b/ext/fg/css/client.css @@ -21,7 +21,7 @@ iframe#yomichan-float { all: initial; background-color: #fff; border: 1px solid #999; - box-shadow: 0 0 10px rgba(0, 0, 0, .5); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); position: fixed; resize: both; visibility: hidden; @@ -29,6 +29,12 @@ iframe#yomichan-float { box-sizing: border-box; } +iframe#yomichan-float[data-yomichan-theme=dark] { + background-color: #1e1e1e; + border: 1px solid #666; + box-shadow: 0 0 10px rgba(255, 255, 255, 0.5); +} + iframe#yomichan-float.yomichan-float-full-width { border-left: none; border-right: none; diff --git a/ext/fg/float.html b/ext/fg/float.html index ac443c01..01bc4250 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -5,6 +5,8 @@ + +
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index f36bb436..3556a52e 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -85,6 +85,7 @@ class Popup { async setOptions(options) { this.options = options; + this.container.dataset.yomichanTheme = options.general.popupTheme; } async show(elementRect, writingMode) { diff --git a/ext/mixed/css/display-dark.css b/ext/mixed/css/display-dark.css new file mode 100644 index 00000000..75e42565 --- /dev/null +++ b/ext/mixed/css/display-dark.css @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019 Alex Yatskov + * Author: Alex Yatskov + * + * This program is free software: you can redistribute it and/or modify + * it under the entrys 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 . + */ + + +body, +html.yomichan-float body { background-color: #1e1e1e; color: #d4d4d4; } + +hr { border-top-color: #2f2f2f; } + +.tag-default { background-color: #69696e; } +.tag-name { background-color: #489148; } +.tag-expression { background-color: #b07f39; } +.tag-popular { background-color: #025caa; } +.tag-frequent { background-color: #4490a7; } +.tag-archaism { background-color: #b04340; } +.tag-dictionary { background-color: #9057ad; } +.tag-frequency { background-color: #489148; } +.tag-partOfSpeech { background-color: #565656; } + +.reasons { color: #888888; } +.glossary li { color: #888888; } +.glossary-item { color: #d4d4d4; } +.label { color: #e1e1e1; } + +.expression .kanji-link { + border-bottom-color: #888888; + color: #CCCCCC; +} + +.expression-popular, .expression-popular .kanji-link { + color: #0275d8; +} + +.expression-rare, .expression-rare .kanji-link { + color: #666666; +} diff --git a/ext/mixed/css/display-default.css b/ext/mixed/css/display-default.css new file mode 100644 index 00000000..ce1008a8 --- /dev/null +++ b/ext/mixed/css/display-default.css @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019 Alex Yatskov + * Author: Alex Yatskov + * + * This program is free software: you can redistribute it and/or modify + * it under the entrys 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 . + */ + + +body, +html.yomichan-float body { background-color: #ffffff; color: #333333; } + +hr { border-top-color: #eeeeee; } + +.tag-default { background-color: #8a8a91; } +.tag-name { background-color: #5cb85c; } +.tag-expression { background-color: #f0ad4e; } +.tag-popular { background-color: #0275d8; } +.tag-frequent { background-color: #5bc0de; } +.tag-archaism { background-color: #d9534f; } +.tag-dictionary { background-color: #aa66cc; } +.tag-frequency { background-color: #5cb85c; } +.tag-partOfSpeech { background-color: #565656; } + +.reasons { color: #777777; } +.glossary li { color: #777777; } +.glossary-item { color: #000000; } +.label { color: #ffffff; } + +.expression .kanji-link { + border-bottom-color: #777777; + color: #333333; +} + +.expression-popular, .expression-popular .kanji-link { + color: #0275d8; +} + +.expression-rare, .expression-rare .kanji-link { + color: #999999; +} diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index e88372bb..d899e9fc 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -30,12 +30,15 @@ * General */ +html.yomichan-float, +html.yomichan-float body { + background-color: transparent; +} + body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.42857143; - color: #333; - background-color: #fff; margin: 0; border: 0; padding: 0; @@ -45,7 +48,8 @@ hr { padding: 0px; margin: 0px; border: 0; - border-top: 1px solid #eee; + border-top-width: 1px; + border-top-style: solid; } ol, ul { @@ -84,42 +88,6 @@ html:root.yomichan-float .note { padding-right: 10px; } -.tag-default { - background-color: #8a8a91; -} - -.tag-name { - background-color: #5cb85c; -} - -.tag-expression { - background-color: #f0ad4e; -} - -.tag-popular { - background-color: #0275d8; -} - -.tag-frequent { - background-color: #5bc0de; -} - -.tag-archaism { - background-color: #d9534f; -} - -.tag-dictionary { - background-color: #aa66cc; -} - -.tag-frequency { - background-color: #5cb85c; -} - -.tag-partOfSpeech { - background-color: #565656; -} - .actions .disabled { pointer-events: none; cursor: default; @@ -152,19 +120,11 @@ html:root.yomichan-float .note { } .expression .kanji-link { - border-bottom: 1px #777 dashed; - color: #333; + border-bottom-width: 1px; + border-bottom-style: dashed; text-decoration: none; } -.expression-popular, .expression-popular .kanji-link { - color: #0275d8; -} - -.expression-rare, .expression-rare .kanji-link { - color: #999; -} - .expression .peek-wrapper { font-size: 14px; white-space: nowrap; @@ -198,7 +158,6 @@ html:root.yomichan-float .note { } .reasons { - color: #777; display: inline-block; } @@ -224,14 +183,6 @@ html:root.yomichan-float .note { content: " | "; } -.glossary li { - color: #777; -} - -.glossary-item { - color: #000; -} - div.glossary-item.compact-glossary { display: inline; } @@ -266,7 +217,6 @@ div.glossary-item.compact-glossary { font-size: 75%; font-weight: 700; line-height: 1; - color: #fff; text-align: center; white-space: nowrap; vertical-align: baseline; diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index cd9f41bd..2bf917aa 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -194,6 +194,17 @@ class Display { async updateOptions(options) { this.options = options ? options : await apiOptionsGet(this.getOptionsContext()); + this.updateTheme(this.options.general.popupTheme); + } + + updateTheme(themeName) { + document.documentElement.dataset.yomichanTheme = themeName; + + const stylesheets = document.querySelectorAll('link[data-yomichan-theme-name]'); + for (const stylesheet of stylesheets) { + const match = (stylesheet.dataset.yomichanThemeName === themeName); + stylesheet.rel = (match ? 'stylesheet' : 'stylesheet alternate'); + } } setInteractive(interactive) { -- cgit v1.2.3 From 883226b0451350a0c01841e6c9a192bbb76dd8b6 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 12 Oct 2019 17:12:34 -0400 Subject: Update how custom CSS is applied --- ext/bg/js/search.js | 4 ++++ ext/fg/js/float.js | 21 +-------------------- ext/fg/js/popup-proxy-host.js | 6 ++++++ ext/fg/js/popup-proxy.js | 5 +++++ ext/fg/js/popup.js | 4 ++++ ext/mixed/js/display.js | 16 ++++++++++++++++ 6 files changed, 36 insertions(+), 20 deletions(-) (limited to 'ext/mixed/js') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 1d780d70..68afe47e 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -118,6 +118,10 @@ class DisplaySearch extends Display { return this.optionsContext; } + setCustomCss() { + // No custom CSS + } + setIntroVisible(visible, animate) { if (this.introVisible === visible) { return; diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 5164cd8f..4b3cd848 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -21,7 +21,6 @@ class DisplayFloat extends Display { constructor() { super(document.querySelector('#spinner'), document.querySelector('#definitions')); this.autoPlayAudioTimer = null; - this.styleNode = null; this.optionsContext = { depth: 0, @@ -101,11 +100,6 @@ class DisplayFloat extends Display { async initialize(options, popupInfo, url, childrenSupported) { await super.initialize(options); - const css = options.general.customPopupCss; - if (css) { - this.setStyle(css); - } - const {id, depth, parentFrameId} = popupInfo; this.optionsContext.depth = depth; this.optionsContext.url = url; @@ -114,20 +108,6 @@ class DisplayFloat extends Display { popupNestedInitialize(id, depth, parentFrameId, url); } } - - setStyle(css) { - const parent = document.head; - - if (this.styleNode === null) { - this.styleNode = document.createElement('style'); - } - - this.styleNode.textContent = css; - - if (this.styleNode.parentNode !== parent) { - parent.appendChild(this.styleNode); - } - } } DisplayFloat.onKeyDownHandlers = { @@ -145,6 +125,7 @@ DisplayFloat.messageHandlers = { kanjiShow: (self, {definitions, context}) => self.kanjiShow(definitions, context), clearAutoPlayTimer: (self) => self.clearAutoPlayTimer(), orphaned: (self) => self.onOrphaned(), + setCustomCss: (self, {css}) => self.setCustomCss(css), initialize: (self, {options, popupInfo, url, childrenSupported}) => self.initialize(options, popupInfo, url, childrenSupported) }; diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index 74a5153a..bb323f64 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -45,6 +45,7 @@ class PopupProxyHost { containsPoint: ({id, x, y}) => this.containsPoint(id, x, y), termsShow: ({id, elementRect, writingMode, definitions, context}) => this.termsShow(id, elementRect, writingMode, definitions, context), kanjiShow: ({id, elementRect, writingMode, definitions, context}) => this.kanjiShow(id, elementRect, writingMode, definitions, context), + setCustomCss: ({id, css}) => this.setCustomCss(id, css), clearAutoPlayTimer: ({id}) => this.clearAutoPlayTimer(id) }); } @@ -126,6 +127,11 @@ class PopupProxyHost { return await popup.kanjiShow(elementRect, writingMode, definitions, context); } + async setCustomCss(id, css) { + const popup = this.getPopup(id); + return popup.setCustomCss(css); + } + async clearAutoPlayTimer(id) { const popup = this.getPopup(id); return popup.clearAutoPlayTimer(); diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index e8d6bc98..6ea94b6a 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -88,6 +88,11 @@ class PopupProxy { return await this.invokeHostApi('kanjiShow', {id, elementRect, writingMode, definitions, context}); } + async setCustomCss(css) { + const id = await this.getPopupId(); + return await this.invokeHostApi('setCustomCss', {id, css}); + } + async clearAutoPlayTimer() { if (this.id === null) { return; diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 3556a52e..5ca8643f 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -292,6 +292,10 @@ class Popup { this.invokeApi('kanjiShow', {definitions, context}); } + async setCustomCss(css) { + this.invokeApi('setCustomCss', {css}); + } + clearAutoPlayTimer() { if (this.isInjected) { this.invokeApi('clearAutoPlayTimer'); diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 2bf917aa..51a3dc22 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -29,6 +29,7 @@ class Display { this.audioPlaying = null; this.audioFallback = null; this.audioCache = {}; + this.styleNode = null; this.eventListeners = []; this.persistentEventListeners = []; @@ -195,6 +196,7 @@ class Display { async updateOptions(options) { this.options = options ? options : await apiOptionsGet(this.getOptionsContext()); this.updateTheme(this.options.general.popupTheme); + this.setCustomCss(this.options.general.customPopupCss); } updateTheme(themeName) { @@ -207,6 +209,20 @@ class Display { } } + setCustomCss(css) { + if (this.styleNode === null) { + if (css.length === 0) { return; } + this.styleNode = document.createElement('style'); + } + + this.styleNode.textContent = css; + + const parent = document.head; + if (this.styleNode.parentNode !== parent) { + parent.appendChild(this.styleNode); + } + } + setInteractive(interactive) { interactive = !!interactive; if (this.interactive === interactive) { return; } -- cgit v1.2.3 From 21a2730cdeef80327285f77a155451a8fbf67939 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 12 Oct 2019 22:50:22 -0400 Subject: Add option for text-to-speech --- ext/bg/js/options.js | 3 ++- ext/bg/js/settings.js | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ ext/bg/settings.html | 11 ++++++++ ext/mixed/js/audio.js | 13 +++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) (limited to 'ext/mixed/js') diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index fac17d68..4854cd65 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -287,7 +287,8 @@ function profileOptionsCreateDefaults() { sources: ['jpod101'], volume: 100, autoPlay: false, - customSourceUrl: '' + customSourceUrl: '', + textToSpeechVoice: '' }, scanning: { diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 77815955..debf9f2f 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -48,6 +48,7 @@ async function formRead(options) { options.audio.autoPlay = $('#auto-play-audio').prop('checked'); options.audio.volume = parseFloat($('#audio-playback-volume').val()); options.audio.customSourceUrl = $('#audio-custom-source').val(); + options.audio.textToSpeechVoice = $('#text-to-speech-voice').val(); options.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked'); options.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked'); @@ -119,6 +120,7 @@ async function formWrite(options) { $('#auto-play-audio').prop('checked', options.audio.autoPlay); $('#audio-playback-volume').val(options.audio.volume); $('#audio-custom-source').val(options.audio.customSourceUrl); + $('#text-to-speech-voice').val(options.audio.textToSpeechVoice).attr('data-value', options.audio.textToSpeechVoice); $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse); $('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled); @@ -326,6 +328,77 @@ async function audioSettingsInitialize() { const options = await apiOptionsGet(optionsContext); audioSourceUI = new AudioSourceUI.Container(options.audio.sources, $('.audio-source-list'), $('.audio-source-add')); audioSourceUI.save = () => apiOptionsSave(); + + textToSpeechInitialize(); +} + +function textToSpeechInitialize() { + if (typeof speechSynthesis === 'undefined') { return; } + + speechSynthesis.addEventListener('voiceschanged', () => updateTextToSpeechVoices(), false); + updateTextToSpeechVoices(); + + $('#text-to-speech-voice-test').on('click', () => textToSpeechTest()); +} + +function updateTextToSpeechVoices() { + const voices = Array.prototype.map.call(speechSynthesis.getVoices(), (voice, index) => ({voice, index})); + voices.sort(textToSpeechVoiceCompare); + if (voices.length > 0) { + $('#text-to-speech-voice-container').css('display', ''); + } + + const select = $('#text-to-speech-voice'); + select.empty(); + select.append($('
+ +
@@ -658,6 +668,7 @@ + diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js index b905140c..5e3b9164 100644 --- a/ext/mixed/js/audio.js +++ b/ext/mixed/js/audio.js @@ -58,3 +58,16 @@ async function audioGetFromSources(expression, sources, optionsContext, createAu } return {audio: null, source: null}; } + +function audioGetTextToSpeechVoice(voiceURI) { + try { + for (const voice of speechSynthesis.getVoices()) { + if (voice.voiceURI === voiceURI) { + return voice; + } + } + } catch (e) { + // NOP + } + return null; +} -- cgit v1.2.3 From 54d4c65854289de9d454198d7575d3db824cf0f1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 12 Oct 2019 23:04:32 -0400 Subject: Rename audioGetFromSources's createAudioObject argument to download --- ext/bg/js/audio.js | 2 +- ext/mixed/js/audio.js | 4 ++-- ext/mixed/js/display.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'ext/mixed/js') diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 9e0ae67c..1a626d42 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -163,7 +163,7 @@ async function audioInject(definition, fields, sources, optionsContext) { audioSourceDefinition = definition.expressions[0]; } - const {url} = await audioGetFromSources(audioSourceDefinition, sources, optionsContext, false); + const {url} = await audioGetFromSources(audioSourceDefinition, sources, optionsContext, true); if (url !== null) { const filename = audioBuildFilename(audioSourceDefinition); if (filename !== null) { diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js index 5e3b9164..50bd321f 100644 --- a/ext/mixed/js/audio.js +++ b/ext/mixed/js/audio.js @@ -32,7 +32,7 @@ function audioGetFromUrl(url) { }); } -async function audioGetFromSources(expression, sources, optionsContext, createAudioObject, cache=null) { +async function audioGetFromSources(expression, sources, optionsContext, download, cache=null) { const key = `${expression.expression}:${expression.reading}`; if (cache !== null && cache.hasOwnProperty(expression)) { return cache[key]; @@ -46,7 +46,7 @@ async function audioGetFromSources(expression, sources, optionsContext, createAu } try { - const audio = createAudioObject ? await audioGetFromUrl(url) : null; + const audio = download ? null : await audioGetFromUrl(url); const result = {audio, url, source}; if (cache !== null) { cache[key] = result; diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 51a3dc22..cf38d09d 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -500,7 +500,7 @@ class Display { } const sources = this.options.audio.sources; - let {audio, source} = await audioGetFromSources(expression, sources, this.getOptionsContext(), true, this.audioCache); + let {audio, source} = await audioGetFromSources(expression, sources, this.getOptionsContext(), false, this.audioCache); let info; if (audio === null) { if (this.audioFallback === null) { -- cgit v1.2.3 From 7bae3824e74461fbd5c9f66f921b05a47a40cbf4 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 12 Oct 2019 23:59:21 -0400 Subject: Add support for text-to-speech playback --- ext/bg/js/audio.js | 18 ++++++++ ext/bg/settings.html | 4 +- ext/mixed/js/audio.js | 113 ++++++++++++++++++++++++++++++++++++++++++++++-- ext/mixed/js/display.js | 1 + 4 files changed, 132 insertions(+), 4 deletions(-) (limited to 'ext/mixed/js') diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 9508abf0..3efcce46 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -86,6 +86,24 @@ const audioUrlBuilders = { throw new Error('Failed to find audio URL'); }, + 'text-to-speech': async (definition, optionsContext) => { + const options = await apiOptionsGet(optionsContext); + const voiceURI = options.audio.textToSpeechVoice; + if (!voiceURI) { + throw new Error('No voice'); + } + + return `tts:?text=${encodeURIComponent(definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; + }, + 'text-to-speech-reading': async (definition, optionsContext) => { + const options = await apiOptionsGet(optionsContext); + const voiceURI = options.audio.textToSpeechVoice; + if (!voiceURI) { + throw new Error('No voice'); + } + + return `tts:?text=${encodeURIComponent(definition.reading || definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; + }, 'custom': async (definition, optionsContext) => { const options = await apiOptionsGet(optionsContext); const customSourceUrl = options.audio.customSourceUrl; diff --git a/ext/bg/settings.html b/ext/bg/settings.html index ffa5533e..15425b44 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -319,8 +319,10 @@
diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js index 50bd321f..cf8b8d24 100644 --- a/ext/mixed/js/audio.js +++ b/ext/mixed/js/audio.js @@ -17,7 +17,90 @@ */ -function audioGetFromUrl(url) { +class TextToSpeechAudio { + constructor(text, voice) { + this.text = text; + this.voice = voice; + this._utterance = null; + this._volume = 1; + } + + get currentTime() { + return 0; + } + set currentTime(value) { + // NOP + } + + get volume() { + return this._volume; + } + set volume(value) { + this._volume = value; + if (this._utterance !== null) { + this._utterance.volume = value; + } + } + + play() { + try { + if (this._utterance === null) { + this._utterance = new SpeechSynthesisUtterance(this.text || ''); + this._utterance.lang = 'ja-JP'; + this._utterance.volume = this._volume; + this._utterance.voice = this.voice; + } + + speechSynthesis.cancel(); + speechSynthesis.speak(this._utterance); + + } catch (e) { + // NOP + } + } + + pause() { + try { + speechSynthesis.cancel(); + } catch (e) { + // NOP + } + } + + static createFromUri(ttsUri) { + const m = /^tts:[^#\?]*\?([^#]*)/.exec(ttsUri); + if (m === null) { return null; } + + const searchParameters = {}; + for (const group of m[1].split('&')) { + const sep = group.indexOf('='); + if (sep < 0) { continue; } + searchParameters[decodeURIComponent(group.substr(0, sep))] = decodeURIComponent(group.substr(sep + 1)); + } + + if (!searchParameters.text) { return null; } + + const voice = audioGetTextToSpeechVoice(searchParameters.voice); + if (voice === null) { return null; } + + return new TextToSpeechAudio(searchParameters.text, voice); + } + +} + +function audioGetFromUrl(url, download) { + const tts = TextToSpeechAudio.createFromUri(url); + if (tts !== null) { + if (download) { + throw new Error('Download not supported for text-to-speech'); + } + return Promise.resolve(tts); + } + + if (download) { + return Promise.resolve(null); + } + return new Promise((resolve, reject) => { const audio = new Audio(url); audio.addEventListener('loadeddata', () => { @@ -46,7 +129,7 @@ async function audioGetFromSources(expression, sources, optionsContext, download } try { - const audio = download ? null : await audioGetFromUrl(url); + const audio = await audioGetFromUrl(url, download); const result = {audio, url, source}; if (cache !== null) { cache[key] = result; @@ -56,7 +139,7 @@ async function audioGetFromSources(expression, sources, optionsContext, download // NOP } } - return {audio: null, source: null}; + return {audio: null, url: null, source: null}; } function audioGetTextToSpeechVoice(voiceURI) { @@ -71,3 +154,27 @@ function audioGetTextToSpeechVoice(voiceURI) { } return null; } + +function audioPrepareTextToSpeech(options) { + if ( + audioPrepareTextToSpeech.state || + !options.audio.textToSpeechVoice || + !( + options.audio.sources.includes('text-to-speech') || + options.audio.sources.includes('text-to-speech-reading') + ) + ) { + // Text-to-speech not in use. + return; + } + + // Chrome needs this value called once before it will become populated. + // The first call will return an empty list. + audioPrepareTextToSpeech.state = true; + try { + speechSynthesis.getVoices(); + } catch (e) { + // NOP + } +} +audioPrepareTextToSpeech.state = false; diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index cf38d09d..e0994f8a 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -197,6 +197,7 @@ class Display { this.options = options ? options : await apiOptionsGet(this.getOptionsContext()); this.updateTheme(this.options.general.popupTheme); this.setCustomCss(this.options.general.customPopupCss); + audioPrepareTextToSpeech(this.options); } updateTheme(themeName) { -- cgit v1.2.3 From 598cd32946c8e10e5aa3fcec26e3fc40af44bddf Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 16 Oct 2019 21:36:10 -0400 Subject: Update *show* APIs to unified showContent and setContent --- ext/bg/js/search.js | 2 +- ext/bg/js/settings-popup-preview.js | 3 +-- ext/fg/js/float.js | 19 ++----------------- ext/fg/js/frontend.js | 17 +++++++++-------- ext/fg/js/popup-proxy-host.js | 23 ++++------------------ ext/fg/js/popup-proxy.js | 16 ++-------------- ext/fg/js/popup.js | 25 +++++++----------------- ext/mixed/js/display.js | 38 +++++++++++++++++++++++++++++++------ 8 files changed, 58 insertions(+), 85 deletions(-) (limited to 'ext/mixed/js') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 68afe47e..a5a815cf 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -101,7 +101,7 @@ class DisplaySearch extends Display { this.updateSearchButton(); if (valid) { const {definitions} = await apiTermsFind(query, this.optionsContext); - this.termsShow(definitions, { + this.setContentTerms(definitions, { focus: false, sentence: null, url: window.location.href diff --git a/ext/bg/js/settings-popup-preview.js b/ext/bg/js/settings-popup-preview.js index 7ccdc7f3..b12fb726 100644 --- a/ext/bg/js/settings-popup-preview.js +++ b/ext/bg/js/settings-popup-preview.js @@ -100,8 +100,7 @@ class SettingsPopupPreview { const elementRect = textSource.getRect(); const writingMode = textSource.getWritingMode(); - const options = this.frontend.options; - this.frontend.popup.show(elementRect, writingMode, options); + this.frontend.popup.showContent(elementRect, writingMode); } onMessage(e) { diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 4b3cd848..089c9422 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -32,25 +32,12 @@ class DisplayFloat extends Display { onError(error) { if (window.yomichan_orphaned) { - this.onOrphaned(); + this.setContentOrphaned(); } else { logError(error, true); } } - onOrphaned() { - const definitions = document.querySelector('#definitions'); - const errorOrphaned = document.querySelector('#error-orphaned'); - - if (definitions !== null) { - definitions.style.setProperty('display', 'none', 'important'); - } - - if (errorOrphaned !== null) { - errorOrphaned.style.setProperty('display', 'block', 'important'); - } - } - onSearchClear() { window.parent.postMessage('popupClose', '*'); } @@ -121,10 +108,8 @@ DisplayFloat.onKeyDownHandlers = { }; DisplayFloat.messageHandlers = { - termsShow: (self, {definitions, context}) => self.termsShow(definitions, context), - kanjiShow: (self, {definitions, context}) => self.kanjiShow(definitions, context), + setContent: (self, {type, details}) => self.setContent(type, details), clearAutoPlayTimer: (self) => self.clearAutoPlayTimer(), - orphaned: (self) => self.onOrphaned(), setCustomCss: (self, {css}) => self.setCustomCss(css), initialize: (self, {options, popupInfo, url, childrenSupported}) => self.initialize(options, popupInfo, url, childrenSupported) }; diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 7ea737d7..eddbf9cc 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -332,9 +332,10 @@ class Frontend { } catch (e) { if (window.yomichan_orphaned) { if (textSource && this.options.scanning.modifier !== 'none') { - this.lastShowPromise = this.popup.showOrphaned( + this.lastShowPromise = this.popup.showContent( textSource.getRect(), - textSource.getWritingMode() + textSource.getWritingMode(), + 'orphaned' ); } } else { @@ -370,11 +371,11 @@ class Frontend { const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); const url = window.location.href; - this.lastShowPromise = this.popup.termsShow( + this.lastShowPromise = this.popup.showContent( textSource.getRect(), textSource.getWritingMode(), - definitions, - {sentence, url, focus} + 'terms', + {definitions, context: {sentence, url, focus}} ); this.textSourceLast = textSource; @@ -400,11 +401,11 @@ class Frontend { const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); const url = window.location.href; - this.lastShowPromise = this.popup.kanjiShow( + this.lastShowPromise = this.popup.showContent( textSource.getRect(), textSource.getWritingMode(), - definitions, - {sentence, url, focus} + 'kanji', + {definitions, context: {sentence, url, focus}} ); this.textSourceLast = textSource; diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index f97d44ac..dcdce604 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -39,12 +39,10 @@ class PopupProxyHost { this.apiReceiver = new FrontendApiReceiver(`popup-proxy-host#${frameId}`, { createNestedPopup: ({parentId}) => this.createNestedPopup(parentId), setOptions: ({id, options}) => this.setOptions(id, options), - showOrphaned: ({id, elementRect}) => this.showOrphaned(id, elementRect), hide: ({id, changeFocus}) => this.hide(id, changeFocus), setVisibleOverride: ({id, visible}) => this.setVisibleOverride(id, visible), containsPoint: ({id, x, y}) => this.containsPoint(id, x, y), - termsShow: ({id, elementRect, writingMode, definitions, context}) => this.termsShow(id, elementRect, writingMode, definitions, context), - kanjiShow: ({id, elementRect, writingMode, definitions, context}) => this.kanjiShow(id, elementRect, writingMode, definitions, context), + showContent: ({id, elementRect, writingMode, type, details}) => this.showContent(id, elementRect, writingMode, type, details), setCustomCss: ({id, css}) => this.setCustomCss(id, css), clearAutoPlayTimer: ({id}) => this.clearAutoPlayTimer(id) }); @@ -94,12 +92,6 @@ class PopupProxyHost { return await popup.setOptions(options); } - async showOrphaned(id, elementRect) { - const popup = this.getPopup(id); - elementRect = this.jsonRectToDOMRect(popup, elementRect); - return await popup.showOrphaned(elementRect); - } - async hide(id, changeFocus) { const popup = this.getPopup(id); return popup.hide(changeFocus); @@ -115,18 +107,11 @@ class PopupProxyHost { return await popup.containsPoint(x, y); } - async termsShow(id, elementRect, writingMode, definitions, context) { - const popup = this.getPopup(id); - elementRect = this.jsonRectToDOMRect(popup, elementRect); - if (!PopupProxyHost.popupCanShow(popup)) { return false; } - return await popup.termsShow(elementRect, writingMode, definitions, context); - } - - async kanjiShow(id, elementRect, writingMode, definitions, context) { + async showContent(id, elementRect, writingMode, type, details) { const popup = this.getPopup(id); elementRect = this.jsonRectToDOMRect(popup, elementRect); - if (!PopupProxyHost.popupCanShow(popup)) { return false; } - return await popup.kanjiShow(elementRect, writingMode, definitions, context); + if (!PopupProxyHost.popupCanShow(popup)) { return Promise.resolve(false); } + return await popup.showContent(elementRect, writingMode, type, details); } async setCustomCss(id, css) { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 6ea94b6a..53b68872 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -51,12 +51,6 @@ class PopupProxy { return await this.invokeHostApi('setOptions', {id, options}); } - async showOrphaned(elementRect) { - const id = await this.getPopupId(); - elementRect = PopupProxy.DOMRectToJson(elementRect); - return await this.invokeHostApi('showOrphaned', {id, elementRect}); - } - async hide(changeFocus) { if (this.id === null) { return; @@ -76,16 +70,10 @@ class PopupProxy { return await this.invokeHostApi('containsPoint', {id: this.id, x, y}); } - async termsShow(elementRect, writingMode, definitions, context) { - const id = await this.getPopupId(); - elementRect = PopupProxy.DOMRectToJson(elementRect); - return await this.invokeHostApi('termsShow', {id, elementRect, writingMode, definitions, context}); - } - - async kanjiShow(elementRect, writingMode, definitions, context) { + async showContent(elementRect, writingMode, type=null, details=null) { const id = await this.getPopupId(); elementRect = PopupProxy.DOMRectToJson(elementRect); - return await this.invokeHostApi('kanjiShow', {id, elementRect, writingMode, definitions, context}); + return await this.invokeHostApi('showContent', {id, elementRect, writingMode, type, details}); } async setCustomCss(css) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 2a9670fc..6c24c0ce 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -90,6 +90,13 @@ class Popup { this.updateTheme(); } + async showContent(elementRect, writingMode, type=null, details=null) { + if (!this.isInitialized()) { return; } + await this.show(elementRect, writingMode); + if (type === null) { return; } + this.invokeApi('setContent', {type, details}); + } + async show(elementRect, writingMode) { await this.inject(); @@ -218,12 +225,6 @@ class Popup { return [position, size, after]; } - async showOrphaned(elementRect, writingMode) { - if (!this.isInitialized()) { return; } - await this.show(elementRect, writingMode); - this.invokeApi('orphaned'); - } - hide(changeFocus) { if (!this.isVisible()) { return; @@ -320,18 +321,6 @@ class Popup { return false; } - async termsShow(elementRect, writingMode, definitions, context) { - if (!this.isInitialized()) { return; } - await this.show(elementRect, writingMode); - this.invokeApi('termsShow', {definitions, context}); - } - - async kanjiShow(elementRect, writingMode, definitions, context) { - if (!this.isInitialized()) { return; } - await this.show(elementRect, writingMode); - this.invokeApi('kanjiShow', {definitions, context}); - } - async setCustomCss(css) { this.invokeApi('setCustomCss', {css}); } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index e0994f8a..bab82015 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -74,8 +74,8 @@ class Display { context.source.source = this.context.source; } - const kanjiDefs = await apiKanjiFind(link.textContent, this.getOptionsContext()); - this.kanjiShow(kanjiDefs, context); + const definitions = await apiKanjiFind(link.textContent, this.getOptionsContext()); + this.setContentKanji(definitions, context); } catch (e) { this.onError(e); } @@ -122,7 +122,7 @@ class Display { context.source.source = this.context.source; } - this.termsShow(definitions, context); + this.setContentTerms(definitions, context); } catch (e) { this.onError(e); } @@ -263,7 +263,20 @@ class Display { }); } - async termsShow(definitions, context) { + setContent(type, details) { + switch (type) { + case 'terms': + return this.setContentTerms(details.definitions, details.context); + case 'kanji': + return this.setContentKanji(details.definitions, details.context); + case 'orphaned': + return this.setContentOrphaned(); + default: + return Promise.resolve(); + } + } + + async setContentTerms(definitions, context) { if (!this.isInitialized()) { return; } try { @@ -317,7 +330,7 @@ class Display { } } - async kanjiShow(definitions, context) { + async setContentKanji(definitions, context) { if (!this.isInitialized()) { return; } try { @@ -363,6 +376,19 @@ class Display { } } + async setContentOrphaned() { + const definitions = document.querySelector('#definitions'); + const errorOrphaned = document.querySelector('#error-orphaned'); + + if (definitions !== null) { + definitions.style.setProperty('display', 'none', 'important'); + } + + if (errorOrphaned !== null) { + errorOrphaned.style.setProperty('display', 'block', 'important'); + } + } + autoPlayAudio() { this.audioPlay(this.definitions[0], this.firstExpressionIndex, 0); } @@ -441,7 +467,7 @@ class Display { source: this.context.source.source }; - this.termsShow(this.context.source.definitions, context); + this.setContentTerms(this.context.source.definitions, context); } } -- cgit v1.2.3 From dbec4bffda00615fe768f66c1eb5d895aea05585 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:28:23 -0400 Subject: Make the search button reuse an open search tab if it exists --- ext/bg/js/api.js | 89 +++++++++++++++++++++++++++++++++++++++++++++++-- ext/bg/js/search.js | 17 ++++++++++ ext/bg/js/settings.js | 11 ++++-- ext/fg/js/frontend.js | 8 +++-- ext/mixed/js/display.js | 4 +-- 5 files changed, 120 insertions(+), 9 deletions(-) (limited to 'ext/mixed/js') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index da5ae4fe..a33ff94f 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -152,8 +152,22 @@ async function apiCommandExec(command) { } } apiCommandExec.handlers = { - search: () => { - chrome.tabs.create({url: chrome.extension.getURL('/bg/search.html')}); + search: async () => { + const url = chrome.extension.getURL('/bg/search.html'); + try { + const tab = await apiFindTab(1000, (url2) => ( + url2 !== null && + url2.startsWith(url) && + (url2.length === url.length || url2[url.length] === '?' || url2[url.length] === '#') + )); + if (tab !== null) { + await apiFocusTab(tab); + return; + } + } catch (e) { + // NOP + } + chrome.tabs.create({url}); }, help: () => { @@ -298,3 +312,74 @@ async function apiGetBrowser() { return 'chrome'; } } + +function apiGetTabUrl(tab) { + return new Promise((resolve) => { + chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => { + let url = null; + if (!chrome.runtime.lastError) { + url = (response !== null && typeof response === 'object' && !Array.isArray(response) ? response.url : null); + if (url !== null && typeof url !== 'string') { + url = null; + } + } + resolve({tab, url}); + }); + }); +} + +async function apiFindTab(timeout, checkUrl) { + // This function works around the need to have the "tabs" permission to access tab.url. + const tabs = await new Promise((resolve) => chrome.tabs.query({}, resolve)); + let matchPromiseResolve = null; + const matchPromise = new Promise((resolve) => { matchPromiseResolve = resolve; }); + + const checkTabUrl = ({tab, url}) => { + if (checkUrl(url, tab)) { + matchPromiseResolve(tab); + } + }; + + const promises = []; + for (const tab of tabs) { + const promise = apiGetTabUrl(tab); + promise.then(checkTabUrl); + promises.push(promise); + } + + const racePromises = [ + matchPromise, + Promise.all(promises).then(() => null) + ]; + if (typeof timeout === 'number') { + racePromises.push(new Promise((resolve) => setTimeout(() => resolve(null), timeout))); + } + + return await Promise.race(racePromises); +} + +async function apiFocusTab(tab) { + const tabWindow = await new Promise((resolve) => { + chrome.windows.get(tab.windowId, {}, (tabWindow) => { + const e = chrome.runtime.lastError; + if (e) { reject(e); } + else { resolve(tabWindow); } + }); + }); + if (!tabWindow.focused) { + await new Promise((resolve, reject) => { + chrome.windows.update(tab.windowId, {focused: true}, () => { + const e = chrome.runtime.lastError; + if (e) { reject(e); } + else { resolve(); } + }); + }); + } + await new Promise((resolve, reject) => { + chrome.tabs.update(tab.id, {active: true}, () => { + const e = chrome.runtime.lastError; + if (e) { reject(e); } + else { resolve(); } + }); + }); +} diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index a5a815cf..431478c9 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -114,6 +114,17 @@ class DisplaySearch extends Display { } } + onRuntimeMessage({action, params}, sender, callback) { + const handlers = DisplaySearch.runtimeMessageHandlers; + if (handlers.hasOwnProperty(action)) { + const handler = handlers[action]; + const result = handler(this, params); + callback(result); + } else { + return super.onRuntimeMessage({action, params}, sender, callback); + } + } + getOptionsContext() { return this.optionsContext; } @@ -188,4 +199,10 @@ class DisplaySearch extends Display { } } +DisplaySearch.runtimeMessageHandlers = { + getUrl: () => { + return {url: window.location.href}; + } +}; + window.yomichan_search = DisplaySearch.create(); diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 2c77a0ed..05a0604a 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -430,9 +430,14 @@ async function onOptionsUpdate({source}) { await formWrite(options); } -function onMessage({action, params}) { - if (action === 'optionsUpdate') { - onOptionsUpdate(params); +function onMessage({action, params}, sender, callback) { + switch (action) { + case 'optionsUpdate': + onOptionsUpdate(params); + break; + case 'getUrl': + callback({url: window.location.href}); + break; } } diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 45d24329..e854f74e 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -237,8 +237,8 @@ class Frontend { const handlers = Frontend.runtimeMessageHandlers; if (handlers.hasOwnProperty(action)) { const handler = handlers[action]; - handler(this, params); - callback(); + const result = handler(this, params); + callback(result); } } @@ -576,5 +576,9 @@ Frontend.runtimeMessageHandlers = { popupSetVisibleOverride: (self, {visible}) => { self.popup.setVisibleOverride(visible); + }, + + getUrl: () => { + return {url: window.location.href}; } }; diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index bab82015..b40228b0 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -175,8 +175,8 @@ class Display { const handlers = Display.runtimeMessageHandlers; if (handlers.hasOwnProperty(action)) { const handler = handlers[action]; - handler(this, params); - callback(); + const result = handler(this, params); + callback(result); } } -- cgit v1.2.3