refactor(common, compact-mode, folders, fonts, glance, images, kbs, media, mods, split-view, tabs, tests, workspaces, vendor, welcome): closes #7628 - Refactor zen components file structure for easier understanding

This commit is contained in:
Mr. M
2025-04-15 14:03:55 +02:00
parent c8d616e657
commit 1135744520
95 changed files with 155 additions and 788 deletions

View File

@@ -1,129 +0,0 @@
import { AppConstants } from 'resource://gre/modules/AppConstants.sys.mjs';
export var ZenCustomizableUI = new (class {
constructor() {}
TYPE_TOOLBAR = 'toolbar';
defaultSidebarIcons = ['preferences-button', 'zen-workspaces-button', 'downloads-button'];
startup(CustomizableUIInternal) {
CustomizableUIInternal.registerArea(
'zen-sidebar-top-buttons',
{
type: this.TYPE_TOOLBAR,
defaultPlacements: [],
defaultCollapsed: null,
overflowable: true,
},
true
);
CustomizableUIInternal.registerArea(
'zen-sidebar-bottom-buttons',
{
type: this.TYPE_TOOLBAR,
defaultPlacements: this.defaultSidebarIcons,
defaultCollapsed: null,
},
true
);
}
// We do not have access to the window object here
init(window) {
this._addSidebarButtons(window);
this._hideToolbarButtons(window);
}
_addSidebarButtons(window) {
const toolbox = window.document.getElementById('navigator-toolbox');
// Set a splitter to navigator-toolbox
const splitter = window.document.createXULElement('splitter');
splitter.setAttribute('id', 'zen-sidebar-splitter');
splitter.setAttribute('orient', 'horizontal');
splitter.setAttribute('resizebefore', 'sibling');
splitter.setAttribute('resizeafter', 'none');
toolbox.insertAdjacentElement('afterend', splitter);
const sidebarBox = window.MozXULElement.parseXULToFragment(`
<toolbar id="zen-sidebar-top-buttons"
fullscreentoolbar="true"
class="browser-toolbar customization-target zen-dont-hide-on-fullscreen"
brighttext="true"
data-l10n-id="tabs-toolbar"
customizable="true"
context="toolbar-context-menu"
flex="1"
skipintoolbarset="true"
customizationtarget="zen-sidebar-top-buttons-customization-target"
overflowable="true"
default-overflowbutton="nav-bar-overflow-button"
default-overflowtarget="widget-overflow-list"
default-overflowpanel="widget-overflow"
addon-webext-overflowbutton="unified-extensions-button"
addon-webext-overflowtarget="overflowed-extensions-list"
mode="icons">
<hbox id="zen-sidebar-top-buttons-customization-target" class="customization-target" flex="1">
<html:div id="zen-sidebar-top-buttons-separator" skipintoolbarset="true" overflows="false"></html:div>
</hbox>
</toolbar>
`);
toolbox.prepend(sidebarBox);
new window.MutationObserver((e) => {
if (e[0].type !== 'attributes' || e[0].attributeName !== 'width') return;
this._dispatchResizeEvent(window);
}).observe(toolbox, {
attributes: true, //configure it to listen to attribute changes
});
// remove all styles except for the width, since we are xulstoring the complet style list
const width = toolbox.style.width || '180px';
toolbox.removeAttribute('style');
toolbox.style.width = width;
const newTab = window.document.getElementById('vertical-tabs-newtab-button');
newTab.classList.add('zen-sidebar-action-button');
for (let id of this.defaultSidebarIcons) {
const elem = window.document.getElementById(id);
if (!elem || elem.id === 'zen-workspaces-button') continue;
elem.setAttribute('removable', 'true');
}
this._moveWindowButtons(window);
}
_moveWindowButtons(window) {
const windowControls = window.document.getElementsByClassName('titlebar-buttonbox-container');
const toolboxIcons = window.document.getElementById('zen-sidebar-top-buttons-customization-target');
if (window.AppConstants.platform === 'macosx' || window.matchMedia('(-moz-gtk-csd-reversed-placement)').matches) {
for (let i = 0; i < windowControls.length; i++) {
if (i === 0) {
toolboxIcons.prepend(windowControls[i]);
continue;
}
windowControls[i].remove();
}
}
}
_hideToolbarButtons(window) {
const wrapper = window.document.getElementById('zen-sidebar-bottom-buttons');
const elementsToHide = ['alltabs-button', 'new-tab-button'];
for (let id of elementsToHide) {
const elem = window.document.getElementById(id);
if (elem) {
wrapper.prepend(elem);
}
}
}
_dispatchResizeEvent(window) {
window.dispatchEvent(new window.Event('resize'));
}
registerToolbarNodes(window) {
window.CustomizableUI.registerToolbarNode(window.document.getElementById('zen-sidebar-top-buttons'));
window.CustomizableUI.registerToolbarNode(window.document.getElementById('zen-sidebar-bottom-buttons'));
}
})();

View File

@@ -1,173 +0,0 @@
{
var ZenStartup = {
init() {
this.openWatermark();
this._changeSidebarLocation();
this._zenInitBrowserLayout();
this._initSearchBar();
},
_zenInitBrowserLayout() {
if (this.__hasInitBrowserLayout) return;
this.__hasInitBrowserLayout = true;
try {
console.info('ZenThemeModifier: init browser layout');
const kNavbarItems = ['nav-bar', 'PersonalToolbar'];
const kNewContainerId = 'zen-appcontent-navbar-container';
let newContainer = document.getElementById(kNewContainerId);
for (let id of kNavbarItems) {
const node = document.getElementById(id);
console.assert(node, 'Could not find node with id: ' + id);
if (!node) continue;
newContainer.appendChild(node);
}
// Fix notification deck
const deckTemplate = document.getElementById('tab-notification-deck-template');
if (deckTemplate) {
document.getElementById('zen-appcontent-navbar-container').appendChild(deckTemplate);
}
this._initSidebarScrolling();
ZenWorkspaces.init();
gZenVerticalTabsManager.init();
gZenUIManager.init();
this._checkForWelcomePage();
document.l10n.setAttributes(document.getElementById('tabs-newtab-button'), 'tabs-toolbar-new-tab');
} catch (e) {
console.error('ZenThemeModifier: Error initializing browser layout', e);
}
if (gBrowserInit.delayedStartupFinished) {
this.delayedStartupFinished();
} else {
Services.obs.addObserver(this, 'browser-delayed-startup-finished');
}
},
observe(aSubject, aTopic) {
// This nsIObserver method allows us to defer initialization until after
// this window has finished painting and starting up.
if (aTopic == 'browser-delayed-startup-finished' && aSubject == window) {
Services.obs.removeObserver(this, 'browser-delayed-startup-finished');
this.delayedStartupFinished();
}
},
delayedStartupFinished() {
ZenWorkspaces.promiseInitialized.then(async () => {
await delayedStartupPromise;
await SessionStore.promiseAllWindowsRestored;
setTimeout(() => {
gZenCompactModeManager.init();
setTimeout(() => {
// A bit of a hack to make sure the tabs toolbar is updated.
// Just in case we didn't get the right size.
gZenUIManager.updateTabsToolbar();
}, 100);
}, 0);
this.closeWatermark();
});
},
openWatermark() {
if (!Services.prefs.getBoolPref('zen.watermark.enabled', false)) {
document.documentElement.removeAttribute('zen-before-loaded');
return;
}
for (let elem of document.querySelectorAll('#browser > *, #urlbar')) {
elem.style.opacity = 0;
}
},
closeWatermark() {
document.documentElement.removeAttribute('zen-before-loaded');
if (Services.prefs.getBoolPref('zen.watermark.enabled', false)) {
gZenUIManager.motion
.animate(
'#browser > *, #urlbar, #tabbrowser-tabbox > *',
{
opacity: [0, 1],
},
{
delay: 0.6,
easing: 'ease-in-out',
}
)
.then(() => {
for (let elem of document.querySelectorAll('#browser > *, #urlbar, #tabbrowser-tabbox > *')) {
elem.style.removeProperty('opacity');
}
});
}
window.requestAnimationFrame(() => {
window.dispatchEvent(new window.Event('resize')); // To recalculate the layout
});
},
_changeSidebarLocation() {
const kElementsToAppend = ['sidebar-splitter', 'sidebar-box'];
const browser = document.getElementById('browser');
const toolbox = document.getElementById('navigator-toolbox');
browser.prepend(toolbox);
const sidebarPanelWrapper = document.getElementById('tabbrowser-tabbox');
for (let id of kElementsToAppend) {
const elem = document.getElementById(id);
if (elem) {
sidebarPanelWrapper.prepend(elem);
}
}
},
_initSidebarScrolling() {
// Disable smooth scroll
const canSmoothScroll = Services.prefs.getBoolPref('zen.startup.smooth-scroll-in-tabs', false);
const tabsWrapper = document.getElementById('zen-tabs-wrapper');
gBrowser.tabContainer.addEventListener('wheel', (event) => {
if (canSmoothScroll) return;
event.preventDefault(); // Prevent the smooth scroll behavior
gBrowser.tabContainer.scrollTop += event.deltaY * 20; // Apply immediate scroll
});
// Detect overflow and underflow
const observer = new ResizeObserver((_) => {
const tabContainer = gBrowser.tabContainer;
// const isVertical = tabContainer.getAttribute('orient') === 'vertical';
// let contentSize = tabsWrapper.getBoundingClientRect()[isVertical ? 'height' : 'width'];
// NOTE: This should be contentSize > scrollClientSize, but due
// to how Gecko internally rounds in those cases, we allow for some
// minor differences (the internal Gecko layout size is 1/60th of a
// pixel, so 0.02 should cover it).
//let overflowing = contentSize - tabContainer.arrowScrollbox.scrollClientSize > 0.02;
let overflowing = true; // cheatign the system, because we want to always show make the element overflowing
window.requestAnimationFrame(() => {
tabContainer.arrowScrollbox.toggleAttribute('overflowing', overflowing);
tabContainer.arrowScrollbox.dispatchEvent(new CustomEvent(overflowing ? 'overflow' : 'underflow'));
});
});
observer.observe(tabsWrapper);
},
_initSearchBar() {
// Only focus the url bar
gURLBar.focus();
gURLBar._initCopyCutController();
gURLBar._initPasteAndGo();
gURLBar._initStripOnShare();
},
_checkForWelcomePage() {
if (!Services.prefs.getBoolPref('zen.welcome-screen.seen', false)) {
Services.prefs.setBoolPref('zen.welcome-screen.seen', true);
Services.scriptloader.loadSubScript('chrome://browser/content/zen-components/ZenWelcome.mjs', window);
}
},
};
ZenStartup.init();
}

View File

@@ -1,799 +0,0 @@
var gZenUIManager = {
_popupTrackingElements: [],
_hoverPausedForExpand: false,
_hasLoadedDOM: false,
init() {
document.addEventListener('popupshowing', this.onPopupShowing.bind(this));
document.addEventListener('popuphidden', this.onPopupHidden.bind(this));
XPCOMUtils.defineLazyPreferenceGetter(this, 'sidebarHeightThrottle', 'zen.view.sidebar-height-throttle', 500);
XPCOMUtils.defineLazyPreferenceGetter(this, 'contentElementSeparation', 'zen.theme.content-element-separation', 0);
XPCOMUtils.defineLazyPreferenceGetter(this, 'urlbarWaitToClear', 'zen.urlbar.wait-to-clear', 0);
XPCOMUtils.defineLazyPreferenceGetter(this, 'urlbarShowDomainOnly', 'zen.urlbar.show-domain-only-in-sidebar', true);
gURLBar._zenTrimURL = this.urlbarTrim.bind(this);
ChromeUtils.defineLazyGetter(this, 'motion', () => {
return ChromeUtils.importESModule('chrome://browser/content/zen-vendor/motion.min.mjs', { global: 'current' });
});
ChromeUtils.defineLazyGetter(this, '_toastContainer', () => {
return document.getElementById('zen-toast-container');
});
new ResizeObserver(this.updateTabsToolbar.bind(this)).observe(document.getElementById('TabsToolbar'));
new ResizeObserver(
gZenCommonActions.throttle(
gZenCompactModeManager.getAndApplySidebarWidth.bind(gZenCompactModeManager),
this.sidebarHeightThrottle
)
).observe(document.getElementById('navigator-toolbox'));
SessionStore.promiseAllWindowsRestored.then(() => {
this._hasLoadedDOM = true;
this.updateTabsToolbar();
});
window.addEventListener('TabClose', this.onTabClose.bind(this));
this.tabsWrapper.addEventListener('scroll', this.saveScrollbarState.bind(this));
gZenMediaController.init();
},
updateTabsToolbar() {
// Set tabs max-height to the "toolbar-items" height
const tabs = this.tabsWrapper;
// Remove tabs so we can accurately calculate the height
// without them affecting the height of the toolbar
for (const tab of gBrowser.tabs) {
if (tab.hasAttribute('zen-essential')) {
continue;
}
tab.style.maxHeight = '0px';
}
tabs.style.flex = '1';
tabs.style.removeProperty('max-height');
const toolbarRect = tabs.getBoundingClientRect();
let height = toolbarRect.height;
for (const tab of gBrowser.tabs) {
if (tab.hasAttribute('zen-essential')) {
continue;
}
tab.style.removeProperty('max-height');
}
tabs.style.removeProperty('flex');
tabs.style.maxHeight = height + 'px';
gZenVerticalTabsManager.actualWindowButtons.removeAttribute('zen-has-hover');
try {
gURLBar.zenUpdateLayoutBreakout();
} catch (error) {
console.error('Error updating layout breakout:', error);
}
},
get tabsWrapper() {
if (this._tabsWrapper) {
return this._tabsWrapper;
}
this._tabsWrapper = document.getElementById('zen-tabs-wrapper');
return this._tabsWrapper;
},
saveScrollbarState() {
this._scrollbarState = this.tabsWrapper.scrollTop;
},
restoreScrollbarState() {
this.tabsWrapper.scrollTop = this._scrollbarState;
},
onTabClose(event) {
this.updateTabsToolbar();
this.restoreScrollbarState();
},
openAndChangeToTab(url, options) {
if (window.ownerGlobal.parent) {
const tab = window.ownerGlobal.parent.gBrowser.addTrustedTab(url, options);
window.ownerGlobal.parent.gBrowser.selectedTab = tab;
return tab;
}
const tab = window.gBrowser.addTrustedTab(url, options);
window.gBrowser.selectedTab = tab;
return tab;
},
generateUuidv4() {
return Services.uuid.generateUUID().toString();
},
toogleBookmarksSidebar() {
const button = document.getElementById('zen-bookmark-button');
SidebarController.toggle('viewBookmarksSidebar', button);
},
createValidXULText(text) {
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
},
/**
* Adds the 'has-popup-menu' attribute to the element when popup is opened on it.
* @param element element to track
*/
addPopupTrackingAttribute(element) {
this._popupTrackingElements.push(element);
},
removePopupTrackingAttribute(element) {
this._popupTrackingElements.remove(element);
},
onPopupShowing(showEvent) {
for (const el of this._popupTrackingElements) {
// target may be inside a shadow root, not directly under the element
// we also ignore menus inside panels
if (
!el.contains(showEvent.explicitOriginalTarget) ||
(showEvent.explicitOriginalTarget instanceof Element && showEvent.explicitOriginalTarget?.closest('panel'))
) {
continue;
}
document.removeEventListener('mousemove', this.__removeHasPopupAttribute);
el.setAttribute('has-popup-menu', '');
this.__currentPopup = showEvent.target;
this.__currentPopupTrackElement = el;
break;
}
},
onPopupHidden(hideEvent) {
if (!this.__currentPopup || this.__currentPopup !== hideEvent.target) {
return;
}
const element = this.__currentPopupTrackElement;
if (document.getElementById('main-window').matches(':hover')) {
element.removeAttribute('has-popup-menu');
} else {
this.__removeHasPopupAttribute = () => element.removeAttribute('has-popup-menu');
document.addEventListener('mousemove', this.__removeHasPopupAttribute, { once: true });
}
this.__currentPopup = null;
this.__currentPopupTrackElement = null;
},
// Section: URL bar
get newtabButtons() {
return document.querySelectorAll('#tabs-newtab-button');
},
_prevUrlbarLabel: null,
_lastSearch: '',
_clearTimeout: null,
_lastTab: null,
handleNewTab(werePassedURL, searchClipboard, where) {
const shouldOpenURLBar = gZenVerticalTabsManager._canReplaceNewTab && !werePassedURL && !searchClipboard && where === 'tab';
if (shouldOpenURLBar) {
if (this._clearTimeout) {
clearTimeout(this._clearTimeout);
}
this._lastTab = gBrowser.selectedTab;
this._lastTab._visuallySelected = false;
this._prevUrlbarLabel = gURLBar._untrimmedValue;
gURLBar._zenHandleUrlbarClose = this.handleUrlbarClose.bind(this);
gURLBar.setAttribute('zen-newtab', true);
for (const button of this.newtabButtons) {
button.setAttribute('in-urlbar', true);
}
document.getElementById('Browser:OpenLocation').doCommand();
gURLBar.search(this._lastSearch);
return true;
}
return false;
},
clearUrlbarData() {
this._prevUrlbarLabel = null;
this._lastSearch = '';
},
handleUrlbarClose(onSwitch) {
gURLBar._zenHandleUrlbarClose = null;
gURLBar.removeAttribute('zen-newtab');
this._lastTab._visuallySelected = true;
this._lastTab = null;
for (const button of this.newtabButtons) {
button.removeAttribute('in-urlbar');
}
if (onSwitch) {
this.clearUrlbarData();
} else {
this._lastSearch = gURLBar._untrimmedValue;
this._clearTimeout = setTimeout(() => {
this.clearUrlbarData();
}, this.urlbarWaitToClear);
}
gURLBar.setURI(this._prevUrlbarLabel, onSwitch, false, false, !onSwitch);
gURLBar.handleRevert();
if (gURLBar.focused) {
gURLBar.view.close({ elementPicked: onSwitch });
gURLBar.updateTextOverflow();
if (gBrowser.selectedTab.linkedBrowser && onSwitch) {
gURLBar.getBrowserState(gBrowser.selectedTab.linkedBrowser).urlbarFocused = false;
}
}
},
urlbarTrim(aURL) {
if (gZenVerticalTabsManager._hasSetSingleToolbar && this.urlbarShowDomainOnly) {
let url = BrowserUIUtils.removeSingleTrailingSlashFromURL(aURL);
return url.startsWith('https://') ? url.split('/')[2] : url;
}
return BrowserUIUtils.trimURL(aURL);
},
// Section: Notification messages
_createToastElement(messageId, options) {
const element = document.createXULElement('vbox');
const label = document.createXULElement('label');
document.l10n.setAttributes(label, messageId, options);
element.appendChild(label);
if (options.descriptionId) {
const description = document.createXULElement('label');
description.classList.add('description');
document.l10n.setAttributes(description, options.descriptionId, options);
element.appendChild(description);
}
element.classList.add('zen-toast');
return element;
},
async showToast(messageId, options = {}) {
const toast = this._createToastElement(messageId, options);
this._toastContainer.removeAttribute('hidden');
this._toastContainer.appendChild(toast);
await this.motion.animate(toast, { opacity: [0, 1], scale: [0.8, 1] }, { type: 'spring', bounce: 0.5, duration: 0.7 });
await new Promise((resolve) => setTimeout(resolve, 3000));
await this.motion.animate(toast, { opacity: [1, 0], scale: [1, 0.9] }, { duration: 0.2, bounce: 0 });
const toastHeight = toast.getBoundingClientRect().height;
// 5 for the separation between toasts
await this.motion.animate(toast, { marginBottom: [0, `-${toastHeight + 5}px`] }, { duration: 0.2 });
toast.remove();
if (!this._toastContainer.hasChildNodes()) {
this._toastContainer.setAttribute('hidden', 'true');
}
},
get panelUIPosition() {
return gZenVerticalTabsManager._hasSetSingleToolbar ? 'bottomleft topleft' : 'bottomright topright';
},
};
var gZenVerticalTabsManager = {
init() {
this._multiWindowFeature = new ZenMultiWindowFeature();
this._initWaitPromise();
ChromeUtils.defineLazyGetter(this, 'isWindowsStyledButtons', () => {
return !(
window.AppConstants.platform === 'macosx' ||
window.matchMedia('(-moz-gtk-csd-reversed-placement)').matches ||
Services.prefs.getBoolPref('zen.view.experimental-force-window-controls-left')
);
});
ChromeUtils.defineLazyGetter(this, 'hidesTabsToolbar', () => {
return (
document.documentElement.getAttribute('chromehidden').includes('toolbar') ||
document.documentElement.getAttribute('chromehidden').includes('menubar')
);
});
XPCOMUtils.defineLazyPreferenceGetter(this, '_canReplaceNewTab', 'zen.urlbar.replace-newtab', true);
var updateEvent = this._updateEvent.bind(this);
var onPrefChange = this._onPrefChange.bind(this);
this.initializePreferences(onPrefChange);
this._toolbarOriginalParent = document.getElementById('nav-bar').parentElement;
gZenCompactModeManager.addEventListener(updateEvent);
this.initRightSideOrderContextMenu();
window.addEventListener('customizationstarting', this._preCustomize.bind(this));
window.addEventListener('aftercustomization', this._postCustomize.bind(this));
this._updateEvent();
if (!this.isWindowsStyledButtons) {
document.documentElement.setAttribute('zen-window-buttons-reversed', true);
}
this._renameTabHalt = this.renameTabHalt.bind(this);
gBrowser.tabContainer.addEventListener('dblclick', this.renameTabStart.bind(this));
},
toggleExpand() {
const newVal = !Services.prefs.getBoolPref('zen.view.sidebar-expanded');
Services.prefs.setBoolPref('zen.view.sidebar-expanded', newVal);
},
get navigatorToolbox() {
if (this._navigatorToolbox) {
return this._navigatorToolbox;
}
this._navigatorToolbox = document.getElementById('navigator-toolbox');
return this._navigatorToolbox;
},
initRightSideOrderContextMenu() {
const kConfigKey = 'zen.tabs.vertical.right-side';
const fragment = window.MozXULElement.parseXULToFragment(`
<menuitem id="zen-toolbar-context-tabs-right"
type="checkbox"
${Services.prefs.getBoolPref(kConfigKey) ? 'checked="true"' : ''}
data-lazy-l10n-id="zen-toolbar-context-tabs-right"
command="cmd_zenToggleTabsOnRight"
/>
`);
document.getElementById('viewToolbarsMenuSeparator').before(fragment);
},
get _topButtonsSeparatorElement() {
if (this.__topButtonsSeparatorElement) {
return this.__topButtonsSeparatorElement;
}
this.__topButtonsSeparatorElement = document.getElementById('zen-sidebar-top-buttons-separator');
return this.__topButtonsSeparatorElement;
},
animateTab(aTab) {
if (!gZenUIManager.motion || !aTab || !gZenUIManager._hasLoadedDOM) {
return;
}
// get next visible tab
const isLastTab = () => {
const visibleTabs = gBrowser.visibleTabs;
return visibleTabs[visibleTabs.length - 1] === aTab;
};
try {
const tabSize = aTab.getBoundingClientRect().height;
const transform = `-${tabSize}px`;
gZenUIManager.motion
.animate(
aTab,
{
opacity: [0, 1],
transform: ['scale(0.95)', 'scale(1)'],
marginBottom: isLastTab() ? [] : [transform, '0px'],
},
{
duration: 0.12,
easing: 'ease-out',
}
)
.then(() => {
aTab.style.removeProperty('margin-bottom');
aTab.style.removeProperty('transform');
aTab.style.removeProperty('opacity');
});
gZenUIManager.motion
.animate(
aTab.querySelector('.tab-content'),
{
filter: ['blur(1px)', 'blur(0px)'],
},
{
duration: 0.12,
easing: 'ease-out',
}
)
.then(() => {
aTab.querySelector('.tab-stack').style.removeProperty('filter');
});
} catch (e) {
console.error(e);
}
},
get actualWindowButtons() {
// we have multiple ".titlebar-buttonbox-container" in the DOM, because of the titlebar
if (!this.__actualWindowButtons) {
this.__actualWindowButtons = !this.isWindowsStyledButtons
? document.querySelector('.titlebar-buttonbox-container') // TODO: test if it works 100% of the time
: document.querySelector('#nav-bar .titlebar-buttonbox-container');
this.__actualWindowButtons.setAttribute('overflows', 'false');
}
return this.__actualWindowButtons;
},
async _preCustomize() {
await this._multiWindowFeature.foreachWindowAsActive(async (browser) => {
browser.gZenVerticalTabsManager._updateEvent({ forCustomizableMode: true, dontRebuildAreas: true });
});
this.rebuildAreas();
this.navigatorToolbox.setAttribute('zen-sidebar-expanded', 'true');
document.documentElement.setAttribute('zen-sidebar-expanded', 'true'); // force expanded sidebar
},
_postCustomize() {
// No need to use `await` here, because the customization is already done
this._multiWindowFeature.foreachWindowAsActive(async (browser) => {
browser.gZenVerticalTabsManager._updateEvent({ dontRebuildAreas: true });
});
},
initializePreferences(updateEvent) {
XPCOMUtils.defineLazyPreferenceGetter(this, '_prefsVerticalTabs', 'zen.tabs.vertical', true, updateEvent);
XPCOMUtils.defineLazyPreferenceGetter(this, '_prefsRightSide', 'zen.tabs.vertical.right-side', false, updateEvent);
XPCOMUtils.defineLazyPreferenceGetter(this, '_prefsUseSingleToolbar', 'zen.view.use-single-toolbar', false, updateEvent);
XPCOMUtils.defineLazyPreferenceGetter(this, '_prefsSidebarExpanded', 'zen.view.sidebar-expanded', false, updateEvent);
XPCOMUtils.defineLazyPreferenceGetter(
this,
'_prefsSidebarExpandedMaxWidth',
'zen.view.sidebar-expanded.max-width',
300,
updateEvent
);
},
_initWaitPromise() {
this._waitPromise = new Promise((resolve) => {
this._resolveWaitPromise = resolve;
});
},
async _onPrefChange() {
this._resolveWaitPromise();
// only run if we are in the active window
await this._multiWindowFeature.foreachWindowAsActive(async (browser) => {
if (browser.gZenVerticalTabsManager._multiWindowFeature.windowIsActive(browser)) {
return;
}
await browser.gZenVerticalTabsManager._waitPromise;
browser.gZenVerticalTabsManager._updateEvent({ dontRebuildAreas: true });
browser.gZenVerticalTabsManager._initWaitPromise();
});
if (ZenMultiWindowFeature.isActiveWindow) {
this._updateEvent();
this._initWaitPromise();
}
},
recalculateURLBarHeight() {
document.getElementById('urlbar').removeAttribute('--urlbar-height');
if (!this._hasSetSingleToolbar) {
document.getElementById('urlbar').style.setProperty('--urlbar-height', '32px');
} else {
try {
gURLBar.zenUpdateLayoutBreakout();
} catch (e) {
console.error(e);
}
}
},
_updateEvent({ forCustomizableMode = false, dontRebuildAreas = false } = {}) {
if (this._isUpdating) {
return;
}
this._isUpdating = true;
try {
this._updateMaxWidth();
if (window.docShell) {
window.docShell.treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIAppWindow).rollupAllPopups();
}
const topButtons = document.getElementById('zen-sidebar-top-buttons');
const isCompactMode = gZenCompactModeManager.preference && !forCustomizableMode;
const isVerticalTabs = this._prefsVerticalTabs || forCustomizableMode;
const isSidebarExpanded = this._prefsSidebarExpanded || !isVerticalTabs;
const isRightSide = this._prefsRightSide && isVerticalTabs;
const isSingleToolbar =
((this._prefsUseSingleToolbar && isVerticalTabs && isSidebarExpanded) || !isVerticalTabs) &&
!forCustomizableMode &&
!this.hidesTabsToolbar;
const titlebar = document.getElementById('titlebar');
gBrowser.tabContainer.setAttribute('orient', isVerticalTabs ? 'vertical' : 'horizontal');
gBrowser.tabContainer.arrowScrollbox.setAttribute('orient', isVerticalTabs ? 'vertical' : 'horizontal');
// on purpose, we set the orient to horizontal, because the arrowScrollbox is vertical
gBrowser.tabContainer.arrowScrollbox.scrollbox.setAttribute(
'orient',
isVerticalTabs && ZenWorkspaces.workspaceEnabled ? 'horizontal' : 'vertical'
);
const buttonsTarget = document.getElementById('zen-sidebar-top-buttons-customization-target');
if (isRightSide) {
this.navigatorToolbox.setAttribute('zen-right-side', 'true');
document.documentElement.setAttribute('zen-right-side', 'true');
} else {
this.navigatorToolbox.removeAttribute('zen-right-side');
document.documentElement.removeAttribute('zen-right-side');
}
if (isSidebarExpanded) {
this.navigatorToolbox.setAttribute('zen-sidebar-expanded', 'true');
document.documentElement.setAttribute('zen-sidebar-expanded', 'true');
gBrowser.tabContainer.setAttribute('expanded', 'true');
} else {
this.navigatorToolbox.removeAttribute('zen-sidebar-expanded');
document.documentElement.removeAttribute('zen-sidebar-expanded');
gBrowser.tabContainer.removeAttribute('expanded');
}
const appContentNavbarContaienr = document.getElementById('zen-appcontent-navbar-container');
let shouldHide = false;
if (
((!isRightSide && this.isWindowsStyledButtons) ||
(isRightSide && !this.isWindowsStyledButtons) ||
(isCompactMode && isSingleToolbar && this.isWindowsStyledButtons)) &&
isSingleToolbar
) {
appContentNavbarContaienr.setAttribute('should-hide', 'true');
shouldHide = true;
} else {
appContentNavbarContaienr.removeAttribute('should-hide');
}
// Check if the sidebar is in hover mode
if (!this.navigatorToolbox.hasAttribute('zen-right-side') && !isCompactMode) {
this.navigatorToolbox.prepend(topButtons);
}
let windowButtons = this.actualWindowButtons;
let doNotChangeWindowButtons = !isCompactMode && isRightSide && this.isWindowsStyledButtons;
const navBar = document.getElementById('nav-bar');
if (isSingleToolbar) {
this._navbarParent = navBar.parentElement;
let elements = document.querySelectorAll(
'#nav-bar-customization-target > :is([cui-areatype="toolbar"], .chromeclass-toolbar-additional):not(#urlbar-container):not(toolbarspring)'
);
elements = Array.from(elements).reverse();
// Add separator if it doesn't exist
if (!this._hasSetSingleToolbar) {
buttonsTarget.append(this._topButtonsSeparatorElement);
}
for (const button of elements) {
this._topButtonsSeparatorElement.after(button);
}
buttonsTarget.prepend(document.getElementById('unified-extensions-button'));
const panelUIButton = document.getElementById('PanelUI-button');
buttonsTarget.prepend(panelUIButton);
panelUIButton.setAttribute('overflows', 'false');
buttonsTarget.parentElement.append(document.getElementById('nav-bar-overflow-button'));
if (this.isWindowsStyledButtons && !doNotChangeWindowButtons) {
appContentNavbarContaienr.append(windowButtons);
}
if (isCompactMode) {
titlebar.prepend(navBar);
titlebar.prepend(topButtons);
} else {
titlebar.before(topButtons);
titlebar.before(navBar);
}
document.documentElement.setAttribute('zen-single-toolbar', true);
this._hasSetSingleToolbar = true;
} else if (this._hasSetSingleToolbar) {
this._hasSetSingleToolbar = false;
// Do the opposite
this._navbarParent.prepend(navBar);
const elements = document.querySelectorAll(
'#zen-sidebar-top-buttons-customization-target > :is([cui-areatype="toolbar"], .chromeclass-toolbar-additional)'
);
for (const button of elements) {
document.getElementById('nav-bar-customization-target').append(button);
}
this._topButtonsSeparatorElement.remove();
document.documentElement.removeAttribute('zen-single-toolbar');
const panelUIButton = document.getElementById('PanelUI-button');
navBar.appendChild(panelUIButton);
panelUIButton.removeAttribute('overflows');
navBar.appendChild(document.getElementById('nav-bar-overflow-button'));
this._toolbarOriginalParent.prepend(navBar);
if (!dontRebuildAreas) {
this.rebuildAreas();
}
}
if (isCompactMode) {
titlebar.prepend(topButtons);
} else {
if (isSidebarExpanded) {
titlebar.before(topButtons);
} else {
titlebar.prepend(topButtons);
}
}
// Case: single toolbar, not compact mode, not right side and macos styled buttons
if (!doNotChangeWindowButtons && isSingleToolbar && !isCompactMode && !isRightSide && !this.isWindowsStyledButtons) {
topButtons.prepend(windowButtons);
}
// Case: single toolbar, compact mode, right side and windows styled buttons
if (isSingleToolbar && isCompactMode && isRightSide && this.isWindowsStyledButtons) {
topButtons.prepend(windowButtons);
}
if (doNotChangeWindowButtons) {
if (isRightSide && !isSidebarExpanded) {
navBar.appendChild(windowButtons);
} else {
topButtons.appendChild(windowButtons);
}
} else if (!isSingleToolbar && !isCompactMode) {
if (this.isWindowsStyledButtons) {
if (isRightSide) {
appContentNavbarContaienr.append(windowButtons);
} else {
navBar.append(windowButtons);
}
} else {
// not windows styled buttons
if (isRightSide || !isSidebarExpanded) {
navBar.prepend(windowButtons);
} else {
topButtons.prepend(windowButtons);
}
}
} else if (!isSingleToolbar && isCompactMode) {
navBar.appendChild(windowButtons);
} else if (isSingleToolbar && isCompactMode) {
if (!isRightSide && !this.isWindowsStyledButtons) {
topButtons.prepend(windowButtons);
}
}
if (shouldHide) {
appContentNavbarContaienr.append(windowButtons);
}
gZenCompactModeManager.updateCompactModeContext(isSingleToolbar);
this.recalculateURLBarHeight();
// Always move the splitter next to the sidebar
this.navigatorToolbox.after(document.getElementById('zen-sidebar-splitter'));
window.dispatchEvent(new Event('resize'));
if (!isCompactMode) {
gZenCompactModeManager.getAndApplySidebarWidth();
}
gZenUIManager.updateTabsToolbar();
} catch (e) {
console.error(e);
}
this._isUpdating = false;
},
rebuildAreas() {
CustomizableUI.zenInternalCU._rebuildRegisteredAreas(/* zenDontRebuildCollapsed */ true);
},
_updateMaxWidth() {
const maxWidth = Services.prefs.getIntPref('zen.view.sidebar-expanded.max-width');
const toolbox = document.getElementById('navigator-toolbox');
if (!this._prefsCompactMode) {
toolbox.style.maxWidth = `${maxWidth}px`;
} else {
toolbox.style.removeProperty('maxWidth');
}
},
get expandButton() {
if (this._expandButton) {
return this._expandButton;
}
this._expandButton = document.getElementById('zen-expand-sidebar-button');
return this._expandButton;
},
toggleTabsOnRight() {
const newVal = !Services.prefs.getBoolPref('zen.tabs.vertical.right-side');
Services.prefs.setBoolPref('zen.tabs.vertical.right-side', newVal);
},
appendCustomizableItem(target, child, placements) {
if (
target.id === 'zen-sidebar-top-buttons-customization-target' &&
this._hasSetSingleToolbar &&
placements.includes(child.id)
) {
return this._topButtonsSeparatorElement.before(child);
}
target.appendChild(child);
},
async renameTabKeydown(event) {
if (event.key === 'Enter') {
let label = this._tabEdited.querySelector('.tab-label-container-editing');
let input = this._tabEdited.querySelector('#tab-label-input');
let newName = input.value.trim();
// Check if name is blank, reset if so
// Always remove, so we can always rename and if it's empty,
// it will reset to the original name anyway
this._tabEdited.removeAttribute('zen-has-static-label');
if (newName) {
gBrowser._setTabLabel(this._tabEdited, newName);
this._tabEdited.setAttribute('zen-has-static-label', 'true');
gZenUIManager.showToast('zen-tabs-renamed');
} else {
gBrowser.setTabTitle(this._tabEdited);
}
if (this._tabEdited.getAttribute('zen-pin-id')) {
// Update pin title in storage
await gZenPinnedTabManager.updatePinTitle(this._tabEdited, this._tabEdited.label, !!newName);
}
document.documentElement.removeAttribute('zen-renaming-tab');
// Maybe add some confetti here?!?
gZenUIManager.motion.animate(
this._tabEdited,
{
scale: [1, 0.98, 1],
},
{
duration: 0.25,
}
);
this._tabEdited.querySelector('.tab-editor-container').remove();
label.classList.remove('tab-label-container-editing');
this._tabEdited = null;
} else if (event.key === 'Escape') {
event.target.blur();
}
},
renameTabStart(event) {
if (
this._tabEdited ||
!Services.prefs.getBoolPref('zen.tabs.rename-tabs') ||
Services.prefs.getBoolPref('browser.tabs.closeTabByDblclick') ||
!gZenVerticalTabsManager._prefsSidebarExpanded
)
return;
this._tabEdited = event.target.closest('.tabbrowser-tab');
if (!this._tabEdited || !this._tabEdited.pinned || this._tabEdited.hasAttribute('zen-essential')) {
this._tabEdited = null;
return;
}
document.documentElement.setAttribute('zen-renaming-tab', 'true');
const label = this._tabEdited.querySelector('.tab-label-container');
label.classList.add('tab-label-container-editing');
const container = window.MozXULElement.parseXULToFragment(`
<vbox class="tab-label-container tab-editor-container" flex="1" align="start" pack="center"></vbox>
`);
label.after(container);
const containerHtml = this._tabEdited.querySelector('.tab-editor-container');
const input = document.createElement('input');
input.id = 'tab-label-input';
input.value = this._tabEdited.label;
input.addEventListener('keydown', this.renameTabKeydown.bind(this));
containerHtml.appendChild(input);
input.focus();
input.select();
input.addEventListener('blur', this._renameTabHalt);
},
renameTabHalt(event) {
if (document.activeElement === event.target || !this._tabEdited) {
return;
}
document.documentElement.removeAttribute('zen-renaming-tab');
this._tabEdited.querySelector('.tab-editor-container').remove();
const label = this._tabEdited.querySelector('.tab-label-container-editing');
label.classList.remove('tab-label-container-editing');
this._tabEdited = null;
},
};

View File

@@ -43,7 +43,4 @@ Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/Zen
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenViewSplitter.mjs", this);
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenGlanceManager.mjs", this);
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenMediaController.mjs", this);
// Unimportant scripts
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenRices.mjs", this);
</script>

View File

@@ -1,82 +1,87 @@
content/browser/zenThemeModifier.js (content/zenThemeModifier.js)
content/browser/ZenStartup.mjs (content/ZenStartup.mjs)
content/browser/zen-sets.js (content/zen-sets.js)
content/browser/ZenUIManager.mjs (content/ZenUIManager.mjs)
content/browser/ZenCustomizableUI.sys.mjs (content/ZenCustomizableUI.sys.mjs)
content/browser/zen-components/ZenUIMigration.mjs (zen-components/ZenUIMigration.mjs)
content/browser/zen-components/ZenCompactMode.mjs (zen-components/ZenCompactMode.mjs)
content/browser/zen-components/ZenViewSplitter.mjs (zen-components/ZenViewSplitter.mjs)
content/browser/zen-components/ZenThemesCommon.mjs (zen-components/ZenThemesCommon.mjs)
content/browser/zen-components/ZenWorkspaces.mjs (zen-components/ZenWorkspaces.mjs)
content/browser/zen-components/ZenWorkspacesStorage.mjs (zen-components/ZenWorkspacesStorage.mjs)
content/browser/zen-components/ZenWorkspacesSync.mjs (zen-components/ZenWorkspacesSync.mjs)
content/browser/zen-components/ZenKeyboardShortcuts.mjs (zen-components/ZenKeyboardShortcuts.mjs)
content/browser/zen-components/ZenThemesImporter.mjs (zen-components/ZenThemesImporter.mjs)
content/browser/zen-components/ZenTabUnloader.mjs (zen-components/ZenTabUnloader.mjs)
content/browser/zen-components/ZenPinnedTabsStorage.mjs (zen-components/ZenPinnedTabsStorage.mjs)
content/browser/zen-components/ZenPinnedTabManager.mjs (zen-components/ZenPinnedTabManager.mjs)
content/browser/zen-components/ZenCommonUtils.mjs (zen-components/ZenCommonUtils.mjs)
content/browser/zen-components/ZenGradientGenerator.mjs (zen-components/ZenGradientGenerator.mjs)
content/browser/zen-components/ZenGlanceManager.mjs (zen-components/ZenGlanceManager.mjs)
content/browser/zen-components/ZenFolders.mjs (zen-components/ZenFolders.mjs)
content/browser/zen-components/ZenActorsManager.mjs (zen-components/ZenActorsManager.mjs)
content/browser/zen-components/ZenRices.mjs (zen-components/ZenRices.mjs)
content/browser/zen-components/ZenEmojies.mjs (zen-components/ZenEmojies.mjs)
content/browser/zen-components/ZenWelcome.mjs (zen-components/ZenWelcome.mjs)
content/browser/zen-components/ZenMediaController.mjs (zen-components/ZenMediaController.mjs)
content/browser/zenThemeModifier.js (../../zen/common/zenThemeModifier.js)
content/browser/ZenStartup.mjs (../../zen/common/ZenStartup.mjs)
content/browser/zen-sets.js (../../zen/common/zen-sets.js)
content/browser/ZenUIManager.mjs (../../zen/common/ZenUIManager.mjs)
content/browser/zen-components/ZenActorsManager.mjs (../../zen/common/ZenActorsManager.mjs)
content/browser/zen-components/ZenEmojies.mjs (../../zen/common/ZenEmojies.mjs)
content/browser/ZenCustomizableUI.sys.mjs (../../zen/common/ZenCustomizableUI.sys.mjs)
content/browser/zen-components/ZenUIMigration.mjs (../../zen/common/ZenUIMigration.mjs)
content/browser/zen-components/ZenCommonUtils.mjs (../../zen/common/ZenCommonUtils.mjs)
content/browser/zen-styles/zen-theme.css (content/zen-styles/zen-theme.css)
content/browser/zen-styles/zen-buttons.css (content/zen-styles/zen-buttons.css)
content/browser/zen-styles/zen-tabs.css (content/zen-styles/zen-tabs.css)
* content/browser/zen-styles/zen-tabs/vertical-tabs.css (content/zen-styles/zen-tabs/vertical-tabs.css)
content/browser/zen-styles/zen-tabs/horizontal-tabs.css (content/zen-styles/zen-tabs/horizontal-tabs.css)
content/browser/zen-styles/zen-browser-ui.css (content/zen-styles/zen-browser-ui.css)
content/browser/zen-styles/zen-animations.css (content/zen-styles/zen-animations.css)
content/browser/zen-styles/zen-panel-ui.css (content/zen-styles/zen-panel-ui.css)
content/browser/zen-styles/zen-single-components.css (content/zen-styles/zen-single-components.css)
content/browser/zen-styles/zen-sidebar.css (content/zen-styles/zen-sidebar.css)
content/browser/zen-styles/zen-toolbar.css (content/zen-styles/zen-toolbar.css)
content/browser/zen-styles/zen-decks.css (content/zen-styles/zen-decks.css)
content/browser/zen-styles/zen-folders.css (content/zen-styles/zen-folders.css)
content/browser/zen-styles/zen-glance.css (content/zen-styles/zen-glance.css)
content/browser/zen-styles/zen-browser-container.css (content/zen-styles/zen-browser-container.css)
content/browser/zen-styles/zen-workspaces.css (content/zen-styles/zen-workspaces.css)
content/browser/zen-styles/zen-urlbar.css (content/zen-styles/zen-urlbar.css)
content/browser/zen-styles/zen-popup.css (content/zen-styles/zen-popup.css)
content/browser/zen-styles/zen-gradient-generator.css (content/zen-styles/zen-gradient-generator.css)
content/browser/zen-styles/zen-rices.css (content/zen-styles/zen-rices.css)
content/browser/zen-styles/zen-branding.css (content/zen-styles/zen-branding.css)
content/browser/zen-styles/zen-welcome.css (content/zen-styles/zen-welcome.css)
content/browser/zen-styles/zen-media-controls.css (content/zen-styles/zen-media-controls.css)
content/browser/zen-styles/zen-theme.css (../../zen/common/styles/zen-theme.css)
content/browser/zen-styles/zen-buttons.css (../../zen/common/styles/zen-buttons.css)
content/browser/zen-styles/zen-browser-ui.css (../../zen/common/styles/zen-browser-ui.css)
content/browser/zen-styles/zen-animations.css (../../zen/common/styles/zen-animations.css)
content/browser/zen-styles/zen-panel-ui.css (../../zen/common/styles/zen-panel-ui.css)
content/browser/zen-styles/zen-single-components.css (../../zen/common/styles/zen-single-components.css)
content/browser/zen-styles/zen-sidebar.css (../../zen/common/styles/zen-sidebar.css)
content/browser/zen-styles/zen-toolbar.css (../../zen/common/styles/zen-toolbar.css)
content/browser/zen-styles/zen-browser-container.css (../../zen/common/styles/zen-browser-container.css)
content/browser/zen-styles/zen-urlbar.css (../../zen/common/styles/zen-urlbar.css)
content/browser/zen-styles/zen-popup.css (../../zen/common/styles/zen-popup.css)
content/browser/zen-styles/zen-branding.css (../../zen/common/styles/zen-branding.css)
content/browser/zen-styles/zen-panels/bookmarks.css (content/zen-styles/zen-panels/bookmarks.css)
content/browser/zen-styles/zen-panels/extensions.css (content/zen-styles/zen-panels/extensions.css)
content/browser/zen-styles/zen-panels/print.css (content/zen-styles/zen-panels/print.css)
content/browser/zen-styles/zen-panels/dialog.css (content/zen-styles/zen-panels/dialog.css)
content/browser/zen-styles/zen-panels/bookmarks.css (../../zen/common/styles/zen-panels/bookmarks.css)
content/browser/zen-styles/zen-panels/extensions.css (../../zen/common/styles/zen-panels/extensions.css)
content/browser/zen-styles/zen-panels/print.css (../../zen/common/styles/zen-panels/print.css)
content/browser/zen-styles/zen-panels/dialog.css (../../zen/common/styles/zen-panels/dialog.css)
* content/browser/zen-styles/zen-compact-mode.css (content/zen-styles/zen-compact-mode.css)
content/browser/zen-components/ZenCompactMode.mjs (../../zen/compact-mode/ZenCompactMode.mjs)
* content/browser/zen-styles/zen-compact-mode.css (../../zen/compact-mode/zen-compact-mode.css)
# Images
content/browser/zen-images/gradient.png (content/zen-images/gradient.png)
content/browser/zen-images/brand-header.svg (content/zen-images/brand-header.svg)
content/browser/zen-images/layouts/collapsed.png (content/zen-images/layouts/collapsed.png)
content/browser/zen-images/layouts/multiple-toolbar.png (content/zen-images/layouts/multiple-toolbar.png)
content/browser/zen-images/layouts/single-toolbar.png (content/zen-images/layouts/single-toolbar.png)
content/browser/zen-images/grain-bg.png (content/zen-images/grain-bg.png)
content/browser/zen-images/note-indicator.svg (content/zen-images/note-indicator.svg)
content/browser/zen-components/ZenViewSplitter.mjs (../../zen/split-view/ZenViewSplitter.mjs)
content/browser/zen-styles/zen-decks.css (../../zen/split-view/zen-decks.css)
# Actors
content/browser/zen-components/actors/ZenThemeMarketplaceParent.sys.mjs (zen-components/actors/ZenThemeMarketplaceParent.sys.mjs)
content/browser/zen-components/actors/ZenThemeMarketplaceChild.sys.mjs (zen-components/actors/ZenThemeMarketplaceChild.sys.mjs)
content/browser/zen-components/actors/ZenGlanceChild.sys.mjs (zen-components/actors/ZenGlanceChild.sys.mjs)
content/browser/zen-components/actors/ZenGlanceParent.sys.mjs (zen-components/actors/ZenGlanceParent.sys.mjs)
content/browser/zen-components/ZenThemesCommon.mjs (../../zen/mods/ZenThemesCommon.mjs)
content/browser/zen-components/ZenThemesImporter.mjs (../../zen/mods/ZenThemesImporter.mjs)
content/browser/zen-components/actors/ZenThemeMarketplaceParent.sys.mjs (../../zen/mods/actors/ZenThemeMarketplaceParent.sys.mjs)
content/browser/zen-components/actors/ZenThemeMarketplaceChild.sys.mjs (../../zen/mods/actors/ZenThemeMarketplaceChild.sys.mjs)
# Fonts
content/browser/zen-fonts/JunicodeVF-Italic.woff2 (content/zen-fonts/JunicodeVF-Italic.woff2)
content/browser/zen-fonts/JunicodeVF-Roman.woff2 (content/zen-fonts/JunicodeVF-Roman.woff2)
content/browser/zen-components/ZenWorkspaces.mjs (../../zen/workspaces/ZenWorkspaces.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/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)
content/browser/zen-components/ZenKeyboardShortcuts.mjs (../../zen/kbs/ZenKeyboardShortcuts.mjs)
content/browser/zen-components/ZenTabUnloader.mjs (../../zen/tabs/ZenTabUnloader.mjs)
content/browser/zen-components/ZenPinnedTabsStorage.mjs (../../zen/tabs/ZenPinnedTabsStorage.mjs)
content/browser/zen-components/ZenPinnedTabManager.mjs (../../zen/tabs/ZenPinnedTabManager.mjs)
content/browser/zen-styles/zen-tabs.css (../../zen/tabs/zen-tabs.css)
* content/browser/zen-styles/zen-tabs/vertical-tabs.css (../../zen/tabs/zen-tabs/vertical-tabs.css)
content/browser/zen-styles/zen-tabs/horizontal-tabs.css (../../zen/tabs/zen-tabs/horizontal-tabs.css)
content/browser/zen-components/ZenGlanceManager.mjs (../../zen/glance/ZenGlanceManager.mjs)
content/browser/zen-styles/zen-glance.css (../../zen/glance/zen-glance.css)
content/browser/zen-components/actors/ZenGlanceChild.sys.mjs (../../zen/glance/actors/ZenGlanceChild.sys.mjs)
content/browser/zen-components/actors/ZenGlanceParent.sys.mjs (../../zen/glance/actors/ZenGlanceParent.sys.mjs)
content/browser/zen-components/ZenFolders.mjs (../../zen/folders/ZenFolders.mjs)
content/browser/zen-styles/zen-folders.css (../../zen/folders/zen-folders.css)
content/browser/zen-components/ZenWelcome.mjs (../../zen/welcome/ZenWelcome.mjs)
content/browser/zen-styles/zen-welcome.css (../../zen/welcome/zen-welcome.css)
content/browser/zen-components/ZenMediaController.mjs (../../zen/media/ZenMediaController.mjs)
content/browser/zen-styles/zen-media-controls.css (../../zen/media/zen-media-controls.css)
# Images
content/browser/zen-images/gradient.png (../../zen/images/gradient.png)
content/browser/zen-images/brand-header.svg (../../zen/images/brand-header.svg)
content/browser/zen-images/layouts/collapsed.png (../../zen/images/layouts/collapsed.png)
content/browser/zen-images/layouts/multiple-toolbar.png (../../zen/images/layouts/multiple-toolbar.png)
content/browser/zen-images/layouts/single-toolbar.png (../../zen/images/layouts/single-toolbar.png)
content/browser/zen-images/grain-bg.png (../../zen/images/grain-bg.png)
content/browser/zen-images/note-indicator.svg (../../zen/images/note-indicator.svg)
# Fonts
content/browser/zen-fonts/JunicodeVF-Italic.woff2 (../../zen/fonts/JunicodeVF-Italic.woff2)
content/browser/zen-fonts/JunicodeVF-Roman.woff2 (../../zen/fonts/JunicodeVF-Roman.woff2)
# JS Vendor
content/browser/zen-vendor/tsparticles.confetti.bundle.min.js (content/zen-vendor/tsparticles.confetti.bundle.min.js)
content/browser/zen-vendor/motion.min.mjs (content/zen-vendor/motion.min.mjs)
content/browser/zen-vendor/tsparticles.confetti.bundle.min.js (../../zen/vendor/tsparticles.confetti.bundle.min.js)
content/browser/zen-vendor/motion.min.mjs (../../zen/vendor/motion.min.mjs)

View File

@@ -1,5 +0,0 @@
<vbox id="zen-glance-sidebar-container" hidden="true">
<toolbarbutton id="zen-glance-sidebar-close" data-l10n-id="zen-general-confirm" class="toolbarbutton-1"/>
<toolbarbutton id="zen-glance-sidebar-open" class="toolbarbutton-1"/>
<toolbarbutton id="zen-glance-sidebar-split" class="toolbarbutton-1"/>
</vbox>

View File

@@ -1 +0,0 @@
<svg width="1920" height="1080" viewBox="0 0 1920 1080" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_23_113)"><rect width="1920" height="1080" fill="url(#paint0_linear_23_113)"/><circle cx="960" cy="1080" r="740" stroke="#F2F0E3" stroke-width="120"/><circle cx="960" cy="1080" r="558.095" stroke="#F2F0E3" stroke-width="80"/><circle cx="960" cy="1080" r="386.19" stroke="#F2F0E3" stroke-width="60"/><circle cx="960" cy="1080" r="214.286" stroke="#F2F0E3" stroke-width="40"/></g><defs><linearGradient id="paint0_linear_23_113" x1="960" y1="0" x2="960" y2="1080" gradientUnits="userSpaceOnUse"><stop stop-color="#EA6E54"/><stop offset="1" stop-color="#D9664E"/></linearGradient><clipPath id="clip0_23_113"><rect width="1920" height="1080" fill="white"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 KiB

View File

@@ -1,100 +0,0 @@
<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="none"/>
<style type="text/css"><![CDATA[
.note {
fill: currentColor;
transform-box: fill-box;
transform-origin: center;
}
@keyframes flyUpLeft {
from {
opacity: 1;
transform: translate(0px, 0px) scale(0.5) rotate(0deg);
}
to {
opacity: 0;
transform: translate(-9px, -35px) scale(5) rotate(0deg);
}
}
@keyframes flyUpRight {
from {
opacity: 1;
transform: translate(0px, 0px) scale(0.5) rotate(0deg);
}
to {
opacity: 0;
transform: translate(9px, -35px) scale(5) rotate(0deg);
}
}
@keyframes flyUpCenter {
from {
opacity: 1;
transform: translate(0px, 0px) scale(0.5) rotate(0deg);
}
to {
opacity: 0;
transform: translate(0px, -35px) scale(5) rotate(0deg);
}
}
}*/
]]></style>
<g class="note-group">
<g class="note" style="animation: flyUpLeft 3s ease-in-out infinite; animation-delay: 0s;">
<path transform="translate(15,40) scale(0.0078)" d="M448.231,166.755C352.139,64.989,240.998,2.38,240.998,2.38
c-3.297-2.625-7.813-3.125-11.609-1.281c-3.813,1.844-6.219,5.688-6.219,9.906v329
c-20.078-8.281-44.688-11.656-71.344-8.344C84.717,340.005,30.514,386.849,30.67,436.255
c0.188,49.453,54.703,82.813,121.75,74.469c67.078-8.328,121.297-55.188,121.125-104.641V164.817
c15.828,3.313,34.391,10.531,54.391,25.219c67.703,49.625,100.905,81.484,58.218,166.859
C475.403,321.974,514.2,236.599,448.231,166.755z"/>
</g>
<g class="note" style="animation: flyUpRight 3s ease-in-out infinite; animation-delay: 0.5s;">
<path transform="translate(15,40) scale(0.0078)" d="M133.703,45.86v86.43v240.66c-16.8-6.173-36.654-8.012-57.11-4.054
c-49.141,9.454-82.977,48.227-75.577,86.559c7.389,38.353,53.195,61.757,102.326,52.292
c43.602-8.4,75.093-39.892,76.449-73.727h0.28V129.246L465.644,93.83v237.595
c-16.811-6.162-36.644-7.98-57.1-4.033c-49.152,9.443-82.966,48.217-75.6,86.559
c7.389,38.342,53.185,61.746,102.327,52.271c43.612-8.389,75.115-39.892,76.449-73.706
H512V90.785V2.152L133.703,45.86z"/>
</g>
<g class="note" style="animation: flyUpCenter 3s ease-in-out infinite; animation-delay: 1s;">
<path transform="translate(15,40) scale(0.0078)" d="M448.231,166.755C352.139,64.989,240.998,2.38,240.998,2.38
c-3.297-2.625-7.813-3.125-11.609-1.281c-3.813,1.844-6.219,5.688-6.219,9.906v329
c-20.078-8.281-44.688-11.656-71.344-8.344C84.717,340.005,30.514,386.849,30.67,436.255
c0.188,49.453,54.703,82.813,121.75,74.469c67.078-8.328,121.297-55.188,121.125-104.641V164.817
c15.828,3.313,34.391,10.531,54.391,25.219c67.703,49.625,100.905,81.484,58.218,166.859
C475.403,321.974,514.2,236.599,448.231,166.755z"/>
</g>
<g class="note" style="animation: flyUpLeft 3s ease-in-out infinite; animation-delay: 1.5s;">
<path transform="translate(15,40) scale(0.0078)" d="M133.703,45.86v86.43v240.66c-16.8-6.173-36.654-8.012-57.11-4.054
c-49.141,9.454-82.977,48.227-75.577,86.559c7.389,38.353,53.195,61.757,102.326,52.292
c43.602-8.4,75.093-39.892,76.449-73.727h0.28V129.246L465.644,93.83v237.595
c-16.811-6.162-36.644-7.98-57.1-4.033c-49.152,9.443-82.966,48.217-75.6,86.559
c7.389,38.342,53.185,61.746,102.327,52.271c43.612-8.389,75.115-39.892,76.449-73.706
H512V90.785V2.152L133.703,45.86z"/>
</g>
<g class="note" style="animation: flyUpRight 3s ease-in-out infinite; animation-delay: 2s;">
<path transform="translate(15,40) scale(0.0078)" d="M448.231,166.755C352.139,64.989,240.998,2.38,240.998,2.38
c-3.297-2.625-7.813-3.125-11.609-1.281c-3.813,1.844-6.219,5.688-6.219,9.906v329
c-20.078-8.281-44.688-11.656-71.344-8.344C84.717,340.005,30.514,386.849,30.67,436.255
c0.188,49.453,54.703,82.813,121.75,74.469c67.078-8.328,121.297-55.188,121.125-104.641V164.817
c15.828,3.313,34.391,10.531,54.391,25.219c67.703,49.625,100.905,81.484,58.218,166.859
C475.403,321.974,514.2,236.599,448.231,166.755z"/>
</g>
<g class="note" style="animation: flyUpCenter 3s ease-in-out infinite; animation-delay: 2.5s;">
<path transform="translate(15,40) scale(0.0078)" d="M133.703,45.86v86.43v240.66c-16.8-6.173-36.654-8.012-57.11-4.054
c-49.141,9.454-82.977,48.227-75.577,86.559c7.389,38.353,53.195,61.757,102.326,52.292
c43.602-8.4,75.093-39.892,76.449-73.727h0.28V129.246L465.644,93.83v237.595
c-16.811-6.162-36.644-7.98-57.1-4.033c-49.152,9.443-82.966,48.217-75.6,86.559
c7.389,38.342,53.185,61.746,102.327,52.271c43.612-8.389,75.115-39.892,76.449-73.706
H512V90.785V2.152L133.703,45.86z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -1,48 +0,0 @@
<toolbar id="zen-media-controls-toolbar"
class="browser-toolbar customization-target zen-sidebar-toolbar"
context="toolbar-context-menu"
mode="icons"
hidden="true">
<toolbaritem>
<vbox id="zen-media-main-vbox">
<vbox>
<hbox id="zen-media-info-container" class="show-on-hover">
<vbox id="zen-media-info-vbox">
<label id="zen-media-title" fadein="true"/>
<label id="zen-media-artist" fadein="true"/>
</vbox>
<hbox id="zen-media-buttons-hbox">
<toolbarbutton id="zen-media-pip-button"
class="toolbarbutton-1" />
<toolbarbutton id="zen-media-close-button"
class="toolbarbutton-1" />
</hbox>
</hbox>
<hbox id="zen-media-progress-hbox" class="show-on-hover">
<label id="zen-media-current-time">0:00</label>
<html:input type="range" id="zen-media-progress-bar"
value="0" min="0" max="100" step="0.1"/>
<label id="zen-media-duration">0:00</label>
</hbox>
</vbox>
<hbox id="zen-media-controls-hbox">
<toolbarbutton id="zen-media-focus-button"
class="toolbarbutton-1" />
<toolbarbutton id="zen-media-previoustrack-button"
class="toolbarbutton-1" />
<toolbarbutton id="zen-media-playpause-button"
class="toolbarbutton-1" />
<toolbarbutton id="zen-media-nexttrack-button"
class="toolbarbutton-1" />
<toolbarbutton id="zen-media-mute-button"
class="toolbarbutton-1" />
<hbox id="media-device-buttons">
<toolbarbutton id="zen-media-mute-mic-button"
class="toolbarbutton-1" />
<toolbarbutton id="zen-media-mute-camera-button"
class="toolbarbutton-1" />
</hbox>
</hbox>
</vbox>
</toolbaritem>
</toolbar>

View File

@@ -1,92 +0,0 @@
document.addEventListener(
'MozBeforeInitialXULLayout',
() => {
// <commandset id="mainCommandSet"> defined in browser-sets.inc
document
.getElementById('zenCommandSet')
// eslint-disable-next-line complexity
.addEventListener('command', (event) => {
switch (event.target.id) {
case 'cmd_zenCompactModeToggle':
gZenCompactModeManager.toggle();
break;
case 'cmd_zenCompactModeShowSidebar':
gZenCompactModeManager.toggleSidebar();
break;
case 'cmd_zenCompactModeShowToolbar':
gZenCompactModeManager.toggleToolbar();
break;
case 'cmd_zenWorkspaceForward':
ZenWorkspaces.changeWorkspaceShortcut();
break;
case 'cmd_zenWorkspaceBackward':
ZenWorkspaces.changeWorkspaceShortcut(-1);
break;
case 'cmd_zenSplitViewGrid':
gZenViewSplitter.toggleShortcut('grid');
break;
case 'cmd_zenSplitViewVertical':
gZenViewSplitter.toggleShortcut('vsep');
break;
case 'cmd_zenSplitViewHorizontal':
gZenViewSplitter.toggleShortcut('hsep');
break;
case 'cmd_zenSplitViewUnsplit':
gZenViewSplitter.toggleShortcut('unsplit');
break;
case 'cmd_zenCopyCurrentURLMarkdown':
gZenCommonActions.copyCurrentURLAsMarkdownToClipboard();
break;
case 'cmd_zenCopyCurrentURL':
gZenCommonActions.copyCurrentURLToClipboard();
break;
case 'cmd_zenPinnedTabReset':
gZenPinnedTabManager.resetPinnedTab(gBrowser.selectedTab);
break;
case 'cmd_zenPinnedTabResetNoTab':
gZenPinnedTabManager.resetPinnedTab();
break;
case 'cmd_zenToggleSidebar':
gZenVerticalTabsManager.toggleExpand();
break;
case 'cmd_zenOpenZenThemePicker':
gZenThemePicker.openThemePicker(event);
break;
case 'cmd_zenChangeWorkspaceTab':
ZenWorkspaces.changeTabWorkspace(event.sourceEvent.target.getAttribute('zen-workspace-id'));
break;
case 'cmd_zenToggleTabsOnRight':
gZenVerticalTabsManager.toggleTabsOnRight();
break;
case 'cmd_zenSplitViewLinkInNewTab':
gZenViewSplitter.splitLinkInNewTab();
break;
case 'cmd_zenReplacePinnedUrlWithCurrent':
gZenPinnedTabManager.replacePinnedUrlWithCurrent();
break;
case 'cmd_zenAddToEssentials':
gZenPinnedTabManager.addToEssentials();
break;
case 'cmd_zenRemoveFromEssentials':
gZenPinnedTabManager.removeEssentials();
break;
case 'cmd_zenUnloadTab':
gZenTabUnloader.unloadTab();
break;
case 'cmd_zenPreventUnloadTab':
gZenTabUnloader.preventUnloadTab();
break;
case 'cmd_zenIgnoreUnloadTab':
gZenTabUnloader.ignoreUnloadTab();
break;
default:
if (event.target.id.startsWith('cmd_zenWorkspaceSwitch')) {
const index = parseInt(event.target.id.replace('cmd_zenWorkspaceSwitch', ''), 10) - 1;
ZenWorkspaces.shortcutSwitchTo(index);
}
break;
}
});
},
{ once: true }
);

View File

@@ -1,4 +1,4 @@
#include zen-media-controls.inc.xhtml
#include ../../../zen/media/zen-media-controls.inc.xhtml
<toolbar brighttext="true"
id="zen-sidebar-bottom-buttons"
fullscreentoolbar="true"

View File

@@ -1,5 +0,0 @@
<hbox id="zen-splitview-overlay-wrapper" hidden="true">
<hbox id="zen-splitview-overlay">
<hbox id ="zen-splitview-dropzone"></hbox>
</hbox>
</hbox>

View File

@@ -1,318 +0,0 @@
/*
* 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/.
*/
@keyframes zen-jello-animation {
0% {
transform: scale3d(0.8, 0.8, 0.8);
}
60% {
transform: scale3d(1.02, 1.02, 1.02);
}
to {
opacity: 1;
transform: scale3d(1, 1, 1);
}
}
@keyframes zen-jello-animation-macos {
0% {
opacity: 0;
-moz-window-transform: scale(0.3);
}
50% {
opacity: 1;
-moz-window-transform: scale(1.04);
}
100% {
opacity: 1;
-moz-window-transform: scale(1);
}
}
@keyframes zen-jello-animation-large {
0% {
transform: scale3d(0.8, 0.8, 0.8);
}
60% {
transform: scale3d(1.02, 1.02, 1.02);
}
to {
opacity: 1;
transform: scale3d(1, 1, 1);
}
}
@keyframes zen-theme-picker-dot-animation {
from {
transform: scale(0.8) translate(-50%, -50%);
}
50% {
transform: scale(1.2) translate(-50%, -50%);
}
to {
transform: scale(1) translate(-50%, -50%);
}
}
@keyframes zen-main-app-wrapper-animation {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes zen-jello-out-animation {
0% {
transform: scale3d(1, 1, 1);
}
60% {
transform: scale3d(1.02, 1.02, 1.02);
}
99% {
opacity: 0;
transform: scale3d(0.8, 0.8, 0.8);
}
100% {
-moz-window-transform: none;
transform: none;
}
}
@keyframes better-sidebar-pinned-hide {
0% {
opacity: 1;
transform: scale(1) rotateX(0deg);
}
100% {
transform: scale(0.99) rotateX(-5deg);
opacity: 0;
}
}
@keyframes better-sidebar-pinned-show {
0% {
opacity: 0;
transform: scale(0.99) rotateX(-5deg);
}
100% {
transform: scale(1) rotateX(0deg);
opacity: 1;
}
}
@keyframes better-sidebar-hide {
0% {
opacity: 1;
transform: scale(1) rotateX(0deg);
}
100% {
transform: scale(0.99) rotateX(-5deg);
opacity: 0;
}
}
@keyframes better-sidebar-show {
0% {
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes better-sidebar-intro-show {
0% {
opacity: 0;
transform: translateY(5px);
}
100% {
transform: translateY(0px);
opacity: 1;
}
}
@keyframes zen-vtabs-animation {
0% {
opacity: 0;
transform: translateX(-10px);
}
20% {
opacity: 1;
}
100% {
transform: translateX(0);
}
}
@keyframes zen-sidebar-panel-animation-right {
0% {
opacity: 0;
transform: translateX(10px);
}
20% {
opacity: 1;
}
100% {
transform: translateX(0);
}
}
@keyframes zen-sidebar-panel-animation-reverse {
0% {
opacity: 1;
transform: translateX(0) scale3d(1, 1, 1);
}
99% {
opacity: 0;
transform: translateX(-50px) scale3d(0.95, 0.95, 0.95);
}
100% {
display: none !important;
}
}
/* Mark: Zen Glance */
@keyframes zen-glance-overlay-animation {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes zen-glance-overlay-animation-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes zen-rice-form-out {
0% {
transform: translateX(0);
max-height: 350px;
opacity: 1;
position: relative;
}
50% {
transform: translateX(-100%);
opacity: 0;
position: relative;
}
99% {
transform: translateX(-100%);
opacity: 0;
max-height: 15px;
position: relative;
}
100% {
transform: translateX(-100%);
opacity: 0;
pointer-events: none;
position: absolute;
}
}
@keyframes zen-rice-form-in {
0% {
position: absolute;
transform: translateX(100%);
opacity: 0;
}
99% {
position: absolute;
transform: translateX(0);
opacity: 1;
}
100% {
position: relative;
}
}
@keyframes zen-rice-form-in-2 {
from {
opacity: 0;
transform: translateX(100%);
max-height: 50px;
}
to {
opacity: 1;
transform: translateX(0);
max-height: 450px;
}
}
@keyframes zen-rice-submit-animation {
/* Scale and shake the dialog */
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
@keyframes zen-back-and-forth-text {
0%,
10% {
transform: translateX(0);
left: 0;
}
45%,
65% {
transform: translateX(calc(-100% - 5px));
left: 100%;
}
90%,
100% {
transform: translateX(0);
left: 0;
}
}

View File

@@ -1,29 +0,0 @@
/*
* 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/.
*/
@font-face {
font-family: 'Zen-Junicode';
src: url('chrome://browser/content/zen-fonts/JunicodeVF-Roman.woff2') format('woff2');
}
@font-face {
font-family: 'Zen-Junicode-Italic';
src: url('chrome://browser/content/zen-fonts/JunicodeVF-Italic.woff2') format('woff2');
}
.zen-branding-title {
font-size: 6rem;
line-height: 0.9;
margin-bottom: 0.4rem;
font-family: 'Zen-Junicode', serif;
font-weight: 400;
font-style: normal;
font-feature-settings: 'swsh' 1;
& .zen-branding-title-italic {
font-family: 'Zen-Junicode-Italic', serif;
}
}

View File

@@ -1,31 +0,0 @@
/*
* 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/.
*/
:root:not([inDOMFullscreen='true']):not([chromehidden~='location']):not([chromehidden~='toolbar']) {
& #tabbrowser-tabbox #tabbrowser-tabpanels .browserSidebarContainer {
width: -moz-available;
position: relative;
overflow: hidden;
:root:not([zen-no-padding='true']) & {
border-radius: var(--zen-native-inner-radius);
box-shadow: var(--zen-big-shadow);
}
& browser[transparent='true'] {
background: rgba(255, 255, 255, 0.1);
}
}
@media -moz-pref('zen.view.experimental-rounded-view') {
#tabbrowser-tabpanels {
:root:not([zen-no-padding='true']) & {
mix-blend-mode: multiply;
-moz-osx-font-smoothing: grayscale;
isolation: isolate;
}
}
}
}

View File

@@ -1,232 +0,0 @@
/*
* 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/.
*/
#tabbrowser-tabpanels {
background: transparent !important;
}
#navigator-toolbox {
/* see issue #426 */
background: var(--zen-navigator-toolbox-background, transparent) !important;
--inactive-titlebar-opacity: 1;
}
#nav-bar,
#navigator-toolbox {
position: inherit;
}
:root:is([inDOMFullscreen='true'], [chromehidden~='location'], [chromehidden~='toolbar']) {
#navigator-toolbox,
#zen-sidebar-splitter {
visibility: collapse;
}
}
:root[zen-before-loaded='true'] #browser > *,
:root[zen-before-loaded='true'] #urlbar {
opacity: 0 !important;
}
#browser {
width: 100%;
background: var(--zen-main-browser-background) !important;
will-change: background-color;
&::after {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
pointer-events: none;
}
&:not([post-animating='true'])::after {
transition: background-color var(--inactive-window-transition);
}
@media -moz-pref('zen.theme.gradient') {
&[animating='true']::after {
background: var(--zen-main-browser-background-old);
backdrop-filter: blur(5px);
animation: zen-main-app-wrapper-animation 0.4s ease forwards;
transition: 0s;
}
}
@media (not (-moz-windows-mica)) and -moz-pref('zen.view.grey-out-inactive-windows') {
transition: color var(--inactive-window-transition);
:root:not([zen-welcome-stage]) &:-moz-window-inactive {
color: var(--toolbox-textcolor-inactive);
&::after {
background-color: var(--toolbox-bgcolor-inactive);
}
}
}
&::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: url(chrome://browser/content/zen-images/grain-bg.png);
pointer-events: none;
z-index: 0;
opacity: var(--zen-grainy-background-opacity, 0);
mix-blend-mode: overlay;
}
}
#sidebar-box {
/** Sidebar is already hidden in full screen mode */
border: none;
}
@supports (-moz-osx-font-smoothing: auto) {
#zen-main-app-wrapper,
#zen-appcontent-wrapper,
#zen-sidebar-splitter {
appearance: -moz-window-titlebar !important;
}
}
:root:not([zen-single-toolbar='true']) #zen-appcontent-wrapper {
z-index: 2;
}
#nav-bar {
/* For some reason, firefox adds a really small border to the top of the nav-bar */
border-top: none !important;
}
#zen-main-app-wrapper {
background: var(--zen-themed-toolbar-bg-transparent);
overflow: hidden;
& > * {
z-index: 1;
}
@media (-moz-windows-accent-color-in-titlebar) and (-moz-windows-mica) {
background-color: ActiveCaption;
color: CaptionText;
transition: background-color var(--inactive-window-transition);
&:-moz-window-inactive {
background-color: InactiveCaption;
color: InactiveCaptionText;
}
}
}
#zen-appcontent-wrapper {
z-index: 1;
/* Use this trick to prevent bookmarks from overflowing the window,
* without using overflow: hidden.
*/
min-width: 1px;
}
:root:not([inDOMFullscreen='true']):not([chromehidden~='location']):not([chromehidden~='toolbar']) {
& #zen-tabbox-wrapper {
margin: var(--zen-element-separation);
margin-top: 0;
}
&:not([zen-right-side='true']) #zen-tabbox-wrapper {
margin-left: 0;
}
&[zen-right-side='true'] #zen-tabbox-wrapper {
margin-right: 0;
}
}
#tabbrowser-tabbox {
display: flex;
flex-direction: row;
position: relative;
gap: var(--zen-element-separation);
}
@media not (-moz-platform: macos) {
.titlebar-buttonbox-container {
height: 100%;
}
}
@media (-moz-platform: macos) {
.titlebar-buttonbox-container {
margin-inline-end: 8px;
padding: 3px 0;
& > .titlebar-buttonbox {
margin-inline-start: var(--zen-toolbox-padding);
}
}
@media -moz-pref('zen.widget.mac.mono-window-controls') {
.titlebar-buttonbox-container {
/* Draw 3 dots as background to represent the window controls,
all with the same cololr as the titlebar */
background-image: radial-gradient(circle, var(--zen-toolbar-element-bg) 6px, transparent 0.5px);
background-size: 20px 22px;
background-position: 53% 50%;
&:not([zen-has-hover='true']) > .titlebar-buttonbox {
opacity: 0;
}
}
}
}
:root[zen-window-buttons-reversed='true'][zen-right-side='true'] .titlebar-buttonbox-container {
margin-inline-start: calc(var(--zen-element-separation) - 3px);
}
.zen-split-view-splitter[orient='vertical'],
#zen-sidebar-splitter {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: var(--zen-element-separation);
background: transparent;
border: none;
cursor: ew-resize;
z-index: 3;
&:is(.zen-split-view-splitter[orient='vertical']) {
/* Bit of a hacky solution, but it works */
width: var(--zen-split-row-gap);
margin-left: calc(var(--zen-element-separation) * -1 - 1px);
height: unset;
cursor: ew-resize;
}
&::before {
height: 50px;
width: 2px;
background: var(--button-primary-bgcolor);
border-radius: 2px;
content: '';
position: absolute;
top: 50%;
left: 50%;
opacity: 0;
transition: opacity 0.1s ease-in-out;
pointer-events: none;
}
&:hover::before {
opacity: 1;
}
}

View File

@@ -1,39 +0,0 @@
/*
* 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/.
*/
@namespace html 'http://www.w3.org/1999/xhtml';
@namespace xul 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
/** This file is used to override UI inside "common-shared.css" */
/** These types of buttons look INSAINELY bad in the preferences page */
xul|button {
border-radius: var(--zen-button-border-radius) !important;
padding: var(--zen-button-padding) !important;
transition: 0.1s;
min-width: 100px !important;
font-weight: 500 !important;
border: 1px solid var(--zen-colors-border);
}
button:not(#zen-workspaces-button):active {
transform: scale(0.98);
}
html|button:not(:is(.tab-button, .ghost-button, .button-toggle, .button-edit, .button-add, )),
xul|button:is(.expander-down) {
transition: 0.2s;
min-width: unset !important;
border-radius: 6px !important;
}
.footer-button {
transition: scale 0.2s;
&:active {
scale: 0.98;
}
}

View File

@@ -1,387 +0,0 @@
/*
* 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/.
*/
/* All overrides for compact mode go here */
:root[zen-compact-mode='true']:not([customizing]):not([inDOMFullscreen='true']) {
%include zen-tabs/vertical-tabs-topbuttons-fix.css
@media -moz-pref('zen.view.compact.hide-tabbar') or -moz-pref('zen.view.use-single-toolbar') {
&:not([zen-compact-animating]) {
& #zen-sidebar-splitter {
display: none !important;
}
#zen-tabbox-wrapper {
/* Remove extra 1px of margine we have to add to the tabbox */
margin-left: var(--zen-element-separation) !important;
margin-right: var(--zen-element-separation) !important;
}
#zen-appcontent-wrapper {
& #tabbrowser-tabbox {
margin-left: 0 !important;
}
}
#zen-sidebar-splitter {
display: none !important;
}
#zen-sidebar-top-buttons-customization-target {
padding-inline-start: calc(var(--zen-toolbox-padding) - var(--toolbarbutton-outer-padding)) !important;
}
&:not([zen-window-buttons-reversed='true']) #zen-appcontent-navbar-container #nav-bar {
margin-left: var(--zen-element-separation) !important;
}
&[zen-window-buttons-reversed='true'] #zen-appcontent-navbar-container #nav-bar {
margin-right: var(--zen-element-separation) !important;
margin-left: calc(var(--zen-element-separation) - 3px) !important;
}
#navigator-toolbox {
--zen-toolbox-max-width: 64px !important;
--zen-compact-float: var(--zen-element-separation);
/* Initial padding for when we are animating */
padding: 0 0 0 var(--zen-toolbox-padding) !important;
&:not([animate='true']) {
position: fixed;
z-index: 10;
transition:
left 0.15s ease,
right 0.15s ease,
opacity 1.5s ease;
top: 0;
bottom: var(--zen-element-separation);
padding: 0 var(--zen-compact-float) !important;
opacity: 0;
:root[zen-single-toolbar='true'] & {
top: calc(var(--zen-element-separation) / 2);
height: calc(100% - var(--zen-element-separation));
}
& #zen-sidebar-top-buttons {
margin: 0 0 calc(var(--zen-toolbox-padding) / 2) 0;
}
}
&:not([zen-right-side='true']) #nav-bar {
margin-left: 0 !important;
}
}
&:not([zen-right-side='true']) #navigator-toolbox {
left: calc(-1 * var(--actual-zen-sidebar-width) + 1px);
}
/* When we have multiple toolbars and the top-toolbar is NOT being hidden,
* we need to adjust the top-padding of the toolbox to account for the
* extra toolbar height. */
@media not -moz-pref('zen.view.compact.hide-toolbar') {
&:not([zen-single-toolbar='true']) {
#navigator-toolbox:not([animate='true']) {
margin-top: var(--zen-toolbar-height) !important;
}
}
}
&[zen-right-side='true'] {
& #navigator-toolbox {
--zen-compact-float: calc(var(--zen-element-separation) + 1px);
&:not([animate='true']) {
right: calc(-1 * var(--actual-zen-sidebar-width) + 1px);
}
}
& .browserSidebarContainer {
margin-left: 0 !important;
margin-right: 0 !important;
}
}
#navigator-toolbox:not([animate='true']) #titlebar {
box-shadow: var(--zen-big-shadow);
border-radius: calc(var(--zen-native-inner-radius) + var(--zen-element-separation) / 4);
padding: var(--zen-toolbox-padding) !important;
position: relative;
background: var(--zen-dialog-background);
outline: 1px solid var(--zen-colors-border-contrast);
outline-offset: -1px;
min-width: var(--zen-toolbox-min-width);
:root[zen-sidebar-expanded='true'] & {
width: calc(var(--zen-sidebar-width) + var(--zen-toolbox-padding));
}
:root[zen-single-toolbar='true'] {
padding-top: 0 !important;
margin-left: 0 !important;
}
@media -moz-pref('zen.view.compact.color-sidebar') {
background: var(--zen-main-browser-background-toolbar) !important;
background-attachment: fixed !important;
background-size: 2000px !important; /* Dont ask me why */
/* NOTE: We MUST not add a backdrop-filter if we want the URL
* bar to be floating correctly:
* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_display/Containing_block#identifying_the_containing_block */
}
& #urlbar[open][zen-floating-urlbar='true'] {
transition: left 0.05s ease;
visibility: visible;
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: url(chrome://browser/content/zen-images/grain-bg.png);
pointer-events: none;
z-index: 0;
opacity: var(--zen-grainy-background-opacity, 0);
mix-blend-mode: overlay;
}
}
#navigator-toolbox[zen-has-hover]:not(:has(#urlbar[zen-floating-urlbar='true']:hover)),
#navigator-toolbox[zen-user-show],
#navigator-toolbox[zen-has-empty-tab],
#navigator-toolbox[flash-popup],
#navigator-toolbox[has-popup-menu],
#navigator-toolbox[movingtab],
#navigator-toolbox:has(.tabbrowser-tab:active),
&[zen-renaming-tab='true'] #navigator-toolbox,
#navigator-toolbox:has(
*:is([panelopen='true'], [open='true'], #urlbar:focus-within):not(#urlbar[zen-floating-urlbar='true']):not(tab):not(.zen-compact-mode-ignore)
) {
&:not([animate='true']) {
--zen-compact-mode-func: linear(
0 0%,
0.002748 1%,
0.010544 2%,
0.022757 3%,
0.038804 4%,
0.058151 5%,
0.080308 6%,
0.104828 7.000000000000001%,
0.131301 8%,
0.159358 9%,
0.188662 10%,
0.21891 11%,
0.249828 12%,
0.281172 13%,
0.312724 14.000000000000002%,
0.344288 15%,
0.375693 16%,
0.40679 17%,
0.437447 18%,
0.467549 19%,
0.497 20%,
0.525718 21%,
0.553633 22%,
0.580688 23%,
0.60684 24%,
0.632052 25%,
0.656298 26%,
0.679562 27%,
0.701831 28.000000000000004%,
0.723104 28.999999999999996%,
0.743381 30%,
0.76267 31%,
0.780983 32%,
0.798335 33%,
0.814744 34%,
0.830233 35%,
0.844826 36%,
0.858549 37%,
0.87143 38%,
0.883498 39%,
0.894782 40%,
0.905314 41%,
0.915125 42%,
0.924247 43%,
0.93271 44%,
0.940547 45%,
0.947787 46%,
0.954463 47%,
0.960603 48%,
0.966239 49%,
0.971397 50%,
0.976106 51%,
0.980394 52%,
0.984286 53%,
0.987808 54%,
0.990984 55.00000000000001%,
0.993837 56.00000000000001%,
0.99639 56.99999999999999%,
0.998664 57.99999999999999%,
1.000679 59%,
1.002456 60%,
1.004011 61%,
1.005363 62%,
1.006528 63%,
1.007522 64%,
1.008359 65%,
1.009054 66%,
1.009618 67%,
1.010065 68%,
1.010405 69%,
1.010649 70%,
1.010808 71%,
1.01089 72%,
1.010904 73%,
1.010857 74%,
1.010757 75%,
1.010611 76%,
1.010425 77%,
1.010205 78%,
1.009955 79%,
1.009681 80%,
1.009387 81%,
1.009077 82%,
1.008754 83%,
1.008422 84%,
1.008083 85%,
1.00774 86%,
1.007396 87%,
1.007052 88%,
1.00671 89%,
1.006372 90%,
1.00604 91%,
1.005713 92%,
1.005394 93%,
1.005083 94%,
1.004782 95%,
1.004489 96%,
1.004207 97%,
1.003935 98%,
1.003674 99%,
1.003423 100%
);
transition:
left 0.25s var(--zen-compact-mode-func),
right 0.25s var(--zen-compact-mode-func);
&:not([supress-primary-adjustment='true']) {
opacity: 1;
left: calc(var(--zen-element-separation) / -2);
:root[zen-right-side='true'] & {
right: calc(var(--zen-element-separation) / -2);
left: auto;
}
}
}
}
}
}
@media -moz-pref('zen.view.compact.hide-toolbar') {
&:not([zen-single-toolbar='true']) {
& #navigator-toolbox {
top: 0;
}
& #navigator-toolbox {
--zen-toolbox-top-align: var(--zen-element-separation);
}
& #sidebar-box,
& #titlebar,
& #zen-appcontent-wrapper,
& #zen-sidebar-web-panel-wrapper:has(#zen-sidebar-web-panel:not([pinned='true'])) {
margin-top: var(--zen-element-separation) !important;
}
& #zen-appcontent-wrapper {
z-index: 3 !important;
}
& #zen-sidebar-web-panel-wrapper:has(#zen-sidebar-web-panel[pinned='true']) {
margin-top: calc(var(--zen-element-separation) * 2) !important;
}
& #zen-appcontent-navbar-container {
--zen-compact-toolbar-offset: 5px;
position: absolute;
top: calc((-1 * var(--zen-toolbar-height)) + var(--zen-element-separation) + 1px);
left: 0;
z-index: 20;
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.3) !important;
border-bottom-left-radius: var(--zen-border-radius);
border-bottom-right-radius: var(--zen-border-radius);
border-top-left-radius: env(-moz-gtk-csd-titlebar-radius);
border-top-right-radius: env(-moz-gtk-csd-titlebar-radius);
transition: all 0.15s ease;
width: 100%;
opacity: 0;
background: var(--zen-dialog-background);
max-height: var(--zen-toolbar-height);
overflow: hidden;
& > * {
position: relative !important;
}
& #urlbar {
opacity: 0 !important;
}
@media -moz-pref('zen.view.compact.color-toolbar') {
background-attachment: fixed;
background: var(--zen-main-browser-background-toolbar);
background-size: 100% 2000px;
border-bottom: 1px solid var(--zen-colors-border);
}
}
& #zen-appcontent-navbar-container[zen-has-hover],
& #zen-appcontent-navbar-container:hover,
& #zen-appcontent-navbar-container:focus-within,
& #zen-appcontent-navbar-container[zen-user-show],
& #zen-appcontent-navbar-container[has-popup-menu],
& #zen-appcontent-navbar-container:has(*:is([panelopen='true'], [open='true']):not(.zen-compact-mode-ignore)) {
opacity: 1;
border-top-width: 1px;
top: -1px;
overflow: initial;
max-height: unset;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: url(chrome://browser/content/zen-images/grain-bg.png);
pointer-events: none;
z-index: 0;
opacity: var(--zen-grainy-background-opacity, 0);
mix-blend-mode: overlay;
}
& #urlbar {
opacity: 1 !important;
}
& #urlbar[breakout-extend='true']:not([zen-floating-urlbar='true']) {
top: 2px !important;
opacity: 1;
}
}
}
}
}

View File

@@ -1,217 +0,0 @@
/*
* 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/.
*/
/** Zen Decks - ONLY USED FOR SPLIT VIEWS, DO NOT USE THIS CLASS FOR ANY OTHER PURPOSE **/
#tabbrowser-tabpanels[zen-split-view='true'] {
display: flex;
flex-direction: row;
margin-top: calc(var(--zen-split-column-gap) * -1);
}
#tabbrowser-tabpanels[zen-split-view='true'] > *:not([zen-split='true']) {
flex: 0;
margin: 0;
}
#zen-splitview-dropzone {
border-radius: var(--zen-native-inner-radius);
transition: inset ease-out 0.08s;
display: none;
background-color: color-mix(in srgb, var(--zen-colors-secondary) 30%, transparent 70%);
}
#zen-splitview-dropzone[enabled='true'] {
display: initial;
}
#tabbrowser-tabpanels[zen-split-view='true'] > [zen-split='true'],
#zen-splitview-dropzone {
flex: 1;
margin: var(--zen-split-column-gap) var(--zen-split-row-gap) !important;
margin-bottom: 0 !important;
margin-left: 0 !important;
position: absolute !important;
overflow: hidden;
}
#tabbrowser-tabpanels[zen-split-view='true']:not([zen-split-resizing]) > [zen-split-anim='true'] {
transition: inset 0.09s ease-out !important;
& browser {
transition: opacity 0.2s ease-out !important;
}
}
#tabbrowser-tabpanels[zen-split-view='true'] .browserSidebarContainer.deck-selected {
&:not(.zen-glance-overlay) {
outline: 1px solid var(--zen-primary-color) !important;
}
&.zen-glance-overlay {
flex: 1;
margin-top: calc(var(--zen-element-separation) / 2);
}
}
#tabbrowser-tabbox:has(#tabbrowser-tabpanels[zen-split-view='true']) {
:root[zen-no-padding='true'] & {
--zen-element-separation: 8px;
}
--zen-split-row-gap: var(--zen-element-separation);
--zen-split-column-gap: calc(var(--zen-element-separation) + 1px);
margin-right: calc(-1 * var(--zen-split-column-gap));
}
#tabbrowser-tabpanels:has(> [zen-split='true']),
#zen-splitview-overlay {
:root:not([zen-compact-mode='true']):not([customizing]) & {
@media -moz-pref('zen.view.compact.hide-toolbar') {
& {
margin-top: calc(var(--zen-split-column-gap) * -1);
}
}
}
}
#tabbrowser-tabpanels[zen-split-view] {
.zen-split-view-splitter {
display: inherit;
}
}
#zen-splitview-overlay-wrapper {
position: absolute;
pointer-events: none;
padding: inherit;
inset: 0;
}
#zen-splitview-overlay {
position: relative;
flex: 1;
z-index: 2;
}
.zen-split-view-splitter {
visibility: inherit;
-moz-subtree-hidden-only-visually: 0;
position: absolute;
pointer-events: all;
}
.zen-split-view-splitter[orient='horizontal'] {
height: var(--zen-split-column-gap);
cursor: ns-resize;
}
#zen-split-views-box:not([hidden='true']) {
display: flex !important;
}
.zen-view-splitter-header-container {
position: absolute;
top: calc(var(--zen-split-column-gap) / -2);
left: 50%;
opacity: 0;
transition: opacity 0.2s;
z-index: 100;
transform: translateX(-50%);
pointer-events: none;
}
.zen-view-splitter-header {
display: flex;
align-items: center;
position: fixed;
padding: 0.4rem 0.6rem;
border-radius: 8px;
background-color: light-dark(rgba(255, 255, 255, 1), rgba(0, 0, 0, 1));
box-shadow: 0 0 0 1px var(--button-primary-border-color);
gap: 0.8rem;
transform: translateX(-50%);
box-shadow: 0 0 5px 1px rgba(0, 0, 0, 0.1);
border-top-left-radius: 0;
border-top-right-radius: 0;
}
:root:not([inDOMFullscreen='true']) .browserSidebarContainer:hover .zen-view-splitter-header-container,
.zen-view-splitter-header-container:hover {
pointer-events: all;
opacity: 1;
transition-delay: 0.2s;
}
.zen-view-splitter-header-container toolbarbutton {
display: block;
-moz-context-properties: fill, fill-opacity;
border-radius: var(--tab-border-radius);
color: inherit;
fill: currentColor;
width: 16px;
height: 16px;
cursor: pointer;
appearance: none;
outline: none;
color: var(--button-primary-bgcolor);
border-top-left-radius: 0;
border-top-right-radius: 0;
& image {
width: 14px;
height: 14px;
}
&.zen-tab-rearrange-button {
cursor: move;
& image {
transform: rotate(90deg);
}
}
}
#zen-split-view-fake-browser {
position: absolute;
height: 100%;
background: rgba(255, 255, 255, 0.1);
border-radius: var(--zen-native-inner-radius);
box-shadow: var(--zen-big-shadow);
overflow: hidden;
&[side='right'] {
right: 0;
&[has-split-view='true'] {
right: var(--zen-element-separation);
}
}
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 3.5rem;
pointer-events: none;
height: 3.5rem;
background: var(--zen-split-view-fake-icon);
background-size: contain;
background-repeat: no-repeat;
background-position: center;
opacity: 0.8;
transition: opacity 0.2s;
transition-delay: 0.1s;
@starting-style {
opacity: 0;
}
}
&.fade-out::after {
opacity: 0;
transition-delay: 0s;
}
}

View File

@@ -1,184 +0,0 @@
tab-group[split-view-group] {
display: flex;
flex-wrap: nowrap;
border-radius: var(--border-radius-medium);
padding: 0 2px;
margin-inline: var(--tab-block-margin);
margin-block: var(--tab-block-margin);
min-height: var(--tab-min-height);
outline: var(--tab-outline);
outline-offset: var(--tab-outline-offset);
outline-color: var(--tab-selected-outline-color);
transition: scale 0.1s ease;
align-items: center;
--zen-split-view-active-tab-bg: color-mix(in srgb, var(--zen-toolbar-element-bg), transparent 40%);
:root:not([zen-sidebar-expanded='true']) & {
padding: 0 2px;
--tab-min-height: 30px;
--tab-collapsed-width: 38px;
margin: 0 4px;
--tab-min-width: 34px;
}
& > .tabbrowser-tab {
--tab-selected-bgcolor: var(--zen-split-view-active-tab-bg);
--tab-hover-background-color: transparent;
--tab-selected-shadow: none;
--border-radius-medium: var(--tab-border-radius);
--zen-active-tab-scale: 1;
:root[zen-sidebar-expanded='true'] & {
--tab-min-height: 28px;
}
container-type: inline-size;
container-name: browser-tab;
flex: 1 !important;
padding-inline: 2px !important;
overflow: clip;
&:not(:last-child)::after {
content: '';
width: 1px;
height: 16px;
background-color: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.2));
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
}
:root:not([zen-sidebar-expanded='true']) &:not(:last-child)::after {
width: 16px;
height: 1px;
top: auto;
bottom: 0;
right: 50%;
transform: translateX(50%);
}
& .tab-content {
min-width: 0;
:root[zen-sidebar-expanded='true'] & {
justify-content: unset !important;
}
}
}
&:has(> tab:is([visuallyselected], [multiselected])) {
background-color: var(--tab-selected-bgcolor);
box-shadow: var(--tab-selected-shadow);
& > .tabbrowser-tab {
--tab-hover-background-color: var(--zen-split-view-active-tab-bg);
& .tab-background {
background-color: var(--zen-split-view-active-tab-bg) !important;
}
&::after {
display: none;
}
}
}
&:active {
scale: var(--zen-active-tab-scale);
}
&:hover {
background-color: var(--zen-toolbar-element-bg);
}
& .tab-close-button,
& .tab-reset-button {
margin-inline-end: -3px !important;
display: none !important;
}
@container browser-tab (min-width: 70px) {
:root[zen-sidebar-expanded='true'] &:hover > .tabbrowser-tab:not([pinned]) .tab-close-button {
display: block !important;
}
}
@media (prefers-reduced-motion: no-preference) {
#tabbrowser-tabs[movingtab] & {
transition: var(--tab-dragover-transition);
}
}
}
:root:not([zen-sidebar-expanded='true']) {
tab-group {
flex-direction: column;
}
}
tab-group[split-view-group] .tabbrowser-tab {
width: 100%;
max-width: unset;
}
tab-group[split-view-group] .tab-group-label-container {
visibility: collapse;
}
tab-group[split-view-group] .tab-close-button {
display: block;
visibility: visible;
}
tab-group[split-view-group] .tab-group-line {
display: none;
background: transparent;
}
tab-group:not([split-view-group]) {
& .tab-group-label-container {
min-width: fit-content;
max-width: 100%;
height: fit-content !important;
display: flex;
justify-content: start;
margin-top: 10px;
margin-bottom: 10px;
}
& .tab-group-label {
text-align: start;
flex-grow: 1 !important;
min-width: fit-content;
max-width: 100%;
font-size: 14px !important;
display: block !important;
padding-right: 8px;
}
& .tab-group-line {
display: none !important;
}
&[collapsed] .tabbrowser-tab {
display: none !important;
}
&:not([collapsed]) .tabbrowser-tab {
margin-left: 10px;
}
&:not([collapsed]) .tabbrowser-tab::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 2px;
height: 100%;
background-color: var(--tab-group-color);
}
}
.tab-group-line {
display: none !important;
}

View File

@@ -1,157 +0,0 @@
/*
* 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/.
*/
#zen-glance-sidebar-container {
position: absolute;
display: flex;
z-index: 999;
&[hidden='true'] {
display: none;
}
top: 10%;
transform: translateY(-50%);
padding: 5px;
gap: 12px;
left: 2%;
& toolbarbutton {
width: 32px;
height: 32px;
background: light-dark(rgb(24, 24, 24), rgb(231, 231, 231));
transition:
background 0.05s ease,
scale 0.05s ease;
border-radius: 999px;
appearance: none;
box-shadow: 0 0 12px 1px rgba(0, 0, 0, 0.07);
opacity: 0;
padding: 8px;
&:hover {
background: light-dark(rgb(41, 41, 41), rgb(204, 204, 204));
scale: 1.05;
}
&:hover:active {
scale: 0.95;
}
& label {
display: none;
&::before {
text-overflow: unset;
}
}
& image {
filter: invert(1);
}
}
& #zen-glance-sidebar-close {
width: fit-content;
& label {
display: block;
max-width: 0px;
margin: 0;
overflow: hidden;
transition:
max-width 0.2s ease,
margin-left 0.2s ease;
}
&[waitconfirmation] {
background: rgb(220, 53, 69);
color: white;
fill: white;
& label {
max-width: 4rem;
margin-left: 8px;
}
& image {
filter: none;
}
}
}
}
:root[zen-no-padding='true'] .browserSidebarContainer.zen-glance-background {
--zen-native-inner-radius: 6px;
--zen-element-separation: 6px;
}
.browserSidebarContainer.zen-glance-background,
.browserSidebarContainer.zen-glance-overlay .browserContainer {
border-radius: var(--zen-native-inner-radius);
box-shadow: var(--zen-big-shadow);
}
.browserSidebarContainer.zen-glance-overlay {
box-shadow: none !important;
visibility: inherit;
:root[zen-no-padding='true'] & {
--zen-native-inner-radius: 0px;
}
& .browserContainer {
background: var(--zen-dialog-background);
position: fixed;
opacity: 0;
top: 0;
left: 0;
flex: unset !important;
&[has-finished-animation='true'] {
position: relative !important;
transition: 0s !important;
transform: none !important;
margin: auto !important;
top: 0 !important;
left: 0 !important;
}
& .browserStack {
border-radius: var(--zen-native-inner-radius);
overflow: hidden;
}
& browser {
width: 100%;
height: 100%;
opacity: 1;
transition: opacity 0.2s ease-in-out;
}
&[animate-full='true'] {
transform: translate(-50%, -50%);
& #zen-glance-sidebar-container {
opacity: 0 !important;
}
}
&[animate='true'] {
position: absolute;
}
}
&[fade-out='true'] {
& browser {
transition: opacity 0.1s ease;
opacity: 0;
}
}
& browser[animate-glance-open='true'] {
transition: opacity 0.2s ease-in-out;
opacity: 0;
}
}

View File

@@ -1,405 +0,0 @@
/*
* 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/.
*/
#PanelUI-zen-gradient-generator {
--panel-width: 320px;
--panel-padding: 10px;
min-width: var(--panel-width);
}
#PanelUI-zen-gradient-generator .panel-viewcontainer,
#PanelUI-zen-gradient-generator .panel-viewstack {
display: flex;
flex-direction: column;
width: 100%;
}
#PanelUI-zen-gradient-generator-controls {
align-items: center;
gap: var(--panel-padding);
}
#zen-theme-picker-color {
align-items: start;
width: 100%;
& label {
font-size: 12px;
margin-left: 0;
font-weight: 600;
margin-bottom: 5px;
}
& input,
& > hbox {
width: 100%;
position: relative;
}
}
#PanelUI-zen-gradient-generator-custom-input {
position: relative;
padding-right: 6px !important;
}
#PanelUI-zen-gradient-generator-color-custom-add {
position: absolute;
right: 0px;
top: 0px;
cursor: pointer;
}
#PanelUI-zen-gradient-colors-wrapper {
display: flex;
justify-content: space-between;
width: 100%;
margin-bottom: 10px;
align-items: center;
gap: 1.5rem;
padding: 0 var(--panel-padding);
@media (-moz-platform: macos) {
gap: 3rem;
}
& label {
margin-left: 0;
font-weight: 600;
margin-inline: 0;
margin-bottom: 2px;
@media (-moz-platform: macos) {
font-size: larger;
}
}
}
#PanelUI-zen-gradient-generator-predefined {
display: flex;
justify-content: space-around;
align-items: center;
margin: 5px auto 10px auto;
width: 100%;
& > box {
width: 18px;
height: 18px;
border-radius: 50%;
cursor: pointer;
position: relative;
transition: transform 0.1s;
&::after {
content: '';
position: absolute;
width: 18px;
height: 18px;
top: 0;
left: 0;
outline: 2px solid var(--zen-toolbar-element-bg);
border-radius: 50%;
pointer-events: none;
transition: transform 0.1s;
}
&:hover {
transform: scale(1.05);
&::after {
transform: scale(1.05);
}
}
&:hover:active {
transform: scale(0.95);
&::after {
transform: scale(0.95);
}
}
}
}
#PanelUI-zen-gradient-generator-custom-list {
margin-top: 15px;
&:not(:has(.zen-theme-picker-custom-list-item)) {
display: none;
}
& .zen-theme-picker-custom-list-item {
display: flex;
padding: 5px;
position: relative;
& .zen-theme-picker-dot.custom {
background: var(--zen-theme-picker-dot-color);
border: 1px solid var(--zen-colors-border);
border-radius: 5px;
width: 20px;
height: 20px;
margin-right: 10px;
}
& .zen-theme-picker-custom-list-item-label {
font-size: 12px;
font-weight: 600;
margin: 0;
display: flex;
align-items: center;
}
& .zen-theme-picker-custom-list-item-remove {
padding: 2px 4px !important;
margin: 0 !important;
margin-left: auto !important;
transition: opacity 0.1s;
opacity: 0;
}
&:hover .zen-theme-picker-custom-list-item-remove {
opacity: 1;
}
}
}
#PanelUI-zen-gradient-generator-opacity,
#PanelUI-zen-gradient-generator-texture {
margin: 0 !important;
margin-top: 5px !important;
background: transparent;
&::-moz-range-thumb {
background: var(--zen-colors-tertiary);
border: 2px solid var(--zen-colors-border);
border-radius: 12px;
height: 25px;
width: 13px;
cursor: pointer;
}
&::-moz-range-track {
background: var(--zen-colors-border);
border-radius: 999px;
height: 6px;
}
&::-moz-range-progress {
background: var(--zen-primary-color);
border-radius: 999px;
height: 8px;
}
}
.zen-theme-picker-gradient {
position: relative;
border-radius: calc(var(--zen-border-radius) - 2px);
overflow: hidden;
border-radius: var(--zen-border-radius);
min-height: calc(var(--panel-width) - var(--panel-padding) * 2);
margin-bottom: 20px;
background: var(--zen-toolbar-element-bg);
background-image: radial-gradient(light-dark(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.4)) 1px, transparent 0);
background-position: -19px -19px;
background-size: 5px 5px;
& .zen-theme-picker-dot {
position: absolute;
z-index: 2;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--zen-theme-picker-dot-color);
@media (-prefers-color-scheme: dark) {
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
}
cursor: pointer;
border: 2px solid #ffffff;
animation: zen-theme-picker-dot-animation 0.5s;
transform: translate(-50%, -50%);
&:first-of-type {
width: 30px;
height: 30px;
border-width: 3px;
z-index: 2;
transition: transform 0.1s;
&:hover {
transform: scale(1.05) translate(-50%, -50%);
}
}
&[dragging='true'] {
transform: scale(1.2) translate(-50%, -50%) !important;
}
}
}
#PanelUI-zen-gradient-generator-color-click-to-add {
position: absolute;
font-weight: 600;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
white-space: nowrap;
pointer-events: none;
&[hidden] {
display: none;
}
}
#PanelUI-zen-gradient-generator-color-actions {
display: flex;
position: absolute;
bottom: 8px;
left: 50%;
z-index: 1;
transform: translateX(-50%);
& .separator,
& #PanelUI-zen-gradient-generator-color-toggle-algo {
background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.5));
}
& button {
border: none !important;
padding: 0.4rem !important;
min-width: fit-content !important;
transition: background 0.2s;
appearance: none;
& .button-box {
gap: 0.1rem;
}
&:not(#PanelUI-zen-gradient-generator-color-toggle-algo) .button-text {
display: none;
}
&:hover {
background: light-dark(#cfcfcf, #313131);
}
&[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
}
& .separator {
width: 1px;
margin: 0 0.5rem;
height: 30px;
}
}
@media not -moz-pref('zen.theme.gradient.show-custom-colors') {
#PanelUI-zen-gradient-generator-custom-colors {
display: none !important;
}
}
#PanelUI-zen-gradient-generator-texture-wrapper {
width: 4rem;
height: 4rem;
position: relative;
@media (-moz-platform: macos) {
width: 25%;
aspect-ratio: 1;
height: unset;
}
&::after {
content: '';
position: absolute;
width: 60%;
height: 60%;
border: 1px solid color-mix(in srgb, var(--zen-colors-border) 50%, transparent 50%);
border-radius: 50%;
/* 3d effect */
background: linear-gradient(-45deg, transparent -10%, light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)) 110%);
z-index: 2;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}
&::before {
background-image: url(chrome://browser/content/zen-images/grain-bg.png);
opacity: var(--zen-grainy-background-opacity, 0);
mix-blend-mode: hard-light;
width: 60%;
height: 60%;
pointer-events: none;
top: 50%;
border-radius: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
content: '';
position: absolute;
}
& .zen-theme-picker-texture-dot {
height: 4px;
width: 4px;
border-radius: 50%;
background: light-dark(rgba(0, 0, 0, 0.3), rgba(255, 255, 255, 0.2));
position: absolute;
transition: opacity 0.2s;
transform: translate(-50%, -50%);
pointer-events: none;
&:not(.active) {
opacity: 0.2;
}
}
& #PanelUI-zen-gradient-generator-texture-handler {
width: 6px;
height: 12px;
background: light-dark(#757575, #d1d1d1);
position: absolute;
transition: height 0.1s;
z-index: 2;
border-radius: 2px;
cursor: pointer;
&:hover {
height: 14px;
}
}
}
#PanelUI-zen-gradient-generator-rotation-line {
border: 1px solid var(--zen-colors-border);
position: absolute;
--rotation-padding: 15px;
width: calc(100% - var(--rotation-padding) * 2);
border-bottom-color: transparent;
height: calc(100% - var(--rotation-padding) * 2);
top: var(--rotation-padding);
left: var(--rotation-padding);
border-radius: 50%;
opacity: 0;
pointer-events: none;
}
#PanelUI-zen-gradient-generator-rotation-dot {
position: absolute;
width: 25px;
height: 25px;
border-radius: 50%;
border: 1px solid var(--zen-colors-border);
background: var(--zen-colors-tertiary);
opacity: 0;
cursor: pointer;
z-index: 2;
transition: transform 0.1s;
&:hover {
transform: scale(1.1);
}
}

View File

@@ -1,302 +0,0 @@
#zen-media-controls-toolbar {
--progress-height: 4px;
--button-spacing: 2px;
display: flex;
justify-content: space-between;
min-width: 0;
background: transparent;
container-type: inline-size;
.toolbarbutton-1 {
border-radius: 5px;
color: white;
}
#zen-media-buttons-hbox {
align-items: start;
margin-top: -4px;
--toolbarbutton-outer-padding: 2px;
}
&:not([media-sharing]) {
#media-device-buttons {
display: none;
}
}
#media-device-buttons {
gap: 2px;
}
&[media-sharing] #zen-media-controls-hbox > toolbarbutton:not(:first-child) {
display: none;
#media-device-buttons {
display: flex;
}
}
&:not([can-pip]) {
#zen-media-info-vbox {
width: calc(100% - 26px);
flex-shrink: 0;
}
#zen-media-pip-button {
display: none;
}
}
#zen-media-prev-button,
#zen-media-play-pause-button,
#zen-media-next-button {
margin: 0;
}
image.toolbarbutton-icon {
padding: 5px;
width: 26px;
height: 26px;
}
#zen-media-progress-bar {
appearance: none;
width: 100%;
height: var(--progress-height);
margin: 0 8px;
border-radius: 2px;
background-color: rgba(255, 255, 255, 0.2);
cursor: pointer;
transition: height 0.15s ease-out;
&::-moz-range-track {
background: var(--zen-colors-border);
border-radius: 999px;
height: var(--progress-height);
}
&::-moz-range-progress {
background: var(--zen-primary-color);
border-radius: 999px;
height: var(--progress-height);
}
&::-moz-range-thumb {
background: var(--zen-primary-color);
border: none;
width: calc(var(--progress-height) * 2);
height: calc(var(--progress-height) * 2);
border-radius: 50%;
cursor: pointer;
transform: scale(0);
transition: transform 0.15s ease-out;
}
&:hover::-moz-range-thumb {
transform: scale(1);
}
}
&:hover {
.show-on-hover {
max-height: 50px;
padding: 5px;
margin-bottom: 0;
opacity: 1;
transform: translateY(0) !important;
pointer-events: auto;
}
}
& #zen-media-focus-button::after {
content: '';
position: absolute;
width: 110%;
height: 110%;
background-repeat: no-repeat;
opacity: 1;
background: url('chrome://browser/content/zen-images/note-indicator.svg') no-repeat;
top: -70%;
left: 50%;
transform: translateX(-50%);
z-index: 0;
pointer-events: none;
transition: opacity 0.8s ease;
opacity: 1;
}
&:is(:not(.playing:not([muted])), :hover) #zen-media-focus-button::after {
opacity: 0;
}
#zen-media-focus-button {
align-self: center;
transition:
opacity 0.2s ease,
transform 0.2s ease;
position: relative;
& image {
&:-moz-broken {
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3C/svg%3E") !important;
background: color-mix(in srgb, var(--zen-primary-color) 70%, transparent 30%);
}
}
}
& > toolbaritem {
--zen-media-control-bg: light-dark(rgba(255, 255, 255, 0.87), rgba(0, 0, 0, 0.87));
flex-grow: 1;
padding: 0;
transition: padding 0.3s ease-out;
position: absolute;
left: 0;
bottom: 0;
padding: 4px 6px;
border-radius: var(--border-radius-medium);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
background-color: var(--zen-media-control-bg);
backdrop-filter: saturate(3) contrast(2) blur(10px);
width: 100%;
}
.show-on-hover {
max-height: 0;
opacity: 0;
transform: translateY(1rem);
padding: 0 6px;
pointer-events: none;
transition:
max-height 0.2s ease,
opacity 0.2s ease,
transform 0.2s ease,
padding 0.2s ease;
}
#zen-media-current-time,
#zen-media-duration {
margin: 0 0 0 1px;
font-size: x-small;
opacity: 0.7;
font-weight: 500;
font-variant-numeric: tabular-nums;
}
}
#zen-media-controls-toolbar {
display: none;
animation: none;
transition: none;
&:not([hidden]) {
display: flex;
height: 2.5rem;
overflow: visible;
position: relative;
z-index: 2;
}
}
#zen-media-title,
#zen-media-artist {
align-self: start;
}
#zen-media-artist {
opacity: 0.7;
font-size: smaller;
&:empty {
display: none;
}
}
#zen-media-title {
height: 16px;
font-size: math;
}
#zen-media-main-vbox,
#zen-media-info-vbox,
#zen-media-progress-hbox {
width: 100%;
}
#zen-media-info-vbox {
#zen-media-controls-toolbar:not([media-position-hidden]) & {
transition-delay: 0.01s !important;
}
overflow-x: hidden;
overflow-x: visible;
white-space: nowrap;
/* Overflow inner box shadow from the left to simulate overflow */
mask-image: linear-gradient(to left, transparent, var(--zen-media-control-bg) 0.6em);
min-width: 1px;
&::before {
content: '';
position: absolute;
width: 0.6em;
background: linear-gradient(to right, var(--zen-media-control-bg) 0%, transparent 100%);
pointer-events: none;
top: 6px;
left: 0;
height: calc(100% - 6px);
z-index: 1;
}
& label {
min-height: 16px;
margin-left: 0;
font-weight: 500;
position: relative; /* For the animation */
&[overflow] {
animation: zen-back-and-forth-text 10s infinite ease-in-out;
}
}
}
#zen-media-main-vbox {
height: 100%;
justify-content: space-between;
}
#zen-media-progress-hbox {
flex-grow: 1;
height: 2rem;
align-items: center;
padding-top: 0px !important;
#zen-media-controls-toolbar[media-position-hidden] & {
display: none;
}
}
#zen-media-controls-hbox {
align-items: flex-end;
justify-content: space-between;
max-width: 100%;
--toolbarbutton-outer-padding: 0;
}
#zen-media-info-container {
padding-right: 0 !important;
}
#zen-media-controls-toolbar[can-pip] {
#zen-media-info-vbox {
flex-shrink: 1;
}
#zen-media-pip-button {
display: flex;
}
}
:root:not([zen-sidebar-expanded='true']) {
#zen-media-controls-toolbar {
display: none;
}
}

View File

@@ -1,66 +0,0 @@
/*
* 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/.
*/
panel[type='arrow'][animate='open'] {
@media (-moz-platform: macos) {
animation: zen-jello-animation-macos 0.4s ease-out;
&[side='bottom'] {
/* Animate from the bottom */
-zen-window-transform-origin: 0 100%;
}
:root[zen-right-side='true'] & {
/* Animate from the right */
-zen-window-transform-origin: 100% 0;
&[side='bottom'] {
/* Animate from the bottom right */
-zen-window-transform-origin: 100% 100%;
}
}
}
@media (-moz-platform: linux) or ((-moz-platform: windows) and (not (-moz-windows-mica-popups))) {
/* Mica popups have a weird background while the animation is running */
&::part(content) {
animation: zen-jello-animation 0.35s ease;
}
}
}
panel[type='arrow'][animate]:not([animate='open']) {
animation: zen-jello-out-animation 0.3s ease-in-out;
}
menupopup,
panel[type='arrow'] {
@media (-moz-windows-mica-popups) {
appearance: auto !important;
-moz-default-appearance: menupopup;
/* The blur behind doesn't blur all that much, add a semi-transparent
* background to improve contrast */
--panel-background: light-dark(rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.5)) !important;
--panel-border-color: transparent !important;
--panel-shadow-margin: 0px !important;
transition-duration: 0s !important;
&::part(content) {
animation: none !important;
}
}
@media (-moz-platform: macos) {
appearance: auto !important;
-moz-default-appearance: menupopup;
/* We set the default background here, rather than on ::part(content),
* because otherwise it'd interfere with the native look. Non-native-looking
* popups should get their background via --panel-background */
background-color: Menu;
--panel-shadow-margin: 0px !important;
--panel-background: transparent !important;
--panel-border-color: transparent;
/* This should be kept in sync with GetMenuMaskImage() */
--panel-border-radius: 6px;
}
}

View File

@@ -1,70 +0,0 @@
/*
* 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/.
*/
#zenEditBookmarkPanelFaviconContainer {
border: 1px solid var(--input-border-color);
width: 100px;
height: 100px;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
margin: 16px;
background: light-dark(#fff, rgba(255, 255, 255, 0.1));
}
#editBookmarkPanel::part(content) {
display: flex;
flex-direction: row;
}
#zenEditBookmarkPanelFavicon {
width: 20px;
height: 20px;
}
#editBookmarkPanel .panel-header {
min-height: 0;
padding-bottom: 0;
padding-top: 16px;
}
#editBMPanel_namePicker {
width: -moz-available;
}
label.editBMPanel_folderRow,
label.editBMPanel_nameRow {
min-width: 60px;
}
hbox.editBMPanel_folderRow {
width: -moz-available;
}
.zenEditBMPanel_fieldContainer:not(:first-child) {
margin-top: 10px;
}
.zenEditBMPanel_fieldContainer {
align-items: center;
}
#editBookmarkPanelBottomContent {
display: none;
}
:host(:not([native])) #label-box {
font-weight: 500;
}
#editBMPanel_folderMenuList::part(icon) {
margin-right: 10px;
}
/* Bookmarks in toolbar */
#PersonalToolbar {
background: transparent;
}

View File

@@ -1,14 +0,0 @@
/*
* 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/.
*/
/* Zen Welcome dialog override */
@media (prefers-color-scheme: dark) {
.dialogBox:not(.spotlightBox) {
border: 1px solid var(--zen-colors-border);
}
}
.dialogBox:not(.spotlightBox) {
min-width: min(80vw, 376px);
}

View File

@@ -1,73 +0,0 @@
/*
* 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/.
*/
#unified-extensions-manage-extensions {
padding-left: 20px;
}
panelview .unified-extensions-item-action-button {
padding: 5px 20px;
}
#unified-extensions-description {
padding: 0 20px;
margin: 0;
}
.unified-extensions-item {
padding: 0;
}
#unified-extensions-view {
--uei-icon-size: 16px;
.unified-extensions-item {
margin-block: 0;
border-radius: var(--arrowpanel-menuitem-border-radius);
> .unified-extensions-item-action-button {
.unified-extensions-item-message-deck {
display: none;
}
&:hover {
background-color: initial;
}
}
> .unified-extensions-item-menu-button {
list-style-image: url('chrome://global/skin/icons/more.svg');
padding: 0;
> .toolbarbutton-icon {
padding: var(--arrowpanel-menuitem-padding-block) var(--arrowpanel-menuitem-padding-inline);
border: none;
opacity: 0;
}
&:hover {
> .toolbarbutton-icon {
background-color: initial;
}
&:active {
color: var(--button-primary-hover-bgcolor);
}
}
}
&:hover {
background-color: var(--panel-item-hover-bgcolor);
> .unified-extensions-item-menu-button > .toolbarbutton-icon {
opacity: 1;
}
}
&:has(> .unified-extensions-item-action-button:not([disabled]):hover:active) {
background-color: var(--panel-item-active-bgcolor);
}
}
}

View File

@@ -1,9 +0,0 @@
/*
* 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/.
*/
.printSettingsBrowser {
min-width: 350px;
}

View File

@@ -1,375 +0,0 @@
/*
* 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/.
*/
@import url('chrome://browser/content/zen-styles/zen-panels/bookmarks.css');
@import url('chrome://browser/content/zen-styles/zen-panels/extensions.css');
@import url('chrome://browser/content/zen-styles/zen-panels/print.css');
@import url('chrome://browser/content/zen-styles/zen-panels/dialog.css');
:root {
--panel-subview-body-padding: 2px 0;
--arrowpanel-menuitem-border-radius: 5px;
--arrowpanel-menuitem-margin: var(--uc-arrowpanel-menuitem-margin-block) var(--uc-arrowpanel-menuitem-margin-inline);
--arrowpanel-menuitem-padding-block: 8px;
--arrowpanel-menuitem-padding-inline: 14px;
--uc-arrowpanel-menuicon-margin-inline: 14px;
--uc-arrowpanel-menuitem-margin-inline: 4px;
--uc-arrowpanel-menuitem-margin-block: 2px;
--panel-separator-margin-vertical: 2px;
--panel-separator-margin-horizontal: 1px;
--uc-panel-zoom-button-padding: 8px;
--uc-panel-zoom-button-inline-padding: 9px;
--uc-panel-zoom-padding-block: calc(var(--panel-separator-margin-vertical) + var(--uc-arrowpanel-menuitem-margin-block));
--uc-autocomplete-panel-menuitem-margin: 4px;
--uc-autocomplete-panel-menuicon-padding-inline: 14px;
--uc-autocomplete-panel-separator-margin-vertical: 4px;
--uc-permission-itemcontainer-padding-block: 8px;
--uc-permission-item-margin-block: 4px;
--uc-permission-item-padding-inline: 16px;
--panel-separator-color: var(--zen-colors-border);
--zen-panel-separator-width: 1px;
}
menupopup,
panel {
--panel-background: var(--arrowpanel-background);
--panel-border-radius: var(--zen-native-inner-radius);
}
/* split-view popup */
#confirmation-hint {
--arrowpanel-background: var(--zen-colors-primary);
}
/* app menu */
.addon-banner-item,
.panel-banner-item {
margin: 2px 4px 2px;
padding-inline: 4px 12px;
padding-block: var(--arrowpanel-menuitem-padding-block);
border-radius: var(--arrowpanel-menuitem-border-radius);
}
#appMenu-fxa-label2 label,
#PanelUI-fxa-menu-syncnow-button label {
margin-block: 0;
}
.widget-overflow-list .toolbarbutton-1:not(.toolbarbutton-combined) > .toolbarbutton-text,
.subviewbutton:not(#appMenu-zoom-controls > .subviewbutton) > .toolbarbutton-icon + .toolbarbutton-text,
#appMenu-fxa-label2 > vbox {
padding-inline-start: var(--uc-arrowpanel-menuicon-margin-inline);
}
/* special case menuitems with no icons */
#appMenu-zoom-controls > .toolbarbutton-text,
#fxa-manage-account-button > vbox,
#PanelUI-fxa-menu-syncnow-button > hbox {
padding-inline-start: calc(16px + var(--uc-arrowpanel-menuicon-margin-inline));
}
/* firefox profile avatar in appmenu */
#appMenu-fxa-label2::before {
content: '';
display: -moz-box;
height: 16px;
width: 16px;
background: var(--avatar-image-url) 0/16px;
scale: 1.25;
border-radius: 99px;
}
/* disable proton account separator */
#appMenu-fxa-separator {
border-image: none;
}
#appMenu-fxa-status2:not([fxastatus]) {
padding-block: 0;
}
#appMenu-fxa-status2:not([fxastatus]) > #appMenu-fxa-label2 {
margin-inline-end: calc(var(--arrowpanel-menuitem-padding-inline) * -1);
}
/* zoom controls */
#appMenu-zoom-controls {
border-top: 1px solid var(--panel-separator-color);
padding-inline: calc(var(--arrowpanel-menuitem-padding-inline) + var(--uc-arrowpanel-menuitem-margin-inline))
var(--uc-arrowpanel-menuitem-margin-inline);
padding-block: var(--uc-panel-zoom-padding-block);
margin: var(--panel-separator-margin-vertical) 0 calc(var(--panel-separator-margin-vertical) * -1);
}
#appMenu-zoom-controls > .subviewbutton {
padding: var(--uc-panel-zoom-button-padding) var(--uc-panel-zoom-button-inline-padding);
margin: 0;
}
#appMenu-zoom-controls > #appMenu-zoomReset-button2 {
padding: var(--uc-panel-zoom-button-padding) calc(var(--uc-panel-zoom-button-padding) / 2);
}
/* #appMenu-zoomReduce-button2, */
#appMenu-zoom-controls > #appMenu-fullscreen-button2 {
margin-left: calc((var(--panel-separator-margin-vertical) + var(--uc-arrowpanel-menuitem-margin-block)) * 2 + 1px);
}
#appMenu-zoom-controls > #appMenu-fullscreen-button2::before {
content: '';
border-inline-start: 1px solid var(--panel-separator-color);
display: block;
position: relative;
height: 32px;
margin-block: calc(var(--uc-panel-zoom-button-padding) * -1);
transform: translateX(
calc(
var(--uc-panel-zoom-button-inline-padding) * -1 -
(var(--panel-separator-margin-vertical) + var(--uc-arrowpanel-menuitem-margin-block)) - 1px
)
);
}
#appMenu-zoomReset-button2 {
height: calc(16px + var(--uc-panel-zoom-button-padding) * 2);
min-height: calc(16px + var(--uc-panel-zoom-button-padding) * 2);
}
#appMenu-zoomReduce-button2:not([disabled], [open], :active):is(:hover),
#appMenu-zoomEnlarge-button2:not([disabled], [open], :active):is(:hover),
#appMenu-fullscreen-button2:not([disabled], [open], :active):is(:hover),
#appMenu-zoomReset-button2:not([disabled], [open], :active):is(:hover) {
background-color: var(--panel-item-hover-bgcolor);
}
#appMenu-zoomReduce-button2:not([disabled]):is([open], :hover:active),
#appMenu-zoomEnlarge-button2:not([disabled]):is([open], :hover:active),
#appMenu-fullscreen-button2:not([disabled]):is([open], :hover:active),
#appMenu-zoomReset-button2:not([disabled]):is([open], :hover:active) {
background-color: var(--panel-item-active-bgcolor);
}
#appMenu-zoomReset-button2 > .toolbarbutton-text,
#appMenu-fullscreen-button2 > .toolbarbutton-icon {
background-color: transparent;
padding: 0;
}
.subviewbutton[shortcut]::after {
opacity: 0.7;
}
#widget-overflow-mainView .panel-subview-body {
padding-bottom: 0;
}
.PanelUI-subView > .panel-header + toolbarseparator {
margin-bottom: 0;
}
.PanelUI-subView > .panel-header + toolbarseparator + .panel-subview-body {
padding-top: var(--panel-separator-margin-vertical);
}
#identity-popup-security-button {
margin-bottom: var(--panel-separator-margin-vertical);
}
#permission-popup-mainView-panel-header,
#identity-popup-mainView-panel-header,
#protections-popup-mainView-panel-header,
.panel-header {
min-height: calc((var(--arrowpanel-menuitem-padding-block) + 4px) * 2 + 16px);
}
/* URL bar popup */
.identity-popup-security-connection > hbox > description {
margin-inline-start: 0;
}
.identity-popup-security-connection.identity-button {
margin-inline-end: calc(-1 * (var(--arrowpanel-menuitem-padding-inline) - 10px));
}
#identity-popup-mainView-panel-header-span,
#permission-popup-mainView-panel-header-span,
#identity-popup-mainView-panel-header label,
#permission-popup-mainView-panel-header label,
#protections-popup-mainView-panel-header-span {
margin-block: 0;
}
.permission-popup-section {
padding-block: var(--uc-permission-itemcontainer-padding-block);
}
#permission-popup-permissions-content {
padding-inline: var(--uc-permission-item-padding-inline);
}
.permission-popup-permission-item,
#permission-popup-storage-access-permission-list-header {
margin-block: var(--uc-permission-item-margin-block);
}
.permission-popup-permission-label,
.permission-popup-permission-header-label {
margin-inline-start: var(--uc-arrowpanel-menuicon-margin-inline);
}
#editBookmarkPanel > #editBookmarkHeaderSeparator,
#editBookmarkPanel > .panel-subview-body > #editBookmarkSeparator {
margin-inline: 0;
}
#identity-popup-mainView > toolbarseparator:first-child,
#unified-extensions-view > toolbarseparator:first-child {
display: none;
opacity: 0;
}
menupopup::part(content),
panel::part(content) {
border: var(--zen-appcontent-border);
}
menupopup,
panel {
box-shadow: none;
}
.panel-subview-footer-button {
padding-top: 10px;
padding-bottom: 10px;
}
#identity-popup-clear-sitedata-footer {
margin: 0;
padding: 0;
}
.panel-subview-footer-button {
--arrowpanel-menuitem-padding-block: 20px;
--arrowpanel-menuitem-padding-inline: 15px;
}
toolbarseparator,
menuseparator {
border-width: var(--zen-panel-separator-width);
}
#appMenu-zoom-controls {
border-top-width: var(--zen-panel-separator-width);
}
#identity-popup-multiView toolbarseparator,
#editBookmarkHeaderSeparator {
display: none;
}
/* Context menu */
menu,
menuitem {
&:where([_moz-menuactive]:not([disabled='true'])) {
background-color: var(--button-hover-bgcolor);
color: var(--button-hover-color);
}
}
/*Bookmark workspace selector styles*/
.workspace-dropdown {
position: relative;
width: 100%;
display: flex;
}
.workspace-trigger {
width: 100%;
text-align: left;
padding: 8px 12px;
border: 1px solid var(--input-border-color);
border-radius: 4px;
background-color: var(--zen-colors-tertiary);
display: flex;
align-items: center;
justify-content: space-between;
}
#editBMPanel_workspaceList {
flex-direction: column;
width: 100%;
max-height: 200px;
overflow-y: auto;
margin-top: 4px;
border: 1px solid var(--zen-colors-border);
border-radius: 4px;
background-color: var(--zen-colors-tertiary);
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
padding: 4px 0;
}
#editBMPanel_workspaceList li {
list-style: none;
margin: 0;
padding: 0;
}
#editBMPanel_workspaceList li > label {
display: flex;
align-items: center;
padding: 4px 12px;
cursor: pointer;
}
#editBMPanel_workspaceList input[type='checkbox'] {
margin-right: 8px;
}
/* Section: Toast notifications */
#zen-toast-container {
position: fixed;
top: calc(var(--zen-element-separation) * 2);
z-index: 1000;
gap: 1rem;
display: flex;
align-items: end;
:root:not([zen-right-side='true']) & {
right: calc(var(--zen-element-separation) * 2);
}
:root[zen-right-side='true'] & {
left: calc(var(--zen-element-separation) * 2);
}
& .zen-toast {
padding: 0.5rem 0.6rem;
border-radius: 12px;
background: linear-gradient(
170deg,
var(--zen-primary-color) -40%,
color-mix(in srgb, var(--zen-primary-color) 85%, #0f0f0f 15%)
);
color: var(--button-primary-color);
box-shadow: 0 0 14px 3px rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.1);
display: flex;
font-weight: 500;
gap: 5px;
flex-direction: column;
gap: 2px;
width: fit-content;
& .description {
opacity: 0.6;
}
}
}

View File

@@ -1,226 +0,0 @@
#zen-rice-share-dialog-overlay:not([hidden]) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 3;
display: flex;
justify-content: center;
align-items: center;
}
#zen-rice-share-dialog,
#zen-rice-share-dialog-notice {
color: var(--panel-color);
background: var(--arrowpanel-background);
border-radius: var(--zen-native-inner-radius);
box-shadow: 0 0 1px 1px hsla(0, 0%, 0%, 0.2);
border: var(--zen-appcontent-border);
overflow: hidden;
animation: zen-jello-animation-large 0.4s ease;
max-width: 400px;
&[animate='true'] {
animation: zen-rice-submit-animation 1s cubic-bezier(0.77, 0, 0.18, 1);
}
& .zen-rice-share-content {
padding: 10px 0;
border-top: var(--zen-appcontent-border);
background: var(--arrowpanel-background);
position: relative;
align-items: center;
min-height: 50px;
&:has(#zen-rice-share-first-form:not([fade-out])),
&:has(#zen-rice-share-success) {
padding-top: 20px;
}
& > vbox {
width: 100%;
padding: 0 10px;
}
& #zen-rice-share-first-form input[type='text'] {
width: 100%;
padding: 1px 2px;
border: 0;
border-bottom: 1px solid var(--zen-appcontent-border);
background: transparent;
font-style: italic;
font-weight: 600;
}
& #zen-rice-share-name {
font-style: normal !important;
font-size: 24px;
margin-left: 1px;
}
& .indent {
margin-left: 30px;
}
}
& .zen-rice-share-header {
height: 200px;
margin-bottom: -20px;
}
& #zen-rice-share-options {
padding: 10px 0;
max-height: 30px;
overflow: hidden;
transition:
max-height 0.3s ease,
height 0.3s ease;
&[zen-collapsed='false'] {
max-height: 350px;
}
& > .options-header {
font-weight: 600;
margin-bottom: 5px;
position: relative;
cursor: pointer;
align-items: center;
color: inherit;
-moz-context-properties: fill, fill-opacity;
fill: var(--toolbarbutton-icon-fill);
& label {
width: fit-content;
cursor: pointer;
}
}
& > .options-header image {
transition: transform 0.3s ease;
width: 15px;
height: 15px;
}
&[zen-collapsed='false'] > .options-header image {
transform: rotate(90deg);
}
& > checkbox {
margin-left: 15px;
}
}
& .panel-footer {
margin: 0;
margin-top: 10px;
& button {
justify-content: center;
}
}
#zen-rice-share-error {
border: 1px solid rgba(255, 0, 0, 0.5);
background: rgba(255, 0, 0, 0.1);
padding: 5px;
transition: opacity 0.3s ease;
border-radius: var(--zen-native-inner-radius);
@starting-style {
opacity: 0;
}
& button {
margin-left: auto;
margin-top: 2px;
}
}
/* Animations */
#zen-rice-share-first-form[fade-out] {
position: absolute;
transform: translateX(-100%);
pointer-events: none;
animation: zen-rice-form-out 0.5s ease;
}
#zen-rice-share-second-form {
animation: zen-rice-form-in 0.5s ease forwards;
justify-content: center;
& > hbox {
width: 100%;
height: 5px;
border-radius: 100px;
border: 1px solid var(--zen-colors-border);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--zen-primary-color);
transition: width 4s cubic-bezier(1, 0, 0, 1);
@starting-style {
width: 0;
}
}
}
&[fade-out] {
position: absolute !important;
animation: zen-rice-form-out 0.5s ease forwards;
}
}
#zen-rice-share-success {
overflow-y: hidden;
max-height: 0px;
animation: zen-rice-form-in-2 0.5s ease forwards;
& > h1 {
margin: 0;
font-size: 20px;
}
& > p {
color: var(--text-color-deemphasized);
}
& label {
margin: 0;
margin-top: 15px;
font-weight: 600;
margin-bottom: 5px;
}
}
}
#zen-rice-share-dialog-notice {
padding: 15px;
& br {
margin-bottom: 10px;
}
& > p {
color: var(--text-color-deemphasized);
margin-top: 10px;
}
.panel-footer {
margin-top: 10px;
align-items: center;
}
}

View File

@@ -1,71 +0,0 @@
/*
* 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/.
*/
#zen-profile-button {
& stack {
padding: 0;
}
& .toolbarbutton-badge {
display: none !important;
}
& stack {
width: calc(2 * var(--toolbarbutton-inner-padding) + 16px) !important;
height: calc(2 * var(--toolbarbutton-inner-padding) + 16px) !important;
display: flex;
justify-content: center;
align-items: center;
& > image {
width: 0;
}
}
& #zen-profile-button-icon {
width: 20px;
height: 20px;
list-style-image: var(--avatar-image-url);
border-radius: 50%;
pointer-events: none;
list-style-image: var(--avatar-image-url);
-moz-context-properties: fill;
fill: currentColor;
border: 1px solid var(--zen-colors-border);
}
}
/* Bookmarks sidebar */
#zen-tabbox-wrapper {
& #sidebar-splitter {
opacity: 0;
margin: 0 calc(-1 * var(--zen-element-separation));
}
& #sidebar-box {
border-radius: var(--zen-native-inner-radius);
box-shadow: var(--zen-big-shadow);
overflow: hidden;
&[positionend=''] {
order: 6;
& ~ #sidebar-splitter {
order: 5;
}
}
:root:not([zen-right-side='true']) &[positionend='true'] {
margin-right: var(--zen-element-separation);
}
:root[zen-right-side='true'] & {
margin-left: var(--zen-element-separation);
}
}
}
/* Menubar */
#toolbar-menubar {
display: none !important;
}

View File

@@ -1,41 +0,0 @@
/*
* 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/.
*/
/* Screenshots */
#screenshotsPagePanel {
position: absolute !important;
top: 3%;
right: 1.5%;
}
#zen-workspaces-button .zen-workspace-sidebar-name {
margin-left: 10px;
display: none;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#customization-footer #customization-toolbar-visibility-button {
display: none !important;
}
#alltabs-button {
display: none !important;
}
.private-browsing-indicator-with-label {
display: none !important;
}
body > #confetti {
z-index: 1;
}
/* Bookmarks */
#PersonalToolbar:not([collapsed='true']) {
min-height: 30px;
}

View File

@@ -1,19 +0,0 @@
/*
* 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/.
*/
/* Styles for both vertical and horizontal tabs */
@import url('chrome://browser/content/zen-styles/zen-tabs/vertical-tabs.css');
@import url('chrome://browser/content/zen-styles/zen-tabs/horizontal-tabs.css');
#zen-tabbox-wrapper {
position: relative;
z-index: 0;
}
@media -moz-pref('zen.workspaces.hide-default-container-indicator') {
.tabbrowser-tab[zenDefaultUserContextId='true'] .tab-context-line {
display: none !important;
}
}

View File

@@ -1,335 +0,0 @@
/*
* 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/.
*/
@media not -moz-pref('zen.tabs.vertical') {
:root #browser {
display: flex !important;
flex-direction: column !important;
}
& #navigator-toolbox {
display: flex !important;
flex-direction: row !important;
max-width: unset !important;
min-width: unset !important;
width: 100% !important;
padding: var(--zen-toolbox-padding) !important;
}
#tabbrowser-tabs {
display: -webkit-box !important;
-webkit-box-orient: horizontal;
-webkit-box-pack: start;
max-width: 10000px !important;
--tabstrip-min-height: calc(var(--tab-min-height) - 4 * var(--tab-block-margin));
--tab-min-height: 10px !important;
}
.vertical-pinned-tabs-container-separator {
display: none !important;
}
#zen-essentials-container,
#vertical-pinned-tabs-container,
#tabbrowser-arrowscrollbox {
-webkit-box-flex: 1;
}
#vertical-pinned-tabs-container:empty {
-webkit-box-flex: 0 !important;
width: 0 !important;
padding: 0 !important;
margin: 0 !important;
border: none !important;
visibility: collapse !important;
}
#navigator-toolbox {
flex-direction: row !important;
align-items: center;
}
#titlebar {
flex-direction: row !important;
width: 100%;
height: 36px !important;
}
#zen-essentials-container {
--tab-min-height: 36px !important;
display: flex !important;
flex-direction: row !important;
padding-inline-end: 0 !important;
}
#vertical-pinned-tabs-container {
display: flex !important;
flex-direction: row !important;
padding-inline-end: 0 !important;
}
#zen-essentials-container .tabbrowser-tab {
width: 0% !important;
}
#vertical-pinned-tabs-container .tabbrowser-tab {
width: 0% !important;
}
.tabbrowser-tab[zen-glance-tab='true'] {
.tab-label-container {
display: none !important;
width: 0px !important;
max-width: 0px !important;
}
}
#tabbrowser-arrowscrollbox {
display: flex !important;
max-width: -moz-available;
overflow: hidden !important;
}
#TabsToolbar {
flex-direction: row !important;
gap: 8px;
overflow: hidden !important;
display: flex !important;
}
#TabsToolbar-customization-target {
flex-direction: row !important;
}
#tabbrowser-tabs[orient='vertical'] {
flex-direction: row !important;
}
tabs {
flex-direction: row !important;
}
#zen-essentials-container {
container-name: tab-container;
container-type: normal;
max-width: 36px !important;
flex: 1 1 36px !important;
}
#vertical-pinned-tabs-container {
container-name: tab-container;
container-type: normal;
max-width: 36px !important;
min-width: 36px !important;
flex: 1 1 36px !important;
}
#vertical-pinned-tabs-container .tab-close-button {
display: none !important;
}
#vertical-pinned-tabs-container .tab-reset-button {
display: none !important;
}
#vertical-pinned-tabs-container .tab-label-container {
display: none !important;
}
#vertical-pinned-tabs-container .tab-icon-image {
margin: 0 !important;
}
.tabbrowser-tab {
container-name: tab-container;
container-type: normal;
min-width: 40px !important;
flex: 1 1 150px !important;
width: -moz-available;
&:is(:hover, [visuallyselected]) .tab-close-button {
display: block;
--tab-inline-padding: 0; /* Avoid weird padding */
margin-inline-end: 0 !important;
}
}
.tabbrowser-tab[selected] {
flex: 2 0 150px !important;
}
@container tab-container (max-width: 80px) {
.tab-close-button {
margin-right: 0px !important;
}
.tab-icon-image {
padding: 0 !important;
margin-left: min(2.5px, 5%) !important;
margin-right: min(10px, 5%) !important;
}
.tab-label-container,
.tab-content {
margin: 0 !important;
padding-left: min(8px, 5%) !important;
padding-right: min(8px, 5%) !important;
}
}
@container tab-container (max-width: 44px) {
.tab-label-container {
display: none !important;
}
.tab-content {
justify-content: space-around !important;
}
.tab-close-button {
display: none !important;
}
.tabbrowser-tab[selected] {
& .tab-icon-image,
.tab-icon-stack {
display: none !important;
}
& .tab-content {
justify-content: space-around !important;
padding: 0 !important;
}
.tab-close-button {
display: block !important;
}
}
}
#vertical-pinned-tabs-container::after {
z-index: -1;
content: '';
position: absolute;
right: 0;
width: 1px;
height: 45%;
top: 50%;
transform: translateY(-50%);
background: hsl(255, 10%, 100%);
}
/* Other UI Elements */
.zen-current-workspace-indicator {
display: none !important;
}
#zen-sidebar-splitter {
display: none !important;
}
#tabbrowser-tabpanels {
padding-left: var(--zen-element-separation) !important;
}
#appcontent * {
overflow: visible !important;
}
#TabsToolbar-customization-target::after {
display: none !important;
}
#zen-sidebar-bottom-buttons {
width: auto !important;
padding: 0 !important;
}
/* Height Adjustments */
#vertical-pinned-tabs-container,
#zen-essentials-container,
#tabbrowser-arrowscrollbox {
max-height: none !important;
}
#PanelUI-zen-gradient-generator {
min-width: 390px !important;
}
#zen-essentials-container:not(:empty),
#vertical-pinned-tabs-container:not(:empty),
#tabbrowser-arrowscrollbox {
-webkit-box-flex: 1;
min-width: min-content;
width: auto !important;
}
#vertical-pinned-tabs-container:not(:empty) {
display: -webkit-box !important;
-webkit-box-orient: horizontal;
min-width: fit-content !important;
width: fit-content !important;
position: relative;
margin-right: -1px !important;
}
#vertical-pinned-tabs-container:not(:empty) .tabbrowser-tab {
position: relative;
display: -webkit-box !important;
}
#tabbrowser-arrowscrollbox {
margin-left: 0 !important;
}
#vertical-pinned-tabs-container:empty,
#zen-essentials-container:empty {
-webkit-box-flex: 0 !important;
width: 0 !important;
min-width: 0 !important;
padding: 0 !important;
margin: 0 !important;
border: none !important;
visibility: collapse !important;
}
#nav-bar {
width: unset !important;
min-width: 500px !important;
}
#tabbrowser-arrowscrollbox-periphery {
margin: 0 !important;
}
hbox#nav-bar-customization-target toolbarbutton.chromeclass-toolbar-additional:nth-of-type(1) {
padding-inline-start: var(--toolbar-start-end-padding) !important;
}
toolbar#PersonalToolbar {
padding-left: 6px !important;
}
.tab-context-line {
width: 100% !important;
height: 3px !important;
}
.tabbrowser-tab[zen-glance-tab='true'] {
flex-basis: fit-content !important;
max-width: 36px !important;
}
#zen-essentials-container .tabbrowser-tab[zen-glance-tab='true'] {
left: 2px;
}
#vertical-pinned-tabs-container .tabbrowser-tab[zen-glance-tab='true'] {
position: absolute !important;
}
#TabsToolbar-customization-target toolbarbutton,
#TabsToolbar-customization-target toolbarpaletteitem {
-webkit-box-flex: 0 !important;
min-width: min-content;
width: auto !important;
.toolbarbutton-text {
display: none !important;
}
}
}

View File

@@ -1,35 +0,0 @@
height: var(--zen-toolbar-height);
@media -moz-pref('zen.view.hide-window-controls') {
& {
transition:
height 0.15s ease,
opacity 0.1s ease-out;
transition-delay: 0.2s;
& > * {
transition: opacity 0.2s ease-out;
transition-delay: 0.2s;
}
}
&:not([zen-has-hover='true']):not([has-popup-menu]):not(:focus-within):not(:has(*:is([panelopen='true'], [open='true']))) {
transition-delay: 0.2s;
height: var(--zen-element-separation);
overflow: hidden;
opacity: 0;
& > * {
opacity: 0;
pointer-events: none;
}
}
@media -moz-pref('zen.view.experimental-no-window-controls') {
&:has(#PersonalToolbar[collapsed='true']) {
max-height: 0 !important;
overflow: hidden;
opacity: 0 !important;
pointer-events: none !important;
}
}
}

View File

@@ -1,16 +0,0 @@
/*
* 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/.
*/
&
#zen-sidebar-top-buttons:not(
:has(#zen-sidebar-top-buttons-customization-target > *:not(#zen-sidebar-top-buttons-separator))
) {
max-height: 0 !important;
min-height: 0 !important;
opacity: 0;
overflow: hidden;
pointer-events: none;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,231 +0,0 @@
/*
* 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/.
*/
/* Here, we contain all the theme related variables, for example theme
* colors, border radius, etc.
* We have `--zen-border-radius` and `--zen-primary-color` as variables.
*/
:host(:is(.anonymous-content-host, notification-message)),
:root,
.zenLooksAndFeelColorOption {
/** We also add `.zenLooksAndFeelColorOption` so that it recalculates the colors when the theme changes
* in the preferences page.
*/
/* Default values */
--zen-border-radius: 7px;
--zen-primary-color: #ffb787;
/* Branding */
--zen-branding-dark: #1d1d1d;
--zen-branding-coral: #f76f53;
--zen-branding-paper: #ebebeb;
--zen-branding-bg: light-dark(var(--zen-branding-paper), var(--zen-branding-dark));
--zen-branding-bg-reverse: light-dark(var(--zen-branding-dark), var(--zen-branding-paper));
/** Zen colors are generated automatically from the primary color */
--zen-colors-primary: color-mix(in srgb, var(--zen-primary-color) 50%, black 50%);
--zen-colors-secondary: color-mix(in srgb, var(--zen-colors-primary) 20%, white 80%);
--zen-colors-tertiary: color-mix(in srgb, var(--zen-primary-color) 2%, white 98%);
--zen-colors-hover-bg: color-mix(in srgb, var(--zen-primary-color) 90%, white 10%);
--zen-colors-primary-foreground: var(--zen-branding-bg-reverse);
--zen-colors-border: color-mix(in srgb, var(--zen-colors-secondary) 97%, black 3%);
--zen-colors-border-contrast: color-mix(in srgb, var(--zen-colors-secondary) 10%, rgba(181, 181, 181, 0.11) 90%);
--zen-colors-input-bg: color-mix(in srgb, var(--zen-primary-color) 1%, var(--zen-colors-tertiary) 99%);
--zen-dialog-background: var(--zen-colors-tertiary);
--zen-urlbar-background: color-mix(in srgb, var(--zen-primary-color) 3%, #f4f4f4 97%);
--zen-secondary-btn-color: var(--zen-colors-primary-foreground);
--in-content-primary-button-background: color-mix(
in srgb,
var(--zen-primary-color) 30%,
var(--zen-branding-bg-reverse) 70%
) !important;
--in-content-primary-button-background-hover: color-mix(
in srgb,
var(--zen-primary-color) 35%,
var(--zen-branding-bg-reverse) 65%
) !important;
--in-content-primary-button-background-active: color-mix(
in srgb,
var(--zen-primary-color) 40%,
var(--zen-branding-bg-reverse) 60%
) !important;
--button-text-color-primary: var(--zen-branding-bg) !important;
--in-content-primary-button-text-color: var(--zen-branding-bg) !important;
--in-content-primary-button-border-color: transparent !important;
--in-content-primary-button-border-hover: transparent !important;
--in-content-primary-button-border-active: var(--zen-colors-border) !important;
--in-content-accent-color: var(--zen-colors-primary) !important;
--in-content-accent-color-active: currentColor !important;
/* This is like the secondary button */
--in-content-button-background: transparent !important;
--in-content-button-text-color: var(--zen-secondary-btn-color) !important;
--in-content-button-background-hover: color-mix(in srgb, currentColor 3%, transparent 97%) !important;
--button-bgcolor: var(--in-content-button-background) !important;
--button-hover-bgcolor: var(--in-content-button-background-hover) !important;
--button-hover-color: var(--in-content-button-text-color-hover) !important;
--focus-outline-color: var(--button-bgcolor) !important;
--toolbarbutton-icon-fill-attention: var(--zen-primary-color) !important;
--toolbarbutton-icon-fill: light-dark(rgba(57, 57, 58, 0.6), rgba(251, 251, 254, 0.6)) !important;
--button-primary-bgcolor: var(--in-content-primary-button-background) !important;
--button-primary-hover-bgcolor: var(--in-content-primary-button-background-hover) !important;
--button-primary-active-bgcolor: var(--in-content-primary-button-background-active) !important;
--button-primary-color: var(--in-content-primary-button-text-color) !important;
--button-background-color: var(--in-content-button-background) !important;
--button-background-color-hover: var(--in-content-button-background-hover) !important;
--button-background-color-active: color-mix(in srgb, currentColor 5%, transparent 95%) !important;
--color-accent-primary: var(--button-primary-bgcolor) !important;
--color-accent-primary-hover: var(--button-primary-hover-bgcolor) !important;
--color-accent-primary-active: var(--button-primary-active-bgcolor) !important;
--link-color: var(--zen-branding-bg-reverse) !important;
--link-color-hover: var(--zen-colors-primary-foreground) !important;
--link-color-active: color-mix(in srgb, var(--zen-colors-primary-foreground) 80%, transparent 20%) !important;
--in-content-page-background: var(--zen-colors-tertiary) !important;
--zen-in-content-dialog-background: var(--zen-colors-tertiary);
--zen-button-border-radius: 5px;
--zen-button-padding: 0.6rem 1.2rem;
--zen-toolbar-element-bg: light-dark(rgba(89, 89, 89, 0.1), rgba(255, 255, 255, 0.1));
/* Toolbar */
--zen-toolbar-height: 38px;
--zen-toolbar-button-inner-padding: 6px;
--toolbarbutton-outer-padding: 4px;
--toolbarbutton-hover-background: color-mix(in srgb, var(--zen-branding-bg-reverse) 10%, transparent 90%);
/* Other colors */
--urlbar-box-bgcolor: var(--zen-urlbar-background) !important;
--urlbar-box-active-bgcolor: var(--toolbarbutton-hover-background) !important;
--toolbar-field-focus-background-color: var(--urlbar-box-bgcolor) !important;
--zen-input-border-color: light-dark(rgb(204, 204, 204), rgb(66, 65, 77));
--urlbar-box-hover-bgcolor: var(--toolbarbutton-hover-background) !important;
--input-bgcolor: light-dark(rgba(255, 255, 255, 0.2), rgba(0, 0, 0, 0.2)) !important;
/* XUL */
--zen-main-browser-background: light-dark(rgb(235, 235, 235), #1b1b1b);
--zen-main-browser-background-toolbar: var(--zen-main-browser-background);
--browser-area-z-index-toolbox: 2 !important;
--attention-dot-color: transparent !important;
--zen-appcontent-border: 1px solid var(--zen-colors-border);
--toolbarbutton-border-radius: 6px;
--urlbar-margin-inline: 1px !important;
--tab-icon-overlay-stroke: light-dark(white, black) !important;
--fp-contextmenu-border-radius: 8px;
--fp-contextmenu-padding: calc(4px - var(--fp-contextmenu-menuitem-border-width)) 0;
--fp-contextmenu-menuitem-border-radius: calc(4px + var(--fp-contextmenu-menuitem-border-width));
--fp-contextmenu-menuitem-padding-block: 6px;
--fp-contextmenu-menuitem-padding-inline: 10px;
--fp-contextmenu-menuitem-border-width: 2px;
--fp-contextmenu-menuicon-margin-inline: 12px;
--fp-contextmenu-menuitem-margin-inline: calc(4px - var(--fp-contextmenu-menuitem-border-width));
--fp-contextmenu-menuitem-margin-block: 0px;
--fp-contextmenu-menuitem-margin: var(--fp-contextmenu-menuitem-margin-block) var(--fp-contextmenu-menuitem-margin-inline);
--fp-contextmenu-separator-vertical: calc(4px - var(--fp-contextmenu-menuitem-border-width));
--fp-contextmenu-separator-horizontal: 0;
--fp-contextmenu-bgcolor: light-dark(Menu, rgb(43 42 51 / 0.95));
--toolbar-bgcolor: transparent;
--tab-close-button-padding: 5px !important;
--toolbarbutton-active-background: var(--zen-toolbar-element-bg);
--input-bgcolor: var(--zen-colors-tertiary) !important;
--input-border-color: var(--zen-input-border-color) !important;
--zen-themed-toolbar-bg: light-dark(rgb(240, 240, 244), #171717);
--zen-themed-toolbar-bg-transparent: light-dark(var(--zen-branding-bg), #171717);
--zen-workspace-indicator-height: 48px;
@media (-moz-windows-mica) or (-moz-platform: macos) {
background: transparent;
--zen-themed-toolbar-bg-transparent: transparent;
@media -moz-pref('zen.widget.windows.acrylic') {
--zen-themed-toolbar-bg-transparent: color-mix(in srgb, var(--zen-themed-toolbar-bg) 35%, transparent 65%);
}
}
@media (-moz-platform: linux) and -moz-pref('zen.widget.linux.transparency') {
background: transparent;
--zen-themed-toolbar-bg-transparent: transparent;
}
--toolbar-field-background-color: var(--zen-colors-input-bg) !important;
--arrowpanel-background: var(--zen-dialog-background) !important;
--zen-big-shadow: 0 0 9.73px 0px light-dark(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.25));
--zen-active-tab-scale: 0.98;
/* Nativity */
--zen-native-content-radius: var(--zen-border-radius);
@media (-moz-platform: linux) {
--zen-native-content-radius: env(-moz-gtk-csd-titlebar-radius);
}
--zen-native-inner-radius: var(
--zen-webview-border-radius,
/* Inner radius calculation:
* 1. If the native radius - the separation is less than 4px, use 4px.
* 2. Otherwise, use the the calculated value (inner radius = outer radius - separation).
*/
max(5px, calc(var(--zen-native-content-radius) - var(--zen-element-separation) / 2))
);
/** Other theme-related styles */
@media (-moz-platform: macos) {
font-family:
SF Pro,
ui-sans-serif,
system-ui,
sans-serif,
Apple Color Emoji,
Segoe UI Emoji,
Segoe UI Symbol,
Noto Color Emoji;
}
}
@media (prefers-color-scheme: dark) {
:host(:is(.anonymous-content-host, notification-message)),
:root {
--zen-in-content-dialog-background: var(--zen-branding-bg);
--zen-dark-color-mix-base: var(--zen-branding-bg);
--zen-colors-primary: color-mix(in srgb, var(--zen-primary-color) 20%, var(--zen-dark-color-mix-base) 80%);
--zen-colors-secondary: color-mix(in srgb, var(--zen-primary-color) 30%, var(--zen-dark-color-mix-base) 70%);
--zen-colors-tertiary: color-mix(in srgb, var(--zen-primary-color) 1%, var(--zen-dark-color-mix-base) 99%);
--zen-colors-hover-bg: color-mix(in srgb, var(--zen-primary-color) 90%, var(--zen-dark-color-mix-base) 10%);
--zen-colors-primary-foreground: color-mix(in srgb, var(--zen-primary-color) 80%, white 20%);
--zen-colors-input-bg: color-mix(in srgb, var(--zen-primary-color) 1%, var(--zen-dark-color-mix-base) 99%);
--zen-colors-border: color-mix(in srgb, var(--zen-colors-secondary) 20%, rgb(79, 79, 79) 80%);
--zen-colors-border-contrast: color-mix(in srgb, var(--zen-colors-secondary) 10%, rgba(255, 255, 255, 0.11) 90%);
--zen-dialog-background: var(--zen-dark-color-mix-base);
--zen-urlbar-background: color-mix(in srgb, var(--zen-primary-color) 4%, rgb(24, 24, 24) 96%);
--zen-browser-gradient-base: color-mix(in srgb, var(--zen-primary-color) 30%, var(--zen-dark-color-mix-base) 70%);
}
}

View File

@@ -1,13 +0,0 @@
/*
* 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/.
*/
#nav-bar,
#zen-sidebar-top-buttons {
background: transparent;
}
:root[inDOMFullscreen='true'] #zen-appcontent-navbar-container {
visibility: collapse;
}

View File

@@ -1,541 +0,0 @@
/*
* 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/.
*/
/* URL and tool bars */
#urlbar-container {
padding-block: 0 !important;
}
#urlbar {
--toolbarbutton-border-radius: 8px;
--urlbarView-separator-color: light-dark(hsl(0, 0%, 90%), hsl(0, 0%, 20%));
--urlbarView-hover-background: var(--toolbarbutton-hover-background);
--urlbarView-highlight-background: var(--toolbarbutton-hover-background);
border-radius: var(--toolbarbutton-border-radius);
padding: 1px;
:root:not([zen-single-toolbar='true']) &[zen-floating-urlbar='true'] {
--urlbar-container-padding: 2px !important;
}
}
.urlbar-input::placeholder {
text-overflow: ellipsis;
}
#searchbar:focus-within {
border-color: transparent !important;
}
#urlbar-background {
background: var(--zen-toolbar-element-bg) !important;
border-radius: var(--border-radius-medium);
outline: none !important;
border: none !important;
margin: 1px;
box-shadow: none !important;
}
#urlbar[focused='true']:not([suppress-focus-border]) > #urlbar-background,
#searchbar:focus-within {
outline: none !important;
outline-offset: none !important;
outline-color: none !important;
}
#urlbar:not([breakout-extend='true']) {
& #urlbar-background {
transition: background-color 0.15s ease;
}
&:hover #urlbar-background {
background-color: light-dark(rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0.2)) !important;
}
}
#identity-box.chromeUI:not([pageproxystate='invalid']) {
& #identity-icon-box {
background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)) !important;
}
& #identity-icon-label {
display: none;
}
}
#identity-permission-box:not(:hover):not(:focus-within) {
background: transparent !important;
}
#searchbar:focus-within {
background: var(--urlbar-box-bgcolor);
}
#identity-box:not(.chromeUI) #identity-icon-label {
padding-inline-start: 8px !important;
}
#urlbar:not([extend='true']) #identity-box #identity-icon-box {
position: relative;
}
#urlbar:not([breakout-extend='true']) #identity-box {
margin-inline-end: 0 !important;
}
.urlbar-page-action,
#tracking-protection-icon-container {
padding: 0 !important;
justify-content: center !important;
align-items: center !important;
margin: 0;
:root[zen-single-toolbar='true'] & {
padding: 6px !important;
width: unset !important;
height: unset !important;
}
}
#tracking-protection-icon-container {
margin: 0 0 0 2px !important;
}
.urlbar-page-action,
#tracking-protection-icon-container {
margin-top: auto !important;
margin-bottom: auto !important;
}
#identity-box:has(#notification-popup-box:not([hidden='true'])) #identity-icon-box,
#identity-box:has(#notification-popup-box:not([hidden='true'])) #identity-permission-box {
margin-right: 0px !important;
}
#identity-box:has(#identity-permission-box:is([hasPermissions], [hasSharingIcon])):not([pageproxystate='invalid'])
#identity-icon-box {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
margin-right: 0 !important;
}
#tracking-protection-icon-container,
#page-action-buttons {
order: 2 !important;
}
#urlbar[breakout] {
position: fixed;
}
#urlbar[breakout-extend='true'] #urlbar-background {
box-shadow: 0px 0px 90px -10px rgba(0, 0, 0, 0.6) !important;
border: 1px solid hsla(0, 0%, 100%, 0.1) !important;
backdrop-filter: none !important;
border-radius: 0.8rem !important;
}
#urlbar-go-button {
display: none;
}
:root[zen-single-toolbar='true'] {
--urlbar-icon-border-radius: 10px !important;
.urlbar-page-action:not([open]):not(#identity-permission-box),
#tracking-protection-icon-container {
display: none;
}
#identity-box:not([pageproxystate='invalid']) #identity-icon-box:not([open]) {
margin-inline-start: calc(-8px - 2 * var(--urlbar-icon-padding));
transform: translateX(100%);
transition: all 0.1s ease;
opacity: 0;
}
#identity-permission-box > *:not(#permissions-granted-icon) {
visibility: collapse;
}
#identity-icon-label {
display: none;
}
#urlbar[open]
:is(#tracking-protection-icon-container, .urlbar-page-action, .identity-box-button):not([hidden='true']):not(
#identity-permission-box
),
#urlbar:hover #identity-icon-box {
opacity: 1 !important;
margin-inline-start: 0 !important;
transform: none !important;
display: flex;
#urlbar:not(:hover) & {
transition: 0;
}
}
#urlbar:not([open]) #userContext-icons {
margin-inline: 0;
}
#urlbar:not([open]) {
#identity-box:not([pageproxystate='invalid']) {
order: 9;
}
}
#notification-popup-box:not([open]) {
margin-inline-start: calc(-10px - 2 * var(--urlbar-icon-padding));
opacity: 0;
transition: all 0.2s;
}
#notification-popup-box {
align-items: center;
justify-content: center;
height: unset !important;
background: transparent !important;
& > image {
margin-top: auto;
margin-bottom: auto;
height: 24px; /* double 12px */
width: 24px;
&:hover {
background: var(--toolbarbutton-hover-background);
border-radius: var(--toolbarbutton-border-radius);
overflow: visible;
}
}
}
}
:root:not([zen-single-toolbar='true']) {
#notification-popup-box {
border-radius: 999px;
margin: 0 4px 0 0 !important;
padding: 0 4px;
min-width: calc(var(--urlbar-min-height) - 4px - 4 * var(--urlbar-container-padding)) !important;
height: calc(var(--urlbar-min-height) - 4px - 4 * var(--urlbar-container-padding)) !important;
justify-content: center;
gap: 8px;
& > image {
padding: 0;
margin-top: auto;
margin-bottom: auto;
}
}
}
#urlbar[breakout-extend='true'] #notification-popup-box {
min-width: calc(var(--urlbar-min-height) - 4 * var(--urlbar-container-padding)) !important;
height: calc(var(--urlbar-min-height) - 4 * var(--urlbar-container-padding)) !important;
}
button.popup-notification-dropmarker {
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
}
.panel-footer:has(button.popup-notification-dropmarker:not([hidden='true'])) button.popup-notification-secondary-button {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.searchbar-engine-one-off-item {
max-width: 28px;
min-width: 28px !important;
transition: background 0s;
justify-content: center;
}
#downloadsHistory {
margin-top: 5px;
}
#urlbar-container {
container: urlbar-container / inline-size;
z-index: 1;
}
@container urlbar-container (width < 350px) {
#userContext-icons {
transition: all 0.1s ease;
}
#userContext-label {
display: none;
}
#userContext-indicator {
margin-inline-end: 4px;
}
#urlbar:hover:not([breakout-extend='true']) #userContext-icons {
margin-inline-end: calc(-16px - 2 * var(--urlbar-icon-padding)) !important;
opacity: 0;
pointer-events: none;
}
}
@media (max-width: 550px) {
#urlbar-container {
width: calc(176px + 2 * (24px + 2 * var(--toolbarbutton-inner-padding)));
}
#nav-bar[downloadsbuttonshown] #urlbar-container,
#nav-bar[unifiedextensionsbuttonshown] #urlbar-container {
width: calc(76px + 24px + 2 * var(--toolbarbutton-inner-padding));
}
#nav-bar[downloadsbuttonshown][unifiedextensionsbuttonshown] #urlbar-container {
width: 176px;
}
#identity-icon-box {
max-width: 70px;
}
#urlbar-zoom-button {
display: none;
}
}
/* Thanks to https://github.com/JLBlk!
* Checkout https://github.com/JLBlk/Zen-Themes/blob/main/SuperUrlBar/SuperUrlBar.css
*/
#notification-popup-box {
border-radius: 10px !important;
}
/* Border radius on hover */
#urlbar .urlbar-page-action,
#urlbar #tracking-protection-icon-container,
#urlbar:not([breakout-extend='true']) #identity-box:is(:not(.chromeUI), [pageproxystate='invalid']) #identity-icon-box {
border-radius: var(--urlbar-icon-border-radius) !important;
}
/* Extensions or similar */
#urlbar:not([breakout-extend='true']) #identity-box.chromeUI:not([pageproxystate='invalid']) #identity-icon-box {
border-radius: 10px !important;
}
/* Notification Stack */
.notificationbox-stack {
background: transparent;
&[notificationside='top'] {
position: fixed;
bottom: calc(var(--zen-element-separation) * 1.5);
right: calc(var(--zen-element-separation) * 1.5);
width: fit-content;
max-width: 30rem !important;
& notification-message {
background: color-mix(in srgb, var(--zen-colors-tertiary) 70%, transparent 30%);
backdrop-filter: blur(10px);
border: 1px solid var(--arrowpanel-border-color);
border-radius: var(--zen-border-radius);
&::before {
display: none;
}
}
}
}
#nav-bar,
#zen-sidebar-top-buttons {
min-height: var(--zen-toolbar-height) !important;
height: var(--zen-toolbar-height) !important;
max-height: var(--zen-toolbar-height) !important;
display: flex;
align-items: center;
}
:root:not([zen-single-toolbar='true']) {
&[zen-right-side='true']:not([zen-window-buttons-reversed='true']) #nav-bar {
margin-inline-start: var(--zen-element-separation);
}
&:not([zen-right-side='true'])[zen-window-buttons-reversed='true'] #nav-bar {
margin-inline-end: var(--zen-element-separation);
}
}
/* Other small tweaks */
#nav-bar-customization-target {
/* Don't grow if potentially-user-sized elements (like the searchbar or the
* bookmarks toolbar item list) are too wide. This forces them to flex to the
* available space as much as possible, see bug 1795260. */
min-width: 0;
--toolbarbutton-inner-padding: var(--zen-toolbar-button-inner-padding);
/* Add space to beginning of toolbar and make that space click the first <toolbarbutton> */
> :is(toolbarbutton, toolbaritem):first-child,
> toolbarpaletteitem:first-child > :is(toolbarbutton, toolbaritem) {
padding-inline-start: 0;
}
}
/* TODO: Fix this for windows and macos */
/*.titlebar-button:last-child {
padding-right: var(--zen-element-separation) !important;
}*/
#urlbar {
& .search-panel-one-offs-header {
display: none;
}
& .search-panel-one-offs-container .searchbar-engine-one-off-item {
box-shadow: none;
}
}
#urlbar[open] {
z-index: 2;
--urlbar-margin-inline: 5px !important;
& #identity-box {
margin-right: var(--urlbar-margin-inline);
}
& #urlbar-background {
/* We cant have a transparent background with a backdrop-filter because on normal websites,
the backdrop woudn't work, we would need to apply a clip-path to the site and that's not recommended
due to performance issues */
background-color: light-dark(
hsl(0, 0%, 100%),
color-mix(in srgb, hsl(0, 0%, 5%) 90%, var(--zen-colors-primary) 10%)
) !important;
@media (-prefers-color-scheme: dark) {
outline: 1px solid rgba(0, 0, 0, 0.3) !important;
}
}
}
:root[zen-single-toolbar='true'] {
#urlbar[open] {
min-width: min(90%, 40rem);
}
&[zen-right-side='true'] #urlbar[open]:not([zen-floating-urlbar='true']) {
right: 0;
}
}
#urlbar[open][zen-floating-urlbar='true'] {
z-index: 1000;
max-width: unset;
min-width: unset !important;
--urlbar-container-height: 55px !important;
--urlbar-margin-inline: 10px !important;
width: min(90%, 50rem) !important;
font-size: 1.15em !important;
@media (-moz-platform: macos) {
font-size: 1.5em !important;
width: min(90%, 60rem) !important;
}
top: 25vh !important;
transform: translateX(-50%);
left: 50% !important;
#urlbar-container:has(&) {
border-radius: 10px;
background: var(--toolbarbutton-hover-background);
}
}
@media not -moz-pref('zen.urlbar.show-protections-icon') {
#tracking-protection-icon-container {
display: none !important;
}
}
/* Code ~~stolen~~ taken inspiration from https://github.com/greeeen-dev/zen-arc-cmd-bar
*
* MIT License
*
* Copyright (c) 2024 green.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
**/
.urlbarView-title,
.urlbarView-title-separator,
.urlbarView-action,
.urlbarView-url {
margin-top: auto !important;
margin-bottom: auto !important;
}
.urlbarView-title {
font-size: 14px !important;
font-weight: 500 !important;
}
.urlbarView-url,
.urlbarView-title-separator::before {
font-size: 14px !important;
font-weight: 500 !important;
color: #aaa !important;
}
.urlbarView-favicon {
margin-left: 0 !important;
margin-right: 12px !important;
padding: 6px !important;
border-radius: 6px !important;
}
.urlbarView-row[has-action]:is([type='switchtab'], [type='remotetab'], [type='clipboard']) .urlbarView-action {
margin-left: auto !important;
margin-right: 0 !important;
}
.urlbarView-row {
.urlbarView-favicon {
transition: background-color 0.05s;
}
&:hover {
.urlbarView-favicon,
& {
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse) 5%, transparent 95%) !important;
}
.urlbarView-url,
.urlbarView-title-separator::before {
color: color-mix(in srgb, var(--zen-colors-primary) 30%, lightgray) !important;
}
}
}

View File

@@ -1,261 +0,0 @@
#zen-welcome,
#zen-welcome-start,
#zen-welcome-pages {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
-moz-window-dragging: drag;
}
:root[zen-welcome-stage] #zen-sidebar-splitter {
display: none;
}
#zen-welcome-start {
flex-direction: column;
-moz-window-dragging: drag;
--zen-primary-color: light-dark(black, white);
#zen-welcome-start-button {
opacity: 0;
list-style-image: url(chrome://browser/skin/zen-icons/forward.svg);
position: absolute;
bottom: 10%;
}
#zen-welcome-title {
text-align: center;
font-size: 5rem;
line-height: 1.1;
max-width: 50%;
font-weight: 500;
white-space: nowrap;
& > span {
display: block;
opacity: 0;
}
}
}
#zen-welcome-pages {
-moz-window-dragging: no-drag;
opacity: 0;
justify-content: start;
align-items: start;
display: none;
background: var(--zen-branding-bg);
border-radius: 1em;
overflow: hidden;
position: relative;
width: 60%;
height: 60%;
box-shadow: var(--zen-big-shadow);
overflow: hidden;
/* Small screens */
@media (max-width: 1400px) {
width: 80%;
height: 80%;
}
@media (max-width: 1200px) {
width: 90%;
height: 90%;
}
@media (max-width: 1000px) {
width: 100%;
height: 100%;
border-radius: 0px;
}
#zen-welcome-page-sidebar {
flex-direction: column;
justify-content: space-between;
padding: 3.8rem;
width: 40%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
& #zen-welcome-heart {
width: 100%;
height: 100%;
opacity: 0;
color: currentColor;
fill: currentColor;
-moz-context-properties: fill, fill-opacity;
background-image: url(chrome://browser/skin/zen-icons/essential-add.svg);
background-size: 15%;
background-repeat: no-repeat;
background-position: center;
}
&[animate-heart] {
overflow: hidden;
}
}
#zen-welcome-page-sidebar-buttons {
flex-direction: column;
gap: 10px;
}
#zen-welcome-page-sidebar-content {
& h1 {
font-size: xx-large;
font-weight: 600;
margin-bottom: 1rem;
}
& p {
margin: 0 0 1.1rem 0;
color: light-dark(rgba(0, 0, 0, 0.6), rgba(255, 255, 255, 0.6));
}
& > * {
transform: translate(300%);
}
}
& button {
justify-content: center;
align-items: center;
transform: translate(300%);
}
#zen-welcome-page-content {
background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1));
width: 60%;
max-width: 80rem;
height: 100%;
position: relative;
overflow: hidden;
justify-content: center;
align-items: center;
display: flex;
flex-direction: column;
gap: 1.6rem;
& label {
opacity: 0;
transition:
scale 0.1s,
box-shadow 0.1s;
padding: 1.5rem 1.1rem;
border-radius: 0.6rem;
width: 50%;
gap: 0.8rem;
display: flex;
border: 2px solid var(--zen-colors-border);
background: light-dark(rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0.4));
align-items: center;
&:hover {
box-shadow: var(--zen-big-shadow);
}
&:has(:checked) {
box-shadow: var(--zen-big-shadow);
border: 2px solid var(--zen-primary-color);
scale: 1.03;
}
}
#zen-welcome-workspace-colors-anchor {
width: 1px;
height: 1px;
}
#zen-welcome-initial-essentials-browser {
width: 70%;
height: 80%;
display: flex;
margin-left: auto;
margin-top: auto;
border-top-left-radius: 1.2em;
box-shadow: var(--zen-big-shadow);
background: light-dark(rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.2));
padding-right: 20%;
overflow: hidden;
opacity: 0;
border: 1px solid var(--zen-colors-border);
border-bottom-width: 0;
border-right-width: 0;
#zen-welcome-initial-essentials-browser-sidebar {
width: 100%;
padding: 1.4rem;
gap: 1.2rem;
background: light-dark(rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0.4));
#zen-welcome-initial-essentials-browser-sidebar-win-buttons {
gap: 0.5rem;
align-items: center;
& > div {
width: 15px;
height: 15px;
border-radius: 50%;
background: var(--zen-toolbar-element-bg);
}
}
#zen-welcome-initial-essentials-browser-sidebar-essentials {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.4rem 1.2rem;
visibility: visible;
& * {
visibility: visible;
}
& .extra-tab {
width: 100%;
height: 3rem;
border-radius: 0.8rem;
margin-top: 0.5rem;
background: var(--zen-toolbar-element-bg);
grid-column: span 3;
}
& .tabbrowser-tab {
--tab-min-height: 5rem !important;
min-width: 5rem !important;
transition: transform 0.1s;
position: relative;
&::after {
position: absolute;
content: '';
width: 1.6rem;
height: 1.6rem;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--zen-tab-icon);
background-position: center;
background-size: cover;
background-repeat: no-repeat;
}
--border-radius-medium: 1rem;
&[visuallyselected] {
transform: scale(1.04);
}
& .tab-background::after {
filter: blur(30px) brightness(1.2);
}
}
}
}
}
}
}

View File

@@ -1,494 +0,0 @@
/*
* 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/.
*/
@namespace html 'http://www.w3.org/1999/xhtml';
#zen-workspaces-button {
justify-content: center;
align-items: center;
display: flex;
font-size: x-small;
position: relative;
&[dont-show='true'] {
display: none !important;
}
--toolbarbutton-hover-background: transparent !important;
border-radius: var(--zen-button-border-radius) !important;
background: transparent;
appearance: unset !important;
height: fit-content;
gap: 3px;
container-type: inline-size;
width: 100%;
& toolbarbutton {
margin: 0;
width: 25px;
display: flex;
justify-content: center;
padding: 0 !important;
align-items: center;
position: relative;
& .zen-workspace-icon[no-icon='true'] {
width: 6px;
height: 6px;
background: light-dark(rgba(0, 0, 0, 0.4), rgba(255, 255, 255, 0.4));
border-radius: 50%;
}
filter: grayscale(1);
opacity: 0.5;
transition:
filter 0.2s,
opacity 0.2s,
width 0.1s;
&[active='true'],
&:hover {
filter: grayscale(0);
opacity: 1;
}
&:hover {
background-color: var(--zen-toolbar-element-bg);
}
}
&[overflow] {
gap: 0 !important;
& toolbarbutton {
margin: 0;
}
& toolbarbutton:not([active='true']),
&:has(toolbarbutton:hover) toolbarbutton[active='true'] {
&:not(:hover) {
width: min(var(--zen-overflowed-workspace-button-width), 25px);
&::after {
content: '';
position: absolute;
width: 4px;
border-radius: 99px;
height: 4px;
background: color-mix(
in srgb,
var(--zen-primary-color) 10%,
light-dark(rgba(0, 0, 0, 0.4), rgba(255, 255, 255, 0.4)) 90%
);
left: 50%;
top: 50%;
filter: saturate(140%) brightness(110%) !important;
transform: translate(-50%, -50%);
}
& .zen-workspace-icon {
display: none;
}
}
}
}
}
#zen-workspaces-button .zen-workspace-sidebar-wrapper {
position: absolute;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
pointer-events: none;
}
#zen-workspaces-button .zen-workspace-sidebar-name {
margin-left: 0.2rem;
display: none;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 13px;
}
/** Keep these selectors in sync with the ones in vertical-tabs.css */
#navigator-toolbox {
& #zen-workspaces-button .zen-workspace-sidebar-name {
display: block;
}
& #zen-workspaces-button .zen-workspace-sidebar-icon {
margin-inline-end: 5px;
}
& #zen-workspaces-button {
overflow: hidden;
display: flex;
gap: 0.5ch;
overflow-x: auto;
scrollbar-width: none;
scroll-behavior: smooth;
}
}
/* Workspaces Panel UI */
#PanelUI-zen-workspaces {
--panel-width: 320px;
--panel-padding: 0;
}
#PanelUI-zen-workspaces > panelmultiview {
align-items: flex-start;
overflow-x: hidden;
overflow-y: auto;
}
#PanelUI-zen-workspaces panelmultiview panelview {
position: relative;
padding: 10px;
width: var(--panel-width);
}
#PanelUI-zen-workspaces-icon-picker toolbarbutton {
width: 30px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
border: 2px solid transparent;
border-radius: 7px;
}
#PanelUI-zen-workspaces-icon-picker toolbarbutton[selected='true'] {
border-color: var(--zen-colors-secondary);
}
#PanelUI-zen-workspaces-icon-picker toolbarbutton .toolbarbutton-icon {
display: none;
}
#PanelUI-zen-workspaces-icon-picker toolbarbutton .toolbarbutton-text {
min-width: unset;
}
#PanelUI-zen-workspaces-icon-picker {
padding: 5px !important;
}
#PanelUI-zen-workspaces-icon-picker-wrapper {
overflow-x: hidden;
justify-items: center;
overflow-y: auto;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
max-height: 250px;
.workspace-icon-button {
min-width: 24px;
min-height: 24px;
font-size: 16px;
margin: 2px;
padding: 4px;
}
}
#PanelUI-zen-workspaces-list {
display: flex;
flex-direction: column;
flex-shrink: 0;
}
#PanelUI-zen-workspaces-list[empty='true'] {
font-weight: 600;
padding: 10px;
width: 100%;
text-align: start;
opacity: 0.5;
}
.PanelUI-zen-workspaces-user-create {
height: 100%;
.PanelUI-zen-workspaces-creation-wraper {
border-radius: 5px;
border: 1px solid var(--zen-colors-border);
margin-top: 10px;
& .PanelUI-zen-workspaces-icons-container {
padding: 10px 0;
min-width: 40px;
display: flex;
align-items: center;
justify-content: center;
border-right: 1px solid var(--zen-colors-border);
margin-right: 2px;
}
& html|input {
border: none;
outline: none !important;
width: 100%;
}
}
}
/* Workspace icon picker styles */
#PanelUI-zen-workspaces-icon-picker-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 5px;
}
#PanelUI-zen-workspaces-icon-search-bar {
display: flex;
position: sticky;
top: 0;
background-color: inherit;
z-index: 1000;
padding: 8px;
width: 100%;
margin: 0;
box-sizing: border-box;
}
#PanelUI-zen-workspaces-icon-search-input {
width: 100%;
padding: 8px 12px;
font-size: 14px;
border: 1px solid var(--panel-separator-color, #ccc);
border-radius: 4px;
box-sizing: border-box;
margin: 0;
}
#PanelUI-zen-workspaces-list toolbarbutton {
padding: 5px;
border-radius: var(--zen-button-border-radius);
margin-left: 0 !important;
margin-right: 0 !important;
display: flex;
align-items: center;
position: relative;
&:first-child {
margin-top: 10px;
}
& .zen-workspace-icon {
position: relative;
width: 30px;
height: 30px;
border-radius: var(--zen-button-border-radius);
margin-right: 10px;
border: 1px solid var(--zen-colors-border);
display: flex;
justify-content: center;
align-items: center;
font-weight: 600;
flex-shrink: 0;
}
&[data-usercontextid] .zen-workspace-icon {
border-color: color-mix(in srgb, var(--identity-tab-color) 40%, transparent 60%);
}
& > vbox:has(> .zen-workspace-name) {
overflow: hidden;
}
& .zen-workspace-name {
font-weight: 600;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
& .zen-workspace-container {
font-size: 12px;
opacity: 0.5;
font-weight: normal;
}
& .zen-workspace-actions,
.zen-workspace-actions-reorder-icon {
display: none;
margin: 0;
margin-left: auto !important;
}
&.zen-workspace-button[active='true'] {
position: relative;
}
&.zen-workspace-button[active='true'] .zen-workspace-icon::before {
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
left: -2px;
width: 2px;
height: 16px;
background-color: var(--toolbarbutton-icon-fill-attention);
border-radius: 5px;
}
}
.zen-workspace-button.dragging {
opacity: 0.5;
}
.zen-workspace-button.dragover::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 2px;
background-color: var(--toolbarbutton-icon-fill-attention);
}
.zen-workspace-last-place-drop-target.dragover {
background-color: var(--toolbarbutton-icon-fill-attention);
}
#PanelUI-zen-workspaces-reorder-mode[active='true'] {
color: var(--toolbarbutton-icon-fill-attention) !important;
}
#PanelUI-zen-workspaces-list:not([reorder-mode='true']) toolbarbutton {
&:hover .zen-workspace-actions,
& .zen-workspace-actions[active='true'] {
display: flex;
}
}
#PanelUI-zen-workspaces-list[reorder-mode='true'] toolbarbutton {
.zen-workspace-actions-reorder-icon {
display: flex;
}
}
#PanelUI-zen-workspaces-list[reorder-mode='true'] .zen-workspace-last-place-drop-target {
display: block;
}
.zen-workspace-last-place-drop-target {
display: none;
height: 4px;
width: 100%;
border-radius: 5px;
}
#PanelUI-zen-workspaces-view > vbox:nth-child(2) {
margin-top: 10px;
}
#PanelUI-zen-workspaces-new,
#PanelUI-zen-workspaces-reorder-mode,
#PanelUI-zen-gradient-generator-color-custom-add {
min-height: 1px !important;
padding: 3px;
border-radius: 4px;
width: 24px;
height: 24px;
}
#PanelUI-zen-workspaces-create-footer,
#PanelUI-zen-workspaces-edit-footer {
padding-bottom: 0 !important;
margin-top: 10px;
margin-left: 0;
margin-bottom: 0 !important;
width: 100%;
}
#PanelUI-zen-workspaces-create-footer button[default='true'],
#PanelUI-zen-workspaces-edit-footer button[default='true'] {
width: 100%;
}
#PanelUI-zen-workspaces-header {
margin-right: auto;
}
/* Mark workspaces indicator */
#zen-current-workspace-indicator-container {
position: relative;
margin-bottom: var(--zen-workspace-indicator-height);
}
.zen-current-workspace-indicator {
padding: calc(15px + var(--zen-toolbox-padding)) calc(4px + var(--tab-inline-padding));
font-weight: 600;
position: absolute;
max-height: var(--zen-workspace-indicator-height);
min-height: var(--zen-workspace-indicator-height);
gap: var(--tab-icon-end-margin);
align-items: center;
flex-direction: row !important;
max-width: 100%;
width: 100%;
&::before {
border-radius: var(--border-radius-medium);
background: transparent;
transition: background 0.1s;
pointer-events: none;
content: '';
position: absolute;
top: var(--zen-toolbox-padding);
left: calc(var(--zen-toolbox-padding) / 2);
width: calc(100% - var(--zen-toolbox-padding));
height: calc(100% - var(--zen-toolbox-padding) * 2);
}
&:hover,
&[open='true'] {
&::before {
background: var(--tab-hover-background-color);
}
}
& .zen-current-workspace-indicator-icon {
font-size: 12px;
line-height: 1;
}
.zen-current-workspace-indicator-name {
opacity: 0.5;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
pointer-events: none;
font-size: small;
}
}
@media not -moz-pref('zen.workspaces.show-workspace-indicator') {
#zen-current-workspace-indicator-container {
display: none !important;
}
}
#zen-current-workspace-indicator-container[hidden='true'] {
display: none !important;
}

View File

@@ -1,2 +1,2 @@
#include zen-splitview-overlay.inc.xhtml
#include zen-glance.inc.xhtml
#include ../../../zen/split-view/zen-splitview-overlay.inc.xhtml
#include ../../../zen/glance/zen-glance.inc.xhtml

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,99 +0,0 @@
/* 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/. */
'use strict';
/* INCLUDE THIS FILE AS:
* <script src="chrome://browser/content/zenThemeModifier.js"></script>
*
* FOR ANY WEBSITE THAT WOULD NEED TO USE THE ACCENT COLOR, ETC
*/
const kZenThemePrefsList = ['zen.theme.accent-color', 'zen.theme.border-radius', 'zen.theme.content-element-separation'];
const kZenMaxElementSeparation = 12;
/**
* ZenThemeModifier controls the application of theme data to the browser,
* for examplem, it injects the accent color to the document. This is used
* because we need a way to apply the accent color without having to worry about
* shadow roots not inheriting the accent color.
*
* note: It must be a firefox builtin page with access to the browser's configuration
* and services.
*/
var ZenThemeModifier = {
_inMainBrowserWindow: false,
/**
* Listen for theming updates from the LightweightThemeChild actor, and
* begin listening to changes in preferred color scheme.
*/
init() {
this._inMainBrowserWindow = window.location.href == 'chrome://browser/content/browser.xhtml';
this.listenForEvents();
this.updateAllThemeBasics();
},
listenForEvents() {
var handleEvent = this.handleEvent.bind(this);
// Listen for changes in the accent color and border radius
for (let pref of kZenThemePrefsList) {
Services.prefs.addObserver(pref, handleEvent);
}
window.addEventListener('unload', () => {
for (let pref of kZenThemePrefsList) {
Services.prefs.removeObserver(pref, handleEvent);
}
});
},
handleEvent(event) {
// note: even might be undefined, but we shoudnt use it!
this.updateAllThemeBasics();
},
/**
* Update all theme basics, like the accent color.
*/
async updateAllThemeBasics() {
this.updateAccentColor();
this.updateBorderRadius();
this.updateElementSeparation();
},
updateBorderRadius() {
const borderRadius = Services.prefs.getIntPref('zen.theme.border-radius');
document.documentElement.style.setProperty('--zen-border-radius', borderRadius + 'px');
},
updateElementSeparation() {
let separation = this.elementSeparation;
document.documentElement.style.setProperty('--zen-element-separation', separation + 'px');
if (separation == 0) {
document.documentElement.setAttribute('zen-no-padding', true);
} else {
document.documentElement.removeAttribute('zen-no-padding');
}
},
get elementSeparation() {
return Math.min(Services.prefs.getIntPref('zen.theme.content-element-separation'), kZenMaxElementSeparation);
},
/**
* Update the accent color.
*/
updateAccentColor() {
const accentColor = Services.prefs.getStringPref('zen.theme.accent-color');
document.documentElement.style.setProperty('--zen-primary-color', accentColor);
// Notify the page that the accent color has changed, only if a function
// handler is defined.
if (typeof window.zenPageAccentColorChanged === 'function') {
window.zenPageAccentColorChanged(accentColor);
}
},
};
if (typeof Services !== 'undefined') ZenThemeModifier.init();

View File

@@ -1,5 +1,5 @@
diff --git a/browser/base/moz.build b/browser/base/moz.build
index 636e0841786735a63ddea00e819c0b6f0b8a5d4d..6685d857180944d68bf4f049919f81361084c481 100644
index 636e0841786735a63ddea00e819c0b6f0b8a5d4d..62e4531bd19caf098fd76c69b213fa32d62546a6 100644
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -87,3 +87,5 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("windows", "gtk"):
@@ -7,4 +7,4 @@ index 636e0841786735a63ddea00e819c0b6f0b8a5d4d..6685d857180944d68bf4f049919f8136
JAR_MANIFESTS += ["jar.mn"]
+
+DIRS += ["zen-components"]
+DIRS += ["../../zen"]

View File

@@ -1,18 +0,0 @@
// Utility to register JSWindowActors
var gZenActorsManager = {
_actors: new Set(),
addJSWindowActor(...args) {
if (this._actors.has(args[0])) {
// Actor already registered, nothing to do
return;
}
try {
ChromeUtils.registerWindowActor(...args);
this._actors.add(args[0]);
} catch (e) {
console.warn(`Failed to register JSWindowActor: ${e}`);
}
},
};

View File

@@ -1,98 +0,0 @@
var gZenOperatingSystemCommonUtils = {
kZenOSToSmallName: {
WINNT: 'windows',
Darwin: 'macos',
Linux: 'linux',
},
get currentOperatingSystem() {
let os = Services.appinfo.OS;
return this.kZenOSToSmallName[os];
},
};
class ZenMultiWindowFeature {
constructor() {}
static get browsers() {
return Services.wm.getEnumerator('navigator:browser');
}
static get currentBrowser() {
return Services.wm.getMostRecentWindow('navigator:browser');
}
static get isActiveWindow() {
return ZenMultiWindowFeature.currentBrowser === window;
}
windowIsActive(browser) {
return browser === ZenMultiWindowFeature.currentBrowser;
}
async foreachWindowAsActive(callback) {
if (!ZenMultiWindowFeature.isActiveWindow) {
return;
}
for (const browser of ZenMultiWindowFeature.browsers) {
try {
await callback(browser);
} catch (e) {
console.error(e);
}
}
}
}
class ZenDOMOperatedFeature {
constructor() {
var initBound = this.init.bind(this);
document.addEventListener('DOMContentLoaded', initBound, { once: true });
}
}
class ZenPreloadedFeature {
constructor() {
var initBound = this.init.bind(this);
document.addEventListener('MozBeforeInitialXULLayout', initBound, { once: true });
}
}
var gZenCommonActions = {
copyCurrentURLToClipboard() {
const currentUrl = gBrowser.currentURI.spec;
if (currentUrl) {
let str = Cc['@mozilla.org/supports-string;1'].createInstance(Ci.nsISupportsString);
str.data = currentUrl;
let transferable = Cc['@mozilla.org/widget/transferable;1'].createInstance(Ci.nsITransferable);
transferable.init(getLoadContext());
transferable.addDataFlavor('text/plain');
transferable.setTransferData('text/plain', str);
Services.clipboard.setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard);
gZenUIManager.showToast('zen-copy-current-url-confirmation');
}
},
copyCurrentURLAsMarkdownToClipboard() {
const currentUrl = gBrowser.currentURI.spec;
const tabTitle = gBrowser.selectedTab.label;
if (currentUrl && tabTitle) {
const markdownLink = `[${tabTitle}](${currentUrl})`;
let str = Cc['@mozilla.org/supports-string;1'].createInstance(Ci.nsISupportsString);
str.data = markdownLink;
let transferable = Cc['@mozilla.org/widget/transferable;1'].createInstance(Ci.nsITransferable);
transferable.init(getLoadContext());
transferable.addDataFlavor('text/plain');
transferable.setTransferData('text/plain', str);
Services.clipboard.setData(transferable, null, Ci.nsIClipboard.kGlobalClipboard);
gZenUIManager.showToast('zen-copy-current-url-confirmation');
}
},
throttle(f, delay) {
let timer = 0;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => f.apply(this, args), delay);
};
},
};

View File

@@ -1,517 +0,0 @@
const lazyCompactMode = {};
XPCOMUtils.defineLazyPreferenceGetter(
lazyCompactMode,
'COMPACT_MODE_FLASH_DURATION',
'zen.view.compact.toolbar-flash-popup.duration',
800
);
XPCOMUtils.defineLazyPreferenceGetter(
lazyCompactMode,
'COMPACT_MODE_FLASH_ENABLED',
'zen.view.compact.toolbar-flash-popup',
true
);
XPCOMUtils.defineLazyPreferenceGetter(
lazyCompactMode,
'COMPACT_MODE_CAN_ANIMATE_SIDEBAR',
'zen.view.compact.animate-sidebar',
true
);
ChromeUtils.defineLazyGetter(lazyCompactMode, 'mainAppWrapper', () => document.getElementById('zen-main-app-wrapper'));
var gZenCompactModeManager = {
_flashTimeouts: {},
_evenListeners: [],
_removeHoverFrames: {},
preInit() {
// Remove it before initializing so we can properly calculate the width
// of the sidebar at startup and avoid overflowing items not being hidden
const isCompactMode = lazyCompactMode.mainAppWrapper.getAttribute('zen-compact-mode') === 'true';
lazyCompactMode.mainAppWrapper.removeAttribute('zen-compact-mode');
this._wasInCompactMode = isCompactMode;
this.addMouseActions();
this.addContextMenu();
},
init() {
Services.prefs.addObserver('zen.tabs.vertical.right-side', this._updateSidebarIsOnRight.bind(this));
gZenUIManager.addPopupTrackingAttribute(this.sidebar);
gZenUIManager.addPopupTrackingAttribute(document.getElementById('zen-appcontent-navbar-container'));
// Clear hover states when window state changes (minimize, maximize, etc.)
window.addEventListener('sizemodechange', () => this._clearAllHoverStates());
if (AppConstants.platform == 'macosx') {
window.addEventListener('mouseover', (event) => {
const buttons = gZenVerticalTabsManager.actualWindowButtons;
if (event.target.closest('.titlebar-buttonbox-container') === buttons) return;
buttons.removeAttribute('zen-has-hover');
});
}
this.preference = this._wasInCompactMode;
},
get preference() {
return lazyCompactMode.mainAppWrapper.getAttribute('zen-compact-mode') === 'true';
},
set preference(value) {
if (this.preference === value || document.documentElement.hasAttribute('zen-compact-animating')) {
if (typeof this._wasInCompactMode !== 'undefined') {
// We wont do anything with it anyway, so we remove it
delete this._wasInCompactMode;
}
// We dont want the user to be able to spam the button
return value;
}
// We use this element in order to make it persis across restarts, by using the XULStore.
// main-window can't store attributes other than window sizes, so we use this instead
lazyCompactMode.mainAppWrapper.setAttribute('zen-compact-mode', value);
document.documentElement.setAttribute('zen-compact-mode', value);
this._updateEvent();
return value;
},
get sidebarIsOnRight() {
if (typeof this._sidebarIsOnRight !== 'undefined') {
return this._sidebarIsOnRight;
}
this._sidebarIsOnRight = Services.prefs.getBoolPref('zen.tabs.vertical.right-side');
return this._sidebarIsOnRight;
},
get sidebar() {
if (!this._sidebar) {
this._sidebar = document.getElementById('navigator-toolbox');
}
return this._sidebar;
},
flashSidebarIfNecessary(aInstant = false) {
// This function is called after exiting DOM fullscreen mode,
// so we do a bit of a hack to re-calculate the URL height
if (aInstant) {
gZenVerticalTabsManager.recalculateURLBarHeight();
}
if (!aInstant && this.preference && lazyCompactMode.COMPACT_MODE_FLASH_ENABLED && !gZenGlanceManager._animating) {
this.flashSidebar();
}
},
addContextMenu() {
const fragment = window.MozXULElement.parseXULToFragment(`
<menu id="zen-context-menu-compact-mode" data-l10n-id="zen-toolbar-context-compact-mode">
<menupopup>
<menuitem id="zen-context-menu-compact-mode-toggle" data-l10n-id="zen-toolbar-context-compact-mode-enable" type="checkbox" oncommand="gZenCompactModeManager.toggle();"/>
<menuseparator/>
<menuitem id="zen-context-menu-compact-mode-hide-sidebar" data-l10n-id="zen-toolbar-context-compact-mode-just-tabs" type="radio" oncommand="gZenCompactModeManager.hideSidebar();"/>
<menuitem id="zen-context-menu-compact-mode-hide-toolbar" data-l10n-id="zen-toolbar-context-compact-mode-just-toolbar" type="radio" oncommand="gZenCompactModeManager.hideToolbar();"/>
<menuitem id="zen-context-menu-compact-mode-hide-both" data-l10n-id="zen-toolbar-context-compact-mode-hide-both" type="radio" oncommand="gZenCompactModeManager.hideBoth();"/>
</menupopup>
</menu>
`);
document.getElementById('viewToolbarsMenuSeparator').before(fragment);
this.updateContextMenu();
},
updateCompactModeContext(isSingleToolbar) {
const IDs = [
'zen-context-menu-compact-mode-hide-sidebar',
'zen-context-menu-compact-mode-hide-toolbar',
'zen-context-menu-compact-mode-hide-both',
];
for (let id of IDs) {
document.getElementById(id).disabled = isSingleToolbar;
}
},
hideSidebar() {
Services.prefs.setBoolPref('zen.view.compact.hide-tabbar', true);
Services.prefs.setBoolPref('zen.view.compact.hide-toolbar', false);
},
hideToolbar() {
Services.prefs.setBoolPref('zen.view.compact.hide-toolbar', true);
Services.prefs.setBoolPref('zen.view.compact.hide-tabbar', false);
},
hideBoth() {
Services.prefs.setBoolPref('zen.view.compact.hide-tabbar', true);
Services.prefs.setBoolPref('zen.view.compact.hide-toolbar', true);
},
addEventListener(callback) {
this._evenListeners.push(callback);
},
async _updateEvent() {
// IF we are animating IN, call the callbacks first so we can calculate the width
// once the window buttons are shown
this.updateContextMenu();
if (!this.preference) {
this._evenListeners.forEach((callback) => callback());
await this.animateCompactMode();
} else {
await this.animateCompactMode();
this._evenListeners.forEach((callback) => callback());
}
gZenUIManager.updateTabsToolbar();
},
// NOTE: Dont actually use event, it's just so we make sure
// the caller is from the ResizeObserver
getAndApplySidebarWidth(event = undefined) {
if (this._ignoreNextResize) {
this._ignoreNextResize = false;
return;
}
let sidebarWidth = this.sidebar.getBoundingClientRect().width;
if (sidebarWidth > 1) {
gZenUIManager.restoreScrollbarState();
// Second variable to get the genuine width of the sidebar
this.sidebar.style.setProperty('--actual-zen-sidebar-width', `${sidebarWidth}px`);
window.dispatchEvent(new window.Event('resize')); // To recalculate the layout
if (event && this.preference) {
return;
}
this.sidebar.style.setProperty('--zen-sidebar-width', `${sidebarWidth}px`);
}
return sidebarWidth;
},
animateCompactMode() {
return new Promise((resolve) => {
// Get the splitter width before hiding it (we need to hide it before animating on right)
document.documentElement.setAttribute('zen-compact-animating', 'true');
// We need to set the splitter width before hiding it
let splitterWidth = document.getElementById('zen-sidebar-splitter').getBoundingClientRect().width;
const isCompactMode = this.preference;
const canHideSidebar =
Services.prefs.getBoolPref('zen.view.compact.hide-tabbar') || gZenVerticalTabsManager._hasSetSingleToolbar;
let canAnimate =
lazyCompactMode.COMPACT_MODE_CAN_ANIMATE_SIDEBAR &&
!this.sidebar.hasAttribute('zen-user-show') &&
!this.sidebar.hasAttribute('zen-has-empty-tab') &&
!this.sidebar.hasAttribute('zen-has-hover');
if (typeof this._wasInCompactMode !== 'undefined') {
canAnimate = false;
delete this._wasInCompactMode;
}
// Do this so we can get the correct width ONCE compact mode styled have been applied
if (canAnimate) {
this.sidebar.setAttribute('animate', 'true');
}
this.sidebar.style.removeProperty('margin-right');
this.sidebar.style.removeProperty('margin-left');
this.sidebar.style.removeProperty('transform');
window.requestAnimationFrame(() => {
let sidebarWidth = this.getAndApplySidebarWidth();
if (!canAnimate) {
this.sidebar.removeAttribute('animate');
document.documentElement.removeAttribute('zen-compact-animating');
this.getAndApplySidebarWidth({});
this._ignoreNextResize = true;
resolve();
return;
}
if (canHideSidebar && isCompactMode) {
const elementSeparation = ZenThemeModifier.elementSeparation;
if (document.documentElement.hasAttribute('zen-sidebar-expanded')) {
sidebarWidth -= 0.5 * splitterWidth;
if (elementSeparation < splitterWidth) {
// Subtract from the splitter width to end up with the correct element separation
sidebarWidth += 1.5 * splitterWidth - elementSeparation;
}
}
gZenUIManager.motion
.animate(
this.sidebar,
{
marginRight: this.sidebarIsOnRight ? `-${sidebarWidth}px` : 0,
marginLeft: this.sidebarIsOnRight ? 0 : `-${sidebarWidth}px`,
},
{
ease: 'easeIn',
type: 'spring',
bounce: 0,
duration: 0.2,
}
)
.then(() => {
this.sidebar.style.transition = 'none';
this.sidebar.style.opacity = 0;
setTimeout(() => {
this.sidebar.removeAttribute('animate');
document.documentElement.removeAttribute('zen-compact-animating');
this.getAndApplySidebarWidth({});
this._ignoreNextResize = true;
this.sidebar.style.removeProperty('margin-right');
this.sidebar.style.removeProperty('margin-left');
this.sidebar.style.removeProperty('opacity');
this.sidebar.style.removeProperty('transition');
resolve();
}, 0);
});
} else if (canHideSidebar && !isCompactMode) {
document.getElementById('browser').style.overflow = 'clip';
if (this.sidebarIsOnRight) {
this.sidebar.style.marginRight = `-${sidebarWidth}px`;
} else {
this.sidebar.style.marginLeft = `-${sidebarWidth}px`;
}
gZenUIManager.motion
.animate(
this.sidebar,
this.sidebarIsOnRight
? {
marginRight: [`-${sidebarWidth}px`, 0],
transform: ['translateX(100%)', 'translateX(0)'],
}
: { marginLeft: 0 },
{
ease: 'easeOut',
type: 'spring',
bounce: 0,
duration: 0.2,
}
)
.then(() => {
this.sidebar.removeAttribute('animate');
document.getElementById('browser').style.removeProperty('overflow');
this.sidebar.style.transition = 'none';
this.sidebar.style.removeProperty('margin-right');
this.sidebar.style.removeProperty('margin-left');
this.sidebar.style.removeProperty('transform');
document.documentElement.removeAttribute('zen-compact-animating');
setTimeout(() => {
this.sidebar.style.removeProperty('transition');
resolve();
});
});
} else {
this.sidebar.removeAttribute('animate'); // remove the attribute if we are not animating
document.documentElement.removeAttribute('zen-compact-animating');
}
});
});
},
updateContextMenu() {
document.getElementById('zen-context-menu-compact-mode-toggle').setAttribute('checked', this.preference);
const hideTabBar = Services.prefs.getBoolPref('zen.view.compact.hide-tabbar', false);
const hideToolbar = Services.prefs.getBoolPref('zen.view.compact.hide-toolbar', false);
const hideBoth = hideTabBar && hideToolbar;
const idName = 'zen-context-menu-compact-mode-hide-';
document.getElementById(idName + 'sidebar').setAttribute('checked', !hideBoth && hideTabBar);
document.getElementById(idName + 'toolbar').setAttribute('checked', !hideBoth && hideToolbar);
document.getElementById(idName + 'both').setAttribute('checked', hideBoth);
},
_removeOpenStateOnUnifiedExtensions() {
// Fix for bug https://github.com/zen-browser/desktop/issues/1925
const buttons = document.querySelectorAll('toolbarbutton:is(#unified-extensions-button, .webextension-browser-action)');
for (let button of buttons) {
button.removeAttribute('open');
}
},
toggle() {
return (this.preference = !this.preference);
},
_updateSidebarIsOnRight() {
this._sidebarIsOnRight = Services.prefs.getBoolPref('zen.tabs.vertical.right-side');
},
toggleSidebar() {
this.sidebar.toggleAttribute('zen-user-show');
},
get hideAfterHoverDuration() {
if (this._hideAfterHoverDuration) {
return this._hideAfterHoverDuration;
}
return Services.prefs.getIntPref('zen.view.compact.toolbar-hide-after-hover.duration');
},
get hoverableElements() {
if (typeof this._showSidebarAndToolbarOnHover === 'undefined') {
this._showSidebarAndToolbarOnHover = Services.prefs.getBoolPref(
'zen.view.compact.show-sidebar-and-toolbar-on-hover',
true
);
}
return [
...(!this._showSidebarAndToolbarOnHover
? []
: [
{
element: this.sidebar,
screenEdge: this.sidebarIsOnRight ? 'right' : 'left',
keepHoverDuration: 100,
},
{
element: document.getElementById('zen-appcontent-navbar-container'),
screenEdge: 'top',
},
]),
{
element: gZenVerticalTabsManager.actualWindowButtons,
},
];
},
flashSidebar(duration = lazyCompactMode.COMPACT_MODE_FLASH_DURATION) {
let tabPanels = document.getElementById('tabbrowser-tabpanels');
if (!tabPanels.matches("[zen-split-view='true']")) {
this.flashElement(this.sidebar, duration, this.sidebar.id);
}
},
flashElement(element, duration, id, attrName = 'flash-popup') {
//if (element.matches(':hover')) {
// return;
//}
if (this._flashTimeouts[id]) {
clearTimeout(this._flashTimeouts[id]);
} else {
requestAnimationFrame(() => element.setAttribute(attrName, 'true'));
}
this._flashTimeouts[id] = setTimeout(() => {
window.requestAnimationFrame(() => {
element.removeAttribute(attrName);
this._flashTimeouts[id] = null;
});
}, duration);
},
clearFlashTimeout(id) {
clearTimeout(this._flashTimeouts[id]);
this._flashTimeouts[id] = null;
},
addMouseActions() {
for (let i = 0; i < this.hoverableElements.length; i++) {
let target = this.hoverableElements[i].element;
const onEnter = (event) => {
if (event.type === 'mouseenter' && !event.target.matches(':hover')) return;
// Dont register the hover if the urlbar is floating and we are hovering over it
if (event.target.querySelector('#urlbar[zen-floating-urlbar]')) return;
this.clearFlashTimeout('has-hover' + target.id);
window.requestAnimationFrame(() => target.setAttribute('zen-has-hover', 'true'));
};
const onLeave = (event) => {
if (AppConstants.platform == 'macosx') {
const buttonRect = gZenVerticalTabsManager.actualWindowButtons.getBoundingClientRect();
const MAC_WINDOW_BUTTONS_X_BORDER = buttonRect.width + buttonRect.x;
const MAC_WINDOW_BUTTONS_Y_BORDER = buttonRect.height + buttonRect.y;
if (
event.clientX < MAC_WINDOW_BUTTONS_X_BORDER &&
event.clientY < MAC_WINDOW_BUTTONS_Y_BORDER &&
event.clientX > buttonRect.x &&
event.clientY > buttonRect.y
) {
return;
}
}
// If it's a child element but not the target, ignore the event
if (target.contains(event.explicitOriginalTarget) && event.explicitOriginalTarget !== target) {
return;
}
if (this.hoverableElements[i].keepHoverDuration && !event.target.querySelector('#urlbar[zen-floating-urlbar]')) {
this.flashElement(target, this.hoverableElements[i].keepHoverDuration, 'has-hover' + target.id, 'zen-has-hover');
} else {
this._removeHoverFrames[target.id] = window.requestAnimationFrame(() => target.removeAttribute('zen-has-hover'));
}
};
target.addEventListener('mouseenter', onEnter);
target.addEventListener('dragover', onEnter);
target.addEventListener('mouseleave', onLeave);
target.addEventListener('dragleave', onLeave);
}
document.documentElement.addEventListener('mouseleave', (event) => {
const screenEdgeCrossed = this._getCrossedEdge(event.pageX, event.pageY);
if (!screenEdgeCrossed) return;
for (let entry of this.hoverableElements) {
if (screenEdgeCrossed !== entry.screenEdge) continue;
const target = entry.element;
const boundAxis = entry.screenEdge === 'right' || entry.screenEdge === 'left' ? 'y' : 'x';
if (!this._positionInBounds(boundAxis, target, event.pageX, event.pageY, 7)) {
continue;
}
window.cancelAnimationFrame(this._removeHoverFrames[target.id]);
this.flashElement(target, this.hideAfterHoverDuration, 'has-hover' + target.id, 'zen-has-hover');
document.addEventListener(
'mousemove',
() => {
if (target.matches(':hover')) return;
target.removeAttribute('zen-has-hover');
this.clearFlashTimeout('has-hover' + target.id);
},
{ once: true }
);
}
});
},
_getCrossedEdge(posX, posY, element = document.documentElement, maxDistance = 10) {
const targetBox = element.getBoundingClientRect();
posX = Math.max(targetBox.left, Math.min(posX, targetBox.right));
posY = Math.max(targetBox.top, Math.min(posY, targetBox.bottom));
return ['top', 'bottom', 'left', 'right'].find((edge, i) => {
const distance = Math.abs((i < 2 ? posY : posX) - targetBox[edge]);
return distance <= maxDistance;
});
},
_positionInBounds(axis = 'x', element, x, y, error = 0) {
const bBox = element.getBoundingClientRect();
if (axis === 'y') return bBox.top - error < y && y < bBox.bottom + error;
else return bBox.left - error < x && x < bBox.right + error;
},
toggleToolbar() {
let toolbar = document.getElementById('zen-appcontent-navbar-container');
toolbar.toggleAttribute('zen-user-show');
},
_clearAllHoverStates() {
// Clear hover attributes from all hoverable elements
for (let entry of this.hoverableElements) {
const target = entry.element;
if (target && !target.matches(':hover') && target.hasAttribute('zen-has-hover')) {
target.removeAttribute('zen-has-hover');
this.clearFlashTimeout('has-hover' + target.id);
}
}
},
};
document.addEventListener(
'MozBeforeInitialXULLayout',
() => {
gZenCompactModeManager.preInit();
},
{ once: true }
);

File diff suppressed because one or more lines are too long

View File

@@ -1,120 +0,0 @@
{
class ZenFolders {
constructor() {
this.#initEventListeners();
}
#initEventListeners() {
document.addEventListener('TabGrouped', this.#onTabGrouped.bind(this));
document.addEventListener('TabUngrouped', this.#onTabUngrouped.bind(this));
document.addEventListener('TabGroupRemoved', this.#onTabGroupRemoved.bind(this));
document.addEventListener('TabGroupCreate', this.#onTabGroupCreate.bind(this));
document.addEventListener('TabPinned', this.#onTabPinned.bind(this));
document.addEventListener('TabUnpinned', this.#onTabUnpinned.bind(this));
}
#onTabGrouped(event) {
const tab = event.target;
const group = tab.group;
group.pinned = tab.pinned;
if (group.hasAttribute('split-view-group') && group.hasAttribute('zen-pinned-changed')) {
// zen-pinned-changed remove it and set it to had-zen-pinned-changed to keep
// track of the original pinned state
group.removeAttribute('zen-pinned-changed');
group.setAttribute('had-zen-pinned-changed', true);
}
}
#onTabUngrouped(event) {
const tab = event.target;
const group = event.detail;
if (group.hasAttribute('split-view-group') && tab.hasAttribute('had-zen-pinned-changed')) {
tab.setAttribute('zen-pinned-changed', true);
tab.removeAttribute('had-zen-pinned-changed');
}
}
#onTabGroupCreate(event) {
const group = event.target;
const tabs = group.tabs;
if (!group.pinned) {
return;
}
for (const tab of tabs) {
if (tab.hasAttribute('zen-pinned-changed')) {
tab.removeAttribute('zen-pinned-changed');
tab.setAttribute('had-zen-pinned-changed', true);
}
}
}
#onTabGroupRemoved(event) {}
#onTabPinned(event) {
const tab = event.target;
const group = tab.group;
if (group && group.hasAttribute('split-view-group')) {
group.pinned = true;
}
}
#onTabUnpinned(event) {
const tab = event.target;
const group = tab.group;
if (group && group.hasAttribute('split-view-group')) {
group.pinned = false;
}
}
expandGroupTabs(group) {
for (const tab of group.tabs.reverse()) {
gBrowser.ungroupTab(tab);
}
}
handleTabPin(tab) {
const group = tab.group;
if (!group) {
return false;
}
if (group.hasAttribute('split-view-group') && !this._piningFolder) {
this._piningFolder = true;
for (const otherTab of group.tabs) {
if (tab === otherTab) {
continue;
}
gBrowser.pinTab(otherTab);
}
this._piningFolder = false;
gBrowser.verticalPinnedTabsContainer.insertBefore(group, gBrowser.verticalPinnedTabsContainer.lastChild);
gBrowser.tabContainer._invalidateCachedTabs();
return true;
}
return this._piningFolder;
}
handleTabUnpin(tab) {
const group = tab.group;
if (!group) {
return false;
}
if (group.hasAttribute('split-view-group') && !this._piningFolder) {
this._piningFolder = true;
for (const otherTab of group.tabs) {
if (tab === otherTab) {
continue;
}
gBrowser.unpinTab(otherTab);
}
this._piningFolder = false;
ZenWorkspaces.activeWorkspaceStrip.prepend(group);
gBrowser.tabContainer._invalidateCachedTabs();
return true;
}
return this._piningFolder;
}
}
window.gZenFolders = new ZenFolders();
}

View File

@@ -1,674 +0,0 @@
{
class ZenGlanceManager extends ZenDOMOperatedFeature {
_animating = false;
_lazyPref = {};
#glances = new Map();
#currentGlanceID = null;
#confirmationTimeout = null;
init() {
window.addEventListener('TabClose', this.onTabClose.bind(this));
window.addEventListener('TabSelect', this.onLocationChange.bind(this));
XPCOMUtils.defineLazyPreferenceGetter(
this._lazyPref,
'SHOULD_OPEN_EXTERNAL_TABS_IN_GLANCE',
'zen.glance.open-essential-external-links',
false
);
ChromeUtils.defineLazyGetter(this, 'sidebarButtons', () => document.getElementById('zen-glance-sidebar-container'));
document.getElementById('tabbrowser-tabpanels').addEventListener('click', this.onOverlayClick.bind(this));
Services.obs.addObserver(this, 'quit-application-requested');
this.#addSidebarButtonListeners();
}
#addSidebarButtonListeners() {
this.sidebarButtons.addEventListener('command', (event) => {
const button = event.target.closest('toolbarbutton');
if (!button) {
return;
}
switch (button.id) {
case 'zen-glance-sidebar-close':
this.closeGlance({ onTabClose: true });
break;
case 'zen-glance-sidebar-open':
this.fullyOpenGlance();
break;
case 'zen-glance-sidebar-split':
this.splitGlance();
break;
}
});
}
get #currentBrowser() {
return this.#glances.get(this.#currentGlanceID)?.browser;
}
get #currentTab() {
return this.#glances.get(this.#currentGlanceID)?.tab;
}
get #currentParentTab() {
return this.#glances.get(this.#currentGlanceID)?.parentTab;
}
onOverlayClick(event) {
if (event.target === this.overlay && event.originalTarget !== this.contentWrapper) {
this.closeGlance({ onTabClose: true });
}
}
observe(subject, topic) {
switch (topic) {
case 'quit-application-requested':
this.onUnload();
break;
}
}
onUnload() {
// clear everything
for (let [id, glance] of this.#glances) {
gBrowser.removeTab(glance.tab, { animate: false });
}
}
getTabPosition(tab) {
return Math.max(gBrowser.pinnedTabCount, tab._tPos);
}
createBrowserElement(url, currentTab, existingTab = null) {
const newTabOptions = {
userContextId: currentTab.getAttribute('usercontextid') || '',
skipBackgroundNotify: true,
insertTab: true,
skipLoad: false,
index: this.getTabPosition(currentTab) + 1,
};
currentTab._selected = true;
const newUUID = gZenUIManager.generateUuidv4();
const newTab = existingTab ?? gBrowser.addTrustedTab(Services.io.newURI(url).spec, newTabOptions);
if (currentTab.hasAttribute('zenDefaultUserContextId')) {
newTab.setAttribute('zenDefaultUserContextId', true);
}
currentTab.querySelector('.tab-content').appendChild(newTab);
newTab.setAttribute('zen-glance-tab', true);
newTab.setAttribute('glance-id', newUUID);
currentTab.setAttribute('glance-id', newUUID);
this.#glances.set(newUUID, {
tab: newTab,
parentTab: currentTab,
browser: newTab.linkedBrowser,
});
this.#currentGlanceID = newUUID;
gBrowser.selectedTab = newTab;
return this.#currentBrowser;
}
fillOverlay(browser) {
this.overlay = browser.closest('.browserSidebarContainer');
this.browserWrapper = browser.closest('.browserContainer');
this.contentWrapper = browser.closest('.browserStack');
}
showSidebarButtons(animate = false) {
if (this.sidebarButtons.hasAttribute('hidden') && animate) {
gZenUIManager.motion.animate(
this.sidebarButtons.querySelectorAll('toolbarbutton'),
{ x: [50, 0], opacity: [0, 1] },
{ delay: gZenUIManager.motion.stagger(0.1) }
);
}
this.sidebarButtons.removeAttribute('hidden');
}
hideSidebarButtons() {
this.sidebarButtons.setAttribute('hidden', true);
}
openGlance(data, existingTab = null, ownerTab = null) {
if (this.#currentBrowser) {
return;
}
if (gBrowser.selectedTab === this.#currentParentTab) {
gBrowser.selectedTab = this.#currentTab;
return;
}
this.animatingOpen = true;
this._animating = true;
const initialX = data.x;
const initialY = data.y;
const initialWidth = data.width;
const initialHeight = data.height;
this.browserWrapper?.removeAttribute('animate');
this.browserWrapper?.removeAttribute('animate-end');
this.browserWrapper?.removeAttribute('animate-full');
this.browserWrapper?.removeAttribute('has-finished-animation');
this.overlay?.removeAttribute('post-fade-out');
const currentTab = ownerTab ?? gBrowser.selectedTab;
const browserElement = this.createBrowserElement(data.url, currentTab, existingTab);
this.fillOverlay(browserElement);
this.overlay.classList.add('zen-glance-overlay');
this.browserWrapper.removeAttribute('animate-end');
window.requestAnimationFrame(() => {
this.quickOpenGlance({ dontOpenButtons: true });
this.showSidebarButtons(true);
gZenUIManager.motion.animate(
this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer'),
{
scale: [1, 0.98],
backdropFilter: ['blur(0px)', 'blur(5px)'],
opacity: [1, 0.5],
},
{
duration: 0.4,
type: 'spring',
bounce: 0.2,
}
);
this.#currentBrowser.setAttribute('animate-glance-open', true);
this.overlay.removeAttribute('fade-out');
this.browserWrapper.setAttribute('animate', true);
const top = initialY + initialHeight / 2;
const left = initialX + initialWidth / 2;
this.browserWrapper.style.top = `${top}px`;
this.browserWrapper.style.left = `${left}px`;
this.browserWrapper.style.width = `${initialWidth}px`;
this.browserWrapper.style.height = `${initialHeight}px`;
this.browserWrapper.style.opacity = 0.8;
this.#glances.get(this.#currentGlanceID).originalPosition = {
top: this.browserWrapper.style.top,
left: this.browserWrapper.style.left,
width: this.browserWrapper.style.width,
height: this.browserWrapper.style.height,
};
this.browserWrapper.style.transform = 'translate(-50%, -50%)';
this.overlay.style.overflow = 'visible';
gZenUIManager.motion
.animate(
this.browserWrapper,
{
top: '50%',
left: '50%',
width: '85%',
height: '100%',
opacity: 1,
},
{
duration: 0.3,
type: 'spring',
bounce: 0.2,
}
)
.then(() => {
this.#currentBrowser.removeAttribute('animate-glance-open');
this.overlay.style.removeProperty('overflow');
this.browserWrapper.removeAttribute('animate');
this.browserWrapper.setAttribute('animate-end', true);
this.browserWrapper.setAttribute('has-finished-animation', true);
this._animating = false;
this.animatingOpen = false;
});
});
}
_clearContainerStyles(container) {
const inset = container.style.inset;
container.removeAttribute('style');
container.style.inset = inset;
}
closeGlance({
noAnimation = false,
onTabClose = false,
setNewID = null,
isDifferent = false,
hasFocused = false,
skipPermitUnload = false,
} = {}) {
if (this._animating || !this.#currentBrowser || this.animatingOpen || this._duringOpening) {
return;
}
if (!skipPermitUnload) {
let { permitUnload } = this.#currentBrowser.permitUnload();
if (!permitUnload) {
return;
}
}
if (onTabClose && hasFocused && !this.#confirmationTimeout) {
const cancelButton = document.getElementById('zen-glance-sidebar-close');
cancelButton.setAttribute('waitconfirmation', true);
this.#confirmationTimeout = setTimeout(() => {
cancelButton.removeAttribute('waitconfirmation');
this.#confirmationTimeout = null;
}, 3000);
return;
}
this.browserWrapper.removeAttribute('has-finished-animation');
if (noAnimation) {
this._clearContainerStyles(this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer'));
this.quickCloseGlance({ closeCurrentTab: false });
return;
}
this.closingGlance = true;
this._animating = true;
gBrowser._insertTabAtIndex(this.#currentTab, {
index: this.getTabPosition(this.#currentParentTab),
});
let quikcCloseZen = false;
if (onTabClose) {
// Create new tab if no more ex
if (gBrowser.tabs.length === 1) {
BrowserCommands.openTab();
return;
}
}
// do NOT touch here, I don't know what it does, but it works...
this.#currentTab.style.display = 'none';
this.overlay.setAttribute('fade-out', true);
this.overlay.style.pointerEvents = 'none';
this.quickCloseGlance({ justAnimateParent: true, clearID: false });
const originalPosition = this.#glances.get(this.#currentGlanceID).originalPosition;
gZenUIManager.motion
.animate(
this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer'),
{
scale: [0.98, 1],
backdropFilter: ['blur(5px)', 'blur(0px)'],
opacity: [0.5, 1],
},
{
duration: 0.4,
type: 'spring',
bounce: 0.2,
}
)
.then(() => {
this._clearContainerStyles(this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer'));
});
this.browserWrapper.style.opacity = 1;
gZenUIManager.motion
.animate(
this.browserWrapper,
{
...originalPosition,
opacity: 0,
},
{ type: 'spring', bounce: 0, duration: 0.5, easing: 'ease-in' }
)
.then(() => {
this.browserWrapper.removeAttribute('animate');
this.browserWrapper.removeAttribute('animate-end');
if (!this.#currentParentTab) {
return;
}
if (!onTabClose || quikcCloseZen) {
this.quickCloseGlance({ clearID: false });
}
this.overlay.removeAttribute('fade-out');
this.browserWrapper.removeAttribute('animate');
this.lastCurrentTab = this.#currentTab;
this.overlay.classList.remove('zen-glance-overlay');
gBrowser._getSwitcher().setTabStateNoAction(this.lastCurrentTab, gBrowser.AsyncTabSwitcher.STATE_UNLOADED);
if (!onTabClose) {
this.#currentParentTab._visuallySelected = false;
}
// reset everything
this.browserWrapper = null;
this.overlay = null;
this.contentWrapper = null;
this.lastCurrentTab.removeAttribute('zen-glance-tab');
this.lastCurrentTab._closingGlance = true;
if (!isDifferent) {
gBrowser.selectedTab = this.#currentParentTab;
}
this._ignoreClose = true;
gBrowser.removeTab(this.lastCurrentTab, { animate: true, skipPermitUnload: true });
gBrowser.tabContainer._invalidateCachedTabs();
this.#currentParentTab.removeAttribute('glance-id');
this.#glances.delete(this.#currentGlanceID);
this.#currentGlanceID = setNewID;
this.lastCurrentTab = null;
this._duringOpening = false;
this._animating = false;
this.closingGlance = false;
if (this.#currentGlanceID) {
this.quickOpenGlance();
}
});
}
quickOpenGlance({ dontOpenButtons = false } = {}) {
if (!this.#currentBrowser || this._duringOpening) {
return;
}
this._duringOpening = true;
if (!dontOpenButtons) {
this.showSidebarButtons();
}
const parentBrowserContainer = this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer');
parentBrowserContainer.classList.add('zen-glance-background');
parentBrowserContainer.classList.remove('zen-glance-overlay');
parentBrowserContainer.classList.add('deck-selected');
this.#currentParentTab.linkedBrowser.zenModeActive = true;
this.#currentParentTab.linkedBrowser.docShellIsActive = true;
this.#currentBrowser.zenModeActive = true;
this.#currentBrowser.docShellIsActive = true;
this.#currentBrowser.setAttribute('zen-glance-selected', true);
this.fillOverlay(this.#currentBrowser);
this.#currentParentTab._visuallySelected = true;
this.overlay.classList.add('deck-selected');
this.overlay.classList.add('zen-glance-overlay');
this._duringOpening = false;
}
quickCloseGlance({ closeCurrentTab = true, closeParentTab = true, justAnimateParent = false, clearID = true } = {}) {
const parentHasBrowser = !!this.#currentParentTab.linkedBrowser;
this.hideSidebarButtons();
if (parentHasBrowser) {
this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer').classList.remove('zen-glance-background');
}
if (!justAnimateParent && this.overlay) {
if (parentHasBrowser) {
if (closeParentTab) {
this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer').classList.remove('deck-selected');
}
this.#currentParentTab.linkedBrowser.zenModeActive = false;
}
this.#currentBrowser.zenModeActive = false;
if (closeParentTab && parentHasBrowser) {
this.#currentParentTab.linkedBrowser.docShellIsActive = false;
}
if (closeCurrentTab) {
this.#currentBrowser.docShellIsActive = false;
this.overlay.classList.remove('deck-selected');
this.#currentTab._selected = false;
}
if (!this.#currentParentTab._visuallySelected && closeParentTab) {
this.#currentParentTab._visuallySelected = false;
}
this.#currentBrowser.removeAttribute('zen-glance-selected');
this.overlay.classList.remove('zen-glance-overlay');
}
if (clearID) {
this.#currentGlanceID = null;
}
}
onLocationChangeOpenGlance() {
if (!this.animatingOpen) {
this.quickOpenGlance();
}
}
clearConfirmationTimeout() {
if (this.#confirmationTimeout) {
clearTimeout(this.#confirmationTimeout);
this.#confirmationTimeout = null;
}
document.getElementById('zen-glance-sidebar-close')?.removeAttribute('waitconfirmation');
}
// note: must be sync to avoid timing issues
onLocationChange(event) {
const tab = event.target;
this.clearConfirmationTimeout();
if (this.animatingFullOpen || this.closingGlance) {
return;
}
if (this._duringOpening || !tab.hasAttribute('glance-id')) {
if (this.#currentGlanceID && !this._duringOpening) {
this.quickCloseGlance();
}
return;
}
if (this.#currentGlanceID && this.#currentGlanceID !== tab.getAttribute('glance-id')) {
this.quickCloseGlance();
}
this.#currentGlanceID = tab.getAttribute('glance-id');
if (gBrowser.selectedTab === this.#currentParentTab && this.#currentBrowser) {
const curTab = this.#currentTab;
const prevTab = event.detail.previousTab;
setTimeout(() => {
gBrowser.selectedTab = curTab;
if (prevTab?.linkedBrowser) {
prevTab.linkedBrowser.closest('.browserSidebarContainer').classList.remove('deck-selected');
}
}, 0);
} else if (gBrowser.selectedTab === this.#currentTab) {
setTimeout(this.onLocationChangeOpenGlance.bind(this), 0);
}
}
onTabClose(event) {
if (event.target === this.#currentParentTab) {
this.closeGlance({ onTabClose: true });
}
}
manageTabClose(tab) {
if (tab.hasAttribute('glance-id')) {
const oldGlanceID = this.#currentGlanceID;
const newGlanceID = tab.getAttribute('glance-id');
this.#currentGlanceID = newGlanceID;
const isDifferent = newGlanceID !== oldGlanceID;
if (this._ignoreClose) {
this._ignoreClose = false;
return false;
}
this.closeGlance({ onTabClose: true, setNewID: isDifferent ? oldGlanceID : null, isDifferent });
// only keep continueing tab close if we are not on the currently selected tab
return !isDifferent;
}
return false;
}
tabDomainsDiffer(tab1, url2) {
try {
if (!tab1) {
return true;
}
let url1 = tab1.linkedBrowser.currentURI.spec;
if (url1.startsWith('about:')) {
return true;
}
// https://github.com/zen-browser/desktop/issues/7173: Only glance up links that are http(s) or file
const url2Spec = url2.spec;
if (!url2Spec.startsWith('http') && !url2Spec.startsWith('https') && !url2Spec.startsWith('file')) {
return false;
}
return Services.io.newURI(url1).host !== url2.host;
} catch (e) {
return true;
}
}
shouldOpenTabInGlance(tab, uri) {
let owner = tab.owner;
return (
owner &&
owner.pinned &&
this._lazyPref.SHOULD_OPEN_EXTERNAL_TABS_IN_GLANCE &&
owner.linkedBrowser?.browsingContext?.isAppTab &&
this.tabDomainsDiffer(owner, uri) &&
Services.prefs.getBoolPref('zen.glance.enabled', true)
);
}
onTabOpen(browser, uri) {
let tab = gBrowser.getTabForBrowser(browser);
if (!tab) {
return;
}
try {
if (this.shouldOpenTabInGlance(tab, uri)) {
const browserRect = gBrowser.tabbox.getBoundingClientRect();
this.openGlance(
{ url: undefined, x: browserRect.width / 2, y: browserRect.height / 2, width: 0, height: 0 },
tab,
tab.owner
);
}
} catch (e) {
console.error(e);
}
}
finishOpeningGlance() {
ZenWorkspaces.updateTabsContainers();
this.browserWrapper.removeAttribute('animate-full');
this.overlay.classList.remove('zen-glance-overlay');
this._clearContainerStyles(this.browserWrapper);
this.animatingFullOpen = false;
this.closeGlance({ noAnimation: true, skipPermitUnload: true });
this.#glances.delete(this.#currentGlanceID);
}
async fullyOpenGlance({ forSplit = false } = {}) {
this.animatingFullOpen = true;
this.#currentTab.setAttribute('zen-dont-split-glance', true);
gBrowser._insertTabAtIndex(this.#currentTab, {
index: this.getTabPosition(this.#currentTab),
});
this.#currentTab.removeAttribute('zen-glance-tab');
this._clearContainerStyles(this.browserWrapper);
this.browserWrapper.removeAttribute('has-finished-animation');
this.browserWrapper.setAttribute('animate-full', true);
this.#currentTab.removeAttribute('glance-id');
this.#currentParentTab.removeAttribute('glance-id');
gBrowser.selectedTab = this.#currentTab;
this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer').classList.remove('zen-glance-background');
this.#currentParentTab._visuallySelected = false;
this.hideSidebarButtons();
if (gReduceMotion || forSplit) {
this.finishOpeningGlance();
return;
}
await gZenUIManager.motion.animate(
this.browserWrapper,
{
width: ['85%', '100%'],
height: ['100%', '100%'],
},
{
duration: 0.4,
type: 'spring',
}
);
this.finishOpeningGlance();
}
openGlanceForBookmark(event) {
const activationMethod = Services.prefs.getStringPref('zen.glance.activation-method', 'ctrl');
if (activationMethod === 'ctrl' && !event.ctrlKey) {
return;
} else if (activationMethod === 'alt' && !event.altKey) {
return;
} else if (activationMethod === 'shift' && !event.shiftKey) {
return;
} else if (activationMethod === 'meta' && !event.metaKey) {
return;
} else if (activationMethod === 'mantain' || typeof activationMethod === 'undefined') {
return;
}
event.preventDefault();
event.stopPropagation();
const rect = event.target.getBoundingClientRect();
const data = {
url: event.target._placesNode.uri,
x: rect.left,
y: rect.top,
width: rect.width,
height: rect.height,
};
this.openGlance(data);
return false;
}
getFocusedTab(aDir) {
return aDir < 0 ? this.#currentParentTab : this.#currentTab;
}
async splitGlance() {
if (this.#currentGlanceID) {
const currentTab = this.#currentTab;
const currentParentTab = this.#currentParentTab;
await this.fullyOpenGlance({ forSplit: true });
gZenViewSplitter.splitTabs([currentTab, currentParentTab], 'vsep', 1);
const browserContainer = currentTab.linkedBrowser?.closest('.browserSidebarContainer');
if (!gReduceMotion && browserContainer) {
gZenViewSplitter.animateBrowserDrop(browserContainer);
}
}
}
}
window.gZenGlanceManager = new ZenGlanceManager();
function registerWindowActors() {
if (Services.prefs.getBoolPref('zen.glance.enabled', true)) {
gZenActorsManager.addJSWindowActor('ZenGlance', {
parent: {
esModuleURI: 'chrome://browser/content/zen-components/actors/ZenGlanceParent.sys.mjs',
},
child: {
esModuleURI: 'chrome://browser/content/zen-components/actors/ZenGlanceChild.sys.mjs',
events: {
DOMContentLoaded: {},
keydown: {
capture: true,
},
},
},
allFrames: true,
matches: ['https://*/*'],
});
}
}
registerWindowActors();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,657 +0,0 @@
{
const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
'RESPECT_PIP_DISABLED',
'media.videocontrols.picture-in-picture.respect-disablePictureInPicture',
true
);
class ZenMediaController {
_currentMediaController = null;
_currentBrowser = null;
_mediaUpdateInterval = null;
mediaTitle = null;
mediaArtist = null;
mediaControlBar = null;
mediaProgressBar = null;
mediaCurrentTime = null;
mediaDuration = null;
mediaFocusButton = null;
mediaProgressBarContainer = null;
supportedKeys = ['playpause', 'previoustrack', 'nexttrack'];
mediaControllersMap = new Map();
_tabTimeout = null;
_controllerSwitchTimeout = null;
#isSeeking = false;
init() {
if (!Services.prefs.getBoolPref('zen.mediacontrols.enabled', true)) return;
this.mediaTitle = document.querySelector('#zen-media-title');
this.mediaArtist = document.querySelector('#zen-media-artist');
this.mediaControlBar = document.querySelector('#zen-media-controls-toolbar');
this.mediaProgressBar = document.querySelector('#zen-media-progress-bar');
this.mediaCurrentTime = document.querySelector('#zen-media-current-time');
this.mediaDuration = document.querySelector('#zen-media-duration');
this.mediaFocusButton = document.querySelector('#zen-media-focus-button');
this.mediaProgressBarContainer = document.querySelector('#zen-media-progress-hbox');
this.onPositionstateChange = this._onPositionstateChange.bind(this);
this.onPlaybackstateChange = this._onPlaybackstateChange.bind(this);
this.onSupportedKeysChange = this._onSupportedKeysChange.bind(this);
this.onMetadataChange = this._onMetadataChange.bind(this);
this.onDeactivated = this._onDeactivated.bind(this);
this.onPipModeChange = this._onPictureInPictureModeChange.bind(this);
this.#initEventListeners();
}
#initEventListeners() {
this.mediaControlBar.addEventListener('mousedown', (event) => {
if (event.target.closest(':is(toolbarbutton,#zen-media-progress-hbox)')) return;
else this.onMediaFocus();
});
this.mediaControlBar.addEventListener('command', (event) => {
const button = event.target.closest('toolbarbutton');
if (!button) return;
switch (button.id) {
case 'zen-media-pip-button':
this.onMediaPip();
break;
case 'zen-media-close-button':
this.onControllerClose();
break;
case 'zen-media-focus-button':
this.onMediaFocus();
break;
case 'zen-media-mute-button':
this.onMediaMute();
break;
case 'zen-media-previoustrack-button':
this.onMediaPlayPrev();
break;
case 'zen-media-nexttrack-button':
this.onMediaPlayNext();
break;
case 'zen-media-playpause-button':
this.onMediaToggle();
break;
case 'zen-media-mute-mic-button':
this.onMicrophoneMuteToggle();
break;
case 'zen-media-mute-camera-button':
this.onCameraMuteToggle();
break;
}
});
this.mediaProgressBar.addEventListener('input', this.onMediaSeekDrag.bind(this));
this.mediaProgressBar.addEventListener('change', this.onMediaSeekComplete.bind(this));
window.addEventListener('TabSelect', (event) => {
if (this.isSharing) return;
const linkedBrowser = event.target.linkedBrowser;
this.switchController();
if (this._currentBrowser) {
if (linkedBrowser.browserId === this._currentBrowser.browserId) {
if (this._tabTimeout) {
clearTimeout(this._tabTimeout);
this._tabTimeout = null;
}
this.hideMediaControls();
} else {
this._tabTimeout = setTimeout(() => {
if (!this.mediaControlBar.hasAttribute('pip')) this.showMediaControls();
else this._tabTimeout = null;
}, 500);
}
}
});
const onTabDiscardedOrClosed = this.onTabDiscardedOrClosed.bind(this);
window.addEventListener('TabClose', onTabDiscardedOrClosed);
window.addEventListener('TabBrowserDiscarded', onTabDiscardedOrClosed);
window.addEventListener('DOMAudioPlaybackStarted', (event) => {
setTimeout(() => {
if (
this._currentMediaController?.isPlaying &&
this.mediaControlBar.hasAttribute('hidden') &&
!this.mediaControlBar.hasAttribute('pip')
) {
const { selectedBrowser } = gBrowser;
if (selectedBrowser.browserId !== this._currentBrowser.browserId) {
this.showMediaControls();
}
}
}, 1000);
this.activateMediaControls(event.target.browsingContext.mediaController, event.target);
});
window.addEventListener('DOMAudioPlaybackStopped', () => this.updateMuteState());
}
onTabDiscardedOrClosed(event) {
const { linkedBrowser } = event.target;
const isCurrentBrowser = linkedBrowser?.browserId === this._currentBrowser?.browserId;
if (isCurrentBrowser) {
this.isSharing = false;
this.hideMediaControls();
}
if (linkedBrowser?.browsingContext.mediaController) {
this.deinitMediaController(linkedBrowser.browsingContext.mediaController, true, isCurrentBrowser, true);
}
}
async deinitMediaController(mediaController, shouldForget = true, shouldOverride = true, shouldHide = true) {
if (shouldForget && mediaController) {
mediaController.removeEventListener('pictureinpicturemodechange', this.onPipModeChange);
mediaController.removeEventListener('positionstatechange', this.onPositionstateChange);
mediaController.removeEventListener('playbackstatechange', this.onPlaybackstateChange);
mediaController.removeEventListener('supportedkeyschange', this.onSupportedKeysChange);
mediaController.removeEventListener('metadatachange', this.onMetadataChange);
mediaController.removeEventListener('deactivated', this.onDeactivated);
this.mediaControllersMap.delete(mediaController.id);
}
if (shouldOverride) {
this._currentMediaController = null;
this._currentBrowser = null;
if (this._mediaUpdateInterval) {
clearInterval(this._mediaUpdateInterval);
this._mediaUpdateInterval = null;
}
if (shouldHide) await this.hideMediaControls();
this.mediaControlBar.removeAttribute('muted');
this.mediaControlBar.classList.remove('playing');
}
}
get isSharing() {
return this.mediaControlBar.hasAttribute('media-sharing');
}
set isSharing(value) {
if (this._currentBrowser?.browsingContext && !value) {
const webRTC = this._currentBrowser.browsingContext.currentWindowGlobal.getActor('WebRTC');
webRTC.sendAsyncMessage('webrtc:UnmuteMicrophone');
webRTC.sendAsyncMessage('webrtc:UnmuteCamera');
}
if (!value) {
this.mediaControlBar.removeAttribute('mic-muted');
this.mediaControlBar.removeAttribute('camera-muted');
} else {
this.mediaControlBar.setAttribute('media-position-hidden', '');
this.mediaControlBar.setAttribute('media-sharing', '');
}
}
hideMediaControls() {
if (this.mediaControlBar.hasAttribute('hidden')) return;
return gZenUIManager.motion
.animate(
this.mediaControlBar,
{
opacity: [1, 0],
y: [0, 10],
},
{
duration: 0.1,
}
)
.then(() => {
this.mediaControlBar.setAttribute('hidden', 'true');
this.mediaControlBar.removeAttribute('media-sharing');
gZenUIManager.updateTabsToolbar();
gZenUIManager.restoreScrollbarState();
});
}
showMediaControls() {
if (!this.mediaControlBar.hasAttribute('hidden')) return;
if (!this.isSharing) {
if (!this._currentMediaController) return;
if (this._currentMediaController.isBeingUsedInPIPModeOrFullscreen) return this.hideMediaControls();
this.updatePipButton();
}
const mediaInfoElements = [this.mediaTitle, this.mediaArtist];
for (const element of mediaInfoElements) {
element.removeAttribute('overflow'); // So we can properly recalculate the overflow
}
this.mediaControlBar.removeAttribute('hidden');
window.requestAnimationFrame(() => {
this.mediaControlBar.style.height =
this.mediaControlBar.querySelector('toolbaritem').getBoundingClientRect().height + 'px';
this.mediaControlBar.style.opacity = 0;
gZenUIManager.updateTabsToolbar();
gZenUIManager.restoreScrollbarState();
gZenUIManager.motion.animate(
this.mediaControlBar,
{
opacity: [0, 1],
y: [10, 0],
},
{}
);
this.addLabelOverflows(mediaInfoElements);
});
}
addLabelOverflows(elements) {
for (const element of elements) {
const parent = element.parentElement;
if (element.scrollWidth > parent.clientWidth) {
element.setAttribute('overflow', '');
} else {
element.removeAttribute('overflow');
}
}
}
setupMediaController(mediaController, browser) {
this._currentMediaController = mediaController;
this._currentBrowser = browser;
this.updatePipButton();
}
setupMediaControlUI(metadata, positionState) {
this.updatePipButton();
if (!this.mediaControlBar.classList.contains('playing') && this._currentMediaController.isPlaying) {
this.mediaControlBar.classList.add('playing');
}
const iconURL = this._currentBrowser.mIconURL || `page-icon:${this._currentBrowser.currentURI.spec}`;
this.mediaFocusButton.style.listStyleImage = `url(${iconURL})`;
this.mediaTitle.textContent = metadata.title || '';
this.mediaArtist.textContent = metadata.artist || '';
gZenUIManager.updateTabsToolbar();
gZenUIManager.restoreScrollbarState();
this._currentPosition = positionState.position;
this._currentDuration = positionState.duration;
this._currentPlaybackRate = positionState.playbackRate;
this.updateMediaPosition();
for (const key of this.supportedKeys) {
const button = this.mediaControlBar.querySelector(`#zen-media-${key}-button`);
button.disabled = !this._currentMediaController.supportedKeys.includes(key);
}
}
activateMediaControls(mediaController, browser) {
this.updateMuteState();
this.switchController();
if (!mediaController.isActive || this._currentBrowser?.browserId === browser.browserId) return;
const metadata = mediaController.getMetadata();
const positionState = mediaController.getPositionState();
this.mediaControllersMap.set(mediaController.id, {
controller: mediaController,
browser,
position: positionState.position,
duration: positionState.duration,
playbackRate: positionState.playbackRate,
lastUpdated: Date.now(),
});
if (!this._currentBrowser && !this.isSharing) {
this.setupMediaController(mediaController, browser);
this.setupMediaControlUI(metadata, positionState);
}
mediaController.addEventListener('pictureinpicturemodechange', this.onPipModeChange);
mediaController.addEventListener('positionstatechange', this.onPositionstateChange);
mediaController.addEventListener('playbackstatechange', this.onPlaybackstateChange);
mediaController.addEventListener('supportedkeyschange', this.onSupportedKeysChange);
mediaController.addEventListener('metadatachange', this.onMetadataChange);
mediaController.addEventListener('deactivated', this.onDeactivated);
}
activateMediaDeviceControls(browser) {
if (browser?.browsingContext.currentWindowGlobal.hasActivePeerConnections()) {
this.mediaControlBar.removeAttribute('can-pip');
this._currentBrowser = browser;
const tab = window.gBrowser.getTabForBrowser(browser);
const iconURL = browser.mIconURL || `page-icon:${browser.currentURI.spec}`;
this.isSharing = true;
this.mediaFocusButton.style.listStyleImage = `url(${iconURL})`;
this.mediaTitle.textContent = tab.label;
this.mediaArtist.textContent = '';
this.showMediaControls();
}
}
updateMediaSharing(data) {
const { windowId, showCameraIndicator, showMicrophoneIndicator } = data;
for (const browser of window.gBrowser.browsers) {
const isMatch = browser.innerWindowID === windowId;
const isCurrentBrowser = this._currentBrowser?.browserId === browser.browserId;
const shouldShow = showCameraIndicator || showMicrophoneIndicator;
if (!isMatch) continue;
if (shouldShow && !(isCurrentBrowser && this.isSharing)) {
const webRTC = browser.browsingContext.currentWindowGlobal.getActor('WebRTC');
webRTC.sendAsyncMessage('webrtc:UnmuteMicrophone');
webRTC.sendAsyncMessage('webrtc:UnmuteCamera');
if (this._currentBrowser) this.isSharing = false;
if (this._currentMediaController) {
this._currentMediaController.pause();
this.deinitMediaController(this._currentMediaController, true, true).then(() =>
this.activateMediaDeviceControls(browser)
);
} else this.activateMediaDeviceControls(browser);
} else if (!shouldShow && isCurrentBrowser && this.isSharing) {
this.isSharing = false;
this._currentBrowser = null;
this.hideMediaControls();
}
break;
}
}
_onDeactivated(event) {
this.deinitMediaController(event.target, true, event.target.id === this._currentMediaController.id, true);
this.switchController();
}
_onPlaybackstateChange() {
if (this._currentMediaController?.isPlaying) {
this.mediaControlBar.classList.add('playing');
} else {
this.switchController();
this.mediaControlBar.classList.remove('playing');
}
}
_onSupportedKeysChange(event) {
if (event.target.id !== this._currentMediaController?.id) return;
for (const key of this.supportedKeys) {
const button = this.mediaControlBar.querySelector(`#zen-media-${key}-button`);
button.disabled = !event.target.supportedKeys.includes(key);
}
}
_onPositionstateChange(event) {
const mediaController = this.mediaControllersMap.get(event.target.id);
this.mediaControllersMap.set(event.target.id, {
...mediaController,
position: event.position,
duration: event.duration,
playbackRate: event.playbackRate,
lastUpdated: Date.now(),
});
if (event.target.id !== this._currentMediaController?.id) return;
this._currentPosition = event.position;
this._currentDuration = event.duration;
this._currentPlaybackRate = event.playbackRate;
this.updateMediaPosition();
}
switchController(force = false) {
let timeout = 3000;
if (this.isSharing) return;
if (this.#isSeeking) return;
if (this._controllerSwitchTimeout) {
clearTimeout(this._controllerSwitchTimeout);
this._controllerSwitchTimeout = null;
}
if (this.mediaControllersMap.size === 1) timeout = 0;
this._controllerSwitchTimeout = setTimeout(() => {
if (!this._currentMediaController?.isPlaying || force) {
const nextController = Array.from(this.mediaControllersMap.values())
.filter(
(ctrl) =>
ctrl.controller.isPlaying &&
gBrowser.selectedBrowser.browserId !== ctrl.browser.browserId &&
ctrl.controller.id !== this._currentMediaController?.id
)
.sort((a, b) => b.lastUpdated - a.lastUpdated)
.shift();
if (nextController) {
this.deinitMediaController(this._currentMediaController, false, true).then(() => {
this.setupMediaController(nextController.controller, nextController.browser);
const elapsedTime = Math.floor((Date.now() - nextController.lastUpdated) / 1000);
this.setupMediaControlUI(nextController.controller.getMetadata(), {
position: nextController.position + (nextController.controller.isPlaying ? elapsedTime : 0),
duration: nextController.duration,
playbackRate: nextController.playbackRate,
});
this.showMediaControls();
});
}
}
this._controllerSwitchTimeout = null;
}, timeout);
}
updateMediaPosition() {
if (this._mediaUpdateInterval) {
clearInterval(this._mediaUpdateInterval);
this._mediaUpdateInterval = null;
}
if (this._currentDuration >= 900_000) return this.mediaControlBar.setAttribute('media-position-hidden', 'true');
else this.mediaControlBar.removeAttribute('media-position-hidden');
if (!this._currentDuration) return;
this.mediaCurrentTime.textContent = this.formatSecondsToTime(this._currentPosition);
this.mediaDuration.textContent = this.formatSecondsToTime(this._currentDuration);
this.mediaProgressBar.value = (this._currentPosition / this._currentDuration) * 100;
this._mediaUpdateInterval = setInterval(() => {
if (this._currentMediaController?.isPlaying) {
this._currentPosition += 1 * this._currentPlaybackRate;
if (this._currentPosition > this._currentDuration) {
this._currentPosition = this._currentDuration;
}
this.mediaCurrentTime.textContent = this.formatSecondsToTime(this._currentPosition);
this.mediaProgressBar.value = (this._currentPosition / this._currentDuration) * 100;
} else {
clearInterval(this._mediaUpdateInterval);
this._mediaUpdateInterval = null;
}
}, 1000);
}
formatSecondsToTime(seconds) {
if (!seconds || isNaN(seconds)) return '0:00';
const totalSeconds = Math.max(0, Math.ceil(seconds));
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60).toString();
const secs = (totalSeconds % 60).toString();
if (hours > 0) {
return `${hours}:${minutes.padStart(2, '0')}:${secs.padStart(2, '0')}`;
}
return `${minutes}:${secs.padStart(2, '0')}`;
}
_onMetadataChange(event) {
if (event.target.id !== this._currentMediaController?.id) return;
this.updatePipButton();
const metadata = event.target.getMetadata();
this.mediaTitle.textContent = metadata.title || '';
this.mediaArtist.textContent = metadata.artist || '';
const mediaInfoElements = [this.mediaTitle, this.mediaArtist];
for (const element of mediaInfoElements) {
element.removeAttribute('overflow');
}
this.addLabelOverflows(mediaInfoElements);
}
_onPictureInPictureModeChange(event) {
if (event.target.id !== this._currentMediaController?.id) return;
if (event.target.isBeingUsedInPIPModeOrFullscreen) {
this.hideMediaControls();
this.mediaControlBar.setAttribute('pip', '');
} else {
const { selectedBrowser } = gBrowser;
if (selectedBrowser.browserId !== this._currentBrowser.browserId) {
this.showMediaControls();
}
this.mediaControlBar.removeAttribute('pip');
}
}
onMediaPlayPrev() {
if (this._currentMediaController?.supportedKeys.includes('previoustrack')) {
this._currentMediaController.prevTrack();
}
}
onMediaPlayNext() {
if (this._currentMediaController?.supportedKeys.includes('nexttrack')) {
this._currentMediaController.nextTrack();
}
}
onMediaSeekDrag(event) {
this.#isSeeking = true;
this._currentMediaController?.pause();
const newTime = (event.target.value / 100) * this._currentDuration;
this.mediaCurrentTime.textContent = this.formatSecondsToTime(newTime);
}
onMediaSeekComplete(event) {
const newPosition = (event.target.value / 100) * this._currentDuration;
if (this._currentMediaController?.supportedKeys.includes('seekto')) {
this._currentMediaController.seekTo(newPosition);
this._currentMediaController.play();
}
this.#isSeeking = false;
}
onMediaFocus() {
if (!this._currentBrowser) return;
if (this._currentMediaController) this._currentMediaController.focus();
else if (this._currentBrowser) {
const tab = window.gBrowser.getTabForBrowser(this._currentBrowser);
if (tab) window.ZenWorkspaces.switchTabIfNeeded(tab);
}
}
onMediaMute() {
if (!this.mediaControlBar.hasAttribute('muted')) {
this._currentBrowser.mute();
this.mediaControlBar.setAttribute('muted', '');
} else {
this._currentBrowser.unmute();
this.mediaControlBar.removeAttribute('muted');
}
}
onMediaToggle() {
if (this.mediaControlBar.classList.contains('playing')) {
this._currentMediaController?.pause();
} else {
this._currentMediaController?.play();
}
}
onControllerClose() {
if (this._currentMediaController) {
this._currentMediaController.pause();
this.deinitMediaController(this._currentMediaController);
} else if (this.isSharing) this.isSharing = false;
this.hideMediaControls();
this.switchController(true);
}
onMediaPip() {
this._currentBrowser.browsingContext.currentWindowGlobal
.getActor('PictureInPictureLauncher')
.sendAsyncMessage('PictureInPicture:KeyToggle');
}
onMicrophoneMuteToggle() {
if (this._currentBrowser) {
const shouldMute = this.mediaControlBar.hasAttribute('mic-muted') ? 'webrtc:UnmuteMicrophone' : 'webrtc:MuteMicrophone';
this._currentBrowser.browsingContext.currentWindowGlobal.getActor('WebRTC').sendAsyncMessage(shouldMute);
this.mediaControlBar.toggleAttribute('mic-muted');
}
}
onCameraMuteToggle() {
if (this._currentBrowser) {
const shouldMute = this.mediaControlBar.hasAttribute('camera-muted') ? 'webrtc:UnmuteCamera' : 'webrtc:MuteCamera';
this._currentBrowser.browsingContext.currentWindowGlobal.getActor('WebRTC').sendAsyncMessage(shouldMute);
this.mediaControlBar.toggleAttribute('camera-muted');
}
}
updateMuteState() {
if (!this._currentBrowser) return;
this.mediaControlBar.toggleAttribute('muted', this._currentBrowser._audioMuted);
}
updatePipButton() {
if (!this._currentBrowser) return;
if (this.isSharing) return;
const { totalPipCount, totalPipDisabled } = PictureInPicture.getEligiblePipVideoCount(this._currentBrowser);
const canPip = totalPipCount === 1 || (totalPipDisabled > 0 && lazy.RESPECT_PIP_DISABLED);
this.mediaControlBar.toggleAttribute('can-pip', canPip);
}
}
window.gZenMediaController = new ZenMediaController();
}

View File

@@ -1,990 +0,0 @@
{
const lazy = {};
class ZenPinnedTabsObserver {
static ALL_EVENTS = ['TabPinned', 'TabUnpinned', 'TabMove'];
#listeners = [];
constructor() {
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
'zenPinnedTabRestorePinnedTabsToPinnedUrl',
'zen.pinned-tab-manager.restore-pinned-tabs-to-pinned-url',
false
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
'zenPinnedTabCloseShortcutBehavior',
'zen.pinned-tab-manager.close-shortcut-behavior',
'switch'
);
ChromeUtils.defineESModuleGetters(lazy, { E10SUtils: 'resource://gre/modules/E10SUtils.sys.mjs' });
this.#listenPinnedTabEvents();
}
#listenPinnedTabEvents() {
const eventListener = this.#eventListener.bind(this);
for (const event of ZenPinnedTabsObserver.ALL_EVENTS) {
window.addEventListener(event, eventListener);
}
window.addEventListener('unload', () => {
for (const event of ZenPinnedTabsObserver.ALL_EVENTS) {
window.removeEventListener(event, eventListener);
}
});
}
#eventListener(event) {
for (const listener of this.#listeners) {
listener(event.type, event);
}
}
addPinnedTabListener(listener) {
this.#listeners.push(listener);
}
}
class ZenPinnedTabManager extends ZenDOMOperatedFeature {
async init() {
if (!this.enabled) {
return;
}
this._canLog = Services.prefs.getBoolPref('zen.pinned-tab-manager.debug', false);
this.observer = new ZenPinnedTabsObserver();
this._initClosePinnedTabShortcut();
this._insertItemsIntoTabContextMenu();
this.observer.addPinnedTabListener(this._onPinnedTabEvent.bind(this));
this._zenClickEventListener = this._onTabClick.bind(this);
ZenWorkspaces.addChangeListeners(this.onWorkspaceChange.bind(this));
await ZenPinnedTabsStorage.promiseInitialized;
ZenWorkspaces._resolvePinnedInitialized();
}
async onWorkspaceChange(newWorkspace, onInit) {
if (!this.enabled || PrivateBrowsingUtils.isWindowPrivate(window)) {
return;
}
if (onInit) {
await this._refreshPinnedTabs({ init: onInit });
this._hasFinishedLoading = true;
}
}
log(message) {
if (this._canLog) {
console.log(`[ZenPinnedTabManager] ${message}`);
}
}
onTabIconChanged(tab, url = null) {
const iconUrl = url ?? tab.iconImage.src;
if (tab.hasAttribute('zen-essential')) {
tab.querySelector('.tab-background').style.setProperty('--zen-tab-icon', `url(${iconUrl})`);
}
// TODO: work on this
//if (tab.hasAttribute('zen-pinned-changed') || !this._pinsCache) {
// return;
//}
// Save if the url is the same as the pinned tab
//const pin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
//if (pin) {
// pin.iconUrl = iconUrl;
// this.savePin(pin);
//}
}
_onTabResetPinButton(event, tab) {
event.stopPropagation();
const pin = this._pinsCache?.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
if (!pin) {
return;
}
let userContextId;
if (tab.hasAttribute('usercontextid')) {
userContextId = tab.getAttribute('usercontextid');
}
const pinnedUrl = Services.io.newURI(pin.url);
const browser = tab.linkedBrowser;
browser.loadURI(pinnedUrl, {
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({
userContextId,
}),
});
this.resetPinChangedUrl(tab);
}
get enabled() {
if (typeof this._enabled === 'undefined') {
this._enabled = !(
PrivateBrowsingUtils.isWindowPrivate(window) ||
document.documentElement.getAttribute('chromehidden')?.includes('toolbar') ||
document.documentElement.getAttribute('chromehidden')?.includes('menubar')
);
}
return this._enabled;
}
async _refreshPinnedTabs({ init = false } = {}) {
await ZenWorkspaces.promiseSectionsInitialized;
await this._initializePinsCache();
await this._initializePinnedTabs(init);
}
async _initializePinsCache() {
try {
// Get pin data
const pins = await ZenPinnedTabsStorage.getPins();
// Enhance pins with favicons
const enhancedPins = await Promise.all(
pins.map(async (pin) => {
try {
const image = await this.getFaviconAsBase64(Services.io.newURI(pin.url).spec);
return {
...pin,
iconUrl: image || null,
};
} catch (ex) {
// If favicon fetch fails, continue without icon
return {
...pin,
iconUrl: null,
};
}
})
);
this._pinsCache = enhancedPins.sort((a, b) => {
if (!a.workspaceUuid && b.workspaceUuid) return -1;
if (a.workspaceUuid && !b.workspaceUuid) return 1;
return 0;
});
} catch (ex) {
console.error('Failed to initialize pins cache:', ex);
this._pinsCache = [];
}
this.log(`Initialized pins cache with ${this._pinsCache.length} pins`);
return this._pinsCache;
}
async _initializePinnedTabs(init = false) {
const pins = this._pinsCache;
if (!pins?.length || !init) {
return;
}
const pinnedTabsByUUID = new Map();
const pinsToCreate = new Set(pins.map((p) => p.uuid));
// First pass: identify existing tabs and remove those without pins
for (let tab of ZenWorkspaces.allStoredTabs) {
const pinId = tab.getAttribute('zen-pin-id');
if (!pinId) {
continue;
}
if (pinsToCreate.has(pinId)) {
// This is a valid pinned tab that matches a pin
pinnedTabsByUUID.set(pinId, tab);
pinsToCreate.delete(pinId);
if (lazy.zenPinnedTabRestorePinnedTabsToPinnedUrl && init) {
this._resetTabToStoredState(tab);
}
} else {
// This is a pinned tab that no longer has a corresponding pin
gBrowser.removeTab(tab);
}
}
// Second pass: For every existing tab, update its label
// and set 'zen-has-static-label' attribute if it's been edited
for (let pin of pins) {
const tab = pinnedTabsByUUID.get(pin.uuid);
if (!tab) {
continue;
}
if (pin.title && (pin.editedTitle || tab.hasAttribute('zen-has-static-label'))) {
tab.removeAttribute('zen-has-static-label'); // So we can set it again
gBrowser._setTabLabel(tab, pin.title, { beforeTabOpen: true });
tab.setAttribute('zen-has-static-label', 'true');
}
}
// Third pass: create new tabs for pins that don't have tabs
for (let pin of pins) {
try {
if (!pinsToCreate.has(pin.uuid)) {
continue; // Skip pins that already have tabs
}
let params = {
skipAnimation: true,
allowInheritPrincipal: false,
skipBackgroundNotify: true,
userContextId: pin.containerTabId || 0,
createLazyBrowser: true,
skipLoad: true,
noInitialLabel: false,
};
// Create and initialize the tab
let newTab = gBrowser.addTrustedTab(pin.url, params);
newTab.setAttribute('zenDefaultUserContextId', true);
// Set initial label/title
if (pin.title) {
gBrowser.setInitialTabTitle(newTab, pin.title);
}
// Set the icon if we have it cached
if (pin.iconUrl) {
gBrowser.setIcon(newTab, pin.iconUrl);
}
newTab.setAttribute('zen-pin-id', pin.uuid);
if (pin.workspaceUuid) {
newTab.setAttribute('zen-workspace-id', pin.workspaceUuid);
}
if (pin.isEssential) {
newTab.setAttribute('zen-essential', 'true');
}
if (pin.editedTitle) {
newTab.setAttribute('zen-has-static-label', 'true');
}
// Initialize browser state if needed
if (!newTab.linkedBrowser._remoteAutoRemoved) {
let state = {
entries: [
{
url: pin.url,
title: pin.title,
triggeringPrincipal_base64: E10SUtils.SERIALIZED_SYSTEMPRINCIPAL,
},
],
userContextId: pin.containerTabId || 0,
image: pin.iconUrl,
};
SessionStore.setTabState(newTab, state);
}
this.log(`Created new pinned tab for pin ${pin.uuid} (isEssential: ${pin.isEssential})`);
gBrowser.pinTab(newTab);
if (!pin.isEssential) {
const container = document.querySelector(
`#vertical-pinned-tabs-container .zen-workspace-tabs-section[zen-workspace-id="${pin.workspaceUuid}"]`
);
if (container) {
container.insertBefore(newTab, container.lastChild);
}
} else {
document.getElementById('zen-essentials-container').appendChild(newTab);
}
gBrowser.tabContainer._invalidateCachedTabs();
newTab.initialize();
if (!ZenWorkspaces.essentialShouldShowTab(newTab)) {
gBrowser.hideTab(newTab, undefined, true);
}
} catch (ex) {
console.error('Failed to initialize pinned tabs:', ex);
}
}
gBrowser._updateTabBarForPinnedTabs();
gZenUIManager.updateTabsToolbar();
}
_onPinnedTabEvent(action, event) {
if (!this.enabled) return;
const tab = event.target;
switch (action) {
case 'TabPinned':
tab._zenClickEventListener = this._zenClickEventListener;
tab.addEventListener('click', tab._zenClickEventListener);
this._setPinnedAttributes(tab);
break;
case 'TabUnpinned':
this._removePinnedAttributes(tab);
if (tab._zenClickEventListener) {
tab.removeEventListener('click', tab._zenClickEventListener);
delete tab._zenClickEventListener;
}
break;
case 'TabMove':
this._onTabMove(tab);
break;
default:
console.warn('ZenPinnedTabManager: Unhandled tab event', action);
break;
}
}
async _onTabMove(tab) {
if (!tab.pinned) {
return;
}
// Recollect pinned tabs and essentials after a tab move
tab.position = tab._tPos;
for (let otherTab of gBrowser.tabs) {
if (otherTab.pinned && otherTab.getAttribute('zen-pin-id') !== tab.getAttribute('zen-pin-id')) {
const actualPin = this._pinsCache.find((pin) => pin.uuid === otherTab.getAttribute('zen-pin-id'));
if (!actualPin) {
continue;
}
actualPin.position = otherTab._tPos;
await this.savePin(actualPin, false);
}
}
const actualPin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
if (!actualPin) {
return;
}
actualPin.position = tab.position;
actualPin.isEssential = tab.hasAttribute('zen-essential');
// There was a bug where the title and hasStaticLabel attribute were not being set
// This is a workaround to fix that
if (tab.hasAttribute('zen-has-static-label')) {
actualPin.editedTitle = true;
actualPin.title = tab.label;
}
await this.savePin(actualPin);
}
_onTabClick(e) {
const tab = e.target?.closest('tab');
if (e.button === 1 && tab) {
this._onCloseTabShortcut(e, tab);
}
}
async resetPinnedTab(tab) {
if (!tab) {
tab = TabContextMenu.contextTab;
}
if (!tab || !tab.pinned) {
return;
}
await this._resetTabToStoredState(tab);
}
async replacePinnedUrlWithCurrent(tab = undefined) {
tab ??= TabContextMenu.contextTab;
if (!tab || !tab.pinned || !tab.getAttribute('zen-pin-id')) {
return;
}
const browser = tab.linkedBrowser;
const pin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
if (!pin) {
return;
}
const userContextId = tab.getAttribute('usercontextid');
pin.title = tab.label || browser.contentTitle;
pin.url = browser.currentURI.spec;
pin.workspaceUuid = tab.getAttribute('zen-workspace-id');
pin.userContextId = userContextId ? parseInt(userContextId, 10) : 0;
await this.savePin(pin);
this.resetPinChangedUrl(tab);
await this._refreshPinnedTabs();
gZenUIManager.showToast('zen-pinned-tab-replaced');
}
async _setPinnedAttributes(tab) {
if (tab.hasAttribute('zen-pin-id') || !this._hasFinishedLoading) {
return;
}
this.log(`Setting pinned attributes for tab ${tab.linkedBrowser.currentURI.spec}`);
const browser = tab.linkedBrowser;
const uuid = gZenUIManager.generateUuidv4();
const userContextId = tab.getAttribute('usercontextid');
let entry = null;
if (tab.getAttribute('zen-pinned-entry')) {
entry = JSON.parse(tab.getAttribute('zen-pinned-entry'));
}
await this.savePin({
uuid,
title: entry?.title || tab.label || browser.contentTitle,
url: entry?.url || browser.currentURI.spec,
containerTabId: userContextId ? parseInt(userContextId, 10) : 0,
workspaceUuid: tab.getAttribute('zen-workspace-id'),
isEssential: tab.getAttribute('zen-essential') === 'true',
});
tab.setAttribute('zen-pin-id', uuid);
// This is used while migrating old pins to new system - we don't want to refresh when migrating
if (tab.getAttribute('zen-pinned-entry')) {
tab.removeAttribute('zen-pinned-entry');
return;
}
this.onLocationChange(browser);
await this._refreshPinnedTabs();
}
async _removePinnedAttributes(tab, isClosing = false) {
tab.removeAttribute('zen-has-static-label');
if (!tab.getAttribute('zen-pin-id') || this._temporarilyUnpiningEssential) {
return;
}
if (Services.startup.shuttingDown || window.skipNextCanClose) {
return;
}
this.log(`Removing pinned attributes for tab ${tab.getAttribute('zen-pin-id')}`);
await ZenPinnedTabsStorage.removePin(tab.getAttribute('zen-pin-id'));
this.resetPinChangedUrl(tab);
if (!isClosing) {
tab.removeAttribute('zen-pin-id');
tab.removeAttribute('zen-essential'); // Just in case
if (!tab.hasAttribute('zen-workspace-id') && ZenWorkspaces.workspaceEnabled) {
const workspace = await ZenWorkspaces.getActiveWorkspace();
tab.setAttribute('zen-workspace-id', workspace.uuid);
}
}
await this._refreshPinnedTabs();
}
_initClosePinnedTabShortcut() {
let cmdClose = document.getElementById('cmd_close');
if (cmdClose) {
cmdClose.addEventListener('command', this._onCloseTabShortcut.bind(this));
}
}
async savePin(pin, notifyObservers = true) {
await ZenPinnedTabsStorage.savePin(pin, notifyObservers);
// Update the cache
const existingPin = this._pinsCache.find((p) => p.uuid === pin.uuid);
if (existingPin) {
Object.assign(existingPin, pin);
}
}
_onCloseTabShortcut(event, selectedTab = gBrowser.selectedTab, behavior = lazy.zenPinnedTabCloseShortcutBehavior) {
if (!selectedTab?.pinned) {
return;
}
event.stopPropagation();
event.preventDefault();
switch (behavior) {
case 'close':
this._removePinnedAttributes(selectedTab, true);
gBrowser.removeTab(selectedTab, { animate: true });
break;
case 'reset-unload-switch':
case 'unload-switch':
case 'reset-switch':
case 'switch':
let { permitUnload } = selectedTab.linkedBrowser?.permitUnload();
if (!permitUnload) {
return;
}
this._handleTabSwitch(selectedTab);
if (behavior.includes('reset')) {
this._resetTabToStoredState(selectedTab);
}
if (behavior.includes('unload')) {
if (selectedTab.hasAttribute('glance-id')) {
break;
}
// Do not unload about:* pages
if (!selectedTab.linkedBrowser?.currentURI.spec.startsWith('about:')) {
gZenTabUnloader.explicitUnloadTabs([selectedTab], { permitUnload });
}
}
break;
case 'reset':
this._resetTabToStoredState(selectedTab);
break;
default:
return;
}
}
_handleTabSwitch(selectedTab) {
if (selectedTab !== gBrowser.selectedTab) {
return;
}
const findNextTab = (direction) =>
gBrowser.tabContainer.findNextTab(selectedTab, {
direction,
filter: (tab) => !tab.hidden && !tab.pinned,
});
let nextTab = findNextTab(1) || findNextTab(-1);
if (!nextTab) {
ZenWorkspaces.selectEmptyTab();
return;
}
if (nextTab) {
gBrowser.selectedTab = nextTab;
}
}
async _resetTabToStoredState(tab) {
const id = tab.getAttribute('zen-pin-id');
if (!id) {
return;
}
const pin = this._pinsCache.find((pin) => pin.uuid === id);
if (!pin) {
return;
}
const tabState = SessionStore.getTabState(tab);
const state = JSON.parse(tabState);
state.entries = [
{
url: pin.url,
title: pin.title,
triggeringPrincipal_base64: lazy.E10SUtils.SERIALIZED_SYSTEMPRINCIPAL,
},
];
state.image = pin.iconUrl || null;
state.index = 0;
SessionStore.setTabState(tab, state);
this.resetPinChangedUrl(tab);
}
async getFaviconAsBase64(pageUrl) {
try {
// Get the favicon data
const faviconData = await PlacesUtils.promiseFaviconData(pageUrl);
// The data comes as an array buffer, we need to convert it to base64
// First create a byte array from the data
const array = new Uint8Array(faviconData.data);
// Convert to base64
const base64String = btoa(
Array.from(array)
.map((b) => String.fromCharCode(b))
.join('')
);
// Return as a proper data URL
return `data:${faviconData.mimeType};base64,${base64String}`;
} catch (ex) {
// console.error("Failed to get favicon:", ex);
return `page-icon:${pageUrl}`; // Use this as a fallback
}
}
addToEssentials(tab) {
const tabs = tab
? // if it's already an array, dont make it [tab]
tab?.length
? tab
: [tab]
: TabContextMenu.contextTab.multiselected
? gBrowser.selectedTabs
: [TabContextMenu.contextTab];
for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i];
if (tab.hasAttribute('zen-essential')) {
continue;
}
tab.setAttribute('zen-essential', 'true');
if (tab.hasAttribute('zen-workspace-id')) {
tab.removeAttribute('zen-workspace-id');
}
if (tab.pinned && tab.hasAttribute('zen-pin-id')) {
const pin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
if (pin) {
pin.isEssential = true;
this.savePin(pin);
}
document.getElementById('zen-essentials-container').appendChild(tab);
gBrowser.tabContainer._invalidateCachedTabs();
} else {
gBrowser.pinTab(tab);
}
this._onTabMove(tab);
this.onTabIconChanged(tab);
}
gZenUIManager.updateTabsToolbar();
}
removeEssentials(tab, unpin = true) {
const tabs = tab ? [tab] : TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab];
for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i];
tab.removeAttribute('zen-essential');
if (ZenWorkspaces.workspaceEnabled && ZenWorkspaces.getActiveWorkspaceFromCache().uuid) {
tab.setAttribute('zen-workspace-id', ZenWorkspaces.getActiveWorkspaceFromCache().uuid);
}
if (unpin) {
gBrowser.unpinTab(tab);
} else {
const pinContainer = ZenWorkspaces.pinnedTabsContainer;
pinContainer.prepend(tab);
gBrowser.tabContainer._invalidateCachedTabs();
this._onTabMove(tab);
}
}
gZenUIManager.updateTabsToolbar();
}
_insertItemsIntoTabContextMenu() {
const elements = window.MozXULElement.parseXULToFragment(`
<menuseparator id="context_zen-pinned-tab-separator" hidden="true"/>
<menuitem id="context_zen-replace-pinned-url-with-current"
data-lazy-l10n-id="tab-context-zen-replace-pinned-url-with-current"
hidden="true"
command="cmd_zenReplacePinnedUrlWithCurrent"/>
<menuitem id="context_zen-reset-pinned-tab"
data-lazy-l10n-id="tab-context-zen-reset-pinned-tab"
hidden="true"
command="cmd_zenPinnedTabResetNoTab"/>
`);
document.getElementById('tabContextMenu').appendChild(elements);
const element = window.MozXULElement.parseXULToFragment(`
<menuitem id="context_zen-add-essential"
data-lazy-l10n-id="tab-context-zen-add-essential"
hidden="true"
command="cmd_zenAddToEssentials"/>
<menuitem id="context_zen-remove-essential"
data-lazy-l10n-id="tab-context-zen-remove-essential"
hidden="true"
command="cmd_zenRemoveFromEssentials"/>
`);
document.getElementById('context_pinTab')?.before(element);
}
// TODO: remove this as it's not possible to know the base pinned url any more as it's now stored in tab state
resetPinnedTabData(tabData) {
if (lazy.zenPinnedTabRestorePinnedTabsToPinnedUrl && tabData.pinned && tabData.zenPinnedEntry) {
tabData.entries = [JSON.parse(tabData.zenPinnedEntry)];
tabData.image = tabData.zenPinnedIcon;
tabData.index = 0;
}
}
updatePinnedTabContextMenu(contextTab) {
if (!this.enabled) {
return;
}
const isVisible = contextTab.pinned && !contextTab.multiselected;
document.getElementById('context_zen-reset-pinned-tab').hidden = !isVisible || !contextTab.getAttribute('zen-pin-id');
document.getElementById('context_zen-replace-pinned-url-with-current').hidden = !isVisible;
document.getElementById('context_zen-add-essential').hidden = contextTab.getAttribute('zen-essential');
document.getElementById('context_zen-remove-essential').hidden = !contextTab.getAttribute('zen-essential');
document.getElementById('context_unpinTab').hidden =
document.getElementById('context_unpinTab').hidden || contextTab.getAttribute('zen-essential');
document.getElementById('context_unpinSelectedTabs').hidden =
document.getElementById('context_unpinSelectedTabs').hidden || contextTab.getAttribute('zen-essential');
document.getElementById('context_zen-pinned-tab-separator').hidden = !isVisible;
}
moveToAnotherTabContainerIfNecessary(event, movingTabs) {
try {
const pinnedTabsTarget =
event.target.closest('#vertical-pinned-tabs-container') || event.target.closest('.zen-current-workspace-indicator');
const essentialTabsTarget = event.target.closest('#zen-essentials-container');
const tabsTarget = event.target.closest('#tabbrowser-arrowscrollbox');
let isVertical = this.expandedSidebarMode;
let moved = false;
for (const draggedTab of movingTabs) {
let isRegularTabs = false;
// Check for pinned tabs container
if (pinnedTabsTarget) {
if (!draggedTab.pinned) {
gBrowser.pinTab(draggedTab);
moved = true;
} else if (draggedTab.hasAttribute('zen-essential')) {
this.removeEssentials(draggedTab, false);
moved = true;
}
}
// Check for essentials container
else if (essentialTabsTarget) {
if (!draggedTab.hasAttribute('zen-essential') && !draggedTab?.group?.hasAttribute('split-view-group')) {
this.addToEssentials(draggedTab);
moved = true;
isVertical = false;
}
}
// Check for normal tabs container
else if (tabsTarget || event.target.id === 'zen-tabs-wrapper') {
if (draggedTab.pinned && !draggedTab.hasAttribute('zen-essential')) {
gBrowser.unpinTab(draggedTab);
moved = true;
isRegularTabs = true;
} else if (draggedTab.hasAttribute('zen-essential')) {
this.removeEssentials(draggedTab);
moved = true;
isRegularTabs = true;
}
}
// If the tab was moved, adjust its position relative to the target tab
if (moved) {
const targetTab = event.target.closest('.tabbrowser-tab');
if (targetTab) {
const rect = targetTab.getBoundingClientRect();
let newIndex = targetTab._tPos;
if (isVertical) {
const middleY = targetTab.screenY + rect.height / 2;
if (!isRegularTabs && event.screenY > middleY) {
newIndex++;
} else if (isRegularTabs && event.screenY < middleY) {
newIndex--;
}
} else {
const middleX = targetTab.screenX + rect.width / 2;
if (event.screenX > middleX) {
newIndex++;
}
}
// If it's the last tab, move it to the end
if (tabsTarget === gBrowser.tabs.at(-1)) {
newIndex++;
}
gBrowser.moveTabTo(draggedTab, newIndex, { forceStandaloneTab: true });
}
}
}
return moved;
} catch (ex) {
console.error('Error moving tabs:', ex);
return false;
}
}
async onLocationChange(browser) {
const tab = gBrowser.getTabForBrowser(browser);
if (!tab || !tab.pinned || tab.hasAttribute('zen-essential') || !this._pinsCache) {
return;
}
const pin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
if (!pin) {
return;
}
// Remove # and ? from the url
const pinUrl = pin.url.split('#')[0].split('?')[0];
const currentUrl = browser.currentURI.spec.split('#')[0].split('?')[0];
// Add an indicator that the pin has been changed
if (pinUrl === currentUrl) {
this.resetPinChangedUrl(tab);
return;
}
this.pinHasChangedUrl(tab, pin);
}
resetPinChangedUrl(tab) {
if (!tab.hasAttribute('zen-pinned-changed')) {
return;
}
tab.removeAttribute('zen-pinned-changed');
tab.removeAttribute('had-zen-pinned-changed');
tab.style.removeProperty('--zen-original-tab-icon');
}
pinHasChangedUrl(tab, pin) {
if (tab.hasAttribute('zen-pinned-changed')) {
return;
}
if (tab.group?.hasAttribute('split-view-group')) {
tab.setAttribute('had-zen-pinned-changed', 'true');
} else {
tab.setAttribute('zen-pinned-changed', 'true');
}
tab.style.setProperty('--zen-original-tab-icon', `url(${pin.iconUrl})`);
}
removeTabContainersDragoverClass() {
this.dragIndicator.remove();
this._dragIndicator = null;
ZenWorkspaces.activeWorkspaceIndicator?.removeAttribute('open');
}
get dragIndicator() {
if (!this._dragIndicator) {
this._dragIndicator = document.createElement('div');
this._dragIndicator.id = 'zen-drag-indicator';
document.body.appendChild(this._dragIndicator);
}
return this._dragIndicator;
}
get expandedSidebarMode() {
return document.documentElement.getAttribute('zen-sidebar-expanded') === 'true';
}
async updatePinTitle(tab, newTitle, isEdited = true, notifyObservers = true) {
const uuid = tab.getAttribute('zen-pin-id');
await ZenPinnedTabsStorage.updatePinTitle(uuid, newTitle, isEdited, notifyObservers);
await this._refreshPinnedTabs();
const browsers = Services.wm.getEnumerator('navigator:browser');
// update the label for the same pin across all windows
for (const browser of browsers) {
const tabs = browser.gBrowser.tabs;
// Fix pinned cache for the browser
const browserCache = browser.gZenPinnedTabManager?._pinsCache;
if (browserCache) {
const pin = browserCache.find((pin) => pin.uuid === uuid);
if (pin) {
pin.title = newTitle;
pin.editedTitle = isEdited;
}
}
for (let i = 0; i < tabs.length; i++) {
const tabToEdit = tabs[i];
if (tabToEdit.getAttribute('zen-pin-id') === uuid && tabToEdit !== tab) {
tabToEdit.removeAttribute('zen-has-static-label');
if (isEdited) {
gBrowser._setTabLabel(tabToEdit, newTitle);
tabToEdit.setAttribute('zen-has-static-label', 'true');
} else {
gBrowser.setTabTitle(tabToEdit);
}
break;
}
}
}
}
applyDragoverClass(event, draggedTab) {
const pinnedTabsTarget = event.target.closest('#vertical-pinned-tabs-container');
const essentialTabsTarget = event.target.closest('#zen-essentials-container');
const tabsTarget = event.target.closest('#tabbrowser-arrowscrollbox');
let targetTab = event.target.closest('.tabbrowser-tab');
targetTab = targetTab?.group || targetTab;
if (event.target.closest('.zen-current-workspace-indicator')) {
this.removeTabContainersDragoverClass();
ZenWorkspaces.activeWorkspaceIndicator?.setAttribute('open', true);
} else {
ZenWorkspaces.activeWorkspaceIndicator?.removeAttribute('open');
}
// If there's no valid target tab, nothing to do
if (!targetTab) {
return;
}
let shouldAddDragOverElement = false;
let isVertical = this.expandedSidebarMode;
// Decide whether we should show a dragover class for the given target
if (pinnedTabsTarget) {
if (!draggedTab.pinned || draggedTab.hasAttribute('zen-essential')) {
shouldAddDragOverElement = true;
}
} else if (essentialTabsTarget) {
if (!draggedTab.hasAttribute('zen-essential')) {
shouldAddDragOverElement = true;
isVertical = false;
}
} else if (tabsTarget) {
if (draggedTab.pinned || draggedTab.hasAttribute('zen-essential')) {
shouldAddDragOverElement = true;
}
}
if (!shouldAddDragOverElement) {
this.removeTabContainersDragoverClass();
return;
}
// Calculate middle to decide 'before' or 'after'
const rect = targetTab.getBoundingClientRect();
if (isVertical) {
const separation = 8;
const middleY = targetTab.screenY + rect.height / 2;
const indicator = this.dragIndicator;
let top = 0;
if (event.screenY > middleY) {
top = rect.top + rect.height + 'px';
} else {
top = rect.top + 'px';
}
indicator.setAttribute('orientation', 'horizontal');
indicator.style.setProperty('--indicator-left', rect.left + separation / 2 + 'px');
indicator.style.setProperty('--indicator-width', rect.width - separation + 'px');
indicator.style.top = top;
indicator.style.removeProperty('left');
} else {
const separation = 8;
const middleX = targetTab.screenX + rect.width / 2;
const indicator = this.dragIndicator;
let left = 0;
if (event.screenX > middleX) {
left = rect.left + rect.width + 1 + 'px';
} else {
left = rect.left - 2 + 'px';
}
indicator.setAttribute('orientation', 'vertical');
indicator.style.setProperty('--indicator-top', rect.top + separation / 2 + 'px');
indicator.style.setProperty('--indicator-height', rect.height - separation + 'px');
indicator.style.left = left;
indicator.style.removeProperty('top');
}
}
async onTabLabelChanged(tab) {
if (!this._pinsCache) {
return;
}
// If our current pin in the cache point to about:blank, we need to update the entry
const pin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
if (!pin) {
return;
}
if (pin.url === 'about:blank') {
await this.replacePinnedUrlWithCurrent(tab);
}
}
}
window.gZenPinnedTabManager = new ZenPinnedTabManager();
}

View File

@@ -1,434 +0,0 @@
var ZenPinnedTabsStorage = {
async init() {
await this._ensureTable();
},
async _ensureTable() {
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage._ensureTable', async (db) => {
// Create the pins table if it doesn't exist
await db.execute(`
CREATE TABLE IF NOT EXISTS zen_pins (
id INTEGER PRIMARY KEY,
uuid TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
url TEXT,
container_id INTEGER,
workspace_uuid TEXT,
position INTEGER NOT NULL DEFAULT 0,
is_essential BOOLEAN NOT NULL DEFAULT 0,
is_group BOOLEAN NOT NULL DEFAULT 0,
parent_uuid TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (parent_uuid) REFERENCES zen_pins(uuid) ON DELETE SET NULL
)
`);
const columns = await db.execute(`PRAGMA table_info(zen_pins)`);
const columnNames = columns.map((row) => row.getResultByName('name'));
// Helper function to add column if it doesn't exist
const addColumnIfNotExists = async (columnName, definition) => {
if (!columnNames.includes(columnName)) {
await db.execute(`ALTER TABLE zen_pins ADD COLUMN ${columnName} ${definition}`);
}
};
// Add edited_title column if it doesn't exist
await addColumnIfNotExists('edited_title', 'BOOLEAN NOT NULL DEFAULT 0');
// Create indices
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_zen_pins_uuid ON zen_pins(uuid)
`);
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_zen_pins_parent_uuid ON zen_pins(parent_uuid)
`);
// Create the changes tracking table if it doesn't exist
await db.execute(`
CREATE TABLE IF NOT EXISTS zen_pins_changes (
uuid TEXT PRIMARY KEY,
timestamp INTEGER NOT NULL
)
`);
// Create an index on the uuid column for changes tracking table
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_zen_pins_changes_uuid ON zen_pins_changes(uuid)
`);
this._resolveInitialized();
});
},
/**
* Private helper method to notify observers with a list of changed UUIDs.
* @param {string} event - The observer event name.
* @param {Array<string>} uuids - Array of changed workspace UUIDs.
*/
_notifyPinsChanged(event, uuids) {
if (uuids.length === 0) return; // No changes to notify
// Convert the array of UUIDs to a JSON string
const data = JSON.stringify(uuids);
Services.obs.notifyObservers(null, event, data);
},
async savePin(pin, notifyObservers = true) {
const changedUUIDs = new Set();
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.savePin', async (db) => {
await db.executeTransaction(async () => {
const now = Date.now();
let newPosition;
if ('position' in pin && Number.isFinite(pin.position)) {
newPosition = pin.position;
} else {
// Get the maximum position within the same parent group (or null for root level)
const maxPositionResult = await db.execute(
`
SELECT MAX("position") as max_position
FROM zen_pins
WHERE COALESCE(parent_uuid, '') = COALESCE(:parent_uuid, '')
`,
{ parent_uuid: pin.parentUuid || null }
);
const maxPosition = maxPositionResult[0].getResultByName('max_position') || 0;
newPosition = maxPosition + 1000;
}
// Insert or replace the pin
await db.executeCached(
`
INSERT OR REPLACE INTO zen_pins (
uuid, title, url, container_id, workspace_uuid, position,
is_essential, is_group, parent_uuid, created_at, updated_at
) VALUES (
:uuid, :title, :url, :container_id, :workspace_uuid, :position,
:is_essential, :is_group, :parent_uuid,
COALESCE((SELECT created_at FROM zen_pins WHERE uuid = :uuid), :now),
:now
)
`,
{
uuid: pin.uuid,
title: pin.title,
url: pin.isGroup ? null : pin.url,
container_id: pin.containerTabId || null,
workspace_uuid: pin.workspaceUuid || null,
position: newPosition,
is_essential: pin.isEssential || false,
is_group: pin.isGroup || false,
parent_uuid: pin.parentUuid || null,
now,
}
);
await db.execute(
`
INSERT OR REPLACE INTO zen_pins_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
`,
{
uuid: pin.uuid,
timestamp: Math.floor(now / 1000),
}
);
changedUUIDs.add(pin.uuid);
await this.updateLastChangeTimestamp(db);
});
});
if (notifyObservers) {
this._notifyPinsChanged('zen-pin-updated', Array.from(changedUUIDs));
}
},
async getPins() {
const db = await PlacesUtils.promiseDBConnection();
const rows = await db.executeCached(`
SELECT * FROM zen_pins
ORDER BY parent_uuid NULLS FIRST, position ASC
`);
return rows.map((row) => ({
uuid: row.getResultByName('uuid'),
title: row.getResultByName('title'),
url: row.getResultByName('url'),
containerTabId: row.getResultByName('container_id'),
workspaceUuid: row.getResultByName('workspace_uuid'),
position: row.getResultByName('position'),
isEssential: Boolean(row.getResultByName('is_essential')),
isGroup: Boolean(row.getResultByName('is_group')),
parentUuid: row.getResultByName('parent_uuid'),
editedTitle: Boolean(row.getResultByName('edited_title')),
}));
},
async getGroupChildren(groupUuid) {
const db = await PlacesUtils.promiseDBConnection();
const rows = await db.executeCached(
`
SELECT * FROM zen_pins
WHERE parent_uuid = :groupUuid
ORDER BY position ASC
`,
{ groupUuid }
);
return rows.map((row) => ({
uuid: row.getResultByName('uuid'),
title: row.getResultByName('title'),
url: row.getResultByName('url'),
containerTabId: row.getResultByName('container_id'),
workspaceUuid: row.getResultByName('workspace_uuid'),
position: row.getResultByName('position'),
isEssential: Boolean(row.getResultByName('is_essential')),
isGroup: Boolean(row.getResultByName('is_group')),
parentUuid: row.getResultByName('parent_uuid'),
editedTitle: Boolean(row.getResultByName('edited_title')),
}));
},
async removePin(uuid, notifyObservers = true) {
const changedUUIDs = [uuid];
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.removePin', async (db) => {
await db.executeTransaction(async () => {
// Get all child UUIDs first for change tracking
const children = await db.execute(`SELECT uuid FROM zen_pins WHERE parent_uuid = :uuid`, { uuid });
// Add child UUIDs to changedUUIDs array
for (const child of children) {
changedUUIDs.push(child.getResultByName('uuid'));
}
// Delete all children in a single statement
await db.execute(`DELETE FROM zen_pins WHERE parent_uuid = :uuid`, { uuid });
// Delete the pin/group itself
await db.execute(`DELETE FROM zen_pins WHERE uuid = :uuid`, { uuid });
// Record the changes
const now = Math.floor(Date.now() / 1000);
for (const changedUuid of changedUUIDs) {
await db.execute(
`
INSERT OR REPLACE INTO zen_pins_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
`,
{
uuid: changedUuid,
timestamp: now,
}
);
}
await this.updateLastChangeTimestamp(db);
});
});
if (notifyObservers) {
this._notifyPinsChanged('zen-pin-removed', changedUUIDs);
}
},
async wipeAllPins() {
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.wipeAllPins', async (db) => {
await db.execute(`DELETE FROM zen_pins`);
await db.execute(`DELETE FROM zen_pins_changes`);
await this.updateLastChangeTimestamp(db);
});
},
async markChanged(uuid) {
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.markChanged', async (db) => {
const now = Date.now();
await db.execute(
`
INSERT OR REPLACE INTO zen_pins_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
`,
{
uuid,
timestamp: Math.floor(now / 1000),
}
);
});
},
async getChangedIDs() {
const db = await PlacesUtils.promiseDBConnection();
const rows = await db.execute(`
SELECT uuid, timestamp FROM zen_pins_changes
`);
const changes = {};
for (const row of rows) {
changes[row.getResultByName('uuid')] = row.getResultByName('timestamp');
}
return changes;
},
async clearChangedIDs() {
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.clearChangedIDs', async (db) => {
await db.execute(`DELETE FROM zen_pins_changes`);
});
},
shouldReorderPins(before, current, after) {
const minGap = 1; // Minimum allowed gap between positions
return (before !== null && current - before < minGap) || (after !== null && after - current < minGap);
},
async reorderAllPins(db, changedUUIDs) {
const pins = await db.execute(`
SELECT uuid
FROM zen_pins
ORDER BY position ASC
`);
for (let i = 0; i < pins.length; i++) {
const newPosition = (i + 1) * 1000; // Use large increments
await db.execute(
`
UPDATE zen_pins
SET position = :newPosition
WHERE uuid = :uuid
`,
{ newPosition, uuid: pins[i].getResultByName('uuid') }
);
changedUUIDs.add(pins[i].getResultByName('uuid'));
}
},
async updateLastChangeTimestamp(db) {
const now = Date.now();
await db.execute(
`
INSERT OR REPLACE INTO moz_meta (key, value)
VALUES ('zen_pins_last_change', :now)
`,
{ now }
);
},
async getLastChangeTimestamp() {
const db = await PlacesUtils.promiseDBConnection();
const result = await db.executeCached(`
SELECT value FROM moz_meta WHERE key = 'zen_pins_last_change'
`);
return result.length ? parseInt(result[0].getResultByName('value'), 10) : 0;
},
async updatePinPositions(pins) {
const changedUUIDs = new Set();
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.updatePinPositions', async (db) => {
await db.executeTransaction(async () => {
const now = Date.now();
for (let i = 0; i < pins.length; i++) {
const pin = pins[i];
const newPosition = (i + 1) * 1000;
await db.execute(
`
UPDATE zen_pins
SET position = :newPosition
WHERE uuid = :uuid
`,
{ newPosition, uuid: pin.uuid }
);
changedUUIDs.add(pin.uuid);
// Record the change
await db.execute(
`
INSERT OR REPLACE INTO zen_pins_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
`,
{
uuid: pin.uuid,
timestamp: Math.floor(now / 1000),
}
);
}
await this.updateLastChangeTimestamp(db);
});
});
this._notifyPinsChanged('zen-pin-updated', Array.from(changedUUIDs));
},
async updatePinTitle(uuid, newTitle, isEdited = true, notifyObservers = true) {
if (!uuid || typeof newTitle !== 'string') {
throw new Error('Invalid parameters: uuid and newTitle are required');
}
const changedUUIDs = new Set();
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.updatePinTitle', async (db) => {
await db.executeTransaction(async () => {
const now = Date.now();
// Update the pin's title and edited_title flag
const result = await db.execute(
`
UPDATE zen_pins
SET title = :newTitle,
edited_title = :isEdited,
updated_at = :now
WHERE uuid = :uuid
`,
{
uuid,
newTitle,
isEdited,
now,
}
);
// Only proceed with change tracking if a row was actually updated
if (result.rowsAffected > 0) {
changedUUIDs.add(uuid);
// Record the change
await db.execute(
`
INSERT OR REPLACE INTO zen_pins_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
`,
{
uuid,
timestamp: Math.floor(now / 1000),
}
);
await this.updateLastChangeTimestamp(db);
}
});
});
if (notifyObservers && changedUUIDs.size > 0) {
this._notifyPinsChanged('zen-pin-updated', Array.from(changedUUIDs));
}
},
async __dropTables() {
await PlacesUtils.withConnectionWrapper('ZenPinnedTabsStorage.__dropTables', async (db) => {
await db.execute(`DROP TABLE IF EXISTS zen_pins`);
await db.execute(`DROP TABLE IF EXISTS zen_pins_changes`);
});
},
};
ZenPinnedTabsStorage.promiseInitialized = new Promise((resolve) => {
ZenPinnedTabsStorage._resolveInitialized = resolve;
ZenPinnedTabsStorage.init();
});

View File

@@ -1,428 +0,0 @@
{
const ZEN_RICE_API = Services.prefs.getStringPref('zen.rice.api.url', '');
class ZenRiceCollector {
constructor() {}
clear() {
this._userChrome = null;
this._userContent = null;
this._enabledMods = null;
this._preferences = null;
this._workspaceThemes = null;
}
async gatherAll({
userUserChrome = true,
userContent = true,
enabledMods = true,
preferences = true,
modPrefs = true,
workspaceThemes = true,
} = {}) {
this.clear();
// Get the mods first, as they may be needed for the preferences
if (enabledMods) {
await this.gatherEnabledMods();
}
await Promise.all([
userUserChrome && this.gatherUserChrome(),
userContent && this.gatherUserContent(),
preferences && this.gatherPreferences({ modPrefs }),
workspaceThemes && this.gatherWorkspaceThemes(),
]);
}
get profileDir() {
return PathUtils.profileDir;
}
async gatherUserChrome() {
try {
const path = PathUtils.join(this.profileDir, 'chrome', 'userChrome.css');
this._userChrome = await IOUtils.readUTF8(path);
} catch (e) {
console.warn('[ZenRiceCollector]: Error reading userChrome.css: ', e);
return null;
}
}
async gatherUserContent() {
try {
const path = PathUtils.join(this.profileDir, 'chrome', 'userContent.css');
this._userContent = await IOUtils.readUTF8(path);
} catch (e) {
console.warn('[ZenRiceCollector]: Error reading userContent.css: ', e);
return null;
}
}
async gatherEnabledMods() {
const activeThemes = await gZenThemesImporter.getEnabledThemes();
if (activeThemes.length === 0) {
return;
}
this._enabledMods = activeThemes;
}
_getThemePrefValue(theme, pref) {
if (pref.type === 'checkbox') {
return Services.prefs.getBoolPref(pref.property);
}
return Services.prefs.getStringPref(pref.property);
}
async gatherPreferences({ modPrefs = true } = {}) {
this._preferences = {};
if (modPrefs && this._enabledMods) {
for (const theme of this._enabledMods) {
const prefs = await ZenThemesCommon.getThemePreferences(theme);
for (const pref of prefs) {
this._preferences[pref.property] = this._getThemePrefValue(theme, pref);
}
}
}
const boolPrefsToCollect = [
'zen.view.use-single-toolbar',
'zen.view.sidebar-expanded',
'zen.tabs.vertical.right-side',
'zen.view.experimental-no-window-controls',
'zen.view.hide-window-controls',
...(gZenOperatingSystemCommonUtils.currentOperatingSystem === 'windows' ? ['widget.windows.mica'] : []),
...(gZenOperatingSystemCommonUtils.currentOperatingSystem === 'macos'
? ['widget.macos.titlebar-blend-mode.behind-window']
: []),
];
const stringPrefsToCollect = ['browser.uiCustomization.state'];
for (const pref of boolPrefsToCollect) {
this._preferences[pref] = Services.prefs.getBoolPref(pref);
}
for (const pref of stringPrefsToCollect) {
this._preferences[pref] = Services.prefs.getStringPref(pref);
}
}
async gatherWorkspaceThemes() {
const workspaces = (await ZenWorkspaces._workspaces()).workspaces;
this._workspaceThemes = workspaces.map((w) => w.theme);
}
async packRice(...args) {
await this.gatherAll(...args);
const rice = {
userChrome: this._userChrome,
userContent: this._userContent,
enabledMods: this._enabledMods?.map((t) => t.id),
preferences: this._preferences,
workspaceThemes: this._workspaceThemes,
};
return rice;
}
}
class ZenRiceManager {
constructor() {
this._collector = new ZenRiceCollector();
}
init() {}
get conffettiWrapper() {
if (!this.confetti) {
Services.scriptloader.loadSubScript('chrome://browser/content/zen-vendor/tsparticles.confetti.bundle.min.js', this);
}
return this.confetti;
}
async packRice() {
return await this._collector.packRice();
}
get shareDialog() {
if (this._shareDialog) {
return this._shareDialog;
}
this._shareDialog = window.MozXULElement.parseXULToFragment(`
<vbox id="zen-rice-share-dialog-overlay" hidden="true">
<vbox id="zen-rice-share-dialog-notice">
<h1 data-l10n-id="zen-rice-share-notice" />
<p data-l10n-id="zen-rice-share-notice-description" />
<html:moz-button-group class="panel-footer">
<html:a href="https://docs.zen-browser.app/guides/" target="_blank" data-l10n-id="zen-learn-more-text" onclick="gZenThemePicker.riceManager.openLink(event)" />
<button onclick="gZenThemePicker.riceManager.acceptNotice()" class="footer-button" data-l10n-id="zen-rice-share-accept" slot="primary" default="true" />
</html:moz-button-group>
</vbox>
<vbox id="zen-rice-share-dialog" hidden="true">
<html:img src="chrome://browser/content/zen-images/brand-header.svg" class="zen-rice-share-header" />
<hbox class="zen-rice-share-content">
<vbox id="zen-rice-share-first-form">
<html:input type="text" data-l10n-id="zen-rice-share-name" id="zen-rice-share-name" oninput="gZenThemePicker.riceManager.validateShareDialog()" />
<hbox class="zen-rice-share-author">
<label data-l10n-id="zen-rice-share-author" />
<html:input type="text" data-l10n-id="zen-rice-share-author-input" id="zen-rice-share-author" oninput="gZenThemePicker.riceManager.validateShareDialog();" />
</hbox>
<vbox zen-collapsed="true" id="zen-rice-share-options" onclick="gZenThemePicker.riceManager.toggleOptions(event)">
<hbox class="options-header">
<label data-l10n-id="zen-rice-share-include" />
<image></image>
</hbox>
<checkbox data-l10n-id="zen-rice-share-include-userchrome" id="zen-rice-share-include-userchrome" />
<checkbox data-l10n-id="zen-rice-share-include-usercontent" id="zen-rice-share-include-usercontent" />
<checkbox data-l10n-id="zen-rice-share-include-mods" id="zen-rice-share-include-mods" />
<vbox class="indent">
<checkbox data-l10n-id="zen-rice-share-include-mod-prefs" id="zen-rice-share-include-mod-prefs" />
</vbox>
<checkbox data-l10n-id="zen-rice-share-include-preferences" id="zen-rice-share-include-preferences" />
<checkbox data-l10n-id="zen-rice-share-include-workspace-themes" id="zen-rice-share-include-workspace-themes" />
</vbox>
<html:moz-button-group class="panel-footer">
<button onclick="gZenThemePicker.riceManager.cancel()" class="footer-button" data-l10n-id="zen-general-cancel" />
<button onclick="gZenThemePicker.riceManager.submit()" class="footer-button" data-l10n-id="zen-rice-share-save" default="true" slot="primary" id="zen-rice-share-save" disabled="true" />
</html:moz-button-group>
</vbox>
<vbox id="zen-rice-share-second-form" hidden="true">
<hbox></hbox>
<vbox id="zen-rice-share-error" hidden="true">
<label data-l10n-id="zen-rice-share-error" />
<button onclick="gZenThemePicker.riceManager.resetShareDialog()" data-l10n-id="zen-close-label" class="footer-button" />
</vbox>
</vbox>
<vbox id="zen-rice-share-success" hidden="true">
<h1 data-l10n-id="zen-rice-share-success" />
<p data-l10n-id="zen-rice-share-succes-details" />
<label data-l10n-id="zen-rice-share-success-link" />
<html:input type="text" readonly="true" id="zen-rice-share-success-link" onclick="this.select()" />
<html:moz-button-group class="panel-footer">
<button onclick="gZenThemePicker.riceManager.resetShareDialog()" data-l10n-id="zen-close-label" class="footer-button" slot="primary" default="true" />
</html:moz-button-group>
</vbox>
</hbox>
</vbox>
</vbox>
`);
document.getElementById('zen-main-app-wrapper').appendChild(this._shareDialog);
this._shareDialog = document.getElementById('zen-rice-share-dialog-overlay');
return this._shareDialog;
}
get hasAcceptedNotice() {
return Services.prefs.getBoolPref('zen.rice.share.notice.accepted', false);
}
set hasAcceptedNotice(value) {
Services.prefs.setBoolPref('zen.rice.share.notice.accepted', value);
}
openLink(event) {
event.stopPropagation();
this.cancel();
gZenUIManager.openAndChangeToTab('https://docs.zen-browser.app/guides/');
}
acceptNotice() {
this.hasAcceptedNotice = true;
const notice = document.getElementById('zen-rice-share-dialog-notice');
notice.setAttribute('hidden', 'true');
this.openShareDialog();
}
toggleOptions(event) {
if (event.originalTarget.closest('.options-header')) {
const options = document.getElementById('zen-rice-share-options');
options.setAttribute('zen-collapsed', options.getAttribute('zen-collapsed') === 'true' ? 'false' : 'true');
}
this.validateShareDialog();
}
openShareDialog() {
window.docShell.treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIAppWindow).rollupAllPopups();
const dialog = this.shareDialog;
dialog.removeAttribute('hidden');
if (!this.hasAcceptedNotice) {
const notice = document.getElementById('zen-rice-share-dialog-notice');
notice.removeAttribute('hidden');
return;
}
document.getElementById('zen-rice-share-dialog').removeAttribute('hidden');
document.getElementById('zen-rice-share-dialog-notice').setAttribute('hidden', 'true');
document.getElementById('zen-rice-share-name').focus();
// Initialize the dialog with the current values
this.validateShareDialog();
}
resetShareDialog() {
const dialog = this.shareDialog;
dialog.setAttribute('hidden', 'true');
document.getElementById('zen-rice-share-dialog').removeAttribute('animate');
document.getElementById('zen-rice-share-name').value = '';
document.getElementById('zen-rice-share-author').value = '';
document.getElementById('zen-rice-share-save').disabled = true;
document.getElementById('zen-rice-share-first-form').removeAttribute('fade-out');
document.getElementById('zen-rice-share-second-form').setAttribute('hidden', 'true');
document.getElementById('zen-rice-share-second-form').removeAttribute('fade-out');
document.getElementById('zen-rice-share-error').setAttribute('hidden', 'true');
document.getElementById('zen-rice-share-success').setAttribute('hidden', 'true');
document.getElementById('zen-rice-share-options').setAttribute('zen-collapsed', 'true');
// Remove confetti module from memory
this.confetti = null;
}
cancel() {
this.resetShareDialog();
}
getAllowedRice() {
return {
userChrome: document.getElementById('zen-rice-share-include-userchrome').checked,
userContent: document.getElementById('zen-rice-share-include-usercontent').checked,
mods: document.getElementById('zen-rice-share-include-mods').checked,
modPrefs: document.getElementById('zen-rice-share-include-mod-prefs').checked,
preferences: document.getElementById('zen-rice-share-include-preferences').checked,
workspaceThemes: document.getElementById('zen-rice-share-include-workspace-themes').checked,
};
}
get userAgent() {
return `ZenBrowser/${Services.appinfo.version} (${gZenOperatingSystemCommonUtils.currentOperatingSystem})`;
}
canShareRice() {
const allowedRice = this.getAllowedRice();
const modsPrefs = document.getElementById('zen-rice-share-include-mod-prefs');
// remove "share mod prefs" if mods are not included
if (!allowedRice.mods) {
allowedRice.modPrefs = false;
modsPrefs.disabled = true;
}
modsPrefs.disabled = !allowedRice.mods;
return Object.values(allowedRice).some((v) => v);
}
validateShareDialog() {
const saveButton = document.getElementById('zen-rice-share-save');
const authorInput = document.getElementById('zen-rice-share-author');
const input = document.getElementById('zen-rice-share-name');
saveButton.disabled =
!this.canShareRice() ||
input.value.trim().length < 3 ||
input.value.trim().length > 30 ||
authorInput.value.trim().length < 3 ||
authorInput.value.trim().length > 15;
}
async submit() {
const firstForm = document.getElementById('zen-rice-share-first-form');
const secondForm = document.getElementById('zen-rice-share-second-form');
firstForm.setAttribute('fade-out', 'true');
secondForm.removeAttribute('hidden');
await this._submit();
}
async _submit() {
const allowedRice = this.getAllowedRice();
const rice = await this._collector.packRice(allowedRice);
const name = document.getElementById('zen-rice-share-name').value;
const author = document.getElementById('zen-rice-share-author').value;
const response = await this._sendRice({ name, author, rice });
if (response) {
this.showSuccessDialog(response);
}
}
async _sendRice({ name, author, rice }) {
const headers = new Headers();
headers.append('X-Zen-Rice-Name', name);
headers.append('X-Zen-Rice-Author', author);
headers.append('User-Agent', this.userAgent);
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
let response;
try {
response = await fetch(`${ZEN_RICE_API}/rices`, {
method: 'POST',
headers,
body: JSON.stringify(rice),
});
} catch (e) {
this.showErrorMessage('An error occurred while sharing your rice. Please try again later.');
console.error(e);
return null;
}
// Here, response will never be a null object
return await this._verifyResponse(response);
}
async _verifyResponse(response) {
const json = await response.json();
if (!response.ok) {
const message = json.message || 'An error occurred while sharing your rice.';
this.showErrorMessage(message);
console.error(json);
return null;
}
return json;
}
showErrorMessage(message) {
const errorBox = document.getElementById('zen-rice-share-error');
errorBox.removeAttribute('hidden');
errorBox.querySelector('label').textContent = message;
}
showSuccessDialog(riceInfo) {
const { slug, token } = riceInfo;
// 'token' is like some sort of password to edit the rice, do NOT expose it
setTimeout(() => {
document.getElementById('zen-rice-share-dialog').setAttribute('animate', 'true');
const successBox = document.getElementById('zen-rice-share-success');
document.getElementById('zen-rice-share-second-form').setAttribute('fade-out', 'true');
successBox.removeAttribute('hidden');
const link = document.getElementById('zen-rice-share-success-link');
link.value = `${ZEN_RICE_API}/rices/${slug}`;
this.showConffetti();
}, 2000);
}
showConffetti() {
const end = Date.now() + 2500;
function frame() {
this.conffettiWrapper({
angle: 135,
spread: 55,
particleCount: 5,
startVelocity: 55,
origin: { y: 0.6 },
});
this.conffettiWrapper({
angle: 45,
spread: 55,
particleCount: 5,
startVelocity: 55,
origin: { y: 0.6 },
});
this.conffettiWrapper({
angle: 90,
spread: 55,
particleCount: 5,
startVelocity: 55,
origin: { y: 0.6 },
});
if (Date.now() < end) {
requestAnimationFrame(frame.bind(this));
}
}
frame.call(this);
}
openRicePage({ name, id, author }) {
console.log('Opening rice page: ', name, id, author);
gBrowser.removeTab(gBrowser.selectedTab);
}
}
window.ZenRiceManager = ZenRiceManager;
}

View File

@@ -1,302 +0,0 @@
{
const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(lazy, 'zenTabUnloaderEnabled', 'zen.tab-unloader.enabled', false);
XPCOMUtils.defineLazyPreferenceGetter(lazy, 'zenTabUnloaderTimeout', 'zen.tab-unloader.timeout-minutes', 20);
XPCOMUtils.defineLazyPreferenceGetter(lazy, 'zenTabUnloaderExcludedUrls', 'zen.tab-unloader.excluded-urls', '');
const ZEN_TAB_UNLOADER_DEFAULT_EXCLUDED_URLS = [
'^about:',
'^chrome:',
'^devtools:',
'^file:',
'^resource:',
'^view-source:',
'^view-image:',
];
class ZenTabsObserver {
static ALL_EVENTS = [
'TabAttrModified',
'TabPinned',
'TabUnpinned',
'TabShow',
'TabHide',
'TabOpen',
'TabClose',
'TabSelect',
'TabMultiSelect',
];
#listeners = [];
constructor() {
this.#listenAllEvents();
}
#listenAllEvents() {
const eventListener = this.#eventListener.bind(this);
for (const event of ZenTabsObserver.ALL_EVENTS) {
window.addEventListener(event, eventListener);
}
window.addEventListener('unload', () => {
for (const event of ZenTabsObserver.ALL_EVENTS) {
window.removeEventListener(event, eventListener);
}
});
}
#eventListener(event) {
for (const listener of this.#listeners) {
listener(event.type, event);
}
}
addTabsListener(listener) {
this.#listeners.push(listener);
}
}
class ZenTabsIntervalUnloader {
static INTERVAL = 1000 * 60; // 1 minute
interval = null;
/** @type {ZenTabUnloader} */
unloader = null;
constructor(unloader) {
this.unloader = unloader;
this.interval = setInterval(this.intervalListener.bind(this), ZenTabsIntervalUnloader.INTERVAL);
}
intervalListener() {
if (!lazy.zenTabUnloaderEnabled) {
return;
}
const currentTimestamp = Date.now();
const tabs = ZenWorkspaces.allStoredTabs;
for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i];
if (this.unloader.canUnloadTab(tab, currentTimestamp)) {
this.unloader.unload(tab);
}
}
}
}
class ZenTabUnloader extends ZenDOMOperatedFeature {
static ACTIVITY_MODIFIERS = ['muted', 'soundplaying', 'label', 'attention'];
#excludedUrls = [];
#compiledExcludedUrls = [];
#lastCheckedUrlTimestamp = 0;
constructor() {
super();
this.#excludedUrls = this.lazyExcludeUrls;
if (!lazy.zenTabUnloaderEnabled) {
return;
}
this.intervalUnloader = new ZenTabsIntervalUnloader(this);
}
init() {
if (!lazy.zenTabUnloaderEnabled) {
return;
}
this.insertIntoContextMenu();
this.observer = new ZenTabsObserver();
this.observer.addTabsListener(this.onTabEvent.bind(this));
}
onTabEvent(action, event) {
const tab = event.target;
switch (action) {
case 'TabPinned':
case 'TabUnpinned':
case 'TabShow':
case 'TabHide':
break;
case 'TabAttrModified':
this.handleTabAttrModified(tab, event);
break;
case 'TabOpen':
this.handleTabOpen(tab);
break;
case 'TabClose':
this.handleTabClose(tab);
break;
case 'TabSelect':
case 'TabMultiSelect':
this.updateTabActivity(tab);
break;
default:
console.warn('ZenTabUnloader: Unhandled tab event', action);
break;
}
}
onLocationChange(browser) {
const tab = browser.ownerGlobal.gBrowser.getTabForBrowser(browser);
this.updateTabActivity(tab);
}
handleTabClose(tab) {
tab.lastActivity = null;
}
handleTabOpen(tab) {
this.updateTabActivity(tab);
}
handleTabAttrModified(tab, event) {
for (const modifier of ZenTabUnloader.ACTIVITY_MODIFIERS) {
if (event.detail.changed.includes(modifier)) {
this.updateTabActivity(tab);
break;
}
}
}
updateTabActivity(tab) {
const currentTimestamp = Date.now();
tab.lastActivity = currentTimestamp;
}
insertIntoContextMenu() {
const element = window.MozXULElement.parseXULToFragment(`
<menuseparator/>
<menuitem id="context_zenUnloadTab"
data-lazy-l10n-id="tab-zen-unload"
command="cmd_zenUnloadTab"/>
<menu data-lazy-l10n-id="zen-tabs-unloader-tab-actions" id="context_zenTabActions">
<menupopup>
<menuitem id="context_zenPreventUnloadTab"
data-lazy-l10n-id="tab-zen-prevent-unload"
command="cmd_zenPreventUnloadTab"/>
<menuitem id="context_zenIgnoreUnloadTab"
data-lazy-l10n-id="tab-zen-ignore-unload"
command="cmd_zenIgnoreUnloadTab"/>
</menupopup>
</menu>
`);
document.getElementById('context_closeDuplicateTabs').parentNode.appendChild(element);
}
get lazyExcludeUrls() {
return [
...ZEN_TAB_UNLOADER_DEFAULT_EXCLUDED_URLS,
...lazy.zenTabUnloaderExcludedUrls.split(',').map((url) => url.trim()),
];
}
arraysEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
const currentTimestamp = Date.now();
if (currentTimestamp - this.#lastCheckedUrlTimestamp < 5 * 1000) {
return true;
}
this.#lastCheckedUrlTimestamp = currentTimestamp;
// If you don't care about the order of the elements inside
// the array, you should sort both arrays here.
// Please note that calling sort on an array will modify that array.
// you might want to clone your array first.
for (let i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
get excludedUrls() {
// Check if excludedrls is the same as the pref value
const excludedUrls = this.lazyExcludeUrls;
if (!this.arraysEqual(this.#excludedUrls, excludedUrls) || !this.#compiledExcludedUrls.length) {
this.#excludedUrls = excludedUrls;
this.#compiledExcludedUrls = excludedUrls.map((url) => new RegExp(url));
}
return this.#compiledExcludedUrls;
}
unload(tab, skipPermitUnload = false) {
gBrowser.explicitUnloadTabs([tab], skipPermitUnload);
tab.removeAttribute('linkedpanel');
}
unloadTab() {
const tabs = TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab];
this.explicitUnloadTabs(tabs);
}
explicitUnloadTabs(tabs, extraArgs = {}) {
for (let i = 0; i < tabs.length; i++) {
if (this.canUnloadTab(tabs[i], Date.now(), true, extraArgs)) {
this.unload(tabs[i], true);
}
}
}
preventUnloadTab() {
const tabs = TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab];
for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i];
tab.zenIgnoreUnload = true;
}
}
ignoreUnloadTab() {
const tabs = TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab];
for (let i = 0; i < tabs.length; i++) {
const tab = tabs[i];
tab.zenIgnoreUnload = false;
}
}
canUnloadTab(tab, currentTimestamp, ignoreTimestamp = false, extraArgs = {}) {
if (
(tab.pinned && !ignoreTimestamp) ||
tab.selected ||
(tab.multiselected && !ignoreTimestamp) ||
(tab.hasAttribute('busy') && !ignoreTimestamp) ||
!tab.linkedPanel ||
tab.splitView ||
tab.group?.hasAttribute('split-view-group') ||
tab.attention ||
tab.hasAttribute('glance-id') ||
tab.linkedBrowser?.zenModeActive ||
(tab.pictureinpicture && !ignoreTimestamp) ||
(tab.soundPlaying && !ignoreTimestamp) ||
(tab.zenIgnoreUnload && !ignoreTimestamp) ||
(this.excludedUrls.some((url) => url.test(tab.linkedBrowser?.currentURI.spec)) &&
tab.linkedBrowser?.currentURI.spec !== 'about:blank')
) {
return false;
}
if (ignoreTimestamp) {
return this._tabPermitsUnload(tab, extraArgs);
}
const lastActivity = tab.lastActivity;
if (!lastActivity) {
return false;
}
const diff = currentTimestamp - lastActivity;
// Check if the tab has been inactive for more than the timeout
return diff > lazy.zenTabUnloaderTimeout * 60 * 1000 && this._tabPermitsUnload(tab, extraArgs);
}
_tabPermitsUnload(tab, extraArgs) {
return typeof extraArgs.permitUnload === 'undefined'
? tab.linkedBrowser?.permitUnload()?.permitUnload
: extraArgs.permitUnload;
}
}
window.gZenTabUnloader = new ZenTabUnloader();
}

View File

@@ -1,120 +0,0 @@
var ZenThemesCommon = {
kZenColors: ['#aac7ff', '#74d7cb', '#a0d490', '#dec663', '#ffb787', '#dec1b1', '#ffb1c0', '#ddbfc3', '#f6b0ea', '#d4bbff'],
get browsers() {
return Services.wm.getEnumerator('navigator:browser');
},
get currentBrowser() {
return Services.wm.getMostRecentWindow('navigator:browser');
},
get themesRootPath() {
return PathUtils.join(PathUtils.profileDir, 'chrome', 'zen-themes');
},
get themesDataFile() {
return PathUtils.join(PathUtils.profileDir, 'zen-themes.json');
},
getThemeFolder(themeId) {
return PathUtils.join(this.themesRootPath, themeId);
},
resetThemesCache() {
this.themes = null;
},
async getThemes() {
if (!this.themes) {
if (!(await IOUtils.exists(this.themesDataFile))) {
await IOUtils.writeJSON(this.themesDataFile, {});
}
try {
this.themes = await IOUtils.readJSON(this.themesDataFile);
} catch (e) {
// If we have a corrupted file, reset it
await IOUtils.writeJSON(this.themesDataFile, {});
this.themes = {};
gNotificationBox.appendNotification(
'zen-themes-corrupted',
{
label: { 'l10n-id': 'zen-themes-corrupted' },
image: 'chrome://browser/skin/notification-icons/persistent-storage-blocked.svg',
priority: gNotificationBox.PRIORITY_WARNING_HIGH,
},
[]
);
}
}
return this.themes;
},
async getThemePreferences(theme) {
const themePath = PathUtils.join(this.themesRootPath, theme.id, 'preferences.json');
if (!(await IOUtils.exists(themePath)) || !theme.preferences) {
return [];
}
const preferences = await IOUtils.readJSON(themePath);
// compat mode for old preferences, all of them can only be checkboxes
if (typeof preferences === 'object' && !Array.isArray(preferences)) {
console.warn(
`[ZenThemes]: Warning, ${theme.name} uses legacy preferences, please migrate them to the new preferences style, as legacy preferences might be removed at a future release. More information at: https://docs.zen-browser.app/themes-store/themes-marketplace-preferences`
);
const newThemePreferences = [];
for (let [entry, label] of Object.entries(preferences)) {
const [_, negation = '', os = '', property] = /(!?)(?:(macos|windows|linux):)?([A-z0-9-_.]+)/g.exec(entry);
const isNegation = negation === '!';
if (
(isNegation && os === gZenOperatingSystemCommonUtils.currentOperatingSystem) ||
(os !== '' && os !== gZenOperatingSystemCommonUtils.currentOperatingSystem && !isNegation)
) {
continue;
}
newThemePreferences.push({
property,
label,
type: 'checkbox',
disabledOn: os !== '' ? [os] : [],
});
}
return newThemePreferences;
}
return preferences.filter(
({ disabledOn = [] }) => !disabledOn.includes(gZenOperatingSystemCommonUtils.currentOperatingSystem)
);
},
throttle(mainFunction, delay) {
let timerFlag = null;
return (...args) => {
if (timerFlag === null) {
mainFunction(...args);
timerFlag = setTimeout(() => {
timerFlag = null;
}, delay);
}
};
},
debounce(mainFunction, wait) {
let timerFlag;
return (...args) => {
clearTimeout(timerFlag);
timerFlag = setTimeout(() => {
mainFunction(...args);
}, wait);
};
},
};

View File

@@ -1,317 +0,0 @@
const kZenStylesheetThemeHeader = '/* Zen Themes - Generated by ZenThemesImporter.';
const kZenStylesheetThemeHeaderBody = `* DO NOT EDIT THIS FILE DIRECTLY!
* Your changes will be overwritten.
* Instead, go to the preferences and edit the themes there.
*/
`;
const kenStylesheetFooter = `
/* End of Zen Themes */
`;
const getCurrentDateTime = () =>
new Intl.DateTimeFormat('en-US', {
dateStyle: 'full',
timeStyle: 'full',
}).format(new Date().getTime());
var gZenStylesheetManager = {
async writeStylesheet(path, themes) {
let content = kZenStylesheetThemeHeader;
content += `\n* FILE GENERATED AT: ${getCurrentDateTime()}\n`;
content += kZenStylesheetThemeHeaderBody;
for (let theme of themes) {
if (theme.enabled !== undefined && !theme.enabled) {
continue;
}
content += this.getThemeCSS(theme);
}
content += kenStylesheetFooter;
const buffer = new TextEncoder().encode(content);
await IOUtils.write(path, buffer);
},
getThemeCSS(theme) {
let css = '\n';
css += `/* Name: ${theme.name} */\n`;
css += `/* Description: ${theme.description} */\n`;
css += `/* Author: @${theme.author} */\n`;
if (theme._readmeURL) {
css += `/* Readme: ${theme.readme} */\n`;
}
css += `@import url("${theme._chromeURL}");\n`;
return css;
},
};
var gZenThemesImporter = new (class {
constructor() {
try {
window.SessionStore.promiseInitialized.then(async () => {
if (Services.prefs.getBoolPref('zen.themes.disable-all', false) || Services.appinfo.inSafeMode) {
console.log('[ZenThemesImporter]: Disabling all themes.');
return;
}
const themes = await this.getEnabledThemes();
const themesWithPreferences = await Promise.all(
themes.map(async (theme) => {
const preferences = await ZenThemesCommon.getThemePreferences(theme);
return {
name: theme.name,
enabled: theme.enabled,
preferences,
};
})
);
this.writeToDom(themesWithPreferences);
await this.insertStylesheet();
});
console.info('[ZenThemesImporter]: Zen Themes imported');
} catch (e) {
console.error('[ZenThemesImporter]: Error importing Zen Themes: ', e);
}
Services.prefs.addObserver('zen.themes.updated-value-observer', this.rebuildThemeStylesheet.bind(this), false);
Services.prefs.addObserver('zen.themes.disable-all', this.handleDisableThemes.bind(this), false);
}
get sss() {
if (!this._sss) {
this._sss = Cc['@mozilla.org/content/style-sheet-service;1'].getService(Ci.nsIStyleSheetService);
}
return this._sss;
}
get styleSheetPath() {
return PathUtils.join(PathUtils.profileDir, 'chrome', 'zen-themes.css');
}
async handleDisableThemes() {
if (Services.prefs.getBoolPref('zen.themes.disable-all', false)) {
console.log('[ZenThemesImporter]: Disabling themes module.');
await this.removeStylesheet();
} else {
console.log('[ZenThemesImporter]: Enabling themes module.');
await this.rebuildThemeStylesheet();
}
}
get styleSheetURI() {
if (!this._styleSheetURI) {
this._styleSheetURI = Services.io.newFileURI(new FileUtils.File(this.styleSheetPath));
}
return this._styleSheetURI;
}
getStylesheetURIForTheme(theme) {
return Services.io.newFileURI(new FileUtils.File(PathUtils.join(ZenThemesCommon.getThemeFolder(theme.id), 'chrome.css')));
}
async insertStylesheet() {
if (await IOUtils.exists(this.styleSheetPath)) {
await this.sss.loadAndRegisterSheet(this.styleSheetURI, this.sss.AGENT_SHEET);
}
if (this.sss.sheetRegistered(this.styleSheetURI, this.sss.AGENT_SHEET)) {
console.debug('[ZenThemesImporter]: Sheet successfully registered');
}
}
async removeStylesheet() {
await this.sss.unregisterSheet(this.styleSheetURI, this.sss.AGENT_SHEET);
await IOUtils.remove(this.styleSheetPath, { ignoreAbsent: true });
if (!this.sss.sheetRegistered(this.styleSheetURI, this.sss.AGENT_SHEET) && !(await IOUtils.exists(this.styleSheetPath))) {
console.debug('[ZenThemesImporter]: Sheet successfully unregistered');
}
}
async rebuildThemeStylesheet() {
if (Services.focus.activeWindow !== window) {
return;
}
ZenThemesCommon.themes = null;
await this.removeStylesheet();
const themes = await this.getEnabledThemes();
await this.writeStylesheet(themes);
const themesWithPreferences = await Promise.all(
themes.map(async (theme) => {
const preferences = await ZenThemesCommon.getThemePreferences(theme);
return {
name: theme.name,
enabled: theme.enabled,
preferences,
};
})
);
this.setDefaults(themesWithPreferences);
this.writeToDom(themesWithPreferences);
await this.insertStylesheet();
}
async getEnabledThemes() {
const themeObject = await ZenThemesCommon.getThemes();
const themes = Object.values(themeObject).filter((theme) => theme.enabled === undefined || theme.enabled);
const themeList = themes.map(({ name }) => name).join(', ');
const message =
themeList !== ''
? `[ZenThemesImporter]: Loading enabled Zen themes: ${themeList}.`
: '[ZenThemesImporter]: No enabled Zen themes.';
console.log(message);
return themes;
}
setDefaults(themesWithPreferences) {
for (const { preferences, enabled } of themesWithPreferences) {
if (enabled !== undefined && !enabled) {
continue;
}
for (const { type, property, defaultValue } of preferences) {
if (defaultValue === undefined) {
continue;
}
switch (type) {
case 'checkbox': {
const value = Services.prefs.getBoolPref(property, false);
if (typeof defaultValue !== 'boolean') {
console.log(`[ZenThemesImporter]: Warning, invalid data type received for expected type boolean, skipping.`);
continue;
}
if (!value) {
Services.prefs.setBoolPref(property, defaultValue);
}
break;
}
default: {
const value = Services.prefs.getStringPref(property, 'zen-property-no-saved');
if (typeof defaultValue !== 'string' && typeof defaultValue !== 'number') {
console.log(`[ZenThemesImporter]: Warning, invalid data type received (${typeof defaultValue}), skipping.`);
continue;
}
if (value === 'zen-property-no-saved') {
Services.prefs.setStringPref(property, defaultValue.toString());
}
}
}
}
}
}
writeToDom(themesWithPreferences) {
for (const browser of ZenMultiWindowFeature.browsers) {
for (const { enabled, preferences, name } of themesWithPreferences) {
const sanitizedName = `theme-${name?.replaceAll(/\s/g, '-')?.replaceAll(/[^A-z_-]+/g, '')}`;
if (enabled !== undefined && !enabled) {
const element = browser.document.getElementById(sanitizedName);
if (element) {
element.remove();
}
for (const { property } of preferences.filter(({ type }) => type !== 'checkbox')) {
const sanitizedProperty = property?.replaceAll(/\./g, '-');
browser.document.querySelector(':root').style.removeProperty(`--${sanitizedProperty}`);
}
continue;
}
for (const { property, type } of preferences) {
const value = Services.prefs.getStringPref(property, '');
const sanitizedProperty = property?.replaceAll(/\./g, '-');
switch (type) {
case 'dropdown': {
if (value !== '') {
let element = browser.document.getElementById(sanitizedName);
if (!element) {
element = browser.document.createElement('div');
element.style.display = 'none';
element.setAttribute('id', sanitizedName);
browser.document.body.appendChild(element);
}
element.setAttribute(sanitizedProperty, value);
}
break;
}
case 'string': {
if (value === '') {
browser.document.querySelector(':root').style.removeProperty(`--${sanitizedProperty}`);
} else {
browser.document.querySelector(':root').style.setProperty(`--${sanitizedProperty}`, value);
}
break;
}
default: {
}
}
}
}
}
}
async writeStylesheet(themeList = []) {
const themes = [];
ZenThemesCommon.themes = null;
for (let theme of themeList) {
theme._chromeURL = this.getStylesheetURIForTheme(theme).spec;
themes.push(theme);
}
await gZenStylesheetManager.writeStylesheet(this.styleSheetPath, themes);
}
})();
gZenActorsManager.addJSWindowActor('ZenThemeMarketplace', {
parent: {
esModuleURI: 'chrome://browser/content/zen-components/actors/ZenThemeMarketplaceParent.sys.mjs',
},
child: {
esModuleURI: 'chrome://browser/content/zen-components/actors/ZenThemeMarketplaceChild.sys.mjs',
events: {
DOMContentLoaded: {},
},
},
matches: [...Services.prefs.getStringPref('zen.injections.match-urls').split(','), 'about:preferences'],
});

View File

@@ -1,62 +0,0 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
BrowserWindowTracker: 'resource:///modules/BrowserWindowTracker.sys.mjs',
});
class ZenUIMigration {
PREF_NAME = 'zen.migration.version';
MIGRATION_VERSION = 1;
init(isNewProfile, win) {
if (!isNewProfile) {
this._migrate(win);
}
this.clearVariables();
}
get _migrationVersion() {
return Services.prefs.getIntPref(this.PREF_NAME, 0);
}
set _migrationVersion(value) {
Services.prefs.setIntPref(this.PREF_NAME, value);
}
_migrate(win) {
if (this._migrationVersion < 1) {
this._migrateV1(win);
}
}
clearVariables() {
this._migrationVersion = this.MIGRATION_VERSION;
}
async _migrateV1(win) {
// Introduction of the new URL bar, show a message to the user
const notification = win.gNotificationBox.appendNotification(
'zen-new-urlbar-notification',
{
label: { 'l10n-id': 'zen-new-urlbar-notification' },
image: 'chrome://browser/skin/notification-icons/persistent-storage-blocked.svg',
priority: win.gNotificationBox.PRIORITY_WARNING_HIGH,
},
[
{
'l10n-id': 'zen-disable',
accessKey: 'D',
callback: () => {
Services.prefs.setBoolPref('zen.urlbar.replace-newtab', false);
},
},
{
link: 'https://docs.zen-browser.app/user-manual/urlbar/',
'l10n-id': 'zen-learn-more-text',
},
]
);
}
}
export var gZenUIMigration = new ZenUIMigration();

File diff suppressed because it is too large Load Diff

View File

@@ -1,547 +0,0 @@
{
var _tabsToPin = [];
var _tabsToPinEssentials = [];
function clearBrowserElements() {
for (const element of document.getElementById('browser').children) {
element.style.display = 'none';
}
}
function getMotion() {
return gZenUIManager.motion;
}
async function animate(...args) {
return getMotion().animate(...args);
}
function initializeZenWelcome() {
document.documentElement.setAttribute('zen-welcome-stage', 'true');
const XUL = `
<html:div id="zen-welcome">
<html:div id="zen-welcome-start">
<html:h1 class="zen-branding-title" id="zen-welcome-title"></html:h1>
<button class="footer-button primary" id="zen-welcome-start-button">
</button>
</html:div>
<hbox id="zen-welcome-pages">
<vbox id="zen-welcome-page-sidebar">
<vbox id="zen-welcome-page-sidebar-content">
</vbox>
<vbox id="zen-welcome-page-sidebar-buttons">
</vbox>
</vbox>
<html:div id="zen-welcome-page-content">
</html:div>
</hbox>
</html:div>
`;
const fragment = window.MozXULElement.parseXULToFragment(XUL);
document.getElementById('browser').appendChild(fragment);
window.MozXULElement.insertFTLIfNeeded('browser/zen-welcome.ftl');
}
function openInitialPinTab() {
const tabs = ['https://reddit.com/r/zen_browser', 'https://x.com/zen_browser'];
for (const url of tabs) {
const tab = window.gBrowser.addTrustedTab(url, {
inBackground: true,
});
_tabsToPin.push(tab);
}
}
class ZenWelcomePages {
constructor(pages) {
this._currentPage = -1;
this._pages = pages;
this.init();
this.next();
}
init() {
document.getElementById('zen-welcome-pages').style.display = 'flex';
document.getElementById('zen-welcome-start').remove();
window.maximize();
animate('#zen-welcome-pages', { opacity: [0, 1] }, { delay: 0.2, duration: 0.2 });
}
async fadeInTitles(page) {
const [title1, description1, description2] = await document.l10n.formatValues(page.text);
const titleElement = document.getElementById('zen-welcome-page-sidebar-content');
titleElement.innerHTML =
`<html:h1>${title1}</html:h1><html:p>${description1}</html:p>` +
(description2 ? `<html:p>${description2}</html:p>` : '');
await animate(
'#zen-welcome-page-sidebar-content > *',
{ x: ['150%', 0] },
{
delay: getMotion().stagger(0.05),
type: 'spring',
bounce: 0.2,
}
);
}
async fadeInButtons(page) {
const buttons = document.getElementById('zen-welcome-page-sidebar-buttons');
let i = 0;
for (const button of page.buttons) {
const buttonElement = document.createXULElement('button');
document.l10n.setAttributes(buttonElement, button.l10n);
if (i++ === 0) {
buttonElement.classList.add('primary');
}
buttonElement.classList.add('footer-button');
buttonElement.addEventListener('click', async () => {
const shouldSkip = await button.onclick();
if (shouldSkip) {
this.next();
}
});
buttons.appendChild(buttonElement);
}
await animate(
'#zen-welcome-page-sidebar-buttons button',
{ x: ['150%', 0] },
{
delay: getMotion().stagger(0.1, { startDelay: 0.4 }),
type: 'spring',
bounce: 0.2,
}
);
}
async fadeInContent() {
await animate(
'#zen-welcome-page-content > *',
{ opacity: [0, 1] },
{
delay: getMotion().stagger(0.1),
type: 'spring',
bounce: 0.2,
}
);
}
async fadeOutButtons() {
await animate(
'#zen-welcome-page-sidebar-buttons button',
{ x: [0, '-150%'] },
{
type: 'spring',
bounce: 0,
delay: getMotion().stagger(0.1, { startDelay: 0.4 }),
}
);
document.getElementById('zen-welcome-page-sidebar-buttons').innerHTML = '';
document.getElementById('zen-welcome-page-sidebar-content').innerHTML = '';
}
async fadeOutTitles() {
await animate(
'#zen-welcome-page-sidebar-content > *',
{ x: [0, '-150%'] },
{
delay: getMotion().stagger(0.05, { startDelay: 0.3 }),
type: 'spring',
bounce: 0,
}
);
}
async fadeOutContent() {
await animate(
'#zen-welcome-page-content > *',
{ opacity: [1, 0] },
{
delay: getMotion().stagger(0.05, { startDelay: 0.3 }),
type: 'spring',
bounce: 0,
duration: 0.2,
}
);
}
async next() {
if (this._currentPage !== -1) {
const previousPage = this._pages[this._currentPage];
const promises = [this.fadeOutTitles(), this.fadeOutButtons()];
if (!previousPage.dontFadeOut) {
promises.push(this.fadeOutContent());
}
await Promise.all(promises);
await previousPage.fadeOut();
document.getElementById('zen-welcome-page-content').innerHTML = '';
}
this._currentPage++;
const currentPage = this._pages[this._currentPage];
if (!currentPage) {
this.finish();
return;
}
await Promise.all([this.fadeInTitles(currentPage), this.fadeInButtons(currentPage)]);
currentPage.fadeIn();
await this.fadeInContent();
}
async finish() {
ZenWorkspaces.reorganizeTabsAfterWelcome();
await animate('#zen-welcome-page-content', { x: [0, '100%'] }, { bounce: 0 });
document.getElementById('zen-welcome-page-content').remove();
await this.animHeart();
this._pinRemainingTabs();
await animate('#zen-welcome-pages', { opacity: [1, 0] });
document.getElementById('zen-welcome').remove();
document.documentElement.removeAttribute('zen-welcome-stage');
for (const element of document.getElementById('browser').children) {
element.style.opacity = 0;
element.style.removeProperty('display');
}
gZenUIManager.updateTabsToolbar();
await animate('#browser > *', { opacity: [0, 1] });
gZenUIManager.showToast('zen-welcome-finished');
}
_pinRemainingTabs() {
for (const tab of _tabsToPin) {
gBrowser.pinTab(tab);
}
for (const tab of _tabsToPinEssentials) {
gZenPinnedTabManager.addToEssentials(tab);
}
}
async animHeart() {
const heart = document.createElement('div');
heart.id = 'zen-welcome-heart';
const sidebar = document.getElementById('zen-welcome-page-sidebar');
sidebar.style.width = '100%';
sidebar.appendChild(heart);
sidebar.setAttribute('animate-heart', 'true');
await animate(
'#zen-welcome-heart',
{ opacity: [0, 1, 1, 1, 0], scale: [0.5, 1, 1.2, 1, 1.2] },
{
duration: 1.5,
delay: 0.2,
bounce: 0,
}
);
}
}
function getWelcomePages() {
return [
{
text: [
{
id: 'zen-welcome-import-title',
},
{
id: 'zen-welcome-import-description-1',
},
{
id: 'zen-welcome-import-description-2',
},
],
buttons: [
{
l10n: 'zen-welcome-import-button',
onclick: async () => {
MigrationUtils.showMigrationWizard(window, {
isStartupMigration: true,
});
document.querySelector('#zen-welcome-page-sidebar-buttons button').remove();
const newButton = document.querySelector('#zen-welcome-page-sidebar-buttons button');
newButton.classList.add('primary');
document.l10n.setAttributes(newButton, 'zen-welcome-next-action');
return false;
},
},
{
l10n: 'zen-welcome-skip-button',
onclick: async () => {
return true;
},
},
],
fadeIn() {
const xul = `
<html:label for="zen-welcome-set-default-browser">
<html:input type="radio" id="zen-welcome-set-default-browser" name="zen-welcome-set-default-browser"></html:input>
<html:span data-l10n-id="zen-welcome-set-default-browser"></html:span>
</html:label>
<html:label for="zen-welcome-dont-set-default-browser">
<html:input checked="true" type="radio" id="zen-welcome-dont-set-default-browser" name="zen-welcome-set-default-browser"></html:input>
<html:span data-l10n-id="zen-welcome-dont-set-default-browser"></html:span>
</html:label>
`;
const fragment = window.MozXULElement.parseXULToFragment(xul);
document.getElementById('zen-welcome-page-content').appendChild(fragment);
},
async fadeOut() {
const shouldSetDefault = document.getElementById('zen-welcome-set-default-browser').checked;
if (AppConstants.HAVE_SHELL_SERVICE && shouldSetDefault) {
let shellSvc = getShellService();
if (!shellSvc) {
return;
}
try {
await shellSvc.setDefaultBrowser(false);
} catch (ex) {
console.error(ex);
return;
}
}
},
},
{
text: [
{
id: 'zen-welcome-initial-essentials-title',
},
{
id: 'zen-welcome-initial-essentials-description-1',
},
{
id: 'zen-welcome-initial-essentials-description-2',
},
],
buttons: [
{
l10n: 'zen-welcome-next-action',
onclick: async () => {
return true;
},
},
],
fadeIn() {
const xul = `
<hbox id="zen-welcome-initial-essentials-browser">
<vbox id="zen-welcome-initial-essentials-browser-sidebar">
<hbox id="zen-welcome-initial-essentials-browser-sidebar-win-buttons">
<html:div></html:div>
<html:div></html:div>
<html:div></html:div>
</hbox>
<html:div id="zen-welcome-initial-essentials-browser-sidebar-essentials">
<html:div class="tabbrowser-tab" fadein="" data-url="https://web.whatsapp.com" style="--zen-tab-icon: url('https://web.whatsapp.com/favicon.ico');">
<stack class="tab-stack">
<html:div class="tab-background"></html:div>
</stack>
</html:div>
<html:div class="tabbrowser-tab" fadein="" visuallyselected="" data-url="https://discord.com" style="--zen-tab-icon: url('https://www.google.com/s2/favicons?domain=discord.com');">
<stack class="tab-stack">
<html:div class="tab-background"></html:div>
</stack>
</html:div>
<html:div class="tabbrowser-tab" fadein="" data-url="https://trello.com" style="--zen-tab-icon: url('https://trello.com/favicon.ico');">
<stack class="tab-stack">
<html:div class="tab-background"></html:div>
</stack>
</html:div>
<html:div class="tabbrowser-tab" fadein="" data-url="https://slack.com/" style="--zen-tab-icon: url('https://a.slack-edge.com/80588/marketing/img/meta/favicon-32.png');">
<stack class="tab-stack">
<html:div class="tab-background"></html:div>
</stack>
</html:div>
<html:div class="tabbrowser-tab" fadein="" data-url="https://github.com" style="--zen-tab-icon: url('https://github.githubassets.com/favicons/favicon-dark.png');">
<stack class="tab-stack">
<html:div class="tab-background"></html:div>
</stack>
</html:div>
<html:div class="tabbrowser-tab" fadein="" data-url="https://twitter.com" style="--zen-tab-icon: url('https://abs.twimg.com/favicons/twitter.ico');">
<stack class="tab-stack">
<html:div class="tab-background"></html:div>
</stack>
</html:div>
<html:div class="tabbrowser-tab" fadein="" visuallyselected="" data-url="https://notion.com" style="--zen-tab-icon: url('https://www.notion.so/front-static/favicon.ico');">
<stack class="tab-stack">
<html:div class="tab-background"></html:div>
</stack>
</html:div>
<html:div class="tabbrowser-tab" fadein="" visuallyselected="" data-url="https://calendar.google.com" style="--zen-tab-icon: url('https://calendar.google.com/googlecalendar/images/favicons_2020q4/calendar_6.ico');">
<stack class="tab-stack">
<html:div class="tab-background"></html:div>
</stack>
</html:div>
<html:div class="tabbrowser-tab" fadein="" data-url="https://youtube.com" style="--zen-tab-icon: url('https://www.youtube.com/favicon.ico');">
<stack class="tab-stack">
<html:div class="tab-background"></html:div>
</stack>
</html:div>
<html:div class="extra-tab"></html:div>
<html:div class="extra-tab"></html:div>
</html:div>
</vbox>
</hbox>
`;
const fragment = window.MozXULElement.parseXULToFragment(xul);
document.getElementById('zen-welcome-page-content').appendChild(fragment);
document
.getElementById('zen-welcome-initial-essentials-browser-sidebar-essentials')
.addEventListener('click', async (event) => {
const tab = event.target.closest('.tabbrowser-tab');
if (!tab) {
return;
}
tab.toggleAttribute('visuallyselected');
});
},
fadeOut() {
const selectedTabs = document
.getElementById('zen-welcome-initial-essentials-browser-sidebar-essentials')
.querySelectorAll('.tabbrowser-tab[visuallyselected]');
for (const tab of selectedTabs) {
const url = tab.getAttribute('data-url');
const createdTab = window.gBrowser.addTrustedTab(url, {
inBackground: true,
});
_tabsToPinEssentials.push(createdTab);
}
openInitialPinTab();
},
},
{
text: [
{
id: 'zen-welcome-workspace-colors-title',
},
{
id: 'zen-welcome-workspace-colors-description',
},
],
buttons: [
{
l10n: 'zen-welcome-next-action',
onclick: async () => {
return true;
},
},
],
fadeIn() {
const anchor = document.createElement('div');
anchor.id = 'zen-welcome-workspace-colors-anchor';
document.getElementById('zen-welcome-page-content').appendChild(anchor);
gZenThemePicker.panel.setAttribute('noautohide', 'true');
gZenThemePicker.panel.setAttribute('consumeoutsideclicks', 'false');
gZenThemePicker.panel.addEventListener(
'popupshowing',
() => {
const panelRect = gZenThemePicker.panel.getBoundingClientRect();
// 20 is the shadow width * 2
anchor.style.height = panelRect.height - 20 + 'px';
anchor.style.width = panelRect.width - 20 + 'px';
},
{ once: true }
);
PanelMultiView.openPopup(gZenThemePicker.panel, anchor, {
position: 'overlap',
});
},
dontFadeOut: true,
async fadeOut() {
gZenThemePicker.panel.removeAttribute('noautohide');
gZenThemePicker.panel.removeAttribute('consumeoutsideclicks');
await animate(gZenThemePicker.panel, { opacity: [1, 0] });
gZenThemePicker.panel.hidePopup();
gZenThemePicker.panel.removeAttribute('style');
},
},
{
text: [
{
id: 'zen-welcome-start-browsing-title',
},
{
id: 'zen-welcome-start-browsing-description-1',
},
],
buttons: [
{
l10n: 'zen-welcome-start-browsing',
onclick: async () => {
return true;
},
},
],
fadeIn() {},
fadeOut() {},
},
];
}
async function animateInitialStage() {
const [title1, title2] = await document.l10n.formatValues([
{ id: 'zen-welcome-title-line1' },
{ id: 'zen-welcome-title-line2' },
]);
const titleElement = document.getElementById('zen-welcome-title');
titleElement.innerHTML = `<html:span>${title1}</html:span><html:span>${title2}</html:span>`;
await animate(
'#zen-welcome-title span',
{ opacity: [0, 1], y: [20, 0], filter: ['blur(2px)', 'blur(0px)'] },
{
delay: getMotion().stagger(0.6, { startDelay: 0.2 }),
type: 'spring',
stiffness: 300,
damping: 20,
mass: 1.8,
}
);
const button = document.getElementById('zen-welcome-start-button');
await animate(
button,
{ opacity: [0, 1], y: [20, 0], filter: ['blur(2px)', 'blur(0px)'] },
{
delay: 0.1,
type: 'spring',
stiffness: 300,
damping: 20,
mass: 1.8,
}
);
button.addEventListener('click', async () => {
await animate(
'#zen-welcome-title span, #zen-welcome-start-button',
{ opacity: [1, 0], y: [0, -10], filter: ['blur(0px)', 'blur(2px)'] },
{
type: 'spring',
ease: [0.755, 0.05, 0.855, 0.06],
bounce: 0.4,
delay: getMotion().stagger(0.4),
}
);
new ZenWelcomePages(getWelcomePages());
});
}
function centerWindowOnScreen() {
window.addEventListener(
'MozAfterPaint',
function () {
window.resizeTo(875, 560);
window.focus();
const appWin = window.docShell.treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIAppWindow);
appWin.rollupAllPopups();
window.moveTo(
screen.availLeft + (screen.availWidth - outerWidth) / 2,
screen.availTop + (screen.availHeight - outerHeight) / 2
);
},
{ once: true }
);
}
function startZenWelcome() {
clearBrowserElements();
centerWindowOnScreen();
initializeZenWelcome();
animateInitialStage();
}
startZenWelcome();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,612 +0,0 @@
var ZenWorkspacesStorage = {
lazy: {},
async init() {
ChromeUtils.defineESModuleGetters(this.lazy, {
PlacesUtils: 'resource://gre/modules/PlacesUtils.sys.mjs',
Weave: 'resource://services-sync/main.sys.mjs',
});
if (!window.ZenWorkspaces) return;
await this._ensureTable();
await ZenWorkspaceBookmarksStorage.init();
},
async _ensureTable() {
await this.lazy.PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage._ensureTable', async (db) => {
// Create the main workspaces table if it doesn't exist
await db.execute(`
CREATE TABLE IF NOT EXISTS zen_workspaces (
id INTEGER PRIMARY KEY,
uuid TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
icon TEXT,
is_default INTEGER NOT NULL DEFAULT 0,
container_id INTEGER,
position INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Add new columns if they don't exist
// SQLite doesn't have a direct "ADD COLUMN IF NOT EXISTS" syntax,
// so we need to check if the columns exist first
const columns = await db.execute(`PRAGMA table_info(zen_workspaces)`);
const columnNames = columns.map((row) => row.getResultByName('name'));
// Helper function to add column if it doesn't exist
const addColumnIfNotExists = async (columnName, definition) => {
if (!columnNames.includes(columnName)) {
await db.execute(`ALTER TABLE zen_workspaces ADD COLUMN ${columnName} ${definition}`);
}
};
// Add each new column if it doesn't exist
await addColumnIfNotExists('theme_type', 'TEXT');
await addColumnIfNotExists('theme_colors', 'TEXT');
await addColumnIfNotExists('theme_opacity', 'REAL');
await addColumnIfNotExists('theme_rotation', 'INTEGER');
await addColumnIfNotExists('theme_texture', 'REAL');
// Create an index on the uuid column
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_zen_workspaces_uuid ON zen_workspaces(uuid)
`);
// Create the changes tracking table if it doesn't exist
await db.execute(`
CREATE TABLE IF NOT EXISTS zen_workspaces_changes (
uuid TEXT PRIMARY KEY,
timestamp INTEGER NOT NULL
)
`);
// Create an index on the uuid column for changes tracking table
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_zen_workspaces_changes_uuid ON zen_workspaces_changes(uuid)
`);
if (!this.lazy.Weave.Service.engineManager.get('workspaces')) {
this.lazy.Weave.Service.engineManager.register(ZenWorkspacesEngine);
await ZenWorkspacesStorage.migrateWorkspacesFromJSON();
}
ZenWorkspaces._resolveDBInitialized();
});
},
async migrateWorkspacesFromJSON() {
const oldWorkspacesPath = PathUtils.join(PathUtils.profileDir, 'zen-workspaces', 'Workspaces.json');
if (await IOUtils.exists(oldWorkspacesPath)) {
console.info('ZenWorkspacesStorage: Migrating workspaces from JSON...');
const oldWorkspaces = await IOUtils.readJSON(oldWorkspacesPath);
if (oldWorkspaces.workspaces) {
for (const workspace of oldWorkspaces.workspaces) {
await this.saveWorkspace(workspace);
}
}
await IOUtils.remove(oldWorkspacesPath);
}
},
/**
* Private helper method to notify observers with a list of changed UUIDs.
* @param {string} event - The observer event name.
* @param {Array<string>} uuids - Array of changed workspace UUIDs.
*/
_notifyWorkspacesChanged(event, uuids) {
if (uuids.length === 0) return; // No changes to notify
// Convert the array of UUIDs to a JSON string
const data = JSON.stringify(uuids);
Services.obs.notifyObservers(null, event, data);
},
async saveWorkspace(workspace, notifyObservers = true) {
const changedUUIDs = new Set();
await this.lazy.PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.saveWorkspace', async (db) => {
await db.executeTransaction(async () => {
const now = Date.now();
// Handle default workspace
if (workspace.default) {
await db.execute(`UPDATE zen_workspaces SET is_default = 0 WHERE uuid != :uuid`, { uuid: workspace.uuid });
const unsetDefaultRows = await db.execute(`SELECT uuid FROM zen_workspaces WHERE is_default = 0 AND uuid != :uuid`, {
uuid: workspace.uuid,
});
for (const row of unsetDefaultRows) {
changedUUIDs.add(row.getResultByName('uuid'));
}
}
let newPosition;
if ('position' in workspace && Number.isFinite(workspace.position)) {
newPosition = workspace.position;
} else {
// Get the maximum position
const maxPositionResult = await db.execute(`SELECT MAX("position") as max_position FROM zen_workspaces`);
const maxPosition = maxPositionResult[0].getResultByName('max_position') || 0;
newPosition = maxPosition + 1000; // Add a large increment to avoid frequent reordering
}
// Insert or replace the workspace
await db.executeCached(
`
INSERT OR REPLACE INTO zen_workspaces (
uuid, name, icon, is_default, container_id, created_at, updated_at, "position",
theme_type, theme_colors, theme_opacity, theme_rotation, theme_texture
) VALUES (
:uuid, :name, :icon, :is_default, :container_id,
COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = :uuid), :now),
:now,
:position,
:theme_type, :theme_colors, :theme_opacity, :theme_rotation, :theme_texture
)
`,
{
uuid: workspace.uuid,
name: workspace.name,
icon: workspace.icon || null,
is_default: workspace.default ? 1 : 0,
container_id: workspace.containerTabId || null,
now,
position: newPosition,
theme_type: workspace.theme?.type || null,
theme_colors: workspace.theme ? JSON.stringify(workspace.theme.gradientColors) : null,
theme_opacity: workspace.theme?.opacity || null,
theme_rotation: workspace.theme?.rotation || null,
theme_texture: workspace.theme?.texture || null,
}
);
// Record the change
await db.execute(
`
INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
`,
{
uuid: workspace.uuid,
timestamp: Math.floor(now / 1000),
}
);
changedUUIDs.add(workspace.uuid);
await this.updateLastChangeTimestamp(db);
});
});
if (notifyObservers) {
this._notifyWorkspacesChanged('zen-workspace-updated', Array.from(changedUUIDs));
}
},
async getWorkspaces() {
const db = await this.lazy.PlacesUtils.promiseDBConnection();
const rows = await db.executeCached(`
SELECT * FROM zen_workspaces ORDER BY created_at ASC
`);
return rows.map((row) => ({
uuid: row.getResultByName('uuid'),
name: row.getResultByName('name'),
icon: row.getResultByName('icon'),
default: !!row.getResultByName('is_default'),
containerTabId: row.getResultByName('container_id'),
position: row.getResultByName('position'),
theme: row.getResultByName('theme_type')
? {
type: row.getResultByName('theme_type'),
gradientColors: JSON.parse(row.getResultByName('theme_colors')),
opacity: row.getResultByName('theme_opacity'),
rotation: row.getResultByName('theme_rotation'),
texture: row.getResultByName('theme_texture'),
}
: null,
}));
},
async removeWorkspace(uuid, notifyObservers = true) {
const changedUUIDs = [uuid];
await this.lazy.PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.removeWorkspace', async (db) => {
await db.execute(
`
DELETE FROM zen_workspaces WHERE uuid = :uuid
`,
{ uuid }
);
// Record the removal as a change
const now = Date.now();
await db.execute(
`
INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
`,
{
uuid,
timestamp: Math.floor(now / 1000),
}
);
await this.updateLastChangeTimestamp(db);
});
if (notifyObservers) {
this._notifyWorkspacesChanged('zen-workspace-removed', changedUUIDs);
}
},
async wipeAllWorkspaces() {
await this.lazy.PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.wipeAllWorkspaces', async (db) => {
await db.execute(`DELETE FROM zen_workspaces`);
await db.execute(`DELETE FROM zen_workspaces_changes`);
await this.updateLastChangeTimestamp(db);
});
},
async setDefaultWorkspace(uuid, notifyObservers = true) {
const changedUUIDs = [];
await this.lazy.PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.setDefaultWorkspace', async (db) => {
await db.executeTransaction(async () => {
const now = Date.now();
// Unset the default flag for all other workspaces
await db.execute(`UPDATE zen_workspaces SET is_default = 0 WHERE uuid != :uuid`, { uuid });
// Collect UUIDs of workspaces that were unset as default
const unsetDefaultRows = await db.execute(`SELECT uuid FROM zen_workspaces WHERE is_default = 0 AND uuid != :uuid`, {
uuid,
});
for (const row of unsetDefaultRows) {
changedUUIDs.push(row.getResultByName('uuid'));
}
// Set the default flag for the specified workspace
await db.execute(`UPDATE zen_workspaces SET is_default = 1 WHERE uuid = :uuid`, { uuid });
// Record the change for the specified workspace
await db.execute(
`
INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
`,
{
uuid,
timestamp: Math.floor(now / 1000),
}
);
// Add the main workspace UUID to the changed set
changedUUIDs.push(uuid);
await this.updateLastChangeTimestamp(db);
});
});
if (notifyObservers) {
this._notifyWorkspacesChanged('zen-workspace-updated', changedUUIDs);
}
},
async markChanged(uuid) {
await this.lazy.PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.markChanged', async (db) => {
const now = Date.now();
await db.execute(
`
INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
`,
{
uuid,
timestamp: Math.floor(now / 1000),
}
);
});
},
async saveWorkspaceTheme(uuid, theme, notifyObservers = true) {
const changedUUIDs = [uuid];
await this.lazy.PlacesUtils.withConnectionWrapper('saveWorkspaceTheme', async (db) => {
await db.execute(
`
UPDATE zen_workspaces
SET
theme_type = :type,
theme_colors = :colors,
theme_opacity = :opacity,
theme_rotation = :rotation,
theme_texture = :texture,
updated_at = :now
WHERE uuid = :uuid
`,
{
type: theme.type,
colors: JSON.stringify(theme.gradientColors),
opacity: theme.opacity,
rotation: theme.rotation,
texture: theme.texture,
now: Date.now(),
uuid,
}
);
await this.markChanged(uuid);
await this.updateLastChangeTimestamp(db);
});
if (notifyObservers) {
this._notifyWorkspacesChanged('zen-workspace-updated', changedUUIDs);
}
},
async getChangedIDs() {
const db = await this.lazy.PlacesUtils.promiseDBConnection();
const rows = await db.execute(`
SELECT uuid, timestamp FROM zen_workspaces_changes
`);
const changes = {};
for (const row of rows) {
changes[row.getResultByName('uuid')] = row.getResultByName('timestamp');
}
return changes;
},
async clearChangedIDs() {
await this.lazy.PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.clearChangedIDs', async (db) => {
await db.execute(`DELETE FROM zen_workspaces_changes`);
});
},
shouldReorderWorkspaces(before, current, after) {
const minGap = 1; // Minimum allowed gap between positions
return (before !== null && current - before < minGap) || (after !== null && after - current < minGap);
},
async reorderAllWorkspaces(db, changedUUIDs) {
const workspaces = await db.execute(`
SELECT uuid
FROM zen_workspaces
ORDER BY "position" ASC
`);
for (let i = 0; i < workspaces.length; i++) {
const newPosition = (i + 1) * 1000; // Use large increments
await db.execute(
`
UPDATE zen_workspaces
SET "position" = :newPosition
WHERE uuid = :uuid
`,
{ newPosition, uuid: workspaces[i].getResultByName('uuid') }
);
changedUUIDs.add(workspaces[i].getResultByName('uuid'));
}
},
async updateLastChangeTimestamp(db) {
const now = Date.now();
await db.execute(
`
INSERT OR REPLACE INTO moz_meta (key, value)
VALUES ('zen_workspaces_last_change', :now)
`,
{ now }
);
},
async getLastChangeTimestamp() {
const db = await this.lazy.PlacesUtils.promiseDBConnection();
const result = await db.executeCached(`
SELECT value FROM moz_meta WHERE key = 'zen_workspaces_last_change'
`);
return result.length ? parseInt(result[0].getResultByName('value'), 10) : 0;
},
async updateWorkspacePositions(workspaces) {
const changedUUIDs = new Set();
await this.lazy.PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.updateWorkspacePositions', async (db) => {
await db.executeTransaction(async () => {
const now = Date.now();
for (let i = 0; i < workspaces.length; i++) {
const workspace = workspaces[i];
const newPosition = (i + 1) * 1000;
await db.execute(
`
UPDATE zen_workspaces
SET "position" = :newPosition
WHERE uuid = :uuid
`,
{ newPosition, uuid: workspace.uuid }
);
changedUUIDs.add(workspace.uuid);
// Record the change
await db.execute(
`
INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp)
VALUES (:uuid, :timestamp)
`,
{
uuid: workspace.uuid,
timestamp: Math.floor(now / 1000),
}
);
}
await this.updateLastChangeTimestamp(db);
});
});
this._notifyWorkspacesChanged('zen-workspace-updated', Array.from(changedUUIDs));
},
};
// Integration of workspace-specific bookmarks into Places
var ZenWorkspaceBookmarksStorage = {
async init() {
await this._ensureTable();
},
async _ensureTable() {
await ZenWorkspacesStorage.lazy.PlacesUtils.withConnectionWrapper('ZenWorkspaceBookmarksStorage.init', async (db) => {
// Create table using GUIDs instead of IDs
await db.execute(`
CREATE TABLE IF NOT EXISTS zen_bookmarks_workspaces (
id INTEGER PRIMARY KEY,
bookmark_guid TEXT NOT NULL,
workspace_uuid TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
UNIQUE(bookmark_guid, workspace_uuid),
FOREIGN KEY(workspace_uuid) REFERENCES zen_workspaces(uuid) ON DELETE CASCADE,
FOREIGN KEY(bookmark_guid) REFERENCES moz_bookmarks(guid) ON DELETE CASCADE
)
`);
// Create index for fast lookups
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_bookmarks_workspaces_lookup
ON zen_bookmarks_workspaces(workspace_uuid, bookmark_guid)
`);
// Add changes tracking table
await db.execute(`
CREATE TABLE IF NOT EXISTS zen_bookmarks_workspaces_changes (
id INTEGER PRIMARY KEY,
bookmark_guid TEXT NOT NULL,
workspace_uuid TEXT NOT NULL,
change_type TEXT NOT NULL,
timestamp INTEGER NOT NULL,
UNIQUE(bookmark_guid, workspace_uuid),
FOREIGN KEY(workspace_uuid) REFERENCES zen_workspaces(uuid) ON DELETE CASCADE,
FOREIGN KEY(bookmark_guid) REFERENCES moz_bookmarks(guid) ON DELETE CASCADE
)
`);
// Create index for changes tracking
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_bookmarks_workspaces_changes
ON zen_bookmarks_workspaces_changes(bookmark_guid, workspace_uuid)
`);
});
},
/**
* Updates the last change timestamp in the metadata table.
* @param {Object} db - The database connection.
*/
async updateLastChangeTimestamp(db) {
const now = Date.now();
await db.execute(
`
INSERT OR REPLACE INTO moz_meta (key, value)
VALUES ('zen_bookmarks_workspaces_last_change', :now)
`,
{ now }
);
},
/**
* Gets the timestamp of the last change.
* @returns {Promise<number>} The timestamp of the last change.
*/
async getLastChangeTimestamp() {
const db = await ZenWorkspacesStorage.lazy.PlacesUtils.promiseDBConnection();
const result = await db.executeCached(`
SELECT value FROM moz_meta WHERE key = 'zen_bookmarks_workspaces_last_change'
`);
return result.length ? parseInt(result[0].getResultByName('value'), 10) : 0;
},
async getBookmarkWorkspaces(bookmarkGuid) {
const db = await ZenWorkspacesStorage.lazy.PlacesUtils.promiseDBConnection();
const rows = await db.execute(
`
SELECT workspace_uuid
FROM zen_bookmarks_workspaces
WHERE bookmark_guid = :bookmark_guid
`,
{ bookmark_guid: bookmarkGuid }
);
return rows.map((row) => row.getResultByName('workspace_uuid'));
},
/**
* Get all bookmark GUIDs organized by workspace UUID.
* @returns {Promise<Object>} A dictionary with workspace UUIDs as keys and arrays of bookmark GUIDs as values.
* @example
* // Returns:
* {
* "workspace-uuid-1": ["bookmark-guid-1", "bookmark-guid-2"],
* "workspace-uuid-2": ["bookmark-guid-3"]
* }
*/
async getBookmarkGuidsByWorkspace() {
const db = await ZenWorkspacesStorage.lazy.PlacesUtils.promiseDBConnection();
const rows = await db.execute(`
SELECT workspace_uuid, GROUP_CONCAT(bookmark_guid) as bookmark_guids
FROM zen_bookmarks_workspaces
GROUP BY workspace_uuid
`);
const result = {};
for (const row of rows) {
const workspaceUuid = row.getResultByName('workspace_uuid');
const bookmarkGuids = row.getResultByName('bookmark_guids');
result[workspaceUuid] = bookmarkGuids ? bookmarkGuids.split(',') : [];
}
return result;
},
/**
* Get all changed bookmarks with their change types.
* @returns {Promise<Object>} An object mapping bookmark+workspace pairs to their change data.
*/
async getChangedIDs() {
const db = await ZenWorkspacesStorage.lazy.PlacesUtils.promiseDBConnection();
const rows = await db.execute(`
SELECT bookmark_guid, workspace_uuid, change_type, timestamp
FROM zen_bookmarks_workspaces_changes
`);
const changes = {};
for (const row of rows) {
const key = `${row.getResultByName('bookmark_guid')}:${row.getResultByName('workspace_uuid')}`;
changes[key] = {
type: row.getResultByName('change_type'),
timestamp: row.getResultByName('timestamp'),
};
}
return changes;
},
/**
* Clear all recorded changes.
*/
async clearChangedIDs() {
await ZenWorkspacesStorage.lazy.PlacesUtils.withConnectionWrapper(
'ZenWorkspaceBookmarksStorage.clearChangedIDs',
async (db) => {
await db.execute(`DELETE FROM zen_bookmarks_workspaces_changes`);
}
);
},
};
ZenWorkspacesStorage.promiseDBInitialized = new Promise((resolve) => {
ZenWorkspacesStorage._resolveDBInitialized = resolve;
ZenWorkspacesStorage.init();
});

View File

@@ -1,448 +0,0 @@
var { Tracker, Store, SyncEngine } = ChromeUtils.importESModule('resource://services-sync/engines.sys.mjs');
var { CryptoWrapper } = ChromeUtils.importESModule('resource://services-sync/record.sys.mjs');
var { Utils } = ChromeUtils.importESModule('resource://services-sync/util.sys.mjs');
var { SCORE_INCREMENT_XLARGE } = ChromeUtils.importESModule('resource://services-sync/constants.sys.mjs');
// Define ZenWorkspaceRecord
function ZenWorkspaceRecord(collection, id) {
CryptoWrapper.call(this, collection, id);
}
ZenWorkspaceRecord.prototype = Object.create(CryptoWrapper.prototype);
ZenWorkspaceRecord.prototype.constructor = ZenWorkspaceRecord;
ZenWorkspaceRecord.prototype._logName = 'Sync.Record.ZenWorkspace';
Utils.deferGetSet(ZenWorkspaceRecord, 'cleartext', [
'name',
'icon',
'default',
'containerTabId',
'position',
'theme_type',
'theme_colors',
'theme_opacity',
'theme_rotation',
'theme_texture',
]);
// Define ZenWorkspacesStore
function ZenWorkspacesStore(name, engine) {
Store.call(this, name, engine);
}
ZenWorkspacesStore.prototype = Object.create(Store.prototype);
ZenWorkspacesStore.prototype.constructor = ZenWorkspacesStore;
/**
* Initializes the store by loading the current changeset.
*/
ZenWorkspacesStore.prototype.initialize = async function () {
await Store.prototype.initialize.call(this);
// Additional initialization if needed
};
/**
* Retrieves all workspace IDs from the storage.
* @returns {Object} An object mapping workspace UUIDs to true.
*/
ZenWorkspacesStore.prototype.getAllIDs = async function () {
try {
const workspaces = await ZenWorkspacesStorage.getWorkspaces();
const ids = {};
for (const workspace of workspaces) {
ids[workspace.uuid] = true;
}
return ids;
} catch (error) {
this._log.error('Error fetching all workspace IDs', error);
throw error;
}
};
/**
* Handles changing the ID of a workspace.
* @param {String} oldID - The old UUID.
* @param {String} newID - The new UUID.
*/
ZenWorkspacesStore.prototype.changeItemID = async function (oldID, newID) {
try {
const workspaces = await ZenWorkspacesStorage.getWorkspaces();
const workspace = workspaces.find((ws) => ws.uuid === oldID);
if (workspace) {
workspace.uuid = newID;
await ZenWorkspacesStorage.saveWorkspace(workspace, false);
// Mark the new ID as changed for sync
await ZenWorkspacesStorage.markChanged(newID);
}
} catch (error) {
this._log.error(`Error changing workspace ID from ${oldID} to ${newID}`, error);
throw error;
}
};
/**
* Checks if a workspace exists.
* @param {String} id - The UUID of the workspace.
* @returns {Boolean} True if the workspace exists, false otherwise.
*/
ZenWorkspacesStore.prototype.itemExists = async function (id) {
try {
const workspaces = await ZenWorkspacesStorage.getWorkspaces();
return workspaces.some((ws) => ws.uuid === id);
} catch (error) {
this._log.error(`Error checking if workspace exists with ID ${id}`, error);
throw error;
}
};
/**
* Creates a record for a workspace.
* @param {String} id - The UUID of the workspace.
* @param {String} collection - The collection name.
* @returns {ZenWorkspaceRecord} The workspace record.
*/
ZenWorkspacesStore.prototype.createRecord = async function (id, collection) {
try {
const workspaces = await ZenWorkspacesStorage.getWorkspaces();
const workspace = workspaces.find((ws) => ws.uuid === id);
const record = new ZenWorkspaceRecord(collection, id);
if (workspace) {
record.name = workspace.name;
record.icon = workspace.icon;
record.default = workspace.default;
record.containerTabId = workspace.containerTabId;
record.position = workspace.position;
if (workspace.theme) {
record.theme_type = workspace.theme.type;
record.theme_colors = JSON.stringify(workspace.theme.gradientColors);
record.theme_opacity = workspace.theme.opacity;
record.theme_rotation = workspace.theme.rotation;
record.theme_texture = workspace.theme.texture;
}
record.deleted = false;
} else {
record.deleted = true;
}
return record;
} catch (error) {
this._log.error(`Error creating record for workspace ID ${id}`, error);
throw error;
}
};
/**
* Creates a new workspace.
* @param {ZenWorkspaceRecord} record - The workspace record to create.
*/
ZenWorkspacesStore.prototype.create = async function (record) {
try {
this._validateRecord(record);
const workspace = {
uuid: record.id,
name: record.name,
icon: record.icon,
default: record.default,
containerTabId: record.containerTabId,
position: record.position,
theme: record.theme_type
? {
type: record.theme_type,
gradientColors: JSON.parse(record.theme_colors),
opacity: record.theme_opacity,
rotation: record.theme_rotation,
texture: record.theme_texture,
}
: null,
};
await ZenWorkspacesStorage.saveWorkspace(workspace, false);
} catch (error) {
this._log.error(`Error creating workspace with ID ${record.id}`, error);
throw error;
}
};
/**
* Updates an existing workspace.
* @param {ZenWorkspaceRecord} record - The workspace record to update.
*/
ZenWorkspacesStore.prototype.update = async function (record) {
try {
this._validateRecord(record);
await this.create(record); // Reuse create for update
} catch (error) {
this._log.error(`Error updating workspace with ID ${record.id}`, error);
throw error;
}
};
/**
* Removes a workspace.
* @param {ZenWorkspaceRecord} record - The workspace record to remove.
*/
ZenWorkspacesStore.prototype.remove = async function (record) {
try {
await ZenWorkspacesStorage.removeWorkspace(record.id, false);
} catch (error) {
this._log.error(`Error removing workspace with ID ${record.id}`, error);
throw error;
}
};
/**
* Wipes all workspaces from the storage.
*/
ZenWorkspacesStore.prototype.wipe = async function () {
try {
await ZenWorkspacesStorage.wipeAllWorkspaces();
} catch (error) {
this._log.error('Error wiping all workspaces', error);
throw error;
}
};
/**
* Validates a workspace record.
* @param {ZenWorkspaceRecord} record - The workspace record to validate.
*/
ZenWorkspacesStore.prototype._validateRecord = function (record) {
if (!record.id || typeof record.id !== 'string') {
throw new Error('Invalid workspace ID');
}
if (!record.name || typeof record.name !== 'string') {
throw new Error(`Invalid workspace name for ID ${record.id}`);
}
if (typeof record.default !== 'boolean') {
record.default = false;
}
if (record.icon != null && typeof record.icon !== 'string') {
throw new Error(`Invalid icon for workspace ID ${record.id}`);
}
if (record.containerTabId != null && typeof record.containerTabId !== 'number') {
throw new Error(`Invalid containerTabId for workspace ID ${record.id}`);
}
if (record.position != null && typeof record.position !== 'number') {
throw new Error(`Invalid position for workspace ID ${record.id}`);
}
// Validate theme properties if they exist
if (record.theme_type) {
if (typeof record.theme_type !== 'string') {
throw new Error(`Invalid theme_type for workspace ID ${record.id}`);
}
if (!record.theme_colors || typeof record.theme_colors !== 'string') {
throw new Error(`Invalid theme_colors for workspace ID ${record.id}`);
}
try {
JSON.parse(record.theme_colors);
} catch (e) {
throw new Error(`Invalid theme_colors JSON for workspace ID ${record.id}`);
}
if (record.theme_opacity != null && typeof record.theme_opacity !== 'number') {
throw new Error(`Invalid theme_opacity for workspace ID ${record.id}`);
}
if (record.theme_rotation != null && typeof record.theme_rotation !== 'number') {
throw new Error(`Invalid theme_rotation for workspace ID ${record.id}`);
}
if (record.theme_texture != null && typeof record.theme_texture !== 'number') {
throw new Error(`Invalid theme_texture for workspace ID ${record.id}`);
}
}
};
/**
* Retrieves changed workspace IDs since the last sync.
* @returns {Object} An object mapping workspace UUIDs to their change timestamps.
*/
ZenWorkspacesStore.prototype.getChangedIDs = async function () {
try {
return await ZenWorkspacesStorage.getChangedIDs();
} catch (error) {
this._log.error('Error retrieving changed IDs from storage', error);
throw error;
}
};
/**
* Clears all recorded changes after a successful sync.
*/
ZenWorkspacesStore.prototype.clearChangedIDs = async function () {
try {
await ZenWorkspacesStorage.clearChangedIDs();
} catch (error) {
this._log.error('Error clearing changed IDs in storage', error);
throw error;
}
};
/**
* Marks a workspace as changed.
* @param {String} uuid - The UUID of the workspace that changed.
*/
ZenWorkspacesStore.prototype.markChanged = async function (uuid) {
try {
await ZenWorkspacesStorage.markChanged(uuid);
} catch (error) {
this._log.error(`Error marking workspace ${uuid} as changed`, error);
throw error;
}
};
/**
* Finalizes the store by ensuring all pending operations are completed.
*/
ZenWorkspacesStore.prototype.finalize = async function () {
await Store.prototype.finalize.call(this);
};
// Define ZenWorkspacesTracker
function ZenWorkspacesTracker(name, engine) {
Tracker.call(this, name, engine);
this._ignoreAll = false;
// Observe profile-before-change to stop the tracker gracefully
Services.obs.addObserver(this.asyncObserver, 'profile-before-change');
}
ZenWorkspacesTracker.prototype = Object.create(Tracker.prototype);
ZenWorkspacesTracker.prototype.constructor = ZenWorkspacesTracker;
/**
* Retrieves changed workspace IDs by delegating to the store.
* @returns {Object} An object mapping workspace UUIDs to their change timestamps.
*/
ZenWorkspacesTracker.prototype.getChangedIDs = async function () {
try {
return await this.engine._store.getChangedIDs();
} catch (error) {
this._log.error('Error retrieving changed IDs from store', error);
throw error;
}
};
/**
* Clears all recorded changes after a successful sync.
*/
ZenWorkspacesTracker.prototype.clearChangedIDs = async function () {
try {
await this.engine._store.clearChangedIDs();
} catch (error) {
this._log.error('Error clearing changed IDs in store', error);
throw error;
}
};
/**
* Called when the tracker starts. Registers observers to listen for workspace changes.
*/
ZenWorkspacesTracker.prototype.onStart = function () {
if (this._started) {
return;
}
this._log.trace('Starting tracker');
// Register observers for workspace changes
Services.obs.addObserver(this.asyncObserver, 'zen-workspace-added');
Services.obs.addObserver(this.asyncObserver, 'zen-workspace-removed');
Services.obs.addObserver(this.asyncObserver, 'zen-workspace-updated');
this._started = true;
};
/**
* Called when the tracker stops. Unregisters observers.
*/
ZenWorkspacesTracker.prototype.onStop = function () {
if (!this._started) {
return;
}
this._log.trace('Stopping tracker');
// Unregister observers for workspace changes
Services.obs.removeObserver(this.asyncObserver, 'zen-workspace-added');
Services.obs.removeObserver(this.asyncObserver, 'zen-workspace-removed');
Services.obs.removeObserver(this.asyncObserver, 'zen-workspace-updated');
this._started = false;
};
/**
* Handles observed events and marks workspaces as changed accordingly.
* @param {nsISupports} subject - The subject of the notification.
* @param {String} topic - The topic of the notification.
* @param {String} data - Additional data (JSON stringified array of UUIDs).
*/
ZenWorkspacesTracker.prototype.observe = async function (subject, topic, data) {
if (this.ignoreAll) {
return;
}
try {
switch (topic) {
case 'profile-before-change':
await this.stop();
break;
case 'zen-workspace-removed':
case 'zen-workspace-updated':
case 'zen-workspace-added':
let workspaceIDs;
if (data) {
try {
workspaceIDs = JSON.parse(data);
if (!Array.isArray(workspaceIDs)) {
throw new Error('Parsed data is not an array');
}
} catch (parseError) {
this._log.error(`Failed to parse workspace UUIDs from data: ${data}`, parseError);
return;
}
} else {
this._log.error(`No data received for event ${topic}`);
return;
}
this._log.trace(`Observed ${topic} for UUIDs: ${workspaceIDs.join(', ')}`);
// Process each UUID
for (const workspaceID of workspaceIDs) {
if (typeof workspaceID === 'string') {
// Inform the store about the change
await this.engine._store.markChanged(workspaceID);
} else {
this._log.warn(`Invalid workspace ID encountered: ${workspaceID}`);
}
}
// Bump the score once after processing all changes
if (workspaceIDs.length > 0) {
this.score += SCORE_INCREMENT_XLARGE;
}
break;
}
} catch (error) {
this._log.error(`Error handling ${topic} in observe method`, error);
}
};
/**
* Finalizes the tracker by ensuring all pending operations are completed.
*/
ZenWorkspacesTracker.prototype.finalize = async function () {
await Tracker.prototype.finalize.call(this);
};
// Define ZenWorkspacesEngine
function ZenWorkspacesEngine(service) {
SyncEngine.call(this, 'Workspaces', service);
}
ZenWorkspacesEngine.prototype = Object.create(SyncEngine.prototype);
ZenWorkspacesEngine.prototype.constructor = ZenWorkspacesEngine;
ZenWorkspacesEngine.prototype._storeObj = ZenWorkspacesStore;
ZenWorkspacesEngine.prototype._trackerObj = ZenWorkspacesTracker;
ZenWorkspacesEngine.prototype._recordObj = ZenWorkspaceRecord;
ZenWorkspacesEngine.prototype.version = 2;
ZenWorkspacesEngine.prototype.syncPriority = 10;
ZenWorkspacesEngine.prototype.allowSkippedRecord = false;
Object.setPrototypeOf(ZenWorkspacesEngine.prototype, SyncEngine.prototype);

View File

@@ -1,135 +0,0 @@
export class ZenGlanceChild extends JSWindowActorChild {
constructor() {
super();
this.mouseUpListener = this.handleMouseUp.bind(this);
this.mouseDownListener = this.handleMouseDown.bind(this);
this.clickListener = this.handleClick.bind(this);
}
async handleEvent(event) {
switch (event.type) {
case 'DOMContentLoaded':
await this.initiateGlance();
break;
case 'keydown':
this.onKeyDown(event);
break;
default:
}
}
async getActivationMethod() {
if (this._activationMethod === undefined) {
this._activationMethod = await this.sendQuery('ZenGlance:GetActivationMethod');
}
return this._activationMethod;
}
async getHoverActivationDelay() {
if (this._hoverActivationDelay === undefined) {
this._hoverActivationDelay = await this.sendQuery('ZenGlance:GetHoverActivationDelay');
}
return this._hoverActivationDelay;
}
async receiveMessage(message) {
switch (message.name) {
}
}
async initiateGlance() {
this.mouseIsDown = false;
const activationMethod = await this.getActivationMethod();
if (activationMethod === 'mantain') {
this.contentWindow.addEventListener('mousedown', this.mouseDownListener);
this.contentWindow.addEventListener('mouseup', this.mouseUpListener);
this.contentWindow.document.removeEventListener('click', this.clickListener);
} else if (activationMethod === 'ctrl' || activationMethod === 'alt' || activationMethod === 'shift') {
this.contentWindow.document.addEventListener('click', this.clickListener, { capture: true });
this.contentWindow.removeEventListener('mousedown', this.mouseDownListener);
this.contentWindow.removeEventListener('mouseup', this.mouseUpListener);
}
}
ensureOnlyKeyModifiers(event) {
return !(event.ctrlKey ^ event.altKey ^ event.shiftKey ^ event.metaKey);
}
openGlance(target) {
let url = target.href;
// Add domain to relative URLs
if (!url.match(/^(?:[a-z]+:)?\/\//i)) {
url = this.contentWindow.location.origin + url;
}
const rect = target.getBoundingClientRect();
this.sendAsyncMessage('ZenGlance:OpenGlance', {
url,
x: rect.left,
y: rect.top,
width: rect.width,
height: rect.height,
});
}
handleMouseUp(event) {
if (this.hasClicked) {
event.preventDefault();
event.stopPropagation();
this.hasClicked = false;
}
this.mouseIsDown = null;
}
async handleMouseDown(event) {
const target = event.target.closest('A');
if (!target) {
return;
}
this.mouseIsDown = target;
const hoverActivationDelay = await this.getHoverActivationDelay();
this.contentWindow.setTimeout(() => {
if (this.mouseIsDown === target) {
this.hasClicked = true;
this.openGlance(target);
}
}, hoverActivationDelay);
}
handleClick(event) {
if (this.ensureOnlyKeyModifiers(event) || event.button !== 0 || event.defaultPrevented) {
return;
}
const activationMethod = this._activationMethod;
if (activationMethod === 'ctrl' && !event.ctrlKey) {
return;
} else if (activationMethod === 'alt' && !event.altKey) {
return;
} else if (activationMethod === 'shift' && !event.shiftKey) {
return;
} else if (activationMethod === 'meta' && !event.metaKey) {
return;
} else if (activationMethod === 'mantain' || typeof activationMethod === 'undefined') {
return;
}
// get closest A element
const target = event.target.closest('A');
if (target) {
event.preventDefault();
event.stopPropagation();
this.openGlance(target);
}
}
onKeyDown(event) {
if (event.defaultPrevented || event.key !== 'Escape') {
return;
}
this.sendAsyncMessage('ZenGlance:CloseGlance', {
hasFocused: this.contentWindow.document.activeElement !== this.contentWindow.document.body,
});
}
}

View File

@@ -1,34 +0,0 @@
export class ZenGlanceParent extends JSWindowActorParent {
constructor() {
super();
}
async receiveMessage(message) {
switch (message.name) {
case 'ZenGlance:GetActivationMethod': {
return Services.prefs.getStringPref('zen.glance.activation-method', 'ctrl');
}
case 'ZenGlance:GetHoverActivationDelay': {
return Services.prefs.getIntPref('zen.glance.hold-duration', 500);
}
case 'ZenGlance:OpenGlance': {
this.openGlance(this.browsingContext.topChromeWindow, message.data);
break;
}
case 'ZenGlance:CloseGlance': {
const params = {
onTabClose: true,
...message.data,
};
this.browsingContext.topChromeWindow.gZenGlanceManager.closeGlance(params);
break;
}
default:
console.warn(`[glance]: Unknown message: ${message.name}`);
}
}
openGlance(window, data) {
window.gZenGlanceManager.openGlance(data);
}
}

View File

@@ -1,190 +0,0 @@
export class ZenThemeMarketplaceChild extends JSWindowActorChild {
constructor() {
super();
}
handleEvent(event) {
switch (event.type) {
case 'DOMContentLoaded':
this.initalizeZenAPI(event);
break;
default:
}
}
initalizeZenAPI(event) {
const verifier = this.contentWindow.document.querySelector('meta[name="zen-content-verified"]');
if (verifier) {
verifier.setAttribute('content', 'verified');
}
const possibleRicePage = this.collectRiceMetadata();
if (possibleRicePage?.id) {
this.sendAsyncMessage('ZenThemeMarketplace:RicePage', possibleRicePage);
return;
}
this.initiateThemeMarketplace();
this.contentWindow.document.addEventListener('ZenCheckForThemeUpdates', this.checkForThemeUpdates.bind(this));
}
collectRiceMetadata() {
const meta = this.contentWindow.document.querySelector('meta[name="zen-rice-data"]');
if (meta) {
return {
id: meta.getAttribute('data-id'),
name: meta.getAttribute('data-name'),
author: meta.getAttribute('data-author'),
};
}
return null;
}
// This function will be caleld from about:preferences
checkForThemeUpdates(event) {
event.preventDefault();
this.sendAsyncMessage('ZenThemeMarketplace:CheckForUpdates');
}
initiateThemeMarketplace() {
this.contentWindow.setTimeout(() => {
this.addInstallButtons();
this.injectMarkplaceAPI();
}, 0);
}
get actionButton() {
return this.contentWindow.document.getElementById('install-theme');
}
get actionButtonUninstall() {
return this.contentWindow.document.getElementById('install-theme-uninstall');
}
async receiveMessage(message) {
switch (message.name) {
case 'ZenThemeMarketplace:ThemeChanged': {
const themeId = message.data.themeId;
const actionButton = this.actionButton;
const actionButtonInstalled = this.actionButtonUninstall;
if (actionButton && actionButtonInstalled) {
actionButton.disabled = false;
actionButtonInstalled.disabled = false;
if (await this.isThemeInstalled(themeId)) {
actionButton.classList.add('hidden');
actionButtonInstalled.classList.remove('hidden');
} else {
actionButton.classList.remove('hidden');
actionButtonInstalled.classList.add('hidden');
}
}
break;
}
case 'ZenThemeMarketplace:CheckForUpdatesFinished': {
const updates = message.data.updates;
this.contentWindow.document.dispatchEvent(
new CustomEvent('ZenThemeMarketplace:CheckForUpdatesFinished', { detail: { updates } })
);
break;
}
case 'ZenThemeMarketplace:GetThemeInfo': {
const themeId = message.data.themeId;
const theme = await this.getThemeInfo(themeId);
return theme;
}
}
}
injectMarkplaceAPI() {
Cu.exportFunction(this.installTheme.bind(this), this.contentWindow, {
defineAs: 'ZenInstallTheme',
});
}
async addInstallButtons() {
const actionButton = this.actionButton;
const actionButtonUnnstall = this.actionButtonUninstall;
const errorMessage = this.contentWindow.document.getElementById('install-theme-error');
if (!actionButton || !actionButtonUnnstall) {
return;
}
errorMessage.classList.add('hidden');
const themeId = actionButton.getAttribute('zen-theme-id');
if (await this.isThemeInstalled(themeId)) {
actionButtonUnnstall.classList.remove('hidden');
} else {
actionButton.classList.remove('hidden');
}
actionButton.addEventListener('click', this.installTheme.bind(this));
actionButtonUnnstall.addEventListener('click', this.uninstallTheme.bind(this));
}
async isThemeInstalled(themeId) {
return await this.sendQuery('ZenThemeMarketplace:IsThemeInstalled', { themeId });
}
addTheme(theme) {
this.sendAsyncMessage('ZenThemeMarketplace:InstallTheme', { theme });
}
getThemeAPIUrl(themeId) {
return `https://zen-browser.github.io/theme-store/themes/${themeId}/theme.json`;
}
async getThemeInfo(themeId) {
const url = this.getThemeAPIUrl(themeId);
const data = await fetch(url, {
mode: 'no-cors',
});
if (data.ok) {
try {
const obj = await data.json();
return obj;
} catch (e) {
console.error('ZTM: Error parsing theme info: ', e);
}
} else console.log(data.status);
return null;
}
async uninstallTheme(event) {
const button = event.target;
button.disabled = true;
const themeId = button.getAttribute('zen-theme-id');
this.sendAsyncMessage('ZenThemeMarketplace:UninstallTheme', { themeId });
}
async installTheme(object) {
// Object can be an event or a theme id
let themeId;
if (object.target) {
const button = object.target;
button.disabled = true;
themeId = button.getAttribute('zen-theme-id');
} else {
themeId = object.themeId;
}
console.info('ZTM: Installing theme with id: ', themeId);
const theme = await this.getThemeInfo(themeId);
if (!theme) {
console.error('ZTM: Error fetching theme info');
return;
}
this.addTheme(theme);
}
}

View File

@@ -1,206 +0,0 @@
export class ZenThemeMarketplaceParent extends JSWindowActorParent {
constructor() {
super();
}
async receiveMessage(message) {
switch (message.name) {
case 'ZenThemeMarketplace:InstallTheme': {
console.info('ZenThemeMarketplaceParent: Updating themes');
const theme = message.data.theme;
theme.enabled = true;
const themes = await this.getThemes();
themes[theme.id] = theme;
this.updateThemes(themes);
this.updateChildProcesses(theme.id);
break;
}
case 'ZenThemeMarketplace:UninstallTheme': {
console.info('ZenThemeMarketplaceParent: Uninstalling theme');
const themeId = message.data.themeId;
const themes = await this.getThemes();
delete themes[themeId];
this.removeTheme(themeId);
this.updateThemes(themes);
this.updateChildProcesses(themeId);
break;
}
case 'ZenThemeMarketplace:IsThemeInstalled': {
const themeId = message.data.themeId;
const themes = await this.getThemes();
return themes[themeId] ? true : false;
}
case 'ZenThemeMarketplace:CheckForUpdates': {
this.checkForThemeUpdates();
break;
}
case 'ZenThemeMarketplace:RicePage': {
this.openRicePage(this.browsingContext.topChromeWindow, message.data);
break;
}
}
}
openRicePage(window, data) {
window.gZenThemePicker.riceManager.openRicePage(data);
}
compareVersions(version1, version2) {
var result = false;
if (typeof version1 !== 'object') {
version1 = version1.toString().split('.');
}
if (typeof version2 !== 'object') {
version2 = version2.toString().split('.');
}
for (var i = 0; i < Math.max(version1.length, version2.length); i++) {
if (version1[i] == undefined) {
version1[i] = 0;
}
if (version2[i] == undefined) {
version2[i] = 0;
}
if (Number(version1[i]) < Number(version2[i])) {
result = true;
break;
}
if (version1[i] != version2[i]) {
break;
}
}
return result;
}
async checkForThemeUpdates() {
console.info('ZenThemeMarketplaceParent: Checking for theme updates');
let updates = [];
this._themes = null;
for (const theme of Object.values(await this.getThemes())) {
const themeInfo = await this.sendQuery('ZenThemeMarketplace:GetThemeInfo', { themeId: theme.id });
if (!themeInfo) {
continue;
}
if (!this.compareVersions(themeInfo.version, theme.version || '0.0.0') && themeInfo.version != theme.version) {
console.info('ZenThemeMarketplaceParent: Theme update found', theme.id, theme.version, themeInfo.version);
themeInfo.enabled = theme.enabled;
updates.push(themeInfo);
await this.removeTheme(theme.id, false);
this._themes[themeInfo.id] = themeInfo;
}
}
await this.updateThemes(this._themes);
this.sendAsyncMessage('ZenThemeMarketplace:CheckForUpdatesFinished', { updates });
}
async updateChildProcesses(themeId) {
this.sendAsyncMessage('ZenThemeMarketplace:ThemeChanged', { themeId });
}
async getThemes() {
if (!this._themes) {
if (!(await IOUtils.exists(this.themesDataFile))) {
await IOUtils.writeJSON(this.themesDataFile, {});
}
this._themes = await IOUtils.readJSON(this.themesDataFile);
}
return this._themes;
}
async updateThemes(themes) {
this._themes = themes;
await IOUtils.writeJSON(this.themesDataFile, themes);
await this.checkForThemeChanges();
}
getStyleSheetFullContent(style = '') {
let stylesheet = '@-moz-document url-prefix("chrome:") {\n';
for (const line of style.split('\n')) {
stylesheet += ` ${line}\n`;
}
stylesheet += '}';
return stylesheet;
}
async downloadUrlToFile(url, path, isStyleSheet = false) {
try {
const response = await fetch(url);
const data = await response.text();
const content = isStyleSheet ? this.getStyleSheetFullContent(data) : data;
// convert the data into a Uint8Array
let buffer = new TextEncoder().encode(content);
await IOUtils.write(path, buffer);
} catch (e) {
console.error('ZenThemeMarketplaceParent: Error downloading file', url, e);
}
}
async downloadThemeFileContents(theme) {
const themePath = PathUtils.join(this.themesRootPath, theme.id);
await IOUtils.makeDirectory(themePath, { ignoreExisting: true });
await this.downloadUrlToFile(theme.style, PathUtils.join(themePath, 'chrome.css'), true);
await this.downloadUrlToFile(theme.readme, PathUtils.join(themePath, 'readme.md'));
if (theme.preferences) {
await this.downloadUrlToFile(theme.preferences, PathUtils.join(themePath, 'preferences.json'));
}
}
get themesRootPath() {
return PathUtils.join(PathUtils.profileDir, 'chrome', 'zen-themes');
}
get themesDataFile() {
return PathUtils.join(PathUtils.profileDir, 'zen-themes.json');
}
triggerThemeUpdate() {
const pref = 'zen.themes.updated-value-observer';
Services.prefs.setBoolPref(pref, !Services.prefs.getBoolPref(pref));
}
async installTheme(theme) {
await this.downloadThemeFileContents(theme);
}
async checkForThemeChanges() {
const themes = await this.getThemes();
const themeIds = Object.keys(themes);
for (const themeId of themeIds) {
const theme = themes[themeId];
if (!theme) {
continue;
}
const themePath = PathUtils.join(this.themesRootPath, themeId);
if (!(await IOUtils.exists(themePath))) {
await this.installTheme(theme);
}
}
this.triggerThemeUpdate();
}
async removeTheme(themeId, triggerUpdate = true) {
const themePath = PathUtils.join(this.themesRootPath, themeId);
await IOUtils.remove(themePath, { recursive: true, ignoreAbsent: true });
if (triggerUpdate) {
this.triggerThemeUpdate();
}
}
}

View File

@@ -1,11 +0,0 @@
BROWSER_CHROME_MANIFESTS += [
"tests/browser.toml",
]
FINAL_TARGET_FILES.actors += [
"actors/ZenGlanceChild.sys.mjs",
"actors/ZenGlanceParent.sys.mjs",
"actors/ZenThemeMarketplaceChild.sys.mjs",
"actors/ZenThemeMarketplaceParent.sys.mjs",
]