Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
1 /*
2 # -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 */
8 //****************************************************************************//
9 // Constants & Enumeration Values
11 var Cc = Components.classes;
12 var Ci = Components.interfaces;
13 var Cr = Components.results;
15 Components.utils.import('resource://gre/modules/Services.jsm');
17 const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
18 const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
19 const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
20 const TYPE_PDF = "application/pdf";
22 const PREF_PDFJS_DISABLED = "pdfjs.disabled";
23 const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged";
25 const PREF_DISABLED_PLUGIN_TYPES = "plugin.disable_full_page_plugin_for_types";
27 // Preferences that affect which entries to show in the list.
28 const PREF_SHOW_PLUGINS_IN_LIST = "browser.download.show_plugins_in_list";
29 const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS =
30 "browser.download.hide_plugins_without_extensions";
32 /*
33 * Preferences where we store handling information about the feed type.
34 *
35 * browser.feeds.handler
36 * - "bookmarks", "reader" (clarified further using the .default preference),
37 * or "ask" -- indicates the default handler being used to process feeds;
38 * "bookmarks" is obsolete; to specify that the handler is bookmarks,
39 * set browser.feeds.handler.default to "bookmarks";
40 *
41 * browser.feeds.handler.default
42 * - "bookmarks", "client" or "web" -- indicates the chosen feed reader used
43 * to display feeds, either transiently (i.e., when the "use as default"
44 * checkbox is unchecked, corresponds to when browser.feeds.handler=="ask")
45 * or more permanently (i.e., the item displayed in the dropdown in Feeds
46 * preferences)
47 *
48 * browser.feeds.handler.webservice
49 * - the URL of the currently selected web service used to read feeds
50 *
51 * browser.feeds.handlers.application
52 * - nsILocalFile, stores the current client-side feed reading app if one has
53 * been chosen
54 */
55 const PREF_FEED_SELECTED_APP = "browser.feeds.handlers.application";
56 const PREF_FEED_SELECTED_WEB = "browser.feeds.handlers.webservice";
57 const PREF_FEED_SELECTED_ACTION = "browser.feeds.handler";
58 const PREF_FEED_SELECTED_READER = "browser.feeds.handler.default";
60 const PREF_VIDEO_FEED_SELECTED_APP = "browser.videoFeeds.handlers.application";
61 const PREF_VIDEO_FEED_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
62 const PREF_VIDEO_FEED_SELECTED_ACTION = "browser.videoFeeds.handler";
63 const PREF_VIDEO_FEED_SELECTED_READER = "browser.videoFeeds.handler.default";
65 const PREF_AUDIO_FEED_SELECTED_APP = "browser.audioFeeds.handlers.application";
66 const PREF_AUDIO_FEED_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
67 const PREF_AUDIO_FEED_SELECTED_ACTION = "browser.audioFeeds.handler";
68 const PREF_AUDIO_FEED_SELECTED_READER = "browser.audioFeeds.handler.default";
70 // The nsHandlerInfoAction enumeration values in nsIHandlerInfo identify
71 // the actions the application can take with content of various types.
72 // But since nsIHandlerInfo doesn't support plugins, there's no value
73 // identifying the "use plugin" action, so we use this constant instead.
74 const kActionUsePlugin = 5;
76 /*
77 #ifdef MOZ_WIDGET_GTK
78 */
79 const ICON_URL_APP = "moz-icon://dummy.exe?size=16";
80 /*
81 #else
82 */
83 const ICON_URL_APP = "chrome://browser/skin/preferences/application.png";
84 /*
85 #endif
86 */
88 // For CSS. Can be one of "ask", "save", "plugin" or "feed". If absent, the icon URL
89 // was set by us to a custom handler icon and CSS should not try to override it.
90 const APP_ICON_ATTR_NAME = "appHandlerIcon";
92 //****************************************************************************//
93 // Utilities
95 function getFileDisplayName(file) {
96 #ifdef XP_WIN
97 if (file instanceof Ci.nsILocalFileWin) {
98 try {
99 return file.getVersionInfoField("FileDescription");
100 } catch (e) {}
101 }
102 #endif
103 #ifdef XP_MACOSX
104 if (file instanceof Ci.nsILocalFileMac) {
105 try {
106 return file.bundleDisplayName;
107 } catch (e) {}
108 }
109 #endif
110 return file.leafName;
111 }
113 function getLocalHandlerApp(aFile) {
114 var localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
115 createInstance(Ci.nsILocalHandlerApp);
116 localHandlerApp.name = getFileDisplayName(aFile);
117 localHandlerApp.executable = aFile;
119 return localHandlerApp;
120 }
122 /**
123 * An enumeration of items in a JS array.
124 *
125 * FIXME: use ArrayConverter once it lands (bug 380839).
126 *
127 * @constructor
128 */
129 function ArrayEnumerator(aItems) {
130 this._index = 0;
131 this._contents = aItems;
132 }
134 ArrayEnumerator.prototype = {
135 _index: 0,
137 hasMoreElements: function() {
138 return this._index < this._contents.length;
139 },
141 getNext: function() {
142 return this._contents[this._index++];
143 }
144 };
146 function isFeedType(t) {
147 return t == TYPE_MAYBE_FEED || t == TYPE_MAYBE_VIDEO_FEED || t == TYPE_MAYBE_AUDIO_FEED;
148 }
150 //****************************************************************************//
151 // HandlerInfoWrapper
153 /**
154 * This object wraps nsIHandlerInfo with some additional functionality
155 * the Applications prefpane needs to display and allow modification of
156 * the list of handled types.
157 *
158 * We create an instance of this wrapper for each entry we might display
159 * in the prefpane, and we compose the instances from various sources,
160 * including plugins and the handler service.
161 *
162 * We don't implement all the original nsIHandlerInfo functionality,
163 * just the stuff that the prefpane needs.
164 *
165 * In theory, all of the custom functionality in this wrapper should get
166 * pushed down into nsIHandlerInfo eventually.
167 */
168 function HandlerInfoWrapper(aType, aHandlerInfo) {
169 this._type = aType;
170 this.wrappedHandlerInfo = aHandlerInfo;
171 }
173 HandlerInfoWrapper.prototype = {
174 // The wrapped nsIHandlerInfo object. In general, this object is private,
175 // but there are a couple cases where callers access it directly for things
176 // we haven't (yet?) implemented, so we make it a public property.
177 wrappedHandlerInfo: null,
180 //**************************************************************************//
181 // Convenience Utils
183 _handlerSvc: Cc["@mozilla.org/uriloader/handler-service;1"].
184 getService(Ci.nsIHandlerService),
186 _prefSvc: Cc["@mozilla.org/preferences-service;1"].
187 getService(Ci.nsIPrefBranch),
189 _categoryMgr: Cc["@mozilla.org/categorymanager;1"].
190 getService(Ci.nsICategoryManager),
192 element: function(aID) {
193 return document.getElementById(aID);
194 },
197 //**************************************************************************//
198 // nsIHandlerInfo
200 // The MIME type or protocol scheme.
201 _type: null,
202 get type() {
203 return this._type;
204 },
206 get description() {
207 if (this.wrappedHandlerInfo.description)
208 return this.wrappedHandlerInfo.description;
210 if (this.primaryExtension) {
211 var extension = this.primaryExtension.toUpperCase();
212 return this.element("bundlePreferences").getFormattedString("fileEnding",
213 [extension]);
214 }
216 return this.type;
217 },
219 get preferredApplicationHandler() {
220 return this.wrappedHandlerInfo.preferredApplicationHandler;
221 },
223 set preferredApplicationHandler(aNewValue) {
224 this.wrappedHandlerInfo.preferredApplicationHandler = aNewValue;
226 // Make sure the preferred handler is in the set of possible handlers.
227 if (aNewValue)
228 this.addPossibleApplicationHandler(aNewValue)
229 },
231 get possibleApplicationHandlers() {
232 return this.wrappedHandlerInfo.possibleApplicationHandlers;
233 },
235 addPossibleApplicationHandler: function(aNewHandler) {
236 var possibleApps = this.possibleApplicationHandlers.enumerate();
237 while (possibleApps.hasMoreElements()) {
238 if (possibleApps.getNext().equals(aNewHandler))
239 return;
240 }
241 this.possibleApplicationHandlers.appendElement(aNewHandler, false);
242 },
244 removePossibleApplicationHandler: function(aHandler) {
245 var defaultApp = this.preferredApplicationHandler;
246 if (defaultApp && aHandler.equals(defaultApp)) {
247 // If the app we remove was the default app, we must make sure
248 // it won't be used anymore
249 this.alwaysAskBeforeHandling = true;
250 this.preferredApplicationHandler = null;
251 }
253 var handlers = this.possibleApplicationHandlers;
254 for (var i = 0; i < handlers.length; ++i) {
255 var handler = handlers.queryElementAt(i, Ci.nsIHandlerApp);
256 if (handler.equals(aHandler)) {
257 handlers.removeElementAt(i);
258 break;
259 }
260 }
261 },
263 get hasDefaultHandler() {
264 return this.wrappedHandlerInfo.hasDefaultHandler;
265 },
267 get defaultDescription() {
268 return this.wrappedHandlerInfo.defaultDescription;
269 },
271 // What to do with content of this type.
272 get preferredAction() {
273 // If we have an enabled plugin, then the action is to use that plugin.
274 if (this.pluginName && !this.isDisabledPluginType)
275 return kActionUsePlugin;
277 // If the action is to use a helper app, but we don't have a preferred
278 // handler app, then switch to using the system default, if any; otherwise
279 // fall back to saving to disk, which is the default action in nsMIMEInfo.
280 // Note: "save to disk" is an invalid value for protocol info objects,
281 // but the alwaysAskBeforeHandling getter will detect that situation
282 // and always return true in that case to override this invalid value.
283 if (this.wrappedHandlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
284 !gApplicationsPane.isValidHandlerApp(this.preferredApplicationHandler)) {
285 if (this.wrappedHandlerInfo.hasDefaultHandler)
286 return Ci.nsIHandlerInfo.useSystemDefault;
287 else
288 return Ci.nsIHandlerInfo.saveToDisk;
289 }
291 return this.wrappedHandlerInfo.preferredAction;
292 },
294 set preferredAction(aNewValue) {
295 // We don't modify the preferred action if the new action is to use a plugin
296 // because handler info objects don't understand our custom "use plugin"
297 // value. Also, leaving it untouched means that we can automatically revert
298 // to the old setting if the user ever removes the plugin.
300 if (aNewValue != kActionUsePlugin)
301 this.wrappedHandlerInfo.preferredAction = aNewValue;
302 },
304 get alwaysAskBeforeHandling() {
305 // If this type is handled only by a plugin, we can't trust the value
306 // in the handler info object, since it'll be a default based on the absence
307 // of any user configuration, and the default in that case is to always ask,
308 // even though we never ask for content handled by a plugin, so special case
309 // plugin-handled types by returning false here.
310 if (this.pluginName && this.handledOnlyByPlugin)
311 return false;
313 // If this is a protocol type and the preferred action is "save to disk",
314 // which is invalid for such types, then return true here to override that
315 // action. This could happen when the preferred action is to use a helper
316 // app, but the preferredApplicationHandler is invalid, and there isn't
317 // a default handler, so the preferredAction getter returns save to disk
318 // instead.
319 if (!(this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
320 this.preferredAction == Ci.nsIHandlerInfo.saveToDisk)
321 return true;
323 return this.wrappedHandlerInfo.alwaysAskBeforeHandling;
324 },
326 set alwaysAskBeforeHandling(aNewValue) {
327 this.wrappedHandlerInfo.alwaysAskBeforeHandling = aNewValue;
328 },
331 //**************************************************************************//
332 // nsIMIMEInfo
334 // The primary file extension associated with this type, if any.
335 //
336 // XXX Plugin objects contain an array of MimeType objects with "suffixes"
337 // properties; if this object has an associated plugin, shouldn't we check
338 // those properties for an extension?
339 get primaryExtension() {
340 try {
341 if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
342 this.wrappedHandlerInfo.primaryExtension)
343 return this.wrappedHandlerInfo.primaryExtension
344 } catch(ex) {}
346 return null;
347 },
350 //**************************************************************************//
351 // Plugin Handling
353 // A plugin that can handle this type, if any.
354 //
355 // Note: just because we have one doesn't mean it *will* handle the type.
356 // That depends on whether or not the type is in the list of types for which
357 // plugin handling is disabled.
358 plugin: null,
360 // Whether or not this type is only handled by a plugin or is also handled
361 // by some user-configured action as specified in the handler info object.
362 //
363 // Note: we can't just check if there's a handler info object for this type,
364 // because OS and user configuration is mixed up in the handler info object,
365 // so we always need to retrieve it for the OS info and can't tell whether
366 // it represents only OS-default information or user-configured information.
367 //
368 // FIXME: once handler info records are broken up into OS-provided records
369 // and user-configured records, stop using this boolean flag and simply
370 // check for the presence of a user-configured record to determine whether
371 // or not this type is only handled by a plugin. Filed as bug 395142.
372 handledOnlyByPlugin: undefined,
374 get isDisabledPluginType() {
375 return this._getDisabledPluginTypes().indexOf(this.type) != -1;
376 },
378 _getDisabledPluginTypes: function() {
379 var types = "";
381 if (this._prefSvc.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES))
382 types = this._prefSvc.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
384 // Only split if the string isn't empty so we don't end up with an array
385 // containing a single empty string.
386 if (types != "")
387 return types.split(",");
389 return [];
390 },
392 disablePluginType: function() {
393 var disabledPluginTypes = this._getDisabledPluginTypes();
395 if (disabledPluginTypes.indexOf(this.type) == -1)
396 disabledPluginTypes.push(this.type);
398 this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
399 disabledPluginTypes.join(","));
401 // Update the category manager so existing browser windows update.
402 this._categoryMgr.deleteCategoryEntry("Gecko-Content-Viewers",
403 this.type,
404 false);
405 },
407 enablePluginType: function() {
408 var disabledPluginTypes = this._getDisabledPluginTypes();
410 var type = this.type;
411 disabledPluginTypes = disabledPluginTypes.filter(function(v) v != type);
413 this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
414 disabledPluginTypes.join(","));
416 // Update the category manager so existing browser windows update.
417 this._categoryMgr.
418 addCategoryEntry("Gecko-Content-Viewers",
419 this.type,
420 "@mozilla.org/content/plugin/document-loader-factory;1",
421 false,
422 true);
423 },
426 //**************************************************************************//
427 // Storage
429 store: function() {
430 this._handlerSvc.store(this.wrappedHandlerInfo);
431 },
434 //**************************************************************************//
435 // Icons
437 get smallIcon() {
438 return this._getIcon(16);
439 },
441 _getIcon: function(aSize) {
442 if (this.primaryExtension)
443 return "moz-icon://goat." + this.primaryExtension + "?size=" + aSize;
445 if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo)
446 return "moz-icon://goat?size=" + aSize + "&contentType=" + this.type;
448 // FIXME: consider returning some generic icon when we can't get a URL for
449 // one (for example in the case of protocol schemes). Filed as bug 395141.
450 return null;
451 }
453 };
456 //****************************************************************************//
457 // Feed Handler Info
459 /**
460 * This object implements nsIHandlerInfo for the feed types. It's a separate
461 * object because we currently store handling information for the feed type
462 * in a set of preferences rather than the nsIHandlerService-managed datastore.
463 *
464 * This object inherits from HandlerInfoWrapper in order to get functionality
465 * that isn't special to the feed type.
466 *
467 * XXX Should we inherit from HandlerInfoWrapper? After all, we override
468 * most of that wrapper's properties and methods, and we have to dance around
469 * the fact that the wrapper expects to have a wrappedHandlerInfo, which we
470 * don't provide.
471 */
473 function FeedHandlerInfo(aMIMEType) {
474 HandlerInfoWrapper.call(this, aMIMEType, null);
475 }
477 FeedHandlerInfo.prototype = {
478 __proto__: HandlerInfoWrapper.prototype,
480 //**************************************************************************//
481 // Convenience Utils
483 _converterSvc:
484 Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
485 getService(Ci.nsIWebContentConverterService),
487 _shellSvc:
488 #ifdef HAVE_SHELL_SERVICE
489 getShellService(),
490 #else
491 null,
492 #endif
495 //**************************************************************************//
496 // nsIHandlerInfo
498 get description() {
499 return this.element("bundlePreferences").getString(this._appPrefLabel);
500 },
502 get preferredApplicationHandler() {
503 switch (this.element(this._prefSelectedReader).value) {
504 case "client":
505 var file = this.element(this._prefSelectedApp).value;
506 if (file)
507 return getLocalHandlerApp(file);
509 return null;
511 case "web":
512 var uri = this.element(this._prefSelectedWeb).value;
513 if (!uri)
514 return null;
515 return this._converterSvc.getWebContentHandlerByURI(this.type, uri);
517 case "bookmarks":
518 default:
519 // When the pref is set to bookmarks, we handle feeds internally,
520 // we don't forward them to a local or web handler app, so there is
521 // no preferred handler.
522 return null;
523 }
524 },
526 set preferredApplicationHandler(aNewValue) {
527 if (aNewValue instanceof Ci.nsILocalHandlerApp) {
528 this.element(this._prefSelectedApp).value = aNewValue.executable;
529 this.element(this._prefSelectedReader).value = "client";
530 }
531 else if (aNewValue instanceof Ci.nsIWebContentHandlerInfo) {
532 this.element(this._prefSelectedWeb).value = aNewValue.uri;
533 this.element(this._prefSelectedReader).value = "web";
534 // Make the web handler be the new "auto handler" for feeds.
535 // Note: we don't have to unregister the auto handler when the user picks
536 // a non-web handler (local app, Live Bookmarks, etc.) because the service
537 // only uses the "auto handler" when the selected reader is a web handler.
538 // We also don't have to unregister it when the user turns on "always ask"
539 // (i.e. preview in browser), since that also overrides the auto handler.
540 this._converterSvc.setAutoHandler(this.type, aNewValue);
541 }
542 },
544 _possibleApplicationHandlers: null,
546 get possibleApplicationHandlers() {
547 if (this._possibleApplicationHandlers)
548 return this._possibleApplicationHandlers;
550 // A minimal implementation of nsIMutableArray. It only supports the two
551 // methods its callers invoke, namely appendElement and nsIArray::enumerate.
552 this._possibleApplicationHandlers = {
553 _inner: [],
554 _removed: [],
556 QueryInterface: function(aIID) {
557 if (aIID.equals(Ci.nsIMutableArray) ||
558 aIID.equals(Ci.nsIArray) ||
559 aIID.equals(Ci.nsISupports))
560 return this;
562 throw Cr.NS_ERROR_NO_INTERFACE;
563 },
565 get length() {
566 return this._inner.length;
567 },
569 enumerate: function() {
570 return new ArrayEnumerator(this._inner);
571 },
573 appendElement: function(aHandlerApp, aWeak) {
574 this._inner.push(aHandlerApp);
575 },
577 removeElementAt: function(aIndex) {
578 this._removed.push(this._inner[aIndex]);
579 this._inner.splice(aIndex, 1);
580 },
582 queryElementAt: function(aIndex, aInterface) {
583 return this._inner[aIndex].QueryInterface(aInterface);
584 }
585 };
587 // Add the selected local app if it's different from the OS default handler.
588 // Unlike for other types, we can store only one local app at a time for the
589 // feed type, since we store it in a preference that historically stores
590 // only a single path. But we display all the local apps the user chooses
591 // while the prefpane is open, only dropping the list when the user closes
592 // the prefpane, for maximum usability and consistency with other types.
593 var preferredAppFile = this.element(this._prefSelectedApp).value;
594 if (preferredAppFile) {
595 let preferredApp = getLocalHandlerApp(preferredAppFile);
596 let defaultApp = this._defaultApplicationHandler;
597 if (!defaultApp || !defaultApp.equals(preferredApp))
598 this._possibleApplicationHandlers.appendElement(preferredApp, false);
599 }
601 // Add the registered web handlers. There can be any number of these.
602 var webHandlers = this._converterSvc.getContentHandlers(this.type);
603 for each (let webHandler in webHandlers)
604 this._possibleApplicationHandlers.appendElement(webHandler, false);
606 return this._possibleApplicationHandlers;
607 },
609 __defaultApplicationHandler: undefined,
610 get _defaultApplicationHandler() {
611 if (typeof this.__defaultApplicationHandler != "undefined")
612 return this.__defaultApplicationHandler;
614 var defaultFeedReader = null;
615 #ifdef HAVE_SHELL_SERVICE
616 try {
617 defaultFeedReader = this._shellSvc.defaultFeedReader;
618 }
619 catch(ex) {
620 // no default reader or _shellSvc is null
621 }
622 #endif
624 if (defaultFeedReader) {
625 let handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
626 createInstance(Ci.nsIHandlerApp);
627 handlerApp.name = getFileDisplayName(defaultFeedReader);
628 handlerApp.QueryInterface(Ci.nsILocalHandlerApp);
629 handlerApp.executable = defaultFeedReader;
631 this.__defaultApplicationHandler = handlerApp;
632 }
633 else {
634 this.__defaultApplicationHandler = null;
635 }
637 return this.__defaultApplicationHandler;
638 },
640 get hasDefaultHandler() {
641 #ifdef HAVE_SHELL_SERVICE
642 try {
643 if (this._shellSvc.defaultFeedReader)
644 return true;
645 }
646 catch(ex) {
647 // no default reader or _shellSvc is null
648 }
649 #endif
651 return false;
652 },
654 get defaultDescription() {
655 if (this.hasDefaultHandler)
656 return this._defaultApplicationHandler.name;
658 // Should we instead return null?
659 return "";
660 },
662 // What to do with content of this type.
663 get preferredAction() {
664 switch (this.element(this._prefSelectedAction).value) {
666 case "bookmarks":
667 return Ci.nsIHandlerInfo.handleInternally;
669 case "reader": {
670 let preferredApp = this.preferredApplicationHandler;
671 let defaultApp = this._defaultApplicationHandler;
673 // If we have a valid preferred app, return useSystemDefault if it's
674 // the default app; otherwise return useHelperApp.
675 if (gApplicationsPane.isValidHandlerApp(preferredApp)) {
676 if (defaultApp && defaultApp.equals(preferredApp))
677 return Ci.nsIHandlerInfo.useSystemDefault;
679 return Ci.nsIHandlerInfo.useHelperApp;
680 }
682 // The pref is set to "reader", but we don't have a valid preferred app.
683 // What do we do now? Not sure this is the best option (perhaps we
684 // should direct the user to the default app, if any), but for now let's
685 // direct the user to live bookmarks.
686 return Ci.nsIHandlerInfo.handleInternally;
687 }
689 // If the action is "ask", then alwaysAskBeforeHandling will override
690 // the action, so it doesn't matter what we say it is, it just has to be
691 // something that doesn't cause the controller to hide the type.
692 case "ask":
693 default:
694 return Ci.nsIHandlerInfo.handleInternally;
695 }
696 },
698 set preferredAction(aNewValue) {
699 switch (aNewValue) {
701 case Ci.nsIHandlerInfo.handleInternally:
702 this.element(this._prefSelectedReader).value = "bookmarks";
703 break;
705 case Ci.nsIHandlerInfo.useHelperApp:
706 this.element(this._prefSelectedAction).value = "reader";
707 // The controller has already set preferredApplicationHandler
708 // to the new helper app.
709 break;
711 case Ci.nsIHandlerInfo.useSystemDefault:
712 this.element(this._prefSelectedAction).value = "reader";
713 this.preferredApplicationHandler = this._defaultApplicationHandler;
714 break;
715 }
716 },
718 get alwaysAskBeforeHandling() {
719 return this.element(this._prefSelectedAction).value == "ask";
720 },
722 set alwaysAskBeforeHandling(aNewValue) {
723 if (aNewValue == true)
724 this.element(this._prefSelectedAction).value = "ask";
725 else
726 this.element(this._prefSelectedAction).value = "reader";
727 },
729 // Whether or not we are currently storing the action selected by the user.
730 // We use this to suppress notification-triggered updates to the list when
731 // we make changes that may spawn such updates, specifically when we change
732 // the action for the feed type, which results in feed preference updates,
733 // which spawn "pref changed" notifications that would otherwise cause us
734 // to rebuild the view unnecessarily.
735 _storingAction: false,
738 //**************************************************************************//
739 // nsIMIMEInfo
741 get primaryExtension() {
742 return "xml";
743 },
746 //**************************************************************************//
747 // Storage
749 // Changes to the preferred action and handler take effect immediately
750 // (we write them out to the preferences right as they happen),
751 // so we when the controller calls store() after modifying the handlers,
752 // the only thing we need to store is the removal of possible handlers
753 // XXX Should we hold off on making the changes until this method gets called?
754 store: function() {
755 for each (let app in this._possibleApplicationHandlers._removed) {
756 if (app instanceof Ci.nsILocalHandlerApp) {
757 let pref = this.element(PREF_FEED_SELECTED_APP);
758 var preferredAppFile = pref.value;
759 if (preferredAppFile) {
760 let preferredApp = getLocalHandlerApp(preferredAppFile);
761 if (app.equals(preferredApp))
762 pref.reset();
763 }
764 }
765 else {
766 app.QueryInterface(Ci.nsIWebContentHandlerInfo);
767 this._converterSvc.removeContentHandler(app.contentType, app.uri);
768 }
769 }
770 this._possibleApplicationHandlers._removed = [];
771 },
774 //**************************************************************************//
775 // Icons
777 get smallIcon() {
778 return this._smallIcon;
779 }
781 };
783 var feedHandlerInfo = {
784 __proto__: new FeedHandlerInfo(TYPE_MAYBE_FEED),
785 _prefSelectedApp: PREF_FEED_SELECTED_APP,
786 _prefSelectedWeb: PREF_FEED_SELECTED_WEB,
787 _prefSelectedAction: PREF_FEED_SELECTED_ACTION,
788 _prefSelectedReader: PREF_FEED_SELECTED_READER,
789 _smallIcon: "chrome://browser/skin/feeds/feedIcon16.png",
790 _appPrefLabel: "webFeed"
791 }
793 var videoFeedHandlerInfo = {
794 __proto__: new FeedHandlerInfo(TYPE_MAYBE_VIDEO_FEED),
795 _prefSelectedApp: PREF_VIDEO_FEED_SELECTED_APP,
796 _prefSelectedWeb: PREF_VIDEO_FEED_SELECTED_WEB,
797 _prefSelectedAction: PREF_VIDEO_FEED_SELECTED_ACTION,
798 _prefSelectedReader: PREF_VIDEO_FEED_SELECTED_READER,
799 _smallIcon: "chrome://browser/skin/feeds/videoFeedIcon16.png",
800 _appPrefLabel: "videoPodcastFeed"
801 }
803 var audioFeedHandlerInfo = {
804 __proto__: new FeedHandlerInfo(TYPE_MAYBE_AUDIO_FEED),
805 _prefSelectedApp: PREF_AUDIO_FEED_SELECTED_APP,
806 _prefSelectedWeb: PREF_AUDIO_FEED_SELECTED_WEB,
807 _prefSelectedAction: PREF_AUDIO_FEED_SELECTED_ACTION,
808 _prefSelectedReader: PREF_AUDIO_FEED_SELECTED_READER,
809 _smallIcon: "chrome://browser/skin/feeds/audioFeedIcon16.png",
810 _appPrefLabel: "audioPodcastFeed"
811 }
813 /**
814 * InternalHandlerInfoWrapper provides a basic mechanism to create an internal
815 * mime type handler that can be enabled/disabled in the applications preference
816 * menu.
817 */
818 function InternalHandlerInfoWrapper(aMIMEType) {
819 var mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
820 var handlerInfo = mimeSvc.getFromTypeAndExtension(aMIMEType, null);
822 HandlerInfoWrapper.call(this, aMIMEType, handlerInfo);
823 }
825 InternalHandlerInfoWrapper.prototype = {
826 __proto__: HandlerInfoWrapper.prototype,
828 // Override store so we so we can notify any code listening for registration
829 // or unregistration of this handler.
830 store: function() {
831 HandlerInfoWrapper.prototype.store.call(this);
832 Services.obs.notifyObservers(null, this._handlerChanged, null);
833 },
835 get enabled() {
836 throw Cr.NS_ERROR_NOT_IMPLEMENTED;
837 },
839 get description() {
840 return this.element("bundlePreferences").getString(this._appPrefLabel);
841 }
842 };
844 var pdfHandlerInfo = {
845 __proto__: new InternalHandlerInfoWrapper(TYPE_PDF),
846 _handlerChanged: TOPIC_PDFJS_HANDLER_CHANGED,
847 _appPrefLabel: "portableDocumentFormat",
848 get enabled() {
849 return !Services.prefs.getBoolPref(PREF_PDFJS_DISABLED);
850 },
851 };
854 //****************************************************************************//
855 // Prefpane Controller
857 var gApplicationsPane = {
858 // The set of types the app knows how to handle. A hash of HandlerInfoWrapper
859 // objects, indexed by type.
860 _handledTypes: {},
862 // The list of types we can show, sorted by the sort column/direction.
863 // An array of HandlerInfoWrapper objects. We build this list when we first
864 // load the data and then rebuild it when users change a pref that affects
865 // what types we can show or change the sort column/direction.
866 // Note: this isn't necessarily the list of types we *will* show; if the user
867 // provides a filter string, we'll only show the subset of types in this list
868 // that match that string.
869 _visibleTypes: [],
871 // A count of the number of times each visible type description appears.
872 // We use these counts to determine whether or not to annotate descriptions
873 // with their types to distinguish duplicate descriptions from each other.
874 // A hash of integer counts, indexed by string description.
875 _visibleTypeDescriptionCount: {},
878 //**************************************************************************//
879 // Convenience & Performance Shortcuts
881 // These get defined by init().
882 _brandShortName : null,
883 _prefsBundle : null,
884 _list : null,
885 _filter : null,
887 _prefSvc : Cc["@mozilla.org/preferences-service;1"].
888 getService(Ci.nsIPrefBranch),
890 _mimeSvc : Cc["@mozilla.org/mime;1"].
891 getService(Ci.nsIMIMEService),
893 _helperAppSvc : Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
894 getService(Ci.nsIExternalHelperAppService),
896 _handlerSvc : Cc["@mozilla.org/uriloader/handler-service;1"].
897 getService(Ci.nsIHandlerService),
899 _ioSvc : Cc["@mozilla.org/network/io-service;1"].
900 getService(Ci.nsIIOService),
903 //**************************************************************************//
904 // Initialization & Destruction
906 init: function() {
907 // Initialize shortcuts to some commonly accessed elements & values.
908 this._brandShortName =
909 document.getElementById("bundleBrand").getString("brandShortName");
910 this._prefsBundle = document.getElementById("bundlePreferences");
911 this._list = document.getElementById("handlersView");
912 this._filter = document.getElementById("filter");
914 // Observe preferences that influence what we display so we can rebuild
915 // the view when they change.
916 this._prefSvc.addObserver(PREF_SHOW_PLUGINS_IN_LIST, this, false);
917 this._prefSvc.addObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this, false);
918 this._prefSvc.addObserver(PREF_FEED_SELECTED_APP, this, false);
919 this._prefSvc.addObserver(PREF_FEED_SELECTED_WEB, this, false);
920 this._prefSvc.addObserver(PREF_FEED_SELECTED_ACTION, this, false);
921 this._prefSvc.addObserver(PREF_FEED_SELECTED_READER, this, false);
923 this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_APP, this, false);
924 this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_WEB, this, false);
925 this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this, false);
926 this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_READER, this, false);
928 this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_APP, this, false);
929 this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_WEB, this, false);
930 this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this, false);
931 this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_READER, this, false);
934 // Listen for window unload so we can remove our preference observers.
935 window.addEventListener("unload", this, false);
937 // Figure out how we should be sorting the list. We persist sort settings
938 // across sessions, so we can't assume the default sort column/direction.
939 // XXX should we be using the XUL sort service instead?
940 if (document.getElementById("actionColumn").hasAttribute("sortDirection")) {
941 this._sortColumn = document.getElementById("actionColumn");
942 // The typeColumn element always has a sortDirection attribute,
943 // either because it was persisted or because the default value
944 // from the xul file was used. If we are sorting on the other
945 // column, we should remove it.
946 document.getElementById("typeColumn").removeAttribute("sortDirection");
947 }
948 else
949 this._sortColumn = document.getElementById("typeColumn");
951 // Load the data and build the list of handlers.
952 // By doing this in a timeout, we let the preferences dialog resize itself
953 // to an appropriate size before we add a bunch of items to the list.
954 // Otherwise, if there are many items, and the Applications prefpane
955 // is the one that gets displayed when the user first opens the dialog,
956 // the dialog might stretch too much in an attempt to fit them all in.
957 // XXX Shouldn't we perhaps just set a max-height on the richlistbox?
958 var _delayedPaneLoad = function(self) {
959 self._loadData();
960 self._rebuildVisibleTypes();
961 self._sortVisibleTypes();
962 self._rebuildView();
964 // Notify observers that the UI is now ready
965 Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService).
966 notifyObservers(window, "app-handler-pane-loaded", null);
967 }
968 setTimeout(_delayedPaneLoad, 0, this);
969 },
971 destroy: function() {
972 window.removeEventListener("unload", this, false);
973 this._prefSvc.removeObserver(PREF_SHOW_PLUGINS_IN_LIST, this);
974 this._prefSvc.removeObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this);
975 this._prefSvc.removeObserver(PREF_FEED_SELECTED_APP, this);
976 this._prefSvc.removeObserver(PREF_FEED_SELECTED_WEB, this);
977 this._prefSvc.removeObserver(PREF_FEED_SELECTED_ACTION, this);
978 this._prefSvc.removeObserver(PREF_FEED_SELECTED_READER, this);
980 this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_APP, this);
981 this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_WEB, this);
982 this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this);
983 this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_READER, this);
985 this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_APP, this);
986 this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_WEB, this);
987 this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this);
988 this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_READER, this);
989 },
992 //**************************************************************************//
993 // nsISupports
995 QueryInterface: function(aIID) {
996 if (aIID.equals(Ci.nsIObserver) ||
997 aIID.equals(Ci.nsIDOMEventListener ||
998 aIID.equals(Ci.nsISupports)))
999 return this;
1001 throw Cr.NS_ERROR_NO_INTERFACE;
1002 },
1005 //**************************************************************************//
1006 // nsIObserver
1008 observe: function (aSubject, aTopic, aData) {
1009 // Rebuild the list when there are changes to preferences that influence
1010 // whether or not to show certain entries in the list.
1011 if (aTopic == "nsPref:changed" && !this._storingAction) {
1012 // These two prefs alter the list of visible types, so we have to rebuild
1013 // that list when they change.
1014 if (aData == PREF_SHOW_PLUGINS_IN_LIST ||
1015 aData == PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS) {
1016 this._rebuildVisibleTypes();
1017 this._sortVisibleTypes();
1018 }
1020 // All the prefs we observe can affect what we display, so we rebuild
1021 // the view when any of them changes.
1022 this._rebuildView();
1023 }
1024 },
1027 //**************************************************************************//
1028 // nsIDOMEventListener
1030 handleEvent: function(aEvent) {
1031 if (aEvent.type == "unload") {
1032 this.destroy();
1033 }
1034 },
1037 //**************************************************************************//
1038 // Composed Model Construction
1040 _loadData: function() {
1041 this._loadFeedHandler();
1042 this._loadInternalHandlers();
1043 this._loadPluginHandlers();
1044 this._loadApplicationHandlers();
1045 },
1047 _loadFeedHandler: function() {
1048 this._handledTypes[TYPE_MAYBE_FEED] = feedHandlerInfo;
1049 feedHandlerInfo.handledOnlyByPlugin = false;
1051 this._handledTypes[TYPE_MAYBE_VIDEO_FEED] = videoFeedHandlerInfo;
1052 videoFeedHandlerInfo.handledOnlyByPlugin = false;
1054 this._handledTypes[TYPE_MAYBE_AUDIO_FEED] = audioFeedHandlerInfo;
1055 audioFeedHandlerInfo.handledOnlyByPlugin = false;
1056 },
1058 /**
1059 * Load higher level internal handlers so they can be turned on/off in the
1060 * applications menu.
1061 */
1062 _loadInternalHandlers: function() {
1063 var internalHandlers = [pdfHandlerInfo];
1064 for (let internalHandler of internalHandlers) {
1065 if (internalHandler.enabled) {
1066 this._handledTypes[internalHandler.type] = internalHandler;
1067 }
1068 }
1069 },
1071 /**
1072 * Load the set of handlers defined by plugins.
1073 *
1074 * Note: if there's more than one plugin for a given MIME type, we assume
1075 * the last one is the one that the application will use. That may not be
1076 * correct, but it's how we've been doing it for years.
1077 *
1078 * Perhaps we should instead query navigator.mimeTypes for the set of types
1079 * supported by the application and then get the plugin from each MIME type's
1080 * enabledPlugin property. But if there's a plugin for a type, we need
1081 * to know about it even if it isn't enabled, since we're going to give
1082 * the user an option to enable it.
1083 *
1084 * Also note that enabledPlugin does not get updated when
1085 * plugin.disable_full_page_plugin_for_types changes, so even if we could use
1086 * enabledPlugin to get the plugin that would be used, we'd still need to
1087 * check the pref ourselves to find out if it's enabled.
1088 */
1089 _loadPluginHandlers: function() {
1090 "use strict";
1092 let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
1093 let pluginTags = pluginHost.getPluginTags();
1095 for (let i = 0; i < pluginTags.length; ++i) {
1096 let pluginTag = pluginTags[i];
1098 let mimeTypes = pluginTag.getMimeTypes();
1099 for (let j = 0; j < mimeTypes.length; ++j) {
1100 let type = mimeTypes[j];
1102 let handlerInfoWrapper;
1103 if (type in this._handledTypes)
1104 handlerInfoWrapper = this._handledTypes[type];
1105 else {
1106 let wrappedHandlerInfo =
1107 this._mimeSvc.getFromTypeAndExtension(type, null);
1108 handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo);
1109 handlerInfoWrapper.handledOnlyByPlugin = true;
1110 this._handledTypes[type] = handlerInfoWrapper;
1111 }
1113 handlerInfoWrapper.pluginName = pluginTag.name;
1114 }
1115 }
1116 },
1118 /**
1119 * Load the set of handlers defined by the application datastore.
1120 */
1121 _loadApplicationHandlers: function() {
1122 var wrappedHandlerInfos = this._handlerSvc.enumerate();
1123 while (wrappedHandlerInfos.hasMoreElements()) {
1124 let wrappedHandlerInfo =
1125 wrappedHandlerInfos.getNext().QueryInterface(Ci.nsIHandlerInfo);
1126 let type = wrappedHandlerInfo.type;
1128 let handlerInfoWrapper;
1129 if (type in this._handledTypes)
1130 handlerInfoWrapper = this._handledTypes[type];
1131 else {
1132 handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo);
1133 this._handledTypes[type] = handlerInfoWrapper;
1134 }
1136 handlerInfoWrapper.handledOnlyByPlugin = false;
1137 }
1138 },
1141 //**************************************************************************//
1142 // View Construction
1144 _rebuildVisibleTypes: function() {
1145 // Reset the list of visible types and the visible type description counts.
1146 this._visibleTypes = [];
1147 this._visibleTypeDescriptionCount = {};
1149 // Get the preferences that help determine what types to show.
1150 var showPlugins = this._prefSvc.getBoolPref(PREF_SHOW_PLUGINS_IN_LIST);
1151 var hidePluginsWithoutExtensions =
1152 this._prefSvc.getBoolPref(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS);
1154 for (let type in this._handledTypes) {
1155 let handlerInfo = this._handledTypes[type];
1157 // Hide plugins without associated extensions if so prefed so we don't
1158 // show a whole bunch of obscure types handled by plugins on Mac.
1159 // Note: though protocol types don't have extensions, we still show them;
1160 // the pref is only meant to be applied to MIME types, since plugins are
1161 // only associated with MIME types.
1162 // FIXME: should we also check the "suffixes" property of the plugin?
1163 // Filed as bug 395135.
1164 if (hidePluginsWithoutExtensions && handlerInfo.handledOnlyByPlugin &&
1165 handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
1166 !handlerInfo.primaryExtension)
1167 continue;
1169 // Hide types handled only by plugins if so prefed.
1170 if (handlerInfo.handledOnlyByPlugin && !showPlugins)
1171 continue;
1173 // We couldn't find any reason to exclude the type, so include it.
1174 this._visibleTypes.push(handlerInfo);
1176 if (handlerInfo.description in this._visibleTypeDescriptionCount)
1177 this._visibleTypeDescriptionCount[handlerInfo.description]++;
1178 else
1179 this._visibleTypeDescriptionCount[handlerInfo.description] = 1;
1180 }
1181 },
1183 _rebuildView: function() {
1184 // Clear the list of entries.
1185 while (this._list.childNodes.length > 1)
1186 this._list.removeChild(this._list.lastChild);
1188 var visibleTypes = this._visibleTypes;
1190 // If the user is filtering the list, then only show matching types.
1191 if (this._filter.value)
1192 visibleTypes = visibleTypes.filter(this._matchesFilter, this);
1194 for each (let visibleType in visibleTypes) {
1195 let item = document.createElement("richlistitem");
1196 item.setAttribute("type", visibleType.type);
1197 item.setAttribute("typeDescription", this._describeType(visibleType));
1198 if (visibleType.smallIcon)
1199 item.setAttribute("typeIcon", visibleType.smallIcon);
1200 item.setAttribute("actionDescription",
1201 this._describePreferredAction(visibleType));
1203 if (!this._setIconClassForPreferredAction(visibleType, item)) {
1204 item.setAttribute("actionIcon",
1205 this._getIconURLForPreferredAction(visibleType));
1206 }
1208 this._list.appendChild(item);
1209 }
1211 this._selectLastSelectedType();
1212 },
1214 _matchesFilter: function(aType) {
1215 var filterValue = this._filter.value.toLowerCase();
1216 return this._describeType(aType).toLowerCase().indexOf(filterValue) != -1 ||
1217 this._describePreferredAction(aType).toLowerCase().indexOf(filterValue) != -1;
1218 },
1220 /**
1221 * Describe, in a human-readable fashion, the type represented by the given
1222 * handler info object. Normally this is just the description provided by
1223 * the info object, but if more than one object presents the same description,
1224 * then we annotate the duplicate descriptions with the type itself to help
1225 * users distinguish between those types.
1226 *
1227 * @param aHandlerInfo {nsIHandlerInfo} the type being described
1228 * @returns {string} a description of the type
1229 */
1230 _describeType: function(aHandlerInfo) {
1231 if (this._visibleTypeDescriptionCount[aHandlerInfo.description] > 1)
1232 return this._prefsBundle.getFormattedString("typeDescriptionWithType",
1233 [aHandlerInfo.description,
1234 aHandlerInfo.type]);
1236 return aHandlerInfo.description;
1237 },
1239 /**
1240 * Describe, in a human-readable fashion, the preferred action to take on
1241 * the type represented by the given handler info object.
1242 *
1243 * XXX Should this be part of the HandlerInfoWrapper interface? It would
1244 * violate the separation of model and view, but it might make more sense
1245 * nonetheless (f.e. it would make sortTypes easier).
1246 *
1247 * @param aHandlerInfo {nsIHandlerInfo} the type whose preferred action
1248 * is being described
1249 * @returns {string} a description of the action
1250 */
1251 _describePreferredAction: function(aHandlerInfo) {
1252 // alwaysAskBeforeHandling overrides the preferred action, so if that flag
1253 // is set, then describe that behavior instead. For most types, this is
1254 // the "alwaysAsk" string, but for the feed type we show something special.
1255 if (aHandlerInfo.alwaysAskBeforeHandling) {
1256 if (isFeedType(aHandlerInfo.type))
1257 return this._prefsBundle.getFormattedString("previewInApp",
1258 [this._brandShortName]);
1259 else
1260 return this._prefsBundle.getString("alwaysAsk");
1261 }
1263 switch (aHandlerInfo.preferredAction) {
1264 case Ci.nsIHandlerInfo.saveToDisk:
1265 return this._prefsBundle.getString("saveFile");
1267 case Ci.nsIHandlerInfo.useHelperApp:
1268 var preferredApp = aHandlerInfo.preferredApplicationHandler;
1269 var name;
1270 if (preferredApp instanceof Ci.nsILocalHandlerApp)
1271 name = getFileDisplayName(preferredApp.executable);
1272 else
1273 name = preferredApp.name;
1274 return this._prefsBundle.getFormattedString("useApp", [name]);
1276 case Ci.nsIHandlerInfo.handleInternally:
1277 // For the feed type, handleInternally means live bookmarks.
1278 if (isFeedType(aHandlerInfo.type)) {
1279 return this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
1280 [this._brandShortName]);
1281 }
1283 if (aHandlerInfo instanceof InternalHandlerInfoWrapper) {
1284 return this._prefsBundle.getFormattedString("previewInApp",
1285 [this._brandShortName]);
1286 }
1288 // For other types, handleInternally looks like either useHelperApp
1289 // or useSystemDefault depending on whether or not there's a preferred
1290 // handler app.
1291 if (this.isValidHandlerApp(aHandlerInfo.preferredApplicationHandler))
1292 return aHandlerInfo.preferredApplicationHandler.name;
1294 return aHandlerInfo.defaultDescription;
1296 // XXX Why don't we say the app will handle the type internally?
1297 // Is it because the app can't actually do that? But if that's true,
1298 // then why would a preferredAction ever get set to this value
1299 // in the first place?
1301 case Ci.nsIHandlerInfo.useSystemDefault:
1302 return this._prefsBundle.getFormattedString("useDefault",
1303 [aHandlerInfo.defaultDescription]);
1305 case kActionUsePlugin:
1306 return this._prefsBundle.getFormattedString("usePluginIn",
1307 [aHandlerInfo.pluginName,
1308 this._brandShortName]);
1309 }
1310 },
1312 _selectLastSelectedType: function() {
1313 // If the list is disabled by the pref.downloads.disable_button.edit_actions
1314 // preference being locked, then don't select the type, as that would cause
1315 // it to appear selected, with a different background and an actions menu
1316 // that makes it seem like you can choose an action for the type.
1317 if (this._list.disabled)
1318 return;
1320 var lastSelectedType = this._list.getAttribute("lastSelectedType");
1321 if (!lastSelectedType)
1322 return;
1324 var item = this._list.getElementsByAttribute("type", lastSelectedType)[0];
1325 if (!item)
1326 return;
1328 this._list.selectedItem = item;
1329 },
1331 /**
1332 * Whether or not the given handler app is valid.
1333 *
1334 * @param aHandlerApp {nsIHandlerApp} the handler app in question
1335 *
1336 * @returns {boolean} whether or not it's valid
1337 */
1338 isValidHandlerApp: function(aHandlerApp) {
1339 if (!aHandlerApp)
1340 return false;
1342 if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
1343 return this._isValidHandlerExecutable(aHandlerApp.executable);
1345 if (aHandlerApp instanceof Ci.nsIWebHandlerApp)
1346 return aHandlerApp.uriTemplate;
1348 if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
1349 return aHandlerApp.uri;
1351 return false;
1352 },
1354 _isValidHandlerExecutable: function(aExecutable) {
1355 return aExecutable &&
1356 aExecutable.exists() &&
1357 aExecutable.isExecutable() &&
1358 // XXXben - we need to compare this with the running instance executable
1359 // just don't know how to do that via script...
1360 // XXXmano TBD: can probably add this to nsIShellService
1361 #ifdef XP_WIN
1362 #expand aExecutable.leafName != "__MOZ_APP_NAME__.exe";
1363 #else
1364 #ifdef XP_MACOSX
1365 #expand aExecutable.leafName != "__MOZ_MACBUNDLE_NAME__";
1366 #else
1367 #expand aExecutable.leafName != "__MOZ_APP_NAME__-bin";
1368 #endif
1369 #endif
1370 },
1372 /**
1373 * Rebuild the actions menu for the selected entry. Gets called by
1374 * the richlistitem constructor when an entry in the list gets selected.
1375 */
1376 rebuildActionsMenu: function() {
1377 var typeItem = this._list.selectedItem;
1378 var handlerInfo = this._handledTypes[typeItem.type];
1379 var menu =
1380 document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
1381 var menuPopup = menu.menupopup;
1383 // Clear out existing items.
1384 while (menuPopup.hasChildNodes())
1385 menuPopup.removeChild(menuPopup.lastChild);
1387 // Add the "Preview in Firefox" option for optional internal handlers.
1388 if (handlerInfo instanceof InternalHandlerInfoWrapper) {
1389 var internalMenuItem = document.createElement("menuitem");
1390 internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
1391 let label = this._prefsBundle.getFormattedString("previewInApp",
1392 [this._brandShortName]);
1393 internalMenuItem.setAttribute("label", label);
1394 internalMenuItem.setAttribute("tooltiptext", label);
1395 internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
1396 menuPopup.appendChild(internalMenuItem);
1397 }
1399 {
1400 var askMenuItem = document.createElement("menuitem");
1401 askMenuItem.setAttribute("alwaysAsk", "true");
1402 let label;
1403 if (isFeedType(handlerInfo.type))
1404 label = this._prefsBundle.getFormattedString("previewInApp",
1405 [this._brandShortName]);
1406 else
1407 label = this._prefsBundle.getString("alwaysAsk");
1408 askMenuItem.setAttribute("label", label);
1409 askMenuItem.setAttribute("tooltiptext", label);
1410 askMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
1411 menuPopup.appendChild(askMenuItem);
1412 }
1414 // Create a menu item for saving to disk.
1415 // Note: this option isn't available to protocol types, since we don't know
1416 // what it means to save a URL having a certain scheme to disk, nor is it
1417 // available to feeds, since the feed code doesn't implement the capability.
1418 if ((handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
1419 !isFeedType(handlerInfo.type)) {
1420 var saveMenuItem = document.createElement("menuitem");
1421 saveMenuItem.setAttribute("action", Ci.nsIHandlerInfo.saveToDisk);
1422 let label = this._prefsBundle.getString("saveFile");
1423 saveMenuItem.setAttribute("label", label);
1424 saveMenuItem.setAttribute("tooltiptext", label);
1425 saveMenuItem.setAttribute(APP_ICON_ATTR_NAME, "save");
1426 menuPopup.appendChild(saveMenuItem);
1427 }
1429 // If this is the feed type, add a Live Bookmarks item.
1430 if (isFeedType(handlerInfo.type)) {
1431 var internalMenuItem = document.createElement("menuitem");
1432 internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
1433 let label = this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
1434 [this._brandShortName]);
1435 internalMenuItem.setAttribute("label", label);
1436 internalMenuItem.setAttribute("tooltiptext", label);
1437 internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "feed");
1438 menuPopup.appendChild(internalMenuItem);
1439 }
1441 // Add a separator to distinguish these items from the helper app items
1442 // that follow them.
1443 let menuItem = document.createElement("menuseparator");
1444 menuPopup.appendChild(menuItem);
1446 // Create a menu item for the OS default application, if any.
1447 if (handlerInfo.hasDefaultHandler) {
1448 var defaultMenuItem = document.createElement("menuitem");
1449 defaultMenuItem.setAttribute("action", Ci.nsIHandlerInfo.useSystemDefault);
1450 let label = this._prefsBundle.getFormattedString("useDefault",
1451 [handlerInfo.defaultDescription]);
1452 defaultMenuItem.setAttribute("label", label);
1453 defaultMenuItem.setAttribute("tooltiptext", handlerInfo.defaultDescription);
1454 defaultMenuItem.setAttribute("image", this._getIconURLForSystemDefault(handlerInfo));
1456 menuPopup.appendChild(defaultMenuItem);
1457 }
1459 // Create menu items for possible handlers.
1460 let preferredApp = handlerInfo.preferredApplicationHandler;
1461 let possibleApps = handlerInfo.possibleApplicationHandlers.enumerate();
1462 var possibleAppMenuItems = [];
1463 while (possibleApps.hasMoreElements()) {
1464 let possibleApp = possibleApps.getNext();
1465 if (!this.isValidHandlerApp(possibleApp))
1466 continue;
1468 let menuItem = document.createElement("menuitem");
1469 menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp);
1470 let label;
1471 if (possibleApp instanceof Ci.nsILocalHandlerApp)
1472 label = getFileDisplayName(possibleApp.executable);
1473 else
1474 label = possibleApp.name;
1475 label = this._prefsBundle.getFormattedString("useApp", [label]);
1476 menuItem.setAttribute("label", label);
1477 menuItem.setAttribute("tooltiptext", label);
1478 menuItem.setAttribute("image", this._getIconURLForHandlerApp(possibleApp));
1480 // Attach the handler app object to the menu item so we can use it
1481 // to make changes to the datastore when the user selects the item.
1482 menuItem.handlerApp = possibleApp;
1484 menuPopup.appendChild(menuItem);
1485 possibleAppMenuItems.push(menuItem);
1486 }
1488 // Create a menu item for the plugin.
1489 if (handlerInfo.pluginName) {
1490 var pluginMenuItem = document.createElement("menuitem");
1491 pluginMenuItem.setAttribute("action", kActionUsePlugin);
1492 let label = this._prefsBundle.getFormattedString("usePluginIn",
1493 [handlerInfo.pluginName,
1494 this._brandShortName]);
1495 pluginMenuItem.setAttribute("label", label);
1496 pluginMenuItem.setAttribute("tooltiptext", label);
1497 pluginMenuItem.setAttribute(APP_ICON_ATTR_NAME, "plugin");
1498 menuPopup.appendChild(pluginMenuItem);
1499 }
1501 // Create a menu item for selecting a local application.
1502 #ifdef XP_WIN
1503 // On Windows, selecting an application to open another application
1504 // would be meaningless so we special case executables.
1505 var executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService)
1506 .getTypeFromExtension("exe");
1507 if (handlerInfo.type != executableType)
1508 #endif
1509 {
1510 let menuItem = document.createElement("menuitem");
1511 menuItem.setAttribute("oncommand", "gApplicationsPane.chooseApp(event)");
1512 let label = this._prefsBundle.getString("useOtherApp");
1513 menuItem.setAttribute("label", label);
1514 menuItem.setAttribute("tooltiptext", label);
1515 menuPopup.appendChild(menuItem);
1516 }
1518 // Create a menu item for managing applications.
1519 if (possibleAppMenuItems.length) {
1520 let menuItem = document.createElement("menuseparator");
1521 menuPopup.appendChild(menuItem);
1522 menuItem = document.createElement("menuitem");
1523 menuItem.setAttribute("oncommand", "gApplicationsPane.manageApp(event)");
1524 menuItem.setAttribute("label", this._prefsBundle.getString("manageApp"));
1525 menuPopup.appendChild(menuItem);
1526 }
1528 // Select the item corresponding to the preferred action. If the always
1529 // ask flag is set, it overrides the preferred action. Otherwise we pick
1530 // the item identified by the preferred action (when the preferred action
1531 // is to use a helper app, we have to pick the specific helper app item).
1532 if (handlerInfo.alwaysAskBeforeHandling)
1533 menu.selectedItem = askMenuItem;
1534 else switch (handlerInfo.preferredAction) {
1535 case Ci.nsIHandlerInfo.handleInternally:
1536 menu.selectedItem = internalMenuItem;
1537 break;
1538 case Ci.nsIHandlerInfo.useSystemDefault:
1539 menu.selectedItem = defaultMenuItem;
1540 break;
1541 case Ci.nsIHandlerInfo.useHelperApp:
1542 if (preferredApp)
1543 menu.selectedItem =
1544 possibleAppMenuItems.filter(function(v) v.handlerApp.equals(preferredApp))[0];
1545 break;
1546 case kActionUsePlugin:
1547 menu.selectedItem = pluginMenuItem;
1548 break;
1549 case Ci.nsIHandlerInfo.saveToDisk:
1550 menu.selectedItem = saveMenuItem;
1551 break;
1552 }
1553 },
1556 //**************************************************************************//
1557 // Sorting & Filtering
1559 _sortColumn: null,
1561 /**
1562 * Sort the list when the user clicks on a column header.
1563 */
1564 sort: function (event) {
1565 var column = event.target;
1567 // If the user clicked on a new sort column, remove the direction indicator
1568 // from the old column.
1569 if (this._sortColumn && this._sortColumn != column)
1570 this._sortColumn.removeAttribute("sortDirection");
1572 this._sortColumn = column;
1574 // Set (or switch) the sort direction indicator.
1575 if (column.getAttribute("sortDirection") == "ascending")
1576 column.setAttribute("sortDirection", "descending");
1577 else
1578 column.setAttribute("sortDirection", "ascending");
1580 this._sortVisibleTypes();
1581 this._rebuildView();
1582 },
1584 /**
1585 * Sort the list of visible types by the current sort column/direction.
1586 */
1587 _sortVisibleTypes: function() {
1588 if (!this._sortColumn)
1589 return;
1591 var t = this;
1593 function sortByType(a, b) {
1594 return t._describeType(a).toLowerCase().
1595 localeCompare(t._describeType(b).toLowerCase());
1596 }
1598 function sortByAction(a, b) {
1599 return t._describePreferredAction(a).toLowerCase().
1600 localeCompare(t._describePreferredAction(b).toLowerCase());
1601 }
1603 switch (this._sortColumn.getAttribute("value")) {
1604 case "type":
1605 this._visibleTypes.sort(sortByType);
1606 break;
1607 case "action":
1608 this._visibleTypes.sort(sortByAction);
1609 break;
1610 }
1612 if (this._sortColumn.getAttribute("sortDirection") == "descending")
1613 this._visibleTypes.reverse();
1614 },
1616 /**
1617 * Filter the list when the user enters a filter term into the filter field.
1618 */
1619 filter: function() {
1620 this._rebuildView();
1621 },
1623 focusFilterBox: function() {
1624 this._filter.focus();
1625 this._filter.select();
1626 },
1629 //**************************************************************************//
1630 // Changes
1632 onSelectAction: function(aActionItem) {
1633 this._storingAction = true;
1635 try {
1636 this._storeAction(aActionItem);
1637 }
1638 finally {
1639 this._storingAction = false;
1640 }
1641 },
1643 _storeAction: function(aActionItem) {
1644 var typeItem = this._list.selectedItem;
1645 var handlerInfo = this._handledTypes[typeItem.type];
1647 if (aActionItem.hasAttribute("alwaysAsk")) {
1648 handlerInfo.alwaysAskBeforeHandling = true;
1649 }
1650 else if (aActionItem.hasAttribute("action")) {
1651 let action = parseInt(aActionItem.getAttribute("action"));
1653 // Set the plugin state if we're enabling or disabling a plugin.
1654 if (action == kActionUsePlugin)
1655 handlerInfo.enablePluginType();
1656 else if (handlerInfo.pluginName && !handlerInfo.isDisabledPluginType)
1657 handlerInfo.disablePluginType();
1659 // Set the preferred application handler.
1660 // We leave the existing preferred app in the list when we set
1661 // the preferred action to something other than useHelperApp so that
1662 // legacy datastores that don't have the preferred app in the list
1663 // of possible apps still include the preferred app in the list of apps
1664 // the user can choose to handle the type.
1665 if (action == Ci.nsIHandlerInfo.useHelperApp)
1666 handlerInfo.preferredApplicationHandler = aActionItem.handlerApp;
1668 // Set the "always ask" flag.
1669 handlerInfo.alwaysAskBeforeHandling = false;
1671 // Set the preferred action.
1672 handlerInfo.preferredAction = action;
1673 }
1675 handlerInfo.store();
1677 // Make sure the handler info object is flagged to indicate that there is
1678 // now some user configuration for the type.
1679 handlerInfo.handledOnlyByPlugin = false;
1681 // Update the action label and image to reflect the new preferred action.
1682 typeItem.setAttribute("actionDescription",
1683 this._describePreferredAction(handlerInfo));
1684 if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
1685 typeItem.setAttribute("actionIcon",
1686 this._getIconURLForPreferredAction(handlerInfo));
1687 }
1688 },
1690 manageApp: function(aEvent) {
1691 // Don't let the normal "on select action" handler get this event,
1692 // as we handle it specially ourselves.
1693 aEvent.stopPropagation();
1695 var typeItem = this._list.selectedItem;
1696 var handlerInfo = this._handledTypes[typeItem.type];
1698 document.documentElement.openSubDialog("chrome://browser/content/preferences/applicationManager.xul",
1699 "", handlerInfo);
1701 // Rebuild the actions menu so that we revert to the previous selection,
1702 // or "Always ask" if the previous default application has been removed
1703 this.rebuildActionsMenu();
1705 // update the richlistitem too. Will be visible when selecting another row
1706 typeItem.setAttribute("actionDescription",
1707 this._describePreferredAction(handlerInfo));
1708 if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
1709 typeItem.setAttribute("actionIcon",
1710 this._getIconURLForPreferredAction(handlerInfo));
1711 }
1712 },
1714 chooseApp: function(aEvent) {
1715 // Don't let the normal "on select action" handler get this event,
1716 // as we handle it specially ourselves.
1717 aEvent.stopPropagation();
1719 var handlerApp;
1720 let chooseAppCallback = function(aHandlerApp) {
1721 // Rebuild the actions menu whether the user picked an app or canceled.
1722 // If they picked an app, we want to add the app to the menu and select it.
1723 // If they canceled, we want to go back to their previous selection.
1724 this.rebuildActionsMenu();
1726 // If the user picked a new app from the menu, select it.
1727 if (aHandlerApp) {
1728 let typeItem = this._list.selectedItem;
1729 let actionsMenu =
1730 document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
1731 let menuItems = actionsMenu.menupopup.childNodes;
1732 for (let i = 0; i < menuItems.length; i++) {
1733 let menuItem = menuItems[i];
1734 if (menuItem.handlerApp && menuItem.handlerApp.equals(aHandlerApp)) {
1735 actionsMenu.selectedIndex = i;
1736 this.onSelectAction(menuItem);
1737 break;
1738 }
1739 }
1740 }
1741 }.bind(this);
1743 #ifdef XP_WIN
1744 var params = {};
1745 var handlerInfo = this._handledTypes[this._list.selectedItem.type];
1747 if (isFeedType(handlerInfo.type)) {
1748 // MIME info will be null, create a temp object.
1749 params.mimeInfo = this._mimeSvc.getFromTypeAndExtension(handlerInfo.type,
1750 handlerInfo.primaryExtension);
1751 } else {
1752 params.mimeInfo = handlerInfo.wrappedHandlerInfo;
1753 }
1755 params.title = this._prefsBundle.getString("fpTitleChooseApp");
1756 params.description = handlerInfo.description;
1757 params.filename = null;
1758 params.handlerApp = null;
1760 window.openDialog("chrome://global/content/appPicker.xul", null,
1761 "chrome,modal,centerscreen,titlebar,dialog=yes",
1762 params);
1764 if (this.isValidHandlerApp(params.handlerApp)) {
1765 handlerApp = params.handlerApp;
1767 // Add the app to the type's list of possible handlers.
1768 handlerInfo.addPossibleApplicationHandler(handlerApp);
1769 }
1771 chooseAppCallback(handlerApp);
1772 #else
1773 let winTitle = this._prefsBundle.getString("fpTitleChooseApp");
1774 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
1775 let fpCallback = function fpCallback_done(aResult) {
1776 if (aResult == Ci.nsIFilePicker.returnOK && fp.file &&
1777 this._isValidHandlerExecutable(fp.file)) {
1778 handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
1779 createInstance(Ci.nsILocalHandlerApp);
1780 handlerApp.name = getFileDisplayName(fp.file);
1781 handlerApp.executable = fp.file;
1783 // Add the app to the type's list of possible handlers.
1784 let handlerInfo = this._handledTypes[this._list.selectedItem.type];
1785 handlerInfo.addPossibleApplicationHandler(handlerApp);
1787 chooseAppCallback(handlerApp);
1788 }
1789 }.bind(this);
1791 // Prompt the user to pick an app. If they pick one, and it's a valid
1792 // selection, then add it to the list of possible handlers.
1793 fp.init(window, winTitle, Ci.nsIFilePicker.modeOpen);
1794 fp.appendFilters(Ci.nsIFilePicker.filterApps);
1795 fp.open(fpCallback);
1796 #endif
1797 },
1799 // Mark which item in the list was last selected so we can reselect it
1800 // when we rebuild the list or when the user returns to the prefpane.
1801 onSelectionChanged: function() {
1802 if (this._list.selectedItem)
1803 this._list.setAttribute("lastSelectedType",
1804 this._list.selectedItem.getAttribute("type"));
1805 },
1807 _setIconClassForPreferredAction: function(aHandlerInfo, aElement) {
1808 // If this returns true, the attribute that CSS sniffs for was set to something
1809 // so you shouldn't manually set an icon URI.
1810 // This removes the existing actionIcon attribute if any, even if returning false.
1811 aElement.removeAttribute("actionIcon");
1813 if (aHandlerInfo.alwaysAskBeforeHandling) {
1814 aElement.setAttribute(APP_ICON_ATTR_NAME, "ask");
1815 return true;
1816 }
1818 switch (aHandlerInfo.preferredAction) {
1819 case Ci.nsIHandlerInfo.saveToDisk:
1820 aElement.setAttribute(APP_ICON_ATTR_NAME, "save");
1821 return true;
1823 case Ci.nsIHandlerInfo.handleInternally:
1824 if (isFeedType(aHandlerInfo.type)) {
1825 aElement.setAttribute(APP_ICON_ATTR_NAME, "feed");
1826 return true;
1827 } else if (aHandlerInfo instanceof InternalHandlerInfoWrapper) {
1828 aElement.setAttribute(APP_ICON_ATTR_NAME, "ask");
1829 return true;
1830 }
1831 break;
1833 case kActionUsePlugin:
1834 aElement.setAttribute(APP_ICON_ATTR_NAME, "plugin");
1835 return true;
1836 }
1837 aElement.removeAttribute(APP_ICON_ATTR_NAME);
1838 return false;
1839 },
1841 _getIconURLForPreferredAction: function(aHandlerInfo) {
1842 switch (aHandlerInfo.preferredAction) {
1843 case Ci.nsIHandlerInfo.useSystemDefault:
1844 return this._getIconURLForSystemDefault(aHandlerInfo);
1846 case Ci.nsIHandlerInfo.useHelperApp:
1847 let (preferredApp = aHandlerInfo.preferredApplicationHandler) {
1848 if (this.isValidHandlerApp(preferredApp))
1849 return this._getIconURLForHandlerApp(preferredApp);
1850 }
1851 break;
1853 // This should never happen, but if preferredAction is set to some weird
1854 // value, then fall back to the generic application icon.
1855 default:
1856 return ICON_URL_APP;
1857 }
1858 },
1860 _getIconURLForHandlerApp: function(aHandlerApp) {
1861 if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
1862 return this._getIconURLForFile(aHandlerApp.executable);
1864 if (aHandlerApp instanceof Ci.nsIWebHandlerApp)
1865 return this._getIconURLForWebApp(aHandlerApp.uriTemplate);
1867 if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
1868 return this._getIconURLForWebApp(aHandlerApp.uri)
1870 // We know nothing about other kinds of handler apps.
1871 return "";
1872 },
1874 _getIconURLForFile: function(aFile) {
1875 var fph = this._ioSvc.getProtocolHandler("file").
1876 QueryInterface(Ci.nsIFileProtocolHandler);
1877 var urlSpec = fph.getURLSpecFromFile(aFile);
1879 return "moz-icon://" + urlSpec + "?size=16";
1880 },
1882 _getIconURLForWebApp: function(aWebAppURITemplate) {
1883 var uri = this._ioSvc.newURI(aWebAppURITemplate, null, null);
1885 // Unfortunately we can't use the favicon service to get the favicon,
1886 // because the service looks for a record with the exact URL we give it, and
1887 // users won't have such records for URLs they don't visit, and users won't
1888 // visit the handler's URL template, they'll only visit URLs derived from
1889 // that template (i.e. with %s in the template replaced by the URL of the
1890 // content being handled).
1892 if (/^https?$/.test(uri.scheme) && this._prefSvc.getBoolPref("browser.chrome.favicons"))
1893 return uri.prePath + "/favicon.ico";
1895 return "";
1896 },
1898 _getIconURLForSystemDefault: function(aHandlerInfo) {
1899 // Handler info objects for MIME types on some OSes implement a property bag
1900 // interface from which we can get an icon for the default app, so if we're
1901 // dealing with a MIME type on one of those OSes, then try to get the icon.
1902 if ("wrappedHandlerInfo" in aHandlerInfo) {
1903 let wrappedHandlerInfo = aHandlerInfo.wrappedHandlerInfo;
1905 if (wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
1906 wrappedHandlerInfo instanceof Ci.nsIPropertyBag) {
1907 try {
1908 let url = wrappedHandlerInfo.getProperty("defaultApplicationIconURL");
1909 if (url)
1910 return url + "?size=16";
1911 }
1912 catch(ex) {}
1913 }
1914 }
1916 // If this isn't a MIME type object on an OS that supports retrieving
1917 // the icon, or if we couldn't retrieve the icon for some other reason,
1918 // then use a generic icon.
1919 return ICON_URL_APP;
1920 }
1922 };