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() { console.info('[ZenThemesImporter]: Initializing Zen Themes Importer'); try { window.SessionStore.promiseInitialized.then(async () => { if (Services.prefs.getBoolPref('zen.themes.disable-all', false)) { console.log('[ZenThemesImporter]: Disabling all themes.'); return; } const themes = await this.getEnabledThemes(); const themesWithPreferences = await Promise.all( themes.map(async (theme) => { const preferences = await ZenThemesCommon.getThemePreferences(theme); return { name: theme.name, enabled: theme.enabled, preferences, }; }) ); this.writeToDom(themesWithPreferences); await this.insertStylesheet(); }); console.info('[ZenThemesImporter]: Zen Themes imported'); } catch (e) { console.error('[ZenThemesImporter]: Error importing Zen Themes: ', e); } Services.prefs.addObserver('zen.themes.updated-value-observer', this.rebuildThemeStylesheet.bind(this), false); Services.prefs.addObserver('zen.themes.disable-all', this.handleDisableThemes.bind(this), false); } get sss() { if (!this._sss) { this._sss = Cc['@mozilla.org/content/style-sheet-service;1'].getService(Ci.nsIStyleSheetService); } return this._sss; } get styleSheetPath() { return PathUtils.join(PathUtils.profileDir, 'chrome', 'zen-themes.css'); } async handleDisableThemes() { if (Services.prefs.getBoolPref('zen.themes.disable-all', false)) { console.log('[ZenThemesImporter]: Disabling themes module.'); await this.removeStylesheet(); } else { console.log('[ZenThemesImporter]: Enabling themes module.'); await this.rebuildThemeStylesheet(); } } get styleSheetURI() { if (!this._styleSheetURI) { this._styleSheetURI = Services.io.newFileURI(new FileUtils.File(this.styleSheetPath)); } return this._styleSheetURI; } getStylesheetURIForTheme(theme) { return Services.io.newFileURI(new FileUtils.File(PathUtils.join(ZenThemesCommon.getThemeFolder(theme.id), 'chrome.css'))); } async insertStylesheet() { if (await IOUtils.exists(this.styleSheetPath)) { await this.sss.loadAndRegisterSheet(this.styleSheetURI, this.sss.AGENT_SHEET); } if (this.sss.sheetRegistered(this.styleSheetURI, this.sss.AGENT_SHEET)) { console.debug('[ZenThemesImporter]: Sheet successfully registered'); } } async removeStylesheet() { await this.sss.unregisterSheet(this.styleSheetURI, this.sss.AGENT_SHEET); await IOUtils.remove(this.styleSheetPath, { ignoreAbsent: true }); if (!this.sss.sheetRegistered(this.styleSheetURI, this.sss.AGENT_SHEET) && !(await IOUtils.exists(this.styleSheetPath))) { console.debug('[ZenThemesImporter]: Sheet successfully unregistered'); } } async rebuildThemeStylesheet() { if (Services.focus.activeWindow !== window) { return; } ZenThemesCommon.themes = null; await this.removeStylesheet(); const themes = await this.getEnabledThemes(); await this.writeStylesheet(themes); const themesWithPreferences = await Promise.all( themes.map(async (theme) => { const preferences = await ZenThemesCommon.getThemePreferences(theme); return { name: theme.name, enabled: theme.enabled, preferences, }; }) ); this.setDefaults(themesWithPreferences); this.writeToDom(themesWithPreferences); await this.insertStylesheet(); } async getEnabledThemes() { const themeObject = await ZenThemesCommon.getThemes(); const themes = Object.values(themeObject).filter((theme) => theme.enabled === undefined || theme.enabled); const themeList = themes.map(({ name }) => name).join(', '); const message = themeList !== '' ? `[ZenThemesImporter]: Loading enabled Zen themes: ${themeList}.` : '[ZenThemesImporter]: No enabled Zen themes.'; console.log(message); return themes; } setDefaults(themesWithPreferences) { for (const { preferences, enabled } of themesWithPreferences) { if (enabled !== undefined && !enabled) { continue; } for (const { type, property, defaultValue } of preferences) { if (defaultValue === undefined) { continue; } switch (type) { case 'checkbox': { const value = Services.prefs.getBoolPref(property, false); if (typeof defaultValue !== 'boolean') { console.log(`[ZenThemesImporter]: Warning, invalid data type received for expected type boolean, skipping.`); continue; } if (!value) { Services.prefs.setBoolPref(property, defaultValue); } break; } default: { const value = Services.prefs.getStringPref(property, 'zen-property-no-saved'); if (typeof defaultValue !== 'string' && typeof defaultValue !== 'number') { console.log(`[ZenThemesImporter]: Warning, invalid data type received (${typeof defaultValue}), skipping.`); continue; } if (value === 'zen-property-no-saved') { Services.prefs.setStringPref(property, defaultValue.toString()); } } } } } } writeToDom(themesWithPreferences) { for (const browser of ZenMultiWindowFeature.browsers) { for (const { enabled, preferences, name } of themesWithPreferences) { const sanitizedName = `theme-${name?.replaceAll(/\s/g, '-')?.replaceAll(/[^A-z_-]+/g, '')}`; if (enabled !== undefined && !enabled) { const element = browser.document.getElementById(sanitizedName); if (element) { element.remove(); } for (const { property } of preferences.filter(({ type }) => type !== 'checkbox')) { const sanitizedProperty = property?.replaceAll(/\./g, '-'); browser.document.querySelector(':root').style.removeProperty(`--${sanitizedProperty}`); } continue; } for (const { property, type } of preferences) { const value = Services.prefs.getStringPref(property, ''); const sanitizedProperty = property?.replaceAll(/\./g, '-'); switch (type) { case 'dropdown': { if (value !== '') { let element = browser.document.getElementById(sanitizedName); if (!element) { element = browser.document.createElement('div'); element.style.display = 'none'; element.setAttribute('id', sanitizedName); browser.document.body.appendChild(element); } element.setAttribute(sanitizedProperty, value); } break; } case 'string': { if (value === '') { browser.document.querySelector(':root').style.removeProperty(`--${sanitizedProperty}`); } else { browser.document.querySelector(':root').style.setProperty(`--${sanitizedProperty}`, value); } break; } default: { } } } } } } async writeStylesheet(themeList = []) { const themes = []; ZenThemesCommon.themes = null; for (let theme of themeList) { theme._chromeURL = this.getStylesheetURIForTheme(theme).spec; themes.push(theme); } await gZenStylesheetManager.writeStylesheet(this.styleSheetPath, themes); } })(); gZenActorsManager.addJSWindowActor("ZenThemeMarketplace", { parent: { esModuleURI: "chrome://browser/content/zen-components/actors/ZenThemeMarketplaceParent.sys.mjs", }, child: { esModuleURI: "chrome://browser/content/zen-components/actors/ZenThemeMarketplaceChild.sys.mjs", events: { DOMContentLoaded: {}, }, }, matches: ["https://*.zen-browser.app/*", "about:preferences"], allFrames: true, });