From dbec4bffda00615fe768f66c1eb5d895aea05585 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:28:23 -0400 Subject: [PATCH 1/8] Make the search button reuse an open search tab if it exists --- ext/bg/js/api.js | 89 ++++++++++++++++++++++++++++++++++++++++- ext/bg/js/search.js | 17 ++++++++ ext/bg/js/settings.js | 11 +++-- ext/fg/js/frontend.js | 8 +++- ext/mixed/js/display.js | 4 +- 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); } } From d9ae34821ca5e0189248c015c2f30b88a8a6a7b2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:30:16 -0400 Subject: [PATCH 2/8] Add support for middle clicks opening new tabs on the context buttons --- ext/bg/js/api.js | 30 ++++++++++++++++-------------- ext/bg/js/backend.js | 2 +- ext/bg/js/context.js | 20 +++++++++++++++++--- ext/fg/js/api.js | 4 ++-- 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index a33ff94f..7b806e27 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -144,28 +144,30 @@ 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: async () => { + search: async (params) => { 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; + 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 } - } catch (e) { - // NOP } chrome.tabs.create({url}); }, 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..a16c8769 100644 --- a/ext/bg/js/context.js +++ b/ext/bg/js/context.js @@ -25,6 +25,20 @@ function showExtensionInfo() { node.textContent = `${manifest.name} v${manifest.version}`; } +function setupButtonEvents(selector, command) { + $(selector) + .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(); + }); +} + $(document).ready(utilAsync(() => { showExtensionInfo(); @@ -33,9 +47,9 @@ $(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')); + setupButtonEvents('.action-open-search', 'search'); + setupButtonEvents('.action-open-options', 'options'); + setupButtonEvents('.action-open-help', 'help'); const optionsContext = { depth: 0, diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js index 2294cb8b..b0746b85 100644 --- a/ext/fg/js/api.js +++ b/ext/fg/js/api.js @@ -49,8 +49,8 @@ function apiAudioGetUrl(definition, source, optionsContext) { return utilInvoke('audioGetUrl', {definition, source, optionsContext}); } -function apiCommandExec(command) { - return utilInvoke('commandExec', {command}); +function apiCommandExec(command, params) { + return utilInvoke('commandExec', {command, params}); } function apiScreenshotGet(options) { From ffb6ff932588fc2a00e2ec6ec586dae40c83ffad Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:32:49 -0400 Subject: [PATCH 3/8] Allow multiple options pages to be opened on middle click --- ext/bg/js/api.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 7b806e27..e8379d10 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -176,8 +176,14 @@ apiCommandExec.handlers = { 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 () => { From 205498ef3b84c5acfe7ee2a76e4085260bc7a3ef Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:45:29 -0400 Subject: [PATCH 4/8] Validate chrome.windows exists before usage --- ext/bg/js/api.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index e8379d10..9ae7c841 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -367,21 +367,23 @@ async function apiFindTab(timeout, checkUrl) { } 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}, () => { + if (typeof chrome.windows === 'object' && chrome.windows !== null) { + const tabWindow = await new Promise((resolve) => { + chrome.windows.get(tab.windowId, {}, (tabWindow) => { const e = chrome.runtime.lastError; if (e) { reject(e); } - else { resolve(); } + 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}, () => { From 7abc7fd0e797f11bec98924b19653de6eea46c5f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:54:58 -0400 Subject: [PATCH 5/8] Use links instead of buttons on context page --- ext/bg/context.html | 6 +++--- ext/bg/js/context.js | 18 +++++++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/ext/bg/context.html b/ext/bg/context.html index 85203053..ace40656 100644 --- a/ext/bg/context.html +++ b/ext/bg/context.html @@ -91,9 +91,9 @@
- - - + + +
diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js index a16c8769..8e1dbce6 100644 --- a/ext/bg/js/context.js +++ b/ext/bg/js/context.js @@ -25,9 +25,9 @@ function showExtensionInfo() { node.textContent = `${manifest.name} v${manifest.version}`; } -function setupButtonEvents(selector, command) { - $(selector) - .on('click', (e) => { +function setupButtonEvents(selector, command, url) { + const node = $(selector); + node.on('click', (e) => { if (e.button !== 0) { return; } apiCommandExec(command, {newTab: e.ctrlKey}); e.preventDefault(); @@ -37,6 +37,12 @@ function setupButtonEvents(selector, command) { 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(() => { @@ -47,8 +53,10 @@ $(document).ready(utilAsync(() => { document.documentElement.dataset.mode = (browser === 'firefox-mobile' ? 'full' : 'mini'); }); - setupButtonEvents('.action-open-search', 'search'); - setupButtonEvents('.action-open-options', 'options'); + 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 = { From ce92591b636c8525696b18f5dc844e12865ee6fc Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:55:32 -0400 Subject: [PATCH 6/8] Fix window focus not always working --- ext/bg/js/api.js | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 9ae7c841..94a53e67 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -367,24 +367,6 @@ async function apiFindTab(timeout, checkUrl) { } async function apiFocusTab(tab) { - if (typeof chrome.windows === 'object' && chrome.windows !== null) { - 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; @@ -392,4 +374,26 @@ async function apiFocusTab(tab) { else { resolve(); } }); }); + + if (!(typeof chrome.windows === 'object' && chrome.windows !== null)) { + // Windows not supported (e.g. on Firefox mobile) + return; + } + + 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(); } + }); + }); + } } From d7dc8ac9cddc95971f542c8e0b98e16f1f00645e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 20 Oct 2019 10:30:44 -0400 Subject: [PATCH 7/8] Add tooltip about middle click behaviour --- ext/bg/context.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bg/context.html b/ext/bg/context.html index ace40656..48fa463f 100644 --- a/ext/bg/context.html +++ b/ext/bg/context.html @@ -91,8 +91,8 @@
- - + +
From 362a1ed9e4621c47b4dca99777015b90fc90451c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 20 Oct 2019 10:58:22 -0400 Subject: [PATCH 8/8] Catch exception thrown on Edge --- ext/bg/js/api.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 94a53e67..93d9c155 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -380,20 +380,24 @@ async function apiFocusTab(tab) { return; } - 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}, () => { + try { + const tabWindow = await new Promise((resolve) => { + chrome.windows.get(tab.windowId, {}, (tabWindow) => { const e = chrome.runtime.lastError; if (e) { reject(e); } - else { resolve(); } + 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. } }