summaryrefslogtreecommitdiff
path: root/ext/bg/js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/bg/js')
-rw-r--r--ext/bg/js/api.js115
-rw-r--r--ext/bg/js/backend.js2
-rw-r--r--ext/bg/js/context.js28
-rw-r--r--ext/bg/js/search.js17
-rw-r--r--ext/bg/js/settings.js11
5 files changed, 160 insertions, 13 deletions
diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js
index da5ae4fe..93d9c155 100644
--- a/ext/bg/js/api.js
+++ b/ext/bg/js/api.js
@@ -144,24 +144,46 @@ async function apiTemplateRender(template, data, dynamic) {
}
}
-async function apiCommandExec(command) {
+async function apiCommandExec(command, params) {
const handlers = apiCommandExec.handlers;
if (handlers.hasOwnProperty(command)) {
const handler = handlers[command];
- handler();
+ handler(params);
}
}
apiCommandExec.handlers = {
- search: () => {
- chrome.tabs.create({url: chrome.extension.getURL('/bg/search.html')});
+ search: async (params) => {
+ const url = chrome.extension.getURL('/bg/search.html');
+ if (!(params && params.newTab)) {
+ 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: () => {
chrome.tabs.create({url: 'https://foosoft.net/projects/yomichan/'});
},
- options: () => {
- chrome.runtime.openOptionsPage();
+ options: (params) => {
+ if (!(params && params.newTab)) {
+ chrome.runtime.openOptionsPage();
+ } else {
+ const manifest = chrome.runtime.getManifest();
+ const url = chrome.extension.getURL(manifest.options_ui.page);
+ chrome.tabs.create({url});
+ }
},
toggle: async () => {
@@ -298,3 +320,84 @@ 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) {
+ await new Promise((resolve, reject) => {
+ chrome.tabs.update(tab.id, {active: true}, () => {
+ const e = chrome.runtime.lastError;
+ if (e) { reject(e); }
+ else { resolve(); }
+ });
+ });
+
+ if (!(typeof chrome.windows === 'object' && chrome.windows !== null)) {
+ // Windows not supported (e.g. on Firefox mobile)
+ return;
+ }
+
+ try {
+ 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(); }
+ });
+ });
+ }
+ } catch (e) {
+ // Edge throws exception for no reason here.
+ }
+}
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index 7560f39e..f29230a2 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -181,7 +181,7 @@ Backend.messageHandlers = {
definitionsAddable: ({definitions, modes, optionsContext}) => apiDefinitionsAddable(definitions, modes, optionsContext),
noteView: ({noteId}) => apiNoteView(noteId),
templateRender: ({template, data, dynamic}) => apiTemplateRender(template, data, dynamic),
- commandExec: ({command}) => apiCommandExec(command),
+ commandExec: ({command, params}) => apiCommandExec(command, params),
audioGetUrl: ({definition, source, optionsContext}) => apiAudioGetUrl(definition, source, optionsContext),
screenshotGet: ({options}, sender) => apiScreenshotGet(options, sender),
forward: ({action, params}, sender) => apiForward(action, params, sender),
diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js
index a29f7aa7..8e1dbce6 100644
--- a/ext/bg/js/context.js
+++ b/ext/bg/js/context.js
@@ -25,6 +25,26 @@ function showExtensionInfo() {
node.textContent = `${manifest.name} v${manifest.version}`;
}
+function setupButtonEvents(selector, command, url) {
+ const node = $(selector);
+ node.on('click', (e) => {
+ if (e.button !== 0) { return; }
+ apiCommandExec(command, {newTab: e.ctrlKey});
+ e.preventDefault();
+ })
+ .on('auxclick', (e) => {
+ if (e.button !== 1) { return; }
+ apiCommandExec(command, {newTab: true});
+ e.preventDefault();
+ });
+
+ if (typeof url === 'string') {
+ node.attr('href', url);
+ node.attr('target', '_blank');
+ node.attr('rel', 'noopener');
+ }
+}
+
$(document).ready(utilAsync(() => {
showExtensionInfo();
@@ -33,9 +53,11 @@ $(document).ready(utilAsync(() => {
document.documentElement.dataset.mode = (browser === 'firefox-mobile' ? 'full' : 'mini');
});
- $('.action-open-search').click(() => apiCommandExec('search'));
- $('.action-open-options').click(() => apiCommandExec('options'));
- $('.action-open-help').click(() => apiCommandExec('help'));
+ const manifest = chrome.runtime.getManifest();
+
+ setupButtonEvents('.action-open-search', 'search', chrome.extension.getURL('/bg/search.html'));
+ setupButtonEvents('.action-open-options', 'options', chrome.extension.getURL(manifest.options_ui.page));
+ setupButtonEvents('.action-open-help', 'help');
const optionsContext = {
depth: 0,
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;
}
}