Compare commits

...
Sign in to create a new pull request.

27 commits

Author SHA1 Message Date
LeMoonStar
7c7a911d1e Undo essentials limit 2025-05-29 00:30:55 +02:00
mr. m
663243264b
feat: Prevent zen's session restore from being removed, b=(no-bug), c=common 2025-05-28 18:19:49 +02:00
mr. m
03ca00748c
fix: Fixed inserting changed titles into pinned database, b=(no-bug), c=tabs 2025-05-28 17:59:51 +02:00
mr. m
cbb1a4bc44
feat: Remove border from menu popups, b=(no-bug), c=common 2025-05-28 17:13:21 +02:00
mr. m
21f3ab23d3
feat: Inherti the split view type when opening a new link, b=(no-bug), c=split-view 2025-05-28 14:17:20 +02:00
mr. m
18944d5ed8
fix: Fixed pinned tab icons not appearing at startup, b=(no-bug), c=tabs 2025-05-28 10:17:33 +02:00
Mr. M
5c6e5f7361
fix: Fixed import mods button not working, b=(no-bug), c=mods, split-view 2025-05-27 21:03:40 +02:00
Mr. M
bd72aebd98
chore: Only export global variable for mods, not the class, b=(no-bug), c=mods 2025-05-27 20:17:06 +02:00
Mr. M
0278aea4f7
chore: Remove support links that target mozilla's support page on the about dialog, b=(bug #8634), c=no-component 2025-05-27 20:14:34 +02:00
Mr. M
3c01004641
fix: Fixed workspace transition animations and icons not appearing on new profiles, b=(no-bug), c=tabs, workspaces 2025-05-27 17:59:26 +02:00
Mr. M
b8213569e5
feat: Disable opening link on split view if the limit is reached, b=(no-bug), c=common, split-view 2025-05-27 17:14:10 +02:00
Mr. M
4d27f9d741
chore: Updated to firefox 139.0, b=(no-bug), c=no-component 2025-05-27 16:53:31 +02:00
Mr. M
3b56abf090
fix: Small fixes for split view and glance, b=(no-bug), c=common, compact-mode, split-view, tabs 2025-05-27 16:51:35 +02:00
Jai A P
590ba6de1b
Finally fix positioning of popups (#8532)
Co-authored-by: mr. m <91018726+mauro-balades@users.noreply.github.com>
2025-05-27 15:29:41 +02:00
Mr. M
4aa215e091
fix: Fixed changing tab having the wrong focus on the website, b=(closes #8587), c=workspaces 2025-05-27 13:38:16 +02:00
Mr. M
de175bff11
fix: Fixer resizing the sidebar break the urlbar formatting, b=(no-bug), c=common, workspaces 2025-05-27 13:38:10 +02:00
Mr. M
a6bc8d7105
chore: Cleanup calls to updateSplitViewButton, b=(no-bug), c=split-view 2025-05-27 13:02:28 +02:00
Mr. M
e48e7caef1
test: Fixed test test_Glance_Basic_Close, b=(no-bug), c=split-view 2025-05-27 12:51:17 +02:00
Mr. M
015cdad2df
feat: Made 'search test on <search engine>' open tabs in glance, b=(no-bug), c=common, glance, split-view, tests 2025-05-27 12:49:12 +02:00
Mr. M
ef6cf5fae1
chore: Remove icons from context menus, b=(no-bug), c=common 2025-05-27 12:11:58 +02:00
Bryan Galdámez
797152da89
refactor(mods): rework ZenMods module (#8618)
Co-authored-by: mr. m <mr.m@tuta.com>
Co-authored-by: mr. m <91018726+mauro-balades@users.noreply.github.com>
2025-05-27 12:02:59 +02:00
Mr. M
316ff45859
feat: Remove titlebar option on context menu, b=(no-bug), c=tabs 2025-05-27 10:43:55 +02:00
Mr. M
e0ac9ba424
chore: Added licenses to remanining files, b=(no-bug), c=common, compact-mode, folders, glance, media, mods, tabs, tests, workspaces 2025-05-25 17:15:19 +02:00
Mr. M
09ca430b88
feat: Improved tab transitions and fixed a couple of issues with glance, b=(no-bug), c=tabs, common, glance, split-view, workspaces 2025-05-25 17:06:22 +02:00
Mr. M
dda1dab6f3
fix: Fixed menu items being missaligned on windows, b=(closes #8590), c=no-component 2025-05-25 11:22:44 +02:00
mr. m
fbf411c096
Merge pull request #8578 from zen-browser/restore-fix 2025-05-24 17:21:40 +02:00
mr. m
4b0c6f2ca5
Merge pull request #8512 from zen-browser/firefox-139 2025-05-24 09:34:46 +02:00
76 changed files with 1821 additions and 1890 deletions

View file

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

View file

@ -1 +1 @@
82a08ea3ce2d17f21f3d45f4b5607a37590b0158 da30619f3ea895b356ded705b8dff9e4f271198f

2
l10n

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

8
package-lock.json generated
View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
diff --git a/browser/base/content/aboutDialog.js b/browser/base/content/aboutDialog.js diff --git a/browser/base/content/aboutDialog.js b/browser/base/content/aboutDialog.js
index f6e1391baf12abb91c85a95107bb3923118746c0..76c7b75a4e29056110f1631a50047c4ddd8b1f4a 100644 index f6e1391baf12abb91c85a95107bb3923118746c0..cac04aa288e8a305d0c8b28e0c919abce87658e5 100644
--- a/browser/base/content/aboutDialog.js --- a/browser/base/content/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js +++ b/browser/base/content/aboutDialog.js
@@ -52,7 +52,7 @@ function init() { @@ -52,7 +52,7 @@ function init() {
@ -20,3 +20,18 @@ index f6e1391baf12abb91c85a95107bb3923118746c0..76c7b75a4e29056110f1631a50047c4d
versionIdKey += "-nightly"; versionIdKey += "-nightly";
let buildID = Services.appinfo.appBuildID; let buildID = Services.appinfo.appBuildID;
let year = buildID.slice(0, 4); 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 diff --git a/browser/base/content/aboutDialog.xhtml b/browser/base/content/aboutDialog.xhtml
index c64980810570fcea84e33fdc2d66ac42a79f4e46..b7198e810a7510fa82cc6801cfd01c88a08d42c1 100644 index c64980810570fcea84e33fdc2d66ac42a79f4e46..6ef9bf4b88f0a0539d833f662c4dd890fd1fde93 100644
--- a/browser/base/content/aboutDialog.xhtml --- a/browser/base/content/aboutDialog.xhtml
+++ b/browser/base/content/aboutDialog.xhtml +++ b/browser/base/content/aboutDialog.xhtml
@@ -35,6 +35,7 @@ @@ -35,6 +35,7 @@
@ -10,7 +10,18 @@ index c64980810570fcea84e33fdc2d66ac42a79f4e46..b7198e810a7510fa82cc6801cfd01c88
</linkset> </linkset>
<html:div id="aboutDialogContainer"> <html:div id="aboutDialogContainer">
@@ -125,21 +126,23 @@ @@ -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 @@
</description> </description>
</vbox> </vbox>
<description class="text-blurb" id="communityDesc" data-l10n-id="community-2"> <description class="text-blurb" id="communityDesc" data-l10n-id="community-2">
@ -18,12 +29,10 @@ index c64980810570fcea84e33fdc2d66ac42a79f4e46..b7198e810a7510fa82cc6801cfd01c88
+ <label is="text-link" href="https://github.com/zen-browser/desktop" data-l10n-name="community-mozillaLink"/> + <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"/> <label is="text-link" useoriginprincipal="true" href="about:credits" data-l10n-name="community-creditsLink"/>
</description> </description>
+#if 0 - <description class="text-blurb" id="contributeDesc" data-l10n-id="helpus">
<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://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"/>
<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>
</description>
+#endif
</vbox> </vbox>
</vbox> </vbox>
</hbox> </hbox>

View file

@ -1,8 +1,35 @@
diff --git a/browser/base/content/browser-addons.js b/browser/base/content/browser-addons.js diff --git a/browser/base/content/browser-addons.js b/browser/base/content/browser-addons.js
index 992d07daaef1abc4554a43aa654888f66963c575..73e620b70b7ed14e9d140e875c2cd5f5ac31456b 100644 index 73593191936cc345ee8e2c28cb251dc13f4c2fd4..e6c459c1ebc60a1f3930a55e212570f696bf07a0 100644
--- a/browser/base/content/browser-addons.js --- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js +++ b/browser/base/content/browser-addons.js
@@ -2105,18 +2105,20 @@ var gUnifiedExtensions = { @@ -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 = {
this._maybeMoveWidgetNodeBack(widgetId); this._maybeMoveWidgetNodeBack(widgetId);
} }

View file

@ -29,10 +29,9 @@
</linkset> </linkset>
# Scripts used all over the browser # Scripts used all over the browser
<script src="chrome://browser/content/ZenUIManager.mjs"></script> <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/ZenFolders.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenThemesCommon.mjs"></script> <script src="chrome://browser/content/zen-components/ZenMods.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/ZenCompactMode.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenPinnedTabsStorage.mjs"></script> <script src="chrome://browser/content/zen-components/ZenPinnedTabsStorage.mjs"></script>
<script src="chrome://browser/content/zen-components/ZenWorkspacesStorage.mjs"></script> <script src="chrome://browser/content/zen-components/ZenWorkspacesStorage.mjs"></script>

View file

@ -9,6 +9,7 @@
content/browser/ZenCustomizableUI.sys.mjs (../../zen/common/ZenCustomizableUI.sys.mjs) content/browser/ZenCustomizableUI.sys.mjs (../../zen/common/ZenCustomizableUI.sys.mjs)
content/browser/zen-components/ZenUIMigration.mjs (../../zen/common/ZenUIMigration.mjs) content/browser/zen-components/ZenUIMigration.mjs (../../zen/common/ZenUIMigration.mjs)
content/browser/zen-components/ZenCommonUtils.mjs (../../zen/common/ZenCommonUtils.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-theme.css (../../zen/common/styles/zen-theme.css)
content/browser/zen-styles/zen-buttons.css (../../zen/common/styles/zen-buttons.css) content/browser/zen-styles/zen-buttons.css (../../zen/common/styles/zen-buttons.css)
@ -34,10 +35,7 @@
content/browser/zen-components/ZenViewSplitter.mjs (../../zen/split-view/ZenViewSplitter.mjs) 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-styles/zen-decks.css (../../zen/split-view/zen-decks.css)
content/browser/zen-components/ZenThemesCommon.mjs (../../zen/mods/ZenThemesCommon.mjs) content/browser/zen-components/ZenMods.mjs (../../zen/mods/ZenMods.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/ZenWorkspaceIcons.mjs (../../zen/workspaces/ZenWorkspaceIcons.mjs) content/browser/zen-components/ZenWorkspaceIcons.mjs (../../zen/workspaces/ZenWorkspaceIcons.mjs)
content/browser/zen-components/ZenWorkspace.mjs (../../zen/workspaces/ZenWorkspace.mjs) content/browser/zen-components/ZenWorkspace.mjs (../../zen/workspaces/ZenWorkspace.mjs)
@ -57,8 +55,6 @@
content/browser/zen-components/ZenGlanceManager.mjs (../../zen/glance/ZenGlanceManager.mjs) 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-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-components/ZenFolders.mjs (../../zen/folders/ZenFolders.mjs)
content/browser/zen-styles/zen-folders.css (../../zen/folders/zen-folders.css) content/browser/zen-styles/zen-folders.css (../../zen/folders/zen-folders.css)
@ -74,7 +70,7 @@
content/browser/zen-styles/zen-download-box-animation.css (../../zen/downloads/zen-download-box-animation.css) content/browser/zen-styles/zen-download-box-animation.css (../../zen/downloads/zen-download-box-animation.css)
# Images # Images
content/browser/zen-images/gradient.png (../../zen/images/gradient.png) content/browser/zen-images/gradient.png (../../zen/images/gradient.png)
content/browser/zen-images/brand-header.svg (../../zen/images/brand-header.svg) content/browser/zen-images/brand-header.svg (../../zen/images/brand-header.svg)
content/browser/zen-images/layouts/collapsed.png (../../zen/images/layouts/collapsed.png) content/browser/zen-images/layouts/collapsed.png (../../zen/images/layouts/collapsed.png)
@ -86,7 +82,7 @@
content/browser/zen-images/downloads/download.svg (../../zen/images/downloads/download.svg) content/browser/zen-images/downloads/download.svg (../../zen/images/downloads/download.svg)
content/browser/zen-images/downloads/archive.svg (../../zen/images/downloads/archive.svg) content/browser/zen-images/downloads/archive.svg (../../zen/images/downloads/archive.svg)
# Fonts # Fonts
content/browser/zen-fonts/JunicodeVF-Italic.woff2 (../../zen/fonts/JunicodeVF-Italic.woff2) content/browser/zen-fonts/JunicodeVF-Italic.woff2 (../../zen/fonts/JunicodeVF-Italic.woff2)
content/browser/zen-fonts/JunicodeVF-Roman.woff2 (../../zen/fonts/JunicodeVF-Roman.woff2) content/browser/zen-fonts/JunicodeVF-Roman.woff2 (../../zen/fonts/JunicodeVF-Roman.woff2)
@ -104,4 +100,4 @@
content/browser/zen-images/favicons/slack.ico (../../zen/images/favicons/slack.ico) content/browser/zen-images/favicons/slack.ico (../../zen/images/favicons/slack.ico)
content/browser/zen-images/favicons/reddit.ico (../../zen/images/favicons/reddit.ico) content/browser/zen-images/favicons/reddit.ico (../../zen/images/favicons/reddit.ico)
content/browser/zen-images/favicons/x.ico (../../zen/images/favicons/x.ico) content/browser/zen-images/favicons/x.ico (../../zen/images/favicons/x.ico)
content/browser/zen-images/favicons/trello.ico (../../zen/images/favicons/trello.ico) content/browser/zen-images/favicons/trello.ico (../../zen/images/favicons/trello.ico)

View file

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

View file

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

View file

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

View file

@ -31,15 +31,6 @@
<html:h1 data-l10n-id="pane-zen-tabs-unloader-title"/> <html:h1 data-l10n-id="pane-zen-tabs-unloader-title"/>
</hbox> </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" <hbox id="zenPinnedTabsManagerCategory"
class="subcategory" class="subcategory"
hidden="true" hidden="true"

View file

@ -0,0 +1,13 @@
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 diff --git a/browser/components/sessionstore/SessionStore.sys.mjs b/browser/components/sessionstore/SessionStore.sys.mjs
index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d81447514e2c 100644 index 8c6047e1ada5a22e57e1e665965237c9e22641d7..8d0585e738a5a758ebbddfa0787c71d634dadd4d 100644
--- a/browser/components/sessionstore/SessionStore.sys.mjs --- a/browser/components/sessionstore/SessionStore.sys.mjs
+++ b/browser/components/sessionstore/SessionStore.sys.mjs +++ b/browser/components/sessionstore/SessionStore.sys.mjs
@@ -2088,7 +2088,6 @@ var SessionStoreInternal = { @@ -2088,7 +2088,6 @@ var SessionStoreInternal = {
@ -31,17 +31,19 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
return; return;
} }
@@ -3925,6 +3922,9 @@ var SessionStoreInternal = { @@ -3925,6 +3922,11 @@ var SessionStoreInternal = {
Math.min(tabState.index, tabState.entries.length) Math.min(tabState.index, tabState.entries.length)
); );
tabState.pinned = false; tabState.pinned = false;
+ tabState.zenEssential = false; + tabState.zenEssential = false;
+ tabState.zenPinnedId = null; + tabState.zenPinnedId = null;
+ tabState.zenIsGlance = false;
+ tabState.zenGlanceId = null;
+ tabState.zenHasStaticLabel = false; + tabState.zenHasStaticLabel = false;
if (inBackground === false) { if (inBackground === false) {
aWindow.gBrowser.selectedTab = newTab; aWindow.gBrowser.selectedTab = newTab;
@@ -5239,7 +5239,7 @@ var SessionStoreInternal = { @@ -5239,7 +5241,7 @@ var SessionStoreInternal = {
} }
let workspaceID = aWindow.getWorkspaceID(); let workspaceID = aWindow.getWorkspaceID();
@ -50,7 +52,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
winData.workspaceID = workspaceID; winData.workspaceID = workspaceID;
} }
}, },
@@ -5430,14 +5430,15 @@ var SessionStoreInternal = { @@ -5430,14 +5432,15 @@ var SessionStoreInternal = {
} }
let tabbrowser = aWindow.gBrowser; let tabbrowser = aWindow.gBrowser;
@ -68,7 +70,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
continue; continue;
} }
let tabData = lazy.TabState.collect(tab, TAB_CUSTOM_VALUES.get(tab)); let tabData = lazy.TabState.collect(tab, TAB_CUSTOM_VALUES.get(tab));
@@ -5456,8 +5457,8 @@ var SessionStoreInternal = { @@ -5456,8 +5459,8 @@ var SessionStoreInternal = {
// We don't store the Firefox View tab in Session Store, so if it was the last selected "tab" when // 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, // 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). // since it's only inserted into the tab strip after it's selected).
@ -79,7 +81,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
winData.title = tabbrowser.tabs[0].label; winData.title = tabbrowser.tabs[0].label;
} }
winData.selected = selectedIndex; winData.selected = selectedIndex;
@@ -5569,8 +5570,8 @@ var SessionStoreInternal = { @@ -5569,8 +5572,8 @@ var SessionStoreInternal = {
// selectTab represents. // selectTab represents.
let selectTab = 0; let selectTab = 0;
if (overwriteTabs) { if (overwriteTabs) {
@ -90,7 +92,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
selectTab = Math.min(selectTab, winData.tabs.length); selectTab = Math.min(selectTab, winData.tabs.length);
} }
@@ -5613,6 +5614,7 @@ var SessionStoreInternal = { @@ -5613,6 +5616,7 @@ var SessionStoreInternal = {
winData.tabs, winData.tabs,
winData.groups ?? [] winData.groups ?? []
); );
@ -98,12 +100,13 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
this._log.debug( this._log.debug(
`restoreWindow, createTabsForSessionRestore returned ${tabs.length} tabs` `restoreWindow, createTabsForSessionRestore returned ${tabs.length} tabs`
); );
@@ -6162,8 +6164,23 @@ var SessionStoreInternal = { @@ -6162,6 +6166,22 @@ var SessionStoreInternal = {
// Most of tabData has been restored, now continue with restoring // Most of tabData has been restored, now continue with restoring
// attributes that may trigger external events. // attributes that may trigger external events.
+ if (tabData.zenEssential) { + if (tabData.zenEssential) {
+ tab.setAttribute("zen-essential", "true"); + tab.setAttribute("zen-essential", "true");
+ tabData.pinned = true; // Essential tabs are always pinned.
+ } + }
+ if (tabData.zenIsEmpty) { + if (tabData.zenIsEmpty) {
+ tab.setAttribute("zen-empty-tab", "true"); + tab.setAttribute("zen-empty-tab", "true");
@ -118,8 +121,5 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
+ tab.setAttribute("zenDefaultUserContextId", true); + tab.setAttribute("zenDefaultUserContextId", true);
+ } + }
- if (tabData.pinned) { if (tabData.pinned) {
+ if (tabData.pinned || tabData.zenEssential) {
tabbrowser.pinTab(tab); 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 diff --git a/browser/components/sessionstore/TabState.sys.mjs b/browser/components/sessionstore/TabState.sys.mjs
index 8f7ed557e6aa61e7e16ed4a8d785ad5fe651b3d8..254849e13f7566029dc780c45e376e0f0d427cb5 100644 index 8f7ed557e6aa61e7e16ed4a8d785ad5fe651b3d8..76f4cf5aef30cb580ef0295fe6928b5a6a362f4b 100644
--- a/browser/components/sessionstore/TabState.sys.mjs --- a/browser/components/sessionstore/TabState.sys.mjs
+++ b/browser/components/sessionstore/TabState.sys.mjs +++ b/browser/components/sessionstore/TabState.sys.mjs
@@ -84,6 +84,16 @@ class _TabState { @@ -84,6 +84,18 @@ class _TabState {
tabData.groupId = tab.group.id; tabData.groupId = tab.group.id;
} }
@ -15,6 +15,8 @@ index 8f7ed557e6aa61e7e16ed4a8d785ad5fe651b3d8..254849e13f7566029dc780c45e376e0f
+ tabData.zenPinnedIcon = tab.getAttribute("zen-pinned-icon"); + tabData.zenPinnedIcon = tab.getAttribute("zen-pinned-icon");
+ tabData.zenIsEmpty = tab.hasAttribute("zen-empty-tab"); + tabData.zenIsEmpty = tab.hasAttribute("zen-empty-tab");
+ tabData.zenHasStaticLabel = tab.hasAttribute("zen-has-static-label"); + 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); 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 diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b032cf200f1 100644 index d5aa64842a35c6697263c63fd3a0571b64b01344..14f5bc046f2e54109bd3fd0402a8f8b598a513c2 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js --- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js +++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -413,11 +413,41 @@ @@ -413,11 +413,41 @@
@ -292,37 +292,17 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
let url = "about:blank"; let url = "about:blank";
if (tabData.entries?.length) { if (tabData.entries?.length) {
@@ -3598,7 +3675,29 @@ @@ -3598,7 +3675,8 @@
skipLoad: true, skipLoad: true,
preferredRemoteType, preferredRemoteType,
}); });
-
+ tab._originalUrl = url; + 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) { if (select) {
tabToSelect = tab; tabToSelect = tab;
} }
@@ -3622,7 +3721,8 @@ @@ -3622,7 +3700,8 @@
// needs calling: // needs calling:
shouldUpdateForPinnedTabs = true; shouldUpdateForPinnedTabs = true;
} }
@ -332,7 +312,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
let { groupId } = tabData; let { groupId } = tabData;
const tabGroup = tabGroupWorkingData.get(groupId); const tabGroup = tabGroupWorkingData.get(groupId);
// if a tab refers to a tab group we don't know, skip any group // if a tab refers to a tab group we don't know, skip any group
@@ -3636,7 +3736,10 @@ @@ -3636,7 +3715,10 @@
tabGroup.stateData.id, tabGroup.stateData.id,
tabGroup.stateData.color, tabGroup.stateData.color,
tabGroup.stateData.collapsed, tabGroup.stateData.collapsed,
@ -344,7 +324,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
); );
tabsFragment.appendChild(tabGroup.node); tabsFragment.appendChild(tabGroup.node);
} }
@@ -3684,8 +3787,16 @@ @@ -3684,8 +3766,16 @@
// to remove the old selected tab. // to remove the old selected tab.
if (tabToSelect) { if (tabToSelect) {
let leftoverTab = this.selectedTab; let leftoverTab = this.selectedTab;
@ -363,7 +343,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
} }
if (tabs.length > 1 || !tabs[0].selected) { if (tabs.length > 1 || !tabs[0].selected) {
@@ -3881,7 +3992,7 @@ @@ -3881,7 +3971,7 @@
// Ensure we have an index if one was not provided. // Ensure we have an index if one was not provided.
if (typeof elementIndex != "number" && typeof tabIndex != "number") { if (typeof elementIndex != "number" && typeof tabIndex != "number") {
// Move the new tab after another tab if needed, to the end otherwise. // Move the new tab after another tab if needed, to the end otherwise.
@ -372,7 +352,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
if ( if (
!bulkOrderedOpen && !bulkOrderedOpen &&
((openerTab && ((openerTab &&
@@ -3904,7 +4015,7 @@ @@ -3904,7 +3994,7 @@
) { ) {
elementIndex = Infinity; elementIndex = Infinity;
} else if (previousTab.visible) { } else if (previousTab.visible) {
@ -381,7 +361,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
} else if (previousTab == FirefoxViewHandler.tab) { } else if (previousTab == FirefoxViewHandler.tab) {
elementIndex = 0; elementIndex = 0;
} }
@@ -3932,10 +4043,10 @@ @@ -3932,14 +4022,14 @@
} }
// Ensure index is within bounds. // Ensure index is within bounds.
if (tab.pinned) { if (tab.pinned) {
@ -395,7 +375,12 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
index = Math.min(index, allItems.length); index = Math.min(index, allItems.length);
} }
/** @type {MozTabbrowserTab|undefined} */ /** @type {MozTabbrowserTab|undefined} */
@@ -3947,7 +4058,7 @@ - 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 @@
this.tabContainer._invalidateCachedTabs(); this.tabContainer._invalidateCachedTabs();
@ -404,7 +389,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
if (this.isTab(itemAfter) && itemAfter.group == tabGroup) { if (this.isTab(itemAfter) && itemAfter.group == tabGroup) {
// Place at the front of, or between tabs in, the same tab group // Place at the front of, or between tabs in, the same tab group
this.tabContainer.insertBefore(tab, itemAfter); this.tabContainer.insertBefore(tab, itemAfter);
@@ -4268,6 +4379,9 @@ @@ -4268,6 +4358,9 @@
return; return;
} }
@ -414,7 +399,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
this.removeTabs(selectedTabs, { telemetrySource }); this.removeTabs(selectedTabs, { telemetrySource });
} }
@@ -4520,6 +4634,7 @@ @@ -4520,6 +4613,7 @@
telemetrySource, telemetrySource,
} = {} } = {}
) { ) {
@ -422,7 +407,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs // When 'closeWindowWithLastTab' pref is enabled, closing all tabs
// can be considered equivalent to closing the window. // can be considered equivalent to closing the window.
if ( if (
@@ -4604,6 +4719,7 @@ @@ -4604,6 +4698,7 @@
if (lastToClose) { if (lastToClose) {
this.removeTab(lastToClose, aParams); this.removeTab(lastToClose, aParams);
} }
@ -430,7 +415,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@@ -4641,6 +4757,12 @@ @@ -4641,6 +4736,12 @@
aTab._closeTimeNoAnimTimerId = Glean.browserTabclose.timeNoAnim.start(); aTab._closeTimeNoAnimTimerId = Glean.browserTabclose.timeNoAnim.start();
} }
@ -443,7 +428,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
// Handle requests for synchronously removing an already // Handle requests for synchronously removing an already
// asynchronously closing tab. // asynchronously closing tab.
if (!animate && aTab.closing) { if (!animate && aTab.closing) {
@@ -4655,7 +4777,9 @@ @@ -4655,7 +4756,9 @@
// frame created for it (for example, by updating the visually selected // frame created for it (for example, by updating the visually selected
// state). // state).
let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width; let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width;
@ -454,7 +439,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
if ( if (
!this._beginRemoveTab(aTab, { !this._beginRemoveTab(aTab, {
closeWindowFastpath: true, closeWindowFastpath: true,
@@ -4821,7 +4945,7 @@ @@ -4821,7 +4924,7 @@
closeWindowWithLastTab != null closeWindowWithLastTab != null
? closeWindowWithLastTab ? closeWindowWithLastTab
: !window.toolbar.visible || : !window.toolbar.visible ||
@ -463,7 +448,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
if (closeWindow) { if (closeWindow) {
// We've already called beforeunload on all the relevant tabs if we get here, // We've already called beforeunload on all the relevant tabs if we get here,
@@ -4845,6 +4969,7 @@ @@ -4845,6 +4948,7 @@
newTab = true; newTab = true;
} }
@ -471,7 +456,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
aTab._endRemoveArgs = [closeWindow, newTab]; aTab._endRemoveArgs = [closeWindow, newTab];
// swapBrowsersAndCloseOther will take care of closing the window without animation. // swapBrowsersAndCloseOther will take care of closing the window without animation.
@@ -4885,9 +5010,7 @@ @@ -4885,9 +4989,7 @@
aTab._mouseleave(); aTab._mouseleave();
if (newTab) { if (newTab) {
@ -482,7 +467,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
} else { } else {
TabBarVisibility.update(); TabBarVisibility.update();
} }
@@ -5016,6 +5139,8 @@ @@ -5016,6 +5118,8 @@
this.tabs[i]._tPos = i; this.tabs[i]._tPos = i;
} }
@ -491,7 +476,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
if (!this._windowIsClosing) { if (!this._windowIsClosing) {
if (wasPinned) { if (wasPinned) {
this.tabContainer._positionPinnedTabs(); this.tabContainer._positionPinnedTabs();
@@ -5230,6 +5355,7 @@ @@ -5230,6 +5334,7 @@
} }
let excludeTabs = new Set(aExcludeTabs); let excludeTabs = new Set(aExcludeTabs);
@ -499,7 +484,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
// If this tab has a successor, it should be selectable, since // If this tab has a successor, it should be selectable, since
// hiding or closing a tab removes that tab as a successor. // hiding or closing a tab removes that tab as a successor.
@@ -5242,13 +5368,13 @@ @@ -5242,13 +5347,13 @@
!excludeTabs.has(aTab.owner) && !excludeTabs.has(aTab.owner) &&
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose") Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
) { ) {
@ -515,7 +500,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
); );
let tab = this.tabContainer.findNextTab(aTab, { let tab = this.tabContainer.findNextTab(aTab, {
@@ -5264,7 +5390,7 @@ @@ -5264,7 +5369,7 @@
} }
if (tab) { if (tab) {
@ -524,7 +509,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
} }
// If no qualifying visible tab was found, see if there is a tab in // If no qualifying visible tab was found, see if there is a tab in
@@ -5285,7 +5411,7 @@ @@ -5285,7 +5390,7 @@
}); });
} }
@ -533,7 +518,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
} }
_blurTab(aTab) { _blurTab(aTab) {
@@ -5686,10 +5812,10 @@ @@ -5686,10 +5791,10 @@
SessionStore.deleteCustomTabValue(aTab, "hiddenBy"); SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
} }
@ -546,7 +531,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
aTab.selected || aTab.selected ||
aTab.closing || aTab.closing ||
// Tabs that are sharing the screen, microphone or camera cannot be hidden. // Tabs that are sharing the screen, microphone or camera cannot be hidden.
@@ -5986,7 +6112,7 @@ @@ -5986,7 +6091,7 @@
// Don't allow mixing pinned and unpinned tabs. // Don't allow mixing pinned and unpinned tabs.
if (this.isTab(element) && element.pinned) { if (this.isTab(element) && element.pinned) {
@ -555,7 +540,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
} else { } else {
tabIndex = Math.max(tabIndex, this.pinnedTabCount); tabIndex = Math.max(tabIndex, this.pinnedTabCount);
} }
@@ -6012,10 +6138,16 @@ @@ -6012,10 +6117,16 @@
this.#handleTabMove( this.#handleTabMove(
element, element,
() => { () => {
@ -574,7 +559,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
if (neighbor && this.isTab(element) && tabIndex > element._tPos) { if (neighbor && this.isTab(element) && tabIndex > element._tPos) {
neighbor.after(element); neighbor.after(element);
} else { } else {
@@ -6084,17 +6216,26 @@ @@ -6084,17 +6195,29 @@
targetElement = targetElement.group; targetElement = targetElement.group;
} }
} }
@ -583,8 +568,12 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
+ element = element.group; + element = element.group;
+ } + }
// Don't allow mixing pinned and unpinned tabs. // Don't allow mixing pinned and unpinned tabs.
if (element.pinned && !targetElement?.pinned) { - if (element.pinned && !targetElement?.pinned) {
- targetElement = this.tabs[this.pinnedTabCount - 1]; - 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]; + targetElement = this.tabs.filter(tab => !tab.hasAttribute('zen-glance-tab'))[this.pinnedTabCount - 1];
moveBefore = false; moveBefore = false;
} else if (!element.pinned && targetElement && targetElement.pinned) { } else if (!element.pinned && targetElement && targetElement.pinned) {
@ -604,7 +593,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
if (element.pinned && this.tabContainer.verticalMode) { if (element.pinned && this.tabContainer.verticalMode) {
return this.tabContainer.verticalPinnedTabsContainer; return this.tabContainer.verticalPinnedTabsContainer;
} }
@@ -6154,7 +6295,7 @@ @@ -6154,7 +6277,7 @@
if (!this.isTab(aTab)) { if (!this.isTab(aTab)) {
throw new Error("Can only move a tab into a tab group"); throw new Error("Can only move a tab into a tab group");
} }
@ -613,7 +602,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
return; return;
} }
if (aTab.group && aTab.group.id === aGroup.id) { if (aTab.group && aTab.group.id === aGroup.id) {
@@ -6248,6 +6389,10 @@ @@ -6248,6 +6371,10 @@
moveActionCallback(); moveActionCallback();
@ -624,7 +613,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
// Clear tabs cache after moving nodes because the order of tabs may have // Clear tabs cache after moving nodes because the order of tabs may have
// changed. // changed.
this.tabContainer._invalidateCachedTabs(); this.tabContainer._invalidateCachedTabs();
@@ -7145,7 +7290,7 @@ @@ -7145,7 +7272,7 @@
// preventDefault(). It will still raise the window if appropriate. // preventDefault(). It will still raise the window if appropriate.
break; break;
} }
@ -633,7 +622,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
window.focus(); window.focus();
aEvent.preventDefault(); aEvent.preventDefault();
break; break;
@@ -8044,6 +8189,7 @@ @@ -8044,6 +8171,7 @@
aWebProgress.isTopLevel aWebProgress.isTopLevel
) { ) {
this.mTab.setAttribute("busy", "true"); this.mTab.setAttribute("busy", "true");
@ -641,7 +630,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
gBrowser._tabAttrModified(this.mTab, ["busy"]); gBrowser._tabAttrModified(this.mTab, ["busy"]);
this.mTab._notselectedsinceload = !this.mTab.selected; this.mTab._notselectedsinceload = !this.mTab.selected;
} }
@@ -9009,7 +9155,7 @@ var TabContextMenu = { @@ -9009,7 +9137,7 @@ var TabContextMenu = {
); );
contextUnpinSelectedTabs.hidden = contextUnpinSelectedTabs.hidden =
!this.contextTab.pinned || !this.multiselected; !this.contextTab.pinned || !this.multiselected;
@ -650,7 +639,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
// Move Tab items // Move Tab items
let contextMoveTabOptions = document.getElementById( let contextMoveTabOptions = document.getElementById(
"context_moveTabOptions" "context_moveTabOptions"
@@ -9278,6 +9424,7 @@ var TabContextMenu = { @@ -9278,6 +9406,7 @@ var TabContextMenu = {
telemetrySource: gBrowser.TabMetrics.METRIC_SOURCE.TAB_STRIP, telemetrySource: gBrowser.TabMetrics.METRIC_SOURCE.TAB_STRIP,
}); });
} else { } else {

View file

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

View file

@ -0,0 +1,12 @@
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,18 +60,6 @@
list-style-image: url('sidebars-right.svg') !important; list-style-image: url('sidebars-right.svg') !important;
} }
#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(ltr):not([positionend]),
#sidebar-button:-moz-locale-dir(rtl)[positionend] { #sidebar-button:-moz-locale-dir(rtl)[positionend] {
list-style-image: url('chrome://browser/skin/sidebars.svg') !important; list-style-image: url('chrome://browser/skin/sidebars.svg') !important;
@ -133,6 +121,10 @@
list-style-image: url('tab.svg') !important; list-style-image: url('tab.svg') !important;
} }
#context-navigation > menuitem {
padding: 4px;
}
#history-panelmenu, #history-panelmenu,
.urlbarView-row[source='history'] .urlbarView-row[source='history']
> .urlbarView-row-inner > .urlbarView-row-inner
@ -173,15 +165,6 @@
list-style-image: url('open.svg') !important; list-style-image: url('open.svg') !important;
} }
#context_zenOpenWorkspace {
--menu-image: url('open.svg') !important;
}
#context_zenEditWorkspace,
#zenToolbarThemePicker {
--menu-image: url('edit-theme.svg') !important;
}
#add-ons-button, #add-ons-button,
#appMenu-extensions-themes-button, #appMenu-extensions-themes-button,
#unified-extensions-button { #unified-extensions-button {
@ -303,6 +286,11 @@
list-style-image: url('home.svg') !important; list-style-image: url('home.svg') !important;
} }
#toggle_toolbar-menubar,
#appMenu_menu_openHelp {
display: none;
}
#library-button { #library-button {
list-style-image: url('library.svg') !important; list-style-image: url('library.svg') !important;
} }
@ -447,6 +435,11 @@
#zen-glance-sidebar-split { #zen-glance-sidebar-split {
list-style-image: url('split.svg'); list-style-image: url('split.svg');
&[disabled='true'] {
opacity: 0.5;
cursor: not-allowed;
}
} }
#sidebar-box[sidebarcommand='viewTabsSidebar'] #sidebar-box[sidebarcommand='viewTabsSidebar']
@ -635,371 +628,6 @@
list-style-image: url('manage.svg') !important; 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'] {
--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 { #appMenuRecentlyClosedTabs {
list-style-image: url('container-tab.svg') !important; list-style-image: url('container-tab.svg') !important;
} }
@ -1020,57 +648,12 @@ menuitem[id='placesContext_showAllBookmarks'],
list-style-image: url('manage.svg') !important; 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...) */ /* FIX header icons for the app menu sub menus (eg. fx account, history...) */
.panel-header > h1 { .panel-header > h1 {
text-align: left; text-align: left;
margin-left: 8px !important; margin-left: 8px !important;
} }
.wordmark::after {
content: 'Plus' !important;
}
/* header icons for the app menu sub menus (eg. fx account, history...) */ /* header icons for the app menu sub menus (eg. fx account, history...) */
.panel-header > h1 > span::before { .panel-header > h1 > span::before {
content: ''; content: '';
@ -1115,67 +698,10 @@ menuitem[id='placesContext_new:separator'] {
--fp-enabled: 1; --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 { #alltabs-button {
list-style-image: url('chrome://browser/skin/tabs.svg') !important; 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-toggle-vertical-tabs,
#toolbar-context-customize-sidebar, #toolbar-context-customize-sidebar,
#sidebarRevampSeparator { #sidebarRevampSeparator {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,6 @@
import { AppConstants } from 'resource://gre/modules/AppConstants.sys.mjs'; // 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 var ZenCustomizableUI = new (class { export var ZenCustomizableUI = new (class {
constructor() {} constructor() {}

File diff suppressed because one or more lines are too long

View file

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

View file

@ -1,9 +1,14 @@
// 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 = { var gZenUIManager = {
_popupTrackingElements: [], _popupTrackingElements: [],
_hoverPausedForExpand: false, _hoverPausedForExpand: false,
_hasLoadedDOM: false, _hasLoadedDOM: false,
testingEnabled: Services.prefs.getBoolPref('zen.testing.enabled', false), testingEnabled: Services.prefs.getBoolPref('zen.testing.enabled', false),
_lastClickPosition: null,
_toastTimeouts: [], _toastTimeouts: [],
init() { init() {
@ -31,6 +36,8 @@ var gZenUIManager = {
gURLBar._zenTrimURL = this.urlbarTrim.bind(this); gURLBar._zenTrimURL = this.urlbarTrim.bind(this);
document.addEventListener('mousedown', this.handleMouseDown.bind(this), true);
ChromeUtils.defineLazyGetter(this, 'motion', () => { ChromeUtils.defineLazyGetter(this, 'motion', () => {
return ChromeUtils.importESModule('chrome://browser/content/zen-vendor/motion.min.mjs', { return ChromeUtils.importESModule('chrome://browser/content/zen-vendor/motion.min.mjs', {
global: 'current', global: 'current',
@ -62,6 +69,13 @@ var gZenUIManager = {
gZenMediaController.init(); gZenMediaController.init();
}, },
handleMouseDown(event) {
this._lastClickPosition = {
clientX: event.clientX,
clientY: event.clientY,
};
},
updateTabsToolbar() { updateTabsToolbar() {
const kUrlbarHeight = 440; const kUrlbarHeight = 440;
gURLBar.textbox.style.setProperty( gURLBar.textbox.style.setProperty(
@ -70,7 +84,6 @@ var gZenUIManager = {
); );
gZenVerticalTabsManager.actualWindowButtons.removeAttribute('zen-has-hover'); gZenVerticalTabsManager.actualWindowButtons.removeAttribute('zen-has-hover');
gZenVerticalTabsManager.recalculateURLBarHeight(); gZenVerticalTabsManager.recalculateURLBarHeight();
setTimeout(gURLBar.formatValue.bind(gURLBar), 350);
if (!this._preventToolbarRebuild) { if (!this._preventToolbarRebuild) {
setTimeout(() => { setTimeout(() => {
gZenWorkspaces.updateTabsContainers(); gZenWorkspaces.updateTabsContainers();
@ -989,43 +1002,54 @@ var gZenVerticalTabsManager = {
async renameTabKeydown(event) { async renameTabKeydown(event) {
event.stopPropagation(); event.stopPropagation();
if (event.key === 'Enter') { if (event.key === 'Enter') {
let label = this._tabEdited.querySelector('.tab-label-container-editing'); const isTab = !!event.target.closest('.tabbrowser-tab');
let input = this._tabEdited.querySelector('#tab-label-input'); let label = isTab
? this._tabEdited.querySelector('.tab-label-container-editing')
: this._tabEdited;
let input = document.getElementById('tab-label-input');
let newName = input.value.trim(); let newName = input.value.trim();
// Check if name is blank, reset if so document.documentElement.removeAttribute('zen-renaming-tab');
// Always remove, so we can always rename and if it's empty, input.remove();
// it will reset to the original name anyway if (!isTab) {
this._tabEdited.removeAttribute('zen-has-static-label'); await this._tabEdited.onRenameFinished(newName);
if (newName) {
gBrowser._setTabLabel(this._tabEdited, newName);
this._tabEdited.setAttribute('zen-has-static-label', 'true');
gZenUIManager.showToast('zen-tabs-renamed');
} else { } else {
gBrowser.setTabTitle(this._tabEdited); // Check if name is blank, reset if so
} // Always remove, so we can always rename and if it's empty,
if (this._tabEdited.getAttribute('zen-pin-id')) { // it will reset to the original name anyway
// Update pin title in storage this._tabEdited.removeAttribute('zen-has-static-label');
await gZenPinnedTabManager.updatePinTitle( if (newName) {
gBrowser._setTabLabel(this._tabEdited, newName);
this._tabEdited.setAttribute('zen-has-static-label', 'true');
gZenUIManager.showToast('zen-tabs-renamed');
} else {
gBrowser.setTabTitle(this._tabEdited);
}
if (this._tabEdited.getAttribute('zen-pin-id')) {
// Update pin title in storage
await gZenPinnedTabManager.updatePinTitle(
this._tabEdited,
this._tabEdited.label,
!!newName
);
}
// Maybe add some confetti here?!?
gZenUIManager.motion.animate(
this._tabEdited, this._tabEdited,
this._tabEdited.label, {
!!newName scale: [1, 0.98, 1],
},
{
duration: 0.25,
}
); );
} }
document.documentElement.removeAttribute('zen-renaming-tab');
// Maybe add some confetti here?!? const editorContainer = this._tabEdited.querySelector('.tab-editor-container');
gZenUIManager.motion.animate( if (editorContainer) {
this._tabEdited, editorContainer.remove();
{ }
scale: [1, 0.98, 1],
},
{
duration: 0.25,
}
);
this._tabEdited.querySelector('.tab-editor-container').remove();
label.classList.remove('tab-label-container-editing'); label.classList.remove('tab-label-container-editing');
this._tabEdited = null; this._tabEdited = null;
@ -1035,34 +1059,40 @@ var gZenVerticalTabsManager = {
}, },
renameTabStart(event) { renameTabStart(event) {
const isTab = !!event.target.closest('.tabbrowser-tab');
if ( if (
this._tabEdited || this._tabEdited ||
!Services.prefs.getBoolPref('zen.tabs.rename-tabs') || ((!Services.prefs.getBoolPref('zen.tabs.rename-tabs') ||
Services.prefs.getBoolPref('browser.tabs.closeTabByDblclick') || Services.prefs.getBoolPref('browser.tabs.closeTabByDblclick')) &&
isTab) ||
!gZenVerticalTabsManager._prefsSidebarExpanded !gZenVerticalTabsManager._prefsSidebarExpanded
) )
return; return;
this._tabEdited = event.target.closest('.tabbrowser-tab'); this._tabEdited = event.target.closest('.tabbrowser-tab');
if ( if (
!this._tabEdited || !this._tabEdited ||
!this._tabEdited.pinned || ((!this._tabEdited.pinned || this._tabEdited.hasAttribute('zen-essential')) && isTab)
this._tabEdited.hasAttribute('zen-essential')
) { ) {
this._tabEdited = null; this._tabEdited = null;
return; return;
} }
event.stopPropagation();
document.documentElement.setAttribute('zen-renaming-tab', 'true'); document.documentElement.setAttribute('zen-renaming-tab', 'true');
const label = this._tabEdited.querySelector('.tab-label-container'); const label = isTab ? this._tabEdited.querySelector('.tab-label-container') : this._tabEdited;
label.classList.add('tab-label-container-editing'); label.classList.add('tab-label-container-editing');
const container = window.MozXULElement.parseXULToFragment(` if (isTab) {
<vbox class="tab-label-container tab-editor-container" flex="1" align="start" pack="center"></vbox> const container = window.MozXULElement.parseXULToFragment(`
`); <vbox class="tab-label-container tab-editor-container" flex="1" align="start" pack="center"></vbox>
label.after(container); `);
const containerHtml = this._tabEdited.querySelector('.tab-editor-container'); label.after(container);
}
const containerHtml = isTab
? this._tabEdited.querySelector('.tab-editor-container')
: this._tabEdited.parentNode;
const input = document.createElement('input'); const input = document.createElement('input');
input.id = 'tab-label-input'; input.id = 'tab-label-input';
input.value = this._tabEdited.label; input.value = isTab ? this._tabEdited.label : this._tabEdited.textContent;
input.addEventListener('keydown', this.renameTabKeydown.bind(this)); input.addEventListener('keydown', this.renameTabKeydown.bind(this));
containerHtml.appendChild(input); containerHtml.appendChild(input);
@ -1077,8 +1107,16 @@ var gZenVerticalTabsManager = {
return; return;
} }
document.documentElement.removeAttribute('zen-renaming-tab'); document.documentElement.removeAttribute('zen-renaming-tab');
this._tabEdited.querySelector('.tab-editor-container').remove(); const editorContainer = this._tabEdited.querySelector('.tab-editor-container');
const label = this._tabEdited.querySelector('.tab-label-container-editing'); 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;
label.classList.remove('tab-label-container-editing'); label.classList.remove('tab-label-container-editing');
this._tabEdited = null; this._tabEdited = null;

View file

@ -1,3 +1,6 @@
// 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 = {}; const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, { ChromeUtils.defineESModuleGetters(lazy, {
@ -87,6 +90,9 @@ class ZenUIMigration {
} }
_migrateV4(win) { _migrateV4(win) {
if (AppConstants.platform === 'linux') {
return;
}
Services.prefs.setBoolPref( Services.prefs.setBoolPref(
'browser.tabs.unloadOnLowMemory', 'browser.tabs.unloadOnLowMemory',
Services.prefs.getBoolPref('zen.tab-unloader.enabled', true) Services.prefs.getBoolPref('zen.tab-unloader.enabled', true)

View file

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

View file

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

View file

@ -1,3 +1,6 @@
// 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 = {}; const lazyCompactMode = {};
XPCOMUtils.defineLazyPreferenceGetter( XPCOMUtils.defineLazyPreferenceGetter(
@ -88,10 +91,6 @@ var gZenCompactModeManager = {
this.preference === value || this.preference === value ||
document.documentElement.hasAttribute('zen-compact-animating') 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 // We dont want the user to be able to spam the button
return value; return value;
} }

View file

@ -1,3 +1,6 @@
// 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'); const { Downloads } = ChromeUtils.importESModule('resource://gre/modules/Downloads.sys.mjs');
@ -11,24 +14,10 @@
}); });
class ZenDownloadAnimation extends ZenDOMOperatedFeature { class ZenDownloadAnimation extends ZenDOMOperatedFeature {
#lastClickPosition = null;
async init() { async init() {
this.#setupClickListener();
await this.#setupDownloadListeners(); await this.#setupDownloadListeners();
} }
#setupClickListener() {
document.addEventListener('mousedown', this.#handleClick.bind(this), true);
}
#handleClick(event) {
this.#lastClickPosition = {
clientX: event.clientX,
clientY: event.clientY,
};
}
async #setupDownloadListeners() { async #setupDownloadListeners() {
try { try {
const list = await Downloads.getList(Downloads.ALL); const list = await Downloads.getList(Downloads.ALL);
@ -50,14 +39,14 @@
return; return;
} }
if (!this.#lastClickPosition) { if (!gZenUIManager._lastClickPosition) {
console.warn( console.warn(
`[${ZenDownloadAnimation.name}] No recent click position available for animation` `[${ZenDownloadAnimation.name}] No recent click position available for animation`
); );
return; return;
} }
this.#animateDownload(this.#lastClickPosition); this.#animateDownload(gZenUIManager._lastClickPosition);
} }
#animateDownload(startPosition) { #animateDownload(startPosition) {

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,6 @@
// 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 = {}; const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter( XPCOMUtils.defineLazyPreferenceGetter(

735
src/zen/mods/ZenMods.mjs Normal file
View file

@ -0,0 +1,735 @@
// 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

@ -1,138 +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/.
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

@ -1,335 +0,0 @@
const kZenStylesheetThemeHeader = '/* Zen Themes - Generated by ZenThemesImporter.';
const kZenStylesheetThemeHeaderBody = `* DO NOT EDIT THIS FILE DIRECTLY!
* Your changes will be overwritten.
* Instead, go to the preferences and edit the themes there.
*/
`;
const kenStylesheetFooter = `
/* End of Zen Themes */
`;
const getCurrentDateTime = () =>
new Intl.DateTimeFormat('en-US', {
dateStyle: 'full',
timeStyle: 'full',
}).format(new Date().getTime());
var gZenStylesheetManager = {
async writeStylesheet(path, themes) {
let content = kZenStylesheetThemeHeader;
content += `\n* FILE GENERATED AT: ${getCurrentDateTime()}\n`;
content += kZenStylesheetThemeHeaderBody;
for (let theme of themes) {
if (theme.enabled !== undefined && !theme.enabled) {
continue;
}
content += this.getThemeCSS(theme);
}
content += kenStylesheetFooter;
const buffer = new TextEncoder().encode(content);
await IOUtils.write(path, buffer);
},
getThemeCSS(theme) {
let css = '\n';
css += `/* Name: ${theme.name} */\n`;
css += `/* Description: ${theme.description} */\n`;
css += `/* Author: @${theme.author} */\n`;
if (theme._readmeURL) {
css += `/* Readme: ${theme.readme} */\n`;
}
css += `@import url("${theme._chromeURL}");\n`;
return css;
},
};
var gZenThemesImporter = new (class {
constructor() {
try {
window.SessionStore.promiseInitialized.then(async () => {
if (
Services.prefs.getBoolPref('zen.themes.disable-all', false) ||
Services.appinfo.inSafeMode
) {
console.log('[ZenThemesImporter]: Disabling all themes.');
return;
}
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

@ -0,0 +1,142 @@
// 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

@ -0,0 +1,65 @@
// 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

@ -1,200 +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 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

@ -1,256 +0,0 @@
export class ZenThemeMarketplaceParent extends JSWindowActorParent {
constructor() {
super();
}
async receiveMessage(message) {
switch (message.name) {
case 'ZenThemeMarketplace:InstallTheme': {
console.info('ZenThemeMarketplaceParent: Updating themes');
const theme = message.data.theme;
theme.enabled = true;
const themes = await this.getThemes();
themes[theme.id] = theme;
this.updateThemes(themes);
this.updateChildProcesses(theme.id);
break;
}
case 'ZenThemeMarketplace:UninstallTheme': {
console.info('ZenThemeMarketplaceParent: Uninstalling theme');
const themeId = message.data.themeId;
const themes = await this.getThemes();
delete themes[themeId];
this.removeTheme(themeId);
this.updateThemes(themes);
this.updateChildProcesses(themeId);
break;
}
case 'ZenThemeMarketplace:IsThemeInstalled': {
const themeId = message.data.themeId;
const themes = await this.getThemes();
return 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,8 +3,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # 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/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
FINAL_TARGET_FILES.actors += [ FINAL_TARGET_FILES.actors += [
"actors/ZenThemeMarketplaceChild.sys.mjs", "actors/ZenModsMarketplaceChild.sys.mjs",
"actors/ZenThemeMarketplaceParent.sys.mjs", "actors/ZenModsMarketplaceParent.sys.mjs",
] ]

View file

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

View file

@ -41,6 +41,10 @@
} }
} }
#zen-splitview-dropzone {
margin-top: 0 !important;
}
#tabbrowser-tabpanels[zen-split-view='true']:not([zen-split-resizing]) > [zen-split='true'] { #tabbrowser-tabpanels[zen-split-view='true']:not([zen-split-resizing]) > [zen-split='true'] {
transition: inset 0.09s ease-out !important; transition: inset 0.09s ease-out !important;
& browser { & browser {
@ -70,15 +74,8 @@
margin-right: calc(-1 * var(--zen-split-column-gap)); margin-right: calc(-1 * var(--zen-split-column-gap));
} }
#tabbrowser-tabpanels:has(> [zen-split='true']), :root:not([customizing]) #zen-splitview-overlay {
#zen-splitview-overlay { margin-top: calc(var(--zen-split-column-gap) * -1);
: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] { #tabbrowser-tabpanels[zen-split-view] {
@ -112,10 +109,6 @@
cursor: ns-resize; cursor: ns-resize;
} }
#zen-split-views-box:not([hidden='true']) {
display: flex !important;
}
.zen-view-splitter-header-container { .zen-view-splitter-header-container {
position: absolute; position: absolute;
top: calc(var(--zen-split-column-gap) / -2); top: calc(var(--zen-split-column-gap) / -2);

View file

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

View file

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

View file

@ -1,3 +1,8 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
height: var(--zen-toolbar-height); height: var(--zen-toolbar-height);
z-index: 1; z-index: 1;

View file

@ -356,6 +356,7 @@
margin-block: 0 !important; margin-block: 0 !important;
margin-inline: 0 !important; margin-inline: 0 !important;
box-shadow: none !important; box-shadow: none !important;
border-radius: calc(var(--border-radius-medium) - 2px);
} }
/* Adjust padding for content */ /* Adjust padding for content */
& .tab-content { & .tab-content {
@ -374,6 +375,27 @@
width: 14px; width: 14px;
height: 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) --- */ /* --- Essentials Glance Tab Specifics (Floating) --- */
@ -1239,7 +1261,7 @@
padding-bottom: var(--zen-toolbox-padding); padding-bottom: var(--zen-toolbox-padding);
overflow: hidden; overflow: hidden;
gap: calc(var(--zen-toolbox-padding) - 2px); gap: 4px;
transition: transition:
max-height 0.3s ease-out, max-height 0.3s ease-out,
grid-template-columns 0.3s ease-out; grid-template-columns 0.3s ease-out;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,6 @@
// 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 { class ZenWorkspace extends MozXULElement {
static get markup() { static get markup() {
@ -6,7 +9,7 @@
<hbox class="zen-current-workspace-indicator-icon"></hbox> <hbox class="zen-current-workspace-indicator-icon"></hbox>
<hbox class="zen-current-workspace-indicator-name"></hbox> <hbox class="zen-current-workspace-indicator-name"></hbox>
</vbox> </vbox>
<arrowscrollbox orient="vertical" tabindex="-1" class="workspace-arrowscrollbox"> <arrowscrollbox orient="vertical" class="workspace-arrowscrollbox">
<vbox class="zen-workspace-tabs-section zen-workspace-pinned-tabs-section"> <vbox class="zen-workspace-tabs-section zen-workspace-pinned-tabs-section">
<html:div class="vertical-pinned-tabs-container-separator"></html:div> <html:div class="vertical-pinned-tabs-container-separator"></html:div>
</vbox> </vbox>

View file

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

View file

@ -42,11 +42,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
this._resolveInitialized = resolve; this._resolveInitialized = resolve;
}); });
workspaceIndicatorXUL = `
<hbox class="zen-current-workspace-indicator-icon"></hbox>
<hbox class="zen-current-workspace-indicator-name"></hbox>
`;
async waitForPromises() { async waitForPromises() {
if (this.privateWindowOrDisabled) { if (this.privateWindowOrDisabled) {
return; return;
@ -858,6 +853,9 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
console.error('gZenWorkspaces: Error initializing theme picker', e); console.error('gZenWorkspaces: Error initializing theme picker', e);
} }
this.onWindowResize(); this.onWindowResize();
if (window.gZenSessionStore) {
await gZenSessionStore.promiseInitialized;
}
await this._selectStartPage(); await this._selectStartPage();
this._fixTabPositions(); this._fixTabPositions();
this._resolveInitialized(); this._resolveInitialized();
@ -915,7 +913,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
) { ) {
this.log(`Found tab to select: ${this._tabToSelect}, ${tabs.length}`); this.log(`Found tab to select: ${this._tabToSelect}, ${tabs.length}`);
setTimeout(() => { setTimeout(() => {
gBrowser.selectedTab = tabs[this._tabToSelect]; gBrowser.selectedTab = gZenGlanceManager.getTabOrGlanceParent(tabs[this._tabToSelect]);
this._removedByStartupPage = true; this._removedByStartupPage = true;
gBrowser.removeTab(this._tabToRemoveForEmpty, { gBrowser.removeTab(this._tabToRemoveForEmpty, {
skipSessionStore: true, skipSessionStore: true,
@ -2325,7 +2323,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
existingTransform = container.style.transform; existingTransform = container.style.transform;
} }
if (shouldAnimate) { if (shouldAnimate) {
container.style.transform = newTransform; container.style.transform = existingTransform;
animations.push( animations.push(
gZenUIManager.motion.animate( gZenUIManager.motion.animate(
container, container,
@ -2542,8 +2540,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
}) })
); );
} }
setTimeout(gURLBar.formatValue.bind(gURLBar), 0);
} }
async _fixCtrlTabBehavior() { async _fixCtrlTabBehavior() {
@ -2941,6 +2937,9 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
} }
_initializeWorkspaceTabContextMenus() { _initializeWorkspaceTabContextMenus() {
if (this.privateWindowOrDisabled) {
return;
}
const menu = document.createXULElement('menu'); const menu = document.createXULElement('menu');
menu.setAttribute('id', 'context-zen-change-workspace-tab'); menu.setAttribute('id', 'context-zen-change-workspace-tab');
menu.setAttribute('data-l10n-id', 'context-zen-change-workspace-tab'); menu.setAttribute('data-l10n-id', 'context-zen-change-workspace-tab');

View file

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