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