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);
+ }
+};
--- 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