diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2020-07-18 20:30:10 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-18 20:30:10 -0400 |
commit | 27e05f800132a1d3a306f511c53ef03c4ddb6c49 (patch) | |
tree | bc9130cf9d22ca9d045fd9f8afcda9fbfbeb8cf3 /ext | |
parent | d7f78c23b50218eaa52ab4570a1e2297c52c287e (diff) |
Reusable backend popup window (#673)
* Update _updateSearchQuery to return the promise
* Update how the clipboard search popup is opened
* Create an API function to open the search popup
* Skip animation on popup creation
* Add API function
Diffstat (limited to 'ext')
-rw-r--r-- | ext/bg/js/backend.js | 142 | ||||
-rw-r--r-- | ext/bg/js/search.js | 4 | ||||
-rw-r--r-- | ext/mixed/js/api.js | 4 |
3 files changed, 118 insertions, 32 deletions
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 854c64d6..9bdcc18a 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -70,7 +70,8 @@ class Backend { null ); - this._popupWindow = null; + this._searchPopupTabId = null; + this._searchPopupTabCreatePromise = null; this._isPrepared = false; this._prepareError = false; @@ -123,7 +124,8 @@ class Backend { ['createActionPort', {async: false, contentScript: true, handler: this._onApiCreateActionPort.bind(this)}], ['modifySettings', {async: true, contentScript: true, handler: this._onApiModifySettings.bind(this)}], ['getSettings', {async: false, contentScript: true, handler: this._onApiGetSettings.bind(this)}], - ['setAllSettings', {async: true, contentScript: false, handler: this._onApiSetAllSettings.bind(this)}] + ['setAllSettings', {async: true, contentScript: false, handler: this._onApiSetAllSettings.bind(this)}], + ['getOrCreateSearchPopup', {async: true, contentScript: true, handler: this._onApiGetOrCreateSearchPopup.bind(this)}] ]); this._messageHandlersWithProgress = new Map([ ['importDictionaryArchive', {async: true, contentScript: false, handler: this._onApiImportDictionaryArchive.bind(this)}], @@ -241,8 +243,14 @@ class Backend { // Event handlers - _onClipboardTextChange({text}) { - this._onCommandSearch({mode: 'popup', query: text}); + async _onClipboardTextChange({text}) { + try { + const {tab, created} = await this._getOrCreateSearchPopup(); + await this._focusTab(tab); + await this._updateSearchQuery(tab.id, text, !created); + } catch (e) { + // NOP + } } _onLog({level}) { @@ -789,14 +797,22 @@ class Backend { await this._onApiOptionsSave({source}); } + async _onApiGetOrCreateSearchPopup({focus=false, text=null}) { + const {tab, created} = await this._getOrCreateSearchPopup(); + if (focus === true || (focus === 'ifCreated' && created)) { + await this._focusTab(tab); + } + if (typeof text === 'string') { + await this._updateSearchQuery(tab.id, text, !created); + } + return {tabId: tab.id, windowId: tab.windowId}; + } + // Command handlers async _onCommandSearch(params) { const {mode='existingOrNewTab', query} = params || {}; - const options = this.getOptions({current: true}); - const {popupWidth, popupHeight} = options.general; - const baseUrl = chrome.runtime.getURL('/bg/search.html'); const queryParams = {mode}; if (query && query.length > 0) { queryParams.query = query; } @@ -814,7 +830,7 @@ class Backend { if (tab !== null) { await this._focusTab(tab); if (queryParams.query) { - await this._updateSearchQuery(tab.id, queryParams.query); + await this._updateSearchQuery(tab.id, queryParams.query, true); } return true; } @@ -832,25 +848,6 @@ class Backend { case 'newTab': chrome.tabs.create({url}); return; - case 'popup': - try { - // chrome.windows not supported (e.g. on Firefox mobile) - if (!isObject(chrome.windows)) { return; } - if (await openInTab()) { return; } - // if the previous popup is open in an invalid state, close it - if (this._popupWindow !== null) { - const callback = () => this._checkLastError(chrome.runtime.lastError); - chrome.windows.remove(this._popupWindow.id, callback); - } - // open new popup - this._popupWindow = await new Promise((resolve) => chrome.windows.create( - {url, width: popupWidth, height: popupHeight, type: 'popup'}, - resolve - )); - } catch (e) { - // NOP - } - return; } } @@ -878,8 +875,93 @@ class Backend { // Utilities - _updateSearchQuery(tabId, text) { - new Promise((resolve, reject) => { + _getOrCreateSearchPopup() { + if (this._searchPopupTabCreatePromise === null) { + const promise = this._getOrCreateSearchPopup2(); + this._searchPopupTabCreatePromise = promise; + promise.then(() => { this._searchPopupTabCreatePromise = null; }); + } + return this._searchPopupTabCreatePromise; + } + + async _getOrCreateSearchPopup2() { + // Reuse same tab + const baseUrl = chrome.runtime.getURL('/bg/search.html'); + if (this._searchPopupTabId !== null) { + const tabId = this._searchPopupTabId; + const tab = await new Promise((resolve) => { + chrome.tabs.get( + tabId, + (result) => { resolve(chrome.runtime.lastError ? null : result); } + ); + }); + if (tab !== null) { + const isValidTab = await new Promise((resolve) => { + chrome.tabs.sendMessage( + tabId, + {action: 'getUrl', params: {}}, + {frameId: 0}, + (response) => { + let result = false; + try { + const {url} = yomichan.getMessageResponseResult(response); + result = url.startsWith(baseUrl); + } catch (e) { + // NOP + } + resolve(result); + } + ); + }); + // windowId + if (isValidTab) { + return {tab, created: false}; + } + } + this._searchPopupTabId = null; + } + + // chrome.windows not supported (e.g. on Firefox mobile) + if (!isObject(chrome.windows)) { + throw new Error('Window creation not supported'); + } + + // Create a new window + const options = this.getOptions({current: true}); + const {popupWidth, popupHeight} = options.general; + const popupWindow = await new Promise((resolve, reject) => { + chrome.windows.create( + { + url: baseUrl, + width: popupWidth, + height: popupHeight, + type: 'popup' + }, + (result) => { + const error = chrome.runtime.lastError; + if (error) { + reject(new Error(error.message)); + } else { + resolve(result); + } + } + ); + }); + + const {tabs} = popupWindow; + if (tabs.length === 0) { + throw new Error('Created window did not contain a tab'); + } + + const tab = tabs[0]; + await this._waitUntilTabFrameIsReady(tab.id, 0, 2000); + + this._searchPopupTabId = tab.id; + return {tab, created: true}; + } + + _updateSearchQuery(tabId, text, animate) { + return new Promise((resolve, reject) => { const callback = (response) => { try { resolve(yomichan.getMessageResponseResult(response)); @@ -888,7 +970,7 @@ class Backend { } }; - const message = {action: 'updateSearchQuery', params: {text}}; + const message = {action: 'updateSearchQuery', params: {text, animate}}; chrome.tabs.sendMessage(tabId, message, callback); }); } diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 52fc19f8..b046806e 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -216,12 +216,12 @@ class DisplaySearch extends Display { this._clipboardMonitor.setPreviousText(window.getSelection().toString().trim()); } - _onExternalSearchUpdate({text}) { + _onExternalSearchUpdate({text, animate=true}) { this._setQuery(text); const url = new URL(window.location.href); url.searchParams.set('query', text); window.history.pushState(null, '', url.toString()); - this._onSearchQueryUpdated(this._query.value, true); + this._onSearchQueryUpdated(this._query.value, animate); } async _onSearchQueryUpdated(query, animate) { diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 4009c86e..47b63617 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -197,6 +197,10 @@ const api = (() => { return this._invoke('setAllSettings', {value, source}); } + getOrCreateSearchPopup(details) { + return this._invoke('getOrCreateSearchPopup', isObject(details) ? details : {}); + } + // Invoke functions with progress importDictionaryArchive(archiveContent, details, onProgress) { |