browser/components/preferences/applications.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

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();
  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();
  1024   },
  1027   //**************************************************************************//
  1028   // nsIDOMEventListener
  1030   handleEvent: function(aEvent) {
  1031     if (aEvent.type == "unload") {
  1032       this.destroy();
  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;
  1069   },
  1071   /**
  1072    * Load the set of handlers defined by plugins.
  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.
  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.
  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;
  1113         handlerInfoWrapper.pluginName = pluginTag.name;
  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;
  1136       handlerInfoWrapper.handledOnlyByPlugin = false;
  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;
  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));
  1208       this._list.appendChild(item);
  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.
  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.
  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).
  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");
  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]);
  1283         if (aHandlerInfo instanceof InternalHandlerInfoWrapper) {
  1284           return this._prefsBundle.getFormattedString("previewInApp",
  1285                                                       [this._brandShortName]);
  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]);
  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.
  1334    * @param aHandlerApp {nsIHandlerApp} the handler app in question
  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);
  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);
  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);
  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);
  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);
  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);
  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);
  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
  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);
  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);
  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;
  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());
  1598     function sortByAction(a, b) {
  1599       return t._describePreferredAction(a).toLowerCase().
  1600              localeCompare(t._describePreferredAction(b).toLowerCase());
  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;
  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);
  1638     finally {
  1639       this._storingAction = false;
  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;
  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;
  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));
  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));
  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;
  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;
  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);
  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);
  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;
  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;
  1831         break;
  1833       case kActionUsePlugin:
  1834         aElement.setAttribute(APP_ICON_ATTR_NAME, "plugin");
  1835         return true;
  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);
  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;
  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";
  1912         catch(ex) {}
  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;
  1922 };

mercurial