Mercurial > nightly_tester_tools
view components/nttAddonCompatibilityService.js @ 0:dada0ac40a8f
initial import
author | Yoshiki Yazawa <yaz@honeyplanet.jp> |
---|---|
date | Tue, 02 Dec 2008 20:31:01 +0900 |
parents | |
children |
line wrap: on
line source
const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; const PREFIX_ITEM_URI = "urn:mozilla:item:"; const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest"; const FILE_INSTALL_MANIFEST = "install.rdf"; const TOOLKIT_ID = "toolkit@mozilla.org" var gEM = null; var gRDF = null; var gApp = null; var gVC = null; var gCheckCompatibility = true; var gCheckUpdateSecurity = true; var gPrefs = null; function EM_NS(property) { return PREFIX_NS_EM + property; } function EM_R(property) { return gRDF.GetResource(EM_NS(property)); } function getRDFProperty(ds, source, property) { var value = ds.GetTarget(source, EM_R(property), true); if (value && value instanceof Ci.nsIRDFLiteral) return value.Value; return null; } function removeRDFProperty(ds, source, property) { var arc = EM_R(property); var targets = ds.GetTargets(source, arc, true); while (targets.hasMoreElements()) ds.Unassert(source, arc, targets.getNext()); } function extractXPI(xpi) { // XXX For 1.9 final we can switch to just extracting/compressing install.rdf var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. createInstance(Ci.nsIZipReader); zipReader.open(xpi); if (!zipReader.hasEntry(FILE_INSTALL_MANIFEST)) { zipReader.close(); return null; } var dirs = Cc["@mozilla.org/file/directory_service;1"]. getService(Ci.nsIProperties); var file = dirs.get("TmpD", Ci.nsILocalFile); file.append("tmpxpi"); file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0755); var entries = zipReader.findEntries("*"); while (entries.hasMore()) { var path = entries.getNext(); var entry = zipReader.getEntry(path); if (path.substring(path.length - 1) == "/") path = path.substring(0, entry.length - 1); var parts = path.split("/"); var target = file.clone(); for (var i = 0; i < parts.length; i++) target.append(parts[i]); if (entry.isDirectory) { if (!target.exists()) target.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); } else { var parent = target.parent; if (!parent.exists()) parent.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); zipReader.extract(path, target); } } zipReader.close(); return file; } function loadManifest(file) { var ioServ = Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService); var fph = ioServ.getProtocolHandler("file") .QueryInterface(Ci.nsIFileProtocolHandler); return gRDF.GetDataSourceBlocking(fph.getURLSpecFromFile(file)); } function recursiveUpdate(zipWriter, path, dir) { var entries = dir.directoryEntries; while (entries.hasMoreElements()) { var entry = entries.getNext().QueryInterface(Ci.nsIFile); if (entry.isDirectory()) { var newPath = path + entry.leafName + "/"; zipWriter.addEntryDirectory(newPath, entry.lastModifiedTime, false); recursiveUpdate(zipWriter, newPath, entry); } else { zipWriter.addEntryFile(path + entry.leafName, Ci.nsIZipWriter.COMPRESSION_NONE, entry, false); } } } function updateXPI(xpi, file) { // XXX For 1.9 final we can switch to just extracting/compressing install.rdf var zipWriter = Cc["@mozilla.org/zipwriter;1"]. createInstance(Ci.nsIZipWriter); zipWriter.open(xpi, 0x04 | 0x08 | 0x20); recursiveUpdate(zipWriter, "", file); zipWriter.close(); } function nttAddonUpdateChecker(addon) { this.addon = addon; } nttAddonUpdateChecker.prototype = { addon: null, busy: null, checkForUpdates: function() { this.busy = true; LOG("Searching for compatibility information for " + this.addon.id); gEM.update([this.addon], 1, Ci.nsIExtensionManager.UPDATE_SYNC_COMPATIBILITY, this); // Spin an event loop to wait for the update check to complete. var tm = Cc["@mozilla.org/thread-manager;1"]. getService(Ci.nsIThreadManager); var thread = tm.currentThread; while (this.busy) thread.processNextEvent(true); }, // nsIAddonUpdateCheckListener implementation onUpdateStarted: function() { }, onUpdateEnded: function() { this.busy = false; }, onAddonUpdateStarted: function(addon) { }, onAddonUpdateEnded: function(addon, status) { if (status & Ci.nsIAddonUpdateCheckListener.STATUS_DATA_FOUND) { LOG("Found new compatibility information for " + addon.id + ": " + addon.minAppVersion + " " + addon.maxAppVersion); this.addon.minAppVersion = addon.minAppVersion; this.addon.maxAppVersion = addon.maxAppVersion; this.addon.targetAppID = addon.targetAppID; this.addon.overrideVersions(); } } }; function nttAddonDetail() { } nttAddonDetail.prototype = { datasource: null, root: null, xpi: null, file: null, id: null, name: null, version: null, type: Ci.nsIUpdateItem.TYPE_EXTENSION, updateRDF: null, updateKey: null, iconURL: "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png", installLocationKey: null, xpiURL: null, xpiHash: null, appResource: null, targetAppID: null, minAppVersion: null, maxAppVersion: null, init: function() { if (!this.id) this.id = getRDFProperty(this.datasource, this.root, "id"); this.name = getRDFProperty(this.datasource, this.root, "name"); this.version = getRDFProperty(this.datasource, this.root, "version"); this.updateRDF = getRDFProperty(this.datasource, this.root, "updateURL"); this.updateKey = getRDFProperty(this.datasource, this.root, "updateKey"); var apps = this.datasource.GetTargets(this.root, EM_R("targetApplication"), true); while (apps.hasMoreElements()) { var app = apps.getNext().QueryInterface(Ci.nsIRDFResource); var id = getRDFProperty(this.datasource, app, "id"); if (id == gApp.ID || id == TOOLKIT_ID) { this.minAppVersion = getRDFProperty(this.datasource, app, "minVersion"); this.maxAppVersion = getRDFProperty(this.datasource, app, "maxVersion"); if (this.minAppVersion && this.maxAppVersion) { this.appResource = app; this.targetAppID = id; if (id == gApp.ID) break; } } } }, initWithXPI: function(xpi) { this.xpi = xpi; this.file = extractXPI(xpi); var rdf = this.file.clone(); rdf.append(FILE_INSTALL_MANIFEST); this.datasource = loadManifest(rdf); this.root = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT); this.init(); }, initWithDataSource: function(ds, root, id) { this.datasource = ds; this.root = root; this.id = id; this.init(); }, cleanup: function() { if (this.file && this.file.exists) this.file.remove(true); }, overrideVersions: function() { removeRDFProperty(this.datasource, this.appResource, "minVersion"); this.datasource.Assert(this.appResource, EM_R("minVersion"), gRDF.GetLiteral(this.minAppVersion), true); removeRDFProperty(this.datasource, this.appResource, "maxVersion"); this.datasource.Assert(this.appResource, EM_R("maxVersion"), gRDF.GetLiteral(this.maxAppVersion), true); this.datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush(); if (this.xpi && this.file) updateXPI(this.xpi, this.file); }, overrideCompatibility: function(ignorePrefs) { if (!this.isValid()) return; var changed = false; if (gCheckCompatibility || ignorePrefs) { var version = (gApp.ID == this.targetAppID) ? gApp.version : gApp.platformVersion; if (gVC.compare(version, this.minAppVersion) < 0) { LOG("minVersion " + this.minAppVersion + " is too high, reducing to " + version); if (!this.datasource.GetTarget(this.appResource, EM_R("oldMinVersion"), true)) this.datasource.Assert(this.appResource, EM_R("oldMinVersion"), gRDF.GetLiteral(this.minAppVersion), true); removeRDFProperty(this.datasource, this.appResource, "minVersion"); this.datasource.Assert(this.appResource, EM_R("minVersion"), gRDF.GetLiteral(version), true); this.minAppVersion = version; changed = true; } else if (gVC.compare(version, this.maxAppVersion) > 0) { LOG("maxVersion " + this.maxAppVersion + " is too low, increasing to " + version); if (!this.datasource.GetTarget(this.appResource, EM_R("oldMaxVersion"), true)) this.datasource.Assert(this.appResource, EM_R("oldMaxVersion"), gRDF.GetLiteral(this.maxAppVersion), true); removeRDFProperty(this.datasource, this.appResource, "maxVersion"); this.datasource.Assert(this.appResource, EM_R("maxVersion"), gRDF.GetLiteral(version), true); this.maxAppVersion = version; changed = true; } if (changed && !this.xpi) { // This updates any UI bound to the datasource var compatprop = EM_R("compatible"); var truth = gRDF.GetLiteral("true"); this.datasource.Assert(this.root, compatprop, truth, true); this.datasource.Unassert(this.root, compatprop, truth); } } if (!this.isUpdateSecure(ignorePrefs)) { LOG("Addon is insecure, removing update URL"); removeRDFProperty(this.datasource, this.root, "updateURL"); this.updateRDF = null; changed = true; // This updates any UI bound to the datasource compatprop = EM_R("providesUpdatesSecurely"); truth = gRDF.GetLiteral("true"); this.datasource.Assert(this.root, compatprop, truth, true); this.datasource.Unassert(this.root, compatprop, truth); } if (changed) { this.datasource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush(); if (this.xpi && this.file) updateXPI(this.xpi, this.file); } }, isValid: function() { return !!this.appResource; }, isCompatible: function(ignorePrefs) { if (!gCheckCompatibility && !ignorePrefs) return true; var version = (gApp.ID == this.targetAppID) ? gApp.version : gApp.platformVersion; if (gVC.compare(version, this.minAppVersion) < 0) return false; if (gVC.compare(version, this.maxAppVersion) > 0) return false; return true; }, isUpdateSecure: function(ignorePrefs) { if (!gCheckUpdateSecurity && !ignorePrefs) return true; if (!this.updateRDF) return true; if (this.updateKey) return true; return (this.updateRDF.substring(0, 6) == "https:"); }, needsOverride: function(ignorePrefs) { return (!this.isCompatible(ignorePrefs) || !this.isUpdateSecure(ignorePrefs)); }, QueryInterface: XPCOMUtils.generateQI([Ci.nttIAddon, Ci.nsIUpdateItem]), }; function nttAddonCompatibilityService() { } nttAddonCompatibilityService.prototype = { id: null, init: function() { Components.utils.import("resource://nightly/Logging.jsm"); gEM = Cc["@mozilla.org/extensions/manager;1"]. getService(Ci.nsIExtensionManager); gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. getService(Ci.nsIRDFService); gApp = Cc["@mozilla.org/xre/app-info;1"]. getService(Ci.nsIXULAppInfo).QueryInterface(Ci.nsIXULRuntime); gVC = Cc["@mozilla.org/xpcom/version-comparator;1"]. getService(Ci.nsIVersionComparator); if (gVC.compare(gApp.platformVersion, "1.9b5") >= 0) this.id = gEM.addInstallListener(this); gPrefs = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefService) .getBranch("extensions.") .QueryInterface(Components.interfaces.nsIPrefBranch2); try { gCheckCompatibility = gPrefs.getBoolPref("checkCompatibility"); } catch (e) { } try { gCheckUpdateSecurity = gPrefs.getBoolPref("checkUpdateSecurity"); } catch (e) { } gPrefs.addObserver("", this, false); }, // nsIAddonCompatibilityService implementation getAddonForID: function(id) { var addon = new nttAddonDetail(); addon.initWithDataSource(gEM.datasource, gRDF.GetResource(PREFIX_ITEM_URI + id), id); return addon; }, confirmOverride: function(addons, count) { var wm = Cc["@mozilla.org/appshell/window-mediator;1"]. getService(Ci.nsIWindowMediator); win = wm.getMostRecentWindow("Extension:Manager"); if (win && win.top) win = win.top; var params = Cc["@mozilla.org/array;1"]. createInstance(Ci.nsIMutableArray); for (var i = 0; i < addons.length; i++) params.appendElement(addons[i], false); var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. getService(Ci.nsIWindowWatcher); ww.openWindow(win, "chrome://nightly/content/extensions/incompatible.xul", "", "chrome,centerscreen,modal,dialog,titlebar", params); return true; }, // nsIAddonInstallListener implementation onDownloadStarted: function(addon) { }, onDownloadProgress: function(addon, value, maxValue) { }, onDownloadEnded: function(addon) { }, onInstallStarted: function(addon) { LOG("Install Started for " + addon.xpiURL); var ioServ = Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService); var fph = ioServ.getProtocolHandler("file") .QueryInterface(Ci.nsIFileProtocolHandler); var file = fph.getFileFromURLSpec(addon.xpiURL); if (file.exists()) { try { var addon = new nttAddonDetail(); addon.initWithXPI(file); if (addon.isValid()) { if (!addon.isCompatible(false)) { // Check if there are remote updates available var checker = new nttAddonUpdateChecker(addon); checker.checkForUpdates(); } if (addon.needsOverride(false)) this.confirmOverride([addon], 1); else LOG("Add-on is already compatible: '" + addon.updateRDF + "' " + addon.minAppVersion + "-" + addon.maxAppVersion); } else { WARN("Add-on seems to be invalid"); } addon.cleanup(); } catch (e) { ERROR("Exception during compatibility check " + e); } } }, onCompatibilityCheckStarted: function(addon) { }, onCompatibilityCheckEnded: function(addon, status) { }, onInstallEnded: function(addon, status) { }, onInstallsCompleted: function() { }, // nsIObserver implementation observe: function(subject, topic, data) { switch (topic) { case "app-startup": var os = Cc["@mozilla.org/observer-service;1"]. getService(Ci.nsIObserverService); os.addObserver(this, "profile-after-change", false); os.addObserver(this, "quit-application", false); break; case "profile-after-change": this.init(); break; case "quit-application": if (this.id) gEM.removeInstallListenerAt(this.id); gEM = null; gRDF = null; gApp = null; gVC = null; gPrefs.removeObserver("", this); gPrefs = null; break; case "nsPref:changed": switch (data) { case "checkCompatibility": try { gCheckCompatibility = gPrefs.getBoolPref(data); } catch (e) { gCheckCompatibility = true; } break; case "checkUpdateSecurity": try { gCheckUpdateSecurity = gPrefs.getBoolPref(data); } catch (e) { gCheckUpdateSecurity = true; } break; } break; default: WARN("Unknown event " + topic); } }, classDescription: "Nightly Tester Install Monitor", contractID: "@oxymoronical.com/nightly/addoncompatibility;1", classID: Components.ID("{801207d5-037c-4565-80ed-ede8f7a7c100}"), QueryInterface: XPCOMUtils.generateQI([Ci.nttIAddonCompatibilityService, Ci.nsIAddonInstallListener, Ci.nsIObserver]), _xpcom_categories: [{ category: "app-startup", service: true }] } function NSGetModule(compMgr, fileSpec) XPCOMUtils.generateModule([nttAddonCompatibilityService]);