From 20188d23a1f511e1f1bbc0a2921cb5e75c0e650a Mon Sep 17 00:00:00 2001 From: "YUKI \"Piro\" Hiroshi" Date: Tue, 30 Apr 2024 16:08:55 +0900 Subject: [PATCH 1/7] Show/hide tabs via WebExtensions APi --- src/background/index.esm.js | 16 +++++++++++++--- web-ext-build.yaml | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/background/index.esm.js b/src/background/index.esm.js index b367090..45daa33 100644 --- a/src/background/index.esm.js +++ b/src/background/index.esm.js @@ -49,7 +49,7 @@ const TST = tstApi({ Object.defineProperty(TST, 'debug', { get() { return debug; }, }); TST.register().catch(() => null) // may very well not be ready yet -.then(() => TST.isRegistered && TST.methods.removeTabState({ tabs: '*', state: [ ].concat(...Object.values(classes)), }).catch(onTstError)); +.then(() => TST.isRegistered && Promise.all([showAll(), TST.methods.removeTabState({ tabs: '*', state: [ ].concat(...Object.values(classes)), }).catch(onTstError),])); options.result.onAnyChange(() => TST.register().catch(notify.error)); /** @@ -159,6 +159,7 @@ async function doSearch({ // clear previous search on empty term if (!term) { + await showAll(); TST.methods.removeTabState({ tabs: '*', state: [ ].concat(...Object.values(classes).slice(0, -1/*searching*/)), }).catch(onTstError); return States.set(windowId, { tabId: -1, result: { windowId, inputTerm: '', matches: 0, cleared: true, }, }).result; } term += ''; const inputTerm = term; @@ -261,6 +262,8 @@ async function doSearch({ TST.methods.removeTabState({ tabs: (!result[state]?.size ? tabs.all : tabs.all.filter(tab => !result[state].has(tab))).map(_=>_.id), state: classes[state], }), result[state]?.size && TST.methods.addTabState({ tabs: Array.from(result[state], _=>_.id), state: classes[state], }), ]).flat(1), + browser.tabs.show(Array.from(result.matching, _=>_.id)), + browser.tabs.hide(Array.from(result.failed, _=>_.id)), (async () => { collapsed.length && (await Promise.all(collapsed.map(tab => TST.methods.expandTree({ tab: tab.id, })))); scrollTo >= 0 && (await TST.methods.scroll({ tab: scrollTo, })); @@ -273,9 +276,16 @@ async function doSearch({ } catch (error) { notify.error('Search failed!', error); return { windowId, inputTerm: '', matches: 0, failed: true, }; } } +async function startSearch() { options.search.children.searchByTabIds.value[1] && Promise.all([showAll(), TST.methods.addTabState({ tabs: '*', state: classes.searching, }).catch(onTstError),]); } +async function stopSearch() { options.search.children.searchByTabIds.value[1] && Promise.all([showAll(), TST.methods.removeTabState({ tabs: '*', state: classes.searching, }).catch(onTstError),]); } -async function startSearch() { options.search.children.searchByTabIds.value[1] && TST.methods.addTabState({ tabs: '*', state: classes.searching, }).catch(onTstError); } -async function stopSearch() { options.search.children.searchByTabIds.value[1] && TST.methods.removeTabState({ tabs: '*', state: classes.searching, }).catch(onTstError); } +async function showAll() { + const tabs = await browser.tabs.query({ + hidden: true, + windowId: this?.windowId || (await Windows.getCurrent()).id, + }); + await browser.tabs.show(Array.from(tabs, _=>_.id)); +} async function focusActiveTab({ diff --git a/web-ext-build.yaml b/web-ext-build.yaml index 92c6cfc..99cd65a 100644 --- a/web-ext-build.yaml +++ b/web-ext-build.yaml @@ -20,6 +20,7 @@ stages: - storage - notifications - tabs + - tabHide commands: globalFocusKey: description: 'Focus the TST Tab Search bar' From ccbbfa8fbcd3421e6d898661903b55d886e63722 Mon Sep 17 00:00:00 2001 From: "YUKI \"Piro\" Hiroshi" Date: Fri, 13 Sep 2024 17:08:02 +0900 Subject: [PATCH 2/7] Hide hidden tabs also --- src/background/index.esm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/background/index.esm.js b/src/background/index.esm.js index 45daa33..968155e 100644 --- a/src/background/index.esm.js +++ b/src/background/index.esm.js @@ -263,7 +263,7 @@ async function doSearch({ result[state]?.size && TST.methods.addTabState({ tabs: Array.from(result[state], _=>_.id), state: classes[state], }), ]).flat(1), browser.tabs.show(Array.from(result.matching, _=>_.id)), - browser.tabs.hide(Array.from(result.failed, _=>_.id)), + browser.tabs.hide(Array.from([...result.failed, ...(result.hidden || []),], _=>_.id)), (async () => { collapsed.length && (await Promise.all(collapsed.map(tab => TST.methods.expandTree({ tab: tab.id, })))); scrollTo >= 0 && (await TST.methods.scroll({ tab: scrollTo, })); From 403759bddf7a1d31f2024bfb737ff393ed556a72 Mon Sep 17 00:00:00 2001 From: "YUKI \"Piro\" Hiroshi" Date: Fri, 13 Sep 2024 17:19:58 +0900 Subject: [PATCH 3/7] Fix initialization error from accessing of a variable before lexical declaration --- src/background/index.esm.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/background/index.esm.js b/src/background/index.esm.js index 968155e..964c84a 100644 --- a/src/background/index.esm.js +++ b/src/background/index.esm.js @@ -12,6 +12,17 @@ import options from '../common/options.esm.js'; let debug = false; options.debug.whenChange(([ value, ]) => { debug = value; }); +// constants +const classes = { + matching: [ 'tst-search:matching', ], // applied to all tabs matching the search + hasChild: [ 'tst-search:child-matching', ], // applied to all tabs who have an "offspring" that is `matching` (potentially in addition to `matching` themselves) + hidden: /**@type{string[]}*/([ ]), // applied to `.hidden` tabs + failed: [ 'tst-search:not-matching', ], // applied to tabs that are neither `matching` nor `hasChild` + active: [ 'tst-search:active', ], // applied to a single one of the `matching` tabs + searching: [ 'tst-search:searching', ], // applied to all tabs while the search bar is focused +}; +let cache = /**@type{{ tabs: Tab[] & { byId: Map, all: Tab[], }, windowId: number, } | null}*/(null); +const queueClearCache = debounce(() => { cache = null; }, 30e3); /// setup of/with TST @@ -69,17 +80,6 @@ options.result.onAnyChange(() => TST.register().catch(notify.error)); /// extension logic -const classes = { - matching: [ 'tst-search:matching', ], // applied to all tabs matching the search - hasChild: [ 'tst-search:child-matching', ], // applied to all tabs who have an "offspring" that is `matching` (potentially in addition to `matching` themselves) - hidden: /**@type{string[]}*/([ ]), // applied to `.hidden` tabs - failed: [ 'tst-search:not-matching', ], // applied to tabs that are neither `matching` nor `hasChild` - active: [ 'tst-search:active', ], // applied to a single one of the `matching` tabs - searching: [ 'tst-search:searching', ], // applied to all tabs while the search bar is focused -}; -let cache = /**@type{{ tabs: Tab[] & { byId: Map, all: Tab[], }, windowId: number, } | null}*/(null); -const queueClearCache = debounce(() => { cache = null; }, 30e3); - /** @typedef {{ tabId: number; result: SearchResult; From 8ebb2ff3355dc97648f399eb778be318ab5cefe9 Mon Sep 17 00:00:00 2001 From: "YUKI \"Piro\" Hiroshi" Date: Fri, 13 Sep 2024 17:22:34 +0900 Subject: [PATCH 4/7] Add "+" suffix to the version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4f4ca94..642e718 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tst-search", - "version": "0.0.7", + "version": "0.0.7+", "title": "TST Tab Search", "description": "Filter Tree Style Tab's sidebar by search terms.", "author": "Niklas Gollenstede", From 88a714cc4dda2d64e962f4a288381c35b05ffe56 Mon Sep 17 00:00:00 2001 From: "YUKI \"Piro\" Hiroshi" Date: Fri, 13 Sep 2024 18:10:38 +0900 Subject: [PATCH 5/7] Respect "hide" option for unmatched tabs --- src/background/index.esm.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/background/index.esm.js b/src/background/index.esm.js index 964c84a..4df8ef7 100644 --- a/src/background/index.esm.js +++ b/src/background/index.esm.js @@ -257,13 +257,21 @@ async function doSearch({ : -1; // apply tab states + const shownTabs = [ + ...(result.matching || []), + ...(!options.result.children.miss.children.styles.children.hide.value && result.failed || []), + ]; + const hiddenTabs = [ + ...(options.result.children.miss.children.styles.children.hide.value && result.failed || []), + ...(result.hidden || []), + ]; (await Promise.all([ ...Object.keys(classes).map(state => !classes[state].length ? [ ] : [ TST.methods.removeTabState({ tabs: (!result[state]?.size ? tabs.all : tabs.all.filter(tab => !result[state].has(tab))).map(_=>_.id), state: classes[state], }), result[state]?.size && TST.methods.addTabState({ tabs: Array.from(result[state], _=>_.id), state: classes[state], }), ]).flat(1), - browser.tabs.show(Array.from(result.matching, _=>_.id)), - browser.tabs.hide(Array.from([...result.failed, ...(result.hidden || []),], _=>_.id)), + browser.tabs.show(Array.from(shownTabs, _=>_.id)), + browser.tabs.hide(Array.from(hiddenTabs, _=>_.id)), (async () => { collapsed.length && (await Promise.all(collapsed.map(tab => TST.methods.expandTree({ tab: tab.id, })))); scrollTo >= 0 && (await TST.methods.scroll({ tab: scrollTo, })); From 0df84b68dbede451eb9a34b44104cb0b1dacf01d Mon Sep 17 00:00:00 2001 From: "YUKI \"Piro\" Hiroshi" Date: Fri, 13 Sep 2024 18:25:59 +0900 Subject: [PATCH 6/7] Don7t call browser.tabs.hide() if no tab is hidden --- src/background/index.esm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/background/index.esm.js b/src/background/index.esm.js index 4df8ef7..f93d558 100644 --- a/src/background/index.esm.js +++ b/src/background/index.esm.js @@ -271,7 +271,7 @@ async function doSearch({ result[state]?.size && TST.methods.addTabState({ tabs: Array.from(result[state], _=>_.id), state: classes[state], }), ]).flat(1), browser.tabs.show(Array.from(shownTabs, _=>_.id)), - browser.tabs.hide(Array.from(hiddenTabs, _=>_.id)), + hiddenTabs.length > 0 && browser.tabs.hide(Array.from(hiddenTabs, _=>_.id)), (async () => { collapsed.length && (await Promise.all(collapsed.map(tab => TST.methods.expandTree({ tab: tab.id, })))); scrollTo >= 0 && (await TST.methods.scroll({ tab: scrollTo, })); From e5ebce0fbd964c6b995be910446dd29f46cf0a6d Mon Sep 17 00:00:00 2001 From: "YUKI \"Piro\" Hiroshi" Date: Fri, 13 Sep 2024 18:37:29 +0900 Subject: [PATCH 7/7] Never shrink tabs on TST 4.0 and later --- src/background/index.esm.js | 49 ++++++++++++++++++++--------------- src/background/tst-api.esm.js | 11 +++++--- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/background/index.esm.js b/src/background/index.esm.js index f93d558..22ef56c 100644 --- a/src/background/index.esm.js +++ b/src/background/index.esm.js @@ -28,27 +28,34 @@ const queueClearCache = debounce(() => { cache = null; }, 30e3); const onTstError = console.error.bind(console, 'TST error'); const TST = tstApi({ - getManifest() { return { - name: manifest.name, - icons: manifest.icons, - style: [ 'miss', 'child', 'hit', 'active', 'custom', ].map( - name => options.result.children[name].children.styles.children.map(style => { try { - const values = Array.isArray(style.value) ? style.value : [ style.value, ]; - return typeof style.model.extra.get === 'function' ? style.model.extra.get(...values) : values[0] ? style.model.extra.value : ''; - } catch (error) { console.error(error); return ''; } }).join('\n') - ).join('\n') - +'\n'+ options.advanced.children.hideHeader.value - +'\n'+ String.raw` - .tab.${globalThis.CSS.escape(classes.searching[0])} tab-label .label-content::before { - content: attr(data-tab-id) ": "; - } - `, - subPanel: { - title: manifest.name, - url: Runtime.getURL('src/content/embed.html'), - initialHeight: '35px', fixedHeight: '35px', - }, - }; }, + async getManifest() { + const supportsShrunkenTabs = await TST.supportsShrunkenTabs(); + return { + name: manifest.name, + icons: manifest.icons, + style: [ 'miss', 'child', 'hit', 'active', 'custom', ].map( + name => options.result.children[name].children.styles.children.map(style => { try { + if (style.name === 'hide' && !supportsShrunkenTabs) { + // TST 4.0 and later does not support shrinking of tabs due to restrictions of its virtual scrolling implementation. + return ''; + } + const values = Array.isArray(style.value) ? style.value : [ style.value, ]; + return typeof style.model.extra.get === 'function' ? style.model.extra.get(...values) : values[0] ? style.model.extra.value : ''; + } catch (error) { console.error(error); return ''; } }).join('\n') + ).join('\n') + +'\n'+ options.advanced.children.hideHeader.value + +'\n'+ String.raw` + .tab.${globalThis.CSS.escape(classes.searching[0])} tab-label .label-content::before { + content: attr(data-tab-id) ": "; + } + `, + subPanel: { + title: manifest.name, + url: Runtime.getURL('src/content/embed.html'), + initialHeight: '35px', fixedHeight: '35px', + }, + }; + }, methods: [ 'get-tree', 'scroll', diff --git a/src/background/tst-api.esm.js b/src/background/tst-api.esm.js index c110bcd..6cf85ca 100644 --- a/src/background/tst-api.esm.js +++ b/src/background/tst-api.esm.js @@ -32,7 +32,7 @@ export default function tstAPI({ getManifest, methods = [ ], events = { __proto_ const ownName = browser.runtime.getManifest().name; async function register() { - const tstManifest = { listeningTypes: [ ...Object.keys(events), 'wait-for-shutdown', ], ...getManifest(), }; + const tstManifest = { listeningTypes: [ ...Object.keys(events), 'wait-for-shutdown', ], ...(await getManifest()), }; //API.debug && console.info(ownName +': registering with TST ...', tstManifest); (await TST.registerSelf(tstManifest)); API.isRegistered = true; @@ -42,8 +42,13 @@ export default function tstAPI({ getManifest, methods = [ ], events = { __proto_ API.isRegistered = false; } + async function supportsShrunkenTabs() { + const version = await TST.getVersion(); + return version === null; + } + const TST = Object.fromEntries([ - 'register-self', 'unregister-self', ...methods, + 'register-self', 'unregister-self', 'get-version', ...methods, ].map(name => [ name.replace(/-([a-z])/g, (_, l) => l.toUpperCase()), (options) => { @@ -66,5 +71,5 @@ export default function tstAPI({ getManifest, methods = [ ], events = { __proto_ } } browser.runtime.onMessageExternal.addListener(onMessageExternal); - const API = { register, unregister, isRegistered: false, methods: TST, debug, }; return API; + const API = { register, unregister, supportsShrunkenTabs, isRegistered: false, methods: TST, debug, }; return API; }