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]); |
