1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/uriloader/exthandler/nsHandlerService.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1407 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +const Ci = Components.interfaces; 1.9 +const Cc = Components.classes; 1.10 +const Cu = Components.utils; 1.11 +const Cr = Components.results; 1.12 + 1.13 + 1.14 +const CLASS_MIMEINFO = "mimetype"; 1.15 +const CLASS_PROTOCOLINFO = "scheme"; 1.16 + 1.17 + 1.18 +// namespace prefix 1.19 +const NC_NS = "http://home.netscape.com/NC-rdf#"; 1.20 + 1.21 +// the most recent default handlers that have been injected. Note that 1.22 +// this is used to construct an RDF resource, which needs to have NC_NS 1.23 +// prepended, since that hasn't been done yet 1.24 +const DEFAULT_HANDLERS_VERSION = "defaultHandlersVersion"; 1.25 + 1.26 +// type list properties 1.27 + 1.28 +const NC_MIME_TYPES = NC_NS + "MIME-types"; 1.29 +const NC_PROTOCOL_SCHEMES = NC_NS + "Protocol-Schemes"; 1.30 + 1.31 +// content type ("type") properties 1.32 + 1.33 +// nsIHandlerInfo::type 1.34 +const NC_VALUE = NC_NS + "value"; 1.35 +const NC_DESCRIPTION = NC_NS + "description"; 1.36 + 1.37 +// additional extensions 1.38 +const NC_FILE_EXTENSIONS = NC_NS + "fileExtensions"; 1.39 + 1.40 +// references nsIHandlerInfo record 1.41 +const NC_HANDLER_INFO = NC_NS + "handlerProp"; 1.42 + 1.43 +// handler info ("info") properties 1.44 + 1.45 +// nsIHandlerInfo::preferredAction 1.46 +const NC_SAVE_TO_DISK = NC_NS + "saveToDisk"; 1.47 +const NC_HANDLE_INTERNALLY = NC_NS + "handleInternal"; 1.48 +const NC_USE_SYSTEM_DEFAULT = NC_NS + "useSystemDefault"; 1.49 + 1.50 +// nsIHandlerInfo::alwaysAskBeforeHandling 1.51 +const NC_ALWAYS_ASK = NC_NS + "alwaysAsk"; 1.52 + 1.53 +// references nsIHandlerApp records 1.54 +const NC_PREFERRED_APP = NC_NS + "externalApplication"; 1.55 +const NC_POSSIBLE_APP = NC_NS + "possibleApplication"; 1.56 + 1.57 +// handler app ("handler") properties 1.58 + 1.59 +// nsIHandlerApp::name 1.60 +const NC_PRETTY_NAME = NC_NS + "prettyName"; 1.61 + 1.62 +// nsILocalHandlerApp::executable 1.63 +const NC_PATH = NC_NS + "path"; 1.64 + 1.65 +// nsIWebHandlerApp::uriTemplate 1.66 +const NC_URI_TEMPLATE = NC_NS + "uriTemplate"; 1.67 + 1.68 +// nsIDBusHandlerApp::service 1.69 +const NC_SERVICE = NC_NS + "service"; 1.70 + 1.71 +// nsIDBusHandlerApp::method 1.72 +const NC_METHOD = NC_NS + "method"; 1.73 + 1.74 +// nsIDBusHandlerApp::objectPath 1.75 +const NC_OBJPATH = NC_NS + "objectPath"; 1.76 + 1.77 +// nsIDBusHandlerApp::dbusInterface 1.78 +const NC_INTERFACE = NC_NS + "dBusInterface"; 1.79 + 1.80 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.81 + 1.82 + 1.83 +function HandlerService() { 1.84 + this._init(); 1.85 +} 1.86 + 1.87 +const HandlerServiceFactory = { 1.88 + _instance: null, 1.89 + createInstance: function (outer, iid) { 1.90 + if (this._instance) 1.91 + return this._instance; 1.92 + 1.93 + let processType = Cc["@mozilla.org/xre/runtime;1"]. 1.94 + getService(Ci.nsIXULRuntime).processType; 1.95 + if (processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) 1.96 + return Cr.NS_ERROR_NOT_IMPLEMENTED; 1.97 + 1.98 + return (this._instance = new HandlerService()); 1.99 + } 1.100 +}; 1.101 + 1.102 +HandlerService.prototype = { 1.103 + //**************************************************************************// 1.104 + // XPCOM Plumbing 1.105 + 1.106 + classID: Components.ID("{32314cc8-22f7-4f7f-a645-1a45453ba6a6}"), 1.107 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIHandlerService]), 1.108 + _xpcom_factory: HandlerServiceFactory, 1.109 + 1.110 + //**************************************************************************// 1.111 + // Initialization & Destruction 1.112 + 1.113 + _init: function HS__init() { 1.114 + // Observe profile-before-change so we can switch to the datasource 1.115 + // in the new profile when the user changes profiles. 1.116 + this._observerSvc.addObserver(this, "profile-before-change", false); 1.117 + 1.118 + // Observe xpcom-shutdown so we can remove these observers 1.119 + // when the application shuts down. 1.120 + this._observerSvc.addObserver(this, "xpcom-shutdown", false); 1.121 + 1.122 + // Observe profile-do-change so that non-default profiles get upgraded too 1.123 + this._observerSvc.addObserver(this, "profile-do-change", false); 1.124 + 1.125 + // do any necessary updating of the datastore 1.126 + this._updateDB(); 1.127 + }, 1.128 + 1.129 + _updateDB: function HS__updateDB() { 1.130 + try { 1.131 + var defaultHandlersVersion = this._datastoreDefaultHandlersVersion; 1.132 + } catch(ex) { 1.133 + // accessing the datastore failed, we can't update anything 1.134 + return; 1.135 + } 1.136 + 1.137 + try { 1.138 + // if we don't have the current version of the default prefs for 1.139 + // this locale, inject any new default handers into the datastore 1.140 + if (defaultHandlersVersion < this._prefsDefaultHandlersVersion) { 1.141 + 1.142 + // set the new version first so that if we recurse we don't 1.143 + // call _injectNewDefaults several times 1.144 + this._datastoreDefaultHandlersVersion = 1.145 + this._prefsDefaultHandlersVersion; 1.146 + this._injectNewDefaults(); 1.147 + } 1.148 + } catch (ex) { 1.149 + // if injecting the defaults failed, set the version back to the 1.150 + // previous value 1.151 + this._datastoreDefaultHandlersVersion = defaultHandlersVersion; 1.152 + } 1.153 + }, 1.154 + 1.155 + get _currentLocale() { 1.156 + var chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"]. 1.157 + getService(Ci.nsIXULChromeRegistry); 1.158 + var currentLocale = chromeRegistry.getSelectedLocale("global"); 1.159 + return currentLocale; 1.160 + }, 1.161 + 1.162 + _destroy: function HS__destroy() { 1.163 + this._observerSvc.removeObserver(this, "profile-before-change"); 1.164 + this._observerSvc.removeObserver(this, "xpcom-shutdown"); 1.165 + this._observerSvc.removeObserver(this, "profile-do-change"); 1.166 + 1.167 + // XXX Should we also null references to all the services that get stored 1.168 + // by our memoizing getters in the Convenience Getters section? 1.169 + }, 1.170 + 1.171 + _onProfileChange: function HS__onProfileChange() { 1.172 + // Lose our reference to the datasource so we reacquire it 1.173 + // from the new profile the next time we need it. 1.174 + this.__ds = null; 1.175 + }, 1.176 + 1.177 + _isInHandlerArray: function HS__isInHandlerArray(aArray, aHandler) { 1.178 + var enumerator = aArray.enumerate(); 1.179 + while (enumerator.hasMoreElements()) { 1.180 + let handler = enumerator.getNext(); 1.181 + handler.QueryInterface(Ci.nsIHandlerApp); 1.182 + if (handler.equals(aHandler)) 1.183 + return true; 1.184 + } 1.185 + 1.186 + return false; 1.187 + }, 1.188 + 1.189 + // note that this applies to the current locale only 1.190 + get _datastoreDefaultHandlersVersion() { 1.191 + var version = this._getValue("urn:root", NC_NS + this._currentLocale + 1.192 + "_" + DEFAULT_HANDLERS_VERSION); 1.193 + 1.194 + return version ? version : -1; 1.195 + }, 1.196 + 1.197 + set _datastoreDefaultHandlersVersion(aNewVersion) { 1.198 + return this._setLiteral("urn:root", NC_NS + this._currentLocale + "_" + 1.199 + DEFAULT_HANDLERS_VERSION, aNewVersion); 1.200 + }, 1.201 + 1.202 + get _prefsDefaultHandlersVersion() { 1.203 + // get handler service pref branch 1.204 + var prefSvc = Cc["@mozilla.org/preferences-service;1"]. 1.205 + getService(Ci.nsIPrefService); 1.206 + var handlerSvcBranch = prefSvc.getBranch("gecko.handlerService."); 1.207 + 1.208 + // get the version of the preferences for this locale 1.209 + return Number(handlerSvcBranch. 1.210 + getComplexValue("defaultHandlersVersion", 1.211 + Ci.nsIPrefLocalizedString).data); 1.212 + }, 1.213 + 1.214 + _injectNewDefaults: function HS__injectNewDefaults() { 1.215 + // get handler service pref branch 1.216 + var prefSvc = Cc["@mozilla.org/preferences-service;1"]. 1.217 + getService(Ci.nsIPrefService); 1.218 + 1.219 + let schemesPrefBranch = prefSvc.getBranch("gecko.handlerService.schemes."); 1.220 + let schemePrefList = schemesPrefBranch.getChildList(""); 1.221 + 1.222 + var schemes = {}; 1.223 + 1.224 + // read all the scheme prefs into a hash 1.225 + for each (var schemePrefName in schemePrefList) { 1.226 + 1.227 + let [scheme, handlerNumber, attribute] = schemePrefName.split("."); 1.228 + 1.229 + try { 1.230 + var attrData = 1.231 + schemesPrefBranch.getComplexValue(schemePrefName, 1.232 + Ci.nsIPrefLocalizedString).data; 1.233 + if (!(scheme in schemes)) 1.234 + schemes[scheme] = {}; 1.235 + 1.236 + if (!(handlerNumber in schemes[scheme])) 1.237 + schemes[scheme][handlerNumber] = {}; 1.238 + 1.239 + schemes[scheme][handlerNumber][attribute] = attrData; 1.240 + } catch (ex) {} 1.241 + } 1.242 + 1.243 + let protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. 1.244 + getService(Ci.nsIExternalProtocolService); 1.245 + for (var scheme in schemes) { 1.246 + 1.247 + // This clause is essentially a reimplementation of 1.248 + // nsIExternalProtocolHandlerService.getProtocolHandlerInfo(). 1.249 + // Necessary because calling that from here would make XPConnect barf 1.250 + // when getService tried to re-enter the constructor for this 1.251 + // service. 1.252 + let osDefaultHandlerFound = {}; 1.253 + let protoInfo = protoSvc.getProtocolHandlerInfoFromOS(scheme, 1.254 + osDefaultHandlerFound); 1.255 + 1.256 + if (this.exists(protoInfo)) 1.257 + this.fillHandlerInfo(protoInfo, null); 1.258 + else 1.259 + protoSvc.setProtocolHandlerDefaults(protoInfo, 1.260 + osDefaultHandlerFound.value); 1.261 + 1.262 + // cache the possible handlers to avoid extra xpconnect traversals. 1.263 + let possibleHandlers = protoInfo.possibleApplicationHandlers; 1.264 + 1.265 + for each (var handlerPrefs in schemes[scheme]) { 1.266 + 1.267 + let handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"]. 1.268 + createInstance(Ci.nsIWebHandlerApp); 1.269 + 1.270 + handlerApp.uriTemplate = handlerPrefs.uriTemplate; 1.271 + handlerApp.name = handlerPrefs.name; 1.272 + 1.273 + if (!this._isInHandlerArray(possibleHandlers, handlerApp)) { 1.274 + possibleHandlers.appendElement(handlerApp, false); 1.275 + } 1.276 + } 1.277 + 1.278 + this.store(protoInfo); 1.279 + } 1.280 + }, 1.281 + 1.282 + //**************************************************************************// 1.283 + // nsIObserver 1.284 + 1.285 + observe: function HS__observe(subject, topic, data) { 1.286 + switch(topic) { 1.287 + case "profile-before-change": 1.288 + this._onProfileChange(); 1.289 + break; 1.290 + case "xpcom-shutdown": 1.291 + this._destroy(); 1.292 + break; 1.293 + case "profile-do-change": 1.294 + this._updateDB(); 1.295 + break; 1.296 + } 1.297 + }, 1.298 + 1.299 + 1.300 + //**************************************************************************// 1.301 + // nsIHandlerService 1.302 + 1.303 + enumerate: function HS_enumerate() { 1.304 + var handlers = Cc["@mozilla.org/array;1"]. 1.305 + createInstance(Ci.nsIMutableArray); 1.306 + this._appendHandlers(handlers, CLASS_MIMEINFO); 1.307 + this._appendHandlers(handlers, CLASS_PROTOCOLINFO); 1.308 + return handlers.enumerate(); 1.309 + }, 1.310 + 1.311 + fillHandlerInfo: function HS_fillHandlerInfo(aHandlerInfo, aOverrideType) { 1.312 + var type = aOverrideType || aHandlerInfo.type; 1.313 + var typeID = this._getTypeID(this._getClass(aHandlerInfo), type); 1.314 + 1.315 + // Determine whether or not information about this handler is available 1.316 + // in the datastore by looking for its "value" property, which stores its 1.317 + // type and should always be present. 1.318 + if (!this._hasValue(typeID, NC_VALUE)) 1.319 + throw Cr.NS_ERROR_NOT_AVAILABLE; 1.320 + 1.321 + // Retrieve the human-readable description of the type. 1.322 + if (this._hasValue(typeID, NC_DESCRIPTION)) 1.323 + aHandlerInfo.description = this._getValue(typeID, NC_DESCRIPTION); 1.324 + 1.325 + // Note: for historical reasons, we don't actually check that the type 1.326 + // record has a "handlerProp" property referencing the info record. It's 1.327 + // unclear whether or not we should start doing this check; perhaps some 1.328 + // legacy datasources don't have such references. 1.329 + var infoID = this._getInfoID(this._getClass(aHandlerInfo), type); 1.330 + 1.331 + aHandlerInfo.preferredAction = this._retrievePreferredAction(infoID); 1.332 + 1.333 + var preferredHandlerID = 1.334 + this._getPreferredHandlerID(this._getClass(aHandlerInfo), type); 1.335 + 1.336 + // Retrieve the preferred handler. 1.337 + // Note: for historical reasons, we don't actually check that the info 1.338 + // record has an "externalApplication" property referencing the preferred 1.339 + // handler record. It's unclear whether or not we should start doing 1.340 + // this check; perhaps some legacy datasources don't have such references. 1.341 + aHandlerInfo.preferredApplicationHandler = 1.342 + this._retrieveHandlerApp(preferredHandlerID); 1.343 + 1.344 + // Fill the array of possible handlers with the ones in the datastore. 1.345 + this._fillPossibleHandlers(infoID, 1.346 + aHandlerInfo.possibleApplicationHandlers, 1.347 + aHandlerInfo.preferredApplicationHandler); 1.348 + 1.349 + // If we have an "always ask" flag stored in the RDF, always use its 1.350 + // value. Otherwise, use the default value stored in the pref service. 1.351 + var alwaysAsk; 1.352 + if (this._hasValue(infoID, NC_ALWAYS_ASK)) { 1.353 + alwaysAsk = (this._getValue(infoID, NC_ALWAYS_ASK) != "false"); 1.354 + } else { 1.355 + var prefSvc = Cc["@mozilla.org/preferences-service;1"]. 1.356 + getService(Ci.nsIPrefService); 1.357 + var prefBranch = prefSvc.getBranch("network.protocol-handler."); 1.358 + try { 1.359 + alwaysAsk = prefBranch.getBoolPref("warn-external." + type); 1.360 + } catch (e) { 1.361 + // will throw if pref didn't exist. 1.362 + try { 1.363 + alwaysAsk = prefBranch.getBoolPref("warn-external-default"); 1.364 + } catch (e) { 1.365 + // Nothing to tell us what to do, so be paranoid and prompt. 1.366 + alwaysAsk = true; 1.367 + } 1.368 + } 1.369 + } 1.370 + aHandlerInfo.alwaysAskBeforeHandling = alwaysAsk; 1.371 + 1.372 + // If the object represents a MIME type handler, then also retrieve 1.373 + // any file extensions. 1.374 + if (aHandlerInfo instanceof Ci.nsIMIMEInfo) 1.375 + for each (let fileExtension in this._retrieveFileExtensions(typeID)) 1.376 + aHandlerInfo.appendExtension(fileExtension); 1.377 + }, 1.378 + 1.379 + store: function HS_store(aHandlerInfo) { 1.380 + // FIXME: when we switch from RDF to something with transactions (like 1.381 + // SQLite), enclose the following changes in a transaction so they all 1.382 + // get rolled back if any of them fail and we don't leave the datastore 1.383 + // in an inconsistent state. 1.384 + 1.385 + this._ensureRecordsForType(aHandlerInfo); 1.386 + this._storePreferredAction(aHandlerInfo); 1.387 + this._storePreferredHandler(aHandlerInfo); 1.388 + this._storePossibleHandlers(aHandlerInfo); 1.389 + this._storeAlwaysAsk(aHandlerInfo); 1.390 + 1.391 + // Write the changes to the database immediately so we don't lose them 1.392 + // if the application crashes. 1.393 + if (this._ds instanceof Ci.nsIRDFRemoteDataSource) 1.394 + this._ds.Flush(); 1.395 + }, 1.396 + 1.397 + exists: function HS_exists(aHandlerInfo) { 1.398 + var found; 1.399 + 1.400 + try { 1.401 + var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.402 + found = this._hasLiteralAssertion(typeID, NC_VALUE, aHandlerInfo.type); 1.403 + } catch (e) { 1.404 + // If the RDF threw (eg, corrupt file), treat as nonexistent. 1.405 + found = false; 1.406 + } 1.407 + 1.408 + return found; 1.409 + }, 1.410 + 1.411 + remove: function HS_remove(aHandlerInfo) { 1.412 + var preferredHandlerID = 1.413 + this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.414 + this._removeAssertions(preferredHandlerID); 1.415 + 1.416 + var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.417 + 1.418 + // Get a list of possible handlers. After we have removed the info record, 1.419 + // we'll check if any other info records reference these handlers, and we'll 1.420 + // remove the handler records that aren't referenced by other info records. 1.421 + var possibleHandlerIDs = []; 1.422 + var possibleHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP); 1.423 + while (possibleHandlerTargets.hasMoreElements()) { 1.424 + let possibleHandlerTarget = possibleHandlerTargets.getNext(); 1.425 + // Note: possibleHandlerTarget should always be an nsIRDFResource. 1.426 + // The conditional is just here in case of a corrupt RDF datasource. 1.427 + if (possibleHandlerTarget instanceof Ci.nsIRDFResource) 1.428 + possibleHandlerIDs.push(possibleHandlerTarget.ValueUTF8); 1.429 + } 1.430 + 1.431 + // Remove the info record. 1.432 + this._removeAssertions(infoID); 1.433 + 1.434 + // Now that we've removed the info record, remove any possible handlers 1.435 + // that aren't referenced by other info records. 1.436 + for each (let possibleHandlerID in possibleHandlerIDs) 1.437 + if (!this._existsResourceTarget(NC_POSSIBLE_APP, possibleHandlerID)) 1.438 + this._removeAssertions(possibleHandlerID); 1.439 + 1.440 + var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.441 + this._removeAssertions(typeID); 1.442 + 1.443 + // Now that there's no longer a handler for this type, remove the type 1.444 + // from the list of types for which there are known handlers. 1.445 + var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo)); 1.446 + var type = this._rdf.GetResource(typeID); 1.447 + var typeIndex = typeList.IndexOf(type); 1.448 + if (typeIndex != -1) 1.449 + typeList.RemoveElementAt(typeIndex, true); 1.450 + 1.451 + // Write the changes to the database immediately so we don't lose them 1.452 + // if the application crashes. 1.453 + // XXX If we're removing a bunch of handlers at once, will flushing 1.454 + // after every removal cause a significant performance hit? 1.455 + if (this._ds instanceof Ci.nsIRDFRemoteDataSource) 1.456 + this._ds.Flush(); 1.457 + }, 1.458 + 1.459 + getTypeFromExtension: function HS_getTypeFromExtension(aFileExtension) { 1.460 + var fileExtension = aFileExtension.toLowerCase(); 1.461 + var typeID; 1.462 + 1.463 + if (this._existsLiteralTarget(NC_FILE_EXTENSIONS, fileExtension)) 1.464 + typeID = this._getSourceForLiteral(NC_FILE_EXTENSIONS, fileExtension); 1.465 + 1.466 + if (typeID && this._hasValue(typeID, NC_VALUE)) { 1.467 + let type = this._getValue(typeID, NC_VALUE); 1.468 + if (type == "") 1.469 + throw Cr.NS_ERROR_FAILURE; 1.470 + return type; 1.471 + } 1.472 + 1.473 + return ""; 1.474 + }, 1.475 + 1.476 + 1.477 + //**************************************************************************// 1.478 + // Retrieval Methods 1.479 + 1.480 + /** 1.481 + * Retrieve the preferred action for the info record with the given ID. 1.482 + * 1.483 + * @param aInfoID {string} the info record ID 1.484 + * 1.485 + * @returns {integer} the preferred action enumeration value 1.486 + */ 1.487 + _retrievePreferredAction: function HS__retrievePreferredAction(aInfoID) { 1.488 + if (this._getValue(aInfoID, NC_SAVE_TO_DISK) == "true") 1.489 + return Ci.nsIHandlerInfo.saveToDisk; 1.490 + 1.491 + if (this._getValue(aInfoID, NC_USE_SYSTEM_DEFAULT) == "true") 1.492 + return Ci.nsIHandlerInfo.useSystemDefault; 1.493 + 1.494 + if (this._getValue(aInfoID, NC_HANDLE_INTERNALLY) == "true") 1.495 + return Ci.nsIHandlerInfo.handleInternally; 1.496 + 1.497 + return Ci.nsIHandlerInfo.useHelperApp; 1.498 + }, 1.499 + 1.500 + /** 1.501 + * Fill an array of possible handlers with the handlers for the given info ID. 1.502 + * 1.503 + * @param aInfoID {string} the ID of the info record 1.504 + * @param aPossibleHandlers {nsIMutableArray} the array of possible handlers 1.505 + * @param aPreferredHandler {nsIHandlerApp} the preferred handler, if any 1.506 + */ 1.507 + _fillPossibleHandlers: function HS__fillPossibleHandlers(aInfoID, 1.508 + aPossibleHandlers, 1.509 + aPreferredHandler) { 1.510 + // The set of possible handlers should include the preferred handler, 1.511 + // but legacy datastores (from before we added possible handlers) won't 1.512 + // include the preferred handler, so check if it's included as we build 1.513 + // the list of handlers, and, if it's not included, add it to the list. 1.514 + if (aPreferredHandler) 1.515 + aPossibleHandlers.appendElement(aPreferredHandler, false); 1.516 + 1.517 + var possibleHandlerTargets = this._getTargets(aInfoID, NC_POSSIBLE_APP); 1.518 + 1.519 + while (possibleHandlerTargets.hasMoreElements()) { 1.520 + let possibleHandlerTarget = possibleHandlerTargets.getNext(); 1.521 + if (!(possibleHandlerTarget instanceof Ci.nsIRDFResource)) 1.522 + continue; 1.523 + 1.524 + let possibleHandlerID = possibleHandlerTarget.ValueUTF8; 1.525 + let possibleHandler = this._retrieveHandlerApp(possibleHandlerID); 1.526 + if (possibleHandler && (!aPreferredHandler || 1.527 + !possibleHandler.equals(aPreferredHandler))) 1.528 + aPossibleHandlers.appendElement(possibleHandler, false); 1.529 + } 1.530 + }, 1.531 + 1.532 + /** 1.533 + * Retrieve the handler app object with the given ID. 1.534 + * 1.535 + * @param aHandlerAppID {string} the ID of the handler app to retrieve 1.536 + * 1.537 + * @returns {nsIHandlerApp} the handler app, if any; otherwise null 1.538 + */ 1.539 + _retrieveHandlerApp: function HS__retrieveHandlerApp(aHandlerAppID) { 1.540 + var handlerApp; 1.541 + 1.542 + // If it has a path, it's a local handler; otherwise, it's a web handler. 1.543 + if (this._hasValue(aHandlerAppID, NC_PATH)) { 1.544 + let executable = 1.545 + this._getFileWithPath(this._getValue(aHandlerAppID, NC_PATH)); 1.546 + if (!executable) 1.547 + return null; 1.548 + 1.549 + handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]. 1.550 + createInstance(Ci.nsILocalHandlerApp); 1.551 + handlerApp.executable = executable; 1.552 + } 1.553 + else if (this._hasValue(aHandlerAppID, NC_URI_TEMPLATE)) { 1.554 + let uriTemplate = this._getValue(aHandlerAppID, NC_URI_TEMPLATE); 1.555 + if (!uriTemplate) 1.556 + return null; 1.557 + 1.558 + handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"]. 1.559 + createInstance(Ci.nsIWebHandlerApp); 1.560 + handlerApp.uriTemplate = uriTemplate; 1.561 + } 1.562 + else if (this._hasValue(aHandlerAppID, NC_SERVICE)) { 1.563 + let service = this._getValue(aHandlerAppID, NC_SERVICE); 1.564 + if (!service) 1.565 + return null; 1.566 + 1.567 + let method = this._getValue(aHandlerAppID, NC_METHOD); 1.568 + if (!method) 1.569 + return null; 1.570 + 1.571 + let objpath = this._getValue(aHandlerAppID, NC_OBJPATH); 1.572 + if (!objpath) 1.573 + return null; 1.574 + 1.575 + let interface = this._getValue(aHandlerAppID, NC_INTERFACE); 1.576 + if (!interface) 1.577 + return null; 1.578 + 1.579 + handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"]. 1.580 + createInstance(Ci.nsIDBusHandlerApp); 1.581 + handlerApp.service = service; 1.582 + handlerApp.method = method; 1.583 + handlerApp.objectPath = objpath; 1.584 + handlerApp.dBusInterface = interface; 1.585 + 1.586 + } 1.587 + else 1.588 + return null; 1.589 + 1.590 + handlerApp.name = this._getValue(aHandlerAppID, NC_PRETTY_NAME); 1.591 + 1.592 + return handlerApp; 1.593 + }, 1.594 + 1.595 + /* 1.596 + * Retrieve file extensions, if any, for the MIME type with the given type ID. 1.597 + * 1.598 + * @param aTypeID {string} the type record ID 1.599 + */ 1.600 + _retrieveFileExtensions: function HS__retrieveFileExtensions(aTypeID) { 1.601 + var fileExtensions = []; 1.602 + 1.603 + var fileExtensionTargets = this._getTargets(aTypeID, NC_FILE_EXTENSIONS); 1.604 + 1.605 + while (fileExtensionTargets.hasMoreElements()) { 1.606 + let fileExtensionTarget = fileExtensionTargets.getNext(); 1.607 + if (fileExtensionTarget instanceof Ci.nsIRDFLiteral && 1.608 + fileExtensionTarget.Value != "") 1.609 + fileExtensions.push(fileExtensionTarget.Value); 1.610 + } 1.611 + 1.612 + return fileExtensions; 1.613 + }, 1.614 + 1.615 + /** 1.616 + * Get the file with the given path. This is not as simple as merely 1.617 + * initializing a local file object with the path, because the path might be 1.618 + * relative to the current process directory, in which case we have to 1.619 + * construct a path starting from that directory. 1.620 + * 1.621 + * @param aPath {string} a path to a file 1.622 + * 1.623 + * @returns {nsILocalFile} the file, or null if the file does not exist 1.624 + */ 1.625 + _getFileWithPath: function HS__getFileWithPath(aPath) { 1.626 + var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); 1.627 + 1.628 + try { 1.629 + file.initWithPath(aPath); 1.630 + 1.631 + if (file.exists()) 1.632 + return file; 1.633 + } 1.634 + catch(ex) { 1.635 + // Note: for historical reasons, we don't actually check to see 1.636 + // if the exception is NS_ERROR_FILE_UNRECOGNIZED_PATH, which is what 1.637 + // nsILocalFile::initWithPath throws when a path is relative. 1.638 + 1.639 + file = this._dirSvc.get("XCurProcD", Ci.nsIFile); 1.640 + 1.641 + try { 1.642 + file.append(aPath); 1.643 + if (file.exists()) 1.644 + return file; 1.645 + } 1.646 + catch(ex) {} 1.647 + } 1.648 + 1.649 + return null; 1.650 + }, 1.651 + 1.652 + 1.653 + //**************************************************************************// 1.654 + // Storage Methods 1.655 + 1.656 + _storePreferredAction: function HS__storePreferredAction(aHandlerInfo) { 1.657 + var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.658 + 1.659 + switch(aHandlerInfo.preferredAction) { 1.660 + case Ci.nsIHandlerInfo.saveToDisk: 1.661 + this._setLiteral(infoID, NC_SAVE_TO_DISK, "true"); 1.662 + this._removeTarget(infoID, NC_HANDLE_INTERNALLY); 1.663 + this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT); 1.664 + break; 1.665 + 1.666 + case Ci.nsIHandlerInfo.handleInternally: 1.667 + this._setLiteral(infoID, NC_HANDLE_INTERNALLY, "true"); 1.668 + this._removeTarget(infoID, NC_SAVE_TO_DISK); 1.669 + this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT); 1.670 + break; 1.671 + 1.672 + case Ci.nsIHandlerInfo.useSystemDefault: 1.673 + this._setLiteral(infoID, NC_USE_SYSTEM_DEFAULT, "true"); 1.674 + this._removeTarget(infoID, NC_SAVE_TO_DISK); 1.675 + this._removeTarget(infoID, NC_HANDLE_INTERNALLY); 1.676 + break; 1.677 + 1.678 + // This value is indicated in the datastore either by the absence of 1.679 + // the three properties or by setting them all "false". Of these two 1.680 + // options, the former seems preferable, because it reduces the size 1.681 + // of the RDF file and thus the amount of stuff we have to parse. 1.682 + case Ci.nsIHandlerInfo.useHelperApp: 1.683 + default: 1.684 + this._removeTarget(infoID, NC_SAVE_TO_DISK); 1.685 + this._removeTarget(infoID, NC_HANDLE_INTERNALLY); 1.686 + this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT); 1.687 + break; 1.688 + } 1.689 + }, 1.690 + 1.691 + _storePreferredHandler: function HS__storePreferredHandler(aHandlerInfo) { 1.692 + var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.693 + var handlerID = 1.694 + this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.695 + 1.696 + var handler = aHandlerInfo.preferredApplicationHandler; 1.697 + 1.698 + if (handler) { 1.699 + this._storeHandlerApp(handlerID, handler); 1.700 + 1.701 + // Make this app be the preferred app for the handler info. 1.702 + // 1.703 + // Note: nsExternalHelperAppService::FillContentHandlerProperties ignores 1.704 + // this setting and instead identifies the preferred app as the resource 1.705 + // whose URI follows the pattern urn:<class>:externalApplication:<type>. 1.706 + // But the old downloadactions.js code used to set this property, so just 1.707 + // in case there is still some code somewhere that relies on its presence, 1.708 + // we set it here. 1.709 + this._setResource(infoID, NC_PREFERRED_APP, handlerID); 1.710 + } 1.711 + else { 1.712 + // There isn't a preferred handler. Remove the existing record for it, 1.713 + // if any. 1.714 + this._removeTarget(infoID, NC_PREFERRED_APP); 1.715 + this._removeAssertions(handlerID); 1.716 + } 1.717 + }, 1.718 + 1.719 + /** 1.720 + * Store the list of possible handler apps for the content type represented 1.721 + * by the given handler info object. 1.722 + * 1.723 + * @param aHandlerInfo {nsIHandlerInfo} the handler info object 1.724 + */ 1.725 + _storePossibleHandlers: function HS__storePossibleHandlers(aHandlerInfo) { 1.726 + var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.727 + 1.728 + // First, retrieve the set of handler apps currently stored for the type, 1.729 + // keeping track of their IDs in a hash that we'll use to determine which 1.730 + // ones are no longer valid and should be removed. 1.731 + var currentHandlerApps = {}; 1.732 + var currentHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP); 1.733 + while (currentHandlerTargets.hasMoreElements()) { 1.734 + let handlerApp = currentHandlerTargets.getNext(); 1.735 + if (handlerApp instanceof Ci.nsIRDFResource) { 1.736 + let handlerAppID = handlerApp.ValueUTF8; 1.737 + currentHandlerApps[handlerAppID] = true; 1.738 + } 1.739 + } 1.740 + 1.741 + // Next, store any new handler apps. 1.742 + var newHandlerApps = 1.743 + aHandlerInfo.possibleApplicationHandlers.enumerate(); 1.744 + while (newHandlerApps.hasMoreElements()) { 1.745 + let handlerApp = 1.746 + newHandlerApps.getNext().QueryInterface(Ci.nsIHandlerApp); 1.747 + let handlerAppID = this._getPossibleHandlerAppID(handlerApp); 1.748 + if (!this._hasResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID)) { 1.749 + this._storeHandlerApp(handlerAppID, handlerApp); 1.750 + this._addResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID); 1.751 + } 1.752 + delete currentHandlerApps[handlerAppID]; 1.753 + } 1.754 + 1.755 + // Finally, remove any old handler apps that aren't being used anymore, 1.756 + // and if those handler apps aren't being used by any other type either, 1.757 + // then completely remove their record from the datastore so we don't 1.758 + // leave it clogged up with information about handler apps we don't care 1.759 + // about anymore. 1.760 + for (let handlerAppID in currentHandlerApps) { 1.761 + this._removeResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID); 1.762 + if (!this._existsResourceTarget(NC_POSSIBLE_APP, handlerAppID)) 1.763 + this._removeAssertions(handlerAppID); 1.764 + } 1.765 + }, 1.766 + 1.767 + /** 1.768 + * Store the given handler app. 1.769 + * 1.770 + * Note: the reason this method takes the ID of the handler app in a param 1.771 + * is that the ID is different than it usually is when the handler app 1.772 + * in question is a preferred handler app, so this method can't just derive 1.773 + * the ID of the handler app by calling _getPossibleHandlerAppID, its callers 1.774 + * have to do that for it. 1.775 + * 1.776 + * @param aHandlerAppID {string} the ID of the handler app to store 1.777 + * @param aHandlerApp {nsIHandlerApp} the handler app to store 1.778 + */ 1.779 + _storeHandlerApp: function HS__storeHandlerApp(aHandlerAppID, aHandlerApp) { 1.780 + aHandlerApp.QueryInterface(Ci.nsIHandlerApp); 1.781 + this._setLiteral(aHandlerAppID, NC_PRETTY_NAME, aHandlerApp.name); 1.782 + 1.783 + // In the case of the preferred handler, the handler ID could have been 1.784 + // used to refer to a different kind of handler in the past (i.e. either 1.785 + // a local hander or a web handler), so if the new handler is a local 1.786 + // handler, then we remove any web handler properties and vice versa. 1.787 + // This is unnecessary but harmless for possible handlers. 1.788 + 1.789 + if (aHandlerApp instanceof Ci.nsILocalHandlerApp) { 1.790 + this._setLiteral(aHandlerAppID, NC_PATH, aHandlerApp.executable.path); 1.791 + this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE); 1.792 + this._removeTarget(aHandlerAppID, NC_METHOD); 1.793 + this._removeTarget(aHandlerAppID, NC_SERVICE); 1.794 + this._removeTarget(aHandlerAppID, NC_OBJPATH); 1.795 + this._removeTarget(aHandlerAppID, NC_INTERFACE); 1.796 + } 1.797 + else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){ 1.798 + aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp); 1.799 + this._setLiteral(aHandlerAppID, NC_URI_TEMPLATE, aHandlerApp.uriTemplate); 1.800 + this._removeTarget(aHandlerAppID, NC_PATH); 1.801 + this._removeTarget(aHandlerAppID, NC_METHOD); 1.802 + this._removeTarget(aHandlerAppID, NC_SERVICE); 1.803 + this._removeTarget(aHandlerAppID, NC_OBJPATH); 1.804 + this._removeTarget(aHandlerAppID, NC_INTERFACE); 1.805 + } 1.806 + else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){ 1.807 + aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp); 1.808 + this._setLiteral(aHandlerAppID, NC_SERVICE, aHandlerApp.service); 1.809 + this._setLiteral(aHandlerAppID, NC_METHOD, aHandlerApp.method); 1.810 + this._setLiteral(aHandlerAppID, NC_OBJPATH, aHandlerApp.objectPath); 1.811 + this._setLiteral(aHandlerAppID, NC_INTERFACE, aHandlerApp.dBusInterface); 1.812 + this._removeTarget(aHandlerAppID, NC_PATH); 1.813 + this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE); 1.814 + } 1.815 + else { 1.816 + throw "unknown handler type"; 1.817 + } 1.818 + 1.819 + }, 1.820 + 1.821 + _storeAlwaysAsk: function HS__storeAlwaysAsk(aHandlerInfo) { 1.822 + var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.823 + this._setLiteral(infoID, 1.824 + NC_ALWAYS_ASK, 1.825 + aHandlerInfo.alwaysAskBeforeHandling ? "true" : "false"); 1.826 + }, 1.827 + 1.828 + 1.829 + //**************************************************************************// 1.830 + // Convenience Getters 1.831 + 1.832 + // Observer Service 1.833 + __observerSvc: null, 1.834 + get _observerSvc() { 1.835 + if (!this.__observerSvc) 1.836 + this.__observerSvc = 1.837 + Cc["@mozilla.org/observer-service;1"]. 1.838 + getService(Ci.nsIObserverService); 1.839 + return this.__observerSvc; 1.840 + }, 1.841 + 1.842 + // Directory Service 1.843 + __dirSvc: null, 1.844 + get _dirSvc() { 1.845 + if (!this.__dirSvc) 1.846 + this.__dirSvc = 1.847 + Cc["@mozilla.org/file/directory_service;1"]. 1.848 + getService(Ci.nsIProperties); 1.849 + return this.__dirSvc; 1.850 + }, 1.851 + 1.852 + // MIME Service 1.853 + __mimeSvc: null, 1.854 + get _mimeSvc() { 1.855 + if (!this.__mimeSvc) 1.856 + this.__mimeSvc = 1.857 + Cc["@mozilla.org/mime;1"]. 1.858 + getService(Ci.nsIMIMEService); 1.859 + return this.__mimeSvc; 1.860 + }, 1.861 + 1.862 + // Protocol Service 1.863 + __protocolSvc: null, 1.864 + get _protocolSvc() { 1.865 + if (!this.__protocolSvc) 1.866 + this.__protocolSvc = 1.867 + Cc["@mozilla.org/uriloader/external-protocol-service;1"]. 1.868 + getService(Ci.nsIExternalProtocolService); 1.869 + return this.__protocolSvc; 1.870 + }, 1.871 + 1.872 + // RDF Service 1.873 + __rdf: null, 1.874 + get _rdf() { 1.875 + if (!this.__rdf) 1.876 + this.__rdf = Cc["@mozilla.org/rdf/rdf-service;1"]. 1.877 + getService(Ci.nsIRDFService); 1.878 + return this.__rdf; 1.879 + }, 1.880 + 1.881 + // RDF Container Utils 1.882 + __containerUtils: null, 1.883 + get _containerUtils() { 1.884 + if (!this.__containerUtils) 1.885 + this.__containerUtils = Cc["@mozilla.org/rdf/container-utils;1"]. 1.886 + getService(Ci.nsIRDFContainerUtils); 1.887 + return this.__containerUtils; 1.888 + }, 1.889 + 1.890 + // RDF datasource containing content handling config (i.e. mimeTypes.rdf) 1.891 + __ds: null, 1.892 + get _ds() { 1.893 + if (!this.__ds) { 1.894 + var file = this._dirSvc.get("UMimTyp", Ci.nsIFile); 1.895 + // FIXME: make this a memoizing getter if we use it anywhere else. 1.896 + var ioService = Cc["@mozilla.org/network/io-service;1"]. 1.897 + getService(Ci.nsIIOService); 1.898 + var fileHandler = ioService.getProtocolHandler("file"). 1.899 + QueryInterface(Ci.nsIFileProtocolHandler); 1.900 + this.__ds = 1.901 + this._rdf.GetDataSourceBlocking(fileHandler.getURLSpecFromFile(file)); 1.902 + } 1.903 + 1.904 + return this.__ds; 1.905 + }, 1.906 + 1.907 + 1.908 + //**************************************************************************// 1.909 + // Datastore Utils 1.910 + 1.911 + /** 1.912 + * Get the string identifying whether this is a MIME or a protocol handler. 1.913 + * This string is used in the URI IDs of various RDF properties. 1.914 + * 1.915 + * @param aHandlerInfo {nsIHandlerInfo} the handler for which to get the class 1.916 + * 1.917 + * @returns {string} the class 1.918 + */ 1.919 + _getClass: function HS__getClass(aHandlerInfo) { 1.920 + if (aHandlerInfo instanceof Ci.nsIMIMEInfo) 1.921 + return CLASS_MIMEINFO; 1.922 + else 1.923 + return CLASS_PROTOCOLINFO; 1.924 + }, 1.925 + 1.926 + /** 1.927 + * Return the unique identifier for a content type record, which stores 1.928 + * the value field plus a reference to the content type's handler info record. 1.929 + * 1.930 + * |urn:<class>:<type>| 1.931 + * 1.932 + * XXX: should this be a property of nsIHandlerInfo? 1.933 + * 1.934 + * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO) 1.935 + * @param aType {string} the type (a MIME type or protocol scheme) 1.936 + * 1.937 + * @returns {string} the ID 1.938 + */ 1.939 + _getTypeID: function HS__getTypeID(aClass, aType) { 1.940 + return "urn:" + aClass + ":" + aType; 1.941 + }, 1.942 + 1.943 + /** 1.944 + * Return the unique identifier for a handler info record, which stores 1.945 + * the preferredAction and alwaysAsk fields plus a reference to the preferred 1.946 + * handler app. Roughly equivalent to the nsIHandlerInfo interface. 1.947 + * 1.948 + * |urn:<class>:handler:<type>| 1.949 + * 1.950 + * FIXME: the type info record should be merged into the type record, 1.951 + * since there's a one to one relationship between them, and this record 1.952 + * merely stores additional attributes of a content type. 1.953 + * 1.954 + * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO) 1.955 + * @param aType {string} the type (a MIME type or protocol scheme) 1.956 + * 1.957 + * @returns {string} the ID 1.958 + */ 1.959 + _getInfoID: function HS__getInfoID(aClass, aType) { 1.960 + return "urn:" + aClass + ":handler:" + aType; 1.961 + }, 1.962 + 1.963 + /** 1.964 + * Return the unique identifier for a preferred handler record, which stores 1.965 + * information about the preferred handler for a given content type, including 1.966 + * its human-readable name and the path to its executable (for a local app) 1.967 + * or its URI template (for a web app). 1.968 + * 1.969 + * |urn:<class>:externalApplication:<type>| 1.970 + * 1.971 + * XXX: should this be a property of nsIHandlerApp? 1.972 + * 1.973 + * FIXME: this should be an arbitrary ID, and we should retrieve it from 1.974 + * the datastore for a given content type via the NC:ExternalApplication 1.975 + * property rather than looking for a specific ID, so a handler doesn't 1.976 + * have to change IDs when it goes from being a possible handler to being 1.977 + * the preferred one (once we support possible handlers). 1.978 + * 1.979 + * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO) 1.980 + * @param aType {string} the type (a MIME type or protocol scheme) 1.981 + * 1.982 + * @returns {string} the ID 1.983 + */ 1.984 + _getPreferredHandlerID: function HS__getPreferredHandlerID(aClass, aType) { 1.985 + return "urn:" + aClass + ":externalApplication:" + aType; 1.986 + }, 1.987 + 1.988 + /** 1.989 + * Return the unique identifier for a handler app record, which stores 1.990 + * information about a possible handler for one or more content types, 1.991 + * including its human-readable name and the path to its executable (for a 1.992 + * local app) or its URI template (for a web app). 1.993 + * 1.994 + * Note: handler app IDs for preferred handlers are different. For those, 1.995 + * see the _getPreferredHandlerID method. 1.996 + * 1.997 + * @param aHandlerApp {nsIHandlerApp} the handler app object 1.998 + */ 1.999 + _getPossibleHandlerAppID: function HS__getPossibleHandlerAppID(aHandlerApp) { 1.1000 + var handlerAppID = "urn:handler:"; 1.1001 + 1.1002 + if (aHandlerApp instanceof Ci.nsILocalHandlerApp) 1.1003 + handlerAppID += "local:" + aHandlerApp.executable.path; 1.1004 + else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){ 1.1005 + aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp); 1.1006 + handlerAppID += "web:" + aHandlerApp.uriTemplate; 1.1007 + } 1.1008 + else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){ 1.1009 + aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp); 1.1010 + handlerAppID += "dbus:" + aHandlerApp.service + " " + aHandlerApp.method + " " + aHandlerApp.uriTemplate; 1.1011 + }else{ 1.1012 + throw "unknown handler type"; 1.1013 + } 1.1014 + 1.1015 + return handlerAppID; 1.1016 + }, 1.1017 + 1.1018 + /** 1.1019 + * Get the list of types for the given class, creating the list if it doesn't 1.1020 + * already exist. The class can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO 1.1021 + * (i.e. the result of a call to _getClass). 1.1022 + * 1.1023 + * |urn:<class>s| 1.1024 + * |urn:<class>s:root| 1.1025 + * 1.1026 + * @param aClass {string} the class for which to retrieve a list of types 1.1027 + * 1.1028 + * @returns {nsIRDFContainer} the list of types 1.1029 + */ 1.1030 + _ensureAndGetTypeList: function HS__ensureAndGetTypeList(aClass) { 1.1031 + var source = this._rdf.GetResource("urn:" + aClass + "s"); 1.1032 + var property = 1.1033 + this._rdf.GetResource(aClass == CLASS_MIMEINFO ? NC_MIME_TYPES 1.1034 + : NC_PROTOCOL_SCHEMES); 1.1035 + var target = this._rdf.GetResource("urn:" + aClass + "s:root"); 1.1036 + 1.1037 + // Make sure we have an arc from the source to the target. 1.1038 + if (!this._ds.HasAssertion(source, property, target, true)) 1.1039 + this._ds.Assert(source, property, target, true); 1.1040 + 1.1041 + // Make sure the target is a container. 1.1042 + if (!this._containerUtils.IsContainer(this._ds, target)) 1.1043 + this._containerUtils.MakeSeq(this._ds, target); 1.1044 + 1.1045 + // Get the type list as an RDF container. 1.1046 + var typeList = Cc["@mozilla.org/rdf/container;1"]. 1.1047 + createInstance(Ci.nsIRDFContainer); 1.1048 + typeList.Init(this._ds, target); 1.1049 + 1.1050 + return typeList; 1.1051 + }, 1.1052 + 1.1053 + /** 1.1054 + * Make sure there are records in the datasource for the given content type 1.1055 + * by creating them if they don't already exist. We have to do this before 1.1056 + * storing any specific data, because we can't assume the presence 1.1057 + * of the records (the nsIHandlerInfo object might have been created 1.1058 + * from the OS), and the records have to all be there in order for the helper 1.1059 + * app service to properly construct an nsIHandlerInfo object for the type. 1.1060 + * 1.1061 + * Based on old downloadactions.js::_ensureMIMERegistryEntry. 1.1062 + * 1.1063 + * @param aHandlerInfo {nsIHandlerInfo} the type to make sure has a record 1.1064 + */ 1.1065 + _ensureRecordsForType: function HS__ensureRecordsForType(aHandlerInfo) { 1.1066 + // Get the list of types. 1.1067 + var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo)); 1.1068 + 1.1069 + // If there's already a record in the datastore for this type, then we 1.1070 + // don't need to do anything more. 1.1071 + var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.1072 + var type = this._rdf.GetResource(typeID); 1.1073 + if (typeList.IndexOf(type) != -1) 1.1074 + return; 1.1075 + 1.1076 + // Create a basic type record for this type. 1.1077 + typeList.AppendElement(type); 1.1078 + this._setLiteral(typeID, NC_VALUE, aHandlerInfo.type); 1.1079 + 1.1080 + // Create a basic info record for this type. 1.1081 + var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.1082 + this._setLiteral(infoID, NC_ALWAYS_ASK, "false"); 1.1083 + this._setResource(typeID, NC_HANDLER_INFO, infoID); 1.1084 + // XXX Shouldn't we set preferredAction to useSystemDefault? 1.1085 + // That's what it is if there's no record in the datastore; why should it 1.1086 + // change to useHelperApp just because we add a record to the datastore? 1.1087 + 1.1088 + // Create a basic preferred handler record for this type. 1.1089 + // XXX Not sure this is necessary, since preferred handlers are optional, 1.1090 + // and nsExternalHelperAppService::FillHandlerInfoForTypeFromDS doesn't seem 1.1091 + // to require the record , but downloadactions.js::_ensureMIMERegistryEntry 1.1092 + // used to create it, so we'll do the same. 1.1093 + var preferredHandlerID = 1.1094 + this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type); 1.1095 + this._setLiteral(preferredHandlerID, NC_PATH, ""); 1.1096 + this._setResource(infoID, NC_PREFERRED_APP, preferredHandlerID); 1.1097 + }, 1.1098 + 1.1099 + /** 1.1100 + * Append known handlers of the given class to the given array. The class 1.1101 + * can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO. 1.1102 + * 1.1103 + * @param aHandlers {array} the array of handlers to append to 1.1104 + * @param aClass {string} the class for which to append handlers 1.1105 + */ 1.1106 + _appendHandlers: function HS__appendHandlers(aHandlers, aClass) { 1.1107 + var typeList = this._ensureAndGetTypeList(aClass); 1.1108 + var enumerator = typeList.GetElements(); 1.1109 + 1.1110 + while (enumerator.hasMoreElements()) { 1.1111 + var element = enumerator.getNext(); 1.1112 + 1.1113 + // This should never happen. If it does, that means our datasource 1.1114 + // is corrupted with type list entries that point to literal values 1.1115 + // instead of resources. If it does happen, let's just do our best 1.1116 + // to recover by ignoring this entry and moving on to the next one. 1.1117 + if (!(element instanceof Ci.nsIRDFResource)) 1.1118 + continue; 1.1119 + 1.1120 + // Get the value of the element's NC:value property, which contains 1.1121 + // the MIME type or scheme for which we're retrieving a handler info. 1.1122 + var type = this._getValue(element.ValueUTF8, NC_VALUE); 1.1123 + if (!type) 1.1124 + continue; 1.1125 + 1.1126 + var handler; 1.1127 + if (typeList.Resource.ValueUTF8 == "urn:mimetypes:root") 1.1128 + handler = this._mimeSvc.getFromTypeAndExtension(type, null); 1.1129 + else 1.1130 + handler = this._protocolSvc.getProtocolHandlerInfo(type); 1.1131 + 1.1132 + aHandlers.appendElement(handler, false); 1.1133 + } 1.1134 + }, 1.1135 + 1.1136 + /** 1.1137 + * Whether or not a property of an RDF source has a value. 1.1138 + * 1.1139 + * @param sourceURI {string} the URI of the source 1.1140 + * @param propertyURI {string} the URI of the property 1.1141 + * @returns {boolean} whether or not the property has a value 1.1142 + */ 1.1143 + _hasValue: function HS__hasValue(sourceURI, propertyURI) { 1.1144 + var source = this._rdf.GetResource(sourceURI); 1.1145 + var property = this._rdf.GetResource(propertyURI); 1.1146 + return this._ds.hasArcOut(source, property); 1.1147 + }, 1.1148 + 1.1149 + /** 1.1150 + * Get the value of a property of an RDF source. 1.1151 + * 1.1152 + * @param sourceURI {string} the URI of the source 1.1153 + * @param propertyURI {string} the URI of the property 1.1154 + * @returns {string} the value of the property 1.1155 + */ 1.1156 + _getValue: function HS__getValue(sourceURI, propertyURI) { 1.1157 + var source = this._rdf.GetResource(sourceURI); 1.1158 + var property = this._rdf.GetResource(propertyURI); 1.1159 + 1.1160 + var target = this._ds.GetTarget(source, property, true); 1.1161 + 1.1162 + if (!target) 1.1163 + return null; 1.1164 + 1.1165 + if (target instanceof Ci.nsIRDFResource) 1.1166 + return target.ValueUTF8; 1.1167 + 1.1168 + if (target instanceof Ci.nsIRDFLiteral) 1.1169 + return target.Value; 1.1170 + 1.1171 + return null; 1.1172 + }, 1.1173 + 1.1174 + /** 1.1175 + * Get all targets for the property of an RDF source. 1.1176 + * 1.1177 + * @param sourceURI {string} the URI of the source 1.1178 + * @param propertyURI {string} the URI of the property 1.1179 + * 1.1180 + * @returns {nsISimpleEnumerator} an enumerator of targets 1.1181 + */ 1.1182 + _getTargets: function HS__getTargets(sourceURI, propertyURI) { 1.1183 + var source = this._rdf.GetResource(sourceURI); 1.1184 + var property = this._rdf.GetResource(propertyURI); 1.1185 + 1.1186 + return this._ds.GetTargets(source, property, true); 1.1187 + }, 1.1188 + 1.1189 + /** 1.1190 + * Set a property of an RDF source to a literal value. 1.1191 + * 1.1192 + * @param sourceURI {string} the URI of the source 1.1193 + * @param propertyURI {string} the URI of the property 1.1194 + * @param value {string} the literal value 1.1195 + */ 1.1196 + _setLiteral: function HS__setLiteral(sourceURI, propertyURI, value) { 1.1197 + var source = this._rdf.GetResource(sourceURI); 1.1198 + var property = this._rdf.GetResource(propertyURI); 1.1199 + var target = this._rdf.GetLiteral(value); 1.1200 + 1.1201 + this._setTarget(source, property, target); 1.1202 + }, 1.1203 + 1.1204 + /** 1.1205 + * Set a property of an RDF source to a resource target. 1.1206 + * 1.1207 + * @param sourceURI {string} the URI of the source 1.1208 + * @param propertyURI {string} the URI of the property 1.1209 + * @param targetURI {string} the URI of the target 1.1210 + */ 1.1211 + _setResource: function HS__setResource(sourceURI, propertyURI, targetURI) { 1.1212 + var source = this._rdf.GetResource(sourceURI); 1.1213 + var property = this._rdf.GetResource(propertyURI); 1.1214 + var target = this._rdf.GetResource(targetURI); 1.1215 + 1.1216 + this._setTarget(source, property, target); 1.1217 + }, 1.1218 + 1.1219 + /** 1.1220 + * Assert an arc into the RDF datasource if there is no arc with the given 1.1221 + * source and property; otherwise, if there is already an existing arc, 1.1222 + * change it to point to the given target. _setLiteral and _setResource 1.1223 + * call this after converting their string arguments into resources 1.1224 + * and literals, and most callers should call one of those two methods 1.1225 + * instead of this one. 1.1226 + * 1.1227 + * @param source {nsIRDFResource} the source 1.1228 + * @param property {nsIRDFResource} the property 1.1229 + * @param target {nsIRDFNode} the target 1.1230 + */ 1.1231 + _setTarget: function HS__setTarget(source, property, target) { 1.1232 + if (this._ds.hasArcOut(source, property)) { 1.1233 + var oldTarget = this._ds.GetTarget(source, property, true); 1.1234 + this._ds.Change(source, property, oldTarget, target); 1.1235 + } 1.1236 + else 1.1237 + this._ds.Assert(source, property, target, true); 1.1238 + }, 1.1239 + 1.1240 + /** 1.1241 + * Assert that a property of an RDF source has a resource target. 1.1242 + * 1.1243 + * The difference between this method and _setResource is that this one adds 1.1244 + * an assertion even if one already exists, which allows its callers to make 1.1245 + * sets of assertions (i.e. to set a property to multiple targets). 1.1246 + * 1.1247 + * @param sourceURI {string} the URI of the source 1.1248 + * @param propertyURI {string} the URI of the property 1.1249 + * @param targetURI {string} the URI of the target 1.1250 + */ 1.1251 + _addResourceAssertion: function HS__addResourceAssertion(sourceURI, 1.1252 + propertyURI, 1.1253 + targetURI) { 1.1254 + var source = this._rdf.GetResource(sourceURI); 1.1255 + var property = this._rdf.GetResource(propertyURI); 1.1256 + var target = this._rdf.GetResource(targetURI); 1.1257 + 1.1258 + this._ds.Assert(source, property, target, true); 1.1259 + }, 1.1260 + 1.1261 + /** 1.1262 + * Remove an assertion with a resource target. 1.1263 + * 1.1264 + * @param sourceURI {string} the URI of the source 1.1265 + * @param propertyURI {string} the URI of the property 1.1266 + * @param targetURI {string} the URI of the target 1.1267 + */ 1.1268 + _removeResourceAssertion: function HS__removeResourceAssertion(sourceURI, 1.1269 + propertyURI, 1.1270 + targetURI) { 1.1271 + var source = this._rdf.GetResource(sourceURI); 1.1272 + var property = this._rdf.GetResource(propertyURI); 1.1273 + var target = this._rdf.GetResource(targetURI); 1.1274 + 1.1275 + this._ds.Unassert(source, property, target); 1.1276 + }, 1.1277 + 1.1278 + /** 1.1279 + * Whether or not a property of an RDF source has a given resource target. 1.1280 + * 1.1281 + * @param sourceURI {string} the URI of the source 1.1282 + * @param propertyURI {string} the URI of the property 1.1283 + * @param targetURI {string} the URI of the target 1.1284 + * 1.1285 + * @returns {boolean} whether or not there is such an assertion 1.1286 + */ 1.1287 + _hasResourceAssertion: function HS__hasResourceAssertion(sourceURI, 1.1288 + propertyURI, 1.1289 + targetURI) { 1.1290 + var source = this._rdf.GetResource(sourceURI); 1.1291 + var property = this._rdf.GetResource(propertyURI); 1.1292 + var target = this._rdf.GetResource(targetURI); 1.1293 + 1.1294 + return this._ds.HasAssertion(source, property, target, true); 1.1295 + }, 1.1296 + 1.1297 + /** 1.1298 + * Whether or not a property of an RDF source has a given literal value. 1.1299 + * 1.1300 + * @param sourceURI {string} the URI of the source 1.1301 + * @param propertyURI {string} the URI of the property 1.1302 + * @param value {string} the literal value 1.1303 + * 1.1304 + * @returns {boolean} whether or not there is such an assertion 1.1305 + */ 1.1306 + _hasLiteralAssertion: function HS__hasLiteralAssertion(sourceURI, 1.1307 + propertyURI, 1.1308 + value) { 1.1309 + var source = this._rdf.GetResource(sourceURI); 1.1310 + var property = this._rdf.GetResource(propertyURI); 1.1311 + var target = this._rdf.GetLiteral(value); 1.1312 + 1.1313 + return this._ds.HasAssertion(source, property, target, true); 1.1314 + }, 1.1315 + 1.1316 + /** 1.1317 + * Whether or not there is an RDF source that has the given property set to 1.1318 + * the given literal value. 1.1319 + * 1.1320 + * @param propertyURI {string} the URI of the property 1.1321 + * @param value {string} the literal value 1.1322 + * 1.1323 + * @returns {boolean} whether or not there is a source 1.1324 + */ 1.1325 + _existsLiteralTarget: function HS__existsLiteralTarget(propertyURI, value) { 1.1326 + var property = this._rdf.GetResource(propertyURI); 1.1327 + var target = this._rdf.GetLiteral(value); 1.1328 + 1.1329 + return this._ds.hasArcIn(target, property); 1.1330 + }, 1.1331 + 1.1332 + /** 1.1333 + * Get the source for a property set to a given literal value. 1.1334 + * 1.1335 + * @param propertyURI {string} the URI of the property 1.1336 + * @param value {string} the literal value 1.1337 + */ 1.1338 + _getSourceForLiteral: function HS__getSourceForLiteral(propertyURI, value) { 1.1339 + var property = this._rdf.GetResource(propertyURI); 1.1340 + var target = this._rdf.GetLiteral(value); 1.1341 + 1.1342 + var source = this._ds.GetSource(property, target, true); 1.1343 + if (source) 1.1344 + return source.ValueUTF8; 1.1345 + 1.1346 + return null; 1.1347 + }, 1.1348 + 1.1349 + /** 1.1350 + * Whether or not there is an RDF source that has the given property set to 1.1351 + * the given resource target. 1.1352 + * 1.1353 + * @param propertyURI {string} the URI of the property 1.1354 + * @param targetURI {string} the URI of the target 1.1355 + * 1.1356 + * @returns {boolean} whether or not there is a source 1.1357 + */ 1.1358 + _existsResourceTarget: function HS__existsResourceTarget(propertyURI, 1.1359 + targetURI) { 1.1360 + var property = this._rdf.GetResource(propertyURI); 1.1361 + var target = this._rdf.GetResource(targetURI); 1.1362 + 1.1363 + return this._ds.hasArcIn(target, property); 1.1364 + }, 1.1365 + 1.1366 + /** 1.1367 + * Remove a property of an RDF source. 1.1368 + * 1.1369 + * @param sourceURI {string} the URI of the source 1.1370 + * @param propertyURI {string} the URI of the property 1.1371 + */ 1.1372 + _removeTarget: function HS__removeTarget(sourceURI, propertyURI) { 1.1373 + var source = this._rdf.GetResource(sourceURI); 1.1374 + var property = this._rdf.GetResource(propertyURI); 1.1375 + 1.1376 + if (this._ds.hasArcOut(source, property)) { 1.1377 + var target = this._ds.GetTarget(source, property, true); 1.1378 + this._ds.Unassert(source, property, target); 1.1379 + } 1.1380 + }, 1.1381 + 1.1382 + /** 1.1383 + * Remove all assertions about a given RDF source. 1.1384 + * 1.1385 + * Note: not recursive. If some assertions point to other resources, 1.1386 + * and you want to remove assertions about those resources too, you need 1.1387 + * to do so manually. 1.1388 + * 1.1389 + * @param sourceURI {string} the URI of the source 1.1390 + */ 1.1391 + _removeAssertions: function HS__removeAssertions(sourceURI) { 1.1392 + var source = this._rdf.GetResource(sourceURI); 1.1393 + var properties = this._ds.ArcLabelsOut(source); 1.1394 + 1.1395 + while (properties.hasMoreElements()) { 1.1396 + let property = properties.getNext(); 1.1397 + let targets = this._ds.GetTargets(source, property, true); 1.1398 + while (targets.hasMoreElements()) { 1.1399 + let target = targets.getNext(); 1.1400 + this._ds.Unassert(source, property, target); 1.1401 + } 1.1402 + } 1.1403 + } 1.1404 + 1.1405 +}; 1.1406 + 1.1407 +//****************************************************************************// 1.1408 +// More XPCOM Plumbing 1.1409 + 1.1410 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HandlerService]);