browser/components/preferences/applications.js

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

mercurial