/* # -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is the Download Actions Manager. # # The Initial Developer of the Original Code is # Ben Goodger. # Portions created by the Initial Developer are Copyright (C) 2000 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Ben Goodger # Jeff Walden # Asaf Romano # Myk Melez # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** */ //****************************************************************************// // Constants & Enumeration Values /* #ifndef XP_MACOSX */ var Cc = Components.classes; var Ci = Components.interfaces; var Cr = Components.results; var TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed"; const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; /* #endif */ const PREF_DISABLED_PLUGIN_TYPES = "plugin.disable_full_page_plugin_for_types"; // Preferences that affect which entries to show in the list. const PREF_SHOW_PLUGINS_IN_LIST = "browser.download.show_plugins_in_list"; const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS = "browser.download.hide_plugins_without_extensions"; /* * Preferences where we store handling information about the feed type. * * browser.feeds.handler * - "bookmarks", "reader" (clarified further using the .default preference), * or "ask" -- indicates the default handler being used to process feeds; * "bookmarks" is obsolete; to specify that the handler is bookmarks, * set browser.feeds.handler.default to "bookmarks"; * * browser.feeds.handler.default * - "bookmarks", "client" or "web" -- indicates the chosen feed reader used * to display feeds, either transiently (i.e., when the "use as default" * checkbox is unchecked, corresponds to when browser.feeds.handler=="ask") * or more permanently (i.e., the item displayed in the dropdown in Feeds * preferences) * * browser.feeds.handler.webservice * - the URL of the currently selected web service used to read feeds * * browser.feeds.handlers.application * - nsILocalFile, stores the current client-side feed reading app if one has * been chosen */ const PREF_FEED_SELECTED_APP = "browser.feeds.handlers.application"; const PREF_FEED_SELECTED_WEB = "browser.feeds.handlers.webservice"; const PREF_FEED_SELECTED_ACTION = "browser.feeds.handler"; const PREF_FEED_SELECTED_READER = "browser.feeds.handler.default"; // The nsHandlerInfoAction enumeration values in nsIHandlerInfo identify // the actions the application can take with content of various types. // But since nsIHandlerInfo doesn't support plugins, there's no value // identifying the "use plugin" action, so we use this constant instead. const kActionUsePlugin = 5; const ICON_URL_PLUGIN = "chrome://browser/skin/preferences/plugin.png"; const ICON_URL_LIVEMARK = "chrome://browser/skin/page-livemarks.png"; const ICON_URL_APP = "chrome://browser/skin/preferences/application.png"; // FIXME: come up with icons for the "always ask" and "save file" actions. // Filed as bug 398627. const ICON_URL_ASK = ICON_URL_APP; const ICON_URL_SAVE = ICON_URL_APP; //****************************************************************************// // Utilities function getDisplayNameForFile(aFile) { /* #ifdef XP_WIN */ if (aFile instanceof Ci.nsILocalFileWin) { try { return aFile.getVersionInfoField("FileDescription"); } catch(ex) { // fall through to the file name } } /* #endif #ifdef XP_MACOSX */ if (aFile instanceof Ci.nsILocalFileMac) { try { return aFile.bundleDisplayName; } catch(ex) { // fall through to the file name } } /* #endif */ return Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService). newFileURI(aFile). QueryInterface(Ci.nsIURL). fileName; } function getLocalHandlerApp(aFile) { var localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]. createInstance(Ci.nsILocalHandlerApp); localHandlerApp.name = getDisplayNameForFile(aFile); localHandlerApp.executable = aFile; return localHandlerApp; } /** * An enumeration of items in a JS array. * * FIXME: use ArrayConverter once it lands (bug 380839). * * @constructor */ function ArrayEnumerator(aItems) { this._index = 0; this._contents = aItems; } ArrayEnumerator.prototype = { _index: 0, hasMoreElements: function() { return this._index < this._contents.length; }, getNext: function() { return this._contents[this._index++]; } }; //****************************************************************************// // HandlerInfoWrapper /** * This object wraps nsIHandlerInfo with some additional functionality * the Applications prefpane needs to display and allow modification of * the list of handled types. * * We create an instance of this wrapper for each entry we might display * in the prefpane, and we compose the instances from various sources, * including navigator.plugins and the handler service. * * We don't implement all the original nsIHandlerInfo functionality, * just the stuff that the prefpane needs. * * In theory, all of the custom functionality in this wrapper should get * pushed down into nsIHandlerInfo eventually. */ function HandlerInfoWrapper(aType, aHandlerInfo) { this._type = aType; this.wrappedHandlerInfo = aHandlerInfo; } HandlerInfoWrapper.prototype = { // The wrapped nsIHandlerInfo object. In general, this object is private, // but there are a couple cases where callers access it directly for things // we haven't (yet?) implemented, so we make it a public property. wrappedHandlerInfo: null, //**************************************************************************// // Convenience Utils _handlerSvc: Cc["@mozilla.org/uriloader/handler-service;1"]. getService(Ci.nsIHandlerService), // Retrieve this as nsIPrefBranch and then immediately QI to nsIPrefBranch2 // so both interfaces are available to callers. _prefSvc: Cc["@mozilla.org/preferences-service;1"]. getService(Ci.nsIPrefBranch). QueryInterface(Ci.nsIPrefBranch2), _categoryMgr: Cc["@mozilla.org/categorymanager;1"]. getService(Ci.nsICategoryManager), element: function(aID) { return document.getElementById(aID); }, //**************************************************************************// // nsIHandlerInfo // The MIME type or protocol scheme. _type: null, get type() { return this._type; }, get description() { if (this.wrappedHandlerInfo.description) return this.wrappedHandlerInfo.description; if (this.primaryExtension) { var extension = this.primaryExtension.toUpperCase(); return this.element("bundlePreferences").getFormattedString("fileEnding", [extension]); } return this.type; }, get preferredApplicationHandler() { return this.wrappedHandlerInfo.preferredApplicationHandler; }, set preferredApplicationHandler(aNewValue) { this.wrappedHandlerInfo.preferredApplicationHandler = aNewValue; // Make sure the preferred handler is in the set of possible handlers. if (aNewValue) this.addPossibleApplicationHandler(aNewValue) }, get possibleApplicationHandlers() { return this.wrappedHandlerInfo.possibleApplicationHandlers; }, addPossibleApplicationHandler: function(aNewHandler) { var possibleApps = this.possibleApplicationHandlers.enumerate(); while (possibleApps.hasMoreElements()) { if (possibleApps.getNext().equals(aNewHandler)) return; } this.possibleApplicationHandlers.appendElement(aNewHandler, false); }, get hasDefaultHandler() { return this.wrappedHandlerInfo.hasDefaultHandler; }, get defaultDescription() { return this.wrappedHandlerInfo.defaultDescription; }, // What to do with content of this type. get preferredAction() { // If we have an enabled plugin, then the action is to use that plugin. if (this.plugin && !this.isDisabledPluginType) return kActionUsePlugin; // If the action is to use a helper app, but we don't have a preferred // handler app, then switch to using the system default, if any; otherwise // fall back to saving to disk, which is the default action in nsMIMEInfo. // Note: "save to disk" is an invalid value for protocol info objects, // but the alwaysAskBeforeHandling getter will detect that situation // and always return true in that case to override this invalid value. if (this.wrappedHandlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp && !gApplicationsPane.isValidHandlerApp(this.preferredApplicationHandler)) { if (this.wrappedHandlerInfo.hasDefaultHandler) return Ci.nsIHandlerInfo.useSystemDefault; else return Ci.nsIHandlerInfo.saveToDisk; } return this.wrappedHandlerInfo.preferredAction; }, set preferredAction(aNewValue) { // We don't modify the preferred action if the new action is to use a plugin // because handler info objects don't understand our custom "use plugin" // value. Also, leaving it untouched means that we can automatically revert // to the old setting if the user ever removes the plugin. if (aNewValue != kActionUsePlugin) this.wrappedHandlerInfo.preferredAction = aNewValue; }, get alwaysAskBeforeHandling() { // If this type is handled only by a plugin, we can't trust the value // in the handler info object, since it'll be a default based on the absence // of any user configuration, and the default in that case is to always ask, // even though we never ask for content handled by a plugin, so special case // plugin-handled types by returning false here. if (this.plugin && this.handledOnlyByPlugin) return false; // If this is a protocol type and the preferred action is "save to disk", // which is invalid for such types, then return true here to override that // action. This could happen when the preferred action is to use a helper // app, but the preferredApplicationHandler is invalid, and there isn't // a default handler, so the preferredAction getter returns save to disk // instead. if (!(this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) && this.preferredAction == Ci.nsIHandlerInfo.saveToDisk) return true; return this.wrappedHandlerInfo.alwaysAskBeforeHandling; }, set alwaysAskBeforeHandling(aNewValue) { this.wrappedHandlerInfo.alwaysAskBeforeHandling = aNewValue; }, //**************************************************************************// // nsIMIMEInfo // The primary file extension associated with this type, if any. // // XXX Plugin objects contain an array of MimeType objects with "suffixes" // properties; if this object has an associated plugin, shouldn't we check // those properties for an extension? get primaryExtension() { try { if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo && this.wrappedHandlerInfo.primaryExtension) return this.wrappedHandlerInfo.primaryExtension } catch(ex) {} return null; }, //**************************************************************************// // Plugin Handling // A plugin that can handle this type, if any. // // Note: just because we have one doesn't mean it *will* handle the type. // That depends on whether or not the type is in the list of types for which // plugin handling is disabled. plugin: null, // Whether or not this type is only handled by a plugin or is also handled // by some user-configured action as specified in the handler info object. // // Note: we can't just check if there's a handler info object for this type, // because OS and user configuration is mixed up in the handler info object, // so we always need to retrieve it for the OS info and can't tell whether // it represents only OS-default information or user-configured information. // // FIXME: once handler info records are broken up into OS-provided records // and user-configured records, stop using this boolean flag and simply // check for the presence of a user-configured record to determine whether // or not this type is only handled by a plugin. Filed as bug 395142. handledOnlyByPlugin: undefined, get isDisabledPluginType() { return this._getDisabledPluginTypes().indexOf(this.type) != -1; }, _getDisabledPluginTypes: function() { var types = ""; if (this._prefSvc.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES)) types = this._prefSvc.getCharPref(PREF_DISABLED_PLUGIN_TYPES); // Only split if the string isn't empty so we don't end up with an array // containing a single empty string. if (types != "") return types.split(","); return []; }, disablePluginType: function() { var disabledPluginTypes = this._getDisabledPluginTypes(); if (disabledPluginTypes.indexOf(this.type) == -1) disabledPluginTypes.push(this.type); this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES, disabledPluginTypes.join(",")); // Update the category manager so existing browser windows update. this._categoryMgr.deleteCategoryEntry("Gecko-Content-Viewers", this.type, false); }, enablePluginType: function() { var disabledPluginTypes = this._getDisabledPluginTypes(); var type = this.type; disabledPluginTypes = disabledPluginTypes.filter(function(v) v != type); this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES, disabledPluginTypes.join(",")); // Update the category manager so existing browser windows update. this._categoryMgr. addCategoryEntry("Gecko-Content-Viewers", this.type, "@mozilla.org/content/plugin/document-loader-factory;1", false, true); }, //**************************************************************************// // Storage store: function() { this._handlerSvc.store(this.wrappedHandlerInfo); }, //**************************************************************************// // Icons get smallIcon() { return this._getIcon(16); }, get largeIcon() { return this._getIcon(32); }, _getIcon: function(aSize) { if (this.primaryExtension) return "moz-icon://goat." + this.primaryExtension + "?size=" + aSize; if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) return "moz-icon://goat?size=" + aSize + "&contentType=" + this.type; // FIXME: consider returning some generic icon when we can't get a URL for // one (for example in the case of protocol schemes). Filed as bug 395141. return null; } }; //****************************************************************************// // Feed Handler Info /** * This object implements nsIHandlerInfo for the feed type. It's a separate * object because we currently store handling information for the feed type * in a set of preferences rather than the nsIHandlerService-managed datastore. * * This object inherits from HandlerInfoWrapper in order to get functionality * that isn't special to the feed type. * * XXX Should we inherit from HandlerInfoWrapper? After all, we override * most of that wrapper's properties and methods, and we have to dance around * the fact that the wrapper expects to have a wrappedHandlerInfo, which we * don't provide. */ var feedHandlerInfo = { __proto__: new HandlerInfoWrapper(TYPE_MAYBE_FEED, null), //**************************************************************************// // Convenience Utils _converterSvc: Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]. getService(Ci.nsIWebContentConverterService), _shellSvc: #ifdef HAVE_SHELL_SERVICE Cc["@mozilla.org/browser/shell-service;1"]. getService(Ci.nsIShellService), #else null, #endif //**************************************************************************// // nsIHandlerInfo get description() { return this.element("bundlePreferences").getString("webFeed"); }, get preferredApplicationHandler() { switch (this.element(PREF_FEED_SELECTED_READER).value) { case "client": var file = this.element(PREF_FEED_SELECTED_APP).value; if (file) return getLocalHandlerApp(file); return null; case "web": var uri = this.element(PREF_FEED_SELECTED_WEB).value; if (!uri) return null; return this._converterSvc.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uri); case "bookmarks": default: // When the pref is set to bookmarks, we handle feeds internally, // we don't forward them to a local or web handler app, so there is // no preferred handler. return null; } }, set preferredApplicationHandler(aNewValue) { if (aNewValue instanceof Ci.nsILocalHandlerApp) { this.element(PREF_FEED_SELECTED_APP).value = aNewValue.executable; this.element(PREF_FEED_SELECTED_READER).value = "client"; } else if (aNewValue instanceof Ci.nsIWebContentHandlerInfo) { this.element(PREF_FEED_SELECTED_WEB).value = aNewValue.uri; this.element(PREF_FEED_SELECTED_READER).value = "web"; // Make the web handler be the new "auto handler" for feeds. // Note: we don't have to unregister the auto handler when the user picks // a non-web handler (local app, Live Bookmarks, etc.) because the service // only uses the "auto handler" when the selected reader is a web handler. // We also don't have to unregister it when the user turns on "always ask" // (i.e. preview in browser), since that also overrides the auto handler. this._converterSvc.setAutoHandler(this.type, aNewValue); } }, _possibleApplicationHandlers: null, get possibleApplicationHandlers() { if (this._possibleApplicationHandlers) return this._possibleApplicationHandlers; // A minimal implementation of nsIMutableArray. It only supports the two // methods its callers invoke, namely appendElement and nsIArray::enumerate. this._possibleApplicationHandlers = { _inner: [], QueryInterface: function(aIID) { if (aIID.equals(Ci.nsIMutableArray) || aIID.equals(Ci.nsIArray) || aIID.equals(Ci.nsISupports)) return this; throw Cr.NS_ERROR_NO_INTERFACE; }, enumerate: function() { return new ArrayEnumerator(this._inner); }, appendElement: function(aHandlerApp, aWeak) { this._inner.push(aHandlerApp); } }; // Add the selected local app if it's different from the OS default handler. // Unlike for other types, we can store only one local app at a time for the // feed type, since we store it in a preference that historically stores // only a single path. But we display all the local apps the user chooses // while the prefpane is open, only dropping the list when the user closes // the prefpane, for maximum usability and consistency with other types. var preferredAppFile = this.element(PREF_FEED_SELECTED_APP).value; if (preferredAppFile) { let preferredApp = getLocalHandlerApp(preferredAppFile); let defaultApp = this._defaultApplicationHandler; if (!defaultApp || !defaultApp.equals(preferredApp)) this._possibleApplicationHandlers.appendElement(preferredApp, false); } // Add the registered web handlers. There can be any number of these. var webHandlers = this._converterSvc.getContentHandlers(this.type, {}); for each (let webHandler in webHandlers) this._possibleApplicationHandlers.appendElement(webHandler, false); return this._possibleApplicationHandlers; }, __defaultApplicationHandler: undefined, get _defaultApplicationHandler() { if (typeof this.__defaultApplicationHandler != "undefined") return this.__defaultApplicationHandler; var defaultFeedReader = null; #ifdef HAVE_SHELL_SERVICE try { defaultFeedReader = this._shellSvc.defaultFeedReader; } catch(ex) { // no default reader } #endif if (defaultFeedReader) { let handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]. createInstance(Ci.nsIHandlerApp); handlerApp.name = getDisplayNameForFile(defaultFeedReader); handlerApp.QueryInterface(Ci.nsILocalHandlerApp); handlerApp.executable = defaultFeedReader; this.__defaultApplicationHandler = handlerApp; } else { this.__defaultApplicationHandler = null; } return this.__defaultApplicationHandler; }, get hasDefaultHandler() { #ifdef HAVE_SHELL_SERVICE try { if (this._shellSvc.defaultFeedReader) return true; } catch(ex) { // no default reader } #endif return false; }, get defaultDescription() { if (this.hasDefaultHandler) return this._defaultApplicationHandler.name; // Should we instead return null? return ""; }, // What to do with content of this type. get preferredAction() { switch (this.element(PREF_FEED_SELECTED_ACTION).value) { case "bookmarks": return Ci.nsIHandlerInfo.handleInternally; case "reader": { let preferredApp = this.preferredApplicationHandler; let defaultApp = this._defaultApplicationHandler; // If we have a valid preferred app, return useSystemDefault if it's // the default app; otherwise return useHelperApp. if (gApplicationsPane.isValidHandlerApp(preferredApp)) { if (defaultApp && defaultApp.equals(preferredApp)) return Ci.nsIHandlerInfo.useSystemDefault; return Ci.nsIHandlerInfo.useHelperApp; } // The pref is set to "reader", but we don't have a valid preferred app. // What do we do now? Not sure this is the best option (perhaps we // should direct the user to the default app, if any), but for now let's // direct the user to live bookmarks. return Ci.nsIHandlerInfo.handleInternally; } // If the action is "ask", then alwaysAskBeforeHandling will override // the action, so it doesn't matter what we say it is, it just has to be // something that doesn't cause the controller to hide the type. case "ask": default: return Ci.nsIHandlerInfo.handleInternally; } }, set preferredAction(aNewValue) { switch (aNewValue) { case Ci.nsIHandlerInfo.handleInternally: this.element(PREF_FEED_SELECTED_READER).value = "bookmarks"; break; case Ci.nsIHandlerInfo.useHelperApp: this.element(PREF_FEED_SELECTED_ACTION).value = "reader"; // The controller has already set preferredApplicationHandler // to the new helper app. break; case Ci.nsIHandlerInfo.useSystemDefault: this.element(PREF_FEED_SELECTED_ACTION).value = "reader"; this.preferredApplicationHandler = this._defaultApplicationHandler; break; } }, get alwaysAskBeforeHandling() { return this.element(PREF_FEED_SELECTED_ACTION).value == "ask"; }, set alwaysAskBeforeHandling(aNewValue) { if (aNewValue == true) this.element(PREF_FEED_SELECTED_ACTION).value = "ask"; else this.element(PREF_FEED_SELECTED_ACTION).value = "reader"; }, // Whether or not we are currently storing the action selected by the user. // We use this to suppress notification-triggered updates to the list when // we make changes that may spawn such updates, specifically when we change // the action for the feed type, which results in feed preference updates, // which spawn "pref changed" notifications that would otherwise cause us // to rebuild the view unnecessarily. _storingAction: false, //**************************************************************************// // nsIMIMEInfo get primaryExtension() { return "xml"; }, //**************************************************************************// // Storage // Changes to the preferred action and handler take effect immediately // (we write them out to the preferences right as they happen), so we don't // need to do anything when the controller calls store() after modifying // the handler. // XXX Should we hold off on making the changes until this method gets called? store: function() {}, //**************************************************************************// // Icons get smallIcon() { return "chrome://browser/skin/feeds/feedIcon16.png"; }, get largeIcon() { return "chrome://browser/skin/feeds/feedIcon.png"; } }; //****************************************************************************// // Prefpane Controller var gApplicationsPane = { // The set of types the app knows how to handle. A hash of HandlerInfoWrapper // objects, indexed by type. _handledTypes: {}, // The list of types we can show, sorted by the sort column/direction. // An array of HandlerInfoWrapper objects. We build this list when we first // load the data and then rebuild it when users change a pref that affects // what types we can show or change the sort column/direction. // Note: this isn't necessarily the list of types we *will* show; if the user // provides a filter string, we'll only show the subset of types in this list // that match that string. _visibleTypes: [], // A count of the number of times each visible type description appears. // We use these counts to determine whether or not to annotate descriptions // with their types to distinguish duplicate descriptions from each other. // A hash of integer counts, indexed by string description. _visibleTypeDescriptionCount: {}, //**************************************************************************// // Convenience & Performance Shortcuts // These get defined by init(). _brandShortName : null, _prefsBundle : null, _list : null, _filter : null, // Retrieve this as nsIPrefBranch and then immediately QI to nsIPrefBranch2 // so both interfaces are available to callers. _prefSvc : Cc["@mozilla.org/preferences-service;1"]. getService(Ci.nsIPrefBranch). QueryInterface(Ci.nsIPrefBranch2), _mimeSvc : Cc["@mozilla.org/mime;1"]. getService(Ci.nsIMIMEService), _helperAppSvc : Cc["@mozilla.org/uriloader/external-helper-app-service;1"]. getService(Ci.nsIExternalHelperAppService), _handlerSvc : Cc["@mozilla.org/uriloader/handler-service;1"]. getService(Ci.nsIHandlerService), _ioSvc : Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService), //**************************************************************************// // Initialization & Destruction init: function() { // Initialize shortcuts to some commonly accessed elements & values. this._brandShortName = document.getElementById("bundleBrand").getString("brandShortName"); this._prefsBundle = document.getElementById("bundlePreferences"); this._list = document.getElementById("handlersView"); this._filter = document.getElementById("filter"); // Observe preferences that influence what we display so we can rebuild // the view when they change. this._prefSvc.addObserver(PREF_SHOW_PLUGINS_IN_LIST, this, false); this._prefSvc.addObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this, false); this._prefSvc.addObserver(PREF_FEED_SELECTED_APP, this, false); this._prefSvc.addObserver(PREF_FEED_SELECTED_WEB, this, false); this._prefSvc.addObserver(PREF_FEED_SELECTED_ACTION, this, false); this._prefSvc.addObserver(PREF_FEED_SELECTED_READER, this, false); // Listen for window unload so we can remove our preference observers. window.addEventListener("unload", this, false); // Figure out how we should be sorting the list. We persist sort settings // across sessions, so we can't assume the default sort column/direction. // XXX should we be using the XUL sort service instead? if (document.getElementById("typeColumn").hasAttribute("sortDirection")) this._sortColumn = document.getElementById("typeColumn"); else if (document.getElementById("actionColumn").hasAttribute("sortDirection")) this._sortColumn = document.getElementById("actionColumn"); // Load the data and build the list of handlers. // By doing this in a timeout, we let the preferences dialog resize itself // to an appropriate size before we add a bunch of items to the list. // Otherwise, if there are many items, and the Applications prefpane // is the one that gets displayed when the user first opens the dialog, // the dialog might stretch too much in an attempt to fit them all in. // XXX Shouldn't we perhaps just set a max-height on the richlistbox? var _delayedPaneLoad = function(self) { self._loadData(); self._rebuildVisibleTypes(); self._sortVisibleTypes(); self._rebuildView(); } setTimeout(_delayedPaneLoad, 0, this); }, destroy: function() { window.removeEventListener("unload", this, false); this._prefSvc.removeObserver(PREF_SHOW_PLUGINS_IN_LIST, this); this._prefSvc.removeObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this); this._prefSvc.removeObserver(PREF_FEED_SELECTED_APP, this); this._prefSvc.removeObserver(PREF_FEED_SELECTED_WEB, this); this._prefSvc.removeObserver(PREF_FEED_SELECTED_ACTION, this); this._prefSvc.removeObserver(PREF_FEED_SELECTED_READER, this); }, //**************************************************************************// // nsISupports QueryInterface: function(aIID) { if (aIID.equals(Ci.nsIObserver) || aIID.equals(Ci.nsIDOMEventListener || aIID.equals(Ci.nsISupports))) return this; throw Cr.NS_ERROR_NO_INTERFACE; }, //**************************************************************************// // nsIObserver observe: function (aSubject, aTopic, aData) { // Rebuild the list when there are changes to preferences that influence // whether or not to show certain entries in the list. if (aTopic == "nsPref:changed" && !this._storingAction) { // These two prefs alter the list of visible types, so we have to rebuild // that list when they change. if (aData == PREF_SHOW_PLUGINS_IN_LIST || aData == PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS) { this._rebuildVisibleTypes(); this._sortVisibleTypes(); } // All the prefs we observe can affect what we display, so we rebuild // the view when any of them changes. this._rebuildView(); } }, //**************************************************************************// // nsIDOMEventListener handleEvent: function(aEvent) { if (aEvent.type == "unload") { this.destroy(); } }, //**************************************************************************// // Composed Model Construction _loadData: function() { this._loadFeedHandler(); this._loadPluginHandlers(); this._loadApplicationHandlers(); }, _loadFeedHandler: function() { this._handledTypes[TYPE_MAYBE_FEED] = feedHandlerInfo; feedHandlerInfo.handledOnlyByPlugin = false; }, /** * Load the set of handlers defined by plugins. * * Note: if there's more than one plugin for a given MIME type, we assume * the last one is the one that the application will use. That may not be * correct, but it's how we've been doing it for years. * * Perhaps we should instead query navigator.mimeTypes for the set of types * supported by the application and then get the plugin from each MIME type's * enabledPlugin property. But if there's a plugin for a type, we need * to know about it even if it isn't enabled, since we're going to give * the user an option to enable it. * * I'll also note that my reading of nsPluginTag::RegisterWithCategoryManager * suggests that enabledPlugin is only determined during registration * and does not get updated when plugin.disable_full_page_plugin_for_types * changes (unless modification of that preference spawns reregistration). * So even if we could use enabledPlugin to get the plugin that would be used, * we'd still need to check the pref ourselves to find out if it's enabled. */ _loadPluginHandlers: function() { for (let i = 0; i < navigator.plugins.length; ++i) { let plugin = navigator.plugins[i]; for (let j = 0; j < plugin.length; ++j) { let type = plugin[j].type; let handlerInfoWrapper; if (type in this._handledTypes) handlerInfoWrapper = this._handledTypes[type]; else { let wrappedHandlerInfo = this._mimeSvc.getFromTypeAndExtension(type, null); handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo); handlerInfoWrapper.handledOnlyByPlugin = true; this._handledTypes[type] = handlerInfoWrapper; } handlerInfoWrapper.plugin = plugin; } } }, /** * Load the set of handlers defined by the application datastore. */ _loadApplicationHandlers: function() { var wrappedHandlerInfos = this._handlerSvc.enumerate(); while (wrappedHandlerInfos.hasMoreElements()) { let wrappedHandlerInfo = wrappedHandlerInfos.getNext().QueryInterface(Ci.nsIHandlerInfo); let type = wrappedHandlerInfo.type; let handlerInfoWrapper; if (type in this._handledTypes) handlerInfoWrapper = this._handledTypes[type]; else { handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo); this._handledTypes[type] = handlerInfoWrapper; } handlerInfoWrapper.handledOnlyByPlugin = false; } }, //**************************************************************************// // View Construction _rebuildVisibleTypes: function() { // Reset the list of visible types and the visible type description counts. this._visibleTypes = []; this._visibleTypeDescriptionCount = {}; // Get the preferences that help determine what types to show. var showPlugins = this._prefSvc.getBoolPref(PREF_SHOW_PLUGINS_IN_LIST); var hideTypesWithoutExtensions = this._prefSvc.getBoolPref(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS); for (let type in this._handledTypes) { let handlerInfo = this._handledTypes[type]; // Hide types without extensions if so prefed so we don't show a whole // bunch of obscure types handled by plugins on Mac. // Note: though protocol types don't have extensions, we still show them; // the pref is only meant to be applied to MIME types. // FIXME: if the type has a plugin, should we also check the "suffixes" // property of the plugin? Filed as bug 395135. if (hideTypesWithoutExtensions && handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo && !handlerInfo.primaryExtension) continue; // Hide types handled only by plugins if so prefed. if (handlerInfo.handledOnlyByPlugin && !showPlugins) continue; // We couldn't find any reason to exclude the type, so include it. this._visibleTypes.push(handlerInfo); if (handlerInfo.description in this._visibleTypeDescriptionCount) this._visibleTypeDescriptionCount[handlerInfo.description]++; else this._visibleTypeDescriptionCount[handlerInfo.description] = 1; } }, _rebuildView: function() { // Clear the list of entries. while (this._list.childNodes.length > 1) this._list.removeChild(this._list.lastChild); var visibleTypes = this._visibleTypes; // If the user is filtering the list, then only show matching types. if (this._filter.value) visibleTypes = visibleTypes.filter(this._matchesFilter, this); for each (let visibleType in visibleTypes) { let item = document.createElement("richlistitem"); item.setAttribute("type", visibleType.type); item.setAttribute("typeDescription", this._describeType(visibleType)); if (visibleType.smallIcon) item.setAttribute("typeIcon", visibleType.smallIcon); item.setAttribute("actionDescription", this._describePreferredAction(visibleType)); item.setAttribute("actionIcon", this._getIconURLForPreferredAction(visibleType)); this._list.appendChild(item); } this._selectLastSelectedType(); }, _matchesFilter: function(aType) { var filterValue = this._filter.value.toLowerCase(); return this._describeType(aType).toLowerCase().indexOf(filterValue) != -1 || this._describePreferredAction(aType).toLowerCase().indexOf(filterValue) != -1; }, /** * Describe, in a human-readable fashion, the type represented by the given * handler info object. Normally this is just the description provided by * the info object, but if more than one object presents the same description, * then we annotate the duplicate descriptions with the type itself to help * users distinguish between those types. * * @param aHandlerInfo {nsIHandlerInfo} the type being described * @returns {string} a description of the type */ _describeType: function(aHandlerInfo) { if (this._visibleTypeDescriptionCount[aHandlerInfo.description] > 1) return this._prefsBundle.getFormattedString("typeDescriptionWithType", [aHandlerInfo.description, aHandlerInfo.type]); return aHandlerInfo.description; }, /** * Describe, in a human-readable fashion, the preferred action to take on * the type represented by the given handler info object. * * XXX Should this be part of the HandlerInfoWrapper interface? It would * violate the separation of model and view, but it might make more sense * nonetheless (f.e. it would make sortTypes easier). * * @param aHandlerInfo {nsIHandlerInfo} the type whose preferred action * is being described * @returns {string} a description of the action */ _describePreferredAction: function(aHandlerInfo) { // alwaysAskBeforeHandling overrides the preferred action, so if that flag // is set, then describe that behavior instead. For most types, this is // the "alwaysAsk" string, but for the feed type we show something special. if (aHandlerInfo.alwaysAskBeforeHandling) { if (aHandlerInfo.type == TYPE_MAYBE_FEED) return this._prefsBundle.getFormattedString("previewInApp", [this._brandShortName]); else return this._prefsBundle.getString("alwaysAsk"); } switch (aHandlerInfo.preferredAction) { case Ci.nsIHandlerInfo.saveToDisk: return this._prefsBundle.getString("saveFile"); case Ci.nsIHandlerInfo.useHelperApp: var preferredApp = aHandlerInfo.preferredApplicationHandler; if (preferredApp instanceof Ci.nsILocalHandlerApp) return getDisplayNameForFile(preferredApp.executable); else return preferredApp.name; case Ci.nsIHandlerInfo.handleInternally: // For the feed type, handleInternally means live bookmarks. if (aHandlerInfo.type == TYPE_MAYBE_FEED) return this._prefsBundle.getFormattedString("liveBookmarksInApp", [this._brandShortName]); // For other types, handleInternally looks like either useHelperApp // or useSystemDefault depending on whether or not there's a preferred // handler app. if (this.isValidHandlerApp(aHandlerInfo.preferredApplicationHandler)) return aHandlerInfo.preferredApplicationHandler.name; return aHandlerInfo.defaultDescription; // XXX Why don't we say the app will handle the type internally? // Is it because the app can't actually do that? But if that's true, // then why would a preferredAction ever get set to this value // in the first place? case Ci.nsIHandlerInfo.useSystemDefault: return aHandlerInfo.defaultDescription; case kActionUsePlugin: return this._prefsBundle.getFormattedString("pluginName", [aHandlerInfo.plugin.name, this._brandShortName]); } }, _selectLastSelectedType: function() { // If the list is disabled by the pref.downloads.disable_button.edit_actions // preference being locked, then don't select the type, as that would cause // it to appear selected, with a different background and an actions menu // that makes it seem like you can choose an action for the type. if (this._list.disabled) return; var lastSelectedType = this._list.getAttribute("lastSelectedType"); if (!lastSelectedType) return; var item = this._list.getElementsByAttribute("type", lastSelectedType)[0]; if (!item) return; this._list.selectedItem = item; }, /** * Whether or not the given handler app is valid. * * @param aHandlerApp {nsIHandlerApp} the handler app in question * * @returns {boolean} whether or not it's valid */ isValidHandlerApp: function(aHandlerApp) { if (!aHandlerApp) return false; if (aHandlerApp instanceof Ci.nsILocalHandlerApp) return this._isValidHandlerExecutable(aHandlerApp.executable); if (aHandlerApp instanceof Ci.nsIWebHandlerApp) return aHandlerApp.uriTemplate; if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo) return aHandlerApp.uri; return false; }, _isValidHandlerExecutable: function(aExecutable) { return aExecutable && aExecutable.exists() && aExecutable.isExecutable() && // XXXben - we need to compare this with the running instance executable // just don't know how to do that via script... // XXXmano TBD: can probably add this to nsIShellService #ifdef XP_WIN #expand aExecutable.leafName != "__MOZ_APP_NAME__.exe"; #else #ifdef XP_MACOSX #expand aExecutable.leafName != "__MOZ_APP_DISPLAYNAME__.app"; #else #expand aExecutable.leafName != "__MOZ_APP_NAME__-bin"; #endif #endif }, /** * Rebuild the actions menu for the selected entry. Gets called by * the richlistitem constructor when an entry in the list gets selected. */ rebuildActionsMenu: function() { var typeItem = this._list.selectedItem; var handlerInfo = this._handledTypes[typeItem.type]; var menu = document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu"); var menuPopup = menu.menupopup; // Clear out existing items. while (menuPopup.hasChildNodes()) menuPopup.removeChild(menuPopup.lastChild); { var askMenuItem = document.createElementNS(kXULNS, "menuitem"); askMenuItem.setAttribute("alwaysAsk", "true"); let label; if (handlerInfo.type == TYPE_MAYBE_FEED) label = this._prefsBundle.getFormattedString("previewInApp", [this._brandShortName]); else label = this._prefsBundle.getString("alwaysAsk"); askMenuItem.setAttribute("label", label); askMenuItem.setAttribute("tooltiptext", label); askMenuItem.setAttribute("image", ICON_URL_ASK); menuPopup.appendChild(askMenuItem); } // If this is the feed type, add a Live Bookmarks item. if (handlerInfo.type == TYPE_MAYBE_FEED) { var internalMenuItem = document.createElementNS(kXULNS, "menuitem"); internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally); let label = this._prefsBundle.getFormattedString("liveBookmarksInApp", [this._brandShortName]); internalMenuItem.setAttribute("label", label); internalMenuItem.setAttribute("tooltiptext", label); internalMenuItem.setAttribute("image", ICON_URL_LIVEMARK); menuPopup.appendChild(internalMenuItem); // Add a separator to distinguish these items from the helper app items // that follow them. let menuItem = document.createElementNS(kXULNS, "menuseparator"); menuPopup.appendChild(menuItem); } // Create a menu item for the OS default application, if any. if (handlerInfo.hasDefaultHandler) { var defaultMenuItem = document.createElementNS(kXULNS, "menuitem"); defaultMenuItem.setAttribute("action", Ci.nsIHandlerInfo.useSystemDefault); defaultMenuItem.setAttribute("label", handlerInfo.defaultDescription); defaultMenuItem.setAttribute("tooltiptext", handlerInfo.defaultDescription); defaultMenuItem.setAttribute("image", this._getIconURLForSystemDefault(handlerInfo)); menuPopup.appendChild(defaultMenuItem); } // Create menu items for possible handlers. let preferredApp = handlerInfo.preferredApplicationHandler; let possibleApps = handlerInfo.possibleApplicationHandlers.enumerate(); var possibleAppMenuItems = []; while (possibleApps.hasMoreElements()) { let possibleApp = possibleApps.getNext(); if (!this.isValidHandlerApp(possibleApp)) continue; let menuItem = document.createElementNS(kXULNS, "menuitem"); menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp); let label; if (possibleApp instanceof Ci.nsILocalHandlerApp) label = getDisplayNameForFile(possibleApp.executable); else label = possibleApp.name; menuItem.setAttribute("label", label); menuItem.setAttribute("tooltiptext", label); menuItem.setAttribute("image", this._getIconURLForHandlerApp(possibleApp)); // Attach the handler app object to the menu item so we can use it // to make changes to the datastore when the user selects the item. menuItem.handlerApp = possibleApp; menuPopup.appendChild(menuItem); possibleAppMenuItems.push(menuItem); } // Create a menu item for the plugin. if (handlerInfo.plugin) { var pluginMenuItem = document.createElementNS(kXULNS, "menuitem"); pluginMenuItem.setAttribute("action", kActionUsePlugin); let label = this._prefsBundle.getFormattedString("pluginName", [handlerInfo.plugin.name, this._brandShortName]); pluginMenuItem.setAttribute("label", label); pluginMenuItem.setAttribute("tooltiptext", label); pluginMenuItem.setAttribute("image", ICON_URL_PLUGIN); menuPopup.appendChild(pluginMenuItem); } // Create a menu item for selecting a local application. #ifdef XP_WIN // On Windows, selecting an application to open another application // would be meaningless so we special case executables. var executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService) .getTypeFromExtension("exe"); if (handlerInfo.type != executableType) #endif { let menuItem = document.createElementNS(kXULNS, "menuitem"); menuItem.setAttribute("oncommand", "gApplicationsPane.chooseApp(event)"); let label = this._prefsBundle.getString("chooseApp"); menuItem.setAttribute("label", label); menuItem.setAttribute("tooltiptext", label); menuPopup.appendChild(menuItem); } // Create a menu item for saving to disk. // Note: this option isn't available to protocol types, since we don't know // what it means to save a URL having a certain scheme to disk, nor is it // available to feeds, since the feed code doesn't implement the capability. if ((handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) && handlerInfo.type != TYPE_MAYBE_FEED) { var saveMenuItem = document.createElementNS(kXULNS, "menuitem"); saveMenuItem.setAttribute("action", Ci.nsIHandlerInfo.saveToDisk); let label = this._prefsBundle.getString("saveFile"); saveMenuItem.setAttribute("label", label); saveMenuItem.setAttribute("tooltiptext", label); saveMenuItem.setAttribute("image", ICON_URL_SAVE); menuPopup.appendChild(saveMenuItem); } // Select the item corresponding to the preferred action. If the always // ask flag is set, it overrides the preferred action. Otherwise we pick // the item identified by the preferred action (when the preferred action // is to use a helper app, we have to pick the specific helper app item). if (handlerInfo.alwaysAskBeforeHandling) menu.selectedItem = askMenuItem; else switch (handlerInfo.preferredAction) { case Ci.nsIHandlerInfo.handleInternally: menu.selectedItem = internalMenuItem; break; case Ci.nsIHandlerInfo.useSystemDefault: menu.selectedItem = defaultMenuItem; break; case Ci.nsIHandlerInfo.useHelperApp: if (preferredApp) menu.selectedItem = possibleAppMenuItems.filter(function(v) v.handlerApp.equals(preferredApp))[0]; break; case kActionUsePlugin: menu.selectedItem = pluginMenuItem; break; case Ci.nsIHandlerInfo.saveToDisk: menu.selectedItem = saveMenuItem; break; } }, //**************************************************************************// // Sorting & Filtering _sortColumn: null, /** * Sort the list when the user clicks on a column header. */ sort: function (event) { var column = event.target; // If the user clicked on a new sort column, remove the direction indicator // from the old column. if (this._sortColumn && this._sortColumn != column) this._sortColumn.removeAttribute("sortDirection"); this._sortColumn = column; // Set (or switch) the sort direction indicator. if (column.getAttribute("sortDirection") == "ascending") column.setAttribute("sortDirection", "descending"); else column.setAttribute("sortDirection", "ascending"); this._sortVisibleTypes(); this._rebuildView(); }, /** * Sort the list of visible types by the current sort column/direction. */ _sortVisibleTypes: function() { if (!this._sortColumn) return; var t = this; function sortByType(a, b) { return t._describeType(a).toLowerCase(). localeCompare(t._describeType(b).toLowerCase()); } function sortByAction(a, b) { return t._describePreferredAction(a).toLowerCase(). localeCompare(t._describePreferredAction(b).toLowerCase()); } switch (this._sortColumn.getAttribute("value")) { case "type": this._visibleTypes.sort(sortByType); break; case "action": this._visibleTypes.sort(sortByAction); break; } if (this._sortColumn.getAttribute("sortDirection") == "descending") this._visibleTypes.reverse(); }, /** * Filter the list when the user enters a filter term into the filter field. */ filter: function() { if (this._filter.value == "") { this.clearFilter(); return; } this._rebuildView(); document.getElementById("clearFilter").disabled = false; }, _filterTimeout: null, onFilterInput: function() { if (this._filterTimeout) clearTimeout(this._filterTimeout); this._filterTimeout = setTimeout("gApplicationsPane.filter()", 500); }, onFilterKeyPress: function(aEvent) { if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE) this.clearFilter(); }, clearFilter: function() { this._filter.value = ""; this._rebuildView(); this._filter.focus(); document.getElementById("clearFilter").disabled = true; }, focusFilterBox: function() { this._filter.focus(); this._filter.select(); }, //**************************************************************************// // Changes onSelectAction: function(aActionItem) { this._storingAction = true; try { this._storeAction(aActionItem); } finally { this._storingAction = false; } }, _storeAction: function(aActionItem) { var typeItem = this._list.selectedItem; var handlerInfo = this._handledTypes[typeItem.type]; if (aActionItem.hasAttribute("alwaysAsk")) { handlerInfo.alwaysAskBeforeHandling = true; } else if (aActionItem.hasAttribute("action")) { let action = parseInt(aActionItem.getAttribute("action")); // Set the plugin state if we're enabling or disabling a plugin. if (action == kActionUsePlugin) handlerInfo.enablePluginType(); else if (handlerInfo.plugin && !handlerInfo.isDisabledPluginType) handlerInfo.disablePluginType(); // Set the preferred application handler. // We leave the existing preferred app in the list when we set // the preferred action to something other than useHelperApp so that // legacy datastores that don't have the preferred app in the list // of possible apps still include the preferred app in the list of apps // the user can choose to handle the type. if (action == Ci.nsIHandlerInfo.useHelperApp) handlerInfo.preferredApplicationHandler = aActionItem.handlerApp; // Set the "always ask" flag. handlerInfo.alwaysAskBeforeHandling = false; // Set the preferred action. handlerInfo.preferredAction = action; } handlerInfo.store(); // Make sure the handler info object is flagged to indicate that there is // now some user configuration for the type. handlerInfo.handledOnlyByPlugin = false; // Update the action label and image to reflect the new preferred action. typeItem.setAttribute("actionDescription", this._describePreferredAction(handlerInfo)); typeItem.setAttribute("actionIcon", this._getIconURLForPreferredAction(handlerInfo)); }, chooseApp: function(aEvent) { // Don't let the normal "on select action" handler get this event, // as we handle it specially ourselves. aEvent.stopPropagation(); var handlerApp; #ifdef XP_WIN var params = {}; var handlerInfo = this._handledTypes[this._list.selectedItem.type]; if (handlerInfo.type == TYPE_MAYBE_FEED) { // MIME info will be null, create a temp object. params.mimeInfo = this._mimeSvc.getFromTypeAndExtension(handlerInfo.type, handlerInfo.primaryExtension); } else { params.mimeInfo = handlerInfo.wrappedHandlerInfo; } params.title = this._prefsBundle.getString("fpTitleChooseApp"); params.description = handlerInfo.description; params.filename = null; params.handlerApp = null; window.openDialog("chrome://global/content/appPicker.xul", null, "chrome,modal,centerscreen,titlebar,dialog=yes", params); if (params.handlerApp && params.handlerApp.executable && params.handlerApp.executable.isFile()) { handlerApp = params.handlerApp; // Add the app to the type's list of possible handlers. handlerInfo.addPossibleApplicationHandler(handlerApp); } #else var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); var winTitle = this._prefsBundle.getString("fpTitleChooseApp"); fp.init(window, winTitle, Ci.nsIFilePicker.modeOpen); fp.appendFilters(Ci.nsIFilePicker.filterApps); // Prompt the user to pick an app. If they pick one, and it's a valid // selection, then add it to the list of possible handlers. if (fp.show() == Ci.nsIFilePicker.returnOK && fp.file && this._isValidHandlerExecutable(fp.file)) { handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]. createInstance(Ci.nsILocalHandlerApp); handlerApp.name = getDisplayNameForFile(fp.file); handlerApp.executable = fp.file; // Add the app to the type's list of possible handlers. let handlerInfo = this._handledTypes[this._list.selectedItem.type]; handlerInfo.addPossibleApplicationHandler(handlerApp); } #endif // Rebuild the actions menu whether the user picked an app or canceled. // If they picked an app, we want to add the app to the menu and select it. // If they canceled, we want to go back to their previous selection. this.rebuildActionsMenu(); // If the user picked a new app from the menu, select it. if (handlerApp) { let typeItem = this._list.selectedItem; let actionsMenu = document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu"); let menuItems = actionsMenu.menupopup.childNodes; for (let i = 0; i < menuItems.length; i++) { let menuItem = menuItems[i]; if (menuItem.handlerApp && menuItem.handlerApp.equals(handlerApp)) { actionsMenu.selectedIndex = i; this.onSelectAction(menuItem); break; } } } }, // Mark which item in the list was last selected so we can reselect it // when we rebuild the list or when the user returns to the prefpane. onSelectionChanged: function() { if (this._list.selectedItem) this._list.setAttribute("lastSelectedType", this._list.selectedItem.getAttribute("type")); }, _getIconURLForPreferredAction: function(aHandlerInfo) { if (aHandlerInfo.alwaysAskBeforeHandling) return ICON_URL_ASK; switch (aHandlerInfo.preferredAction) { case Ci.nsIHandlerInfo.saveToDisk: return ICON_URL_SAVE; case Ci.nsIHandlerInfo.handleInternally: if (aHandlerInfo.type == TYPE_MAYBE_FEED) return ICON_URL_LIVEMARK; break; case Ci.nsIHandlerInfo.useSystemDefault: return this._getIconURLForSystemDefault(aHandlerInfo); case Ci.nsIHandlerInfo.useHelperApp: let (preferredApp = aHandlerInfo.preferredApplicationHandler) { if (this.isValidHandlerApp(preferredApp)) return this._getIconURLForHandlerApp(preferredApp); } break; case kActionUsePlugin: return ICON_URL_PLUGIN; // This should never happen, but if preferredAction is set to some weird // value, then fall back to the generic application icon. default: return ICON_URL_APP; } }, _getIconURLForHandlerApp: function(aHandlerApp) { if (aHandlerApp instanceof Ci.nsILocalHandlerApp) return this._getIconURLForFile(aHandlerApp.executable); if (aHandlerApp instanceof Ci.nsIWebHandlerApp) return this._getIconURLForWebApp(aHandlerApp.uriTemplate); if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo) return this._getIconURLForWebApp(aHandlerApp.uri) // We know nothing about other kinds of handler apps. return ""; }, _getIconURLForFile: function(aFile) { var fph = this._ioSvc.getProtocolHandler("file"). QueryInterface(Ci.nsIFileProtocolHandler); var urlSpec = fph.getURLSpecFromFile(aFile); return "moz-icon://" + urlSpec + "?size=16"; }, _getIconURLForWebApp: function(aWebAppURITemplate) { var uri = this._ioSvc.newURI(aWebAppURITemplate, null, null); // Unfortunately we can't use the favicon service to get the favicon, // because the service looks in the annotations table for a record with // the exact URL we give it, and users won't have such records for URLs // they don't visit, and users won't visit the web app's URL template, // they'll only visit URLs derived from that template (i.e. with %s // in the template replaced by the URL of the content being handled). if (/^https?/.test(uri.scheme)) return uri.prePath + "/favicon.ico"; return ""; }, _getIconURLForSystemDefault: function(aHandlerInfo) { // Handler info objects for MIME types on some OSes implement a property bag // interface from which we can get an icon for the default app, so if we're // dealing with a MIME type on one of those OSes, then try to get the icon. if ("wrappedHandlerInfo" in aHandlerInfo) { let wrappedHandlerInfo = aHandlerInfo.wrappedHandlerInfo; if (wrappedHandlerInfo instanceof Ci.nsIMIMEInfo && wrappedHandlerInfo instanceof Ci.nsIPropertyBag) { try { let url = wrappedHandlerInfo.getProperty("defaultApplicationIconURL"); if (url) return url + "?size=16"; } catch(ex) {} } } // If this isn't a MIME type object on an OS that supports retrieving // the icon, or if we couldn't retrieve the icon for some other reason, // then use a generic icon. return ICON_URL_APP; } };