diff options
Diffstat (limited to 'ext/bg/js/search.js')
-rw-r--r-- | ext/bg/js/search.js | 144 |
1 files changed, 87 insertions, 57 deletions
diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index fe48773f..a4103ef2 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2017 Alex Yatskov <alex@foosoft.net> + * Copyright (C) 2016-2020 Alex Yatskov <alex@foosoft.net> * Author: Alex Yatskov <alex@foosoft.net> * * This program is free software: you can redistribute it and/or modify @@ -13,16 +13,9 @@ * 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 <http://www.gnu.org/licenses/>. + * along with this program. If not, see <https://www.gnu.org/licenses/>. */ - -let IS_FIREFOX = null; -(async () => { - const {browser} = await apiGetEnvironmentInfo(); - IS_FIREFOX = ['firefox', 'firefox-mobile'].includes(browser); -})(); - class DisplaySearch extends Display { constructor() { super(document.querySelector('#spinner'), document.querySelector('#content')); @@ -43,8 +36,12 @@ class DisplaySearch extends Display { this.introVisible = true; this.introAnimationTimer = null; - this.clipboardMonitorIntervalId = null; - this.clipboardPrevText = null; + this.isFirefox = false; + + this.clipboardMonitorTimerId = null; + this.clipboardMonitorTimerToken = null; + this.clipboardInterval = 250; + this.clipboardPreviousText = null; } static create() { @@ -56,6 +53,7 @@ class DisplaySearch extends Display { async prepare() { try { await this.initialize(); + this.isFirefox = await DisplaySearch._isFirefox(); if (this.search !== null) { this.search.addEventListener('click', (e) => this.onSearch(e), false); @@ -207,10 +205,14 @@ class DisplaySearch extends Display { async onSearchQueryUpdated(query, animate) { try { const details = {}; - const match = /[*\uff0a]+$/.exec(query); + const match = /^([*\uff0a]*)([\w\W]*?)([*\uff0a]*)$/.exec(query); if (match !== null) { - details.wildcard = true; - query = query.substring(0, query.length - match[0].length); + if (match[1]) { + details.wildcard = 'prefix'; + } else if (match[3]) { + details.wildcard = 'suffix'; + } + query = match[2]; } const valid = (query.length > 0); @@ -224,63 +226,81 @@ class DisplaySearch extends Display { sentence: {text: query, offset: 0}, url: window.location.href }); - this.setTitleText(query); } else { this.container.textContent = ''; } + this.setTitleText(query); window.parent.postMessage('popupClose', '*'); } catch (e) { this.onError(e); } } - onRuntimeMessage({action, params}, sender, callback) { - const handlers = DisplaySearch.runtimeMessageHandlers; - if (hasOwn(handlers, action)) { - const handler = handlers[action]; - const result = handler(this, params); - callback(result); - } else { - return super.onRuntimeMessage({action, params}, sender, callback); - } - } - initClipboardMonitor() { // ignore copy from search page window.addEventListener('copy', () => { - this.clipboardPrevText = document.getSelection().toString().trim(); + this.clipboardPreviousText = document.getSelection().toString().trim(); }); } startClipboardMonitor() { - this.clipboardMonitorIntervalId = setInterval(async () => { - let curText = null; - // TODO get rid of this and figure out why apiClipboardGet doesn't work on Firefox - if (IS_FIREFOX) { - curText = (await navigator.clipboard.readText()).trim(); - } else if (IS_FIREFOX === false) { - curText = (await apiClipboardGet()).trim(); - } - if (curText && (curText !== this.clipboardPrevText) && jpIsJapaneseText(curText)) { - if (this.isWanakanaEnabled()) { - this.setQuery(window.wanakana.toKana(curText)); - } else { - this.setQuery(curText); + // The token below is used as a unique identifier to ensure that a new clipboard monitor + // hasn't been started during the await call. The check below the await this.getClipboardText() + // call will exit early if the reference has changed. + const token = {}; + const intervalCallback = async () => { + this.clipboardMonitorTimerId = null; + + let text = await this.getClipboardText(); + if (this.clipboardMonitorTimerToken !== token) { return; } + + if ( + typeof text === 'string' && + (text = text.trim()).length > 0 && + text !== this.clipboardPreviousText + ) { + this.clipboardPreviousText = text; + if (jpIsJapaneseText(text)) { + this.setQuery(this.isWanakanaEnabled() ? window.wanakana.toKana(text) : text); + window.history.pushState(null, '', `${window.location.pathname}?query=${encodeURIComponent(text)}`); + this.onSearchQueryUpdated(this.query.value, true); } + } - const queryString = curText.length > 0 ? `?query=${encodeURIComponent(curText)}` : ''; - window.history.pushState(null, '', `${window.location.pathname}${queryString}`); - this.onSearchQueryUpdated(this.query.value, true); + this.clipboardMonitorTimerId = setTimeout(intervalCallback, this.clipboardInterval); + }; - this.clipboardPrevText = curText; - } - }, 100); + this.clipboardMonitorTimerToken = token; + + intervalCallback(); } stopClipboardMonitor() { - if (this.clipboardMonitorIntervalId) { - clearInterval(this.clipboardMonitorIntervalId); - this.clipboardMonitorIntervalId = null; + this.clipboardMonitorTimerToken = null; + if (this.clipboardMonitorTimerId !== null) { + clearTimeout(this.clipboardMonitorTimerId); + this.clipboardMonitorTimerId = null; + } + } + + async getClipboardText() { + /* + Notes: + apiClipboardGet doesn't work on Firefox because document.execCommand('paste') + results in an empty string on the web extension background page. + This may be a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1603985 + Therefore, navigator.clipboard.readText() is used on Firefox. + + navigator.clipboard.readText() can't be used in Chrome for two reasons: + * Requires page to be focused, else it rejects with an exception. + * When the page is focused, Chrome will request clipboard permission, despite already + being an extension with clipboard permissions. It effectively asks for the + non-extension permission for clipboard access. + */ + try { + return this.isFirefox ? await navigator.clipboard.readText() : await apiClipboardGet(); + } catch (e) { + return null; } } @@ -360,22 +380,32 @@ class DisplaySearch extends Display { setTitleText(text) { // Chrome limits title to 1024 characters if (text.length > 1000) { - text = text.slice(0, 1000) + '...'; + text = text.substring(0, 1000) + '...'; + } + + if (text.length === 0) { + document.title = 'Yomichan Search'; + } else { + document.title = `${text} - Yomichan Search`; } - document.title = `${text} - Yomichan Search`; } static getSearchQueryFromLocation(url) { const match = /^[^?#]*\?(?:[^&#]*&)?query=([^&#]*)/.exec(url); return match !== null ? decodeURIComponent(match[1]) : null; } -} -DisplaySearch.runtimeMessageHandlers = { - getUrl: () => { - return {url: window.location.href}; + static async _isFirefox() { + const {browser} = await apiGetEnvironmentInfo(); + switch (browser) { + case 'firefox': + case 'firefox-mobile': + return true; + default: + return false; + } } -}; +} DisplaySearch.onKeyDownIgnoreKeys = { 'ANY_MOD': [ @@ -392,4 +422,4 @@ DisplaySearch.onKeyDownIgnoreKeys = { 'Shift': [] }; -window.yomichan_search = DisplaySearch.create(); +DisplaySearch.instance = DisplaySearch.create(); |