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 { ComponentUtils } = ChromeUtils.import(
6 "resource://gre/modules/ComponentUtils.jsm"
8 const { AppConstants } = ChromeUtils.import(
9 "resource://gre/modules/AppConstants.jsm"
11 const { XPCOMUtils } = ChromeUtils.import(
12 "resource://gre/modules/XPCOMUtils.jsm"
14 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
16 const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged";
18 ChromeUtils.defineModuleGetter(
21 "resource://gre/modules/FileUtils.jsm"
23 ChromeUtils.defineModuleGetter(
26 "resource://gre/modules/JSONFile.jsm"
28 const { Integration } = ChromeUtils.import(
29 "resource://gre/modules/Integration.jsm"
32 /* global DownloadIntegration */
33 Integration.downloads.defineModuleGetter(
35 "DownloadIntegration",
36 "resource://gre/modules/DownloadIntegration.jsm"
39 XPCOMUtils.defineLazyServiceGetter(
41 "gExternalProtocolService",
42 "@mozilla.org/uriloader/external-protocol-service;1",
43 "nsIExternalProtocolService"
45 XPCOMUtils.defineLazyServiceGetter(
48 "@mozilla.org/mime;1",
52 function HandlerService() {
53 // Observe handlersvc-json-replace so we can switch to the datasource
54 Services.obs.addObserver(this, "handlersvc-json-replace", true);
57 HandlerService.prototype = {
58 classID: Components.ID("{220cc253-b60f-41f6-b9cf-fdcb325f970f}"),
59 QueryInterface: ChromeUtils.generateQI([
60 "nsISupportsWeakReference",
68 this.__store = new JSONFile({
70 Services.dirsvc.get("ProfD", Ci.nsIFile).path,
73 dataPostProcessor: this._dataPostProcessor.bind(this),
77 // Always call this even if this.__store was set, since it may have been
78 // set by asyncInit, which might not have completed yet.
79 this._ensureStoreInitialized();
83 __storeInitialized: false,
84 _ensureStoreInitialized() {
85 if (!this.__storeInitialized) {
86 this.__storeInitialized = true;
87 this.__store.ensureDataReady();
89 this._injectDefaultProtocolHandlersIfNeeded();
90 this._migrateProtocolHandlersIfNeeded();
92 Services.obs.notifyObservers(null, "handlersvc-store-initialized");
94 // Bug 1736924: run migration for browser.download.improvements_to_download_panel if applicable.
95 // Since we need DownloadsViewInternally to verify mimetypes, we run this after
96 // DownloadsViewInternally is registered via the 'handlersvc-store-initialized' notification.
97 this._migrateDownloadsImprovementsIfNeeded();
101 _dataPostProcessor(data) {
102 return data.defaultHandlersVersion
105 defaultHandlersVersion: {},
108 isDownloadsImprovementsAlreadyMigrated: false,
113 * Injects new default protocol handlers if the version in the preferences is
114 * newer than the one in the data store.
116 _injectDefaultProtocolHandlersIfNeeded() {
117 let prefsDefaultHandlersVersion;
119 prefsDefaultHandlersVersion = Services.prefs.getComplexValue(
120 "gecko.handlerService.defaultHandlersVersion",
121 Ci.nsIPrefLocalizedString
125 ex instanceof Components.Exception &&
126 ex.result == Cr.NS_ERROR_UNEXPECTED
128 // This platform does not have any default protocol handlers configured.
135 prefsDefaultHandlersVersion = Number(prefsDefaultHandlersVersion.data);
136 let locale = Services.locale.appLocaleAsBCP47;
138 let defaultHandlersVersion =
139 this._store.data.defaultHandlersVersion[locale] || 0;
140 if (defaultHandlersVersion < prefsDefaultHandlersVersion) {
141 this._injectDefaultProtocolHandlers();
142 this._store.data.defaultHandlersVersion[
144 ] = prefsDefaultHandlersVersion;
145 // Now save the result:
146 this._store.saveSoon();
153 _injectDefaultProtocolHandlers() {
154 let schemesPrefBranch = Services.prefs.getBranch(
155 "gecko.handlerService.schemes."
157 let schemePrefList = schemesPrefBranch.getChildList("");
161 // read all the scheme prefs into a hash
162 for (let schemePrefName of schemePrefList) {
163 let [scheme, handlerNumber, attribute] = schemePrefName.split(".");
166 let attrData = schemesPrefBranch.getComplexValue(
168 Ci.nsIPrefLocalizedString
170 if (!(scheme in schemes)) {
171 schemes[scheme] = {};
174 if (!(handlerNumber in schemes[scheme])) {
175 schemes[scheme][handlerNumber] = {};
178 schemes[scheme][handlerNumber][attribute] = attrData;
182 // Now drop any entries without a uriTemplate, or with a broken one.
183 // The Array.from calls ensure we can safely delete things without
184 // affecting the iterator.
185 for (let [scheme, handlerObject] of Array.from(Object.entries(schemes))) {
186 let handlers = Array.from(Object.entries(handlerObject));
187 let validHandlers = 0;
188 for (let [key, obj] of handlers) {
191 !obj.uriTemplate.startsWith("https://") ||
192 !obj.uriTemplate.toLowerCase().includes("%s")
194 delete handlerObject[key];
199 if (!validHandlers) {
200 delete schemes[scheme];
204 // Now, we're going to cheat. Terribly. The idiologically correct way
205 // of implementing the following bit of code would be to fetch the
206 // handler info objects from the protocol service, manipulate those,
207 // and then store each of them.
208 // However, that's expensive. It causes us to talk to the OS about
209 // default apps, which causes the OS to go hit the disk.
210 // All we're trying to do is insert some web apps into the list. We
211 // don't care what's already in the file, we just want to do the
212 // equivalent of appending into the database. So let's just go do that:
213 for (let scheme of Object.keys(schemes)) {
214 let existingSchemeInfo = this._store.data.schemes[scheme];
215 if (!existingSchemeInfo) {
216 // Haven't seen this scheme before. Default to asking which app the
217 // user wants to use:
218 existingSchemeInfo = {
219 // Signal to future readers that we didn't ask the OS anything.
220 // When the entry is first used, get the info from the OS.
222 // The first item in the list is the preferred handler, and
223 // there isn't one, so we fill in null:
226 this._store.data.schemes[scheme] = existingSchemeInfo;
228 let { handlers } = existingSchemeInfo;
229 for (let handlerNumber of Object.keys(schemes[scheme])) {
230 let newHandler = schemes[scheme][handlerNumber];
231 // If there is already a handler registered with the same template
232 // URL, ignore the new one:
233 let matchingTemplate = handler =>
234 handler && handler.uriTemplate == newHandler.uriTemplate;
235 if (!handlers.some(matchingTemplate)) {
236 handlers.push(newHandler);
243 * Execute any migrations. Migrations are defined here for any changes or removals for
244 * existing handlers. Additions are still handled via the localized prefs infrastructure.
246 * This depends on the browser.handlers.migrations pref being set by migrateUI in
247 * nsBrowserGlue (for Fx Desktop) or similar mechanisms for other products.
248 * This is a comma-separated list of identifiers of migrations that need running.
249 * This avoids both re-running older migrations and keeping an additional
250 * pref around permanently.
252 _migrateProtocolHandlersIfNeeded() {
253 const kMigrations = {
255 const k30BoxesRegex = /^https?:\/\/(?:www\.)?30boxes.com\/external\/widget/i;
256 let webcalHandler = gExternalProtocolService.getProtocolHandlerInfo(
259 if (this.exists(webcalHandler)) {
260 this.fillHandlerInfo(webcalHandler, "");
261 let shouldStore = false;
262 // First remove 30boxes from possible handlers.
263 let handlers = webcalHandler.possibleApplicationHandlers;
264 for (let i = handlers.length - 1; i >= 0; i--) {
265 let app = handlers.queryElementAt(i, Ci.nsIHandlerApp);
267 app instanceof Ci.nsIWebHandlerApp &&
268 k30BoxesRegex.test(app.uriTemplate)
271 handlers.removeElementAt(i);
274 // Then remove as a preferred handler.
275 if (webcalHandler.preferredApplicationHandler) {
276 let app = webcalHandler.preferredApplicationHandler;
278 app instanceof Ci.nsIWebHandlerApp &&
279 k30BoxesRegex.test(app.uriTemplate)
281 webcalHandler.preferredApplicationHandler = null;
285 // Then store, if we changed anything.
287 this.store(webcalHandler);
291 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1526890 for context.
292 "secure-mail": () => {
293 const kSubstitutions = new Map([
295 "http://compose.mail.yahoo.co.jp/ym/Compose?To=%s",
296 "https://mail.yahoo.co.jp/compose/?To=%s",
299 "http://www.inbox.lv/rfc2368/?value=%s",
300 "https://mail.inbox.lv/compose?to=%s",
303 "http://poczta.interia.pl/mh/?mailto=%s",
304 "https://poczta.interia.pl/mh/?mailto=%s",
307 "http://win.mail.ru/cgi-bin/sentmsg?mailto=%s",
308 "https://e.mail.ru/cgi-bin/sentmsg?mailto=%s",
312 function maybeReplaceURL(app) {
313 if (app instanceof Ci.nsIWebHandlerApp) {
314 let { uriTemplate } = app;
315 let sub = kSubstitutions.get(uriTemplate);
317 app.uriTemplate = sub;
323 let mailHandler = gExternalProtocolService.getProtocolHandlerInfo(
326 if (this.exists(mailHandler)) {
327 this.fillHandlerInfo(mailHandler, "");
328 let handlers = mailHandler.possibleApplicationHandlers;
329 let shouldStore = false;
330 for (let i = handlers.length - 1; i >= 0; i--) {
331 let app = handlers.queryElementAt(i, Ci.nsIHandlerApp);
332 // Note: will evaluate the RHS because it's a binary rather than
334 shouldStore |= maybeReplaceURL(app);
336 // Then check the preferred handler.
337 if (mailHandler.preferredApplicationHandler) {
338 shouldStore |= maybeReplaceURL(
339 mailHandler.preferredApplicationHandler
342 // Then store, if we changed anything. Note that store() handles
343 // duplicates, so we don't have to.
345 this.store(mailHandler);
350 let migrationsToRun = Services.prefs.getCharPref(
351 "browser.handlers.migrations",
354 migrationsToRun = migrationsToRun ? migrationsToRun.split(",") : [];
355 for (let migration of migrationsToRun) {
358 kMigrations[migration]();
364 if (migrationsToRun.length) {
365 Services.prefs.clearUserPref("browser.handlers.migrations");
370 return (async () => {
372 await this.__store.finalize();
375 this.__storeInitialized = false;
376 })().catch(Cu.reportError);
380 observe(subject, topic, data) {
381 if (topic != "handlersvc-json-replace") {
384 let promise = this._onDBChange();
386 Services.obs.notifyObservers(null, "handlersvc-json-replace-complete");
393 this.__store = new JSONFile({
394 path: PathUtils.join(
395 Services.dirsvc.get("ProfD", Ci.nsIFile).path,
398 dataPostProcessor: this._dataPostProcessor.bind(this),
403 // __store can be null if we called _onDBChange in the mean time.
405 this._ensureStoreInitialized();
408 .catch(Cu.reportError);
413 * Update already existing handlers for non-internal mimetypes to have prefs set from alwaysAsk
414 * to saveToDisk. However, if reading an internal mimetype and set to alwaysAsk, update to use handleInternally.
415 * This migration is needed since browser.download.improvements_to_download_panel does not
416 * override user preferences if preferredAction = alwaysAsk. By doing so, we can ensure that file prompt
417 * behaviours remain consistent for most files.
419 * See Bug 1736924 for more information.
421 _migrateDownloadsImprovementsIfNeeded() {
422 // Migrate if the preference is enabled AND if the migration has never been run before.
423 // Otherwise, we risk overwriting preferences for existing profiles!
425 Services.prefs.getBoolPref(
426 "browser.download.improvements_to_download_panel"
428 !Services.policies.getActivePolicies()?.Handlers &&
429 !this._store.data.isDownloadsImprovementsAlreadyMigrated
431 for (let [type, mimeInfo] of Object.entries(this._store.data.mimeTypes)) {
432 let isViewableInternally = DownloadIntegration.shouldViewDownloadInternally(
435 let isAskOnly = mimeInfo && mimeInfo.ask;
438 if (isViewableInternally) {
439 mimeInfo.action = Ci.nsIHandlerInfo.handleInternally;
441 mimeInfo.action = Ci.nsIHandlerInfo.saveToDisk;
444 // Sets alwaysAskBeforeHandling to false. Needed to ensure that:
445 // preferredAction appears as expected in preferences table; and
446 // downloads behaviour is updated to never show UCT window.
447 mimeInfo.ask = false;
451 this._store.data.isDownloadsImprovementsAlreadyMigrated = true;
452 this._store.saveSoon();
458 let handlers = Cc["@mozilla.org/array;1"].createInstance(
461 for (let [type, typeInfo] of Object.entries(this._store.data.mimeTypes)) {
462 let primaryExtension = typeInfo.extensions?.[0] ?? null;
463 let handler = gMIMEService.getFromTypeAndExtension(
467 handlers.appendElement(handler);
469 for (let type of Object.keys(this._store.data.schemes)) {
470 // nsIExternalProtocolService.getProtocolHandlerInfo can be expensive
471 // on Windows, so we return a proxy to delay retrieving the nsIHandlerInfo
472 // until one of its properties is accessed.
474 // Note: our caller still needs to yield periodically when iterating
475 // the enumerator and accessing handler properties to avoid monopolizing
478 let handler = new Proxy(
480 QueryInterface: ChromeUtils.generateQI(["nsIHandlerInfo"]),
483 delete this._handlerInfo;
484 return (this._handlerInfo = gExternalProtocolService.getProtocolHandlerInfo(
491 return target[name] || target._handlerInfo[name];
493 set(target, name, value) {
494 target._handlerInfo[name] = value;
498 handlers.appendElement(handler);
500 return handlers.enumerate(Ci.nsIHandlerInfo);
505 let handlerList = this._getHandlerListByHandlerInfoType(handlerInfo);
507 // Retrieve an existing entry if present, instead of creating a new one, so
508 // that we preserve unknown properties for forward compatibility.
509 let storedHandlerInfo = handlerList[handlerInfo.type];
510 if (!storedHandlerInfo) {
511 storedHandlerInfo = {};
512 handlerList[handlerInfo.type] = storedHandlerInfo;
515 // Only a limited number of preferredAction values is allowed.
517 handlerInfo.preferredAction == Ci.nsIHandlerInfo.saveToDisk ||
518 handlerInfo.preferredAction == Ci.nsIHandlerInfo.useSystemDefault ||
519 handlerInfo.preferredAction == Ci.nsIHandlerInfo.handleInternally ||
520 // For files (ie mimetype rather than protocol handling info), ensure
521 // we can store the "always ask" state, too:
522 (handlerInfo.preferredAction == Ci.nsIHandlerInfo.alwaysAsk &&
523 this._isMIMEInfo(handlerInfo) &&
524 Services.prefs.getBoolPref(
525 "browser.download.improvements_to_download_panel"
528 storedHandlerInfo.action = handlerInfo.preferredAction;
530 storedHandlerInfo.action = Ci.nsIHandlerInfo.useHelperApp;
533 if (handlerInfo.alwaysAskBeforeHandling) {
534 storedHandlerInfo.ask = true;
536 delete storedHandlerInfo.ask;
539 // Build a list of unique nsIHandlerInfo instances to process later.
541 if (handlerInfo.preferredApplicationHandler) {
542 handlers.push(handlerInfo.preferredApplicationHandler);
544 for (let handler of handlerInfo.possibleApplicationHandlers.enumerate(
547 // If the caller stored duplicate handlers, we save them only once.
548 if (!handlers.some(h => h.equals(handler))) {
549 handlers.push(handler);
553 // If any of the nsIHandlerInfo instances cannot be serialized, it is not
554 // included in the final list. The first element is always the preferred
555 // handler, or null if there is none.
556 let serializableHandlers = handlers
557 .map(h => this.handlerAppToSerializable(h))
559 if (serializableHandlers.length) {
560 if (!handlerInfo.preferredApplicationHandler) {
561 serializableHandlers.unshift(null);
563 storedHandlerInfo.handlers = serializableHandlers;
565 delete storedHandlerInfo.handlers;
568 if (this._isMIMEInfo(handlerInfo)) {
569 let extensions = storedHandlerInfo.extensions || [];
570 for (let extension of handlerInfo.getFileExtensions()) {
571 extension = extension.toLowerCase();
572 // If the caller stored duplicate extensions, we save them only once.
573 if (!extensions.includes(extension)) {
574 extensions.push(extension);
577 if (extensions.length) {
578 storedHandlerInfo.extensions = extensions;
580 delete storedHandlerInfo.extensions;
584 // If we're saving *anything*, it stops being a stub:
585 delete storedHandlerInfo.stubEntry;
587 this._store.saveSoon();
589 // Now notify PDF.js. This is hacky, but a lot better than expecting all
590 // the consumers to do it...
591 if (handlerInfo.type == "application/pdf") {
592 Services.obs.notifyObservers(null, TOPIC_PDFJS_HANDLER_CHANGED);
597 fillHandlerInfo(handlerInfo, overrideType) {
598 let type = overrideType || handlerInfo.type;
599 let storedHandlerInfo = this._getHandlerListByHandlerInfoType(handlerInfo)[
602 if (!storedHandlerInfo) {
603 throw new Components.Exception(
604 "handlerSvc fillHandlerInfo: don't know this type",
605 Cr.NS_ERROR_NOT_AVAILABLE
609 let isStub = !!storedHandlerInfo.stubEntry;
610 // In the normal case, this is not a stub, so we can just read stored info
611 // and write to the handlerInfo object we were passed.
613 handlerInfo.preferredAction = storedHandlerInfo.action;
614 handlerInfo.alwaysAskBeforeHandling = !!storedHandlerInfo.ask;
616 // If we've got a stub, ensure the defaults are still set:
617 gExternalProtocolService.setProtocolHandlerDefaults(
619 handlerInfo.hasDefaultHandler
622 handlerInfo.preferredAction == Ci.nsIHandlerInfo.alwaysAsk &&
623 handlerInfo.alwaysAskBeforeHandling
625 // `store` will default to `useHelperApp` because `alwaysAsk` is
626 // not one of the 3 recognized options; for compatibility, do
628 handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
631 // If it *is* a stub, don't override alwaysAskBeforeHandling or the
632 // preferred actions. Instead, just append the stored handlers, without
633 // overriding the preferred app, and then schedule a task to store proper
634 // info for this handler.
635 this._appendStoredHandlers(handlerInfo, storedHandlerInfo.handlers, isStub);
637 if (this._isMIMEInfo(handlerInfo) && storedHandlerInfo.extensions) {
638 for (let extension of storedHandlerInfo.extensions) {
639 handlerInfo.appendExtension(extension);
641 } else if (this._mockedHandler) {
642 this._insertMockedHandler(handlerInfo);
647 * Private method to inject stored handler information into an nsIHandlerInfo
649 * @param handlerInfo the nsIHandlerInfo instance to write to
650 * @param storedHandlers the stored handlers
651 * @param keepPreferredApp whether to keep the handlerInfo's
652 * preferredApplicationHandler or override it
653 * (default: false, ie override it)
655 _appendStoredHandlers(handlerInfo, storedHandlers, keepPreferredApp) {
656 // If the first item is not null, it is also the preferred handler. Since
657 // we cannot modify the stored array, use a boolean to keep track of this.
658 let isFirstItem = true;
659 for (let handler of storedHandlers || [null]) {
660 let handlerApp = this.handlerAppFromSerializable(handler || {});
663 // Do not overwrite the preferred app unless that's allowed
664 if (!keepPreferredApp) {
665 handlerInfo.preferredApplicationHandler = handlerApp;
669 handlerInfo.possibleApplicationHandlers.appendElement(handlerApp);
676 * A nsIHandlerApp handler app
677 * @returns Serializable representation of a handler app object.
679 handlerAppToSerializable(handler) {
680 if (handler instanceof Ci.nsILocalHandlerApp) {
683 path: handler.executable.path,
685 } else if (handler instanceof Ci.nsIWebHandlerApp) {
688 uriTemplate: handler.uriTemplate,
690 } else if (handler instanceof Ci.nsIDBusHandlerApp) {
693 service: handler.service,
694 method: handler.method,
695 objectPath: handler.objectPath,
696 dBusInterface: handler.dBusInterface,
698 } else if (handler instanceof Ci.nsIGIOMimeApp) {
701 command: handler.command,
704 // If the handler is an unknown handler type, return null.
705 // Android default application handler is the case.
711 * Serializable representation of a handler object.
712 * @returns {nsIHandlerApp} the handler app, if any; otherwise null
714 handlerAppFromSerializable(handlerObj) {
716 if ("path" in handlerObj) {
718 let file = new FileUtils.File(handlerObj.path);
719 if (!file.exists()) {
723 "@mozilla.org/uriloader/local-handler-app;1"
724 ].createInstance(Ci.nsILocalHandlerApp);
725 handlerApp.executable = file;
729 } else if ("uriTemplate" in handlerObj) {
731 "@mozilla.org/uriloader/web-handler-app;1"
732 ].createInstance(Ci.nsIWebHandlerApp);
733 handlerApp.uriTemplate = handlerObj.uriTemplate;
734 } else if ("service" in handlerObj) {
736 "@mozilla.org/uriloader/dbus-handler-app;1"
737 ].createInstance(Ci.nsIDBusHandlerApp);
738 handlerApp.service = handlerObj.service;
739 handlerApp.method = handlerObj.method;
740 handlerApp.objectPath = handlerObj.objectPath;
741 handlerApp.dBusInterface = handlerObj.dBusInterface;
742 } else if ("command" in handlerObj && "@mozilla.org/gio-service;1" in Cc) {
744 handlerApp = Cc["@mozilla.org/gio-service;1"]
745 .getService(Ci.nsIGIOService)
746 .createAppFromCommand(handlerObj.command, handlerObj.name);
754 handlerApp.name = handlerObj.name;
759 * The function returns a reference to the "mimeTypes" or "schemes" object
760 * based on which type of handlerInfo is provided.
762 _getHandlerListByHandlerInfoType(handlerInfo) {
763 return this._isMIMEInfo(handlerInfo)
764 ? this._store.data.mimeTypes
765 : this._store.data.schemes;
769 * Determines whether an nsIHandlerInfo instance represents a MIME type.
771 _isMIMEInfo(handlerInfo) {
772 // We cannot rely only on the instanceof check because on Android both MIME
773 // types and protocols are instances of nsIMIMEInfo. We still do the check
774 // so that properties of nsIMIMEInfo become available to the callers.
776 handlerInfo instanceof Ci.nsIMIMEInfo && handlerInfo.type.includes("/")
781 exists(handlerInfo) {
783 handlerInfo.type in this._getHandlerListByHandlerInfoType(handlerInfo)
788 remove(handlerInfo) {
789 delete this._getHandlerListByHandlerInfoType(handlerInfo)[handlerInfo.type];
790 this._store.saveSoon();
794 getTypeFromExtension(fileExtension) {
795 let extension = fileExtension.toLowerCase();
796 let mimeTypes = this._store.data.mimeTypes;
797 for (let type of Object.keys(mimeTypes)) {
799 mimeTypes[type].extensions &&
800 mimeTypes[type].extensions.includes(extension)
808 _mockedHandler: null,
809 _mockedProtocol: null,
811 _insertMockedHandler(handlerInfo) {
812 if (handlerInfo.type == this._mockedProtocol) {
813 handlerInfo.preferredApplicationHandler = this._mockedHandler;
814 handlerInfo.possibleApplicationHandlers.insertElementAt(
821 // test-only: mock the handler instance for a particular protocol/scheme
822 mockProtocolHandler(protocol) {
824 this._mockedProtocol = null;
825 this._mockedHandler = null;
828 this._mockedProtocol = protocol;
829 this._mockedHandler = {
830 QueryInterface: ChromeUtils.generateQI([Ci.nsILocalHandlerApp]),
831 launchWithURI(uri, context) {
832 Services.obs.notifyObservers(uri, "mocked-protocol-handler");
834 name: "Mocked handler",
835 detailedDescription: "Mocked handler for tests",
840 if (AppConstants.platform == "macosx") {
841 // We need an app path that isn't us, nor in our app bundle, and
842 // Apple no longer allows us to read the default-shipped apps
843 // in /Applications/ - except for Safari, it would appear!
844 let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
845 f.initWithPath("/Applications/Safari.app");
848 return Services.dirsvc.get("XCurProcD", Ci.nsIFile);
851 clearParameters() {},
852 appendParameter() {},
861 this.NSGetFactory = ComponentUtils.generateNSGetFactory([HandlerService]);