browser/components/preferences/in-content/applications.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/preferences/in-content/applications.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1916 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +//****************************************************************************//
     1.9 +// Constants & Enumeration Values
    1.10 +
    1.11 +Components.utils.import('resource://gre/modules/Services.jsm');
    1.12 +const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
    1.13 +const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
    1.14 +const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
    1.15 +const TYPE_PDF = "application/pdf";
    1.16 +
    1.17 +const PREF_PDFJS_DISABLED = "pdfjs.disabled";
    1.18 +const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged";
    1.19 +
    1.20 +const PREF_DISABLED_PLUGIN_TYPES = "plugin.disable_full_page_plugin_for_types";
    1.21 +
    1.22 +// Preferences that affect which entries to show in the list.
    1.23 +const PREF_SHOW_PLUGINS_IN_LIST = "browser.download.show_plugins_in_list";
    1.24 +const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS =
    1.25 +  "browser.download.hide_plugins_without_extensions";
    1.26 +
    1.27 +/*
    1.28 + * Preferences where we store handling information about the feed type.
    1.29 + *
    1.30 + * browser.feeds.handler
    1.31 + * - "bookmarks", "reader" (clarified further using the .default preference),
    1.32 + *   or "ask" -- indicates the default handler being used to process feeds;
    1.33 + *   "bookmarks" is obsolete; to specify that the handler is bookmarks,
    1.34 + *   set browser.feeds.handler.default to "bookmarks";
    1.35 + *
    1.36 + * browser.feeds.handler.default
    1.37 + * - "bookmarks", "client" or "web" -- indicates the chosen feed reader used
    1.38 + *   to display feeds, either transiently (i.e., when the "use as default"
    1.39 + *   checkbox is unchecked, corresponds to when browser.feeds.handler=="ask")
    1.40 + *   or more permanently (i.e., the item displayed in the dropdown in Feeds
    1.41 + *   preferences)
    1.42 + *
    1.43 + * browser.feeds.handler.webservice
    1.44 + * - the URL of the currently selected web service used to read feeds
    1.45 + *
    1.46 + * browser.feeds.handlers.application
    1.47 + * - nsILocalFile, stores the current client-side feed reading app if one has
    1.48 + *   been chosen
    1.49 + */
    1.50 +const PREF_FEED_SELECTED_APP    = "browser.feeds.handlers.application";
    1.51 +const PREF_FEED_SELECTED_WEB    = "browser.feeds.handlers.webservice";
    1.52 +const PREF_FEED_SELECTED_ACTION = "browser.feeds.handler";
    1.53 +const PREF_FEED_SELECTED_READER = "browser.feeds.handler.default";
    1.54 +
    1.55 +const PREF_VIDEO_FEED_SELECTED_APP    = "browser.videoFeeds.handlers.application";
    1.56 +const PREF_VIDEO_FEED_SELECTED_WEB    = "browser.videoFeeds.handlers.webservice";
    1.57 +const PREF_VIDEO_FEED_SELECTED_ACTION = "browser.videoFeeds.handler";
    1.58 +const PREF_VIDEO_FEED_SELECTED_READER = "browser.videoFeeds.handler.default";
    1.59 +
    1.60 +const PREF_AUDIO_FEED_SELECTED_APP    = "browser.audioFeeds.handlers.application";
    1.61 +const PREF_AUDIO_FEED_SELECTED_WEB    = "browser.audioFeeds.handlers.webservice";
    1.62 +const PREF_AUDIO_FEED_SELECTED_ACTION = "browser.audioFeeds.handler";
    1.63 +const PREF_AUDIO_FEED_SELECTED_READER = "browser.audioFeeds.handler.default";
    1.64 +
    1.65 +// The nsHandlerInfoAction enumeration values in nsIHandlerInfo identify
    1.66 +// the actions the application can take with content of various types.
    1.67 +// But since nsIHandlerInfo doesn't support plugins, there's no value
    1.68 +// identifying the "use plugin" action, so we use this constant instead.
    1.69 +const kActionUsePlugin = 5;
    1.70 +
    1.71 +/*
    1.72 +#if MOZ_WIDGET_GTK == 2
    1.73 +*/
    1.74 +const ICON_URL_APP      = "moz-icon://dummy.exe?size=16";
    1.75 +/*
    1.76 +#else
    1.77 +*/
    1.78 +const ICON_URL_APP      = "chrome://browser/skin/preferences/application.png";
    1.79 +/*
    1.80 +#endif
    1.81 +*/
    1.82 +
    1.83 +// For CSS. Can be one of "ask", "save", "plugin" or "feed". If absent, the icon URL
    1.84 +// was set by us to a custom handler icon and CSS should not try to override it.
    1.85 +const APP_ICON_ATTR_NAME = "appHandlerIcon";
    1.86 +
    1.87 +//****************************************************************************//
    1.88 +// Utilities
    1.89 +
    1.90 +function getFileDisplayName(file) {
    1.91 +#ifdef XP_WIN
    1.92 +  if (file instanceof Ci.nsILocalFileWin) {
    1.93 +    try {
    1.94 +      return file.getVersionInfoField("FileDescription");
    1.95 +    } catch (e) {}
    1.96 +  }
    1.97 +#endif
    1.98 +#ifdef XP_MACOSX
    1.99 +  if (file instanceof Ci.nsILocalFileMac) {
   1.100 +    try {
   1.101 +      return file.bundleDisplayName;
   1.102 +    } catch (e) {}
   1.103 +  }
   1.104 +#endif
   1.105 +  return file.leafName;
   1.106 +}
   1.107 +
   1.108 +function getLocalHandlerApp(aFile) {
   1.109 +  var localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
   1.110 +                        createInstance(Ci.nsILocalHandlerApp);
   1.111 +  localHandlerApp.name = getFileDisplayName(aFile);
   1.112 +  localHandlerApp.executable = aFile;
   1.113 +
   1.114 +  return localHandlerApp;
   1.115 +}
   1.116 +
   1.117 +/**
   1.118 + * An enumeration of items in a JS array.
   1.119 + *
   1.120 + * FIXME: use ArrayConverter once it lands (bug 380839).
   1.121 + * 
   1.122 + * @constructor
   1.123 + */
   1.124 +function ArrayEnumerator(aItems) {
   1.125 +  this._index = 0;
   1.126 +  this._contents = aItems;
   1.127 +}
   1.128 +
   1.129 +ArrayEnumerator.prototype = {
   1.130 +  _index: 0,
   1.131 +
   1.132 +  hasMoreElements: function() {
   1.133 +    return this._index < this._contents.length;
   1.134 +  },
   1.135 +
   1.136 +  getNext: function() {
   1.137 +    return this._contents[this._index++];
   1.138 +  }
   1.139 +};
   1.140 +
   1.141 +function isFeedType(t) {
   1.142 +  return t == TYPE_MAYBE_FEED || t == TYPE_MAYBE_VIDEO_FEED || t == TYPE_MAYBE_AUDIO_FEED;
   1.143 +}
   1.144 +
   1.145 +//****************************************************************************//
   1.146 +// HandlerInfoWrapper
   1.147 +
   1.148 +/**
   1.149 + * This object wraps nsIHandlerInfo with some additional functionality
   1.150 + * the Applications prefpane needs to display and allow modification of
   1.151 + * the list of handled types.
   1.152 + *
   1.153 + * We create an instance of this wrapper for each entry we might display
   1.154 + * in the prefpane, and we compose the instances from various sources,
   1.155 + * including plugins and the handler service.
   1.156 + *
   1.157 + * We don't implement all the original nsIHandlerInfo functionality,
   1.158 + * just the stuff that the prefpane needs.
   1.159 + *
   1.160 + * In theory, all of the custom functionality in this wrapper should get
   1.161 + * pushed down into nsIHandlerInfo eventually.
   1.162 + */
   1.163 +function HandlerInfoWrapper(aType, aHandlerInfo) {
   1.164 +  this._type = aType;
   1.165 +  this.wrappedHandlerInfo = aHandlerInfo;
   1.166 +}
   1.167 +
   1.168 +HandlerInfoWrapper.prototype = {
   1.169 +  // The wrapped nsIHandlerInfo object.  In general, this object is private,
   1.170 +  // but there are a couple cases where callers access it directly for things
   1.171 +  // we haven't (yet?) implemented, so we make it a public property.
   1.172 +  wrappedHandlerInfo: null,
   1.173 +
   1.174 +
   1.175 +  //**************************************************************************//
   1.176 +  // Convenience Utils
   1.177 +
   1.178 +  _handlerSvc: Cc["@mozilla.org/uriloader/handler-service;1"].
   1.179 +               getService(Ci.nsIHandlerService),
   1.180 +
   1.181 +  _prefSvc: Cc["@mozilla.org/preferences-service;1"].
   1.182 +            getService(Ci.nsIPrefBranch),
   1.183 +
   1.184 +  _categoryMgr: Cc["@mozilla.org/categorymanager;1"].
   1.185 +                getService(Ci.nsICategoryManager),
   1.186 +
   1.187 +  element: function(aID) {
   1.188 +    return document.getElementById(aID);
   1.189 +  },
   1.190 +
   1.191 +
   1.192 +  //**************************************************************************//
   1.193 +  // nsIHandlerInfo
   1.194 +
   1.195 +  // The MIME type or protocol scheme.
   1.196 +  _type: null,
   1.197 +  get type() {
   1.198 +    return this._type;
   1.199 +  },
   1.200 +
   1.201 +  get description() {
   1.202 +    if (this.wrappedHandlerInfo.description)
   1.203 +      return this.wrappedHandlerInfo.description;
   1.204 +
   1.205 +    if (this.primaryExtension) {
   1.206 +      var extension = this.primaryExtension.toUpperCase();
   1.207 +      return this.element("bundlePreferences").getFormattedString("fileEnding",
   1.208 +                                                                  [extension]);
   1.209 +    }
   1.210 +
   1.211 +    return this.type;
   1.212 +  },
   1.213 +
   1.214 +  get preferredApplicationHandler() {
   1.215 +    return this.wrappedHandlerInfo.preferredApplicationHandler;
   1.216 +  },
   1.217 +
   1.218 +  set preferredApplicationHandler(aNewValue) {
   1.219 +    this.wrappedHandlerInfo.preferredApplicationHandler = aNewValue;
   1.220 +
   1.221 +    // Make sure the preferred handler is in the set of possible handlers.
   1.222 +    if (aNewValue)
   1.223 +      this.addPossibleApplicationHandler(aNewValue)
   1.224 +  },
   1.225 +
   1.226 +  get possibleApplicationHandlers() {
   1.227 +    return this.wrappedHandlerInfo.possibleApplicationHandlers;
   1.228 +  },
   1.229 +
   1.230 +  addPossibleApplicationHandler: function(aNewHandler) {
   1.231 +    var possibleApps = this.possibleApplicationHandlers.enumerate();
   1.232 +    while (possibleApps.hasMoreElements()) {
   1.233 +      if (possibleApps.getNext().equals(aNewHandler))
   1.234 +        return;
   1.235 +    }
   1.236 +    this.possibleApplicationHandlers.appendElement(aNewHandler, false);
   1.237 +  },
   1.238 +
   1.239 +  removePossibleApplicationHandler: function(aHandler) {
   1.240 +    var defaultApp = this.preferredApplicationHandler;
   1.241 +    if (defaultApp && aHandler.equals(defaultApp)) {
   1.242 +      // If the app we remove was the default app, we must make sure
   1.243 +      // it won't be used anymore
   1.244 +      this.alwaysAskBeforeHandling = true;
   1.245 +      this.preferredApplicationHandler = null;
   1.246 +    }
   1.247 +
   1.248 +    var handlers = this.possibleApplicationHandlers;
   1.249 +    for (var i = 0; i < handlers.length; ++i) {
   1.250 +      var handler = handlers.queryElementAt(i, Ci.nsIHandlerApp);
   1.251 +      if (handler.equals(aHandler)) {
   1.252 +        handlers.removeElementAt(i);
   1.253 +        break;
   1.254 +      }
   1.255 +    }
   1.256 +  },
   1.257 +
   1.258 +  get hasDefaultHandler() {
   1.259 +    return this.wrappedHandlerInfo.hasDefaultHandler;
   1.260 +  },
   1.261 +
   1.262 +  get defaultDescription() {
   1.263 +    return this.wrappedHandlerInfo.defaultDescription;
   1.264 +  },
   1.265 +
   1.266 +  // What to do with content of this type.
   1.267 +  get preferredAction() {
   1.268 +    // If we have an enabled plugin, then the action is to use that plugin.
   1.269 +    if (this.pluginName && !this.isDisabledPluginType)
   1.270 +      return kActionUsePlugin;
   1.271 +
   1.272 +    // If the action is to use a helper app, but we don't have a preferred
   1.273 +    // handler app, then switch to using the system default, if any; otherwise
   1.274 +    // fall back to saving to disk, which is the default action in nsMIMEInfo.
   1.275 +    // Note: "save to disk" is an invalid value for protocol info objects,
   1.276 +    // but the alwaysAskBeforeHandling getter will detect that situation
   1.277 +    // and always return true in that case to override this invalid value.
   1.278 +    if (this.wrappedHandlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
   1.279 +        !gApplicationsPane.isValidHandlerApp(this.preferredApplicationHandler)) {
   1.280 +      if (this.wrappedHandlerInfo.hasDefaultHandler)
   1.281 +        return Ci.nsIHandlerInfo.useSystemDefault;
   1.282 +      else
   1.283 +        return Ci.nsIHandlerInfo.saveToDisk;
   1.284 +    }
   1.285 +
   1.286 +    return this.wrappedHandlerInfo.preferredAction;
   1.287 +  },
   1.288 +
   1.289 +  set preferredAction(aNewValue) {
   1.290 +    // We don't modify the preferred action if the new action is to use a plugin
   1.291 +    // because handler info objects don't understand our custom "use plugin"
   1.292 +    // value.  Also, leaving it untouched means that we can automatically revert
   1.293 +    // to the old setting if the user ever removes the plugin.
   1.294 +
   1.295 +    if (aNewValue != kActionUsePlugin)
   1.296 +      this.wrappedHandlerInfo.preferredAction = aNewValue;
   1.297 +  },
   1.298 +
   1.299 +  get alwaysAskBeforeHandling() {
   1.300 +    // If this type is handled only by a plugin, we can't trust the value
   1.301 +    // in the handler info object, since it'll be a default based on the absence
   1.302 +    // of any user configuration, and the default in that case is to always ask,
   1.303 +    // even though we never ask for content handled by a plugin, so special case
   1.304 +    // plugin-handled types by returning false here.
   1.305 +    if (this.pluginName && this.handledOnlyByPlugin)
   1.306 +      return false;
   1.307 +
   1.308 +    // If this is a protocol type and the preferred action is "save to disk",
   1.309 +    // which is invalid for such types, then return true here to override that
   1.310 +    // action.  This could happen when the preferred action is to use a helper
   1.311 +    // app, but the preferredApplicationHandler is invalid, and there isn't
   1.312 +    // a default handler, so the preferredAction getter returns save to disk
   1.313 +    // instead.
   1.314 +    if (!(this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
   1.315 +        this.preferredAction == Ci.nsIHandlerInfo.saveToDisk)
   1.316 +      return true;
   1.317 +
   1.318 +    return this.wrappedHandlerInfo.alwaysAskBeforeHandling;
   1.319 +  },
   1.320 +
   1.321 +  set alwaysAskBeforeHandling(aNewValue) {
   1.322 +    this.wrappedHandlerInfo.alwaysAskBeforeHandling = aNewValue;
   1.323 +  },
   1.324 +
   1.325 +
   1.326 +  //**************************************************************************//
   1.327 +  // nsIMIMEInfo
   1.328 +
   1.329 +  // The primary file extension associated with this type, if any.
   1.330 +  //
   1.331 +  // XXX Plugin objects contain an array of MimeType objects with "suffixes"
   1.332 +  // properties; if this object has an associated plugin, shouldn't we check
   1.333 +  // those properties for an extension?
   1.334 +  get primaryExtension() {
   1.335 +    try {
   1.336 +      if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
   1.337 +          this.wrappedHandlerInfo.primaryExtension)
   1.338 +        return this.wrappedHandlerInfo.primaryExtension
   1.339 +    } catch(ex) {}
   1.340 +
   1.341 +    return null;
   1.342 +  },
   1.343 +
   1.344 +
   1.345 +  //**************************************************************************//
   1.346 +  // Plugin Handling
   1.347 +
   1.348 +  // A plugin that can handle this type, if any.
   1.349 +  //
   1.350 +  // Note: just because we have one doesn't mean it *will* handle the type.
   1.351 +  // That depends on whether or not the type is in the list of types for which
   1.352 +  // plugin handling is disabled.
   1.353 +  plugin: null,
   1.354 +
   1.355 +  // Whether or not this type is only handled by a plugin or is also handled
   1.356 +  // by some user-configured action as specified in the handler info object.
   1.357 +  //
   1.358 +  // Note: we can't just check if there's a handler info object for this type,
   1.359 +  // because OS and user configuration is mixed up in the handler info object,
   1.360 +  // so we always need to retrieve it for the OS info and can't tell whether
   1.361 +  // it represents only OS-default information or user-configured information.
   1.362 +  //
   1.363 +  // FIXME: once handler info records are broken up into OS-provided records
   1.364 +  // and user-configured records, stop using this boolean flag and simply
   1.365 +  // check for the presence of a user-configured record to determine whether
   1.366 +  // or not this type is only handled by a plugin.  Filed as bug 395142.
   1.367 +  handledOnlyByPlugin: undefined,
   1.368 +
   1.369 +  get isDisabledPluginType() {
   1.370 +    return this._getDisabledPluginTypes().indexOf(this.type) != -1;
   1.371 +  },
   1.372 +
   1.373 +  _getDisabledPluginTypes: function() {
   1.374 +    var types = "";
   1.375 +
   1.376 +    if (this._prefSvc.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES))
   1.377 +      types = this._prefSvc.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
   1.378 +
   1.379 +    // Only split if the string isn't empty so we don't end up with an array
   1.380 +    // containing a single empty string.
   1.381 +    if (types != "")
   1.382 +      return types.split(",");
   1.383 +
   1.384 +    return [];
   1.385 +  },
   1.386 +
   1.387 +  disablePluginType: function() {
   1.388 +    var disabledPluginTypes = this._getDisabledPluginTypes();
   1.389 +
   1.390 +    if (disabledPluginTypes.indexOf(this.type) == -1)
   1.391 +      disabledPluginTypes.push(this.type);
   1.392 +
   1.393 +    this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
   1.394 +                              disabledPluginTypes.join(","));
   1.395 +
   1.396 +    // Update the category manager so existing browser windows update.
   1.397 +    this._categoryMgr.deleteCategoryEntry("Gecko-Content-Viewers",
   1.398 +                                          this.type,
   1.399 +                                          false);
   1.400 +  },
   1.401 +
   1.402 +  enablePluginType: function() {
   1.403 +    var disabledPluginTypes = this._getDisabledPluginTypes();
   1.404 +
   1.405 +    var type = this.type;
   1.406 +    disabledPluginTypes = disabledPluginTypes.filter(function(v) v != type);
   1.407 +
   1.408 +    this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
   1.409 +                              disabledPluginTypes.join(","));
   1.410 +
   1.411 +    // Update the category manager so existing browser windows update.
   1.412 +    this._categoryMgr.
   1.413 +      addCategoryEntry("Gecko-Content-Viewers",
   1.414 +                       this.type,
   1.415 +                       "@mozilla.org/content/plugin/document-loader-factory;1",
   1.416 +                       false,
   1.417 +                       true);
   1.418 +  },
   1.419 +
   1.420 +
   1.421 +  //**************************************************************************//
   1.422 +  // Storage
   1.423 +
   1.424 +  store: function() {
   1.425 +    this._handlerSvc.store(this.wrappedHandlerInfo);
   1.426 +  },
   1.427 +
   1.428 +
   1.429 +  //**************************************************************************//
   1.430 +  // Icons
   1.431 +
   1.432 +  get smallIcon() {
   1.433 +    return this._getIcon(16);
   1.434 +  },
   1.435 +
   1.436 +  _getIcon: function(aSize) {
   1.437 +    if (this.primaryExtension)
   1.438 +      return "moz-icon://goat." + this.primaryExtension + "?size=" + aSize;
   1.439 +
   1.440 +    if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo)
   1.441 +      return "moz-icon://goat?size=" + aSize + "&contentType=" + this.type;
   1.442 +
   1.443 +    // FIXME: consider returning some generic icon when we can't get a URL for
   1.444 +    // one (for example in the case of protocol schemes).  Filed as bug 395141.
   1.445 +    return null;
   1.446 +  }
   1.447 +
   1.448 +};
   1.449 +
   1.450 +
   1.451 +//****************************************************************************//
   1.452 +// Feed Handler Info
   1.453 +
   1.454 +/**
   1.455 + * This object implements nsIHandlerInfo for the feed types.  It's a separate
   1.456 + * object because we currently store handling information for the feed type
   1.457 + * in a set of preferences rather than the nsIHandlerService-managed datastore.
   1.458 + * 
   1.459 + * This object inherits from HandlerInfoWrapper in order to get functionality
   1.460 + * that isn't special to the feed type.
   1.461 + * 
   1.462 + * XXX Should we inherit from HandlerInfoWrapper?  After all, we override
   1.463 + * most of that wrapper's properties and methods, and we have to dance around
   1.464 + * the fact that the wrapper expects to have a wrappedHandlerInfo, which we
   1.465 + * don't provide.
   1.466 + */
   1.467 +
   1.468 +function FeedHandlerInfo(aMIMEType) {
   1.469 +  HandlerInfoWrapper.call(this, aMIMEType, null);
   1.470 +}
   1.471 +
   1.472 +FeedHandlerInfo.prototype = {
   1.473 +  __proto__: HandlerInfoWrapper.prototype,
   1.474 +
   1.475 +  //**************************************************************************//
   1.476 +  // Convenience Utils
   1.477 +
   1.478 +  _converterSvc:
   1.479 +    Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
   1.480 +    getService(Ci.nsIWebContentConverterService),
   1.481 +
   1.482 +  _shellSvc:
   1.483 +#ifdef HAVE_SHELL_SERVICE
   1.484 +    getShellService(),
   1.485 +#else
   1.486 +    null,
   1.487 +#endif
   1.488 +
   1.489 +
   1.490 +  //**************************************************************************//
   1.491 +  // nsIHandlerInfo
   1.492 +
   1.493 +  get description() {
   1.494 +    return this.element("bundlePreferences").getString(this._appPrefLabel);
   1.495 +  },
   1.496 +
   1.497 +  get preferredApplicationHandler() {
   1.498 +    switch (this.element(this._prefSelectedReader).value) {
   1.499 +      case "client":
   1.500 +        var file = this.element(this._prefSelectedApp).value;
   1.501 +        if (file)
   1.502 +          return getLocalHandlerApp(file);
   1.503 +
   1.504 +        return null;
   1.505 +
   1.506 +      case "web":
   1.507 +        var uri = this.element(this._prefSelectedWeb).value;
   1.508 +        if (!uri)
   1.509 +          return null;
   1.510 +        return this._converterSvc.getWebContentHandlerByURI(this.type, uri);
   1.511 +
   1.512 +      case "bookmarks":
   1.513 +      default:
   1.514 +        // When the pref is set to bookmarks, we handle feeds internally,
   1.515 +        // we don't forward them to a local or web handler app, so there is
   1.516 +        // no preferred handler.
   1.517 +        return null;
   1.518 +    }
   1.519 +  },
   1.520 +
   1.521 +  set preferredApplicationHandler(aNewValue) {
   1.522 +    if (aNewValue instanceof Ci.nsILocalHandlerApp) {
   1.523 +      this.element(this._prefSelectedApp).value = aNewValue.executable;
   1.524 +      this.element(this._prefSelectedReader).value = "client";
   1.525 +    }
   1.526 +    else if (aNewValue instanceof Ci.nsIWebContentHandlerInfo) {
   1.527 +      this.element(this._prefSelectedWeb).value = aNewValue.uri;
   1.528 +      this.element(this._prefSelectedReader).value = "web";
   1.529 +      // Make the web handler be the new "auto handler" for feeds.
   1.530 +      // Note: we don't have to unregister the auto handler when the user picks
   1.531 +      // a non-web handler (local app, Live Bookmarks, etc.) because the service
   1.532 +      // only uses the "auto handler" when the selected reader is a web handler.
   1.533 +      // We also don't have to unregister it when the user turns on "always ask"
   1.534 +      // (i.e. preview in browser), since that also overrides the auto handler.
   1.535 +      this._converterSvc.setAutoHandler(this.type, aNewValue);
   1.536 +    }
   1.537 +  },
   1.538 +
   1.539 +  _possibleApplicationHandlers: null,
   1.540 +
   1.541 +  get possibleApplicationHandlers() {
   1.542 +    if (this._possibleApplicationHandlers)
   1.543 +      return this._possibleApplicationHandlers;
   1.544 +
   1.545 +    // A minimal implementation of nsIMutableArray.  It only supports the two
   1.546 +    // methods its callers invoke, namely appendElement and nsIArray::enumerate.
   1.547 +    this._possibleApplicationHandlers = {
   1.548 +      _inner: [],
   1.549 +      _removed: [],
   1.550 +
   1.551 +      QueryInterface: function(aIID) {
   1.552 +        if (aIID.equals(Ci.nsIMutableArray) ||
   1.553 +            aIID.equals(Ci.nsIArray) ||
   1.554 +            aIID.equals(Ci.nsISupports))
   1.555 +          return this;
   1.556 +
   1.557 +        throw Cr.NS_ERROR_NO_INTERFACE;
   1.558 +      },
   1.559 +
   1.560 +      get length() {
   1.561 +        return this._inner.length;
   1.562 +      },
   1.563 +
   1.564 +      enumerate: function() {
   1.565 +        return new ArrayEnumerator(this._inner);
   1.566 +      },
   1.567 +
   1.568 +      appendElement: function(aHandlerApp, aWeak) {
   1.569 +        this._inner.push(aHandlerApp);
   1.570 +      },
   1.571 +
   1.572 +      removeElementAt: function(aIndex) {
   1.573 +        this._removed.push(this._inner[aIndex]);
   1.574 +        this._inner.splice(aIndex, 1);
   1.575 +      },
   1.576 +
   1.577 +      queryElementAt: function(aIndex, aInterface) {
   1.578 +        return this._inner[aIndex].QueryInterface(aInterface);
   1.579 +      }
   1.580 +    };
   1.581 +
   1.582 +    // Add the selected local app if it's different from the OS default handler.
   1.583 +    // Unlike for other types, we can store only one local app at a time for the
   1.584 +    // feed type, since we store it in a preference that historically stores
   1.585 +    // only a single path.  But we display all the local apps the user chooses
   1.586 +    // while the prefpane is open, only dropping the list when the user closes
   1.587 +    // the prefpane, for maximum usability and consistency with other types.
   1.588 +    var preferredAppFile = this.element(this._prefSelectedApp).value;
   1.589 +    if (preferredAppFile) {
   1.590 +      let preferredApp = getLocalHandlerApp(preferredAppFile);
   1.591 +      let defaultApp = this._defaultApplicationHandler;
   1.592 +      if (!defaultApp || !defaultApp.equals(preferredApp))
   1.593 +        this._possibleApplicationHandlers.appendElement(preferredApp, false);
   1.594 +    }
   1.595 +
   1.596 +    // Add the registered web handlers.  There can be any number of these.
   1.597 +    var webHandlers = this._converterSvc.getContentHandlers(this.type);
   1.598 +    for each (let webHandler in webHandlers)
   1.599 +      this._possibleApplicationHandlers.appendElement(webHandler, false);
   1.600 +
   1.601 +    return this._possibleApplicationHandlers;
   1.602 +  },
   1.603 +
   1.604 +  __defaultApplicationHandler: undefined,
   1.605 +  get _defaultApplicationHandler() {
   1.606 +    if (typeof this.__defaultApplicationHandler != "undefined")
   1.607 +      return this.__defaultApplicationHandler;
   1.608 +
   1.609 +    var defaultFeedReader = null;
   1.610 +#ifdef HAVE_SHELL_SERVICE
   1.611 +    try {
   1.612 +      defaultFeedReader = this._shellSvc.defaultFeedReader;
   1.613 +    }
   1.614 +    catch(ex) {
   1.615 +      // no default reader or _shellSvc is null
   1.616 +    }
   1.617 +#endif
   1.618 +
   1.619 +    if (defaultFeedReader) {
   1.620 +      let handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
   1.621 +                       createInstance(Ci.nsIHandlerApp);
   1.622 +      handlerApp.name = getFileDisplayName(defaultFeedReader);
   1.623 +      handlerApp.QueryInterface(Ci.nsILocalHandlerApp);
   1.624 +      handlerApp.executable = defaultFeedReader;
   1.625 +
   1.626 +      this.__defaultApplicationHandler = handlerApp;
   1.627 +    }
   1.628 +    else {
   1.629 +      this.__defaultApplicationHandler = null;
   1.630 +    }
   1.631 +
   1.632 +    return this.__defaultApplicationHandler;
   1.633 +  },
   1.634 +
   1.635 +  get hasDefaultHandler() {
   1.636 +#ifdef HAVE_SHELL_SERVICE
   1.637 +    try {
   1.638 +      if (this._shellSvc.defaultFeedReader)
   1.639 +        return true;
   1.640 +    }
   1.641 +    catch(ex) {
   1.642 +      // no default reader or _shellSvc is null
   1.643 +    }
   1.644 +#endif
   1.645 +
   1.646 +    return false;
   1.647 +  },
   1.648 +
   1.649 +  get defaultDescription() {
   1.650 +    if (this.hasDefaultHandler)
   1.651 +      return this._defaultApplicationHandler.name;
   1.652 +
   1.653 +    // Should we instead return null?
   1.654 +    return "";
   1.655 +  },
   1.656 +
   1.657 +  // What to do with content of this type.
   1.658 +  get preferredAction() {
   1.659 +    switch (this.element(this._prefSelectedAction).value) {
   1.660 +
   1.661 +      case "bookmarks":
   1.662 +        return Ci.nsIHandlerInfo.handleInternally;
   1.663 +
   1.664 +      case "reader": {
   1.665 +        let preferredApp = this.preferredApplicationHandler;
   1.666 +        let defaultApp = this._defaultApplicationHandler;
   1.667 +
   1.668 +        // If we have a valid preferred app, return useSystemDefault if it's
   1.669 +        // the default app; otherwise return useHelperApp.
   1.670 +        if (gApplicationsPane.isValidHandlerApp(preferredApp)) {
   1.671 +          if (defaultApp && defaultApp.equals(preferredApp))
   1.672 +            return Ci.nsIHandlerInfo.useSystemDefault;
   1.673 +
   1.674 +          return Ci.nsIHandlerInfo.useHelperApp;
   1.675 +        }
   1.676 +
   1.677 +        // The pref is set to "reader", but we don't have a valid preferred app.
   1.678 +        // What do we do now?  Not sure this is the best option (perhaps we
   1.679 +        // should direct the user to the default app, if any), but for now let's
   1.680 +        // direct the user to live bookmarks.
   1.681 +        return Ci.nsIHandlerInfo.handleInternally;
   1.682 +      }
   1.683 +
   1.684 +      // If the action is "ask", then alwaysAskBeforeHandling will override
   1.685 +      // the action, so it doesn't matter what we say it is, it just has to be
   1.686 +      // something that doesn't cause the controller to hide the type.
   1.687 +      case "ask":
   1.688 +      default:
   1.689 +        return Ci.nsIHandlerInfo.handleInternally;
   1.690 +    }
   1.691 +  },
   1.692 +
   1.693 +  set preferredAction(aNewValue) {
   1.694 +    switch (aNewValue) {
   1.695 +
   1.696 +      case Ci.nsIHandlerInfo.handleInternally:
   1.697 +        this.element(this._prefSelectedReader).value = "bookmarks";
   1.698 +        break;
   1.699 +
   1.700 +      case Ci.nsIHandlerInfo.useHelperApp:
   1.701 +        this.element(this._prefSelectedAction).value = "reader";
   1.702 +        // The controller has already set preferredApplicationHandler
   1.703 +        // to the new helper app.
   1.704 +        break;
   1.705 +
   1.706 +      case Ci.nsIHandlerInfo.useSystemDefault:
   1.707 +        this.element(this._prefSelectedAction).value = "reader";
   1.708 +        this.preferredApplicationHandler = this._defaultApplicationHandler;
   1.709 +        break;
   1.710 +    }
   1.711 +  },
   1.712 +
   1.713 +  get alwaysAskBeforeHandling() {
   1.714 +    return this.element(this._prefSelectedAction).value == "ask";
   1.715 +  },
   1.716 +
   1.717 +  set alwaysAskBeforeHandling(aNewValue) {
   1.718 +    if (aNewValue == true)
   1.719 +      this.element(this._prefSelectedAction).value = "ask";
   1.720 +    else
   1.721 +      this.element(this._prefSelectedAction).value = "reader";
   1.722 +  },
   1.723 +
   1.724 +  // Whether or not we are currently storing the action selected by the user.
   1.725 +  // We use this to suppress notification-triggered updates to the list when
   1.726 +  // we make changes that may spawn such updates, specifically when we change
   1.727 +  // the action for the feed type, which results in feed preference updates,
   1.728 +  // which spawn "pref changed" notifications that would otherwise cause us
   1.729 +  // to rebuild the view unnecessarily.
   1.730 +  _storingAction: false,
   1.731 +
   1.732 +
   1.733 +  //**************************************************************************//
   1.734 +  // nsIMIMEInfo
   1.735 +
   1.736 +  get primaryExtension() {
   1.737 +    return "xml";
   1.738 +  },
   1.739 +
   1.740 +
   1.741 +  //**************************************************************************//
   1.742 +  // Storage
   1.743 +
   1.744 +  // Changes to the preferred action and handler take effect immediately
   1.745 +  // (we write them out to the preferences right as they happen),
   1.746 +  // so we when the controller calls store() after modifying the handlers,
   1.747 +  // the only thing we need to store is the removal of possible handlers
   1.748 +  // XXX Should we hold off on making the changes until this method gets called?
   1.749 +  store: function() {
   1.750 +    for each (let app in this._possibleApplicationHandlers._removed) {
   1.751 +      if (app instanceof Ci.nsILocalHandlerApp) {
   1.752 +        let pref = this.element(PREF_FEED_SELECTED_APP);
   1.753 +        var preferredAppFile = pref.value;
   1.754 +        if (preferredAppFile) {
   1.755 +          let preferredApp = getLocalHandlerApp(preferredAppFile);
   1.756 +          if (app.equals(preferredApp))
   1.757 +            pref.reset();
   1.758 +        }
   1.759 +      }
   1.760 +      else {
   1.761 +        app.QueryInterface(Ci.nsIWebContentHandlerInfo);
   1.762 +        this._converterSvc.removeContentHandler(app.contentType, app.uri);
   1.763 +      }
   1.764 +    }
   1.765 +    this._possibleApplicationHandlers._removed = [];
   1.766 +  },
   1.767 +
   1.768 +
   1.769 +  //**************************************************************************//
   1.770 +  // Icons
   1.771 +
   1.772 +  get smallIcon() {
   1.773 +    return this._smallIcon;
   1.774 +  }
   1.775 +
   1.776 +};
   1.777 +
   1.778 +var feedHandlerInfo = {
   1.779 +  __proto__: new FeedHandlerInfo(TYPE_MAYBE_FEED),
   1.780 +  _prefSelectedApp: PREF_FEED_SELECTED_APP, 
   1.781 +  _prefSelectedWeb: PREF_FEED_SELECTED_WEB, 
   1.782 +  _prefSelectedAction: PREF_FEED_SELECTED_ACTION, 
   1.783 +  _prefSelectedReader: PREF_FEED_SELECTED_READER,
   1.784 +  _smallIcon: "chrome://browser/skin/feeds/feedIcon16.png",
   1.785 +  _appPrefLabel: "webFeed"
   1.786 +}
   1.787 +
   1.788 +var videoFeedHandlerInfo = {
   1.789 +  __proto__: new FeedHandlerInfo(TYPE_MAYBE_VIDEO_FEED),
   1.790 +  _prefSelectedApp: PREF_VIDEO_FEED_SELECTED_APP, 
   1.791 +  _prefSelectedWeb: PREF_VIDEO_FEED_SELECTED_WEB, 
   1.792 +  _prefSelectedAction: PREF_VIDEO_FEED_SELECTED_ACTION, 
   1.793 +  _prefSelectedReader: PREF_VIDEO_FEED_SELECTED_READER,
   1.794 +  _smallIcon: "chrome://browser/skin/feeds/videoFeedIcon16.png",
   1.795 +  _appPrefLabel: "videoPodcastFeed"
   1.796 +}
   1.797 +
   1.798 +var audioFeedHandlerInfo = {
   1.799 +  __proto__: new FeedHandlerInfo(TYPE_MAYBE_AUDIO_FEED),
   1.800 +  _prefSelectedApp: PREF_AUDIO_FEED_SELECTED_APP, 
   1.801 +  _prefSelectedWeb: PREF_AUDIO_FEED_SELECTED_WEB, 
   1.802 +  _prefSelectedAction: PREF_AUDIO_FEED_SELECTED_ACTION, 
   1.803 +  _prefSelectedReader: PREF_AUDIO_FEED_SELECTED_READER,
   1.804 +  _smallIcon: "chrome://browser/skin/feeds/audioFeedIcon16.png",
   1.805 +  _appPrefLabel: "audioPodcastFeed"
   1.806 +}
   1.807 +
   1.808 +/**
   1.809 + * InternalHandlerInfoWrapper provides a basic mechanism to create an internal
   1.810 + * mime type handler that can be enabled/disabled in the applications preference
   1.811 + * menu.
   1.812 + */
   1.813 +function InternalHandlerInfoWrapper(aMIMEType) {
   1.814 +  var mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
   1.815 +  var handlerInfo = mimeSvc.getFromTypeAndExtension(aMIMEType, null);
   1.816 +
   1.817 +  HandlerInfoWrapper.call(this, aMIMEType, handlerInfo);
   1.818 +}
   1.819 +
   1.820 +InternalHandlerInfoWrapper.prototype = {
   1.821 +  __proto__: HandlerInfoWrapper.prototype,
   1.822 +
   1.823 +  // Override store so we so we can notify any code listening for registration
   1.824 +  // or unregistration of this handler.
   1.825 +  store: function() {
   1.826 +    HandlerInfoWrapper.prototype.store.call(this);
   1.827 +    Services.obs.notifyObservers(null, this._handlerChanged, null);
   1.828 +  },
   1.829 +
   1.830 +  get enabled() {
   1.831 +    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   1.832 +  },
   1.833 +
   1.834 +  get description() {
   1.835 +    return this.element("bundlePreferences").getString(this._appPrefLabel);
   1.836 +  }
   1.837 +};
   1.838 +
   1.839 +var pdfHandlerInfo = {
   1.840 +  __proto__: new InternalHandlerInfoWrapper(TYPE_PDF),
   1.841 +  _handlerChanged: TOPIC_PDFJS_HANDLER_CHANGED,
   1.842 +  _appPrefLabel: "portableDocumentFormat",
   1.843 +  get enabled() {
   1.844 +    return !Services.prefs.getBoolPref(PREF_PDFJS_DISABLED);
   1.845 +  },
   1.846 +};
   1.847 +
   1.848 +
   1.849 +//****************************************************************************//
   1.850 +// Prefpane Controller
   1.851 +
   1.852 +var gApplicationsPane = {
   1.853 +  // The set of types the app knows how to handle.  A hash of HandlerInfoWrapper
   1.854 +  // objects, indexed by type.
   1.855 +  _handledTypes: {},
   1.856 +  
   1.857 +  // The list of types we can show, sorted by the sort column/direction.
   1.858 +  // An array of HandlerInfoWrapper objects.  We build this list when we first
   1.859 +  // load the data and then rebuild it when users change a pref that affects
   1.860 +  // what types we can show or change the sort column/direction.
   1.861 +  // Note: this isn't necessarily the list of types we *will* show; if the user
   1.862 +  // provides a filter string, we'll only show the subset of types in this list
   1.863 +  // that match that string.
   1.864 +  _visibleTypes: [],
   1.865 +
   1.866 +  // A count of the number of times each visible type description appears.
   1.867 +  // We use these counts to determine whether or not to annotate descriptions
   1.868 +  // with their types to distinguish duplicate descriptions from each other.
   1.869 +  // A hash of integer counts, indexed by string description.
   1.870 +  _visibleTypeDescriptionCount: {},
   1.871 +
   1.872 +
   1.873 +  //**************************************************************************//
   1.874 +  // Convenience & Performance Shortcuts
   1.875 +
   1.876 +  // These get defined by init().
   1.877 +  _brandShortName : null,
   1.878 +  _prefsBundle    : null,
   1.879 +  _list           : null,
   1.880 +  _filter         : null,
   1.881 +
   1.882 +  _prefSvc      : Cc["@mozilla.org/preferences-service;1"].
   1.883 +                  getService(Ci.nsIPrefBranch),
   1.884 +
   1.885 +  _mimeSvc      : Cc["@mozilla.org/mime;1"].
   1.886 +                  getService(Ci.nsIMIMEService),
   1.887 +
   1.888 +  _helperAppSvc : Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
   1.889 +                  getService(Ci.nsIExternalHelperAppService),
   1.890 +
   1.891 +  _handlerSvc   : Cc["@mozilla.org/uriloader/handler-service;1"].
   1.892 +                  getService(Ci.nsIHandlerService),
   1.893 +
   1.894 +  _ioSvc        : Cc["@mozilla.org/network/io-service;1"].
   1.895 +                  getService(Ci.nsIIOService),
   1.896 +
   1.897 +
   1.898 +  //**************************************************************************//
   1.899 +  // Initialization & Destruction
   1.900 +
   1.901 +  init: function() {
   1.902 +    // Initialize shortcuts to some commonly accessed elements & values.
   1.903 +    this._brandShortName =
   1.904 +      document.getElementById("bundleBrand").getString("brandShortName");
   1.905 +    this._prefsBundle = document.getElementById("bundlePreferences");
   1.906 +    this._list = document.getElementById("handlersView");
   1.907 +    this._filter = document.getElementById("filter");
   1.908 +
   1.909 +    // Observe preferences that influence what we display so we can rebuild
   1.910 +    // the view when they change.
   1.911 +    this._prefSvc.addObserver(PREF_SHOW_PLUGINS_IN_LIST, this, false);
   1.912 +    this._prefSvc.addObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this, false);
   1.913 +    this._prefSvc.addObserver(PREF_FEED_SELECTED_APP, this, false);
   1.914 +    this._prefSvc.addObserver(PREF_FEED_SELECTED_WEB, this, false);
   1.915 +    this._prefSvc.addObserver(PREF_FEED_SELECTED_ACTION, this, false);
   1.916 +    this._prefSvc.addObserver(PREF_FEED_SELECTED_READER, this, false);
   1.917 +
   1.918 +    this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_APP, this, false);
   1.919 +    this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_WEB, this, false);
   1.920 +    this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this, false);
   1.921 +    this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_READER, this, false);
   1.922 +
   1.923 +    this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_APP, this, false);
   1.924 +    this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_WEB, this, false);
   1.925 +    this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this, false);
   1.926 +    this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_READER, this, false);
   1.927 +
   1.928 +
   1.929 +    // Listen for window unload so we can remove our preference observers.
   1.930 +    window.addEventListener("unload", this, false);
   1.931 +
   1.932 +    // Figure out how we should be sorting the list.  We persist sort settings
   1.933 +    // across sessions, so we can't assume the default sort column/direction.
   1.934 +    // XXX should we be using the XUL sort service instead?
   1.935 +    if (document.getElementById("actionColumn").hasAttribute("sortDirection")) {
   1.936 +      this._sortColumn = document.getElementById("actionColumn");
   1.937 +      // The typeColumn element always has a sortDirection attribute,
   1.938 +      // either because it was persisted or because the default value
   1.939 +      // from the xul file was used.  If we are sorting on the other
   1.940 +      // column, we should remove it.
   1.941 +      document.getElementById("typeColumn").removeAttribute("sortDirection");
   1.942 +    }
   1.943 +    else 
   1.944 +      this._sortColumn = document.getElementById("typeColumn");
   1.945 +
   1.946 +    // Load the data and build the list of handlers.
   1.947 +    // By doing this in a timeout, we let the preferences dialog resize itself
   1.948 +    // to an appropriate size before we add a bunch of items to the list.
   1.949 +    // Otherwise, if there are many items, and the Applications prefpane
   1.950 +    // is the one that gets displayed when the user first opens the dialog,
   1.951 +    // the dialog might stretch too much in an attempt to fit them all in.
   1.952 +    // XXX Shouldn't we perhaps just set a max-height on the richlistbox?
   1.953 +    var _delayedPaneLoad = function(self) {
   1.954 +      self._loadData();
   1.955 +      self._rebuildVisibleTypes();
   1.956 +      self._sortVisibleTypes();
   1.957 +      self._rebuildView();
   1.958 +
   1.959 +      // Notify observers that the UI is now ready
   1.960 +      Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService).
   1.961 +      notifyObservers(window, "app-handler-pane-loaded", null);
   1.962 +    }
   1.963 +    setTimeout(_delayedPaneLoad, 0, this);
   1.964 +  },
   1.965 +
   1.966 +  destroy: function() {
   1.967 +    window.removeEventListener("unload", this, false);
   1.968 +    this._prefSvc.removeObserver(PREF_SHOW_PLUGINS_IN_LIST, this);
   1.969 +    this._prefSvc.removeObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this);
   1.970 +    this._prefSvc.removeObserver(PREF_FEED_SELECTED_APP, this);
   1.971 +    this._prefSvc.removeObserver(PREF_FEED_SELECTED_WEB, this);
   1.972 +    this._prefSvc.removeObserver(PREF_FEED_SELECTED_ACTION, this);
   1.973 +    this._prefSvc.removeObserver(PREF_FEED_SELECTED_READER, this);
   1.974 +
   1.975 +    this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_APP, this);
   1.976 +    this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_WEB, this);
   1.977 +    this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this);
   1.978 +    this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_READER, this);
   1.979 +
   1.980 +    this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_APP, this);
   1.981 +    this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_WEB, this);
   1.982 +    this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this);
   1.983 +    this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_READER, this);
   1.984 +  },
   1.985 +
   1.986 +
   1.987 +  //**************************************************************************//
   1.988 +  // nsISupports
   1.989 +
   1.990 +  QueryInterface: function(aIID) {
   1.991 +    if (aIID.equals(Ci.nsIObserver) ||
   1.992 +        aIID.equals(Ci.nsIDOMEventListener ||
   1.993 +        aIID.equals(Ci.nsISupports)))
   1.994 +      return this;
   1.995 +
   1.996 +    throw Cr.NS_ERROR_NO_INTERFACE;
   1.997 +  },
   1.998 +
   1.999 +
  1.1000 +  //**************************************************************************//
  1.1001 +  // nsIObserver
  1.1002 +
  1.1003 +  observe: function (aSubject, aTopic, aData) {
  1.1004 +    // Rebuild the list when there are changes to preferences that influence
  1.1005 +    // whether or not to show certain entries in the list.
  1.1006 +    if (aTopic == "nsPref:changed" && !this._storingAction) {
  1.1007 +      // These two prefs alter the list of visible types, so we have to rebuild
  1.1008 +      // that list when they change.
  1.1009 +      if (aData == PREF_SHOW_PLUGINS_IN_LIST ||
  1.1010 +          aData == PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS) {
  1.1011 +        this._rebuildVisibleTypes();
  1.1012 +        this._sortVisibleTypes();
  1.1013 +      }
  1.1014 +
  1.1015 +      // All the prefs we observe can affect what we display, so we rebuild
  1.1016 +      // the view when any of them changes.
  1.1017 +      this._rebuildView();
  1.1018 +    }
  1.1019 +  },
  1.1020 +
  1.1021 +
  1.1022 +  //**************************************************************************//
  1.1023 +  // nsIDOMEventListener
  1.1024 +
  1.1025 +  handleEvent: function(aEvent) {
  1.1026 +    if (aEvent.type == "unload") {
  1.1027 +      this.destroy();
  1.1028 +    }
  1.1029 +  },
  1.1030 +
  1.1031 +
  1.1032 +  //**************************************************************************//
  1.1033 +  // Composed Model Construction
  1.1034 +
  1.1035 +  _loadData: function() {
  1.1036 +    this._loadFeedHandler();
  1.1037 +    this._loadInternalHandlers();
  1.1038 +    this._loadPluginHandlers();
  1.1039 +    this._loadApplicationHandlers();
  1.1040 +  },
  1.1041 +
  1.1042 +  _loadFeedHandler: function() {
  1.1043 +    this._handledTypes[TYPE_MAYBE_FEED] = feedHandlerInfo;
  1.1044 +    feedHandlerInfo.handledOnlyByPlugin = false;
  1.1045 +
  1.1046 +    this._handledTypes[TYPE_MAYBE_VIDEO_FEED] = videoFeedHandlerInfo;
  1.1047 +    videoFeedHandlerInfo.handledOnlyByPlugin = false;
  1.1048 +
  1.1049 +    this._handledTypes[TYPE_MAYBE_AUDIO_FEED] = audioFeedHandlerInfo;
  1.1050 +    audioFeedHandlerInfo.handledOnlyByPlugin = false;
  1.1051 +  },
  1.1052 +
  1.1053 +  /**
  1.1054 +   * Load higher level internal handlers so they can be turned on/off in the
  1.1055 +   * applications menu.
  1.1056 +   */
  1.1057 +  _loadInternalHandlers: function() {
  1.1058 +    var internalHandlers = [pdfHandlerInfo];
  1.1059 +    for (let internalHandler of internalHandlers) {
  1.1060 +      if (internalHandler.enabled) {
  1.1061 +        this._handledTypes[internalHandler.type] = internalHandler;
  1.1062 +      }
  1.1063 +    }
  1.1064 +  },
  1.1065 +
  1.1066 +  /**
  1.1067 +   * Load the set of handlers defined by plugins.
  1.1068 +   *
  1.1069 +   * Note: if there's more than one plugin for a given MIME type, we assume
  1.1070 +   * the last one is the one that the application will use.  That may not be
  1.1071 +   * correct, but it's how we've been doing it for years.
  1.1072 +   *
  1.1073 +   * Perhaps we should instead query navigator.mimeTypes for the set of types
  1.1074 +   * supported by the application and then get the plugin from each MIME type's
  1.1075 +   * enabledPlugin property.  But if there's a plugin for a type, we need
  1.1076 +   * to know about it even if it isn't enabled, since we're going to give
  1.1077 +   * the user an option to enable it.
  1.1078 +   *
  1.1079 +   * Also note that enabledPlugin does not get updated when
  1.1080 +   * plugin.disable_full_page_plugin_for_types changes, so even if we could use
  1.1081 +   * enabledPlugin to get the plugin that would be used, we'd still need to
  1.1082 +   * check the pref ourselves to find out if it's enabled.
  1.1083 +   */
  1.1084 +  _loadPluginHandlers: function() {
  1.1085 +    "use strict";
  1.1086 +
  1.1087 +    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
  1.1088 +    let pluginTags = pluginHost.getPluginTags();
  1.1089 +
  1.1090 +    for (let i = 0; i < pluginTags.length; ++i) {
  1.1091 +      let pluginTag = pluginTags[i];
  1.1092 +
  1.1093 +      let mimeTypes = pluginTag.getMimeTypes();
  1.1094 +      for (let j = 0; j < mimeTypes.length; ++j) {
  1.1095 +        let type = mimeTypes[j];
  1.1096 +
  1.1097 +        let handlerInfoWrapper;
  1.1098 +        if (type in this._handledTypes)
  1.1099 +          handlerInfoWrapper = this._handledTypes[type];
  1.1100 +        else {
  1.1101 +          let wrappedHandlerInfo =
  1.1102 +            this._mimeSvc.getFromTypeAndExtension(type, null);
  1.1103 +          handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo);
  1.1104 +          handlerInfoWrapper.handledOnlyByPlugin = true;
  1.1105 +          this._handledTypes[type] = handlerInfoWrapper;
  1.1106 +        }
  1.1107 +
  1.1108 +        handlerInfoWrapper.pluginName = pluginTag.name;
  1.1109 +      }
  1.1110 +    }
  1.1111 +  },
  1.1112 +
  1.1113 +  /**
  1.1114 +   * Load the set of handlers defined by the application datastore.
  1.1115 +   */
  1.1116 +  _loadApplicationHandlers: function() {
  1.1117 +    var wrappedHandlerInfos = this._handlerSvc.enumerate();
  1.1118 +    while (wrappedHandlerInfos.hasMoreElements()) {
  1.1119 +      let wrappedHandlerInfo =
  1.1120 +        wrappedHandlerInfos.getNext().QueryInterface(Ci.nsIHandlerInfo);
  1.1121 +      let type = wrappedHandlerInfo.type;
  1.1122 +
  1.1123 +      let handlerInfoWrapper;
  1.1124 +      if (type in this._handledTypes)
  1.1125 +        handlerInfoWrapper = this._handledTypes[type];
  1.1126 +      else {
  1.1127 +        handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo);
  1.1128 +        this._handledTypes[type] = handlerInfoWrapper;
  1.1129 +      }
  1.1130 +
  1.1131 +      handlerInfoWrapper.handledOnlyByPlugin = false;
  1.1132 +    }
  1.1133 +  },
  1.1134 +
  1.1135 +
  1.1136 +  //**************************************************************************//
  1.1137 +  // View Construction
  1.1138 +
  1.1139 +  _rebuildVisibleTypes: function() {
  1.1140 +    // Reset the list of visible types and the visible type description counts.
  1.1141 +    this._visibleTypes = [];
  1.1142 +    this._visibleTypeDescriptionCount = {};
  1.1143 +
  1.1144 +    // Get the preferences that help determine what types to show.
  1.1145 +    var showPlugins = this._prefSvc.getBoolPref(PREF_SHOW_PLUGINS_IN_LIST);
  1.1146 +    var hidePluginsWithoutExtensions =
  1.1147 +      this._prefSvc.getBoolPref(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS);
  1.1148 +
  1.1149 +    for (let type in this._handledTypes) {
  1.1150 +      let handlerInfo = this._handledTypes[type];
  1.1151 +
  1.1152 +      // Hide plugins without associated extensions if so prefed so we don't
  1.1153 +      // show a whole bunch of obscure types handled by plugins on Mac.
  1.1154 +      // Note: though protocol types don't have extensions, we still show them;
  1.1155 +      // the pref is only meant to be applied to MIME types, since plugins are
  1.1156 +      // only associated with MIME types.
  1.1157 +      // FIXME: should we also check the "suffixes" property of the plugin?
  1.1158 +      // Filed as bug 395135.
  1.1159 +      if (hidePluginsWithoutExtensions && handlerInfo.handledOnlyByPlugin &&
  1.1160 +          handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
  1.1161 +          !handlerInfo.primaryExtension)
  1.1162 +        continue;
  1.1163 +
  1.1164 +      // Hide types handled only by plugins if so prefed.
  1.1165 +      if (handlerInfo.handledOnlyByPlugin && !showPlugins)
  1.1166 +        continue;
  1.1167 +
  1.1168 +      // We couldn't find any reason to exclude the type, so include it.
  1.1169 +      this._visibleTypes.push(handlerInfo);
  1.1170 +
  1.1171 +      if (handlerInfo.description in this._visibleTypeDescriptionCount)
  1.1172 +        this._visibleTypeDescriptionCount[handlerInfo.description]++;
  1.1173 +      else
  1.1174 +        this._visibleTypeDescriptionCount[handlerInfo.description] = 1;
  1.1175 +    }
  1.1176 +  },
  1.1177 +
  1.1178 +  _rebuildView: function() {
  1.1179 +    // Clear the list of entries.
  1.1180 +    while (this._list.childNodes.length > 1)
  1.1181 +      this._list.removeChild(this._list.lastChild);
  1.1182 +
  1.1183 +    var visibleTypes = this._visibleTypes;
  1.1184 +
  1.1185 +    // If the user is filtering the list, then only show matching types.
  1.1186 +    if (this._filter.value)
  1.1187 +      visibleTypes = visibleTypes.filter(this._matchesFilter, this);
  1.1188 +
  1.1189 +    for each (let visibleType in visibleTypes) {
  1.1190 +      let item = document.createElement("richlistitem");
  1.1191 +      item.setAttribute("type", visibleType.type);
  1.1192 +      item.setAttribute("typeDescription", this._describeType(visibleType));
  1.1193 +      if (visibleType.smallIcon)
  1.1194 +        item.setAttribute("typeIcon", visibleType.smallIcon);
  1.1195 +      item.setAttribute("actionDescription",
  1.1196 +                        this._describePreferredAction(visibleType));
  1.1197 +
  1.1198 +      if (!this._setIconClassForPreferredAction(visibleType, item)) {
  1.1199 +        item.setAttribute("actionIcon",
  1.1200 +                          this._getIconURLForPreferredAction(visibleType));
  1.1201 +      }
  1.1202 +
  1.1203 +      this._list.appendChild(item);
  1.1204 +    }
  1.1205 +
  1.1206 +    this._selectLastSelectedType();
  1.1207 +  },
  1.1208 +
  1.1209 +  _matchesFilter: function(aType) {
  1.1210 +    var filterValue = this._filter.value.toLowerCase();
  1.1211 +    return this._describeType(aType).toLowerCase().indexOf(filterValue) != -1 ||
  1.1212 +           this._describePreferredAction(aType).toLowerCase().indexOf(filterValue) != -1;
  1.1213 +  },
  1.1214 +
  1.1215 +  /**
  1.1216 +   * Describe, in a human-readable fashion, the type represented by the given
  1.1217 +   * handler info object.  Normally this is just the description provided by
  1.1218 +   * the info object, but if more than one object presents the same description,
  1.1219 +   * then we annotate the duplicate descriptions with the type itself to help
  1.1220 +   * users distinguish between those types.
  1.1221 +   *
  1.1222 +   * @param aHandlerInfo {nsIHandlerInfo} the type being described
  1.1223 +   * @returns {string} a description of the type
  1.1224 +   */
  1.1225 +  _describeType: function(aHandlerInfo) {
  1.1226 +    if (this._visibleTypeDescriptionCount[aHandlerInfo.description] > 1)
  1.1227 +      return this._prefsBundle.getFormattedString("typeDescriptionWithType",
  1.1228 +                                                  [aHandlerInfo.description,
  1.1229 +                                                   aHandlerInfo.type]);
  1.1230 +
  1.1231 +    return aHandlerInfo.description;
  1.1232 +  },
  1.1233 +
  1.1234 +  /**
  1.1235 +   * Describe, in a human-readable fashion, the preferred action to take on
  1.1236 +   * the type represented by the given handler info object.
  1.1237 +   *
  1.1238 +   * XXX Should this be part of the HandlerInfoWrapper interface?  It would
  1.1239 +   * violate the separation of model and view, but it might make more sense
  1.1240 +   * nonetheless (f.e. it would make sortTypes easier).
  1.1241 +   *
  1.1242 +   * @param aHandlerInfo {nsIHandlerInfo} the type whose preferred action
  1.1243 +   *                                      is being described
  1.1244 +   * @returns {string} a description of the action
  1.1245 +   */
  1.1246 +  _describePreferredAction: function(aHandlerInfo) {
  1.1247 +    // alwaysAskBeforeHandling overrides the preferred action, so if that flag
  1.1248 +    // is set, then describe that behavior instead.  For most types, this is
  1.1249 +    // the "alwaysAsk" string, but for the feed type we show something special.
  1.1250 +    if (aHandlerInfo.alwaysAskBeforeHandling) {
  1.1251 +      if (isFeedType(aHandlerInfo.type))
  1.1252 +        return this._prefsBundle.getFormattedString("previewInApp",
  1.1253 +                                                    [this._brandShortName]);
  1.1254 +      else
  1.1255 +        return this._prefsBundle.getString("alwaysAsk");
  1.1256 +    }
  1.1257 +
  1.1258 +    switch (aHandlerInfo.preferredAction) {
  1.1259 +      case Ci.nsIHandlerInfo.saveToDisk:
  1.1260 +        return this._prefsBundle.getString("saveFile");
  1.1261 +
  1.1262 +      case Ci.nsIHandlerInfo.useHelperApp:
  1.1263 +        var preferredApp = aHandlerInfo.preferredApplicationHandler;
  1.1264 +        var name;
  1.1265 +        if (preferredApp instanceof Ci.nsILocalHandlerApp)
  1.1266 +          name = getFileDisplayName(preferredApp.executable);
  1.1267 +        else
  1.1268 +          name = preferredApp.name;
  1.1269 +        return this._prefsBundle.getFormattedString("useApp", [name]);
  1.1270 +
  1.1271 +      case Ci.nsIHandlerInfo.handleInternally:
  1.1272 +        // For the feed type, handleInternally means live bookmarks.
  1.1273 +        if (isFeedType(aHandlerInfo.type)) {
  1.1274 +          return this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
  1.1275 +                                                      [this._brandShortName]);
  1.1276 +        }
  1.1277 +
  1.1278 +        if (aHandlerInfo instanceof InternalHandlerInfoWrapper) {
  1.1279 +          return this._prefsBundle.getFormattedString("previewInApp",
  1.1280 +                                                      [this._brandShortName]);
  1.1281 +        }
  1.1282 +
  1.1283 +        // For other types, handleInternally looks like either useHelperApp
  1.1284 +        // or useSystemDefault depending on whether or not there's a preferred
  1.1285 +        // handler app.
  1.1286 +        if (this.isValidHandlerApp(aHandlerInfo.preferredApplicationHandler))
  1.1287 +          return aHandlerInfo.preferredApplicationHandler.name;
  1.1288 +
  1.1289 +        return aHandlerInfo.defaultDescription;
  1.1290 +
  1.1291 +        // XXX Why don't we say the app will handle the type internally?
  1.1292 +        // Is it because the app can't actually do that?  But if that's true,
  1.1293 +        // then why would a preferredAction ever get set to this value
  1.1294 +        // in the first place?
  1.1295 +
  1.1296 +      case Ci.nsIHandlerInfo.useSystemDefault:
  1.1297 +        return this._prefsBundle.getFormattedString("useDefault",
  1.1298 +                                                    [aHandlerInfo.defaultDescription]);
  1.1299 +
  1.1300 +      case kActionUsePlugin:
  1.1301 +        return this._prefsBundle.getFormattedString("usePluginIn",
  1.1302 +                                                    [aHandlerInfo.pluginName,
  1.1303 +                                                     this._brandShortName]);
  1.1304 +    }
  1.1305 +  },
  1.1306 +
  1.1307 +  _selectLastSelectedType: function() {
  1.1308 +    // If the list is disabled by the pref.downloads.disable_button.edit_actions
  1.1309 +    // preference being locked, then don't select the type, as that would cause
  1.1310 +    // it to appear selected, with a different background and an actions menu
  1.1311 +    // that makes it seem like you can choose an action for the type.
  1.1312 +    if (this._list.disabled)
  1.1313 +      return;
  1.1314 +
  1.1315 +    var lastSelectedType = this._list.getAttribute("lastSelectedType");
  1.1316 +    if (!lastSelectedType)
  1.1317 +      return;
  1.1318 +
  1.1319 +    var item = this._list.getElementsByAttribute("type", lastSelectedType)[0];
  1.1320 +    if (!item)
  1.1321 +      return;
  1.1322 +
  1.1323 +    this._list.selectedItem = item;
  1.1324 +  },
  1.1325 +
  1.1326 +  /**
  1.1327 +   * Whether or not the given handler app is valid.
  1.1328 +   *
  1.1329 +   * @param aHandlerApp {nsIHandlerApp} the handler app in question
  1.1330 +   *
  1.1331 +   * @returns {boolean} whether or not it's valid
  1.1332 +   */
  1.1333 +  isValidHandlerApp: function(aHandlerApp) {
  1.1334 +    if (!aHandlerApp)
  1.1335 +      return false;
  1.1336 +
  1.1337 +    if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
  1.1338 +      return this._isValidHandlerExecutable(aHandlerApp.executable);
  1.1339 +
  1.1340 +    if (aHandlerApp instanceof Ci.nsIWebHandlerApp)
  1.1341 +      return aHandlerApp.uriTemplate;
  1.1342 +
  1.1343 +    if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
  1.1344 +      return aHandlerApp.uri;
  1.1345 +
  1.1346 +    return false;
  1.1347 +  },
  1.1348 +
  1.1349 +  _isValidHandlerExecutable: function(aExecutable) {
  1.1350 +    return aExecutable &&
  1.1351 +           aExecutable.exists() &&
  1.1352 +           aExecutable.isExecutable() &&
  1.1353 +// XXXben - we need to compare this with the running instance executable
  1.1354 +//          just don't know how to do that via script...
  1.1355 +// XXXmano TBD: can probably add this to nsIShellService
  1.1356 +#ifdef XP_WIN
  1.1357 +#expand    aExecutable.leafName != "__MOZ_APP_NAME__.exe";
  1.1358 +#else
  1.1359 +#ifdef XP_MACOSX
  1.1360 +#expand    aExecutable.leafName != "__MOZ_MACBUNDLE_NAME__";
  1.1361 +#else
  1.1362 +#expand    aExecutable.leafName != "__MOZ_APP_NAME__-bin";
  1.1363 +#endif
  1.1364 +#endif
  1.1365 +  },
  1.1366 +
  1.1367 +  /**
  1.1368 +   * Rebuild the actions menu for the selected entry.  Gets called by
  1.1369 +   * the richlistitem constructor when an entry in the list gets selected.
  1.1370 +   */
  1.1371 +  rebuildActionsMenu: function() {
  1.1372 +    var typeItem = this._list.selectedItem;
  1.1373 +    var handlerInfo = this._handledTypes[typeItem.type];
  1.1374 +    var menu =
  1.1375 +      document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
  1.1376 +    var menuPopup = menu.menupopup;
  1.1377 +
  1.1378 +    // Clear out existing items.
  1.1379 +    while (menuPopup.hasChildNodes())
  1.1380 +      menuPopup.removeChild(menuPopup.lastChild);
  1.1381 +
  1.1382 +    // Add the "Preview in Firefox" option for optional internal handlers.
  1.1383 +    if (handlerInfo instanceof InternalHandlerInfoWrapper) {
  1.1384 +      var internalMenuItem = document.createElement("menuitem");
  1.1385 +      internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
  1.1386 +      let label = this._prefsBundle.getFormattedString("previewInApp",
  1.1387 +                                                       [this._brandShortName]);
  1.1388 +      internalMenuItem.setAttribute("label", label);
  1.1389 +      internalMenuItem.setAttribute("tooltiptext", label);
  1.1390 +      internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
  1.1391 +      menuPopup.appendChild(internalMenuItem);
  1.1392 +    }
  1.1393 +
  1.1394 +    {
  1.1395 +      var askMenuItem = document.createElement("menuitem");
  1.1396 +      askMenuItem.setAttribute("alwaysAsk", "true");
  1.1397 +      let label;
  1.1398 +      if (isFeedType(handlerInfo.type))
  1.1399 +        label = this._prefsBundle.getFormattedString("previewInApp",
  1.1400 +                                                     [this._brandShortName]);
  1.1401 +      else
  1.1402 +        label = this._prefsBundle.getString("alwaysAsk");
  1.1403 +      askMenuItem.setAttribute("label", label);
  1.1404 +      askMenuItem.setAttribute("tooltiptext", label);
  1.1405 +      askMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
  1.1406 +      menuPopup.appendChild(askMenuItem);
  1.1407 +    }
  1.1408 +
  1.1409 +    // Create a menu item for saving to disk.
  1.1410 +    // Note: this option isn't available to protocol types, since we don't know
  1.1411 +    // what it means to save a URL having a certain scheme to disk, nor is it
  1.1412 +    // available to feeds, since the feed code doesn't implement the capability.
  1.1413 +    if ((handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
  1.1414 +        !isFeedType(handlerInfo.type)) {
  1.1415 +      var saveMenuItem = document.createElement("menuitem");
  1.1416 +      saveMenuItem.setAttribute("action", Ci.nsIHandlerInfo.saveToDisk);
  1.1417 +      let label = this._prefsBundle.getString("saveFile");
  1.1418 +      saveMenuItem.setAttribute("label", label);
  1.1419 +      saveMenuItem.setAttribute("tooltiptext", label);
  1.1420 +      saveMenuItem.setAttribute(APP_ICON_ATTR_NAME, "save");
  1.1421 +      menuPopup.appendChild(saveMenuItem);
  1.1422 +    }
  1.1423 +
  1.1424 +    // If this is the feed type, add a Live Bookmarks item.
  1.1425 +    if (isFeedType(handlerInfo.type)) {
  1.1426 +      var internalMenuItem = document.createElement("menuitem");
  1.1427 +      internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
  1.1428 +      let label = this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
  1.1429 +                                                       [this._brandShortName]);
  1.1430 +      internalMenuItem.setAttribute("label", label);
  1.1431 +      internalMenuItem.setAttribute("tooltiptext", label);
  1.1432 +      internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "feed");
  1.1433 +      menuPopup.appendChild(internalMenuItem);
  1.1434 +    }
  1.1435 +
  1.1436 +    // Add a separator to distinguish these items from the helper app items
  1.1437 +    // that follow them.
  1.1438 +    let menuItem = document.createElement("menuseparator");
  1.1439 +    menuPopup.appendChild(menuItem);
  1.1440 +
  1.1441 +    // Create a menu item for the OS default application, if any.
  1.1442 +    if (handlerInfo.hasDefaultHandler) {
  1.1443 +      var defaultMenuItem = document.createElement("menuitem");
  1.1444 +      defaultMenuItem.setAttribute("action", Ci.nsIHandlerInfo.useSystemDefault);
  1.1445 +      let label = this._prefsBundle.getFormattedString("useDefault",
  1.1446 +                                                       [handlerInfo.defaultDescription]);
  1.1447 +      defaultMenuItem.setAttribute("label", label);
  1.1448 +      defaultMenuItem.setAttribute("tooltiptext", handlerInfo.defaultDescription);
  1.1449 +      defaultMenuItem.setAttribute("image", this._getIconURLForSystemDefault(handlerInfo));
  1.1450 +
  1.1451 +      menuPopup.appendChild(defaultMenuItem);
  1.1452 +    }
  1.1453 +
  1.1454 +    // Create menu items for possible handlers.
  1.1455 +    let preferredApp = handlerInfo.preferredApplicationHandler;
  1.1456 +    let possibleApps = handlerInfo.possibleApplicationHandlers.enumerate();
  1.1457 +    var possibleAppMenuItems = [];
  1.1458 +    while (possibleApps.hasMoreElements()) {
  1.1459 +      let possibleApp = possibleApps.getNext();
  1.1460 +      if (!this.isValidHandlerApp(possibleApp))
  1.1461 +        continue;
  1.1462 +
  1.1463 +      let menuItem = document.createElement("menuitem");
  1.1464 +      menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp);
  1.1465 +      let label;
  1.1466 +      if (possibleApp instanceof Ci.nsILocalHandlerApp)
  1.1467 +        label = getFileDisplayName(possibleApp.executable);
  1.1468 +      else
  1.1469 +        label = possibleApp.name;
  1.1470 +      label = this._prefsBundle.getFormattedString("useApp", [label]);
  1.1471 +      menuItem.setAttribute("label", label);
  1.1472 +      menuItem.setAttribute("tooltiptext", label);
  1.1473 +      menuItem.setAttribute("image", this._getIconURLForHandlerApp(possibleApp));
  1.1474 +
  1.1475 +      // Attach the handler app object to the menu item so we can use it
  1.1476 +      // to make changes to the datastore when the user selects the item.
  1.1477 +      menuItem.handlerApp = possibleApp;
  1.1478 +
  1.1479 +      menuPopup.appendChild(menuItem);
  1.1480 +      possibleAppMenuItems.push(menuItem);
  1.1481 +    }
  1.1482 +
  1.1483 +    // Create a menu item for the plugin.
  1.1484 +    if (handlerInfo.pluginName) {
  1.1485 +      var pluginMenuItem = document.createElement("menuitem");
  1.1486 +      pluginMenuItem.setAttribute("action", kActionUsePlugin);
  1.1487 +      let label = this._prefsBundle.getFormattedString("usePluginIn",
  1.1488 +                                                       [handlerInfo.pluginName,
  1.1489 +                                                        this._brandShortName]);
  1.1490 +      pluginMenuItem.setAttribute("label", label);
  1.1491 +      pluginMenuItem.setAttribute("tooltiptext", label);
  1.1492 +      pluginMenuItem.setAttribute(APP_ICON_ATTR_NAME, "plugin");
  1.1493 +      menuPopup.appendChild(pluginMenuItem);
  1.1494 +    }
  1.1495 +
  1.1496 +    // Create a menu item for selecting a local application.
  1.1497 +#ifdef XP_WIN
  1.1498 +    // On Windows, selecting an application to open another application
  1.1499 +    // would be meaningless so we special case executables.
  1.1500 +    var executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService)
  1.1501 +                                                  .getTypeFromExtension("exe");
  1.1502 +    if (handlerInfo.type != executableType)
  1.1503 +#endif
  1.1504 +    {
  1.1505 +      let menuItem = document.createElement("menuitem");
  1.1506 +      menuItem.setAttribute("oncommand", "gApplicationsPane.chooseApp(event)");
  1.1507 +      let label = this._prefsBundle.getString("useOtherApp");
  1.1508 +      menuItem.setAttribute("label", label);
  1.1509 +      menuItem.setAttribute("tooltiptext", label);
  1.1510 +      menuPopup.appendChild(menuItem);
  1.1511 +    }
  1.1512 +
  1.1513 +    // Create a menu item for managing applications.
  1.1514 +    if (possibleAppMenuItems.length) {
  1.1515 +      let menuItem = document.createElement("menuseparator");
  1.1516 +      menuPopup.appendChild(menuItem);
  1.1517 +      menuItem = document.createElement("menuitem");
  1.1518 +      menuItem.setAttribute("oncommand", "gApplicationsPane.manageApp(event)");
  1.1519 +      menuItem.setAttribute("label", this._prefsBundle.getString("manageApp"));
  1.1520 +      menuPopup.appendChild(menuItem);
  1.1521 +    }
  1.1522 +
  1.1523 +    // Select the item corresponding to the preferred action.  If the always
  1.1524 +    // ask flag is set, it overrides the preferred action.  Otherwise we pick
  1.1525 +    // the item identified by the preferred action (when the preferred action
  1.1526 +    // is to use a helper app, we have to pick the specific helper app item).
  1.1527 +    if (handlerInfo.alwaysAskBeforeHandling)
  1.1528 +      menu.selectedItem = askMenuItem;
  1.1529 +    else switch (handlerInfo.preferredAction) {
  1.1530 +      case Ci.nsIHandlerInfo.handleInternally:
  1.1531 +        menu.selectedItem = internalMenuItem;
  1.1532 +        break;
  1.1533 +      case Ci.nsIHandlerInfo.useSystemDefault:
  1.1534 +        menu.selectedItem = defaultMenuItem;
  1.1535 +        break;
  1.1536 +      case Ci.nsIHandlerInfo.useHelperApp:
  1.1537 +        if (preferredApp)
  1.1538 +          menu.selectedItem = 
  1.1539 +            possibleAppMenuItems.filter(function(v) v.handlerApp.equals(preferredApp))[0];
  1.1540 +        break;
  1.1541 +      case kActionUsePlugin:
  1.1542 +        menu.selectedItem = pluginMenuItem;
  1.1543 +        break;
  1.1544 +      case Ci.nsIHandlerInfo.saveToDisk:
  1.1545 +        menu.selectedItem = saveMenuItem;
  1.1546 +        break;
  1.1547 +    }
  1.1548 +  },
  1.1549 +
  1.1550 +
  1.1551 +  //**************************************************************************//
  1.1552 +  // Sorting & Filtering
  1.1553 +
  1.1554 +  _sortColumn: null,
  1.1555 +
  1.1556 +  /**
  1.1557 +   * Sort the list when the user clicks on a column header.
  1.1558 +   */
  1.1559 +  sort: function (event) {
  1.1560 +    var column = event.target;
  1.1561 +
  1.1562 +    // If the user clicked on a new sort column, remove the direction indicator
  1.1563 +    // from the old column.
  1.1564 +    if (this._sortColumn && this._sortColumn != column)
  1.1565 +      this._sortColumn.removeAttribute("sortDirection");
  1.1566 +
  1.1567 +    this._sortColumn = column;
  1.1568 +
  1.1569 +    // Set (or switch) the sort direction indicator.
  1.1570 +    if (column.getAttribute("sortDirection") == "ascending")
  1.1571 +      column.setAttribute("sortDirection", "descending");
  1.1572 +    else
  1.1573 +      column.setAttribute("sortDirection", "ascending");
  1.1574 +
  1.1575 +    this._sortVisibleTypes();
  1.1576 +    this._rebuildView();
  1.1577 +  },
  1.1578 +
  1.1579 +  /**
  1.1580 +   * Sort the list of visible types by the current sort column/direction.
  1.1581 +   */
  1.1582 +  _sortVisibleTypes: function() {
  1.1583 +    if (!this._sortColumn)
  1.1584 +      return;
  1.1585 +
  1.1586 +    var t = this;
  1.1587 +
  1.1588 +    function sortByType(a, b) {
  1.1589 +      return t._describeType(a).toLowerCase().
  1.1590 +             localeCompare(t._describeType(b).toLowerCase());
  1.1591 +    }
  1.1592 +
  1.1593 +    function sortByAction(a, b) {
  1.1594 +      return t._describePreferredAction(a).toLowerCase().
  1.1595 +             localeCompare(t._describePreferredAction(b).toLowerCase());
  1.1596 +    }
  1.1597 +
  1.1598 +    switch (this._sortColumn.getAttribute("value")) {
  1.1599 +      case "type":
  1.1600 +        this._visibleTypes.sort(sortByType);
  1.1601 +        break;
  1.1602 +      case "action":
  1.1603 +        this._visibleTypes.sort(sortByAction);
  1.1604 +        break;
  1.1605 +    }
  1.1606 +
  1.1607 +    if (this._sortColumn.getAttribute("sortDirection") == "descending")
  1.1608 +      this._visibleTypes.reverse();
  1.1609 +  },
  1.1610 +
  1.1611 +  /**
  1.1612 +   * Filter the list when the user enters a filter term into the filter field.
  1.1613 +   */
  1.1614 +  filter: function() {
  1.1615 +    this._rebuildView();
  1.1616 +  },
  1.1617 +
  1.1618 +  focusFilterBox: function() {
  1.1619 +    this._filter.focus();
  1.1620 +    this._filter.select();
  1.1621 +  },
  1.1622 +
  1.1623 +
  1.1624 +  //**************************************************************************//
  1.1625 +  // Changes
  1.1626 +
  1.1627 +  onSelectAction: function(aActionItem) {
  1.1628 +    this._storingAction = true;
  1.1629 +
  1.1630 +    try {
  1.1631 +      this._storeAction(aActionItem);
  1.1632 +    }
  1.1633 +    finally {
  1.1634 +      this._storingAction = false;
  1.1635 +    }
  1.1636 +  },
  1.1637 +
  1.1638 +  _storeAction: function(aActionItem) {
  1.1639 +    var typeItem = this._list.selectedItem;
  1.1640 +    var handlerInfo = this._handledTypes[typeItem.type];
  1.1641 +
  1.1642 +    if (aActionItem.hasAttribute("alwaysAsk")) {
  1.1643 +      handlerInfo.alwaysAskBeforeHandling = true;
  1.1644 +    }
  1.1645 +    else if (aActionItem.hasAttribute("action")) {
  1.1646 +      let action = parseInt(aActionItem.getAttribute("action"));
  1.1647 +
  1.1648 +      // Set the plugin state if we're enabling or disabling a plugin.
  1.1649 +      if (action == kActionUsePlugin)
  1.1650 +        handlerInfo.enablePluginType();
  1.1651 +      else if (handlerInfo.pluginName && !handlerInfo.isDisabledPluginType)
  1.1652 +        handlerInfo.disablePluginType();
  1.1653 +
  1.1654 +      // Set the preferred application handler.
  1.1655 +      // We leave the existing preferred app in the list when we set
  1.1656 +      // the preferred action to something other than useHelperApp so that
  1.1657 +      // legacy datastores that don't have the preferred app in the list
  1.1658 +      // of possible apps still include the preferred app in the list of apps
  1.1659 +      // the user can choose to handle the type.
  1.1660 +      if (action == Ci.nsIHandlerInfo.useHelperApp)
  1.1661 +        handlerInfo.preferredApplicationHandler = aActionItem.handlerApp;
  1.1662 +
  1.1663 +      // Set the "always ask" flag.
  1.1664 +      handlerInfo.alwaysAskBeforeHandling = false;
  1.1665 +
  1.1666 +      // Set the preferred action.
  1.1667 +      handlerInfo.preferredAction = action;
  1.1668 +    }
  1.1669 +
  1.1670 +    handlerInfo.store();
  1.1671 +
  1.1672 +    // Make sure the handler info object is flagged to indicate that there is
  1.1673 +    // now some user configuration for the type.
  1.1674 +    handlerInfo.handledOnlyByPlugin = false;
  1.1675 +
  1.1676 +    // Update the action label and image to reflect the new preferred action.
  1.1677 +    typeItem.setAttribute("actionDescription",
  1.1678 +                          this._describePreferredAction(handlerInfo));
  1.1679 +    if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
  1.1680 +      typeItem.setAttribute("actionIcon",
  1.1681 +                            this._getIconURLForPreferredAction(handlerInfo));
  1.1682 +    }
  1.1683 +  },
  1.1684 +
  1.1685 +  manageApp: function(aEvent) {
  1.1686 +    // Don't let the normal "on select action" handler get this event,
  1.1687 +    // as we handle it specially ourselves.
  1.1688 +    aEvent.stopPropagation();
  1.1689 +
  1.1690 +    var typeItem = this._list.selectedItem;
  1.1691 +    var handlerInfo = this._handledTypes[typeItem.type];
  1.1692 +
  1.1693 +    openDialog("chrome://browser/content/preferences/applicationManager.xul",
  1.1694 +               "",
  1.1695 +               "modal,centerscreen,resizable=no",
  1.1696 +               handlerInfo);
  1.1697 +
  1.1698 +    // Rebuild the actions menu so that we revert to the previous selection,
  1.1699 +    // or "Always ask" if the previous default application has been removed
  1.1700 +    this.rebuildActionsMenu();
  1.1701 +
  1.1702 +    // update the richlistitem too. Will be visible when selecting another row
  1.1703 +    typeItem.setAttribute("actionDescription",
  1.1704 +                          this._describePreferredAction(handlerInfo));
  1.1705 +    if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
  1.1706 +      typeItem.setAttribute("actionIcon",
  1.1707 +                            this._getIconURLForPreferredAction(handlerInfo));
  1.1708 +    }
  1.1709 +  },
  1.1710 +
  1.1711 +  chooseApp: function(aEvent) {
  1.1712 +    // Don't let the normal "on select action" handler get this event,
  1.1713 +    // as we handle it specially ourselves.
  1.1714 +    aEvent.stopPropagation();
  1.1715 +
  1.1716 +    var handlerApp;
  1.1717 +    let chooseAppCallback = function(aHandlerApp) {
  1.1718 +      // Rebuild the actions menu whether the user picked an app or canceled.
  1.1719 +      // If they picked an app, we want to add the app to the menu and select it.
  1.1720 +      // If they canceled, we want to go back to their previous selection.
  1.1721 +      this.rebuildActionsMenu();
  1.1722 +
  1.1723 +      // If the user picked a new app from the menu, select it.
  1.1724 +      if (aHandlerApp) {
  1.1725 +        let typeItem = this._list.selectedItem;
  1.1726 +        let actionsMenu =
  1.1727 +          document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
  1.1728 +        let menuItems = actionsMenu.menupopup.childNodes;
  1.1729 +        for (let i = 0; i < menuItems.length; i++) {
  1.1730 +          let menuItem = menuItems[i];
  1.1731 +          if (menuItem.handlerApp && menuItem.handlerApp.equals(aHandlerApp)) {
  1.1732 +            actionsMenu.selectedIndex = i;
  1.1733 +            this.onSelectAction(menuItem);
  1.1734 +            break;
  1.1735 +          }
  1.1736 +        }
  1.1737 +      }
  1.1738 +    }.bind(this);
  1.1739 +
  1.1740 +#ifdef XP_WIN
  1.1741 +    var params = {};
  1.1742 +    var handlerInfo = this._handledTypes[this._list.selectedItem.type];
  1.1743 +
  1.1744 +    if (isFeedType(handlerInfo.type)) {
  1.1745 +      // MIME info will be null, create a temp object.
  1.1746 +      params.mimeInfo = this._mimeSvc.getFromTypeAndExtension(handlerInfo.type, 
  1.1747 +                                                 handlerInfo.primaryExtension);
  1.1748 +    } else {
  1.1749 +      params.mimeInfo = handlerInfo.wrappedHandlerInfo;
  1.1750 +    }
  1.1751 +
  1.1752 +    params.title         = this._prefsBundle.getString("fpTitleChooseApp");
  1.1753 +    params.description   = handlerInfo.description;
  1.1754 +    params.filename      = null;
  1.1755 +    params.handlerApp    = null;
  1.1756 +
  1.1757 +    window.openDialog("chrome://global/content/appPicker.xul", null,
  1.1758 +                      "chrome,modal,centerscreen,titlebar,dialog=yes",
  1.1759 +                      params);
  1.1760 +
  1.1761 +    if (this.isValidHandlerApp(params.handlerApp)) {
  1.1762 +      handlerApp = params.handlerApp;
  1.1763 +
  1.1764 +      // Add the app to the type's list of possible handlers.
  1.1765 +      handlerInfo.addPossibleApplicationHandler(handlerApp);
  1.1766 +    }
  1.1767 +
  1.1768 +    chooseAppCallback(handlerApp);
  1.1769 +#else
  1.1770 +    let winTitle = this._prefsBundle.getString("fpTitleChooseApp");
  1.1771 +    let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  1.1772 +    let fpCallback = function fpCallback_done(aResult) {
  1.1773 +      if (aResult == Ci.nsIFilePicker.returnOK && fp.file &&
  1.1774 +          this._isValidHandlerExecutable(fp.file)) {
  1.1775 +        handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
  1.1776 +                     createInstance(Ci.nsILocalHandlerApp);
  1.1777 +        handlerApp.name = getFileDisplayName(fp.file);
  1.1778 +        handlerApp.executable = fp.file;
  1.1779 +
  1.1780 +        // Add the app to the type's list of possible handlers.
  1.1781 +        let handlerInfo = this._handledTypes[this._list.selectedItem.type];
  1.1782 +        handlerInfo.addPossibleApplicationHandler(handlerApp);
  1.1783 +
  1.1784 +        chooseAppCallback(handlerApp);
  1.1785 +      }
  1.1786 +    }.bind(this);
  1.1787 +
  1.1788 +    // Prompt the user to pick an app.  If they pick one, and it's a valid
  1.1789 +    // selection, then add it to the list of possible handlers.
  1.1790 +    fp.init(window, winTitle, Ci.nsIFilePicker.modeOpen);
  1.1791 +    fp.appendFilters(Ci.nsIFilePicker.filterApps);
  1.1792 +    fp.open(fpCallback);
  1.1793 +#endif
  1.1794 +  },
  1.1795 +
  1.1796 +  // Mark which item in the list was last selected so we can reselect it
  1.1797 +  // when we rebuild the list or when the user returns to the prefpane.
  1.1798 +  onSelectionChanged: function() {
  1.1799 +    if (this._list.selectedItem)
  1.1800 +      this._list.setAttribute("lastSelectedType",
  1.1801 +                              this._list.selectedItem.getAttribute("type"));
  1.1802 +  },
  1.1803 +
  1.1804 +  _setIconClassForPreferredAction: function(aHandlerInfo, aElement) {
  1.1805 +    // If this returns true, the attribute that CSS sniffs for was set to something
  1.1806 +    // so you shouldn't manually set an icon URI.
  1.1807 +    // This removes the existing actionIcon attribute if any, even if returning false.
  1.1808 +    aElement.removeAttribute("actionIcon");
  1.1809 +
  1.1810 +    if (aHandlerInfo.alwaysAskBeforeHandling) {
  1.1811 +      aElement.setAttribute(APP_ICON_ATTR_NAME, "ask");
  1.1812 +      return true;
  1.1813 +    }
  1.1814 +
  1.1815 +    switch (aHandlerInfo.preferredAction) {
  1.1816 +      case Ci.nsIHandlerInfo.saveToDisk:
  1.1817 +        aElement.setAttribute(APP_ICON_ATTR_NAME, "save");
  1.1818 +        return true;
  1.1819 +
  1.1820 +      case Ci.nsIHandlerInfo.handleInternally:
  1.1821 +        if (isFeedType(aHandlerInfo.type)) {
  1.1822 +          aElement.setAttribute(APP_ICON_ATTR_NAME, "feed");
  1.1823 +          return true;
  1.1824 +        } else if (aHandlerInfo instanceof InternalHandlerInfoWrapper) {
  1.1825 +          aElement.setAttribute(APP_ICON_ATTR_NAME, "ask");
  1.1826 +          return true;
  1.1827 +        }
  1.1828 +        break;
  1.1829 +
  1.1830 +      case kActionUsePlugin:
  1.1831 +        aElement.setAttribute(APP_ICON_ATTR_NAME, "plugin");
  1.1832 +        return true;
  1.1833 +    }
  1.1834 +    aElement.removeAttribute(APP_ICON_ATTR_NAME);
  1.1835 +    return false;
  1.1836 +  },
  1.1837 +
  1.1838 +  _getIconURLForPreferredAction: function(aHandlerInfo) {
  1.1839 +    switch (aHandlerInfo.preferredAction) {
  1.1840 +      case Ci.nsIHandlerInfo.useSystemDefault:
  1.1841 +        return this._getIconURLForSystemDefault(aHandlerInfo);
  1.1842 +
  1.1843 +      case Ci.nsIHandlerInfo.useHelperApp:
  1.1844 +        let (preferredApp = aHandlerInfo.preferredApplicationHandler) {
  1.1845 +          if (this.isValidHandlerApp(preferredApp))
  1.1846 +            return this._getIconURLForHandlerApp(preferredApp);
  1.1847 +        }
  1.1848 +        break;
  1.1849 +
  1.1850 +      // This should never happen, but if preferredAction is set to some weird
  1.1851 +      // value, then fall back to the generic application icon.
  1.1852 +      default:
  1.1853 +        return ICON_URL_APP;
  1.1854 +    }
  1.1855 +  },
  1.1856 +
  1.1857 +  _getIconURLForHandlerApp: function(aHandlerApp) {
  1.1858 +    if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
  1.1859 +      return this._getIconURLForFile(aHandlerApp.executable);
  1.1860 +
  1.1861 +    if (aHandlerApp instanceof Ci.nsIWebHandlerApp)
  1.1862 +      return this._getIconURLForWebApp(aHandlerApp.uriTemplate);
  1.1863 +
  1.1864 +    if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
  1.1865 +      return this._getIconURLForWebApp(aHandlerApp.uri)
  1.1866 +
  1.1867 +    // We know nothing about other kinds of handler apps.
  1.1868 +    return "";
  1.1869 +  },
  1.1870 +
  1.1871 +  _getIconURLForFile: function(aFile) {
  1.1872 +    var fph = this._ioSvc.getProtocolHandler("file").
  1.1873 +              QueryInterface(Ci.nsIFileProtocolHandler);
  1.1874 +    var urlSpec = fph.getURLSpecFromFile(aFile);
  1.1875 +
  1.1876 +    return "moz-icon://" + urlSpec + "?size=16";
  1.1877 +  },
  1.1878 +
  1.1879 +  _getIconURLForWebApp: function(aWebAppURITemplate) {
  1.1880 +    var uri = this._ioSvc.newURI(aWebAppURITemplate, null, null);
  1.1881 +
  1.1882 +    // Unfortunately we can't use the favicon service to get the favicon,
  1.1883 +    // because the service looks in the annotations table for a record with
  1.1884 +    // the exact URL we give it, and users won't have such records for URLs
  1.1885 +    // they don't visit, and users won't visit the web app's URL template,
  1.1886 +    // they'll only visit URLs derived from that template (i.e. with %s
  1.1887 +    // in the template replaced by the URL of the content being handled).
  1.1888 +
  1.1889 +    if (/^https?$/.test(uri.scheme) && this._prefSvc.getBoolPref("browser.chrome.favicons"))
  1.1890 +      return uri.prePath + "/favicon.ico";
  1.1891 +
  1.1892 +    return "";
  1.1893 +  },
  1.1894 +
  1.1895 +  _getIconURLForSystemDefault: function(aHandlerInfo) {
  1.1896 +    // Handler info objects for MIME types on some OSes implement a property bag
  1.1897 +    // interface from which we can get an icon for the default app, so if we're
  1.1898 +    // dealing with a MIME type on one of those OSes, then try to get the icon.
  1.1899 +    if ("wrappedHandlerInfo" in aHandlerInfo) {
  1.1900 +      let wrappedHandlerInfo = aHandlerInfo.wrappedHandlerInfo;
  1.1901 +
  1.1902 +      if (wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
  1.1903 +          wrappedHandlerInfo instanceof Ci.nsIPropertyBag) {
  1.1904 +        try {
  1.1905 +          let url = wrappedHandlerInfo.getProperty("defaultApplicationIconURL");
  1.1906 +          if (url)
  1.1907 +            return url + "?size=16";
  1.1908 +        }
  1.1909 +        catch(ex) {}
  1.1910 +      }
  1.1911 +    }
  1.1912 +
  1.1913 +    // If this isn't a MIME type object on an OS that supports retrieving
  1.1914 +    // the icon, or if we couldn't retrieve the icon for some other reason,
  1.1915 +    // then use a generic icon.
  1.1916 +    return ICON_URL_APP;
  1.1917 +  }
  1.1918 +
  1.1919 +};

mercurial