forked from ZenBrowserMirrors/zen-desktop
Compare commits
27 commits
zen-librar
...
dev
Author | SHA1 | Date | |
---|---|---|---|
|
7c7a911d1e | ||
|
663243264b | ||
|
03ca00748c | ||
|
cbb1a4bc44 | ||
|
21f3ab23d3 | ||
|
18944d5ed8 | ||
|
5c6e5f7361 | ||
|
bd72aebd98 | ||
|
0278aea4f7 | ||
|
3c01004641 | ||
|
b8213569e5 | ||
|
4d27f9d741 | ||
|
3b56abf090 | ||
|
590ba6de1b | ||
|
4aa215e091 | ||
|
de175bff11 | ||
|
a6bc8d7105 | ||
|
e48e7caef1 | ||
|
015cdad2df | ||
|
ef6cf5fae1 | ||
|
797152da89 | ||
|
316ff45859 | ||
|
e0ac9ba424 | ||
|
09ca430b88 | ||
|
dda1dab6f3 | ||
|
fbf411c096 | ||
|
4b0c6f2ca5 |
76 changed files with 1821 additions and 1890 deletions
|
@ -29,7 +29,7 @@
|
|||
|
||||
## 🖥️ 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`!
|
||||
- Check out the latest [release notes](https://zen-browser.app/release-notes)!
|
||||
|
|
|
@ -1 +1 @@
|
|||
82a08ea3ce2d17f21f3d45f4b5607a37590b0158
|
||||
da30619f3ea895b356ded705b8dff9e4f271198f
|
2
l10n
2
l10n
|
@ -1 +1 @@
|
|||
Subproject commit 644474b8c92e306288d835698eb6714081a650d8
|
||||
Subproject commit ebecb32da8929e4f14f9a20f40acb9dab401101c
|
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -9,7 +9,7 @@
|
|||
"version": "1.0.0",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@zen-browser/surfer": "^1.11.12"
|
||||
"@zen-browser/surfer": "^1.11.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "^7.27.0",
|
||||
|
@ -817,9 +817,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@zen-browser/surfer": {
|
||||
"version": "1.11.12",
|
||||
"resolved": "https://registry.npmjs.org/@zen-browser/surfer/-/surfer-1.11.12.tgz",
|
||||
"integrity": "sha512-wny52xOFvZe5aPXxLVxEcAzDNEiWWoDiCZFlzsNxkyQ5Lw6vzqroMWpjQPJwBRJOc/JssgiXMdd1uwl2LLnovQ==",
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmjs.org/@zen-browser/surfer/-/surfer-1.11.13.tgz",
|
||||
"integrity": "sha512-D0TyunAWYtTdJkuUkYeOc6VBXzE9aoDs58kWu/Oi/aU3vd8IbqXPbZZfYwj5FWPWDReMrJUNkkKAEdbL44y9aw==",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"@resvg/resvg-js": "^1.4.0",
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/zen-browser/desktop#readme",
|
||||
"dependencies": {
|
||||
"@zen-browser/surfer": "^1.11.12"
|
||||
"@zen-browser/surfer": "^1.11.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "^7.27.0",
|
||||
|
|
|
@ -16,6 +16,7 @@ pref('browser.toolbars.bookmarks.visibility', 'never');
|
|||
pref("browser.bookmarks.openInTabClosesMenu", false);
|
||||
pref("browser.menu.showViewImageInfo", true);
|
||||
pref("findbar.highlightAll", true);
|
||||
|
||||
pref("layout.word_select.eat_space_to_next_word", false);
|
||||
|
||||
// Better Windows theming
|
||||
|
|
|
@ -22,13 +22,17 @@ pref('zen.mediacontrols.enabled', true);
|
|||
// Exposure:
|
||||
pref('zen.haptic-feedback.enabled', true);
|
||||
|
||||
pref('zen.mods.auto-update-days', 20); // In days
|
||||
#ifdef MOZILLA_OFFICIAL
|
||||
pref('zen.mods.auto-update', true);
|
||||
pref('zen.rice.api.url', 'https://share.zen-browser.app', locked);
|
||||
pref('zen.injections.match-urls', 'https://zen-browser.app/*,https://share.zen-browser.app/*', locked);
|
||||
#else
|
||||
pref('zen.mods.auto-update', false);
|
||||
pref('zen.rice.api.url', "http://localhost", locked);
|
||||
pref('zen.injections.match-urls', 'http://localhost/*', locked);
|
||||
#endif
|
||||
|
||||
pref('zen.rice.share.notice.accepted', false);
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
+++ b/browser/base/content/aboutDialog.js
|
||||
@@ -52,7 +52,7 @@ function init() {
|
||||
|
@ -20,3 +20,18 @@ index f6e1391baf12abb91c85a95107bb3923118746c0..76c7b75a4e29056110f1631a50047c4d
|
|||
versionIdKey += "-nightly";
|
||||
let buildID = Services.appinfo.appBuildID;
|
||||
let year = buildID.slice(0, 4);
|
||||
@@ -125,14 +125,6 @@ function init() {
|
||||
window.close();
|
||||
});
|
||||
if (AppConstants.MOZ_UPDATER) {
|
||||
- document
|
||||
- .getElementById("aboutDialogHelpLink")
|
||||
- .addEventListener("click", () => {
|
||||
- openHelpLink("firefox-help");
|
||||
- });
|
||||
- document
|
||||
- .getElementById("submit-feedback")
|
||||
- .addEventListener("click", openFeedbackPage);
|
||||
document
|
||||
.getElementById("checkForUpdatesButton")
|
||||
.addEventListener("command", () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
+++ b/browser/base/content/aboutDialog.xhtml
|
||||
@@ -35,6 +35,7 @@
|
||||
|
@ -10,7 +10,18 @@ index c64980810570fcea84e33fdc2d66ac42a79f4e46..b7198e810a7510fa82cc6801cfd01c88
|
|||
</linkset>
|
||||
|
||||
<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>
|
||||
</vbox>
|
||||
<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" useoriginprincipal="true" href="about:credits" data-l10n-name="community-creditsLink"/>
|
||||
</description>
|
||||
+#if 0
|
||||
<description class="text-blurb" id="contributeDesc" data-l10n-id="helpus">
|
||||
<label is="text-link" href="https://foundation.mozilla.org/?form=firefox-about" data-l10n-name="helpus-donateLink"/>
|
||||
<label is="text-link" href="https://www.mozilla.org/contribute/?utm_source=firefox-browser&utm_medium=firefox-desktop&utm_campaign=about-dialog" data-l10n-name="helpus-getInvolvedLink"/>
|
||||
</description>
|
||||
+#endif
|
||||
- <description class="text-blurb" id="contributeDesc" data-l10n-id="helpus">
|
||||
- <label is="text-link" href="https://foundation.mozilla.org/?form=firefox-about" data-l10n-name="helpus-donateLink"/>
|
||||
- <label is="text-link" href="https://www.mozilla.org/contribute/?utm_source=firefox-browser&utm_medium=firefox-desktop&utm_campaign=about-dialog" data-l10n-name="helpus-getInvolvedLink"/>
|
||||
- </description>
|
||||
</vbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
|
|
|
@ -1,8 +1,35 @@
|
|||
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
|
||||
+++ 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,8 +31,7 @@
|
|||
# Scripts used all over the browser
|
||||
<script src="chrome://browser/content/ZenUIManager.mjs"></script>
|
||||
<script src="chrome://browser/content/zen-components/ZenFolders.mjs"></script>
|
||||
<script src="chrome://browser/content/zen-components/ZenThemesCommon.mjs"></script>
|
||||
<script src="chrome://browser/content/zen-components/ZenThemesImporter.mjs"></script>
|
||||
<script src="chrome://browser/content/zen-components/ZenMods.mjs"></script>
|
||||
<script src="chrome://browser/content/zen-components/ZenCompactMode.mjs"></script>
|
||||
<script src="chrome://browser/content/zen-components/ZenPinnedTabsStorage.mjs"></script>
|
||||
<script src="chrome://browser/content/zen-components/ZenWorkspacesStorage.mjs"></script>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
content/browser/ZenCustomizableUI.sys.mjs (../../zen/common/ZenCustomizableUI.sys.mjs)
|
||||
content/browser/zen-components/ZenUIMigration.mjs (../../zen/common/ZenUIMigration.mjs)
|
||||
content/browser/zen-components/ZenCommonUtils.mjs (../../zen/common/ZenCommonUtils.mjs)
|
||||
content/browser/zen-components/ZenSessionStore.mjs (../../zen/common/ZenSessionStore.mjs)
|
||||
|
||||
content/browser/zen-styles/zen-theme.css (../../zen/common/styles/zen-theme.css)
|
||||
content/browser/zen-styles/zen-buttons.css (../../zen/common/styles/zen-buttons.css)
|
||||
|
@ -34,10 +35,7 @@
|
|||
content/browser/zen-components/ZenViewSplitter.mjs (../../zen/split-view/ZenViewSplitter.mjs)
|
||||
content/browser/zen-styles/zen-decks.css (../../zen/split-view/zen-decks.css)
|
||||
|
||||
content/browser/zen-components/ZenThemesCommon.mjs (../../zen/mods/ZenThemesCommon.mjs)
|
||||
content/browser/zen-components/ZenThemesImporter.mjs (../../zen/mods/ZenThemesImporter.mjs)
|
||||
content/browser/zen-components/actors/ZenThemeMarketplaceParent.sys.mjs (../../zen/mods/actors/ZenThemeMarketplaceParent.sys.mjs)
|
||||
content/browser/zen-components/actors/ZenThemeMarketplaceChild.sys.mjs (../../zen/mods/actors/ZenThemeMarketplaceChild.sys.mjs)
|
||||
content/browser/zen-components/ZenMods.mjs (../../zen/mods/ZenMods.mjs)
|
||||
|
||||
content/browser/zen-components/ZenWorkspaceIcons.mjs (../../zen/workspaces/ZenWorkspaceIcons.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-styles/zen-glance.css (../../zen/glance/zen-glance.css)
|
||||
content/browser/zen-components/actors/ZenGlanceChild.sys.mjs (../../zen/glance/actors/ZenGlanceChild.sys.mjs)
|
||||
content/browser/zen-components/actors/ZenGlanceParent.sys.mjs (../../zen/glance/actors/ZenGlanceParent.sys.mjs)
|
||||
|
||||
content/browser/zen-components/ZenFolders.mjs (../../zen/folders/ZenFolders.mjs)
|
||||
content/browser/zen-styles/zen-folders.css (../../zen/folders/zen-folders.css)
|
||||
|
|
|
@ -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/ZenWorkspacesSync.mjs"></script>
|
||||
<script type="text/javascript" src="chrome://browser/content/zen-components/ZenActorsManager.mjs"></script>
|
||||
<script type="text/javascript" src="chrome://browser/content/zen-components/ZenSessionStore.mjs"></script>
|
||||
|
||||
|
|
|
@ -14,30 +14,37 @@ var gZenMarketplaceManager = {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!window.gZenMods) {
|
||||
window.gZenMods = ZenMultiWindowFeature.currentBrowser.gZenMods;
|
||||
}
|
||||
|
||||
header.appendChild(this._initDisableAll());
|
||||
|
||||
this._initImportExport();
|
||||
|
||||
this.__hasInitializedEvents = true;
|
||||
|
||||
await this._buildThemesList();
|
||||
await this._buildModsList();
|
||||
|
||||
Services.prefs.addObserver(this.updatePref, this);
|
||||
Services.prefs.addObserver(gZenMods.updatePref, this);
|
||||
|
||||
const checkForUpdateClick = (event) => {
|
||||
if (event.target === checkForUpdates) {
|
||||
event.preventDefault();
|
||||
|
||||
this._checkForThemeUpdates(event);
|
||||
}
|
||||
};
|
||||
|
||||
checkForUpdates.addEventListener('click', checkForUpdateClick);
|
||||
|
||||
document.addEventListener('ZenThemeMarketplace:CheckForUpdatesFinished', (event) => {
|
||||
document.addEventListener('ZenModsMarketplace:CheckForUpdatesFinished', (event) => {
|
||||
checkForUpdates.disabled = false;
|
||||
|
||||
const updates = event.detail.updates;
|
||||
const success = document.getElementById('zenThemeMarketplaceUpdatesSuccess');
|
||||
const error = document.getElementById('zenThemeMarketplaceUpdatesFailure');
|
||||
|
||||
if (updates) {
|
||||
success.hidden = false;
|
||||
error.hidden = true;
|
||||
|
@ -48,13 +55,16 @@ var gZenMarketplaceManager = {
|
|||
});
|
||||
|
||||
window.addEventListener('unload', () => {
|
||||
Services.prefs.removeObserver(this.updatePref, this);
|
||||
Services.prefs.removeObserver(gZenMods.updatePref, this);
|
||||
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);
|
||||
this.themesList.innerHTML = '';
|
||||
this._doNotRebuildThemesList = false;
|
||||
|
||||
this.modsList.innerHTML = '';
|
||||
this._doNotRebuildModsList = false;
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -63,36 +73,32 @@ var gZenMarketplaceManager = {
|
|||
const exportButton = document.getElementById('zenThemeMarketplaceExport');
|
||||
|
||||
if (importButton) {
|
||||
importButton.addEventListener('click', async () => {
|
||||
await this._importThemes();
|
||||
});
|
||||
importButton.addEventListener('click', this._importThemes.bind(this));
|
||||
}
|
||||
|
||||
if (exportButton) {
|
||||
exportButton.addEventListener('click', async () => {
|
||||
await this._exportThemes();
|
||||
});
|
||||
exportButton.addEventListener('click', this._exportThemes.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
_initDisableAll() {
|
||||
const areThemesDisabled = Services.prefs.getBoolPref('zen.themes.disable-all', false);
|
||||
const browser = ZenThemesCommon.currentBrowser;
|
||||
const areModsDisabled = Services.prefs.getBoolPref('zen.themes.disable-all', false);
|
||||
const browser = ZenMultiWindowFeature.currentBrowser;
|
||||
const mozToggle = document.createElement('moz-toggle');
|
||||
|
||||
mozToggle.className =
|
||||
'zenThemeMarketplaceItemPreferenceToggle zenThemeMarketplaceDisableAllToggle';
|
||||
mozToggle.pressed = !areThemesDisabled;
|
||||
mozToggle.pressed = !areModsDisabled;
|
||||
|
||||
browser.document.l10n.setAttributes(
|
||||
mozToggle,
|
||||
`zen-theme-disable-all-${!areThemesDisabled ? 'enabled' : 'disabled'}`
|
||||
`zen-theme-disable-all-${!areModsDisabled ? 'enabled' : 'disabled'}`
|
||||
);
|
||||
|
||||
mozToggle.addEventListener('toggle', async (event) => {
|
||||
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);
|
||||
browser.document.l10n.setAttributes(
|
||||
mozToggle,
|
||||
|
@ -100,90 +106,65 @@ var gZenMarketplaceManager = {
|
|||
);
|
||||
});
|
||||
|
||||
if (areThemesDisabled) {
|
||||
this.themesList.style.display = 'none';
|
||||
if (areModsDisabled) {
|
||||
this.modsList.style.display = 'none';
|
||||
}
|
||||
|
||||
return mozToggle;
|
||||
},
|
||||
|
||||
async observe() {
|
||||
await this._buildThemesList();
|
||||
await this._buildModsList();
|
||||
},
|
||||
|
||||
_checkForThemeUpdates(event) {
|
||||
// Send a message to the child to check for theme updates.
|
||||
event.target.disabled = true;
|
||||
// send an event that will be listened by the child process.
|
||||
document.dispatchEvent(new CustomEvent('ZenCheckForThemeUpdates'));
|
||||
document.dispatchEvent(new CustomEvent('ZenCheckForModUpdates'));
|
||||
},
|
||||
|
||||
get updatePref() {
|
||||
return 'zen.themes.updated-value-observer';
|
||||
},
|
||||
|
||||
triggerThemeUpdate() {
|
||||
Services.prefs.setBoolPref(this.updatePref, !Services.prefs.getBoolPref(this.updatePref));
|
||||
},
|
||||
|
||||
get themesList() {
|
||||
if (!this._themesList) {
|
||||
this._themesList = document.getElementById('zenThemeMarketplaceList');
|
||||
get modsList() {
|
||||
if (!this._modsList) {
|
||||
this._modsList = document.getElementById('zenThemeMarketplaceList');
|
||||
}
|
||||
return this._themesList;
|
||||
return this._modsList;
|
||||
},
|
||||
|
||||
async removeTheme(themeId) {
|
||||
const themePath = ZenThemesCommon.getThemeFolder(themeId);
|
||||
async removeMod(modId) {
|
||||
await gZenMods.removeMod(modId);
|
||||
|
||||
console.info(`[ZenThemeMarketplaceParent:settings]: Removing theme ${themePath}`);
|
||||
|
||||
await IOUtils.remove(themePath, { recursive: true, ignoreAbsent: true });
|
||||
|
||||
const themes = await ZenThemesCommon.getThemes();
|
||||
delete themes[themeId];
|
||||
await IOUtils.writeJSON(ZenThemesCommon.themesDataFile, themes);
|
||||
|
||||
this.triggerThemeUpdate();
|
||||
gZenMods.triggerModsUpdate();
|
||||
},
|
||||
|
||||
async disableTheme(themeId) {
|
||||
const themes = await ZenThemesCommon.getThemes();
|
||||
const theme = themes[themeId];
|
||||
async disableMod(modId) {
|
||||
await gZenMods.disableMod(modId);
|
||||
|
||||
console.log(`[ZenThemeMarketplaceParent:settings]: Disabling theme ${theme.name}`);
|
||||
|
||||
theme.enabled = false;
|
||||
|
||||
await IOUtils.writeJSON(ZenThemesCommon.themesDataFile, themes);
|
||||
this._doNotRebuildThemesList = true;
|
||||
this.triggerThemeUpdate();
|
||||
this._doNotRebuildModsList = true;
|
||||
gZenMods.triggerModsUpdate();
|
||||
},
|
||||
|
||||
async enableTheme(themeId) {
|
||||
const themes = await ZenThemesCommon.getThemes();
|
||||
const theme = themes[themeId];
|
||||
async enableMod(modId) {
|
||||
await gZenMods.enableMod(modId);
|
||||
|
||||
console.log(`[ZenThemeMarketplaceParent:settings]: Enabling theme ${theme.name}`);
|
||||
|
||||
theme.enabled = true;
|
||||
|
||||
await IOUtils.writeJSON(ZenThemesCommon.themesDataFile, themes);
|
||||
this._doNotRebuildThemesList = true;
|
||||
this.triggerThemeUpdate();
|
||||
this._doNotRebuildModsList = true;
|
||||
gZenMods.triggerModsUpdate();
|
||||
},
|
||||
|
||||
_triggerBuildUpdateWithoutRebuild() {
|
||||
this._doNotRebuildThemesList = true;
|
||||
this.triggerThemeUpdate();
|
||||
this._doNotRebuildModsList = true;
|
||||
gZenMods.triggerModsUpdate();
|
||||
},
|
||||
|
||||
async _importThemes() {
|
||||
const errorBox = document.getElementById('zenThemeMarketplaceImportFailure');
|
||||
const successBox = document.getElementById('zenThemeMarketplaceImportSuccess');
|
||||
|
||||
successBox.hidden = true;
|
||||
errorBox.hidden = true;
|
||||
|
||||
const input = document.createElement('input');
|
||||
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.style.display = 'none';
|
||||
|
@ -191,37 +172,52 @@ var gZenMarketplaceManager = {
|
|||
input.setAttribute('accept', '.json');
|
||||
|
||||
let timeout;
|
||||
|
||||
const filePromise = new Promise((resolve) => {
|
||||
input.addEventListener('change', (event) => {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
const file = event.target.files[0];
|
||||
resolve(file);
|
||||
});
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
console.warn('[ZenThemeMarketplaceParent:settings]: Import timeout reached, aborting.');
|
||||
console.warn('[ZenSettings:ZenMods]: Import timeout reached, aborting.');
|
||||
resolve(null);
|
||||
}, 60000);
|
||||
});
|
||||
|
||||
input.addEventListener('cancel', () => {
|
||||
console.warn('[ZenSettings:ZenMods]: Import cancelled by user.');
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
|
||||
input.click();
|
||||
|
||||
try {
|
||||
const file = await filePromise;
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = await file.text();
|
||||
|
||||
const themes = JSON.parse(content);
|
||||
for (const theme of Object.values(themes)) {
|
||||
theme.themeId = theme.id;
|
||||
window.ZenInstallTheme(theme);
|
||||
const mods = JSON.parse(content);
|
||||
|
||||
for (const mod of Object.values(mods)) {
|
||||
mod.modId = mod.id;
|
||||
await window.ZenInstallMod(mod);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ZenThemeMarketplaceParent:settings]: Error while importing themes:', error);
|
||||
console.error('[ZenSettings:ZenMods]: Error while importing mods:', error);
|
||||
errorBox.hidden = false;
|
||||
} finally {
|
||||
if (input) input.remove();
|
||||
}
|
||||
|
||||
if (input) {
|
||||
input.remove();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -232,51 +228,54 @@ var gZenMarketplaceManager = {
|
|||
successBox.hidden = true;
|
||||
errorBox.hidden = true;
|
||||
|
||||
let a, url;
|
||||
let temporalAnchor, temporalUrl;
|
||||
try {
|
||||
const themes = await ZenThemesCommon.getThemes();
|
||||
const themesJson = JSON.stringify(themes, null, 2);
|
||||
const blob = new Blob([themesJson], { type: 'application/json' });
|
||||
url = URL.createObjectURL(blob);
|
||||
// Creating a link to download the JSON file
|
||||
a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'zen-themes-export.json';
|
||||
const mods = await gZenMods.getMods();
|
||||
const modsJson = JSON.stringify(mods, null, 2);
|
||||
const blob = new Blob([modsJson], { type: 'application/json' });
|
||||
|
||||
temporalUrl = URL.createObjectURL(blob);
|
||||
// Creating a link to download the JSON file
|
||||
temporalAnchor = document.createElement('a');
|
||||
temporalAnchor.href = temporalUrl;
|
||||
temporalAnchor.download = 'zen-mods-export.json';
|
||||
|
||||
document.body.appendChild(temporalAnchor);
|
||||
temporalAnchor.click();
|
||||
temporalAnchor.remove();
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
successBox.hidden = false;
|
||||
} catch (error) {
|
||||
console.error('[ZenThemeMarketplaceParent:settings]: Error while exporting themes:', error);
|
||||
console.error('[ZenSettings:ZenMods]: Error while exporting mods:', error);
|
||||
errorBox.hidden = false;
|
||||
} finally {
|
||||
if (a) {
|
||||
a.remove();
|
||||
}
|
||||
if (url) {
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
if (temporalAnchor) {
|
||||
temporalAnchor.remove();
|
||||
}
|
||||
|
||||
if (temporalUrl) {
|
||||
URL.revokeObjectURL(temporalUrl);
|
||||
}
|
||||
},
|
||||
|
||||
async _buildThemesList() {
|
||||
if (!this.themesList) {
|
||||
async _buildModsList() {
|
||||
if (!this.modsList) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._doNotRebuildThemesList) {
|
||||
this._doNotRebuildThemesList = false;
|
||||
if (this._doNotRebuildModsList) {
|
||||
this._doNotRebuildModsList = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const themes = await ZenThemesCommon.getThemes();
|
||||
const mods = await gZenMods.getMods();
|
||||
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))) {
|
||||
const sanitizedName = `theme-${theme.name?.replaceAll(/\s/g, '-')?.replaceAll(/[^A-Za-z_-]+/g, '')}`;
|
||||
const isThemeEnabled = theme.enabled === undefined || theme.enabled;
|
||||
for (const mod of Object.values(mods).sort((a, b) => a.name.localeCompare(b.name))) {
|
||||
const sanitizedName = gZenMods.sanitizeModName(mod.name);
|
||||
const isModEnabled = mod.enabled === undefined || mod.enabled;
|
||||
const fragment = window.MozXULElement.parseXULToFragment(`
|
||||
<vbox class="zenThemeMarketplaceItem">
|
||||
<vbox class="zenThemeMarketplaceItemContent">
|
||||
|
@ -286,14 +285,14 @@ var gZenMarketplaceManager = {
|
|||
<description class="description-deemphasized zenThemeMarketplaceItemDescription"></description>
|
||||
</vbox>
|
||||
<hbox class="zenThemeMarketplaceItemActions">
|
||||
${theme.preferences ? `<button id="zenThemeMarketplaceItemConfigureButton-${sanitizedName}" class="zenThemeMarketplaceItemConfigureButton" hidden="true"></button>` : ''}
|
||||
${theme.homepage ? `<button id="zenThemeMarketplaceItemHomePageLink-${sanitizedName}" class="zenThemeMarketplaceItemHomepageButton" zen-theme-id="${theme.id}"></button>` : ''}
|
||||
<button class="zenThemeMarketplaceItemUninstallButton" data-l10n-id="zen-theme-marketplace-remove-button" zen-theme-id="${theme.id}"></button>
|
||||
${mod.preferences ? `<button id="zenThemeMarketplaceItemConfigureButton-${sanitizedName}" class="zenThemeMarketplaceItemConfigureButton" hidden="true"></button>` : ''}
|
||||
${mod.homepage ? `<button id="zenThemeMarketplaceItemHomePageLink-${sanitizedName}" class="zenThemeMarketplaceItemHomepageButton" zen-mod-id="${mod.id}"></button>` : ''}
|
||||
<button class="zenThemeMarketplaceItemUninstallButton" data-l10n-id="zen-theme-marketplace-remove-button" zen-mod-id="${mod.id}"></button>
|
||||
</hbox>
|
||||
</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 baseHeader = fragment.querySelector('#zenThemeMarketplaceItemContentHeader');
|
||||
|
@ -308,7 +307,7 @@ var gZenMarketplaceManager = {
|
|||
|
||||
mainDialogDiv.className = 'zenThemeMarketplaceItemPreferenceDialog';
|
||||
headerDiv.className = 'zenThemeMarketplaceItemPreferenceDialogTopBar';
|
||||
headerTitle.textContent = themeName;
|
||||
headerTitle.textContent = modName;
|
||||
browser.document.l10n.setAttributes(headerTitle, 'zen-theme-marketplace-theme-header-title', {
|
||||
name: sanitizedName,
|
||||
});
|
||||
|
@ -319,10 +318,10 @@ var gZenMarketplaceManager = {
|
|||
contentDiv.className = 'zenThemeMarketplaceItemPreferenceDialogContent';
|
||||
mozToggle.className = 'zenThemeMarketplaceItemPreferenceToggle';
|
||||
|
||||
mozToggle.pressed = isThemeEnabled;
|
||||
mozToggle.pressed = isModEnabled;
|
||||
browser.document.l10n.setAttributes(
|
||||
mozToggle,
|
||||
`zen-theme-marketplace-toggle-${isThemeEnabled ? 'enabled' : 'disabled'}-button`
|
||||
`zen-theme-marketplace-toggle-${isModEnabled ? 'enabled' : 'disabled'}-button`
|
||||
);
|
||||
|
||||
baseHeader.appendChild(mozToggle);
|
||||
|
@ -340,34 +339,34 @@ var gZenMarketplaceManager = {
|
|||
});
|
||||
|
||||
mozToggle.addEventListener('toggle', async (event) => {
|
||||
const themeId = event.target
|
||||
const modId = event.target
|
||||
.closest('.zenThemeMarketplaceItem')
|
||||
.querySelector('.zenThemeMarketplaceItemUninstallButton')
|
||||
.getAttribute('zen-theme-id');
|
||||
.getAttribute('zen-mod-id');
|
||||
event.target.setAttribute('disabled', true);
|
||||
|
||||
if (!event.target.hasAttribute('pressed')) {
|
||||
await this.disableTheme(themeId);
|
||||
await this.disableMod(modId);
|
||||
|
||||
browser.document.l10n.setAttributes(
|
||||
mozToggle,
|
||||
'zen-theme-marketplace-toggle-disabled-button'
|
||||
);
|
||||
|
||||
if (theme.preferences) {
|
||||
if (mod.preferences) {
|
||||
document
|
||||
.getElementById(`zenThemeMarketplaceItemConfigureButton-${sanitizedName}`)
|
||||
.setAttribute('hidden', true);
|
||||
}
|
||||
} else {
|
||||
await this.enableTheme(themeId);
|
||||
await this.enableMod(modId);
|
||||
|
||||
browser.document.l10n.setAttributes(
|
||||
mozToggle,
|
||||
'zen-theme-marketplace-toggle-enabled-button'
|
||||
);
|
||||
|
||||
if (theme.preferences) {
|
||||
if (mod.preferences) {
|
||||
document
|
||||
.getElementById(`zenThemeMarketplaceItemConfigureButton-${sanitizedName}`)
|
||||
.removeAttribute('hidden');
|
||||
|
@ -379,8 +378,8 @@ var gZenMarketplaceManager = {
|
|||
}, 400);
|
||||
});
|
||||
|
||||
fragment.querySelector('.zenThemeMarketplaceItemTitle').textContent = themeName;
|
||||
fragment.querySelector('.zenThemeMarketplaceItemDescription').textContent = theme.description;
|
||||
fragment.querySelector('.zenThemeMarketplaceItemTitle').textContent = modName;
|
||||
fragment.querySelector('.zenThemeMarketplaceItemDescription').textContent = mod.description;
|
||||
fragment
|
||||
.querySelector('.zenThemeMarketplaceItemUninstallButton')
|
||||
.addEventListener('click', async (event) => {
|
||||
|
@ -392,34 +391,34 @@ var gZenMarketplaceManager = {
|
|||
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');
|
||||
homepageButton.addEventListener('click', () => {
|
||||
// open the homepage url in a new tab
|
||||
const url = theme.homepage;
|
||||
const url = mod.homepage;
|
||||
|
||||
window.open(url, '_blank');
|
||||
});
|
||||
}
|
||||
|
||||
if (theme.preferences) {
|
||||
if (mod.preferences) {
|
||||
fragment
|
||||
.querySelector('.zenThemeMarketplaceItemConfigureButton')
|
||||
.addEventListener('click', () => {
|
||||
dialog.showModal();
|
||||
});
|
||||
|
||||
if (isThemeEnabled) {
|
||||
if (isModEnabled) {
|
||||
fragment
|
||||
.querySelector('.zenThemeMarketplaceItemConfigureButton')
|
||||
.removeAttribute('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
const preferences = await ZenThemesCommon.getThemePreferences(theme);
|
||||
const preferences = await gZenMods.getModPreferences(mod);
|
||||
|
||||
if (preferences.length > 0) {
|
||||
const preferencesWrapper = document.createXULElement('vbox');
|
||||
|
@ -471,7 +470,7 @@ var gZenMarketplaceManager = {
|
|||
|
||||
if (!['string', 'number'].includes(valueType)) {
|
||||
console.log(
|
||||
`[ZenThemeMarketplaceParent:settings]: Warning, invalid data type received (${valueType}), skipping.`
|
||||
`[ZenSettings:ZenMods]: Warning, invalid data type received (${valueType}), skipping.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
@ -583,7 +582,7 @@ var gZenMarketplaceManager = {
|
|||
|
||||
input.addEventListener(
|
||||
'change',
|
||||
ZenThemesCommon.debounce((event) => {
|
||||
gZenMods.debounce((event) => {
|
||||
const value = event.target.value;
|
||||
|
||||
Services.prefs.setStringPref(property, value);
|
||||
|
@ -617,18 +616,18 @@ var gZenMarketplaceManager = {
|
|||
|
||||
default:
|
||||
console.log(
|
||||
`[ZenThemeMarketplaceParent:settings]: Warning, unknown preference type received (${type}), skipping.`
|
||||
`[ZenSettings:ZenMods]: Warning, unknown preference type received (${type}), skipping.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
contentDiv.appendChild(preferencesWrapper);
|
||||
}
|
||||
themeList.appendChild(fragment);
|
||||
modList.appendChild(fragment);
|
||||
}
|
||||
|
||||
this.themesList.replaceChildren(...themeList.children);
|
||||
themeList.remove();
|
||||
this.modsList.replaceChildren(...modList.children);
|
||||
modList.remove();
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -636,6 +635,19 @@ const kZenExtendedSidebar = 'zen.view.sidebar-expanded';
|
|||
const kZenSingleToolbar = 'zen.view.use-single-toolbar';
|
||||
|
||||
var gZenLooksAndFeel = {
|
||||
kZenColors: [
|
||||
'#aac7ff',
|
||||
'#74d7cb',
|
||||
'#a0d490',
|
||||
'#dec663',
|
||||
'#ffb787',
|
||||
'#dec1b1',
|
||||
'#ffb1c0',
|
||||
'#ddbfc3',
|
||||
'#f6b0ea',
|
||||
'#d4bbff',
|
||||
],
|
||||
|
||||
init() {
|
||||
if (this.__hasInitialized) return;
|
||||
this.__hasInitialized = true;
|
||||
|
@ -737,7 +749,8 @@ var gZenLooksAndFeel = {
|
|||
_initializeColorPicker(accentColor) {
|
||||
let elem = document.getElementById('zenLooksAndFeelColorOptions');
|
||||
elem.innerHTML = '';
|
||||
for (let color of ZenThemesCommon.kZenColors) {
|
||||
|
||||
for (let color of this.kZenColors) {
|
||||
let colorElemParen = document.createElement('div');
|
||||
let colorElem = document.createElement('div');
|
||||
colorElemParen.classList.add('zenLooksAndFeelColorOptionParen');
|
||||
|
@ -761,7 +774,7 @@ var gZenLooksAndFeel = {
|
|||
},
|
||||
|
||||
_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,
|
||||
},
|
||||
{
|
||||
id: 'browser.tabs.unloadOnLowMemory',
|
||||
id: 'zen.mods.auto-update',
|
||||
type: 'bool',
|
||||
default: true,
|
||||
},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script src="chrome://browser/content/zen-components/ZenCommonUtils.mjs" defer=""/>
|
||||
<script src="chrome://browser/content/zen-components/ZenThemesCommon.mjs" defer=""/>
|
||||
<html:template id="template-paneZenMarketplace">
|
||||
<hbox id="ZenMarketplaceCategory"
|
||||
class="subcategory"
|
||||
|
@ -21,6 +20,10 @@
|
|||
<button id="zenThemeMarketplaceCheckForUpdates" data-l10n-id="zen-theme-marketplace-check-for-updates-button" />
|
||||
</hbox>
|
||||
|
||||
<checkbox id="zenWorkspacesForceContainerTabsToWorkspace"
|
||||
data-l10n-id="zen-themes-auto-update"
|
||||
preference="zen.mods.auto-update"/>
|
||||
|
||||
<description class="description-deemphasized" data-l10n-id="zen-theme-marketplace-updates-success" hidden="true" id="zenThemeMarketplaceUpdatesSuccess" />
|
||||
<description class="description-deemphasized" data-l10n-id="zen-theme-marketplace-updates-failure" hidden="true" id="zenThemeMarketplaceUpdatesFailure" />
|
||||
|
||||
|
|
|
@ -31,15 +31,6 @@
|
|||
<html:h1 data-l10n-id="pane-zen-tabs-unloader-title"/>
|
||||
</hbox>
|
||||
|
||||
<groupbox id="zenTabsUnloadGroup" data-category="paneZenTabManagement" hidden="true" class="highlighting-group">
|
||||
<label><html:h2 data-l10n-id="zen-tabs-unloader-header"/></label>
|
||||
<description class="description-deemphasized" data-l10n-id="zen-tabs-unloader-description" />
|
||||
|
||||
<checkbox id="zenTabsUnloadActivate"
|
||||
data-l10n-id="zen-tabs-unloader-enabled"
|
||||
preference="browser.tabs.unloadOnLowMemory"/>
|
||||
</groupbox>
|
||||
|
||||
<hbox id="zenPinnedTabsManagerCategory"
|
||||
class="subcategory"
|
||||
hidden="true"
|
||||
|
|
13
src/browser/components/search/SearchUIUtils-sys-mjs.patch
Normal file
13
src/browser/components/search/SearchUIUtils-sys-mjs.patch
Normal 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 };
|
||||
},
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
+++ b/browser/components/sessionstore/SessionStore.sys.mjs
|
||||
@@ -2088,7 +2088,6 @@ var SessionStoreInternal = {
|
||||
|
@ -31,17 +31,19 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
|
|||
return;
|
||||
}
|
||||
|
||||
@@ -3925,6 +3922,9 @@ var SessionStoreInternal = {
|
||||
@@ -3925,6 +3922,11 @@ var SessionStoreInternal = {
|
||||
Math.min(tabState.index, tabState.entries.length)
|
||||
);
|
||||
tabState.pinned = false;
|
||||
+ tabState.zenEssential = false;
|
||||
+ tabState.zenPinnedId = null;
|
||||
+ tabState.zenIsGlance = false;
|
||||
+ tabState.zenGlanceId = null;
|
||||
+ tabState.zenHasStaticLabel = false;
|
||||
|
||||
if (inBackground === false) {
|
||||
aWindow.gBrowser.selectedTab = newTab;
|
||||
@@ -5239,7 +5239,7 @@ var SessionStoreInternal = {
|
||||
@@ -5239,7 +5241,7 @@ var SessionStoreInternal = {
|
||||
}
|
||||
|
||||
let workspaceID = aWindow.getWorkspaceID();
|
||||
|
@ -50,7 +52,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
|
|||
winData.workspaceID = workspaceID;
|
||||
}
|
||||
},
|
||||
@@ -5430,14 +5430,15 @@ var SessionStoreInternal = {
|
||||
@@ -5430,14 +5432,15 @@ var SessionStoreInternal = {
|
||||
}
|
||||
|
||||
let tabbrowser = aWindow.gBrowser;
|
||||
|
@ -68,7 +70,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
|
|||
continue;
|
||||
}
|
||||
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
|
||||
// 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).
|
||||
|
@ -79,7 +81,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
|
|||
winData.title = tabbrowser.tabs[0].label;
|
||||
}
|
||||
winData.selected = selectedIndex;
|
||||
@@ -5569,8 +5570,8 @@ var SessionStoreInternal = {
|
||||
@@ -5569,8 +5572,8 @@ var SessionStoreInternal = {
|
||||
// selectTab represents.
|
||||
let selectTab = 0;
|
||||
if (overwriteTabs) {
|
||||
|
@ -90,7 +92,7 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
|
|||
selectTab = Math.min(selectTab, winData.tabs.length);
|
||||
}
|
||||
|
||||
@@ -5613,6 +5614,7 @@ var SessionStoreInternal = {
|
||||
@@ -5613,6 +5616,7 @@ var SessionStoreInternal = {
|
||||
winData.tabs,
|
||||
winData.groups ?? []
|
||||
);
|
||||
|
@ -98,12 +100,13 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
|
|||
this._log.debug(
|
||||
`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
|
||||
// attributes that may trigger external events.
|
||||
+ if (tabData.zenEssential) {
|
||||
+ tab.setAttribute("zen-essential", "true");
|
||||
+ tabData.pinned = true; // Essential tabs are always pinned.
|
||||
+ }
|
||||
+ if (tabData.zenIsEmpty) {
|
||||
+ tab.setAttribute("zen-empty-tab", "true");
|
||||
|
@ -118,8 +121,5 @@ index 8c6047e1ada5a22e57e1e665965237c9e22641d7..ccd2779d66eda9d034ca51cc3200d814
|
|||
+ tab.setAttribute("zenDefaultUserContextId", true);
|
||||
+ }
|
||||
|
||||
- if (tabData.pinned) {
|
||||
+ if (tabData.pinned || tabData.zenEssential) {
|
||||
if (tabData.pinned) {
|
||||
tabbrowser.pinTab(tab);
|
||||
} else {
|
||||
tabbrowser.unpinTab(tab);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
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
|
||||
+++ b/browser/components/sessionstore/TabState.sys.mjs
|
||||
@@ -84,6 +84,16 @@ class _TabState {
|
||||
@@ -84,6 +84,18 @@ class _TabState {
|
||||
tabData.groupId = tab.group.id;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,8 @@ index 8f7ed557e6aa61e7e16ed4a8d785ad5fe651b3d8..254849e13f7566029dc780c45e376e0f
|
|||
+ tabData.zenPinnedIcon = tab.getAttribute("zen-pinned-icon");
|
||||
+ tabData.zenIsEmpty = tab.hasAttribute("zen-empty-tab");
|
||||
+ tabData.zenHasStaticLabel = tab.hasAttribute("zen-has-static-label");
|
||||
+ tabData.zenGlanceId = tab.getAttribute("glance-id");
|
||||
+ tabData.zenIsGlance = tab.hasAttribute("zen-glance-tab");
|
||||
+
|
||||
tabData.searchMode = tab.ownerGlobal.gURLBar.getSearchMode(browser, true);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
+++ b/browser/components/tabbrowser/content/tabbrowser.js
|
||||
@@ -413,11 +413,41 @@
|
||||
|
@ -292,37 +292,17 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
|
||||
let url = "about:blank";
|
||||
if (tabData.entries?.length) {
|
||||
@@ -3598,7 +3675,29 @@
|
||||
@@ -3598,7 +3675,8 @@
|
||||
skipLoad: true,
|
||||
preferredRemoteType,
|
||||
});
|
||||
-
|
||||
+ tab._originalUrl = url;
|
||||
|
||||
+ 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);
|
||||
+ }
|
||||
+ gZenSessionStore.restoreInitialTabData(tab, tabData);
|
||||
if (select) {
|
||||
tabToSelect = tab;
|
||||
}
|
||||
@@ -3622,7 +3721,8 @@
|
||||
@@ -3622,7 +3700,8 @@
|
||||
// needs calling:
|
||||
shouldUpdateForPinnedTabs = true;
|
||||
}
|
||||
|
@ -332,7 +312,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
let { groupId } = tabData;
|
||||
const tabGroup = tabGroupWorkingData.get(groupId);
|
||||
// if a tab refers to a tab group we don't know, skip any group
|
||||
@@ -3636,7 +3736,10 @@
|
||||
@@ -3636,7 +3715,10 @@
|
||||
tabGroup.stateData.id,
|
||||
tabGroup.stateData.color,
|
||||
tabGroup.stateData.collapsed,
|
||||
|
@ -344,7 +324,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
);
|
||||
tabsFragment.appendChild(tabGroup.node);
|
||||
}
|
||||
@@ -3684,8 +3787,16 @@
|
||||
@@ -3684,8 +3766,16 @@
|
||||
// to remove the old selected tab.
|
||||
if (tabToSelect) {
|
||||
let leftoverTab = this.selectedTab;
|
||||
|
@ -363,7 +343,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
}
|
||||
|
||||
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.
|
||||
if (typeof elementIndex != "number" && typeof tabIndex != "number") {
|
||||
// Move the new tab after another tab if needed, to the end otherwise.
|
||||
|
@ -372,7 +352,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
if (
|
||||
!bulkOrderedOpen &&
|
||||
((openerTab &&
|
||||
@@ -3904,7 +4015,7 @@
|
||||
@@ -3904,7 +3994,7 @@
|
||||
) {
|
||||
elementIndex = Infinity;
|
||||
} else if (previousTab.visible) {
|
||||
|
@ -381,7 +361,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
} else if (previousTab == FirefoxViewHandler.tab) {
|
||||
elementIndex = 0;
|
||||
}
|
||||
@@ -3932,10 +4043,10 @@
|
||||
@@ -3932,14 +4022,14 @@
|
||||
}
|
||||
// Ensure index is within bounds.
|
||||
if (tab.pinned) {
|
||||
|
@ -395,7 +375,12 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
index = Math.min(index, allItems.length);
|
||||
}
|
||||
/** @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();
|
||||
|
||||
|
@ -404,7 +389,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
if (this.isTab(itemAfter) && itemAfter.group == tabGroup) {
|
||||
// Place at the front of, or between tabs in, the same tab group
|
||||
this.tabContainer.insertBefore(tab, itemAfter);
|
||||
@@ -4268,6 +4379,9 @@
|
||||
@@ -4268,6 +4358,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -414,7 +399,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
this.removeTabs(selectedTabs, { telemetrySource });
|
||||
}
|
||||
|
||||
@@ -4520,6 +4634,7 @@
|
||||
@@ -4520,6 +4613,7 @@
|
||||
telemetrySource,
|
||||
} = {}
|
||||
) {
|
||||
|
@ -422,7 +407,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs
|
||||
// can be considered equivalent to closing the window.
|
||||
if (
|
||||
@@ -4604,6 +4719,7 @@
|
||||
@@ -4604,6 +4698,7 @@
|
||||
if (lastToClose) {
|
||||
this.removeTab(lastToClose, aParams);
|
||||
}
|
||||
|
@ -430,7 +415,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -4641,6 +4757,12 @@
|
||||
@@ -4641,6 +4736,12 @@
|
||||
aTab._closeTimeNoAnimTimerId = Glean.browserTabclose.timeNoAnim.start();
|
||||
}
|
||||
|
||||
|
@ -443,7 +428,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
// Handle requests for synchronously removing an already
|
||||
// asynchronously closing tab.
|
||||
if (!animate && aTab.closing) {
|
||||
@@ -4655,7 +4777,9 @@
|
||||
@@ -4655,7 +4756,9 @@
|
||||
// frame created for it (for example, by updating the visually selected
|
||||
// state).
|
||||
let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width;
|
||||
|
@ -454,7 +439,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
if (
|
||||
!this._beginRemoveTab(aTab, {
|
||||
closeWindowFastpath: true,
|
||||
@@ -4821,7 +4945,7 @@
|
||||
@@ -4821,7 +4924,7 @@
|
||||
closeWindowWithLastTab != null
|
||||
? closeWindowWithLastTab
|
||||
: !window.toolbar.visible ||
|
||||
|
@ -463,7 +448,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
|
||||
if (closeWindow) {
|
||||
// We've already called beforeunload on all the relevant tabs if we get here,
|
||||
@@ -4845,6 +4969,7 @@
|
||||
@@ -4845,6 +4948,7 @@
|
||||
|
||||
newTab = true;
|
||||
}
|
||||
|
@ -471,7 +456,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
aTab._endRemoveArgs = [closeWindow, newTab];
|
||||
|
||||
// swapBrowsersAndCloseOther will take care of closing the window without animation.
|
||||
@@ -4885,9 +5010,7 @@
|
||||
@@ -4885,9 +4989,7 @@
|
||||
aTab._mouseleave();
|
||||
|
||||
if (newTab) {
|
||||
|
@ -482,7 +467,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
} else {
|
||||
TabBarVisibility.update();
|
||||
}
|
||||
@@ -5016,6 +5139,8 @@
|
||||
@@ -5016,6 +5118,8 @@
|
||||
this.tabs[i]._tPos = i;
|
||||
}
|
||||
|
||||
|
@ -491,7 +476,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
if (!this._windowIsClosing) {
|
||||
if (wasPinned) {
|
||||
this.tabContainer._positionPinnedTabs();
|
||||
@@ -5230,6 +5355,7 @@
|
||||
@@ -5230,6 +5334,7 @@
|
||||
}
|
||||
|
||||
let excludeTabs = new Set(aExcludeTabs);
|
||||
|
@ -499,7 +484,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
|
||||
// If this tab has a successor, it should be selectable, since
|
||||
// hiding or closing a tab removes that tab as a successor.
|
||||
@@ -5242,13 +5368,13 @@
|
||||
@@ -5242,13 +5347,13 @@
|
||||
!excludeTabs.has(aTab.owner) &&
|
||||
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
|
||||
) {
|
||||
|
@ -515,7 +500,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
);
|
||||
|
||||
let tab = this.tabContainer.findNextTab(aTab, {
|
||||
@@ -5264,7 +5390,7 @@
|
||||
@@ -5264,7 +5369,7 @@
|
||||
}
|
||||
|
||||
if (tab) {
|
||||
|
@ -524,7 +509,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
}
|
||||
|
||||
// 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) {
|
||||
@@ -5686,10 +5812,10 @@
|
||||
@@ -5686,10 +5791,10 @@
|
||||
SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
|
||||
}
|
||||
|
||||
|
@ -546,7 +531,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
aTab.selected ||
|
||||
aTab.closing ||
|
||||
// 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.
|
||||
if (this.isTab(element) && element.pinned) {
|
||||
|
@ -555,7 +540,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
} else {
|
||||
tabIndex = Math.max(tabIndex, this.pinnedTabCount);
|
||||
}
|
||||
@@ -6012,10 +6138,16 @@
|
||||
@@ -6012,10 +6117,16 @@
|
||||
this.#handleTabMove(
|
||||
element,
|
||||
() => {
|
||||
|
@ -574,7 +559,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
if (neighbor && this.isTab(element) && tabIndex > element._tPos) {
|
||||
neighbor.after(element);
|
||||
} else {
|
||||
@@ -6084,17 +6216,26 @@
|
||||
@@ -6084,17 +6195,29 @@
|
||||
targetElement = targetElement.group;
|
||||
}
|
||||
}
|
||||
|
@ -583,8 +568,12 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
+ element = element.group;
|
||||
+ }
|
||||
// Don't allow mixing pinned and unpinned tabs.
|
||||
if (element.pinned && !targetElement?.pinned) {
|
||||
- if (element.pinned && !targetElement?.pinned) {
|
||||
- targetElement = this.tabs[this.pinnedTabCount - 1];
|
||||
+ if (element.hasAttribute('zen-essential') && !targetElement?.hasAttribute('zen-essential')) {
|
||||
+ targetElement = this.tabs.filter(tab => !tab.hasAttribute('zen-glance-tab'))[this._numZenEssentials - 1];
|
||||
+ moveBefore = false;
|
||||
+ } else if (element.pinned && !targetElement?.pinned) {
|
||||
+ targetElement = this.tabs.filter(tab => !tab.hasAttribute('zen-glance-tab'))[this.pinnedTabCount - 1];
|
||||
moveBefore = false;
|
||||
} else if (!element.pinned && targetElement && targetElement.pinned) {
|
||||
|
@ -604,7 +593,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
if (element.pinned && this.tabContainer.verticalMode) {
|
||||
return this.tabContainer.verticalPinnedTabsContainer;
|
||||
}
|
||||
@@ -6154,7 +6295,7 @@
|
||||
@@ -6154,7 +6277,7 @@
|
||||
if (!this.isTab(aTab)) {
|
||||
throw new Error("Can only move a tab into a tab group");
|
||||
}
|
||||
|
@ -613,7 +602,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
return;
|
||||
}
|
||||
if (aTab.group && aTab.group.id === aGroup.id) {
|
||||
@@ -6248,6 +6389,10 @@
|
||||
@@ -6248,6 +6371,10 @@
|
||||
|
||||
moveActionCallback();
|
||||
|
||||
|
@ -624,7 +613,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
// Clear tabs cache after moving nodes because the order of tabs may have
|
||||
// changed.
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
@@ -7145,7 +7290,7 @@
|
||||
@@ -7145,7 +7272,7 @@
|
||||
// preventDefault(). It will still raise the window if appropriate.
|
||||
break;
|
||||
}
|
||||
|
@ -633,7 +622,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
window.focus();
|
||||
aEvent.preventDefault();
|
||||
break;
|
||||
@@ -8044,6 +8189,7 @@
|
||||
@@ -8044,6 +8171,7 @@
|
||||
aWebProgress.isTopLevel
|
||||
) {
|
||||
this.mTab.setAttribute("busy", "true");
|
||||
|
@ -641,7 +630,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
gBrowser._tabAttrModified(this.mTab, ["busy"]);
|
||||
this.mTab._notselectedsinceload = !this.mTab.selected;
|
||||
}
|
||||
@@ -9009,7 +9155,7 @@ var TabContextMenu = {
|
||||
@@ -9009,7 +9137,7 @@ var TabContextMenu = {
|
||||
);
|
||||
contextUnpinSelectedTabs.hidden =
|
||||
!this.contextTab.pinned || !this.multiselected;
|
||||
|
@ -650,7 +639,7 @@ index d5aa64842a35c6697263c63fd3a0571b64b01344..6943dc7f1d3d95ab1da315cbdbda0b03
|
|||
// Move Tab items
|
||||
let contextMoveTabOptions = document.getElementById(
|
||||
"context_moveTabOptions"
|
||||
@@ -9278,6 +9424,7 @@ var TabContextMenu = {
|
||||
@@ -9278,6 +9406,7 @@ var TabContextMenu = {
|
||||
telemetrySource: gBrowser.TabMetrics.METRIC_SOURCE.TAB_STRIP,
|
||||
});
|
||||
} else {
|
||||
|
|
13
src/browser/components/tabbrowser/content/tabgroup-js.patch
Normal file
13
src/browser/components/tabbrowser/content/tabgroup-js.patch
Normal 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 =
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
+++ b/browser/components/tabbrowser/content/tabs.js
|
||||
@@ -83,7 +83,7 @@
|
||||
|
@ -46,6 +46,15 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
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 @@
|
||||
}
|
||||
|
||||
|
@ -91,16 +100,23 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
);
|
||||
let size = this.verticalMode ? "height" : "width";
|
||||
let screenAxis = this.verticalMode ? "screenY" : "screenX";
|
||||
@@ -1211,7 +1229,7 @@
|
||||
item.removeAttribute("tabdrop-samewindow");
|
||||
resolve();
|
||||
};
|
||||
- if (gReduceMotion) {
|
||||
+ if (true || gReduceMotion) {
|
||||
postTransitionCleanup();
|
||||
@@ -1135,8 +1153,14 @@
|
||||
(lastMovingTabScreen + tabSize);
|
||||
|
||||
if (this.verticalMode) {
|
||||
+ if (oldTranslateY > 0 && translateOffsetY > tabHeight / 2) {
|
||||
+ newTranslateY += tabHeight;
|
||||
+ }
|
||||
+ if (oldTranslateY < 0 && -translateOffsetY > tabHeight / 2) {
|
||||
+ newTranslateY -= tabHeight;
|
||||
+ }
|
||||
newTranslateY = Math.min(
|
||||
- Math.max(oldTranslateY, firstBound),
|
||||
+ Math.max(newTranslateY, firstBound),
|
||||
lastBound
|
||||
);
|
||||
} else {
|
||||
let onTransitionEnd = transitionendEvent => {
|
||||
@@ -1337,6 +1355,7 @@
|
||||
@@ -1337,6 +1361,7 @@
|
||||
|
||||
let nextItem = this.ariaFocusableItems[newIndex];
|
||||
let tabGroup = isTab(nextItem) && nextItem.group;
|
||||
|
@ -108,7 +124,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
gBrowser.loadTabs(urls, {
|
||||
inBackground,
|
||||
replace,
|
||||
@@ -1369,6 +1388,17 @@
|
||||
@@ -1369,6 +1394,17 @@
|
||||
|
||||
this.finishMoveTogetherSelectedTabs(draggedTab);
|
||||
this.finishAnimateTabMove();
|
||||
|
@ -126,7 +142,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
this.#expandGroupOnDrop(draggedTab);
|
||||
|
||||
if (
|
||||
@@ -1597,7 +1627,7 @@
|
||||
@@ -1597,7 +1633,7 @@
|
||||
}
|
||||
|
||||
get newTabButton() {
|
||||
|
@ -135,7 +151,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
}
|
||||
|
||||
get verticalMode() {
|
||||
@@ -1621,29 +1651,54 @@
|
||||
@@ -1621,29 +1657,54 @@
|
||||
if (this.#allTabs) {
|
||||
return this.#allTabs;
|
||||
}
|
||||
|
@ -179,7 +195,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
+ if (glanceTab) {
|
||||
+ // insert right after the parent tab. note: it must be inserted before
|
||||
+ // the last pinned tab so it can be inserted in the correct order
|
||||
+ allTabs.splice(Math.max(i++, lastPinnedTabIdx), 0, glanceTab);
|
||||
+ allTabs.splice(Math.max(i++ + 1, lastPinnedTabIdx), 0, glanceTab);
|
||||
+ } else if (tab.classList.contains("vertical-pinned-tabs-container-separator")) {
|
||||
+ // remove the separator from the list
|
||||
+ allTabs.splice(i, 1);
|
||||
|
@ -198,7 +214,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
}
|
||||
|
||||
/**
|
||||
@@ -1698,23 +1753,18 @@
|
||||
@@ -1698,23 +1759,18 @@
|
||||
}
|
||||
|
||||
let elementIndex = 0;
|
||||
|
@ -226,7 +242,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
let visibleTabsInGroup = child.tabs.filter(tab => tab.visible);
|
||||
visibleTabsInGroup.forEach(tab => {
|
||||
tab.elementIndex = elementIndex++;
|
||||
@@ -1724,10 +1774,7 @@
|
||||
@@ -1724,10 +1780,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,7 +254,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
|
||||
return this.#focusableItems;
|
||||
}
|
||||
@@ -1735,6 +1782,7 @@
|
||||
@@ -1735,6 +1788,7 @@
|
||||
_invalidateCachedTabs() {
|
||||
this.#allTabs = null;
|
||||
this._invalidateCachedVisibleTabs();
|
||||
|
@ -246,7 +262,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
}
|
||||
|
||||
_invalidateCachedVisibleTabs() {
|
||||
@@ -1749,8 +1797,8 @@
|
||||
@@ -1749,8 +1803,8 @@
|
||||
#isContainerVerticalPinnedGrid(tab) {
|
||||
return (
|
||||
this.verticalMode &&
|
||||
|
@ -257,7 +273,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
!this.expandOnHover
|
||||
);
|
||||
}
|
||||
@@ -1766,7 +1814,7 @@
|
||||
@@ -1766,7 +1820,7 @@
|
||||
|
||||
if (node == null) {
|
||||
// We have a container for non-tab elements at the end of the scrollbox.
|
||||
|
@ -266,7 +282,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
}
|
||||
|
||||
node.before(tab);
|
||||
@@ -1861,7 +1909,7 @@
|
||||
@@ -1861,7 +1915,7 @@
|
||||
// There are separate "new tab" buttons for horizontal tabs toolbar, vertical tabs and
|
||||
// for when the tab strip is overflowed (which is shared by vertical and horizontal tabs);
|
||||
// Attach the long click popup to all of them.
|
||||
|
@ -275,7 +291,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
const newTab2 = this.newTabButton;
|
||||
const newTabVertical = document.getElementById(
|
||||
"vertical-tabs-newtab-button"
|
||||
@@ -1956,10 +2004,12 @@
|
||||
@@ -1956,10 +2010,12 @@
|
||||
|
||||
_handleTabSelect(aInstant) {
|
||||
let selectedTab = this.selectedItem;
|
||||
|
@ -288,7 +304,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
selectedTab._notselectedsinceload = false;
|
||||
}
|
||||
|
||||
@@ -2132,6 +2182,7 @@
|
||||
@@ -2132,6 +2188,7 @@
|
||||
}
|
||||
|
||||
_positionPinnedTabs() {
|
||||
|
@ -296,7 +312,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
let tabs = this.visibleTabs;
|
||||
let numPinned = gBrowser.pinnedTabCount;
|
||||
let absPositionHorizontalTabs =
|
||||
@@ -2206,7 +2257,7 @@
|
||||
@@ -2206,7 +2263,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -305,16 +321,25 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
|
||||
let directionX = screenX > dragData.animLastScreenX;
|
||||
let directionY = screenY > dragData.animLastScreenY;
|
||||
@@ -2214,7 +2265,7 @@
|
||||
dragData.animLastScreenX = screenX;
|
||||
@@ -2215,6 +2272,8 @@
|
||||
|
||||
let { width: tabWidth, height: tabHeight } =
|
||||
- draggedTab.getBoundingClientRect();
|
||||
+ (draggedTab.group?.hasAttribute("split-view-group") ? draggedTab.group : draggedTab).getBoundingClientRect();
|
||||
draggedTab.getBoundingClientRect();
|
||||
+ tabWidth += 4; // Add 4px to account for the gap
|
||||
+ tabHeight += 4;
|
||||
let shiftSizeX = tabWidth * movingTabs.length;
|
||||
let shiftSizeY = tabHeight;
|
||||
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();
|
||||
|
||||
|
@ -335,7 +360,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
|
||||
if (this.#rtlMode) {
|
||||
tabs.reverse();
|
||||
@@ -2408,7 +2463,7 @@
|
||||
@@ -2408,7 +2471,7 @@
|
||||
let size = this.verticalMode ? "height" : "width";
|
||||
let translateAxis = this.verticalMode ? "translateY" : "translateX";
|
||||
let scrollDirection = this.verticalMode ? "scrollTop" : "scrollLeft";
|
||||
|
@ -344,7 +369,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
let translateX = event.screenX - dragData.screenX;
|
||||
let translateY = event.screenY - dragData.screenY;
|
||||
|
||||
@@ -2422,12 +2477,21 @@
|
||||
@@ -2422,12 +2485,21 @@
|
||||
let lastTab = tabs.at(-1);
|
||||
let lastMovingTab = movingTabs.at(-1);
|
||||
let firstMovingTab = movingTabs[0];
|
||||
|
@ -367,7 +392,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
translate +=
|
||||
this.arrowScrollbox.scrollbox[scrollDirection] - dragData.scrollPos;
|
||||
} else if (isPinned && this.verticalMode) {
|
||||
@@ -2446,6 +2510,9 @@
|
||||
@@ -2446,6 +2518,9 @@
|
||||
// Shift the `.tab-group-label-container` to shift the label element.
|
||||
item = item.parentElement;
|
||||
}
|
||||
|
@ -377,7 +402,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
item.style.transform = `${translateAxis}(${translate}px)`;
|
||||
}
|
||||
|
||||
@@ -2583,6 +2650,9 @@
|
||||
@@ -2583,6 +2658,9 @@
|
||||
break;
|
||||
}
|
||||
let element = tabs[mid];
|
||||
|
@ -387,7 +412,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
let elementForSize = isTabGroupLabel(element)
|
||||
? element.parentElement
|
||||
: element;
|
||||
@@ -2605,6 +2675,10 @@
|
||||
@@ -2605,6 +2683,10 @@
|
||||
if (!dropElement) {
|
||||
dropElement = this.ariaFocusableItems[oldDropElementIndex];
|
||||
}
|
||||
|
@ -398,7 +423,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
let newDropElementIndex = dropElement
|
||||
? dropElement.elementIndex
|
||||
: oldDropElementIndex;
|
||||
@@ -2613,7 +2687,7 @@
|
||||
@@ -2613,7 +2695,7 @@
|
||||
let shouldCreateGroupOnDrop;
|
||||
let dropBefore;
|
||||
if (dropElement) {
|
||||
|
@ -407,7 +432,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
? dropElement.parentElement
|
||||
: dropElement;
|
||||
|
||||
@@ -2675,12 +2749,12 @@
|
||||
@@ -2675,12 +2757,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -422,7 +447,16 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
dropElement != draggedTab &&
|
||||
isTab(dropElement) &&
|
||||
!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
|
||||
// would currently be dropped.
|
||||
for (let item of tabs) {
|
||||
|
@ -431,7 +465,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
continue;
|
||||
}
|
||||
|
||||
@@ -2759,6 +2833,9 @@
|
||||
@@ -2759,6 +2841,9 @@
|
||||
if (isTabGroupLabel(item)) {
|
||||
// Shift the `.tab-group-label-container` to shift the label element.
|
||||
item = item.parentElement;
|
||||
|
@ -441,7 +475,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
}
|
||||
item.style.transform = transform;
|
||||
}
|
||||
@@ -2811,8 +2888,9 @@
|
||||
@@ -2811,8 +2896,9 @@
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -453,7 +487,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
return;
|
||||
}
|
||||
|
||||
@@ -2824,6 +2902,12 @@
|
||||
@@ -2824,6 +2910,12 @@
|
||||
item = item.parentElement;
|
||||
}
|
||||
item.style.transform = "";
|
||||
|
@ -466,7 +500,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
item.removeAttribute("dragover-createGroup");
|
||||
}
|
||||
this.removeAttribute("movingtab-createGroup");
|
||||
@@ -2870,7 +2954,7 @@
|
||||
@@ -2870,7 +2962,7 @@
|
||||
let postTransitionCleanup = () => {
|
||||
movingTab._moveTogetherSelectedTabsData.animate = false;
|
||||
};
|
||||
|
@ -475,7 +509,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
postTransitionCleanup();
|
||||
} else {
|
||||
let onTransitionEnd = transitionendEvent => {
|
||||
@@ -3043,7 +3127,7 @@
|
||||
@@ -3043,7 +3135,7 @@
|
||||
}
|
||||
|
||||
_notifyBackgroundTab(aTab) {
|
||||
|
@ -484,7 +518,7 @@ index 84d633471c89230b981d8a07babef4e0c76c0338..1a9c56846ff27d68c16b939fb759ea95
|
|||
return;
|
||||
}
|
||||
|
||||
@@ -3169,6 +3253,9 @@
|
||||
@@ -3169,6 +3261,9 @@
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -60,18 +60,6 @@
|
|||
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(rtl)[positionend] {
|
||||
list-style-image: url('chrome://browser/skin/sidebars.svg') !important;
|
||||
|
@ -133,6 +121,10 @@
|
|||
list-style-image: url('tab.svg') !important;
|
||||
}
|
||||
|
||||
#context-navigation > menuitem {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#history-panelmenu,
|
||||
.urlbarView-row[source='history']
|
||||
> .urlbarView-row-inner
|
||||
|
@ -173,15 +165,6 @@
|
|||
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,
|
||||
#appMenu-extensions-themes-button,
|
||||
#unified-extensions-button {
|
||||
|
@ -303,6 +286,11 @@
|
|||
list-style-image: url('home.svg') !important;
|
||||
}
|
||||
|
||||
#toggle_toolbar-menubar,
|
||||
#appMenu_menu_openHelp {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#library-button {
|
||||
list-style-image: url('library.svg') !important;
|
||||
}
|
||||
|
@ -447,6 +435,11 @@
|
|||
|
||||
#zen-glance-sidebar-split {
|
||||
list-style-image: url('split.svg');
|
||||
|
||||
&[disabled='true'] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
#sidebar-box[sidebarcommand='viewTabsSidebar']
|
||||
|
@ -635,371 +628,6 @@
|
|||
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 {
|
||||
list-style-image: url('container-tab.svg') !important;
|
||||
}
|
||||
|
@ -1020,57 +648,12 @@ menuitem[id='placesContext_showAllBookmarks'],
|
|||
list-style-image: url('manage.svg') !important;
|
||||
}
|
||||
|
||||
menuitem[id='placesContext_new:bookmark'],
|
||||
menuitem[id='placesContext_new:folder'],
|
||||
menuitem[id='placesContext_new:separator'] {
|
||||
--menu-image: url('plus.svg');
|
||||
}
|
||||
|
||||
#context-savelinktopocket,
|
||||
#context-pocket {
|
||||
--menu-image: url('pocket-outline.svg');
|
||||
}
|
||||
|
||||
#context_moveTabOptions {
|
||||
--menu-image: url('move-tab.svg');
|
||||
}
|
||||
|
||||
.share-tab-url-item {
|
||||
--menu-image: url('share.svg');
|
||||
}
|
||||
|
||||
#context_reopenInContainer {
|
||||
--menu-image: url('container-tab.svg');
|
||||
}
|
||||
|
||||
#context_closeTab {
|
||||
--menu-image: url('close.svg');
|
||||
}
|
||||
|
||||
#context_closeTabOptions {
|
||||
--menu-image: url('close-all.svg');
|
||||
}
|
||||
|
||||
#context_unloadTab,
|
||||
#context_zenTabActions {
|
||||
--menu-image: url('close-all.svg');
|
||||
}
|
||||
|
||||
.customize-context-reportExtension,
|
||||
.unified-extensions-context-menu-report-extension {
|
||||
--menu-image: url('report.svg');
|
||||
}
|
||||
|
||||
/* FIX header icons for the app menu sub menus (eg. fx account, history...) */
|
||||
.panel-header > h1 {
|
||||
text-align: left;
|
||||
margin-left: 8px !important;
|
||||
}
|
||||
|
||||
.wordmark::after {
|
||||
content: 'Plus' !important;
|
||||
}
|
||||
|
||||
/* header icons for the app menu sub menus (eg. fx account, history...) */
|
||||
.panel-header > h1 > span::before {
|
||||
content: '';
|
||||
|
@ -1115,67 +698,10 @@ menuitem[id='placesContext_new:separator'] {
|
|||
--fp-enabled: 1;
|
||||
}
|
||||
|
||||
@media not (-moz-platform: linux) {
|
||||
.unified-extensions-context-menu-pin-to-toolbar {
|
||||
--menu-image: url('pin.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.unified-extensions-context-menu-move-widget-down {
|
||||
--menu-image: url('arrow-down.svg');
|
||||
}
|
||||
|
||||
.unified-extensions-context-menu-move-widget-up {
|
||||
--menu-image: url('arrow-up.svg');
|
||||
}
|
||||
|
||||
#alltabs-button {
|
||||
list-style-image: url('chrome://browser/skin/tabs.svg') !important;
|
||||
}
|
||||
|
||||
:not(:not(menubar) > menu, #ContentSelectDropdown)
|
||||
> menupopup
|
||||
> menuitem:not(
|
||||
.menuitem-iconic,
|
||||
[type='checkbox'],
|
||||
[type='radio'],
|
||||
.in-menulist,
|
||||
.in-menulist menuitem,
|
||||
.unified-nav-current
|
||||
),
|
||||
:not(:not(menubar) > menu, #ContentSelectDropdown)
|
||||
> menupopup
|
||||
> menu:not(
|
||||
.menu-iconic,
|
||||
[type='checkbox'],
|
||||
[type='radio'],
|
||||
.in-menulist,
|
||||
.in-menulist menu,
|
||||
.unified-nav-current
|
||||
),
|
||||
:not(:not(menubar) > menu, #ContentSelectDropdown) > menupopup > menucaption {
|
||||
padding-inline-start: calc(
|
||||
var(--zen-contextmenu-menuitem-padding-inline) + var(--zen-contextmenu-menuicon-margin-inline) /
|
||||
2
|
||||
) !important;
|
||||
}
|
||||
|
||||
menupopup > menuitem:is([type='checkbox']) .menu-iconic-left {
|
||||
--menu-image: none !important;
|
||||
margin-inline-start: 4px;
|
||||
|
||||
@media not (-moz-platform: windows) {
|
||||
margin-inline-end: 0;
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (-moz-platform: windows) {
|
||||
menupopup > menuitem[checked='true'] {
|
||||
padding-inline-start: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
#toolbar-context-toggle-vertical-tabs,
|
||||
#toolbar-context-customize-sidebar,
|
||||
#sidebarRevampSeparator {
|
||||
|
|
3
src/zen/@types/lib.gecko.darwin.d.ts
vendored
3
src/zen/@types/lib.gecko.darwin.d.ts
vendored
|
@ -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.
|
||||
* Content was generated from source XPCOM .idl files.
|
||||
|
|
3
src/zen/@types/lib.gecko.dom.d.ts
vendored
3
src/zen/@types/lib.gecko.dom.d.ts
vendored
|
@ -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.
|
||||
* Content was generated from source .webidl files.
|
||||
|
|
3
src/zen/@types/lib.gecko.glean.d.ts
vendored
3
src/zen/@types/lib.gecko.glean.d.ts
vendored
|
@ -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.
|
||||
* Content was generated from source metrics.yaml files.
|
||||
|
|
3
src/zen/@types/lib.gecko.linux.d.ts
vendored
3
src/zen/@types/lib.gecko.linux.d.ts
vendored
|
@ -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.
|
||||
* Content was generated from source XPCOM .idl files.
|
||||
|
|
3
src/zen/@types/lib.gecko.modules.d.ts
vendored
3
src/zen/@types/lib.gecko.modules.d.ts
vendored
|
@ -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.
|
||||
* Content was generated by running "mach ts paths".
|
||||
|
|
3
src/zen/@types/lib.gecko.nsresult.d.ts
vendored
3
src/zen/@types/lib.gecko.nsresult.d.ts
vendored
|
@ -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.
|
||||
* Content was generated from xpc.msg and error_list.json.
|
||||
|
|
3
src/zen/@types/lib.gecko.services.d.ts
vendored
3
src/zen/@types/lib.gecko.services.d.ts
vendored
|
@ -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.
|
||||
* Content was generated from services.json.
|
||||
|
|
3
src/zen/@types/lib.gecko.tweaks.d.ts
vendored
3
src/zen/@types/lib.gecko.tweaks.d.ts
vendored
|
@ -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.
|
||||
*/
|
||||
|
|
3
src/zen/@types/lib.gecko.win32.d.ts
vendored
3
src/zen/@types/lib.gecko.win32.d.ts
vendored
|
@ -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.
|
||||
* Content was generated from source XPCOM .idl files.
|
||||
|
|
3
src/zen/@types/lib.gecko.xpcom.d.ts
vendored
3
src/zen/@types/lib.gecko.xpcom.d.ts
vendored
|
@ -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.
|
||||
* Content was generated from source XPCOM .idl files.
|
||||
|
|
3
src/zen/@types/lib.gecko.xpidl.d.ts
vendored
3
src/zen/@types/lib.gecko.xpidl.d.ts
vendored
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
||||
var gZenActorsManager = {
|
||||
_actors: new Set(),
|
||||
_lazy: {},
|
||||
|
||||
addJSWindowActor(...args) {
|
||||
if (this._actors.has(args[0])) {
|
||||
init() {
|
||||
ChromeUtils.defineESModuleGetters(this._lazy, {
|
||||
ActorManagerParent: 'resource://gre/modules/ActorManagerParent.sys.mjs',
|
||||
});
|
||||
},
|
||||
|
||||
addJSWindowActor(name, data) {
|
||||
if (!this._lazy.ActorManagerParent) {
|
||||
this.init();
|
||||
}
|
||||
if (this._actors.has(name)) {
|
||||
// Actor already registered, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
const decl = {};
|
||||
decl[name] = data;
|
||||
try {
|
||||
ChromeUtils.registerWindowActor(...args);
|
||||
this._actors.add(args[0]);
|
||||
this._lazy.ActorManagerParent.addJSWindowActors(decl);
|
||||
this._actors.add(name);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to register JSWindowActor: ${e}`);
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
kZenOSToSmallName: {
|
||||
WINNT: 'windows',
|
||||
|
|
|
@ -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 {
|
||||
constructor() {}
|
||||
|
|
File diff suppressed because one or more lines are too long
50
src/zen/common/ZenSessionStore.mjs
Normal file
50
src/zen/common/ZenSessionStore.mjs
Normal 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();
|
||||
}
|
|
@ -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 = {
|
||||
init() {
|
||||
|
|
|
@ -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 = {
|
||||
_popupTrackingElements: [],
|
||||
_hoverPausedForExpand: false,
|
||||
_hasLoadedDOM: false,
|
||||
testingEnabled: Services.prefs.getBoolPref('zen.testing.enabled', false),
|
||||
|
||||
_lastClickPosition: null,
|
||||
|
||||
_toastTimeouts: [],
|
||||
|
||||
init() {
|
||||
|
@ -31,6 +36,8 @@ var gZenUIManager = {
|
|||
|
||||
gURLBar._zenTrimURL = this.urlbarTrim.bind(this);
|
||||
|
||||
document.addEventListener('mousedown', this.handleMouseDown.bind(this), true);
|
||||
|
||||
ChromeUtils.defineLazyGetter(this, 'motion', () => {
|
||||
return ChromeUtils.importESModule('chrome://browser/content/zen-vendor/motion.min.mjs', {
|
||||
global: 'current',
|
||||
|
@ -62,6 +69,13 @@ var gZenUIManager = {
|
|||
gZenMediaController.init();
|
||||
},
|
||||
|
||||
handleMouseDown(event) {
|
||||
this._lastClickPosition = {
|
||||
clientX: event.clientX,
|
||||
clientY: event.clientY,
|
||||
};
|
||||
},
|
||||
|
||||
updateTabsToolbar() {
|
||||
const kUrlbarHeight = 440;
|
||||
gURLBar.textbox.style.setProperty(
|
||||
|
@ -70,7 +84,6 @@ var gZenUIManager = {
|
|||
);
|
||||
gZenVerticalTabsManager.actualWindowButtons.removeAttribute('zen-has-hover');
|
||||
gZenVerticalTabsManager.recalculateURLBarHeight();
|
||||
setTimeout(gURLBar.formatValue.bind(gURLBar), 350);
|
||||
if (!this._preventToolbarRebuild) {
|
||||
setTimeout(() => {
|
||||
gZenWorkspaces.updateTabsContainers();
|
||||
|
@ -989,10 +1002,18 @@ var gZenVerticalTabsManager = {
|
|||
async renameTabKeydown(event) {
|
||||
event.stopPropagation();
|
||||
if (event.key === 'Enter') {
|
||||
let label = this._tabEdited.querySelector('.tab-label-container-editing');
|
||||
let input = this._tabEdited.querySelector('#tab-label-input');
|
||||
const isTab = !!event.target.closest('.tabbrowser-tab');
|
||||
let label = isTab
|
||||
? this._tabEdited.querySelector('.tab-label-container-editing')
|
||||
: this._tabEdited;
|
||||
let input = document.getElementById('tab-label-input');
|
||||
let newName = input.value.trim();
|
||||
|
||||
document.documentElement.removeAttribute('zen-renaming-tab');
|
||||
input.remove();
|
||||
if (!isTab) {
|
||||
await this._tabEdited.onRenameFinished(newName);
|
||||
} else {
|
||||
// Check if name is blank, reset if so
|
||||
// Always remove, so we can always rename and if it's empty,
|
||||
// it will reset to the original name anyway
|
||||
|
@ -1012,7 +1033,6 @@ var gZenVerticalTabsManager = {
|
|||
!!newName
|
||||
);
|
||||
}
|
||||
document.documentElement.removeAttribute('zen-renaming-tab');
|
||||
|
||||
// Maybe add some confetti here?!?
|
||||
gZenUIManager.motion.animate(
|
||||
|
@ -1024,8 +1044,12 @@ var gZenVerticalTabsManager = {
|
|||
duration: 0.25,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this._tabEdited.querySelector('.tab-editor-container').remove();
|
||||
const editorContainer = this._tabEdited.querySelector('.tab-editor-container');
|
||||
if (editorContainer) {
|
||||
editorContainer.remove();
|
||||
}
|
||||
label.classList.remove('tab-label-container-editing');
|
||||
|
||||
this._tabEdited = null;
|
||||
|
@ -1035,34 +1059,40 @@ var gZenVerticalTabsManager = {
|
|||
},
|
||||
|
||||
renameTabStart(event) {
|
||||
const isTab = !!event.target.closest('.tabbrowser-tab');
|
||||
if (
|
||||
this._tabEdited ||
|
||||
!Services.prefs.getBoolPref('zen.tabs.rename-tabs') ||
|
||||
Services.prefs.getBoolPref('browser.tabs.closeTabByDblclick') ||
|
||||
((!Services.prefs.getBoolPref('zen.tabs.rename-tabs') ||
|
||||
Services.prefs.getBoolPref('browser.tabs.closeTabByDblclick')) &&
|
||||
isTab) ||
|
||||
!gZenVerticalTabsManager._prefsSidebarExpanded
|
||||
)
|
||||
return;
|
||||
this._tabEdited = event.target.closest('.tabbrowser-tab');
|
||||
if (
|
||||
!this._tabEdited ||
|
||||
!this._tabEdited.pinned ||
|
||||
this._tabEdited.hasAttribute('zen-essential')
|
||||
((!this._tabEdited.pinned || this._tabEdited.hasAttribute('zen-essential')) && isTab)
|
||||
) {
|
||||
this._tabEdited = null;
|
||||
return;
|
||||
}
|
||||
event.stopPropagation();
|
||||
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');
|
||||
|
||||
if (isTab) {
|
||||
const container = window.MozXULElement.parseXULToFragment(`
|
||||
<vbox class="tab-label-container tab-editor-container" flex="1" align="start" pack="center"></vbox>
|
||||
`);
|
||||
label.after(container);
|
||||
const containerHtml = this._tabEdited.querySelector('.tab-editor-container');
|
||||
}
|
||||
const containerHtml = isTab
|
||||
? this._tabEdited.querySelector('.tab-editor-container')
|
||||
: this._tabEdited.parentNode;
|
||||
const input = document.createElement('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));
|
||||
|
||||
containerHtml.appendChild(input);
|
||||
|
@ -1077,8 +1107,16 @@ var gZenVerticalTabsManager = {
|
|||
return;
|
||||
}
|
||||
document.documentElement.removeAttribute('zen-renaming-tab');
|
||||
this._tabEdited.querySelector('.tab-editor-container').remove();
|
||||
const label = this._tabEdited.querySelector('.tab-label-container-editing');
|
||||
const editorContainer = this._tabEdited.querySelector('.tab-editor-container');
|
||||
let input = document.getElementById('tab-label-input');
|
||||
input.remove();
|
||||
if (editorContainer) {
|
||||
editorContainer.remove();
|
||||
}
|
||||
const isTab = !!this._tabEdited.closest('.tabbrowser-tab');
|
||||
const label = isTab
|
||||
? this._tabEdited.querySelector('.tab-label-container-editing')
|
||||
: this._tabEdited;
|
||||
label.classList.remove('tab-label-container-editing');
|
||||
|
||||
this._tabEdited = null;
|
||||
|
|
|
@ -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 = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
|
@ -87,6 +90,9 @@ class ZenUIMigration {
|
|||
}
|
||||
|
||||
_migrateV4(win) {
|
||||
if (AppConstants.platform === 'linux') {
|
||||
return;
|
||||
}
|
||||
Services.prefs.setBoolPref(
|
||||
'browser.tabs.unloadOnLowMemory',
|
||||
Services.prefs.getBoolPref('zen.tab-unloader.enabled', true)
|
||||
|
|
|
@ -78,16 +78,6 @@
|
|||
transition: background-color var(--inactive-window-transition);
|
||||
}
|
||||
|
||||
@media (not (-moz-windows-mica)) and -moz-pref('zen.view.grey-out-inactive-windows') {
|
||||
transition: color var(--inactive-window-transition);
|
||||
:root:not([zen-welcome-stage]) &:-moz-window-inactive {
|
||||
color: var(--toolbox-textcolor-inactive);
|
||||
&::before {
|
||||
background-color: var(--toolbox-bgcolor-inactive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#zen-browser-grain {
|
||||
content: '';
|
||||
width: 100%;
|
||||
|
@ -131,9 +121,7 @@
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
@media (-moz-windows-accent-color-in-titlebar) and (-moz-windows-mica) {
|
||||
background-color: ActiveCaption;
|
||||
color: CaptionText;
|
||||
@media -moz-pref('zen.view.grey-out-inactive-windows') {
|
||||
transition: background-color var(--inactive-window-transition);
|
||||
&:-moz-window-inactive {
|
||||
background-color: InactiveCaption;
|
||||
|
@ -264,6 +252,7 @@
|
|||
opacity: 0;
|
||||
transition: opacity 0.1s ease-in-out;
|
||||
pointer-events: none;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
|
|
|
@ -35,17 +35,13 @@
|
|||
--uc-permission-item-margin-block: 4px;
|
||||
--uc-permission-item-padding-inline: 16px;
|
||||
--zen-panel-separator-width: 1px;
|
||||
|
||||
--zen-contextmenu-menuitem-padding-inline: 10px;
|
||||
--zen-contextmenu-menuicon-margin-inline: 12px;
|
||||
--zen-contextmenu-menuitem-margin: 0px 2px;
|
||||
}
|
||||
|
||||
menupopup,
|
||||
panel {
|
||||
--panel-background: var(--arrowpanel-background);
|
||||
--panel-border-radius: var(--zen-native-inner-radius);
|
||||
--menuitem-padding: 6px 5px !important;
|
||||
--menuitem-padding: 6px !important;
|
||||
}
|
||||
|
||||
/* split-view popup */
|
||||
|
@ -247,11 +243,6 @@ panel {
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
menupopup::part(content),
|
||||
panel::part(content) {
|
||||
border: var(--zen-appcontent-border);
|
||||
}
|
||||
|
||||
menupopup,
|
||||
panel {
|
||||
box-shadow: none;
|
||||
|
|
|
@ -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 = {};
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
|
@ -88,10 +91,6 @@ var gZenCompactModeManager = {
|
|||
this.preference === value ||
|
||||
document.documentElement.hasAttribute('zen-compact-animating')
|
||||
) {
|
||||
if (typeof this._wasInCompactMode !== 'undefined') {
|
||||
// We wont do anything with it anyway, so we remove it
|
||||
delete this._wasInCompactMode;
|
||||
}
|
||||
// We dont want the user to be able to spam the button
|
||||
return value;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
@ -11,24 +14,10 @@
|
|||
});
|
||||
|
||||
class ZenDownloadAnimation extends ZenDOMOperatedFeature {
|
||||
#lastClickPosition = null;
|
||||
|
||||
async init() {
|
||||
this.#setupClickListener();
|
||||
await this.#setupDownloadListeners();
|
||||
}
|
||||
|
||||
#setupClickListener() {
|
||||
document.addEventListener('mousedown', this.#handleClick.bind(this), true);
|
||||
}
|
||||
|
||||
#handleClick(event) {
|
||||
this.#lastClickPosition = {
|
||||
clientX: event.clientX,
|
||||
clientY: event.clientY,
|
||||
};
|
||||
}
|
||||
|
||||
async #setupDownloadListeners() {
|
||||
try {
|
||||
const list = await Downloads.getList(Downloads.ALL);
|
||||
|
@ -50,14 +39,14 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (!this.#lastClickPosition) {
|
||||
if (!gZenUIManager._lastClickPosition) {
|
||||
console.warn(
|
||||
`[${ZenDownloadAnimation.name}] No recent click position available for animation`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.#animateDownload(this.#lastClickPosition);
|
||||
this.#animateDownload(gZenUIManager._lastClickPosition);
|
||||
}
|
||||
|
||||
#animateDownload(startPosition) {
|
||||
|
|
|
@ -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 {
|
||||
constructor() {
|
||||
|
|
|
@ -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 {
|
||||
_animating = false;
|
||||
|
@ -151,8 +154,8 @@
|
|||
this.animatingOpen = true;
|
||||
this._animating = true;
|
||||
|
||||
const initialX = data.x;
|
||||
const initialY = data.y;
|
||||
const initialX = data.clientX;
|
||||
const initialY = data.clientY;
|
||||
const initialWidth = data.width;
|
||||
const initialHeight = data.height;
|
||||
|
||||
|
@ -594,8 +597,10 @@
|
|||
this.openGlance(
|
||||
{
|
||||
url: undefined,
|
||||
x: browserRect.width / 2,
|
||||
y: browserRect.height / 2,
|
||||
...(gZenUIManager._lastClickPosition || {
|
||||
clientX: browserRect.width / 2,
|
||||
clientY: browserRect.height / 2,
|
||||
}),
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
|
@ -636,7 +641,12 @@
|
|||
.classList.remove('zen-glance-background');
|
||||
this.#currentParentTab._visuallySelected = false;
|
||||
this.hideSidebarButtons();
|
||||
if (forSplit) {
|
||||
this.finishOpeningGlance();
|
||||
return;
|
||||
}
|
||||
if (gReduceMotion || forSplit) {
|
||||
gZenViewSplitter.deactivateCurrentSplitView();
|
||||
this.finishOpeningGlance();
|
||||
return;
|
||||
}
|
||||
|
@ -651,6 +661,7 @@
|
|||
type: 'spring',
|
||||
}
|
||||
);
|
||||
gZenViewSplitter.deactivateCurrentSplitView();
|
||||
this.finishOpeningGlance();
|
||||
}
|
||||
|
||||
|
@ -675,8 +686,8 @@
|
|||
const rect = event.target.getBoundingClientRect();
|
||||
const data = {
|
||||
url: event.target._placesNode.uri,
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
clientX: rect.left,
|
||||
clientY: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
};
|
||||
|
@ -705,8 +716,8 @@
|
|||
}
|
||||
|
||||
getTabOrGlanceParent(tab) {
|
||||
if (tab?.hasAttribute('glance-id')) {
|
||||
const parentTab = this.#glances.get(tab.getAttribute('glance-id')).parentTab;
|
||||
if (tab?.hasAttribute('glance-id') && this.#glances) {
|
||||
const parentTab = this.#glances.get(tab.getAttribute('glance-id'))?.parentTab;
|
||||
if (parentTab) {
|
||||
return parentTab;
|
||||
}
|
||||
|
@ -734,18 +745,43 @@
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
onSearchSelectCommand(where) {
|
||||
if (where !== 'tab') {
|
||||
return;
|
||||
}
|
||||
const currentTab = gBrowser.selectedTab;
|
||||
const parentTab = currentTab.owner;
|
||||
if (!parentTab) {
|
||||
return;
|
||||
}
|
||||
// Open a new glance if the current tab is a glance tab
|
||||
const browserRect = gBrowser.tabbox.getBoundingClientRect();
|
||||
this.openGlance(
|
||||
{
|
||||
url: undefined,
|
||||
...(gZenUIManager._lastClickPosition || {
|
||||
clientX: browserRect.width / 2,
|
||||
clientY: browserRect.height / 2,
|
||||
}),
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
currentTab,
|
||||
parentTab
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
window.gZenGlanceManager = new ZenGlanceManager();
|
||||
|
||||
function registerWindowActors() {
|
||||
if (Services.prefs.getBoolPref('zen.glance.enabled', true)) {
|
||||
gZenActorsManager.addJSWindowActor('ZenGlance', {
|
||||
parent: {
|
||||
esModuleURI: 'chrome://browser/content/zen-components/actors/ZenGlanceParent.sys.mjs',
|
||||
esModuleURI: 'resource:///actors/ZenGlanceParent.sys.mjs',
|
||||
},
|
||||
child: {
|
||||
esModuleURI: 'chrome://browser/content/zen-components/actors/ZenGlanceChild.sys.mjs',
|
||||
esModuleURI: 'resource:///actors/ZenGlanceChild.sys.mjs',
|
||||
events: {
|
||||
DOMContentLoaded: {},
|
||||
keydown: {
|
||||
|
@ -755,9 +791,9 @@
|
|||
},
|
||||
allFrames: true,
|
||||
matches: ['*://*/*'],
|
||||
enablePreference: 'zen.glance.enabled',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerWindowActors();
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -71,8 +74,8 @@ export class ZenGlanceChild extends JSWindowActorChild {
|
|||
const rect = target.getBoundingClientRect();
|
||||
this.sendAsyncMessage('ZenGlance:OpenGlance', {
|
||||
url,
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
clientX: rect.left,
|
||||
clientY: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
constructor() {
|
||||
super();
|
||||
|
|
|
@ -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 = {};
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
|
|
735
src/zen/mods/ZenMods.mjs
Normal file
735
src/zen/mods/ZenMods.mjs
Normal 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',
|
||||
],
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
},
|
||||
};
|
|
@ -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',
|
||||
],
|
||||
});
|
142
src/zen/mods/actors/ZenModsMarketplaceChild.sys.mjs
Normal file
142
src/zen/mods/actors/ZenModsMarketplaceChild.sys.mjs
Normal 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 });
|
||||
}
|
||||
}
|
65
src/zen/mods/actors/ZenModsMarketplaceParent.sys.mjs
Normal file
65
src/zen/mods/actors/ZenModsMarketplaceParent.sys.mjs
Normal 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 });
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,7 @@
|
|||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
FINAL_TARGET_FILES.actors += [
|
||||
"actors/ZenThemeMarketplaceChild.sys.mjs",
|
||||
"actors/ZenThemeMarketplaceParent.sys.mjs",
|
||||
"actors/ZenModsMarketplaceChild.sys.mjs",
|
||||
"actors/ZenModsMarketplaceParent.sys.mjs",
|
||||
]
|
||||
|
|
|
@ -104,6 +104,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
);
|
||||
|
||||
window.addEventListener('TabClose', this.handleTabClose.bind(this));
|
||||
window.addEventListener('TabBrowserDiscarded', this.handleTabBrowserDiscarded.bind(this));
|
||||
window.addEventListener('TabSelect', this.onTabSelect.bind(this));
|
||||
this.initializeContextMenu();
|
||||
this.insertIntoContextMenu();
|
||||
|
@ -150,6 +151,22 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
this.removeTabFromGroup(tab, groupIndex, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event - The event that triggered the tab browser discard.
|
||||
* @description Handles the tab browser discard event.
|
||||
*/
|
||||
async handleTabBrowserDiscarded(event) {
|
||||
const tab = event.target;
|
||||
if (tab.group?.hasAttribute('split-view-group')) {
|
||||
gBrowser.explicitUnloadTabs(tab.group.tabs);
|
||||
for (const t of tab.group.tabs) {
|
||||
if (t.glanceTab) {
|
||||
gBrowser.explicitUnloadTabs([t.glanceTab]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event - The event that triggered the tab select.
|
||||
* @description Handles the tab select event.
|
||||
|
@ -193,6 +210,10 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
const node = this.getSplitNodeFromTab(tab);
|
||||
const toUpdate = this.removeNode(node);
|
||||
this.applyGridLayout(toUpdate);
|
||||
// Select next tab if the removed tab was selected
|
||||
if (gBrowser.selectedTab === tab) {
|
||||
gBrowser.selectedTab = group.tabs[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -876,7 +897,9 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
tabCount: window.gBrowser.selectedTabs.length,
|
||||
});
|
||||
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.target.ownerDocument.location.href;
|
||||
const currentTab = gZenGlanceManager.getTabOrGlanceParent(window.gBrowser.selectedTab);
|
||||
const newTab = this.openAndSwitchToTab(url);
|
||||
this.splitTabs([currentTab, newTab]);
|
||||
const newTab = this.openAndSwitchToTab(url, { inBackground: false });
|
||||
this.splitTabs([currentTab, newTab], undefined, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -976,7 +999,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
tab = tab.parentNode.closest('.tabbrowser-tab');
|
||||
console.assert(tab, 'Tab not found for zen-glance-tab');
|
||||
}
|
||||
this.updateSplitViewButton(!tab?.splitView);
|
||||
if (tab) {
|
||||
this.updateSplitView(tab);
|
||||
tab.linkedBrowser.docShellIsActive = true;
|
||||
|
@ -1008,7 +1030,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
* Splits the given tabs.
|
||||
*
|
||||
* @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) {
|
||||
// TODO: Add support for splitting essential tabs
|
||||
|
@ -1074,7 +1096,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
};
|
||||
this._data.push(splitData);
|
||||
if (!this._sessionRestoring) {
|
||||
window.gBrowser.selectedTab = tabs[0];
|
||||
window.gBrowser.selectedTab = tabs[initialIndex] ?? tabs[0];
|
||||
}
|
||||
|
||||
// Add tabs to the split view group
|
||||
|
@ -1110,7 +1132,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
|
||||
if (oldView === newView) return;
|
||||
if (newView < 0 && oldView >= 0) {
|
||||
this.updateSplitViewButton(true);
|
||||
this.deactivateCurrentSplitView();
|
||||
return;
|
||||
}
|
||||
|
@ -1122,6 +1143,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
* Deactivates the split view.
|
||||
*/
|
||||
deactivateCurrentSplitView() {
|
||||
if (this.currentView < 0) return;
|
||||
this.setTabsDocShellState(this._data[this.currentView].tabs, false);
|
||||
for (const tab of this._data[this.currentView].tabs) {
|
||||
const container = tab.linkedBrowser.closest('.browserSidebarContainer');
|
||||
|
@ -1129,9 +1151,9 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
}
|
||||
this.removeSplitters();
|
||||
this.tabBrowserPanel.removeAttribute('zen-split-view');
|
||||
this.updateSplitViewButton(true);
|
||||
this.currentView = -1;
|
||||
this.toggleWrapperDisplay(false);
|
||||
this.maybeDisableOpeningTabOnSplitView();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1153,7 +1175,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
|
||||
this.tabBrowserPanel.setAttribute('zen-split-view', 'true');
|
||||
|
||||
this.updateSplitViewButton(false);
|
||||
this.applyGridToTabs(splitData.tabs);
|
||||
this.applyGridLayout(splitData.layoutTree);
|
||||
this.setTabsDocShellState(splitData.tabs, true);
|
||||
|
@ -1290,6 +1311,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
});
|
||||
}
|
||||
});
|
||||
this.maybeDisableOpeningTabOnSplitView();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1480,20 +1502,6 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
container.style.inset = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the split view button visibility.
|
||||
*
|
||||
* @param {boolean} hidden - Indicates if the button should be hidden.
|
||||
*/
|
||||
updateSplitViewButton(hidden) {
|
||||
const button = document.getElementById('zen-split-views-box');
|
||||
if (hidden) {
|
||||
button?.setAttribute('hidden', 'true');
|
||||
} else {
|
||||
button?.removeAttribute('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI of the panel.
|
||||
*
|
||||
|
@ -1668,6 +1676,15 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
if (splitGroup && (!draggedTab.group || draggedTab.group !== splitGroup)) {
|
||||
this._moveTabsToContainer([draggedTab], droppedOnTab);
|
||||
gBrowser.moveTabToGroup(draggedTab, splitGroup);
|
||||
if (hoverSide === 'left' || hoverSide === 'top') {
|
||||
try {
|
||||
splitGroup.tabs[0].before(draggedTab);
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`Failed to move tab ${draggedTab.id} before ${splitGroup.tabs[0].id}: ${e}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const droppedOnSplitNode = this.getSplitNodeFromTab(droppedOnTab);
|
||||
|
@ -1857,6 +1874,26 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
|||
this.onLocationChange(gBrowser.selectedTab.linkedBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
maybeDisableOpeningTabOnSplitView() {
|
||||
const shouldBeDisabled = !this.canOpenLinkInSplitView();
|
||||
document
|
||||
.getElementById('cmd_zenSplitViewLinkInNewTab')
|
||||
.setAttribute('disabled', shouldBeDisabled);
|
||||
document.getElementById('zen-glance-sidebar-split').setAttribute('disabled', shouldBeDisabled);
|
||||
}
|
||||
|
||||
canOpenLinkInSplitView() {
|
||||
const currentView = this.currentView;
|
||||
if (currentView < 0) {
|
||||
return true;
|
||||
}
|
||||
const group = this._data[currentView];
|
||||
if (!group || group.tabs.length >= this.MAX_TABS) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
window.gZenViewSplitter = new ZenViewSplitter();
|
||||
|
|
|
@ -41,6 +41,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
#zen-splitview-dropzone {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
#tabbrowser-tabpanels[zen-split-view='true']:not([zen-split-resizing]) > [zen-split='true'] {
|
||||
transition: inset 0.09s ease-out !important;
|
||||
& browser {
|
||||
|
@ -70,15 +74,8 @@
|
|||
margin-right: calc(-1 * var(--zen-split-column-gap));
|
||||
}
|
||||
|
||||
#tabbrowser-tabpanels:has(> [zen-split='true']),
|
||||
#zen-splitview-overlay {
|
||||
:root:not([zen-compact-mode='true']):not([customizing]) & {
|
||||
@media -moz-pref('zen.view.compact.hide-toolbar') {
|
||||
& {
|
||||
:root:not([customizing]) #zen-splitview-overlay {
|
||||
margin-top: calc(var(--zen-split-column-gap) * -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#tabbrowser-tabpanels[zen-split-view] {
|
||||
|
@ -112,10 +109,6 @@
|
|||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
#zen-split-views-box:not([hidden='true']) {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.zen-view-splitter-header-container {
|
||||
position: absolute;
|
||||
top: calc(var(--zen-split-column-gap) / -2);
|
||||
|
|
|
@ -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 = {};
|
||||
|
||||
|
@ -49,8 +52,6 @@
|
|||
}
|
||||
|
||||
class ZenPinnedTabManager extends ZenDOMOperatedFeature {
|
||||
MAX_ESSENTIALS_TABS = 12;
|
||||
|
||||
async init() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
|
@ -87,10 +88,14 @@
|
|||
|
||||
onTabIconChanged(tab, url = null) {
|
||||
const iconUrl = url ?? tab.iconImage.src;
|
||||
if (!iconUrl) {
|
||||
if (!iconUrl && tab.hasAttribute('zen-pin-id')) {
|
||||
try {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await this.promisePinnedCacheInitialized;
|
||||
const pin = this._pinsCache?.find(
|
||||
(pin) => pin.uuid === tab.getAttribute('zen-pin-id')
|
||||
);
|
||||
let favicon = await PlacesUtils.favicons.getFaviconForPage(
|
||||
Services.io.newURI(pin.url)
|
||||
);
|
||||
|
@ -156,6 +161,9 @@
|
|||
await gZenWorkspaces.promiseSectionsInitialized;
|
||||
await this._initializePinsCache();
|
||||
await this._initializePinnedTabs(init);
|
||||
if (init) {
|
||||
this._resolveInitializedPinnedCache();
|
||||
}
|
||||
}
|
||||
|
||||
async _initializePinsCache() {
|
||||
|
@ -659,7 +667,7 @@
|
|||
for (let i = 0; i < tabs.length; i++) {
|
||||
let tab = tabs[i];
|
||||
const section = gZenWorkspaces.getEssentialsSection(tab);
|
||||
if (section.children.length >= this.MAX_ESSENTIALS_TABS) {
|
||||
if (!this.canEssentialBeAdded(tab)) {
|
||||
movedAll = false;
|
||||
continue;
|
||||
}
|
||||
|
@ -791,7 +799,7 @@
|
|||
document.getElementById('context_zen-add-essential').hidden =
|
||||
contextTab.getAttribute('zen-essential') ||
|
||||
!!contextTab.group ||
|
||||
gBrowser._numZenEssentials >= this.MAX_ESSENTIALS_TABS;
|
||||
!this.canEssentialBeAdded(contextTab);
|
||||
document.getElementById('context_zen-remove-essential').hidden =
|
||||
!contextTab.getAttribute('zen-essential');
|
||||
document.getElementById('context_unpinTab').hidden =
|
||||
|
@ -943,7 +951,7 @@
|
|||
} else {
|
||||
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() {
|
||||
|
@ -1004,6 +1012,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
canEssentialBeAdded(tab) {
|
||||
return (
|
||||
!(
|
||||
(tab.getAttribute('usercontextid') || 0) !=
|
||||
gZenWorkspaces.getActiveWorkspaceFromCache().containerTabId &&
|
||||
gZenWorkspaces.containerSpecificEssentials
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
applyDragoverClass(event, draggedTab) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
|
@ -1037,10 +1055,7 @@
|
|||
shouldAddDragOverElement = true;
|
||||
}
|
||||
} else if (essentialTabsTarget) {
|
||||
if (
|
||||
!draggedTab.hasAttribute('zen-essential') &&
|
||||
gBrowser._numZenEssentials < this.MAX_ESSENTIALS_TABS
|
||||
) {
|
||||
if (!draggedTab.hasAttribute('zen-essential') && this.canEssentialBeAdded(draggedTab)) {
|
||||
shouldAddDragOverElement = true;
|
||||
isVertical = false;
|
||||
}
|
||||
|
@ -1117,4 +1132,8 @@
|
|||
}
|
||||
|
||||
window.gZenPinnedTabManager = new ZenPinnedTabManager();
|
||||
|
||||
gZenPinnedTabManager.promisePinnedCacheInitialized = new Promise((resolve) => {
|
||||
gZenPinnedTabManager._resolveInitializedPinnedCache = resolve;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
async init() {
|
||||
await this._ensureTable();
|
||||
|
@ -106,8 +109,8 @@ var ZenPinnedTabsStorage = {
|
|||
`
|
||||
INSERT OR REPLACE INTO zen_pins (
|
||||
uuid, title, url, container_id, workspace_uuid, position,
|
||||
is_essential, is_group, parent_uuid, created_at, updated_at,
|
||||
edited_title
|
||||
is_essential, is_group, parent_uuid, edited_title, created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
:uuid, :title, :url, :container_id, :workspace_uuid, :position,
|
||||
:is_essential, :is_group, :parent_uuid, :edited_title,
|
||||
|
|
|
@ -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);
|
||||
z-index: 1;
|
||||
|
||||
|
|
|
@ -356,6 +356,7 @@
|
|||
margin-block: 0 !important;
|
||||
margin-inline: 0 !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: calc(var(--border-radius-medium) - 2px);
|
||||
}
|
||||
/* Adjust padding for content */
|
||||
& .tab-content {
|
||||
|
@ -374,6 +375,27 @@
|
|||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
& .tab-audio-button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
&[soundplaying]:not([muted]) .tab-icon-stack::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 110%;
|
||||
height: 110%;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 1;
|
||||
background: url('chrome://browser/content/zen-images/note-indicator.svg') no-repeat;
|
||||
top: -70%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.8s ease;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Essentials Glance Tab Specifics (Floating) --- */
|
||||
|
@ -1239,7 +1261,7 @@
|
|||
|
||||
padding-bottom: var(--zen-toolbox-padding);
|
||||
overflow: hidden;
|
||||
gap: calc(var(--zen-toolbox-padding) - 2px);
|
||||
gap: 4px;
|
||||
transition:
|
||||
max-height 0.3s ease-out,
|
||||
grid-template-columns 0.3s ease-out;
|
||||
|
|
|
@ -9,8 +9,8 @@ function openGlanceOnTab(callback, close = true) {
|
|||
gZenGlanceManager
|
||||
.openGlance({
|
||||
url: 'https://example.com',
|
||||
x: 0,
|
||||
y: 0,
|
||||
clientX: 0,
|
||||
clientY: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
|
|
|
@ -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 += [
|
||||
"compact_mode/browser.toml",
|
||||
|
|
|
@ -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"
|
||||
SOURCES += [
|
||||
|
|
|
@ -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 += [
|
||||
"nsIZenCommonUtils.idl",
|
||||
|
|
|
@ -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"
|
||||
SOURCES += [
|
||||
|
|
|
@ -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 += [
|
||||
"common",
|
||||
|
|
|
@ -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 {
|
||||
static get markup() {
|
||||
|
@ -6,7 +9,7 @@
|
|||
<hbox class="zen-current-workspace-indicator-icon"></hbox>
|
||||
<hbox class="zen-current-workspace-indicator-name"></hbox>
|
||||
</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">
|
||||
<html:div class="vertical-pinned-tabs-container-separator"></html:div>
|
||||
</vbox>
|
||||
|
|
|
@ -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 {
|
||||
constructor() {
|
||||
|
|
|
@ -42,11 +42,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||
this._resolveInitialized = resolve;
|
||||
});
|
||||
|
||||
workspaceIndicatorXUL = `
|
||||
<hbox class="zen-current-workspace-indicator-icon"></hbox>
|
||||
<hbox class="zen-current-workspace-indicator-name"></hbox>
|
||||
`;
|
||||
|
||||
async waitForPromises() {
|
||||
if (this.privateWindowOrDisabled) {
|
||||
return;
|
||||
|
@ -858,6 +853,9 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||
console.error('gZenWorkspaces: Error initializing theme picker', e);
|
||||
}
|
||||
this.onWindowResize();
|
||||
if (window.gZenSessionStore) {
|
||||
await gZenSessionStore.promiseInitialized;
|
||||
}
|
||||
await this._selectStartPage();
|
||||
this._fixTabPositions();
|
||||
this._resolveInitialized();
|
||||
|
@ -915,7 +913,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||
) {
|
||||
this.log(`Found tab to select: ${this._tabToSelect}, ${tabs.length}`);
|
||||
setTimeout(() => {
|
||||
gBrowser.selectedTab = tabs[this._tabToSelect];
|
||||
gBrowser.selectedTab = gZenGlanceManager.getTabOrGlanceParent(tabs[this._tabToSelect]);
|
||||
this._removedByStartupPage = true;
|
||||
gBrowser.removeTab(this._tabToRemoveForEmpty, {
|
||||
skipSessionStore: true,
|
||||
|
@ -2325,7 +2323,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||
existingTransform = container.style.transform;
|
||||
}
|
||||
if (shouldAnimate) {
|
||||
container.style.transform = newTransform;
|
||||
container.style.transform = existingTransform;
|
||||
animations.push(
|
||||
gZenUIManager.motion.animate(
|
||||
container,
|
||||
|
@ -2542,8 +2540,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
setTimeout(gURLBar.formatValue.bind(gURLBar), 0);
|
||||
}
|
||||
|
||||
async _fixCtrlTabBehavior() {
|
||||
|
@ -2941,6 +2937,9 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||
}
|
||||
|
||||
_initializeWorkspaceTabContextMenus() {
|
||||
if (this.privateWindowOrDisabled) {
|
||||
return;
|
||||
}
|
||||
const menu = document.createXULElement('menu');
|
||||
menu.setAttribute('id', 'context-zen-change-workspace-tab');
|
||||
menu.setAttribute('data-l10n-id', 'context-zen-change-workspace-tab');
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"binaryName": "zen",
|
||||
"version": {
|
||||
"product": "firefox",
|
||||
"version": "138.0.4",
|
||||
"version": "139.0",
|
||||
"candidate": "139.0"
|
||||
},
|
||||
"buildOptions": {
|
||||
|
@ -19,7 +19,7 @@
|
|||
"brandShortName": "Zen",
|
||||
"brandFullName": "Zen Browser",
|
||||
"release": {
|
||||
"displayVersion": "1.12.8b",
|
||||
"displayVersion": "1.12.9b",
|
||||
"github": {
|
||||
"repo": "zen-browser/desktop"
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue