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