Refactor nsIMIMEInfo and nsExternalHelperAppService to support local and web-based protocol handlers (bug 384374), r=biesi, sr=sicking
authordmose@mozilla.org
Thu, 05 Jul 2007 12:31:44 -0700
changeset 3161 024c13a97e3a8c793f527bad445b61b03b3aaf14
parent 3160 dd311b5dc8791b2cc2b8aa4a866a7c475c352e2b
child 3162 589dd4564f338f924c42703e39e001d96627abe6
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbiesi, sicking
bugs384374
milestone1.9a7pre
Refactor nsIMIMEInfo and nsExternalHelperAppService to support local and web-based protocol handlers (bug 384374), r=biesi, sr=sicking
docshell/build/nsDocShellModule.cpp
dom/src/base/nsMimeTypeArray.cpp
embedding/components/ui/helperAppDlg/nsHelperAppDlg.js
embedding/components/ui/progressDlg/nsProgressDialog.js
netwerk/mime/public/nsIMIMEInfo.idl
toolkit/mozapps/downloads/content/helperApps.js
toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
uriloader/exthandler/Makefile.in
uriloader/exthandler/mac/nsMIMEInfoMac.cpp
uriloader/exthandler/nsCExternalHandlerService.idl
uriloader/exthandler/nsExternalHelperAppService.cpp
uriloader/exthandler/nsExternalHelperAppService.h
uriloader/exthandler/nsExternalProtocolHandler.cpp
uriloader/exthandler/nsHandlerAppImpl.cpp
uriloader/exthandler/nsHandlerAppImpl.h
uriloader/exthandler/nsHelperAppRDF.h
uriloader/exthandler/nsMIMEInfoImpl.cpp
uriloader/exthandler/nsMIMEInfoImpl.h
uriloader/exthandler/win/nsMIMEInfoWin.cpp
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -49,16 +49,17 @@
 #include "nsAboutRedirector.h"
 
 // uriloader
 #include "nsURILoader.h"
 #include "nsDocLoader.h"
 #include "nsOSHelperAppService.h"
 #include "nsExternalProtocolHandler.h"
 #include "nsPrefetchService.h"
+#include "nsHandlerAppImpl.h"
 
 // session history
 #include "nsSHEntry.h"
 #include "nsSHistory.h"
 #include "nsSHTransaction.h"
 
 // global history
 #include "nsGlobalHistoryAdapter.h"
@@ -94,16 +95,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWe
 
 // uriloader
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsURILoader)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDocLoader, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsOSHelperAppService, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsExternalProtocolHandler)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsBlockedExternalProtocolHandler)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrefetchService, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsLocalHandlerApp)
 
 #if defined(XP_MAC) || defined(XP_MACOSX)
 #include "nsInternetConfigService.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsInternetConfigService)
 #endif
 
 // session history
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHEntry)
@@ -198,21 +200,23 @@ static const nsModuleComponentInfo gDocS
   { "Netscape Mime Mapping Service", NS_EXTERNALHELPERAPPSERVICE_CID, NS_MIMESERVICE_CONTRACTID, 
      nsOSHelperAppServiceConstructor, },
   { "Netscape Default Protocol Handler", NS_EXTERNALPROTOCOLHANDLER_CID, NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"default", 
      nsExternalProtocolHandlerConstructor, },
   { "Netscape Default Blocked Protocol Handler", NS_BLOCKEDEXTERNALPROTOCOLHANDLER_CID, NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"default-blocked", 
      nsBlockedExternalProtocolHandlerConstructor, },
   {  NS_PREFETCHSERVICE_CLASSNAME, NS_PREFETCHSERVICE_CID, NS_PREFETCHSERVICE_CONTRACTID,
      nsPrefetchServiceConstructor, },
+  { "Local Application Handler App", NS_LOCALHANDLERAPP_CID, 
+    NS_LOCALHANDLERAPP_CONTRACTID, nsLocalHandlerAppConstructor, },
 #if defined(XP_MAC) || defined(XP_MACOSX)
   { "Internet Config Service", NS_INTERNETCONFIGSERVICE_CID, NS_INTERNETCONFIGSERVICE_CONTRACTID,
     nsInternetConfigServiceConstructor, },
 #endif
