diff --git a/src/background/user-storage.ts b/src/background/user-storage.ts index 54612fb3b1f6..298e5032fc94 100644 --- a/src/background/user-storage.ts +++ b/src/background/user-storage.ts @@ -5,7 +5,7 @@ import {PromiseBarrier} from '../utils/promise-barrier'; import {isURLMatched} from '../utils/url'; import {validateSettings} from '../utils/validation'; -import {readSyncStorage, readLocalStorage, writeSyncStorage, writeLocalStorage, removeSyncStorage, removeLocalStorage} from './utils/extension-api'; +import {readManagedStorage, readSyncStorage, readLocalStorage, writeSyncStorage, writeLocalStorage, removeSyncStorage, removeLocalStorage} from './utils/extension-api'; import {logWarn} from './utils/log'; @@ -78,12 +78,7 @@ export default class UserStorage { return settings; } - private static async loadSettingsFromStorage(): Promise { - if (UserStorage.loadBarrier) { - return await UserStorage.loadBarrier.entry(); - } - UserStorage.loadBarrier = new PromiseBarrier(); - + private static async loadSettingsFromStorageWithoutManaged(): Promise { let local = await readLocalStorage(DEFAULT_SETTINGS); if (local.schemeVersion < 2) { @@ -113,10 +108,8 @@ export default class UserStorage { if (local.syncSettings == null) { local.syncSettings = DEFAULT_SETTINGS.syncSettings; } + if (!local.syncSettings) { - UserStorage.migrateAutomationSettings(local); - UserStorage.fillDefaults(local); - UserStorage.loadBarrier.resolve(local); return local; } @@ -126,18 +119,34 @@ export default class UserStorage { local.syncSettings = false; UserStorage.set({syncSettings: false}); UserStorage.saveSyncSetting(false); - UserStorage.loadBarrier.resolve(local); return local; } const {errors: syncCfgErrors} = validateSettings($sync); syncCfgErrors.forEach((err) => logWarn(err)); + return $sync; + } + + private static async loadSettingsFromStorage(): Promise { + if (UserStorage.loadBarrier) { + return await UserStorage.loadBarrier.entry(); + } + UserStorage.loadBarrier = new PromiseBarrier(); - UserStorage.migrateAutomationSettings($sync); - UserStorage.fillDefaults($sync); + let settings = await UserStorage.loadSettingsFromStorageWithoutManaged(); - UserStorage.loadBarrier.resolve($sync); - return $sync; + const managed = await readManagedStorage(settings); + const {errors: managedCfgErrors} = validateSettings(managed); + if (managedCfgErrors.length === 0) { + settings = managed; + } else { + managedCfgErrors.forEach((err) => logWarn(err)); + } + + UserStorage.migrateAutomationSettings(settings); + UserStorage.fillDefaults(settings); + UserStorage.loadBarrier.resolve(settings); + return settings; } static async saveSettings(): Promise { diff --git a/src/background/utils/extension-api.ts b/src/background/utils/extension-api.ts index 6d18fc0919df..6812ac2e4224 100644 --- a/src/background/utils/extension-api.ts +++ b/src/background/utils/extension-api.ts @@ -97,6 +97,19 @@ export async function readLocalStorage(defaults: }); } +export async function readManagedStorage(defaults: T): Promise { + return new Promise((resolve) => { + chrome.storage.managed.get(defaults, (managed: T) => { + if (chrome.runtime.lastError) { + console.error(chrome.runtime.lastError.message); + resolve(defaults); + return; + } + resolve(managed); + }); + }); +} + function prepareSyncStorage(values: T): {[key: string]: any} { for (const key in values) { const value = values[key]; diff --git a/src/managed-storage.json b/src/managed-storage.json new file mode 100644 index 000000000000..e394d0f1ff60 --- /dev/null +++ b/src/managed-storage.json @@ -0,0 +1,304 @@ +{ + + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "schemeVersion": { + "type": "integer" + }, + "enabled": { + "type": "boolean" + }, + "fetchNews": { + "type": "boolean" + }, + "theme": { + "$ref": "Theme" + }, + "presets": { + "type": "array", + "items": { + "$ref": "ThemePreset" + } + }, + "customThemes": { + "type": "array", + "items": { + "$ref": "CustomSiteConfig" + } + }, + "enabledByDefault": { + "type": "boolean" + }, + "enabledFor": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "disabledFor": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "changeBrowserTheme": { + "type": "boolean" + }, + "syncSettings": { + "type": "boolean" + }, + "syncSitesFixes": { + "type": "boolean" + }, + "automation": { + "$ref": "Automation" + }, + "time": { + "$ref": "TimeSettings" + }, + "location": { + "$ref": "LocationSettings" + }, + "previewNewDesign": { + "type": "boolean" + }, + "previewNewestDesign": { + "type": "boolean" + }, + "enableForPDF": { + "type": "boolean" + }, + "enableForProtectedPages": { + "type": "boolean" + }, + "enableContextMenus": { + "type": "boolean" + }, + "detectDarkTheme": { + "type": "boolean" + }, + // Chrome's JSON schema format is weird and doesn't support `definitions` property and thus `#/definitions` references + // https://datatracker.ietf.org/doc/html/draft-zyp-json-schema-03 + // This "property" mimics it + "definitions": { + "type": "object", + "properties": { + "Theme": { + "id": "Theme", + "type": "object", + "properties": { + "mode": { + "$ref": "FilterMode" + }, + "brightness": { + "type": "integer", + "minimum": 0, + "maximum": 200 + }, + "contrast": { + "type": "integer", + "minimum": 0, + "maximum": 200 + }, + "grayscale": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "sepia": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "useFont": { + "type": "boolean" + }, + "fontFamily": { + "type": "string", + "minLength": 1 + }, + "textStroke": { + "type": "number" + }, + "engine": { + "type": "string", + "enum": [ + "cssFilter", + "svgFilter", + "staticTheme", + "dynamicTheme" + ] + }, + "stylesheet": { + "type": "string" + }, + "darkSchemeBackgroundColor": { + "$ref": "HexColor" + }, + "darkSchemeTextColor": { + "$ref": "HexColor" + }, + "lightSchemeBackgroundColor": { + "$ref": "HexColor" + }, + "lightSchemeTextColor": { + "$ref": "HexColor" + }, + "scrollbarColor": { + "$ref": "HexColorOrAuto" + }, + "selectionColor": { + "$ref": "HexColorOrAuto" + }, + "styleSystemControls": { + "type": "boolean" + }, + "lightColorScheme": { + "type": "string", + "minLength": 1 + }, + "darkColorScheme": { + "type": "string", + "minLength": 1 + }, + "immediateModify": { + "type": "boolean" + } + } + }, + "HexColor": { + "id": "HexColor", + "type": "string", + "pattern": "^[0-9a-f]{6}$" + }, + "HexColorOrAuto": { + "id": "HexColorOrAuto", + "type": "string", + "pattern": "^([0-9a-f]{6}|auto)$" + }, + "FilterMode": { + "id": "FilterMode", + "type": "integer", + "enum": [ + 0, + 1 + ] + }, + "ThemePreset": { + "id": "ThemePreset", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "urls": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "theme": { + "$ref": "Theme" + } + }, + "required": [ + "id", + "name", + "urls", + "theme" + ] + }, + "CustomSiteConfig": { + "id": "CustomSiteConfig", + "type": "object", + "properties": { + "url": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "theme": { + "$ref": "Theme" + }, + "builtin": { + "type": "boolean" + } + }, + "required": [ + "url", + "theme" + ] + }, + "Automation": { + "id": "Automation", + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "mode": { + "$ref": "AutomationMode" + }, + "behavior": { + "type": "string", + "enum": [ + "OnOff", + "Scheme" + ] + } + } + }, + "AutomationMode": { + "id": "AutomationMode", + "type": "string", + "enum": [ + "", + "time", + "system", + "location" + ] + }, + "TimeSettings": { + "id": "TimeSettings", + "type": "object", + "properties": { + "activation": { + "$ref": "Time" + }, + "deactivation": { + "$ref": "Time" + } + } + }, + "Time": { + "id": "Time", + "type": "string", + "pattern": "^((0?[0-9])|(1[0-9])|(2[0-3])):([0-5][0-9])$" + }, + "LocationSettings": { + "id": "LocationSettings", + "type": "object", + "properties": { + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + } + } + } + } + } + } +} diff --git a/tasks/bundle-manifest.js b/tasks/bundle-manifest.js index ae29531e67b7..f4058a129f52 100644 --- a/tasks/bundle-manifest.js +++ b/tasks/bundle-manifest.js @@ -4,6 +4,7 @@ import {PLATFORM} from './platform.js'; import * as reload from './reload.js'; import {createTask} from './task.js'; import {readJSON, writeJSON} from './utils.js'; +import {copyFile} from 'node:fs/promises'; async function patchManifest(platform, debug, watch, test) { const manifest = await readJSON(absolutePath('src/manifest.json')); @@ -16,6 +17,11 @@ async function patchManifest(platform, debug, watch, test) { if (platform === PLATFORM.CHROMIUM_MV3) { patched.browser_action = undefined; } + if (platform === PLATFORM.CHROMIUM_MV2 || platform === PLATFORM.CHROMIUM_MV3) { + patched.storage = { + managed_schema: 'managed-storage.json', + }; + } if (debug) { patched.version = '1'; patched.description = `Debug build, platform: ${platform}, watch: ${watch ? 'yes' : 'no'}.`; @@ -42,6 +48,9 @@ async function manifests({platforms, debug, watch, test}) { const manifest = await patchManifest(platform, debug, watch, test); const destDir = getDestDir({debug, platform}); await writeJSON(`${destDir}/manifest.json`, manifest); + if (platform === PLATFORM.CHROMIUM_MV2 || platform === PLATFORM.CHROMIUM_MV3) { + await copyFile(absolutePath('src/managed-storage.json'), `${destDir}/managed-storage.json`); + } } } @@ -49,7 +58,7 @@ const bundleManifestTask = createTask( 'bundle-manifest', manifests, ).addWatcher( - ['src/manifest*.json'], + ['src/manifest*.json', 'src/managed-storage.json'], async (changedFiles, _, buildPlatforms) => { const chrome = changedFiles.some((file) => file.endsWith('manifest.json')); const platforms = {};