Compare commits

..

2 commits

Author SHA1 Message Date
mr. m
11a671be05
Discard changes to src/zen/common/ZenUIManager.mjs 2025-05-25 13:13:27 +02:00
Mr. M
3724f2a759
feat: Added library, b=(no-bug), c=common, tabs, workspaces 2025-05-24 17:20:50 +02:00
85 changed files with 2411 additions and 2879 deletions

View file

@ -29,7 +29,7 @@
## 🖥️ Compatibility
Zen is currently built using Firefox version `139.0`! 🚀
Zen is currently built using Firefox version `138.0.4`! 🚀
- [`Zen Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 139.0`!
- Check out the latest [release notes](https://zen-browser.app/release-notes)!

View file

@ -1 +1 @@
da30619f3ea895b356ded705b8dff9e4f271198f
82a08ea3ce2d17f21f3d45f4b5607a37590b0158

2
l10n

@ -1 +1 @@
Subproject commit ebecb32da8929e4f14f9a20f40acb9dab401101c
Subproject commit 644474b8c92e306288d835698eb6714081a650d8

8
package-lock.json generated
View file

@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "MPL-2.0",
"dependencies": {
"@zen-browser/surfer": "^1.11.13"
"@zen-browser/surfer": "^1.11.12"
},
"devDependencies": {
"@babel/preset-typescript": "^7.27.0",
@ -817,9 +817,9 @@
"license": "MIT"
},
"node_modules/@zen-browser/surfer": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/@zen-browser/surfer/-/surfer-1.11.13.tgz",
"integrity": "sha512-D0TyunAWYtTdJkuUkYeOc6VBXzE9aoDs58kWu/Oi/aU3vd8IbqXPbZZfYwj5FWPWDReMrJUNkkKAEdbL44y9aw==",
"version": "1.11.12",
"resolved": "https://registry.npmjs.org/@zen-browser/surfer/-/surfer-1.11.12.tgz",
"integrity": "sha512-wny52xOFvZe5aPXxLVxEcAzDNEiWWoDiCZFlzsNxkyQ5Lw6vzqroMWpjQPJwBRJOc/JssgiXMdd1uwl2LLnovQ==",
"license": "MPL-2.0",
"dependencies": {
"@resvg/resvg-js": "^1.4.0",

View file

@ -42,7 +42,7 @@
},
"homepage": "https://github.com/zen-browser/desktop#readme",
"dependencies": {
"@zen-browser/surfer": "^1.11.13"
"@zen-browser/surfer": "^1.11.12"
},
"devDependencies": {
"@babel/preset-typescript": "^7.27.0",

View file

@ -16,7 +16,6 @@ pref('browser.toolbars.bookmarks.visibility', 'never');
pref("browser.bookmarks.openInTabClosesMenu", false);
pref("browser.menu.showViewImageInfo", true);
pref("findbar.highlightAll", true);
pref("layout.word_select.eat_space_to_next_word", false);
// Better Windows theming

View file

@ -22,17 +22,13 @@ pref('zen.mediacontrols.enabled', true);
// Exposure:
pref('zen.haptic-feedback.enabled', true);
pref('zen.mods.auto-update-days', 20); // In days
#ifdef MOZILLA_OFFICIAL
pref('zen.mods.auto-update', true);
pref('zen.rice.api.url', 'https://share.zen-browser.app', locked);
pref('zen.injections.match-urls', 'https://zen-browser.app/*,https://share.zen-browser.app/*', locked);
#else
pref('zen.mods.auto-update', false);
pref('zen.rice.api.url', "http://localhost", locked);
pref('zen.injections.match-urls', 'http://localhost/*', locked);
#endif
pref('zen.rice.share.notice.accepted', false);
#ifdef XP_MACOSX

View file

@ -1,5 +1,5 @@
diff --git a/browser/base/content/aboutDialog.js b/browser/base/content/aboutDialog.js
index f6e1391baf12abb91c85a95107bb3923118746c0..cac04aa288e8a305d0c8b28e0c919abce87658e5 100644
index f6e1391baf12abb91c85a95107bb3923118746c0..76c7b75a4e29056110f1631a50047c4ddd8b1f4a 100644
--- a/browser/base/content/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js
@@ -52,7 +52,7 @@ function init() {
@ -20,18 +20,3 @@ index f6e1391baf12abb91c85a95107bb3923118746c0..cac04aa288e8a305d0c8b28e0c919abc
versionIdKey += "-nightly";
let buildID = Services.appinfo.appBuildID;
let year = buildID.slice(0, 4);
@@ -125,14 +125,6 @@ function init() {
window.close();
});
if (AppConstants.MOZ_UPDATER) {
- document
- .getElementById("aboutDialogHelpLink")
- .addEventListener("click", () => {
- openHelpLink("firefox-help");
- });
- document
- .getElementById("submit-feedback")
- .addEventListener("click", openFeedbackPage);
document
.getElementById("checkForUpdatesButton")
.addEventListener("command", () => {

View file

@ -1,5 +1,5 @@
diff --git a/browser/base/content/aboutDialog.xhtml b/browser/base/content/aboutDialog.xhtml
index c64980810570fcea84e33fdc2d66ac42a79f4e46..6ef9bf4b88f0a0539d833f662c4dd890fd1fde93 100644
index c64980810570fcea84e33fdc2d66ac42a79f4e46..b7198e810a7510fa82cc6801cfd01c88a08d42c1 100644
--- a/browser/base/content/aboutDialog.xhtml
+++ b/browser/base/content/aboutDialog.xhtml
@@ -35,6 +35,7 @@
@ -10,18 +10,7 @@ index c64980810570fcea84e33fdc2d66ac42a79f4e46..6ef9bf4b88f0a0539d833f662c4dd890
</linkset>
<html:div id="aboutDialogContainer">
@@ -102,10 +103,6 @@
<label id="version" class="update"/>
<label id="releasenotes" is="text-link" hidden="true" data-l10n-id="releaseNotes-link"/>
</hbox>
- <description class="text-blurb">
- <label id="aboutDialogHelpLink" is="text-link" data-l10n-id="aboutdialog-help-user"/>
- <label id="submit-feedback" is="text-link" data-l10n-id="aboutdialog-submit-feedback"/>
- </description>
</vbox>
#endif
</hbox>
@@ -125,21 +122,17 @@
@@ -125,21 +126,23 @@
</description>
</vbox>
<description class="text-blurb" id="communityDesc" data-l10n-id="community-2">
@ -29,10 +18,12 @@ index c64980810570fcea84e33fdc2d66ac42a79f4e46..6ef9bf4b88f0a0539d833f662c4dd890
+ <label is="text-link" href="https://github.com/zen-browser/desktop" data-l10n-name="community-mozillaLink"/>
<label is="text-link" useoriginprincipal="true" href="about:credits" data-l10n-name="community-creditsLink"/>
</description>
- <description class="text-blurb" id="contributeDesc" data-l10n-id="helpus">
- <label is="text-link" href="https://foundation.mozilla.org/?form=firefox-about" data-l10n-name="helpus-donateLink"/>
- <label is="text-link" href="https://www.mozilla.org/contribute/?utm_source=firefox-browser&#38;utm_medium=firefox-desktop&#38;utm_campaign=about-dialog" data-l10n-name="helpus-getInvolvedLink"/>
- </description>
+#if 0
<description class="text-blurb" id="contributeDesc" data-l10n-id="helpus">
<label is="text-link" href="https://foundation.mozilla.org/?form=firefox-about" data-l10n-name="helpus-donateLink"/>
<label is="text-link" href="https://www.mozilla.org/contribute/?utm_source=firefox-browser&#38;utm_medium=firefox-desktop&#38;utm_campaign=about-dialog" data-l10n-name="helpus-getInvolvedLink"/>
</description>
+#endif
</vbox>
</vbox>
</hbox>

View file

@ -1,35 +1,8 @@
diff --git a/browser/base/content/browser-addons.js b/browser/base/content/browser-addons.js
index 73593191936cc345ee8e2c28cb251dc13f4c2fd4..e6c459c1ebc60a1f3930a55e212570f696bf07a0 100644
index 992d07daaef1abc4554a43aa654888f66963c575..73e620b70b7ed14e9d140e875c2cd5f5ac31456b 100644
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -735,7 +735,7 @@ var gXPInstallObserver = {
persistent: true,
hideClose: true,
popupOptions: {
- position: "bottomright topright",
+ position: gZenUIManager.panelUIPosition,
},
};
@@ -942,7 +942,7 @@ var gXPInstallObserver = {
hideClose: true,
timeout: Date.now() + 30000,
popupOptions: {
- position: "bottomright topright",
+ position: gZenUIManager.panelUIPosition,
},
};
@@ -2125,7 +2125,7 @@ var gUnifiedExtensions = {
panel.hidden = false;
PanelMultiView.openPopup(panel, this._button, {
- position: "bottomright topright",
+ position: gZenUIManager.panelUIPosition,
triggerEvent: aEvent,
});
}
@@ -2294,18 +2294,20 @@ var gUnifiedExtensions = {
@@ -2105,18 +2105,20 @@ var gUnifiedExtensions = {
this._maybeMoveWidgetNodeBack(widgetId);
}

View file

@ -1,8 +1,16 @@
diff --git a/browser/base/content/browser-box.inc.xhtml b/browser/base/content/browser-box.inc.xhtml
index 7d7e8697f02f90d4f336c9ab0a73a89848e0c21c..64e950106dd05b443ce72107613ac9cc405d56ea 100644
index 7d7e8697f02f90d4f336c9ab0a73a89848e0c21c..3819ae72f97900b6d212f8a54550ae569d497741 100644
--- a/browser/base/content/browser-box.inc.xhtml
+++ b/browser/base/content/browser-box.inc.xhtml
@@ -23,7 +23,15 @@
@@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
<hbox flex="1" id="browser">
+ <zen-library/>
<box context="sidebar-context-menu" id="sidebar-main" hidden="true">
<html:sidebar-main flex="1">
<box id="vertical-tabs" slot="tabstrip" customizable="true" contextmenu="toolbar-context-menu"></box>
@@ -23,7 +24,15 @@
<browser id="sidebar" autoscroll="false" disablehistory="true" disablefullscreen="true" tooltip="aHTMLTooltip"/>
</vbox>
<splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" resizebefore="sibling" resizeafter="none" hidden="true"/>

View file

@ -16,6 +16,7 @@
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-urlbar.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-workspaces.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-decks.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-library.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-folders.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-glance.css" />
<link rel="stylesheet" type="text/css" href="chrome://browser/content/zen-styles/zen-popup.css" />
@ -31,7 +32,8 @@
# Scripts used all over the browser
<script src="chrome://browser/content/ZenUIManager.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenFolders.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenMods.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenThemesCommon.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenThemesImporter.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenCompactMode.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenPinnedTabsStorage.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenWorkspacesStorage.mjs"></script>
@ -41,3 +43,4 @@
<script src="chrome://browser/content/zen-components/ZenGlanceManager.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenMediaController.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenDownloadAnimation.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenLibrary.mjs"></script>

View file

@ -9,7 +9,6 @@
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-components/ZenSessionStore.mjs (../../zen/common/ZenSessionStore.mjs)
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)
@ -35,7 +34,13 @@
content/browser/zen-components/ZenViewSplitter.mjs (../../zen/split-view/ZenViewSplitter.mjs)
content/browser/zen-styles/zen-decks.css (../../zen/split-view/zen-decks.css)
content/browser/zen-components/ZenMods.mjs (../../zen/mods/ZenMods.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)
content/browser/zen-components/ZenLibrary.mjs (../../zen/library/ZenLibrary.mjs)
content/browser/zen-styles/zen-library.css (../../zen/library/zen-library.css)
content/browser/zen-components/ZenWorkspaceIcons.mjs (../../zen/workspaces/ZenWorkspaceIcons.mjs)
content/browser/zen-components/ZenWorkspace.mjs (../../zen/workspaces/ZenWorkspace.mjs)
@ -55,6 +60,8 @@
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)

View file

@ -43,6 +43,11 @@
<command id="cmd_zenCopyCurrentURL" />
<command id="cmd_zenCopyCurrentURLMarkdown" />
<command id="cmd_zenCtxDeleteWorkspace" />
<command id="cmd_zenChangeWorkspaceName" />
<command id="cmd_zenToggleLibrary" />
</commandset>
<keyset id="zenKeyset"></keyset>

View file

@ -57,67 +57,11 @@
</panelmultiview>
</panel>
<panel flip="slide" type="arrow" orient="vertical" id="PanelUI-zen-workspaces" position="bottomright topright" mainview="true" side="left">
<panelmultiview id="PanelUI-zen-workspaces-multiview" mainViewId="PanelUI-zen-workspaces-view">
<panelview id="PanelUI-zen-workspaces-view" class="PanelUI-subView" role="document" mainview-with-header="true" has-custom-header="true" closemenu="none">
<vbox>
<hbox>
<h3 data-l10n-id="zen-panel-ui-workspaces-text" id="PanelUI-zen-workspaces-header"></h3>
<toolbarbutton id="PanelUI-zen-workspaces-reorder-mode" class="subviewbutton">
<image></image>
</toolbarbutton>
<toolbarbutton id="PanelUI-zen-workspaces-new" class="subviewbutton">
<image></image>
</toolbarbutton>
</hbox>
</vbox>
<html:div id="PanelUI-zen-workspaces-list">
</html:div>
</panelview>
<panelview id="PanelUI-zen-workspaces-create" class="PanelUI-subView" role="document" mainview-with-header="true" has-custom-header="true">
<vbox class="PanelUI-zen-workspaces-user-create">
<h1 data-l10n-id="zen-panel-ui-workspaces-create-text"></h1>
<hbox class="PanelUI-zen-workspaces-creation-wraper">
<hbox class="PanelUI-zen-workspaces-icons-container create"></hbox>
<html:input autofocus="true" id="PanelUI-zen-workspaces-create-input" type="text" placeholder="Enter workspace name" />
</hbox>
</vbox>
<html:moz-button-group class="panel-footer" id="PanelUI-zen-workspaces-create-footer">
<button disabled="true" default="true" slot="primary" id="PanelUI-zen-workspaces-create-save" class="footer-button" data-l10n-id="zen-panel-ui-workspaces-create-save">
</button>
<button id="PanelUI-zen-workspaces-create-cancel" class="footer-button" data-l10n-id="zen-panel-ui-workspaces-create-cancel">
</button>
</html:moz-button-group>
</panelview>
<panelview id="PanelUI-zen-workspaces-edit" class="PanelUI-subView" role="document" mainview-with-header="true" has-custom-header="true">
<vbox class="PanelUI-zen-workspaces-user-create">
<h1 data-l10n-id="zen-panel-ui-workspaces-edit-text"></h1>
<hbox class="PanelUI-zen-workspaces-creation-wraper">
<hbox class="PanelUI-zen-workspaces-icons-container edit"></hbox>
<html:input autofocus="true" id="PanelUI-zen-workspaces-edit-input" type="text" placeholder="Enter workspace name" />
</hbox>
</vbox>
<html:moz-button-group class="panel-footer" id="PanelUI-zen-workspaces-edit-footer">
<button disabled="true" default="true" slot="primary" id="PanelUI-zen-workspaces-edit-save" class="footer-button" data-l10n-id="zen-panel-ui-workspaces-edit-save">
</button>
<button id="PanelUI-zen-workspaces-edit-cancel" class="footer-button" data-l10n-id="zen-panel-ui-workspaces-edit-cancel">
</button>
</html:moz-button-group>
</panelview>
<panelview id="PanelUI-zen-workspaces-icon-picker" class="PanelUI-subView" role="document" mainview-with-header="true" has-custom-header="true">
<vbox id="PanelUI-zen-workspaces-icon-picker-wrapper">
<html:div id="PanelUI-zen-workspaces-icon-search-bar">
<html:input autofocus="true" type="text" id="PanelUI-zen-workspaces-icon-search-input"/>
</html:div>
</vbox>
</panelview>
</panelmultiview>
</panel>
<menupopup id="zenWorkspaceActionsMenu">
<menuitem id="context_zenOpenWorkspace" data-l10n-id="zen-workspaces-panel-context-open"/>
<menuseparator/>
<menuitem id="context_zenEditWorkspace" data-l10n-id="zen-workspaces-panel-context-edit"/>
<menupopup id="zenWorkspaceMoreActions">
<menuitem id="context_zenEditWorkspace" data-l10n-id="zen-workspaces-panel-change-name" command="cmd_zenChangeWorkspaceName"/>
<menuitem class="zenToolbarThemePicker"
data-l10n-id="zen-workspaces-change-gradient"
command="cmd_zenOpenZenThemePicker"/>
<menu id="context_zenWorkspacesOpenInContainerTab"
data-l10n-id="zen-workspaces-panel-context-open-in-container-tab"
selection-type="single"
@ -127,5 +71,5 @@
<menupopup />
</menu>
<menuseparator/>
<menuitem id="context_zenDeleteWorkspace" data-l10n-id="zen-workspaces-panel-context-delete"/>
<menuitem id="context_zenDeleteWorkspace" data-l10n-id="zen-workspaces-panel-context-delete" command="cmd_zenCtxDeleteWorkspace"/>
</menupopup>

View file

@ -9,5 +9,3 @@
<script type="text/javascript" src="chrome://browser/content/zen-components/ZenWorkspaces.mjs"></script>
<script type="text/javascript" src="chrome://browser/content/zen-components/ZenWorkspacesSync.mjs"></script>
<script type="text/javascript" src="chrome://browser/content/zen-components/ZenActorsManager.mjs"></script>
<script type="text/javascript" src="chrome://browser/content/zen-components/ZenSessionStore.mjs"></script>

View file

@ -9,5 +9,6 @@
context="toolbar-context-menu"
mode="icons">
<toolbarbutton removable="true" class="chromeclass-toolbar-additional toolbarbutton-1 zen-sidebar-action-button" id="zen-expand-sidebar-button" command="cmd_zenToggleSidebar" data-l10n-id="sidebar-zen-expand"></toolbarbutton>
<toolbarbutton removable="true" class="chromeclass-toolbar-additional toolbarbutton-1 zen-sidebar-action-button" id="zen-open-library" command="cmd_zenToggleLibrary"></toolbarbutton>
<zen-workspace-icons id="zen-workspaces-button" overflows="false" removable="false"></zen-workspace-icons>
</toolbar>

View file

@ -14,37 +14,30 @@ var gZenMarketplaceManager = {
return;
}
if (!window.gZenMods) {
window.gZenMods = ZenMultiWindowFeature.currentBrowser.gZenMods;
}
header.appendChild(this._initDisableAll());
this._initImportExport();
this.__hasInitializedEvents = true;
await this._buildModsList();
await this._buildThemesList();
Services.prefs.addObserver(gZenMods.updatePref, this);
Services.prefs.addObserver(this.updatePref, this);
const checkForUpdateClick = (event) => {
if (event.target === checkForUpdates) {
event.preventDefault();
this._checkForThemeUpdates(event);
}
};
checkForUpdates.addEventListener('click', checkForUpdateClick);
document.addEventListener('ZenModsMarketplace:CheckForUpdatesFinished', (event) => {
document.addEventListener('ZenThemeMarketplace:CheckForUpdatesFinished', (event) => {
checkForUpdates.disabled = false;
const updates = event.detail.updates;
const success = document.getElementById('zenThemeMarketplaceUpdatesSuccess');
const error = document.getElementById('zenThemeMarketplaceUpdatesFailure');
if (updates) {
success.hidden = false;
error.hidden = true;
@ -55,16 +48,13 @@ var gZenMarketplaceManager = {
});
window.addEventListener('unload', () => {
Services.prefs.removeObserver(gZenMods.updatePref, this);
Services.prefs.removeObserver(this.updatePref, this);
this.__hasInitializedEvents = false;
document.removeEventListener('ZenModsMarketplace:CheckForUpdatesFinished', this);
document.removeEventListener('ZenCheckForModUpdates', this);
document.removeEventListener('ZenThemeMarketplace:CheckForUpdatesFinished', this);
document.removeEventListener('ZenCheckForThemeUpdates', this);
checkForUpdates.removeEventListener('click', checkForUpdateClick);
this.modsList.innerHTML = '';
this._doNotRebuildModsList = false;
this.themesList.innerHTML = '';
this._doNotRebuildThemesList = false;
});
},
@ -73,32 +63,36 @@ var gZenMarketplaceManager = {
const exportButton = document.getElementById('zenThemeMarketplaceExport');
if (importButton) {
importButton.addEventListener('click', this._importThemes.bind(this));
importButton.addEventListener('click', async () => {
await this._importThemes();
});
}
if (exportButton) {
exportButton.addEventListener('click', this._exportThemes.bind(this));
exportButton.addEventListener('click', async () => {
await this._exportThemes();
});
}
},
_initDisableAll() {
const areModsDisabled = Services.prefs.getBoolPref('zen.themes.disable-all', false);
const browser = ZenMultiWindowFeature.currentBrowser;
const areThemesDisabled = Services.prefs.getBoolPref('zen.themes.disable-all', false);
const browser = ZenThemesCommon.currentBrowser;
const mozToggle = document.createElement('moz-toggle');
mozToggle.className =
'zenThemeMarketplaceItemPreferenceToggle zenThemeMarketplaceDisableAllToggle';
mozToggle.pressed = !areModsDisabled;
mozToggle.pressed = !areThemesDisabled;
browser.document.l10n.setAttributes(
mozToggle,
`zen-theme-disable-all-${!areModsDisabled ? 'enabled' : 'disabled'}`
`zen-theme-disable-all-${!areThemesDisabled ? 'enabled' : 'disabled'}`
);
mozToggle.addEventListener('toggle', async (event) => {
const { pressed = false } = event.target || {};
this.modsList.style.display = pressed ? '' : 'none';
this.themesList.style.display = pressed ? '' : 'none';
Services.prefs.setBoolPref('zen.themes.disable-all', !pressed);
browser.document.l10n.setAttributes(
mozToggle,
@ -106,65 +100,90 @@ var gZenMarketplaceManager = {
);
});
if (areModsDisabled) {
this.modsList.style.display = 'none';
if (areThemesDisabled) {
this.themesList.style.display = 'none';
}
return mozToggle;
},
async observe() {
await this._buildModsList();
await this._buildThemesList();
},
_checkForThemeUpdates(event) {
// Send a message to the child to check for theme updates.
event.target.disabled = true;
// send an event that will be listened by the child process.
document.dispatchEvent(new CustomEvent('ZenCheckForModUpdates'));
document.dispatchEvent(new CustomEvent('ZenCheckForThemeUpdates'));
},
get modsList() {
if (!this._modsList) {
this._modsList = document.getElementById('zenThemeMarketplaceList');
get updatePref() {
return 'zen.themes.updated-value-observer';
},
triggerThemeUpdate() {
Services.prefs.setBoolPref(this.updatePref, !Services.prefs.getBoolPref(this.updatePref));
},
get themesList() {
if (!this._themesList) {
this._themesList = document.getElementById('zenThemeMarketplaceList');
}
return this._modsList;
return this._themesList;
},
async removeMod(modId) {
await gZenMods.removeMod(modId);
async removeTheme(themeId) {
const themePath = ZenThemesCommon.getThemeFolder(themeId);
gZenMods.triggerModsUpdate();
console.info(`[ZenThemeMarketplaceParent:settings]: Removing theme ${themePath}`);
await IOUtils.remove(themePath, { recursive: true, ignoreAbsent: true });
const themes = await ZenThemesCommon.getThemes();
delete themes[themeId];
await IOUtils.writeJSON(ZenThemesCommon.themesDataFile, themes);
this.triggerThemeUpdate();
},
async disableMod(modId) {
await gZenMods.disableMod(modId);
async disableTheme(themeId) {
const themes = await ZenThemesCommon.getThemes();
const theme = themes[themeId];
this._doNotRebuildModsList = true;
gZenMods.triggerModsUpdate();
console.log(`[ZenThemeMarketplaceParent:settings]: Disabling theme ${theme.name}`);
theme.enabled = false;
await IOUtils.writeJSON(ZenThemesCommon.themesDataFile, themes);
this._doNotRebuildThemesList = true;
this.triggerThemeUpdate();
},
async enableMod(modId) {
await gZenMods.enableMod(modId);
async enableTheme(themeId) {
const themes = await ZenThemesCommon.getThemes();
const theme = themes[themeId];
this._doNotRebuildModsList = true;
gZenMods.triggerModsUpdate();
console.log(`[ZenThemeMarketplaceParent:settings]: Enabling theme ${theme.name}`);
theme.enabled = true;
await IOUtils.writeJSON(ZenThemesCommon.themesDataFile, themes);
this._doNotRebuildThemesList = true;
this.triggerThemeUpdate();
},
_triggerBuildUpdateWithoutRebuild() {
this._doNotRebuildModsList = true;
gZenMods.triggerModsUpdate();
this._doNotRebuildThemesList = true;
this.triggerThemeUpdate();
},
async _importThemes() {
const errorBox = document.getElementById('zenThemeMarketplaceImportFailure');
const successBox = document.getElementById('zenThemeMarketplaceImportSuccess');
successBox.hidden = true;
errorBox.hidden = true;
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.style.display = 'none';
@ -172,52 +191,37 @@ var gZenMarketplaceManager = {
input.setAttribute('accept', '.json');
let timeout;
const filePromise = new Promise((resolve) => {
input.addEventListener('change', (event) => {
if (timeout) {
clearTimeout(timeout);
}
if (timeout) clearTimeout(timeout);
const file = event.target.files[0];
resolve(file);
});
timeout = setTimeout(() => {
console.warn('[ZenSettings:ZenMods]: Import timeout reached, aborting.');
console.warn('[ZenThemeMarketplaceParent:settings]: Import timeout reached, aborting.');
resolve(null);
}, 60000);
});
input.addEventListener('cancel', () => {
console.warn('[ZenSettings:ZenMods]: Import cancelled by user.');
clearTimeout(timeout);
});
input.click();
try {
const file = await filePromise;
if (!file) {
return;
}
const content = await file.text();
const mods = JSON.parse(content);
for (const mod of Object.values(mods)) {
mod.modId = mod.id;
await window.ZenInstallMod(mod);
const themes = JSON.parse(content);
for (const theme of Object.values(themes)) {
theme.themeId = theme.id;
window.ZenInstallTheme(theme);
}
} catch (error) {
console.error('[ZenSettings:ZenMods]: Error while importing mods:', error);
console.error('[ZenThemeMarketplaceParent:settings]: Error while importing themes:', error);
errorBox.hidden = false;
}
if (input) {
input.remove();
} finally {
if (input) input.remove();
}
},
@ -228,54 +232,51 @@ var gZenMarketplaceManager = {
successBox.hidden = true;
errorBox.hidden = true;
let temporalAnchor, temporalUrl;
let a, url;
try {
const mods = await gZenMods.getMods();
const modsJson = JSON.stringify(mods, null, 2);
const blob = new Blob([modsJson], { type: 'application/json' });
temporalUrl = URL.createObjectURL(blob);
const themes = await ZenThemesCommon.getThemes();
const themesJson = JSON.stringify(themes, null, 2);
const blob = new Blob([themesJson], { type: 'application/json' });
url = URL.createObjectURL(blob);
// Creating a link to download the JSON file
temporalAnchor = document.createElement('a');
temporalAnchor.href = temporalUrl;
temporalAnchor.download = 'zen-mods-export.json';
document.body.appendChild(temporalAnchor);
temporalAnchor.click();
temporalAnchor.remove();
a = document.createElement('a');
a.href = url;
a.download = 'zen-themes-export.json';
document.body.appendChild(a);
a.click();
a.remove();
successBox.hidden = false;
} catch (error) {
console.error('[ZenSettings:ZenMods]: Error while exporting mods:', error);
console.error('[ZenThemeMarketplaceParent:settings]: Error while exporting themes:', error);
errorBox.hidden = false;
} finally {
if (a) {
a.remove();
}
if (temporalAnchor) {
temporalAnchor.remove();
if (url) {
URL.revokeObjectURL(url);
}
if (temporalUrl) {
URL.revokeObjectURL(temporalUrl);
}
},
async _buildModsList() {
if (!this.modsList) {
async _buildThemesList() {
if (!this.themesList) {
return;
}
if (this._doNotRebuildModsList) {
this._doNotRebuildModsList = false;
if (this._doNotRebuildThemesList) {
this._doNotRebuildThemesList = false;
return;
}
const mods = await gZenMods.getMods();
const themes = await ZenThemesCommon.getThemes();
const browser = ZenMultiWindowFeature.currentBrowser;
const modList = document.createElement('div');
const themeList = document.createElement('div');
for (const mod of Object.values(mods).sort((a, b) => a.name.localeCompare(b.name))) {
const sanitizedName = gZenMods.sanitizeModName(mod.name);
const isModEnabled = mod.enabled === undefined || mod.enabled;
for (const theme of Object.values(themes).sort((a, b) => a.name.localeCompare(b.name))) {
const sanitizedName = `theme-${theme.name?.replaceAll(/\s/g, '-')?.replaceAll(/[^A-Za-z_-]+/g, '')}`;
const isThemeEnabled = theme.enabled === undefined || theme.enabled;
const fragment = window.MozXULElement.parseXULToFragment(`
<vbox class="zenThemeMarketplaceItem">
<vbox class="zenThemeMarketplaceItemContent">
@ -285,14 +286,14 @@ var gZenMarketplaceManager = {
<description class="description-deemphasized zenThemeMarketplaceItemDescription"></description>
</vbox>
<hbox class="zenThemeMarketplaceItemActions">
${mod.preferences ? `<button id="zenThemeMarketplaceItemConfigureButton-${sanitizedName}" class="zenThemeMarketplaceItemConfigureButton" hidden="true"></button>` : ''}
${mod.homepage ? `<button id="zenThemeMarketplaceItemHomePageLink-${sanitizedName}" class="zenThemeMarketplaceItemHomepageButton" zen-mod-id="${mod.id}"></button>` : ''}
<button class="zenThemeMarketplaceItemUninstallButton" data-l10n-id="zen-theme-marketplace-remove-button" zen-mod-id="${mod.id}"></button>
${theme.preferences ? `<button id="zenThemeMarketplaceItemConfigureButton-${sanitizedName}" class="zenThemeMarketplaceItemConfigureButton" hidden="true"></button>` : ''}
${theme.homepage ? `<button id="zenThemeMarketplaceItemHomePageLink-${sanitizedName}" class="zenThemeMarketplaceItemHomepageButton" zen-theme-id="${theme.id}"></button>` : ''}
<button class="zenThemeMarketplaceItemUninstallButton" data-l10n-id="zen-theme-marketplace-remove-button" zen-theme-id="${theme.id}"></button>
</hbox>
</vbox>
`);
const modName = `${mod.name} (v${mod.version ?? '1.0.0'})`;
const themeName = `${theme.name} (v${theme.version || '1.0.0'})`;
const base = fragment.querySelector('.zenThemeMarketplaceItem');
const baseHeader = fragment.querySelector('#zenThemeMarketplaceItemContentHeader');
@ -307,7 +308,7 @@ var gZenMarketplaceManager = {
mainDialogDiv.className = 'zenThemeMarketplaceItemPreferenceDialog';
headerDiv.className = 'zenThemeMarketplaceItemPreferenceDialogTopBar';
headerTitle.textContent = modName;
headerTitle.textContent = themeName;
browser.document.l10n.setAttributes(headerTitle, 'zen-theme-marketplace-theme-header-title', {
name: sanitizedName,
});
@ -318,10 +319,10 @@ var gZenMarketplaceManager = {
contentDiv.className = 'zenThemeMarketplaceItemPreferenceDialogContent';
mozToggle.className = 'zenThemeMarketplaceItemPreferenceToggle';
mozToggle.pressed = isModEnabled;
mozToggle.pressed = isThemeEnabled;
browser.document.l10n.setAttributes(
mozToggle,
`zen-theme-marketplace-toggle-${isModEnabled ? 'enabled' : 'disabled'}-button`
`zen-theme-marketplace-toggle-${isThemeEnabled ? 'enabled' : 'disabled'}-button`
);
baseHeader.appendChild(mozToggle);
@ -339,34 +340,34 @@ var gZenMarketplaceManager = {
});
mozToggle.addEventListener('toggle', async (event) => {
const modId = event.target
const themeId = event.target
.closest('.zenThemeMarketplaceItem')
.querySelector('.zenThemeMarketplaceItemUninstallButton')
.getAttribute('zen-mod-id');
.getAttribute('zen-theme-id');
event.target.setAttribute('disabled', true);
if (!event.target.hasAttribute('pressed')) {
await this.disableMod(modId);
await this.disableTheme(themeId);
browser.document.l10n.setAttributes(
mozToggle,
'zen-theme-marketplace-toggle-disabled-button'
);
if (mod.preferences) {
if (theme.preferences) {
document
.getElementById(`zenThemeMarketplaceItemConfigureButton-${sanitizedName}`)
.setAttribute('hidden', true);
}
} else {
await this.enableMod(modId);
await this.enableTheme(themeId);
browser.document.l10n.setAttributes(
mozToggle,
'zen-theme-marketplace-toggle-enabled-button'
);
if (mod.preferences) {
if (theme.preferences) {
document
.getElementById(`zenThemeMarketplaceItemConfigureButton-${sanitizedName}`)
.removeAttribute('hidden');
@ -378,8 +379,8 @@ var gZenMarketplaceManager = {
}, 400);
});
fragment.querySelector('.zenThemeMarketplaceItemTitle').textContent = modName;
fragment.querySelector('.zenThemeMarketplaceItemDescription').textContent = mod.description;
fragment.querySelector('.zenThemeMarketplaceItemTitle').textContent = themeName;
fragment.querySelector('.zenThemeMarketplaceItemDescription').textContent = theme.description;
fragment
.querySelector('.zenThemeMarketplaceItemUninstallButton')
.addEventListener('click', async (event) => {
@ -391,34 +392,34 @@ var gZenMarketplaceManager = {
return;
}
await this.removeMod(event.target.getAttribute('zen-mod-id'));
await this.removeTheme(event.target.getAttribute('zen-theme-id'));
});
if (mod.homepage) {
if (theme.homepage) {
const homepageButton = fragment.querySelector('.zenThemeMarketplaceItemHomepageButton');
homepageButton.addEventListener('click', () => {
// open the homepage url in a new tab
const url = mod.homepage;
const url = theme.homepage;
window.open(url, '_blank');
});
}
if (mod.preferences) {
if (theme.preferences) {
fragment
.querySelector('.zenThemeMarketplaceItemConfigureButton')
.addEventListener('click', () => {
dialog.showModal();
});
if (isModEnabled) {
if (isThemeEnabled) {
fragment
.querySelector('.zenThemeMarketplaceItemConfigureButton')
.removeAttribute('hidden');
}
}
const preferences = await gZenMods.getModPreferences(mod);
const preferences = await ZenThemesCommon.getThemePreferences(theme);
if (preferences.length > 0) {
const preferencesWrapper = document.createXULElement('vbox');
@ -470,7 +471,7 @@ var gZenMarketplaceManager = {
if (!['string', 'number'].includes(valueType)) {
console.log(
`[ZenSettings:ZenMods]: Warning, invalid data type received (${valueType}), skipping.`
`[ZenThemeMarketplaceParent:settings]: Warning, invalid data type received (${valueType}), skipping.`
);
continue;
}
@ -582,7 +583,7 @@ var gZenMarketplaceManager = {
input.addEventListener(
'change',
gZenMods.debounce((event) => {
ZenThemesCommon.debounce((event) => {
const value = event.target.value;
Services.prefs.setStringPref(property, value);
@ -616,18 +617,18 @@ var gZenMarketplaceManager = {
default:
console.log(
`[ZenSettings:ZenMods]: Warning, unknown preference type received (${type}), skipping.`
`[ZenThemeMarketplaceParent:settings]: Warning, unknown preference type received (${type}), skipping.`
);
continue;
}
}
contentDiv.appendChild(preferencesWrapper);
}
modList.appendChild(fragment);
themeList.appendChild(fragment);
}
this.modsList.replaceChildren(...modList.children);
modList.remove();
this.themesList.replaceChildren(...themeList.children);
themeList.remove();
},
};
@ -635,19 +636,6 @@ const kZenExtendedSidebar = 'zen.view.sidebar-expanded';
const kZenSingleToolbar = 'zen.view.use-single-toolbar';
var gZenLooksAndFeel = {
kZenColors: [
'#aac7ff',
'#74d7cb',
'#a0d490',
'#dec663',
'#ffb787',
'#dec1b1',
'#ffb1c0',
'#ddbfc3',
'#f6b0ea',
'#d4bbff',
],
init() {
if (this.__hasInitialized) return;
this.__hasInitialized = true;
@ -749,8 +737,7 @@ var gZenLooksAndFeel = {
_initializeColorPicker(accentColor) {
let elem = document.getElementById('zenLooksAndFeelColorOptions');
elem.innerHTML = '';
for (let color of this.kZenColors) {
for (let color of ZenThemesCommon.kZenColors) {
let colorElemParen = document.createElement('div');
let colorElem = document.createElement('div');
colorElemParen.classList.add('zenLooksAndFeelColorOptionParen');
@ -774,7 +761,7 @@ var gZenLooksAndFeel = {
},
_getInitialAccentColor() {
return Services.prefs.getStringPref('zen.theme.accent-color', this.kZenColors[0]);
return Services.prefs.getStringPref('zen.theme.accent-color', ZenThemesCommon.kZenColors[0]);
},
};
@ -1233,7 +1220,7 @@ Preferences.addAll([
default: false,
},
{
id: 'zen.mods.auto-update',
id: 'browser.tabs.unloadOnLowMemory',
type: 'bool',
default: true,
},

View file

@ -1,4 +1,5 @@
<script src="chrome://browser/content/zen-components/ZenCommonUtils.mjs" defer=""/>
<script src="chrome://browser/content/zen-components/ZenThemesCommon.mjs" defer=""/>
<html:template id="template-paneZenMarketplace">
<hbox id="ZenMarketplaceCategory"
class="subcategory"
@ -20,10 +21,6 @@
<button id="zenThemeMarketplaceCheckForUpdates" data-l10n-id="zen-theme-marketplace-check-for-updates-button" />
</hbox>
<checkbox id="zenWorkspacesForceContainerTabsToWorkspace"
data-l10n-id="zen-themes-auto-update"
preference="zen.mods.auto-update"/>
<description class="description-deemphasized" data-l10n-id="zen-theme-marketplace-updates-success" hidden="true" id="zenThemeMarketplaceUpdatesSuccess" />
<description class="description-deemphasized" data-l10n-id="zen-theme-marketplace-updates-failure" hidden="true" id="zenThemeMarketplaceUpdatesFailure" />

View file

@ -31,6 +31,15 @@
<html:h1 data-l10n-id="pane-zen-tabs-unloader-title"/>
</hbox>
<groupbox id="zenTabsUnloadGroup" data-category="paneZenTabManagement" hidden="true" class="highlighting-group">
<label><html:h2 data-l10n-id="zen-tabs-unloader-header"/></label>
<description class="description-deemphasized" data-l10n-id="zen-tabs-unloader-description" />
<checkbox id="zenTabsUnloadActivate"
data-l10n-id="zen-tabs-unloader-enabled"
preference="browser.tabs.unloadOnLowMemory"/>
</groupbox>
<hbox id="zenPinnedTabsManagerCategory"
class="subcategory"
hidden="true"

View file

@ -1,13 +0,0 @@
diff --git a/browser/components/search/SearchUIUtils.sys.mjs b/browser/components/search/SearchUIUtils.sys.mjs
index ecebaad93acfc9eb7dfd9d9f56fec2e1a4abe392..8bb1348b3258dbc518d23ec39181a81f87fc8c1e 100644
--- a/browser/components/search/SearchUIUtils.sys.mjs
+++ b/browser/components/search/SearchUIUtils.sys.mjs
@@ -403,7 +403,7 @@ export var SearchUIUtils = {
triggeringSearchEngine: engine.name,
},
});
-
+ window.gZenGlanceManager?.onSearchSelectCommand(where);
return { engine, url: submission.uri };
},

View file

@ -1,5 +1,5 @@
diff --git a/browser/components/sessionstore/SessionStore.sys.mjs b/browser/components/sessionstore/SessionStore.sys.mjs
index 8c6047e1ada5a22e57e1e665965237c9e22641d7..8d0585e738a5a758ebbddfa0787c71d634dadd4d 100644
index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d81447514e2c 100644
--- a/browser/components/sessionstore/SessionStore.sys.mjs
+++ b/browser/components/sessionstore/SessionStore.sys.mjs
@@ -2088,7 +2088,6 @@ var SessionStoreInternal = {
@ -31,19 +31,17 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..8d0585e738a5a758ebbddfa0787c71d6
return;
}
@@ -3925,6 +3922,11 @@ var SessionStoreInternal = {
@@ -3925,6 +3922,9 @@ var SessionStoreInternal = {
Math.min(tabState.index, tabState.entries.length)
);
tabState.pinned = false;
+ tabState.zenEssential = false;
+ tabState.zenPinnedId = null;
+ tabState.zenIsGlance = false;
+ tabState.zenGlanceId = null;
+ tabState.zenHasStaticLabel = false;
if (inBackground === false) {
aWindow.gBrowser.selectedTab = newTab;
@@ -5239,7 +5241,7 @@ var SessionStoreInternal = {
@@ -5239,7 +5239,7 @@ var SessionStoreInternal = {
}
let workspaceID = aWindow.getWorkspaceID();
@ -52,7 +50,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..8d0585e738a5a758ebbddfa0787c71d6
winData.workspaceID = workspaceID;
}
},
@@ -5430,14 +5432,15 @@ var SessionStoreInternal = {
@@ -5430,14 +5430,15 @@ var SessionStoreInternal = {
}
let tabbrowser = aWindow.gBrowser;
@ -70,7 +68,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..8d0585e738a5a758ebbddfa0787c71d6
continue;
}
let tabData = lazy.TabState.collect(tab, TAB_CUSTOM_VALUES.get(tab));
@@ -5456,8 +5459,8 @@ var SessionStoreInternal = {
@@ -5456,8 +5457,8 @@ var SessionStoreInternal = {
// We don't store the Firefox View tab in Session Store, so if it was the last selected "tab" when
// a window is closed, point to the first item in the tab strip instead (it will never be the Firefox View tab,
// since it's only inserted into the tab strip after it's selected).
@ -81,7 +79,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..8d0585e738a5a758ebbddfa0787c71d6
winData.title = tabbrowser.tabs[0].label;
}
winData.selected = selectedIndex;
@@ -5569,8 +5572,8 @@ var SessionStoreInternal = {
@@ -5569,8 +5570,8 @@ var SessionStoreInternal = {
// selectTab represents.
let selectTab = 0;
if (overwriteTabs) {
@ -92,7 +90,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..8d0585e738a5a758ebbddfa0787c71d6
selectTab = Math.min(selectTab, winData.tabs.length);
}
@@ -5613,6 +5616,7 @@ var SessionStoreInternal = {
@@ -5613,6 +5614,7 @@ var SessionStoreInternal = {
winData.tabs,
winData.groups ?? []
);
@ -100,13 +98,12 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..8d0585e738a5a758ebbddfa0787c71d6
this._log.debug(
`restoreWindow, createTabsForSessionRestore returned ${tabs.length} tabs`
);
@@ -6162,6 +6166,22 @@ var SessionStoreInternal = {
@@ -6162,8 +6164,23 @@ var SessionStoreInternal = {
// Most of tabData has been restored, now continue with restoring
// attributes that may trigger external events.
+ if (tabData.zenEssential) {
+ tab.setAttribute("zen-essential", "true");
+ tabData.pinned = true; // Essential tabs are always pinned.
+ }
+ if (tabData.zenIsEmpty) {
+ tab.setAttribute("zen-empty-tab", "true");
@ -121,5 +118,8 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..8d0585e738a5a758ebbddfa0787c71d6
+ tab.setAttribute("zenDefaultUserContextId", true);
+ }
if (tabData.pinned) {
- if (tabData.pinned) {
+ if (tabData.pinned || tabData.zenEssential) {
tabbrowser.pinTab(tab);
} else {
tabbrowser.unpinTab(tab);

View file

@ -1,8 +1,8 @@
diff --git a/browser/components/sessionstore/TabState.sys.mjs b/browser/components/sessionstore/TabState.sys.mjs
index 8f7ed557e6aa61e7e16ed4a8d785ad5fe651b3d8..76f4cf5aef30cb580ef0295fe6928b5a6a362f4b 100644
index 8f7ed557e6aa61e7e16ed4a8d785ad5fe651b3d8..254849e13f7566029dc780c45e376e0f0d427cb5 100644
--- a/browser/components/sessionstore/TabState.sys.mjs
+++ b/browser/components/sessionstore/TabState.sys.mjs
@@ -84,6 +84,18 @@ class _TabState {
@@ -84,6 +84,16 @@ class _TabState {
tabData.groupId = tab.group.id;
}
@ -15,8 +15,6 @@ index 8f7ed557e6aa61e7e16ed4a8d785ad5fe651b3d8..76f4cf5aef30cb580ef0295fe6928b5a
+ tabData.zenPinnedIcon = tab.getAttribute("zen-pinned-icon");
+ tabData.zenIsEmpty = tab.hasAttribute("zen-empty-tab");
+ tabData.zenHasStaticLabel = tab.hasAttribute("zen-has-static-label");
+ tabData.zenGlanceId = tab.getAttribute("glance-id");
+ tabData.zenIsGlance = tab.hasAttribute("zen-glance-tab");
+
tabData.searchMode = tab.ownerGlobal.gURLBar.getSearchMode(browser, true);

View file

@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b598a513c2 100644
index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b032cf200f1 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -413,11 +413,41 @@
@ -292,17 +292,37 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
let url = "about:blank";
if (tabData.entries?.length) {
@@ -3598,7 +3675,8 @@
@@ -3598,7 +3675,29 @@
skipLoad: true,
preferredRemoteType,
});
-
+ tab._originalUrl = url;
+ gZenSessionStore.restoreInitialTabData(tab, tabData);
+ if (tabData.zenWorkspace) {
+ tab.setAttribute("zen-workspace-id", tabData.zenWorkspace);
+ }
+ if (tabData.zenPinnedId) {
+ tab.setAttribute("zen-pin-id", tabData.zenPinnedId);
+ }
+ if (tabData.zenIsEmpty) {
+ tab.setAttribute("zen-empty-tab", "true");
+ }
+ if (tabData.zenHasStaticLabel) {
+ tab.setAttribute("zen-has-static-label", "true");
+ }
+ if (tabData.zenEssential) {
+ tab.setAttribute("zen-essential", "true");
+ }
+ if (tabData.zenDefaultUserContextId) {
+ tab.setAttribute("zenDefaultUserContextId", "true");
+ }
+ if (tabData.zenPinnedEntry) {
+ tab.setAttribute("zen-pinned-entry", tabData.zenPinnedEntry);
+ }
if (select) {
tabToSelect = tab;
}
@@ -3622,7 +3700,8 @@
@@ -3622,7 +3721,8 @@
// needs calling:
shouldUpdateForPinnedTabs = true;
}
@ -312,7 +332,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
let { groupId } = tabData;
const tabGroup = tabGroupWorkingData.get(groupId);
// if a tab refers to a tab group we don't know, skip any group
@@ -3636,7 +3715,10 @@
@@ -3636,7 +3736,10 @@
tabGroup.stateData.id,
tabGroup.stateData.color,
tabGroup.stateData.collapsed,
@ -324,7 +344,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
);
tabsFragment.appendChild(tabGroup.node);
}
@@ -3684,8 +3766,16 @@
@@ -3684,8 +3787,16 @@
// to remove the old selected tab.
if (tabToSelect) {
let leftoverTab = this.selectedTab;
@ -343,7 +363,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
}
if (tabs.length > 1 || !tabs[0].selected) {
@@ -3881,7 +3971,7 @@
@@ -3881,7 +3992,7 @@
// Ensure we have an index if one was not provided.
if (typeof elementIndex != "number" && typeof tabIndex != "number") {
// Move the new tab after another tab if needed, to the end otherwise.
@ -352,7 +372,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
if (
!bulkOrderedOpen &&
((openerTab &&
@@ -3904,7 +3994,7 @@
@@ -3904,7 +4015,7 @@
) {
elementIndex = Infinity;
} else if (previousTab.visible) {
@ -361,7 +381,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
} else if (previousTab == FirefoxViewHandler.tab) {
elementIndex = 0;
}
@@ -3932,14 +4022,14 @@
@@ -3932,10 +4043,10 @@
}
// Ensure index is within bounds.
if (tab.pinned) {
@ -375,12 +395,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
index = Math.min(index, allItems.length);
}
/** @type {MozTabbrowserTab|undefined} */
- let itemAfter = allItems.at(index);
+ let itemAfter = gZenGlanceManager.getTabOrGlanceParent(allItems.at(index));
// Prevent a flash of unstyled content by setting up the tab content
// and inherited attributes before appending it (see Bug 1592054):
@@ -3947,7 +4037,7 @@
@@ -3947,7 +4058,7 @@
this.tabContainer._invalidateCachedTabs();
@ -389,7 +404,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
if (this.isTab(itemAfter) && itemAfter.group == tabGroup) {
// Place at the front of, or between tabs in, the same tab group
this.tabContainer.insertBefore(tab, itemAfter);
@@ -4268,6 +4358,9 @@
@@ -4268,6 +4379,9 @@
return;
}
@ -399,7 +414,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
this.removeTabs(selectedTabs, { telemetrySource });
}
@@ -4520,6 +4613,7 @@
@@ -4520,6 +4634,7 @@
telemetrySource,
} = {}
) {
@ -407,7 +422,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs
// can be considered equivalent to closing the window.
if (
@@ -4604,6 +4698,7 @@
@@ -4604,6 +4719,7 @@
if (lastToClose) {
this.removeTab(lastToClose, aParams);
}
@ -415,7 +430,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
} catch (e) {
console.error(e);
}
@@ -4641,6 +4736,12 @@
@@ -4641,6 +4757,12 @@
aTab._closeTimeNoAnimTimerId = Glean.browserTabclose.timeNoAnim.start();
}
@ -428,7 +443,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
// Handle requests for synchronously removing an already
// asynchronously closing tab.
if (!animate && aTab.closing) {
@@ -4655,7 +4756,9 @@
@@ -4655,7 +4777,9 @@
// frame created for it (for example, by updating the visually selected
// state).
let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width;
@ -439,7 +454,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
if (
!this._beginRemoveTab(aTab, {
closeWindowFastpath: true,
@@ -4821,7 +4924,7 @@
@@ -4821,7 +4945,7 @@
closeWindowWithLastTab != null
? closeWindowWithLastTab
: !window.toolbar.visible ||
@ -448,7 +463,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
if (closeWindow) {
// We've already called beforeunload on all the relevant tabs if we get here,
@@ -4845,6 +4948,7 @@
@@ -4845,6 +4969,7 @@
newTab = true;
}
@ -456,7 +471,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
aTab._endRemoveArgs = [closeWindow, newTab];
// swapBrowsersAndCloseOther will take care of closing the window without animation.
@@ -4885,9 +4989,7 @@
@@ -4885,9 +5010,7 @@
aTab._mouseleave();
if (newTab) {
@ -467,7 +482,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
} else {
TabBarVisibility.update();
}
@@ -5016,6 +5118,8 @@
@@ -5016,6 +5139,8 @@
this.tabs[i]._tPos = i;
}
@ -476,7 +491,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
if (!this._windowIsClosing) {
if (wasPinned) {
this.tabContainer._positionPinnedTabs();
@@ -5230,6 +5334,7 @@
@@ -5230,6 +5355,7 @@
}
let excludeTabs = new Set(aExcludeTabs);
@ -484,7 +499,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
// If this tab has a successor, it should be selectable, since
// hiding or closing a tab removes that tab as a successor.
@@ -5242,13 +5347,13 @@
@@ -5242,13 +5368,13 @@
!excludeTabs.has(aTab.owner) &&
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
) {
@ -500,7 +515,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
);
let tab = this.tabContainer.findNextTab(aTab, {
@@ -5264,7 +5369,7 @@
@@ -5264,7 +5390,7 @@
}
if (tab) {
@ -509,7 +524,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
}
// If no qualifying visible tab was found, see if there is a tab in
@@ -5285,7 +5390,7 @@
@@ -5285,7 +5411,7 @@
});
}
@ -518,7 +533,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
}
_blurTab(aTab) {
@@ -5686,10 +5791,10 @@
@@ -5686,10 +5812,10 @@
SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
}
@ -531,7 +546,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
aTab.selected ||
aTab.closing ||
// Tabs that are sharing the screen, microphone or camera cannot be hidden.
@@ -5986,7 +6091,7 @@
@@ -5986,7 +6112,7 @@
// Don't allow mixing pinned and unpinned tabs.
if (this.isTab(element) && element.pinned) {
@ -540,7 +555,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
} else {
tabIndex = Math.max(tabIndex, this.pinnedTabCount);
}
@@ -6012,10 +6117,16 @@
@@ -6012,10 +6138,16 @@
this.#handleTabMove(
element,
() => {
@ -559,7 +574,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
if (neighbor && this.isTab(element) && tabIndex > element._tPos) {
neighbor.after(element);
} else {
@@ -6084,17 +6195,29 @@
@@ -6084,17 +6216,26 @@
targetElement = targetElement.group;
}
}
@ -568,12 +583,8 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
+ element = element.group;
+ }
// Don't allow mixing pinned and unpinned tabs.
- if (element.pinned && !targetElement?.pinned) {
if (element.pinned && !targetElement?.pinned) {
- targetElement = this.tabs[this.pinnedTabCount - 1];
+ if (element.hasAttribute('zen-essential') && !targetElement?.hasAttribute('zen-essential')) {
+ targetElement = this.tabs.filter(tab => !tab.hasAttribute('zen-glance-tab'))[this._numZenEssentials - 1];
+ moveBefore = false;
+ } else if (element.pinned && !targetElement?.pinned) {
+ targetElement = this.tabs.filter(tab => !tab.hasAttribute('zen-glance-tab'))[this.pinnedTabCount - 1];
moveBefore = false;
} else if (!element.pinned && targetElement && targetElement.pinned) {
@ -593,7 +604,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
if (element.pinned && this.tabContainer.verticalMode) {
return this.tabContainer.verticalPinnedTabsContainer;
}
@@ -6154,7 +6277,7 @@
@@ -6154,7 +6295,7 @@
if (!this.isTab(aTab)) {
throw new Error("Can only move a tab into a tab group");
}
@ -602,7 +613,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
return;
}
if (aTab.group && aTab.group.id === aGroup.id) {
@@ -6248,6 +6371,10 @@
@@ -6248,6 +6389,10 @@
moveActionCallback();
@ -613,7 +624,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
// Clear tabs cache after moving nodes because the order of tabs may have
// changed.
this.tabContainer._invalidateCachedTabs();
@@ -7145,7 +7272,7 @@
@@ -7145,7 +7290,7 @@
// preventDefault(). It will still raise the window if appropriate.
break;
}
@ -622,7 +633,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
window.focus();
aEvent.preventDefault();
break;
@@ -8044,6 +8171,7 @@
@@ -8044,6 +8189,7 @@
aWebProgress.isTopLevel
) {
this.mTab.setAttribute("busy", "true");
@ -630,7 +641,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
gBrowser._tabAttrModified(this.mTab, ["busy"]);
this.mTab._notselectedsinceload = !this.mTab.selected;
}
@@ -9009,7 +9137,7 @@ var TabContextMenu = {
@@ -9009,7 +9155,7 @@ var TabContextMenu = {
);
contextUnpinSelectedTabs.hidden =
!this.contextTab.pinned || !this.multiselected;
@ -639,7 +650,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b5
// Move Tab items
let contextMoveTabOptions = document.getElementById(
"context_moveTabOptions"
@@ -9278,6 +9406,7 @@ var TabContextMenu = {
@@ -9278,6 +9424,7 @@ var TabContextMenu = {
telemetrySource: gBrowser.TabMetrics.METRIC_SOURCE.TAB_STRIP,
});
} else {

View file

@ -1,13 +0,0 @@
diff --git a/browser/components/tabbrowser/content/tabgroup.js b/browser/components/tabbrowser/content/tabgroup.js
index 6dc774ea335b0c5dba7dcf76cdb23728faae1343..b0b9ef236c2e8517db4bcf3270596456bbefe11d 100644
--- a/browser/components/tabbrowser/content/tabgroup.js
+++ b/browser/components/tabbrowser/content/tabgroup.js
@@ -301,7 +301,7 @@
*/
addTabs(tabs, metricsContext) {
for (let tab of tabs) {
- if (tab.pinned) {
+ if (tab.pinned !== this.pinned) {
tab.ownerGlobal.gBrowser.unpinTab(tab);
}
let tabToMove =

View file

@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabs.js b/browser/components/tabbrowser/content/tabs.js
index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c574dfc103 100644
index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea9596403cef 100644
--- a/browser/components/tabbrowser/content/tabs.js
+++ b/browser/components/tabbrowser/content/tabs.js
@@ -83,7 +83,7 @@
@ -46,15 +46,6 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
expandGroupOnDrop = true;
}
}
@@ -868,7 +869,7 @@
? event.screenY - window.screenY - tabOffset
: event.screenY - window.screenY,
scrollPos:
- this.verticalMode && tab.pinned
+ this.verticalMode && tab.pinned && false
? this.verticalPinnedTabsContainer.scrollPosition
: this.arrowScrollbox.scrollPosition,
screenX: event.screenX,
@@ -921,6 +922,10 @@
}
@ -100,23 +91,16 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
);
let size = this.verticalMode ? "height" : "width";
let screenAxis = this.verticalMode ? "screenY" : "screenX";
@@ -1135,8 +1153,14 @@
(lastMovingTabScreen + tabSize);
if (this.verticalMode) {
+ if (oldTranslateY > 0 && translateOffsetY > tabHeight / 2) {
+ newTranslateY += tabHeight;
+ }
+ if (oldTranslateY < 0 && -translateOffsetY > tabHeight / 2) {
+ newTranslateY -= tabHeight;
+ }
newTranslateY = Math.min(
- Math.max(oldTranslateY, firstBound),
+ Math.max(newTranslateY, firstBound),
lastBound
);
@@ -1211,7 +1229,7 @@
item.removeAttribute("tabdrop-samewindow");
resolve();
};
- if (gReduceMotion) {
+ if (true || gReduceMotion) {
postTransitionCleanup();
} else {
@@ -1337,6 +1361,7 @@
let onTransitionEnd = transitionendEvent => {
@@ -1337,6 +1355,7 @@
let nextItem = this.ariaFocusableItems[newIndex];
let tabGroup = isTab(nextItem) && nextItem.group;
@ -124,7 +108,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
gBrowser.loadTabs(urls, {
inBackground,
replace,
@@ -1369,6 +1394,17 @@
@@ -1369,6 +1388,17 @@
this.finishMoveTogetherSelectedTabs(draggedTab);
this.finishAnimateTabMove();
@ -142,7 +126,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
this.#expandGroupOnDrop(draggedTab);
if (
@@ -1597,7 +1633,7 @@
@@ -1597,7 +1627,7 @@
}
get newTabButton() {
@ -151,7 +135,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
}
get verticalMode() {
@@ -1621,29 +1657,54 @@
@@ -1621,29 +1651,54 @@
if (this.#allTabs) {
return this.#allTabs;
}
@ -195,7 +179,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
+ if (glanceTab) {
+ // insert right after the parent tab. note: it must be inserted before
+ // the last pinned tab so it can be inserted in the correct order
+ allTabs.splice(Math.max(i++ + 1, lastPinnedTabIdx), 0, glanceTab);
+ allTabs.splice(Math.max(i++, lastPinnedTabIdx), 0, glanceTab);
+ } else if (tab.classList.contains("vertical-pinned-tabs-container-separator")) {
+ // remove the separator from the list
+ allTabs.splice(i, 1);
@ -214,7 +198,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
}
/**
@@ -1698,23 +1759,18 @@
@@ -1698,23 +1753,18 @@
}
let elementIndex = 0;
@ -242,7 +226,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
let visibleTabsInGroup = child.tabs.filter(tab => tab.visible);
visibleTabsInGroup.forEach(tab => {
tab.elementIndex = elementIndex++;
@@ -1724,10 +1780,7 @@
@@ -1724,10 +1774,7 @@
}
}
@ -254,7 +238,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
return this.#focusableItems;
}
@@ -1735,6 +1788,7 @@
@@ -1735,6 +1782,7 @@
_invalidateCachedTabs() {
this.#allTabs = null;
this._invalidateCachedVisibleTabs();
@ -262,7 +246,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
}
_invalidateCachedVisibleTabs() {
@@ -1749,8 +1803,8 @@
@@ -1749,8 +1797,8 @@
#isContainerVerticalPinnedGrid(tab) {
return (
this.verticalMode &&
@ -273,7 +257,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
!this.expandOnHover
);
}
@@ -1766,7 +1820,7 @@
@@ -1766,7 +1814,7 @@
if (node == null) {
// We have a container for non-tab elements at the end of the scrollbox.
@ -282,7 +266,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
}
node.before(tab);
@@ -1861,7 +1915,7 @@
@@ -1861,7 +1909,7 @@
// There are separate "new tab" buttons for horizontal tabs toolbar, vertical tabs and
// for when the tab strip is overflowed (which is shared by vertical and horizontal tabs);
// Attach the long click popup to all of them.
@ -291,7 +275,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
const newTab2 = this.newTabButton;
const newTabVertical = document.getElementById(
"vertical-tabs-newtab-button"
@@ -1956,10 +2010,12 @@
@@ -1956,10 +2004,12 @@
_handleTabSelect(aInstant) {
let selectedTab = this.selectedItem;
@ -304,7 +288,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
selectedTab._notselectedsinceload = false;
}
@@ -2132,6 +2188,7 @@
@@ -2132,6 +2182,7 @@
}
_positionPinnedTabs() {
@ -312,7 +296,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
let tabs = this.visibleTabs;
let numPinned = gBrowser.pinnedTabCount;
let absPositionHorizontalTabs =
@@ -2206,7 +2263,7 @@
@@ -2206,7 +2257,7 @@
return;
}
@ -321,25 +305,16 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
let directionX = screenX > dragData.animLastScreenX;
let directionY = screenY > dragData.animLastScreenY;
@@ -2215,6 +2272,8 @@
@@ -2214,7 +2265,7 @@
dragData.animLastScreenX = screenX;
let { width: tabWidth, height: tabHeight } =
draggedTab.getBoundingClientRect();
+ tabWidth += 4; // Add 4px to account for the gap
+ tabHeight += 4;
- draggedTab.getBoundingClientRect();
+ (draggedTab.group?.hasAttribute("split-view-group") ? draggedTab.group : draggedTab).getBoundingClientRect();
let shiftSizeX = tabWidth * movingTabs.length;
let shiftSizeY = tabHeight;
dragData.tabWidth = tabWidth;
@@ -2244,7 +2303,7 @@
let translateX = screenX - dragData.screenX;
let translateY = screenY - dragData.screenY;
translateY +=
- this.verticalPinnedTabsContainer.scrollPosition - dragData.scrollPos;
+ dragData.scrollPos;
let firstBoundX = firstTabInRow.screenX - firstMovingTabScreenX;
let firstBoundY = firstTabInRow.screenY - firstMovingTabScreenY;
let lastBoundX =
@@ -2389,12 +2448,16 @@
@@ -2389,12 +2440,16 @@
this.#clearDragOverCreateGroupTimer();
@ -360,7 +335,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
if (this.#rtlMode) {
tabs.reverse();
@@ -2408,7 +2471,7 @@
@@ -2408,7 +2463,7 @@
let size = this.verticalMode ? "height" : "width";
let translateAxis = this.verticalMode ? "translateY" : "translateX";
let scrollDirection = this.verticalMode ? "scrollTop" : "scrollLeft";
@ -369,7 +344,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
let translateX = event.screenX - dragData.screenX;
let translateY = event.screenY - dragData.screenY;
@@ -2422,12 +2485,21 @@
@@ -2422,12 +2477,21 @@
let lastTab = tabs.at(-1);
let lastMovingTab = movingTabs.at(-1);
let firstMovingTab = movingTabs[0];
@ -392,7 +367,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
translate +=
this.arrowScrollbox.scrollbox[scrollDirection] - dragData.scrollPos;
} else if (isPinned && this.verticalMode) {
@@ -2446,6 +2518,9 @@
@@ -2446,6 +2510,9 @@
// Shift the `.tab-group-label-container` to shift the label element.
item = item.parentElement;
}
@ -402,7 +377,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
item.style.transform = `${translateAxis}(${translate}px)`;
}
@@ -2583,6 +2658,9 @@
@@ -2583,6 +2650,9 @@
break;
}
let element = tabs[mid];
@ -412,7 +387,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
let elementForSize = isTabGroupLabel(element)
? element.parentElement
: element;
@@ -2605,6 +2683,10 @@
@@ -2605,6 +2675,10 @@
if (!dropElement) {
dropElement = this.ariaFocusableItems[oldDropElementIndex];
}
@ -423,7 +398,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
let newDropElementIndex = dropElement
? dropElement.elementIndex
: oldDropElementIndex;
@@ -2613,7 +2695,7 @@
@@ -2613,7 +2687,7 @@
let shouldCreateGroupOnDrop;
let dropBefore;
if (dropElement) {
@ -432,7 +407,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
? dropElement.parentElement
: dropElement;
@@ -2675,12 +2757,12 @@
@@ -2675,12 +2749,12 @@
}
}
@ -447,16 +422,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
dropElement != draggedTab &&
isTab(dropElement) &&
!dropElement?.group &&
@@ -2720,7 +2802,7 @@
// Dropping right before the tab group.
dropElement = dropElementGroup;
colorCode = undefined;
- } else if (dropElementGroup.collapsed) {
+ } else if (dropElement?.group?.hasAttribute("split-view-group")) {
// Dropping right after the collapsed tab group.
dropElement = dropElementGroup;
colorCode = undefined;
@@ -2750,7 +2832,7 @@
@@ -2750,7 +2824,7 @@
// Shift background tabs to leave a gap where the dragged tab
// would currently be dropped.
for (let item of tabs) {
@ -465,7 +431,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
continue;
}
@@ -2759,6 +2841,9 @@
@@ -2759,6 +2833,9 @@
if (isTabGroupLabel(item)) {
// Shift the `.tab-group-label-container` to shift the label element.
item = item.parentElement;
@ -475,7 +441,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
}
item.style.transform = transform;
}
@@ -2811,8 +2896,9 @@
@@ -2811,8 +2888,9 @@
);
}
@ -487,7 +453,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
return;
}
@@ -2824,6 +2910,12 @@
@@ -2824,6 +2902,12 @@
item = item.parentElement;
}
item.style.transform = "";
@ -500,7 +466,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
item.removeAttribute("dragover-createGroup");
}
this.removeAttribute("movingtab-createGroup");
@@ -2870,7 +2962,7 @@
@@ -2870,7 +2954,7 @@
let postTransitionCleanup = () => {
movingTab._moveTogetherSelectedTabsData.animate = false;
};
@ -509,7 +475,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
postTransitionCleanup();
} else {
let onTransitionEnd = transitionendEvent => {
@@ -3043,7 +3135,7 @@
@@ -3043,7 +3127,7 @@
}
_notifyBackgroundTab(aTab) {
@ -518,7 +484,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..de8b1ecf7cb844f6cf3e66a41b6024c5
return;
}
@@ -3169,6 +3261,9 @@
@@ -3169,6 +3253,9 @@
return null;
}
}

View file

@ -1,12 +0,0 @@
diff --git a/browser/components/urlbar/UrlbarValueFormatter.sys.mjs b/browser/components/urlbar/UrlbarValueFormatter.sys.mjs
index dfa91b76ad3890ceadb1b1b5d7a63b7074fbb776..6369fa1cdb242de32338bbce6debcdab2a04ca02 100644
--- a/browser/components/urlbar/UrlbarValueFormatter.sys.mjs
+++ b/browser/components/urlbar/UrlbarValueFormatter.sys.mjs
@@ -585,6 +585,7 @@ export class UrlbarValueFormatter {
this.window.requestAnimationFrame(() => {
if (instance == this._resizeInstance) {
this.#ensureFormattedHostVisible();
+ this._formatURL();
}
});
}, 100);

View file

@ -60,6 +60,26 @@
list-style-image: url('sidebars-right.svg') !important;
}
#zen-library-sidebar-workspaces {
list-style-image: url('duplicate-tab.svg');
}
#zen-library-sidebar-mods {
list-style-image: url('edit.svg');
}
#context_zenSplitTabs {
--menu-image: url('sidebars-right.svg') !important;
}
#context-zen-change-workspace-tab {
--menu-image: url('move-tab.svg') !important;
}
#context-zenSplitLink {
--menu-image: url('split.svg') !important;
}
#sidebar-button:-moz-locale-dir(ltr):not([positionend]),
#sidebar-button:-moz-locale-dir(rtl)[positionend] {
list-style-image: url('chrome://browser/skin/sidebars.svg') !important;
@ -107,6 +127,7 @@
#PanelUI-menu-button,
#appMenu-more-button2,
.zen-workspaces-actions,
#zen-workspace-actions-menu-icon {
list-style-image: url('menu.svg') !important;
}
@ -121,10 +142,6 @@
list-style-image: url('tab.svg') !important;
}
#context-navigation > menuitem {
padding: 4px;
}
#history-panelmenu,
.urlbarView-row[source='history']
> .urlbarView-row-inner
@ -165,6 +182,10 @@
list-style-image: url('open.svg') !important;
}
.zenToolbarThemePicker {
--menu-image: url('edit-theme.svg') !important;
}
#add-ons-button,
#appMenu-extensions-themes-button,
#unified-extensions-button {
@ -182,16 +203,10 @@
#appMenu-zoomEnlarge-button2,
#PanelUI-zen-profiles-newProfile,
#zen-sidebar-add-panel-button,
#PanelUI-zen-workspaces-new image,
#PanelUI-zen-gradient-generator-color-custom-add image {
list-style-image: url('plus.svg') !important;
}
#PanelUI-zen-workspaces-reorder-mode image {
list-style-image: url('move-tab.svg') !important;
rotate: 90deg;
}
#cut-button {
list-style-image: url('edit-cut.svg') !important;
}
@ -286,12 +301,8 @@
list-style-image: url('home.svg') !important;
}
#toggle_toolbar-menubar,
#appMenu_menu_openHelp {
display: none;
}
#library-button {
#library-button,
#zen-open-library {
list-style-image: url('library.svg') !important;
}
@ -435,11 +446,6 @@
#zen-glance-sidebar-split {
list-style-image: url('split.svg');
&[disabled='true'] {
opacity: 0.5;
cursor: not-allowed;
}
}
#sidebar-box[sidebarcommand='viewTabsSidebar']
@ -628,6 +634,372 @@
list-style-image: url('manage.svg') !important;
}
/* Context Menu Icons */
#context-video-pictureinpicture:not([checked='true']) .menu-iconic-icon {
list-style-image: url('media-pip.svg') !important;
}
#context-media-loop:not([checked='true']) .menu-iconic-icon {
list-style-image: url('media-loop.svg') !important;
}
:not(:not(menubar) > menu, #ContentSelectDropdown)
> menupopup
> menuitem:not(
.menuitem-iconic,
[type='checkbox'],
[type='radio'],
.in-menulist,
.in-menulist menuitem,
.unified-nav-current
),
:not(:not(menubar) > menu, #ContentSelectDropdown)
> menupopup
> menu:not(
.menu-iconic,
[type='checkbox'],
[type='radio'],
.in-menulist,
.in-menulist menu,
.unified-nav-current
),
#toggle_toolbar-menubar,
#PanelUI-history toolbarbutton,
#unified-extensions-context-menu menuitem {
background-image: var(--menu-image) !important;
background-size: 16px !important;
background-position: var(--zen-contextmenu-menuitem-padding-inline) center !important;
background-repeat: no-repeat !important;
-moz-context-properties: fill, fill-opacity !important;
fill: currentColor !important;
}
@media not (-moz-platform: windows) {
menu > .menu-iconic-text,
menuitem > .menu-iconic-text {
padding-inline-start: var(--zen-contextmenu-menuicon-margin-inline) !important;
}
}
#context-savepage {
--menu-image: url('save.svg');
}
#context-selectall,
.textbox-contextmenu menuitem[cmd*='selectAll'],
#context_selectAllTabs,
#toolbar-context-selectAllTabs {
--menu-image: url('edit-select-all.svg');
}
#context-undo,
.textbox-contextmenu menuitem[cmd*='undo'],
#context_undoCloseTab,
#toolbar-context-undoCloseTab {
--menu-image: url('edit-undo.svg');
}
#toggle_toolbar-menubar {
--menu-image: url('menu-bar.svg');
}
#context-redo,
.textbox-contextmenu menuitem[cmd*='redo'] {
--menu-image: url('edit-redo.svg');
}
#context-copy,
.textbox-contextmenu menuitem[cmd*='copy'],
.textbox-contextmenu #strip-on-share,
#placesContext_copy {
--menu-image: url('edit-copy.svg');
}
#context-paste,
.textbox-contextmenu menuitem[cmd*='paste'],
#placesContext_paste_group {
--menu-image: url('edit-paste.svg');
}
#context-cut,
.textbox-contextmenu menuitem[cmd*='cut'],
#placesContext_cut {
--menu-image: url('edit-cut.svg');
}
#context-delete,
.customize-context-removeExtension,
.unified-extensions-context-menu-remove-extension,
.textbox-contextmenu menuitem[cmd*='delete'],
menuitem[id='placesContext_deleteBookmark'],
menuitem[id='placesContext_deleteFolder'],
menuitem[id='placesContext_delete'],
menuitem[id='placesContext_delete_history'],
menuitem[id='placesContext_deleteHost'],
#context_zenDeleteWebPanel,
#context_zenDeleteWorkspace {
--menu-image: url('edit-delete.svg');
}
#paste-and-go {
--menu-image: url('paste-and-go.svg');
}
#context-print-selection {
--menu-image: url('print.svg');
}
#context-take-screenshot {
--menu-image: url('screenshot.svg');
}
#context-viewsource {
--menu-image: url('source-code.svg');
}
#context-inspect-a11y {
--menu-image: url('accessibility.svg');
}
#context-inspect {
--menu-image: url('inspect.svg');
}
#context-searchselect {
--menu-image: url('search-glass.svg');
}
#context-viewimage {
--menu-image: url('image-open.svg');
}
#context-viewimageinfo {
--menu-image: url('info.svg');
}
#context-saveimage,
#context-video-saveimage {
--menu-image: url('image-save.svg');
}
#context-savevideo {
--menu-image: url('video-save.svg');
}
#context-viewvideo {
--menu-image: url('video-open.svg');
}
#context-saveaudio {
--menu-image: url('audio-save.svg');
}
#context-copyimage-contents {
--menu-image: url('image-copy.svg');
}
#context-copyimage,
#context-copyvideourl,
#context-copylink,
#context-stripOnShareLink,
#context_zenOpenNewTabWebPanel,
#context-pdfjs-copy {
--menu-image: url('link.svg');
}
#context-openlinkincurrent {
--menu-image: url('ext-link.svg');
}
#context-viewsource,
#context-viewframesource,
#context-viewpartialsource-selection {
--menu-image: url('source-code.svg');
}
#context-sendimage,
#context-sendvideo,
#context-sendaudio {
--menu-image: url('mail.svg');
}
#context-setDesktopBackground,
.viewCustomizeToolbar {
--menu-image: url('customize.svg');
}
#context-reloadimage,
#context_reloadTab,
#context_reloadSelectedTabs,
#toolbar-context-reloadSelectedTab,
#toolbar-context-reloadSelectedTabs,
#context_zen-reset-pinned-tab {
--menu-image: url('reload.svg');
}
#context-sendlinktodevice,
#context_sendTabToDevice,
#context-sendpagetodevice {
--menu-image: url('send-to-device.svg');
}
#context-openlinkintab,
#context-openlinkincontainertab,
#context_zenWorkspacesOpenInContainerTab,
#context_zenWebPanelContextInContainer,
menuitem[id='placesContext_open:newtab'],
menuitem[id='placesContext_openLinks:tabs'],
menuitem[id='placesContext_openBookmarkLinks:tabs'],
menuitem[id='placesContext_openBookmarkContainer:tabs'] {
--menu-image: url('tab.svg');
}
#context_openANewTab,
#toolbar-context-openANewTab {
--menu-image: url('new-tab-image.svg');
}
#context-openlinkinusercontext-menu,
menu[id='placesContext_open:newcontainertab'],
menu[id='placesContext_openContainer:tabs'] {
--menu-image: url('container-tab.svg');
}
#context-openlink,
menuitem[id='placesContext_open:newwindow'] {
--menu-image: url('window.svg');
}
#context-openlinkprivate,
menuitem[id='placesContext_open:newprivatewindow'] {
--menu-image: url('private-window.svg');
}
#context-savelink {
--menu-image: url('downloads.svg');
}
#spell-add-to-dictionary {
--menu-image: url('add-to-dictionary.svg');
}
#manage-saved-logins {
--menu-image: url('passwords.svg');
}
#context-media-play,
#context_playTab,
#context_playSelectedTabs {
--menu-image: url('media-play.svg');
}
#context-media-pause {
--menu-image: url('media-pause.svg');
}
#context-media-mute,
#context_toggleMuteTab,
#context_toggleMuteSelectedTabs,
#context_zenToggleMuteWebPanel {
--menu-image: url('media-mute.svg');
}
#context-media-unmute,
#context_toggleMuteTab[muted],
#context_toggleMuteSelectedTabs[muted],
#context_zenToggleMuteWebPanel[muted] {
--menu-image: url('media-unmute.svg');
}
#context-media-playbackrate {
--menu-image: url('media-speed.svg');
}
#context-video-fullscreen {
--menu-image: url('fullscreen.svg');
}
#context-leave-dom-fullscreen,
menuitem[contexttype='fullscreen'][label*='Exit'] {
--menu-image: url('fullscreen-exit.svg');
}
#context-media-hidecontrols,
#context-media-showcontrols {
--menu-image: url('permissions.svg');
}
#context_pinTab,
#context_unpinTab,
#context_pinSelectedTabs,
#context_unpinSelectedTabs,
.customize-context-moveToPanel,
#context_zen-replace-pinned-url-with-current {
--menu-image: url('pin.svg');
}
#context_zen-add-essential {
--menu-image: url('essential-add.svg');
}
#context_zen-remove-essential {
--menu-image: url('essential-remove.svg');
}
.customize-context-removeFromToolbar {
--menu-image: url('unpin.svg');
}
#zen-sidebar-web-panel-pinned[pinned='true'] {
list-style-image: url('pin.svg') !important;
}
#zen-sidebar-web-panel-pinned {
list-style-image: url('unpin.svg') !important;
}
#context_duplicateTab,
#context_duplicateTabs {
--menu-image: url('duplicate-tab.svg');
}
#zen-context-menu-compact-mode {
--menu-image: url('sidebar.svg');
}
#context_bookmarkTab,
#context_bookmarkSelectedTabs,
#toggle_PersonalToolbar,
#context-bookmarklink,
#toolbar-context-bookmarkSelectedTab,
#toolbar-context-bookmarkSelectedTabs {
--menu-image: url('bookmark-hollow.svg');
}
menuitem[id='placesContext_show_bookmark:info'],
menuitem[id='placesContext_show_folder:info'],
menuitem[id='placesContext_show:info'],
#context_zenEditWorkspace {
--menu-image: url('edit.svg');
}
menuitem[id='placesContext_showAllBookmarks'],
#BMB_bookmarksShowAllTop,
#BMB_bookmarksShowAll,
.customize-context-manageExtension,
.unified-extensions-context-menu-manage-extension {
--menu-image: url('manage.svg');
}
#BMB_viewBookmarksSidebar {
--menu-image: url('chrome://browser/skin/sidebars.svg');
}
#BMB_searchBookmarks {
--menu-image: url('search-page.svg');
}
#appMenuRecentlyClosedTabs {
list-style-image: url('container-tab.svg') !important;
}
@ -648,12 +1020,57 @@
list-style-image: url('manage.svg') !important;
}
menuitem[id='placesContext_new:bookmark'],
menuitem[id='placesContext_new:folder'],
menuitem[id='placesContext_new:separator'] {
--menu-image: url('plus.svg');
}
#context-savelinktopocket,
#context-pocket {
--menu-image: url('pocket-outline.svg');
}
#context_moveTabOptions {
--menu-image: url('move-tab.svg');
}
.share-tab-url-item {
--menu-image: url('share.svg');
}
#context_reopenInContainer {
--menu-image: url('container-tab.svg');
}
#context_closeTab {
--menu-image: url('close.svg');
}
#context_closeTabOptions {
--menu-image: url('close-all.svg');
}
#context_unloadTab,
#context_zenTabActions {
--menu-image: url('close-all.svg');
}
.customize-context-reportExtension,
.unified-extensions-context-menu-report-extension {
--menu-image: url('report.svg');
}
/* FIX header icons for the app menu sub menus (eg. fx account, history...) */
.panel-header > h1 {
text-align: left;
margin-left: 8px !important;
}
.wordmark::after {
content: 'Plus' !important;
}
/* header icons for the app menu sub menus (eg. fx account, history...) */
.panel-header > h1 > span::before {
content: '';
@ -698,10 +1115,67 @@
--fp-enabled: 1;
}
@media not (-moz-platform: linux) {
.unified-extensions-context-menu-pin-to-toolbar {
--menu-image: url('pin.svg');
}
}
.unified-extensions-context-menu-move-widget-down {
--menu-image: url('arrow-down.svg');
}
.unified-extensions-context-menu-move-widget-up {
--menu-image: url('arrow-up.svg');
}
#alltabs-button {
list-style-image: url('chrome://browser/skin/tabs.svg') !important;
}
:not(:not(menubar) > menu, #ContentSelectDropdown)
> menupopup
> menuitem:not(
.menuitem-iconic,
[type='checkbox'],
[type='radio'],
.in-menulist,
.in-menulist menuitem,
.unified-nav-current
),
:not(:not(menubar) > menu, #ContentSelectDropdown)
> menupopup
> menu:not(
.menu-iconic,
[type='checkbox'],
[type='radio'],
.in-menulist,
.in-menulist menu,
.unified-nav-current
),
:not(:not(menubar) > menu, #ContentSelectDropdown) > menupopup > menucaption {
padding-inline-start: calc(
var(--zen-contextmenu-menuitem-padding-inline) + var(--zen-contextmenu-menuicon-margin-inline) /
2
) !important;
}
menupopup > menuitem:is([type='checkbox']) .menu-iconic-left {
--menu-image: none !important;
margin-inline-start: 4px;
@media not (-moz-platform: windows) {
margin-inline-end: 0;
padding-inline-end: 0;
}
}
@media (-moz-platform: windows) {
menupopup > menuitem[checked='true'] {
padding-inline-start: 6px;
}
}
#toolbar-context-toggle-vertical-tabs,
#toolbar-context-customize-sidebar,
#sidebarRevampSeparator {

View file

@ -1,6 +1,3 @@
// 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/.
/**
* NOTE: Do not modify this file by hand.
* Content was generated from source XPCOM .idl files.

View file

@ -1,6 +1,3 @@
// 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/.
/**
* NOTE: Do not modify this file by hand.
* Content was generated from source .webidl files.

View file

@ -1,6 +1,3 @@
// 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/.
/**
* NOTE: Do not modify this file by hand.
* Content was generated from source metrics.yaml files.

View file

@ -1,6 +1,3 @@
// 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/.
/**
* NOTE: Do not modify this file by hand.
* Content was generated from source XPCOM .idl files.

View file

@ -1,6 +1,3 @@
// 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/.
/**
* NOTE: Do not modify this file by hand.
* Content was generated by running "mach ts paths".

View file

@ -1,6 +1,3 @@
// 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/.
/**
* NOTE: Do not modify this file by hand.
* Content was generated from xpc.msg and error_list.json.

View file

@ -1,6 +1,3 @@
// 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/.
/**
* NOTE: Do not modify this file by hand.
* Content was generated from services.json.

View file

@ -1,6 +1,3 @@
// 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/.
/**
* Gecko generic/specialized adjustments for xpcom and webidl types.
*/

View file

@ -1,6 +1,3 @@
// 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/.
/**
* NOTE: Do not modify this file by hand.
* Content was generated from source XPCOM .idl files.

View file

@ -1,6 +1,3 @@
// 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/.
/**
* NOTE: Do not modify this file by hand.
* Content was generated from source XPCOM .idl files.

View file

@ -1,6 +1,3 @@
// 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/.
/**
* Gecko XPIDL base types.
*/

View file

@ -1,32 +1,16 @@
// 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/.
// Utility to register JSWindowActors
var gZenActorsManager = {
_actors: new Set(),
_lazy: {},
init() {
ChromeUtils.defineESModuleGetters(this._lazy, {
ActorManagerParent: 'resource://gre/modules/ActorManagerParent.sys.mjs',
});
},
addJSWindowActor(name, data) {
if (!this._lazy.ActorManagerParent) {
this.init();
}
if (this._actors.has(name)) {
addJSWindowActor(...args) {
if (this._actors.has(args[0])) {
// Actor already registered, nothing to do
return;
}
const decl = {};
decl[name] = data;
try {
this._lazy.ActorManagerParent.addJSWindowActors(decl);
this._actors.add(name);
ChromeUtils.registerWindowActor(...args);
this._actors.add(args[0]);
} catch (e) {
console.warn(`Failed to register JSWindowActor: ${e}`);
}

View file

@ -1,7 +1,3 @@
// 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/.
var gZenOperatingSystemCommonUtils = {
kZenOSToSmallName: {
WINNT: 'windows',

View file

@ -1,12 +1,10 @@
// 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 { 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'];
defaultSidebarIcons = ['zen-open-library', 'zen-workspaces-button', 'downloads-button'];
startup(CustomizableUIInternal) {
CustomizableUIInternal.registerArea(

File diff suppressed because one or more lines are too long

View file

@ -1,50 +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/.
{
class ZenSessionStore extends ZenPreloadedFeature {
init() {
this.#waitAndCleanup();
}
promiseInitialized = new Promise((resolve) => {
this._resolveInitialized = resolve;
});
restoreInitialTabData(tab, tabData) {
if (tabData.zenWorkspace) {
tab.setAttribute('zen-workspace-id', tabData.zenWorkspace);
}
if (tabData.zenPinnedId) {
tab.setAttribute('zen-pin-id', tabData.zenPinnedId);
}
if (tabData.zenIsEmpty) {
tab.setAttribute('zen-empty-tab', 'true');
}
if (tabData.zenHasStaticLabel) {
tab.setAttribute('zen-has-static-label', 'true');
}
if (tabData.zenEssential) {
tab.setAttribute('zen-essential', 'true');
}
if (tabData.zenDefaultUserContextId) {
tab.setAttribute('zenDefaultUserContextId', 'true');
}
if (tabData.zenPinnedEntry) {
tab.setAttribute('zen-pinned-entry', tabData.zenPinnedEntry);
}
}
async #waitAndCleanup() {
await SessionStore.promiseAllWindowsRestored;
await SessionStore.promiseInitialized;
this.#cleanup();
}
#cleanup() {
this._resolveInitialized();
}
}
window.gZenSessionStore = new ZenSessionStore();
}

View file

@ -1,6 +1,3 @@
// 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/.
{
var ZenStartup = {
init() {

View file

@ -1,14 +1,9 @@
// 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/.
var gZenUIManager = {
_popupTrackingElements: [],
_hoverPausedForExpand: false,
_hasLoadedDOM: false,
testingEnabled: Services.prefs.getBoolPref('zen.testing.enabled', false),
_lastClickPosition: null,
_toastTimeouts: [],
init() {
@ -36,8 +31,6 @@ var gZenUIManager = {
gURLBar._zenTrimURL = this.urlbarTrim.bind(this);
document.addEventListener('mousedown', this.handleMouseDown.bind(this), true);
ChromeUtils.defineLazyGetter(this, 'motion', () => {
return ChromeUtils.importESModule('chrome://browser/content/zen-vendor/motion.min.mjs', {
global: 'current',
@ -69,13 +62,6 @@ var gZenUIManager = {
gZenMediaController.init();
},
handleMouseDown(event) {
this._lastClickPosition = {
clientX: event.clientX,
clientY: event.clientY,
};
},
updateTabsToolbar() {
const kUrlbarHeight = 440;
gURLBar.textbox.style.setProperty(
@ -84,6 +70,7 @@ var gZenUIManager = {
);
gZenVerticalTabsManager.actualWindowButtons.removeAttribute('zen-has-hover');
gZenVerticalTabsManager.recalculateURLBarHeight();
setTimeout(gURLBar.formatValue.bind(gURLBar), 350);
if (!this._preventToolbarRebuild) {
setTimeout(() => {
gZenWorkspaces.updateTabsContainers();
@ -1002,18 +989,10 @@ var gZenVerticalTabsManager = {
async renameTabKeydown(event) {
event.stopPropagation();
if (event.key === 'Enter') {
const isTab = !!event.target.closest('.tabbrowser-tab');
let label = isTab
? this._tabEdited.querySelector('.tab-label-container-editing')
: this._tabEdited;
let input = document.getElementById('tab-label-input');
let label = this._tabEdited.querySelector('.tab-label-container-editing');
let input = this._tabEdited.querySelector('#tab-label-input');
let newName = input.value.trim();
document.documentElement.removeAttribute('zen-renaming-tab');
input.remove();
if (!isTab) {
await this._tabEdited.onRenameFinished(newName);
} else {
// 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
@ -1033,6 +1012,7 @@ var gZenVerticalTabsManager = {
!!newName
);
}
document.documentElement.removeAttribute('zen-renaming-tab');
// Maybe add some confetti here?!?
gZenUIManager.motion.animate(
@ -1044,12 +1024,8 @@ var gZenVerticalTabsManager = {
duration: 0.25,
}
);
}
const editorContainer = this._tabEdited.querySelector('.tab-editor-container');
if (editorContainer) {
editorContainer.remove();
}
this._tabEdited.querySelector('.tab-editor-container').remove();
label.classList.remove('tab-label-container-editing');
this._tabEdited = null;
@ -1059,40 +1035,34 @@ var gZenVerticalTabsManager = {
},
renameTabStart(event) {
const isTab = !!event.target.closest('.tabbrowser-tab');
if (
this._tabEdited ||
((!Services.prefs.getBoolPref('zen.tabs.rename-tabs') ||
Services.prefs.getBoolPref('browser.tabs.closeTabByDblclick')) &&
isTab) ||
!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')) && isTab)
!this._tabEdited.pinned ||
this._tabEdited.hasAttribute('zen-essential')
) {
this._tabEdited = null;
return;
}
event.stopPropagation();
document.documentElement.setAttribute('zen-renaming-tab', 'true');
const label = isTab ? this._tabEdited.querySelector('.tab-label-container') : this._tabEdited;
const label = this._tabEdited.querySelector('.tab-label-container');
label.classList.add('tab-label-container-editing');
if (isTab) {
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 = isTab
? this._tabEdited.querySelector('.tab-editor-container')
: this._tabEdited.parentNode;
const containerHtml = this._tabEdited.querySelector('.tab-editor-container');
const input = document.createElement('input');
input.id = 'tab-label-input';
input.value = isTab ? this._tabEdited.label : this._tabEdited.textContent;
input.value = this._tabEdited.label;
input.addEventListener('keydown', this.renameTabKeydown.bind(this));
containerHtml.appendChild(input);
@ -1107,16 +1077,8 @@ var gZenVerticalTabsManager = {
return;
}
document.documentElement.removeAttribute('zen-renaming-tab');
const editorContainer = this._tabEdited.querySelector('.tab-editor-container');
let input = document.getElementById('tab-label-input');
input.remove();
if (editorContainer) {
editorContainer.remove();
}
const isTab = !!this._tabEdited.closest('.tabbrowser-tab');
const label = isTab
? this._tabEdited.querySelector('.tab-label-container-editing')
: this._tabEdited;
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

@ -1,6 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
@ -90,9 +87,6 @@ class ZenUIMigration {
}
_migrateV4(win) {
if (AppConstants.platform === 'linux') {
return;
}
Services.prefs.setBoolPref(
'browser.tabs.unloadOnLowMemory',
Services.prefs.getBoolPref('zen.tab-unloader.enabled', true)

View file

@ -78,6 +78,16 @@
transition: background-color var(--inactive-window-transition);
}
@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);
&::before {
background-color: var(--toolbox-bgcolor-inactive);
}
}
}
#zen-browser-grain {
content: '';
width: 100%;
@ -121,7 +131,9 @@
z-index: 1;
}
@media -moz-pref('zen.view.grey-out-inactive-windows') {
@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;
@ -252,7 +264,6 @@
opacity: 0;
transition: opacity 0.1s ease-in-out;
pointer-events: none;
transform: translateX(-50%);
}
&:hover::before {

View file

@ -35,13 +35,17 @@
--uc-permission-item-margin-block: 4px;
--uc-permission-item-padding-inline: 16px;
--zen-panel-separator-width: 1px;
--zen-contextmenu-menuitem-padding-inline: 10px;
--zen-contextmenu-menuicon-margin-inline: 12px;
--zen-contextmenu-menuitem-margin: 0px 2px;
}
menupopup,
panel {
--panel-background: var(--arrowpanel-background);
--panel-border-radius: var(--zen-native-inner-radius);
--menuitem-padding: 6px !important;
--menuitem-padding: 6px 5px !important;
}
/* split-view popup */
@ -243,6 +247,11 @@ panel {
opacity: 0;
}
menupopup::part(content),
panel::part(content) {
border: var(--zen-appcontent-border);
}
menupopup,
panel {
box-shadow: none;

View file

@ -73,6 +73,9 @@ document.addEventListener(
event.sourceEvent.target.getAttribute('zen-workspace-id')
);
break;
case 'cmd_zenToggleLibrary':
gZenLibrary.toggle();
break;
case 'cmd_zenToggleTabsOnRight':
gZenVerticalTabsManager.toggleTabsOnRight();
break;
@ -88,6 +91,12 @@ document.addEventListener(
case 'cmd_zenRemoveFromEssentials':
gZenPinnedTabManager.removeEssentials();
break;
case 'cmd_zenCtxDeleteWorkspace':
gZenWorkspaces.contextDeleteWorkspace(event);
break;
case 'cmd_zenChangeWorkspaceName':
gZenVerticalTabsManager.renameTabStart(event);
break;
default:
if (event.target.id.startsWith('cmd_zenWorkspaceSwitch')) {
const index = parseInt(event.target.id.replace('cmd_zenWorkspaceSwitch', ''), 10) - 1;

View file

@ -1,6 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
const lazyCompactMode = {};
XPCOMUtils.defineLazyPreferenceGetter(
@ -91,6 +88,10 @@ var gZenCompactModeManager = {
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;
}

View file

@ -1,6 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
{
const { Downloads } = ChromeUtils.importESModule('resource://gre/modules/Downloads.sys.mjs');
@ -14,10 +11,24 @@
});
class ZenDownloadAnimation extends ZenDOMOperatedFeature {
#lastClickPosition = null;
async init() {
this.#setupClickListener();
await this.#setupDownloadListeners();
}
#setupClickListener() {
document.addEventListener('mousedown', this.#handleClick.bind(this), true);
}
#handleClick(event) {
this.#lastClickPosition = {
clientX: event.clientX,
clientY: event.clientY,
};
}
async #setupDownloadListeners() {
try {
const list = await Downloads.getList(Downloads.ALL);
@ -39,14 +50,14 @@
return;
}
if (!gZenUIManager._lastClickPosition) {
if (!this.#lastClickPosition) {
console.warn(
`[${ZenDownloadAnimation.name}] No recent click position available for animation`
);
return;
}
this.#animateDownload(gZenUIManager._lastClickPosition);
this.#animateDownload(this.#lastClickPosition);
}
#animateDownload(startPosition) {

View file

@ -1,6 +1,3 @@
// 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/.
{
class ZenFolders {
constructor() {

View file

@ -1,6 +1,3 @@
// 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/.
{
class ZenGlanceManager extends ZenDOMOperatedFeature {
_animating = false;
@ -154,8 +151,8 @@
this.animatingOpen = true;
this._animating = true;
const initialX = data.clientX;
const initialY = data.clientY;
const initialX = data.x;
const initialY = data.y;
const initialWidth = data.width;
const initialHeight = data.height;
@ -597,10 +594,8 @@
this.openGlance(
{
url: undefined,
...(gZenUIManager._lastClickPosition || {
clientX: browserRect.width / 2,
clientY: browserRect.height / 2,
}),
x: browserRect.width / 2,
y: browserRect.height / 2,
width: 0,
height: 0,
},
@ -641,12 +636,7 @@
.classList.remove('zen-glance-background');
this.#currentParentTab._visuallySelected = false;
this.hideSidebarButtons();
if (forSplit) {
this.finishOpeningGlance();
return;
}
if (gReduceMotion || forSplit) {
gZenViewSplitter.deactivateCurrentSplitView();
this.finishOpeningGlance();
return;
}
@ -661,7 +651,6 @@
type: 'spring',
}
);
gZenViewSplitter.deactivateCurrentSplitView();
this.finishOpeningGlance();
}
@ -686,8 +675,8 @@
const rect = event.target.getBoundingClientRect();
const data = {
url: event.target._placesNode.uri,
clientX: rect.left,
clientY: rect.top,
x: rect.left,
y: rect.top,
width: rect.width,
height: rect.height,
};
@ -716,8 +705,8 @@
}
getTabOrGlanceParent(tab) {
if (tab?.hasAttribute('glance-id') && this.#glances) {
const parentTab = this.#glances.get(tab.getAttribute('glance-id'))?.parentTab;
if (tab?.hasAttribute('glance-id')) {
const parentTab = this.#glances.get(tab.getAttribute('glance-id')).parentTab;
if (parentTab) {
return parentTab;
}
@ -745,43 +734,18 @@
}
return false;
}
onSearchSelectCommand(where) {
if (where !== 'tab') {
return;
}
const currentTab = gBrowser.selectedTab;
const parentTab = currentTab.owner;
if (!parentTab) {
return;
}
// Open a new glance if the current tab is a glance tab
const browserRect = gBrowser.tabbox.getBoundingClientRect();
this.openGlance(
{
url: undefined,
...(gZenUIManager._lastClickPosition || {
clientX: browserRect.width / 2,
clientY: browserRect.height / 2,
}),
width: 0,
height: 0,
},
currentTab,
parentTab
);
}
}
window.gZenGlanceManager = new ZenGlanceManager();
function registerWindowActors() {
if (Services.prefs.getBoolPref('zen.glance.enabled', true)) {
gZenActorsManager.addJSWindowActor('ZenGlance', {
parent: {
esModuleURI: 'resource:///actors/ZenGlanceParent.sys.mjs',
esModuleURI: 'chrome://browser/content/zen-components/actors/ZenGlanceParent.sys.mjs',
},
child: {
esModuleURI: 'resource:///actors/ZenGlanceChild.sys.mjs',
esModuleURI: 'chrome://browser/content/zen-components/actors/ZenGlanceChild.sys.mjs',
events: {
DOMContentLoaded: {},
keydown: {
@ -791,9 +755,9 @@
},
allFrames: true,
matches: ['*://*/*'],
enablePreference: 'zen.glance.enabled',
});
}
}
registerWindowActors();
}

View file

@ -1,6 +1,3 @@
// 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/.
export class ZenGlanceChild extends JSWindowActorChild {
constructor() {
super();
@ -74,8 +71,8 @@ export class ZenGlanceChild extends JSWindowActorChild {
const rect = target.getBoundingClientRect();
this.sendAsyncMessage('ZenGlance:OpenGlance', {
url,
clientX: rect.left,
clientY: rect.top,
x: rect.left,
y: rect.top,
width: rect.width,
height: rect.height,
});

View file

@ -1,6 +1,3 @@
// 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/.
export class ZenGlanceParent extends JSWindowActorParent {
constructor() {
super();

View file

@ -0,0 +1,270 @@
{
class ZenLibraryElement extends MozXULElement {
#currentTab = null;
#availableTabs = ['workspaces', 'mods'];
static get markup() {
return `
<vbox id="zen-library-sidebar">
<vbox id="zen-library-sidebar-buttons" flex="1">
<toolbarbutton class="toolbarbutton-1 zen-library-sidebar-button" id="zen-library-sidebar-workspaces" data-l10n-id="zen-library-sidebar-workspaces"/>
<toolbarbutton class="toolbarbutton-1 zen-library-sidebar-button" id="zen-library-sidebar-mods" data-l10n-id="zen-library-sidebar-mods"/>
</vbox>
<hbox id="zen-library-sidebar-footer">
<toolbarbutton removable="true" class="chromeclass-toolbar-additional toolbarbutton-1 zen-sidebar-action-button" id="zen-open-library" command="cmd_zenToggleLibrary"></toolbarbutton>
</hbox>
</vbox>
<vbox id="zen-library-content">
<hbox content="workspaces" size="big">
</hbox>
<hbox content="mods" size="small"></hbox>
</vbox>
`;
}
static get inheritedAttributes() {
return {
'#zen-library-content': 'content,size=content-size',
'#zen-library-sidebar': 'content',
};
}
constructor() {
super();
}
set currentTab(tab) {
if (!this.#availableTabs.includes(tab)) {
throw new Error(`Tab "${tab}" is not available in Zen Library.`);
}
this.#currentTab = tab;
this.setAttribute('content', tab);
// Also set if the size is big or small based on the tab.
const element = this.querySelector(`#zen-library-content hbox[content="${tab}"]`);
this.setAttribute('content-size', element.getAttribute('size') || 'small');
for (const availableTab of this.#availableTabs) {
const button = this.querySelector(`#zen-library-sidebar-${availableTab}`);
if (availableTab === tab) {
button.setAttribute('active', 'true');
} else {
button.removeAttribute('active');
}
const contentContainer = this.#getContentContainer(availableTab);
if (availableTab === tab) {
contentContainer.removeAttribute('hidden');
} else {
contentContainer.setAttribute('hidden', 'true');
}
}
}
get currentTab() {
return this.#currentTab;
}
set open(value) {
if (value) {
this.setAttribute('open', 'true');
this.onOpen(); // Trigger the onOpen method to populate the content
} else {
this.removeAttribute('open');
this.onClose(); // Trigger the onClose method if needed
}
}
get open() {
return this.getAttribute('open') === 'true';
}
connectedCallback() {
if (this.delayConnectedCallback()) {
// If we are not ready yet, or if we have already connected, we
// don't need to do anything.
return;
}
this.id = 'zen-library';
this.appendChild(this.constructor.fragment);
this.initializeAttributeInheritance();
for (const availableTab of this.#availableTabs) {
const button = this.querySelector(`#zen-library-sidebar-${availableTab}`);
button.addEventListener('command', () => {
this.currentTab = availableTab;
});
}
window.addEventListener('TabSelect', this);
this.currentTab = 'workspaces'; // Default tab
}
#getContentContainer(tab) {
return this.querySelector(`#zen-library-content hbox[content="${tab}"]`);
}
#createWorkspaceElement(workspace) {
const fragment = window.MozXULElement.parseXULToFragment(`
<vbox class="zen-workspace-item" zen-workspace-id="${workspace.uuid}">
<hbox class="zen-workspace-item-header">
<label class="zen-workspace-item-name"></label>
</hbox>
<vbox class="zen-workspace-item-content">
</vbox>
</vbox>
`);
const workspaceLabel = fragment.querySelector('.zen-workspace-item-name');
workspaceLabel.textContent = workspace.name;
workspaceLabel.addEventListener(
'click',
gZenVerticalTabsManager.renameTabStart.bind(gZenVerticalTabsManager)
);
const workspaceItem = fragment.querySelector('.zen-workspace-item');
workspaceItem.style.setProperty(
'--zen-workspace-gradient',
gZenThemePicker.getGradient(workspace.theme.gradientColors, true, workspace.theme.rotation)
);
// TODO: Not jet! Figure this out
//const workspaceElement = gZenWorkspaces.workspaceElement(workspace.uuid);
//fragment.querySelector('.zen-workspace-item-content').appendChild(workspaceElement);
return fragment;
}
async onOpen(event) {
const conatainer = this.#getContentContainer('workspaces');
conatainer.innerHTML = ''; // Clear the container
const workspaces = await gZenWorkspaces._workspaces();
for (const workspace of workspaces.workspaces) {
const workspaceElement = this.#createWorkspaceElement(workspace);
conatainer.appendChild(workspaceElement);
}
}
async onClose(event) {}
on_TabSelect(event) {
if (!this.open) return;
gZenLibrary.close();
}
}
customElements.define('zen-library', ZenLibraryElement);
class ZenLibrary {
#animating = false;
constructor() {
ChromeUtils.defineLazyGetter(this, 'wrapper', () => document.getElementById('zen-library'));
}
get isOpen() {
return this.wrapper.hasAttribute('open');
}
set isOpen(value) {
if (this.#animating) {
return; // Prevent multiple animations from running at the same time
}
this.#animating = true;
if (value) {
this.wrapper.open = value;
this.animateLibrary(false).then(() => {
this.#animating = false;
});
} else {
this.animateLibrary(true).then(() => {
this.wrapper.open = value;
this.#animating = false;
});
}
}
open() {
this.isOpen = true;
}
close() {
this.isOpen = false;
}
toggle() {
this.isOpen = !this.isOpen;
}
async animateLibrary(open) {
window.docShell.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIAppWindow)
.rollupAllPopups();
let elementsToAnimate = [gNavToolbox];
if (gZenVerticalTabsManager._hasSetSingleToolbar) {
elementsToAnimate.push(gURLBar.textbox);
}
const wrapperWidth = this.wrapper.getBoundingClientRect().width;
const appContentWrapper = document.getElementById('zen-appcontent-wrapper');
if (open) {
await Promise.all([
gZenUIManager.motion.animate(
elementsToAnimate,
{
transform: ['translateX(100%)', 'translateX(0)'],
opacity: [0, 1],
},
{
duration: 0.2,
easing: 'ease-in-out',
}
),
gZenUIManager.motion.animate(
this.wrapper,
{
marginLeft: ['0px', `${-wrapperWidth}px`],
transform: ['translateX(0)', 'translateX(100%)'],
},
{
duration: 0.2,
easing: 'ease-in-out',
}
),
]);
appContentWrapper.style.minWidth = ''; // Reset the min-width after the animation
gNavToolbox.style.display = ''; // Hide the toolbox during the animation
} else {
appContentWrapper.style.minWidth = `${appContentWrapper.getBoundingClientRect().width}px`;
await Promise.all([
gZenUIManager.motion.animate(
elementsToAnimate,
{
transform: ['translateX(0)', 'translateX(100%)'],
opacity: [1, 0],
},
{
duration: 0.2,
easing: 'ease-in-out',
}
),
gZenUIManager.motion.animate(
this.wrapper,
{
marginLeft: [`${-wrapperWidth}px`, '0px'],
transform: ['translateX(-100%)', 'translateX(0%)'],
},
{
duration: 0.2,
easing: 'ease-in-out',
}
),
]);
gNavToolbox.style.display = 'none'; // Show the toolbox after the animation
}
}
}
window.gZenLibrary = new ZenLibrary();
}

View file

@ -0,0 +1,103 @@
#zen-library {
order: -1;
z-index: 1;
pointer-events: none;
position: fixed;
left: -200%;
&[open='true'] {
display: flex;
pointer-events: all;
position: relative;
left: 0;
}
}
#zen-library-sidebar {
height: 100%;
background: rgba(255, 255, 255, 0.1);
padding: 0.6rem;
box-shadow: var(--zen-big-shadow);
-moz-window-dragging: drag;
border-right: 1px solid var(--zen-colors-border);
#zen-library-sidebar-buttons {
justify-content: center;
gap: 12px;
}
& .zen-library-sidebar-button {
transition: background-color 0.1s;
padding: 0.8rem 0.7rem;
border-radius: 6px;
background: transparent;
justify-content: center;
align-items: center;
flex-direction: column;
appearance: none;
gap: 0.5rem;
border-radius: 6px !important;
&:hover,
&[active='true'] {
background: light-dark(rgba(255, 255, 255, 0.2), rgba(0, 0, 0, 0.2));
}
}
#zen-library-sidebar-footer {
justify-content: space-between;
& toolbarbutton {
appearance: none;
}
}
}
#zen-library-content {
transition: width 0.15s;
width: 20vw;
overflow-x: auto;
&[size='big'] {
width: 60vw;
}
& > * {
height: 100%;
}
& > [content='workspaces'] {
justify-content: center;
align-items: center;
gap: 2rem;
padding: 6rem;
& .zen-workspace-item {
width: 16rem;
padding: 0.7rem;
border-radius: var(--zen-border-radius);
box-shadow: var(--zen-big-shadow);
background: var(--zen-workspace-gradient);
border: 1px solid light-dark(
rgba(255, 255, 255, 0.2),
rgba(0, 0, 0, 0.2)
);
height: 100%;
&::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;
}
}
}
}

View file

@ -1,6 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
{
const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(

View file

@ -1,735 +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/.
{
class ZenMods extends ZenPreloadedFeature {
// private properties start
#kZenStylesheetModHeader = '/* Zen Mods - Generated by ZenMods.';
#kZenStylesheetModHeaderBody = `* DO NOT EDIT THIS FILE DIRECTLY!
* Your changes will be overwritten.
* Instead, go to the preferences and edit the mods there.
*/
`;
#kZenStylesheetModFooter = `
/* End of Zen Mods */
`;
#getCurrentDateTime = () =>
new Intl.DateTimeFormat('en-US', {
dateStyle: 'full',
timeStyle: 'full',
}).format(new Date().getTime());
constructor() {
console.log('[ZenMods]: Initializing ZenMods module');
super();
}
// Stylesheet service
#sss = null;
get #stylesheetService() {
if (!this.#sss) {
this.#sss = Cc['@mozilla.org/content/style-sheet-service;1'].getService(
Ci.nsIStyleSheetService
);
}
return this.#sss;
}
#ssu = null;
get #styleSheetUri() {
if (!this.#ssu) {
this.#ssu = Services.io.newFileURI(new FileUtils.File(this.#styleSheetPath));
}
return this.#ssu;
}
get #styleSheetPath() {
return PathUtils.join(PathUtils.profileDir, 'chrome', 'zen-themes.css');
}
async #handleDisableMods() {
if (Services.prefs.getBoolPref('zen.themes.disable-all', false)) {
console.log('[ZenMods]: Disabling mods module.');
await this.#removeStylesheet();
} else {
console.log('[ZenMods]: Enabling mods module.');
await this.#rebuildModsStylesheet();
}
}
#getStylesheetURIForMod(mod) {
return Services.io.newFileURI(
new FileUtils.File(PathUtils.join(this.getModFolder(mod.id), 'chrome.css'))
);
}
async #insertStylesheet() {
if (await IOUtils.exists(this.#styleSheetPath)) {
await this.#stylesheetService.loadAndRegisterSheet(
this.#styleSheetUri,
this.#stylesheetService.AGENT_SHEET
);
}
if (
!this.#stylesheetService.sheetRegistered(
this.#styleSheetUri,
this.#stylesheetService.AGENT_SHEET
)
) {
console.error(`[ZenMods]: Failed to register stylesheet at ${this.#styleSheetUri.spec}.`);
}
}
async #removeStylesheet() {
await this.#stylesheetService.unregisterSheet(
this.#styleSheetUri,
this.#stylesheetService.AGENT_SHEET
);
const rv = this.#stylesheetService.sheetRegistered(
this.#styleSheetUri,
this.#stylesheetService.AGENT_SHEET
);
await IOUtils.remove(this.#styleSheetPath, { ignoreAbsent: true });
if (rv || (await IOUtils.exists(this.#styleSheetPath))) {
console.error(`[ZenMods]: Failed to unregister stylesheet at ${this.#styleSheetUri.spec}.`);
}
}
async #rebuildModsStylesheet() {
await this.#removeStylesheet();
const mods = await this.#getEnabledMods();
await this.#writeStylesheet(mods);
const modsWithPreferences = await Promise.all(
mods.map(async (mod) => {
const preferences = await this.getModPreferences(mod);
return {
name: mod.name,
enabled: mod.enabled,
preferences,
};
})
);
this.#setDefaults(modsWithPreferences);
this.#writeToDom(modsWithPreferences);
await this.#insertStylesheet();
}
async #getEnabledMods() {
const modsObject = await this.getMods();
const mods = Object.values(modsObject).filter(
(mod) => mod.enabled === undefined || mod.enabled
);
const modList = mods.map(({ name }) => name).join(', ');
const message =
modList !== ''
? `[ZenMods]: Loading enabled Zen mods: ${modList}.`
: '[ZenMods]: No enabled Zen mods.';
console.log(message);
return mods;
}
#setDefaults(modsWithPreferences) {
for (const { preferences, enabled } of modsWithPreferences) {
if (enabled !== undefined && !enabled) {
continue;
}
for (const { type, property, defaultValue } of preferences) {
if (defaultValue === undefined) {
continue;
}
if (type === 'checkbox') {
const value = Services.prefs.getBoolPref(property, false);
if (typeof defaultValue !== 'boolean') {
console.warn(
'[ZenMods]: Warning, invalid data type received for expected type boolean, skipping.'
);
continue;
}
if (!value) {
Services.prefs.setBoolPref(property, defaultValue);
}
} else {
const value = Services.prefs.getStringPref(property, 'zen-property-no-saved');
if (typeof defaultValue !== 'string' && typeof defaultValue !== 'number') {
console.warn(
`[ZenMods]: Warning, invalid data type received (${typeof defaultValue}), skipping.`
);
continue;
}
if (value === 'zen-property-no-saved') {
Services.prefs.setStringPref(property, defaultValue.toString());
}
}
}
}
}
#writeToDom(modsWithPreferences) {
for (const browser of ZenMultiWindowFeature.browsers) {
for (const { enabled, preferences, name } of modsWithPreferences) {
const sanitizedName = this.sanitizeModName(name);
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(modList = []) {
const mods = [];
for (let mod of modList) {
mod._chromeURL = this.#getStylesheetURIForMod(mod).spec;
mods.push(mod);
}
let content = this.#kZenStylesheetModHeader;
content += `\n* FILE GENERATED AT: ${this.#getCurrentDateTime()}\n`;
content += this.#kZenStylesheetModHeaderBody;
for (let mod of mods) {
if (mod.enabled !== undefined && !mod.enabled) {
continue;
}
content += `\n/* Name: ${mod.name} */\n`;
content += `/* Description: ${mod.description} */\n`;
content += `/* Author: @${mod.author} */\n`;
if (mod._readmeURL) {
content += `/* Readme: ${mod.readme} */\n`;
}
content += `@import url("${mod._chromeURL}");\n`;
}
content += this.#kZenStylesheetModFooter;
const buffer = new TextEncoder().encode(content);
await IOUtils.write(this.#styleSheetPath, buffer);
}
#compareVersions(version1, version2) {
let result = false;
if (typeof version1 !== 'object') {
version1 = version1.toString().split('.');
}
if (typeof version2 !== 'object') {
version2 = version2.toString().split('.');
}
for (let 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;
}
#composeModApiUrl(modId) {
// keeping theme here as it would require changes to CI to change the name
return `https://zen-browser.github.io/theme-store/themes/${modId}/theme.json`;
}
async #downloadUrlToFile(url, path, isStyleSheet = false, maxRetries = 3, retryDelayMs = 500) {
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status} for url: ${url}`);
}
const data = await response.text();
let content = data;
if (isStyleSheet) {
content = '@-moz-document url-prefix("chrome:") {\n';
for (const line of data.split('\n')) {
content += ` ${line}\n`;
}
content += '}';
}
// convert the data into a Uint8Array
const buffer = new TextEncoder().encode(content);
await IOUtils.write(path, buffer);
return; // to exit the loop
} catch (e) {
attempt++;
if (attempt >= maxRetries) {
console.error('[ZenMods]: Error downloading file after retries', url, e);
} else {
console.warn(
`[ZenMods]: Download failed (attempt ${attempt} of ${maxRetries}), retrying in ${retryDelayMs}ms...`,
url,
e
);
await new Promise((res) => setTimeout(res, retryDelayMs));
}
}
}
}
// private properties end
// public properties start
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);
};
}
sanitizeModName(name) {
// Do not change to "mod-" for backwards compatibility
return `theme-${name?.replaceAll(/\s/g, '-')?.replaceAll(/[^A-Za-z_-]+/g, '')}`;
}
get updatePref() {
return 'zen.themes.updated-value-observer';
}
get modsRootPath() {
return PathUtils.join(PathUtils.profileDir, 'chrome', 'zen-themes');
}
get modsDataFile() {
return PathUtils.join(PathUtils.profileDir, 'zen-themes.json');
}
getModFolder(modId) {
return PathUtils.join(this.modsRootPath, modId);
}
async getMods() {
if (!(await IOUtils.exists(this.modsDataFile))) {
await IOUtils.writeJSON(this.modsDataFile, {});
return {};
}
let mods = {};
try {
mods = await IOUtils.readJSON(this.modsDataFile);
if (mods === null || typeof mods !== 'object') {
throw new Error('Mods data file is invalid');
}
} catch {
// If we have a corrupted file, reset it
await IOUtils.writeJSON(this.modsDataFile, {});
Services.wm
.getMostRecentWindow('navigator:browser')
.gZenUIManager.showToast('zen-themes-corrupted', {
timeout: 8000,
});
}
return mods;
}
async getModPreferences(mod) {
const modPath = PathUtils.join(this.modsRootPath, mod.id, 'preferences.json');
if (!(await IOUtils.exists(modPath)) || !mod.preferences) {
return [];
}
try {
const preferences = await IOUtils.readJSON(modPath);
return preferences.filter(({ disabledOn = [] }) => {
return !disabledOn.includes(gZenOperatingSystemCommonUtils.currentOperatingSystem);
});
} catch (e) {
console.error(`[ZenMods]: Error reading mod preferences for ${mod.name}:`, e);
return [];
}
}
async init() {
try {
await SessionStore.promiseInitialized;
if (
Services.prefs.getBoolPref('zen.themes.disable-all', false) ||
Services.appinfo.inSafeMode
) {
console.log('[ZenMods]: Mods disabled by user or in safe mode.');
return;
}
await this.getMods(); // Check for any errors in the themes data file
const mods = await this.#getEnabledMods();
const modsWithPreferences = await Promise.all(
mods.map(async (mod) => {
const preferences = await this.getModPreferences(mod);
return {
name: mod.name,
enabled: mod.enabled,
preferences,
};
})
);
this.#writeToDom(modsWithPreferences);
await this.#insertStylesheet();
this.#setNewMilestoneIfNeeded();
if (this.#shouldAutoUpdate()) {
requestIdleCallback(
() => {
if (!window.closed) {
requestAnimationFrame(() => {
this.checkForModsUpdates();
});
}
},
{ timeout: 1000 }
);
}
} catch (e) {
console.error('[ZenMods]: Error loading Zen Mods:', e);
}
Services.prefs.addObserver(this.updatePref, this.#rebuildModsStylesheet.bind(this), false);
Services.prefs.addObserver(
'zen.themes.disable-all',
this.#handleDisableMods.bind(this),
false
);
}
#setNewMilestoneIfNeeded() {
const previousMilestone = Services.prefs.getStringPref('zen.mods.milestone', '');
if (previousMilestone != Services.appinfo.version) {
Services.prefs.setStringPref('zen.mods.milestone', Services.appinfo.version);
Services.prefs.clearUserPref('zen.mods.last-update');
}
}
#shouldAutoUpdate() {
const daysBeforeUpdate = Services.prefs.getIntPref('zen.mods.auto-update-days');
const lastUpdatedSec = Services.prefs.getIntPref('zen.mods.last-update', -1);
const nowSec = Math.floor(Date.now() / 1000);
const daysSinceUpdate = (nowSec - lastUpdatedSec) / (60 * 60 * 24);
return (
(Services.prefs.getBoolPref('zen.mods.auto-update', true) &&
daysSinceUpdate >= daysBeforeUpdate) ||
lastUpdatedSec < 0
);
}
async checkForModsUpdates() {
const mods = await this.getMods();
const updates = await Promise.all(
Object.values(mods).map(async (currentMod) => {
try {
const possibleNewModVersion = await this.requestMod(currentMod.id);
if (!possibleNewModVersion) {
return null;
}
if (
!this.#compareVersions(
possibleNewModVersion.version,
currentMod.version ?? '0.0.0'
) &&
possibleNewModVersion.version != currentMod.version
) {
console.log(
`[ZenMods]: Mod update found for mod ${currentMod.name} (${currentMod.id}), current: ${currentMod.version}, new: ${possibleNewModVersion.version}`
);
possibleNewModVersion.enabled = currentMod.enabled;
await this.removeMod(currentMod.id, false);
mods[currentMod.id] = possibleNewModVersion;
return possibleNewModVersion;
}
return null;
} catch (e) {
console.error('[ZenMods]: Error checking for mod updates', e);
return null;
}
})
);
await this.updateMods(mods);
Services.prefs.setIntPref('zen.mods.last-update', Math.floor(Date.now() / 1000));
return updates.filter((update) => {
return update !== null;
});
}
async removeMod(modId, triggerUpdate = true) {
const modPath = this.getModFolder(modId);
console.log(`[ZenMods]: Removing mod ${modPath}`);
await IOUtils.remove(modPath, { recursive: true, ignoreAbsent: true });
const mods = await this.getMods();
delete mods[modId];
await IOUtils.writeJSON(this.modsDataFile, mods);
if (triggerUpdate) {
this.triggerModsUpdate();
}
}
async enableMod(modId) {
const mods = await this.getMods();
const mod = mods[modId];
console.log(`[ZenMods]: Enabling mod ${mod.name}`);
mod.enabled = true;
await IOUtils.writeJSON(this.modsDataFile, mods);
}
async disableMod(modId) {
const mods = await this.getMods();
const mod = mods[modId];
console.log(`[ZenMods]: Disabling mod ${mod.name}`);
mod.enabled = false;
await IOUtils.writeJSON(this.modsDataFile, mods);
}
async updateMods(mods = undefined) {
if (!mods) {
mods = await this.getMods();
}
await IOUtils.writeJSON(this.modsDataFile, mods);
await this.checkForModChanges();
}
triggerModsUpdate() {
Services.prefs.setBoolPref(this.updatePref, !Services.prefs.getBoolPref(this.updatePref));
}
async installMod(mod) {
try {
const modPath = PathUtils.join(this.modsRootPath, mod.id);
await IOUtils.makeDirectory(modPath, { ignoreExisting: true });
await this.#downloadUrlToFile(mod.style, PathUtils.join(modPath, 'chrome.css'), true);
await this.#downloadUrlToFile(mod.readme, PathUtils.join(modPath, 'readme.md'));
if (mod.preferences) {
await this.#downloadUrlToFile(
mod.preferences,
PathUtils.join(modPath, 'preferences.json')
);
}
} catch (e) {
console.error('[ZenMods]: Error installing mod', mod.id, e);
}
}
async checkForModChanges() {
const mods = await this.getMods();
for (const [modId, mod] of Object.entries(mods)) {
try {
if (!mod) {
continue;
}
if (!(await IOUtils.exists(this.getModFolder(modId)))) {
await this.installMod(mod);
}
} catch (e) {
console.error('[ZenMods]: Error checking for mod changes', e);
}
}
this.triggerModsUpdate();
}
async requestMod(modId) {
const url = this.#composeModApiUrl(modId);
console.debug(`[ZenMods]: Fetching mod ${modId} info from ${url}`);
const data = await fetch(url, {
mode: 'no-cors',
});
if (data.ok) {
try {
const obj = await data.json();
return obj;
} catch (e) {
console.error(`[ZenMods]: Error parsing mod ${modId} info:`, e);
}
} else {
console.error(`[ZenMods]: Error fetching mod ${modId} info:`, data.status);
}
return null;
}
async isModInstalled(modId) {
const mods = await this.getMods();
return Boolean(mods?.[modId]);
}
// public properties end
}
window.gZenMods = new ZenMods();
gZenActorsManager.addJSWindowActor('ZenModsMarketplace', {
parent: {
esModuleURI: 'resource:///actors/ZenModsMarketplaceParent.sys.mjs',
},
child: {
esModuleURI: 'resource:///actors/ZenModsMarketplaceChild.sys.mjs',
events: {
DOMContentLoaded: {},
},
},
matches: [
...Services.prefs.getStringPref('zen.injections.match-urls').split(','),
'about:preferences',
],
});
}

View file

@ -0,0 +1,138 @@
// 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/.
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);
},
async getThemes() {
if (!(await IOUtils.exists(this.themesDataFile))) {
await IOUtils.writeJSON(this.themesDataFile, {});
}
let themes = {};
try {
themes = await IOUtils.readJSON(this.themesDataFile);
if (themes === null || typeof themes !== 'object') {
throw new Error('Themes data file is null');
}
} catch {
// If we have a corrupted file, reset it
await IOUtils.writeJSON(this.themesDataFile, {});
Services.wm
.getMostRecentWindow('navigator:browser')
.gZenUIManager.showToast('zen-themes-corrupted', {
timeout: 8000,
});
}
return themes;
},
async getThemePreferences(theme) {
const themePath = PathUtils.join(this.themesRootPath, theme.id, 'preferences.json');
if (!(await IOUtils.exists(themePath)) || !theme.preferences) {
return [];
}
try {
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-Za-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)
);
} catch (e) {
console.error(`[ZenThemes]: Error reading preferences for ${theme.name}:`, e);
return [];
}
},
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

@ -0,0 +1,335 @@
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;
}
await ZenThemesCommon.getThemes(); // Check for any errors in the themes data file
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);
const rv = this.sss.sheetRegistered(this.styleSheetURI, this.sss.AGENT_SHEET);
await IOUtils.remove(this.styleSheetPath, { ignoreAbsent: true });
if (!rv && !(await IOUtils.exists(this.styleSheetPath))) {
console.debug('[ZenThemesImporter]: Sheet successfully unregistered');
}
}
async rebuildThemeStylesheet() {
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;
}
if (type === '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);
}
} else {
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-Za-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 = [];
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,142 +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/.
export class ZenModsMarketplaceChild extends JSWindowActorChild {
constructor() {
super();
}
handleEvent(event) {
if (event.type === 'DOMContentLoaded') {
const verifier = this.contentWindow.document.querySelector(
'meta[name="zen-content-verified"]'
);
if (verifier) {
verifier.setAttribute('content', 'verified');
}
this.initiateModsMarketplace();
this.contentWindow.document.addEventListener(
'ZenCheckForModUpdates',
this.checkForModUpdates.bind(this)
);
}
}
// This function will be called from about:preferences
checkForModUpdates(event) {
event.preventDefault();
this.sendAsyncMessage('ZenModsMarketplace:CheckForUpdates');
}
initiateModsMarketplace() {
this.contentWindow.setTimeout(() => {
this.addButtons();
this.injectMarketplaceAPI();
}, 0);
}
get actionButton() {
return this.contentWindow.document.getElementById('install-theme');
}
get actionButtonUninstall() {
return this.contentWindow.document.getElementById('install-theme-uninstall');
}
async isThemeInstalled(themeId) {
return await this.sendQuery('ZenModsMarketplace:IsModInstalled', { themeId });
}
async receiveMessage(message) {
switch (message.name) {
case 'ZenModsMarketplace:ModChanged': {
const modId = message.data.modId;
const actionButton = this.actionButton;
const actionButtonInstalled = this.actionButtonUninstall;
if (actionButton && actionButtonInstalled) {
actionButton.disabled = false;
actionButtonInstalled.disabled = false;
if (await this.isThemeInstalled(modId)) {
actionButton.classList.add('hidden');
actionButtonInstalled.classList.remove('hidden');
} else {
actionButton.classList.remove('hidden');
actionButtonInstalled.classList.add('hidden');
}
}
break;
}
case 'ZenModsMarketplace:CheckForUpdatesFinished': {
const updates = message.data.updates;
this.contentWindow.document.dispatchEvent(
new CustomEvent('ZenModsMarketplace:CheckForUpdatesFinished', { detail: { updates } })
);
break;
}
}
}
injectMarketplaceAPI() {
Cu.exportFunction(this.handleModInstallationEvent.bind(this), this.contentWindow, {
defineAs: 'ZenInstallMod',
});
}
async addButtons() {
const actionButton = this.actionButton;
const actionButtonUninstall = this.actionButtonUninstall;
const errorMessage = this.contentWindow.document.getElementById('install-theme-error');
if (!actionButton || !actionButtonUninstall) {
return;
}
errorMessage.classList.add('hidden');
const themeId = actionButton.getAttribute('zen-theme-id');
if (await this.isThemeInstalled(themeId)) {
actionButtonUninstall.classList.remove('hidden');
} else {
actionButton.classList.remove('hidden');
}
actionButton.addEventListener('click', this.handleModInstallationEvent.bind(this));
actionButtonUninstall.addEventListener('click', this.handleModUninstallEvent.bind(this));
}
async handleModUninstallEvent(event) {
const button = event.target;
button.disabled = true;
const modId = button.getAttribute('zen-theme-id');
this.sendAsyncMessage('ZenModsMarketplace:UninstallMod', { modId });
}
async handleModInstallationEvent(event) {
// Object can be an event or a theme id
let modId;
if (event.target) {
const button = event.target;
button.disabled = true;
modId = button.getAttribute('zen-theme-id');
} else {
// Backwards compatibility is... Interesting
modId = event.themeId ?? event.modId ?? event.id;
}
this.sendAsyncMessage('ZenModsMarketplace:InstallMod', { modId });
}
}

View file

@ -1,65 +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/.
export class ZenModsMarketplaceParent extends JSWindowActorParent {
constructor() {
super();
}
get modsManager() {
return this.browsingContext.topChromeWindow.gZenMods;
}
async receiveMessage(message) {
switch (message.name) {
case 'ZenModsMarketplace:InstallMod': {
const modId = message.data.modId;
const mod = await this.modsManager.requestMod(modId);
console.log(`[ZenModsMarketplaceParent]: Installing mod ${mod.id}`);
mod.enabled = true;
const mods = await this.modsManager.getMods();
mods[mod.id] = mod;
await this.modsManager.updateMods(mods);
await this.updateChildProcesses(mod.id);
break;
}
case 'ZenModsMarketplace:UninstallMod': {
const modId = message.data.modId;
console.log(`[ZenModsMarketplaceParent]: Uninstalling mod ${modId}`);
const mods = await this.modsManager.getMods();
delete mods[modId];
await this.modsManager.removeMod(modId);
await this.modsManager.updateMods(mods);
await this.updateChildProcesses(modId);
break;
}
case 'ZenModsMarketplace:CheckForUpdates': {
const updates = await this.modsManager.checkForModsUpdates();
this.sendAsyncMessage('ZenModsMarketplace:CheckForUpdatesFinished', { updates });
break;
}
case 'ZenModsMarketplace:IsModInstalled': {
const themeId = message.data.themeId;
const themes = await this.modsManager.getMods();
return Boolean(themes?.[themeId]);
}
}
}
async updateChildProcesses(modId) {
this.sendAsyncMessage('ZenModsMarketplace:ModChanged', { modId });
}
}

View file

@ -0,0 +1,200 @@
// 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/.
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 called from about:preferences
checkForThemeUpdates(event) {
event.preventDefault();
this.sendAsyncMessage('ZenThemeMarketplace:CheckForUpdates');
}
initiateThemeMarketplace() {
this.contentWindow.setTimeout(() => {
this.addInstallButtons();
this.injectMarketplaceAPI();
}, 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;
}
}
}
injectMarketplaceAPI() {
Cu.exportFunction(this.installTheme.bind(this), this.contentWindow, {
defineAs: 'ZenInstallTheme',
});
}
async addInstallButtons() {
const actionButton = this.actionButton;
const actionButtonUninstall = this.actionButtonUninstall;
const errorMessage = this.contentWindow.document.getElementById('install-theme-error');
if (!actionButton || !actionButtonUninstall) {
return;
}
errorMessage.classList.add('hidden');
const themeId = actionButton.getAttribute('zen-theme-id');
if (await this.isThemeInstalled(themeId)) {
actionButtonUninstall.classList.remove('hidden');
} else {
actionButton.classList.remove('hidden');
}
actionButton.addEventListener('click', this.installTheme.bind(this));
actionButtonUninstall.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('ZenThemeMarketplace: Error parsing theme info: ', e);
}
} else {
console.error('ZenThemeMarketplace: Error fetching theme info: ', 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('ZenThemeMarketplace: Installing theme with id: ', themeId);
const theme = await this.getThemeInfo(themeId);
if (!theme) {
console.error('ZenThemeMarketplace: Error fetching theme info');
return;
}
this.addTheme(theme);
}
}

View file

@ -0,0 +1,256 @@
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 Boolean(themes?.[themeId]);
}
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) {
let result = false;
if (typeof version1 !== 'object') {
version1 = version1.toString().split('.');
}
if (typeof version2 !== 'object') {
version2 = version2.toString().split('.');
}
for (let 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 = [];
const themes = await this.getThemes();
for (const theme of Object.values(themes)) {
try {
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);
themes[themeInfo.id] = themeInfo;
}
} catch (e) {
console.error('ZenThemeMarketplaceParent: Error checking for theme updates', e);
}
}
await this.updateThemes(themes);
this.sendAsyncMessage('ZenThemeMarketplace:CheckForUpdatesFinished', { updates });
}
async updateChildProcesses(themeId) {
this.sendAsyncMessage('ZenThemeMarketplace:ThemeChanged', { themeId });
}
async getThemes() {
return await IOUtils.readJSON(this.themesDataFile);
}
async updateThemes(themes = undefined) {
if (!themes) {
themes = await this.getThemes();
}
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, maxRetries = 3, retryDelayMs = 500) {
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(
`ZenThemeMarketplaceParent: HTTP error! status: ${response.status} for url: ${url}`
);
}
const data = await response.text();
const content = isStyleSheet ? this.getStyleSheetFullContent(data) : data;
// convert the data into a Uint8Array
const buffer = new TextEncoder().encode(content);
await IOUtils.write(path, buffer);
return;
} catch (e) {
attempt++;
if (attempt >= maxRetries) {
console.error('ZenThemeMarketplaceParent: Error downloading file after retries', url, e);
} else {
console.warn(
`ZenThemeMarketplaceParent: Download failed (attempt ${attempt} of ${maxRetries}), retrying in ${retryDelayMs}ms...`,
url,
e
);
await new Promise((res) => setTimeout(res, retryDelayMs));
}
}
}
}
async downloadThemeFileContents(theme) {
try {
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')
);
}
} catch (e) {
console.log('ZenThemeMarketplaceParent: Error downloading theme file contents', theme.id, e);
}
}
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) {
try {
await this.downloadThemeFileContents(theme);
} catch (e) {
console.error('ZenThemeMarketplaceParent: Error installing theme', theme.id, e);
}
}
async checkForThemeChanges() {
const themes = await this.getThemes();
const themeIds = Object.keys(themes);
for (const themeId of themeIds) {
try {
const theme = themes[themeId];
if (!theme) {
continue;
}
const themePath = PathUtils.join(this.themesRootPath, themeId);
if (!(await IOUtils.exists(themePath))) {
await this.installTheme(theme);
}
} catch (e) {
console.error('ZenThemeMarketplaceParent: Error checking for theme changes', e);
}
}
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

@ -3,7 +3,8 @@
# 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/.
FINAL_TARGET_FILES.actors += [
"actors/ZenModsMarketplaceChild.sys.mjs",
"actors/ZenModsMarketplaceParent.sys.mjs",
"actors/ZenThemeMarketplaceChild.sys.mjs",
"actors/ZenThemeMarketplaceParent.sys.mjs",
]

View file

@ -104,7 +104,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
);
window.addEventListener('TabClose', this.handleTabClose.bind(this));
window.addEventListener('TabBrowserDiscarded', this.handleTabBrowserDiscarded.bind(this));
window.addEventListener('TabSelect', this.onTabSelect.bind(this));
this.initializeContextMenu();
this.insertIntoContextMenu();
@ -151,22 +150,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
this.removeTabFromGroup(tab, groupIndex, true);
}
/**
* @param {Event} event - The event that triggered the tab browser discard.
* @description Handles the tab browser discard event.
*/
async handleTabBrowserDiscarded(event) {
const tab = event.target;
if (tab.group?.hasAttribute('split-view-group')) {
gBrowser.explicitUnloadTabs(tab.group.tabs);
for (const t of tab.group.tabs) {
if (t.glanceTab) {
gBrowser.explicitUnloadTabs([t.glanceTab]);
}
}
}
}
/**
* @param {Event} event - The event that triggered the tab select.
* @description Handles the tab select event.
@ -210,10 +193,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
const node = this.getSplitNodeFromTab(tab);
const toUpdate = this.removeNode(node);
this.applyGridLayout(toUpdate);
// Select next tab if the removed tab was selected
if (gBrowser.selectedTab === tab) {
gBrowser.selectedTab = group.tabs[0];
}
}
}
@ -897,9 +876,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
tabCount: window.gBrowser.selectedTabs.length,
});
document.getElementById('context_zenSplitTabs').setAttribute('data-l10n-args', tabCountInfo);
document
.getElementById('context_zenSplitTabs')
.setAttribute('disabled', !this.contextCanSplitTabs());
document.getElementById('context_zenSplitTabs').disabled = !this.contextCanSplitTabs();
});
}
@ -952,8 +929,8 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
window.gContextMenu.contentData.docLocation ||
window.gContextMenu.target.ownerDocument.location.href;
const currentTab = gZenGlanceManager.getTabOrGlanceParent(window.gBrowser.selectedTab);
const newTab = this.openAndSwitchToTab(url, { inBackground: false });
this.splitTabs([currentTab, newTab], undefined, 1);
const newTab = this.openAndSwitchToTab(url);
this.splitTabs([currentTab, newTab]);
}
/**
@ -999,6 +976,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
tab = tab.parentNode.closest('.tabbrowser-tab');
console.assert(tab, 'Tab not found for zen-glance-tab');
}
this.updateSplitViewButton(!tab?.splitView);
if (tab) {
this.updateSplitView(tab);
tab.linkedBrowser.docShellIsActive = true;
@ -1030,7 +1008,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
* Splits the given tabs.
*
* @param {Tab[]} tabs - The tabs to split.
* @param {string|undefined} gridType - The type of grid layout.
* @param {string} gridType - The type of grid layout.
*/
splitTabs(tabs, gridType, initialIndex = 0) {
// TODO: Add support for splitting essential tabs
@ -1096,7 +1074,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
};
this._data.push(splitData);
if (!this._sessionRestoring) {
window.gBrowser.selectedTab = tabs[initialIndex] ?? tabs[0];
window.gBrowser.selectedTab = tabs[0];
}
// Add tabs to the split view group
@ -1132,6 +1110,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
if (oldView === newView) return;
if (newView < 0 && oldView >= 0) {
this.updateSplitViewButton(true);
this.deactivateCurrentSplitView();
return;
}
@ -1143,7 +1122,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
* Deactivates the split view.
*/
deactivateCurrentSplitView() {
if (this.currentView < 0) return;
this.setTabsDocShellState(this._data[this.currentView].tabs, false);
for (const tab of this._data[this.currentView].tabs) {
const container = tab.linkedBrowser.closest('.browserSidebarContainer');
@ -1151,9 +1129,9 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
}
this.removeSplitters();
this.tabBrowserPanel.removeAttribute('zen-split-view');
this.updateSplitViewButton(true);
this.currentView = -1;
this.toggleWrapperDisplay(false);
this.maybeDisableOpeningTabOnSplitView();
}
/**
@ -1175,6 +1153,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
this.tabBrowserPanel.setAttribute('zen-split-view', 'true');
this.updateSplitViewButton(false);
this.applyGridToTabs(splitData.tabs);
this.applyGridLayout(splitData.layoutTree);
this.setTabsDocShellState(splitData.tabs, true);
@ -1311,7 +1290,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
});
}
});
this.maybeDisableOpeningTabOnSplitView();
}
/**
@ -1502,6 +1480,20 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
container.style.inset = '';
}
/**
* Updates the split view button visibility.
*
* @param {boolean} hidden - Indicates if the button should be hidden.
*/
updateSplitViewButton(hidden) {
const button = document.getElementById('zen-split-views-box');
if (hidden) {
button?.setAttribute('hidden', 'true');
} else {
button?.removeAttribute('hidden');
}
}
/**
* Updates the UI of the panel.
*
@ -1676,15 +1668,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
if (splitGroup && (!draggedTab.group || draggedTab.group !== splitGroup)) {
this._moveTabsToContainer([draggedTab], droppedOnTab);
gBrowser.moveTabToGroup(draggedTab, splitGroup);
if (hoverSide === 'left' || hoverSide === 'top') {
try {
splitGroup.tabs[0].before(draggedTab);
} catch (e) {
console.warn(
`Failed to move tab ${draggedTab.id} before ${splitGroup.tabs[0].id}: ${e}`
);
}
}
}
const droppedOnSplitNode = this.getSplitNodeFromTab(droppedOnTab);
@ -1874,26 +1857,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
this.onLocationChange(gBrowser.selectedTab.linkedBrowser);
}
}
maybeDisableOpeningTabOnSplitView() {
const shouldBeDisabled = !this.canOpenLinkInSplitView();
document
.getElementById('cmd_zenSplitViewLinkInNewTab')
.setAttribute('disabled', shouldBeDisabled);
document.getElementById('zen-glance-sidebar-split').setAttribute('disabled', shouldBeDisabled);
}
canOpenLinkInSplitView() {
const currentView = this.currentView;
if (currentView < 0) {
return true;
}
const group = this._data[currentView];
if (!group || group.tabs.length >= this.MAX_TABS) {
return false;
}
return true;
}
}
window.gZenViewSplitter = new ZenViewSplitter();

View file

@ -41,10 +41,6 @@
}
}
#zen-splitview-dropzone {
margin-top: 0 !important;
}
#tabbrowser-tabpanels[zen-split-view='true']:not([zen-split-resizing]) > [zen-split='true'] {
transition: inset 0.09s ease-out !important;
& browser {
@ -74,9 +70,16 @@
margin-right: calc(-1 * var(--zen-split-column-gap));
}
:root:not([customizing]) #zen-splitview-overlay {
#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 {
@ -109,6 +112,10 @@
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);

View file

@ -1,6 +1,3 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
{
const lazy = {};
@ -52,6 +49,8 @@
}
class ZenPinnedTabManager extends ZenDOMOperatedFeature {
MAX_ESSENTIALS_TABS = 12;
async init() {
if (!this.enabled) {
return;
@ -88,14 +87,10 @@
onTabIconChanged(tab, url = null) {
const iconUrl = url ?? tab.iconImage.src;
if (!iconUrl && tab.hasAttribute('zen-pin-id')) {
if (!iconUrl) {
try {
setTimeout(async () => {
try {
await this.promisePinnedCacheInitialized;
const pin = this._pinsCache?.find(
(pin) => pin.uuid === tab.getAttribute('zen-pin-id')
);
let favicon = await PlacesUtils.favicons.getFaviconForPage(
Services.io.newURI(pin.url)
);
@ -161,9 +156,6 @@
await gZenWorkspaces.promiseSectionsInitialized;
await this._initializePinsCache();
await this._initializePinnedTabs(init);
if (init) {
this._resolveInitializedPinnedCache();
}
}
async _initializePinsCache() {
@ -667,7 +659,7 @@
for (let i = 0; i < tabs.length; i++) {
let tab = tabs[i];
const section = gZenWorkspaces.getEssentialsSection(tab);
if (!this.canEssentialBeAdded(tab)) {
if (section.children.length >= this.MAX_ESSENTIALS_TABS) {
movedAll = false;
continue;
}
@ -799,7 +791,7 @@
document.getElementById('context_zen-add-essential').hidden =
contextTab.getAttribute('zen-essential') ||
!!contextTab.group ||
!this.canEssentialBeAdded(contextTab);
gBrowser._numZenEssentials >= this.MAX_ESSENTIALS_TABS;
document.getElementById('context_zen-remove-essential').hidden =
!contextTab.getAttribute('zen-essential');
document.getElementById('context_unpinTab').hidden =
@ -951,7 +943,7 @@
} else {
tab.setAttribute('zen-pinned-changed', 'true');
}
tab.style.setProperty('--zen-original-tab-icon', `url(${pin.iconUrl.spec})`);
tab.style.setProperty('--zen-original-tab-icon', `url(${pin.iconUrl})`);
}
removeTabContainersDragoverClass() {
@ -1012,16 +1004,6 @@
}
}
canEssentialBeAdded(tab) {
return (
!(
(tab.getAttribute('usercontextid') || 0) !=
gZenWorkspaces.getActiveWorkspaceFromCache().containerTabId &&
gZenWorkspaces.containerSpecificEssentials
)
);
}
applyDragoverClass(event, draggedTab) {
if (!this.enabled) {
return;
@ -1055,7 +1037,10 @@
shouldAddDragOverElement = true;
}
} else if (essentialTabsTarget) {
if (!draggedTab.hasAttribute('zen-essential') && this.canEssentialBeAdded(draggedTab)) {
if (
!draggedTab.hasAttribute('zen-essential') &&
gBrowser._numZenEssentials < this.MAX_ESSENTIALS_TABS
) {
shouldAddDragOverElement = true;
isVertical = false;
}
@ -1132,8 +1117,4 @@
}
window.gZenPinnedTabManager = new ZenPinnedTabManager();
gZenPinnedTabManager.promisePinnedCacheInitialized = new Promise((resolve) => {
gZenPinnedTabManager._resolveInitializedPinnedCache = resolve;
});
}

View file

@ -1,6 +1,3 @@
// 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/.
var ZenPinnedTabsStorage = {
async init() {
await this._ensureTable();
@ -109,8 +106,8 @@ var ZenPinnedTabsStorage = {
`
INSERT OR REPLACE INTO zen_pins (
uuid, title, url, container_id, workspace_uuid, position,
is_essential, is_group, parent_uuid, edited_title, created_at,
updated_at
is_essential, is_group, parent_uuid, created_at, updated_at,
edited_title
) VALUES (
:uuid, :title, :url, :container_id, :workspace_uuid, :position,
:is_essential, :is_group, :parent_uuid, :edited_title,

View file

@ -1,8 +1,3 @@
/*
* 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/.
*/
height: var(--zen-toolbar-height);
z-index: 1;

View file

@ -356,7 +356,6 @@
margin-block: 0 !important;
margin-inline: 0 !important;
box-shadow: none !important;
border-radius: calc(var(--border-radius-medium) - 2px);
}
/* Adjust padding for content */
& .tab-content {
@ -375,27 +374,6 @@
width: 14px;
height: 14px;
}
& .tab-audio-button {
display: none !important;
}
&[soundplaying]:not([muted]) .tab-icon-stack::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;
}
}
/* --- Essentials Glance Tab Specifics (Floating) --- */
@ -731,6 +709,7 @@
/* Hide text labels */
& .zen-current-workspace-indicator-name,
& .zen-workspace-actions,
& zen-workspace .toolbarbutton-text {
display: none !important;
}
@ -1261,7 +1240,7 @@
padding-bottom: var(--zen-toolbox-padding);
overflow: hidden;
gap: 4px;
gap: calc(var(--zen-toolbox-padding) - 2px);
transition:
max-height 0.3s ease-out,
grid-template-columns 0.3s ease-out;

View file

@ -9,8 +9,8 @@ function openGlanceOnTab(callback, close = true) {
gZenGlanceManager
.openGlance({
url: 'https://example.com',
clientX: 0,
clientY: 0,
x: 0,
y: 0,
width: 0,
height: 0,
})

View file

@ -1,6 +1,3 @@
# 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/.
BROWSER_CHROME_MANIFESTS += [
"compact_mode/browser.toml",

View file

@ -1,6 +1,3 @@
# 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/.
FINAL_LIBRARY = "xul"
SOURCES += [

View file

@ -1,6 +1,3 @@
# 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/.
XPIDL_SOURCES += [
"nsIZenCommonUtils.idl",

View file

@ -1,6 +1,3 @@
# 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/.
FINAL_LIBRARY = "xul"
SOURCES += [

View file

@ -1,6 +1,3 @@
# 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/.
DIRS += [
"common",

View file

@ -73,7 +73,7 @@
initContextMenu() {
const menu = window.MozXULElement.parseXULToFragment(`
<menuitem id="zenToolbarThemePicker"
<menuitem class="zenToolbarThemePicker"
data-lazy-l10n-id="zen-workspaces-change-gradient"
command="cmd_zenOpenZenThemePicker"/>
`);
@ -944,7 +944,10 @@
return `rgba(${color.c[0]}, ${color.c[1]}, ${color.c[2]}, ${this.currentOpacity})`;
}
getGradient(colors, forToolbar = false) {
getGradient(colors, forToolbar = false, rotation = undefined) {
if (typeof rotation === 'undefined') {
rotation = this.currentRotation;
}
const themedColors = this.themedColors(colors);
this.useAlgo = themedColors[0]?.algorithm ?? '';
@ -955,12 +958,12 @@
} else if (themedColors.length === 1) {
return this.getSingleRGBColor(themedColors[0], forToolbar);
} else if (themedColors.length !== 3) {
return `linear-gradient(${this.currentRotation}deg, ${themedColors.map((color) => this.getSingleRGBColor(color, forToolbar)).join(', ')})`;
return `linear-gradient(${rotation}deg, ${themedColors.map((color) => this.getSingleRGBColor(color, forToolbar)).join(', ')})`;
} else {
let color1 = this.getSingleRGBColor(themedColors[2], forToolbar);
let color2 = this.getSingleRGBColor(themedColors[0], forToolbar);
let color3 = this.getSingleRGBColor(themedColors[1], forToolbar);
return `linear-gradient(${this.currentRotation}deg, ${color1}, ${color2}, ${color3})`;
return `linear-gradient(${rotation}deg, ${color1}, ${color2}, ${color3})`;
}
}
@ -1122,7 +1125,6 @@
}
}
const appBackground = browser.document.getElementById('zen-browser-background');
if (!skipUpdate) {
browser.document.documentElement.style.setProperty(
'--zen-main-browser-background-old',

View file

@ -1,6 +1,3 @@
// 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/.
{
class ZenWorkspace extends MozXULElement {
static get markup() {
@ -8,8 +5,9 @@
<vbox class="zen-workspace-tabs-section zen-current-workspace-indicator" flex="1">
<hbox class="zen-current-workspace-indicator-icon"></hbox>
<hbox class="zen-current-workspace-indicator-name"></hbox>
<toolbarbutton class="toolbarbutton-1 chromeclass-toolbar-additional zen-workspaces-actions" context="zenWorkspaceMoreActions"></toolbarbutton>
</vbox>
<arrowscrollbox orient="vertical" class="workspace-arrowscrollbox">
<arrowscrollbox orient="vertical" tabindex="-1" class="workspace-arrowscrollbox">
<vbox class="zen-workspace-tabs-section zen-workspace-pinned-tabs-section">
<html:div class="vertical-pinned-tabs-container-separator"></html:div>
</vbox>
@ -60,6 +58,13 @@
false
);
this.indicator.querySelector('.zen-current-workspace-indicator-name').onRenameFinished =
this.onIndicatorRenameFinished.bind(this);
this.indicator
.querySelector('.zen-workspaces-actions')
.addEventListener('click', this.onActionsCommand.bind(this));
this.scrollbox.addEventListener('wheel', this, true);
this.scrollbox.addEventListener('underflow', this);
this.scrollbox.addEventListener('overflow', this);
@ -171,6 +176,38 @@
gBrowser.tabContainer.handleEvent(event);
}
}
get workspaceUuid() {
return this.id;
}
async onIndicatorRenameFinished(newName) {
if (newName === '') {
return;
}
let workspaces = (await gZenWorkspaces._workspaces()).workspaces;
let workspaceData = workspaces.find((workspace) => workspace.uuid === this.workspaceUuid);
workspaceData.name = newName;
await gZenWorkspaces.saveWorkspace(workspaceData);
this.indicator.querySelector('.zen-current-workspace-indicator-name').textContent = newName;
gZenUIManager.showToast('zen-workspace-renamed-toast');
}
onActionsCommand(event) {
event.stopPropagation();
const popup = document.getElementById('zenWorkspaceMoreActions');
event.target.setAttribute('open', 'true');
this.indicator.setAttribute('open', 'true');
popup.addEventListener(
'popuphidden',
() => {
event.target.removeAttribute('open');
this.indicator.removeAttribute('open');
},
{ once: true }
);
popup.openPopup(event.target, 'after_start');
}
}
customElements.define('zen-workspace', ZenWorkspace);

View file

@ -1,6 +1,3 @@
// 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/.
{
class ZenWorkspaceIcons extends MozXULElement {
constructor() {

View file

@ -837,7 +837,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
async initializeWorkspaces() {
if (this.workspaceEnabled) {
this._initializeWorkspaceCreationIcons();
this._initializeWorkspaceTabContextMenus();
await this.workspaceBookmarks();
window.addEventListener('TabBrowserInserted', this.onTabBrowserInserted.bind(this));
@ -853,9 +852,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
console.error('gZenWorkspaces: Error initializing theme picker', e);
}
this.onWindowResize();
if (window.gZenSessionStore) {
await gZenSessionStore.promiseInitialized;
}
await this._selectStartPage();
this._fixTabPositions();
this._resolveInitialized();
@ -913,7 +909,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
) {
this.log(`Found tab to select: ${this._tabToSelect}, ${tabs.length}`);
setTimeout(() => {
gBrowser.selectedTab = gZenGlanceManager.getTabOrGlanceParent(tabs[this._tabToSelect]);
gBrowser.selectedTab = tabs[this._tabToSelect];
this._removedByStartupPage = true;
gBrowser.removeTab(this._tabToRemoveForEmpty, {
skipSessionStore: true,
@ -1064,84 +1060,48 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
}
addPopupListeners() {
const popup = document.getElementById('PanelUI-zen-workspaces');
const contextMenu = document.getElementById('zenWorkspaceActionsMenu');
const workspaceActions = document.getElementById('zenWorkspaceMoreActions');
workspaceActions.addEventListener('popupshowing', this.updateWorkspaceActionsMenu.bind(this));
popup.addEventListener('popuphidden', this.handlePanelHidden.bind(this));
popup.addEventListener('command', this.handlePanelCommand.bind(this));
contextMenu.addEventListener('popuphidden', (event) => {
if (event.target === contextMenu) {
this.onContextMenuClose(event);
const contextChangeContainerTabMenu = document.getElementById(
'context_zenWorkspacesOpenInContainerTab'
);
contextChangeContainerTabMenu.addEventListener(
'popupshowing',
this.updateWorkspaceActionsMenuContainer.bind(this)
);
contextChangeContainerTabMenu.addEventListener(
'command',
this.contextChangeContainerTab.bind(this)
);
}
updateWorkspaceActionsMenu(event) {
if (event.target.id !== 'zenWorkspaceMoreActions') {
return;
}
const openInContainerMenuItem = document.getElementById(
'context_zenWorkspacesOpenInContainerTab'
);
if (this.shouldShowContainers) {
openInContainerMenuItem.removeAttribute('hidden');
} else {
openInContainerMenuItem.setAttribute('hidden', 'true');
}
}
updateWorkspaceActionsMenuContainer(event) {
const workspace = this.getActiveWorkspaceFromCache();
let containerTabId = workspace.containerTabId;
return window.createUserContextMenu(event, {
isContextMenu: true,
excludeUserContextId: containerTabId,
showDefaultTab: true,
});
contextMenu.addEventListener('popupshowing', this.updateContextMenu.bind(this));
contextMenu.addEventListener('command', this.handleContextMenuCommand.bind(this));
const submenu = document.querySelector('#context_zenWorkspacesOpenInContainerTab > menupopup');
if (submenu) {
submenu.addEventListener('popupshowing', this.createContainerTabMenu.bind(this));
submenu.addEventListener('command', this.contextChangeContainerTab.bind(this));
}
const onWorkspaceIconContainerClick = this.onWorkspaceIconContainerClick.bind(this);
for (const element of document.querySelectorAll('.PanelUI-zen-workspaces-icons-container')) {
element.addEventListener('click', onWorkspaceIconContainerClick);
}
document
.getElementById('PanelUI-zen-workspaces-create-input')
.addEventListener('input', this.onWorkspaceCreationNameChange.bind(this));
document
.getElementById('PanelUI-zen-workspaces-edit-input')
.addEventListener('input', this.onWorkspaceEditChange.bind(this));
document
.getElementById('PanelUI-zen-workspaces-icon-search-input')
.addEventListener('input', this.conductSearch.bind(this));
}
handlePanelCommand(event) {
let target = event.target.closest('toolbarbutton');
target ??= event.target.closest('button');
if (!target) {
return;
}
switch (target.id) {
case 'PanelUI-zen-workspaces-reorder-mode':
this.toggleReorderMode();
break;
case 'PanelUI-zen-workspaces-new':
this.openSaveDialog();
break;
case 'PanelUI-zen-workspaces-create-save':
this.saveWorkspaceFromCreate();
break;
case 'PanelUI-zen-workspaces-edit-cancel':
case 'PanelUI-zen-workspaces-create-cancel':
this.closeWorkspacesSubView();
break;
case 'PanelUI-zen-workspaces-edit-save':
this.saveWorkspaceFromEdit();
break;
}
}
handleContextMenuCommand(event) {
const target = event.target.closest('menuitem');
if (!target) {
return;
}
switch (target.id) {
case 'context_zenOpenWorkspace':
this.openWorkspace();
break;
case 'context_zenEditWorkspace':
this.contextEdit(event);
break;
case 'context_zenDeleteWorkspace':
this.contextDelete(event);
break;
}
async contextDeleteWorkspace() {
await this.removeWorkspace(this.activeWorkspace, true);
}
searchIcons(input, icons) {
@ -1205,69 +1165,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
return filteredEmojiScores.map((score) => score.emoji);
}
resetWorkspaceIconSearch() {
let container = document.getElementById('PanelUI-zen-workspaces-icon-picker-wrapper');
let searchInput = document.getElementById('PanelUI-zen-workspaces-icon-search-input');
// Clear the search input field
searchInput.value = '';
for (let button of container.querySelectorAll('.toolbarbutton-1')) {
button.style.display = '';
}
}
_initializeWorkspaceCreationIcons() {
let container = document.getElementById('PanelUI-zen-workspaces-icon-picker-wrapper');
let searchInput = document.getElementById('PanelUI-zen-workspaces-icon-search-input');
searchInput.value = '';
for (let iconData of this.emojis) {
const icon = iconData[0];
let button = document.createXULElement('toolbarbutton');
button.className = 'toolbarbutton-1 workspace-icon-button';
button.setAttribute('label', icon);
button.onclick = (event) => {
const button = event.target;
let wasSelected = button.hasAttribute('selected');
for (let button of container.children) {
button.removeAttribute('selected');
}
if (!wasSelected) {
button.setAttribute('selected', 'true');
}
if (this.onIconChangeConnectedCallback) {
this.onIconChangeConnectedCallback(icon);
} else {
this.onWorkspaceIconChangeInner('create', icon);
}
};
container.appendChild(button);
}
}
conductSearch() {
const container = document.getElementById('PanelUI-zen-workspaces-icon-picker-wrapper');
const searchInput = document.getElementById('PanelUI-zen-workspaces-icon-search-input');
const query = searchInput.value.toLowerCase();
if (query === '') {
this.resetWorkspaceIconSearch();
return;
}
const buttons = Array.from(container.querySelectorAll('.toolbarbutton-1'));
buttons.forEach((button) => (button.style.display = 'none'));
const filteredIcons = this.searchIcons(query, this.emojis);
filteredIcons.forEach((emoji) => {
const matchingButton = buttons.find((button) => button.getAttribute('label') === emoji);
if (matchingButton) {
matchingButton.style.display = '';
container.appendChild(matchingButton);
}
});
}
async saveWorkspace(workspaceData, preventPropagation = false) {
if (this.privateWindowOrDisabled) {
return;
@ -1314,83 +1211,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
);
}
// Workspaces dialog UI management
openSaveDialog() {
let parentPanel = document.getElementById('PanelUI-zen-workspaces-multiview');
// randomly select an icon
let icon = this.emojis[Math.floor(Math.random() * (this.emojis.length - 257))][0];
this._workspaceCreateInput.textContent = '';
this._workspaceCreateInput.value = '';
this._workspaceCreateInput.setAttribute('data-initial-value', '');
document
.querySelectorAll('#PanelUI-zen-workspaces-icon-picker-wrapper toolbarbutton')
.forEach((button) => {
if (button.label === icon) {
button.setAttribute('selected', 'true');
} else {
button.removeAttribute('selected');
}
});
document.querySelector('.PanelUI-zen-workspaces-icons-container.create').textContent = icon;
PanelUI.showSubView('PanelUI-zen-workspaces-create', parentPanel);
}
async openEditDialog(workspaceUuid) {
this._workspaceEditDialog.setAttribute('data-workspace-uuid', workspaceUuid);
document.getElementById('PanelUI-zen-workspaces-edit-save').setAttribute('disabled', 'true');
let workspaces = (await this._workspaces()).workspaces;
let workspaceData = workspaces.find((workspace) => workspace.uuid === workspaceUuid);
this._workspaceEditInput.textContent = workspaceData.name;
this._workspaceEditInput.value = workspaceData.name;
this._workspaceEditInput.setAttribute('data-initial-value', workspaceData.name);
this._workspaceEditIconsContainer.setAttribute('data-initial-value', workspaceData.icon);
this.onIconChangeConnectedCallback = (...args) => {
this.onWorkspaceIconChangeInner('edit', ...args);
this.onWorkspaceEditChange(...args);
};
document
.querySelectorAll('#PanelUI-zen-workspaces-icon-picker-wrapper toolbarbutton')
.forEach((button) => {
if (button.label === workspaceData.icon) {
button.setAttribute('selected', 'true');
} else {
button.removeAttribute('selected');
}
});
document.querySelector('.PanelUI-zen-workspaces-icons-container.edit').textContent =
this.getWorkspaceIcon(workspaceData);
let parentPanel = document.getElementById('PanelUI-zen-workspaces-multiview');
PanelUI.showSubView('PanelUI-zen-workspaces-edit', parentPanel);
}
onWorkspaceIconChangeInner(type = 'create', icon) {
const container = document.querySelector(`.PanelUI-zen-workspaces-icons-container.${type}`);
if (container.textContent !== icon) {
container.textContent = icon;
}
this.goToPreviousSubView();
}
onWorkspaceIconContainerClick(event) {
event.preventDefault();
const parentPanel = document.getElementById('PanelUI-zen-workspaces-edit');
PanelUI.showSubView('PanelUI-zen-workspaces-icon-picker', parentPanel);
const container = parentPanel.parentNode.querySelector('.panel-viewcontainer');
setTimeout(() => {
if (container) {
container.style.minHeight = 'unset';
}
});
}
goToPreviousSubView() {
const parentPanel = document.getElementById('PanelUI-zen-workspaces-multiview');
parentPanel.goBack();
}
workspaceHasIcon(workspace) {
return workspace.icon && workspace.icon !== '';
}
@ -1424,230 +1244,15 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
return;
}
let workspaceList = browser.document.getElementById('PanelUI-zen-workspaces-list');
const createWorkspaceElement = (workspace) => {
let element = browser.document.createXULElement('toolbarbutton');
element.className = 'subviewbutton zen-workspace-button';
element.setAttribute('tooltiptext', workspace.name);
element.setAttribute('zen-workspace-id', workspace.uuid);
if (this.isWorkspaceActive(workspace)) {
element.setAttribute('active', 'true');
}
let containerGroup = undefined;
try {
containerGroup = browser.ContextualIdentityService.getPublicIdentities().find(
(container) => container.userContextId === workspace.containerTabId
);
} catch (e) {
console.warn('gZenWorkspaces: Error setting container color', e);
}
if (containerGroup) {
element.classList.add('identity-color-' + containerGroup.color);
element.setAttribute('data-usercontextid', containerGroup.userContextId);
}
// Set draggable attribute based on reorder mode
if (this.isReorderModeOn(browser)) {
element.setAttribute('draggable', 'true');
}
element.addEventListener(
'dragstart',
function (event) {
if (this.isReorderModeOn(browser)) {
this.draggedElement = element;
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', element.getAttribute('zen-workspace-id'));
// Create a transparent drag image for Linux
if (AppConstants.platform === 'linux') {
const dragImage = document.createElement('canvas');
dragImage.width = 1;
dragImage.height = 1;
event.dataTransfer.setDragImage(dragImage, 0, 0);
}
element.classList.add('dragging');
} else {
event.preventDefault();
}
}.bind(browser.gZenWorkspaces)
);
element.addEventListener(
'dragover',
function (event) {
if (this.isReorderModeOn(browser) && this.draggedElement) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
// Ensure the dragover effect is visible on Linux
if (AppConstants.platform === 'linux') {
const targetId = element.getAttribute('zen-workspace-id');
const draggedId = this.draggedElement.getAttribute('zen-workspace-id');
if (targetId !== draggedId) {
element.classList.add('dragover');
}
}
}
}.bind(browser.gZenWorkspaces)
);
element.addEventListener('dragenter', function (event) {
if (this.isReorderModeOn(browser) && this.draggedElement) {
element.classList.add('dragover');
}
});
element.addEventListener('dragleave', function (event) {
element.classList.remove('dragover');
});
element.addEventListener(
'drop',
async function (event) {
event.preventDefault();
element.classList.remove('dragover');
if (this.isReorderModeOn(browser)) {
const draggedWorkspaceId = event.dataTransfer.getData('text/plain');
const targetWorkspaceId = element.getAttribute('zen-workspace-id');
if (draggedWorkspaceId !== targetWorkspaceId) {
await this.moveWorkspace(draggedWorkspaceId, targetWorkspaceId);
}
if (this.draggedElement) {
this.draggedElement.classList.remove('dragging');
this.draggedElement = null;
}
}
}.bind(browser.gZenWorkspaces)
);
element.addEventListener(
'dragend',
function (event) {
if (this.draggedElement) {
this.draggedElement.classList.remove('dragging');
this.draggedElement = null;
}
const workspaceElements = browser.document.querySelectorAll('.zen-workspace-button');
for (const elem of workspaceElements) {
elem.classList.remove('dragover');
}
}.bind(browser.gZenWorkspaces)
);
let childs = browser.MozXULElement.parseXULToFragment(`
<div class="zen-workspace-icon">
</div>
<vbox>
<div class="zen-workspace-name">
</div>
<div class="zen-workspace-container" ${containerGroup ? '' : 'hidden="true"'}>
</div>
</vbox>
<image class="toolbarbutton-icon zen-workspace-actions-reorder-icon" ></image>
<toolbarbutton closemenu="none" class="toolbarbutton-1 zen-workspace-actions">
<image class="toolbarbutton-icon" id="zen-workspace-actions-menu-icon"></image>
</toolbarbutton>
`);
// use text content instead of innerHTML to avoid XSS
childs.querySelector('.zen-workspace-icon').textContent =
browser.gZenWorkspaces.getWorkspaceIcon(workspace);
childs.querySelector('.zen-workspace-name').textContent = workspace.name;
if (containerGroup) {
childs.querySelector('.zen-workspace-container').textContent =
ContextualIdentityService.getUserContextLabel(containerGroup.userContextId);
}
childs.querySelector('.zen-workspace-actions').addEventListener(
'command',
((event) => {
let button = event.target;
this._contextMenuId = button
.closest('toolbarbutton[zen-workspace-id]')
.getAttribute('zen-workspace-id');
const popup = button.ownerDocument.getElementById('zenWorkspaceActionsMenu');
popup.openPopup(button, 'after_end');
}).bind(browser.gZenWorkspaces)
);
element.appendChild(childs);
element.onclick = (async () => {
if (this.isReorderModeOn(browser)) {
return; // Return early if reorder mode is on
}
if (event.target.closest('.zen-workspace-actions')) {
return; // Ignore clicks on the actions button
}
const workspaceId = element.getAttribute('zen-workspace-id');
const workspaces = await this._workspaces();
const workspace = workspaces.workspaces.find((w) => w.uuid === workspaceId);
await this.changeWorkspace(workspace);
let panel = this.ownerWindow.document.getElementById('PanelUI-zen-workspaces');
PanelMultiView.hidePopup(panel);
}).bind(browser.gZenWorkspaces);
return element;
};
const createLastPositionDropTarget = () => {
const element = browser.document.createXULElement('div');
element.className = 'zen-workspace-last-place-drop-target';
element.addEventListener(
'dragover',
function (event) {
if (this.isReorderModeOn(browser) && this.draggedElement) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
// Ensure the dragover effect is visible on Linux
if (AppConstants.platform === 'linux') {
element.classList.add('dragover');
}
}
}.bind(browser.gZenWorkspaces)
);
element.addEventListener(
'dragenter',
function (event) {
if (this.isReorderModeOn(browser) && this.draggedElement) {
element.classList.add('dragover');
}
}.bind(browser.gZenWorkspaces)
);
element.addEventListener(
'dragleave',
function (event) {
element.classList.remove('dragover');
}.bind(browser.gZenWorkspaces)
);
element.addEventListener(
'drop',
async function (event) {
event.preventDefault();
element.classList.remove('dragover');
if (this.isReorderModeOn(browser)) {
const draggedWorkspaceId = event.dataTransfer.getData('text/plain');
await this.moveWorkspaceToEnd(draggedWorkspaceId);
if (this.draggedElement) {
this.draggedElement.classList.remove('dragging');
this.draggedElement = null;
}
}
}.bind(browser.gZenWorkspaces)
);
return element;
};
if (clearCache) {
browser.gZenWorkspaces._workspaceCache = null;
browser.gZenWorkspaces._workspaceBookmarksCache = null;
await browser.gZenWorkspaces.workspaceBookmarks();
}
let workspaces = await browser.gZenWorkspaces._workspaces();
browser.document
.getElementById('cmd_zenCtxDeleteWorkspace')
.setAttribute('disabled', workspaces.workspaces.length <= 1);
if (clearCache) {
browser.dispatchEvent(
new CustomEvent('ZenWorkspacesUIUpdate', {
@ -1656,39 +1261,12 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
})
);
}
await browser.gZenWorkspaces.workspaceBookmarks();
workspaceList.innerHTML = '';
workspaceList.parentNode.style.display = 'flex';
if (workspaces.workspaces.length <= 0) {
workspaceList.innerHTML = 'No workspaces available';
workspaceList.setAttribute('empty', 'true');
} else {
workspaceList.removeAttribute('empty');
}
for (let workspace of workspaces.workspaces) {
let workspaceElement = createWorkspaceElement(workspace);
workspaceList.appendChild(workspaceElement);
}
workspaceList.appendChild(createLastPositionDropTarget());
if (!ignoreStrip) {
browser.gZenWorkspaces._fixIndicatorsNames(workspaces);
}
});
}
handlePanelHidden() {
const workspacesList = document.getElementById('PanelUI-zen-workspaces-list');
const reorderModeButton = document.getElementById('PanelUI-zen-workspaces-reorder-mode');
workspacesList?.removeAttribute('reorder-mode');
reorderModeButton?.removeAttribute('active');
this.resetWorkspaceIconSearch();
this.clearEmojis();
}
async moveWorkspaceToEnd(draggedWorkspaceId) {
const workspaces = (await this._workspaces()).workspaces;
const draggedIndex = workspaces.findIndex((w) => w.uuid === draggedWorkspaceId);
@ -1699,39 +1277,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
await this._propagateWorkspaceData();
}
isReorderModeOn(browser) {
return (
browser.document
.getElementById('PanelUI-zen-workspaces-list')
.getAttribute('reorder-mode') === 'true'
);
}
toggleReorderMode() {
const workspacesList = document.getElementById('PanelUI-zen-workspaces-list');
const reorderModeButton = document.getElementById('PanelUI-zen-workspaces-reorder-mode');
const isActive = workspacesList.getAttribute('reorder-mode') === 'true';
if (isActive) {
workspacesList.removeAttribute('reorder-mode');
reorderModeButton.removeAttribute('active');
} else {
workspacesList.setAttribute('reorder-mode', 'true');
reorderModeButton.setAttribute('active', 'true');
}
// Update draggable attribute
const workspaceElements = document.querySelectorAll('.zen-workspace-button');
workspaceElements.forEach((elem) => {
// When reorder mode is toggled off, remove draggable attribute
// When reorder mode is toggled on, set draggable attribute
if (isActive) {
elem.removeAttribute('draggable');
} else {
elem.setAttribute('draggable', 'true');
}
});
}
async moveWorkspace(draggedWorkspaceId, targetWorkspaceId) {
const workspaces = (await this._workspaces()).workspaces;
const draggedIndex = workspaces.findIndex((w) => w.uuid === draggedWorkspaceId);
@ -1743,45 +1288,8 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
await this._propagateWorkspaceData();
}
async openWorkspacesDialog(event) {
if (!this.workspaceEnabled || this.isPrivateWindow) {
return;
}
let target = event.target.closest('.zen-current-workspace-indicator');
let panel = document.getElementById('PanelUI-zen-workspaces');
await this._propagateWorkspaceData({
ignoreStrip: true,
clearCache: false,
});
PanelMultiView.openPopup(panel, target, {
position: 'bottomright topright',
triggerEvent: event,
}).catch(console.error);
}
closeWorkspacesSubView() {
let parentPanel = document.getElementById('PanelUI-zen-workspaces-multiview');
parentPanel.goBack(parentPanel);
}
// Workspaces management
get _workspaceCreateInput() {
return document.getElementById('PanelUI-zen-workspaces-create-input');
}
get _workspaceEditDialog() {
return document.getElementById('PanelUI-zen-workspaces-edit');
}
get _workspaceEditInput() {
return document.getElementById('PanelUI-zen-workspaces-edit-input');
}
get _workspaceEditIconsContainer() {
return document.getElementById('PanelUI-zen-workspaces-icon-picker');
}
_deleteAllTabsInWorkspace(workspaceID) {
gBrowser.removeTabs(
Array.from(this.allStoredTabs).filter(
@ -1847,57 +1355,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
}
}
async saveWorkspaceFromCreate() {
let workspaceName = this._workspaceCreateInput.value;
if (!workspaceName) {
return;
}
this._workspaceCreateInput.value = '';
let icon = document.querySelector('#PanelUI-zen-workspaces-icon-picker-wrapper [selected]');
icon?.removeAttribute('selected');
await this.createAndSaveWorkspace(workspaceName, icon?.label);
this.goToPreviousSubView();
}
async saveWorkspaceFromEdit() {
let workspaceUuid = this._workspaceEditDialog.getAttribute('data-workspace-uuid');
let workspaceName = this._workspaceEditInput.value;
if (!workspaceName) {
return;
}
this._workspaceEditInput.value = '';
let icon = document.querySelector('#PanelUI-zen-workspaces-icon-picker-wrapper [selected]');
icon?.removeAttribute('selected');
let workspaces = (await this._workspaces()).workspaces;
let workspaceData = workspaces.find((workspace) => workspace.uuid === workspaceUuid);
workspaceData.name = workspaceName;
workspaceData.icon = icon?.label;
await this.saveWorkspace(workspaceData);
this.goToPreviousSubView();
}
onWorkspaceCreationNameChange() {
let button = document.getElementById('PanelUI-zen-workspaces-create-save');
if (this._workspaceCreateInput.value === '') {
button.setAttribute('disabled', 'true');
return;
}
button.removeAttribute('disabled');
}
onWorkspaceEditChange(icon) {
let button = document.getElementById('PanelUI-zen-workspaces-edit-save');
let name = this._workspaceEditInput.value;
if (
name === this._workspaceEditInput.getAttribute('data-initial-value') &&
icon === this._workspaceEditIconsContainer.getAttribute('data-initial-value')
) {
button.setAttribute('disabled', 'true');
return;
}
button.removeAttribute('disabled');
}
addChangeListeners(func) {
if (!this._changeListeners) {
this._changeListeners = [];
@ -2323,7 +1780,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
existingTransform = container.style.transform;
}
if (shouldAnimate) {
container.style.transform = existingTransform;
container.style.transform = newTransform;
animations.push(
gZenUIManager.motion.animate(
container,
@ -2540,6 +1997,8 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
})
);
}
setTimeout(gURLBar.formatValue.bind(gURLBar), 0);
}
async _fixCtrlTabBehavior() {
@ -2806,49 +2265,9 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
}
}
// Context menu management
_contextMenuId = null;
async updateContextMenu(_) {
console.assert(this._contextMenuId, 'No context menu ID set');
document
.querySelector(
`#PanelUI-zen-workspaces [zen-workspace-id="${this._contextMenuId}"] .zen-workspace-actions`
)
.setAttribute('active', 'true');
const workspaces = await this._workspaces();
let deleteMenuItem = document.getElementById('context_zenDeleteWorkspace');
if (workspaces.workspaces.length <= 1) {
deleteMenuItem.setAttribute('disabled', 'true');
} else {
deleteMenuItem.removeAttribute('disabled');
}
let openMenuItem = document.getElementById('context_zenOpenWorkspace');
if (
workspaces.workspaces.find(
(workspace) => workspace.uuid === this._contextMenuId && this.isWorkspaceActive(workspace)
)
) {
openMenuItem.setAttribute('disabled', 'true');
} else {
openMenuItem.removeAttribute('disabled');
}
const openInContainerMenuItem = document.getElementById(
'context_zenWorkspacesOpenInContainerTab'
);
if (this.shouldShowContainers) {
openInContainerMenuItem.removeAttribute('hidden');
} else {
openInContainerMenuItem.setAttribute('hidden', 'true');
}
}
async contextChangeContainerTab(event) {
this._organizingWorkspaceStrip = true;
let workspaces = await this._workspaces();
let workspace = workspaces.workspaces.find(
(workspace) => workspace.uuid === this._contextMenuId
);
let workspace = await this.getActiveWorkspace();
let userContextId = parseInt(event.target.getAttribute('data-usercontextid'));
workspace.containerTabId = userContextId + 0; // +0 to convert to number
await this.saveWorkspace(workspace);
@ -2861,16 +2280,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
}, 0);
}
onContextMenuClose() {
let target = document.querySelector(
`#PanelUI-zen-workspaces [zen-workspace-id="${this._contextMenuId}"] .zen-workspace-actions`
);
if (target) {
target.removeAttribute('active');
}
this._contextMenuId = null;
}
findTabToBlur(tab) {
if ((!this._shouldChangeToTab(tab) || !tab) && this._emptyTab) {
return this._emptyTab;
@ -2878,26 +2287,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
return tab;
}
async openWorkspace() {
let workspaces = await this._workspaces();
let workspace = workspaces.workspaces.find(
(workspace) => workspace.uuid === this._contextMenuId
);
await this.changeWorkspace(workspace);
}
async contextDelete(event) {
this.__contextIsDelete = true;
event.stopPropagation();
await this.removeWorkspace(this._contextMenuId);
this.__contextIsDelete = false;
}
async contextEdit(event) {
event.stopPropagation();
await this.openEditDialog(this._contextMenuId);
}
get emojis() {
if (this._emojis) {
return this._emojis;
@ -2976,18 +2365,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
);
}
// Tab browser utilities
createContainerTabMenu(event) {
let window = event.target.ownerGlobal;
const workspace = this.getWorkspaceFromId(this._contextMenuId);
let containerTabId = workspace.containerTabId;
return window.createUserContextMenu(event, {
isContextMenu: true,
excludeUserContextId: containerTabId,
showDefaultTab: true,
});
}
getContextIdIfNeeded(userContextId, fromExternal, allowInheritPrincipal) {
if (!this.workspaceEnabled) {
return [userContextId, false, undefined];

View file

@ -122,327 +122,9 @@
}
}
/* 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);
}
/* Enhanced visual feedback for Linux platform */
@supports (-moz-gtk-csd-available) {
.zen-workspace-button.dragover {
background-color: color-mix(in srgb, var(--toolbarbutton-icon-fill-attention) 15%, transparent);
}
.zen-workspace-button.dragover::after {
height: 3px;
}
}
.zen-workspace-last-place-drop-target.dragover {
background-color: var(--toolbarbutton-icon-fill-attention);
}
/* Enhanced visual feedback for Linux platform */
@supports (-moz-gtk-csd-available) {
.zen-workspace-last-place-drop-target {
height: 6px;
margin: 4px 0;
}
.zen-workspace-last-place-drop-target.dragover {
background-color: var(--toolbarbutton-icon-fill-attention);
box-shadow: 0 0 4px 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 {
padding: calc(15px + var(--zen-toolbox-padding))
calc(4px + var(--tab-inline-padding) + var(--zen-toolbox-padding));
padding: calc(4px + var(--tab-inline-padding) + var(--zen-toolbox-padding));
font-weight: 600;
position: relative;
max-height: var(--zen-workspace-indicator-height);
@ -452,6 +134,8 @@
flex-direction: row !important;
max-width: 100%;
width: 100%;
font-size: small;
padding-right: 10px;
&::before {
border-radius: var(--border-radius-medium);
@ -486,8 +170,23 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
pointer-events: none;
font-size: small;
cursor: auto;
}
.zen-workspaces-actions {
--toolbarbutton-inner-padding: 4px;
margin-left: auto !important;
opacity: 0;
visibility: collapse;
transition: opacity 0.1s;
order: 5;
}
:root:not([zen-private-window]) &:hover .zen-workspaces-actions,
& .zen-workspaces-actions[open='true'] {
visibility: visible;
pointer-events: auto;
opacity: 1;
}
}

View file

@ -5,7 +5,7 @@
"binaryName": "zen",
"version": {
"product": "firefox",
"version": "139.0",
"version": "138.0.4",
"candidate": "139.0"
},
"buildOptions": {
@ -19,7 +19,7 @@
"brandShortName": "Zen",
"brandFullName": "Zen Browser",
"release": {
"displayVersion": "1.12.9b",
"displayVersion": "1.12.8b",
"github": {
"repo": "zen-browser/desktop"
},