-    
+        
     // session history
    { "nsSHEntry", NS_SHENTRY_CID,
       NS_SHENTRY_CONTRACTID, nsSHEntryConstructor },
    { "nsSHEntry", NS_HISTORYENTRY_CID,
       NS_HISTORYENTRY_CONTRACTID, nsSHEntryConstructor },
    { "nsSHTransaction", NS_SHTRANSACTION_CID,
       NS_SHTRANSACTION_CONTRACTID, nsSHTransactionConstructor },
    { "nsSHistory", NS_SHISTORY_CID,
--- a/dom/src/base/nsMimeTypeArray.cpp
+++ b/dom/src/base/nsMimeTypeArray.cpp
@@ -132,23 +132,23 @@ nsMimeTypeArray::NamedItem(const nsAStri
   // Now let's check with the MIME service.
   nsCOMPtr<nsIMIMEService> mimeSrv = do_GetService("@mozilla.org/mime;1");
   if (mimeSrv) {
     nsCOMPtr<nsIMIMEInfo> mimeInfo;
     mimeSrv->GetFromTypeAndExtension(NS_ConvertUTF16toUTF8(aName), EmptyCString(),
                                      getter_AddRefs(mimeInfo));
     if (mimeInfo) {
       // Now we check whether we can really claim to support this type
-      nsMIMEInfoHandleAction action = nsIMIMEInfo::saveToDisk;
+      nsHandlerInfoAction action = nsIHandlerInfo::saveToDisk;
       mimeInfo->GetPreferredAction(&action);
       if (action != nsIMIMEInfo::handleInternally) {
         PRBool hasHelper = PR_FALSE;
         mimeInfo->GetHasDefaultHandler(&hasHelper);
         if (!hasHelper) {
-          nsCOMPtr<nsIFile> helper;
+          nsCOMPtr<nsIHandlerApp> helper;
           mimeInfo->GetPreferredApplicationHandler(getter_AddRefs(helper));
           if (!helper) {
             // mime info from the OS may not have a PreferredApplicationHandler
             // so just check for an empty default description
             nsAutoString defaultDescription;
             mimeInfo->GetDefaultDescription(defaultDescription);
             if (defaultDescription.IsEmpty()) {
               // no support; just leave
--- a/embedding/components/ui/helperAppDlg/nsHelperAppDlg.js
+++ b/embedding/components/ui/helperAppDlg/nsHelperAppDlg.js
@@ -461,18 +461,20 @@ nsHelperAppDialog.prototype = {
     // initAppAndSaveToDiskValues:
     initAppAndSaveToDiskValues: function() {
         // Fill in helper app info, if there is any.
         this.chosenApp = this.mLauncher.MIMEInfo.preferredApplicationHandler;
         // Initialize "default application" field.
         this.initDefaultApp();
 
         // Fill application name textbox.
-        if (this.chosenApp && this.chosenApp.path) {
-            this.dialogElement( "appPath" ).value = this.getPath(this.chosenApp);
+        if (this.chosenApp && this.chosenApp.executable &&
+            this.chosenApp.executable.path) {
+            this.dialogElement( "appPath" ).value = 
+              this.getPath(this.chosenApp.executable);
         }
 
         var useDefault = this.dialogElement( "useSystemDefault" );;
         if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault &&
             this.mReason != REASON_SERVERREQUEST) {
             // Open (using system default).
             useDefault.radioGroup.selectedItem = useDefault;
         } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp &&
@@ -512,31 +514,36 @@ nsHelperAppDialog.prototype = {
     // Returns the user-selected application
     helperAppChoice: function() {
         var result = this.chosenApp;
         if (!this.mIsMac) {
             var typed  = this.dialogElement( "appPath" ).value;
             // First, see if one was chosen via the Choose... button.
             if ( result ) {
                 // Verify that the user didn't type in something different later.
-                if ( typed != result.path ) {
+                if ( typed != result.executable.path ) {
                     // Use what was typed in.
                     try {
-                        result.QueryInterface( Components.interfaces.nsILocalFile ).initWithPath( typed );
+                        result.executable.QueryInterface( Components.interfaces.nsILocalFile ).initWithPath( typed );
                     } catch( e ) {
                         // Invalid path was typed.
                         result = null;
                     }
                 }
             } else {
                 // The user didn't use the Choose... button, try using what they typed in.
-                result = Components.classes[ "@mozilla.org/file/local;1" ]
+                var localFile = Components.classes[ "@mozilla.org/file/local;1" ]
                     .createInstance( Components.interfaces.nsILocalFile );
                 try {
-                    result.initWithPath( typed );
+                    localFile.initWithPath( typed );
+                    result = Components.classes[
+                      "@mozilla.org/uriloader/local-handler-app;1"].
+                      createInstance(Components.interfaces.nsILocalHandlerApp);
+                    result.executable = localFile;
+
                 } catch( e ) {
                     result = null;
                 }
             }
             // Remember what was chosen.
             this.chosenApp = result;
         }
         return result;
@@ -586,20 +593,19 @@ nsHelperAppDialog.prototype = {
             if ( needUpdate )
                 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
         } else if ( this.dialogElement( "openUsing" ).selected ) {
             // For "open with", we need to check both preferred action and whether the user chose
             // a new app.
             needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
             if ( needUpdate ) {
                 this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
-                // App may have changed - Update application and description
+                // App may have changed - Update application
                 var app = this.helperAppChoice();
                 this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
-                this.mLauncher.MIMEInfo.applicationDescription = "";
             }
         }
         // Only care about the state of "always ask" if this dialog wasn't forced
         if ( this.mReason == REASON_CANTHANDLE )
         {
           // We will also need to update if the "always ask" flag has changed.
           needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling == this.dialogElement( "alwaysHandle" ).checked;
 
@@ -630,17 +636,17 @@ nsHelperAppDialog.prototype = {
                                  this );
     },
 
     // onOK:
     onOK: function() {
         // Verify typed app path, if necessary.
         if ( this.dialogElement( "openUsing" ).selected ) {
             var helperApp = this.helperAppChoice();
-            if ( !helperApp || !helperApp.exists() ) {
+            if ( !helperApp || !helperApp.executable || !helperApp.exists() ) {
                 // Show alert and try again.
                 var msg = this.replaceInsert( this.getString( "badApp" ), 1, this.dialogElement( "appPath" ).value );
                 var svc = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
                             .getService( Components.interfaces.nsIPromptService );
                 svc.alert( this.mDialog, this.getString( "badApp.title" ), msg );
                 // Disable the OK button.
                 this.mDialog.document.documentElement.getButton( "accept" ).disabled = true;
                 // Select and focus the input field if input field is not disabled
@@ -726,19 +732,25 @@ nsHelperAppDialog.prototype = {
                  this.getString( "chooseAppFilePickerTitle" ),
                  nsIFilePicker.modeOpen );
 
         // XXX - We want to say nsIFilePicker.filterExecutable or something
         fp.appendFilters( nsIFilePicker.filterAll );
 
         if ( fp.show() == nsIFilePicker.returnOK && fp.file ) {
             // Remember the file they chose to run.
-            this.chosenApp = fp.file;
+
+            var localHandler = Components.classes[
+              "@mozilla.org/uriloader/local-handler-app;1"].
+                createInstance(Components.interfaces.nsILocalHandlerApp);
+            localHandler.executable = fp.file;
+            this.chosenApp = localHandler;
             // Update dialog.
-            this.dialogElement( "appPath" ).value = this.getPath(this.chosenApp);
+            this.dialogElement( "appPath" ).value = 
+              this.getPath(this.chosenApp.executable);
         }
     },
 
     // dumpInfo:
     doDebug: function() {
         const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
         // Open new progress dialog.
         var progress = Components.classes[ "@mozilla.org/progressdialog;1" ]
--- a/embedding/components/ui/progressDlg/nsProgressDialog.js
+++ b/embedding/components/ui/progressDlg/nsProgressDialog.js
@@ -440,18 +440,21 @@ nsProgressDialog.prototype = {
     // load dialog with initial contents
     loadDialog: function() {
         // Check whether we're saving versus opening with a helper app.
         if ( !this.saving ) {
             // Put proper label on source field.
             this.setValue( "sourceLabel", this.getString( "openingSource" ) );
 
             // Target is the "preferred" application.  Hide if empty.
-            if ( this.MIMEInfo && this.MIMEInfo.preferredApplicationHandler ) {
-                var appName = this.MIMEInfo.preferredApplicationHandler.leafName;
+            if ( this.MIMEInfo && 
+                 this.MIMEInfo.preferredApplicationHandler &&
+                 this.MIMEInfo.preferredApplicationHandler.executable ) {
+                var appName = 
+                  this.MIMEInfo.preferredApplicationHandler.executable.leafName;
                 if ( appName == null || appName.length == 0 ) {
                     this.hide( "targetRow" );
                 } else {
                     // Use the "with:" label.
                     this.setValue( "targetLabel", this.getString( "openingTarget" ) );
                     // Name of application.
                     this.setValue( "target", appName );
                 }
--- a/netwerk/mime/public/nsIMIMEInfo.idl
+++ b/netwerk/mime/public/nsIMIMEInfo.idl
@@ -15,52 +15,115 @@
  * The Original Code is mozilla.org code.
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 1998
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
+ *   Dan Mosedale <dmose@mozilla.org>
  *
  * 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 ***** */
 
-/**
- * An nsIMIMEInfo gives a user access to mime information.
- * there is a one-to-many relationship between MIME types
- * and file extensions. This means that a MIMEInfo object
- * may have multiple file extensions associated with it.
- * However, the reverse is not true.
- *
- * MIMEInfo objects are generally retrieved from the MIME Service
- * @see nsIMIMEService
- */
-
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIFile;
 interface nsIUTF8StringEnumerator;
+interface nsIHandlerApp;
 
-typedef long nsMIMEInfoHandleAction;
+typedef long nsHandlerInfoAction;
+
+/**
+ * nsIHandlerInfo gives access to the information about how a given protocol
+ * scheme or MIME-type is handled.
+ */
+[scriptable, uuid(feaa5a46-45aa-4124-8e9e-bc23d517c2d4)]
+interface nsIHandlerInfo : nsISupports {
+    /**
+     * A human readable description of the handler type
+     */
+    attribute AString description;
+
+    /**
+     * The application the user has said they want associated with this content
+     * type. This is not always guaranteed to be set!!
+     */
+    attribute nsIHandlerApp preferredApplicationHandler;
+
+    /**
+     * Indicates whether a default application handler exists,
+     * i.e. whether launchWithFile with action = useSystemDefault is possible
+     * and defaultDescription will contain usable information.
+     */
+    readonly attribute boolean hasDefaultHandler;
+
+    /**
+     * A pretty name description of the associated default application. Only
+     * usable if hasDefaultHandler is true.
+     */
+    readonly attribute AString defaultDescription;
 
-[scriptable, uuid(1448b42f-cf0d-466e-9a15-64e876ebe857)]
-interface nsIMIMEInfo : nsISupports {
+    /**
+     * Launches the application with the specified file, in a way that
+     * depends on the value of preferredAction. preferredAction must be
+     * useHelperApp or useSystemDefault.
+     *
+     * @param aFile The file to launch this application with.
+     *
+     * @throw NS_ERROR_INVALID_ARG if action is not valid for this function.
+     * Other exceptions may be thrown.
+     */
+    void launchWithFile(in nsIFile aFile);
+
+    /**
+     * preferredAction is how the user specified they would like to handle
+     * this content type: save to disk, use specified helper app, use OS
+     * default handler or handle using navigator; possible value constants
+     * listed below
+     */
+    attribute nsHandlerInfoAction preferredAction;
+
+    const long saveToDisk = 0;
+    const long alwaysAsk = 1;
+    const long useHelperApp = 2;
+    const long handleInternally = 3;
+    const long useSystemDefault = 4;
+
+    /**
+     * alwaysAskBeforeHandling: if true, we should always give the user a
+     * dialog asking how to dispose of this content.
+     */
+    attribute boolean alwaysAskBeforeHandling;
+};
+
+/**
+ * nsIMIMEInfo extends nsIHandlerInfo with a bunch of information specific to
+ * MIME content-types. There is a one-to-many relationship between MIME types
+ * and file extensions. This means that a MIMEInfo object may have multiple
+ * file extensions associated with it.  However, the reverse is not true.
+ *
+ * MIMEInfo objects are generally retrieved from the MIME Service
+ * @see nsIMIMEService
+ */
+[scriptable, uuid(ba01eb13-c2fd-4652-94f8-b33c61e6d77e)]
+interface nsIMIMEInfo : nsIHandlerInfo {
     /**
      * Gives you an array of file types associated with this type.
      *
      * @return Number of elements in the array.
      * @return Array of extensions.
      */
     nsIUTF8StringEnumerator getFileExtensions();
 
@@ -93,86 +156,67 @@ interface nsIMIMEInfo : nsISupports {
     /**
      * The MIME type of this MIMEInfo.
      * 
      * @return String representing the MIME type.
      */
     readonly attribute ACString MIMEType;
 
     /**
-     * A human readable description of the MIME info.
-     *
-     * @return The description
-     */
-    attribute AString description;
-
-    /**
      * Mac Type and creator types
      */
     attribute PRUint32 macType;
     attribute PRUint32 macCreator;
 
     /**
-     * Returns whether or not these two MIME infos are logically
-     * equivalent maintaining the one-to-many relationship between
-     * MIME types and file extensions.
+     * Returns whether or not these two nsIMIMEInfos are logically
+     * equivalent.
      *
-     * @returns TRUE if the two are considered equal
+     * @returns PR_TRUE if the two are considered equal
      */
     boolean equals(in nsIMIMEInfo aMIMEInfo);
-
-    /**
-     * Returns a nsIFile that points to the application the user has said
-     * they want associated with this content type. This is not always
-     * guaranteed to be set!!
-     */
-    attribute nsIFile preferredApplicationHandler;
+};
 
-    /**
-     * A pretty name description of the preferred application.
-     */
-    attribute AString applicationDescription;
-
-    /**
-     * Indicates whether a default application handler exists,
-     * i.e. whether launchWithFile with action = useSystemDefault is possible
-     * and applicationDescription will contain usable information.
-     */
-    readonly attribute boolean hasDefaultHandler;
+/**
+ * nsIHandlerApp represents an external application that can handle content
+ * of some sort (either a MIME-type or a protocol).  Note that for theoretical
+ * cleanliness, nsI{Local,Web}HandlerApp really ought to inherit from
+ * nsIHandlerApp.  After that's done, we should also try and make
+ * nsIWebContentHandlerInfo inherit from or possibly be replaced by
+ * nsIWebHandlerApp.
+ */
+[scriptable, uuid(08a543dc-081f-4933-b325-252cf68d6ad9)]
+interface nsIHandlerApp : nsISupports {
 
     /**
-     * A pretty name description of the associated default application. Only
-     * usable if hasDefaultHandler is true.
+     * Human readable name for the handler
      */
-    readonly attribute AString defaultDescription;
+    attribute AString name;
+};
+
+/**
+ * nsILocalHandlerApp is a local OS-level executable
+ */
+[scriptable, uuid(e21f3d75-9103-490e-bfb9-1bf09cc3f103)]
+interface nsILocalHandlerApp: nsISupports {
 
     /**
-     * Launches the application with the specified file, in a way that
-     * depends on the value of preferredAction. preferredAction must be
-     * useHelperApp or useSystemDefault.
-     *
-     * @param aFile The file to launch this application with.
-     *
-     * @throw NS_ERROR_INVALID_ARG if action is not valid for this function.
-     * Other exceptions may be thrown.
+     * Pointer to the executable file used to handle content
      */
-    void launchWithFile(in nsIFile aFile);
+    attribute nsIFile executable;
+};
 
-    const long saveToDisk = 0;
-    const long alwaysAsk = 1;
-    const long useHelperApp = 2;
-    const long handleInternally = 3;
-    const long useSystemDefault = 4;
+/**
+ * nsIWebHandlerApp is a web-based handler, as speced by the WhatWG HTML5
+ * draft.  Currently, only GET-based handlers are supported.  At some point, 
+ * we probably want to work with WhatWG to spec out and implement POST-based
+ * handlers as well.
+ */
+[scriptable, uuid(065cd099-2f71-4ac8-9dab-17fc079e9647)]
+interface nsIWebHandlerApp: nsISupports {
 
     /**
-     * preferredAction is how the user specified they would like to handle
-     * this content type: save to disk, use specified helper app, use OS
-     * default handler or handle using navigator.
+     * Template used to construct the URI to GET.  Template is expected to have
+     * a %s in it, and the escaped URI to be handled is inserted in place of 
+     * that %s, as per the HTML5 spec.
      */
-    attribute nsMIMEInfoHandleAction preferredAction;
-
-    /**
-     * alwaysAskBeforeHandling: if true, we should always give the user a
-     * dialog asking how to dispose of this content.
-     */
-    attribute boolean alwaysAskBeforeHandling;
+    attribute AUTF8String uriTemplate;
 };
-
--- a/toolkit/mozapps/downloads/content/helperApps.js
+++ b/toolkit/mozapps/downloads/content/helperApps.js
@@ -161,18 +161,18 @@ HelperApps.prototype = {
         // Creating a new entry, set path.
         entry.appPath = "";
       }
     } 
     else {
       entry.saveToDisk       = false;
       entry.useSystemDefault = false;
       entry.handleInternal   = false;
-      entry.appPath = aMIMEInfo.preferredApplicationHandler.path;
-      entry.appDisplayName = aMIMEInfo.applicationDescription;
+      entry.appPath = aMIMEInfo.preferredApplicationHandler.executable.path;
+      entry.appDisplayName = aMIMEInfo.preferredApplicationHandler.name;
     }
     
     // Do RDF magic.
     entry.buildLinks();
     this.flush();
   },
 
   getLiteralValue: function (aResource, aProperty)
--- a/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
+++ b/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
@@ -20,16 +20,17 @@
 # Portions created by the Initial Developer are Copyright (C) 2001
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Bill Law <law@netscape.com>
 #   Scott MacGregor <mscott@netscape.com>
 #   Ben Goodger <ben@bengoodger.com> (2.0)
 #   Fredrik Holmqvist <thesuckiestemail@yahoo.se>
+#   Dan Mosedale <dmose@mozilla.org>
 #
 # 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
@@ -678,19 +679,21 @@ nsUnknownContentTypeDialog.prototype = {
       // Fill in helper app info, if there is any.
       this.chosenApp = this.mLauncher.MIMEInfo.preferredApplicationHandler;
       // Initialize "default application" field.
       this.initDefaultApp();
 
       var otherHandler = this.dialogElement("otherHandler");
               
       // Fill application name textbox.
-      if (this.chosenApp && this.chosenApp.path) {
-        otherHandler.setAttribute("path", this.getPath(this.chosenApp));
-        otherHandler.label = this.chosenApp.leafName;
+      if (this.chosenApp && this.chosenApp.executable && 
+          this.chosenApp.executable.path) {
+        otherHandler.setAttribute("path",
+                                  this.getPath(this.chosenApp.executable));
+        otherHandler.label = this.chosenApp.executable.leafName;
         otherHandler.hidden = false;
       }
 
       var useDefault = this.dialogElement("useSystemDefault");
       var openHandler = this.dialogElement("openHandler");
       openHandler.selectedIndex = 0;
 
       if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
@@ -803,20 +806,19 @@ nsUnknownContentTypeDialog.prototype = {
           this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
       } 
       else {
         // For "open with", we need to check both preferred action and whether the user chose
         // a new app.
         needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
         if (needUpdate) {
           this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
-          // App may have changed - Update application and description
+          // App may have changed - Update application
           var app = this.helperAppChoice();
           this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
-          this.mLauncher.MIMEInfo.applicationDescription = "";
         }
       }
       // We will also need to update if the "always ask" flag has changed.
       needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked);
 
       // One last special case: If the input "always ask" flag was false, then we always
       // update.  In that case we are displaying the helper app dialog for the first
       // time for this mime type and we need to store the user's action in the mimeTypes.rdf
@@ -839,17 +841,18 @@ nsUnknownContentTypeDialog.prototype = {
       ha.destroy();
     },
     
     // onOK:
     onOK: function() {
       // Verify typed app path, if necessary.
       if (this.useOtherHandler) {
         var helperApp = this.helperAppChoice();
-        if (!helperApp || !helperApp.exists()) {
+        if (!helperApp || !helperApp.executable ||
+            !helperApp.executable.exists()) {
           // Show alert and try again.        
           var bundle = this.dialogElement("strings");                    
           var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").path]);
           var svc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
           svc.alert(this.mDialog, bundle.getString("badApp.title"), msg);
 
           // Disable the OK button.
           this.mDialog.document.documentElement.getButton("accept").disabled = true;
@@ -954,22 +957,27 @@ nsUnknownContentTypeDialog.prototype = {
       fp.appendFilters(nsIFilePicker.filterApps);
 
       if (fp.show() == nsIFilePicker.returnOK && fp.file) {
         // Show the "handler" menulist since we have a (user-specified) 
         // application now.
         this.dialogElement("modeDeck").setAttribute("selectedIndex", "0");
         
         // Remember the file they chose to run.
-        this.chosenApp = fp.file;
+        var localHandlerApp = 
+          Components.classes["@mozilla.org/uriloader/local-handler-app;1"].
+          createInstance(Components.interfaces.nsILocalHandlerApp);
+        localHandlerApp.executable = fp.file;
+        this.chosenApp = localHandlerApp;
+        
         // Update dialog.
         var otherHandler = this.dialogElement("otherHandler");
         otherHandler.removeAttribute("hidden");
-        otherHandler.setAttribute("path", this.getPath(this.chosenApp));
-        otherHandler.label = this.chosenApp.leafName;
+        otherHandler.setAttribute("path", this.getPath(this.chosenApp.executable));
+        otherHandler.label = this.chosenApp.executable.leafName;
         this.dialogElement("openHandler").selectedIndex = 1;
         this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler");
         
         this.dialogElement("mode").selectedItem = this.dialogElement("open");
       }
       else {
         var openHandler = this.dialogElement("openHandler");
         var lastSelectedID = openHandler.getAttribute("lastSelectedItemID");
--- a/uriloader/exthandler/Makefile.in
+++ b/uriloader/exthandler/Makefile.in
@@ -133,16 +133,17 @@ XPIDLSRCS = \
 ifneq (,$(filter mac cocoa,$(MOZ_WIDGET_TOOLKIT)))
 XPIDLSRCS		+= nsIInternetConfigService.idl
 endif
 
 CPPSRCS	= \
 	nsExternalHelperAppService.cpp	\
 	nsExternalProtocolHandler.cpp \
 	nsMIMEInfoImpl.cpp \
+	nsHandlerAppImpl.cpp \
 	$(OSHELPER) \
 	$(NULL)
 
 ifeq ($(OS_ARCH),WINNT WINCE)
 OS_LIBS		+= shell32.lib
 GARBAGE		+= nsOSHelperAppService.cpp $(srcdir)/nsOSHelperAppService.cpp \
              nsMIMEInfoWin.cpp $(srcdir)/nsMIMEInfoWin.cpp
 endif
--- a/uriloader/exthandler/mac/nsMIMEInfoMac.cpp
+++ b/uriloader/exthandler/mac/nsMIMEInfoMac.cpp
@@ -41,21 +41,27 @@
 #include <LaunchServices.h>
 
 #include "nsMIMEInfoMac.h"
 #include "nsILocalFileMac.h"
 
 NS_IMETHODIMP
 nsMIMEInfoMac::LaunchWithFile(nsIFile* aFile)
 {
-  nsIFile* application;
+  nsCOMPtr<nsIFile> application;
 
-  if (mPreferredAction == useHelperApp)
-    application = mPreferredApplication;
-  else if (mPreferredAction == useSystemDefault)
+  if (mPreferredAction == useHelperApp) {
+    nsresult rv;
+    nsCOMPtr<nsILocalHandlerApp> localHandlerApp =
+      do_QueryInterface(mPreferredApplication, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+          
+    rv = localHandlerApp->GetExecutable(getter_AddRefs(application));
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else if (mPreferredAction == useSystemDefault)
     application = mDefaultApplication;
   else
     return NS_ERROR_INVALID_ARG;
 
   if (application) {
     nsresult rv;
     nsCOMPtr<nsILocalFileMac> app = do_QueryInterface(application, &rv);
     if (NS_FAILED(rv)) return rv;
--- a/uriloader/exthandler/nsCExternalHandlerService.idl
+++ b/uriloader/exthandler/nsCExternalHandlerService.idl
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications, Inc.
  * Portions created by the Initial Developer are Copyright (C) 1999
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Scott MacGregor <mscott@netscape.com>
+ *   Dan Mosedale <dmose@mozilla.org>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of 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
@@ -62,10 +63,17 @@ nsIExternalHelperAppService
 
 #define NS_EXTERNALPROTOCOLHANDLER_CID	\
 { 0xbd6390c8, 0xfbea, 0x11d4, {0x98, 0xf6, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } }
 
 /* 9fa83ce7-d0ab-4ed3-938e-afafee435670 */
 #define NS_BLOCKEDEXTERNALPROTOCOLHANDLER_CID	\
 { 0x9fa83ce7, 0xd0ab, 0x4ed3, {0x93, 0x8e, 0xaf, 0xaf, 0xee, 0x43, 0x56, 0x70 } }
 
+/* bc0017e3-2438-47be-a567-41db58f17627 */
+#define NS_LOCALHANDLERAPP_CID \
+{ 0xbc0017e3, 0x2438, 0x47be, {0xa5, 0x67, 0x41, 0xdb, 0x58, 0xf1, 0x76, 0x27 } }
+
+#define NS_LOCALHANDLERAPP_CONTRACTID \
+"@mozilla.org/uriloader/local-handler-app;1"
+
 %}
 
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -115,16 +115,19 @@
 #include "nsIGlobalHistory2.h" // to mark downloads as visited
 
 #include "nsIDOMWindow.h"
 #include "nsIDOMWindowInternal.h"
 #include "nsIDocShell.h"
 
 #include "nsCRT.h"
 
+#include "nsMIMEInfoImpl.h"
+#include "nsHandlerAppImpl.h"
+
 #ifdef PR_LOGGING
 PRLogModuleInfo* nsExternalHelperAppService::mLog = nsnull;
 #endif
 
 // Using level 3 here because the OSHelperAppServices use a log level
 // of PR_LOG_DEBUG (4), and we want less detailed output here
 // Using 3 instead of PR_LOG_WARN because we don't output warnings
 #define LOG(args) PR_LOG(mLog, 3, args)
@@ -561,17 +564,20 @@ nsresult nsExternalHelperAppService::Ini
                      getter_AddRefs(kNC_SaveToDisk));
     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_USESYSTEMDEFAULT),
                      getter_AddRefs(kNC_UseSystemDefault));
     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_HANDLEINTERNAL),
                      getter_AddRefs(kNC_HandleInternal));
     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_ALWAYSASK),
                      getter_AddRefs(kNC_AlwaysAsk));  
     rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_PRETTYNAME),
-                     getter_AddRefs(kNC_PrettyName));  
+                     getter_AddRefs(kNC_PrettyName));
+    rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_URITEMPLATE),
+                     getter_AddRefs(kNC_UriTemplate));
+     
   }
   
   mDataSourceInitialized = PR_TRUE;
 
   return rv;
 #else
   return NS_ERROR_NOT_AVAILABLE;
 #endif
@@ -710,33 +716,29 @@ NS_IMETHODIMP nsExternalHelperAppService
       *aApplyDecoding = PR_FALSE;
       break;
     }
   }
   return NS_OK;
 }
 
 #ifdef MOZ_RDF
-nsresult nsExternalHelperAppService::FillTopLevelProperties(nsIRDFResource * aContentTypeNodeResource, 
-                                                            nsIRDFService * aRDFService, nsIMIMEInfo * aMIMEInfo)
+nsresult nsExternalHelperAppService::FillMIMEExtensionProperties(
+  nsIRDFResource * aContentTypeNodeResource, nsIRDFService * aRDFService,
+  nsIMIMEInfo * aMIMEInfo)
 {
   nsresult rv = NS_OK;
   nsCOMPtr<nsIRDFNode> target;
   nsCOMPtr<nsIRDFLiteral> literal;
   const PRUnichar * stringValue;
   
   rv = InitDataSource();
   if (NS_FAILED(rv)) return NS_OK;
 
-  // set the pretty name description, if nonempty
-  FillLiteralValueFromTarget(aContentTypeNodeResource,kNC_Description, &stringValue);
-  if (stringValue && *stringValue)
-    aMIMEInfo->SetDescription(nsDependentString(stringValue));
-
-  // now iterate over all the file type extensions...
+  // iterate over all the file type extensions...
   nsCOMPtr<nsISimpleEnumerator> fileExtensions;
   mOverRideDataSource->GetTargets(aContentTypeNodeResource, kNC_FileExtensions, PR_TRUE, getter_AddRefs(fileExtensions));
 
   PRBool hasMoreElements = PR_FALSE;
   nsCAutoString fileExtension; 
   nsCOMPtr<nsISupports> element;
 
   if (fileExtensions)
@@ -782,87 +784,110 @@ nsresult nsExternalHelperAppService::Fil
     literal->GetValueConst(aLiteralValue);
   }
   else
     rv = NS_ERROR_FAILURE;
 
   return rv;
 }
 
-nsresult nsExternalHelperAppService::FillContentHandlerProperties(const char * aContentType, 
-                                                                  nsIRDFResource * aContentTypeNodeResource, 
-                                                                  nsIRDFService * aRDFService, 
-                                                                  nsIMIMEInfo * aMIMEInfo)
+nsresult nsExternalHelperAppService::FillContentHandlerProperties(
+  const char * aContentType, const char * aTypeNodePrefix,
+  nsIRDFService * aRDFService, nsIHandlerInfo * aHandlerInfo)
 {
   nsCOMPtr<nsIRDFNode> target;
   nsCOMPtr<nsIRDFLiteral> literal;
   const PRUnichar * stringValue = nsnull;
   nsresult rv = NS_OK;
 
   rv = InitDataSource();
   if (NS_FAILED(rv)) return rv;
 
-  nsCAutoString contentTypeHandlerNodeName(NC_CONTENT_NODE_HANDLER_PREFIX);
+  nsCAutoString contentTypeHandlerNodeName(aTypeNodePrefix);
+  contentTypeHandlerNodeName.Append(NC_HANDLER_SUFFIX);
   contentTypeHandlerNodeName.Append(aContentType);
 
   nsCOMPtr<nsIRDFResource> contentTypeHandlerNodeResource;
   aRDFService->GetResource(contentTypeHandlerNodeName, getter_AddRefs(contentTypeHandlerNodeResource));
   NS_ENSURE_TRUE(contentTypeHandlerNodeResource, NS_ERROR_FAILURE); // that's not good! we have an error in the rdf file
 
   // now process the application handler information
-  aMIMEInfo->SetPreferredAction(nsIMIMEInfo::useHelperApp);
+  aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useHelperApp);
 
   // save to disk
   FillLiteralValueFromTarget(contentTypeHandlerNodeResource,kNC_SaveToDisk, &stringValue);
   NS_NAMED_LITERAL_STRING(trueString, "true");
   NS_NAMED_LITERAL_STRING(falseString, "false");
   if (stringValue && trueString.Equals(stringValue))
-       aMIMEInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+       aHandlerInfo->SetPreferredAction(nsIHandlerInfo::saveToDisk);
 
   // use system default
   FillLiteralValueFromTarget(contentTypeHandlerNodeResource,kNC_UseSystemDefault, &stringValue);
   if (stringValue && trueString.Equals(stringValue))
-      aMIMEInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+      aHandlerInfo->SetPreferredAction(nsIHandlerInfo::useSystemDefault);
 
   // handle internal
   FillLiteralValueFromTarget(contentTypeHandlerNodeResource,kNC_HandleInternal, &stringValue);
   if (stringValue && trueString.Equals(stringValue))
-       aMIMEInfo->SetPreferredAction(nsIMIMEInfo::handleInternally);
+       aHandlerInfo->SetPreferredAction(nsIHandlerInfo::handleInternally);
   
   // always ask
   FillLiteralValueFromTarget(contentTypeHandlerNodeResource,kNC_AlwaysAsk, &stringValue);
   // Only skip asking if we are absolutely sure the user does not want
   // to be asked.  Any sort of bofus data should mean we ask.
-  aMIMEInfo->SetAlwaysAskBeforeHandling(!stringValue ||
+  aHandlerInfo->SetAlwaysAskBeforeHandling(!stringValue ||
                                         !falseString.Equals(stringValue));
 
 
   // now digest the external application information
 
-  nsCAutoString externalAppNodeName (NC_CONTENT_NODE_EXTERNALAPP_PREFIX);
+  nsCAutoString externalAppNodeName(aTypeNodePrefix);
+  externalAppNodeName.AppendLiteral(NC_EXTERNALAPP_SUFFIX);
   externalAppNodeName.Append(aContentType);
   nsCOMPtr<nsIRDFResource> externalAppNodeResource;
   aRDFService->GetResource(externalAppNodeName, getter_AddRefs(externalAppNodeResource));
 
   // Clear out any possibly set preferred application, to match the datasource
-  aMIMEInfo->SetApplicationDescription(EmptyString());
-  aMIMEInfo->SetPreferredApplicationHandler(nsnull);
+  aHandlerInfo->SetPreferredApplicationHandler(nsnull);
   if (externalAppNodeResource)
   {
-    FillLiteralValueFromTarget(externalAppNodeResource, kNC_PrettyName, &stringValue);
-    if (stringValue)
-      aMIMEInfo->SetApplicationDescription(nsDependentString(stringValue));
- 
+    const PRUnichar * appName;
+    FillLiteralValueFromTarget(externalAppNodeResource, kNC_PrettyName,
+                               &appName);
+
+    // if we've got a path name, this must be a local app
     FillLiteralValueFromTarget(externalAppNodeResource, kNC_Path, &stringValue);
     if (stringValue && stringValue[0])
     {
       nsCOMPtr<nsIFile> application;
       GetFileTokenForPath(stringValue, getter_AddRefs(application));
-      if (application)
-        aMIMEInfo->SetPreferredApplicationHandler(application);
+      if (application) {
+        nsLocalHandlerApp *handlerApp(new nsLocalHandlerApp(appName, application));
+        if (!handlerApp) { 
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        rv = aHandlerInfo->SetPreferredApplicationHandler(handlerApp);
+      }
+    } else {
+      // if we got here, there's no path name in the RDF graph, so this must 
+      // be a web app
+      FillLiteralValueFromTarget(externalAppNodeResource, kNC_UriTemplate, 
+                                 &stringValue);
+      if (stringValue && stringValue[0]) {
+        nsWebHandlerApp *handlerApp(new nsWebHandlerApp(appName, 
+          NS_ConvertUTF16toUTF8(stringValue)));
+        
+        if (!handlerApp) {
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
+        rv = aHandlerInfo->SetPreferredApplicationHandler(handlerApp);
+      } else {
+        return NS_ERROR_FAILURE; // no path name _and_ no uri template
+      }
     }
   }
 
   return rv;
 }
 #endif /* MOZ_RDF */
 
 PRBool nsExternalHelperAppService::MIMETypeIsInDataSource(const char * aContentType)
@@ -898,80 +923,142 @@ PRBool nsExternalHelperAppService::MIMET
     rv = mOverRideDataSource->HasAssertion(contentTypeNodeResource, kNC_Value, mimeLiteral, PR_TRUE, &exists );
     
     if (NS_SUCCEEDED(rv) && exists) return PR_TRUE;
   }
 #endif
   return PR_FALSE;
 }
 
-nsresult nsExternalHelperAppService::GetMIMEInfoForMimeTypeFromDS(const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo)
+nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromDS(
+  const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo)
 {
 #ifdef MOZ_RDF
   NS_ENSURE_ARG_POINTER(aMIMEInfo);
   nsresult rv = InitDataSource();
   if (NS_FAILED(rv)) return rv;
 
   // can't do anything if we have no datasource...
   if (!mOverRideDataSource)
     return NS_ERROR_FAILURE;
 
   // Get the RDF service.
   nsCOMPtr<nsIRDFService> rdf = do_GetService(kRDFServiceCID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  
-  // Build uri for the mimetype resource.
-  nsCAutoString contentTypeNodeName(NC_CONTENT_NODE_PREFIX);
-  nsCAutoString contentType(aContentType);
-  ToLowerCase(contentType);
-  contentTypeNodeName.Append(contentType);
-
-  // Get the mime type resource.
-  nsCOMPtr<nsIRDFResource> contentTypeNodeResource;
-  rv = rdf->GetResource(contentTypeNodeName, getter_AddRefs(contentTypeNodeResource));
+  // get lowercase typename & uri for the handlertype resource.
+  nsCAutoString typeNodeName(NC_CONTENT_NODE_PREFIX);
+  nsCAutoString type(aContentType);
+  ToLowerCase(type);
+  typeNodeName.Append(type);
+
+  // Get the handler type resource.
+  nsCOMPtr<nsIRDFResource> typeNodeResource;
+  rv = rdf->GetResource(typeNodeName, getter_AddRefs(typeNodeResource));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // fill in MIME-specific extension info
+  rv = FillMIMEExtensionProperties(typeNodeResource, rdf, aMIMEInfo);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // we need a way to determine if this content type resource is really in the graph or not...
-  // ...Test that there's a #value arc from the mimetype resource to the mimetype literal string.
-  nsCOMPtr<nsIRDFLiteral> mimeLiteral;
-  NS_ConvertUTF8toUTF16 mimeType(contentType);
-  rv = rdf->GetLiteral( mimeType.get(), getter_AddRefs( mimeLiteral ) );
+  return FillHandlerInfoForTypeFromDS(typeNodeResource.get(), type, rdf, 
+                                      NC_CONTENT_NODE_PREFIX, aMIMEInfo); 
+#else
+  return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+nsresult nsExternalHelperAppService::FillProtoInfoForSchemeFromDS(
+    const nsACString& aType, nsIHandlerInfo * aHandlerInfo)
+{
+#ifdef MOZ_RDF
+  NS_ENSURE_ARG_POINTER(aHandlerInfo);
+  nsresult rv = InitDataSource();
+  if (NS_FAILED(rv)) return rv;
+
+  // can't do anything if we have no datasource...
+  if (!mOverRideDataSource)
+    return NS_ERROR_FAILURE;
+
+  // Get the RDF service.
+  nsCOMPtr<nsIRDFService> rdf = do_GetService(kRDFServiceCID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // get lowercase typename & get uri for the handlertype resource.
+  nsCAutoString typeNodeName(NC_SCHEME_NODE_PREFIX);
+  nsCAutoString type(aType);
+  ToLowerCase(type);
+  typeNodeName.Append(type);
+
+  // Get the handler type resource.
+  nsCOMPtr<nsIRDFResource> typeNodeResource;
+  rv = rdf->GetResource(typeNodeName, getter_AddRefs(typeNodeResource));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return FillHandlerInfoForTypeFromDS(typeNodeResource.get(), type, rdf,
+                                      NC_SCHEME_NODE_PREFIX, aHandlerInfo);
+#else
+  return NS_ERROR_NOT_AVAILABLE;
+#endif
+}
+
+nsresult nsExternalHelperAppService::FillHandlerInfoForTypeFromDS(
+  nsIRDFResource *aTypeNodeResource, const nsCAutoString &aType, 
+  nsIRDFService *rdf, const char *aTypeNodePrefix,
+  nsIHandlerInfo * aHandlerInfo)
+{
+#ifdef MOZ_RDF
+
+  // we need a way to determine if this type resource is really in the graph
+  // or not... Test that there's a #value arc from the type resource to the
+  // type literal string.
+  nsCOMPtr<nsIRDFLiteral> typeLiteral;
+  NS_ConvertUTF8toUTF16 UTF16Type(aType);
+  nsresult rv = rdf->GetLiteral( UTF16Type.get(), 
+                                 getter_AddRefs( typeLiteral ) );
   NS_ENSURE_SUCCESS(rv, rv);
   
   PRBool exists = PR_FALSE;
-  rv = mOverRideDataSource->HasAssertion(contentTypeNodeResource, kNC_Value, mimeLiteral, PR_TRUE, &exists );
-
+  rv = mOverRideDataSource->HasAssertion(aTypeNodeResource, kNC_Value,
+                                         typeLiteral, PR_TRUE, &exists );
   if (NS_SUCCEEDED(rv) && exists)
   {
-     // fill the mimeinfo in based on the values from the data source
-     rv = FillTopLevelProperties(contentTypeNodeResource, rdf, aMIMEInfo);
-     NS_ENSURE_SUCCESS(rv, rv);
-     rv = FillContentHandlerProperties(contentType.get(), contentTypeNodeResource, rdf, aMIMEInfo);
+     // fill in the handlerinfo based on the values from the data source
+
+     // set the pretty name description, if nonempty
+     const PRUnichar *stringValue;
+     FillLiteralValueFromTarget(aTypeNodeResource, kNC_Description, 
+                                &stringValue);
+     if (stringValue && *stringValue)
+       aHandlerInfo->SetDescription(nsDependentString(stringValue));
+
+     rv = FillContentHandlerProperties(aType.get(), aTypeNodePrefix,
+                                       rdf, aHandlerInfo);
 
   } // if we have a node in the graph for this content type
   // If we had success, but entry doesn't exist, we don't want to return success
   else if (NS_SUCCEEDED(rv)) {
     rv = NS_ERROR_NOT_AVAILABLE;
   }
 
   return rv;
 #else
   return NS_ERROR_NOT_AVAILABLE;
 #endif /* MOZ_RDF */
 }
 
-nsresult nsExternalHelperAppService::GetMIMEInfoForExtensionFromDS(const nsACString& aFileExtension, nsIMIMEInfo * aMIMEInfo)
+nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromDS(
+  const nsACString& aFileExtension, nsIMIMEInfo * aMIMEInfo)
 {
   nsCAutoString type;
   PRBool found = GetTypeFromDS(aFileExtension, type);
   if (!found)
     return NS_ERROR_NOT_AVAILABLE;
 
-  return GetMIMEInfoForMimeTypeFromDS(type, aMIMEInfo);
+  return FillMIMEInfoForMimeTypeFromDS(type, aMIMEInfo);
 }
 
 PRBool nsExternalHelperAppService::GetTypeFromDS(const nsACString& aExtension,
                                                  nsACString& aType)
 {
 #ifdef MOZ_RDF
   nsresult rv = InitDataSource();
   if (NS_FAILED(rv))
@@ -1046,26 +1133,27 @@ nsresult nsExternalHelperAppService::Get
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////////////////
 // begin external protocol service default implementation...
 //////////////////////////////////////////////////////////////////////////////////////////////////////
 NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme,
                                                                         PRBool * aHandlerExists)
 {
-  // check for web-based handler
-  nsCAutoString uriTemplate;
-  nsresult rv = GetWebProtocolHandlerURITemplate(
-    nsDependentCString(aProtocolScheme), uriTemplate);
+  // if we've got handler info in the datasource, that means that at least one
+  // web handler exists
+  nsCOMPtr<nsIHandlerInfo> handlerInfo;
+  nsresult rv = GetProtocolHandlerInfo(
+      nsDependentCString(aProtocolScheme), getter_AddRefs(handlerInfo));
   if (NS_SUCCEEDED(rv)) {
     *aHandlerExists = PR_TRUE;
     return NS_OK;
   }
 
-  // fall back on an os-based handler
+  // if not, fall back on an os-based handler
   return OSProtocolHandlerExists(aProtocolScheme, aHandlerExists);
 }
 
 NS_IMETHODIMP nsExternalHelperAppService::IsExposedProtocol(const char * aProtocolScheme, PRBool * aResult)
 {
   // by default, no protocol is exposed.  i.e., by default all link clicks must
   // go through the external protocol service.  most applications override this
   // default behavior.
@@ -1370,49 +1458,42 @@ nsresult nsExternalHelperAppService::Exp
   }
 
   mTemporaryFilesList.Clear();
 
   return NS_OK;
 }
 
 nsresult
-nsExternalHelperAppService::GetWebProtocolHandlerURITemplate(const nsACString &aScheme, 
-                                                             nsACString &aUriTemplate)
+nsExternalHelperAppService::GetProtocolHandlerInfo(const nsACString &aScheme, 
+                                                   nsIHandlerInfo **aHandlerInfo)
 {
-  nsresult rv;
-  nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID,
-                                                     &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-    
-  // before we expose this to the UI, we need sort out our strategy re
+  // XXX before we expose this to the UI, we need sort out our strategy re
   // the "warning" and "exposed" prefs
 
   // XXX enterprise customers should be able to turn this support off with a
   // single master pref (maybe use one of the "exposed" prefs here?)
+
+  // nsIMIMEInfo is a superset of nsIHandlerInfo.  Furthermore, nsMimeInfoImpl
+  // and subclasses have lots of good platform specific-knowledge of local
+  // applications which we might need later.  For now, just use nsMIMEInfoImpl
+  // instead of implementating a separate nsIHandlerInfo object.
+  nsMIMEInfoImpl *mimeInfo = new nsMIMEInfoImpl;
+  if (!mimeInfo) {
+    return NS_ERROR_OUT_OF_MEMORY;    
+  }
+  NS_ADDREF(*aHandlerInfo = mimeInfo);
+     
+  nsresult rv = FillProtoInfoForSchemeFromDS(aScheme, *aHandlerInfo);     
+  if (NS_FAILED(rv)) {
+    NS_RELEASE(*aHandlerInfo);
+    return rv;
+  }
   
-  // do we have an automatic handler for this protocol?
-  nsCAutoString autoTemplatePref("network.protocol-handler.web.auto.");
-  autoTemplatePref += aScheme;
-    
-  nsCAutoString autoTemplate;
-  rv = prefBranch->GetCharPref(autoTemplatePref.get(), 
-                               getter_Copies(autoTemplate));
-  // if so, return it.
-  if (NS_SUCCEEDED(rv)) {
-      aUriTemplate.Assign(autoTemplate);
-      return NS_OK;
-  }
-
-  // XXX since there's no automatic handler, if we have any choices, offer them
-  // up as well as including the option to make one of the choices automatic.  
-  // do so by returning "about:protoHandlerChooser?uri=%s" as the template or 
-  // perhaps by opening a dialog
-
-  return NS_ERROR_FAILURE;
+  return NS_OK;
 }
  
 // XPCOM profile change observer
 NS_IMETHODIMP
 nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData )
 {
   if (!strcmp(aTopic, "profile-before-change")) {
     ExpungeTemporaryFiles();
@@ -1889,17 +1970,17 @@ NS_IMETHODIMP nsExternalAppHandler::OnSt
      * executable.  If it could, we had better not try to open it!
      * We can skip this check, though, if we have a setting to open in a
      * helper app.
      * This code mirrors the code in
      * nsExternalAppHandler::LaunchWithApplication so that what we
      * test here is as close as possible to what will really be
      * happening if we decide to execute
      */
-    nsCOMPtr<nsIFile> prefApp;
+    nsCOMPtr<nsIHandlerApp> prefApp;
     mMimeInfo->GetPreferredApplicationHandler(getter_AddRefs(prefApp));
     if (action != nsIMIMEInfo::useHelperApp || !prefApp) {
       nsCOMPtr<nsIFile> fileToTest;
       GetTargetFile(getter_AddRefs(fileToTest));
       if (fileToTest) {
         PRBool isExecutable;
         rv = fileToTest->IsExecutable(&isExecutable);
         if (NS_FAILED(rv) || isExecutable) {  // checking NS_FAILED, because paranoia is good
@@ -2161,17 +2242,17 @@ NS_IMETHODIMP nsExternalAppHandler::OnSt
   return NS_OK;
 }
 
 nsresult nsExternalAppHandler::ExecuteDesiredAction()
 {
   nsresult rv = NS_OK;
   if (mProgressListenerInitialized && !mCanceled)
   {
-    nsMIMEInfoHandleAction action = nsIMIMEInfo::saveToDisk;
+    nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
     mMimeInfo->GetPreferredAction(&action);
     if (action == nsIMIMEInfo::useHelperApp ||
         action == nsIMIMEInfo::useSystemDefault)
     {
       // Make sure the suggested name is unique since in this case we don't
       // have a file name that was guaranteed to be unique by going through
       // the File Save dialog
       rv = mFinalFileDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
@@ -2516,18 +2597,21 @@ NS_IMETHODIMP nsExternalAppHandler::Laun
 {
   if (mCanceled)
     return NS_OK;
 
   // user has chosen to launch using an application, fire any refresh tags now...
   ProcessAnyRefreshTags(); 
   
   mReceivedDispositionInfo = PR_TRUE; 
-  if (mMimeInfo && aApplication)
-    mMimeInfo->SetPreferredApplicationHandler(aApplication);
+  if (mMimeInfo && aApplication) {
+    nsLocalHandlerApp *handlerApp(new nsLocalHandlerApp(EmptyString(), 
+                                                        aApplication));
+    mMimeInfo->SetPreferredApplicationHandler(handlerApp);
+  }
 
   // Now check if the file is local, in which case we won't bother with saving
   // it to a temporary directory and just launch it from where it is
   nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(mSourceUrl));
   if (fileUrl && mIsFileChannel)
   {
     Cancel(NS_BINDING_ABORTED);
     nsCOMPtr<nsIFile> file;
@@ -2604,17 +2688,17 @@ NS_IMETHODIMP nsExternalAppHandler::Canc
   }
 
   // clean up after ourselves and delete the temp file...
   // but only if we got asked to open the file. when saving,
   // we leave the file there - the partial file might be useful
   // But if we haven't received disposition info yet, then we're
   // here because the user cancelled the helper app dialog.
   // Delete the file in this case.
-  nsMIMEInfoHandleAction action = nsIMIMEInfo::saveToDisk;
+  nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
   mMimeInfo->GetPreferredAction(&action);
   if (mTempFile &&
       (!mReceivedDispositionInfo || action != nsIMIMEInfo::saveToDisk))
   {
     mTempFile->Remove(PR_FALSE);
     mTempFile = nsnull;
   }
 
@@ -2743,25 +2827,25 @@ NS_IMETHODIMP nsExternalHelperAppService
   LOG(("OS gave back 0x%p - found: %i\n", *_retval, found));
   // If we got no mimeinfo, something went wrong. Probably lack of memory.
   if (!*_retval)
     return NS_ERROR_OUT_OF_MEMORY;
 
   // (2) Now, let's see if we can find something in our datasource
   // This will not overwrite the OS information that interests us
   // (i.e. default application, default app. description)
-  nsresult rv = GetMIMEInfoForMimeTypeFromDS(typeToUse, *_retval);
+  nsresult rv = FillMIMEInfoForMimeTypeFromDS(typeToUse, *_retval);
   found = found || NS_SUCCEEDED(rv);
 
   LOG(("Data source: Via type: retval 0x%08x\n", rv));
 
   if (!found || NS_FAILED(rv)) {
     // No type match, try extension match
     if (!aFileExt.IsEmpty()) {
-      rv = GetMIMEInfoForExtensionFromDS(aFileExt, *_retval);
+      rv = FillMIMEInfoForExtensionFromDS(aFileExt, *_retval);
       LOG(("Data source: Via ext: retval 0x%08x\n", rv));
       found = found || NS_SUCCEEDED(rv);
     }
   }
 
   // (3) No match yet. Ask extras.
   if (!found) {
     rv = NS_ERROR_FAILURE;
@@ -2770,21 +2854,21 @@ NS_IMETHODIMP nsExternalHelperAppService
      * extension issues caused by the fix for bug 116938.  See bug
      * 120327, comment 271 for why this is needed.  Not even sure we
      * want to remove this once we have fixed all this stuff to work
      * right; any info we get from extras on this type is pretty much
      * useless....
      */
     if (!typeToUse.Equals(APPLICATION_OCTET_STREAM, nsCaseInsensitiveCStringComparator()))
 #endif
-      rv = GetMIMEInfoForMimeTypeFromExtras(typeToUse, *_retval);
+      rv = FillMIMEInfoForMimeTypeFromExtras(typeToUse, *_retval);
     LOG(("Searched extras (by type), rv 0x%08X\n", rv));
     // If that didn't work out, try file extension from extras
     if (NS_FAILED(rv) && !aFileExt.IsEmpty()) {
-      rv = GetMIMEInfoForExtensionFromExtras(aFileExt, *_retval);
+      rv = FillMIMEInfoForExtensionFromExtras(aFileExt, *_retval);
       LOG(("Searched extras (by ext), rv 0x%08X\n", rv));
     }
   }
 
   // Finally, check if we got a file extension and if yes, if it is an
   // extension on the mimeinfo, in which case we want it to be the primary one
   if (!aFileExt.IsEmpty()) {
     PRBool matches = PR_FALSE;
@@ -2982,17 +3066,18 @@ NS_IMETHODIMP nsExternalHelperAppService
 #endif
 
   // Windows, unix and mac when no type match occured.   
   if (fileExt.IsEmpty())
     return NS_ERROR_FAILURE;    
   return GetTypeFromExtension(fileExt, aContentType);
 }
 
-nsresult nsExternalHelperAppService::GetMIMEInfoForMimeTypeFromExtras(const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo )
+nsresult nsExternalHelperAppService::FillMIMEInfoForMimeTypeFromExtras(
+  const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo)
 {
   NS_ENSURE_ARG( aMIMEInfo );
 
   NS_ENSURE_ARG( !aContentType.IsEmpty() );
 
   // Look for default entry with matching mime type.
   nsCAutoString MIMEType(aContentType);
   ToLowerCase(MIMEType);
@@ -3009,23 +3094,24 @@ nsresult nsExternalHelperAppService::Get
 
           return NS_OK;
       }
   }
 
   return NS_ERROR_NOT_AVAILABLE;
 }
 
-nsresult nsExternalHelperAppService::GetMIMEInfoForExtensionFromExtras(const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo)
+nsresult nsExternalHelperAppService::FillMIMEInfoForExtensionFromExtras(
+  const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo)
 {
   nsCAutoString type;
   PRBool found = GetTypeFromExtras(aExtension, type);
   if (!found)
     return NS_ERROR_NOT_AVAILABLE;
-  return GetMIMEInfoForMimeTypeFromExtras(type, aMIMEInfo);
+  return FillMIMEInfoForMimeTypeFromExtras(type, aMIMEInfo);
 }
 
 PRBool nsExternalHelperAppService::GetTypeFromExtras(const nsACString& aExtension, nsACString& aMIMEType)
 {
   NS_ASSERTION(!aExtension.IsEmpty(), "Empty aExtension parameter!");
 
   // Look for default entry with matching extension.
   nsDependentCString::const_iterator start, end, iter;
--- a/uriloader/exthandler/nsExternalHelperAppService.h
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -116,35 +116,61 @@ public:
 
   /**
    * Initializes internal state. Will be called automatically when
    * this service is first instantiated.
    */
   NS_HIDDEN_(nsresult) Init();
  
   /**
-   * Given a content type, look up the user override information to see if
-   * we have a mime info object representing this content type. The user
-   * over ride information is contained in a in memory data source.
+   * Given an existing MIME info object and a MIME type, fill in any user
+   * override info from the in-memory data source.
+   *
+   * @param aContentType  The MIME content-type 
+   * @param aMIMEInfo     The mime info to fill with the information
+   */
+  NS_HIDDEN_(nsresult) FillMIMEInfoForMimeTypeFromDS(
+    const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo);
+
+  /**
+   * Given an existing protocol info object and a protocol scheme, fill in
+   * any user override info from the in-memory data source.
+   *
+   * @param aScheme   The protocol scheme
    * @param aMIMEInfo The mime info to fill with the information
    */
-  NS_HIDDEN_(nsresult) GetMIMEInfoForMimeTypeFromDS(const nsACString& aContentType,
-                                                    nsIMIMEInfo * aMIMEInfo);
-  
+  NS_HIDDEN_(nsresult) FillProtoInfoForSchemeFromDS(
+    const nsACString& aScheme, nsIHandlerInfo * aMIMEInfo);
+
+  /**
+   * Fill in the generic handler info stuff; called by Fill*InfoFor*FromDS.
+   * 
+   * @param aTypeNodeResource  RDF resource representing the top level scheme
+   *                           or MIME-type node in the graph
+   * @param aType              content-type or scheme name 
+   * @param aRDFService        the RDF service
+   * @param aTypeNodePrefix    One of NC_{CONTENT,SCHEME}_NODE_PREFIX
+   * @param aHandlerInfo       object to be filled in
+   */
+  NS_HIDDEN_(nsresult) FillHandlerInfoForTypeFromDS(
+    nsIRDFResource *aTypeNodeResource, const nsCAutoString& aType,
+    nsIRDFService *aRDFService, const char *aTypeNodePrefix, 
+    nsIHandlerInfo * aHandlerInfo);
+    
   /**
    * Given an extension, look up the user override information to see if we
    * have a mime info object representing this extension. The user over ride
    * information is contained in an in-memory data source.
    *
    * Does not change the MIME Type of the MIME Info.
    *
    * @param aMIMEInfo The mime info to fill with the information
    */
-  NS_HIDDEN_(nsresult) GetMIMEInfoForExtensionFromDS(const nsACString& aFileExtension,
-                                                     nsIMIMEInfo * aMIMEInfo);
+  NS_HIDDEN_(nsresult) FillMIMEInfoForExtensionFromDS(
+    const nsACString& aFileExtension, nsIMIMEInfo * aMIMEInfo);
 
   /**
    * Looks up the MIME Type for a given extension in the RDF Datasource.
    * @param aExtension The extension to look for
    * @param aType [out] The type, if found
    * @return PR_TRUE if found, PR_FALSE otherwise
    */
   NS_HIDDEN_(PRBool) GetTypeFromDS(const nsACString& aFileExtension,
@@ -191,18 +217,18 @@ public:
    * mimeTypes.rdf data source
    */
   NS_HIDDEN_(PRBool) MIMETypeIsInDataSource(const char * aContentType);
 
   /**
    * Return the URI template for any configured web handler.  This will
    * probably be replaced by something on nsIWebContentConverterService soon. 
    */
-  static NS_HIDDEN_(nsresult) GetWebProtocolHandlerURITemplate(const nsACString &aScheme,
-                                                               nsACString &aUriTemplate);
+  NS_HIDDEN_(nsresult) GetProtocolHandlerInfo(const nsACString &aScheme,
+                                              nsIHandlerInfo **aHandlerInfo);
 
   virtual NS_HIDDEN_(nsresult) OSProtocolHandlerExists(const char *aScheme,
                                                        PRBool *aExists) = 0;
 
 protected:
   /**
    * Pointer to the datasource that contains the user override information.
    * @see InitDataSource
@@ -214,39 +240,41 @@ protected:
   nsCOMPtr<nsIRDFResource> kNC_Value;
   nsCOMPtr<nsIRDFResource> kNC_FileExtensions;
   nsCOMPtr<nsIRDFResource> kNC_Path;
   nsCOMPtr<nsIRDFResource> kNC_UseSystemDefault;
   nsCOMPtr<nsIRDFResource> kNC_SaveToDisk;
   nsCOMPtr<nsIRDFResource> kNC_AlwaysAsk;
   nsCOMPtr<nsIRDFResource> kNC_HandleInternal;
   nsCOMPtr<nsIRDFResource> kNC_PrettyName;
+  nsCOMPtr<nsIRDFResource> kNC_UriTemplate;
 #endif
 
   /**
    * Whether mOverRideDataSource is initialized
    */
   PRBool mDataSourceInitialized;
 
   /**
    * Helper routines for digesting the data source and filling in a mime info
    * object for a given content type inside that data source.
    * The content type of the MIME Info will not be changed.
    */
 #ifdef MOZ_RDF
-  NS_HIDDEN_(nsresult) FillTopLevelProperties(nsIRDFResource * aContentTypeNodeResource, 
-                                              nsIRDFService * aRDFService,
-                                              nsIMIMEInfo * aMIMEInfo);
+  NS_HIDDEN_(nsresult) FillMIMEExtensionProperties(
+    nsIRDFResource * aContentTypeNodeResource, nsIRDFService * aRDFService,
+    nsIMIMEInfo * aMIMEInfo);
+  
   /**
-   * @see FillTopLevelProperties
+   * @see FillMIMEExtensionProperties
    */
   NS_HIDDEN_(nsresult) FillContentHandlerProperties(const char * aContentType,
-                                                    nsIRDFResource * aContentTypeNodeResource,
+                                                    const char * aNodePrefix,
                                                     nsIRDFService * aRDFService,
-                                                    nsIMIMEInfo * aMIMEInfo);
+                                                    nsIHandlerInfo * aHandler);
 
   /**
    * A small helper function which gets the target for a given source and
    * property. QIs to a literal and returns a CONST ptr to the string value
    * of that target
    */
   NS_HIDDEN_(nsresult) FillLiteralValueFromTarget(nsIRDFResource * aSource,
                                                   nsIRDFResource * aProperty,
@@ -256,28 +284,28 @@ protected:
   /**
    * Searches the "extra" array of MIMEInfo objects for an object
    * with a specific type. If found, it will modify the passed-in
    * MIMEInfo. Otherwise, it will return an error and the MIMEInfo
    * will be untouched.
    * @param aContentType The type to search for.
    * @param aMIMEInfo    [inout] The mime info, if found
    */
-  NS_HIDDEN_(nsresult) GetMIMEInfoForMimeTypeFromExtras(const nsACString& aContentType,
-                                                        nsIMIMEInfo * aMIMEInfo);
+  NS_HIDDEN_(nsresult) FillMIMEInfoForMimeTypeFromExtras(
+    const nsACString& aContentType, nsIMIMEInfo * aMIMEInfo);
   /**
    * Searches the "extra" array of MIMEInfo objects for an object
    * with a specific extension.
    *
    * Does not change the MIME Type of the MIME Info.
    *
-   * @see GetMIMEInfoForMimeTypeFromExtras
+   * @see FillMIMEInfoForMimeTypeFromExtras
    */
-  NS_HIDDEN_(nsresult) GetMIMEInfoForExtensionFromExtras(const nsACString& aExtension,
-                                                         nsIMIMEInfo * aMIMEInfo);
+  NS_HIDDEN_(nsresult) FillMIMEInfoForExtensionFromExtras(
+    const nsACString& aExtension, nsIMIMEInfo * aMIMEInfo);
 
   /**
    * Searches the "extra" array for a MIME type, and gets its extension.
    * @param aExtension The extension to search for
    * @param aMIMEType [out] The found MIME type.
    * @return PR_TRUE if the extension was found, PR_FALSE otherwise.
    */
   NS_HIDDEN_(PRBool) GetTypeFromExtras(const nsACString& aExtension,
--- a/uriloader/exthandler/nsExternalProtocolHandler.cpp
+++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp
@@ -64,50 +64,57 @@
 
 ////////////////////////////////////////////////////////////////////////
 // a stub channel implemenation which will map calls to AsyncRead and OpenInputStream
 // to calls in the OS for loading the url.
 ////////////////////////////////////////////////////////////////////////
 
 class nsExtProtocolChannel : public nsIChannel
 {
-    friend class nsWebProtocolRedirect;
+    friend class nsProtocolRedirect;
 
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSICHANNEL
     NS_DECL_NSIREQUEST
 
     nsExtProtocolChannel();
     virtual ~nsExtProtocolChannel();
 
     nsresult SetURI(nsIURI*);
 
 private:
     nsresult OpenURL();
-
+    void Finish(nsresult aResult);
+    
     nsCOMPtr<nsIURI> mUrl;
     nsCOMPtr<nsIURI> mOriginalURI;
     nsresult mStatus;
     nsLoadFlags mLoadFlags;
-
+    PRBool mIsPending;
+    PRBool mWasOpened;
+    
     nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
     nsCOMPtr<nsILoadGroup> mLoadGroup;
+    nsCOMPtr<nsIStreamListener> mListener;
+    nsCOMPtr<nsISupports> mContext;
 };
 
 NS_IMPL_THREADSAFE_ADDREF(nsExtProtocolChannel)
 NS_IMPL_THREADSAFE_RELEASE(nsExtProtocolChannel)
 
 NS_INTERFACE_MAP_BEGIN(nsExtProtocolChannel)
    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel)
    NS_INTERFACE_MAP_ENTRY(nsIChannel)
    NS_INTERFACE_MAP_ENTRY(nsIRequest)
 NS_INTERFACE_MAP_END_THREADSAFE
 
-nsExtProtocolChannel::nsExtProtocolChannel() : mStatus(NS_OK)
+nsExtProtocolChannel::nsExtProtocolChannel() : mStatus(NS_OK), 
+                                               mIsPending(PR_FALSE),
+                                               mWasOpened(PR_FALSE)
 {
 }
 
 nsExtProtocolChannel::~nsExtProtocolChannel()
 {}
 
 NS_IMETHODIMP nsExtProtocolChannel::GetLoadGroup(nsILoadGroup * *aLoadGroup)
 {
@@ -194,31 +201,54 @@ nsresult nsExtProtocolChannel::OpenURL()
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::Open(nsIInputStream **_retval)
 {
   OpenURL();
   return NS_ERROR_NO_CONTENT; // force caller to abort.
 }
 
-class nsWebProtocolRedirect : public nsRunnable {
+class nsProtocolRedirect : public nsRunnable {
   public:
-    nsWebProtocolRedirect(nsIURI *aURI, const nsACString & aUriTemplate,
-                          nsIStreamListener *aListener, nsISupports *aContext,
-                          nsExtProtocolChannel *aOriginalChannel)
-      : mURI(aURI), mUriTemplate(aUriTemplate), mListener(aListener), 
+    nsProtocolRedirect(nsIURI *aURI, nsIHandlerInfo *aHandlerInfo,
+                       nsIStreamListener *aListener, nsISupports *aContext,
+                       nsExtProtocolChannel *aOriginalChannel)
+      : mURI(aURI), mHandlerInfo(aHandlerInfo), mListener(aListener), 
         mContext(aContext), mOriginalChannel(aOriginalChannel) {}
 
     NS_IMETHOD Run() 
     {
+      // for now, this code path is only take for a web-based protocol handler
+      nsCOMPtr<nsIHandlerApp> handlerApp;
+      nsresult rv = 
+        mHandlerInfo->GetPreferredApplicationHandler(getter_AddRefs(handlerApp));
+      if (NS_FAILED(rv)) {
+        mOriginalChannel->Finish(rv);
+        return NS_OK;
+      }
+
+      nsCOMPtr<nsIWebHandlerApp> webHandlerApp = do_QueryInterface(handlerApp,
+                                                                   &rv);
+      if (NS_FAILED(rv)) {
+        mOriginalChannel->Finish(rv);
+        return NS_OK; 
+      }
+
+      nsCAutoString uriTemplate;
+      rv = webHandlerApp->GetUriTemplate(uriTemplate);
+      if (NS_FAILED(rv)) {
+        mOriginalChannel->Finish(rv);
+        return NS_OK; 
+      }
+            
       // get the URI spec so we can escape it for insertion into the template 
       nsCAutoString uriSpecToHandle;
-      nsresult rv = mURI->GetSpec(uriSpecToHandle);
+      rv = mURI->GetSpec(uriSpecToHandle);
       if (NS_FAILED(rv)) {
-        AbandonOriginalChannel(rv);
+        mOriginalChannel->Finish(rv);
         return NS_OK; 
       }
 
       // XXX need to strip passwd & username from URI to handle, as per the
       // WhatWG HTML5 draft.  nsSimpleURL, which is what we're going to get,
       // can't do this directly.  Ideally, we'd fix nsStandardURL to make it
       // possible to turn off all of its quirks handling, and use that...
 
@@ -230,119 +260,159 @@ class nsWebProtocolRedirect : public nsR
       nsCAutoString escapedUriSpecToHandle;
       NS_EscapeURL(uriSpecToHandle, esc_Minimal | esc_Forced | esc_Colon,
                    escapedUriSpecToHandle);
 
       // Note that this replace all occurrences of %s with the URL to be
       // handled.  The HTML5 draft doesn't prohibit %s from occurring more than
       // once, and if it does, I can't think of any problems that could
       // cause, (though I don't know why anyone would need or want to do it). 
-      mUriTemplate.ReplaceSubstring(NS_LITERAL_CSTRING("%s"),
-                                    escapedUriSpecToHandle);
-  
+      uriTemplate.ReplaceSubstring(NS_LITERAL_CSTRING("%s"),
+                                   escapedUriSpecToHandle);
+
       // convert spec to URI; no original charset needed since there's no way
       // to communicate that information to any handler
       nsCOMPtr<nsIURI> uriToSend;
-      rv = NS_NewURI(getter_AddRefs(uriToSend), mUriTemplate);
+      rv = NS_NewURI(getter_AddRefs(uriToSend), uriTemplate);
       if (NS_FAILED(rv)) {
-        AbandonOriginalChannel(rv);
+        mOriginalChannel->Finish(rv);
         return NS_OK; 
       }
 
       // create a channel
       nsCOMPtr<nsIChannel> newChannel;
       rv = NS_NewChannel(getter_AddRefs(newChannel), uriToSend, nsnull,
                          mOriginalChannel->mLoadGroup,
                          mOriginalChannel->mCallbacks,
-                         mOriginalChannel->mLoadFlags);
+                         mOriginalChannel->mLoadFlags 
+                         | nsIChannel::LOAD_REPLACE);
       if (NS_FAILED(rv)) {
-        AbandonOriginalChannel(rv);
+        mOriginalChannel->Finish(rv);
         return NS_OK; 
       }
 
       nsCOMPtr<nsIChannelEventSink> eventSink;
       NS_QueryNotificationCallbacks(mOriginalChannel->mCallbacks,
                                     mOriginalChannel->mLoadGroup, eventSink);
 
       if (eventSink) {
         // XXX decide on and audit for correct session & global hist behavior 
         rv = eventSink->OnChannelRedirect(mOriginalChannel, newChannel, 
                                           nsIChannelEventSink::REDIRECT_TEMPORARY |
                                           nsIChannelEventSink::REDIRECT_INTERNAL);
         if (NS_FAILED(rv)) {
-          AbandonOriginalChannel(rv);
+          mOriginalChannel->Finish(rv);
           return NS_OK;
         }
       }
 
       rv = newChannel->AsyncOpen(mListener, mContext);
       if (NS_FAILED(rv)) {
-        AbandonOriginalChannel(rv);
+        mOriginalChannel->Finish(rv);
         return NS_OK; 
       }
       
-      mOriginalChannel->mStatus = NS_BINDING_REDIRECTED;
+      mOriginalChannel->Finish(NS_BINDING_REDIRECTED);
       return NS_OK;
     }
 
   private:
     nsCOMPtr<nsIURI> mURI;
-    nsCString mUriTemplate;
+    nsCOMPtr<nsIHandlerInfo> mHandlerInfo;
     nsCOMPtr<nsIStreamListener> mListener;
     nsCOMPtr<nsISupports> mContext;
     nsCOMPtr<nsExtProtocolChannel> mOriginalChannel;
-
-    // necko guarantees that after an AsyncOpen has succeeded, OnStartRequest
-    // and OnStopRequest will get called    
-    void AbandonOriginalChannel(const nsresult aStatus)
-    {
-      mOriginalChannel->mStatus = aStatus;
-      (void)mListener->OnStartRequest(mOriginalChannel, mContext);
-      (void)mListener->OnStopRequest(mOriginalChannel, mContext, aStatus);
-
-      return;
-    }
-     
 };
 
 NS_IMETHODIMP nsExtProtocolChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
 {
-  // check whether the scheme is one that we have a web handler for
+  NS_ENSURE_ARG_POINTER(listener);
+  NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+  NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
+
+  mWasOpened = PR_TRUE;
+  mListener = listener;
+  mContext = ctxt;
+
+  if (!gExtProtSvc) {
+    return NS_ERROR_FAILURE;
+  }
+
   nsCAutoString urlScheme;  
   nsresult rv = mUrl->GetScheme(urlScheme);
-  NS_ENSURE_SUCCESS(rv, rv);
-  
-  nsCOMPtr<nsIExternalProtocolService> extProtService =
-    do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-  
-  nsCAutoString uriTemplate;
-  rv = nsExternalHelperAppService::GetWebProtocolHandlerURITemplate(urlScheme,
-       uriTemplate);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // check whether the scheme is one that we have a web handler for
+  nsCOMPtr<nsIHandlerInfo> handlerInfo;
+  rv = gExtProtSvc->GetProtocolHandlerInfo(urlScheme, 
+                                           getter_AddRefs(handlerInfo));
   if (NS_SUCCEEDED(rv)) {
+    PRInt32 preferredAction;                                           
+    rv = handlerInfo->GetPreferredAction(&preferredAction);
+
+    // for now, anything that triggers a helper app is going to be a web-based
+    // protocol handler, so we use that to decide which path to take...
+    if (preferredAction == nsIHandlerInfo::useHelperApp) {
 
-    // redirecting to the web handler involvegs calling OnChannelRedirect,
-    // which is supposed to happen after AsyncOpen completes, so we do it in an
-    // event
-    nsCOMPtr<nsIRunnable> event = new nsWebProtocolRedirect(mUrl, uriTemplate,
-                                                            listener, ctxt, 
-                                                            this);
-    // We don't check if |event| was successfully created because
-    // |NS_DispatchToCurrentThread| will do that for us.
-    rv = NS_DispatchToCurrentThread(event);
-    if (NS_SUCCEEDED(rv)) {
-      return rv;
+      // redirecting to the web handler involves calling OnChannelRedirect
+      // (which is supposed to happen after AsyncOpen completes) or possibly
+      // opening a dialog, so we do it in an event
+      nsCOMPtr<nsIRunnable> event = new nsProtocolRedirect(mUrl, handlerInfo,
+                                                           listener, ctxt,
+                                                           this);
+
+      // We don't check if |event| was successfully created because
+      // |NS_DispatchToCurrentThread| will do that for us.
+      rv = NS_DispatchToCurrentThread(event);
+      if (NS_SUCCEEDED(rv)) {
+        mIsPending = PR_TRUE;
+
+        // add ourselves to the load group, since this isn't going to finish
+        // immediately
+        if (mLoadGroup)
+          (void)mLoadGroup->AddRequest(this, nsnull);
+
+        return rv;
+      }
     }
   }
-
-  // try for an OS-provided handler
+  
+  // no protocol info found, just fall back on whatever the OS has to offer
   OpenURL();
   return NS_ERROR_NO_CONTENT; // force caller to abort.
 }
 
+/**
+ * Finish out what was started in AsyncOpen.  This can be called in either the
+ * success or the failure case.  
+ *
+ * @param aStatus  used to set the channel's status, and, if this set to 
+ *                 anything other than NS_BINDING_REDIRECTED, OnStartRequest
+ *                 and OnStopRequest will be called, since Necko guarantees
+ *                 this will happen unless the redirect took place.
+ */
+void nsExtProtocolChannel::Finish(nsresult aStatus)
+{
+  mStatus = aStatus;
+
+  if (aStatus != NS_BINDING_REDIRECTED && mListener) {
+    (void)mListener->OnStartRequest(this, mContext);
+    (void)mListener->OnStopRequest(this, mContext, aStatus);
+  }
+  
+  mIsPending = PR_FALSE;
+  
+  if (mLoadGroup) {
+    (void)mLoadGroup->RemoveRequest(this, nsnull, aStatus);
+  }
+  return;
+}
+
 NS_IMETHODIMP nsExtProtocolChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
 {
   *aLoadFlags = mLoadFlags;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
 {
@@ -401,17 +471,17 @@ NS_IMETHODIMP nsExtProtocolChannel::SetO
 
 NS_IMETHODIMP nsExtProtocolChannel::GetName(nsACString &result)
 {
   return mUrl->GetSpec(result);
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::IsPending(PRBool *result)
 {
-  *result = PR_TRUE;
+  *result = mIsPending;
   return NS_OK; 
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::GetStatus(nsresult *status)
 {
   *status = mStatus;
   return NS_OK;
 }
new file mode 100644
--- /dev/null
+++ b/uriloader/exthandler/nsHandlerAppImpl.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * ***** 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 the Mozilla browser.
+ *
+ * The Initial Developer of the Original Code is 
+ * the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Dan Mosedale <dmose@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#include "nsHandlerAppImpl.h"
+
+// XXX why does nsMIMEInfoImpl have a threadsafe nsISupports?  do we need one 
+// here too?
+
+NS_IMPL_ISUPPORTS1(nsHandlerAppBase, nsIHandlerApp)
+
+/* AString name; */
+NS_IMETHODIMP nsHandlerAppBase::GetName(nsAString & aName)
+{
+  aName.Assign(mName);
+  
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsHandlerAppBase::SetName(const nsAString & aName)
+{
+  mName.Assign(aName);
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED1(nsLocalHandlerApp, nsHandlerAppBase, nsILocalHandlerApp)
+
+NS_IMETHODIMP nsLocalHandlerApp::GetName(nsAString& aName)
+{
+  if (mName.IsEmpty() && mExecutable) {
+    // Don't want to cache this, just in case someone resets the app
+    // without changing the description....
+    mExecutable->GetLeafName(aName);
+  } else {
+    aName.Assign(mName);
+  }
+  
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalHandlerApp::GetExecutable(nsIFile **aExecutable)
+{
+  NS_IF_ADDREF(*aExecutable = mExecutable);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalHandlerApp::SetExecutable(nsIFile *aExecutable)
+{
+  mExecutable = aExecutable;
+  return NS_OK;
+}
+
+
+NS_IMPL_ISUPPORTS_INHERITED1(nsWebHandlerApp, nsHandlerAppBase,
+                             nsIWebHandlerApp)
+
+
+NS_IMETHODIMP nsWebHandlerApp::GetUriTemplate(nsACString &aUriTemplate)
+{
+  aUriTemplate.Assign(mUriTemplate);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsWebHandlerApp::SetUriTemplate(const nsACString &aUriTemplate)
+{
+  mUriTemplate.Assign(aUriTemplate);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/uriloader/exthandler/nsHandlerAppImpl.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:expandtab:shiftwidth=2:tabstop=2:cin:
+ * ***** 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 the Mozilla browser.
+ *
+ * The Initial Developer of the Original Code is 
+ * the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Dan Mosedale <dmose@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of 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 ***** */
+
+#ifndef __nshandlerappimpl_h__
+#define __nshandlerappimpl_h__
+
+#include "nsString.h"
+#include "nsIMIMEInfo.h"
+#include "nsIFile.h"
+
+class nsHandlerAppBase : public nsIHandlerApp
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIHANDLERAPP
+
+  nsHandlerAppBase() NS_HIDDEN {}
+  nsHandlerAppBase(const PRUnichar *aName) NS_HIDDEN  { mName.Assign(aName); };
+  nsHandlerAppBase(const nsAString & aName) NS_HIDDEN  { mName.Assign(aName); };
+  virtual ~nsHandlerAppBase() {};
+
+protected:
+  nsString mName;
+};
+
+class nsLocalHandlerApp : public nsHandlerAppBase, public nsILocalHandlerApp
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSILOCALHANDLERAPP
+  
+  nsLocalHandlerApp() {};
+
+  nsLocalHandlerApp(const PRUnichar *aName, nsIFile *aExecutable) 
+    : nsHandlerAppBase(aName), mExecutable(aExecutable) {}
+
+  nsLocalHandlerApp(const nsAString & aName, nsIFile *aExecutable) 
+    : nsHandlerAppBase(aName), mExecutable(aExecutable) {}
+
+  virtual ~nsLocalHandlerApp() {};
+
+  // overriding to keep old caching behavior (that a useful name is returned
+  // even if none was given to the constructor)
+  NS_IMETHOD GetName(nsAString & aName);
+    
+protected: 
+  nsCOMPtr<nsIFile> mExecutable;
+};
+
+class nsWebHandlerApp : public nsHandlerAppBase, public nsIWebHandlerApp
+{
+  public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIWEBHANDLERAPP
+
+  nsWebHandlerApp(const PRUnichar *aName, const nsACString &aUriTemplate)
+    : nsHandlerAppBase(aName), mUriTemplate(aUriTemplate) { }
+
+  virtual ~nsWebHandlerApp() {};
+
+  protected:
+  nsCString mUriTemplate;
+      
+};
+
+#endif //  __nshandlerappimpl_h__
--- a/uriloader/exthandler/nsHelperAppRDF.h
+++ b/uriloader/exthandler/nsHelperAppRDF.h
@@ -52,25 +52,31 @@
 #define NC_RDF_EDITABLE   			NC_NAMESPACE_URI"editable"
 #define NC_RDF_LARGEICON				NC_NAMESPACE_URI"largeIcon"
 #define NC_RDF_SMALLICON				NC_NAMESPACE_URI"smallIcon"
 #define NC_RDF_HANDLER				  NC_NAMESPACE_URI"handler"
 #define NC_RDF_FILEEXTENSIONS	  NC_NAMESPACE_URI"fileExtensions"
 #define NC_RDF_CHILD            NC_NAMESPACE_URI"child"
 #define NC_RDF_ROOT 			      "NC:HelperAppRoot"
 #define NC_CONTENT_NODE_PREFIX  "urn:mimetype:"
-#define NC_CONTENT_NODE_HANDLER_PREFIX "urn:mimetype:handler:"
-#define NC_CONTENT_NODE_EXTERNALAPP_PREFIX "urn:mimetype:externalApplication:"
+#define NC_HANDLER_SUFFIX "handler:"
+#define NC_EXTERNALAPP_SUFFIX "externalApplication:"
+
+// for URI schemes.  We re-use NC_RDF_HANDLER as an arc from these nodes.
+#define NC_SCHEME_NODE_PREFIX "urn:scheme:"
+#define NS_RDF_PROTOCOLSCHEMES NC_NAMESPACE_URI"Protocol-Schemes"
 
 // File Extensions have file extension properties....
 #define NC_RDF_FILEEXTENSION    NC_NAMESPACE_URI"fileExtension"
 
 // handler properties
 #define NC_RDF_SAVETODISK				    NC_NAMESPACE_URI"saveToDisk"
 #define NC_RDF_USESYSTEMDEFAULT     NC_NAMESPACE_URI"useSystemDefault"
 #define NC_RDF_HANDLEINTERNAL       NC_NAMESPACE_URI"handleInternal"
 #define NC_RDF_ALWAYSASK            NC_NAMESPACE_URI"alwaysAsk"
 #define NC_RDF_EXTERNALAPPLICATION  NC_NAMESPACE_URI"externalApplication"
 
 // external applications properties....
 #define NC_RDF_PRETTYNAME 			    NC_NAMESPACE_URI"prettyName"
+// for local apps, we'll have the path (but not uriTemplate)
 #define NC_RDF_PATH 			          NC_NAMESPACE_URI"path"
-
+// for web apps, we'll have the uriTemplate (but not path)
+#define NC_RDF_URITEMPLATE NC_NAMESPACE_URI"uriTemplate"  
--- a/uriloader/exthandler/nsMIMEInfoImpl.cpp
+++ b/uriloader/exthandler/nsMIMEInfoImpl.cpp
@@ -38,17 +38,17 @@
 
 #include "nsMIMEInfoImpl.h"
 #include "nsXPIDLString.h"
 #include "nsReadableUtils.h"
 #include "nsStringEnumerator.h"
 #include "nsIProcess.h"
 
 // nsISupports methods
-NS_IMPL_THREADSAFE_ISUPPORTS1(nsMIMEInfoBase, nsIMIMEInfo)
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsMIMEInfoBase, nsIMIMEInfo, nsIHandlerInfo)
 
 // nsMIMEInfoImpl methods
 nsMIMEInfoBase::nsMIMEInfoBase(const char *aMIMEType) :
     mMacType(0),
     mMacCreator(0),
     mMIMEType(aMIMEType),
     mPreferredAction(nsIMIMEInfo::saveToDisk),
     mAlwaysAskBeforeHandling(PR_TRUE)
@@ -221,67 +221,46 @@ nsMIMEInfoBase::SetFileExtensions(const 
         extList.Cut(0, breakLocation+1 );
     }
     if ( !extList.IsEmpty() )
         mExtensions.AppendCString( extList );
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsMIMEInfoBase::GetApplicationDescription(nsAString& aApplicationDescription)
-{
-  if (mPreferredAppDescription.IsEmpty() && mPreferredApplication) {
-    // Don't want to cache this, just in case someone resets the app
-    // without changing the description....
-    mPreferredApplication->GetLeafName(aApplicationDescription);
-  } else {
-    aApplicationDescription = mPreferredAppDescription;
-  }
-  
-  return NS_OK;
-}
- 
-NS_IMETHODIMP
-nsMIMEInfoBase::SetApplicationDescription(const nsAString& aApplicationDescription)
-{
-  mPreferredAppDescription = aApplicationDescription;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsMIMEInfoBase::GetDefaultDescription(nsAString& aDefaultDescription)
 {
   aDefaultDescription = mDefaultAppDescription;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsMIMEInfoBase::GetPreferredApplicationHandler(nsIFile ** aPreferredAppHandler)
+nsMIMEInfoBase::GetPreferredApplicationHandler(nsIHandlerApp ** aPreferredAppHandler)
 {
   *aPreferredAppHandler = mPreferredApplication;
   NS_IF_ADDREF(*aPreferredAppHandler);
   return NS_OK;
 }
  
 NS_IMETHODIMP
-nsMIMEInfoBase::SetPreferredApplicationHandler(nsIFile * aPreferredAppHandler)
+nsMIMEInfoBase::SetPreferredApplicationHandler(nsIHandlerApp * aPreferredAppHandler)
 {
   mPreferredApplication = aPreferredAppHandler;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsMIMEInfoBase::GetPreferredAction(nsMIMEInfoHandleAction * aPreferredAction)
+nsMIMEInfoBase::GetPreferredAction(nsHandlerInfoAction * aPreferredAction)
 {
   *aPreferredAction = mPreferredAction;
   return NS_OK;
 }
  
 NS_IMETHODIMP
-nsMIMEInfoBase::SetPreferredAction(nsMIMEInfoHandleAction aPreferredAction)
+nsMIMEInfoBase::SetPreferredAction(nsHandlerInfoAction aPreferredAction)
 {
   mPreferredAction = aPreferredAction;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMIMEInfoBase::GetAlwaysAskBeforeHandling(PRBool * aAlwaysAsk)
 {
@@ -299,17 +278,26 @@ nsMIMEInfoBase::SetAlwaysAskBeforeHandli
 
 NS_IMETHODIMP
 nsMIMEInfoBase::LaunchWithFile(nsIFile* aFile)
 {
   if (mPreferredAction == useHelperApp) {
     if (!mPreferredApplication)
       return NS_ERROR_FILE_NOT_FOUND;
 
-    return LaunchWithIProcess(mPreferredApplication, aFile);
+    nsCOMPtr<nsILocalHandlerApp> localHandler;
+    nsresult rv;
+    localHandler = do_QueryInterface(mPreferredApplication, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+        
+    nsCOMPtr<nsIFile> executable;
+    rv = localHandler->GetExecutable(getter_AddRefs(executable));
+    NS_ENSURE_SUCCESS(rv, rv);
+    
+    return LaunchWithIProcess(executable, aFile);
   }
   else if (mPreferredAction == useSystemDefault) {
     return LaunchDefaultWithFile(aFile);
   }
 
   return NS_ERROR_INVALID_ARG;
 }
 
--- a/uriloader/exthandler/nsMIMEInfoImpl.h
+++ b/uriloader/exthandler/nsMIMEInfoImpl.h
@@ -16,16 +16,17 @@
  * The Original Code is mozilla.org code.
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 1998
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
+ *   Dan Mosedale <dmose@mozilla.org>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of 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
@@ -76,24 +77,22 @@ class nsMIMEInfoBase : public nsIMIMEInf
     NS_IMETHOD GetMIMEType(nsACString & aMIMEType);
     NS_IMETHOD GetDescription(nsAString & aDescription);
     NS_IMETHOD SetDescription(const nsAString & aDescription);
     NS_IMETHOD GetMacType(PRUint32 *aMacType);
     NS_IMETHOD SetMacType(PRUint32 aMacType);
     NS_IMETHOD GetMacCreator(PRUint32 *aMacCreator);
     NS_IMETHOD SetMacCreator(PRUint32 aMacCreator);
     NS_IMETHOD Equals(nsIMIMEInfo *aMIMEInfo, PRBool *_retval);
-    NS_IMETHOD GetPreferredApplicationHandler(nsIFile * *aPreferredApplicationHandler);
-    NS_IMETHOD SetPreferredApplicationHandler(nsIFile * aPreferredApplicationHandler);
-    NS_IMETHOD GetApplicationDescription(nsAString & aApplicationDescription);
-    NS_IMETHOD SetApplicationDescription(const nsAString & aApplicationDescription);
+    NS_IMETHOD GetPreferredApplicationHandler(nsIHandlerApp * *aPreferredApplicationHandler);
+    NS_IMETHOD SetPreferredApplicationHandler(nsIHandlerApp * aPreferredApplicationHandler);
     NS_IMETHOD GetDefaultDescription(nsAString & aDefaultDescription);
     NS_IMETHOD LaunchWithFile(nsIFile *aFile);
-    NS_IMETHOD GetPreferredAction(nsMIMEInfoHandleAction *aPreferredAction);
-    NS_IMETHOD SetPreferredAction(nsMIMEInfoHandleAction aPreferredAction);
+    NS_IMETHOD GetPreferredAction(nsHandlerInfoAction *aPreferredAction);
+    NS_IMETHOD SetPreferredAction(nsHandlerInfoAction aPreferredAction);
     NS_IMETHOD GetAlwaysAskBeforeHandling(PRBool *aAlwaysAskBeforeHandling);
     NS_IMETHOD SetAlwaysAskBeforeHandling(PRBool aAlwaysAskBeforeHandling); 
 
     // nsMIMEInfoBase methods
     nsMIMEInfoBase(const char *aMIMEType = "") NS_HIDDEN;
     nsMIMEInfoBase(const nsACString& aMIMEType) NS_HIDDEN;
     virtual ~nsMIMEInfoBase();        // must be virtual, as the the base class's Release should call the subclass's destructor
 
@@ -138,18 +137,18 @@ class nsMIMEInfoBase : public nsIMIMEInf
      */
     static NS_HIDDEN_(nsresult) LaunchWithIProcess(nsIFile* aApp, nsIFile* aFile);
 
     // member variables
     nsCStringArray         mExtensions; ///< array of file extensions associated w/ this MIME obj
     nsString               mDescription; ///< human readable description
     PRUint32               mMacType, mMacCreator; ///< Mac file type and creator
     nsCString              mMIMEType;
-    nsCOMPtr<nsIFile>      mPreferredApplication; ///< preferred application associated with this type.
-    nsMIMEInfoHandleAction mPreferredAction; ///< preferred action to associate with this type
+    nsCOMPtr<nsIHandlerApp> mPreferredApplication;
+    nsHandlerInfoAction    mPreferredAction; ///< preferred action to associate with this type
     nsString               mPreferredAppDescription;
     nsString               mDefaultAppDescription;
     PRBool                 mAlwaysAskBeforeHandling;
 };
 
 
 /**
  * This is a complete implementation of nsIMIMEInfo, and contains all necessary
--- a/uriloader/exthandler/win/nsMIMEInfoWin.cpp
+++ b/uriloader/exthandler/win/nsMIMEInfoWin.cpp
@@ -106,16 +106,29 @@ static nsresult GetIconURLVariant(nsIFil
   nsCOMPtr<nsIWritableVariant> writable(do_QueryInterface(*_retval));
   writable->SetAsAUTF8String(iconURLSpec);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMIMEInfoWin::GetProperty(const nsAString& aName, nsIVariant* *_retval)
 {
-  nsresult rv = NS_ERROR_FAILURE;
-  if (mDefaultApplication && aName.EqualsLiteral(PROPERTY_DEFAULT_APP_ICON_URL))
+  nsresult rv;
+  if (mDefaultApplication && aName.EqualsLiteral(PROPERTY_DEFAULT_APP_ICON_URL)) {
     rv = GetIconURLVariant(mDefaultApplication, _retval);
-  else if (mPreferredApplication && aName.EqualsLiteral(PROPERTY_CUSTOM_APP_ICON_URL))
-    rv = GetIconURLVariant(mPreferredApplication, _retval);
-  return rv;
+    NS_ENSURE_SUCCESS(rv, rv);    
+  } else if (mPreferredApplication && 
+             aName.EqualsLiteral(PROPERTY_CUSTOM_APP_ICON_URL)) {
+    nsCOMPtr<nsILocalHandlerApp> localHandler =
+      do_QueryInterface(mPreferredApplication, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    
+    nsCOMPtr<nsIFile> executable;
+    rv = localHandler->GetExecutable(getter_AddRefs(executable));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = GetIconURLVariant(executable, _retval);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
 }