Improve search page popup detection (#1378)

* Add _getAllTabs function

* Add _findTabs

* Use _findTabs instead of _findTab

* Remove _findTab

* Refactor tab check

* Add ability to search for a popup native window

* Fix dangling comma
This commit is contained in:
toasted-nutbread 2021-02-13 12:13:01 -05:00 committed by GitHub
parent d5964ee4d4
commit edc22b98e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 101 additions and 28 deletions

View File

@ -737,7 +737,7 @@ class Backend {
url += `?${queryString}`; url += `?${queryString}`;
} }
const isTabMatch = (url2) => { const predicate = ({url: url2}) => {
if (url2 === null || !url2.startsWith(baseUrl)) { return false; } if (url2 === null || !url2.startsWith(baseUrl)) { return false; }
const parsedUrl = new URL(url2); const parsedUrl = new URL(url2);
const baseUrl2 = `${parsedUrl.origin}${parsedUrl.pathname}`; const baseUrl2 = `${parsedUrl.origin}${parsedUrl.pathname}`;
@ -746,7 +746,7 @@ class Backend {
}; };
const openInTab = async () => { const openInTab = async () => {
const tab = await this._findTab(1000, isTabMatch); const tab = await this._findTabs(1000, false, predicate, false);
if (tab !== null) { if (tab !== null) {
await this._focusTab(tab); await this._focusTab(tab);
if (queryParams.query) { if (queryParams.query) {
@ -821,16 +821,25 @@ class Backend {
} }
async _getOrCreateSearchPopup2() { async _getOrCreateSearchPopup2() {
// Reuse same tab // Use existing tab
const baseUrl = chrome.runtime.getURL('/search.html'); const baseUrl = chrome.runtime.getURL('/search.html');
const urlPredicate = (url) => url !== null && url.startsWith(baseUrl);
if (this._searchPopupTabId !== null) { if (this._searchPopupTabId !== null) {
const tab = await this._checkTabUrl(this._searchPopupTabId, (url) => url.startsWith(baseUrl)); const tab = await this._checkTabUrl(this._searchPopupTabId, urlPredicate);
if (tab !== null) { if (tab !== null) {
return {tab, created: false}; return {tab, created: false};
} }
this._searchPopupTabId = null; this._searchPopupTabId = null;
} }
// Find existing tab
const existingTabInfo = await this._findSearchPopupTab(urlPredicate);
if (existingTabInfo !== null) {
const existingTab = existingTabInfo.tab;
this._searchPopupTabId = existingTab.id;
return {tab: existingTab, created: false};
}
// chrome.windows not supported (e.g. on Firefox mobile) // chrome.windows not supported (e.g. on Firefox mobile)
if (!isObject(chrome.windows)) { if (!isObject(chrome.windows)) {
throw new Error('Window creation not supported'); throw new Error('Window creation not supported');
@ -863,6 +872,23 @@ class Backend {
return {tab, created: true}; return {tab, created: true};
} }
async _findSearchPopupTab(urlPredicate) {
const predicate = async ({url, tab}) => {
if (!urlPredicate(url)) { return false; }
try {
const mode = await this._sendMessageTabPromise(
tab.id,
{action: 'getMode', params: {}},
{frameId: 0}
);
return mode === 'popup';
} catch (e) {
return false;
}
};
return await this._findTabs(1000, false, predicate, true);
}
_getSearchPopupWindowCreateData(url, options) { _getSearchPopupWindowCreateData(url, options) {
const {popupWindow: {width, height, left, top, useLeft, useTop, windowType}} = options; const {popupWindow: {width, height, left, top, useLeft, useTop, windowType}} = options;
return { return {
@ -1333,33 +1359,74 @@ class Backend {
return null; return null;
} }
async _findTab(timeout, checkUrl) { _getAllTabs() {
// This function works around the need to have the "tabs" permission to access tab.url. return new Promise((resolve, reject) => {
const tabs = await new Promise((resolve) => chrome.tabs.query({}, resolve)); chrome.tabs.query({}, (tabs) => {
const {promise: matchPromise, resolve: matchPromiseResolve} = deferPromise(); const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
resolve(tabs);
}
});
});
}
const checkTabUrl = ({tab, url}) => { async _findTabs(timeout, multiple, predicate, predicateIsAsync) {
if (checkUrl(url, tab)) { // This function works around the need to have the "tabs" permission to access tab.url.
matchPromiseResolve(tab); const tabs = await this._getAllTabs();
let done = false;
const checkTab = async (tab, add) => {
const url = await this._getTabUrl(tab.id);
if (done) { return; }
let okay = false;
const item = {tab, url};
try {
okay = predicate(item);
if (predicateIsAsync) { okay = await okay; }
} catch (e) {
// NOP
}
if (okay && !done) {
if (add(item)) {
done = true;
}
} }
}; };
const promises = []; if (multiple) {
for (const tab of tabs) { const results = [];
const promise = this._getTabUrl(tab.id); const add = (value) => {
promise.then((url) => checkTabUrl({url, tab})); results.push(value);
promises.push(promise); return false;
};
const checkTabPromises = tabs.map((tab) => checkTab(tab, add));
await Promise.race([
Promise.all(checkTabPromises),
promiseTimeout(timeout)
]);
return results;
} else {
const {promise, resolve} = deferPromise();
let result = null;
const add = (value) => {
result = value;
resolve();
return true;
};
const checkTabPromises = tabs.map((tab) => checkTab(tab, add));
await Promise.race([
promise,
Promise.all(checkTabPromises),
promiseTimeout(timeout)
]);
resolve();
return result;
} }
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 _focusTab(tab) { async _focusTab(tab) {
@ -1835,7 +1902,8 @@ class Backend {
switch (mode) { switch (mode) {
case 'existingOrNewTab': case 'existingOrNewTab':
if (useSettingsV2) { if (useSettingsV2) {
const tab = await this._findTab(1000, (url2) => (url2 !== null && url2.startsWith(url))); const predicate = ({url: url2}) => (url2 !== null && url2.startsWith(url));
const tab = await this._findTabs(1000, false, predicate, false);
if (tab !== null) { if (tab !== null) {
await this._focusTab(tab); await this._focusTab(tab);
} else { } else {

View File

@ -139,7 +139,8 @@ class Display extends EventDispatcher {
['previousEntryDifferentDictionary', () => { this._focusEntryWithDifferentDictionary(-1, true); }] ['previousEntryDifferentDictionary', () => { this._focusEntryWithDifferentDictionary(-1, true); }]
]); ]);
this.registerMessageHandlers([ this.registerMessageHandlers([
['setMode', {async: false, handler: this._onMessageSetMode.bind(this)}] ['setMode', {async: false, handler: this._onMessageSetMode.bind(this)}],
['getMode', {async: false, handler: this._onMessageGetMode.bind(this)}]
]); ]);
this.registerDirectMessageHandlers([ this.registerDirectMessageHandlers([
['setOptionsContext', {async: false, handler: this._onMessageSetOptionsContext.bind(this)}], ['setOptionsContext', {async: false, handler: this._onMessageSetOptionsContext.bind(this)}],
@ -493,6 +494,10 @@ class Display extends EventDispatcher {
this._setMode(mode, true); this._setMode(mode, true);
} }
_onMessageGetMode() {
return this._mode;
}
_onMessageSetOptionsContext({optionsContext}) { _onMessageSetOptionsContext({optionsContext}) {
this.setOptionsContext(optionsContext); this.setOptionsContext(optionsContext);
this.searchLast(); this.searchLast();