Bug 402252 - application details should be accessible from prefs UI. r=Mano, ui-r=beltzner (over IRC), a1.9=damons.
authorflorian@queze.net
Tue, 29 Jan 2008 07:30:53 -0800
changeset 10914 b5ceaf650aaf0fdfce3b406962a4ce1da5f5f39d
parent 10913 8b7a09b502d3a00e699e25f6a9cfa3c2b249dcda
child 10915 71ec64f4a1c9eebf208e2bf5c979801c8d80a888
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMano, beltzner
bugs402252
milestone1.9b3pre
Bug 402252 - application details should be accessible from prefs UI. r=Mano, ui-r=beltzner (over IRC), a1.9=damons.
browser/components/preferences/applicationManager.js
browser/components/preferences/applicationManager.xul
browser/components/preferences/applications.js
browser/components/preferences/jar.mn
browser/locales/en-US/chrome/browser/preferences/applicationManager.dtd
browser/locales/en-US/chrome/browser/preferences/applicationManager.properties
browser/locales/jar.mn
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/applicationManager.js
@@ -0,0 +1,132 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org Code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Florian Queze <florian@queze.net> (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+var TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+
+var gAppManagerDialog = {
+  _removed: [],
+
+  init: function appManager_init() {
+    this.handlerInfo = window.arguments[0];
+
+    var bundle = document.getElementById("appManagerBundle");
+    var contentText;
+    if (this.handlerInfo.type == TYPE_MAYBE_FEED)
+      contentText = bundle.getString("handleWebFeeds");
+    else {
+      var description = gApplicationsPane._describeType(this.handlerInfo);
+      var key =
+        (this.handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) ? "handleFile"
+                                                                        : "handleProtocol";
+        contentText = bundle.getFormattedString(key, [description]);
+    }
+    contentText = bundle.getFormattedString("descriptionApplications", [contentText]);
+    document.getElementById("appDescription").textContent = contentText;
+
+    var list = document.getElementById("appList");
+    var apps = this.handlerInfo.possibleApplicationHandlers.enumerate();
+    while (apps.hasMoreElements()) {
+      let app = apps.getNext();
+      app.QueryInterface(Ci.nsIHandlerApp);
+      var item = list.appendItem(app.name);
+      item.setAttribute("image", gApplicationsPane._getIconURLForHandlerApp(app));
+      item.className = "listitem-iconic";
+      item.app = app;
+    }
+
+    list.selectedIndex = 0;
+  },
+
+  onOK: function appManager_onOK() {
+    if (!this._removed.length) {
+      // return early to avoid calling the |store| method.
+      return;
+    }
+
+    for (var i = 0; i < this._removed.length; ++i)
+      this.handlerInfo.removePossibleApplicationHandler(this._removed[i]);
+
+    this.handlerInfo.store();
+  },
+
+  onCancel: function appManager_onCancel() {
+    // do nothing
+  },
+
+  remove: function appManager_remove() {
+    var list = document.getElementById("appList");
+    this._removed.push(list.selectedItem.app);
+    var index = list.selectedIndex;
+    list.removeItemAt(index);
+    if (list.getRowCount() == 0) {
+      // The list is now empty, make the bottom part disappear
+      document.getElementById("appDetails").hidden = true;
+    }
+    else {
+      // Select the item at the same index, if we removed the last
+      // item of the list, select the previous item
+      if (index == list.getRowCount())
+        --index;
+      list.selectedIndex = index;
+    }
+  },
+
+  onSelect: function appManager_onSelect() {
+    var list = document.getElementById("appList");
+    if (!list.selectedItem) {
+      document.getElementById("remove").disabled = true;
+      return;
+    }
+    document.getElementById("remove").disabled = false;
+    var app = list.selectedItem.app;
+    var address = "";
+    if (app instanceof Ci.nsILocalHandlerApp)
+      address = app.executable.path;
+    else if (app instanceof Ci.nsIWebHandlerApp)
+      address = app.uriTemplate;
+    else if (app instanceof Ci.nsIWebContentHandlerInfo)
+      address = app.uri;
+    document.getElementById("appLocation").value = address;
+    var bundle = document.getElementById("appManagerBundle");
+    var appType = app instanceof Ci.nsILocalHandlerApp ? "descriptionLocalApp"
+                                                       : "descriptionWebApp";
+    document.getElementById("appType").value = bundle.getString(appType);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/applicationManager.xul
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org Code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Florian Queze <florian@queze.net> (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/applicationManager.dtd">
+
+<dialog id="appManager"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        buttons="accept,cancel"
+        onload="gAppManagerDialog.init();"
+        ondialogaccept="gAppManagerDialog.onOK();"
+        ondialogcancel="gAppManagerDialog.onCancel();"
+        title="&appManager.title;"
+        style="&appManager.style;"
+        persist="screenX screenY">
+
+  <script type="application/javascript"
+          src="chrome://browser/content/preferences/applicationManager.js"/>
+  <script type="application/javascript"
+          src="chrome://browser/content/preferences/applications.js"/>
+
+  <commandset id="appManagerCommandSet">
+    <command id="cmd_remove"
+             oncommand="gAppManagerDialog.remove();"
+             disabled="true"/>
+  </commandset>
+
+  <keyset id="appManagerKeyset">
+    <key id="delete" keycode="VK_DELETE" command="cmd_remove"/>
+  </keyset>
+
+  <stringbundleset id="appManagerBundleset">
+    <stringbundle id="appManagerBundle"
+                  src="chrome://browser/locale/preferences/applicationManager.properties"/>
+  </stringbundleset>
+
+  <description id="appDescription"/>
+  <separator class="thin"/>
+  <hbox flex="1">
+    <listbox id="appList" onselect="gAppManagerDialog.onSelect();" flex="1"/>
+    <vbox>
+      <button id="remove"
+              label="&remove.label;"
+              accesskey="&remove.accesskey;"
+              command="cmd_remove"/>
+      <spacer flex="1"/>
+    </vbox>
+  </hbox>
+  <vbox id="appDetails">
+    <separator class="thin"/>
+    <label id="appType"/>
+    <textbox id="appLocation" readonly="true" class="plain"/>
+  </vbox>
+</dialog>
--- a/browser/components/preferences/applications.js
+++ b/browser/components/preferences/applications.js
@@ -20,16 +20,17 @@
 # Portions created by the Initial Developer are Copyright (C) 2000
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Ben Goodger <ben@mozilla.org>
 #   Jeff Walden <jwalden+code@mit.edu>
 #   Asaf Romano <mozilla.mano@sent.com>
 #   Myk Melez <myk@mozilla.org>
+#   Florian Queze <florian@queze.net>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
@@ -267,16 +268,35 @@ HandlerInfoWrapper.prototype = {
     var possibleApps = this.possibleApplicationHandlers.enumerate();
     while (possibleApps.hasMoreElements()) {
       if (possibleApps.getNext().equals(aNewHandler))
         return;
     }
     this.possibleApplicationHandlers.appendElement(aNewHandler, false);
   },
 
+  removePossibleApplicationHandler: function(aHandler) {
+    var defaultApp = this.preferredApplicationHandler;
+    if (defaultApp && aHandler.equals(defaultApp)) {
+      // If the app we remove was the default app, we must make sure
+      // it won't be used anymore
+      this.alwaysAskBeforeHandling = true;
+      this.preferredApplicationHandler = null;
+    }
+
+    var handlers = this.possibleApplicationHandlers;
+    for (var i = 0; i < handlers.length; ++i) {
+      var handler = handlers.queryElementAt(i, Ci.nsIHandlerApp);
+      if (handler.equals(aHandler)) {
+        handlers.removeElementAt(i);
+        break;
+      }
+    }
+  },
+
   get hasDefaultHandler() {
     return this.wrappedHandlerInfo.hasDefaultHandler;
   },
 
   get defaultDescription() {
     return this.wrappedHandlerInfo.defaultDescription;
   },
 
@@ -561,32 +581,46 @@ var feedHandlerInfo = {
   get possibleApplicationHandlers() {
     if (this._possibleApplicationHandlers)
       return this._possibleApplicationHandlers;
 
     // A minimal implementation of nsIMutableArray.  It only supports the two
     // methods its callers invoke, namely appendElement and nsIArray::enumerate.
     this._possibleApplicationHandlers = {
       _inner: [],
+      _removed: [],
 
       QueryInterface: function(aIID) {
         if (aIID.equals(Ci.nsIMutableArray) ||
             aIID.equals(Ci.nsIArray) ||
             aIID.equals(Ci.nsISupports))
           return this;
 
         throw Cr.NS_ERROR_NO_INTERFACE;
       },
 
+      get length() {
+        return this._inner.length;
+      },
+
       enumerate: function() {
         return new ArrayEnumerator(this._inner);
       },
 
       appendElement: function(aHandlerApp, aWeak) {
         this._inner.push(aHandlerApp);
+      },
+
+      removeElementAt: function(aIndex) {
+        this._removed.push(this._inner[aIndex]);
+        this._inner.splice(aIndex, 1);
+      },
+
+      queryElementAt: function(aIndex, aInterface) {
+        return this._inner[aIndex].QueryInterface(aInterface);
       }
     };
 
     // Add the selected local app if it's different from the OS default handler.
     // Unlike for other types, we can store only one local app at a time for the
     // feed type, since we store it in a preference that historically stores
     // only a single path.  But we display all the local apps the user chooses
     // while the prefpane is open, only dropping the list when the user closes
@@ -743,21 +777,38 @@ var feedHandlerInfo = {
     return "xml";
   },
 
 
   //**************************************************************************//
   // Storage
 
   // Changes to the preferred action and handler take effect immediately
-  // (we write them out to the preferences right as they happen), so we don't
-  // need to do anything when the controller calls store() after modifying
-  // the handler.
+  // (we write them out to the preferences right as they happen),
+  // so we when the controller calls store() after modifying the handlers,
+  // the only thing we need to store is the removal of possible handlers
   // XXX Should we hold off on making the changes until this method gets called?
-  store: function() {},
+  store: function() {
+    for each (let app in this._possibleApplicationHandlers._removed) {
+      if (app instanceof Ci.nsILocalHandlerApp) {
+        let pref = this.element(PREF_FEED_SELECTED_APP);
+        var preferredAppFile = pref.value;
+        if (preferredAppFile) {
+          let preferredApp = getLocalHandlerApp(preferredAppFile);
+          if (app.equals(preferredApp))
+            pref.reset();
+        }
+      }
+      else {
+        app.QueryInterface(Ci.nsIWebContentHandlerInfo);
+        this._converterSvc.removeContentHandler(app.contentType, app.uri);
+      }
+    }
+    this._possibleApplicationHandlers._removed = [];
+  },
 
 
   //**************************************************************************//
   // Icons
 
   get smallIcon() {
     return "chrome://browser/skin/feeds/feedIcon16.png";
   },
@@ -1132,45 +1183,48 @@ var gApplicationsPane = {
     }
 
     switch (aHandlerInfo.preferredAction) {
       case Ci.nsIHandlerInfo.saveToDisk:
         return this._prefsBundle.getString("saveFile");
 
       case Ci.nsIHandlerInfo.useHelperApp:
         var preferredApp = aHandlerInfo.preferredApplicationHandler;
+        var name;
         if (preferredApp instanceof Ci.nsILocalHandlerApp)
-          return getDisplayNameForFile(preferredApp.executable);
+          name = getDisplayNameForFile(preferredApp.executable);
         else
-          return preferredApp.name;
+          name = preferredApp.name;
+        return this._prefsBundle.getFormattedString("useApp", [name]);
 
       case Ci.nsIHandlerInfo.handleInternally:
         // For the feed type, handleInternally means live bookmarks.
         if (aHandlerInfo.type == TYPE_MAYBE_FEED)
-          return this._prefsBundle.getFormattedString("liveBookmarksInApp",
+          return this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
                                                       [this._brandShortName]);
 
         // For other types, handleInternally looks like either useHelperApp
         // or useSystemDefault depending on whether or not there's a preferred
         // handler app.
         if (this.isValidHandlerApp(aHandlerInfo.preferredApplicationHandler))
           return aHandlerInfo.preferredApplicationHandler.name;
 
         return aHandlerInfo.defaultDescription;
 
         // XXX Why don't we say the app will handle the type internally?
         // Is it because the app can't actually do that?  But if that's true,
         // then why would a preferredAction ever get set to this value
         // in the first place?
 
       case Ci.nsIHandlerInfo.useSystemDefault:
-        return aHandlerInfo.defaultDescription;
+        return this._prefsBundle.getFormattedString("useDefault",
+                                                    [aHandlerInfo.defaultDescription]);
 
       case kActionUsePlugin:
-        return this._prefsBundle.getFormattedString("pluginName",
+        return this._prefsBundle.getFormattedString("usePluginIn",
                                                     [aHandlerInfo.plugin.name,
                                                      this._brandShortName]);
     }
   },
 
   _selectLastSelectedType: function() {
     // If the list is disabled by the pref.downloads.disable_button.edit_actions
     // preference being locked, then don't select the type, as that would cause
@@ -1242,91 +1296,109 @@ var gApplicationsPane = {
       document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
     var menuPopup = menu.menupopup;
 
     // Clear out existing items.
     while (menuPopup.hasChildNodes())
       menuPopup.removeChild(menuPopup.lastChild);
 
     {
-      var askMenuItem = document.createElementNS(kXULNS, "menuitem");
+      var askMenuItem = document.createElement("menuitem");
       askMenuItem.setAttribute("alwaysAsk", "true");
       let label;
       if (handlerInfo.type == TYPE_MAYBE_FEED)
         label = this._prefsBundle.getFormattedString("previewInApp",
                                                      [this._brandShortName]);
       else
         label = this._prefsBundle.getString("alwaysAsk");
       askMenuItem.setAttribute("label", label);
       askMenuItem.setAttribute("tooltiptext", label);
       askMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
       menuPopup.appendChild(askMenuItem);
     }
 
+    // Create a menu item for saving to disk.
+    // Note: this option isn't available to protocol types, since we don't know
+    // what it means to save a URL having a certain scheme to disk, nor is it
+    // available to feeds, since the feed code doesn't implement the capability.
+    if ((handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
+        handlerInfo.type != TYPE_MAYBE_FEED) {
+      var saveMenuItem = document.createElement("menuitem");
+      saveMenuItem.setAttribute("action", Ci.nsIHandlerInfo.saveToDisk);
+      let label = this._prefsBundle.getString("saveFile");
+      saveMenuItem.setAttribute("label", label);
+      saveMenuItem.setAttribute("tooltiptext", label);
+      saveMenuItem.setAttribute(APP_ICON_ATTR_NAME, "save");
+      menuPopup.appendChild(saveMenuItem);
+    }
+
     // If this is the feed type, add a Live Bookmarks item.
     if (handlerInfo.type == TYPE_MAYBE_FEED) {
-      var internalMenuItem = document.createElementNS(kXULNS, "menuitem");
+      var internalMenuItem = document.createElement("menuitem");
       internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
-      let label = this._prefsBundle.getFormattedString("liveBookmarksInApp",
+      let label = this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
                                                        [this._brandShortName]);
       internalMenuItem.setAttribute("label", label);
       internalMenuItem.setAttribute("tooltiptext", label);
       internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "feed");
       menuPopup.appendChild(internalMenuItem);
+    }
 
-      // Add a separator to distinguish these items from the helper app items
-      // that follow them.
-      let menuItem = document.createElementNS(kXULNS, "menuseparator");
-      menuPopup.appendChild(menuItem);
-    }
+    // Add a separator to distinguish these items from the helper app items
+    // that follow them.
+    let menuItem = document.createElement("menuseparator");
+    menuPopup.appendChild(menuItem);
 
     // Create a menu item for the OS default application, if any.
     if (handlerInfo.hasDefaultHandler) {
-      var defaultMenuItem = document.createElementNS(kXULNS, "menuitem");
+      var defaultMenuItem = document.createElement("menuitem");
       defaultMenuItem.setAttribute("action", Ci.nsIHandlerInfo.useSystemDefault);
-      defaultMenuItem.setAttribute("label", handlerInfo.defaultDescription);
+      let label = this._prefsBundle.getFormattedString("useDefault",
+                                                       [handlerInfo.defaultDescription]);
+      defaultMenuItem.setAttribute("label", label);
       defaultMenuItem.setAttribute("tooltiptext", handlerInfo.defaultDescription);
       defaultMenuItem.setAttribute("image", this._getIconURLForSystemDefault(handlerInfo));
 
       menuPopup.appendChild(defaultMenuItem);
     }
 
     // Create menu items for possible handlers.
     let preferredApp = handlerInfo.preferredApplicationHandler;
     let possibleApps = handlerInfo.possibleApplicationHandlers.enumerate();
     var possibleAppMenuItems = [];
     while (possibleApps.hasMoreElements()) {
       let possibleApp = possibleApps.getNext();
       if (!this.isValidHandlerApp(possibleApp))
         continue;
 
-      let menuItem = document.createElementNS(kXULNS, "menuitem");
+      let menuItem = document.createElement("menuitem");
       menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp);
       let label;
       if (possibleApp instanceof Ci.nsILocalHandlerApp)
         label = getDisplayNameForFile(possibleApp.executable);
       else
         label = possibleApp.name;
+      label = this._prefsBundle.getFormattedString("useApp", [label]);
       menuItem.setAttribute("label", label);
       menuItem.setAttribute("tooltiptext", label);
       menuItem.setAttribute("image", this._getIconURLForHandlerApp(possibleApp));
 
       // Attach the handler app object to the menu item so we can use it
       // to make changes to the datastore when the user selects the item.
       menuItem.handlerApp = possibleApp;
 
       menuPopup.appendChild(menuItem);
       possibleAppMenuItems.push(menuItem);
     }
 
     // Create a menu item for the plugin.
     if (handlerInfo.plugin) {
-      var pluginMenuItem = document.createElementNS(kXULNS, "menuitem");
+      var pluginMenuItem = document.createElement("menuitem");
       pluginMenuItem.setAttribute("action", kActionUsePlugin);
-      let label = this._prefsBundle.getFormattedString("pluginName",
+      let label = this._prefsBundle.getFormattedString("usePluginIn",
                                                        [handlerInfo.plugin.name,
                                                         this._brandShortName]);
       pluginMenuItem.setAttribute("label", label);
       pluginMenuItem.setAttribute("tooltiptext", label);
       pluginMenuItem.setAttribute(APP_ICON_ATTR_NAME, "plugin");
       menuPopup.appendChild(pluginMenuItem);
     }
 
@@ -1334,37 +1406,32 @@ var gApplicationsPane = {
 #ifdef XP_WIN
     // On Windows, selecting an application to open another application
     // would be meaningless so we special case executables.
     var executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService)
                                                   .getTypeFromExtension("exe");
     if (handlerInfo.type != executableType)
 #endif
     {
-      let menuItem = document.createElementNS(kXULNS, "menuitem");
+      let menuItem = document.createElement("menuitem");
       menuItem.setAttribute("oncommand", "gApplicationsPane.chooseApp(event)");
-      let label = this._prefsBundle.getString("chooseApp");
+      let label = this._prefsBundle.getString("useOtherApp");
       menuItem.setAttribute("label", label);
       menuItem.setAttribute("tooltiptext", label);
       menuPopup.appendChild(menuItem);
     }
 
-    // Create a menu item for saving to disk.
-    // Note: this option isn't available to protocol types, since we don't know
-    // what it means to save a URL having a certain scheme to disk, nor is it
-    // available to feeds, since the feed code doesn't implement the capability.
-    if ((handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
-        handlerInfo.type != TYPE_MAYBE_FEED) {
-      var saveMenuItem = document.createElementNS(kXULNS, "menuitem");
-      saveMenuItem.setAttribute("action", Ci.nsIHandlerInfo.saveToDisk);
-      let label = this._prefsBundle.getString("saveFile");
-      saveMenuItem.setAttribute("label", label);
-      saveMenuItem.setAttribute("tooltiptext", label);
-      saveMenuItem.setAttribute(APP_ICON_ATTR_NAME, "save");
-      menuPopup.appendChild(saveMenuItem);
+    // Create a menu item for managing applications.
+    if (possibleAppMenuItems.length) {
+      let menuItem = document.createElement("menuseparator");
+      menuPopup.appendChild(menuItem);
+      menuItem = document.createElement("menuitem");
+      menuItem.setAttribute("oncommand", "gApplicationsPane.manageApp(event)");
+      menuItem.setAttribute("label", this._prefsBundle.getString("manageApp"));
+      menuPopup.appendChild(menuItem);
     }
 
     // Select the item corresponding to the preferred action.  If the always
     // ask flag is set, it overrides the preferred action.  Otherwise we pick
     // the item identified by the preferred action (when the preferred action
     // is to use a helper app, we have to pick the specific helper app item).
     if (handlerInfo.alwaysAskBeforeHandling)
       menu.selectedItem = askMenuItem;
@@ -1548,16 +1615,38 @@ var gApplicationsPane = {
     typeItem.setAttribute("actionDescription",
                           this._describePreferredAction(handlerInfo));
     if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
       typeItem.setAttribute("actionIcon",
                             this._getIconURLForPreferredAction(handlerInfo));
     }
   },
 
+  manageApp: function(aEvent) {
+    // Don't let the normal "on select action" handler get this event,
+    // as we handle it specially ourselves.
+    aEvent.stopPropagation();
+
+    var typeItem = this._list.selectedItem;
+    var handlerInfo = this._handledTypes[typeItem.type];
+
+    document.documentElement.openSubDialog("chrome://browser/content/preferences/applicationManager.xul",
+                                           "", handlerInfo);
+
+    // Rebuild the actions menu so that we revert to the previous selection,
+    // or "Always ask" if the previous default application has been removed
+    this.rebuildActionsMenu();
+
+    // update the richlistitem too. Will be visible when selecting another row
+    typeItem.setAttribute("actionDescription",
+                          this._describePreferredAction(handlerInfo));
+    typeItem.setAttribute("actionIcon",
+                          this._getIconURLForPreferredAction(handlerInfo));
+  },
+
   chooseApp: function(aEvent) {
     // Don't let the normal "on select action" handler get this event,
     // as we handle it specially ourselves.
     aEvent.stopPropagation();
 
     var handlerApp;
 
 #ifdef XP_WIN
--- a/browser/components/preferences/jar.mn
+++ b/browser/components/preferences/jar.mn
@@ -1,14 +1,16 @@
 browser.jar:
 *   content/browser/preferences/advanced.xul
 *   content/browser/preferences/advanced.js
 *   content/browser/preferences/advanced-scripts.xul
 *   content/browser/preferences/applications.xul
 *   content/browser/preferences/applications.js
+*   content/browser/preferences/applicationManager.xul
+*   content/browser/preferences/applicationManager.js
 *   content/browser/preferences/colors.xul
 *   content/browser/preferences/cookies.xul
 *   content/browser/preferences/cookies.js
 *   content/browser/preferences/content.xul
 *   content/browser/preferences/content.js
 *   content/browser/preferences/connection.xul
 *   content/browser/preferences/connection.js
 *   content/browser/preferences/fallbackEULA.xhtml
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/applicationManager.dtd
@@ -0,0 +1,4 @@
+<!ENTITY appManager.title     "Application details">
+<!ENTITY appManager.style     "width: 30em; min-height: 20em;">
+<!ENTITY remove.label         "Remove">
+<!ENTITY remove.accesskey     "R">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/applicationManager.properties
@@ -0,0 +1,10 @@
+# LOCALIZATION NOTE
+# in descriptionApplications, %S will be replaced by one of the 3 following strings
+descriptionApplications=The following applications can be used to handle %S.
+
+handleProtocol=%S links
+handleWebFeeds=Web Feeds
+handleFile=%S content
+
+descriptionWebApp=This web application is hosted at:
+descriptionLocalApp=This application is located at:
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -41,16 +41,18 @@
 #endif
     locale/browser/feeds/subscribe.dtd              (%chrome/browser/feeds/subscribe.dtd)
     locale/browser/feeds/subscribe.properties       (%chrome/browser/feeds/subscribe.properties)
     locale/browser/history/history.dtd             (%chrome/browser/history/history.dtd)
     locale/browser/migration/migration.dtd         (%chrome/browser/migration/migration.dtd)
     locale/browser/migration/migration.properties  (%chrome/browser/migration/migration.properties)
     locale/browser/preferences/advanced.dtd           (%chrome/browser/preferences/advanced.dtd)
 *   locale/browser/preferences/advanced-scripts.dtd   (%chrome/browser/preferences/advanced-scripts.dtd)
+    locale/browser/preferences/applicationManager.dtd        (%chrome/browser/preferences/applicationManager.dtd)
+    locale/browser/preferences/applicationManager.properties (%chrome/browser/preferences/applicationManager.properties)
     locale/browser/preferences/colors.dtd             (%chrome/browser/preferences/colors.dtd)
     locale/browser/preferences/cookies.dtd            (%chrome/browser/preferences/cookies.dtd)
     locale/browser/preferences/content.dtd            (%chrome/browser/preferences/content.dtd)
     locale/browser/preferences/connection.dtd         (%chrome/browser/preferences/connection.dtd)
     locale/browser/preferences/applications.dtd       (%chrome/browser/preferences/applications.dtd)
     locale/browser/preferences/fallbackEULA.dtd       (%chrome/browser/preferences/fallbackEULA.dtd)
     locale/browser/preferences/fonts.dtd              (%chrome/browser/preferences/fonts.dtd)
     locale/browser/preferences/main.dtd               (%chrome/browser/preferences/main.dtd)