From c9158a37b509ba58cd1e364e3d7a31cd43de5789 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 13 Oct 2019 11:46:27 -0400 Subject: Allow outer popup stylesheets to be injected --- ext/bg/js/api.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'ext/bg/js/api.js') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index f768e6f9..94a70c34 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -241,3 +241,32 @@ function apiFrameInformationGet(sender) { const frameId = sender.frameId; return Promise.resolve({frameId}); } + +function apiInjectStylesheet(css, sender) { + if (!sender.tab) { + return Promise.reject(new Error('Invalid tab')); + } + + const tabId = sender.tab.id; + const frameId = sender.frameId; + const details = { + code: css, + runAt: 'document_start', + cssOrigin: 'user', + allFrames: false + }; + if (typeof frameId === 'number') { + details.frameId = frameId; + } + + return new Promise((resolve, reject) => { + chrome.tabs.insertCSS(tabId, details, () => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(); + } + }); + }); +} -- cgit v1.2.3 From 69b28571bdf8fb4c13198223e8a5668cf490840c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 12 Oct 2019 23:09:58 -0400 Subject: audioBuildUrl => audioGetUrl and simplify --- ext/bg/js/api.js | 2 +- ext/bg/js/audio.js | 18 ++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) (limited to 'ext/bg/js/api.js') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 94a70c34..9fefadca 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -176,7 +176,7 @@ apiCommandExec.handlers = { }; async function apiAudioGetUrl(definition, source, optionsContext) { - return audioBuildUrl(definition, source, optionsContext); + return audioGetUrl(definition, source, optionsContext); } async function apiInjectScreenshot(definition, fields, screenshot) { diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 1a626d42..9508abf0 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -93,20 +93,14 @@ const audioUrlBuilders = { } }; -async function audioBuildUrl(definition, mode, optionsContext, cache={}) { - const cacheKey = `${mode}:${definition.expression}`; - if (cache.hasOwnProperty(cacheKey)) { - return Promise.resolve(cache[cacheKey]); - } - +async function audioGetUrl(definition, mode, optionsContext, download) { if (audioUrlBuilders.hasOwnProperty(mode)) { const handler = audioUrlBuilders[mode]; - return handler(definition, optionsContext).then( - (url) => { - cache[cacheKey] = url; - return url; - }, - () => null); + try { + return await handler(definition, optionsContext, download); + } catch (e) { + // NOP + } } return null; } -- cgit v1.2.3 From cb236a743081e8ea4809a8a559abf9f0f22e771c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 13 Oct 2019 17:20:55 -0400 Subject: Add apiGetEnvironmentInfo function --- ext/bg/js/api.js | 28 ++++++++++++++++++++++++++++ ext/bg/js/backend.js | 3 ++- ext/bg/js/settings.js | 19 +------------------ ext/fg/js/api.js | 4 ++++ 4 files changed, 35 insertions(+), 19 deletions(-) (limited to 'ext/bg/js/api.js') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 9fefadca..da5ae4fe 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -270,3 +270,31 @@ function apiInjectStylesheet(css, sender) { }); }); } + +async function apiGetEnvironmentInfo() { + const browser = await apiGetBrowser(); + const platform = await new Promise((resolve) => chrome.runtime.getPlatformInfo(resolve)); + return { + browser, + platform: { + os: platform.os + } + }; +} + +async function apiGetBrowser() { + if (EXTENSION_IS_BROWSER_EDGE) { + return 'edge'; + } + if (typeof browser !== 'undefined') { + try { + const info = await browser.runtime.getBrowserInfo(); + if (info.name === 'Fennec') { + return 'firefox-mobile'; + } + } catch (e) { } + return 'firefox'; + } else { + return 'chrome'; + } +} diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 32ff1bef..7560f39e 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -186,7 +186,8 @@ Backend.messageHandlers = { screenshotGet: ({options}, sender) => apiScreenshotGet(options, sender), forward: ({action, params}, sender) => apiForward(action, params, sender), frameInformationGet: (params, sender) => apiFrameInformationGet(sender), - injectStylesheet: ({css}, sender) => apiInjectStylesheet(css, sender) + injectStylesheet: ({css}, sender) => apiInjectStylesheet(css, sender), + getEnvironmentInfo: () => apiGetEnvironmentInfo() }; window.yomichan_backend = new Backend(); diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index acc47f69..ba02641b 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -819,23 +819,6 @@ async function onAnkiFieldTemplatesReset(e) { * Storage */ -async function getBrowser() { - if (EXTENSION_IS_BROWSER_EDGE) { - return 'edge'; - } - if (typeof browser !== 'undefined') { - try { - const info = await browser.runtime.getBrowserInfo(); - if (info.name === 'Fennec') { - return 'firefox-mobile'; - } - } catch (e) { } - return 'firefox'; - } else { - return 'chrome'; - } -} - function storageBytesToLabeledString(size) { const base = 1000; const labels = [' bytes', 'KB', 'MB', 'GB']; @@ -865,7 +848,7 @@ async function isStoragePeristent() { async function storageInfoInitialize() { storagePersistInitialize(); - const browser = await getBrowser(); + const {browser} = await apiGetEnvironmentInfo(); const container = document.querySelector('#storage-info'); container.setAttribute('data-browser', browser); diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js index dcfb2a09..2294cb8b 100644 --- a/ext/fg/js/api.js +++ b/ext/fg/js/api.js @@ -68,3 +68,7 @@ function apiFrameInformationGet() { function apiInjectStylesheet(css) { return utilInvoke('injectStylesheet', {css}); } + +function apiGetEnvironmentInfo() { + return utilInvoke('getEnvironmentInfo'); +} -- cgit v1.2.3 From dbec4bffda00615fe768f66c1eb5d895aea05585 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:28:23 -0400 Subject: 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(-) (limited to 'ext/bg/js/api.js') 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); } } -- cgit v1.2.3 From d9ae34821ca5e0189248c015c2f30b88a8a6a7b2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:30:16 -0400 Subject: 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(-) (limited to 'ext/bg/js/api.js') 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) { -- cgit v1.2.3 From ffb6ff932588fc2a00e2ec6ec586dae40c83ffad Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:32:49 -0400 Subject: Allow multiple options pages to be opened on middle click --- ext/bg/js/api.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'ext/bg/js/api.js') 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 () => { -- cgit v1.2.3 From 205498ef3b84c5acfe7ee2a76e4085260bc7a3ef Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:45:29 -0400 Subject: Validate chrome.windows exists before usage --- ext/bg/js/api.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'ext/bg/js/api.js') 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}, () => { -- cgit v1.2.3 From ce92591b636c8525696b18f5dc844e12865ee6fc Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 19 Oct 2019 22:55:32 -0400 Subject: Fix window focus not always working --- ext/bg/js/api.js | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) (limited to 'ext/bg/js/api.js') 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(); } + }); + }); + } } -- cgit v1.2.3 From 362a1ed9e4621c47b4dca99777015b90fc90451c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 20 Oct 2019 10:58:22 -0400 Subject: Catch exception thrown on Edge --- ext/bg/js/api.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) (limited to 'ext/bg/js/api.js') 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. } } -- cgit v1.2.3