michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cc = Components.classes; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: michael@0: const CLASS_MIMEINFO = "mimetype"; michael@0: const CLASS_PROTOCOLINFO = "scheme"; michael@0: michael@0: michael@0: // namespace prefix michael@0: const NC_NS = "http://home.netscape.com/NC-rdf#"; michael@0: michael@0: // the most recent default handlers that have been injected. Note that michael@0: // this is used to construct an RDF resource, which needs to have NC_NS michael@0: // prepended, since that hasn't been done yet michael@0: const DEFAULT_HANDLERS_VERSION = "defaultHandlersVersion"; michael@0: michael@0: // type list properties michael@0: michael@0: const NC_MIME_TYPES = NC_NS + "MIME-types"; michael@0: const NC_PROTOCOL_SCHEMES = NC_NS + "Protocol-Schemes"; michael@0: michael@0: // content type ("type") properties michael@0: michael@0: // nsIHandlerInfo::type michael@0: const NC_VALUE = NC_NS + "value"; michael@0: const NC_DESCRIPTION = NC_NS + "description"; michael@0: michael@0: // additional extensions michael@0: const NC_FILE_EXTENSIONS = NC_NS + "fileExtensions"; michael@0: michael@0: // references nsIHandlerInfo record michael@0: const NC_HANDLER_INFO = NC_NS + "handlerProp"; michael@0: michael@0: // handler info ("info") properties michael@0: michael@0: // nsIHandlerInfo::preferredAction michael@0: const NC_SAVE_TO_DISK = NC_NS + "saveToDisk"; michael@0: const NC_HANDLE_INTERNALLY = NC_NS + "handleInternal"; michael@0: const NC_USE_SYSTEM_DEFAULT = NC_NS + "useSystemDefault"; michael@0: michael@0: // nsIHandlerInfo::alwaysAskBeforeHandling michael@0: const NC_ALWAYS_ASK = NC_NS + "alwaysAsk"; michael@0: michael@0: // references nsIHandlerApp records michael@0: const NC_PREFERRED_APP = NC_NS + "externalApplication"; michael@0: const NC_POSSIBLE_APP = NC_NS + "possibleApplication"; michael@0: michael@0: // handler app ("handler") properties michael@0: michael@0: // nsIHandlerApp::name michael@0: const NC_PRETTY_NAME = NC_NS + "prettyName"; michael@0: michael@0: // nsILocalHandlerApp::executable michael@0: const NC_PATH = NC_NS + "path"; michael@0: michael@0: // nsIWebHandlerApp::uriTemplate michael@0: const NC_URI_TEMPLATE = NC_NS + "uriTemplate"; michael@0: michael@0: // nsIDBusHandlerApp::service michael@0: const NC_SERVICE = NC_NS + "service"; michael@0: michael@0: // nsIDBusHandlerApp::method michael@0: const NC_METHOD = NC_NS + "method"; michael@0: michael@0: // nsIDBusHandlerApp::objectPath michael@0: const NC_OBJPATH = NC_NS + "objectPath"; michael@0: michael@0: // nsIDBusHandlerApp::dbusInterface michael@0: const NC_INTERFACE = NC_NS + "dBusInterface"; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: michael@0: function HandlerService() { michael@0: this._init(); michael@0: } michael@0: michael@0: const HandlerServiceFactory = { michael@0: _instance: null, michael@0: createInstance: function (outer, iid) { michael@0: if (this._instance) michael@0: return this._instance; michael@0: michael@0: let processType = Cc["@mozilla.org/xre/runtime;1"]. michael@0: getService(Ci.nsIXULRuntime).processType; michael@0: if (processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) michael@0: return Cr.NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: return (this._instance = new HandlerService()); michael@0: } michael@0: }; michael@0: michael@0: HandlerService.prototype = { michael@0: //**************************************************************************// michael@0: // XPCOM Plumbing michael@0: michael@0: classID: Components.ID("{32314cc8-22f7-4f7f-a645-1a45453ba6a6}"), michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIHandlerService]), michael@0: _xpcom_factory: HandlerServiceFactory, michael@0: michael@0: //**************************************************************************// michael@0: // Initialization & Destruction michael@0: michael@0: _init: function HS__init() { michael@0: // Observe profile-before-change so we can switch to the datasource michael@0: // in the new profile when the user changes profiles. michael@0: this._observerSvc.addObserver(this, "profile-before-change", false); michael@0: michael@0: // Observe xpcom-shutdown so we can remove these observers michael@0: // when the application shuts down. michael@0: this._observerSvc.addObserver(this, "xpcom-shutdown", false); michael@0: michael@0: // Observe profile-do-change so that non-default profiles get upgraded too michael@0: this._observerSvc.addObserver(this, "profile-do-change", false); michael@0: michael@0: // do any necessary updating of the datastore michael@0: this._updateDB(); michael@0: }, michael@0: michael@0: _updateDB: function HS__updateDB() { michael@0: try { michael@0: var defaultHandlersVersion = this._datastoreDefaultHandlersVersion; michael@0: } catch(ex) { michael@0: // accessing the datastore failed, we can't update anything michael@0: return; michael@0: } michael@0: michael@0: try { michael@0: // if we don't have the current version of the default prefs for michael@0: // this locale, inject any new default handers into the datastore michael@0: if (defaultHandlersVersion < this._prefsDefaultHandlersVersion) { michael@0: michael@0: // set the new version first so that if we recurse we don't michael@0: // call _injectNewDefaults several times michael@0: this._datastoreDefaultHandlersVersion = michael@0: this._prefsDefaultHandlersVersion; michael@0: this._injectNewDefaults(); michael@0: } michael@0: } catch (ex) { michael@0: // if injecting the defaults failed, set the version back to the michael@0: // previous value michael@0: this._datastoreDefaultHandlersVersion = defaultHandlersVersion; michael@0: } michael@0: }, michael@0: michael@0: get _currentLocale() { michael@0: var chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"]. michael@0: getService(Ci.nsIXULChromeRegistry); michael@0: var currentLocale = chromeRegistry.getSelectedLocale("global"); michael@0: return currentLocale; michael@0: }, michael@0: michael@0: _destroy: function HS__destroy() { michael@0: this._observerSvc.removeObserver(this, "profile-before-change"); michael@0: this._observerSvc.removeObserver(this, "xpcom-shutdown"); michael@0: this._observerSvc.removeObserver(this, "profile-do-change"); michael@0: michael@0: // XXX Should we also null references to all the services that get stored michael@0: // by our memoizing getters in the Convenience Getters section? michael@0: }, michael@0: michael@0: _onProfileChange: function HS__onProfileChange() { michael@0: // Lose our reference to the datasource so we reacquire it michael@0: // from the new profile the next time we need it. michael@0: this.__ds = null; michael@0: }, michael@0: michael@0: _isInHandlerArray: function HS__isInHandlerArray(aArray, aHandler) { michael@0: var enumerator = aArray.enumerate(); michael@0: while (enumerator.hasMoreElements()) { michael@0: let handler = enumerator.getNext(); michael@0: handler.QueryInterface(Ci.nsIHandlerApp); michael@0: if (handler.equals(aHandler)) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: // note that this applies to the current locale only michael@0: get _datastoreDefaultHandlersVersion() { michael@0: var version = this._getValue("urn:root", NC_NS + this._currentLocale + michael@0: "_" + DEFAULT_HANDLERS_VERSION); michael@0: michael@0: return version ? version : -1; michael@0: }, michael@0: michael@0: set _datastoreDefaultHandlersVersion(aNewVersion) { michael@0: return this._setLiteral("urn:root", NC_NS + this._currentLocale + "_" + michael@0: DEFAULT_HANDLERS_VERSION, aNewVersion); michael@0: }, michael@0: michael@0: get _prefsDefaultHandlersVersion() { michael@0: // get handler service pref branch michael@0: var prefSvc = Cc["@mozilla.org/preferences-service;1"]. michael@0: getService(Ci.nsIPrefService); michael@0: var handlerSvcBranch = prefSvc.getBranch("gecko.handlerService."); michael@0: michael@0: // get the version of the preferences for this locale michael@0: return Number(handlerSvcBranch. michael@0: getComplexValue("defaultHandlersVersion", michael@0: Ci.nsIPrefLocalizedString).data); michael@0: }, michael@0: michael@0: _injectNewDefaults: function HS__injectNewDefaults() { michael@0: // get handler service pref branch michael@0: var prefSvc = Cc["@mozilla.org/preferences-service;1"]. michael@0: getService(Ci.nsIPrefService); michael@0: michael@0: let schemesPrefBranch = prefSvc.getBranch("gecko.handlerService.schemes."); michael@0: let schemePrefList = schemesPrefBranch.getChildList(""); michael@0: michael@0: var schemes = {}; michael@0: michael@0: // read all the scheme prefs into a hash michael@0: for each (var schemePrefName in schemePrefList) { michael@0: michael@0: let [scheme, handlerNumber, attribute] = schemePrefName.split("."); michael@0: michael@0: try { michael@0: var attrData = michael@0: schemesPrefBranch.getComplexValue(schemePrefName, michael@0: Ci.nsIPrefLocalizedString).data; michael@0: if (!(scheme in schemes)) michael@0: schemes[scheme] = {}; michael@0: michael@0: if (!(handlerNumber in schemes[scheme])) michael@0: schemes[scheme][handlerNumber] = {}; michael@0: michael@0: schemes[scheme][handlerNumber][attribute] = attrData; michael@0: } catch (ex) {} michael@0: } michael@0: michael@0: let protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]. michael@0: getService(Ci.nsIExternalProtocolService); michael@0: for (var scheme in schemes) { michael@0: michael@0: // This clause is essentially a reimplementation of michael@0: // nsIExternalProtocolHandlerService.getProtocolHandlerInfo(). michael@0: // Necessary because calling that from here would make XPConnect barf michael@0: // when getService tried to re-enter the constructor for this michael@0: // service. michael@0: let osDefaultHandlerFound = {}; michael@0: let protoInfo = protoSvc.getProtocolHandlerInfoFromOS(scheme, michael@0: osDefaultHandlerFound); michael@0: michael@0: if (this.exists(protoInfo)) michael@0: this.fillHandlerInfo(protoInfo, null); michael@0: else michael@0: protoSvc.setProtocolHandlerDefaults(protoInfo, michael@0: osDefaultHandlerFound.value); michael@0: michael@0: // cache the possible handlers to avoid extra xpconnect traversals. michael@0: let possibleHandlers = protoInfo.possibleApplicationHandlers; michael@0: michael@0: for each (var handlerPrefs in schemes[scheme]) { michael@0: michael@0: let handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"]. michael@0: createInstance(Ci.nsIWebHandlerApp); michael@0: michael@0: handlerApp.uriTemplate = handlerPrefs.uriTemplate; michael@0: handlerApp.name = handlerPrefs.name; michael@0: michael@0: if (!this._isInHandlerArray(possibleHandlers, handlerApp)) { michael@0: possibleHandlers.appendElement(handlerApp, false); michael@0: } michael@0: } michael@0: michael@0: this.store(protoInfo); michael@0: } michael@0: }, michael@0: michael@0: //**************************************************************************// michael@0: // nsIObserver michael@0: michael@0: observe: function HS__observe(subject, topic, data) { michael@0: switch(topic) { michael@0: case "profile-before-change": michael@0: this._onProfileChange(); michael@0: break; michael@0: case "xpcom-shutdown": michael@0: this._destroy(); michael@0: break; michael@0: case "profile-do-change": michael@0: this._updateDB(); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: michael@0: //**************************************************************************// michael@0: // nsIHandlerService michael@0: michael@0: enumerate: function HS_enumerate() { michael@0: var handlers = Cc["@mozilla.org/array;1"]. michael@0: createInstance(Ci.nsIMutableArray); michael@0: this._appendHandlers(handlers, CLASS_MIMEINFO); michael@0: this._appendHandlers(handlers, CLASS_PROTOCOLINFO); michael@0: return handlers.enumerate(); michael@0: }, michael@0: michael@0: fillHandlerInfo: function HS_fillHandlerInfo(aHandlerInfo, aOverrideType) { michael@0: var type = aOverrideType || aHandlerInfo.type; michael@0: var typeID = this._getTypeID(this._getClass(aHandlerInfo), type); michael@0: michael@0: // Determine whether or not information about this handler is available michael@0: // in the datastore by looking for its "value" property, which stores its michael@0: // type and should always be present. michael@0: if (!this._hasValue(typeID, NC_VALUE)) michael@0: throw Cr.NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // Retrieve the human-readable description of the type. michael@0: if (this._hasValue(typeID, NC_DESCRIPTION)) michael@0: aHandlerInfo.description = this._getValue(typeID, NC_DESCRIPTION); michael@0: michael@0: // Note: for historical reasons, we don't actually check that the type michael@0: // record has a "handlerProp" property referencing the info record. It's michael@0: // unclear whether or not we should start doing this check; perhaps some michael@0: // legacy datasources don't have such references. michael@0: var infoID = this._getInfoID(this._getClass(aHandlerInfo), type); michael@0: michael@0: aHandlerInfo.preferredAction = this._retrievePreferredAction(infoID); michael@0: michael@0: var preferredHandlerID = michael@0: this._getPreferredHandlerID(this._getClass(aHandlerInfo), type); michael@0: michael@0: // Retrieve the preferred handler. michael@0: // Note: for historical reasons, we don't actually check that the info michael@0: // record has an "externalApplication" property referencing the preferred michael@0: // handler record. It's unclear whether or not we should start doing michael@0: // this check; perhaps some legacy datasources don't have such references. michael@0: aHandlerInfo.preferredApplicationHandler = michael@0: this._retrieveHandlerApp(preferredHandlerID); michael@0: michael@0: // Fill the array of possible handlers with the ones in the datastore. michael@0: this._fillPossibleHandlers(infoID, michael@0: aHandlerInfo.possibleApplicationHandlers, michael@0: aHandlerInfo.preferredApplicationHandler); michael@0: michael@0: // If we have an "always ask" flag stored in the RDF, always use its michael@0: // value. Otherwise, use the default value stored in the pref service. michael@0: var alwaysAsk; michael@0: if (this._hasValue(infoID, NC_ALWAYS_ASK)) { michael@0: alwaysAsk = (this._getValue(infoID, NC_ALWAYS_ASK) != "false"); michael@0: } else { michael@0: var prefSvc = Cc["@mozilla.org/preferences-service;1"]. michael@0: getService(Ci.nsIPrefService); michael@0: var prefBranch = prefSvc.getBranch("network.protocol-handler."); michael@0: try { michael@0: alwaysAsk = prefBranch.getBoolPref("warn-external." + type); michael@0: } catch (e) { michael@0: // will throw if pref didn't exist. michael@0: try { michael@0: alwaysAsk = prefBranch.getBoolPref("warn-external-default"); michael@0: } catch (e) { michael@0: // Nothing to tell us what to do, so be paranoid and prompt. michael@0: alwaysAsk = true; michael@0: } michael@0: } michael@0: } michael@0: aHandlerInfo.alwaysAskBeforeHandling = alwaysAsk; michael@0: michael@0: // If the object represents a MIME type handler, then also retrieve michael@0: // any file extensions. michael@0: if (aHandlerInfo instanceof Ci.nsIMIMEInfo) michael@0: for each (let fileExtension in this._retrieveFileExtensions(typeID)) michael@0: aHandlerInfo.appendExtension(fileExtension); michael@0: }, michael@0: michael@0: store: function HS_store(aHandlerInfo) { michael@0: // FIXME: when we switch from RDF to something with transactions (like michael@0: // SQLite), enclose the following changes in a transaction so they all michael@0: // get rolled back if any of them fail and we don't leave the datastore michael@0: // in an inconsistent state. michael@0: michael@0: this._ensureRecordsForType(aHandlerInfo); michael@0: this._storePreferredAction(aHandlerInfo); michael@0: this._storePreferredHandler(aHandlerInfo); michael@0: this._storePossibleHandlers(aHandlerInfo); michael@0: this._storeAlwaysAsk(aHandlerInfo); michael@0: michael@0: // Write the changes to the database immediately so we don't lose them michael@0: // if the application crashes. michael@0: if (this._ds instanceof Ci.nsIRDFRemoteDataSource) michael@0: this._ds.Flush(); michael@0: }, michael@0: michael@0: exists: function HS_exists(aHandlerInfo) { michael@0: var found; michael@0: michael@0: try { michael@0: var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: found = this._hasLiteralAssertion(typeID, NC_VALUE, aHandlerInfo.type); michael@0: } catch (e) { michael@0: // If the RDF threw (eg, corrupt file), treat as nonexistent. michael@0: found = false; michael@0: } michael@0: michael@0: return found; michael@0: }, michael@0: michael@0: remove: function HS_remove(aHandlerInfo) { michael@0: var preferredHandlerID = michael@0: this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: this._removeAssertions(preferredHandlerID); michael@0: michael@0: var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: michael@0: // Get a list of possible handlers. After we have removed the info record, michael@0: // we'll check if any other info records reference these handlers, and we'll michael@0: // remove the handler records that aren't referenced by other info records. michael@0: var possibleHandlerIDs = []; michael@0: var possibleHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP); michael@0: while (possibleHandlerTargets.hasMoreElements()) { michael@0: let possibleHandlerTarget = possibleHandlerTargets.getNext(); michael@0: // Note: possibleHandlerTarget should always be an nsIRDFResource. michael@0: // The conditional is just here in case of a corrupt RDF datasource. michael@0: if (possibleHandlerTarget instanceof Ci.nsIRDFResource) michael@0: possibleHandlerIDs.push(possibleHandlerTarget.ValueUTF8); michael@0: } michael@0: michael@0: // Remove the info record. michael@0: this._removeAssertions(infoID); michael@0: michael@0: // Now that we've removed the info record, remove any possible handlers michael@0: // that aren't referenced by other info records. michael@0: for each (let possibleHandlerID in possibleHandlerIDs) michael@0: if (!this._existsResourceTarget(NC_POSSIBLE_APP, possibleHandlerID)) michael@0: this._removeAssertions(possibleHandlerID); michael@0: michael@0: var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: this._removeAssertions(typeID); michael@0: michael@0: // Now that there's no longer a handler for this type, remove the type michael@0: // from the list of types for which there are known handlers. michael@0: var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo)); michael@0: var type = this._rdf.GetResource(typeID); michael@0: var typeIndex = typeList.IndexOf(type); michael@0: if (typeIndex != -1) michael@0: typeList.RemoveElementAt(typeIndex, true); michael@0: michael@0: // Write the changes to the database immediately so we don't lose them michael@0: // if the application crashes. michael@0: // XXX If we're removing a bunch of handlers at once, will flushing michael@0: // after every removal cause a significant performance hit? michael@0: if (this._ds instanceof Ci.nsIRDFRemoteDataSource) michael@0: this._ds.Flush(); michael@0: }, michael@0: michael@0: getTypeFromExtension: function HS_getTypeFromExtension(aFileExtension) { michael@0: var fileExtension = aFileExtension.toLowerCase(); michael@0: var typeID; michael@0: michael@0: if (this._existsLiteralTarget(NC_FILE_EXTENSIONS, fileExtension)) michael@0: typeID = this._getSourceForLiteral(NC_FILE_EXTENSIONS, fileExtension); michael@0: michael@0: if (typeID && this._hasValue(typeID, NC_VALUE)) { michael@0: let type = this._getValue(typeID, NC_VALUE); michael@0: if (type == "") michael@0: throw Cr.NS_ERROR_FAILURE; michael@0: return type; michael@0: } michael@0: michael@0: return ""; michael@0: }, michael@0: michael@0: michael@0: //**************************************************************************// michael@0: // Retrieval Methods michael@0: michael@0: /** michael@0: * Retrieve the preferred action for the info record with the given ID. michael@0: * michael@0: * @param aInfoID {string} the info record ID michael@0: * michael@0: * @returns {integer} the preferred action enumeration value michael@0: */ michael@0: _retrievePreferredAction: function HS__retrievePreferredAction(aInfoID) { michael@0: if (this._getValue(aInfoID, NC_SAVE_TO_DISK) == "true") michael@0: return Ci.nsIHandlerInfo.saveToDisk; michael@0: michael@0: if (this._getValue(aInfoID, NC_USE_SYSTEM_DEFAULT) == "true") michael@0: return Ci.nsIHandlerInfo.useSystemDefault; michael@0: michael@0: if (this._getValue(aInfoID, NC_HANDLE_INTERNALLY) == "true") michael@0: return Ci.nsIHandlerInfo.handleInternally; michael@0: michael@0: return Ci.nsIHandlerInfo.useHelperApp; michael@0: }, michael@0: michael@0: /** michael@0: * Fill an array of possible handlers with the handlers for the given info ID. michael@0: * michael@0: * @param aInfoID {string} the ID of the info record michael@0: * @param aPossibleHandlers {nsIMutableArray} the array of possible handlers michael@0: * @param aPreferredHandler {nsIHandlerApp} the preferred handler, if any michael@0: */ michael@0: _fillPossibleHandlers: function HS__fillPossibleHandlers(aInfoID, michael@0: aPossibleHandlers, michael@0: aPreferredHandler) { michael@0: // The set of possible handlers should include the preferred handler, michael@0: // but legacy datastores (from before we added possible handlers) won't michael@0: // include the preferred handler, so check if it's included as we build michael@0: // the list of handlers, and, if it's not included, add it to the list. michael@0: if (aPreferredHandler) michael@0: aPossibleHandlers.appendElement(aPreferredHandler, false); michael@0: michael@0: var possibleHandlerTargets = this._getTargets(aInfoID, NC_POSSIBLE_APP); michael@0: michael@0: while (possibleHandlerTargets.hasMoreElements()) { michael@0: let possibleHandlerTarget = possibleHandlerTargets.getNext(); michael@0: if (!(possibleHandlerTarget instanceof Ci.nsIRDFResource)) michael@0: continue; michael@0: michael@0: let possibleHandlerID = possibleHandlerTarget.ValueUTF8; michael@0: let possibleHandler = this._retrieveHandlerApp(possibleHandlerID); michael@0: if (possibleHandler && (!aPreferredHandler || michael@0: !possibleHandler.equals(aPreferredHandler))) michael@0: aPossibleHandlers.appendElement(possibleHandler, false); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Retrieve the handler app object with the given ID. michael@0: * michael@0: * @param aHandlerAppID {string} the ID of the handler app to retrieve michael@0: * michael@0: * @returns {nsIHandlerApp} the handler app, if any; otherwise null michael@0: */ michael@0: _retrieveHandlerApp: function HS__retrieveHandlerApp(aHandlerAppID) { michael@0: var handlerApp; michael@0: michael@0: // If it has a path, it's a local handler; otherwise, it's a web handler. michael@0: if (this._hasValue(aHandlerAppID, NC_PATH)) { michael@0: let executable = michael@0: this._getFileWithPath(this._getValue(aHandlerAppID, NC_PATH)); michael@0: if (!executable) michael@0: return null; michael@0: michael@0: handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]. michael@0: createInstance(Ci.nsILocalHandlerApp); michael@0: handlerApp.executable = executable; michael@0: } michael@0: else if (this._hasValue(aHandlerAppID, NC_URI_TEMPLATE)) { michael@0: let uriTemplate = this._getValue(aHandlerAppID, NC_URI_TEMPLATE); michael@0: if (!uriTemplate) michael@0: return null; michael@0: michael@0: handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"]. michael@0: createInstance(Ci.nsIWebHandlerApp); michael@0: handlerApp.uriTemplate = uriTemplate; michael@0: } michael@0: else if (this._hasValue(aHandlerAppID, NC_SERVICE)) { michael@0: let service = this._getValue(aHandlerAppID, NC_SERVICE); michael@0: if (!service) michael@0: return null; michael@0: michael@0: let method = this._getValue(aHandlerAppID, NC_METHOD); michael@0: if (!method) michael@0: return null; michael@0: michael@0: let objpath = this._getValue(aHandlerAppID, NC_OBJPATH); michael@0: if (!objpath) michael@0: return null; michael@0: michael@0: let interface = this._getValue(aHandlerAppID, NC_INTERFACE); michael@0: if (!interface) michael@0: return null; michael@0: michael@0: handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"]. michael@0: createInstance(Ci.nsIDBusHandlerApp); michael@0: handlerApp.service = service; michael@0: handlerApp.method = method; michael@0: handlerApp.objectPath = objpath; michael@0: handlerApp.dBusInterface = interface; michael@0: michael@0: } michael@0: else michael@0: return null; michael@0: michael@0: handlerApp.name = this._getValue(aHandlerAppID, NC_PRETTY_NAME); michael@0: michael@0: return handlerApp; michael@0: }, michael@0: michael@0: /* michael@0: * Retrieve file extensions, if any, for the MIME type with the given type ID. michael@0: * michael@0: * @param aTypeID {string} the type record ID michael@0: */ michael@0: _retrieveFileExtensions: function HS__retrieveFileExtensions(aTypeID) { michael@0: var fileExtensions = []; michael@0: michael@0: var fileExtensionTargets = this._getTargets(aTypeID, NC_FILE_EXTENSIONS); michael@0: michael@0: while (fileExtensionTargets.hasMoreElements()) { michael@0: let fileExtensionTarget = fileExtensionTargets.getNext(); michael@0: if (fileExtensionTarget instanceof Ci.nsIRDFLiteral && michael@0: fileExtensionTarget.Value != "") michael@0: fileExtensions.push(fileExtensionTarget.Value); michael@0: } michael@0: michael@0: return fileExtensions; michael@0: }, michael@0: michael@0: /** michael@0: * Get the file with the given path. This is not as simple as merely michael@0: * initializing a local file object with the path, because the path might be michael@0: * relative to the current process directory, in which case we have to michael@0: * construct a path starting from that directory. michael@0: * michael@0: * @param aPath {string} a path to a file michael@0: * michael@0: * @returns {nsILocalFile} the file, or null if the file does not exist michael@0: */ michael@0: _getFileWithPath: function HS__getFileWithPath(aPath) { michael@0: var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); michael@0: michael@0: try { michael@0: file.initWithPath(aPath); michael@0: michael@0: if (file.exists()) michael@0: return file; michael@0: } michael@0: catch(ex) { michael@0: // Note: for historical reasons, we don't actually check to see michael@0: // if the exception is NS_ERROR_FILE_UNRECOGNIZED_PATH, which is what michael@0: // nsILocalFile::initWithPath throws when a path is relative. michael@0: michael@0: file = this._dirSvc.get("XCurProcD", Ci.nsIFile); michael@0: michael@0: try { michael@0: file.append(aPath); michael@0: if (file.exists()) michael@0: return file; michael@0: } michael@0: catch(ex) {} michael@0: } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: michael@0: //**************************************************************************// michael@0: // Storage Methods michael@0: michael@0: _storePreferredAction: function HS__storePreferredAction(aHandlerInfo) { michael@0: var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: michael@0: switch(aHandlerInfo.preferredAction) { michael@0: case Ci.nsIHandlerInfo.saveToDisk: michael@0: this._setLiteral(infoID, NC_SAVE_TO_DISK, "true"); michael@0: this._removeTarget(infoID, NC_HANDLE_INTERNALLY); michael@0: this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT); michael@0: break; michael@0: michael@0: case Ci.nsIHandlerInfo.handleInternally: michael@0: this._setLiteral(infoID, NC_HANDLE_INTERNALLY, "true"); michael@0: this._removeTarget(infoID, NC_SAVE_TO_DISK); michael@0: this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT); michael@0: break; michael@0: michael@0: case Ci.nsIHandlerInfo.useSystemDefault: michael@0: this._setLiteral(infoID, NC_USE_SYSTEM_DEFAULT, "true"); michael@0: this._removeTarget(infoID, NC_SAVE_TO_DISK); michael@0: this._removeTarget(infoID, NC_HANDLE_INTERNALLY); michael@0: break; michael@0: michael@0: // This value is indicated in the datastore either by the absence of michael@0: // the three properties or by setting them all "false". Of these two michael@0: // options, the former seems preferable, because it reduces the size michael@0: // of the RDF file and thus the amount of stuff we have to parse. michael@0: case Ci.nsIHandlerInfo.useHelperApp: michael@0: default: michael@0: this._removeTarget(infoID, NC_SAVE_TO_DISK); michael@0: this._removeTarget(infoID, NC_HANDLE_INTERNALLY); michael@0: this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: _storePreferredHandler: function HS__storePreferredHandler(aHandlerInfo) { michael@0: var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: var handlerID = michael@0: this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: michael@0: var handler = aHandlerInfo.preferredApplicationHandler; michael@0: michael@0: if (handler) { michael@0: this._storeHandlerApp(handlerID, handler); michael@0: michael@0: // Make this app be the preferred app for the handler info. michael@0: // michael@0: // Note: nsExternalHelperAppService::FillContentHandlerProperties ignores michael@0: // this setting and instead identifies the preferred app as the resource michael@0: // whose URI follows the pattern urn::externalApplication:. michael@0: // But the old downloadactions.js code used to set this property, so just michael@0: // in case there is still some code somewhere that relies on its presence, michael@0: // we set it here. michael@0: this._setResource(infoID, NC_PREFERRED_APP, handlerID); michael@0: } michael@0: else { michael@0: // There isn't a preferred handler. Remove the existing record for it, michael@0: // if any. michael@0: this._removeTarget(infoID, NC_PREFERRED_APP); michael@0: this._removeAssertions(handlerID); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Store the list of possible handler apps for the content type represented michael@0: * by the given handler info object. michael@0: * michael@0: * @param aHandlerInfo {nsIHandlerInfo} the handler info object michael@0: */ michael@0: _storePossibleHandlers: function HS__storePossibleHandlers(aHandlerInfo) { michael@0: var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: michael@0: // First, retrieve the set of handler apps currently stored for the type, michael@0: // keeping track of their IDs in a hash that we'll use to determine which michael@0: // ones are no longer valid and should be removed. michael@0: var currentHandlerApps = {}; michael@0: var currentHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP); michael@0: while (currentHandlerTargets.hasMoreElements()) { michael@0: let handlerApp = currentHandlerTargets.getNext(); michael@0: if (handlerApp instanceof Ci.nsIRDFResource) { michael@0: let handlerAppID = handlerApp.ValueUTF8; michael@0: currentHandlerApps[handlerAppID] = true; michael@0: } michael@0: } michael@0: michael@0: // Next, store any new handler apps. michael@0: var newHandlerApps = michael@0: aHandlerInfo.possibleApplicationHandlers.enumerate(); michael@0: while (newHandlerApps.hasMoreElements()) { michael@0: let handlerApp = michael@0: newHandlerApps.getNext().QueryInterface(Ci.nsIHandlerApp); michael@0: let handlerAppID = this._getPossibleHandlerAppID(handlerApp); michael@0: if (!this._hasResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID)) { michael@0: this._storeHandlerApp(handlerAppID, handlerApp); michael@0: this._addResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID); michael@0: } michael@0: delete currentHandlerApps[handlerAppID]; michael@0: } michael@0: michael@0: // Finally, remove any old handler apps that aren't being used anymore, michael@0: // and if those handler apps aren't being used by any other type either, michael@0: // then completely remove their record from the datastore so we don't michael@0: // leave it clogged up with information about handler apps we don't care michael@0: // about anymore. michael@0: for (let handlerAppID in currentHandlerApps) { michael@0: this._removeResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID); michael@0: if (!this._existsResourceTarget(NC_POSSIBLE_APP, handlerAppID)) michael@0: this._removeAssertions(handlerAppID); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Store the given handler app. michael@0: * michael@0: * Note: the reason this method takes the ID of the handler app in a param michael@0: * is that the ID is different than it usually is when the handler app michael@0: * in question is a preferred handler app, so this method can't just derive michael@0: * the ID of the handler app by calling _getPossibleHandlerAppID, its callers michael@0: * have to do that for it. michael@0: * michael@0: * @param aHandlerAppID {string} the ID of the handler app to store michael@0: * @param aHandlerApp {nsIHandlerApp} the handler app to store michael@0: */ michael@0: _storeHandlerApp: function HS__storeHandlerApp(aHandlerAppID, aHandlerApp) { michael@0: aHandlerApp.QueryInterface(Ci.nsIHandlerApp); michael@0: this._setLiteral(aHandlerAppID, NC_PRETTY_NAME, aHandlerApp.name); michael@0: michael@0: // In the case of the preferred handler, the handler ID could have been michael@0: // used to refer to a different kind of handler in the past (i.e. either michael@0: // a local hander or a web handler), so if the new handler is a local michael@0: // handler, then we remove any web handler properties and vice versa. michael@0: // This is unnecessary but harmless for possible handlers. michael@0: michael@0: if (aHandlerApp instanceof Ci.nsILocalHandlerApp) { michael@0: this._setLiteral(aHandlerAppID, NC_PATH, aHandlerApp.executable.path); michael@0: this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE); michael@0: this._removeTarget(aHandlerAppID, NC_METHOD); michael@0: this._removeTarget(aHandlerAppID, NC_SERVICE); michael@0: this._removeTarget(aHandlerAppID, NC_OBJPATH); michael@0: this._removeTarget(aHandlerAppID, NC_INTERFACE); michael@0: } michael@0: else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){ michael@0: aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp); michael@0: this._setLiteral(aHandlerAppID, NC_URI_TEMPLATE, aHandlerApp.uriTemplate); michael@0: this._removeTarget(aHandlerAppID, NC_PATH); michael@0: this._removeTarget(aHandlerAppID, NC_METHOD); michael@0: this._removeTarget(aHandlerAppID, NC_SERVICE); michael@0: this._removeTarget(aHandlerAppID, NC_OBJPATH); michael@0: this._removeTarget(aHandlerAppID, NC_INTERFACE); michael@0: } michael@0: else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){ michael@0: aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp); michael@0: this._setLiteral(aHandlerAppID, NC_SERVICE, aHandlerApp.service); michael@0: this._setLiteral(aHandlerAppID, NC_METHOD, aHandlerApp.method); michael@0: this._setLiteral(aHandlerAppID, NC_OBJPATH, aHandlerApp.objectPath); michael@0: this._setLiteral(aHandlerAppID, NC_INTERFACE, aHandlerApp.dBusInterface); michael@0: this._removeTarget(aHandlerAppID, NC_PATH); michael@0: this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE); michael@0: } michael@0: else { michael@0: throw "unknown handler type"; michael@0: } michael@0: michael@0: }, michael@0: michael@0: _storeAlwaysAsk: function HS__storeAlwaysAsk(aHandlerInfo) { michael@0: var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: this._setLiteral(infoID, michael@0: NC_ALWAYS_ASK, michael@0: aHandlerInfo.alwaysAskBeforeHandling ? "true" : "false"); michael@0: }, michael@0: michael@0: michael@0: //**************************************************************************// michael@0: // Convenience Getters michael@0: michael@0: // Observer Service michael@0: __observerSvc: null, michael@0: get _observerSvc() { michael@0: if (!this.__observerSvc) michael@0: this.__observerSvc = michael@0: Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService); michael@0: return this.__observerSvc; michael@0: }, michael@0: michael@0: // Directory Service michael@0: __dirSvc: null, michael@0: get _dirSvc() { michael@0: if (!this.__dirSvc) michael@0: this.__dirSvc = michael@0: Cc["@mozilla.org/file/directory_service;1"]. michael@0: getService(Ci.nsIProperties); michael@0: return this.__dirSvc; michael@0: }, michael@0: michael@0: // MIME Service michael@0: __mimeSvc: null, michael@0: get _mimeSvc() { michael@0: if (!this.__mimeSvc) michael@0: this.__mimeSvc = michael@0: Cc["@mozilla.org/mime;1"]. michael@0: getService(Ci.nsIMIMEService); michael@0: return this.__mimeSvc; michael@0: }, michael@0: michael@0: // Protocol Service michael@0: __protocolSvc: null, michael@0: get _protocolSvc() { michael@0: if (!this.__protocolSvc) michael@0: this.__protocolSvc = michael@0: Cc["@mozilla.org/uriloader/external-protocol-service;1"]. michael@0: getService(Ci.nsIExternalProtocolService); michael@0: return this.__protocolSvc; michael@0: }, michael@0: michael@0: // RDF Service michael@0: __rdf: null, michael@0: get _rdf() { michael@0: if (!this.__rdf) michael@0: this.__rdf = Cc["@mozilla.org/rdf/rdf-service;1"]. michael@0: getService(Ci.nsIRDFService); michael@0: return this.__rdf; michael@0: }, michael@0: michael@0: // RDF Container Utils michael@0: __containerUtils: null, michael@0: get _containerUtils() { michael@0: if (!this.__containerUtils) michael@0: this.__containerUtils = Cc["@mozilla.org/rdf/container-utils;1"]. michael@0: getService(Ci.nsIRDFContainerUtils); michael@0: return this.__containerUtils; michael@0: }, michael@0: michael@0: // RDF datasource containing content handling config (i.e. mimeTypes.rdf) michael@0: __ds: null, michael@0: get _ds() { michael@0: if (!this.__ds) { michael@0: var file = this._dirSvc.get("UMimTyp", Ci.nsIFile); michael@0: // FIXME: make this a memoizing getter if we use it anywhere else. michael@0: var ioService = Cc["@mozilla.org/network/io-service;1"]. michael@0: getService(Ci.nsIIOService); michael@0: var fileHandler = ioService.getProtocolHandler("file"). michael@0: QueryInterface(Ci.nsIFileProtocolHandler); michael@0: this.__ds = michael@0: this._rdf.GetDataSourceBlocking(fileHandler.getURLSpecFromFile(file)); michael@0: } michael@0: michael@0: return this.__ds; michael@0: }, michael@0: michael@0: michael@0: //**************************************************************************// michael@0: // Datastore Utils michael@0: michael@0: /** michael@0: * Get the string identifying whether this is a MIME or a protocol handler. michael@0: * This string is used in the URI IDs of various RDF properties. michael@0: * michael@0: * @param aHandlerInfo {nsIHandlerInfo} the handler for which to get the class michael@0: * michael@0: * @returns {string} the class michael@0: */ michael@0: _getClass: function HS__getClass(aHandlerInfo) { michael@0: if (aHandlerInfo instanceof Ci.nsIMIMEInfo) michael@0: return CLASS_MIMEINFO; michael@0: else michael@0: return CLASS_PROTOCOLINFO; michael@0: }, michael@0: michael@0: /** michael@0: * Return the unique identifier for a content type record, which stores michael@0: * the value field plus a reference to the content type's handler info record. michael@0: * michael@0: * |urn::| michael@0: * michael@0: * XXX: should this be a property of nsIHandlerInfo? michael@0: * michael@0: * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO) michael@0: * @param aType {string} the type (a MIME type or protocol scheme) michael@0: * michael@0: * @returns {string} the ID michael@0: */ michael@0: _getTypeID: function HS__getTypeID(aClass, aType) { michael@0: return "urn:" + aClass + ":" + aType; michael@0: }, michael@0: michael@0: /** michael@0: * Return the unique identifier for a handler info record, which stores michael@0: * the preferredAction and alwaysAsk fields plus a reference to the preferred michael@0: * handler app. Roughly equivalent to the nsIHandlerInfo interface. michael@0: * michael@0: * |urn::handler:| michael@0: * michael@0: * FIXME: the type info record should be merged into the type record, michael@0: * since there's a one to one relationship between them, and this record michael@0: * merely stores additional attributes of a content type. michael@0: * michael@0: * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO) michael@0: * @param aType {string} the type (a MIME type or protocol scheme) michael@0: * michael@0: * @returns {string} the ID michael@0: */ michael@0: _getInfoID: function HS__getInfoID(aClass, aType) { michael@0: return "urn:" + aClass + ":handler:" + aType; michael@0: }, michael@0: michael@0: /** michael@0: * Return the unique identifier for a preferred handler record, which stores michael@0: * information about the preferred handler for a given content type, including michael@0: * its human-readable name and the path to its executable (for a local app) michael@0: * or its URI template (for a web app). michael@0: * michael@0: * |urn::externalApplication:| michael@0: * michael@0: * XXX: should this be a property of nsIHandlerApp? michael@0: * michael@0: * FIXME: this should be an arbitrary ID, and we should retrieve it from michael@0: * the datastore for a given content type via the NC:ExternalApplication michael@0: * property rather than looking for a specific ID, so a handler doesn't michael@0: * have to change IDs when it goes from being a possible handler to being michael@0: * the preferred one (once we support possible handlers). michael@0: * michael@0: * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO) michael@0: * @param aType {string} the type (a MIME type or protocol scheme) michael@0: * michael@0: * @returns {string} the ID michael@0: */ michael@0: _getPreferredHandlerID: function HS__getPreferredHandlerID(aClass, aType) { michael@0: return "urn:" + aClass + ":externalApplication:" + aType; michael@0: }, michael@0: michael@0: /** michael@0: * Return the unique identifier for a handler app record, which stores michael@0: * information about a possible handler for one or more content types, michael@0: * including its human-readable name and the path to its executable (for a michael@0: * local app) or its URI template (for a web app). michael@0: * michael@0: * Note: handler app IDs for preferred handlers are different. For those, michael@0: * see the _getPreferredHandlerID method. michael@0: * michael@0: * @param aHandlerApp {nsIHandlerApp} the handler app object michael@0: */ michael@0: _getPossibleHandlerAppID: function HS__getPossibleHandlerAppID(aHandlerApp) { michael@0: var handlerAppID = "urn:handler:"; michael@0: michael@0: if (aHandlerApp instanceof Ci.nsILocalHandlerApp) michael@0: handlerAppID += "local:" + aHandlerApp.executable.path; michael@0: else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){ michael@0: aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp); michael@0: handlerAppID += "web:" + aHandlerApp.uriTemplate; michael@0: } michael@0: else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){ michael@0: aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp); michael@0: handlerAppID += "dbus:" + aHandlerApp.service + " " + aHandlerApp.method + " " + aHandlerApp.uriTemplate; michael@0: }else{ michael@0: throw "unknown handler type"; michael@0: } michael@0: michael@0: return handlerAppID; michael@0: }, michael@0: michael@0: /** michael@0: * Get the list of types for the given class, creating the list if it doesn't michael@0: * already exist. The class can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO michael@0: * (i.e. the result of a call to _getClass). michael@0: * michael@0: * |urn:s| michael@0: * |urn:s:root| michael@0: * michael@0: * @param aClass {string} the class for which to retrieve a list of types michael@0: * michael@0: * @returns {nsIRDFContainer} the list of types michael@0: */ michael@0: _ensureAndGetTypeList: function HS__ensureAndGetTypeList(aClass) { michael@0: var source = this._rdf.GetResource("urn:" + aClass + "s"); michael@0: var property = michael@0: this._rdf.GetResource(aClass == CLASS_MIMEINFO ? NC_MIME_TYPES michael@0: : NC_PROTOCOL_SCHEMES); michael@0: var target = this._rdf.GetResource("urn:" + aClass + "s:root"); michael@0: michael@0: // Make sure we have an arc from the source to the target. michael@0: if (!this._ds.HasAssertion(source, property, target, true)) michael@0: this._ds.Assert(source, property, target, true); michael@0: michael@0: // Make sure the target is a container. michael@0: if (!this._containerUtils.IsContainer(this._ds, target)) michael@0: this._containerUtils.MakeSeq(this._ds, target); michael@0: michael@0: // Get the type list as an RDF container. michael@0: var typeList = Cc["@mozilla.org/rdf/container;1"]. michael@0: createInstance(Ci.nsIRDFContainer); michael@0: typeList.Init(this._ds, target); michael@0: michael@0: return typeList; michael@0: }, michael@0: michael@0: /** michael@0: * Make sure there are records in the datasource for the given content type michael@0: * by creating them if they don't already exist. We have to do this before michael@0: * storing any specific data, because we can't assume the presence michael@0: * of the records (the nsIHandlerInfo object might have been created michael@0: * from the OS), and the records have to all be there in order for the helper michael@0: * app service to properly construct an nsIHandlerInfo object for the type. michael@0: * michael@0: * Based on old downloadactions.js::_ensureMIMERegistryEntry. michael@0: * michael@0: * @param aHandlerInfo {nsIHandlerInfo} the type to make sure has a record michael@0: */ michael@0: _ensureRecordsForType: function HS__ensureRecordsForType(aHandlerInfo) { michael@0: // Get the list of types. michael@0: var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo)); michael@0: michael@0: // If there's already a record in the datastore for this type, then we michael@0: // don't need to do anything more. michael@0: var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: var type = this._rdf.GetResource(typeID); michael@0: if (typeList.IndexOf(type) != -1) michael@0: return; michael@0: michael@0: // Create a basic type record for this type. michael@0: typeList.AppendElement(type); michael@0: this._setLiteral(typeID, NC_VALUE, aHandlerInfo.type); michael@0: michael@0: // Create a basic info record for this type. michael@0: var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: this._setLiteral(infoID, NC_ALWAYS_ASK, "false"); michael@0: this._setResource(typeID, NC_HANDLER_INFO, infoID); michael@0: // XXX Shouldn't we set preferredAction to useSystemDefault? michael@0: // That's what it is if there's no record in the datastore; why should it michael@0: // change to useHelperApp just because we add a record to the datastore? michael@0: michael@0: // Create a basic preferred handler record for this type. michael@0: // XXX Not sure this is necessary, since preferred handlers are optional, michael@0: // and nsExternalHelperAppService::FillHandlerInfoForTypeFromDS doesn't seem michael@0: // to require the record , but downloadactions.js::_ensureMIMERegistryEntry michael@0: // used to create it, so we'll do the same. michael@0: var preferredHandlerID = michael@0: this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type); michael@0: this._setLiteral(preferredHandlerID, NC_PATH, ""); michael@0: this._setResource(infoID, NC_PREFERRED_APP, preferredHandlerID); michael@0: }, michael@0: michael@0: /** michael@0: * Append known handlers of the given class to the given array. The class michael@0: * can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO. michael@0: * michael@0: * @param aHandlers {array} the array of handlers to append to michael@0: * @param aClass {string} the class for which to append handlers michael@0: */ michael@0: _appendHandlers: function HS__appendHandlers(aHandlers, aClass) { michael@0: var typeList = this._ensureAndGetTypeList(aClass); michael@0: var enumerator = typeList.GetElements(); michael@0: michael@0: while (enumerator.hasMoreElements()) { michael@0: var element = enumerator.getNext(); michael@0: michael@0: // This should never happen. If it does, that means our datasource michael@0: // is corrupted with type list entries that point to literal values michael@0: // instead of resources. If it does happen, let's just do our best michael@0: // to recover by ignoring this entry and moving on to the next one. michael@0: if (!(element instanceof Ci.nsIRDFResource)) michael@0: continue; michael@0: michael@0: // Get the value of the element's NC:value property, which contains michael@0: // the MIME type or scheme for which we're retrieving a handler info. michael@0: var type = this._getValue(element.ValueUTF8, NC_VALUE); michael@0: if (!type) michael@0: continue; michael@0: michael@0: var handler; michael@0: if (typeList.Resource.ValueUTF8 == "urn:mimetypes:root") michael@0: handler = this._mimeSvc.getFromTypeAndExtension(type, null); michael@0: else michael@0: handler = this._protocolSvc.getProtocolHandlerInfo(type); michael@0: michael@0: aHandlers.appendElement(handler, false); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Whether or not a property of an RDF source has a value. michael@0: * michael@0: * @param sourceURI {string} the URI of the source michael@0: * @param propertyURI {string} the URI of the property michael@0: * @returns {boolean} whether or not the property has a value michael@0: */ michael@0: _hasValue: function HS__hasValue(sourceURI, propertyURI) { michael@0: var source = this._rdf.GetResource(sourceURI); michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: return this._ds.hasArcOut(source, property); michael@0: }, michael@0: michael@0: /** michael@0: * Get the value of a property of an RDF source. michael@0: * michael@0: * @param sourceURI {string} the URI of the source michael@0: * @param propertyURI {string} the URI of the property michael@0: * @returns {string} the value of the property michael@0: */ michael@0: _getValue: function HS__getValue(sourceURI, propertyURI) { michael@0: var source = this._rdf.GetResource(sourceURI); michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: michael@0: var target = this._ds.GetTarget(source, property, true); michael@0: michael@0: if (!target) michael@0: return null; michael@0: michael@0: if (target instanceof Ci.nsIRDFResource) michael@0: return target.ValueUTF8; michael@0: michael@0: if (target instanceof Ci.nsIRDFLiteral) michael@0: return target.Value; michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Get all targets for the property of an RDF source. michael@0: * michael@0: * @param sourceURI {string} the URI of the source michael@0: * @param propertyURI {string} the URI of the property michael@0: * michael@0: * @returns {nsISimpleEnumerator} an enumerator of targets michael@0: */ michael@0: _getTargets: function HS__getTargets(sourceURI, propertyURI) { michael@0: var source = this._rdf.GetResource(sourceURI); michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: michael@0: return this._ds.GetTargets(source, property, true); michael@0: }, michael@0: michael@0: /** michael@0: * Set a property of an RDF source to a literal value. michael@0: * michael@0: * @param sourceURI {string} the URI of the source michael@0: * @param propertyURI {string} the URI of the property michael@0: * @param value {string} the literal value michael@0: */ michael@0: _setLiteral: function HS__setLiteral(sourceURI, propertyURI, value) { michael@0: var source = this._rdf.GetResource(sourceURI); michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: var target = this._rdf.GetLiteral(value); michael@0: michael@0: this._setTarget(source, property, target); michael@0: }, michael@0: michael@0: /** michael@0: * Set a property of an RDF source to a resource target. michael@0: * michael@0: * @param sourceURI {string} the URI of the source michael@0: * @param propertyURI {string} the URI of the property michael@0: * @param targetURI {string} the URI of the target michael@0: */ michael@0: _setResource: function HS__setResource(sourceURI, propertyURI, targetURI) { michael@0: var source = this._rdf.GetResource(sourceURI); michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: var target = this._rdf.GetResource(targetURI); michael@0: michael@0: this._setTarget(source, property, target); michael@0: }, michael@0: michael@0: /** michael@0: * Assert an arc into the RDF datasource if there is no arc with the given michael@0: * source and property; otherwise, if there is already an existing arc, michael@0: * change it to point to the given target. _setLiteral and _setResource michael@0: * call this after converting their string arguments into resources michael@0: * and literals, and most callers should call one of those two methods michael@0: * instead of this one. michael@0: * michael@0: * @param source {nsIRDFResource} the source michael@0: * @param property {nsIRDFResource} the property michael@0: * @param target {nsIRDFNode} the target michael@0: */ michael@0: _setTarget: function HS__setTarget(source, property, target) { michael@0: if (this._ds.hasArcOut(source, property)) { michael@0: var oldTarget = this._ds.GetTarget(source, property, true); michael@0: this._ds.Change(source, property, oldTarget, target); michael@0: } michael@0: else michael@0: this._ds.Assert(source, property, target, true); michael@0: }, michael@0: michael@0: /** michael@0: * Assert that a property of an RDF source has a resource target. michael@0: * michael@0: * The difference between this method and _setResource is that this one adds michael@0: * an assertion even if one already exists, which allows its callers to make michael@0: * sets of assertions (i.e. to set a property to multiple targets). michael@0: * michael@0: * @param sourceURI {string} the URI of the source michael@0: * @param propertyURI {string} the URI of the property michael@0: * @param targetURI {string} the URI of the target michael@0: */ michael@0: _addResourceAssertion: function HS__addResourceAssertion(sourceURI, michael@0: propertyURI, michael@0: targetURI) { michael@0: var source = this._rdf.GetResource(sourceURI); michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: var target = this._rdf.GetResource(targetURI); michael@0: michael@0: this._ds.Assert(source, property, target, true); michael@0: }, michael@0: michael@0: /** michael@0: * Remove an assertion with a resource target. michael@0: * michael@0: * @param sourceURI {string} the URI of the source michael@0: * @param propertyURI {string} the URI of the property michael@0: * @param targetURI {string} the URI of the target michael@0: */ michael@0: _removeResourceAssertion: function HS__removeResourceAssertion(sourceURI, michael@0: propertyURI, michael@0: targetURI) { michael@0: var source = this._rdf.GetResource(sourceURI); michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: var target = this._rdf.GetResource(targetURI); michael@0: michael@0: this._ds.Unassert(source, property, target); michael@0: }, michael@0: michael@0: /** michael@0: * Whether or not a property of an RDF source has a given resource target. michael@0: * michael@0: * @param sourceURI {string} the URI of the source michael@0: * @param propertyURI {string} the URI of the property michael@0: * @param targetURI {string} the URI of the target michael@0: * michael@0: * @returns {boolean} whether or not there is such an assertion michael@0: */ michael@0: _hasResourceAssertion: function HS__hasResourceAssertion(sourceURI, michael@0: propertyURI, michael@0: targetURI) { michael@0: var source = this._rdf.GetResource(sourceURI); michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: var target = this._rdf.GetResource(targetURI); michael@0: michael@0: return this._ds.HasAssertion(source, property, target, true); michael@0: }, michael@0: michael@0: /** michael@0: * Whether or not a property of an RDF source has a given literal value. michael@0: * michael@0: * @param sourceURI {string} the URI of the source michael@0: * @param propertyURI {string} the URI of the property michael@0: * @param value {string} the literal value michael@0: * michael@0: * @returns {boolean} whether or not there is such an assertion michael@0: */ michael@0: _hasLiteralAssertion: function HS__hasLiteralAssertion(sourceURI, michael@0: propertyURI, michael@0: value) { michael@0: var source = this._rdf.GetResource(sourceURI); michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: var target = this._rdf.GetLiteral(value); michael@0: michael@0: return this._ds.HasAssertion(source, property, target, true); michael@0: }, michael@0: michael@0: /** michael@0: * Whether or not there is an RDF source that has the given property set to michael@0: * the given literal value. michael@0: * michael@0: * @param propertyURI {string} the URI of the property michael@0: * @param value {string} the literal value michael@0: * michael@0: * @returns {boolean} whether or not there is a source michael@0: */ michael@0: _existsLiteralTarget: function HS__existsLiteralTarget(propertyURI, value) { michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: var target = this._rdf.GetLiteral(value); michael@0: michael@0: return this._ds.hasArcIn(target, property); michael@0: }, michael@0: michael@0: /** michael@0: * Get the source for a property set to a given literal value. michael@0: * michael@0: * @param propertyURI {string} the URI of the property michael@0: * @param value {string} the literal value michael@0: */ michael@0: _getSourceForLiteral: function HS__getSourceForLiteral(propertyURI, value) { michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: var target = this._rdf.GetLiteral(value); michael@0: michael@0: var source = this._ds.GetSource(property, target, true); michael@0: if (source) michael@0: return source.ValueUTF8; michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Whether or not there is an RDF source that has the given property set to michael@0: * the given resource target. michael@0: * michael@0: * @param propertyURI {string} the URI of the property michael@0: * @param targetURI {string} the URI of the target michael@0: * michael@0: * @returns {boolean} whether or not there is a source michael@0: */ michael@0: _existsResourceTarget: function HS__existsResourceTarget(propertyURI, michael@0: targetURI) { michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: var target = this._rdf.GetResource(targetURI); michael@0: michael@0: return this._ds.hasArcIn(target, property); michael@0: }, michael@0: michael@0: /** michael@0: * Remove a property of an RDF source. michael@0: * michael@0: * @param sourceURI {string} the URI of the source michael@0: * @param propertyURI {string} the URI of the property michael@0: */ michael@0: _removeTarget: function HS__removeTarget(sourceURI, propertyURI) { michael@0: var source = this._rdf.GetResource(sourceURI); michael@0: var property = this._rdf.GetResource(propertyURI); michael@0: michael@0: if (this._ds.hasArcOut(source, property)) { michael@0: var target = this._ds.GetTarget(source, property, true); michael@0: this._ds.Unassert(source, property, target); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Remove all assertions about a given RDF source. michael@0: * michael@0: * Note: not recursive. If some assertions point to other resources, michael@0: * and you want to remove assertions about those resources too, you need michael@0: * to do so manually. michael@0: * michael@0: * @param sourceURI {string} the URI of the source michael@0: */ michael@0: _removeAssertions: function HS__removeAssertions(sourceURI) { michael@0: var source = this._rdf.GetResource(sourceURI); michael@0: var properties = this._ds.ArcLabelsOut(source); michael@0: michael@0: while (properties.hasMoreElements()) { michael@0: let property = properties.getNext(); michael@0: let targets = this._ds.GetTargets(source, property, true); michael@0: while (targets.hasMoreElements()) { michael@0: let target = targets.getNext(); michael@0: this._ds.Unassert(source, property, target); michael@0: } michael@0: } michael@0: } michael@0: michael@0: }; michael@0: michael@0: //****************************************************************************// michael@0: // More XPCOM Plumbing michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HandlerService]);