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