Mercurial > nightly_tester_tools
comparison components/nttAddonCompatibilityService.js @ 0:dada0ac40a8f
initial import
author | Yoshiki Yazawa <yaz@honeyplanet.jp> |
---|---|
date | Tue, 02 Dec 2008 20:31:01 +0900 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:dada0ac40a8f |
---|---|
1 const Cc = Components.classes; | |
2 const Ci = Components.interfaces; | |
3 const Cr = Components.results; | |
4 | |
5 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); | |
6 | |
7 const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; | |
8 const PREFIX_ITEM_URI = "urn:mozilla:item:"; | |
9 const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest"; | |
10 const FILE_INSTALL_MANIFEST = "install.rdf"; | |
11 const TOOLKIT_ID = "toolkit@mozilla.org" | |
12 | |
13 var gEM = null; | |
14 var gRDF = null; | |
15 var gApp = null; | |
16 var gVC = null; | |
17 var gCheckCompatibility = true; | |
18 var gCheckUpdateSecurity = true; | |
19 var gPrefs = null; | |
20 | |
21 function EM_NS(property) { | |
22 return PREFIX_NS_EM + property; | |
23 } | |
24 | |
25 function EM_R(property) { | |
26 return gRDF.GetResource(EM_NS(property)); | |
27 } | |
28 | |
29 function getRDFProperty(ds, source, property) { | |
30 var value = ds.GetTarget(source, EM_R(property), true); | |
31 if (value && value instanceof Ci.nsIRDFLiteral) | |
32 return value.Value; | |
33 return null; | |
34 } | |
35 | |
36 function removeRDFProperty(ds, source, property) { | |
37 var arc = EM_R(property); | |
38 var targets = ds.GetTargets(source, arc, true); | |
39 while (targets.hasMoreElements()) | |
40 ds.Unassert(source, arc, targets.getNext()); | |
41 } | |
42 | |
43 function extractXPI(xpi) { | |
44 // XXX For 1.9 final we can switch to just extracting/compressing install.rdf | |
45 var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. | |
46 createInstance(Ci.nsIZipReader); | |
47 zipReader.open(xpi); | |
48 if (!zipReader.hasEntry(FILE_INSTALL_MANIFEST)) { | |
49 zipReader.close(); | |
50 return null; | |
51 } | |
52 var dirs = Cc["@mozilla.org/file/directory_service;1"]. | |
53 getService(Ci.nsIProperties); | |
54 var file = dirs.get("TmpD", Ci.nsILocalFile); | |
55 file.append("tmpxpi"); | |
56 file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0755); | |
57 var entries = zipReader.findEntries("*"); | |
58 while (entries.hasMore()) { | |
59 var path = entries.getNext(); | |
60 var entry = zipReader.getEntry(path); | |
61 if (path.substring(path.length - 1) == "/") | |
62 path = path.substring(0, entry.length - 1); | |
63 var parts = path.split("/"); | |
64 var target = file.clone(); | |
65 for (var i = 0; i < parts.length; i++) | |
66 target.append(parts[i]); | |
67 if (entry.isDirectory) { | |
68 if (!target.exists()) | |
69 target.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); | |
70 } | |
71 else { | |
72 var parent = target.parent; | |
73 if (!parent.exists()) | |
74 parent.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); | |
75 zipReader.extract(path, target); | |
76 } | |
77 } | |
78 zipReader.close(); | |
79 return file; | |
80 } | |
81 | |
82 function loadManifest(file) { | |
83 var ioServ = Cc["@mozilla.org/network/io-service;1"]. | |
84 getService(Ci.nsIIOService); | |
85 var fph = ioServ.getProtocolHandler("file") | |
86 .QueryInterface(Ci.nsIFileProtocolHandler); | |
87 return gRDF.GetDataSourceBlocking(fph.getURLSpecFromFile(file)); | |
88 } | |
89 | |
90 function recursiveUpdate(zipWriter, path, dir) { | |
91 var entries = dir.directoryEntries; | |
92 while (entries.hasMoreElements()) { | |
93 var entry = entries.getNext().QueryInterface(Ci.nsIFile); | |
94 if (entry.isDirectory()) { | |
95 var newPath = path + entry.leafName + "/"; | |
96 zipWriter.addEntryDirectory(newPath, entry.lastModifiedTime, false); | |
97 recursiveUpdate(zipWriter, newPath, entry); | |
98 } | |
99 else { | |
100 zipWriter.addEntryFile(path + entry.leafName, Ci.nsIZipWriter.COMPRESSION_NONE, | |
101 entry, false); | |
102 } | |
103 } | |
104 } | |
105 | |
106 function updateXPI(xpi, file) { | |
107 // XXX For 1.9 final we can switch to just extracting/compressing install.rdf | |
108 var zipWriter = Cc["@mozilla.org/zipwriter;1"]. | |
109 createInstance(Ci.nsIZipWriter); | |
110 zipWriter.open(xpi, 0x04 | 0x08 | 0x20); | |
111 recursiveUpdate(zipWriter, "", file); | |
112 zipWriter.close(); | |
113 } | |
114 | |
115 function nttAddonUpdateChecker(addon) { | |
116 this.addon = addon; | |
117 } | |
118 | |
119 nttAddonUpdateChecker.prototype = { | |
120 addon: null, | |
121 busy: null, | |
122 | |
123 checkForUpdates: function() { | |
124 this.busy = true; | |
125 LOG("Searching for compatibility information for " + this.addon.id); | |
126 gEM.update([this.addon], 1, | |
127 Ci.nsIExtensionManager.UPDATE_SYNC_COMPATIBILITY, this); | |
128 | |
129 // Spin an event loop to wait for the update check to complete. | |
130 var tm = Cc["@mozilla.org/thread-manager;1"]. | |
131 getService(Ci.nsIThreadManager); | |
132 var thread = tm.currentThread; | |
133 while (this.busy) | |
134 thread.processNextEvent(true); | |
135 }, | |
136 | |
137 // nsIAddonUpdateCheckListener implementation | |
138 onUpdateStarted: function() { | |
139 }, | |
140 | |
141 onUpdateEnded: function() { | |
142 this.busy = false; | |
143 }, | |
144 | |
145 onAddonUpdateStarted: function(addon) { | |
146 }, | |
147 | |
148 onAddonUpdateEnded: function(addon, status) { | |
149 if (status & Ci.nsIAddonUpdateCheckListener.STATUS_DATA_FOUND) { | |
150 LOG("Found new compatibility information for " + addon.id + ": " + addon.minAppVersion + " " + addon.maxAppVersion); | |
151 this.addon.minAppVersion = addon.minAppVersion; | |
152 this.addon.maxAppVersion = addon.maxAppVersion; | |
153 this.addon.targetAppID = addon.targetAppID; | |
154 this.addon.overrideVersions(); | |
155 } | |
156 } | |
157 }; | |
158 | |
159 function nttAddonDetail() { | |
160 } | |
161 | |
162 nttAddonDetail.prototype = { | |
163 datasource: null, | |
164 root: null, | |
165 | |
166 xpi: null, | |
167 file: null, | |
168 | |
169 id: null, | |
170 name: null, | |
171 version: null, | |
172 type: Ci.nsIUpdateItem.TYPE_EXTENSION, | |
173 updateRDF: null, | |
174 updateKey: null, | |
175 iconURL: "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png", | |
176 | |
177 installLocationKey: null, | |
178 xpiURL: null, | |
179 xpiHash: null, | |
180 | |
181 appResource: null, | |
182 targetAppID: null, | |
183 minAppVersion: null, | |
184 maxAppVersion: null, | |
185 | |
186 init: function() { | |
187 if (!this.id) | |
188 this.id = getRDFProperty(this.datasource, this.root, "id"); | |
189 this.name = getRDFProperty(this.datasource, this.root, "name"); | |
190 this.version = getRDFProperty(this.datasource, this.root, "version"); | |
191 this.updateRDF = getRDFProperty(this.datasource, this.root, "updateURL"); | |
192 this.updateKey = getRDFProperty(this.datasource, this.root, "updateKey"); | |
193 | |
194 var apps = this.datasource.GetTargets(this.root, EM_R("targetApplication"), true); | |
195 while (apps.hasMoreElements()) { | |
196 var app = apps.getNext().QueryInterface(Ci.nsIRDFResource); | |
197 var id = getRDFProperty(this.datasource, app, "id"); | |
198 if (id == gApp.ID || id == TOOLKIT_ID) { | |
199 this.minAppVersion = getRDFProperty(this.datasource, app, "minVersion"); | |
200 this.maxAppVersion = getRDFProperty(this.datasource, app, "maxVersion"); | |
201 if (this.minAppVersion && this.maxAppVersion) { | |
202 this.appResource = app; | |
203 this.targetAppID = id; | |
204 if (id == gApp.ID) | |
205 break; | |
206 } | |
207 } | |
208 } | |
209 }, | |
210 | |
211 initWithXPI: function(xpi) { | |
212 this.xpi = xpi; | |
213 this.file = extractXPI(xpi); | |
214 var rdf = this.file.clone(); | |
215 rdf.append(FILE_INSTALL_MANIFEST); | |
216 this.datasource = loadManifest(rdf); | |
217 this.root = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT); | |
218 this.init(); | |
219 }, | |
220 | |
221 initWithDataSource: function(ds, root, id) { | |
222 this.datasource = ds; | |
223 this.root = root; | |
224 this.id = id; | |
225 this.init(); | |
226 }, | |
227 | |
228 cleanup: function() { | |
229 if (this.file && this.file.exists) | |
230 this.file.remove(true); | |
231 }, | |
232 | |
233 overrideVersions: function() { | |
234 removeRDFProperty(this.datasource, this.appResource, "minVersion"); | |
235 this.datasource.Assert(this.appResource, EM_R("minVersion"), gRDF.GetLiteral(this.minAppVersion), true); | |
236 removeRDFProperty(this.datasource, this.appResource, "maxVersion"); | |
237 this.datasource.Assert(this.appResource, EM_R("maxVersion"), gRDF.GetLiteral(this.maxAppVersion), true); | |
238 this.datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush(); | |
239 if (this.xpi && this.file) | |
240 updateXPI(this.xpi, this.file); | |
241 }, | |
242 | |
243 overrideCompatibility: function(ignorePrefs) { | |
244 if (!this.isValid()) | |
245 return; | |
246 | |
247 var changed = false; | |
248 | |
249 if (gCheckCompatibility || ignorePrefs) { | |
250 var version = (gApp.ID == this.targetAppID) ? gApp.version : gApp.platformVersion; | |
251 if (gVC.compare(version, this.minAppVersion) < 0) { | |
252 LOG("minVersion " + this.minAppVersion + " is too high, reducing to " + version); | |
253 if (!this.datasource.GetTarget(this.appResource, EM_R("oldMinVersion"), true)) | |
254 this.datasource.Assert(this.appResource, EM_R("oldMinVersion"), gRDF.GetLiteral(this.minAppVersion), true); | |
255 removeRDFProperty(this.datasource, this.appResource, "minVersion"); | |
256 this.datasource.Assert(this.appResource, EM_R("minVersion"), gRDF.GetLiteral(version), true); | |
257 this.minAppVersion = version; | |
258 changed = true; | |
259 } | |
260 else if (gVC.compare(version, this.maxAppVersion) > 0) { | |
261 LOG("maxVersion " + this.maxAppVersion + " is too low, increasing to " + version); | |
262 if (!this.datasource.GetTarget(this.appResource, EM_R("oldMaxVersion"), true)) | |
263 this.datasource.Assert(this.appResource, EM_R("oldMaxVersion"), gRDF.GetLiteral(this.maxAppVersion), true); | |
264 removeRDFProperty(this.datasource, this.appResource, "maxVersion"); | |
265 this.datasource.Assert(this.appResource, EM_R("maxVersion"), gRDF.GetLiteral(version), true); | |
266 this.maxAppVersion = version; | |
267 changed = true; | |
268 } | |
269 | |
270 if (changed && !this.xpi) { | |
271 // This updates any UI bound to the datasource | |
272 var compatprop = EM_R("compatible"); | |
273 var truth = gRDF.GetLiteral("true"); | |
274 this.datasource.Assert(this.root, compatprop, truth, true); | |
275 this.datasource.Unassert(this.root, compatprop, truth); | |
276 } | |
277 } | |
278 | |
279 if (!this.isUpdateSecure(ignorePrefs)) { | |
280 LOG("Addon is insecure, removing update URL"); | |
281 removeRDFProperty(this.datasource, this.root, "updateURL"); | |
282 this.updateRDF = null; | |
283 changed = true; | |
284 | |
285 // This updates any UI bound to the datasource | |
286 compatprop = EM_R("providesUpdatesSecurely"); | |
287 truth = gRDF.GetLiteral("true"); | |
288 this.datasource.Assert(this.root, compatprop, truth, true); | |
289 this.datasource.Unassert(this.root, compatprop, truth); | |
290 } | |
291 | |
292 if (changed) { | |
293 this.datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush(); | |
294 if (this.xpi && this.file) | |
295 updateXPI(this.xpi, this.file); | |
296 } | |
297 }, | |
298 | |
299 isValid: function() { | |
300 return !!this.appResource; | |
301 }, | |
302 | |
303 isCompatible: function(ignorePrefs) { | |
304 if (!gCheckCompatibility && !ignorePrefs) | |
305 return true; | |
306 | |
307 var version = (gApp.ID == this.targetAppID) ? gApp.version : gApp.platformVersion; | |
308 if (gVC.compare(version, this.minAppVersion) < 0) | |
309 return false; | |
310 if (gVC.compare(version, this.maxAppVersion) > 0) | |
311 return false; | |
312 return true; | |
313 }, | |
314 | |
315 isUpdateSecure: function(ignorePrefs) { | |
316 if (!gCheckUpdateSecurity && !ignorePrefs) | |
317 return true; | |
318 | |
319 if (!this.updateRDF) | |
320 return true; | |
321 if (this.updateKey) | |
322 return true; | |
323 return (this.updateRDF.substring(0, 6) == "https:"); | |
324 }, | |
325 | |
326 needsOverride: function(ignorePrefs) { | |
327 return (!this.isCompatible(ignorePrefs) || !this.isUpdateSecure(ignorePrefs)); | |
328 }, | |
329 | |
330 QueryInterface: XPCOMUtils.generateQI([Ci.nttIAddon, Ci.nsIUpdateItem]), | |
331 }; | |
332 | |
333 function nttAddonCompatibilityService() { | |
334 } | |
335 | |
336 nttAddonCompatibilityService.prototype = { | |
337 id: null, | |
338 | |
339 init: function() { | |
340 Components.utils.import("resource://nightly/Logging.jsm"); | |
341 | |
342 gEM = Cc["@mozilla.org/extensions/manager;1"]. | |
343 getService(Ci.nsIExtensionManager); | |
344 gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. | |
345 getService(Ci.nsIRDFService); | |
346 gApp = Cc["@mozilla.org/xre/app-info;1"]. | |
347 getService(Ci.nsIXULAppInfo).QueryInterface(Ci.nsIXULRuntime); | |
348 gVC = Cc["@mozilla.org/xpcom/version-comparator;1"]. | |
349 getService(Ci.nsIVersionComparator); | |
350 if (gVC.compare(gApp.platformVersion, "1.9b5") >= 0) | |
351 this.id = gEM.addInstallListener(this); | |
352 gPrefs = Components.classes["@mozilla.org/preferences-service;1"] | |
353 .getService(Components.interfaces.nsIPrefService) | |
354 .getBranch("extensions.") | |
355 .QueryInterface(Components.interfaces.nsIPrefBranch2); | |
356 try { | |
357 gCheckCompatibility = gPrefs.getBoolPref("checkCompatibility"); | |
358 } | |
359 catch (e) { } | |
360 try { | |
361 gCheckUpdateSecurity = gPrefs.getBoolPref("checkUpdateSecurity"); | |
362 } | |
363 catch (e) { } | |
364 gPrefs.addObserver("", this, false); | |
365 }, | |
366 | |
367 // nsIAddonCompatibilityService implementation | |
368 getAddonForID: function(id) { | |
369 var addon = new nttAddonDetail(); | |
370 addon.initWithDataSource(gEM.datasource, gRDF.GetResource(PREFIX_ITEM_URI + id), id); | |
371 return addon; | |
372 }, | |
373 | |
374 confirmOverride: function(addons, count) { | |
375 var wm = Cc["@mozilla.org/appshell/window-mediator;1"]. | |
376 getService(Ci.nsIWindowMediator); | |
377 win = wm.getMostRecentWindow("Extension:Manager"); | |
378 if (win && win.top) | |
379 win = win.top; | |
380 | |
381 var params = Cc["@mozilla.org/array;1"]. | |
382 createInstance(Ci.nsIMutableArray); | |
383 for (var i = 0; i < addons.length; i++) | |
384 params.appendElement(addons[i], false); | |
385 var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. | |
386 getService(Ci.nsIWindowWatcher); | |
387 ww.openWindow(win, "chrome://nightly/content/extensions/incompatible.xul", "", | |
388 "chrome,centerscreen,modal,dialog,titlebar", params); | |
389 return true; | |
390 }, | |
391 | |
392 // nsIAddonInstallListener implementation | |
393 onDownloadStarted: function(addon) { | |
394 }, | |
395 | |
396 onDownloadProgress: function(addon, value, maxValue) { | |
397 }, | |
398 | |
399 onDownloadEnded: function(addon) { | |
400 }, | |
401 | |
402 onInstallStarted: function(addon) { | |
403 LOG("Install Started for " + addon.xpiURL); | |
404 var ioServ = Cc["@mozilla.org/network/io-service;1"]. | |
405 getService(Ci.nsIIOService); | |
406 var fph = ioServ.getProtocolHandler("file") | |
407 .QueryInterface(Ci.nsIFileProtocolHandler); | |
408 var file = fph.getFileFromURLSpec(addon.xpiURL); | |
409 if (file.exists()) { | |
410 try { | |
411 var addon = new nttAddonDetail(); | |
412 addon.initWithXPI(file); | |
413 if (addon.isValid()) { | |
414 if (!addon.isCompatible(false)) { | |
415 // Check if there are remote updates available | |
416 var checker = new nttAddonUpdateChecker(addon); | |
417 checker.checkForUpdates(); | |
418 } | |
419 | |
420 if (addon.needsOverride(false)) | |
421 this.confirmOverride([addon], 1); | |
422 else | |
423 LOG("Add-on is already compatible: '" + addon.updateRDF + "' " + addon.minAppVersion + "-" + addon.maxAppVersion); | |
424 } | |
425 else { | |
426 WARN("Add-on seems to be invalid"); | |
427 } | |
428 addon.cleanup(); | |
429 } | |
430 catch (e) { | |
431 ERROR("Exception during compatibility check " + e); | |
432 } | |
433 } | |
434 }, | |
435 | |
436 onCompatibilityCheckStarted: function(addon) { | |
437 }, | |
438 | |
439 onCompatibilityCheckEnded: function(addon, status) { | |
440 }, | |
441 | |
442 onInstallEnded: function(addon, status) { | |
443 }, | |
444 | |
445 onInstallsCompleted: function() { | |
446 }, | |
447 | |
448 // nsIObserver implementation | |
449 observe: function(subject, topic, data) { | |
450 switch (topic) { | |
451 case "app-startup": | |
452 var os = Cc["@mozilla.org/observer-service;1"]. | |
453 getService(Ci.nsIObserverService); | |
454 os.addObserver(this, "profile-after-change", false); | |
455 os.addObserver(this, "quit-application", false); | |
456 break; | |
457 case "profile-after-change": | |
458 this.init(); | |
459 break; | |
460 case "quit-application": | |
461 if (this.id) | |
462 gEM.removeInstallListenerAt(this.id); | |
463 gEM = null; | |
464 gRDF = null; | |
465 gApp = null; | |
466 gVC = null; | |
467 gPrefs.removeObserver("", this); | |
468 gPrefs = null; | |
469 break; | |
470 case "nsPref:changed": | |
471 switch (data) { | |
472 case "checkCompatibility": | |
473 try { | |
474 gCheckCompatibility = gPrefs.getBoolPref(data); | |
475 } | |
476 catch (e) { | |
477 gCheckCompatibility = true; | |
478 } | |
479 break; | |
480 case "checkUpdateSecurity": | |
481 try { | |
482 gCheckUpdateSecurity = gPrefs.getBoolPref(data); | |
483 } | |
484 catch (e) { | |
485 gCheckUpdateSecurity = true; | |
486 } | |
487 break; | |
488 } | |
489 break; | |
490 default: | |
491 WARN("Unknown event " + topic); | |
492 } | |
493 }, | |
494 | |
495 classDescription: "Nightly Tester Install Monitor", | |
496 contractID: "@oxymoronical.com/nightly/addoncompatibility;1", | |
497 classID: Components.ID("{801207d5-037c-4565-80ed-ede8f7a7c100}"), | |
498 QueryInterface: XPCOMUtils.generateQI([Ci.nttIAddonCompatibilityService, Ci.nsIAddonInstallListener, Ci.nsIObserver]), | |
499 _xpcom_categories: [{ | |
500 category: "app-startup", | |
501 service: true | |
502 }] | |
503 } | |
504 | |
505 function NSGetModule(compMgr, fileSpec) | |
506 XPCOMUtils.generateModule([nttAddonCompatibilityService]); |