summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2020-07-18 20:30:10 -0400
committerGitHub <noreply@github.com>2020-07-18 20:30:10 -0400
commit27e05f800132a1d3a306f511c53ef03c4ddb6c49 (patch)
treebc9130cf9d22ca9d045fd9f8afcda9fbfbeb8cf3 /ext
parentd7f78c23b50218eaa52ab4570a1e2297c52c287e (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.js142
-rw-r--r--ext/bg/js/search.js4
-rw-r--r--ext/mixed/js/api.js4
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) {