fune/browser/components/distribution.sys.mjs
Stanca Serban 708910a8f3 Backed out 5 changesets (bug 1552815) for causing bp-nu bustages in nsFaviconService.cpp.
Backed out changeset 08a2ac7a9834 (bug 1552815)
Backed out changeset bb9854e6ffe7 (bug 1552815)
Backed out changeset 4e06ccb2599e (bug 1552815)
Backed out changeset d28dd2aec9f4 (bug 1552815)
Backed out changeset 392389a8d495 (bug 1552815)
2024-04-24 03:03:10 +03:00

652 lines
19 KiB
JavaScript

/* 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 DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC =
"distribution-customization-complete";
const PREF_CACHED_FILE_EXISTENCE = "distribution.iniFile.exists.value";
const PREF_CACHED_FILE_APPVERSION = "distribution.iniFile.exists.appversion";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});
export function DistributionCustomizer() {}
DistributionCustomizer.prototype = {
// These prefixes must only contain characters
// allowed by PlacesUtils.isValidGuid
BOOKMARK_GUID_PREFIX: "DstB-",
FOLDER_GUID_PREFIX: "DstF-",
get _iniFile() {
// For parallel xpcshell testing purposes allow loading the distribution.ini
// file from the profile folder through an hidden pref.
let loadFromProfile = Services.prefs.getBoolPref(
"distribution.testing.loadFromProfile",
false
);
let iniFile;
try {
iniFile = loadFromProfile
? Services.dirsvc.get("ProfD", Ci.nsIFile)
: Services.dirsvc.get("XREAppDist", Ci.nsIFile);
if (loadFromProfile) {
iniFile.leafName = "distribution";
}
iniFile.append("distribution.ini");
} catch (ex) {}
this.__defineGetter__("_iniFile", () => iniFile);
return iniFile;
},
get _hasDistributionIni() {
if (Services.prefs.prefHasUserValue(PREF_CACHED_FILE_EXISTENCE)) {
let knownForVersion = Services.prefs.getStringPref(
PREF_CACHED_FILE_APPVERSION,
"unknown"
);
// StartupCacheInfo isn't available in xpcshell tests.
if (
knownForVersion == AppConstants.MOZ_APP_VERSION &&
(Cu.isInAutomation ||
Cc["@mozilla.org/startupcacheinfo;1"].getService(
Ci.nsIStartupCacheInfo
).FoundDiskCacheOnInit)
) {
return Services.prefs.getBoolPref(PREF_CACHED_FILE_EXISTENCE);
}
}
let fileExists = this._iniFile.exists();
Services.prefs.setBoolPref(PREF_CACHED_FILE_EXISTENCE, fileExists);
Services.prefs.setStringPref(
PREF_CACHED_FILE_APPVERSION,
AppConstants.MOZ_APP_VERSION
);
this.__defineGetter__("_hasDistributionIni", () => fileExists);
return fileExists;
},
get _ini() {
let ini = null;
try {
if (this._hasDistributionIni) {
ini = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
.getService(Ci.nsIINIParserFactory)
.createINIParser(this._iniFile);
}
} catch (e) {
if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
// We probably had cached the file existence as true,
// but it no longer exists. We could set the new cache
// value here, but let's just invalidate the cache and
// let it be cached by a single code path on the next check.
Services.prefs.clearUserPref(PREF_CACHED_FILE_EXISTENCE);
} else {
// Unable to parse INI.
console.error("Unable to parse distribution.ini");
}
}
this.__defineGetter__("_ini", () => ini);
return this._ini;
},
get _locale() {
const locale = Services.locale.requestedLocale || "en-US";
this.__defineGetter__("_locale", () => locale);
return this._locale;
},
get _language() {
let language = this._locale.split("-")[0];
this.__defineGetter__("_language", () => language);
return this._language;
},
async _removeDistributionBookmarks() {
await lazy.PlacesUtils.bookmarks.fetch(
{ guidPrefix: this.BOOKMARK_GUID_PREFIX },
bookmark => lazy.PlacesUtils.bookmarks.remove(bookmark).catch()
);
await lazy.PlacesUtils.bookmarks.fetch(
{ guidPrefix: this.FOLDER_GUID_PREFIX },
folder => {
lazy.PlacesUtils.bookmarks.remove(folder).catch();
}
);
},
async _parseBookmarksSection(parentGuid, section) {
let keys = Array.from(this._ini.getKeys(section)).sort();
let re = /^item\.(\d+)\.(\w+)\.?(\w*)/;
let items = {};
let defaultIndex = -1;
let maxIndex = -1;
for (let key of keys) {
let m = re.exec(key);
if (m) {
let [, itemIndex, iprop, ilocale] = m;
itemIndex = parseInt(itemIndex);
if (ilocale) {
continue;
}
if (keys.includes(key + "." + this._locale)) {
key += "." + this._locale;
} else if (keys.includes(key + "." + this._language)) {
key += "." + this._language;
}
if (!items[itemIndex]) {
items[itemIndex] = {};
}
items[itemIndex][iprop] = this._ini.getString(section, key);
if (iprop == "type" && items[itemIndex].type == "default") {
defaultIndex = itemIndex;
}
if (maxIndex < itemIndex) {
maxIndex = itemIndex;
}
} else {
dump(`Key did not match: ${key}\n`);
}
}
let prependIndex = 0;
for (let itemIndex = 0; itemIndex <= maxIndex; itemIndex++) {
if (!items[itemIndex]) {
continue;
}
let index = lazy.PlacesUtils.bookmarks.DEFAULT_INDEX;
let item = items[itemIndex];
switch (item.type) {
case "default":
break;
case "folder":
if (itemIndex < defaultIndex) {
index = prependIndex++;
}
let folder = await lazy.PlacesUtils.bookmarks.insert({
type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
guid: lazy.PlacesUtils.generateGuidWithPrefix(
this.FOLDER_GUID_PREFIX
),
parentGuid,
index,
title: item.title,
});
await this._parseBookmarksSection(
folder.guid,
"BookmarksFolder-" + item.folderId
);
break;
case "separator":
if (itemIndex < defaultIndex) {
index = prependIndex++;
}
await lazy.PlacesUtils.bookmarks.insert({
type: lazy.PlacesUtils.bookmarks.TYPE_SEPARATOR,
parentGuid,
index,
});
break;
case "livemark":
// Livemarks are no more supported, instead of a livemark we'll insert
// a bookmark pointing to the site uri, if available.
if (!item.siteLink) {
break;
}
if (itemIndex < defaultIndex) {
index = prependIndex++;
}
await lazy.PlacesUtils.bookmarks.insert({
parentGuid,
index,
title: item.title,
url: item.siteLink,
});
break;
case "bookmark":
default:
if (itemIndex < defaultIndex) {
index = prependIndex++;
}
await lazy.PlacesUtils.bookmarks.insert({
guid: lazy.PlacesUtils.generateGuidWithPrefix(
this.BOOKMARK_GUID_PREFIX
),
parentGuid,
index,
title: item.title,
url: item.link,
});
if (item.icon && item.iconData) {
try {
let faviconURI = Services.io.newURI(item.icon);
lazy.PlacesUtils.favicons.replaceFaviconDataFromDataURL(
faviconURI,
item.iconData,
0,
Services.scriptSecurityManager.getSystemPrincipal()
);
lazy.PlacesUtils.favicons.setAndFetchFaviconForPage(
Services.io.newURI(item.link),
faviconURI,
false,
lazy.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null,
Services.scriptSecurityManager.getSystemPrincipal()
);
} catch (e) {
console.error(e);
}
}
break;
}
}
},
_newProfile: false,
_customizationsApplied: false,
applyCustomizations: function DIST_applyCustomizations() {
this._customizationsApplied = true;
if (!Services.prefs.prefHasUserValue("browser.migration.version")) {
this._newProfile = true;
}
if (!this._ini) {
return this._checkCustomizationComplete();
}
if (!this._prefDefaultsApplied) {
this.applyPrefDefaults();
}
},
_bookmarksApplied: false,
async applyBookmarks() {
let prefs = Services.prefs
.getChildList("distribution.yandex")
.concat(Services.prefs.getChildList("distribution.mailru"))
.concat(Services.prefs.getChildList("distribution.okru"));
if (prefs.length) {
let extensionIDs = [
"sovetnik-yandex@yandex.ru",
"vb@yandex.ru",
"ntp-mail@corp.mail.ru",
"ntp-okru@corp.mail.ru",
];
for (let extensionID of extensionIDs) {
let addon = await lazy.AddonManager.getAddonByID(extensionID);
if (addon) {
await addon.disable();
}
}
for (let pref of prefs) {
Services.prefs.clearUserPref(pref);
}
await this._removeDistributionBookmarks();
} else {
await this._doApplyBookmarks();
}
this._bookmarksApplied = true;
this._checkCustomizationComplete();
},
async _doApplyBookmarks() {
if (!this._ini) {
return;
}
let sections = enumToObject(this._ini.getSections());
// The global section, and several of its fields, is required
// (we also check here to be consistent with applyPrefDefaults below)
if (!sections.Global) {
return;
}
let globalPrefs = enumToObject(this._ini.getKeys("Global"));
if (!(globalPrefs.id && globalPrefs.version && globalPrefs.about)) {
return;
}
let bmProcessedPref;
try {
bmProcessedPref = this._ini.getString(
"Global",
"bookmarks.initialized.pref"
);
} catch (e) {
bmProcessedPref =
"distribution." +
this._ini.getString("Global", "id") +
".bookmarksProcessed";
}
if (Services.prefs.getBoolPref(bmProcessedPref, false)) {
return;
}
let { ProfileAge } = ChromeUtils.importESModule(
"resource://gre/modules/ProfileAge.sys.mjs"
);
let profileAge = await ProfileAge();
let resetDate = await profileAge.reset;
// If the profile has been reset, don't recreate bookmarks.
if (!resetDate) {
if (sections.BookmarksMenu) {
await this._parseBookmarksSection(
lazy.PlacesUtils.bookmarks.menuGuid,
"BookmarksMenu"
);
}
if (sections.BookmarksToolbar) {
await this._parseBookmarksSection(
lazy.PlacesUtils.bookmarks.toolbarGuid,
"BookmarksToolbar"
);
}
}
Services.prefs.setBoolPref(bmProcessedPref, true);
},
_prefDefaultsApplied: false,
applyPrefDefaults: function DIST_applyPrefDefaults() {
this._prefDefaultsApplied = true;
if (!this._ini) {
return this._checkCustomizationComplete();
}
let sections = enumToObject(this._ini.getSections());
// The global section, and several of its fields, is required
if (!sections.Global) {
return this._checkCustomizationComplete();
}
let globalPrefs = enumToObject(this._ini.getKeys("Global"));
if (!(globalPrefs.id && globalPrefs.version)) {
return this._checkCustomizationComplete();
}
let distroID = this._ini.getString("Global", "id");
if (!globalPrefs.about && !distroID.startsWith("mozilla-")) {
// About is required unless it is a mozilla distro.
return this._checkCustomizationComplete();
}
let defaults = Services.prefs.getDefaultBranch(null);
// Global really contains info we set as prefs. They're only
// separate because they are "special" (read: required)
defaults.setStringPref("distribution.id", distroID);
if (
distroID.startsWith("yandex") ||
distroID.startsWith("mailru") ||
distroID.startsWith("okru")
) {
this.__defineGetter__("_ini", () => null);
return this._checkCustomizationComplete();
}
defaults.setStringPref(
"distribution.version",
this._ini.getString("Global", "version")
);
let partnerAbout;
try {
if (globalPrefs["about." + this._locale]) {
partnerAbout = this._ini.getString("Global", "about." + this._locale);
} else if (globalPrefs["about." + this._language]) {
partnerAbout = this._ini.getString("Global", "about." + this._language);
} else {
partnerAbout = this._ini.getString("Global", "about");
}
defaults.setStringPref("distribution.about", partnerAbout);
} catch (e) {
/* ignore bad prefs due to bug 895473 and move on */
}
/* order of precedence is locale->language->default */
let preferences = new Map();
if (sections.Preferences) {
for (let key of this._ini.getKeys("Preferences")) {
let value = this._ini.getString("Preferences", key);
if (value) {
preferences.set(key, value);
}
}
}
if (sections["Preferences-" + this._language]) {
for (let key of this._ini.getKeys("Preferences-" + this._language)) {
let value = this._ini.getString("Preferences-" + this._language, key);
if (value) {
preferences.set(key, value);
} else {
// If something was set by Preferences, but it's empty in language,
// it should be removed.
preferences.delete(key);
}
}
}
if (sections["Preferences-" + this._locale]) {
for (let key of this._ini.getKeys("Preferences-" + this._locale)) {
let value = this._ini.getString("Preferences-" + this._locale, key);
if (value) {
preferences.set(key, value);
} else {
// If something was set by Preferences, but it's empty in locale,
// it should be removed.
preferences.delete(key);
}
}
}
for (let [prefName, prefValue] of preferences) {
prefValue = prefValue.replace(/%LOCALE%/g, this._locale);
prefValue = prefValue.replace(/%LANGUAGE%/g, this._language);
prefValue = parseValue(prefValue);
try {
if (prefName == "general.useragent.locale") {
defaults.setStringPref("intl.locale.requested", prefValue);
} else {
switch (typeof prefValue) {
case "boolean":
defaults.setBoolPref(prefName, prefValue);
break;
case "number":
defaults.setIntPref(prefName, prefValue);
break;
case "string":
defaults.setStringPref(prefName, prefValue);
break;
}
}
} catch (e) {
/* ignore bad prefs and move on */
}
}
if (this._ini.getString("Global", "id") == "yandex") {
// All yandex distributions have the same distribution ID,
// so we're using an internal preference to name them correctly.
// This is needed for search to work properly.
try {
defaults.setStringPref(
"distribution.id",
defaults
.get("extensions.yasearch@yandex.ru.clids.vendor")
.replace("firefox", "yandex")
);
} catch (e) {
// Just use the default distribution ID.
}
}
let localizedStr = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(
Ci.nsIPrefLocalizedString
);
let localizablePreferences = new Map();
if (sections.LocalizablePreferences) {
for (let key of this._ini.getKeys("LocalizablePreferences")) {
let value = this._ini.getString("LocalizablePreferences", key);
if (value) {
localizablePreferences.set(key, value);
}
}
}
if (sections["LocalizablePreferences-" + this._language]) {
for (let key of this._ini.getKeys(
"LocalizablePreferences-" + this._language
)) {
let value = this._ini.getString(
"LocalizablePreferences-" + this._language,
key
);
if (value) {
localizablePreferences.set(key, value);
} else {
// If something was set by Preferences, but it's empty in language,
// it should be removed.
localizablePreferences.delete(key);
}
}
}
if (sections["LocalizablePreferences-" + this._locale]) {
for (let key of this._ini.getKeys(
"LocalizablePreferences-" + this._locale
)) {
let value = this._ini.getString(
"LocalizablePreferences-" + this._locale,
key
);
if (value) {
localizablePreferences.set(key, value);
} else {
// If something was set by Preferences, but it's empty in locale,
// it should be removed.
localizablePreferences.delete(key);
}
}
}
for (let [prefName, prefValue] of localizablePreferences) {
prefValue = parseValue(prefValue);
prefValue = prefValue.replace(/%LOCALE%/g, this._locale);
prefValue = prefValue.replace(/%LANGUAGE%/g, this._language);
localizedStr.data = "data:text/plain," + prefName + "=" + prefValue;
try {
defaults.setComplexValue(
prefName,
Ci.nsIPrefLocalizedString,
localizedStr
);
} catch (e) {
/* ignore bad prefs and move on */
}
}
return this._checkCustomizationComplete();
},
_checkCustomizationComplete: function DIST__checkCustomizationComplete() {
const BROWSER_DOCURL = AppConstants.BROWSER_CHROME_URL;
if (this._newProfile) {
try {
var showPersonalToolbar = Services.prefs.getBoolPref(
"browser.showPersonalToolbar"
);
if (showPersonalToolbar) {
Services.prefs.setCharPref(
"browser.toolbars.bookmarks.visibility",
"always"
);
}
} catch (e) {}
try {
var showMenubar = Services.prefs.getBoolPref("browser.showMenubar");
if (showMenubar) {
Services.xulStore.setValue(
BROWSER_DOCURL,
"toolbar-menubar",
"autohide",
"false"
);
}
} catch (e) {}
}
let prefDefaultsApplied = this._prefDefaultsApplied || !this._ini;
if (
this._customizationsApplied &&
this._bookmarksApplied &&
prefDefaultsApplied
) {
Services.obs.notifyObservers(
null,
DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC
);
}
},
};
function parseValue(value) {
try {
value = JSON.parse(value);
} catch (e) {
// JSON.parse catches numbers and booleans.
// Anything else, we assume is a string.
// Remove the quotes that aren't needed anymore.
value = value.replace(/^"/, "");
value = value.replace(/"$/, "");
}
return value;
}
function enumToObject(UTF8Enumerator) {
let ret = {};
for (let i of UTF8Enumerator) {
ret[i] = 1;
}
return ret;
}