michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = [ "DistributionCustomizer" ]; michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cc = Components.classes; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: michael@0: const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC = michael@0: "distribution-customization-complete"; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", michael@0: "resource://gre/modules/PlacesUtils.jsm"); michael@0: michael@0: this.DistributionCustomizer = function DistributionCustomizer() { michael@0: // For parallel xpcshell testing purposes allow loading the distribution.ini michael@0: // file from the profile folder through an hidden pref. michael@0: let loadFromProfile = false; michael@0: try { michael@0: loadFromProfile = Services.prefs.getBoolPref("distribution.testing.loadFromProfile"); michael@0: } catch(ex) {} michael@0: let dirSvc = Cc["@mozilla.org/file/directory_service;1"]. michael@0: getService(Ci.nsIProperties); michael@0: let iniFile = loadFromProfile ? dirSvc.get("ProfD", Ci.nsIFile) michael@0: : dirSvc.get("XREExeF", Ci.nsIFile); michael@0: iniFile.leafName = "distribution"; michael@0: iniFile.append("distribution.ini"); michael@0: if (iniFile.exists()) michael@0: this._iniFile = iniFile; michael@0: } michael@0: michael@0: DistributionCustomizer.prototype = { michael@0: _iniFile: null, michael@0: michael@0: get _ini() { michael@0: let ini = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]. michael@0: getService(Ci.nsIINIParserFactory). michael@0: createINIParser(this._iniFile); michael@0: this.__defineGetter__("_ini", function() ini); michael@0: return this._ini; michael@0: }, michael@0: michael@0: get _locale() { michael@0: let locale; michael@0: try { michael@0: locale = this._prefs.getCharPref("general.useragent.locale"); michael@0: } michael@0: catch (e) { michael@0: locale = "en-US"; michael@0: } michael@0: this.__defineGetter__("_locale", function() locale); michael@0: return this._locale; michael@0: }, michael@0: michael@0: get _prefSvc() { michael@0: let svc = Cc["@mozilla.org/preferences-service;1"]. michael@0: getService(Ci.nsIPrefService); michael@0: this.__defineGetter__("_prefSvc", function() svc); michael@0: return this._prefSvc; michael@0: }, michael@0: michael@0: get _prefs() { michael@0: let branch = this._prefSvc.getBranch(null); michael@0: this.__defineGetter__("_prefs", function() branch); michael@0: return this._prefs; michael@0: }, michael@0: michael@0: get _ioSvc() { michael@0: let svc = Cc["@mozilla.org/network/io-service;1"]. michael@0: getService(Ci.nsIIOService); michael@0: this.__defineGetter__("_ioSvc", function() svc); michael@0: return this._ioSvc; michael@0: }, michael@0: michael@0: _makeURI: function DIST__makeURI(spec) { michael@0: return this._ioSvc.newURI(spec, null, null); michael@0: }, michael@0: michael@0: _parseBookmarksSection: michael@0: function DIST_parseBookmarksSection(parentId, section) { michael@0: let keys = []; michael@0: for (let i in enumerate(this._ini.getKeys(section))) michael@0: keys.push(i); michael@0: keys.sort(); michael@0: michael@0: let items = {}; michael@0: let defaultItemId = -1; michael@0: let maxItemId = -1; michael@0: michael@0: for (let i = 0; i < keys.length; i++) { michael@0: let m = /^item\.(\d+)\.(\w+)\.?(\w*)/.exec(keys[i]); michael@0: if (m) { michael@0: let [foo, iid, iprop, ilocale] = m; michael@0: iid = parseInt(iid); michael@0: michael@0: if (ilocale) michael@0: continue; michael@0: michael@0: if (!items[iid]) michael@0: items[iid] = {}; michael@0: if (keys.indexOf(keys[i] + "." + this._locale) >= 0) { michael@0: items[iid][iprop] = this._ini.getString(section, keys[i] + "." + michael@0: this._locale); michael@0: } else { michael@0: items[iid][iprop] = this._ini.getString(section, keys[i]); michael@0: } michael@0: michael@0: if (iprop == "type" && items[iid]["type"] == "default") michael@0: defaultItemId = iid; michael@0: michael@0: if (maxItemId < iid) michael@0: maxItemId = iid; michael@0: } else { michael@0: dump("Key did not match: " + keys[i] + "\n"); michael@0: } michael@0: } michael@0: michael@0: let prependIndex = 0; michael@0: for (let iid = 0; iid <= maxItemId; iid++) { michael@0: if (!items[iid]) michael@0: continue; michael@0: michael@0: let index = PlacesUtils.bookmarks.DEFAULT_INDEX; michael@0: let newId; michael@0: michael@0: switch (items[iid]["type"]) { michael@0: case "default": michael@0: break; michael@0: michael@0: case "folder": michael@0: if (iid < defaultItemId) michael@0: index = prependIndex++; michael@0: michael@0: newId = PlacesUtils.bookmarks.createFolder(parentId, michael@0: items[iid]["title"], michael@0: index); michael@0: michael@0: this._parseBookmarksSection(newId, "BookmarksFolder-" + michael@0: items[iid]["folderId"]); michael@0: michael@0: if (items[iid]["description"]) michael@0: PlacesUtils.annotations.setItemAnnotation(newId, michael@0: "bookmarkProperties/description", michael@0: items[iid]["description"], 0, michael@0: PlacesUtils.annotations.EXPIRE_NEVER); michael@0: michael@0: break; michael@0: michael@0: case "separator": michael@0: if (iid < defaultItemId) michael@0: index = prependIndex++; michael@0: PlacesUtils.bookmarks.insertSeparator(parentId, index); michael@0: break; michael@0: michael@0: case "livemark": michael@0: if (iid < defaultItemId) michael@0: index = prependIndex++; michael@0: michael@0: // Don't bother updating the livemark contents on creation. michael@0: PlacesUtils.livemarks.addLivemark({ title: items[iid]["title"] michael@0: , parentId: parentId michael@0: , index: index michael@0: , feedURI: this._makeURI(items[iid]["feedLink"]) michael@0: , siteURI: this._makeURI(items[iid]["siteLink"]) michael@0: }).then(null, Cu.reportError); michael@0: break; michael@0: michael@0: case "bookmark": michael@0: default: michael@0: if (iid < defaultItemId) michael@0: index = prependIndex++; michael@0: michael@0: newId = PlacesUtils.bookmarks.insertBookmark(parentId, michael@0: this._makeURI(items[iid]["link"]), michael@0: index, items[iid]["title"]); michael@0: michael@0: if (items[iid]["description"]) michael@0: PlacesUtils.annotations.setItemAnnotation(newId, michael@0: "bookmarkProperties/description", michael@0: items[iid]["description"], 0, michael@0: PlacesUtils.annotations.EXPIRE_NEVER); michael@0: michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _customizationsApplied: false, michael@0: applyCustomizations: function DIST_applyCustomizations() { michael@0: this._customizationsApplied = true; michael@0: if (!this._iniFile) michael@0: return this._checkCustomizationComplete(); michael@0: michael@0: // nsPrefService loads very early. Reload prefs so we can set michael@0: // distribution defaults during the prefservice:after-app-defaults michael@0: // notification (see applyPrefDefaults below) michael@0: this._prefSvc.QueryInterface(Ci.nsIObserver); michael@0: this._prefSvc.observe(null, "reload-default-prefs", null); michael@0: }, michael@0: michael@0: _bookmarksApplied: false, michael@0: applyBookmarks: function DIST_applyBookmarks() { michael@0: this._bookmarksApplied = true; michael@0: if (!this._iniFile) michael@0: return this._checkCustomizationComplete(); michael@0: michael@0: let sections = enumToObject(this._ini.getSections()); michael@0: michael@0: // The global section, and several of its fields, is required michael@0: // (we also check here to be consistent with applyPrefDefaults below) michael@0: if (!sections["Global"]) michael@0: return this._checkCustomizationComplete(); michael@0: let globalPrefs = enumToObject(this._ini.getKeys("Global")); michael@0: if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"])) michael@0: return this._checkCustomizationComplete(); michael@0: michael@0: let bmProcessedPref; michael@0: try { michael@0: bmProcessedPref = this._ini.getString("Global", michael@0: "bookmarks.initialized.pref"); michael@0: } michael@0: catch (e) { michael@0: bmProcessedPref = "distribution." + michael@0: this._ini.getString("Global", "id") + ".bookmarksProcessed"; michael@0: } michael@0: michael@0: let bmProcessed = false; michael@0: try { michael@0: bmProcessed = this._prefs.getBoolPref(bmProcessedPref); michael@0: } michael@0: catch (e) {} michael@0: michael@0: if (!bmProcessed) { michael@0: if (sections["BookmarksMenu"]) michael@0: this._parseBookmarksSection(PlacesUtils.bookmarksMenuFolderId, michael@0: "BookmarksMenu"); michael@0: if (sections["BookmarksToolbar"]) michael@0: this._parseBookmarksSection(PlacesUtils.toolbarFolderId, michael@0: "BookmarksToolbar"); michael@0: this._prefs.setBoolPref(bmProcessedPref, true); michael@0: } michael@0: return this._checkCustomizationComplete(); michael@0: }, michael@0: michael@0: _prefDefaultsApplied: false, michael@0: applyPrefDefaults: function DIST_applyPrefDefaults() { michael@0: this._prefDefaultsApplied = true; michael@0: if (!this._iniFile) michael@0: return this._checkCustomizationComplete(); michael@0: michael@0: let sections = enumToObject(this._ini.getSections()); michael@0: michael@0: // The global section, and several of its fields, is required michael@0: if (!sections["Global"]) michael@0: return this._checkCustomizationComplete(); michael@0: let globalPrefs = enumToObject(this._ini.getKeys("Global")); michael@0: if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"])) michael@0: return this._checkCustomizationComplete(); michael@0: michael@0: let defaults = this._prefSvc.getDefaultBranch(null); michael@0: michael@0: // Global really contains info we set as prefs. They're only michael@0: // separate because they are "special" (read: required) michael@0: michael@0: defaults.setCharPref("distribution.id", this._ini.getString("Global", "id")); michael@0: defaults.setCharPref("distribution.version", michael@0: this._ini.getString("Global", "version")); michael@0: michael@0: let partnerAbout = Cc["@mozilla.org/supports-string;1"]. michael@0: createInstance(Ci.nsISupportsString); michael@0: try { michael@0: if (globalPrefs["about." + this._locale]) { michael@0: partnerAbout.data = this._ini.getString("Global", "about." + this._locale); michael@0: } else { michael@0: partnerAbout.data = this._ini.getString("Global", "about"); michael@0: } michael@0: defaults.setComplexValue("distribution.about", michael@0: Ci.nsISupportsString, partnerAbout); michael@0: } catch (e) { michael@0: /* ignore bad prefs due to bug 895473 and move on */ michael@0: Cu.reportError(e); michael@0: } michael@0: michael@0: if (sections["Preferences"]) { michael@0: for (let key in enumerate(this._ini.getKeys("Preferences"))) { michael@0: try { michael@0: let value = eval(this._ini.getString("Preferences", key)); michael@0: switch (typeof value) { michael@0: case "boolean": michael@0: defaults.setBoolPref(key, value); michael@0: break; michael@0: case "number": michael@0: defaults.setIntPref(key, value); michael@0: break; michael@0: case "string": michael@0: defaults.setCharPref(key, value); michael@0: break; michael@0: case "undefined": michael@0: defaults.setCharPref(key, value); michael@0: break; michael@0: } michael@0: } catch (e) { /* ignore bad prefs and move on */ } michael@0: } michael@0: } michael@0: michael@0: // We eval() the localizable prefs as well (even though they'll michael@0: // always get set as a string) to keep the INI format consistent: michael@0: // string prefs always need to be in quotes michael@0: michael@0: let localizedStr = Cc["@mozilla.org/pref-localizedstring;1"]. michael@0: createInstance(Ci.nsIPrefLocalizedString); michael@0: michael@0: if (sections["LocalizablePreferences"]) { michael@0: for (let key in enumerate(this._ini.getKeys("LocalizablePreferences"))) { michael@0: try { michael@0: let value = eval(this._ini.getString("LocalizablePreferences", key)); michael@0: value = value.replace("%LOCALE%", this._locale, "g"); michael@0: localizedStr.data = "data:text/plain," + key + "=" + value; michael@0: defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr); michael@0: } catch (e) { /* ignore bad prefs and move on */ } michael@0: } michael@0: } michael@0: michael@0: if (sections["LocalizablePreferences-" + this._locale]) { michael@0: for (let key in enumerate(this._ini.getKeys("LocalizablePreferences-" + this._locale))) { michael@0: try { michael@0: let value = eval(this._ini.getString("LocalizablePreferences-" + this._locale, key)); michael@0: localizedStr.data = "data:text/plain," + key + "=" + value; michael@0: defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr); michael@0: } catch (e) { /* ignore bad prefs and move on */ } michael@0: } michael@0: } michael@0: michael@0: return this._checkCustomizationComplete(); michael@0: }, michael@0: michael@0: _checkCustomizationComplete: function DIST__checkCustomizationComplete() { michael@0: let prefDefaultsApplied = this._prefDefaultsApplied || !this._iniFile; michael@0: if (this._customizationsApplied && this._bookmarksApplied && michael@0: prefDefaultsApplied) { michael@0: let os = Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService); michael@0: os.notifyObservers(null, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC, null); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: function enumerate(UTF8Enumerator) { michael@0: while (UTF8Enumerator.hasMore()) michael@0: yield UTF8Enumerator.getNext(); michael@0: } michael@0: michael@0: function enumToObject(UTF8Enumerator) { michael@0: let ret = {}; michael@0: for (let i in enumerate(UTF8Enumerator)) michael@0: ret[i] = 1; michael@0: return ret; michael@0: }