uriloader/exthandler/nsHandlerService.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 const Ci = Components.interfaces;
michael@0 6 const Cc = Components.classes;
michael@0 7 const Cu = Components.utils;
michael@0 8 const Cr = Components.results;
michael@0 9
michael@0 10
michael@0 11 const CLASS_MIMEINFO = "mimetype";
michael@0 12 const CLASS_PROTOCOLINFO = "scheme";
michael@0 13
michael@0 14
michael@0 15 // namespace prefix
michael@0 16 const NC_NS = "http://home.netscape.com/NC-rdf#";
michael@0 17
michael@0 18 // the most recent default handlers that have been injected. Note that
michael@0 19 // this is used to construct an RDF resource, which needs to have NC_NS
michael@0 20 // prepended, since that hasn't been done yet
michael@0 21 const DEFAULT_HANDLERS_VERSION = "defaultHandlersVersion";
michael@0 22
michael@0 23 // type list properties
michael@0 24
michael@0 25 const NC_MIME_TYPES = NC_NS + "MIME-types";
michael@0 26 const NC_PROTOCOL_SCHEMES = NC_NS + "Protocol-Schemes";
michael@0 27
michael@0 28 // content type ("type") properties
michael@0 29
michael@0 30 // nsIHandlerInfo::type
michael@0 31 const NC_VALUE = NC_NS + "value";
michael@0 32 const NC_DESCRIPTION = NC_NS + "description";
michael@0 33
michael@0 34 // additional extensions
michael@0 35 const NC_FILE_EXTENSIONS = NC_NS + "fileExtensions";
michael@0 36
michael@0 37 // references nsIHandlerInfo record
michael@0 38 const NC_HANDLER_INFO = NC_NS + "handlerProp";
michael@0 39
michael@0 40 // handler info ("info") properties
michael@0 41
michael@0 42 // nsIHandlerInfo::preferredAction
michael@0 43 const NC_SAVE_TO_DISK = NC_NS + "saveToDisk";
michael@0 44 const NC_HANDLE_INTERNALLY = NC_NS + "handleInternal";
michael@0 45 const NC_USE_SYSTEM_DEFAULT = NC_NS + "useSystemDefault";
michael@0 46
michael@0 47 // nsIHandlerInfo::alwaysAskBeforeHandling
michael@0 48 const NC_ALWAYS_ASK = NC_NS + "alwaysAsk";
michael@0 49
michael@0 50 // references nsIHandlerApp records
michael@0 51 const NC_PREFERRED_APP = NC_NS + "externalApplication";
michael@0 52 const NC_POSSIBLE_APP = NC_NS + "possibleApplication";
michael@0 53
michael@0 54 // handler app ("handler") properties
michael@0 55
michael@0 56 // nsIHandlerApp::name
michael@0 57 const NC_PRETTY_NAME = NC_NS + "prettyName";
michael@0 58
michael@0 59 // nsILocalHandlerApp::executable
michael@0 60 const NC_PATH = NC_NS + "path";
michael@0 61
michael@0 62 // nsIWebHandlerApp::uriTemplate
michael@0 63 const NC_URI_TEMPLATE = NC_NS + "uriTemplate";
michael@0 64
michael@0 65 // nsIDBusHandlerApp::service
michael@0 66 const NC_SERVICE = NC_NS + "service";
michael@0 67
michael@0 68 // nsIDBusHandlerApp::method
michael@0 69 const NC_METHOD = NC_NS + "method";
michael@0 70
michael@0 71 // nsIDBusHandlerApp::objectPath
michael@0 72 const NC_OBJPATH = NC_NS + "objectPath";
michael@0 73
michael@0 74 // nsIDBusHandlerApp::dbusInterface
michael@0 75 const NC_INTERFACE = NC_NS + "dBusInterface";
michael@0 76
michael@0 77 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 78
michael@0 79
michael@0 80 function HandlerService() {
michael@0 81 this._init();
michael@0 82 }
michael@0 83
michael@0 84 const HandlerServiceFactory = {
michael@0 85 _instance: null,
michael@0 86 createInstance: function (outer, iid) {
michael@0 87 if (this._instance)
michael@0 88 return this._instance;
michael@0 89
michael@0 90 let processType = Cc["@mozilla.org/xre/runtime;1"].
michael@0 91 getService(Ci.nsIXULRuntime).processType;
michael@0 92 if (processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
michael@0 93 return Cr.NS_ERROR_NOT_IMPLEMENTED;
michael@0 94
michael@0 95 return (this._instance = new HandlerService());
michael@0 96 }
michael@0 97 };
michael@0 98
michael@0 99 HandlerService.prototype = {
michael@0 100 //**************************************************************************//
michael@0 101 // XPCOM Plumbing
michael@0 102
michael@0 103 classID: Components.ID("{32314cc8-22f7-4f7f-a645-1a45453ba6a6}"),
michael@0 104 QueryInterface: XPCOMUtils.generateQI([Ci.nsIHandlerService]),
michael@0 105 _xpcom_factory: HandlerServiceFactory,
michael@0 106
michael@0 107 //**************************************************************************//
michael@0 108 // Initialization & Destruction
michael@0 109
michael@0 110 _init: function HS__init() {
michael@0 111 // Observe profile-before-change so we can switch to the datasource
michael@0 112 // in the new profile when the user changes profiles.
michael@0 113 this._observerSvc.addObserver(this, "profile-before-change", false);
michael@0 114
michael@0 115 // Observe xpcom-shutdown so we can remove these observers
michael@0 116 // when the application shuts down.
michael@0 117 this._observerSvc.addObserver(this, "xpcom-shutdown", false);
michael@0 118
michael@0 119 // Observe profile-do-change so that non-default profiles get upgraded too
michael@0 120 this._observerSvc.addObserver(this, "profile-do-change", false);
michael@0 121
michael@0 122 // do any necessary updating of the datastore
michael@0 123 this._updateDB();
michael@0 124 },
michael@0 125
michael@0 126 _updateDB: function HS__updateDB() {
michael@0 127 try {
michael@0 128 var defaultHandlersVersion = this._datastoreDefaultHandlersVersion;
michael@0 129 } catch(ex) {
michael@0 130 // accessing the datastore failed, we can't update anything
michael@0 131 return;
michael@0 132 }
michael@0 133
michael@0 134 try {
michael@0 135 // if we don't have the current version of the default prefs for
michael@0 136 // this locale, inject any new default handers into the datastore
michael@0 137 if (defaultHandlersVersion < this._prefsDefaultHandlersVersion) {
michael@0 138
michael@0 139 // set the new version first so that if we recurse we don't
michael@0 140 // call _injectNewDefaults several times
michael@0 141 this._datastoreDefaultHandlersVersion =
michael@0 142 this._prefsDefaultHandlersVersion;
michael@0 143 this._injectNewDefaults();
michael@0 144 }
michael@0 145 } catch (ex) {
michael@0 146 // if injecting the defaults failed, set the version back to the
michael@0 147 // previous value
michael@0 148 this._datastoreDefaultHandlersVersion = defaultHandlersVersion;
michael@0 149 }
michael@0 150 },
michael@0 151
michael@0 152 get _currentLocale() {
michael@0 153 var chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].
michael@0 154 getService(Ci.nsIXULChromeRegistry);
michael@0 155 var currentLocale = chromeRegistry.getSelectedLocale("global");
michael@0 156 return currentLocale;
michael@0 157 },
michael@0 158
michael@0 159 _destroy: function HS__destroy() {
michael@0 160 this._observerSvc.removeObserver(this, "profile-before-change");
michael@0 161 this._observerSvc.removeObserver(this, "xpcom-shutdown");
michael@0 162 this._observerSvc.removeObserver(this, "profile-do-change");
michael@0 163
michael@0 164 // XXX Should we also null references to all the services that get stored
michael@0 165 // by our memoizing getters in the Convenience Getters section?
michael@0 166 },
michael@0 167
michael@0 168 _onProfileChange: function HS__onProfileChange() {
michael@0 169 // Lose our reference to the datasource so we reacquire it
michael@0 170 // from the new profile the next time we need it.
michael@0 171 this.__ds = null;
michael@0 172 },
michael@0 173
michael@0 174 _isInHandlerArray: function HS__isInHandlerArray(aArray, aHandler) {
michael@0 175 var enumerator = aArray.enumerate();
michael@0 176 while (enumerator.hasMoreElements()) {
michael@0 177 let handler = enumerator.getNext();
michael@0 178 handler.QueryInterface(Ci.nsIHandlerApp);
michael@0 179 if (handler.equals(aHandler))
michael@0 180 return true;
michael@0 181 }
michael@0 182
michael@0 183 return false;
michael@0 184 },
michael@0 185
michael@0 186 // note that this applies to the current locale only
michael@0 187 get _datastoreDefaultHandlersVersion() {
michael@0 188 var version = this._getValue("urn:root", NC_NS + this._currentLocale +
michael@0 189 "_" + DEFAULT_HANDLERS_VERSION);
michael@0 190
michael@0 191 return version ? version : -1;
michael@0 192 },
michael@0 193
michael@0 194 set _datastoreDefaultHandlersVersion(aNewVersion) {
michael@0 195 return this._setLiteral("urn:root", NC_NS + this._currentLocale + "_" +
michael@0 196 DEFAULT_HANDLERS_VERSION, aNewVersion);
michael@0 197 },
michael@0 198
michael@0 199 get _prefsDefaultHandlersVersion() {
michael@0 200 // get handler service pref branch
michael@0 201 var prefSvc = Cc["@mozilla.org/preferences-service;1"].
michael@0 202 getService(Ci.nsIPrefService);
michael@0 203 var handlerSvcBranch = prefSvc.getBranch("gecko.handlerService.");
michael@0 204
michael@0 205 // get the version of the preferences for this locale
michael@0 206 return Number(handlerSvcBranch.
michael@0 207 getComplexValue("defaultHandlersVersion",
michael@0 208 Ci.nsIPrefLocalizedString).data);
michael@0 209 },
michael@0 210
michael@0 211 _injectNewDefaults: function HS__injectNewDefaults() {
michael@0 212 // get handler service pref branch
michael@0 213 var prefSvc = Cc["@mozilla.org/preferences-service;1"].
michael@0 214 getService(Ci.nsIPrefService);
michael@0 215
michael@0 216 let schemesPrefBranch = prefSvc.getBranch("gecko.handlerService.schemes.");
michael@0 217 let schemePrefList = schemesPrefBranch.getChildList("");
michael@0 218
michael@0 219 var schemes = {};
michael@0 220
michael@0 221 // read all the scheme prefs into a hash
michael@0 222 for each (var schemePrefName in schemePrefList) {
michael@0 223
michael@0 224 let [scheme, handlerNumber, attribute] = schemePrefName.split(".");
michael@0 225
michael@0 226 try {
michael@0 227 var attrData =
michael@0 228 schemesPrefBranch.getComplexValue(schemePrefName,
michael@0 229 Ci.nsIPrefLocalizedString).data;
michael@0 230 if (!(scheme in schemes))
michael@0 231 schemes[scheme] = {};
michael@0 232
michael@0 233 if (!(handlerNumber in schemes[scheme]))
michael@0 234 schemes[scheme][handlerNumber] = {};
michael@0 235
michael@0 236 schemes[scheme][handlerNumber][attribute] = attrData;
michael@0 237 } catch (ex) {}
michael@0 238 }
michael@0 239
michael@0 240 let protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
michael@0 241 getService(Ci.nsIExternalProtocolService);
michael@0 242 for (var scheme in schemes) {
michael@0 243
michael@0 244 // This clause is essentially a reimplementation of
michael@0 245 // nsIExternalProtocolHandlerService.getProtocolHandlerInfo().
michael@0 246 // Necessary because calling that from here would make XPConnect barf
michael@0 247 // when getService tried to re-enter the constructor for this
michael@0 248 // service.
michael@0 249 let osDefaultHandlerFound = {};
michael@0 250 let protoInfo = protoSvc.getProtocolHandlerInfoFromOS(scheme,
michael@0 251 osDefaultHandlerFound);
michael@0 252
michael@0 253 if (this.exists(protoInfo))
michael@0 254 this.fillHandlerInfo(protoInfo, null);
michael@0 255 else
michael@0 256 protoSvc.setProtocolHandlerDefaults(protoInfo,
michael@0 257 osDefaultHandlerFound.value);
michael@0 258
michael@0 259 // cache the possible handlers to avoid extra xpconnect traversals.
michael@0 260 let possibleHandlers = protoInfo.possibleApplicationHandlers;
michael@0 261
michael@0 262 for each (var handlerPrefs in schemes[scheme]) {
michael@0 263
michael@0 264 let handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"].
michael@0 265 createInstance(Ci.nsIWebHandlerApp);
michael@0 266
michael@0 267 handlerApp.uriTemplate = handlerPrefs.uriTemplate;
michael@0 268 handlerApp.name = handlerPrefs.name;
michael@0 269
michael@0 270 if (!this._isInHandlerArray(possibleHandlers, handlerApp)) {
michael@0 271 possibleHandlers.appendElement(handlerApp, false);
michael@0 272 }
michael@0 273 }
michael@0 274
michael@0 275 this.store(protoInfo);
michael@0 276 }
michael@0 277 },
michael@0 278
michael@0 279 //**************************************************************************//
michael@0 280 // nsIObserver
michael@0 281
michael@0 282 observe: function HS__observe(subject, topic, data) {
michael@0 283 switch(topic) {
michael@0 284 case "profile-before-change":
michael@0 285 this._onProfileChange();
michael@0 286 break;
michael@0 287 case "xpcom-shutdown":
michael@0 288 this._destroy();
michael@0 289 break;
michael@0 290 case "profile-do-change":
michael@0 291 this._updateDB();
michael@0 292 break;
michael@0 293 }
michael@0 294 },
michael@0 295
michael@0 296
michael@0 297 //**************************************************************************//
michael@0 298 // nsIHandlerService
michael@0 299
michael@0 300 enumerate: function HS_enumerate() {
michael@0 301 var handlers = Cc["@mozilla.org/array;1"].
michael@0 302 createInstance(Ci.nsIMutableArray);
michael@0 303 this._appendHandlers(handlers, CLASS_MIMEINFO);
michael@0 304 this._appendHandlers(handlers, CLASS_PROTOCOLINFO);
michael@0 305 return handlers.enumerate();
michael@0 306 },
michael@0 307
michael@0 308 fillHandlerInfo: function HS_fillHandlerInfo(aHandlerInfo, aOverrideType) {
michael@0 309 var type = aOverrideType || aHandlerInfo.type;
michael@0 310 var typeID = this._getTypeID(this._getClass(aHandlerInfo), type);
michael@0 311
michael@0 312 // Determine whether or not information about this handler is available
michael@0 313 // in the datastore by looking for its "value" property, which stores its
michael@0 314 // type and should always be present.
michael@0 315 if (!this._hasValue(typeID, NC_VALUE))
michael@0 316 throw Cr.NS_ERROR_NOT_AVAILABLE;
michael@0 317
michael@0 318 // Retrieve the human-readable description of the type.
michael@0 319 if (this._hasValue(typeID, NC_DESCRIPTION))
michael@0 320 aHandlerInfo.description = this._getValue(typeID, NC_DESCRIPTION);
michael@0 321
michael@0 322 // Note: for historical reasons, we don't actually check that the type
michael@0 323 // record has a "handlerProp" property referencing the info record. It's
michael@0 324 // unclear whether or not we should start doing this check; perhaps some
michael@0 325 // legacy datasources don't have such references.
michael@0 326 var infoID = this._getInfoID(this._getClass(aHandlerInfo), type);
michael@0 327
michael@0 328 aHandlerInfo.preferredAction = this._retrievePreferredAction(infoID);
michael@0 329
michael@0 330 var preferredHandlerID =
michael@0 331 this._getPreferredHandlerID(this._getClass(aHandlerInfo), type);
michael@0 332
michael@0 333 // Retrieve the preferred handler.
michael@0 334 // Note: for historical reasons, we don't actually check that the info
michael@0 335 // record has an "externalApplication" property referencing the preferred
michael@0 336 // handler record. It's unclear whether or not we should start doing
michael@0 337 // this check; perhaps some legacy datasources don't have such references.
michael@0 338 aHandlerInfo.preferredApplicationHandler =
michael@0 339 this._retrieveHandlerApp(preferredHandlerID);
michael@0 340
michael@0 341 // Fill the array of possible handlers with the ones in the datastore.
michael@0 342 this._fillPossibleHandlers(infoID,
michael@0 343 aHandlerInfo.possibleApplicationHandlers,
michael@0 344 aHandlerInfo.preferredApplicationHandler);
michael@0 345
michael@0 346 // If we have an "always ask" flag stored in the RDF, always use its
michael@0 347 // value. Otherwise, use the default value stored in the pref service.
michael@0 348 var alwaysAsk;
michael@0 349 if (this._hasValue(infoID, NC_ALWAYS_ASK)) {
michael@0 350 alwaysAsk = (this._getValue(infoID, NC_ALWAYS_ASK) != "false");
michael@0 351 } else {
michael@0 352 var prefSvc = Cc["@mozilla.org/preferences-service;1"].
michael@0 353 getService(Ci.nsIPrefService);
michael@0 354 var prefBranch = prefSvc.getBranch("network.protocol-handler.");
michael@0 355 try {
michael@0 356 alwaysAsk = prefBranch.getBoolPref("warn-external." + type);
michael@0 357 } catch (e) {
michael@0 358 // will throw if pref didn't exist.
michael@0 359 try {
michael@0 360 alwaysAsk = prefBranch.getBoolPref("warn-external-default");
michael@0 361 } catch (e) {
michael@0 362 // Nothing to tell us what to do, so be paranoid and prompt.
michael@0 363 alwaysAsk = true;
michael@0 364 }
michael@0 365 }
michael@0 366 }
michael@0 367 aHandlerInfo.alwaysAskBeforeHandling = alwaysAsk;
michael@0 368
michael@0 369 // If the object represents a MIME type handler, then also retrieve
michael@0 370 // any file extensions.
michael@0 371 if (aHandlerInfo instanceof Ci.nsIMIMEInfo)
michael@0 372 for each (let fileExtension in this._retrieveFileExtensions(typeID))
michael@0 373 aHandlerInfo.appendExtension(fileExtension);
michael@0 374 },
michael@0 375
michael@0 376 store: function HS_store(aHandlerInfo) {
michael@0 377 // FIXME: when we switch from RDF to something with transactions (like
michael@0 378 // SQLite), enclose the following changes in a transaction so they all
michael@0 379 // get rolled back if any of them fail and we don't leave the datastore
michael@0 380 // in an inconsistent state.
michael@0 381
michael@0 382 this._ensureRecordsForType(aHandlerInfo);
michael@0 383 this._storePreferredAction(aHandlerInfo);
michael@0 384 this._storePreferredHandler(aHandlerInfo);
michael@0 385 this._storePossibleHandlers(aHandlerInfo);
michael@0 386 this._storeAlwaysAsk(aHandlerInfo);
michael@0 387
michael@0 388 // Write the changes to the database immediately so we don't lose them
michael@0 389 // if the application crashes.
michael@0 390 if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
michael@0 391 this._ds.Flush();
michael@0 392 },
michael@0 393
michael@0 394 exists: function HS_exists(aHandlerInfo) {
michael@0 395 var found;
michael@0 396
michael@0 397 try {
michael@0 398 var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 399 found = this._hasLiteralAssertion(typeID, NC_VALUE, aHandlerInfo.type);
michael@0 400 } catch (e) {
michael@0 401 // If the RDF threw (eg, corrupt file), treat as nonexistent.
michael@0 402 found = false;
michael@0 403 }
michael@0 404
michael@0 405 return found;
michael@0 406 },
michael@0 407
michael@0 408 remove: function HS_remove(aHandlerInfo) {
michael@0 409 var preferredHandlerID =
michael@0 410 this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 411 this._removeAssertions(preferredHandlerID);
michael@0 412
michael@0 413 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 414
michael@0 415 // Get a list of possible handlers. After we have removed the info record,
michael@0 416 // we'll check if any other info records reference these handlers, and we'll
michael@0 417 // remove the handler records that aren't referenced by other info records.
michael@0 418 var possibleHandlerIDs = [];
michael@0 419 var possibleHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP);
michael@0 420 while (possibleHandlerTargets.hasMoreElements()) {
michael@0 421 let possibleHandlerTarget = possibleHandlerTargets.getNext();
michael@0 422 // Note: possibleHandlerTarget should always be an nsIRDFResource.
michael@0 423 // The conditional is just here in case of a corrupt RDF datasource.
michael@0 424 if (possibleHandlerTarget instanceof Ci.nsIRDFResource)
michael@0 425 possibleHandlerIDs.push(possibleHandlerTarget.ValueUTF8);
michael@0 426 }
michael@0 427
michael@0 428 // Remove the info record.
michael@0 429 this._removeAssertions(infoID);
michael@0 430
michael@0 431 // Now that we've removed the info record, remove any possible handlers
michael@0 432 // that aren't referenced by other info records.
michael@0 433 for each (let possibleHandlerID in possibleHandlerIDs)
michael@0 434 if (!this._existsResourceTarget(NC_POSSIBLE_APP, possibleHandlerID))
michael@0 435 this._removeAssertions(possibleHandlerID);
michael@0 436
michael@0 437 var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 438 this._removeAssertions(typeID);
michael@0 439
michael@0 440 // Now that there's no longer a handler for this type, remove the type
michael@0 441 // from the list of types for which there are known handlers.
michael@0 442 var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo));
michael@0 443 var type = this._rdf.GetResource(typeID);
michael@0 444 var typeIndex = typeList.IndexOf(type);
michael@0 445 if (typeIndex != -1)
michael@0 446 typeList.RemoveElementAt(typeIndex, true);
michael@0 447
michael@0 448 // Write the changes to the database immediately so we don't lose them
michael@0 449 // if the application crashes.
michael@0 450 // XXX If we're removing a bunch of handlers at once, will flushing
michael@0 451 // after every removal cause a significant performance hit?
michael@0 452 if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
michael@0 453 this._ds.Flush();
michael@0 454 },
michael@0 455
michael@0 456 getTypeFromExtension: function HS_getTypeFromExtension(aFileExtension) {
michael@0 457 var fileExtension = aFileExtension.toLowerCase();
michael@0 458 var typeID;
michael@0 459
michael@0 460 if (this._existsLiteralTarget(NC_FILE_EXTENSIONS, fileExtension))
michael@0 461 typeID = this._getSourceForLiteral(NC_FILE_EXTENSIONS, fileExtension);
michael@0 462
michael@0 463 if (typeID && this._hasValue(typeID, NC_VALUE)) {
michael@0 464 let type = this._getValue(typeID, NC_VALUE);
michael@0 465 if (type == "")
michael@0 466 throw Cr.NS_ERROR_FAILURE;
michael@0 467 return type;
michael@0 468 }
michael@0 469
michael@0 470 return "";
michael@0 471 },
michael@0 472
michael@0 473
michael@0 474 //**************************************************************************//
michael@0 475 // Retrieval Methods
michael@0 476
michael@0 477 /**
michael@0 478 * Retrieve the preferred action for the info record with the given ID.
michael@0 479 *
michael@0 480 * @param aInfoID {string} the info record ID
michael@0 481 *
michael@0 482 * @returns {integer} the preferred action enumeration value
michael@0 483 */
michael@0 484 _retrievePreferredAction: function HS__retrievePreferredAction(aInfoID) {
michael@0 485 if (this._getValue(aInfoID, NC_SAVE_TO_DISK) == "true")
michael@0 486 return Ci.nsIHandlerInfo.saveToDisk;
michael@0 487
michael@0 488 if (this._getValue(aInfoID, NC_USE_SYSTEM_DEFAULT) == "true")
michael@0 489 return Ci.nsIHandlerInfo.useSystemDefault;
michael@0 490
michael@0 491 if (this._getValue(aInfoID, NC_HANDLE_INTERNALLY) == "true")
michael@0 492 return Ci.nsIHandlerInfo.handleInternally;
michael@0 493
michael@0 494 return Ci.nsIHandlerInfo.useHelperApp;
michael@0 495 },
michael@0 496
michael@0 497 /**
michael@0 498 * Fill an array of possible handlers with the handlers for the given info ID.
michael@0 499 *
michael@0 500 * @param aInfoID {string} the ID of the info record
michael@0 501 * @param aPossibleHandlers {nsIMutableArray} the array of possible handlers
michael@0 502 * @param aPreferredHandler {nsIHandlerApp} the preferred handler, if any
michael@0 503 */
michael@0 504 _fillPossibleHandlers: function HS__fillPossibleHandlers(aInfoID,
michael@0 505 aPossibleHandlers,
michael@0 506 aPreferredHandler) {
michael@0 507 // The set of possible handlers should include the preferred handler,
michael@0 508 // but legacy datastores (from before we added possible handlers) won't
michael@0 509 // include the preferred handler, so check if it's included as we build
michael@0 510 // the list of handlers, and, if it's not included, add it to the list.
michael@0 511 if (aPreferredHandler)
michael@0 512 aPossibleHandlers.appendElement(aPreferredHandler, false);
michael@0 513
michael@0 514 var possibleHandlerTargets = this._getTargets(aInfoID, NC_POSSIBLE_APP);
michael@0 515
michael@0 516 while (possibleHandlerTargets.hasMoreElements()) {
michael@0 517 let possibleHandlerTarget = possibleHandlerTargets.getNext();
michael@0 518 if (!(possibleHandlerTarget instanceof Ci.nsIRDFResource))
michael@0 519 continue;
michael@0 520
michael@0 521 let possibleHandlerID = possibleHandlerTarget.ValueUTF8;
michael@0 522 let possibleHandler = this._retrieveHandlerApp(possibleHandlerID);
michael@0 523 if (possibleHandler && (!aPreferredHandler ||
michael@0 524 !possibleHandler.equals(aPreferredHandler)))
michael@0 525 aPossibleHandlers.appendElement(possibleHandler, false);
michael@0 526 }
michael@0 527 },
michael@0 528
michael@0 529 /**
michael@0 530 * Retrieve the handler app object with the given ID.
michael@0 531 *
michael@0 532 * @param aHandlerAppID {string} the ID of the handler app to retrieve
michael@0 533 *
michael@0 534 * @returns {nsIHandlerApp} the handler app, if any; otherwise null
michael@0 535 */
michael@0 536 _retrieveHandlerApp: function HS__retrieveHandlerApp(aHandlerAppID) {
michael@0 537 var handlerApp;
michael@0 538
michael@0 539 // If it has a path, it's a local handler; otherwise, it's a web handler.
michael@0 540 if (this._hasValue(aHandlerAppID, NC_PATH)) {
michael@0 541 let executable =
michael@0 542 this._getFileWithPath(this._getValue(aHandlerAppID, NC_PATH));
michael@0 543 if (!executable)
michael@0 544 return null;
michael@0 545
michael@0 546 handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
michael@0 547 createInstance(Ci.nsILocalHandlerApp);
michael@0 548 handlerApp.executable = executable;
michael@0 549 }
michael@0 550 else if (this._hasValue(aHandlerAppID, NC_URI_TEMPLATE)) {
michael@0 551 let uriTemplate = this._getValue(aHandlerAppID, NC_URI_TEMPLATE);
michael@0 552 if (!uriTemplate)
michael@0 553 return null;
michael@0 554
michael@0 555 handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"].
michael@0 556 createInstance(Ci.nsIWebHandlerApp);
michael@0 557 handlerApp.uriTemplate = uriTemplate;
michael@0 558 }
michael@0 559 else if (this._hasValue(aHandlerAppID, NC_SERVICE)) {
michael@0 560 let service = this._getValue(aHandlerAppID, NC_SERVICE);
michael@0 561 if (!service)
michael@0 562 return null;
michael@0 563
michael@0 564 let method = this._getValue(aHandlerAppID, NC_METHOD);
michael@0 565 if (!method)
michael@0 566 return null;
michael@0 567
michael@0 568 let objpath = this._getValue(aHandlerAppID, NC_OBJPATH);
michael@0 569 if (!objpath)
michael@0 570 return null;
michael@0 571
michael@0 572 let interface = this._getValue(aHandlerAppID, NC_INTERFACE);
michael@0 573 if (!interface)
michael@0 574 return null;
michael@0 575
michael@0 576 handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"].
michael@0 577 createInstance(Ci.nsIDBusHandlerApp);
michael@0 578 handlerApp.service = service;
michael@0 579 handlerApp.method = method;
michael@0 580 handlerApp.objectPath = objpath;
michael@0 581 handlerApp.dBusInterface = interface;
michael@0 582
michael@0 583 }
michael@0 584 else
michael@0 585 return null;
michael@0 586
michael@0 587 handlerApp.name = this._getValue(aHandlerAppID, NC_PRETTY_NAME);
michael@0 588
michael@0 589 return handlerApp;
michael@0 590 },
michael@0 591
michael@0 592 /*
michael@0 593 * Retrieve file extensions, if any, for the MIME type with the given type ID.
michael@0 594 *
michael@0 595 * @param aTypeID {string} the type record ID
michael@0 596 */
michael@0 597 _retrieveFileExtensions: function HS__retrieveFileExtensions(aTypeID) {
michael@0 598 var fileExtensions = [];
michael@0 599
michael@0 600 var fileExtensionTargets = this._getTargets(aTypeID, NC_FILE_EXTENSIONS);
michael@0 601
michael@0 602 while (fileExtensionTargets.hasMoreElements()) {
michael@0 603 let fileExtensionTarget = fileExtensionTargets.getNext();
michael@0 604 if (fileExtensionTarget instanceof Ci.nsIRDFLiteral &&
michael@0 605 fileExtensionTarget.Value != "")
michael@0 606 fileExtensions.push(fileExtensionTarget.Value);
michael@0 607 }
michael@0 608
michael@0 609 return fileExtensions;
michael@0 610 },
michael@0 611
michael@0 612 /**
michael@0 613 * Get the file with the given path. This is not as simple as merely
michael@0 614 * initializing a local file object with the path, because the path might be
michael@0 615 * relative to the current process directory, in which case we have to
michael@0 616 * construct a path starting from that directory.
michael@0 617 *
michael@0 618 * @param aPath {string} a path to a file
michael@0 619 *
michael@0 620 * @returns {nsILocalFile} the file, or null if the file does not exist
michael@0 621 */
michael@0 622 _getFileWithPath: function HS__getFileWithPath(aPath) {
michael@0 623 var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
michael@0 624
michael@0 625 try {
michael@0 626 file.initWithPath(aPath);
michael@0 627
michael@0 628 if (file.exists())
michael@0 629 return file;
michael@0 630 }
michael@0 631 catch(ex) {
michael@0 632 // Note: for historical reasons, we don't actually check to see
michael@0 633 // if the exception is NS_ERROR_FILE_UNRECOGNIZED_PATH, which is what
michael@0 634 // nsILocalFile::initWithPath throws when a path is relative.
michael@0 635
michael@0 636 file = this._dirSvc.get("XCurProcD", Ci.nsIFile);
michael@0 637
michael@0 638 try {
michael@0 639 file.append(aPath);
michael@0 640 if (file.exists())
michael@0 641 return file;
michael@0 642 }
michael@0 643 catch(ex) {}
michael@0 644 }
michael@0 645
michael@0 646 return null;
michael@0 647 },
michael@0 648
michael@0 649
michael@0 650 //**************************************************************************//
michael@0 651 // Storage Methods
michael@0 652
michael@0 653 _storePreferredAction: function HS__storePreferredAction(aHandlerInfo) {
michael@0 654 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 655
michael@0 656 switch(aHandlerInfo.preferredAction) {
michael@0 657 case Ci.nsIHandlerInfo.saveToDisk:
michael@0 658 this._setLiteral(infoID, NC_SAVE_TO_DISK, "true");
michael@0 659 this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
michael@0 660 this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
michael@0 661 break;
michael@0 662
michael@0 663 case Ci.nsIHandlerInfo.handleInternally:
michael@0 664 this._setLiteral(infoID, NC_HANDLE_INTERNALLY, "true");
michael@0 665 this._removeTarget(infoID, NC_SAVE_TO_DISK);
michael@0 666 this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
michael@0 667 break;
michael@0 668
michael@0 669 case Ci.nsIHandlerInfo.useSystemDefault:
michael@0 670 this._setLiteral(infoID, NC_USE_SYSTEM_DEFAULT, "true");
michael@0 671 this._removeTarget(infoID, NC_SAVE_TO_DISK);
michael@0 672 this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
michael@0 673 break;
michael@0 674
michael@0 675 // This value is indicated in the datastore either by the absence of
michael@0 676 // the three properties or by setting them all "false". Of these two
michael@0 677 // options, the former seems preferable, because it reduces the size
michael@0 678 // of the RDF file and thus the amount of stuff we have to parse.
michael@0 679 case Ci.nsIHandlerInfo.useHelperApp:
michael@0 680 default:
michael@0 681 this._removeTarget(infoID, NC_SAVE_TO_DISK);
michael@0 682 this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
michael@0 683 this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
michael@0 684 break;
michael@0 685 }
michael@0 686 },
michael@0 687
michael@0 688 _storePreferredHandler: function HS__storePreferredHandler(aHandlerInfo) {
michael@0 689 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 690 var handlerID =
michael@0 691 this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 692
michael@0 693 var handler = aHandlerInfo.preferredApplicationHandler;
michael@0 694
michael@0 695 if (handler) {
michael@0 696 this._storeHandlerApp(handlerID, handler);
michael@0 697
michael@0 698 // Make this app be the preferred app for the handler info.
michael@0 699 //
michael@0 700 // Note: nsExternalHelperAppService::FillContentHandlerProperties ignores
michael@0 701 // this setting and instead identifies the preferred app as the resource
michael@0 702 // whose URI follows the pattern urn:<class>:externalApplication:<type>.
michael@0 703 // But the old downloadactions.js code used to set this property, so just
michael@0 704 // in case there is still some code somewhere that relies on its presence,
michael@0 705 // we set it here.
michael@0 706 this._setResource(infoID, NC_PREFERRED_APP, handlerID);
michael@0 707 }
michael@0 708 else {
michael@0 709 // There isn't a preferred handler. Remove the existing record for it,
michael@0 710 // if any.
michael@0 711 this._removeTarget(infoID, NC_PREFERRED_APP);
michael@0 712 this._removeAssertions(handlerID);
michael@0 713 }
michael@0 714 },
michael@0 715
michael@0 716 /**
michael@0 717 * Store the list of possible handler apps for the content type represented
michael@0 718 * by the given handler info object.
michael@0 719 *
michael@0 720 * @param aHandlerInfo {nsIHandlerInfo} the handler info object
michael@0 721 */
michael@0 722 _storePossibleHandlers: function HS__storePossibleHandlers(aHandlerInfo) {
michael@0 723 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 724
michael@0 725 // First, retrieve the set of handler apps currently stored for the type,
michael@0 726 // keeping track of their IDs in a hash that we'll use to determine which
michael@0 727 // ones are no longer valid and should be removed.
michael@0 728 var currentHandlerApps = {};
michael@0 729 var currentHandlerTargets = this._getTargets(infoID, NC_POSSIBLE_APP);
michael@0 730 while (currentHandlerTargets.hasMoreElements()) {
michael@0 731 let handlerApp = currentHandlerTargets.getNext();
michael@0 732 if (handlerApp instanceof Ci.nsIRDFResource) {
michael@0 733 let handlerAppID = handlerApp.ValueUTF8;
michael@0 734 currentHandlerApps[handlerAppID] = true;
michael@0 735 }
michael@0 736 }
michael@0 737
michael@0 738 // Next, store any new handler apps.
michael@0 739 var newHandlerApps =
michael@0 740 aHandlerInfo.possibleApplicationHandlers.enumerate();
michael@0 741 while (newHandlerApps.hasMoreElements()) {
michael@0 742 let handlerApp =
michael@0 743 newHandlerApps.getNext().QueryInterface(Ci.nsIHandlerApp);
michael@0 744 let handlerAppID = this._getPossibleHandlerAppID(handlerApp);
michael@0 745 if (!this._hasResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID)) {
michael@0 746 this._storeHandlerApp(handlerAppID, handlerApp);
michael@0 747 this._addResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID);
michael@0 748 }
michael@0 749 delete currentHandlerApps[handlerAppID];
michael@0 750 }
michael@0 751
michael@0 752 // Finally, remove any old handler apps that aren't being used anymore,
michael@0 753 // and if those handler apps aren't being used by any other type either,
michael@0 754 // then completely remove their record from the datastore so we don't
michael@0 755 // leave it clogged up with information about handler apps we don't care
michael@0 756 // about anymore.
michael@0 757 for (let handlerAppID in currentHandlerApps) {
michael@0 758 this._removeResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID);
michael@0 759 if (!this._existsResourceTarget(NC_POSSIBLE_APP, handlerAppID))
michael@0 760 this._removeAssertions(handlerAppID);
michael@0 761 }
michael@0 762 },
michael@0 763
michael@0 764 /**
michael@0 765 * Store the given handler app.
michael@0 766 *
michael@0 767 * Note: the reason this method takes the ID of the handler app in a param
michael@0 768 * is that the ID is different than it usually is when the handler app
michael@0 769 * in question is a preferred handler app, so this method can't just derive
michael@0 770 * the ID of the handler app by calling _getPossibleHandlerAppID, its callers
michael@0 771 * have to do that for it.
michael@0 772 *
michael@0 773 * @param aHandlerAppID {string} the ID of the handler app to store
michael@0 774 * @param aHandlerApp {nsIHandlerApp} the handler app to store
michael@0 775 */
michael@0 776 _storeHandlerApp: function HS__storeHandlerApp(aHandlerAppID, aHandlerApp) {
michael@0 777 aHandlerApp.QueryInterface(Ci.nsIHandlerApp);
michael@0 778 this._setLiteral(aHandlerAppID, NC_PRETTY_NAME, aHandlerApp.name);
michael@0 779
michael@0 780 // In the case of the preferred handler, the handler ID could have been
michael@0 781 // used to refer to a different kind of handler in the past (i.e. either
michael@0 782 // a local hander or a web handler), so if the new handler is a local
michael@0 783 // handler, then we remove any web handler properties and vice versa.
michael@0 784 // This is unnecessary but harmless for possible handlers.
michael@0 785
michael@0 786 if (aHandlerApp instanceof Ci.nsILocalHandlerApp) {
michael@0 787 this._setLiteral(aHandlerAppID, NC_PATH, aHandlerApp.executable.path);
michael@0 788 this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE);
michael@0 789 this._removeTarget(aHandlerAppID, NC_METHOD);
michael@0 790 this._removeTarget(aHandlerAppID, NC_SERVICE);
michael@0 791 this._removeTarget(aHandlerAppID, NC_OBJPATH);
michael@0 792 this._removeTarget(aHandlerAppID, NC_INTERFACE);
michael@0 793 }
michael@0 794 else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){
michael@0 795 aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp);
michael@0 796 this._setLiteral(aHandlerAppID, NC_URI_TEMPLATE, aHandlerApp.uriTemplate);
michael@0 797 this._removeTarget(aHandlerAppID, NC_PATH);
michael@0 798 this._removeTarget(aHandlerAppID, NC_METHOD);
michael@0 799 this._removeTarget(aHandlerAppID, NC_SERVICE);
michael@0 800 this._removeTarget(aHandlerAppID, NC_OBJPATH);
michael@0 801 this._removeTarget(aHandlerAppID, NC_INTERFACE);
michael@0 802 }
michael@0 803 else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){
michael@0 804 aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp);
michael@0 805 this._setLiteral(aHandlerAppID, NC_SERVICE, aHandlerApp.service);
michael@0 806 this._setLiteral(aHandlerAppID, NC_METHOD, aHandlerApp.method);
michael@0 807 this._setLiteral(aHandlerAppID, NC_OBJPATH, aHandlerApp.objectPath);
michael@0 808 this._setLiteral(aHandlerAppID, NC_INTERFACE, aHandlerApp.dBusInterface);
michael@0 809 this._removeTarget(aHandlerAppID, NC_PATH);
michael@0 810 this._removeTarget(aHandlerAppID, NC_URI_TEMPLATE);
michael@0 811 }
michael@0 812 else {
michael@0 813 throw "unknown handler type";
michael@0 814 }
michael@0 815
michael@0 816 },
michael@0 817
michael@0 818 _storeAlwaysAsk: function HS__storeAlwaysAsk(aHandlerInfo) {
michael@0 819 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 820 this._setLiteral(infoID,
michael@0 821 NC_ALWAYS_ASK,
michael@0 822 aHandlerInfo.alwaysAskBeforeHandling ? "true" : "false");
michael@0 823 },
michael@0 824
michael@0 825
michael@0 826 //**************************************************************************//
michael@0 827 // Convenience Getters
michael@0 828
michael@0 829 // Observer Service
michael@0 830 __observerSvc: null,
michael@0 831 get _observerSvc() {
michael@0 832 if (!this.__observerSvc)
michael@0 833 this.__observerSvc =
michael@0 834 Cc["@mozilla.org/observer-service;1"].
michael@0 835 getService(Ci.nsIObserverService);
michael@0 836 return this.__observerSvc;
michael@0 837 },
michael@0 838
michael@0 839 // Directory Service
michael@0 840 __dirSvc: null,
michael@0 841 get _dirSvc() {
michael@0 842 if (!this.__dirSvc)
michael@0 843 this.__dirSvc =
michael@0 844 Cc["@mozilla.org/file/directory_service;1"].
michael@0 845 getService(Ci.nsIProperties);
michael@0 846 return this.__dirSvc;
michael@0 847 },
michael@0 848
michael@0 849 // MIME Service
michael@0 850 __mimeSvc: null,
michael@0 851 get _mimeSvc() {
michael@0 852 if (!this.__mimeSvc)
michael@0 853 this.__mimeSvc =
michael@0 854 Cc["@mozilla.org/mime;1"].
michael@0 855 getService(Ci.nsIMIMEService);
michael@0 856 return this.__mimeSvc;
michael@0 857 },
michael@0 858
michael@0 859 // Protocol Service
michael@0 860 __protocolSvc: null,
michael@0 861 get _protocolSvc() {
michael@0 862 if (!this.__protocolSvc)
michael@0 863 this.__protocolSvc =
michael@0 864 Cc["@mozilla.org/uriloader/external-protocol-service;1"].
michael@0 865 getService(Ci.nsIExternalProtocolService);
michael@0 866 return this.__protocolSvc;
michael@0 867 },
michael@0 868
michael@0 869 // RDF Service
michael@0 870 __rdf: null,
michael@0 871 get _rdf() {
michael@0 872 if (!this.__rdf)
michael@0 873 this.__rdf = Cc["@mozilla.org/rdf/rdf-service;1"].
michael@0 874 getService(Ci.nsIRDFService);
michael@0 875 return this.__rdf;
michael@0 876 },
michael@0 877
michael@0 878 // RDF Container Utils
michael@0 879 __containerUtils: null,
michael@0 880 get _containerUtils() {
michael@0 881 if (!this.__containerUtils)
michael@0 882 this.__containerUtils = Cc["@mozilla.org/rdf/container-utils;1"].
michael@0 883 getService(Ci.nsIRDFContainerUtils);
michael@0 884 return this.__containerUtils;
michael@0 885 },
michael@0 886
michael@0 887 // RDF datasource containing content handling config (i.e. mimeTypes.rdf)
michael@0 888 __ds: null,
michael@0 889 get _ds() {
michael@0 890 if (!this.__ds) {
michael@0 891 var file = this._dirSvc.get("UMimTyp", Ci.nsIFile);
michael@0 892 // FIXME: make this a memoizing getter if we use it anywhere else.
michael@0 893 var ioService = Cc["@mozilla.org/network/io-service;1"].
michael@0 894 getService(Ci.nsIIOService);
michael@0 895 var fileHandler = ioService.getProtocolHandler("file").
michael@0 896 QueryInterface(Ci.nsIFileProtocolHandler);
michael@0 897 this.__ds =
michael@0 898 this._rdf.GetDataSourceBlocking(fileHandler.getURLSpecFromFile(file));
michael@0 899 }
michael@0 900
michael@0 901 return this.__ds;
michael@0 902 },
michael@0 903
michael@0 904
michael@0 905 //**************************************************************************//
michael@0 906 // Datastore Utils
michael@0 907
michael@0 908 /**
michael@0 909 * Get the string identifying whether this is a MIME or a protocol handler.
michael@0 910 * This string is used in the URI IDs of various RDF properties.
michael@0 911 *
michael@0 912 * @param aHandlerInfo {nsIHandlerInfo} the handler for which to get the class
michael@0 913 *
michael@0 914 * @returns {string} the class
michael@0 915 */
michael@0 916 _getClass: function HS__getClass(aHandlerInfo) {
michael@0 917 if (aHandlerInfo instanceof Ci.nsIMIMEInfo)
michael@0 918 return CLASS_MIMEINFO;
michael@0 919 else
michael@0 920 return CLASS_PROTOCOLINFO;
michael@0 921 },
michael@0 922
michael@0 923 /**
michael@0 924 * Return the unique identifier for a content type record, which stores
michael@0 925 * the value field plus a reference to the content type's handler info record.
michael@0 926 *
michael@0 927 * |urn:<class>:<type>|
michael@0 928 *
michael@0 929 * XXX: should this be a property of nsIHandlerInfo?
michael@0 930 *
michael@0 931 * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
michael@0 932 * @param aType {string} the type (a MIME type or protocol scheme)
michael@0 933 *
michael@0 934 * @returns {string} the ID
michael@0 935 */
michael@0 936 _getTypeID: function HS__getTypeID(aClass, aType) {
michael@0 937 return "urn:" + aClass + ":" + aType;
michael@0 938 },
michael@0 939
michael@0 940 /**
michael@0 941 * Return the unique identifier for a handler info record, which stores
michael@0 942 * the preferredAction and alwaysAsk fields plus a reference to the preferred
michael@0 943 * handler app. Roughly equivalent to the nsIHandlerInfo interface.
michael@0 944 *
michael@0 945 * |urn:<class>:handler:<type>|
michael@0 946 *
michael@0 947 * FIXME: the type info record should be merged into the type record,
michael@0 948 * since there's a one to one relationship between them, and this record
michael@0 949 * merely stores additional attributes of a content type.
michael@0 950 *
michael@0 951 * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
michael@0 952 * @param aType {string} the type (a MIME type or protocol scheme)
michael@0 953 *
michael@0 954 * @returns {string} the ID
michael@0 955 */
michael@0 956 _getInfoID: function HS__getInfoID(aClass, aType) {
michael@0 957 return "urn:" + aClass + ":handler:" + aType;
michael@0 958 },
michael@0 959
michael@0 960 /**
michael@0 961 * Return the unique identifier for a preferred handler record, which stores
michael@0 962 * information about the preferred handler for a given content type, including
michael@0 963 * its human-readable name and the path to its executable (for a local app)
michael@0 964 * or its URI template (for a web app).
michael@0 965 *
michael@0 966 * |urn:<class>:externalApplication:<type>|
michael@0 967 *
michael@0 968 * XXX: should this be a property of nsIHandlerApp?
michael@0 969 *
michael@0 970 * FIXME: this should be an arbitrary ID, and we should retrieve it from
michael@0 971 * the datastore for a given content type via the NC:ExternalApplication
michael@0 972 * property rather than looking for a specific ID, so a handler doesn't
michael@0 973 * have to change IDs when it goes from being a possible handler to being
michael@0 974 * the preferred one (once we support possible handlers).
michael@0 975 *
michael@0 976 * @param aClass {string} the class (CLASS_MIMEINFO or CLASS_PROTOCOLINFO)
michael@0 977 * @param aType {string} the type (a MIME type or protocol scheme)
michael@0 978 *
michael@0 979 * @returns {string} the ID
michael@0 980 */
michael@0 981 _getPreferredHandlerID: function HS__getPreferredHandlerID(aClass, aType) {
michael@0 982 return "urn:" + aClass + ":externalApplication:" + aType;
michael@0 983 },
michael@0 984
michael@0 985 /**
michael@0 986 * Return the unique identifier for a handler app record, which stores
michael@0 987 * information about a possible handler for one or more content types,
michael@0 988 * including its human-readable name and the path to its executable (for a
michael@0 989 * local app) or its URI template (for a web app).
michael@0 990 *
michael@0 991 * Note: handler app IDs for preferred handlers are different. For those,
michael@0 992 * see the _getPreferredHandlerID method.
michael@0 993 *
michael@0 994 * @param aHandlerApp {nsIHandlerApp} the handler app object
michael@0 995 */
michael@0 996 _getPossibleHandlerAppID: function HS__getPossibleHandlerAppID(aHandlerApp) {
michael@0 997 var handlerAppID = "urn:handler:";
michael@0 998
michael@0 999 if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
michael@0 1000 handlerAppID += "local:" + aHandlerApp.executable.path;
michael@0 1001 else if(aHandlerApp instanceof Ci.nsIWebHandlerApp){
michael@0 1002 aHandlerApp.QueryInterface(Ci.nsIWebHandlerApp);
michael@0 1003 handlerAppID += "web:" + aHandlerApp.uriTemplate;
michael@0 1004 }
michael@0 1005 else if(aHandlerApp instanceof Ci.nsIDBusHandlerApp){
michael@0 1006 aHandlerApp.QueryInterface(Ci.nsIDBusHandlerApp);
michael@0 1007 handlerAppID += "dbus:" + aHandlerApp.service + " " + aHandlerApp.method + " " + aHandlerApp.uriTemplate;
michael@0 1008 }else{
michael@0 1009 throw "unknown handler type";
michael@0 1010 }
michael@0 1011
michael@0 1012 return handlerAppID;
michael@0 1013 },
michael@0 1014
michael@0 1015 /**
michael@0 1016 * Get the list of types for the given class, creating the list if it doesn't
michael@0 1017 * already exist. The class can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO
michael@0 1018 * (i.e. the result of a call to _getClass).
michael@0 1019 *
michael@0 1020 * |urn:<class>s|
michael@0 1021 * |urn:<class>s:root|
michael@0 1022 *
michael@0 1023 * @param aClass {string} the class for which to retrieve a list of types
michael@0 1024 *
michael@0 1025 * @returns {nsIRDFContainer} the list of types
michael@0 1026 */
michael@0 1027 _ensureAndGetTypeList: function HS__ensureAndGetTypeList(aClass) {
michael@0 1028 var source = this._rdf.GetResource("urn:" + aClass + "s");
michael@0 1029 var property =
michael@0 1030 this._rdf.GetResource(aClass == CLASS_MIMEINFO ? NC_MIME_TYPES
michael@0 1031 : NC_PROTOCOL_SCHEMES);
michael@0 1032 var target = this._rdf.GetResource("urn:" + aClass + "s:root");
michael@0 1033
michael@0 1034 // Make sure we have an arc from the source to the target.
michael@0 1035 if (!this._ds.HasAssertion(source, property, target, true))
michael@0 1036 this._ds.Assert(source, property, target, true);
michael@0 1037
michael@0 1038 // Make sure the target is a container.
michael@0 1039 if (!this._containerUtils.IsContainer(this._ds, target))
michael@0 1040 this._containerUtils.MakeSeq(this._ds, target);
michael@0 1041
michael@0 1042 // Get the type list as an RDF container.
michael@0 1043 var typeList = Cc["@mozilla.org/rdf/container;1"].
michael@0 1044 createInstance(Ci.nsIRDFContainer);
michael@0 1045 typeList.Init(this._ds, target);
michael@0 1046
michael@0 1047 return typeList;
michael@0 1048 },
michael@0 1049
michael@0 1050 /**
michael@0 1051 * Make sure there are records in the datasource for the given content type
michael@0 1052 * by creating them if they don't already exist. We have to do this before
michael@0 1053 * storing any specific data, because we can't assume the presence
michael@0 1054 * of the records (the nsIHandlerInfo object might have been created
michael@0 1055 * from the OS), and the records have to all be there in order for the helper
michael@0 1056 * app service to properly construct an nsIHandlerInfo object for the type.
michael@0 1057 *
michael@0 1058 * Based on old downloadactions.js::_ensureMIMERegistryEntry.
michael@0 1059 *
michael@0 1060 * @param aHandlerInfo {nsIHandlerInfo} the type to make sure has a record
michael@0 1061 */
michael@0 1062 _ensureRecordsForType: function HS__ensureRecordsForType(aHandlerInfo) {
michael@0 1063 // Get the list of types.
michael@0 1064 var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo));
michael@0 1065
michael@0 1066 // If there's already a record in the datastore for this type, then we
michael@0 1067 // don't need to do anything more.
michael@0 1068 var typeID = this._getTypeID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 1069 var type = this._rdf.GetResource(typeID);
michael@0 1070 if (typeList.IndexOf(type) != -1)
michael@0 1071 return;
michael@0 1072
michael@0 1073 // Create a basic type record for this type.
michael@0 1074 typeList.AppendElement(type);
michael@0 1075 this._setLiteral(typeID, NC_VALUE, aHandlerInfo.type);
michael@0 1076
michael@0 1077 // Create a basic info record for this type.
michael@0 1078 var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 1079 this._setLiteral(infoID, NC_ALWAYS_ASK, "false");
michael@0 1080 this._setResource(typeID, NC_HANDLER_INFO, infoID);
michael@0 1081 // XXX Shouldn't we set preferredAction to useSystemDefault?
michael@0 1082 // That's what it is if there's no record in the datastore; why should it
michael@0 1083 // change to useHelperApp just because we add a record to the datastore?
michael@0 1084
michael@0 1085 // Create a basic preferred handler record for this type.
michael@0 1086 // XXX Not sure this is necessary, since preferred handlers are optional,
michael@0 1087 // and nsExternalHelperAppService::FillHandlerInfoForTypeFromDS doesn't seem
michael@0 1088 // to require the record , but downloadactions.js::_ensureMIMERegistryEntry
michael@0 1089 // used to create it, so we'll do the same.
michael@0 1090 var preferredHandlerID =
michael@0 1091 this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
michael@0 1092 this._setLiteral(preferredHandlerID, NC_PATH, "");
michael@0 1093 this._setResource(infoID, NC_PREFERRED_APP, preferredHandlerID);
michael@0 1094 },
michael@0 1095
michael@0 1096 /**
michael@0 1097 * Append known handlers of the given class to the given array. The class
michael@0 1098 * can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO.
michael@0 1099 *
michael@0 1100 * @param aHandlers {array} the array of handlers to append to
michael@0 1101 * @param aClass {string} the class for which to append handlers
michael@0 1102 */
michael@0 1103 _appendHandlers: function HS__appendHandlers(aHandlers, aClass) {
michael@0 1104 var typeList = this._ensureAndGetTypeList(aClass);
michael@0 1105 var enumerator = typeList.GetElements();
michael@0 1106
michael@0 1107 while (enumerator.hasMoreElements()) {
michael@0 1108 var element = enumerator.getNext();
michael@0 1109
michael@0 1110 // This should never happen. If it does, that means our datasource
michael@0 1111 // is corrupted with type list entries that point to literal values
michael@0 1112 // instead of resources. If it does happen, let's just do our best
michael@0 1113 // to recover by ignoring this entry and moving on to the next one.
michael@0 1114 if (!(element instanceof Ci.nsIRDFResource))
michael@0 1115 continue;
michael@0 1116
michael@0 1117 // Get the value of the element's NC:value property, which contains
michael@0 1118 // the MIME type or scheme for which we're retrieving a handler info.
michael@0 1119 var type = this._getValue(element.ValueUTF8, NC_VALUE);
michael@0 1120 if (!type)
michael@0 1121 continue;
michael@0 1122
michael@0 1123 var handler;
michael@0 1124 if (typeList.Resource.ValueUTF8 == "urn:mimetypes:root")
michael@0 1125 handler = this._mimeSvc.getFromTypeAndExtension(type, null);
michael@0 1126 else
michael@0 1127 handler = this._protocolSvc.getProtocolHandlerInfo(type);
michael@0 1128
michael@0 1129 aHandlers.appendElement(handler, false);
michael@0 1130 }
michael@0 1131 },
michael@0 1132
michael@0 1133 /**
michael@0 1134 * Whether or not a property of an RDF source has a value.
michael@0 1135 *
michael@0 1136 * @param sourceURI {string} the URI of the source
michael@0 1137 * @param propertyURI {string} the URI of the property
michael@0 1138 * @returns {boolean} whether or not the property has a value
michael@0 1139 */
michael@0 1140 _hasValue: function HS__hasValue(sourceURI, propertyURI) {
michael@0 1141 var source = this._rdf.GetResource(sourceURI);
michael@0 1142 var property = this._rdf.GetResource(propertyURI);
michael@0 1143 return this._ds.hasArcOut(source, property);
michael@0 1144 },
michael@0 1145
michael@0 1146 /**
michael@0 1147 * Get the value of a property of an RDF source.
michael@0 1148 *
michael@0 1149 * @param sourceURI {string} the URI of the source
michael@0 1150 * @param propertyURI {string} the URI of the property
michael@0 1151 * @returns {string} the value of the property
michael@0 1152 */
michael@0 1153 _getValue: function HS__getValue(sourceURI, propertyURI) {
michael@0 1154 var source = this._rdf.GetResource(sourceURI);
michael@0 1155 var property = this._rdf.GetResource(propertyURI);
michael@0 1156
michael@0 1157 var target = this._ds.GetTarget(source, property, true);
michael@0 1158
michael@0 1159 if (!target)
michael@0 1160 return null;
michael@0 1161
michael@0 1162 if (target instanceof Ci.nsIRDFResource)
michael@0 1163 return target.ValueUTF8;
michael@0 1164
michael@0 1165 if (target instanceof Ci.nsIRDFLiteral)
michael@0 1166 return target.Value;
michael@0 1167
michael@0 1168 return null;
michael@0 1169 },
michael@0 1170
michael@0 1171 /**
michael@0 1172 * Get all targets for the property of an RDF source.
michael@0 1173 *
michael@0 1174 * @param sourceURI {string} the URI of the source
michael@0 1175 * @param propertyURI {string} the URI of the property
michael@0 1176 *
michael@0 1177 * @returns {nsISimpleEnumerator} an enumerator of targets
michael@0 1178 */
michael@0 1179 _getTargets: function HS__getTargets(sourceURI, propertyURI) {
michael@0 1180 var source = this._rdf.GetResource(sourceURI);
michael@0 1181 var property = this._rdf.GetResource(propertyURI);
michael@0 1182
michael@0 1183 return this._ds.GetTargets(source, property, true);
michael@0 1184 },
michael@0 1185
michael@0 1186 /**
michael@0 1187 * Set a property of an RDF source to a literal value.
michael@0 1188 *
michael@0 1189 * @param sourceURI {string} the URI of the source
michael@0 1190 * @param propertyURI {string} the URI of the property
michael@0 1191 * @param value {string} the literal value
michael@0 1192 */
michael@0 1193 _setLiteral: function HS__setLiteral(sourceURI, propertyURI, value) {
michael@0 1194 var source = this._rdf.GetResource(sourceURI);
michael@0 1195 var property = this._rdf.GetResource(propertyURI);
michael@0 1196 var target = this._rdf.GetLiteral(value);
michael@0 1197
michael@0 1198 this._setTarget(source, property, target);
michael@0 1199 },
michael@0 1200
michael@0 1201 /**
michael@0 1202 * Set a property of an RDF source to a resource target.
michael@0 1203 *
michael@0 1204 * @param sourceURI {string} the URI of the source
michael@0 1205 * @param propertyURI {string} the URI of the property
michael@0 1206 * @param targetURI {string} the URI of the target
michael@0 1207 */
michael@0 1208 _setResource: function HS__setResource(sourceURI, propertyURI, targetURI) {
michael@0 1209 var source = this._rdf.GetResource(sourceURI);
michael@0 1210 var property = this._rdf.GetResource(propertyURI);
michael@0 1211 var target = this._rdf.GetResource(targetURI);
michael@0 1212
michael@0 1213 this._setTarget(source, property, target);
michael@0 1214 },
michael@0 1215
michael@0 1216 /**
michael@0 1217 * Assert an arc into the RDF datasource if there is no arc with the given
michael@0 1218 * source and property; otherwise, if there is already an existing arc,
michael@0 1219 * change it to point to the given target. _setLiteral and _setResource
michael@0 1220 * call this after converting their string arguments into resources
michael@0 1221 * and literals, and most callers should call one of those two methods
michael@0 1222 * instead of this one.
michael@0 1223 *
michael@0 1224 * @param source {nsIRDFResource} the source
michael@0 1225 * @param property {nsIRDFResource} the property
michael@0 1226 * @param target {nsIRDFNode} the target
michael@0 1227 */
michael@0 1228 _setTarget: function HS__setTarget(source, property, target) {
michael@0 1229 if (this._ds.hasArcOut(source, property)) {
michael@0 1230 var oldTarget = this._ds.GetTarget(source, property, true);
michael@0 1231 this._ds.Change(source, property, oldTarget, target);
michael@0 1232 }
michael@0 1233 else
michael@0 1234 this._ds.Assert(source, property, target, true);
michael@0 1235 },
michael@0 1236
michael@0 1237 /**
michael@0 1238 * Assert that a property of an RDF source has a resource target.
michael@0 1239 *
michael@0 1240 * The difference between this method and _setResource is that this one adds
michael@0 1241 * an assertion even if one already exists, which allows its callers to make
michael@0 1242 * sets of assertions (i.e. to set a property to multiple targets).
michael@0 1243 *
michael@0 1244 * @param sourceURI {string} the URI of the source
michael@0 1245 * @param propertyURI {string} the URI of the property
michael@0 1246 * @param targetURI {string} the URI of the target
michael@0 1247 */
michael@0 1248 _addResourceAssertion: function HS__addResourceAssertion(sourceURI,
michael@0 1249 propertyURI,
michael@0 1250 targetURI) {
michael@0 1251 var source = this._rdf.GetResource(sourceURI);
michael@0 1252 var property = this._rdf.GetResource(propertyURI);
michael@0 1253 var target = this._rdf.GetResource(targetURI);
michael@0 1254
michael@0 1255 this._ds.Assert(source, property, target, true);
michael@0 1256 },
michael@0 1257
michael@0 1258 /**
michael@0 1259 * Remove an assertion with a resource target.
michael@0 1260 *
michael@0 1261 * @param sourceURI {string} the URI of the source
michael@0 1262 * @param propertyURI {string} the URI of the property
michael@0 1263 * @param targetURI {string} the URI of the target
michael@0 1264 */
michael@0 1265 _removeResourceAssertion: function HS__removeResourceAssertion(sourceURI,
michael@0 1266 propertyURI,
michael@0 1267 targetURI) {
michael@0 1268 var source = this._rdf.GetResource(sourceURI);
michael@0 1269 var property = this._rdf.GetResource(propertyURI);
michael@0 1270 var target = this._rdf.GetResource(targetURI);
michael@0 1271
michael@0 1272 this._ds.Unassert(source, property, target);
michael@0 1273 },
michael@0 1274
michael@0 1275 /**
michael@0 1276 * Whether or not a property of an RDF source has a given resource target.
michael@0 1277 *
michael@0 1278 * @param sourceURI {string} the URI of the source
michael@0 1279 * @param propertyURI {string} the URI of the property
michael@0 1280 * @param targetURI {string} the URI of the target
michael@0 1281 *
michael@0 1282 * @returns {boolean} whether or not there is such an assertion
michael@0 1283 */
michael@0 1284 _hasResourceAssertion: function HS__hasResourceAssertion(sourceURI,
michael@0 1285 propertyURI,
michael@0 1286 targetURI) {
michael@0 1287 var source = this._rdf.GetResource(sourceURI);
michael@0 1288 var property = this._rdf.GetResource(propertyURI);
michael@0 1289 var target = this._rdf.GetResource(targetURI);
michael@0 1290
michael@0 1291 return this._ds.HasAssertion(source, property, target, true);
michael@0 1292 },
michael@0 1293
michael@0 1294 /**
michael@0 1295 * Whether or not a property of an RDF source has a given literal value.
michael@0 1296 *
michael@0 1297 * @param sourceURI {string} the URI of the source
michael@0 1298 * @param propertyURI {string} the URI of the property
michael@0 1299 * @param value {string} the literal value
michael@0 1300 *
michael@0 1301 * @returns {boolean} whether or not there is such an assertion
michael@0 1302 */
michael@0 1303 _hasLiteralAssertion: function HS__hasLiteralAssertion(sourceURI,
michael@0 1304 propertyURI,
michael@0 1305 value) {
michael@0 1306 var source = this._rdf.GetResource(sourceURI);
michael@0 1307 var property = this._rdf.GetResource(propertyURI);
michael@0 1308 var target = this._rdf.GetLiteral(value);
michael@0 1309
michael@0 1310 return this._ds.HasAssertion(source, property, target, true);
michael@0 1311 },
michael@0 1312
michael@0 1313 /**
michael@0 1314 * Whether or not there is an RDF source that has the given property set to
michael@0 1315 * the given literal value.
michael@0 1316 *
michael@0 1317 * @param propertyURI {string} the URI of the property
michael@0 1318 * @param value {string} the literal value
michael@0 1319 *
michael@0 1320 * @returns {boolean} whether or not there is a source
michael@0 1321 */
michael@0 1322 _existsLiteralTarget: function HS__existsLiteralTarget(propertyURI, value) {
michael@0 1323 var property = this._rdf.GetResource(propertyURI);
michael@0 1324 var target = this._rdf.GetLiteral(value);
michael@0 1325
michael@0 1326 return this._ds.hasArcIn(target, property);
michael@0 1327 },
michael@0 1328
michael@0 1329 /**
michael@0 1330 * Get the source for a property set to a given literal value.
michael@0 1331 *
michael@0 1332 * @param propertyURI {string} the URI of the property
michael@0 1333 * @param value {string} the literal value
michael@0 1334 */
michael@0 1335 _getSourceForLiteral: function HS__getSourceForLiteral(propertyURI, value) {
michael@0 1336 var property = this._rdf.GetResource(propertyURI);
michael@0 1337 var target = this._rdf.GetLiteral(value);
michael@0 1338
michael@0 1339 var source = this._ds.GetSource(property, target, true);
michael@0 1340 if (source)
michael@0 1341 return source.ValueUTF8;
michael@0 1342
michael@0 1343 return null;
michael@0 1344 },
michael@0 1345
michael@0 1346 /**
michael@0 1347 * Whether or not there is an RDF source that has the given property set to
michael@0 1348 * the given resource target.
michael@0 1349 *
michael@0 1350 * @param propertyURI {string} the URI of the property
michael@0 1351 * @param targetURI {string} the URI of the target
michael@0 1352 *
michael@0 1353 * @returns {boolean} whether or not there is a source
michael@0 1354 */
michael@0 1355 _existsResourceTarget: function HS__existsResourceTarget(propertyURI,
michael@0 1356 targetURI) {
michael@0 1357 var property = this._rdf.GetResource(propertyURI);
michael@0 1358 var target = this._rdf.GetResource(targetURI);
michael@0 1359
michael@0 1360 return this._ds.hasArcIn(target, property);
michael@0 1361 },
michael@0 1362
michael@0 1363 /**
michael@0 1364 * Remove a property of an RDF source.
michael@0 1365 *
michael@0 1366 * @param sourceURI {string} the URI of the source
michael@0 1367 * @param propertyURI {string} the URI of the property
michael@0 1368 */
michael@0 1369 _removeTarget: function HS__removeTarget(sourceURI, propertyURI) {
michael@0 1370 var source = this._rdf.GetResource(sourceURI);
michael@0 1371 var property = this._rdf.GetResource(propertyURI);
michael@0 1372
michael@0 1373 if (this._ds.hasArcOut(source, property)) {
michael@0 1374 var target = this._ds.GetTarget(source, property, true);
michael@0 1375 this._ds.Unassert(source, property, target);
michael@0 1376 }
michael@0 1377 },
michael@0 1378
michael@0 1379 /**
michael@0 1380 * Remove all assertions about a given RDF source.
michael@0 1381 *
michael@0 1382 * Note: not recursive. If some assertions point to other resources,
michael@0 1383 * and you want to remove assertions about those resources too, you need
michael@0 1384 * to do so manually.
michael@0 1385 *
michael@0 1386 * @param sourceURI {string} the URI of the source
michael@0 1387 */
michael@0 1388 _removeAssertions: function HS__removeAssertions(sourceURI) {
michael@0 1389 var source = this._rdf.GetResource(sourceURI);
michael@0 1390 var properties = this._ds.ArcLabelsOut(source);
michael@0 1391
michael@0 1392 while (properties.hasMoreElements()) {
michael@0 1393 let property = properties.getNext();
michael@0 1394 let targets = this._ds.GetTargets(source, property, true);
michael@0 1395 while (targets.hasMoreElements()) {
michael@0 1396 let target = targets.getNext();
michael@0 1397 this._ds.Unassert(source, property, target);
michael@0 1398 }
michael@0 1399 }
michael@0 1400 }
michael@0 1401
michael@0 1402 };
michael@0 1403
michael@0 1404 //****************************************************************************//
michael@0 1405 // More XPCOM Plumbing
michael@0 1406
michael@0 1407 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HandlerService]);

mercurial