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
This commit is contained in:
parent
d7f78c23b5
commit
27e05f8001
@ -70,7 +70,8 @@ class Backend {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
this._popupWindow = null;
|
this._searchPopupTabId = null;
|
||||||
|
this._searchPopupTabCreatePromise = null;
|
||||||
|
|
||||||
this._isPrepared = false;
|
this._isPrepared = false;
|
||||||
this._prepareError = false;
|
this._prepareError = false;
|
||||||
@ -123,7 +124,8 @@ class Backend {
|
|||||||
['createActionPort', {async: false, contentScript: true, handler: this._onApiCreateActionPort.bind(this)}],
|
['createActionPort', {async: false, contentScript: true, handler: this._onApiCreateActionPort.bind(this)}],
|
||||||
['modifySettings', {async: true, contentScript: true, handler: this._onApiModifySettings.bind(this)}],
|
['modifySettings', {async: true, contentScript: true, handler: this._onApiModifySettings.bind(this)}],
|
||||||
['getSettings', {async: false, contentScript: true, handler: this._onApiGetSettings.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([
|
this._messageHandlersWithProgress = new Map([
|
||||||
['importDictionaryArchive', {async: true, contentScript: false, handler: this._onApiImportDictionaryArchive.bind(this)}],
|
['importDictionaryArchive', {async: true, contentScript: false, handler: this._onApiImportDictionaryArchive.bind(this)}],
|
||||||
@ -241,8 +243,14 @@ class Backend {
|
|||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
|
|
||||||
_onClipboardTextChange({text}) {
|
async _onClipboardTextChange({text}) {
|
||||||
this._onCommandSearch({mode: 'popup', query: text});
|
try {
|
||||||
|
const {tab, created} = await this._getOrCreateSearchPopup();
|
||||||
|
await this._focusTab(tab);
|
||||||
|
await this._updateSearchQuery(tab.id, text, !created);
|
||||||
|
} catch (e) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onLog({level}) {
|
_onLog({level}) {
|
||||||
@ -789,14 +797,22 @@ class Backend {
|
|||||||
await this._onApiOptionsSave({source});
|
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
|
// Command handlers
|
||||||
|
|
||||||
async _onCommandSearch(params) {
|
async _onCommandSearch(params) {
|
||||||
const {mode='existingOrNewTab', query} = 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 baseUrl = chrome.runtime.getURL('/bg/search.html');
|
||||||
const queryParams = {mode};
|
const queryParams = {mode};
|
||||||
if (query && query.length > 0) { queryParams.query = query; }
|
if (query && query.length > 0) { queryParams.query = query; }
|
||||||
@ -814,7 +830,7 @@ class Backend {
|
|||||||
if (tab !== null) {
|
if (tab !== null) {
|
||||||
await this._focusTab(tab);
|
await this._focusTab(tab);
|
||||||
if (queryParams.query) {
|
if (queryParams.query) {
|
||||||
await this._updateSearchQuery(tab.id, queryParams.query);
|
await this._updateSearchQuery(tab.id, queryParams.query, true);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -832,25 +848,6 @@ class Backend {
|
|||||||
case 'newTab':
|
case 'newTab':
|
||||||
chrome.tabs.create({url});
|
chrome.tabs.create({url});
|
||||||
return;
|
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
|
// Utilities
|
||||||
|
|
||||||
_updateSearchQuery(tabId, text) {
|
_getOrCreateSearchPopup() {
|
||||||
new Promise((resolve, reject) => {
|
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) => {
|
const callback = (response) => {
|
||||||
try {
|
try {
|
||||||
resolve(yomichan.getMessageResponseResult(response));
|
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);
|
chrome.tabs.sendMessage(tabId, message, callback);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -216,12 +216,12 @@ class DisplaySearch extends Display {
|
|||||||
this._clipboardMonitor.setPreviousText(window.getSelection().toString().trim());
|
this._clipboardMonitor.setPreviousText(window.getSelection().toString().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
_onExternalSearchUpdate({text}) {
|
_onExternalSearchUpdate({text, animate=true}) {
|
||||||
this._setQuery(text);
|
this._setQuery(text);
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.set('query', text);
|
url.searchParams.set('query', text);
|
||||||
window.history.pushState(null, '', url.toString());
|
window.history.pushState(null, '', url.toString());
|
||||||
this._onSearchQueryUpdated(this._query.value, true);
|
this._onSearchQueryUpdated(this._query.value, animate);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onSearchQueryUpdated(query, animate) {
|
async _onSearchQueryUpdated(query, animate) {
|
||||||
|
@ -197,6 +197,10 @@ const api = (() => {
|
|||||||
return this._invoke('setAllSettings', {value, source});
|
return this._invoke('setAllSettings', {value, source});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOrCreateSearchPopup(details) {
|
||||||
|
return this._invoke('getOrCreateSearchPopup', isObject(details) ? details : {});
|
||||||
|
}
|
||||||
|
|
||||||
// Invoke functions with progress
|
// Invoke functions with progress
|
||||||
|
|
||||||
importDictionaryArchive(archiveContent, details, onProgress) {
|
importDictionaryArchive(archiveContent, details, onProgress) {
|
||||||
|
Loading…
Reference in New Issue
Block a user