summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/bg/js/api.js89
-rw-r--r--ext/bg/js/search.js17
-rw-r--r--ext/bg/js/settings.js11
-rw-r--r--ext/fg/js/frontend.js8
-rw-r--r--ext/mixed/js/display.js4
5 files changed, 120 insertions, 9 deletions
diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js
index da5ae4fe..a33ff94f 100644
--- a/ext/bg/js/api.js
+++ b/ext/bg/js/api.js
@@ -152,8 +152,22 @@ async function apiCommandExec(command) {
}
}
apiCommandExec.handlers = {
- search: () => {
- chrome.tabs.create({url: chrome.extension.getURL('/bg/search.html')});
+ search: async () => {
+ const url = chrome.extension.getURL('/bg/search.html');
+ try {
+ const tab = await apiFindTab(1000, (url2) => (
+ url2 !== null &&
+ url2.startsWith(url) &&
+ (url2.length === url.length || url2[url.length] === '?' || url2[url.length] === '#')
+ ));
+ if (tab !== null) {
+ await apiFocusTab(tab);
+ return;
+ }
+ } catch (e) {
+ // NOP
+ }
+ chrome.tabs.create({url});
},
help: () => {
@@ -298,3 +312,74 @@ async function apiGetBrowser() {
return 'chrome';
}
}
+
+function apiGetTabUrl(tab) {
+ return new Promise((resolve) => {
+ chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => {
+ let url = null;
+ if (!chrome.runtime.lastError) {
+ url = (response !== null && typeof response === 'object' && !Array.isArray(response) ? response.url : null);
+ if (url !== null && typeof url !== 'string') {
+ url = null;
+ }
+ }
+ resolve({tab, url});
+ });
+ });
+}
+
+async function apiFindTab(timeout, checkUrl) {
+ // This function works around the need to have the "tabs" permission to access tab.url.
+ const tabs = await new Promise((resolve) => chrome.tabs.query({}, resolve));
+ let matchPromiseResolve = null;
+ const matchPromise = new Promise((resolve) => { matchPromiseResolve = resolve; });
+
+ const checkTabUrl = ({tab, url}) => {
+ if (checkUrl(url, tab)) {
+ matchPromiseResolve(tab);
+ }
+ };
+
+ const promises = [];
+ for (const tab of tabs) {
+ const promise = apiGetTabUrl(tab);
+ promise.then(checkTabUrl);
+ promises.push(promise);
+ }
+
+ 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 function apiFocusTab(tab) {
+ const tabWindow = await new Promise((resolve) => {
+ chrome.windows.get(tab.windowId, {}, (tabWindow) => {
+ const e = chrome.runtime.lastError;
+ if (e) { reject(e); }
+ else { resolve(tabWindow); }
+ });
+ });
+ if (!tabWindow.focused) {
+ await new Promise((resolve, reject) => {
+ chrome.windows.update(tab.windowId, {focused: true}, () => {
+ const e = chrome.runtime.lastError;
+ if (e) { reject(e); }
+ else { resolve(); }
+ });
+ });
+ }
+ await new Promise((resolve, reject) => {
+ chrome.tabs.update(tab.id, {active: true}, () => {
+ const e = chrome.runtime.lastError;
+ if (e) { reject(e); }
+ else { resolve(); }
+ });
+ });
+}
diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js
index a5a815cf..431478c9 100644
--- a/ext/bg/js/search.js
+++ b/ext/bg/js/search.js
@@ -114,6 +114,17 @@ class DisplaySearch extends Display {
}
}
+ onRuntimeMessage({action, params}, sender, callback) {
+ const handlers = DisplaySearch.runtimeMessageHandlers;
+ if (handlers.hasOwnProperty(action)) {
+ const handler = handlers[action];
+ const result = handler(this, params);
+ callback(result);
+ } else {
+ return super.onRuntimeMessage({action, params}, sender, callback);
+ }
+ }
+
getOptionsContext() {
return this.optionsContext;
}
@@ -188,4 +199,10 @@ class DisplaySearch extends Display {
}
}
+DisplaySearch.runtimeMessageHandlers = {
+ getUrl: () => {
+ return {url: window.location.href};
+ }
+};
+
window.yomichan_search = DisplaySearch.create();
diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js
index 2c77a0ed..05a0604a 100644
--- a/ext/bg/js/settings.js
+++ b/ext/bg/js/settings.js
@@ -430,9 +430,14 @@ async function onOptionsUpdate({source}) {
await formWrite(options);
}
-function onMessage({action, params}) {
- if (action === 'optionsUpdate') {
- onOptionsUpdate(params);
+function onMessage({action, params}, sender, callback) {
+ switch (action) {
+ case 'optionsUpdate':
+ onOptionsUpdate(params);
+ break;
+ case 'getUrl':
+ callback({url: window.location.href});
+ break;
}
}
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 45d24329..e854f74e 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -237,8 +237,8 @@ class Frontend {
const handlers = Frontend.runtimeMessageHandlers;
if (handlers.hasOwnProperty(action)) {
const handler = handlers[action];
- handler(this, params);
- callback();
+ const result = handler(this, params);
+ callback(result);
}
}
@@ -576,5 +576,9 @@ Frontend.runtimeMessageHandlers = {
popupSetVisibleOverride: (self, {visible}) => {
self.popup.setVisibleOverride(visible);
+ },
+
+ getUrl: () => {
+ return {url: window.location.href};
}
};
diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js
index bab82015..b40228b0 100644
--- a/ext/mixed/js/display.js
+++ b/ext/mixed/js/display.js
@@ -175,8 +175,8 @@ class Display {
const handlers = Display.runtimeMessageHandlers;
if (handlers.hasOwnProperty(action)) {
const handler = handlers[action];
- handler(this, params);
- callback();
+ const result = handler(this, params);
+ callback(result);
}
}