From a7fb22c5cd8749c249c7ed9337a0ce32ea19e62f Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:24:45 +1100 Subject: [PATCH 01/12] feat: add intelligent tab grouping by similarity This feature automatically organizes workspace tabs into categories based on their content and domain. How it works: - Analyzes all unpinned tabs in the active workspace - Uses intelligent keyword matching to categorize tabs by topic (Development, Social, Shopping, Video, News, etc.) - Groups similar tabs together with visual category headers - Maintains logical ordering with priority-based sorting - New tabs automatically stay above categorized groups The grouping is triggered via a new 'Group' button in the workspace toolbar. Categories include: Development, Social Media, Shopping, Video/Streaming, News, Productivity, Finance, Gaming, and more. Localization support added for: en-US, en-GB, it --- .../en-GB/browser/browser/zen-workspaces.ftl | 50 ++-- .../en-US/browser/browser/zen-workspaces.ftl | 8 + locales/it/browser/browser/zen-workspaces.ftl | 50 ++-- .../base/content/zen-commands.inc.xhtml | 1 + src/browser/themes/shared/zen-icons/icons.css | 12 + src/zen/common/zen-sets.js | 3 + src/zen/tabs/zen-tabs/vertical-tabs.css | 46 ++++ src/zen/workspaces/ZenWorkspace.mjs | 4 + src/zen/workspaces/ZenWorkspaces.mjs | 226 ++++++++++++++++++ surfer.json | 2 +- 10 files changed, 359 insertions(+), 43 deletions(-) diff --git a/locales/en-GB/browser/browser/zen-workspaces.ftl b/locales/en-GB/browser/browser/zen-workspaces.ftl index 0439556353..95d34d0d8a 100644 --- a/locales/en-GB/browser/browser/zen-workspaces.ftl +++ b/locales/en-GB/browser/browser/zen-workspaces.ftl @@ -3,53 +3,53 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. zen-panel-ui-workspaces-text = Other workspaces -zen-panel-ui-workspaces-create = +zen-panel-ui-workspaces-create = .label = Create Space -zen-panel-ui-folder-create = +zen-panel-ui-folder-create = .label = Create Folder -zen-panel-ui-new-empty-split = +zen-panel-ui-new-empty-split = .label = New Split -zen-workspaces-panel-context-delete = +zen-workspaces-panel-context-delete = .label = Delete Workspace .accesskey = D -zen-workspaces-panel-change-name = +zen-workspaces-panel-change-name = .label = Change Name -zen-workspaces-panel-change-icon = +zen-workspaces-panel-change-icon = .label = Change Icon -zen-workspaces-panel-context-default-profile = +zen-workspaces-panel-context-default-profile = .label = Set Profile -zen-workspaces-panel-unload = +zen-workspaces-panel-unload = .label = Unload Space zen-workspaces-how-to-reorder-title = How to reorder spaces zen-workspaces-how-to-reorder-desc = Drag the space icons at the bottom of the sidebar to reorder them -zen-workspaces-change-theme = +zen-workspaces-change-theme = .label = Edit Theme -zen-workspaces-panel-context-open = +zen-workspaces-panel-context-open = .label = Open Workspace .accesskey = O -zen-workspaces-panel-context-edit = +zen-workspaces-panel-context-edit = .label = Edit Workspace .accesskey = E -context-zen-change-workspace-tab = +context-zen-change-workspace-tab = .label = Change Tab(s) To Workspace .accesskey = C -zen-bookmark-edit-panel-workspace-selector = +zen-bookmark-edit-panel-workspace-selector = .value = Other Workspace .accesskey = W -zen-panel-ui-gradient-generator-algo-complementary = +zen-panel-ui-gradient-generator-algo-complementary = .label = Complementary -zen-panel-ui-gradient-generator-algo-splitComplementary = +zen-panel-ui-gradient-generator-algo-splitComplementary = .label = Split -zen-panel-ui-gradient-generator-algo-analogous = +zen-panel-ui-gradient-generator-algo-analogous = .label = Analogous -zen-panel-ui-gradient-generator-algo-triadic = +zen-panel-ui-gradient-generator-algo-triadic = .label = Triadic -zen-panel-ui-gradient-generator-algo-floating = +zen-panel-ui-gradient-generator-algo-floating = .label = Floating zen-panel-ui-gradient-click-to-add = Click to add a colour -zen-workspace-creation-name = +zen-workspace-creation-name = .placeholder = Space Name -zen-workspaces-panel-context-reorder = +zen-workspaces-panel-context-reorder = .label = Reorder Spaces zen-workspace-creation-profile = Profile .tooltiptext = Profiles are used to separate cookies and site data between spaces. @@ -60,6 +60,14 @@ zen-workspaces-delete-workspace-body = Are you sure you want to delete { $name } # Note that the html tag MUST not be changed or removed, as it is used to better # display the shortcut in the toast notification. zen-workspaces-close-all-unpinned-tabs-toast = Tabs Closed! Use { $shortcut } to undo. -zen-workspaces-close-all-unpinned-tabs-title = +zen-workspaces-close-all-unpinned-tabs-title = .label = Clear .tooltiptext = Close all unpinned tabs + +zen-workspaces-group-tabs-toast = Created { $count } { $count -> + [one] group + *[other] groups + }! +zen-workspaces-group-tabs-title = + .label = Group + .tooltiptext = Group tabs by similarity diff --git a/locales/en-US/browser/browser/zen-workspaces.ftl b/locales/en-US/browser/browser/zen-workspaces.ftl index 67f43f7fe6..02f3a10cfa 100644 --- a/locales/en-US/browser/browser/zen-workspaces.ftl +++ b/locales/en-US/browser/browser/zen-workspaces.ftl @@ -83,3 +83,11 @@ zen-workspaces-close-all-unpinned-tabs-toast = Tabs Closed! Use { $shortcu zen-workspaces-close-all-unpinned-tabs-title = .label = Clear .tooltiptext = Close all unpinned tabs + +zen-workspaces-group-tabs-toast = Created { $count } { $count -> + [one] group + *[other] groups + }! +zen-workspaces-group-tabs-title = + .label = Group + .tooltiptext = Group tabs by similarity diff --git a/locales/it/browser/browser/zen-workspaces.ftl b/locales/it/browser/browser/zen-workspaces.ftl index 45eb15dbc9..70927fd956 100644 --- a/locales/it/browser/browser/zen-workspaces.ftl +++ b/locales/it/browser/browser/zen-workspaces.ftl @@ -3,53 +3,53 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. zen-panel-ui-workspaces-text = Spazi -zen-panel-ui-workspaces-create = +zen-panel-ui-workspaces-create = .label = Crea Spazio -zen-panel-ui-folder-create = +zen-panel-ui-folder-create = .label = Crea Cartella -zen-panel-ui-new-empty-split = +zen-panel-ui-new-empty-split = .label = Nuova Divisione -zen-workspaces-panel-context-delete = +zen-workspaces-panel-context-delete = .label = Elimina lo Spazio .accesskey = D -zen-workspaces-panel-change-name = +zen-workspaces-panel-change-name = .label = Cambia Nome -zen-workspaces-panel-change-icon = +zen-workspaces-panel-change-icon = .label = Cambia Icona -zen-workspaces-panel-context-default-profile = +zen-workspaces-panel-context-default-profile = .label = Imposta Profilo -zen-workspaces-panel-unload = +zen-workspaces-panel-unload = .label = Scarica Spazio zen-workspaces-how-to-reorder-title = Come riordinare gli spazi zen-workspaces-how-to-reorder-desc = Trascina le icone degli spazi in fondo alla barra laterale per riordinarle -zen-workspaces-change-theme = +zen-workspaces-change-theme = .label = Modifica Tema -zen-workspaces-panel-context-open = +zen-workspaces-panel-context-open = .label = Apri Spazio .accesskey = O -zen-workspaces-panel-context-edit = +zen-workspaces-panel-context-edit = .label = Modifica Spazio .accesskey = E -context-zen-change-workspace-tab = +context-zen-change-workspace-tab = .label = Metti scheda/e su uno spazio di lavoro .accesskey = C -zen-bookmark-edit-panel-workspace-selector = +zen-bookmark-edit-panel-workspace-selector = .value = Spazi .accesskey = W -zen-panel-ui-gradient-generator-algo-complementary = +zen-panel-ui-gradient-generator-algo-complementary = .label = Complementare -zen-panel-ui-gradient-generator-algo-splitComplementary = +zen-panel-ui-gradient-generator-algo-splitComplementary = .label = Dividi -zen-panel-ui-gradient-generator-algo-analogous = +zen-panel-ui-gradient-generator-algo-analogous = .label = Analogo -zen-panel-ui-gradient-generator-algo-triadic = +zen-panel-ui-gradient-generator-algo-triadic = .label = Triadico -zen-panel-ui-gradient-generator-algo-floating = +zen-panel-ui-gradient-generator-algo-floating = .label = Fluttuante zen-panel-ui-gradient-click-to-add = Clicca per aggiungere un colore -zen-workspace-creation-name = +zen-workspace-creation-name = .placeholder = Nome dello Spazio -zen-workspaces-panel-context-reorder = +zen-workspaces-panel-context-reorder = .label = Riordina Spazi zen-workspace-creation-profile = Profilo .tooltiptext = I profili vengono usati per separare i cookie e i dati dei siti tra gli spazi. @@ -60,6 +60,14 @@ zen-workspaces-delete-workspace-body = Sei sicuro di voler cancellare { $name }? # Note that the html tag MUST not be changed or removed, as it is used to better # display the shortcut in the toast notification. zen-workspaces-close-all-unpinned-tabs-toast = Scheda chiusa! Usa { $shortcut } per riaprirla. -zen-workspaces-close-all-unpinned-tabs-title = +zen-workspaces-close-all-unpinned-tabs-title = .label = Pulisci .tooltiptext = Chiudi tutte le schede non bloccate + +zen-workspaces-group-tabs-toast = { $count -> + [one] Creato { $count } gruppo + *[other] Creati { $count } gruppi + }! +zen-workspaces-group-tabs-title = + .label = Raggruppa + .tooltiptext = Raggruppa le schede per similarità diff --git a/src/browser/base/content/zen-commands.inc.xhtml b/src/browser/base/content/zen-commands.inc.xhtml index 66cfe1c5bc..5d7dbe64cc 100644 --- a/src/browser/base/content/zen-commands.inc.xhtml +++ b/src/browser/base/content/zen-commands.inc.xhtml @@ -61,4 +61,5 @@ + diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 71416c647d..57573466ae 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -393,6 +393,18 @@ list-style-image: url('arrow-down.svg') !important; } +.zen-workspace-group-tabs-button { + list-style-image: none !important; + + & .toolbarbutton-icon { + display: none !important; + } + + @media -moz-pref('zen.view.show-clear-tabs-button', false) { + display: none; + } +} + .zen-workspace-close-unpinned-tabs-button { list-style-image: url('dart-down.svg'); diff --git a/src/zen/common/zen-sets.js b/src/zen/common/zen-sets.js index b7b6c00ad6..1a1cd09602 100644 --- a/src/zen/common/zen-sets.js +++ b/src/zen/common/zen-sets.js @@ -125,6 +125,9 @@ document.addEventListener( case 'cmd_zenCloseUnpinnedTabs': gZenWorkspaces.closeAllUnpinnedTabs(); break; + case 'cmd_zenGroupTabs': + gZenWorkspaces.groupTabsBySimilarity(); + break; case 'cmd_zenUnloadWorkspace': { gZenWorkspaces.unloadWorkspace(); break; diff --git a/src/zen/tabs/zen-tabs/vertical-tabs.css b/src/zen/tabs/zen-tabs/vertical-tabs.css index 258ea20f61..2a85da36f4 100644 --- a/src/zen/tabs/zen-tabs/vertical-tabs.css +++ b/src/zen/tabs/zen-tabs/vertical-tabs.css @@ -1357,3 +1357,49 @@ .tab-group-label-container[zen-dragtarget] { z-index: 9 !important; } + +/* ========================================================================== + Tab Category Headers (for Group function) + ========================================================================== */ +.tabbrowser-tab[zen-category-first="true"] { + display: flex; + flex-direction: column; +} + +.tabbrowser-tab[zen-category-first="true"]::before { + content: attr(zen-category); + display: block; + width: 100%; + padding: 8px var(--zen-toolbox-padding); + margin-top: 12px; + margin-bottom: 4px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--sidebar-text-color); + opacity: 0.5; + user-select: none; + pointer-events: none; + animation: zenCategoryHeaderFadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; + order: -1; +} + +:root:not([zen-sidebar-expanded='true']) .tabbrowser-tab[zen-category-first="true"]::before { + display: none; +} + +:root:not([zen-sidebar-expanded='true']) .tabbrowser-tab[zen-category-first="true"] { + display: initial; +} + +@keyframes zenCategoryHeaderFadeIn { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/src/zen/workspaces/ZenWorkspace.mjs b/src/zen/workspaces/ZenWorkspace.mjs index aa92b13f7b..f0ec9e16af 100644 --- a/src/zen/workspaces/ZenWorkspace.mjs +++ b/src/zen/workspaces/ZenWorkspace.mjs @@ -14,6 +14,10 @@ + { + if (a.priority !== b.priority) { + return a.priority - b.priority; + } + return a.category.localeCompare(b.category); + }); + + // Clear any existing category attributes + for (const tab of gBrowser.tabs) { + tab.removeAttribute('zen-category'); + tab.removeAttribute('zen-category-first'); + } + + // Group tabs by category and mark them + let currentPosition = gBrowser._numPinnedTabs; + let currentCategory = null; + const categoryCount = new Set(categorizedTabs.map(ct => ct.category)).size; + + for (const { tab, category } of categorizedTabs) { + // Mark tab with category and check if it's the first in its category + tab.setAttribute('zen-category', category); + if (category !== currentCategory) { + tab.setAttribute('zen-category-first', 'true'); + currentCategory = category; + } + + // Move tab to current position + gBrowser.moveTabTo(tab, currentPosition); + currentPosition++; + } + + // Re-enable the new tab listener after grouping + setTimeout(() => { + this._groupingInProgress = false; + }, 500); + + if (categoryCount > 0) { + gZenUIManager.showToast('zen-workspaces-group-tabs-toast', { + l10nArgs: { + count: categoryCount + } + }); + } + } + + #getFirstCategorizedTabPosition() { + // Find the first tab with a category attribute + for (let i = gBrowser._numPinnedTabs; i < gBrowser.tabs.length; i++) { + const tab = gBrowser.tabs[i]; + if (tab.hasAttribute('zen-category')) { + return i; + } + } + return -1; + } + + _movingTab = null; // Flag to prevent infinite loops + _groupingInProgress = false; // Flag to disable listener during grouping + + #ensureNewTabsAboveCategories(event) { + // Skip if we're currently grouping tabs + if (this._groupingInProgress) { + return; + } + + const tab = event.target || event.detail?.tab; + if (!tab) { + return; + } + + const eventType = event.type; + + // Skip if it's a pinned tab or essential tab + if (tab.pinned || tab.hasAttribute('zen-essential')) { + return; + } + + // Skip if we're already moving this tab to prevent infinite loops + if (this._movingTab === tab) { + return; + } + + // Check immediately for TabMove, with delay for TabOpen + const checkAndMove = () => { + // Skip if tab has a category (was already grouped) + if (tab.hasAttribute('zen-category')) { + return; + } + + // Get position of first categorized tab + const firstCategoryPos = this.#getFirstCategorizedTabPosition(); + if (firstCategoryPos === -1) { + return; + } + + // Get current tab position + const currentPos = tab._tPos; + + // If tab is at or after the first categorized tab, move it before all categories + if (currentPos >= firstCategoryPos) { + this._movingTab = tab; // Set flag + gBrowser.moveTabTo(tab, firstCategoryPos); + // Clear flag after a delay + setTimeout(() => { + this._movingTab = null; + }, 300); + } + }; + + if (eventType === 'TabMove') { + // For TabMove, check immediately + checkAndMove(); + } else { + // For TabOpen, wait longer to ensure tab is fully loaded + setTimeout(checkAndMove, 500); + } + } + + #getCategoryFromKeywords(domain, title) { + // Keyword-based categorization system + // Each category has keywords and a priority (lower = higher priority) + const categories = [ + // Development & Tech + { name: 'Development', priority: 1, keywords: ['github', 'gitlab', 'bitbucket', 'stackoverflow', 'stack overflow', 'codepen', 'jsfiddle', 'replit', 'dev.to', 'hackernews', 'code', 'developer', 'programming', 'api', 'documentation', 'docs', 'npm', 'pypi', 'maven', 'react', 'vue', 'angular', 'svelte', 'nextjs', 'nuxt', 'vite', 'webpack', 'typescript', 'javascript'] }, + + // Video & Streaming + { name: 'Video', priority: 2, keywords: ['youtube', 'vimeo', 'twitch', 'dailymotion', 'video', 'watch', 'stream', 'streaming'] }, + { name: 'Streaming', priority: 2, keywords: ['netflix', 'hulu', 'disney', 'primevideo', 'hbomax', 'paramount', 'crunchyroll', 'tv', 'movie', 'series', 'film'] }, + + // Social Media + { name: 'Social', priority: 3, keywords: ['facebook', 'twitter', 'x.com', 'instagram', 'linkedin', 'reddit', 'tumblr', 'pinterest', 'tiktok', 'snapchat', 'social', 'profile', 'post', 'feed', 'community'] }, + + // Shopping & E-commerce + { name: 'Shopping', priority: 4, keywords: ['amazon', 'ebay', 'etsy', 'shopify', 'shop', 'store', 'buy', 'cart', 'checkout', 'product', 'price', 'sale', 'deal'] }, + { name: 'Fashion', priority: 4, keywords: ['nike', 'adidas', 'zara', 'hm', 'uniqlo', 'asos', 'fashion', 'clothing', 'shoes', 'apparel', 'wear', 'outfit'] }, + + // Work & Productivity + { name: 'Productivity', priority: 5, keywords: ['notion', 'trello', 'asana', 'monday', 'todoist', 'evernote', 'calendar', 'task', 'project', 'workspace', 'board', 'jira', 'confluence'] }, + { name: 'Email', priority: 5, keywords: ['gmail', 'outlook', 'yahoo', 'mail', 'inbox', 'email', 'protonmail'] }, + { name: 'Communication', priority: 5, keywords: ['slack', 'discord', 'zoom', 'teams', 'meet', 'skype', 'telegram', 'whatsapp', 'chat', 'message', 'call', 'meeting'] }, + { name: 'Cloud', priority: 5, keywords: ['drive', 'dropbox', 'onedrive', 'icloud', 'cloud', 'storage', 'file', 'sync'] }, + + // News & Media + { name: 'News', priority: 6, keywords: ['news', 'cnn', 'bbc', 'nytimes', 'guardian', 'reuters', 'bloomberg', 'wsj', 'forbes', 'article', 'breaking', 'headline'] }, + { name: 'Articles', priority: 6, keywords: ['medium', 'substack', 'blog', 'article', 'read', 'story', 'publication', 'writer'] }, + { name: 'Reference', priority: 6, keywords: ['wikipedia', 'wiki', 'encyclopedia', 'dictionary', 'reference', 'learn', 'education'] }, + + // Entertainment + { name: 'Music', priority: 7, keywords: ['spotify', 'soundcloud', 'applemusic', 'music', 'song', 'artist', 'album', 'playlist', 'audio', 'listen'] }, + { name: 'Gaming', priority: 7, keywords: ['steam', 'epicgames', 'game', 'gaming', 'play', 'gamer', 'xbox', 'playstation', 'nintendo'] }, + + // Automotive & Transportation + { name: 'Automotive', priority: 8, keywords: ['tesla', 'car', 'auto', 'vehicle', 'automotive', 'electric car', 'electric vehicle'] }, + { name: 'Travel', priority: 8, keywords: ['booking', 'airbnb', 'expedia', 'tripadvisor', 'travel', 'hotel', 'flight', 'trip', 'vacation'] }, + + // Finance + { name: 'Finance', priority: 9, keywords: ['bank', 'paypal', 'stripe', 'venmo', 'finance', 'payment', 'transaction', 'money', 'crypto', 'bitcoin', 'invest', 'trading'] }, + + // Local Development - must be checked first with exact matches + { name: 'Development (Local)', priority: 0, keywords: ['localhost', '127.0.0.1', '0.0.0.0'] }, + ]; + + const searchText = `${domain} ${title}`.toLowerCase(); + + // Check for exact localhost/IP matches first + if (domain === 'localhost' || domain === '127.0.0.1' || domain === '0.0.0.0' || + domain.includes('localhost') || domain.startsWith('192.168.') || domain.startsWith('10.')) { + return { category: 'Development (Local)', priority: 0 }; + } + + // Find the first matching category based on keywords + for (const category of categories) { + for (const keyword of category.keywords) { + if (searchText.includes(keyword.toLowerCase())) { + return { category: category.name, priority: category.priority }; + } + } + } + + // If no match, return "Other" + return { category: 'Other', priority: 999 }; + } + async contextDeleteWorkspace() { const workspaceId = this.#contextMenuData?.workspaceId || this.activeWorkspace; const [title, body] = await document.l10n.formatValues([ diff --git a/surfer.json b/surfer.json index e88c81e65f..49ef57c5ca 100644 --- a/surfer.json +++ b/surfer.json @@ -53,4 +53,4 @@ "licenseType": "MPL-2.0" }, "updateHostname": "updates.zen-browser.app" -} +} \ No newline at end of file From 9f05d11313a397f3a9192a33691c17d97aa3f4c2 Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:20:49 +1100 Subject: [PATCH 02/12] feat: AI-powered tab grouping with local ML models Uses Firefox's smart-tab-embedding and smart-tab-topic models to cluster tabs by semantic similarity and generate category labels based on URLs, titles, and browsing history. --- src/zen/workspaces/ZenWorkspaces.mjs | 597 ++++++++++++++++++++++----- 1 file changed, 501 insertions(+), 96 deletions(-) diff --git a/src/zen/workspaces/ZenWorkspaces.mjs b/src/zen/workspaces/ZenWorkspaces.mjs index 3829ed1957..d00ea1eb7b 100644 --- a/src/zen/workspaces/ZenWorkspaces.mjs +++ b/src/zen/workspaces/ZenWorkspaces.mjs @@ -2738,11 +2738,7 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { async groupTabsBySimilarity() { const workspaceId = this.activeWorkspace; - - // Disable the new tab listener during grouping this._groupingInProgress = true; - - // Get all unpinned tabs in the workspace const unpinnedTabs = this.#unpinnedTabsInWorkspace(workspaceId); if (unpinnedTabs.length < 2) { @@ -2750,63 +2746,106 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { return; } - // Categorize tabs using intelligent keyword matching - const categorizedTabs = []; + const tabData = []; + const TAB_URLS_TO_EXCLUDE = [ + 'about:newtab', + 'about:home', + 'about:privatebrowsing', + 'chrome://browser/content/blanktab.html', + 'about:firefoxview', + ]; for (const tab of unpinnedTabs) { try { const uri = tab.linkedBrowser?.currentURI; - if (!uri || uri.scheme === 'about' || uri.scheme === 'chrome') { - categorizedTabs.push({ tab, category: 'Other', priority: 999 }); + if (!uri) { continue; } - const domain = uri.host.replace(/^(www\.|m\.|mobile\.)/, ''); - const title = tab.label || ''; + const url = uri.spec; + if (TAB_URLS_TO_EXCLUDE.includes(url)) { + continue; + } - // Get category using intelligent keyword matching - const { category, priority } = this.#getCategoryFromKeywords(domain, title); + if (uri.scheme === 'about' || uri.scheme === 'chrome') { + continue; + } - categorizedTabs.push({ tab, category, priority }); + const domain = uri.host.replace(/^(www\.|m\.|mobile\.)/, ''); + const title = this.#preprocessText(tab.label || ''); + + let openerInfo = null; + if (tab.openerTab && !tab.openerTab.closing) { + try { + const openerUri = tab.openerTab.linkedBrowser?.currentURI; + if (openerUri) { + const openerDomain = openerUri.host.replace(/^(www\.|m\.|mobile\.)/, ''); + const openerTitle = this.#preprocessText(tab.openerTab.label || ''); + openerInfo = { + domain: openerDomain, + title: openerTitle, + url: openerUri.spec, + }; + } + } catch (e) {} + } + + tabData.push({ + tab, + domain, + title, + url, + opener: openerInfo, + combinedText: `${domain} ${title}`, + }); } catch (error) { console.error('Error processing tab for grouping:', error); - categorizedTabs.push({ tab, category: 'Other', priority: 999 }); } } - // Sort tabs: first by category priority, then alphabetically by category name - categorizedTabs.sort((a, b) => { - if (a.priority !== b.priority) { - return a.priority - b.priority; - } - return a.category.localeCompare(b.category); - }); + if (tabData.length < 2) { + this._groupingInProgress = false; + return; + } + + const clusters = await this.#clusterTabsBySimilarity(tabData); - // Clear any existing category attributes for (const tab of gBrowser.tabs) { tab.removeAttribute('zen-category'); tab.removeAttribute('zen-category-first'); } - // Group tabs by category and mark them + clusters.sort((a, b) => { + if (b.tabs.length !== a.tabs.length) { + return b.tabs.length - a.tabs.length; + } + return a.label.localeCompare(b.label); + }); + let currentPosition = gBrowser._numPinnedTabs; - let currentCategory = null; - const categoryCount = new Set(categorizedTabs.map(ct => ct.category)).size; + let categoryCount = 0; - for (const { tab, category } of categorizedTabs) { - // Mark tab with category and check if it's the first in its category - tab.setAttribute('zen-category', category); - if (category !== currentCategory) { - tab.setAttribute('zen-category-first', 'true'); - currentCategory = category; + for (const cluster of clusters) { + if (cluster.tabs.length === 0) { + continue; } - // Move tab to current position - gBrowser.moveTabTo(tab, currentPosition); - currentPosition++; + categoryCount++; + const category = cluster.label; + let isFirst = true; + + for (const tab of cluster.tabs) { + tab.setAttribute('zen-category', category); + if (isFirst) { + tab.setAttribute('zen-category-first', 'true'); + isFirst = false; + } + + gBrowser.moveTabTo(tab, currentPosition); + currentPosition++; + } } - // Re-enable the new tab listener after grouping setTimeout(() => { this._groupingInProgress = false; }, 500); @@ -2821,7 +2860,6 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { } #getFirstCategorizedTabPosition() { - // Find the first tab with a category attribute for (let i = gBrowser._numPinnedTabs; i < gBrowser.tabs.length; i++) { const tab = gBrowser.tabs[i]; if (tab.hasAttribute('zen-category')) { @@ -2831,11 +2869,10 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { return -1; } - _movingTab = null; // Flag to prevent infinite loops - _groupingInProgress = false; // Flag to disable listener during grouping + _movingTab = null; + _groupingInProgress = false; #ensureNewTabsAboveCategories(event) { - // Skip if we're currently grouping tabs if (this._groupingInProgress) { return; } @@ -2847,37 +2884,29 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { const eventType = event.type; - // Skip if it's a pinned tab or essential tab if (tab.pinned || tab.hasAttribute('zen-essential')) { return; } - // Skip if we're already moving this tab to prevent infinite loops if (this._movingTab === tab) { return; } - // Check immediately for TabMove, with delay for TabOpen const checkAndMove = () => { - // Skip if tab has a category (was already grouped) if (tab.hasAttribute('zen-category')) { return; } - // Get position of first categorized tab const firstCategoryPos = this.#getFirstCategorizedTabPosition(); if (firstCategoryPos === -1) { return; } - // Get current tab position const currentPos = tab._tPos; - // If tab is at or after the first categorized tab, move it before all categories if (currentPos >= firstCategoryPos) { - this._movingTab = tab; // Set flag + this._movingTab = tab; gBrowser.moveTabTo(tab, firstCategoryPos); - // Clear flag after a delay setTimeout(() => { this._movingTab = null; }, 300); @@ -2885,77 +2914,453 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { }; if (eventType === 'TabMove') { - // For TabMove, check immediately checkAndMove(); } else { - // For TabOpen, wait longer to ensure tab is fully loaded setTimeout(checkAndMove, 500); } } - #getCategoryFromKeywords(domain, title) { - // Keyword-based categorization system - // Each category has keywords and a priority (lower = higher priority) - const categories = [ - // Development & Tech - { name: 'Development', priority: 1, keywords: ['github', 'gitlab', 'bitbucket', 'stackoverflow', 'stack overflow', 'codepen', 'jsfiddle', 'replit', 'dev.to', 'hackernews', 'code', 'developer', 'programming', 'api', 'documentation', 'docs', 'npm', 'pypi', 'maven', 'react', 'vue', 'angular', 'svelte', 'nextjs', 'nuxt', 'vite', 'webpack', 'typescript', 'javascript'] }, + /** + * Preprocess text by removing trailing domain information + * Inspired by Firefox's SmartTabGrouping.preprocessText + * + * @param {string} text - The text to preprocess + * @returns {string} - The preprocessed text + */ + #preprocessText(text) { + if (!text) { + return ''; + } - // Video & Streaming - { name: 'Video', priority: 2, keywords: ['youtube', 'vimeo', 'twitch', 'dailymotion', 'video', 'watch', 'stream', 'streaming'] }, - { name: 'Streaming', priority: 2, keywords: ['netflix', 'hulu', 'disney', 'primevideo', 'hbomax', 'paramount', 'crunchyroll', 'tv', 'movie', 'series', 'film'] }, + const delimiters = /(?<=\s)[|–-]+(?=\s)/; + const splitText = text.split(delimiters); + const hasEnoughInfo = + !!splitText.length && splitText.slice(0, -1).join(' ').length > 5; + const isPotentialDomainInfo = + splitText.length > 1 && splitText[splitText.length - 1].length < 20; - // Social Media - { name: 'Social', priority: 3, keywords: ['facebook', 'twitter', 'x.com', 'instagram', 'linkedin', 'reddit', 'tumblr', 'pinterest', 'tiktok', 'snapchat', 'social', 'profile', 'post', 'feed', 'community'] }, + if (hasEnoughInfo && isPotentialDomainInfo) { + return splitText + .slice(0, -1) + .map(t => t.trim()) + .filter(Boolean) + .join(' ') + .trim(); + } - // Shopping & E-commerce - { name: 'Shopping', priority: 4, keywords: ['amazon', 'ebay', 'etsy', 'shopify', 'shop', 'store', 'buy', 'cart', 'checkout', 'product', 'price', 'sale', 'deal'] }, - { name: 'Fashion', priority: 4, keywords: ['nike', 'adidas', 'zara', 'hm', 'uniqlo', 'asos', 'fashion', 'clothing', 'shoes', 'apparel', 'wear', 'outfit'] }, + return text.trim(); + } - // Work & Productivity - { name: 'Productivity', priority: 5, keywords: ['notion', 'trello', 'asana', 'monday', 'todoist', 'evernote', 'calendar', 'task', 'project', 'workspace', 'board', 'jira', 'confluence'] }, - { name: 'Email', priority: 5, keywords: ['gmail', 'outlook', 'yahoo', 'mail', 'inbox', 'email', 'protonmail'] }, - { name: 'Communication', priority: 5, keywords: ['slack', 'discord', 'zoom', 'teams', 'meet', 'skype', 'telegram', 'whatsapp', 'chat', 'message', 'call', 'meeting'] }, - { name: 'Cloud', priority: 5, keywords: ['drive', 'dropbox', 'onedrive', 'icloud', 'cloud', 'storage', 'file', 'sync'] }, + /** + * Generate AI embeddings for all tabs using Firefox's ML + * + * @param {Array} tabData - Array of tab data + * @returns {Promise} - Array of embeddings or null if failed + */ + async #generateTabEmbeddings(tabData) { + if (!Services.prefs.getBoolPref('browser.ml.enable', false)) { + return null; + } - // News & Media - { name: 'News', priority: 6, keywords: ['news', 'cnn', 'bbc', 'nytimes', 'guardian', 'reuters', 'bloomberg', 'wsj', 'forbes', 'article', 'breaking', 'headline'] }, - { name: 'Articles', priority: 6, keywords: ['medium', 'substack', 'blog', 'article', 'read', 'story', 'publication', 'writer'] }, - { name: 'Reference', priority: 6, keywords: ['wikipedia', 'wiki', 'encyclopedia', 'dictionary', 'reference', 'learn', 'education'] }, + try { + const { createEngine } = ChromeUtils.importESModule( + 'chrome://global/content/ml/EngineProcess.sys.mjs' + ); - // Entertainment - { name: 'Music', priority: 7, keywords: ['spotify', 'soundcloud', 'applemusic', 'music', 'song', 'artist', 'album', 'playlist', 'audio', 'listen'] }, - { name: 'Gaming', priority: 7, keywords: ['steam', 'epicgames', 'game', 'gaming', 'play', 'gamer', 'xbox', 'playstation', 'nintendo'] }, + const engine = await createEngine({ + taskName: 'feature-extraction', + modelId: 'Mozilla/smart-tab-embedding', + modelHub: 'huggingface', + engineId: 'embedding-engine', + }); - // Automotive & Transportation - { name: 'Automotive', priority: 8, keywords: ['tesla', 'car', 'auto', 'vehicle', 'automotive', 'electric car', 'electric vehicle'] }, - { name: 'Travel', priority: 8, keywords: ['booking', 'airbnb', 'expedia', 'tripadvisor', 'travel', 'hotel', 'flight', 'trip', 'vacation'] }, + console.log('🤖 Generating AI embeddings for tabs...'); + + const embeddings = await Promise.all( + tabData.map(async (data, index) => { + try { + const text = `${data.title} ${data.domain}`; + const result = await engine.run({ args: [text] }); + + let embedding; + if (result?.[0]?.embedding && Array.isArray(result[0].embedding)) { + embedding = result[0].embedding; + } else if (result?.[0] && Array.isArray(result[0])) { + embedding = result[0]; + } else if (Array.isArray(result)) { + embedding = result; + } else { + return null; + } - // Finance - { name: 'Finance', priority: 9, keywords: ['bank', 'paypal', 'stripe', 'venmo', 'finance', 'payment', 'transaction', 'money', 'crypto', 'bitcoin', 'invest', 'trading'] }, + if (Array.isArray(embedding) && embedding.length > 0) { + let pooled; + if (typeof embedding[0] === 'number') { + pooled = embedding; + } else if (Array.isArray(embedding[0])) { + const len = embedding[0].length; + pooled = new Array(len).fill(0); + for (const arr of embedding) { + for (let i = 0; i < len; i++) { + pooled[i] += arr[i]; + } + } + for (let i = 0; i < len; i++) { + pooled[i] /= embedding.length; + } + } else { + return null; + } - // Local Development - must be checked first with exact matches - { name: 'Development (Local)', priority: 0, keywords: ['localhost', '127.0.0.1', '0.0.0.0'] }, - ]; + const norm = Math.sqrt(pooled.reduce((sum, v) => sum + v * v, 0)); + return norm === 0 ? pooled : pooled.map(v => v / norm); + } + return null; + } catch (error) { + console.warn(`Failed to generate embedding for tab ${index}:`, error); + return null; + } + }) + ); + + const validCount = embeddings.filter(e => e !== null).length; + console.log(`✓ Generated ${validCount}/${tabData.length} embeddings`); + + return embeddings; + } catch (error) { + console.warn('Failed to generate embeddings, falling back to text similarity:', error); + return null; + } + } + + /** + * Calculate cosine similarity between two embedding vectors + * + * @param {Array} vec1 - First embedding vector + * @param {Array} vec2 - Second embedding vector + * @returns {number} - Similarity score between -1 and 1 + */ + #cosineSimilarity(vec1, vec2) { + if (!vec1 || !vec2 || vec1.length !== vec2.length) { + return 0; + } + + let dotProduct = 0; + let norm1 = 0; + let norm2 = 0; + + for (let i = 0; i < vec1.length; i++) { + dotProduct += vec1[i] * vec2[i]; + norm1 += vec1[i] * vec1[i]; + norm2 += vec2[i] * vec2[i]; + } + + const denominator = Math.sqrt(norm1) * Math.sqrt(norm2); + return denominator === 0 ? 0 : dotProduct / denominator; + } + + /** + * Calculate text similarity using Jaccard similarity + * + * @param {string} text1 - First text + * @param {string} text2 - Second text + * @returns {number} - Similarity score between 0 and 1 + */ + #calculateTextSimilarity(text1, text2) { + const words1 = new Set( + text1 + .toLowerCase() + .split(/\s+/) + .filter(w => w.length > 2) + ); + const words2 = new Set( + text2 + .toLowerCase() + .split(/\s+/) + .filter(w => w.length > 2) + ); + + if (words1.size === 0 && words2.size === 0) { + return 1.0; + } + if (words1.size === 0 || words2.size === 0) { + return 0.0; + } - const searchText = `${domain} ${title}`.toLowerCase(); + const intersection = new Set([...words1].filter(w => words2.has(w))); + const union = new Set([...words1, ...words2]); + return intersection.size / union.size; + } - // Check for exact localhost/IP matches first - if (domain === 'localhost' || domain === '127.0.0.1' || domain === '0.0.0.0' || - domain.includes('localhost') || domain.startsWith('192.168.') || domain.startsWith('10.')) { - return { category: 'Development (Local)', priority: 0 }; + /** + * Cluster tabs dynamically using AI embeddings + similarity analysis + * + * @param {Array} tabData - Array of tab data objects + * @returns {Promise} - Array of clusters with labels and tabs + */ + async #clusterTabsBySimilarity(tabData) { + const SIMILARITY_THRESHOLD = 0.22; + + console.log('\n========== Smart Tab Clustering (AI-Enhanced) =========='); + console.log(`Total tabs to cluster: ${tabData.length}`); + console.log(`Similarity threshold: ${SIMILARITY_THRESHOLD}`); + + const embeddings = await this.#generateTabEmbeddings(tabData); + const useAI = embeddings !== null; + console.log(`Using ${useAI ? 'AI embeddings' : 'text similarity'} for clustering`); + + const similarities = []; + for (let i = 0; i < tabData.length; i++) { + similarities[i] = []; + for (let j = 0; j < tabData.length; j++) { + if (i === j) { + similarities[i][j] = 1.0; + } else if (useAI && embeddings[i] && embeddings[j]) { + similarities[i][j] = this.#cosineSimilarity(embeddings[i], embeddings[j]); + } else { + similarities[i][j] = this.#calculateTextSimilarity( + tabData[i].combinedText, + tabData[j].combinedText + ); + } + } } - // Find the first matching category based on keywords - for (const category of categories) { - for (const keyword of category.keywords) { - if (searchText.includes(keyword.toLowerCase())) { - return { category: category.name, priority: category.priority }; + const clusters = []; + const used = new Array(tabData.length).fill(false); + + for (let i = 0; i < tabData.length; i++) { + if (used[i]) continue; + + const clusterIndices = [i]; + const clusterTabs = [tabData[i].tab]; + used[i] = true; + + for (let j = 0; j < tabData.length; j++) { + if (i !== j && !used[j] && similarities[i][j] > SIMILARITY_THRESHOLD) { + clusterIndices.push(j); + clusterTabs.push(tabData[j].tab); + used[j] = true; } } + + clusters.push({ + indices: new Set(clusterIndices), + tabs: clusterTabs, + }); + } + + for (const cluster of clusters) { + cluster.label = await this.#generateDynamicClusterLabel( + Array.from(cluster.indices).map(i => tabData[i]) + ); + } + + console.log(`✓ Created ${clusters.length} cluster${clusters.length !== 1 ? 's' : ''}`); + + return clusters; + } + + /** + * Generate AI-powered label from cluster content using local ML + * + * @param {Array} clusterData - Array of tab data in the cluster + * @returns {Promise} - Generated label + */ + async #generateDynamicClusterLabel(clusterData) { + if (clusterData.length === 1) { + return this.#generateClusterLabel(clusterData[0].domain); + } + + try { + const label = await this.#generateAIClusterLabel(clusterData); + if (label) { + return label; + } + } catch (error) { + console.warn('AI label generation failed, using fallback:', error); + } + + const wordFreq = new Map(); + + for (const data of clusterData) { + const text = `${data.domain} ${data.title}`.toLowerCase(); + const words = text.split(/\s+/).filter(w => w.length > 3); + + for (const word of words) { + wordFreq.set(word, (wordFreq.get(word) || 0) + 1); + } + } + + let bestWord = null; + let bestCount = 0; + + for (const [word, count] of wordFreq.entries()) { + if (count > 1 && count > bestCount) { + bestCount = count; + bestWord = word; + } + } + + if (bestWord) { + return bestWord.charAt(0).toUpperCase() + bestWord.slice(1); + } + + return this.#generateClusterLabel(clusterData[0].domain); + } + + /** + * Generate cluster label using local AI/ML + * + * @param {Array} clusterData - Array of tab data in the cluster + * @returns {Promise} - AI generated label or null + */ + async #generateAIClusterLabel(clusterData) { + if (!Services.prefs.getBoolPref('browser.ml.enable', false)) { + return null; + } + + try { + const { createEngine } = ChromeUtils.importESModule( + 'chrome://global/content/ml/EngineProcess.sys.mjs' + ); + + const tabDescriptions = clusterData + .map((data, idx) => { + let desc = `${idx + 1}. ${data.url}\n Title: ${data.title}`; + if (data.opener) { + desc += `\n ↳ Opened from: ${data.opener.domain} (${data.opener.title})`; + } + return desc; + }) + .join('\n\n'); + + const titles = clusterData.map(data => data.title).filter(t => t.length > 0); + const keywords = this.#extractKeywords(titles); + const domains = [...new Set(clusterData.map(d => d.domain))].join(', '); + + const prompt = `You are an expert organizer who creates concise, descriptive category names. + +I have a group of browser tabs that belong together. Please create a short, descriptive category name (1-3 words) for this group. + +Tabs in this group: +${tabDescriptions} + +Common domains: ${domains} +${keywords.length > 0 ? `Keywords in common: ${keywords.join(', ')}` : ''} + +Instructions: +- Look at the FULL URLs to understand the context (e.g., /shop/, /cars/, /docs/) +- Use the browsing history (↳ Opened from) to understand the user's intent +- Choose a GENERAL category name, not a specific brand name +- If tabs contain multiple brands (Nike, Adidas), use the category (e.g., "Sportswear" or "Athletic Brands") +- If tabs are car brands/manufacturers (Audi, Mercedes, Tesla), use "Automotive" or "Cars" +- If tabs are video platforms (YouTube, Vimeo), use "Video Platforms" or "Videos" +- If tabs are developer tools (GitHub, Jira, GitLab), use "Development" or "Dev Tools" +- Use the common theme or purpose, not individual site names +- Keep it 1-3 words maximum +- Capitalize properly + +Category name:`; + + const engine = await createEngine({ + taskName: 'text2text-generation', + modelId: 'Mozilla/smart-tab-topic', + modelHub: 'huggingface', + engineId: 'group-namer', + }); + + const aiResult = await engine.run({ + args: [prompt], + options: { + max_new_tokens: 10, + temperature: 0.5, + }, + }); + + let name = (aiResult[0]?.generated_text || '') + .split('\n')[0] + .trim(); + + if (!name || /none|adult content/i.test(name)) { + return null; + } + + name = this.#toTitleCase(name); + name = name + .replace(/^['"`]+|['"`]+$/g, '') + .replace(/[.?!,:;]+$/, '') + .replace(/^(category name:|name:)\s*/i, '') + .trim() + .slice(0, 30); + + if (name && name.length > 0) { + console.log(`✨ AI generated label: "${name}"`); + return name; + } + + return null; + } catch (error) { + console.warn('AI label generation failed:', error); + return null; + } + } + + /** + * Extract keywords from titles + */ + #extractKeywords(titles) { + const allWords = titles + .join(' ') + .toLowerCase() + .replace(/[^\w\s]/g, ' ') + .split(/\s+/) + .filter(w => w.length > 2); + + const wordCount = new Map(); + allWords.forEach(word => { + wordCount.set(word, (wordCount.get(word) || 0) + 1); + }); + + const stopWords = new Set([ + 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', + 'had', 'her', 'was', 'one', 'our', 'out', 'day', 'get', 'has', + 'him', 'his', 'how', 'man', 'new', 'now', 'old', 'see', 'two', + 'way', 'who', 'boy', 'did', 'its', 'let', 'put', 'say', 'she', + 'too', 'use', 'com', 'www', 'http', 'https', 'org' + ]); + + return Array.from(wordCount.entries()) + .filter(([word]) => !stopWords.has(word)) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5) + .map(([word]) => word); + } + + /** + * Convert string to title case + */ + #toTitleCase(str) { + if (!str || typeof str !== 'string') return ''; + return str.toLowerCase().split(' ').map(word => + word.charAt(0).toUpperCase() + word.slice(1) + ).join(' '); + } + + /** + * Generate a readable label from domain name + * + * @param {string} domain - The domain name + * @returns {string} - A readable label + */ + #generateClusterLabel(domain) { + if (!domain) { + return 'Other'; } - // If no match, return "Other" - return { category: 'Other', priority: 999 }; + let label = domain.split('.')[0]; + label = label.charAt(0).toUpperCase() + label.slice(1); + return label; } async contextDeleteWorkspace() { From 889b02e2c523b3e46e0e8084c867a33e70aa58d5 Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Sat, 22 Nov 2025 17:39:08 +1100 Subject: [PATCH 03/12] feat: enhance group tabs button UI with loading state and new icon --- src/zen/workspaces/ZenWorkspace.mjs | 47 +++++++++++++++++++++++++-- src/zen/workspaces/ZenWorkspaces.mjs | 25 +++----------- src/zen/workspaces/zen-workspaces.css | 10 ++++++ 3 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/zen/workspaces/ZenWorkspace.mjs b/src/zen/workspaces/ZenWorkspace.mjs index f0ec9e16af..5d82e3b374 100644 --- a/src/zen/workspaces/ZenWorkspace.mjs +++ b/src/zen/workspaces/ZenWorkspace.mjs @@ -15,9 +15,27 @@ + class="zen-workspace-group-tabs-button toolbarbutton-1"> +
+ + + + + + + + + + + + + + + + +
+ +
{ this._groupingInProgress = false; + window.dispatchEvent(new CustomEvent('ZenGroupingTabsEnd')); }, 500); if (categoryCount > 0) { @@ -2974,8 +2968,6 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { engineId: 'embedding-engine', }); - console.log('🤖 Generating AI embeddings for tabs...'); - const embeddings = await Promise.all( tabData.map(async (data, index) => { try { @@ -3024,7 +3016,6 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { ); const validCount = embeddings.filter(e => e !== null).length; - console.log(`✓ Generated ${validCount}/${tabData.length} embeddings`); return embeddings; } catch (error) { @@ -3101,13 +3092,8 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { async #clusterTabsBySimilarity(tabData) { const SIMILARITY_THRESHOLD = 0.22; - console.log('\n========== Smart Tab Clustering (AI-Enhanced) =========='); - console.log(`Total tabs to cluster: ${tabData.length}`); - console.log(`Similarity threshold: ${SIMILARITY_THRESHOLD}`); - const embeddings = await this.#generateTabEmbeddings(tabData); const useAI = embeddings !== null; - console.log(`Using ${useAI ? 'AI embeddings' : 'text similarity'} for clustering`); const similarities = []; for (let i = 0; i < tabData.length; i++) { @@ -3156,8 +3142,6 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { ); } - console.log(`✓ Created ${clusters.length} cluster${clusters.length !== 1 ? 's' : ''}`); - return clusters; } @@ -3295,7 +3279,6 @@ Category name:`; .slice(0, 30); if (name && name.length > 0) { - console.log(`✨ AI generated label: "${name}"`); return name; } diff --git a/src/zen/workspaces/zen-workspaces.css b/src/zen/workspaces/zen-workspaces.css index bc4c528b64..cdb52ac68b 100644 --- a/src/zen/workspaces/zen-workspaces.css +++ b/src/zen/workspaces/zen-workspaces.css @@ -6,6 +6,16 @@ @namespace html 'http://www.w3.org/1999/xhtml'; @namespace xul 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; +.zen-group-loading-icon { + display: none; +} +.zen-workspace-group-tabs-button[grouping="true"] .zen-group-loading-icon { + display: block; +} +.zen-workspace-group-tabs-button[grouping="true"] .zen-group-icon { + display: none; +} + #zen-workspaces-button { justify-content: center; align-items: center; From 63d3439d9c15da03b12c81afc5abda9fcaac5139 Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:33:29 +1100 Subject: [PATCH 04/12] Removed unnecessary !important --- src/browser/themes/shared/zen-icons/icons.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 57573466ae..af184d20cb 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -394,7 +394,7 @@ } .zen-workspace-group-tabs-button { - list-style-image: none !important; + list-style-image: none; & .toolbarbutton-icon { display: none !important; From fabc9f99939c7f38f8571ec1ec994151f46015f3 Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:15:20 +1100 Subject: [PATCH 05/12] Used nowfirefox's existing tab group implementation instead of custom category implementation --- src/zen/tabs/zen-tabs/vertical-tabs.css | 62 ++++++---------- src/zen/workspaces/ZenWorkspaces.mjs | 97 +++++++++++++++++++++---- 2 files changed, 105 insertions(+), 54 deletions(-) diff --git a/src/zen/tabs/zen-tabs/vertical-tabs.css b/src/zen/tabs/zen-tabs/vertical-tabs.css index 2a85da36f4..24f818c9ea 100644 --- a/src/zen/tabs/zen-tabs/vertical-tabs.css +++ b/src/zen/tabs/zen-tabs/vertical-tabs.css @@ -1358,48 +1358,34 @@ z-index: 9 !important; } -/* ========================================================================== - Tab Category Headers (for Group function) - ========================================================================== */ -.tabbrowser-tab[zen-category-first="true"] { - display: flex; - flex-direction: column; +/* Override for Tab Groups to look like Categories */ +tab-group:not([split-view-group]) .tab-group-label-container { + background: transparent !important; + border: none !important; + box-shadow: none !important; + height: auto !important; + &::before, + &::after { + display: none !important; + background: transparent !important; + } } -.tabbrowser-tab[zen-category-first="true"]::before { - content: attr(zen-category); - display: block; - width: 100%; - padding: 8px var(--zen-toolbox-padding); - margin-top: 12px; - margin-bottom: 4px; - font-size: 11px; - font-weight: 600; +tab-group:not([split-view-group]) .tab-group-label { + background: transparent !important; + color: var(--sidebar-text-color) !important; + opacity: 0.6; + font-size: 11px !important; + font-weight: 600 !important; text-transform: uppercase; letter-spacing: 0.5px; - color: var(--sidebar-text-color); - opacity: 0.5; - user-select: none; - pointer-events: none; - animation: zenCategoryHeaderFadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards; - order: -1; + margin: 0 !important; + text-align: start; } -:root:not([zen-sidebar-expanded='true']) .tabbrowser-tab[zen-category-first="true"]::before { - display: none; -} - -:root:not([zen-sidebar-expanded='true']) .tabbrowser-tab[zen-category-first="true"] { - display: initial; -} - -@keyframes zenCategoryHeaderFadeIn { - from { - opacity: 0; - transform: translateY(-8px); - } - to { - opacity: 1; - transform: translateY(0); - } +#tabbrowser-tabs[orient='vertical'][expanded] tab-group + > :is(.tab-group-label-container, .tab-group-container, .tabbrowser-tab), +#tabbrowser-tabs[orient='vertical']:not([expanded]) tab-group + > :is(.tab-group-label-container, .tab-group-container, .tabbrowser-tab) { + margin-inline-start: 0; } diff --git a/src/zen/workspaces/ZenWorkspaces.mjs b/src/zen/workspaces/ZenWorkspaces.mjs index 1b98b2927e..f670950218 100644 --- a/src/zen/workspaces/ZenWorkspaces.mjs +++ b/src/zen/workspaces/ZenWorkspaces.mjs @@ -2808,6 +2808,27 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { tab.removeAttribute('zen-category-first'); } + const autoCategoryGroups = Array.from(gBrowser.tabGroups ?? []).filter( + (group) => + group && + typeof group.hasAttribute === 'function' && + group.hasAttribute('zen-auto-category-group') + ); + if (typeof gBrowser.ungroupTab === 'function') { + for (const group of autoCategoryGroups) { + try { + const tabsInGroup = Array.from(group.tabs ?? []); + for (const tab of tabsInGroup) { + if (!tab?.closing && tab.ownerGlobal && !tab.ownerGlobal.closed) { + gBrowser.ungroupTab(tab); + } + } + } catch (error) { + console.error('Error ungrouping automatic tab group:', error); + } + } + } + clusters.sort((a, b) => { if (b.tabs.length !== a.tabs.length) { return b.tabs.length - a.tabs.length; @@ -2815,28 +2836,76 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { return a.label.localeCompare(b.label); }); - let currentPosition = gBrowser._numPinnedTabs; let categoryCount = 0; for (const cluster of clusters) { - if (cluster.tabs.length === 0) { + const clusterTabs = cluster.tabs + .filter( + (tab) => + tab && + !tab.closing && + !tab.hasAttribute('zen-essential') && + tab.ownerGlobal && + !tab.ownerGlobal.closed + ) + .sort((a, b) => a._tPos - b._tPos); + + if (!clusterTabs.length) { continue; } - categoryCount++; - const category = cluster.label; - let isFirst = true; + const anchorTab = clusterTabs[0]; + if (!anchorTab?.parentNode) { + continue; + } - for (const tab of cluster.tabs) { + if (typeof gBrowser.ungroupTab === 'function') { + for (const tab of clusterTabs) { + const tabGroup = tab.group; + if ( + tabGroup && + !tabGroup.isZenFolder && + !tabGroup.hasAttribute?.('split-view-group') + ) { + try { + gBrowser.ungroupTab(tab); + } catch (error) { + console.error('Error ungrouping tab before regrouping:', error); + } + } + } + } + + const groupsBefore = new Set(Array.from(gBrowser.tabGroups ?? [])); + const category = cluster.label || 'Group'; + try { + gBrowser.addTabGroup(clusterTabs, { + label: category, + showCreateUI: false, + insertBefore: anchorTab, + }); + } catch (error) { + console.error('Error creating tab group for category:', error); + continue; + } + + const createdGroup = Array.from(gBrowser.tabGroups ?? []).find( + (group) => !groupsBefore.has(group) + ); + if (createdGroup && typeof createdGroup.setAttribute === 'function') { + createdGroup.setAttribute('zen-auto-category-group', 'true'); + } + + clusterTabs.forEach((tab, index) => { tab.setAttribute('zen-category', category); - if (isFirst) { + if (index === 0) { tab.setAttribute('zen-category-first', 'true'); - isFirst = false; + } else { + tab.removeAttribute('zen-category-first'); } + }); - gBrowser.moveTabTo(tab, currentPosition); - currentPosition++; - } + categoryCount++; } setTimeout(() => { @@ -3015,8 +3084,6 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { }) ); - const validCount = embeddings.filter(e => e !== null).length; - return embeddings; } catch (error) { console.warn('Failed to generate embeddings, falling back to text similarity:', error); @@ -3262,9 +3329,7 @@ Category name:`; }, }); - let name = (aiResult[0]?.generated_text || '') - .split('\n')[0] - .trim(); + let name = (aiResult[0]?.generated_text || '').split('\n')[0].trim(); if (!name || /none|adult content/i.test(name)) { return null; From e091e962416187c3c50722074f6e99db56b36f5c Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:35:50 +1100 Subject: [PATCH 06/12] Icons separeted and not SVG inline now --- .../themes/shared/zen-icons/jar.inc.mn | 6 ++++ .../themes/shared/zen-icons/lin/ZenGroup.svg | 7 +++++ .../shared/zen-icons/lin/ZenGroupSpinner.svg | 17 +++++++++++ src/zen/workspaces/ZenWorkspace.mjs | 30 +++++++++---------- src/zen/workspaces/zen-workspaces.css | 15 ++++++++-- 5 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 src/browser/themes/shared/zen-icons/lin/ZenGroup.svg create mode 100644 src/browser/themes/shared/zen-icons/lin/ZenGroupSpinner.svg diff --git a/src/browser/themes/shared/zen-icons/jar.inc.mn b/src/browser/themes/shared/zen-icons/jar.inc.mn index d52bde3b58..fb6b66e71e 100644 --- a/src/browser/themes/shared/zen-icons/jar.inc.mn +++ b/src/browser/themes/shared/zen-icons/jar.inc.mn @@ -154,6 +154,8 @@ * skin/classic/browser/zen-icons/xr.svg (../shared/zen-icons/lin/xr.svg) * skin/classic/browser/zen-icons/zoom-control.svg (../shared/zen-icons/lin/zoom-control.svg) * skin/classic/browser/zen-icons/zoom-out.svg (../shared/zen-icons/lin/zoom-out.svg) +* skin/classic/browser/zen-icons/ZenGroup.svg (../shared/zen-icons/lin/ZenGroup.svg) +* skin/classic/browser/zen-icons/ZenGroupSpinner.svg (../shared/zen-icons/lin/ZenGroupSpinner.svg) #endif #ifdef XP_MACOSX * skin/classic/browser/zen-icons/accessibility.svg (../shared/zen-icons/lin/accessibility.svg) @@ -307,6 +309,8 @@ * skin/classic/browser/zen-icons/xr.svg (../shared/zen-icons/lin/xr.svg) * skin/classic/browser/zen-icons/zoom-control.svg (../shared/zen-icons/lin/zoom-control.svg) * skin/classic/browser/zen-icons/zoom-out.svg (../shared/zen-icons/lin/zoom-out.svg) +* skin/classic/browser/zen-icons/ZenGroup.svg (../shared/zen-icons/lin/ZenGroup.svg) +* skin/classic/browser/zen-icons/ZenGroupSpinner.svg (../shared/zen-icons/lin/ZenGroupSpinner.svg) #endif #ifdef XP_LINUX * skin/classic/browser/zen-icons/accessibility.svg (../shared/zen-icons/lin/accessibility.svg) @@ -460,6 +464,8 @@ * skin/classic/browser/zen-icons/xr.svg (../shared/zen-icons/lin/xr.svg) * skin/classic/browser/zen-icons/zoom-control.svg (../shared/zen-icons/lin/zoom-control.svg) * skin/classic/browser/zen-icons/zoom-out.svg (../shared/zen-icons/lin/zoom-out.svg) +* skin/classic/browser/zen-icons/ZenGroup.svg (../shared/zen-icons/lin/ZenGroup.svg) +* skin/classic/browser/zen-icons/ZenGroupSpinner.svg (../shared/zen-icons/lin/ZenGroupSpinner.svg) #endif * skin/classic/browser/zen-icons/urlbar-arrow.svg (../shared/zen-icons/common/urlbar-arrow.svg) * skin/classic/browser/zen-icons/selectable/airplane.svg (../shared/zen-icons/common/selectable/airplane.svg) diff --git a/src/browser/themes/shared/zen-icons/lin/ZenGroup.svg b/src/browser/themes/shared/zen-icons/lin/ZenGroup.svg new file mode 100644 index 0000000000..67181b7992 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/lin/ZenGroup.svg @@ -0,0 +1,7 @@ +#filter dumbComments emptyLines substitution +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + diff --git a/src/browser/themes/shared/zen-icons/lin/ZenGroupSpinner.svg b/src/browser/themes/shared/zen-icons/lin/ZenGroupSpinner.svg new file mode 100644 index 0000000000..392863f09e --- /dev/null +++ b/src/browser/themes/shared/zen-icons/lin/ZenGroupSpinner.svg @@ -0,0 +1,17 @@ +#filter dumbComments emptyLines substitution +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + + + + + + + + + + + diff --git a/src/zen/workspaces/ZenWorkspace.mjs b/src/zen/workspaces/ZenWorkspace.mjs index 5d82e3b374..37666b479e 100644 --- a/src/zen/workspaces/ZenWorkspace.mjs +++ b/src/zen/workspaces/ZenWorkspace.mjs @@ -17,22 +17,20 @@
- - - - - - - - - - - - - - - - + +
diff --git a/src/zen/workspaces/zen-workspaces.css b/src/zen/workspaces/zen-workspaces.css index cdb52ac68b..8eb563f5a5 100644 --- a/src/zen/workspaces/zen-workspaces.css +++ b/src/zen/workspaces/zen-workspaces.css @@ -15,6 +15,15 @@ .zen-workspace-group-tabs-button[grouping="true"] .zen-group-icon { display: none; } +.zen-group-loading-icon, +.zen-group-icon { + width: 16px; + height: 16px; + grid-area: 1 / 1; + -moz-context-properties: fill, fill-opacity, stroke, stroke-opacity; + fill: currentColor; + stroke: currentColor; +} #zen-workspaces-button { justify-content: center; @@ -59,7 +68,7 @@ &:is(img) { width: 14px; } - + &[no-icon='true'] { width: 6px; height: 6px; @@ -111,14 +120,14 @@ background: transparent; } - /* Inlcude separately since ther'es a bug in the + /* Inlcude separately since ther'es a bug in the * rendering of XUL in firefox */ & toolbarbutton:not([active='true']) { %include overflow-icons.inc.css } &:has(toolbarbutton:hover) toolbarbutton[active='true']:not([dragged='true']) { -%include overflow-icons.inc.css +%include overflow-icons.inc.css } } } From 77b45b372ca31d5b4502ca6b6b4f1895153782bc Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:11:29 +1100 Subject: [PATCH 07/12] Fix: Moved the ML Logic to an extra file --- .../base/content/zen-assets.jar.inc.mn | 1 + src/zen/ml/ZenTabsTidy.sys.mjs | 647 ++++++++++++++++++ src/zen/workspaces/ZenWorkspaces.mjs | 629 +---------------- 3 files changed, 672 insertions(+), 605 deletions(-) create mode 100644 src/zen/ml/ZenTabsTidy.sys.mjs diff --git a/src/browser/base/content/zen-assets.jar.inc.mn b/src/browser/base/content/zen-assets.jar.inc.mn index d8ee81f6ed..1e038ef388 100644 --- a/src/browser/base/content/zen-assets.jar.inc.mn +++ b/src/browser/base/content/zen-assets.jar.inc.mn @@ -44,6 +44,7 @@ content/browser/zen-components/ZenWorkspaceCreation.mjs (../../zen/workspaces/ZenWorkspaceCreation.mjs) content/browser/zen-components/ZenWorkspacesStorage.mjs (../../zen/workspaces/ZenWorkspacesStorage.mjs) content/browser/zen-components/ZenWorkspacesSync.mjs (../../zen/workspaces/ZenWorkspacesSync.mjs) + content/browser/zen-components/ZenTabsTidy.sys.mjs (../../zen/ml/ZenTabsTidy.sys.mjs) content/browser/zen-components/ZenGradientGenerator.mjs (../../zen/workspaces/ZenGradientGenerator.mjs) * content/browser/zen-styles/zen-workspaces.css (../../zen/workspaces/zen-workspaces.css) content/browser/zen-styles/zen-gradient-generator.css (../../zen/workspaces/zen-gradient-generator.css) diff --git a/src/zen/ml/ZenTabsTidy.sys.mjs b/src/zen/ml/ZenTabsTidy.sys.mjs new file mode 100644 index 0000000000..de29c23d6b --- /dev/null +++ b/src/zen/ml/ZenTabsTidy.sys.mjs @@ -0,0 +1,647 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +const Services = globalThis.Services; +const { setTimeout } = ChromeUtils.importESModule('resource://gre/modules/Timer.sys.mjs'); + +export async function groupTabsBySimilarity({ + window: browserWindow, + gBrowser, + gZenUIManager, + workspaceId, + unpinnedTabs = [], + setGroupingInProgress = () => {}, +}) { + if (!browserWindow || !gBrowser) { + console.error('ZenTabsTidy: Missing browser window or tab browser'); + return; + } + + const CustomEventCtor = browserWindow.CustomEvent || CustomEvent; + browserWindow.dispatchEvent(new CustomEventCtor('ZenGroupingTabsStart')); + setGroupingInProgress(true); + + try { + if (unpinnedTabs.length < 2) { + setGroupingInProgress(false); + browserWindow.dispatchEvent(new CustomEventCtor('ZenGroupingTabsEnd')); + return; + } + + const tabData = collectTabData(unpinnedTabs); + + if (tabData.length < 2) { + setGroupingInProgress(false); + browserWindow.dispatchEvent(new CustomEventCtor('ZenGroupingTabsEnd')); + return; + } + + const clusters = await clusterTabsBySimilarity(tabData); + + for (const tab of gBrowser.tabs) { + tab.removeAttribute('zen-category'); + tab.removeAttribute('zen-category-first'); + } + + await ungroupPreviousAutoCategories(gBrowser); + + clusters.sort((a, b) => { + if (b.tabs.length !== a.tabs.length) { + return b.tabs.length - a.tabs.length; + } + return a.label.localeCompare(b.label); + }); + + let categoryCount = 0; + + for (const cluster of clusters) { + const clusterTabs = cluster.tabs + .filter( + (tab) => + tab && + !tab.closing && + !tab.hasAttribute('zen-essential') && + tab.ownerGlobal && + !tab.ownerGlobal.closed + ) + .sort((a, b) => a._tPos - b._tPos); + + if (!clusterTabs.length) { + continue; + } + + const anchorTab = clusterTabs[0]; + if (!anchorTab?.parentNode) { + continue; + } + + if (typeof gBrowser.ungroupTab === 'function') { + for (const tab of clusterTabs) { + const tabGroup = tab.group; + if ( + tabGroup && + !tabGroup.isZenFolder && + !tabGroup.hasAttribute?.('split-view-group') + ) { + try { + gBrowser.ungroupTab(tab); + } catch (error) { + console.error('ZenTabsTidy: Error ungrouping tab before regrouping:', error); + } + } + } + } + + const groupsBefore = new Set(Array.from(gBrowser.tabGroups ?? [])); + const category = cluster.label || 'Group'; + try { + gBrowser.addTabGroup(clusterTabs, { + label: category, + showCreateUI: false, + insertBefore: anchorTab, + }); + } catch (error) { + console.error('ZenTabsTidy: Error creating tab group for category:', error); + continue; + } + + const createdGroup = Array.from(gBrowser.tabGroups ?? []).find( + (group) => !groupsBefore.has(group) + ); + if (createdGroup && typeof createdGroup.setAttribute === 'function') { + createdGroup.setAttribute('zen-auto-category-group', 'true'); + } + + clusterTabs.forEach((tab, index) => { + tab.setAttribute('zen-category', category); + if (index === 0) { + tab.setAttribute('zen-category-first', 'true'); + } else { + tab.removeAttribute('zen-category-first'); + } + }); + + categoryCount++; + } + + setTimeout(() => { + setGroupingInProgress(false); + browserWindow.dispatchEvent(new CustomEventCtor('ZenGroupingTabsEnd')); + }, 500); + + if (categoryCount > 0 && gZenUIManager?.showToast) { + gZenUIManager.showToast('zen-workspaces-group-tabs-toast', { + l10nArgs: { + count: categoryCount, + }, + }); + } + } catch (error) { + console.error('ZenTabsTidy: Failed to group tabs by similarity:', error); + setGroupingInProgress(false); + browserWindow.dispatchEvent(new CustomEventCtor('ZenGroupingTabsEnd')); + if (Services?.console?.logStringMessage) { + Services.console.logStringMessage( + `ZenTabsTidy: grouping failure${workspaceId ? ` for workspace ${workspaceId}` : ''}` + ); + } + } +} + +function collectTabData(unpinnedTabs) { + const tabData = []; + + for (const tab of unpinnedTabs) { + try { + const uri = tab.linkedBrowser?.currentURI; + if (!uri) { + continue; + } + + if (uri.scheme === 'about' || uri.scheme === 'chrome') { + continue; + } + + const domain = uri.host.replace(/^(www\.|m\.|mobile\.)/, ''); + const title = preprocessText(tab.label || ''); + const opener = collectOpenerInfo(tab); + + tabData.push({ + tab, + domain, + title, + url: uri.spec, + opener, + combinedText: `${domain} ${title}`, + }); + } catch (error) { + console.error('ZenTabsTidy: Error processing tab for grouping:', error); + } + } + + return tabData; +} + +function collectOpenerInfo(tab) { + if (!tab.openerTab || tab.openerTab.closing) { + return null; + } + + try { + const openerUri = tab.openerTab.linkedBrowser?.currentURI; + if (!openerUri) { + return null; + } + + const openerDomain = openerUri.host.replace(/^(www\.|m\.|mobile\.)/, ''); + const openerTitle = preprocessText(tab.openerTab.label || ''); + + return { + domain: openerDomain, + title: openerTitle, + url: openerUri.spec, + }; + } catch (_) { + return null; + } +} + +async function ungroupPreviousAutoCategories(gBrowser) { + const autoCategoryGroups = Array.from(gBrowser.tabGroups ?? []).filter( + (group) => + group && + typeof group.hasAttribute === 'function' && + group.hasAttribute('zen-auto-category-group') + ); + + if (typeof gBrowser.ungroupTab !== 'function') { + return; + } + + for (const group of autoCategoryGroups) { + try { + const tabsInGroup = Array.from(group.tabs ?? []); + for (const tab of tabsInGroup) { + if (!tab?.closing && tab.ownerGlobal && !tab.ownerGlobal.closed) { + gBrowser.ungroupTab(tab); + } + } + } catch (error) { + console.error('ZenTabsTidy: Error ungrouping automatic tab group:', error); + } + } +} + +async function clusterTabsBySimilarity(tabData) { + const SIMILARITY_THRESHOLD = 0.22; + + const embeddings = await generateTabEmbeddings(tabData); + const useAi = embeddings !== null; + + const similarities = []; + for (let i = 0; i < tabData.length; i++) { + similarities[i] = []; + for (let j = 0; j < tabData.length; j++) { + if (i === j) { + similarities[i][j] = 1.0; + } else if (useAi && embeddings[i] && embeddings[j]) { + similarities[i][j] = cosineSimilarity(embeddings[i], embeddings[j]); + } else { + similarities[i][j] = calculateTextSimilarity( + tabData[i].combinedText, + tabData[j].combinedText + ); + } + } + } + + const clusters = []; + const used = new Array(tabData.length).fill(false); + + for (let i = 0; i < tabData.length; i++) { + if (used[i]) continue; + + const clusterIndices = [i]; + const clusterTabs = [tabData[i].tab]; + used[i] = true; + + for (let j = 0; j < tabData.length; j++) { + if (i !== j && !used[j] && similarities[i][j] > SIMILARITY_THRESHOLD) { + clusterIndices.push(j); + clusterTabs.push(tabData[j].tab); + used[j] = true; + } + } + + clusters.push({ + indices: new Set(clusterIndices), + tabs: clusterTabs, + }); + } + + for (const cluster of clusters) { + cluster.label = await generateDynamicClusterLabel( + Array.from(cluster.indices).map((i) => tabData[i]) + ); + } + + return clusters; +} + +function preprocessText(text) { + if (!text) { + return ''; + } + + const delimiters = /(?<=\s)[|–-]+(?=\s)/; + const splitText = text.split(delimiters); + const hasEnoughInfo = !!splitText.length && splitText.slice(0, -1).join(' ').length > 5; + const isPotentialDomainInfo = + splitText.length > 1 && splitText[splitText.length - 1].length < 20; + + if (hasEnoughInfo && isPotentialDomainInfo) { + return splitText + .slice(0, -1) + .map((segment) => segment.trim()) + .filter(Boolean) + .join(' ') + .trim(); + } + + return text.trim(); +} + +async function generateTabEmbeddings(tabData) { + if (!Services.prefs.getBoolPref('browser.ml.enable', false)) { + return null; + } + + try { + const { createEngine } = ChromeUtils.importESModule( + 'chrome://global/content/ml/EngineProcess.sys.mjs' + ); + + const engine = await createEngine({ + taskName: 'feature-extraction', + modelId: 'Mozilla/smart-tab-embedding', + modelHub: 'huggingface', + engineId: 'embedding-engine', + }); + + const embeddings = await Promise.all( + tabData.map(async (data, index) => { + try { + const text = `${data.title} ${data.domain}`; + const result = await engine.run({ args: [text] }); + + let embedding; + if (result?.[0]?.embedding && Array.isArray(result[0].embedding)) { + embedding = result[0].embedding; + } else if (result?.[0] && Array.isArray(result[0])) { + embedding = result[0]; + } else if (Array.isArray(result)) { + embedding = result; + } else { + return null; + } + + if (Array.isArray(embedding) && embedding.length > 0) { + let pooled; + if (typeof embedding[0] === 'number') { + pooled = embedding; + } else if (Array.isArray(embedding[0])) { + const len = embedding[0].length; + pooled = new Array(len).fill(0); + for (const arr of embedding) { + for (let i = 0; i < len; i++) { + pooled[i] += arr[i]; + } + } + for (let i = 0; i < len; i++) { + pooled[i] /= embedding.length; + } + } else { + return null; + } + + const norm = Math.sqrt(pooled.reduce((sum, value) => sum + value * value, 0)); + return norm === 0 ? pooled : pooled.map((value) => value / norm); + } + return null; + } catch (error) { + console.warn(`ZenTabsTidy: Failed to generate embedding for tab ${index}:`, error); + return null; + } + }) + ); + + return embeddings; + } catch (error) { + console.warn('ZenTabsTidy: Failed to generate embeddings, falling back to text similarity:', error); + return null; + } +} + +function cosineSimilarity(vec1, vec2) { + if (!vec1 || !vec2 || vec1.length !== vec2.length) { + return 0; + } + + let dotProduct = 0; + let norm1 = 0; + let norm2 = 0; + + for (let i = 0; i < vec1.length; i++) { + dotProduct += vec1[i] * vec2[i]; + norm1 += vec1[i] * vec1[i]; + norm2 += vec2[i] * vec2[i]; + } + + const denominator = Math.sqrt(norm1) * Math.sqrt(norm2); + return denominator === 0 ? 0 : dotProduct / denominator; +} + +function calculateTextSimilarity(text1, text2) { + const words1 = new Set( + text1 + .toLowerCase() + .split(/\s+/) + .filter((word) => word.length > 2) + ); + const words2 = new Set( + text2 + .toLowerCase() + .split(/\s+/) + .filter((word) => word.length > 2) + ); + + if (words1.size === 0 && words2.size === 0) { + return 1.0; + } + if (words1.size === 0 || words2.size === 0) { + return 0.0; + } + + const intersection = new Set([...words1].filter((word) => words2.has(word))); + const union = new Set([...words1, ...words2]); + return intersection.size / union.size; +} + +async function generateDynamicClusterLabel(clusterData) { + if (clusterData.length === 1) { + return generateClusterLabel(clusterData[0].domain); + } + + try { + const label = await generateAiClusterLabel(clusterData); + if (label) { + return label; + } + } catch (error) { + console.warn('ZenTabsTidy: AI label generation failed, using fallback:', error); + } + + const wordFreq = new Map(); + + for (const data of clusterData) { + const text = `${data.domain} ${data.title}`.toLowerCase(); + const words = text.split(/\s+/).filter((word) => word.length > 3); + + for (const word of words) { + wordFreq.set(word, (wordFreq.get(word) || 0) + 1); + } + } + + let bestWord = null; + let bestCount = 0; + + for (const [word, count] of wordFreq.entries()) { + if (count > 1 && count > bestCount) { + bestCount = count; + bestWord = word; + } + } + + if (bestWord) { + return bestWord.charAt(0).toUpperCase() + bestWord.slice(1); + } + + return generateClusterLabel(clusterData[0].domain); +} + +async function generateAiClusterLabel(clusterData) { + if (!Services.prefs.getBoolPref('browser.ml.enable', false)) { + return null; + } + + try { + const { createEngine } = ChromeUtils.importESModule( + 'chrome://global/content/ml/EngineProcess.sys.mjs' + ); + + const tabDescriptions = clusterData + .map((data, idx) => { + let desc = `${idx + 1}. ${data.url}\n Title: ${data.title}`; + if (data.opener) { + desc += `\n ↳ Opened from: ${data.opener.domain} (${data.opener.title})`; + } + return desc; + }) + .join('\n\n'); + + const titles = clusterData.map((data) => data.title).filter((title) => title.length > 0); + const keywords = extractKeywords(titles); + const domains = [...new Set(clusterData.map((data) => data.domain))].join(', '); + + const prompt = `You are an expert organizer who creates concise, descriptive category names. + +I have a group of browser tabs that belong together. Please create a short, descriptive category name (1-3 words) for this group. + +Tabs in this group: +${tabDescriptions} + +Common domains: ${domains} +${keywords.length > 0 ? `Keywords in common: ${keywords.join(', ')}` : ''} + +Instructions: +- Look at the FULL URLs to understand the context (e.g., /shop/, /cars/, /docs/) +- Use the browsing history (↳ Opened from) to understand the user's intent +- Choose a GENERAL category name, not a specific brand name +- If tabs contain multiple brands (Nike, Adidas), use the category (e.g., "Sportswear" or "Athletic Brands") +- If tabs are car brands/manufacturers (Audi, Mercedes, Tesla), use "Automotive" or "Cars" +- If tabs are video platforms (YouTube, Vimeo), use "Video Platforms" or "Videos" +- If tabs are developer tools (GitHub, Jira, GitLab), use "Development" or "Dev Tools" +- Use the common theme or purpose, not individual site names +- Keep it 1-3 words maximum +- Capitalize properly + +Category name:`; + + const engine = await createEngine({ + taskName: 'text2text-generation', + modelId: 'Mozilla/smart-tab-topic', + modelHub: 'huggingface', + engineId: 'group-namer', + }); + + const aiResult = await engine.run({ + args: [prompt], + options: { + max_new_tokens: 10, + temperature: 0.5, + }, + }); + + let name = (aiResult[0]?.generated_text || '').split('\n')[0].trim(); + + if (!name || /none|adult content/i.test(name)) { + return null; + } + + name = toTitleCase(name); + name = name + .replace(/^['"`]+|['"`]+$/g, '') + .replace(/[.?!,:;]+$/, '') + .replace(/^(category name:|name:)\s*/i, '') + .trim() + .slice(0, 30); + + if (name && name.length > 0) { + return name; + } + + return null; + } catch (error) { + console.warn('ZenTabsTidy: AI label generation failed:', error); + return null; + } +} + +function extractKeywords(titles) { + const allWords = titles + .join(' ') + .toLowerCase() + .replace(/[^\w\s]/g, ' ') + .split(/\s+/) + .filter((word) => word.length > 2); + + const wordCount = new Map(); + allWords.forEach((word) => { + wordCount.set(word, (wordCount.get(word) || 0) + 1); + }); + + const stopWords = new Set([ + 'the', + 'and', + 'for', + 'are', + 'but', + 'not', + 'you', + 'all', + 'can', + 'had', + 'her', + 'was', + 'one', + 'our', + 'out', + 'day', + 'get', + 'has', + 'him', + 'his', + 'how', + 'man', + 'new', + 'now', + 'old', + 'see', + 'two', + 'way', + 'who', + 'boy', + 'did', + 'its', + 'let', + 'put', + 'say', + 'she', + 'too', + 'use', + 'com', + 'www', + 'http', + 'https', + 'org', + ]); + + return Array.from(wordCount.entries()) + .filter(([word]) => !stopWords.has(word)) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5) + .map(([word]) => word); +} + +function toTitleCase(str) { + if (!str || typeof str !== 'string') { + return ''; + } + return str + .toLowerCase() + .split(' ') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +} + +function generateClusterLabel(domain) { + if (!domain) { + return 'Other'; + } + + let label = domain.split('.')[0]; + label = label.charAt(0).toUpperCase() + label.slice(1); + return label; +} + diff --git a/src/zen/workspaces/ZenWorkspaces.mjs b/src/zen/workspaces/ZenWorkspaces.mjs index f670950218..d4947972df 100644 --- a/src/zen/workspaces/ZenWorkspaces.mjs +++ b/src/zen/workspaces/ZenWorkspaces.mjs @@ -12,6 +12,8 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { #canDebug = Services.prefs.getBoolPref('zen.workspaces.debug', false); + #tabsTidyModulePromise = null; + _swipeState = { isGestureActive: true, lastDelta: 0, @@ -51,6 +53,15 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { await Promise.all([this.promiseDBInitialized, this.promisePinnedInitialized]); } + async #getTabsTidyModule() { + if (!this.#tabsTidyModulePromise) { + this.#tabsTidyModulePromise = ChromeUtils.importESModule( + 'chrome://browser/content/zen-components/ZenTabsTidy.sys.mjs' + ); + } + return this.#tabsTidyModulePromise; + } + async init() { // Initialize tab selection state this._tabSelectionState = { @@ -2737,188 +2748,25 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { } async groupTabsBySimilarity() { - window.dispatchEvent(new CustomEvent('ZenGroupingTabsStart')); const workspaceId = this.activeWorkspace; - this._groupingInProgress = true; const unpinnedTabs = this.#unpinnedTabsInWorkspace(workspaceId); - if (unpinnedTabs.length < 2) { - this._groupingInProgress = false; - window.dispatchEvent(new CustomEvent('ZenGroupingTabsEnd')); - return; - } - - const tabData = []; - - for (const tab of unpinnedTabs) { - try { - const uri = tab.linkedBrowser?.currentURI; - if (!uri) { - continue; - } - - const url = uri.spec; - - if (uri.scheme === 'about' || uri.scheme === 'chrome') { - continue; - } - - const domain = uri.host.replace(/^(www\.|m\.|mobile\.)/, ''); - const title = this.#preprocessText(tab.label || ''); - - let openerInfo = null; - if (tab.openerTab && !tab.openerTab.closing) { - try { - const openerUri = tab.openerTab.linkedBrowser?.currentURI; - if (openerUri) { - const openerDomain = openerUri.host.replace(/^(www\.|m\.|mobile\.)/, ''); - const openerTitle = this.#preprocessText(tab.openerTab.label || ''); - openerInfo = { - domain: openerDomain, - title: openerTitle, - url: openerUri.spec, - }; - } - } catch (e) {} - } - - tabData.push({ - tab, - domain, - title, - url, - opener: openerInfo, - combinedText: `${domain} ${title}`, - }); - } catch (error) { - console.error('Error processing tab for grouping:', error); - } - } - - if (tabData.length < 2) { - this._groupingInProgress = false; - window.dispatchEvent(new CustomEvent('ZenGroupingTabsEnd')); - return; - } - - const clusters = await this.#clusterTabsBySimilarity(tabData); - - for (const tab of gBrowser.tabs) { - tab.removeAttribute('zen-category'); - tab.removeAttribute('zen-category-first'); - } - - const autoCategoryGroups = Array.from(gBrowser.tabGroups ?? []).filter( - (group) => - group && - typeof group.hasAttribute === 'function' && - group.hasAttribute('zen-auto-category-group') - ); - if (typeof gBrowser.ungroupTab === 'function') { - for (const group of autoCategoryGroups) { - try { - const tabsInGroup = Array.from(group.tabs ?? []); - for (const tab of tabsInGroup) { - if (!tab?.closing && tab.ownerGlobal && !tab.ownerGlobal.closed) { - gBrowser.ungroupTab(tab); - } - } - } catch (error) { - console.error('Error ungrouping automatic tab group:', error); - } - } - } - - clusters.sort((a, b) => { - if (b.tabs.length !== a.tabs.length) { - return b.tabs.length - a.tabs.length; - } - return a.label.localeCompare(b.label); - }); - - let categoryCount = 0; - - for (const cluster of clusters) { - const clusterTabs = cluster.tabs - .filter( - (tab) => - tab && - !tab.closing && - !tab.hasAttribute('zen-essential') && - tab.ownerGlobal && - !tab.ownerGlobal.closed - ) - .sort((a, b) => a._tPos - b._tPos); - - if (!clusterTabs.length) { - continue; - } - - const anchorTab = clusterTabs[0]; - if (!anchorTab?.parentNode) { - continue; - } - - if (typeof gBrowser.ungroupTab === 'function') { - for (const tab of clusterTabs) { - const tabGroup = tab.group; - if ( - tabGroup && - !tabGroup.isZenFolder && - !tabGroup.hasAttribute?.('split-view-group') - ) { - try { - gBrowser.ungroupTab(tab); - } catch (error) { - console.error('Error ungrouping tab before regrouping:', error); - } - } - } - } - - const groupsBefore = new Set(Array.from(gBrowser.tabGroups ?? [])); - const category = cluster.label || 'Group'; - try { - gBrowser.addTabGroup(clusterTabs, { - label: category, - showCreateUI: false, - insertBefore: anchorTab, - }); - } catch (error) { - console.error('Error creating tab group for category:', error); - continue; - } - - const createdGroup = Array.from(gBrowser.tabGroups ?? []).find( - (group) => !groupsBefore.has(group) - ); - if (createdGroup && typeof createdGroup.setAttribute === 'function') { - createdGroup.setAttribute('zen-auto-category-group', 'true'); - } - - clusterTabs.forEach((tab, index) => { - tab.setAttribute('zen-category', category); - if (index === 0) { - tab.setAttribute('zen-category-first', 'true'); - } else { - tab.removeAttribute('zen-category-first'); - } + try { + const { groupTabsBySimilarity } = await this.#getTabsTidyModule(); + await groupTabsBySimilarity({ + window, + gBrowser, + gZenUIManager, + workspaceId, + unpinnedTabs, + setGroupingInProgress: (isInProgress) => { + this._groupingInProgress = isInProgress; + }, }); - - categoryCount++; - } - - setTimeout(() => { + } catch (error) { this._groupingInProgress = false; window.dispatchEvent(new CustomEvent('ZenGroupingTabsEnd')); - }, 500); - - if (categoryCount > 0) { - gZenUIManager.showToast('zen-workspaces-group-tabs-toast', { - l10nArgs: { - count: categoryCount - } - }); + console.error('gZenWorkspaces: Failed to load ZenTabsTidy.sys.mjs', error); } } @@ -2982,435 +2830,6 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { setTimeout(checkAndMove, 500); } } - - /** - * Preprocess text by removing trailing domain information - * Inspired by Firefox's SmartTabGrouping.preprocessText - * - * @param {string} text - The text to preprocess - * @returns {string} - The preprocessed text - */ - #preprocessText(text) { - if (!text) { - return ''; - } - - const delimiters = /(?<=\s)[|–-]+(?=\s)/; - const splitText = text.split(delimiters); - const hasEnoughInfo = - !!splitText.length && splitText.slice(0, -1).join(' ').length > 5; - const isPotentialDomainInfo = - splitText.length > 1 && splitText[splitText.length - 1].length < 20; - - if (hasEnoughInfo && isPotentialDomainInfo) { - return splitText - .slice(0, -1) - .map(t => t.trim()) - .filter(Boolean) - .join(' ') - .trim(); - } - - return text.trim(); - } - - /** - * Generate AI embeddings for all tabs using Firefox's ML - * - * @param {Array} tabData - Array of tab data - * @returns {Promise} - Array of embeddings or null if failed - */ - async #generateTabEmbeddings(tabData) { - if (!Services.prefs.getBoolPref('browser.ml.enable', false)) { - return null; - } - - try { - const { createEngine } = ChromeUtils.importESModule( - 'chrome://global/content/ml/EngineProcess.sys.mjs' - ); - - const engine = await createEngine({ - taskName: 'feature-extraction', - modelId: 'Mozilla/smart-tab-embedding', - modelHub: 'huggingface', - engineId: 'embedding-engine', - }); - - const embeddings = await Promise.all( - tabData.map(async (data, index) => { - try { - const text = `${data.title} ${data.domain}`; - const result = await engine.run({ args: [text] }); - - let embedding; - if (result?.[0]?.embedding && Array.isArray(result[0].embedding)) { - embedding = result[0].embedding; - } else if (result?.[0] && Array.isArray(result[0])) { - embedding = result[0]; - } else if (Array.isArray(result)) { - embedding = result; - } else { - return null; - } - - if (Array.isArray(embedding) && embedding.length > 0) { - let pooled; - if (typeof embedding[0] === 'number') { - pooled = embedding; - } else if (Array.isArray(embedding[0])) { - const len = embedding[0].length; - pooled = new Array(len).fill(0); - for (const arr of embedding) { - for (let i = 0; i < len; i++) { - pooled[i] += arr[i]; - } - } - for (let i = 0; i < len; i++) { - pooled[i] /= embedding.length; - } - } else { - return null; - } - - const norm = Math.sqrt(pooled.reduce((sum, v) => sum + v * v, 0)); - return norm === 0 ? pooled : pooled.map(v => v / norm); - } - return null; - } catch (error) { - console.warn(`Failed to generate embedding for tab ${index}:`, error); - return null; - } - }) - ); - - return embeddings; - } catch (error) { - console.warn('Failed to generate embeddings, falling back to text similarity:', error); - return null; - } - } - - /** - * Calculate cosine similarity between two embedding vectors - * - * @param {Array} vec1 - First embedding vector - * @param {Array} vec2 - Second embedding vector - * @returns {number} - Similarity score between -1 and 1 - */ - #cosineSimilarity(vec1, vec2) { - if (!vec1 || !vec2 || vec1.length !== vec2.length) { - return 0; - } - - let dotProduct = 0; - let norm1 = 0; - let norm2 = 0; - - for (let i = 0; i < vec1.length; i++) { - dotProduct += vec1[i] * vec2[i]; - norm1 += vec1[i] * vec1[i]; - norm2 += vec2[i] * vec2[i]; - } - - const denominator = Math.sqrt(norm1) * Math.sqrt(norm2); - return denominator === 0 ? 0 : dotProduct / denominator; - } - - /** - * Calculate text similarity using Jaccard similarity - * - * @param {string} text1 - First text - * @param {string} text2 - Second text - * @returns {number} - Similarity score between 0 and 1 - */ - #calculateTextSimilarity(text1, text2) { - const words1 = new Set( - text1 - .toLowerCase() - .split(/\s+/) - .filter(w => w.length > 2) - ); - const words2 = new Set( - text2 - .toLowerCase() - .split(/\s+/) - .filter(w => w.length > 2) - ); - - if (words1.size === 0 && words2.size === 0) { - return 1.0; - } - if (words1.size === 0 || words2.size === 0) { - return 0.0; - } - - const intersection = new Set([...words1].filter(w => words2.has(w))); - const union = new Set([...words1, ...words2]); - return intersection.size / union.size; - } - - /** - * Cluster tabs dynamically using AI embeddings + similarity analysis - * - * @param {Array} tabData - Array of tab data objects - * @returns {Promise} - Array of clusters with labels and tabs - */ - async #clusterTabsBySimilarity(tabData) { - const SIMILARITY_THRESHOLD = 0.22; - - const embeddings = await this.#generateTabEmbeddings(tabData); - const useAI = embeddings !== null; - - const similarities = []; - for (let i = 0; i < tabData.length; i++) { - similarities[i] = []; - for (let j = 0; j < tabData.length; j++) { - if (i === j) { - similarities[i][j] = 1.0; - } else if (useAI && embeddings[i] && embeddings[j]) { - similarities[i][j] = this.#cosineSimilarity(embeddings[i], embeddings[j]); - } else { - similarities[i][j] = this.#calculateTextSimilarity( - tabData[i].combinedText, - tabData[j].combinedText - ); - } - } - } - - const clusters = []; - const used = new Array(tabData.length).fill(false); - - for (let i = 0; i < tabData.length; i++) { - if (used[i]) continue; - - const clusterIndices = [i]; - const clusterTabs = [tabData[i].tab]; - used[i] = true; - - for (let j = 0; j < tabData.length; j++) { - if (i !== j && !used[j] && similarities[i][j] > SIMILARITY_THRESHOLD) { - clusterIndices.push(j); - clusterTabs.push(tabData[j].tab); - used[j] = true; - } - } - - clusters.push({ - indices: new Set(clusterIndices), - tabs: clusterTabs, - }); - } - - for (const cluster of clusters) { - cluster.label = await this.#generateDynamicClusterLabel( - Array.from(cluster.indices).map(i => tabData[i]) - ); - } - - return clusters; - } - - /** - * Generate AI-powered label from cluster content using local ML - * - * @param {Array} clusterData - Array of tab data in the cluster - * @returns {Promise} - Generated label - */ - async #generateDynamicClusterLabel(clusterData) { - if (clusterData.length === 1) { - return this.#generateClusterLabel(clusterData[0].domain); - } - - try { - const label = await this.#generateAIClusterLabel(clusterData); - if (label) { - return label; - } - } catch (error) { - console.warn('AI label generation failed, using fallback:', error); - } - - const wordFreq = new Map(); - - for (const data of clusterData) { - const text = `${data.domain} ${data.title}`.toLowerCase(); - const words = text.split(/\s+/).filter(w => w.length > 3); - - for (const word of words) { - wordFreq.set(word, (wordFreq.get(word) || 0) + 1); - } - } - - let bestWord = null; - let bestCount = 0; - - for (const [word, count] of wordFreq.entries()) { - if (count > 1 && count > bestCount) { - bestCount = count; - bestWord = word; - } - } - - if (bestWord) { - return bestWord.charAt(0).toUpperCase() + bestWord.slice(1); - } - - return this.#generateClusterLabel(clusterData[0].domain); - } - - /** - * Generate cluster label using local AI/ML - * - * @param {Array} clusterData - Array of tab data in the cluster - * @returns {Promise} - AI generated label or null - */ - async #generateAIClusterLabel(clusterData) { - if (!Services.prefs.getBoolPref('browser.ml.enable', false)) { - return null; - } - - try { - const { createEngine } = ChromeUtils.importESModule( - 'chrome://global/content/ml/EngineProcess.sys.mjs' - ); - - const tabDescriptions = clusterData - .map((data, idx) => { - let desc = `${idx + 1}. ${data.url}\n Title: ${data.title}`; - if (data.opener) { - desc += `\n ↳ Opened from: ${data.opener.domain} (${data.opener.title})`; - } - return desc; - }) - .join('\n\n'); - - const titles = clusterData.map(data => data.title).filter(t => t.length > 0); - const keywords = this.#extractKeywords(titles); - const domains = [...new Set(clusterData.map(d => d.domain))].join(', '); - - const prompt = `You are an expert organizer who creates concise, descriptive category names. - -I have a group of browser tabs that belong together. Please create a short, descriptive category name (1-3 words) for this group. - -Tabs in this group: -${tabDescriptions} - -Common domains: ${domains} -${keywords.length > 0 ? `Keywords in common: ${keywords.join(', ')}` : ''} - -Instructions: -- Look at the FULL URLs to understand the context (e.g., /shop/, /cars/, /docs/) -- Use the browsing history (↳ Opened from) to understand the user's intent -- Choose a GENERAL category name, not a specific brand name -- If tabs contain multiple brands (Nike, Adidas), use the category (e.g., "Sportswear" or "Athletic Brands") -- If tabs are car brands/manufacturers (Audi, Mercedes, Tesla), use "Automotive" or "Cars" -- If tabs are video platforms (YouTube, Vimeo), use "Video Platforms" or "Videos" -- If tabs are developer tools (GitHub, Jira, GitLab), use "Development" or "Dev Tools" -- Use the common theme or purpose, not individual site names -- Keep it 1-3 words maximum -- Capitalize properly - -Category name:`; - - const engine = await createEngine({ - taskName: 'text2text-generation', - modelId: 'Mozilla/smart-tab-topic', - modelHub: 'huggingface', - engineId: 'group-namer', - }); - - const aiResult = await engine.run({ - args: [prompt], - options: { - max_new_tokens: 10, - temperature: 0.5, - }, - }); - - let name = (aiResult[0]?.generated_text || '').split('\n')[0].trim(); - - if (!name || /none|adult content/i.test(name)) { - return null; - } - - name = this.#toTitleCase(name); - name = name - .replace(/^['"`]+|['"`]+$/g, '') - .replace(/[.?!,:;]+$/, '') - .replace(/^(category name:|name:)\s*/i, '') - .trim() - .slice(0, 30); - - if (name && name.length > 0) { - return name; - } - - return null; - } catch (error) { - console.warn('AI label generation failed:', error); - return null; - } - } - - /** - * Extract keywords from titles - */ - #extractKeywords(titles) { - const allWords = titles - .join(' ') - .toLowerCase() - .replace(/[^\w\s]/g, ' ') - .split(/\s+/) - .filter(w => w.length > 2); - - const wordCount = new Map(); - allWords.forEach(word => { - wordCount.set(word, (wordCount.get(word) || 0) + 1); - }); - - const stopWords = new Set([ - 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', - 'had', 'her', 'was', 'one', 'our', 'out', 'day', 'get', 'has', - 'him', 'his', 'how', 'man', 'new', 'now', 'old', 'see', 'two', - 'way', 'who', 'boy', 'did', 'its', 'let', 'put', 'say', 'she', - 'too', 'use', 'com', 'www', 'http', 'https', 'org' - ]); - - return Array.from(wordCount.entries()) - .filter(([word]) => !stopWords.has(word)) - .sort((a, b) => b[1] - a[1]) - .slice(0, 5) - .map(([word]) => word); - } - - /** - * Convert string to title case - */ - #toTitleCase(str) { - if (!str || typeof str !== 'string') return ''; - return str.toLowerCase().split(' ').map(word => - word.charAt(0).toUpperCase() + word.slice(1) - ).join(' '); - } - - /** - * Generate a readable label from domain name - * - * @param {string} domain - The domain name - * @returns {string} - A readable label - */ - #generateClusterLabel(domain) { - if (!domain) { - return 'Other'; - } - - let label = domain.split('.')[0]; - label = label.charAt(0).toUpperCase() + label.slice(1); - return label; - } - async contextDeleteWorkspace() { const workspaceId = this.#contextMenuData?.workspaceId || this.activeWorkspace; const [title, body] = await document.l10n.formatValues([ From 56176ef9b68ebd3f476bae69bc126e0d11a3a111 Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:34:33 +1100 Subject: [PATCH 08/12] feat: enhance vertical tabs UI with new separator styles and animations for grouping, now like arc has the squiggly line, removed the spinner --- .../themes/shared/zen-icons/jar.inc.mn | 1091 ++++++++--------- .../shared/zen-icons/lin/ZenGroupSpinner.svg | 17 - src/zen/tabs/zen-tabs/vertical-tabs.css | 42 + src/zen/workspaces/ZenWorkspace.mjs | 9 +- src/zen/workspaces/zen-workspaces.css | 10 - 5 files changed, 588 insertions(+), 581 deletions(-) delete mode 100644 src/browser/themes/shared/zen-icons/lin/ZenGroupSpinner.svg diff --git a/src/browser/themes/shared/zen-icons/jar.inc.mn b/src/browser/themes/shared/zen-icons/jar.inc.mn index fb6b66e71e..3c11db3f06 100644 --- a/src/browser/themes/shared/zen-icons/jar.inc.mn +++ b/src/browser/themes/shared/zen-icons/jar.inc.mn @@ -3,555 +3,552 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. #ifdef XP_WIN -* skin/classic/browser/zen-icons/accessibility.svg (../shared/zen-icons/lin/accessibility.svg) -* skin/classic/browser/zen-icons/add-to-dictionary.svg (../shared/zen-icons/lin/add-to-dictionary.svg) -* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/lin/arrow-down.svg) -* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/lin/arrow-left.svg) -* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/lin/arrow-right.svg) -* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/lin/arrow-up.svg) -* skin/classic/browser/zen-icons/audio-save.svg (../shared/zen-icons/lin/audio-save.svg) -* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/lin/autoplay-media-blocked.svg) -* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/lin/autoplay-media-fill.svg) -* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/lin/autoplay-media.svg) -* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/lin/back.svg) -* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/lin/bookmark-hollow.svg) -* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/lin/bookmark-star-on-tray.svg) -* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/lin/bookmark.svg) -* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/lin/camera-blocked.svg) -* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/lin/camera-fill.svg) -* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/lin/camera.svg) -* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/lin/canvas-blocked.svg) -* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/lin/canvas.svg) -* skin/classic/browser/zen-icons/checkmark.svg (../shared/zen-icons/lin/checkmark.svg) -* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/lin/chevron.svg) -* skin/classic/browser/zen-icons/close-all.svg (../shared/zen-icons/lin/close-all.svg) -* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/lin/close.svg) -* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/lin/container-tab.svg) -* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/lin/cookies-fill.svg) -* skin/classic/browser/zen-icons/customize.svg (../shared/zen-icons/lin/customize.svg) -* skin/classic/browser/zen-icons/dart-down.svg (../shared/zen-icons/lin/dart-down.svg) -* skin/classic/browser/zen-icons/desktop-notification-blocked.svg (../shared/zen-icons/lin/desktop-notification-blocked.svg) -* skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/lin/desktop-notification-fill.svg) -* skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/lin/desktop-notification.svg) -* skin/classic/browser/zen-icons/developer.svg (../shared/zen-icons/lin/developer.svg) -* skin/classic/browser/zen-icons/downloads.svg (../shared/zen-icons/lin/downloads.svg) -* skin/classic/browser/zen-icons/drag-indicator.svg (../shared/zen-icons/lin/drag-indicator.svg) -* skin/classic/browser/zen-icons/duplicate-tab.svg (../shared/zen-icons/lin/duplicate-tab.svg) -* skin/classic/browser/zen-icons/edit-copy.svg (../shared/zen-icons/lin/edit-copy.svg) -* skin/classic/browser/zen-icons/edit-cut.svg (../shared/zen-icons/lin/edit-cut.svg) -* skin/classic/browser/zen-icons/edit-delete.svg (../shared/zen-icons/lin/edit-delete.svg) -* skin/classic/browser/zen-icons/edit-paste.svg (../shared/zen-icons/lin/edit-paste.svg) -* skin/classic/browser/zen-icons/edit-redo.svg (../shared/zen-icons/lin/edit-redo.svg) -* skin/classic/browser/zen-icons/edit-select-all.svg (../shared/zen-icons/lin/edit-select-all.svg) -* skin/classic/browser/zen-icons/edit-theme.svg (../shared/zen-icons/lin/edit-theme.svg) -* skin/classic/browser/zen-icons/edit-undo.svg (../shared/zen-icons/lin/edit-undo.svg) -* skin/classic/browser/zen-icons/edit.svg (../shared/zen-icons/lin/edit.svg) -* skin/classic/browser/zen-icons/essential-add.svg (../shared/zen-icons/lin/essential-add.svg) -* skin/classic/browser/zen-icons/essential-remove.svg (../shared/zen-icons/lin/essential-remove.svg) -* skin/classic/browser/zen-icons/expand-sidebar.svg (../shared/zen-icons/lin/expand-sidebar.svg) -* skin/classic/browser/zen-icons/ext-link.svg (../shared/zen-icons/lin/ext-link.svg) -* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/lin/extension-blocked.svg) -* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/lin/extension-fill.svg) -* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/lin/extension.svg) -* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/lin/face-sun.svg) -* skin/classic/browser/zen-icons/firefox.svg (../shared/zen-icons/lin/firefox.svg) -* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/lin/folder.svg) -* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/lin/forget.svg) -* skin/classic/browser/zen-icons/forward.svg (../shared/zen-icons/lin/forward.svg) -* skin/classic/browser/zen-icons/fullscreen-exit.svg (../shared/zen-icons/lin/fullscreen-exit.svg) -* skin/classic/browser/zen-icons/fullscreen.svg (../shared/zen-icons/lin/fullscreen.svg) -* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/lin/geo-blocked.svg) -* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/lin/geo-fill.svg) -* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/lin/geo.svg) -* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/lin/help.svg) -* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/lin/history.svg) -* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/lin/home.svg) -* skin/classic/browser/zen-icons/image-copy.svg (../shared/zen-icons/lin/image-copy.svg) -* skin/classic/browser/zen-icons/image-open.svg (../shared/zen-icons/lin/image-open.svg) -* skin/classic/browser/zen-icons/image-save.svg (../shared/zen-icons/lin/image-save.svg) -* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/lin/info.svg) -* skin/classic/browser/zen-icons/inspect.svg (../shared/zen-icons/lin/inspect.svg) -* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/lin/library.svg) -* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/lin/link.svg) -* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/lin/mail.svg) -* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) -* skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) -* skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) -* skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) -* skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) -* skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) -* skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) -* skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) -* skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) -* skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) -* skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) -* skin/classic/browser/zen-icons/menu.svg (../shared/zen-icons/lin/menu.svg) -* skin/classic/browser/zen-icons/microphone-blocked-fill.svg (../shared/zen-icons/lin/microphone-blocked-fill.svg) -* skin/classic/browser/zen-icons/microphone-blocked.svg (../shared/zen-icons/lin/microphone-blocked.svg) -* skin/classic/browser/zen-icons/microphone-fill.svg (../shared/zen-icons/lin/microphone-fill.svg) -* skin/classic/browser/zen-icons/microphone.svg (../shared/zen-icons/lin/microphone.svg) -* skin/classic/browser/zen-icons/midi.svg (../shared/zen-icons/lin/midi.svg) -* skin/classic/browser/zen-icons/moon-stars.svg (../shared/zen-icons/lin/moon-stars.svg) -* skin/classic/browser/zen-icons/move-tab.svg (../shared/zen-icons/lin/move-tab.svg) -* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/lin/new-tab-image.svg) -* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/lin/open.svg) -* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/lin/page-portrait.svg) -* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/lin/palette.svg) -* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/lin/passwords.svg) -* skin/classic/browser/zen-icons/paste-and-go.svg (../shared/zen-icons/lin/paste-and-go.svg) -* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/lin/permissions-fill.svg) -* skin/classic/browser/zen-icons/permissions.svg (../shared/zen-icons/lin/permissions.svg) -* skin/classic/browser/zen-icons/persistent-storage-blocked.svg (../shared/zen-icons/lin/persistent-storage-blocked.svg) -* skin/classic/browser/zen-icons/persistent-storage-fill.svg (../shared/zen-icons/lin/persistent-storage-fill.svg) -* skin/classic/browser/zen-icons/persistent-storage.svg (../shared/zen-icons/lin/persistent-storage.svg) -* skin/classic/browser/zen-icons/pin.svg (../shared/zen-icons/lin/pin.svg) -* skin/classic/browser/zen-icons/plus.svg (../shared/zen-icons/lin/plus.svg) -* skin/classic/browser/zen-icons/popup-fill.svg (../shared/zen-icons/lin/popup-fill.svg) -* skin/classic/browser/zen-icons/popup.svg (../shared/zen-icons/lin/popup.svg) -* skin/classic/browser/zen-icons/print.svg (../shared/zen-icons/lin/print.svg) -* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/lin/private-window.svg) -* skin/classic/browser/zen-icons/privateBrowsing.svg (../shared/zen-icons/lin/privateBrowsing.svg) -* skin/classic/browser/zen-icons/reader-mode.svg (../shared/zen-icons/lin/reader-mode.svg) -* skin/classic/browser/zen-icons/reload.svg (../shared/zen-icons/lin/reload.svg) -* skin/classic/browser/zen-icons/report.svg (../shared/zen-icons/lin/report.svg) -* skin/classic/browser/zen-icons/save.svg (../shared/zen-icons/lin/save.svg) -* skin/classic/browser/zen-icons/screen-blocked.svg (../shared/zen-icons/lin/screen-blocked.svg) -* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/lin/screen.svg) -* skin/classic/browser/zen-icons/screenshot.svg (../shared/zen-icons/lin/screenshot.svg) -* skin/classic/browser/zen-icons/search-glass.svg (../shared/zen-icons/lin/search-glass.svg) -* skin/classic/browser/zen-icons/search-page.svg (../shared/zen-icons/lin/search-page.svg) -* skin/classic/browser/zen-icons/security-broken.svg (../shared/zen-icons/lin/security-broken.svg) -* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/lin/security-warning.svg) -* skin/classic/browser/zen-icons/security.svg (../shared/zen-icons/lin/security.svg) -* skin/classic/browser/zen-icons/send-to-device.svg (../shared/zen-icons/lin/send-to-device.svg) -* skin/classic/browser/zen-icons/settings-fill.svg (../shared/zen-icons/lin/settings-fill.svg) -* skin/classic/browser/zen-icons/settings.svg (../shared/zen-icons/lin/settings.svg) -* skin/classic/browser/zen-icons/share.svg (../shared/zen-icons/lin/share.svg) -* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/lin/sidebar-right.svg) -* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/lin/sidebar.svg) -* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/lin/sidebars-right.svg) -* skin/classic/browser/zen-icons/source-code.svg (../shared/zen-icons/lin/source-code.svg) -* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/lin/sparkles.svg) -* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/lin/spell-check.svg) -* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/lin/split.svg) -* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/lin/tab-audio-blocked-small.svg) -* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/lin/tab-audio-muted-small.svg) -* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/lin/tab-audio-playing-small.svg) -* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/lin/tab.svg) -* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/lin/tool-profiler.svg) -* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/lin/tracking-protection-fill.svg) -* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/lin/tracking-protection.svg) -* skin/classic/browser/zen-icons/translations.svg (../shared/zen-icons/lin/translations.svg) -* skin/classic/browser/zen-icons/trash.svg (../shared/zen-icons/lin/trash.svg) -* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/lin/unpin.svg) -* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/lin/video-blocked-fill.svg) -* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/lin/video-fill.svg) -* skin/classic/browser/zen-icons/video-open.svg (../shared/zen-icons/lin/video-open.svg) -* skin/classic/browser/zen-icons/video-save.svg (../shared/zen-icons/lin/video-save.svg) -* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/lin/window.svg) -* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/lin/xr-blocked.svg) -* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/lin/xr-fill.svg) -* skin/classic/browser/zen-icons/xr.svg (../shared/zen-icons/lin/xr.svg) -* skin/classic/browser/zen-icons/zoom-control.svg (../shared/zen-icons/lin/zoom-control.svg) -* skin/classic/browser/zen-icons/zoom-out.svg (../shared/zen-icons/lin/zoom-out.svg) -* skin/classic/browser/zen-icons/ZenGroup.svg (../shared/zen-icons/lin/ZenGroup.svg) -* skin/classic/browser/zen-icons/ZenGroupSpinner.svg (../shared/zen-icons/lin/ZenGroupSpinner.svg) +* skin/classic/browser/zen-icons/accessibility.svg (../shared/zen-icons/lin/accessibility.svg) +* skin/classic/browser/zen-icons/add-to-dictionary.svg (../shared/zen-icons/lin/add-to-dictionary.svg) +* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/lin/arrow-down.svg) +* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/lin/arrow-left.svg) +* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/lin/arrow-right.svg) +* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/lin/arrow-up.svg) +* skin/classic/browser/zen-icons/audio-save.svg (../shared/zen-icons/lin/audio-save.svg) +* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/lin/autoplay-media-blocked.svg) +* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/lin/autoplay-media-fill.svg) +* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/lin/autoplay-media.svg) +* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/lin/back.svg) +* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/lin/bookmark-hollow.svg) +* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/lin/bookmark-star-on-tray.svg) +* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/lin/bookmark.svg) +* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/lin/camera-blocked.svg) +* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/lin/camera-fill.svg) +* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/lin/camera.svg) +* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/lin/canvas-blocked.svg) +* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/lin/canvas.svg) +* skin/classic/browser/zen-icons/checkmark.svg (../shared/zen-icons/lin/checkmark.svg) +* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/lin/chevron.svg) +* skin/classic/browser/zen-icons/close-all.svg (../shared/zen-icons/lin/close-all.svg) +* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/lin/close.svg) +* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/lin/container-tab.svg) +* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/lin/cookies-fill.svg) +* skin/classic/browser/zen-icons/customize.svg (../shared/zen-icons/lin/customize.svg) +* skin/classic/browser/zen-icons/dart-down.svg (../shared/zen-icons/lin/dart-down.svg) +* skin/classic/browser/zen-icons/desktop-notification-blocked.svg (../shared/zen-icons/lin/desktop-notification-blocked.svg) +* skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/lin/desktop-notification-fill.svg) +* skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/lin/desktop-notification.svg) +* skin/classic/browser/zen-icons/developer.svg (../shared/zen-icons/lin/developer.svg) +* skin/classic/browser/zen-icons/downloads.svg (../shared/zen-icons/lin/downloads.svg) +* skin/classic/browser/zen-icons/drag-indicator.svg (../shared/zen-icons/lin/drag-indicator.svg) +* skin/classic/browser/zen-icons/duplicate-tab.svg (../shared/zen-icons/lin/duplicate-tab.svg) +* skin/classic/browser/zen-icons/edit-copy.svg (../shared/zen-icons/lin/edit-copy.svg) +* skin/classic/browser/zen-icons/edit-cut.svg (../shared/zen-icons/lin/edit-cut.svg) +* skin/classic/browser/zen-icons/edit-delete.svg (../shared/zen-icons/lin/edit-delete.svg) +* skin/classic/browser/zen-icons/edit-paste.svg (../shared/zen-icons/lin/edit-paste.svg) +* skin/classic/browser/zen-icons/edit-redo.svg (../shared/zen-icons/lin/edit-redo.svg) +* skin/classic/browser/zen-icons/edit-select-all.svg (../shared/zen-icons/lin/edit-select-all.svg) +* skin/classic/browser/zen-icons/edit-theme.svg (../shared/zen-icons/lin/edit-theme.svg) +* skin/classic/browser/zen-icons/edit-undo.svg (../shared/zen-icons/lin/edit-undo.svg) +* skin/classic/browser/zen-icons/edit.svg (../shared/zen-icons/lin/edit.svg) +* skin/classic/browser/zen-icons/essential-add.svg (../shared/zen-icons/lin/essential-add.svg) +* skin/classic/browser/zen-icons/essential-remove.svg (../shared/zen-icons/lin/essential-remove.svg) +* skin/classic/browser/zen-icons/expand-sidebar.svg (../shared/zen-icons/lin/expand-sidebar.svg) +* skin/classic/browser/zen-icons/ext-link.svg (../shared/zen-icons/lin/ext-link.svg) +* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/lin/extension-blocked.svg) +* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/lin/extension-fill.svg) +* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/lin/extension.svg) +* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/lin/face-sun.svg) +* skin/classic/browser/zen-icons/firefox.svg (../shared/zen-icons/lin/firefox.svg) +* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/lin/folder.svg) +* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/lin/forget.svg) +* skin/classic/browser/zen-icons/forward.svg (../shared/zen-icons/lin/forward.svg) +* skin/classic/browser/zen-icons/fullscreen-exit.svg (../shared/zen-icons/lin/fullscreen-exit.svg) +* skin/classic/browser/zen-icons/fullscreen.svg (../shared/zen-icons/lin/fullscreen.svg) +* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/lin/geo-blocked.svg) +* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/lin/geo-fill.svg) +* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/lin/geo.svg) +* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/lin/help.svg) +* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/lin/history.svg) +* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/lin/home.svg) +* skin/classic/browser/zen-icons/image-copy.svg (../shared/zen-icons/lin/image-copy.svg) +* skin/classic/browser/zen-icons/image-open.svg (../shared/zen-icons/lin/image-open.svg) +* skin/classic/browser/zen-icons/image-save.svg (../shared/zen-icons/lin/image-save.svg) +* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/lin/info.svg) +* skin/classic/browser/zen-icons/inspect.svg (../shared/zen-icons/lin/inspect.svg) +* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/lin/library.svg) +* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/lin/link.svg) +* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/lin/mail.svg) +* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) +* skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) +* skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) +* skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) +* skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) +* skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) +* skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) +* skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) +* skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) +* skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) +* skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) +* skin/classic/browser/zen-icons/menu.svg (../shared/zen-icons/lin/menu.svg) +* skin/classic/browser/zen-icons/microphone-blocked-fill.svg (../shared/zen-icons/lin/microphone-blocked-fill.svg) +* skin/classic/browser/zen-icons/microphone-blocked.svg (../shared/zen-icons/lin/microphone-blocked.svg) +* skin/classic/browser/zen-icons/microphone-fill.svg (../shared/zen-icons/lin/microphone-fill.svg) +* skin/classic/browser/zen-icons/microphone.svg (../shared/zen-icons/lin/microphone.svg) +* skin/classic/browser/zen-icons/midi.svg (../shared/zen-icons/lin/midi.svg) +* skin/classic/browser/zen-icons/moon-stars.svg (../shared/zen-icons/lin/moon-stars.svg) +* skin/classic/browser/zen-icons/move-tab.svg (../shared/zen-icons/lin/move-tab.svg) +* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/lin/new-tab-image.svg) +* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/lin/open.svg) +* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/lin/page-portrait.svg) +* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/lin/palette.svg) +* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/lin/passwords.svg) +* skin/classic/browser/zen-icons/paste-and-go.svg (../shared/zen-icons/lin/paste-and-go.svg) +* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/lin/permissions-fill.svg) +* skin/classic/browser/zen-icons/permissions.svg (../shared/zen-icons/lin/permissions.svg) +* skin/classic/browser/zen-icons/persistent-storage-blocked.svg (../shared/zen-icons/lin/persistent-storage-blocked.svg) +* skin/classic/browser/zen-icons/persistent-storage-fill.svg (../shared/zen-icons/lin/persistent-storage-fill.svg) +* skin/classic/browser/zen-icons/persistent-storage.svg (../shared/zen-icons/lin/persistent-storage.svg) +* skin/classic/browser/zen-icons/pin.svg (../shared/zen-icons/lin/pin.svg) +* skin/classic/browser/zen-icons/plus.svg (../shared/zen-icons/lin/plus.svg) +* skin/classic/browser/zen-icons/popup-fill.svg (../shared/zen-icons/lin/popup-fill.svg) +* skin/classic/browser/zen-icons/popup.svg (../shared/zen-icons/lin/popup.svg) +* skin/classic/browser/zen-icons/print.svg (../shared/zen-icons/lin/print.svg) +* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/lin/private-window.svg) +* skin/classic/browser/zen-icons/privateBrowsing.svg (../shared/zen-icons/lin/privateBrowsing.svg) +* skin/classic/browser/zen-icons/reader-mode.svg (../shared/zen-icons/lin/reader-mode.svg) +* skin/classic/browser/zen-icons/reload.svg (../shared/zen-icons/lin/reload.svg) +* skin/classic/browser/zen-icons/report.svg (../shared/zen-icons/lin/report.svg) +* skin/classic/browser/zen-icons/save.svg (../shared/zen-icons/lin/save.svg) +* skin/classic/browser/zen-icons/screen-blocked.svg (../shared/zen-icons/lin/screen-blocked.svg) +* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/lin/screen.svg) +* skin/classic/browser/zen-icons/screenshot.svg (../shared/zen-icons/lin/screenshot.svg) +* skin/classic/browser/zen-icons/search-glass.svg (../shared/zen-icons/lin/search-glass.svg) +* skin/classic/browser/zen-icons/search-page.svg (../shared/zen-icons/lin/search-page.svg) +* skin/classic/browser/zen-icons/security-broken.svg (../shared/zen-icons/lin/security-broken.svg) +* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/lin/security-warning.svg) +* skin/classic/browser/zen-icons/security.svg (../shared/zen-icons/lin/security.svg) +* skin/classic/browser/zen-icons/send-to-device.svg (../shared/zen-icons/lin/send-to-device.svg) +* skin/classic/browser/zen-icons/settings-fill.svg (../shared/zen-icons/lin/settings-fill.svg) +* skin/classic/browser/zen-icons/settings.svg (../shared/zen-icons/lin/settings.svg) +* skin/classic/browser/zen-icons/share.svg (../shared/zen-icons/lin/share.svg) +* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/lin/sidebar-right.svg) +* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/lin/sidebar.svg) +* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/lin/sidebars-right.svg) +* skin/classic/browser/zen-icons/source-code.svg (../shared/zen-icons/lin/source-code.svg) +* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/lin/sparkles.svg) +* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/lin/spell-check.svg) +* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/lin/split.svg) +* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/lin/tab-audio-blocked-small.svg) +* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/lin/tab-audio-muted-small.svg) +* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/lin/tab-audio-playing-small.svg) +* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/lin/tab.svg) +* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/lin/tool-profiler.svg) +* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/lin/tracking-protection-fill.svg) +* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/lin/tracking-protection.svg) +* skin/classic/browser/zen-icons/translations.svg (../shared/zen-icons/lin/translations.svg) +* skin/classic/browser/zen-icons/trash.svg (../shared/zen-icons/lin/trash.svg) +* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/lin/unpin.svg) +* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/lin/video-blocked-fill.svg) +* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/lin/video-fill.svg) +* skin/classic/browser/zen-icons/video-open.svg (../shared/zen-icons/lin/video-open.svg) +* skin/classic/browser/zen-icons/video-save.svg (../shared/zen-icons/lin/video-save.svg) +* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/lin/window.svg) +* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/lin/xr-blocked.svg) +* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/lin/xr-fill.svg) +* skin/classic/browser/zen-icons/xr.svg (../shared/zen-icons/lin/xr.svg) +* skin/classic/browser/zen-icons/zoom-control.svg (../shared/zen-icons/lin/zoom-control.svg) +* skin/classic/browser/zen-icons/zoom-out.svg (../shared/zen-icons/lin/zoom-out.svg) +* skin/classic/browser/zen-icons/ZenGroup.svg (../shared/zen-icons/lin/ZenGroup.svg) #endif #ifdef XP_MACOSX -* skin/classic/browser/zen-icons/accessibility.svg (../shared/zen-icons/lin/accessibility.svg) -* skin/classic/browser/zen-icons/add-to-dictionary.svg (../shared/zen-icons/lin/add-to-dictionary.svg) -* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/lin/arrow-down.svg) -* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/lin/arrow-left.svg) -* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/lin/arrow-right.svg) -* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/lin/arrow-up.svg) -* skin/classic/browser/zen-icons/audio-save.svg (../shared/zen-icons/lin/audio-save.svg) -* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/lin/autoplay-media-blocked.svg) -* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/lin/autoplay-media-fill.svg) -* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/lin/autoplay-media.svg) -* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/lin/back.svg) -* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/lin/bookmark-hollow.svg) -* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/lin/bookmark-star-on-tray.svg) -* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/lin/bookmark.svg) -* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/lin/camera-blocked.svg) -* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/lin/camera-fill.svg) -* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/lin/camera.svg) -* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/lin/canvas-blocked.svg) -* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/lin/canvas.svg) -* skin/classic/browser/zen-icons/checkmark.svg (../shared/zen-icons/lin/checkmark.svg) -* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/lin/chevron.svg) -* skin/classic/browser/zen-icons/close-all.svg (../shared/zen-icons/lin/close-all.svg) -* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/lin/close.svg) -* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/lin/container-tab.svg) -* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/lin/cookies-fill.svg) -* skin/classic/browser/zen-icons/customize.svg (../shared/zen-icons/lin/customize.svg) -* skin/classic/browser/zen-icons/dart-down.svg (../shared/zen-icons/lin/dart-down.svg) -* skin/classic/browser/zen-icons/desktop-notification-blocked.svg (../shared/zen-icons/lin/desktop-notification-blocked.svg) -* skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/lin/desktop-notification-fill.svg) -* skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/lin/desktop-notification.svg) -* skin/classic/browser/zen-icons/developer.svg (../shared/zen-icons/lin/developer.svg) -* skin/classic/browser/zen-icons/downloads.svg (../shared/zen-icons/lin/downloads.svg) -* skin/classic/browser/zen-icons/drag-indicator.svg (../shared/zen-icons/lin/drag-indicator.svg) -* skin/classic/browser/zen-icons/duplicate-tab.svg (../shared/zen-icons/lin/duplicate-tab.svg) -* skin/classic/browser/zen-icons/edit-copy.svg (../shared/zen-icons/lin/edit-copy.svg) -* skin/classic/browser/zen-icons/edit-cut.svg (../shared/zen-icons/lin/edit-cut.svg) -* skin/classic/browser/zen-icons/edit-delete.svg (../shared/zen-icons/lin/edit-delete.svg) -* skin/classic/browser/zen-icons/edit-paste.svg (../shared/zen-icons/lin/edit-paste.svg) -* skin/classic/browser/zen-icons/edit-redo.svg (../shared/zen-icons/lin/edit-redo.svg) -* skin/classic/browser/zen-icons/edit-select-all.svg (../shared/zen-icons/lin/edit-select-all.svg) -* skin/classic/browser/zen-icons/edit-theme.svg (../shared/zen-icons/lin/edit-theme.svg) -* skin/classic/browser/zen-icons/edit-undo.svg (../shared/zen-icons/lin/edit-undo.svg) -* skin/classic/browser/zen-icons/edit.svg (../shared/zen-icons/lin/edit.svg) -* skin/classic/browser/zen-icons/essential-add.svg (../shared/zen-icons/lin/essential-add.svg) -* skin/classic/browser/zen-icons/essential-remove.svg (../shared/zen-icons/lin/essential-remove.svg) -* skin/classic/browser/zen-icons/expand-sidebar.svg (../shared/zen-icons/lin/expand-sidebar.svg) -* skin/classic/browser/zen-icons/ext-link.svg (../shared/zen-icons/lin/ext-link.svg) -* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/lin/extension-blocked.svg) -* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/lin/extension-fill.svg) -* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/lin/extension.svg) -* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/lin/face-sun.svg) -* skin/classic/browser/zen-icons/firefox.svg (../shared/zen-icons/lin/firefox.svg) -* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/lin/folder.svg) -* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/lin/forget.svg) -* skin/classic/browser/zen-icons/forward.svg (../shared/zen-icons/lin/forward.svg) -* skin/classic/browser/zen-icons/fullscreen-exit.svg (../shared/zen-icons/lin/fullscreen-exit.svg) -* skin/classic/browser/zen-icons/fullscreen.svg (../shared/zen-icons/lin/fullscreen.svg) -* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/lin/geo-blocked.svg) -* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/lin/geo-fill.svg) -* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/lin/geo.svg) -* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/lin/help.svg) -* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/lin/history.svg) -* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/lin/home.svg) -* skin/classic/browser/zen-icons/image-copy.svg (../shared/zen-icons/lin/image-copy.svg) -* skin/classic/browser/zen-icons/image-open.svg (../shared/zen-icons/lin/image-open.svg) -* skin/classic/browser/zen-icons/image-save.svg (../shared/zen-icons/lin/image-save.svg) -* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/lin/info.svg) -* skin/classic/browser/zen-icons/inspect.svg (../shared/zen-icons/lin/inspect.svg) -* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/lin/library.svg) -* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/lin/link.svg) -* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/lin/mail.svg) -* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) -* skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) -* skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) -* skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) -* skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) -* skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) -* skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) -* skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) -* skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) -* skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) -* skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) -* skin/classic/browser/zen-icons/menu.svg (../shared/zen-icons/lin/menu.svg) -* skin/classic/browser/zen-icons/microphone-blocked-fill.svg (../shared/zen-icons/lin/microphone-blocked-fill.svg) -* skin/classic/browser/zen-icons/microphone-blocked.svg (../shared/zen-icons/lin/microphone-blocked.svg) -* skin/classic/browser/zen-icons/microphone-fill.svg (../shared/zen-icons/lin/microphone-fill.svg) -* skin/classic/browser/zen-icons/microphone.svg (../shared/zen-icons/lin/microphone.svg) -* skin/classic/browser/zen-icons/midi.svg (../shared/zen-icons/lin/midi.svg) -* skin/classic/browser/zen-icons/moon-stars.svg (../shared/zen-icons/lin/moon-stars.svg) -* skin/classic/browser/zen-icons/move-tab.svg (../shared/zen-icons/lin/move-tab.svg) -* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/lin/new-tab-image.svg) -* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/lin/open.svg) -* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/lin/page-portrait.svg) -* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/lin/palette.svg) -* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/lin/passwords.svg) -* skin/classic/browser/zen-icons/paste-and-go.svg (../shared/zen-icons/lin/paste-and-go.svg) -* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/lin/permissions-fill.svg) -* skin/classic/browser/zen-icons/permissions.svg (../shared/zen-icons/lin/permissions.svg) -* skin/classic/browser/zen-icons/persistent-storage-blocked.svg (../shared/zen-icons/lin/persistent-storage-blocked.svg) -* skin/classic/browser/zen-icons/persistent-storage-fill.svg (../shared/zen-icons/lin/persistent-storage-fill.svg) -* skin/classic/browser/zen-icons/persistent-storage.svg (../shared/zen-icons/lin/persistent-storage.svg) -* skin/classic/browser/zen-icons/pin.svg (../shared/zen-icons/lin/pin.svg) -* skin/classic/browser/zen-icons/plus.svg (../shared/zen-icons/lin/plus.svg) -* skin/classic/browser/zen-icons/popup-fill.svg (../shared/zen-icons/lin/popup-fill.svg) -* skin/classic/browser/zen-icons/popup.svg (../shared/zen-icons/lin/popup.svg) -* skin/classic/browser/zen-icons/print.svg (../shared/zen-icons/lin/print.svg) -* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/lin/private-window.svg) -* skin/classic/browser/zen-icons/privateBrowsing.svg (../shared/zen-icons/lin/privateBrowsing.svg) -* skin/classic/browser/zen-icons/reader-mode.svg (../shared/zen-icons/lin/reader-mode.svg) -* skin/classic/browser/zen-icons/reload.svg (../shared/zen-icons/lin/reload.svg) -* skin/classic/browser/zen-icons/report.svg (../shared/zen-icons/lin/report.svg) -* skin/classic/browser/zen-icons/save.svg (../shared/zen-icons/lin/save.svg) -* skin/classic/browser/zen-icons/screen-blocked.svg (../shared/zen-icons/lin/screen-blocked.svg) -* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/lin/screen.svg) -* skin/classic/browser/zen-icons/screenshot.svg (../shared/zen-icons/lin/screenshot.svg) -* skin/classic/browser/zen-icons/search-glass.svg (../shared/zen-icons/lin/search-glass.svg) -* skin/classic/browser/zen-icons/search-page.svg (../shared/zen-icons/lin/search-page.svg) -* skin/classic/browser/zen-icons/security-broken.svg (../shared/zen-icons/lin/security-broken.svg) -* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/lin/security-warning.svg) -* skin/classic/browser/zen-icons/security.svg (../shared/zen-icons/lin/security.svg) -* skin/classic/browser/zen-icons/send-to-device.svg (../shared/zen-icons/lin/send-to-device.svg) -* skin/classic/browser/zen-icons/settings-fill.svg (../shared/zen-icons/lin/settings-fill.svg) -* skin/classic/browser/zen-icons/settings.svg (../shared/zen-icons/lin/settings.svg) -* skin/classic/browser/zen-icons/share.svg (../shared/zen-icons/lin/share.svg) -* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/lin/sidebar-right.svg) -* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/lin/sidebar.svg) -* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/lin/sidebars-right.svg) -* skin/classic/browser/zen-icons/source-code.svg (../shared/zen-icons/lin/source-code.svg) -* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/lin/sparkles.svg) -* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/lin/spell-check.svg) -* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/lin/split.svg) -* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/lin/tab-audio-blocked-small.svg) -* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/lin/tab-audio-muted-small.svg) -* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/lin/tab-audio-playing-small.svg) -* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/lin/tab.svg) -* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/lin/tool-profiler.svg) -* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/lin/tracking-protection-fill.svg) -* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/lin/tracking-protection.svg) -* skin/classic/browser/zen-icons/translations.svg (../shared/zen-icons/lin/translations.svg) -* skin/classic/browser/zen-icons/trash.svg (../shared/zen-icons/lin/trash.svg) -* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/lin/unpin.svg) -* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/lin/video-blocked-fill.svg) -* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/lin/video-fill.svg) -* skin/classic/browser/zen-icons/video-open.svg (../shared/zen-icons/lin/video-open.svg) -* skin/classic/browser/zen-icons/video-save.svg (../shared/zen-icons/lin/video-save.svg) -* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/lin/window.svg) -* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/lin/xr-blocked.svg) -* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/lin/xr-fill.svg) -* skin/classic/browser/zen-icons/xr.svg (../shared/zen-icons/lin/xr.svg) -* skin/classic/browser/zen-icons/zoom-control.svg (../shared/zen-icons/lin/zoom-control.svg) -* skin/classic/browser/zen-icons/zoom-out.svg (../shared/zen-icons/lin/zoom-out.svg) -* skin/classic/browser/zen-icons/ZenGroup.svg (../shared/zen-icons/lin/ZenGroup.svg) -* skin/classic/browser/zen-icons/ZenGroupSpinner.svg (../shared/zen-icons/lin/ZenGroupSpinner.svg) +* skin/classic/browser/zen-icons/accessibility.svg (../shared/zen-icons/lin/accessibility.svg) +* skin/classic/browser/zen-icons/add-to-dictionary.svg (../shared/zen-icons/lin/add-to-dictionary.svg) +* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/lin/arrow-down.svg) +* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/lin/arrow-left.svg) +* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/lin/arrow-right.svg) +* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/lin/arrow-up.svg) +* skin/classic/browser/zen-icons/audio-save.svg (../shared/zen-icons/lin/audio-save.svg) +* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/lin/autoplay-media-blocked.svg) +* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/lin/autoplay-media-fill.svg) +* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/lin/autoplay-media.svg) +* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/lin/back.svg) +* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/lin/bookmark-hollow.svg) +* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/lin/bookmark-star-on-tray.svg) +* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/lin/bookmark.svg) +* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/lin/camera-blocked.svg) +* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/lin/camera-fill.svg) +* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/lin/camera.svg) +* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/lin/canvas-blocked.svg) +* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/lin/canvas.svg) +* skin/classic/browser/zen-icons/checkmark.svg (../shared/zen-icons/lin/checkmark.svg) +* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/lin/chevron.svg) +* skin/classic/browser/zen-icons/close-all.svg (../shared/zen-icons/lin/close-all.svg) +* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/lin/close.svg) +* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/lin/container-tab.svg) +* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/lin/cookies-fill.svg) +* skin/classic/browser/zen-icons/customize.svg (../shared/zen-icons/lin/customize.svg) +* skin/classic/browser/zen-icons/dart-down.svg (../shared/zen-icons/lin/dart-down.svg) +* skin/classic/browser/zen-icons/desktop-notification-blocked.svg (../shared/zen-icons/lin/desktop-notification-blocked.svg) +* skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/lin/desktop-notification-fill.svg) +* skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/lin/desktop-notification.svg) +* skin/classic/browser/zen-icons/developer.svg (../shared/zen-icons/lin/developer.svg) +* skin/classic/browser/zen-icons/downloads.svg (../shared/zen-icons/lin/downloads.svg) +* skin/classic/browser/zen-icons/drag-indicator.svg (../shared/zen-icons/lin/drag-indicator.svg) +* skin/classic/browser/zen-icons/duplicate-tab.svg (../shared/zen-icons/lin/duplicate-tab.svg) +* skin/classic/browser/zen-icons/edit-copy.svg (../shared/zen-icons/lin/edit-copy.svg) +* skin/classic/browser/zen-icons/edit-cut.svg (../shared/zen-icons/lin/edit-cut.svg) +* skin/classic/browser/zen-icons/edit-delete.svg (../shared/zen-icons/lin/edit-delete.svg) +* skin/classic/browser/zen-icons/edit-paste.svg (../shared/zen-icons/lin/edit-paste.svg) +* skin/classic/browser/zen-icons/edit-redo.svg (../shared/zen-icons/lin/edit-redo.svg) +* skin/classic/browser/zen-icons/edit-select-all.svg (../shared/zen-icons/lin/edit-select-all.svg) +* skin/classic/browser/zen-icons/edit-theme.svg (../shared/zen-icons/lin/edit-theme.svg) +* skin/classic/browser/zen-icons/edit-undo.svg (../shared/zen-icons/lin/edit-undo.svg) +* skin/classic/browser/zen-icons/edit.svg (../shared/zen-icons/lin/edit.svg) +* skin/classic/browser/zen-icons/essential-add.svg (../shared/zen-icons/lin/essential-add.svg) +* skin/classic/browser/zen-icons/essential-remove.svg (../shared/zen-icons/lin/essential-remove.svg) +* skin/classic/browser/zen-icons/expand-sidebar.svg (../shared/zen-icons/lin/expand-sidebar.svg) +* skin/classic/browser/zen-icons/ext-link.svg (../shared/zen-icons/lin/ext-link.svg) +* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/lin/extension-blocked.svg) +* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/lin/extension-fill.svg) +* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/lin/extension.svg) +* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/lin/face-sun.svg) +* skin/classic/browser/zen-icons/firefox.svg (../shared/zen-icons/lin/firefox.svg) +* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/lin/folder.svg) +* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/lin/forget.svg) +* skin/classic/browser/zen-icons/forward.svg (../shared/zen-icons/lin/forward.svg) +* skin/classic/browser/zen-icons/fullscreen-exit.svg (../shared/zen-icons/lin/fullscreen-exit.svg) +* skin/classic/browser/zen-icons/fullscreen.svg (../shared/zen-icons/lin/fullscreen.svg) +* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/lin/geo-blocked.svg) +* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/lin/geo-fill.svg) +* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/lin/geo.svg) +* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/lin/help.svg) +* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/lin/history.svg) +* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/lin/home.svg) +* skin/classic/browser/zen-icons/image-copy.svg (../shared/zen-icons/lin/image-copy.svg) +* skin/classic/browser/zen-icons/image-open.svg (../shared/zen-icons/lin/image-open.svg) +* skin/classic/browser/zen-icons/image-save.svg (../shared/zen-icons/lin/image-save.svg) +* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/lin/info.svg) +* skin/classic/browser/zen-icons/inspect.svg (../shared/zen-icons/lin/inspect.svg) +* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/lin/library.svg) +* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/lin/link.svg) +* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/lin/mail.svg) +* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) +* skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) +* skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) +* skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) +* skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) +* skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) +* skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) +* skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) +* skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) +* skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) +* skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) +* skin/classic/browser/zen-icons/menu.svg (../shared/zen-icons/lin/menu.svg) +* skin/classic/browser/zen-icons/microphone-blocked-fill.svg (../shared/zen-icons/lin/microphone-blocked-fill.svg) +* skin/classic/browser/zen-icons/microphone-blocked.svg (../shared/zen-icons/lin/microphone-blocked.svg) +* skin/classic/browser/zen-icons/microphone-fill.svg (../shared/zen-icons/lin/microphone-fill.svg) +* skin/classic/browser/zen-icons/microphone.svg (../shared/zen-icons/lin/microphone.svg) +* skin/classic/browser/zen-icons/midi.svg (../shared/zen-icons/lin/midi.svg) +* skin/classic/browser/zen-icons/moon-stars.svg (../shared/zen-icons/lin/moon-stars.svg) +* skin/classic/browser/zen-icons/move-tab.svg (../shared/zen-icons/lin/move-tab.svg) +* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/lin/new-tab-image.svg) +* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/lin/open.svg) +* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/lin/page-portrait.svg) +* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/lin/palette.svg) +* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/lin/passwords.svg) +* skin/classic/browser/zen-icons/paste-and-go.svg (../shared/zen-icons/lin/paste-and-go.svg) +* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/lin/permissions-fill.svg) +* skin/classic/browser/zen-icons/permissions.svg (../shared/zen-icons/lin/permissions.svg) +* skin/classic/browser/zen-icons/persistent-storage-blocked.svg (../shared/zen-icons/lin/persistent-storage-blocked.svg) +* skin/classic/browser/zen-icons/persistent-storage-fill.svg (../shared/zen-icons/lin/persistent-storage-fill.svg) +* skin/classic/browser/zen-icons/persistent-storage.svg (../shared/zen-icons/lin/persistent-storage.svg) +* skin/classic/browser/zen-icons/pin.svg (../shared/zen-icons/lin/pin.svg) +* skin/classic/browser/zen-icons/plus.svg (../shared/zen-icons/lin/plus.svg) +* skin/classic/browser/zen-icons/popup-fill.svg (../shared/zen-icons/lin/popup-fill.svg) +* skin/classic/browser/zen-icons/popup.svg (../shared/zen-icons/lin/popup.svg) +* skin/classic/browser/zen-icons/print.svg (../shared/zen-icons/lin/print.svg) +* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/lin/private-window.svg) +* skin/classic/browser/zen-icons/privateBrowsing.svg (../shared/zen-icons/lin/privateBrowsing.svg) +* skin/classic/browser/zen-icons/reader-mode.svg (../shared/zen-icons/lin/reader-mode.svg) +* skin/classic/browser/zen-icons/reload.svg (../shared/zen-icons/lin/reload.svg) +* skin/classic/browser/zen-icons/report.svg (../shared/zen-icons/lin/report.svg) +* skin/classic/browser/zen-icons/save.svg (../shared/zen-icons/lin/save.svg) +* skin/classic/browser/zen-icons/screen-blocked.svg (../shared/zen-icons/lin/screen-blocked.svg) +* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/lin/screen.svg) +* skin/classic/browser/zen-icons/screenshot.svg (../shared/zen-icons/lin/screenshot.svg) +* skin/classic/browser/zen-icons/search-glass.svg (../shared/zen-icons/lin/search-glass.svg) +* skin/classic/browser/zen-icons/search-page.svg (../shared/zen-icons/lin/search-page.svg) +* skin/classic/browser/zen-icons/security-broken.svg (../shared/zen-icons/lin/security-broken.svg) +* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/lin/security-warning.svg) +* skin/classic/browser/zen-icons/security.svg (../shared/zen-icons/lin/security.svg) +* skin/classic/browser/zen-icons/send-to-device.svg (../shared/zen-icons/lin/send-to-device.svg) +* skin/classic/browser/zen-icons/settings-fill.svg (../shared/zen-icons/lin/settings-fill.svg) +* skin/classic/browser/zen-icons/settings.svg (../shared/zen-icons/lin/settings.svg) +* skin/classic/browser/zen-icons/share.svg (../shared/zen-icons/lin/share.svg) +* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/lin/sidebar-right.svg) +* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/lin/sidebar.svg) +* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/lin/sidebars-right.svg) +* skin/classic/browser/zen-icons/source-code.svg (../shared/zen-icons/lin/source-code.svg) +* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/lin/sparkles.svg) +* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/lin/spell-check.svg) +* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/lin/split.svg) +* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/lin/tab-audio-blocked-small.svg) +* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/lin/tab-audio-muted-small.svg) +* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/lin/tab-audio-playing-small.svg) +* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/lin/tab.svg) +* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/lin/tool-profiler.svg) +* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/lin/tracking-protection-fill.svg) +* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/lin/tracking-protection.svg) +* skin/classic/browser/zen-icons/translations.svg (../shared/zen-icons/lin/translations.svg) +* skin/classic/browser/zen-icons/trash.svg (../shared/zen-icons/lin/trash.svg) +* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/lin/unpin.svg) +* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/lin/video-blocked-fill.svg) +* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/lin/video-fill.svg) +* skin/classic/browser/zen-icons/video-open.svg (../shared/zen-icons/lin/video-open.svg) +* skin/classic/browser/zen-icons/video-save.svg (../shared/zen-icons/lin/video-save.svg) +* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/lin/window.svg) +* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/lin/xr-blocked.svg) +* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/lin/xr-fill.svg) +* skin/classic/browser/zen-icons/xr.svg (../shared/zen-icons/lin/xr.svg) +* skin/classic/browser/zen-icons/zoom-control.svg (../shared/zen-icons/lin/zoom-control.svg) +* skin/classic/browser/zen-icons/zoom-out.svg (../shared/zen-icons/lin/zoom-out.svg) +* skin/classic/browser/zen-icons/ZenGroup.svg (../shared/zen-icons/lin/ZenGroup.svg) #endif #ifdef XP_LINUX -* skin/classic/browser/zen-icons/accessibility.svg (../shared/zen-icons/lin/accessibility.svg) -* skin/classic/browser/zen-icons/add-to-dictionary.svg (../shared/zen-icons/lin/add-to-dictionary.svg) -* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/lin/arrow-down.svg) -* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/lin/arrow-left.svg) -* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/lin/arrow-right.svg) -* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/lin/arrow-up.svg) -* skin/classic/browser/zen-icons/audio-save.svg (../shared/zen-icons/lin/audio-save.svg) -* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/lin/autoplay-media-blocked.svg) -* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/lin/autoplay-media-fill.svg) -* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/lin/autoplay-media.svg) -* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/lin/back.svg) -* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/lin/bookmark-hollow.svg) -* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/lin/bookmark-star-on-tray.svg) -* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/lin/bookmark.svg) -* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/lin/camera-blocked.svg) -* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/lin/camera-fill.svg) -* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/lin/camera.svg) -* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/lin/canvas-blocked.svg) -* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/lin/canvas.svg) -* skin/classic/browser/zen-icons/checkmark.svg (../shared/zen-icons/lin/checkmark.svg) -* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/lin/chevron.svg) -* skin/classic/browser/zen-icons/close-all.svg (../shared/zen-icons/lin/close-all.svg) -* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/lin/close.svg) -* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/lin/container-tab.svg) -* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/lin/cookies-fill.svg) -* skin/classic/browser/zen-icons/customize.svg (../shared/zen-icons/lin/customize.svg) -* skin/classic/browser/zen-icons/dart-down.svg (../shared/zen-icons/lin/dart-down.svg) -* skin/classic/browser/zen-icons/desktop-notification-blocked.svg (../shared/zen-icons/lin/desktop-notification-blocked.svg) -* skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/lin/desktop-notification-fill.svg) -* skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/lin/desktop-notification.svg) -* skin/classic/browser/zen-icons/developer.svg (../shared/zen-icons/lin/developer.svg) -* skin/classic/browser/zen-icons/downloads.svg (../shared/zen-icons/lin/downloads.svg) -* skin/classic/browser/zen-icons/drag-indicator.svg (../shared/zen-icons/lin/drag-indicator.svg) -* skin/classic/browser/zen-icons/duplicate-tab.svg (../shared/zen-icons/lin/duplicate-tab.svg) -* skin/classic/browser/zen-icons/edit-copy.svg (../shared/zen-icons/lin/edit-copy.svg) -* skin/classic/browser/zen-icons/edit-cut.svg (../shared/zen-icons/lin/edit-cut.svg) -* skin/classic/browser/zen-icons/edit-delete.svg (../shared/zen-icons/lin/edit-delete.svg) -* skin/classic/browser/zen-icons/edit-paste.svg (../shared/zen-icons/lin/edit-paste.svg) -* skin/classic/browser/zen-icons/edit-redo.svg (../shared/zen-icons/lin/edit-redo.svg) -* skin/classic/browser/zen-icons/edit-select-all.svg (../shared/zen-icons/lin/edit-select-all.svg) -* skin/classic/browser/zen-icons/edit-theme.svg (../shared/zen-icons/lin/edit-theme.svg) -* skin/classic/browser/zen-icons/edit-undo.svg (../shared/zen-icons/lin/edit-undo.svg) -* skin/classic/browser/zen-icons/edit.svg (../shared/zen-icons/lin/edit.svg) -* skin/classic/browser/zen-icons/essential-add.svg (../shared/zen-icons/lin/essential-add.svg) -* skin/classic/browser/zen-icons/essential-remove.svg (../shared/zen-icons/lin/essential-remove.svg) -* skin/classic/browser/zen-icons/expand-sidebar.svg (../shared/zen-icons/lin/expand-sidebar.svg) -* skin/classic/browser/zen-icons/ext-link.svg (../shared/zen-icons/lin/ext-link.svg) -* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/lin/extension-blocked.svg) -* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/lin/extension-fill.svg) -* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/lin/extension.svg) -* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/lin/face-sun.svg) -* skin/classic/browser/zen-icons/firefox.svg (../shared/zen-icons/lin/firefox.svg) -* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/lin/folder.svg) -* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/lin/forget.svg) -* skin/classic/browser/zen-icons/forward.svg (../shared/zen-icons/lin/forward.svg) -* skin/classic/browser/zen-icons/fullscreen-exit.svg (../shared/zen-icons/lin/fullscreen-exit.svg) -* skin/classic/browser/zen-icons/fullscreen.svg (../shared/zen-icons/lin/fullscreen.svg) -* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/lin/geo-blocked.svg) -* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/lin/geo-fill.svg) -* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/lin/geo.svg) -* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/lin/help.svg) -* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/lin/history.svg) -* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/lin/home.svg) -* skin/classic/browser/zen-icons/image-copy.svg (../shared/zen-icons/lin/image-copy.svg) -* skin/classic/browser/zen-icons/image-open.svg (../shared/zen-icons/lin/image-open.svg) -* skin/classic/browser/zen-icons/image-save.svg (../shared/zen-icons/lin/image-save.svg) -* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/lin/info.svg) -* skin/classic/browser/zen-icons/inspect.svg (../shared/zen-icons/lin/inspect.svg) -* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/lin/library.svg) -* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/lin/link.svg) -* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/lin/mail.svg) -* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) -* skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) -* skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) -* skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) -* skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) -* skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) -* skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) -* skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) -* skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) -* skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) -* skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) -* skin/classic/browser/zen-icons/menu.svg (../shared/zen-icons/lin/menu.svg) -* skin/classic/browser/zen-icons/microphone-blocked-fill.svg (../shared/zen-icons/lin/microphone-blocked-fill.svg) -* skin/classic/browser/zen-icons/microphone-blocked.svg (../shared/zen-icons/lin/microphone-blocked.svg) -* skin/classic/browser/zen-icons/microphone-fill.svg (../shared/zen-icons/lin/microphone-fill.svg) -* skin/classic/browser/zen-icons/microphone.svg (../shared/zen-icons/lin/microphone.svg) -* skin/classic/browser/zen-icons/midi.svg (../shared/zen-icons/lin/midi.svg) -* skin/classic/browser/zen-icons/moon-stars.svg (../shared/zen-icons/lin/moon-stars.svg) -* skin/classic/browser/zen-icons/move-tab.svg (../shared/zen-icons/lin/move-tab.svg) -* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/lin/new-tab-image.svg) -* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/lin/open.svg) -* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/lin/page-portrait.svg) -* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/lin/palette.svg) -* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/lin/passwords.svg) -* skin/classic/browser/zen-icons/paste-and-go.svg (../shared/zen-icons/lin/paste-and-go.svg) -* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/lin/permissions-fill.svg) -* skin/classic/browser/zen-icons/permissions.svg (../shared/zen-icons/lin/permissions.svg) -* skin/classic/browser/zen-icons/persistent-storage-blocked.svg (../shared/zen-icons/lin/persistent-storage-blocked.svg) -* skin/classic/browser/zen-icons/persistent-storage-fill.svg (../shared/zen-icons/lin/persistent-storage-fill.svg) -* skin/classic/browser/zen-icons/persistent-storage.svg (../shared/zen-icons/lin/persistent-storage.svg) -* skin/classic/browser/zen-icons/pin.svg (../shared/zen-icons/lin/pin.svg) -* skin/classic/browser/zen-icons/plus.svg (../shared/zen-icons/lin/plus.svg) -* skin/classic/browser/zen-icons/popup-fill.svg (../shared/zen-icons/lin/popup-fill.svg) -* skin/classic/browser/zen-icons/popup.svg (../shared/zen-icons/lin/popup.svg) -* skin/classic/browser/zen-icons/print.svg (../shared/zen-icons/lin/print.svg) -* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/lin/private-window.svg) -* skin/classic/browser/zen-icons/privateBrowsing.svg (../shared/zen-icons/lin/privateBrowsing.svg) -* skin/classic/browser/zen-icons/reader-mode.svg (../shared/zen-icons/lin/reader-mode.svg) -* skin/classic/browser/zen-icons/reload.svg (../shared/zen-icons/lin/reload.svg) -* skin/classic/browser/zen-icons/report.svg (../shared/zen-icons/lin/report.svg) -* skin/classic/browser/zen-icons/save.svg (../shared/zen-icons/lin/save.svg) -* skin/classic/browser/zen-icons/screen-blocked.svg (../shared/zen-icons/lin/screen-blocked.svg) -* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/lin/screen.svg) -* skin/classic/browser/zen-icons/screenshot.svg (../shared/zen-icons/lin/screenshot.svg) -* skin/classic/browser/zen-icons/search-glass.svg (../shared/zen-icons/lin/search-glass.svg) -* skin/classic/browser/zen-icons/search-page.svg (../shared/zen-icons/lin/search-page.svg) -* skin/classic/browser/zen-icons/security-broken.svg (../shared/zen-icons/lin/security-broken.svg) -* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/lin/security-warning.svg) -* skin/classic/browser/zen-icons/security.svg (../shared/zen-icons/lin/security.svg) -* skin/classic/browser/zen-icons/send-to-device.svg (../shared/zen-icons/lin/send-to-device.svg) -* skin/classic/browser/zen-icons/settings-fill.svg (../shared/zen-icons/lin/settings-fill.svg) -* skin/classic/browser/zen-icons/settings.svg (../shared/zen-icons/lin/settings.svg) -* skin/classic/browser/zen-icons/share.svg (../shared/zen-icons/lin/share.svg) -* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/lin/sidebar-right.svg) -* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/lin/sidebar.svg) -* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/lin/sidebars-right.svg) -* skin/classic/browser/zen-icons/source-code.svg (../shared/zen-icons/lin/source-code.svg) -* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/lin/sparkles.svg) -* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/lin/spell-check.svg) -* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/lin/split.svg) -* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/lin/tab-audio-blocked-small.svg) -* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/lin/tab-audio-muted-small.svg) -* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/lin/tab-audio-playing-small.svg) -* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/lin/tab.svg) -* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/lin/tool-profiler.svg) -* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/lin/tracking-protection-fill.svg) -* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/lin/tracking-protection.svg) -* skin/classic/browser/zen-icons/translations.svg (../shared/zen-icons/lin/translations.svg) -* skin/classic/browser/zen-icons/trash.svg (../shared/zen-icons/lin/trash.svg) -* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/lin/unpin.svg) -* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/lin/video-blocked-fill.svg) -* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/lin/video-fill.svg) -* skin/classic/browser/zen-icons/video-open.svg (../shared/zen-icons/lin/video-open.svg) -* skin/classic/browser/zen-icons/video-save.svg (../shared/zen-icons/lin/video-save.svg) -* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/lin/window.svg) -* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/lin/xr-blocked.svg) -* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/lin/xr-fill.svg) -* skin/classic/browser/zen-icons/xr.svg (../shared/zen-icons/lin/xr.svg) -* skin/classic/browser/zen-icons/zoom-control.svg (../shared/zen-icons/lin/zoom-control.svg) -* skin/classic/browser/zen-icons/zoom-out.svg (../shared/zen-icons/lin/zoom-out.svg) -* skin/classic/browser/zen-icons/ZenGroup.svg (../shared/zen-icons/lin/ZenGroup.svg) -* skin/classic/browser/zen-icons/ZenGroupSpinner.svg (../shared/zen-icons/lin/ZenGroupSpinner.svg) +* skin/classic/browser/zen-icons/accessibility.svg (../shared/zen-icons/lin/accessibility.svg) +* skin/classic/browser/zen-icons/add-to-dictionary.svg (../shared/zen-icons/lin/add-to-dictionary.svg) +* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/lin/arrow-down.svg) +* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/lin/arrow-left.svg) +* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/lin/arrow-right.svg) +* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/lin/arrow-up.svg) +* skin/classic/browser/zen-icons/audio-save.svg (../shared/zen-icons/lin/audio-save.svg) +* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/lin/autoplay-media-blocked.svg) +* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/lin/autoplay-media-fill.svg) +* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/lin/autoplay-media.svg) +* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/lin/back.svg) +* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/lin/bookmark-hollow.svg) +* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/lin/bookmark-star-on-tray.svg) +* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/lin/bookmark.svg) +* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/lin/camera-blocked.svg) +* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/lin/camera-fill.svg) +* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/lin/camera.svg) +* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/lin/canvas-blocked.svg) +* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/lin/canvas.svg) +* skin/classic/browser/zen-icons/checkmark.svg (../shared/zen-icons/lin/checkmark.svg) +* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/lin/chevron.svg) +* skin/classic/browser/zen-icons/close-all.svg (../shared/zen-icons/lin/close-all.svg) +* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/lin/close.svg) +* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/lin/container-tab.svg) +* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/lin/cookies-fill.svg) +* skin/classic/browser/zen-icons/customize.svg (../shared/zen-icons/lin/customize.svg) +* skin/classic/browser/zen-icons/dart-down.svg (../shared/zen-icons/lin/dart-down.svg) +* skin/classic/browser/zen-icons/desktop-notification-blocked.svg (../shared/zen-icons/lin/desktop-notification-blocked.svg) +* skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/lin/desktop-notification-fill.svg) +* skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/lin/desktop-notification.svg) +* skin/classic/browser/zen-icons/developer.svg (../shared/zen-icons/lin/developer.svg) +* skin/classic/browser/zen-icons/downloads.svg (../shared/zen-icons/lin/downloads.svg) +* skin/classic/browser/zen-icons/drag-indicator.svg (../shared/zen-icons/lin/drag-indicator.svg) +* skin/classic/browser/zen-icons/duplicate-tab.svg (../shared/zen-icons/lin/duplicate-tab.svg) +* skin/classic/browser/zen-icons/edit-copy.svg (../shared/zen-icons/lin/edit-copy.svg) +* skin/classic/browser/zen-icons/edit-cut.svg (../shared/zen-icons/lin/edit-cut.svg) +* skin/classic/browser/zen-icons/edit-delete.svg (../shared/zen-icons/lin/edit-delete.svg) +* skin/classic/browser/zen-icons/edit-paste.svg (../shared/zen-icons/lin/edit-paste.svg) +* skin/classic/browser/zen-icons/edit-redo.svg (../shared/zen-icons/lin/edit-redo.svg) +* skin/classic/browser/zen-icons/edit-select-all.svg (../shared/zen-icons/lin/edit-select-all.svg) +* skin/classic/browser/zen-icons/edit-theme.svg (../shared/zen-icons/lin/edit-theme.svg) +* skin/classic/browser/zen-icons/edit-undo.svg (../shared/zen-icons/lin/edit-undo.svg) +* skin/classic/browser/zen-icons/edit.svg (../shared/zen-icons/lin/edit.svg) +* skin/classic/browser/zen-icons/essential-add.svg (../shared/zen-icons/lin/essential-add.svg) +* skin/classic/browser/zen-icons/essential-remove.svg (../shared/zen-icons/lin/essential-remove.svg) +* skin/classic/browser/zen-icons/expand-sidebar.svg (../shared/zen-icons/lin/expand-sidebar.svg) +* skin/classic/browser/zen-icons/ext-link.svg (../shared/zen-icons/lin/ext-link.svg) +* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/lin/extension-blocked.svg) +* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/lin/extension-fill.svg) +* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/lin/extension.svg) +* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/lin/face-sun.svg) +* skin/classic/browser/zen-icons/firefox.svg (../shared/zen-icons/lin/firefox.svg) +* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/lin/folder.svg) +* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/lin/forget.svg) +* skin/classic/browser/zen-icons/forward.svg (../shared/zen-icons/lin/forward.svg) +* skin/classic/browser/zen-icons/fullscreen-exit.svg (../shared/zen-icons/lin/fullscreen-exit.svg) +* skin/classic/browser/zen-icons/fullscreen.svg (../shared/zen-icons/lin/fullscreen.svg) +* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/lin/geo-blocked.svg) +* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/lin/geo-fill.svg) +* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/lin/geo.svg) +* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/lin/help.svg) +* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/lin/history.svg) +* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/lin/home.svg) +* skin/classic/browser/zen-icons/image-copy.svg (../shared/zen-icons/lin/image-copy.svg) +* skin/classic/browser/zen-icons/image-open.svg (../shared/zen-icons/lin/image-open.svg) +* skin/classic/browser/zen-icons/image-save.svg (../shared/zen-icons/lin/image-save.svg) +* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/lin/info.svg) +* skin/classic/browser/zen-icons/inspect.svg (../shared/zen-icons/lin/inspect.svg) +* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/lin/library.svg) +* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/lin/link.svg) +* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/lin/mail.svg) +* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/lin/manage.svg) +* skin/classic/browser/zen-icons/media-loop.svg (../shared/zen-icons/lin/media-loop.svg) +* skin/classic/browser/zen-icons/media-mute.svg (../shared/zen-icons/lin/media-mute.svg) +* skin/classic/browser/zen-icons/media-next.svg (../shared/zen-icons/lin/media-next.svg) +* skin/classic/browser/zen-icons/media-pause.svg (../shared/zen-icons/lin/media-pause.svg) +* skin/classic/browser/zen-icons/media-pip.svg (../shared/zen-icons/lin/media-pip.svg) +* skin/classic/browser/zen-icons/media-play.svg (../shared/zen-icons/lin/media-play.svg) +* skin/classic/browser/zen-icons/media-previous.svg (../shared/zen-icons/lin/media-previous.svg) +* skin/classic/browser/zen-icons/media-speed.svg (../shared/zen-icons/lin/media-speed.svg) +* skin/classic/browser/zen-icons/media-unmute.svg (../shared/zen-icons/lin/media-unmute.svg) +* skin/classic/browser/zen-icons/menu-bar.svg (../shared/zen-icons/lin/menu-bar.svg) +* skin/classic/browser/zen-icons/menu.svg (../shared/zen-icons/lin/menu.svg) +* skin/classic/browser/zen-icons/microphone-blocked-fill.svg (../shared/zen-icons/lin/microphone-blocked-fill.svg) +* skin/classic/browser/zen-icons/microphone-blocked.svg (../shared/zen-icons/lin/microphone-blocked.svg) +* skin/classic/browser/zen-icons/microphone-fill.svg (../shared/zen-icons/lin/microphone-fill.svg) +* skin/classic/browser/zen-icons/microphone.svg (../shared/zen-icons/lin/microphone.svg) +* skin/classic/browser/zen-icons/midi.svg (../shared/zen-icons/lin/midi.svg) +* skin/classic/browser/zen-icons/moon-stars.svg (../shared/zen-icons/lin/moon-stars.svg) +* skin/classic/browser/zen-icons/move-tab.svg (../shared/zen-icons/lin/move-tab.svg) +* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/lin/new-tab-image.svg) +* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/lin/open.svg) +* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/lin/page-portrait.svg) +* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/lin/palette.svg) +* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/lin/passwords.svg) +* skin/classic/browser/zen-icons/paste-and-go.svg (../shared/zen-icons/lin/paste-and-go.svg) +* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/lin/permissions-fill.svg) +* skin/classic/browser/zen-icons/permissions.svg (../shared/zen-icons/lin/permissions.svg) +* skin/classic/browser/zen-icons/persistent-storage-blocked.svg (../shared/zen-icons/lin/persistent-storage-blocked.svg) +* skin/classic/browser/zen-icons/persistent-storage-fill.svg (../shared/zen-icons/lin/persistent-storage-fill.svg) +* skin/classic/browser/zen-icons/persistent-storage.svg (../shared/zen-icons/lin/persistent-storage.svg) +* skin/classic/browser/zen-icons/pin.svg (../shared/zen-icons/lin/pin.svg) +* skin/classic/browser/zen-icons/plus.svg (../shared/zen-icons/lin/plus.svg) +* skin/classic/browser/zen-icons/popup-fill.svg (../shared/zen-icons/lin/popup-fill.svg) +* skin/classic/browser/zen-icons/popup.svg (../shared/zen-icons/lin/popup.svg) +* skin/classic/browser/zen-icons/print.svg (../shared/zen-icons/lin/print.svg) +* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/lin/private-window.svg) +* skin/classic/browser/zen-icons/privateBrowsing.svg (../shared/zen-icons/lin/privateBrowsing.svg) +* skin/classic/browser/zen-icons/reader-mode.svg (../shared/zen-icons/lin/reader-mode.svg) +* skin/classic/browser/zen-icons/reload.svg (../shared/zen-icons/lin/reload.svg) +* skin/classic/browser/zen-icons/report.svg (../shared/zen-icons/lin/report.svg) +* skin/classic/browser/zen-icons/save.svg (../shared/zen-icons/lin/save.svg) +* skin/classic/browser/zen-icons/screen-blocked.svg (../shared/zen-icons/lin/screen-blocked.svg) +* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/lin/screen.svg) +* skin/classic/browser/zen-icons/screenshot.svg (../shared/zen-icons/lin/screenshot.svg) +* skin/classic/browser/zen-icons/search-glass.svg (../shared/zen-icons/lin/search-glass.svg) +* skin/classic/browser/zen-icons/search-page.svg (../shared/zen-icons/lin/search-page.svg) +* skin/classic/browser/zen-icons/security-broken.svg (../shared/zen-icons/lin/security-broken.svg) +* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/lin/security-warning.svg) +* skin/classic/browser/zen-icons/security.svg (../shared/zen-icons/lin/security.svg) +* skin/classic/browser/zen-icons/send-to-device.svg (../shared/zen-icons/lin/send-to-device.svg) +* skin/classic/browser/zen-icons/settings-fill.svg (../shared/zen-icons/lin/settings-fill.svg) +* skin/classic/browser/zen-icons/settings.svg (../shared/zen-icons/lin/settings.svg) +* skin/classic/browser/zen-icons/share.svg (../shared/zen-icons/lin/share.svg) +* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/lin/sidebar-right.svg) +* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/lin/sidebar.svg) +* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/lin/sidebars-right.svg) +* skin/classic/browser/zen-icons/source-code.svg (../shared/zen-icons/lin/source-code.svg) +* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/lin/sparkles.svg) +* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/lin/spell-check.svg) +* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/lin/split.svg) +* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/lin/tab-audio-blocked-small.svg) +* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/lin/tab-audio-muted-small.svg) +* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/lin/tab-audio-playing-small.svg) +* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/lin/tab.svg) +* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/lin/tool-profiler.svg) +* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/lin/tracking-protection-fill.svg) +* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/lin/tracking-protection.svg) +* skin/classic/browser/zen-icons/translations.svg (../shared/zen-icons/lin/translations.svg) +* skin/classic/browser/zen-icons/trash.svg (../shared/zen-icons/lin/trash.svg) +* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/lin/unpin.svg) +* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/lin/video-blocked-fill.svg) +* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/lin/video-fill.svg) +* skin/classic/browser/zen-icons/video-open.svg (../shared/zen-icons/lin/video-open.svg) +* skin/classic/browser/zen-icons/video-save.svg (../shared/zen-icons/lin/video-save.svg) +* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/lin/window.svg) +* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/lin/xr-blocked.svg) +* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/lin/xr-fill.svg) +* skin/classic/browser/zen-icons/xr.svg (../shared/zen-icons/lin/xr.svg) +* skin/classic/browser/zen-icons/zoom-control.svg (../shared/zen-icons/lin/zoom-control.svg) +* skin/classic/browser/zen-icons/zoom-out.svg (../shared/zen-icons/lin/zoom-out.svg) +* skin/classic/browser/zen-icons/ZenGroup.svg (../shared/zen-icons/lin/ZenGroup.svg) #endif -* skin/classic/browser/zen-icons/urlbar-arrow.svg (../shared/zen-icons/common/urlbar-arrow.svg) -* skin/classic/browser/zen-icons/selectable/airplane.svg (../shared/zen-icons/common/selectable/airplane.svg) -* skin/classic/browser/zen-icons/selectable/american-football.svg (../shared/zen-icons/common/selectable/american-football.svg) -* skin/classic/browser/zen-icons/selectable/baseball.svg (../shared/zen-icons/common/selectable/baseball.svg) -* skin/classic/browser/zen-icons/selectable/basket.svg (../shared/zen-icons/common/selectable/basket.svg) -* skin/classic/browser/zen-icons/selectable/bed.svg (../shared/zen-icons/common/selectable/bed.svg) -* skin/classic/browser/zen-icons/selectable/bell.svg (../shared/zen-icons/common/selectable/bell.svg) -* skin/classic/browser/zen-icons/selectable/book.svg (../shared/zen-icons/common/selectable/book.svg) -* skin/classic/browser/zen-icons/selectable/bookmark.svg (../shared/zen-icons/common/selectable/bookmark.svg) -* skin/classic/browser/zen-icons/selectable/briefcase.svg (../shared/zen-icons/common/selectable/briefcase.svg) -* skin/classic/browser/zen-icons/selectable/brush.svg (../shared/zen-icons/common/selectable/brush.svg) -* skin/classic/browser/zen-icons/selectable/bug.svg (../shared/zen-icons/common/selectable/bug.svg) -* skin/classic/browser/zen-icons/selectable/build.svg (../shared/zen-icons/common/selectable/build.svg) -* skin/classic/browser/zen-icons/selectable/cafe.svg (../shared/zen-icons/common/selectable/cafe.svg) -* skin/classic/browser/zen-icons/selectable/call.svg (../shared/zen-icons/common/selectable/call.svg) -* skin/classic/browser/zen-icons/selectable/card.svg (../shared/zen-icons/common/selectable/card.svg) -* skin/classic/browser/zen-icons/selectable/chat.svg (../shared/zen-icons/common/selectable/chat.svg) -* skin/classic/browser/zen-icons/selectable/checkbox.svg (../shared/zen-icons/common/selectable/checkbox.svg) -* skin/classic/browser/zen-icons/selectable/circle.svg (../shared/zen-icons/common/selectable/circle.svg) -* skin/classic/browser/zen-icons/selectable/cloud.svg (../shared/zen-icons/common/selectable/cloud.svg) -* skin/classic/browser/zen-icons/selectable/code.svg (../shared/zen-icons/common/selectable/code.svg) -* skin/classic/browser/zen-icons/selectable/coins.svg (../shared/zen-icons/common/selectable/coins.svg) -* skin/classic/browser/zen-icons/selectable/construct.svg (../shared/zen-icons/common/selectable/construct.svg) -* skin/classic/browser/zen-icons/selectable/cutlery.svg (../shared/zen-icons/common/selectable/cutlery.svg) -* skin/classic/browser/zen-icons/selectable/egg.svg (../shared/zen-icons/common/selectable/egg.svg) -* skin/classic/browser/zen-icons/selectable/extension-puzzle.svg (../shared/zen-icons/common/selectable/extension-puzzle.svg) -* skin/classic/browser/zen-icons/selectable/eye.svg (../shared/zen-icons/common/selectable/eye.svg) -* skin/classic/browser/zen-icons/selectable/fast-food.svg (../shared/zen-icons/common/selectable/fast-food.svg) -* skin/classic/browser/zen-icons/selectable/fish.svg (../shared/zen-icons/common/selectable/fish.svg) -* skin/classic/browser/zen-icons/selectable/flag.svg (../shared/zen-icons/common/selectable/flag.svg) -* skin/classic/browser/zen-icons/selectable/flame.svg (../shared/zen-icons/common/selectable/flame.svg) -* skin/classic/browser/zen-icons/selectable/flask.svg (../shared/zen-icons/common/selectable/flask.svg) -* skin/classic/browser/zen-icons/selectable/folder.svg (../shared/zen-icons/common/selectable/folder.svg) -* skin/classic/browser/zen-icons/selectable/game-controller.svg (../shared/zen-icons/common/selectable/game-controller.svg) -* skin/classic/browser/zen-icons/selectable/globe-1.svg (../shared/zen-icons/common/selectable/globe-1.svg) -* skin/classic/browser/zen-icons/selectable/globe.svg (../shared/zen-icons/common/selectable/globe.svg) -* skin/classic/browser/zen-icons/selectable/grid-2x2.svg (../shared/zen-icons/common/selectable/grid-2x2.svg) -* skin/classic/browser/zen-icons/selectable/grid-3x3.svg (../shared/zen-icons/common/selectable/grid-3x3.svg) -* skin/classic/browser/zen-icons/selectable/heart.svg (../shared/zen-icons/common/selectable/heart.svg) -* skin/classic/browser/zen-icons/selectable/ice-cream.svg (../shared/zen-icons/common/selectable/ice-cream.svg) -* skin/classic/browser/zen-icons/selectable/image.svg (../shared/zen-icons/common/selectable/image.svg) -* skin/classic/browser/zen-icons/selectable/inbox.svg (../shared/zen-icons/common/selectable/inbox.svg) -* skin/classic/browser/zen-icons/selectable/key.svg (../shared/zen-icons/common/selectable/key.svg) -* skin/classic/browser/zen-icons/selectable/layers.svg (../shared/zen-icons/common/selectable/layers.svg) -* skin/classic/browser/zen-icons/selectable/leaf.svg (../shared/zen-icons/common/selectable/leaf.svg) -* skin/classic/browser/zen-icons/selectable/lightning.svg (../shared/zen-icons/common/selectable/lightning.svg) -* skin/classic/browser/zen-icons/selectable/location.svg (../shared/zen-icons/common/selectable/location.svg) -* skin/classic/browser/zen-icons/selectable/lock-closed.svg (../shared/zen-icons/common/selectable/lock-closed.svg) -* skin/classic/browser/zen-icons/selectable/logo-rss.svg (../shared/zen-icons/common/selectable/logo-rss.svg) -* skin/classic/browser/zen-icons/selectable/logo-usd.svg (../shared/zen-icons/common/selectable/logo-usd.svg) -* skin/classic/browser/zen-icons/selectable/mail.svg (../shared/zen-icons/common/selectable/mail.svg) -* skin/classic/browser/zen-icons/selectable/map.svg (../shared/zen-icons/common/selectable/map.svg) -* skin/classic/browser/zen-icons/selectable/megaphone.svg (../shared/zen-icons/common/selectable/megaphone.svg) -* skin/classic/browser/zen-icons/selectable/moon.svg (../shared/zen-icons/common/selectable/moon.svg) -* skin/classic/browser/zen-icons/selectable/music.svg (../shared/zen-icons/common/selectable/music.svg) -* skin/classic/browser/zen-icons/selectable/navigate.svg (../shared/zen-icons/common/selectable/navigate.svg) -* skin/classic/browser/zen-icons/selectable/nuclear.svg (../shared/zen-icons/common/selectable/nuclear.svg) -* skin/classic/browser/zen-icons/selectable/page.svg (../shared/zen-icons/common/selectable/page.svg) -* skin/classic/browser/zen-icons/selectable/palette.svg (../shared/zen-icons/common/selectable/palette.svg) -* skin/classic/browser/zen-icons/selectable/paw.svg (../shared/zen-icons/common/selectable/paw.svg) -* skin/classic/browser/zen-icons/selectable/people.svg (../shared/zen-icons/common/selectable/people.svg) -* skin/classic/browser/zen-icons/selectable/pizza.svg (../shared/zen-icons/common/selectable/pizza.svg) -* skin/classic/browser/zen-icons/selectable/planet.svg (../shared/zen-icons/common/selectable/planet.svg) -* skin/classic/browser/zen-icons/selectable/present.svg (../shared/zen-icons/common/selectable/present.svg) -* skin/classic/browser/zen-icons/selectable/rocket.svg (../shared/zen-icons/common/selectable/rocket.svg) -* skin/classic/browser/zen-icons/selectable/school.svg (../shared/zen-icons/common/selectable/school.svg) -* skin/classic/browser/zen-icons/selectable/shapes.svg (../shared/zen-icons/common/selectable/shapes.svg) -* skin/classic/browser/zen-icons/selectable/shirt.svg (../shared/zen-icons/common/selectable/shirt.svg) -* skin/classic/browser/zen-icons/selectable/skull.svg (../shared/zen-icons/common/selectable/skull.svg) -* skin/classic/browser/zen-icons/selectable/square.svg (../shared/zen-icons/common/selectable/square.svg) -* skin/classic/browser/zen-icons/selectable/squares.svg (../shared/zen-icons/common/selectable/squares.svg) -* skin/classic/browser/zen-icons/selectable/star-1.svg (../shared/zen-icons/common/selectable/star-1.svg) -* skin/classic/browser/zen-icons/selectable/star.svg (../shared/zen-icons/common/selectable/star.svg) -* skin/classic/browser/zen-icons/selectable/stats-chart.svg (../shared/zen-icons/common/selectable/stats-chart.svg) -* skin/classic/browser/zen-icons/selectable/sun.svg (../shared/zen-icons/common/selectable/sun.svg) -* skin/classic/browser/zen-icons/selectable/tada.svg (../shared/zen-icons/common/selectable/tada.svg) -* skin/classic/browser/zen-icons/selectable/terminal.svg (../shared/zen-icons/common/selectable/terminal.svg) -* skin/classic/browser/zen-icons/selectable/ticket.svg (../shared/zen-icons/common/selectable/ticket.svg) -* skin/classic/browser/zen-icons/selectable/time.svg (../shared/zen-icons/common/selectable/time.svg) -* skin/classic/browser/zen-icons/selectable/trash.svg (../shared/zen-icons/common/selectable/trash.svg) -* skin/classic/browser/zen-icons/selectable/triangle.svg (../shared/zen-icons/common/selectable/triangle.svg) -* skin/classic/browser/zen-icons/selectable/video.svg (../shared/zen-icons/common/selectable/video.svg) -* skin/classic/browser/zen-icons/selectable/volume-high.svg (../shared/zen-icons/common/selectable/volume-high.svg) -* skin/classic/browser/zen-icons/selectable/wallet.svg (../shared/zen-icons/common/selectable/wallet.svg) -* skin/classic/browser/zen-icons/selectable/warning.svg (../shared/zen-icons/common/selectable/warning.svg) -* skin/classic/browser/zen-icons/selectable/water.svg (../shared/zen-icons/common/selectable/water.svg) -* skin/classic/browser/zen-icons/selectable/weight.svg (../shared/zen-icons/common/selectable/weight.svg) - skin/classic/browser/zen-icons/icons.css (../shared/zen-icons/icons.css) +* skin/classic/browser/zen-icons/urlbar-arrow.svg (../shared/zen-icons/common/urlbar-arrow.svg) +* skin/classic/browser/zen-icons/selectable/airplane.svg (../shared/zen-icons/common/selectable/airplane.svg) +* skin/classic/browser/zen-icons/selectable/american-football.svg (../shared/zen-icons/common/selectable/american-football.svg) +* skin/classic/browser/zen-icons/selectable/baseball.svg (../shared/zen-icons/common/selectable/baseball.svg) +* skin/classic/browser/zen-icons/selectable/basket.svg (../shared/zen-icons/common/selectable/basket.svg) +* skin/classic/browser/zen-icons/selectable/bed.svg (../shared/zen-icons/common/selectable/bed.svg) +* skin/classic/browser/zen-icons/selectable/bell.svg (../shared/zen-icons/common/selectable/bell.svg) +* skin/classic/browser/zen-icons/selectable/book.svg (../shared/zen-icons/common/selectable/book.svg) +* skin/classic/browser/zen-icons/selectable/bookmark.svg (../shared/zen-icons/common/selectable/bookmark.svg) +* skin/classic/browser/zen-icons/selectable/briefcase.svg (../shared/zen-icons/common/selectable/briefcase.svg) +* skin/classic/browser/zen-icons/selectable/brush.svg (../shared/zen-icons/common/selectable/brush.svg) +* skin/classic/browser/zen-icons/selectable/bug.svg (../shared/zen-icons/common/selectable/bug.svg) +* skin/classic/browser/zen-icons/selectable/build.svg (../shared/zen-icons/common/selectable/build.svg) +* skin/classic/browser/zen-icons/selectable/cafe.svg (../shared/zen-icons/common/selectable/cafe.svg) +* skin/classic/browser/zen-icons/selectable/call.svg (../shared/zen-icons/common/selectable/call.svg) +* skin/classic/browser/zen-icons/selectable/card.svg (../shared/zen-icons/common/selectable/card.svg) +* skin/classic/browser/zen-icons/selectable/chat.svg (../shared/zen-icons/common/selectable/chat.svg) +* skin/classic/browser/zen-icons/selectable/checkbox.svg (../shared/zen-icons/common/selectable/checkbox.svg) +* skin/classic/browser/zen-icons/selectable/circle.svg (../shared/zen-icons/common/selectable/circle.svg) +* skin/classic/browser/zen-icons/selectable/cloud.svg (../shared/zen-icons/common/selectable/cloud.svg) +* skin/classic/browser/zen-icons/selectable/code.svg (../shared/zen-icons/common/selectable/code.svg) +* skin/classic/browser/zen-icons/selectable/coins.svg (../shared/zen-icons/common/selectable/coins.svg) +* skin/classic/browser/zen-icons/selectable/construct.svg (../shared/zen-icons/common/selectable/construct.svg) +* skin/classic/browser/zen-icons/selectable/cutlery.svg (../shared/zen-icons/common/selectable/cutlery.svg) +* skin/classic/browser/zen-icons/selectable/egg.svg (../shared/zen-icons/common/selectable/egg.svg) +* skin/classic/browser/zen-icons/selectable/extension-puzzle.svg (../shared/zen-icons/common/selectable/extension-puzzle.svg) +* skin/classic/browser/zen-icons/selectable/eye.svg (../shared/zen-icons/common/selectable/eye.svg) +* skin/classic/browser/zen-icons/selectable/fast-food.svg (../shared/zen-icons/common/selectable/fast-food.svg) +* skin/classic/browser/zen-icons/selectable/fish.svg (../shared/zen-icons/common/selectable/fish.svg) +* skin/classic/browser/zen-icons/selectable/flag.svg (../shared/zen-icons/common/selectable/flag.svg) +* skin/classic/browser/zen-icons/selectable/flame.svg (../shared/zen-icons/common/selectable/flame.svg) +* skin/classic/browser/zen-icons/selectable/flask.svg (../shared/zen-icons/common/selectable/flask.svg) +* skin/classic/browser/zen-icons/selectable/folder.svg (../shared/zen-icons/common/selectable/folder.svg) +* skin/classic/browser/zen-icons/selectable/game-controller.svg (../shared/zen-icons/common/selectable/game-controller.svg) +* skin/classic/browser/zen-icons/selectable/globe-1.svg (../shared/zen-icons/common/selectable/globe-1.svg) +* skin/classic/browser/zen-icons/selectable/globe.svg (../shared/zen-icons/common/selectable/globe.svg) +* skin/classic/browser/zen-icons/selectable/grid-2x2.svg (../shared/zen-icons/common/selectable/grid-2x2.svg) +* skin/classic/browser/zen-icons/selectable/grid-3x3.svg (../shared/zen-icons/common/selectable/grid-3x3.svg) +* skin/classic/browser/zen-icons/selectable/heart.svg (../shared/zen-icons/common/selectable/heart.svg) +* skin/classic/browser/zen-icons/selectable/ice-cream.svg (../shared/zen-icons/common/selectable/ice-cream.svg) +* skin/classic/browser/zen-icons/selectable/image.svg (../shared/zen-icons/common/selectable/image.svg) +* skin/classic/browser/zen-icons/selectable/inbox.svg (../shared/zen-icons/common/selectable/inbox.svg) +* skin/classic/browser/zen-icons/selectable/key.svg (../shared/zen-icons/common/selectable/key.svg) +* skin/classic/browser/zen-icons/selectable/layers.svg (../shared/zen-icons/common/selectable/layers.svg) +* skin/classic/browser/zen-icons/selectable/leaf.svg (../shared/zen-icons/common/selectable/leaf.svg) +* skin/classic/browser/zen-icons/selectable/lightning.svg (../shared/zen-icons/common/selectable/lightning.svg) +* skin/classic/browser/zen-icons/selectable/location.svg (../shared/zen-icons/common/selectable/location.svg) +* skin/classic/browser/zen-icons/selectable/lock-closed.svg (../shared/zen-icons/common/selectable/lock-closed.svg) +* skin/classic/browser/zen-icons/selectable/logo-rss.svg (../shared/zen-icons/common/selectable/logo-rss.svg) +* skin/classic/browser/zen-icons/selectable/logo-usd.svg (../shared/zen-icons/common/selectable/logo-usd.svg) +* skin/classic/browser/zen-icons/selectable/mail.svg (../shared/zen-icons/common/selectable/mail.svg) +* skin/classic/browser/zen-icons/selectable/map.svg (../shared/zen-icons/common/selectable/map.svg) +* skin/classic/browser/zen-icons/selectable/megaphone.svg (../shared/zen-icons/common/selectable/megaphone.svg) +* skin/classic/browser/zen-icons/selectable/moon.svg (../shared/zen-icons/common/selectable/moon.svg) +* skin/classic/browser/zen-icons/selectable/music.svg (../shared/zen-icons/common/selectable/music.svg) +* skin/classic/browser/zen-icons/selectable/navigate.svg (../shared/zen-icons/common/selectable/navigate.svg) +* skin/classic/browser/zen-icons/selectable/nuclear.svg (../shared/zen-icons/common/selectable/nuclear.svg) +* skin/classic/browser/zen-icons/selectable/page.svg (../shared/zen-icons/common/selectable/page.svg) +* skin/classic/browser/zen-icons/selectable/palette.svg (../shared/zen-icons/common/selectable/palette.svg) +* skin/classic/browser/zen-icons/selectable/paw.svg (../shared/zen-icons/common/selectable/paw.svg) +* skin/classic/browser/zen-icons/selectable/people.svg (../shared/zen-icons/common/selectable/people.svg) +* skin/classic/browser/zen-icons/selectable/pizza.svg (../shared/zen-icons/common/selectable/pizza.svg) +* skin/classic/browser/zen-icons/selectable/planet.svg (../shared/zen-icons/common/selectable/planet.svg) +* skin/classic/browser/zen-icons/selectable/present.svg (../shared/zen-icons/common/selectable/present.svg) +* skin/classic/browser/zen-icons/selectable/rocket.svg (../shared/zen-icons/common/selectable/rocket.svg) +* skin/classic/browser/zen-icons/selectable/school.svg (../shared/zen-icons/common/selectable/school.svg) +* skin/classic/browser/zen-icons/selectable/shapes.svg (../shared/zen-icons/common/selectable/shapes.svg) +* skin/classic/browser/zen-icons/selectable/shirt.svg (../shared/zen-icons/common/selectable/shirt.svg) +* skin/classic/browser/zen-icons/selectable/skull.svg (../shared/zen-icons/common/selectable/skull.svg) +* skin/classic/browser/zen-icons/selectable/square.svg (../shared/zen-icons/common/selectable/square.svg) +* skin/classic/browser/zen-icons/selectable/squares.svg (../shared/zen-icons/common/selectable/squares.svg) +* skin/classic/browser/zen-icons/selectable/star-1.svg (../shared/zen-icons/common/selectable/star-1.svg) +* skin/classic/browser/zen-icons/selectable/star.svg (../shared/zen-icons/common/selectable/star.svg) +* skin/classic/browser/zen-icons/selectable/stats-chart.svg (../shared/zen-icons/common/selectable/stats-chart.svg) +* skin/classic/browser/zen-icons/selectable/sun.svg (../shared/zen-icons/common/selectable/sun.svg) +* skin/classic/browser/zen-icons/selectable/tada.svg (../shared/zen-icons/common/selectable/tada.svg) +* skin/classic/browser/zen-icons/selectable/terminal.svg (../shared/zen-icons/common/selectable/terminal.svg) +* skin/classic/browser/zen-icons/selectable/ticket.svg (../shared/zen-icons/common/selectable/ticket.svg) +* skin/classic/browser/zen-icons/selectable/time.svg (../shared/zen-icons/common/selectable/time.svg) +* skin/classic/browser/zen-icons/selectable/trash.svg (../shared/zen-icons/common/selectable/trash.svg) +* skin/classic/browser/zen-icons/selectable/triangle.svg (../shared/zen-icons/common/selectable/triangle.svg) +* skin/classic/browser/zen-icons/selectable/video.svg (../shared/zen-icons/common/selectable/video.svg) +* skin/classic/browser/zen-icons/selectable/volume-high.svg (../shared/zen-icons/common/selectable/volume-high.svg) +* skin/classic/browser/zen-icons/selectable/wallet.svg (../shared/zen-icons/common/selectable/wallet.svg) +* skin/classic/browser/zen-icons/selectable/warning.svg (../shared/zen-icons/common/selectable/warning.svg) +* skin/classic/browser/zen-icons/selectable/water.svg (../shared/zen-icons/common/selectable/water.svg) +* skin/classic/browser/zen-icons/selectable/weight.svg (../shared/zen-icons/common/selectable/weight.svg) + skin/classic/browser/zen-icons/icons.css (../shared/zen-icons/icons.css) diff --git a/src/browser/themes/shared/zen-icons/lin/ZenGroupSpinner.svg b/src/browser/themes/shared/zen-icons/lin/ZenGroupSpinner.svg deleted file mode 100644 index 392863f09e..0000000000 --- a/src/browser/themes/shared/zen-icons/lin/ZenGroupSpinner.svg +++ /dev/null @@ -1,17 +0,0 @@ -#filter dumbComments emptyLines substitution -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - - - - - - - - - - - - diff --git a/src/zen/tabs/zen-tabs/vertical-tabs.css b/src/zen/tabs/zen-tabs/vertical-tabs.css index 24f818c9ea..06bbaead18 100644 --- a/src/zen/tabs/zen-tabs/vertical-tabs.css +++ b/src/zen/tabs/zen-tabs/vertical-tabs.css @@ -146,12 +146,36 @@ & toolbarseparator { height: 1px; background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)); + color: light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.65)); padding: 0; margin: auto 4px; + position: relative; + border: none; + overflow: visible; &::before { border-inline-start: none; } + + &::after { + content: ''; + position: absolute; + inset: auto 4px; + left: 4px; + right: 4px; + top: 50%; + height: 12px; + transform: translateY(-50%); + background-color: currentColor; + mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2048%2012'%20fill='none'%3E%3Cpath%20d='M0%206%20Q6%200%2012%206%20T24%206%20T36%206%20T48%206'%20stroke='white'%20stroke-width='2'%20stroke-linecap='round'%20fill='none'/%3E%3C/svg%3E"); + mask-size: 48px 12px; + mask-repeat: repeat; + mask-position: 0 50%; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease-in-out; + will-change: mask-position; + } } #tabbrowser-tabs[movingtab] & { @@ -210,6 +234,24 @@ transform: translateY(2px); } } + + &[data-grouping='true'] toolbarseparator { + background: transparent; + } + + &[data-grouping='true'] toolbarseparator::after { + opacity: 0.9; + animation: zenWorkspaceSquiggle 1.2s linear infinite; + } +} + +@keyframes zenWorkspaceSquiggle { + from { + mask-position: 0 50%; + } + to { + mask-position: 48px 50%; + } } /* ========================================================================== diff --git a/src/zen/workspaces/ZenWorkspace.mjs b/src/zen/workspaces/ZenWorkspace.mjs index 37666b479e..f759225c69 100644 --- a/src/zen/workspaces/ZenWorkspace.mjs +++ b/src/zen/workspaces/ZenWorkspace.mjs @@ -17,13 +17,6 @@
- Date: Tue, 25 Nov 2025 11:30:10 +1100 Subject: [PATCH 09/12] refactor: update vertical tabs CSS by removing deprecated styles and enhancing separator animations for grouping --- src/zen/tabs/zen-tabs/vertical-tabs.css | 43 ++++++++++++------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/zen/tabs/zen-tabs/vertical-tabs.css b/src/zen/tabs/zen-tabs/vertical-tabs.css index 06bbaead18..becd31a357 100644 --- a/src/zen/tabs/zen-tabs/vertical-tabs.css +++ b/src/zen/tabs/zen-tabs/vertical-tabs.css @@ -157,25 +157,6 @@ border-inline-start: none; } - &::after { - content: ''; - position: absolute; - inset: auto 4px; - left: 4px; - right: 4px; - top: 50%; - height: 12px; - transform: translateY(-50%); - background-color: currentColor; - mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2048%2012'%20fill='none'%3E%3Cpath%20d='M0%206%20Q6%200%2012%206%20T24%206%20T36%206%20T48%206'%20stroke='white'%20stroke-width='2'%20stroke-linecap='round'%20fill='none'/%3E%3C/svg%3E"); - mask-size: 48px 12px; - mask-repeat: repeat; - mask-position: 0 50%; - opacity: 0; - pointer-events: none; - transition: opacity 0.2s ease-in-out; - will-change: mask-position; - } } #tabbrowser-tabs[movingtab] & { @@ -237,11 +218,27 @@ &[data-grouping='true'] toolbarseparator { background: transparent; - } - &[data-grouping='true'] toolbarseparator::after { - opacity: 0.9; - animation: zenWorkspaceSquiggle 1.2s linear infinite; + &::after { + content: ''; + position: absolute; + inset: auto 4px; + left: 4px; + right: 4px; + top: 50%; + height: 12px; + transform: translateY(-50%); + background-color: currentColor; + mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2048%2012'%20fill='none'%3E%3Cpath%20d='M0%206%20Q6%200%2012%206%20T24%206%20T36%206%20T48%206'%20stroke='white'%20stroke-width='2'%20stroke-linecap='round'%20fill='none'/%3E%3C/svg%3E"); + mask-size: 48px 12px; + mask-repeat: repeat; + mask-position: 0 50%; + opacity: 0.9; + pointer-events: none; + transition: opacity 0.2s ease-in-out; + will-change: mask-position; + animation: zenWorkspaceSquiggle 1.2s linear infinite; + } } } From 8956cfb59ce3fd1e47285689da7453f0e0a99633 Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:30:59 +1100 Subject: [PATCH 10/12] refactor: remove unused Services import from ZenTabsTidy.sys.mjs --- src/zen/ml/ZenTabsTidy.sys.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/zen/ml/ZenTabsTidy.sys.mjs b/src/zen/ml/ZenTabsTidy.sys.mjs index de29c23d6b..f5d6fd36bc 100644 --- a/src/zen/ml/ZenTabsTidy.sys.mjs +++ b/src/zen/ml/ZenTabsTidy.sys.mjs @@ -2,7 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -const Services = globalThis.Services; const { setTimeout } = ChromeUtils.importESModule('resource://gre/modules/Timer.sys.mjs'); export async function groupTabsBySimilarity({ From c33c23cc4847e3b9479390cf1b0b02b9657636cc Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:34:40 +1100 Subject: [PATCH 11/12] refactor: optimize EngineProcess import by using lazy loading in ZenTabsTidy.sys.mjs --- src/zen/ml/ZenTabsTidy.sys.mjs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/zen/ml/ZenTabsTidy.sys.mjs b/src/zen/ml/ZenTabsTidy.sys.mjs index f5d6fd36bc..8a83039d23 100644 --- a/src/zen/ml/ZenTabsTidy.sys.mjs +++ b/src/zen/ml/ZenTabsTidy.sys.mjs @@ -3,6 +3,11 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. const { setTimeout } = ChromeUtils.importESModule('resource://gre/modules/Timer.sys.mjs'); +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + EngineProcess: 'chrome://global/content/ml/EngineProcess.sys.mjs', +}); export async function groupTabsBySimilarity({ window: browserWindow, @@ -317,9 +322,7 @@ async function generateTabEmbeddings(tabData) { } try { - const { createEngine } = ChromeUtils.importESModule( - 'chrome://global/content/ml/EngineProcess.sys.mjs' - ); + const { createEngine } = lazy.EngineProcess; const engine = await createEngine({ taskName: 'feature-extraction', @@ -475,9 +478,7 @@ async function generateAiClusterLabel(clusterData) { } try { - const { createEngine } = ChromeUtils.importESModule( - 'chrome://global/content/ml/EngineProcess.sys.mjs' - ); + const { createEngine } = lazy.EngineProcess; const tabDescriptions = clusterData .map((data, idx) => { From 4cf856ebbf2cb51c8d06ead23470ac2fc5f92a2d Mon Sep 17 00:00:00 2001 From: Matteo Mekhail <67237370+matteoiscrying@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:01:27 +1100 Subject: [PATCH 12/12] refactor: update ZenTabsTidy import path and clean up zen-assets.jar.inc.mn --- src/browser/base/content/zen-assets.jar.inc.mn | 1 - src/zen/ml/moz.build | 8 ++++++++ src/zen/moz.build | 1 + src/zen/workspaces/ZenWorkspaces.mjs | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 src/zen/ml/moz.build diff --git a/src/browser/base/content/zen-assets.jar.inc.mn b/src/browser/base/content/zen-assets.jar.inc.mn index 1e038ef388..d8ee81f6ed 100644 --- a/src/browser/base/content/zen-assets.jar.inc.mn +++ b/src/browser/base/content/zen-assets.jar.inc.mn @@ -44,7 +44,6 @@ content/browser/zen-components/ZenWorkspaceCreation.mjs (../../zen/workspaces/ZenWorkspaceCreation.mjs) content/browser/zen-components/ZenWorkspacesStorage.mjs (../../zen/workspaces/ZenWorkspacesStorage.mjs) content/browser/zen-components/ZenWorkspacesSync.mjs (../../zen/workspaces/ZenWorkspacesSync.mjs) - content/browser/zen-components/ZenTabsTidy.sys.mjs (../../zen/ml/ZenTabsTidy.sys.mjs) content/browser/zen-components/ZenGradientGenerator.mjs (../../zen/workspaces/ZenGradientGenerator.mjs) * content/browser/zen-styles/zen-workspaces.css (../../zen/workspaces/zen-workspaces.css) content/browser/zen-styles/zen-gradient-generator.css (../../zen/workspaces/zen-gradient-generator.css) diff --git a/src/zen/ml/moz.build b/src/zen/ml/moz.build new file mode 100644 index 0000000000..0c0e1c65fa --- /dev/null +++ b/src/zen/ml/moz.build @@ -0,0 +1,8 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXTRA_JS_MODULES += [ + "ZenTabsTidy.sys.mjs", +] + diff --git a/src/zen/moz.build b/src/zen/moz.build index 21915a69ab..660e34bdba 100644 --- a/src/zen/moz.build +++ b/src/zen/moz.build @@ -9,6 +9,7 @@ EXTRA_PP_COMPONENTS += [ DIRS += [ "common", "glance", + "ml", "mods", "tests", "urlbar", diff --git a/src/zen/workspaces/ZenWorkspaces.mjs b/src/zen/workspaces/ZenWorkspaces.mjs index d4947972df..eae658e2cc 100644 --- a/src/zen/workspaces/ZenWorkspaces.mjs +++ b/src/zen/workspaces/ZenWorkspaces.mjs @@ -56,7 +56,7 @@ var gZenWorkspaces = new (class extends nsZenMultiWindowFeature { async #getTabsTidyModule() { if (!this.#tabsTidyModulePromise) { this.#tabsTidyModulePromise = ChromeUtils.importESModule( - 'chrome://browser/content/zen-components/ZenTabsTidy.sys.mjs' + 'resource:///modules/ZenTabsTidy.sys.mjs' ); } return this.#tabsTidyModulePromise;