|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 this.EXPORTED_SYMBOLS = [ "DistributionCustomizer" ]; |
|
6 |
|
7 const Ci = Components.interfaces; |
|
8 const Cc = Components.classes; |
|
9 const Cr = Components.results; |
|
10 const Cu = Components.utils; |
|
11 |
|
12 const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC = |
|
13 "distribution-customization-complete"; |
|
14 |
|
15 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
16 Cu.import("resource://gre/modules/Services.jsm"); |
|
17 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", |
|
18 "resource://gre/modules/PlacesUtils.jsm"); |
|
19 |
|
20 this.DistributionCustomizer = function DistributionCustomizer() { |
|
21 // For parallel xpcshell testing purposes allow loading the distribution.ini |
|
22 // file from the profile folder through an hidden pref. |
|
23 let loadFromProfile = false; |
|
24 try { |
|
25 loadFromProfile = Services.prefs.getBoolPref("distribution.testing.loadFromProfile"); |
|
26 } catch(ex) {} |
|
27 let dirSvc = Cc["@mozilla.org/file/directory_service;1"]. |
|
28 getService(Ci.nsIProperties); |
|
29 let iniFile = loadFromProfile ? dirSvc.get("ProfD", Ci.nsIFile) |
|
30 : dirSvc.get("XREExeF", Ci.nsIFile); |
|
31 iniFile.leafName = "distribution"; |
|
32 iniFile.append("distribution.ini"); |
|
33 if (iniFile.exists()) |
|
34 this._iniFile = iniFile; |
|
35 } |
|
36 |
|
37 DistributionCustomizer.prototype = { |
|
38 _iniFile: null, |
|
39 |
|
40 get _ini() { |
|
41 let ini = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]. |
|
42 getService(Ci.nsIINIParserFactory). |
|
43 createINIParser(this._iniFile); |
|
44 this.__defineGetter__("_ini", function() ini); |
|
45 return this._ini; |
|
46 }, |
|
47 |
|
48 get _locale() { |
|
49 let locale; |
|
50 try { |
|
51 locale = this._prefs.getCharPref("general.useragent.locale"); |
|
52 } |
|
53 catch (e) { |
|
54 locale = "en-US"; |
|
55 } |
|
56 this.__defineGetter__("_locale", function() locale); |
|
57 return this._locale; |
|
58 }, |
|
59 |
|
60 get _prefSvc() { |
|
61 let svc = Cc["@mozilla.org/preferences-service;1"]. |
|
62 getService(Ci.nsIPrefService); |
|
63 this.__defineGetter__("_prefSvc", function() svc); |
|
64 return this._prefSvc; |
|
65 }, |
|
66 |
|
67 get _prefs() { |
|
68 let branch = this._prefSvc.getBranch(null); |
|
69 this.__defineGetter__("_prefs", function() branch); |
|
70 return this._prefs; |
|
71 }, |
|
72 |
|
73 get _ioSvc() { |
|
74 let svc = Cc["@mozilla.org/network/io-service;1"]. |
|
75 getService(Ci.nsIIOService); |
|
76 this.__defineGetter__("_ioSvc", function() svc); |
|
77 return this._ioSvc; |
|
78 }, |
|
79 |
|
80 _makeURI: function DIST__makeURI(spec) { |
|
81 return this._ioSvc.newURI(spec, null, null); |
|
82 }, |
|
83 |
|
84 _parseBookmarksSection: |
|
85 function DIST_parseBookmarksSection(parentId, section) { |
|
86 let keys = []; |
|
87 for (let i in enumerate(this._ini.getKeys(section))) |
|
88 keys.push(i); |
|
89 keys.sort(); |
|
90 |
|
91 let items = {}; |
|
92 let defaultItemId = -1; |
|
93 let maxItemId = -1; |
|
94 |
|
95 for (let i = 0; i < keys.length; i++) { |
|
96 let m = /^item\.(\d+)\.(\w+)\.?(\w*)/.exec(keys[i]); |
|
97 if (m) { |
|
98 let [foo, iid, iprop, ilocale] = m; |
|
99 iid = parseInt(iid); |
|
100 |
|
101 if (ilocale) |
|
102 continue; |
|
103 |
|
104 if (!items[iid]) |
|
105 items[iid] = {}; |
|
106 if (keys.indexOf(keys[i] + "." + this._locale) >= 0) { |
|
107 items[iid][iprop] = this._ini.getString(section, keys[i] + "." + |
|
108 this._locale); |
|
109 } else { |
|
110 items[iid][iprop] = this._ini.getString(section, keys[i]); |
|
111 } |
|
112 |
|
113 if (iprop == "type" && items[iid]["type"] == "default") |
|
114 defaultItemId = iid; |
|
115 |
|
116 if (maxItemId < iid) |
|
117 maxItemId = iid; |
|
118 } else { |
|
119 dump("Key did not match: " + keys[i] + "\n"); |
|
120 } |
|
121 } |
|
122 |
|
123 let prependIndex = 0; |
|
124 for (let iid = 0; iid <= maxItemId; iid++) { |
|
125 if (!items[iid]) |
|
126 continue; |
|
127 |
|
128 let index = PlacesUtils.bookmarks.DEFAULT_INDEX; |
|
129 let newId; |
|
130 |
|
131 switch (items[iid]["type"]) { |
|
132 case "default": |
|
133 break; |
|
134 |
|
135 case "folder": |
|
136 if (iid < defaultItemId) |
|
137 index = prependIndex++; |
|
138 |
|
139 newId = PlacesUtils.bookmarks.createFolder(parentId, |
|
140 items[iid]["title"], |
|
141 index); |
|
142 |
|
143 this._parseBookmarksSection(newId, "BookmarksFolder-" + |
|
144 items[iid]["folderId"]); |
|
145 |
|
146 if (items[iid]["description"]) |
|
147 PlacesUtils.annotations.setItemAnnotation(newId, |
|
148 "bookmarkProperties/description", |
|
149 items[iid]["description"], 0, |
|
150 PlacesUtils.annotations.EXPIRE_NEVER); |
|
151 |
|
152 break; |
|
153 |
|
154 case "separator": |
|
155 if (iid < defaultItemId) |
|
156 index = prependIndex++; |
|
157 PlacesUtils.bookmarks.insertSeparator(parentId, index); |
|
158 break; |
|
159 |
|
160 case "livemark": |
|
161 if (iid < defaultItemId) |
|
162 index = prependIndex++; |
|
163 |
|
164 // Don't bother updating the livemark contents on creation. |
|
165 PlacesUtils.livemarks.addLivemark({ title: items[iid]["title"] |
|
166 , parentId: parentId |
|
167 , index: index |
|
168 , feedURI: this._makeURI(items[iid]["feedLink"]) |
|
169 , siteURI: this._makeURI(items[iid]["siteLink"]) |
|
170 }).then(null, Cu.reportError); |
|
171 break; |
|
172 |
|
173 case "bookmark": |
|
174 default: |
|
175 if (iid < defaultItemId) |
|
176 index = prependIndex++; |
|
177 |
|
178 newId = PlacesUtils.bookmarks.insertBookmark(parentId, |
|
179 this._makeURI(items[iid]["link"]), |
|
180 index, items[iid]["title"]); |
|
181 |
|
182 if (items[iid]["description"]) |
|
183 PlacesUtils.annotations.setItemAnnotation(newId, |
|
184 "bookmarkProperties/description", |
|
185 items[iid]["description"], 0, |
|
186 PlacesUtils.annotations.EXPIRE_NEVER); |
|
187 |
|
188 break; |
|
189 } |
|
190 } |
|
191 }, |
|
192 |
|
193 _customizationsApplied: false, |
|
194 applyCustomizations: function DIST_applyCustomizations() { |
|
195 this._customizationsApplied = true; |
|
196 if (!this._iniFile) |
|
197 return this._checkCustomizationComplete(); |
|
198 |
|
199 // nsPrefService loads very early. Reload prefs so we can set |
|
200 // distribution defaults during the prefservice:after-app-defaults |
|
201 // notification (see applyPrefDefaults below) |
|
202 this._prefSvc.QueryInterface(Ci.nsIObserver); |
|
203 this._prefSvc.observe(null, "reload-default-prefs", null); |
|
204 }, |
|
205 |
|
206 _bookmarksApplied: false, |
|
207 applyBookmarks: function DIST_applyBookmarks() { |
|
208 this._bookmarksApplied = true; |
|
209 if (!this._iniFile) |
|
210 return this._checkCustomizationComplete(); |
|
211 |
|
212 let sections = enumToObject(this._ini.getSections()); |
|
213 |
|
214 // The global section, and several of its fields, is required |
|
215 // (we also check here to be consistent with applyPrefDefaults below) |
|
216 if (!sections["Global"]) |
|
217 return this._checkCustomizationComplete(); |
|
218 let globalPrefs = enumToObject(this._ini.getKeys("Global")); |
|
219 if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"])) |
|
220 return this._checkCustomizationComplete(); |
|
221 |
|
222 let bmProcessedPref; |
|
223 try { |
|
224 bmProcessedPref = this._ini.getString("Global", |
|
225 "bookmarks.initialized.pref"); |
|
226 } |
|
227 catch (e) { |
|
228 bmProcessedPref = "distribution." + |
|
229 this._ini.getString("Global", "id") + ".bookmarksProcessed"; |
|
230 } |
|
231 |
|
232 let bmProcessed = false; |
|
233 try { |
|
234 bmProcessed = this._prefs.getBoolPref(bmProcessedPref); |
|
235 } |
|
236 catch (e) {} |
|
237 |
|
238 if (!bmProcessed) { |
|
239 if (sections["BookmarksMenu"]) |
|
240 this._parseBookmarksSection(PlacesUtils.bookmarksMenuFolderId, |
|
241 "BookmarksMenu"); |
|
242 if (sections["BookmarksToolbar"]) |
|
243 this._parseBookmarksSection(PlacesUtils.toolbarFolderId, |
|
244 "BookmarksToolbar"); |
|
245 this._prefs.setBoolPref(bmProcessedPref, true); |
|
246 } |
|
247 return this._checkCustomizationComplete(); |
|
248 }, |
|
249 |
|
250 _prefDefaultsApplied: false, |
|
251 applyPrefDefaults: function DIST_applyPrefDefaults() { |
|
252 this._prefDefaultsApplied = true; |
|
253 if (!this._iniFile) |
|
254 return this._checkCustomizationComplete(); |
|
255 |
|
256 let sections = enumToObject(this._ini.getSections()); |
|
257 |
|
258 // The global section, and several of its fields, is required |
|
259 if (!sections["Global"]) |
|
260 return this._checkCustomizationComplete(); |
|
261 let globalPrefs = enumToObject(this._ini.getKeys("Global")); |
|
262 if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"])) |
|
263 return this._checkCustomizationComplete(); |
|
264 |
|
265 let defaults = this._prefSvc.getDefaultBranch(null); |
|
266 |
|
267 // Global really contains info we set as prefs. They're only |
|
268 // separate because they are "special" (read: required) |
|
269 |
|
270 defaults.setCharPref("distribution.id", this._ini.getString("Global", "id")); |
|
271 defaults.setCharPref("distribution.version", |
|
272 this._ini.getString("Global", "version")); |
|
273 |
|
274 let partnerAbout = Cc["@mozilla.org/supports-string;1"]. |
|
275 createInstance(Ci.nsISupportsString); |
|
276 try { |
|
277 if (globalPrefs["about." + this._locale]) { |
|
278 partnerAbout.data = this._ini.getString("Global", "about." + this._locale); |
|
279 } else { |
|
280 partnerAbout.data = this._ini.getString("Global", "about"); |
|
281 } |
|
282 defaults.setComplexValue("distribution.about", |
|
283 Ci.nsISupportsString, partnerAbout); |
|
284 } catch (e) { |
|
285 /* ignore bad prefs due to bug 895473 and move on */ |
|
286 Cu.reportError(e); |
|
287 } |
|
288 |
|
289 if (sections["Preferences"]) { |
|
290 for (let key in enumerate(this._ini.getKeys("Preferences"))) { |
|
291 try { |
|
292 let value = eval(this._ini.getString("Preferences", key)); |
|
293 switch (typeof value) { |
|
294 case "boolean": |
|
295 defaults.setBoolPref(key, value); |
|
296 break; |
|
297 case "number": |
|
298 defaults.setIntPref(key, value); |
|
299 break; |
|
300 case "string": |
|
301 defaults.setCharPref(key, value); |
|
302 break; |
|
303 case "undefined": |
|
304 defaults.setCharPref(key, value); |
|
305 break; |
|
306 } |
|
307 } catch (e) { /* ignore bad prefs and move on */ } |
|
308 } |
|
309 } |
|
310 |
|
311 // We eval() the localizable prefs as well (even though they'll |
|
312 // always get set as a string) to keep the INI format consistent: |
|
313 // string prefs always need to be in quotes |
|
314 |
|
315 let localizedStr = Cc["@mozilla.org/pref-localizedstring;1"]. |
|
316 createInstance(Ci.nsIPrefLocalizedString); |
|
317 |
|
318 if (sections["LocalizablePreferences"]) { |
|
319 for (let key in enumerate(this._ini.getKeys("LocalizablePreferences"))) { |
|
320 try { |
|
321 let value = eval(this._ini.getString("LocalizablePreferences", key)); |
|
322 value = value.replace("%LOCALE%", this._locale, "g"); |
|
323 localizedStr.data = "data:text/plain," + key + "=" + value; |
|
324 defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr); |
|
325 } catch (e) { /* ignore bad prefs and move on */ } |
|
326 } |
|
327 } |
|
328 |
|
329 if (sections["LocalizablePreferences-" + this._locale]) { |
|
330 for (let key in enumerate(this._ini.getKeys("LocalizablePreferences-" + this._locale))) { |
|
331 try { |
|
332 let value = eval(this._ini.getString("LocalizablePreferences-" + this._locale, key)); |
|
333 localizedStr.data = "data:text/plain," + key + "=" + value; |
|
334 defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr); |
|
335 } catch (e) { /* ignore bad prefs and move on */ } |
|
336 } |
|
337 } |
|
338 |
|
339 return this._checkCustomizationComplete(); |
|
340 }, |
|
341 |
|
342 _checkCustomizationComplete: function DIST__checkCustomizationComplete() { |
|
343 let prefDefaultsApplied = this._prefDefaultsApplied || !this._iniFile; |
|
344 if (this._customizationsApplied && this._bookmarksApplied && |
|
345 prefDefaultsApplied) { |
|
346 let os = Cc["@mozilla.org/observer-service;1"]. |
|
347 getService(Ci.nsIObserverService); |
|
348 os.notifyObservers(null, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC, null); |
|
349 } |
|
350 } |
|
351 }; |
|
352 |
|
353 function enumerate(UTF8Enumerator) { |
|
354 while (UTF8Enumerator.hasMore()) |
|
355 yield UTF8Enumerator.getNext(); |
|
356 } |
|
357 |
|
358 function enumToObject(UTF8Enumerator) { |
|
359 let ret = {}; |
|
360 for (let i in enumerate(UTF8Enumerator)) |
|
361 ret[i] = 1; |
|
362 return ret; |
|
363 } |