1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const Ci = Components.interfaces;
6 const Cc = Components.classes;
7 const Cu = Components.utils;
8 const Cr = Components.results;
11 const CLASS_MIMEINFO = "mimetype";
12 const CLASS_PROTOCOLINFO = "scheme";
16 const NC_NS = "http://home.netscape.com/NC-rdf#";
18 // the most recent default handlers that have been injected. Note that
19 // this is used to construct an RDF resource, which needs to have NC_NS
20 // prepended, since that hasn't been done yet
21 const DEFAULT_HANDLERS_VERSION = "defaultHandlersVersion";
23 // type list properties
25 const NC_MIME_TYPES = NC_NS + "MIME-types";
26 const NC_PROTOCOL_SCHEMES = NC_NS + "Protocol-Schemes";
28 // content type ("type") properties
30 // nsIHandlerInfo::type
31 const NC_VALUE = NC_NS + "value";
32 const NC_DESCRIPTION = NC_NS + "description";
34 // additional extensions
35 const NC_FILE_EXTENSIONS = NC_NS + "fileExtensions";
37 // references nsIHandlerInfo record
38 const NC_HANDLER_INFO = NC_NS + "handlerProp";
40 // handler info ("info") properties
42 // nsIHandlerInfo::preferredAction
43 const NC_SAVE_TO_DISK = NC_NS + "saveToDisk";
44 const NC_HANDLE_INTERNALLY = NC_NS + "handleInternal";
45 const NC_USE_SYSTEM_DEFAULT = NC_NS + "useSystemDefault";
47 // nsIHandlerInfo::alwaysAskBeforeHandling
48 const NC_ALWAYS_ASK = NC_NS + "alwaysAsk";
50 // references nsIHandlerApp records
51 const NC_PREFERRED_APP = NC_NS + "externalApplication";
52 const NC_POSSIBLE_APP = NC_NS + "possibleApplication";
54 // handler app ("handler") properties
56 // nsIHandlerApp::name
57 const NC_PRETTY_NAME = NC_NS + "prettyName";
59 // nsILocalHandlerApp::executable
60 const NC_PATH = NC_NS + "path";
62 // nsIWebHandlerApp::uriTemplate
63 const NC_URI_TEMPLATE = NC_NS + "uriTemplate";
65 // nsIDBusHandlerApp::service
66 const NC_SERVICE = NC_NS + "service";
68 // nsIDBusHandlerApp::method
69 const NC_METHOD = NC_NS + "method";
71 // nsIDBusHandlerApp::objectPath
72 const NC_OBJPATH = NC_NS + "objectPath";
74 // nsIDBusHandlerApp::dbusInterface
75 const NC_INTERFACE = NC_NS + "dBusInterface";
77 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
80 function HandlerService() {
84 const HandlerServiceFactory = {
86 createInstance: function (outer, iid) {
88 return this._instance;
90 let processType = Cc["@mozilla.org/xre/runtime;1"].
91 getService(Ci.nsIXULRuntime).processType;
92 if (processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
93 return Cr.NS_ERROR_NOT_IMPLEMENTED;
95 return (this._instance = new HandlerService());
99 HandlerService.prototype = {
100 //**************************************************************************//
103 classID: Components.ID("{32314cc8-22f7-4f7f-a645-1a45453ba6a6}"),
104 QueryInterface: XPCOMUtils.generateQI([Ci.nsIHandlerService]),
105 _xpcom_factory: HandlerServiceFactory,
107 //**************************************************************************//
108 // Initialization & Destruction
110 _init: function HS__init() {
111 // Observe profile-before-change so we can switch to the datasource
112 // in the new profile when the user changes profiles.
113 this._observerSvc.addObserver(this, "profile-before-change", false);
115 // Observe xpcom-shutdown so we can remove these observers
116 // when the application shuts down.
117 this._observerSvc.addObserver(this, "xpcom-shutdown", false);
119 // Observe profile-do-change so that non-default profiles get upgraded too
120 this._observerSvc.addObserver(this, "profile-do-change", false);
122 // do any necessary updating of the datastore
126 _updateDB: function HS__updateDB() {
128 var defaultHandlersVersion = this._datastoreDefaultHandlersVersion;
130 // accessing the datastore failed, we can't update anything
135 // if we don't have the current version of the default prefs for
136 // this locale, inject any new default handers into the datastore
137 if (defaultHandlersVersion < this._prefsDefaultHandlersVersion) {
139 // set the new version first so that if we recurse we don't
140 // call _injectNewDefaults several times
141 this._datastoreDefaultHandlersVersion =
142 this._prefsDefaultHandlersVersion;
143 this._injectNewDefaults();
146 // if injecting the defaults failed, set the version back to the
148 this._datastoreDefaultHandlersVersion = defaultHandlersVersion;
152 get _currentLocale() {
153 var chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].
154 getService(Ci.nsIXULChromeRegistry);
155 var currentLocale = chromeRegistry.getSelectedLocale("global");
156 return currentLocale;
159 _destroy: function HS__destroy() {
160 this._observerSvc.removeObserver(this, "profile-before-change");
161 this._observerSvc.removeObserver(this, "xpcom-shutdown");
162 this._observerSvc.removeObserver(this, "profile-do-change");
164 // XXX Should we also null references to all the services that get stored
165 // by our memoizing getters in the Convenience Getters section?
168 _onProfileChange: function HS__onProfileChange() {
169 // Lose our reference to the datasource so we reacquire it
170 // from the new profile the next time we need it.
174 _isInHandlerArray: function HS__isInHandlerArray(aArray, aHandler) {
175 var enumerator = aArray.enumerate();
176 while (enumerator.hasMoreElements()) {
177 let handler = enumerator.getNext();
178 handler.QueryInterface(Ci.nsIHandlerApp);
179 if (handler.equals(aHandler))
186 // note that this applies to the current locale only
187 get _datastoreDefaultHandlersVersion() {
188 var version = this._getValue("urn:root", NC_NS + this._currentLocale +
189 "_" + DEFAULT_HANDLERS_VERSION);
191 return version ? version : -1;
194 set _datastoreDefaultHandlersVersion(aNewVersion) {
195 return this._setLiteral("urn:root", NC_NS + this._currentLocale + "_" +
196 DEFAULT_HANDLERS_VERSION, aNewVersion);
199 get _prefsDefaultHandlersVersion() {
200 // get handler service pref branch
201 var prefSvc = Cc["@mozilla.org/preferences-service;1"].
202 getService(Ci.nsIPrefService);
203 var handlerSvcBranch = prefSvc.getBranch("gecko.handlerService.");
205 // get the version of the preferences for this locale
206 return Number(handlerSvcBranch.
207 getComplexValue("defaultHandlersVersion",
208 Ci.nsIPrefLocalizedString).data);
211 _injectNewDefaults: function HS__injectNewDefaults() {
212 // get handler service pref branch
213 var prefSvc = Cc["@mozilla.org/preferences-service;1"].
214 getService(Ci.nsIPrefService);
216 let schemesPrefBranch = prefSvc.getBranch("gecko.handlerService.schemes.");
217 let schemePrefList = schemesPrefBranch.getChildList("");
221 // read all the scheme prefs into a hash
222 for each (var schemePrefName in schemePrefList) {
224 let [scheme, handlerNumber, attribute] = schemePrefName.split(".");
228 schemesPrefBranch.getComplexValue(schemePrefName,
229 Ci.nsIPrefLocalizedString).data;
230 if (!(scheme in schemes))
231 schemes[scheme] = {};
233 if (!(handlerNumber in schemes[scheme]))
234 schemes[scheme][handlerNumber] = {};
236 schemes[scheme][handlerNumber][attribute] = attrData;
240 let protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
241 getService(Ci.nsIExternalProtocolService);
242 for (var scheme in schemes) {
244 // This clause is essentially a reimplementation of
245 // nsIExternalProtocolHandlerService.getProtocolHandlerInfo().
246 // Necessary because calling that from here would make XPConnect barf
247 // when getService tried to re-enter the constructor for this
249 let osDefaultHandlerFound = {};
250 let protoInfo = protoSvc.getProtocolHandlerInfoFromOS(scheme,
251 osDefaultHandlerFound);
253 if (this.exists(protoInfo))
254 this.fillHandlerInfo(protoInfo, null);
256 protoSvc.setProtocolHandlerDefaults(protoInfo,
257 osDefaultHandlerFound.value);
259 // cache the possible handlers to avoid extra xpconnect traversals.
260 let possibleHandlers = protoInfo.possibleApplicationHandlers;
262 for each (var handlerPrefs in schemes[scheme]) {
264 let handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"].
265 createInstance(Ci.nsIWebHandlerApp);
267 handlerApp.uriTemplate = handlerPrefs.uriTemplate;
268 handlerApp.name = handlerPrefs.name;
270 if (!this._isInHandlerArray(possibleHandlers, handlerApp)) {
271 possibleHandlers.appendElement(handlerApp, false);
275 this.store(protoInfo);
279 //**************************************************************************//
282 observe: function HS__observe(subject, topic, data) {
284 case "profile-before-change":
285 this._onProfileChange();
287 case "xpcom-shutdown":
290 case "profile-do-change":
297 //**************************************************************************//
300 enumerate: function HS_enumerate() {
301 var handlers = Cc["@mozilla.org/array;1"].
302 createInstance(Ci.nsIMutableArray);
303 this._appendHandlers(handlers, CLASS_MIMEINFO);
304 this._appendHandlers(handlers, CLASS_PROTOCOLINFO);
305 return handlers.enumerate();
308 fillHandlerInfo: function HS_fillHandlerInfo(aHandlerInfo, aOverrideType) {
309 var type = aOverrideType || aHandlerInfo.type;
310 var typeID = this._getTypeID(this._getClass(aHandlerInfo), type);
312 // Determine whether or not information about this handler is available
313 // in the datastore by looking for its "value" property, which stores its
314 // type and should always be present.
315 if (!this._hasValue(typeID, NC_VALUE))
316 throw Cr.NS_ERROR_NOT_AVAILABLE;
318 // Retrieve the human-readable description of the type.
319 if (this._hasValue(typeID, NC_DESCRIPTION))
320 aHandlerInfo.description = this._getValue(typeID, NC_DESCRIPTION);
322 // Note: for historical reasons, we don't actually check that the type
323 // record has a "handlerProp" property referencing the info record. It's
324 // unclear whether or not we should start doing this check; perhaps some
325 // legacy datasources don't have such references.
326 var infoID = this._getInfoID(this._getClass(aHandlerInfo), type);
328 aHandlerInfo.preferredAction = this._retrievePreferredAction(infoID);
330 var preferredHandlerID =
331 this._getPreferredHandlerID(this._getClass(aHandlerInfo), type);
333 // Retrieve the preferred handler.
334 // Note: for historical reasons, we don't actually check that the info
335 // record has an "externalApplication" property referencing the preferred
336 // handler record. It's unclear whether or not we should start doing
337 // this check; perhaps some legacy datasources don't have such references.
338 aHandlerInfo.preferredApplicationHandler =
339 this._retrieveHandlerApp(preferredHandlerID);
341 // Fill the array of possible handlers with the ones in the datastore.
342 this._fillPossibleHandlers(infoID,
343 aHandlerInfo.possibleApplicationHandlers,
344 aHandlerInfo.preferredApplicationHandler);
346 // If we have an "always ask" flag stored in the RDF, always use its
347 // value. Otherwise, use the default value stored in the pref service.
349 if (this._hasValue(infoID, NC_ALWAYS_ASK)) {
350 alwaysAsk = (this._getValue(infoID, NC_ALWAYS_ASK) != "false");
352 var prefSvc = Cc["@mozilla.org/preferences-service;1"].
353 getService(Ci.nsIPrefService);
354 var prefBranch = prefSvc.getBranch("network.protocol-handler.");
356 alwaysAsk = prefBranch.getBoolPref("warn-external." + type);
358 // will throw if pref didn't exist.
360 alwaysAsk = prefBranch.getBoolPref("warn-external-default");
362 // Nothing to tell us what to do, so be paranoid and prompt.
367 aHandlerInfo.alwaysAskBeforeHandling = alwaysAsk;
369 // If the object represents a MIME type handler, then also retrieve
370 // any file extensions.
371 if (aHandlerInfo instanceof Ci.nsIMIMEInfo)
372 for each (let fileExtension in this._retrieveFileExtensions(typeID))
373 aHandlerInfo.appendExtension(fileExtension);
376 store: function HS_store(aHandlerInfo) {
377 // FIXME: when we switch from RDF to something with transactions (like
378 // SQLite), enclose the following changes in a transaction so they all
379 // get rolled back if any of them fail and we don't leave the datastore
380 // in an inconsistent state.
382 this._ensureRecordsForType(aHandlerInfo);
383 this._storePreferredAction(aHandlerInfo);
384 this._storePreferredHandler(aHandlerInfo);
385 this._storePossibleHandlers(aHandlerInfo);
386 this._storeAlwaysAsk(aHandlerInfo);
388 // Write the changes to the database immediately so we don't lose them
389 // if the application crashes.
390 if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
394 exists: function HS_exists(aHandlerInfo) {
398 var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
399 found = this._hasLiteralAssertion(typeID, NC_VALUE, aHandlerInfo.type);
401 // If the RDF threw (eg, corrupt file), treat as nonexistent.
408 remove: function HS_remove(aHandlerInfo) {
409 var preferredHandlerID =
410 this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
411 this._removeAssertions(preferredHandlerID);
413 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
415 // Get a list of possible handlers. After we have removed the info record,
416 // we'll check if any other info records reference these handlers, and we'll
417 // remove the handler records that aren't referenced by other info records.
418 var possibleHandlerIDs = [];
419 var possibleHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP);
420 while (possibleHandlerTargets.hasMoreElements()) {
421 let possibleHandlerTarget = possibleHandlerTargets.getNext();
422 // Note: possibleHandlerTarget should always be an nsIRDFResource.
423 // The conditional is just here in case of a corrupt RDF datasource.
424 if (possibleHandlerTarget instanceof Ci.nsIRDFResource)
425 possibleHandlerIDs.push(possibleHandlerTarget.ValueUTF8);
428 // Remove the info record.
429 this._removeAssertions(infoID);
431 // Now that we've removed the info record, remove any possible handlers
432 // that aren't referenced by other info records.
433 for each (let possibleHandlerID in possibleHandlerIDs)
434 if (!this._existsResourceTarget(NC_POSSIBLE_APP, possibleHandlerID))
435 this._removeAssertions(possibleHandlerID);
437 var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
438 this._removeAssertions(typeID);
440 // Now that there's no longer a handler for this type, remove the type
441 // from the list of types for which there are known handlers.
442 var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo));
443 var type = this._rdf.GetResource(typeID);
444 var typeIndex = typeList.IndexOf(type);
446 typeList.RemoveElementAt(typeIndex, true);
448 // Write the changes to the database immediately so we don't lose them
449 // if the application crashes.
450 // XXX If we're removing a bunch of handlers at once, will flushing
451 // after every removal cause a significant performance hit?
452 if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
456 getTypeFromExtension: function HS_getTypeFromExtension(aFileExtension) {
457 var fileExtension = aFileExtension.toLowerCase();
460 if (this._existsLiteralTarget(NC_FILE_EXTENSIONS, fileExtension))
461 typeID = this._getSourceForLiteral(NC_FILE_EXTENSIONS, fileExtension);
463 if (typeID && this._hasValue(typeID, NC_VALUE)) {
464 let type = this._getValue(typeID, NC_VALUE);
466 throw Cr.NS_ERROR_FAILURE;
474 //**************************************************************************//
478 * Retrieve the preferred action for the info record with the given ID.
480 * @param aInfoID {string} the info record ID
482 * @returns {integer} the preferred action enumeration value
484 _retrievePreferredAction: function HS__retrievePreferredAction(aInfoID) {
485 if (this._getValue(aInfoID, NC_SAVE_TO_DISK) == "true")
486 return Ci.nsIHandlerInfo.saveToDisk;
488 if (this._getValue(aInfoID, NC_USE_SYSTEM_DEFAULT) == "true")
489 return Ci.nsIHandlerInfo.useSystemDefault;
491 if (this._getValue(aInfoID, NC_HANDLE_INTERNALLY) == "true")
492 return Ci.nsIHandlerInfo.handleInternally;
494 return Ci.nsIHandlerInfo.useHelperApp;
498 * Fill an array of possible handlers with the handlers for the given info ID.
500 * @param aInfoID {string} the ID of the info record
501 * @param aPossibleHandlers {nsIMutableArray} the array of possible handlers
502 * @param aPreferredHandler {nsIHandlerApp} the preferred handler, if any
504 _fillPossibleHandlers: function HS__fillPossibleHandlers(aInfoID,
507 // The set of possible handlers should include the preferred handler,
508 // but legacy datastores (from before we added possible handlers) won't
509 // include the preferred handler, so check if it's included as we build
510 // the list of handlers, and, if it's not included, add it to the list.
511 if (aPreferredHandler)
512 aPossibleHandlers.appendElement(aPreferredHandler, false);
514 var possibleHandlerTargets = this._getTargets(aInfoID, NC_POSSIBLE_APP);
516 while (possibleHandlerTargets.hasMoreElements()) {
517 let possibleHandlerTarget = possibleHandlerTargets.getNext();
518 if (!(possibleHandlerTarget instanceof Ci.nsIRDFResource))
521 let possibleHandlerID = possibleHandlerTarget.ValueUTF8;
522 let possibleHandler = this._retrieveHandlerApp(possibleHandlerID);
523 if (possibleHandler && (!aPreferredHandler ||
524 !possibleHandler.equals(aPreferredHandler)))
525 aPossibleHandlers.appendElement(possibleHandler, false);
530 * Retrieve the handler app object with the given ID.
532 * @param aHandlerAppID {string} the ID of the handler app to retrieve
534 * @returns {nsIHandlerApp} the handler app, if any; otherwise null
536 _retrieveHandlerApp: function HS__retrieveHandlerApp(aHandlerAppID) {
539 // If it has a path, it's a local handler; otherwise, it's a web handler.
540 if (this._hasValue(aHandlerAppID, NC_PATH)) {
542 this._getFileWithPath(this._getValue(aHandlerAppID, NC_PATH));
546 handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
547 createInstance(Ci.nsILocalHandlerApp);
548 handlerApp.executable = executable;
550 else if (this._hasValue(aHandlerAppID, NC_URI_TEMPLATE)) {
551 let uriTemplate = this._getValue(aHandlerAppID, NC_URI_TEMPLATE);
555 handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"].
556 createInstance(Ci.nsIWebHandlerApp);
557 handlerApp.uriTemplate = uriTemplate;
559 else if (this._hasValue(aHandlerAppID, NC_SERVICE)) {
560 let service = this._getValue(aHandlerAppID, NC_SERVICE);
564 let method = this._getValue(aHandlerAppID, NC_METHOD);
568 let objpath = this._getValue(aHandlerAppID, NC_OBJPATH);
572 let interface = this._getValue(aHandlerAppID, NC_INTERFACE);
576 handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"].
577 createInstance(Ci.nsIDBusHandlerApp);
578 handlerApp.service = service;
579 handlerApp.method = method;
580 handlerApp.objectPath = objpath;
581 handlerApp.dBusInterface = interface;
587 handlerApp.name = this._getValue(aHandlerAppID, NC_PRETTY_NAME);
593 * Retrieve file extensions, if any, for the MIME type with the given type ID.
595 * @param aTypeID {string} the type record ID
597 _retrieveFileExtensions: function HS__retrieveFileExtensions(aTypeID) {
598 var fileExtensions = [];
600 var fileExtensionTargets = this._getTargets(aTypeID, NC_FILE_EXTENSIONS);
602 while (fileExtensionTargets.hasMoreElements()) {
603 let fileExtensionTarget = fileExtensionTargets.getNext();
604 if (fileExtensionTarget instanceof Ci.nsIRDFLiteral &&
605 fileExtensionTarget.Value != "")
606 fileExtensions.push(fileExtensionTarget.Value);
609 return fileExtensions;
613 * Get the file with the given path. This is not as simple as merely
614 * initializing a local file object with the path, because the path might be
615 * relative to the current process directory, in which case we have to
616 * construct a path starting from that directory.
618 * @param aPath {string} a path to a file
620 * @returns {nsILocalFile} the file, or null if the file does not exist
622 _getFileWithPath: function HS__getFileWithPath(aPath) {
623 var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
626 file.initWithPath(aPath);
632 // Note: for historical reasons, we don't actually check to see
633 // if the exception is NS_ERROR_FILE_UNRECOGNIZED_PATH, which is what
634 // nsILocalFile::initWithPath throws when a path is relative.
636 file = this._dirSvc.get("XCurProcD", Ci.nsIFile);
650 //**************************************************************************//
653 _storePreferredAction: function HS__storePreferredAction(aHandlerInfo) {
654 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
656 switch(aHandlerInfo.preferredAction) {
657 case Ci.nsIHandlerInfo.saveToDisk:
658 this._setLiteral(infoID, NC_SAVE_TO_DISK, "true");
659 this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
660 this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
663 case Ci.nsIHandlerInfo.handleInternally:
664 this._setLiteral(infoID, NC_HANDLE_INTERNALLY, "true");
665 this._removeTarget(infoID, NC_SAVE_TO_DISK);
666 this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
669 case Ci.nsIHandlerInfo.useSystemDefault:
670 this._setLiteral(infoID, NC_USE_SYSTEM_DEFAULT, "true");
671 this._removeTarget(infoID, NC_SAVE_TO_DISK);
672 this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
675 // This value is indicated in the datastore either by the absence of
676 // the three properties or by setting them all "false". Of these two
677 // options, the former seems preferable, because it reduces the size
678 // of the RDF file and thus the amount of stuff we have to parse.
679 case Ci.nsIHandlerInfo.useHelperApp:
681 this._removeTarget(infoID, NC_SAVE_TO_DISK);
682 this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
683 this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
688 _storePreferredHandler: function HS__storePreferredHandler(aHandlerInfo) {
689 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
691 this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
693 var handler = aHandlerInfo.preferredApplicationHandler;
696 this._storeHandlerApp(handlerID, handler);
698 // Make this app be the preferred app for the handler info.
700 // Note: nsExternalHelperAppService::FillContentHandlerProperties ignores
701 // this setting and instead identifies the preferred app as the resource
702 // whose URI follows the pattern urn:<class>:externalApplication:<type>.
703 // But the old downloadactions.js code used to set this property, so just
704 // in case there is still some code somewhere that relies on its presence,
706 this._setResource(infoID, NC_PREFERRED_APP, handlerID);
709 // There isn't a preferred handler. Remove the existing record for it,
711 this._removeTarget(infoID, NC_PREFERRED_APP);
712 this._removeAssertions(handlerID);
717 * Store the list of possible handler apps for the content type represented
718 * by the given handler info object.
720 * @param aHandlerInfo {nsIHandlerInfo} the handler info object
722 _storePossibleHandlers: function HS__storePossibleHandlers(aHandlerInfo) {
723 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
725 // First, retrieve the set of handler apps currently stored for the type,
726 // keeping track of their IDs in a hash that we'll use to determine which
727 // ones are no longer valid and should be removed.
728 var currentHandlerApps = {};
729 var currentHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP);
730 while (currentHandlerTargets.hasMoreElements()) {
731 let handlerApp = currentHandlerTargets.getNext();
732 if (handlerApp instanceof Ci.nsIRDFResource) {
733 let handlerAppID = handlerApp.ValueUTF8;
734 currentHandlerApps[handlerAppID] = true;
738 // Next, store any new handler apps.
740 aHandlerInfo.possibleApplicationHandlers.enumerate();
741 while (newHandlerApps.hasMoreElements()) {
743 newHandlerApps.getNext().QueryInterface(Ci.nsIHandlerApp);
744 let handlerAppID = this._getPossibleHandlerAppID(handlerApp);
745 if (!this._hasResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID)) {
746 this._storeHandlerApp(handlerAppID, handlerApp);
747 this._addResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID);
749 delete currentHandlerApps[handlerAppID];
752 // Finally, remove any old handler apps that aren't being used anymore,
753 // and if those handler apps aren't being used by any other type either,
754 // then completely remove their record from the datastore so we don't
755 // leave it clogged up with information about handler apps we don't care
757 for (let handlerAppID in currentHandlerApps) {
758 this._removeResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID);
759 if (!this._existsResourceTarget(NC_POSSIBLE_APP, handlerAppID))
760 this._removeAssertions(handlerAppID);
765 * Store the given handler app.
767 * Note: the reason this method takes the ID of the handler app in a param
768 * is that the ID is different than it usually is when the handler app
769 * in question is a preferred handler app, so this method can't just derive
770 * the ID of the handler app by calling _getPossibleHandlerAppID, its callers
771 * have to do that for it.
773 * @param aHandlerAppID {string} the ID of the handler app to store
774 * @param aHandlerApp {nsIHandlerApp} the handler app to store
776 _storeHandlerApp: function HS__storeHandlerApp(aHandlerAppID, aHandlerApp) {
777 aHandlerApp.QueryInterface(Ci.nsIHandlerApp);
778 this._setLiteral(aHandlerAppID, NC_PRETTY_NAME, aHandlerApp.name);
780 // In the case of the preferred handler, the handler ID could have been
781 // used to refer to a different kind of handler in the past (i.e. either
782 // a local hander or a web handler), so if the new handler is a local
783 // handler, then we remove any web handler properties and vice versa.
784 // This is unnecessary but harmless for possible handlers.
786 if (aHandlerApp instanceof Ci.nsILocalHandlerApp) {
787 this._setLiteral(aHandlerAppID, NC_PATH, aHandlerApp.executable.path);
788 this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE);
789 this._removeTarget(aHandlerAppID, NC_METHOD);
790 this._removeTarget(aHandlerAppID, NC_SERVICE);
791 this._removeTarget(aHandlerAppID, NC_OBJPATH);
792 this._removeTarget(aHandlerAppID, NC_INTERFACE);
794 else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){
795 aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp);
796 this._setLiteral(aHandlerAppID, NC_URI_TEMPLATE, aHandlerApp.uriTemplate);
797 this._removeTarget(aHandlerAppID, NC_PATH);
798 this._removeTarget(aHandlerAppID, NC_METHOD);
799 this._removeTarget(aHandlerAppID, NC_SERVICE);
800 this._removeTarget(aHandlerAppID, NC_OBJPATH);
801 this._removeTarget(aHandlerAppID, NC_INTERFACE);
803 else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){
804 aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp);
805 this._setLiteral(aHandlerAppID, NC_SERVICE, aHandlerApp.service);
806 this._setLiteral(aHandlerAppID, NC_METHOD, aHandlerApp.method);
807 this._setLiteral(aHandlerAppID, NC_OBJPATH, aHandlerApp.objectPath);
808 this._setLiteral(aHandlerAppID, NC_INTERFACE, aHandlerApp.dBusInterface);
809 this._removeTarget(aHandlerAppID, NC_PATH);
810 this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE);
813 throw "unknown handler type";
818 _storeAlwaysAsk: function HS__storeAlwaysAsk(aHandlerInfo) {
819 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
820 this._setLiteral(infoID,
822 aHandlerInfo.alwaysAskBeforeHandling ? "true" : "false");
826 //**************************************************************************//
827 // Convenience Getters
832 if (!this.__observerSvc)
834 Cc["@mozilla.org/observer-service;1"].
835 getService(Ci.nsIObserverService);
836 return this.__observerSvc;
844 Cc["@mozilla.org/file/directory_service;1"].
845 getService(Ci.nsIProperties);
846 return this.__dirSvc;
854 Cc["@mozilla.org/mime;1"].
855 getService(Ci.nsIMIMEService);
856 return this.__mimeSvc;
862 if (!this.__protocolSvc)
864 Cc["@mozilla.org/uriloader/external-protocol-service;1"].
865 getService(Ci.nsIExternalProtocolService);
866 return this.__protocolSvc;
873 this.__rdf = Cc["@mozilla.org/rdf/rdf-service;1"].
874 getService(Ci.nsIRDFService);
878 // RDF Container Utils
879 __containerUtils: null,
880 get _containerUtils() {
881 if (!this.__containerUtils)
882 this.__containerUtils = Cc["@mozilla.org/rdf/container-utils;1"].
883 getService(Ci.nsIRDFContainerUtils);
884 return this.__containerUtils;
887 // RDF datasource containing content handling config (i.e. mimeTypes.rdf)
891 var file = this._dirSvc.get("UMimTyp", Ci.nsIFile);
892 // FIXME: make this a memoizing getter if we use it anywhere else.
893 var ioService = Cc["@mozilla.org/network/io-service;1"].
894 getService(Ci.nsIIOService);
895 var fileHandler = ioService.getProtocolHandler("file").
896 QueryInterface(Ci.nsIFileProtocolHandler);
898 this._rdf.GetDataSourceBlocking(fileHandler.getURLSpecFromFile(file));
905 //**************************************************************************//
909 * Get the string identifying whether this is a MIME or a protocol handler.
910 * This string is used in the URI IDs of various RDF properties.
912 * @param aHandlerInfo {nsIHandlerInfo} the handler for which to get the class
914 * @returns {string} the class
916 _getClass: function HS__getClass(aHandlerInfo) {
917 if (aHandlerInfo instanceof Ci.nsIMIMEInfo)
918 return CLASS_MIMEINFO;
920 return CLASS_PROTOCOLINFO;
924 * Return the unique identifier for a content type record, which stores
925 * the value field plus a reference to the content type's handler info record.
927 * |urn:<class>:<type>|
929 * XXX: should this be a property of nsIHandlerInfo?
931 * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
932 * @param aType {string} the type (a MIME type or protocol scheme)
934 * @returns {string} the ID
936 _getTypeID: function HS__getTypeID(aClass, aType) {
937 return "urn:" + aClass + ":" + aType;
941 * Return the unique identifier for a handler info record, which stores
942 * the preferredAction and alwaysAsk fields plus a reference to the preferred
943 * handler app. Roughly equivalent to the nsIHandlerInfo interface.
945 * |urn:<class>:handler:<type>|
947 * FIXME: the type info record should be merged into the type record,
948 * since there's a one to one relationship between them, and this record
949 * merely stores additional attributes of a content type.
951 * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
952 * @param aType {string} the type (a MIME type or protocol scheme)
954 * @returns {string} the ID
956 _getInfoID: function HS__getInfoID(aClass, aType) {
957 return "urn:" + aClass + ":handler:" + aType;
961 * Return the unique identifier for a preferred handler record, which stores
962 * information about the preferred handler for a given content type, including
963 * its human-readable name and the path to its executable (for a local app)
964 * or its URI template (for a web app).
966 * |urn:<class>:externalApplication:<type>|
968 * XXX: should this be a property of nsIHandlerApp?
970 * FIXME: this should be an arbitrary ID, and we should retrieve it from
971 * the datastore for a given content type via the NC:ExternalApplication
972 * property rather than looking for a specific ID, so a handler doesn't
973 * have to change IDs when it goes from being a possible handler to being
974 * the preferred one (once we support possible handlers).
976 * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
977 * @param aType {string} the type (a MIME type or protocol scheme)
979 * @returns {string} the ID
981 _getPreferredHandlerID: function HS__getPreferredHandlerID(aClass, aType) {
982 return "urn:" + aClass + ":externalApplication:" + aType;
986 * Return the unique identifier for a handler app record, which stores
987 * information about a possible handler for one or more content types,
988 * including its human-readable name and the path to its executable (for a
989 * local app) or its URI template (for a web app).
991 * Note: handler app IDs for preferred handlers are different. For those,
992 * see the _getPreferredHandlerID method.
994 * @param aHandlerApp {nsIHandlerApp} the handler app object
996 _getPossibleHandlerAppID: function HS__getPossibleHandlerAppID(aHandlerApp) {
997 var handlerAppID = "urn:handler:";
999 if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
1000 handlerAppID += "local:" + aHandlerApp.executable.path;
1001 else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){
1002 aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp);
1003 handlerAppID += "web:" + aHandlerApp.uriTemplate;
1005 else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){
1006 aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp);
1007 handlerAppID += "dbus:" + aHandlerApp.service + " " + aHandlerApp.method + " " + aHandlerApp.uriTemplate;
1009 throw "unknown handler type";
1012 return handlerAppID;
1016 * Get the list of types for the given class, creating the list if it doesn't
1017 * already exist. The class can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO
1018 * (i.e. the result of a call to _getClass).
1021 * |urn:<class>s:root|
1023 * @param aClass {string} the class for which to retrieve a list of types
1025 * @returns {nsIRDFContainer} the list of types
1027 _ensureAndGetTypeList: function HS__ensureAndGetTypeList(aClass) {
1028 var source = this._rdf.GetResource("urn:" + aClass + "s");
1030 this._rdf.GetResource(aClass == CLASS_MIMEINFO ? NC_MIME_TYPES
1031 : NC_PROTOCOL_SCHEMES);
1032 var target = this._rdf.GetResource("urn:" + aClass + "s:root");
1034 // Make sure we have an arc from the source to the target.
1035 if (!this._ds.HasAssertion(source, property, target, true))
1036 this._ds.Assert(source, property, target, true);
1038 // Make sure the target is a container.
1039 if (!this._containerUtils.IsContainer(this._ds, target))
1040 this._containerUtils.MakeSeq(this._ds, target);
1042 // Get the type list as an RDF container.
1043 var typeList = Cc["@mozilla.org/rdf/container;1"].
1044 createInstance(Ci.nsIRDFContainer);
1045 typeList.Init(this._ds, target);
1051 * Make sure there are records in the datasource for the given content type
1052 * by creating them if they don't already exist. We have to do this before
1053 * storing any specific data, because we can't assume the presence
1054 * of the records (the nsIHandlerInfo object might have been created
1055 * from the OS), and the records have to all be there in order for the helper
1056 * app service to properly construct an nsIHandlerInfo object for the type.
1058 * Based on old downloadactions.js::_ensureMIMERegistryEntry.
1060 * @param aHandlerInfo {nsIHandlerInfo} the type to make sure has a record
1062 _ensureRecordsForType: function HS__ensureRecordsForType(aHandlerInfo) {
1063 // Get the list of types.
1064 var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo));
1066 // If there's already a record in the datastore for this type, then we
1067 // don't need to do anything more.
1068 var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
1069 var type = this._rdf.GetResource(typeID);
1070 if (typeList.IndexOf(type) != -1)
1073 // Create a basic type record for this type.
1074 typeList.AppendElement(type);
1075 this._setLiteral(typeID, NC_VALUE, aHandlerInfo.type);
1077 // Create a basic info record for this type.
1078 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
1079 this._setLiteral(infoID, NC_ALWAYS_ASK, "false");
1080 this._setResource(typeID, NC_HANDLER_INFO, infoID);
1081 // XXX Shouldn't we set preferredAction to useSystemDefault?
1082 // That's what it is if there's no record in the datastore; why should it
1083 // change to useHelperApp just because we add a record to the datastore?
1085 // Create a basic preferred handler record for this type.
1086 // XXX Not sure this is necessary, since preferred handlers are optional,
1087 // and nsExternalHelperAppService::FillHandlerInfoForTypeFromDS doesn't seem
1088 // to require the record , but downloadactions.js::_ensureMIMERegistryEntry
1089 // used to create it, so we'll do the same.
1090 var preferredHandlerID =
1091 this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
1092 this._setLiteral(preferredHandlerID, NC_PATH, "");
1093 this._setResource(infoID, NC_PREFERRED_APP, preferredHandlerID);
1097 * Append known handlers of the given class to the given array. The class
1098 * can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO.
1100 * @param aHandlers {array} the array of handlers to append to
1101 * @param aClass {string} the class for which to append handlers
1103 _appendHandlers: function HS__appendHandlers(aHandlers, aClass) {
1104 var typeList = this._ensureAndGetTypeList(aClass);
1105 var enumerator = typeList.GetElements();
1107 while (enumerator.hasMoreElements()) {
1108 var element = enumerator.getNext();
1110 // This should never happen. If it does, that means our datasource
1111 // is corrupted with type list entries that point to literal values
1112 // instead of resources. If it does happen, let's just do our best
1113 // to recover by ignoring this entry and moving on to the next one.
1114 if (!(element instanceof Ci.nsIRDFResource))
1117 // Get the value of the element's NC:value property, which contains
1118 // the MIME type or scheme for which we're retrieving a handler info.
1119 var type = this._getValue(element.ValueUTF8, NC_VALUE);
1124 if (typeList.Resource.ValueUTF8 == "urn:mimetypes:root")
1125 handler = this._mimeSvc.getFromTypeAndExtension(type, null);
1127 handler = this._protocolSvc.getProtocolHandlerInfo(type);
1129 aHandlers.appendElement(handler, false);
1134 * Whether or not a property of an RDF source has a value.
1136 * @param sourceURI {string} the URI of the source
1137 * @param propertyURI {string} the URI of the property
1138 * @returns {boolean} whether or not the property has a value
1140 _hasValue: function HS__hasValue(sourceURI, propertyURI) {
1141 var source = this._rdf.GetResource(sourceURI);
1142 var property = this._rdf.GetResource(propertyURI);
1143 return this._ds.hasArcOut(source, property);
1147 * Get the value of a property of an RDF source.
1149 * @param sourceURI {string} the URI of the source
1150 * @param propertyURI {string} the URI of the property
1151 * @returns {string} the value of the property
1153 _getValue: function HS__getValue(sourceURI, propertyURI) {
1154 var source = this._rdf.GetResource(sourceURI);
1155 var property = this._rdf.GetResource(propertyURI);
1157 var target = this._ds.GetTarget(source, property, true);
1162 if (target instanceof Ci.nsIRDFResource)
1163 return target.ValueUTF8;
1165 if (target instanceof Ci.nsIRDFLiteral)
1166 return target.Value;
1172 * Get all targets for the property of an RDF source.
1174 * @param sourceURI {string} the URI of the source
1175 * @param propertyURI {string} the URI of the property
1177 * @returns {nsISimpleEnumerator} an enumerator of targets
1179 _getTargets: function HS__getTargets(sourceURI, propertyURI) {
1180 var source = this._rdf.GetResource(sourceURI);
1181 var property = this._rdf.GetResource(propertyURI);
1183 return this._ds.GetTargets(source, property, true);
1187 * Set a property of an RDF source to a literal value.
1189 * @param sourceURI {string} the URI of the source
1190 * @param propertyURI {string} the URI of the property
1191 * @param value {string} the literal value
1193 _setLiteral: function HS__setLiteral(sourceURI, propertyURI, value) {
1194 var source = this._rdf.GetResource(sourceURI);
1195 var property = this._rdf.GetResource(propertyURI);
1196 var target = this._rdf.GetLiteral(value);
1198 this._setTarget(source, property, target);
1202 * Set a property of an RDF source to a resource target.
1204 * @param sourceURI {string} the URI of the source
1205 * @param propertyURI {string} the URI of the property
1206 * @param targetURI {string} the URI of the target
1208 _setResource: function HS__setResource(sourceURI, propertyURI, targetURI) {
1209 var source = this._rdf.GetResource(sourceURI);
1210 var property = this._rdf.GetResource(propertyURI);
1211 var target = this._rdf.GetResource(targetURI);
1213 this._setTarget(source, property, target);
1217 * Assert an arc into the RDF datasource if there is no arc with the given
1218 * source and property; otherwise, if there is already an existing arc,
1219 * change it to point to the given target. _setLiteral and _setResource
1220 * call this after converting their string arguments into resources
1221 * and literals, and most callers should call one of those two methods
1222 * instead of this one.
1224 * @param source {nsIRDFResource} the source
1225 * @param property {nsIRDFResource} the property
1226 * @param target {nsIRDFNode} the target
1228 _setTarget: function HS__setTarget(source, property, target) {
1229 if (this._ds.hasArcOut(source, property)) {
1230 var oldTarget = this._ds.GetTarget(source, property, true);
1231 this._ds.Change(source, property, oldTarget, target);
1234 this._ds.Assert(source, property, target, true);
1238 * Assert that a property of an RDF source has a resource target.
1240 * The difference between this method and _setResource is that this one adds
1241 * an assertion even if one already exists, which allows its callers to make
1242 * sets of assertions (i.e. to set a property to multiple targets).
1244 * @param sourceURI {string} the URI of the source
1245 * @param propertyURI {string} the URI of the property
1246 * @param targetURI {string} the URI of the target
1248 _addResourceAssertion: function HS__addResourceAssertion(sourceURI,
1251 var source = this._rdf.GetResource(sourceURI);
1252 var property = this._rdf.GetResource(propertyURI);
1253 var target = this._rdf.GetResource(targetURI);
1255 this._ds.Assert(source, property, target, true);
1259 * Remove an assertion with a resource target.
1261 * @param sourceURI {string} the URI of the source
1262 * @param propertyURI {string} the URI of the property
1263 * @param targetURI {string} the URI of the target
1265 _removeResourceAssertion: function HS__removeResourceAssertion(sourceURI,
1268 var source = this._rdf.GetResource(sourceURI);
1269 var property = this._rdf.GetResource(propertyURI);
1270 var target = this._rdf.GetResource(targetURI);
1272 this._ds.Unassert(source, property, target);
1276 * Whether or not a property of an RDF source has a given resource target.
1278 * @param sourceURI {string} the URI of the source
1279 * @param propertyURI {string} the URI of the property
1280 * @param targetURI {string} the URI of the target
1282 * @returns {boolean} whether or not there is such an assertion
1284 _hasResourceAssertion: function HS__hasResourceAssertion(sourceURI,
1287 var source = this._rdf.GetResource(sourceURI);
1288 var property = this._rdf.GetResource(propertyURI);
1289 var target = this._rdf.GetResource(targetURI);
1291 return this._ds.HasAssertion(source, property, target, true);
1295 * Whether or not a property of an RDF source has a given literal value.
1297 * @param sourceURI {string} the URI of the source
1298 * @param propertyURI {string} the URI of the property
1299 * @param value {string} the literal value
1301 * @returns {boolean} whether or not there is such an assertion
1303 _hasLiteralAssertion: function HS__hasLiteralAssertion(sourceURI,
1306 var source = this._rdf.GetResource(sourceURI);
1307 var property = this._rdf.GetResource(propertyURI);
1308 var target = this._rdf.GetLiteral(value);
1310 return this._ds.HasAssertion(source, property, target, true);
1314 * Whether or not there is an RDF source that has the given property set to
1315 * the given literal value.
1317 * @param propertyURI {string} the URI of the property
1318 * @param value {string} the literal value
1320 * @returns {boolean} whether or not there is a source
1322 _existsLiteralTarget: function HS__existsLiteralTarget(propertyURI, value) {
1323 var property = this._rdf.GetResource(propertyURI);
1324 var target = this._rdf.GetLiteral(value);
1326 return this._ds.hasArcIn(target, property);
1330 * Get the source for a property set to a given literal value.
1332 * @param propertyURI {string} the URI of the property
1333 * @param value {string} the literal value
1335 _getSourceForLiteral: function HS__getSourceForLiteral(propertyURI, value) {
1336 var property = this._rdf.GetResource(propertyURI);
1337 var target = this._rdf.GetLiteral(value);
1339 var source = this._ds.GetSource(property, target, true);
1341 return source.ValueUTF8;
1347 * Whether or not there is an RDF source that has the given property set to
1348 * the given resource target.
1350 * @param propertyURI {string} the URI of the property
1351 * @param targetURI {string} the URI of the target
1353 * @returns {boolean} whether or not there is a source
1355 _existsResourceTarget: function HS__existsResourceTarget(propertyURI,
1357 var property = this._rdf.GetResource(propertyURI);
1358 var target = this._rdf.GetResource(targetURI);
1360 return this._ds.hasArcIn(target, property);
1364 * Remove a property of an RDF source.
1366 * @param sourceURI {string} the URI of the source
1367 * @param propertyURI {string} the URI of the property
1369 _removeTarget: function HS__removeTarget(sourceURI, propertyURI) {
1370 var source = this._rdf.GetResource(sourceURI);
1371 var property = this._rdf.GetResource(propertyURI);
1373 if (this._ds.hasArcOut(source, property)) {
1374 var target = this._ds.GetTarget(source, property, true);
1375 this._ds.Unassert(source, property, target);
1380 * Remove all assertions about a given RDF source.
1382 * Note: not recursive. If some assertions point to other resources,
1383 * and you want to remove assertions about those resources too, you need
1384 * to do so manually.
1386 * @param sourceURI {string} the URI of the source
1388 _removeAssertions: function HS__removeAssertions(sourceURI) {
1389 var source = this._rdf.GetResource(sourceURI);
1390 var properties = this._ds.ArcLabelsOut(source);
1392 while (properties.hasMoreElements()) {
1393 let property = properties.getNext();
1394 let targets = this._ds.GetTargets(source, property, true);
1395 while (targets.hasMoreElements()) {
1396 let target = targets.getNext();
1397 this._ds.Unassert(source, property, target);
1404 //****************************************************************************//
1405 // More XPCOM Plumbing
1407 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HandlerService]);