Implement backend changes for web-based protocol handlers a la WhatWG HTML5 draft (bug 380415). r=cbiesinger@gmx.at, sr=jonas@sicking.cc
authordmose@mozilla.org
Fri, 25 May 2007 08:17:44 -0700
changeset 1859 897a57fd4d9658b6fd1daaa2c9f9778ddf4b1585
parent 1858 89f46ab3c6ca409f2273895425af4be9c62a43c5
child 1860 27ad0b2617bb831f5d417ac8a3b8fbe30a11d8d4
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerscbiesinger, jonas
bugs380415
milestone1.9a5pre
Implement backend changes for web-based protocol handlers a la WhatWG HTML5 draft (bug 380415). r=cbiesinger@gmx.at, sr=jonas@sicking.cc
uriloader/exthandler/beos/nsOSHelperAppService.cpp
uriloader/exthandler/beos/nsOSHelperAppService.h
uriloader/exthandler/mac/nsOSHelperAppService.cpp
uriloader/exthandler/mac/nsOSHelperAppService.h
uriloader/exthandler/nsExternalHelperAppService.cpp
uriloader/exthandler/nsExternalHelperAppService.h
uriloader/exthandler/nsExternalProtocolHandler.cpp
uriloader/exthandler/nsExternalProtocolHandler.h
uriloader/exthandler/nsIExternalProtocolService.idl
uriloader/exthandler/os2/nsOSHelperAppService.cpp
uriloader/exthandler/os2/nsOSHelperAppService.h
uriloader/exthandler/unix/nsOSHelperAppService.cpp
uriloader/exthandler/unix/nsOSHelperAppService.h
uriloader/exthandler/win/nsOSHelperAppService.cpp
uriloader/exthandler/win/nsOSHelperAppService.h
--- a/uriloader/exthandler/beos/nsOSHelperAppService.cpp
+++ b/uriloader/exthandler/beos/nsOSHelperAppService.cpp
@@ -57,19 +57,19 @@
 
 nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
 {
 }
 
 nsOSHelperAppService::~nsOSHelperAppService()
 {}
 
-NS_IMETHODIMP nsOSHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists)
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists)
 {
-	LOG(("-- nsOSHelperAppService::ExternalProtocolHandlerExists for '%s'\n",
+	LOG(("-- nsOSHelperAppService::OSProtocolHandlerExists for '%s'\n",
 	     aProtocolScheme));
 	// look up the protocol scheme in the MIME database
 	*aHandlerExists = PR_FALSE;
 	if (aProtocolScheme && *aProtocolScheme)
 	{
 		BString protoStr(aProtocolScheme);
 		protoStr.Prepend("application/x-vnd.Be.URL.");
 		BMimeType protocol;
--- a/uriloader/exthandler/beos/nsOSHelperAppService.h
+++ b/uriloader/exthandler/beos/nsOSHelperAppService.h
@@ -51,17 +51,17 @@ class nsOSHelperAppService : public nsEx
 {
 public:
 	nsOSHelperAppService();
 	virtual ~nsOSHelperAppService();
 
 	already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, PRBool *aFound);
 
 	// override nsIExternalProtocolService methods
-	NS_IMETHOD ExternalProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists);
+	nsresult OSProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists);
 	nsresult LoadUriInternal(nsIURI * aURL);
 
 protected:
 	nsresult SetMIMEInfoForType(const char *aMIMEType, nsMIMEInfoBeOS **_retval);
 	nsresult GetMimeInfoFromExtension(const char *aFileExt, nsMIMEInfoBeOS **_retval);
 	nsresult GetMimeInfoFromMIMEType(const char *aMIMEType, nsMIMEInfoBeOS **_retval);
 };
 
--- a/uriloader/exthandler/mac/nsOSHelperAppService.cpp
+++ b/uriloader/exthandler/mac/nsOSHelperAppService.cpp
@@ -69,17 +69,17 @@ extern "C" {
 
 nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
 {
 }
 
 nsOSHelperAppService::~nsOSHelperAppService()
 {}
 
-NS_IMETHODIMP nsOSHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists)
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists)
 {
   // look up the protocol scheme in Internet Config....if we find a match then we have a handler for it...
   *aHandlerExists = PR_FALSE;
   // ask the internet config service to look it up for us...
   nsresult rv = NS_ERROR_FAILURE;
   nsCOMPtr<nsIInternetConfigService> icService (do_GetService(NS_INTERNETCONFIGSERVICE_CONTRACTID));
   if (icService)
   {
--- a/uriloader/exthandler/mac/nsOSHelperAppService.h
+++ b/uriloader/exthandler/mac/nsOSHelperAppService.h
@@ -51,29 +51,30 @@
 
 class nsOSHelperAppService : public nsExternalHelperAppService
 {
 public:
   nsOSHelperAppService();
   virtual ~nsOSHelperAppService();
 
   // override nsIExternalProtocolService methods
-  NS_IMETHOD ExternalProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists);
   NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval);
   nsresult LoadUriInternal(nsIURI * aURL);
   
   // method overrides --> used to hook the mime service into internet config....
   NS_IMETHOD GetFromTypeAndExtension(const nsACString& aType, const nsACString& aFileExt, nsIMIMEInfo ** aMIMEInfo);
   already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, PRBool * aFound);
 
   // GetFileTokenForPath must be implemented by each platform. 
   // platformAppPath --> a platform specific path to an application that we got out of the 
   //                     rdf data source. This can be a mac file spec, a unix path or a windows path depending on the platform
   // aFile --> an nsIFile representation of that platform application path.
   virtual nsresult GetFileTokenForPath(const PRUnichar * platformAppPath, nsIFile ** aFile);
-  
+
+  nsresult OSProtocolHandlerExists(const char * aScheme,
+                                   PRBool * aHandlerExists);
+
 protected:
   // add any mac specific service state here
   void UpdateCreatorInfo(nsIMIMEInfo * aMIMEInfo);
-
 };
 
 #endif // nsOSHelperAppService_h__
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -19,16 +19,17 @@
  * 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>
  *   Bill Law <law@netscape.com>
  *   Christian Biesinger <cbiesinger@web.de>
+ *   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
@@ -137,17 +138,17 @@ static const char NEVER_ASK_FOR_OPEN_FIL
 #ifdef MOZ_RDF
 static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
 #endif
 static NS_DEFINE_CID(kPluginManagerCID, NS_PLUGINMANAGER_CID);
 
 /**
  * Contains a pointer to the helper app service, set in its constructor
  */
-static nsExternalHelperAppService* sSrv;
+nsExternalHelperAppService* gExtProtSvc;
 
 // Helper functions for Content-Disposition headers
 
 /**
  * Given a URI fragment, unescape it
  * @param aFragment The string to unescape
  * @param aURI The URI from which this fragment is taken. Only its character set
  *             will be used.
@@ -487,17 +488,17 @@ NS_IMPL_ISUPPORTS6(
   nsIExternalProtocolService,
   nsIMIMEService,
   nsIObserver,
   nsISupportsWeakReference)
 
 nsExternalHelperAppService::nsExternalHelperAppService()
 : mDataSourceInitialized(PR_FALSE)
 {
-  sSrv = this;
+  gExtProtSvc = this;
 }
 nsresult nsExternalHelperAppService::Init()
 {
   // Add an observer for profile change
   nsresult rv = NS_OK;
   nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -509,17 +510,17 @@ nsresult nsExternalHelperAppService::Ini
   }
 #endif
 
   return obs->AddObserver(this, "profile-before-change", PR_TRUE);
 }
 
 nsExternalHelperAppService::~nsExternalHelperAppService()
 {
-  sSrv = nsnull;
+  gExtProtSvc = nsnull;
 }
 
 nsresult nsExternalHelperAppService::InitDataSource()
 {
 #ifdef MOZ_RDF
   nsresult rv = NS_OK;
 
   // don't re-initialize the data source if we've already done so...
@@ -1046,19 +1047,27 @@ nsresult nsExternalHelperAppService::Get
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////////////////
 // begin external protocol service default implementation...
 //////////////////////////////////////////////////////////////////////////////////////////////////////
 NS_IMETHODIMP nsExternalHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme,
                                                                         PRBool * aHandlerExists)
 {
-  // this method should only be implemented by each OS specific implementation of this service.
-  *aHandlerExists = PR_FALSE;
-  return NS_ERROR_NOT_IMPLEMENTED;
+  // check for web-based handler
+  nsCAutoString uriTemplate;
+  nsresult rv = GetWebProtocolHandlerURITemplate(
+    nsDependentCString(aProtocolScheme), uriTemplate);
+  if (NS_SUCCEEDED(rv)) {
+    *aHandlerExists = PR_TRUE;
+    return NS_OK;
+  }
+
+  // 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.
   *aResult = PR_FALSE;
@@ -1101,18 +1110,18 @@ NS_IMETHODIMP nsExternalHelperAppService
 //  callback to make sure we don't hang someone up.
 
 class nsExternalLoadRequest : public nsRunnable {
   public:
     nsExternalLoadRequest(nsIURI *uri, nsIPrompt *prompt)
       : mURI(uri), mPrompt(prompt) {}
 
     NS_IMETHOD Run() {
-      if (sSrv && sSrv->isExternalLoadOK(mURI, mPrompt))
-        sSrv->LoadUriInternal(mURI);
+      if (gExtProtSvc && gExtProtSvc->isExternalLoadOK(mURI, mPrompt))
+        gExtProtSvc->LoadUriInternal(mURI);
       return NS_OK;
     }
 
   private:
     nsCOMPtr<nsIURI>    mURI;
     nsCOMPtr<nsIPrompt> mPrompt;
 };
 
@@ -1361,16 +1370,52 @@ nsresult nsExternalHelperAppService::Exp
       localFile->Remove(PR_FALSE);
   }
 
   mTemporaryFilesList.Clear();
 
   return NS_OK;
 }
 
+nsresult
+nsExternalHelperAppService::GetWebProtocolHandlerURITemplate(const nsACString &aScheme, 
+                                                             nsACString &aUriTemplate)
+{
+  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
+  // 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?)
+  
+  // 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;
+}
+ 
 // XPCOM profile change observer
 NS_IMETHODIMP
 nsExternalHelperAppService::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData )
 {
   if (!strcmp(aTopic, "profile-before-change")) {
     ExpungeTemporaryFiles();
 #ifdef MOZ_RDF
     nsCOMPtr <nsIRDFRemoteDataSource> flushableDataSource = do_QueryInterface(mOverRideDataSource);
@@ -1426,23 +1471,23 @@ nsExternalAppHandler::nsExternalAppHandl
 
   // replace platform specific path separator and illegal characters to avoid any confusion
   mSuggestedFileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
   mTempFileExtension.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-');
   
   // Make sure extension is correct.
   EnsureSuggestedFileName();
 
-  sSrv->AddRef();
+  gExtProtSvc->AddRef();
 }
 
 nsExternalAppHandler::~nsExternalAppHandler()
 {
-  // Not using NS_RELEASE, since we don't want to set sSrv to NULL
-  sSrv->Release();
+  // Not using NS_RELEASE, since we don't want to set gExtProtSvc to NULL
+  gExtProtSvc->Release();
 }
 
 NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener)
 { 
   // this call back means we've successfully brought up the 
   // progress window so set the appropriate flag, even though
   // aWebProgressListener might be null
   
@@ -1750,20 +1795,19 @@ NS_IMETHODIMP nsExternalAppHandler::OnSt
           PRBool hasMore;
           rv = encEnum->HasMore(&hasMore);
           if (NS_SUCCEEDED(rv) && hasMore)
           {
             nsCAutoString encType;
             rv = encEnum->GetNext(encType);
             if (NS_SUCCEEDED(rv) && !encType.IsEmpty())
             {
-              NS_ASSERTION(sSrv, "Where did the service go?");
-              sSrv->ApplyDecodingForExtension(extension,
-                                              encType,
-                                              &applyConversion);
+              NS_ASSERTION(gExtProtSvc, "Where did the service go?");
+              gExtProtSvc->ApplyDecodingForExtension(extension, encType,
+                                                     &applyConversion);
             }
           }
         }
       }    
     }
 
     encChannel->SetApplyConversion( applyConversion );
   }
@@ -1786,18 +1830,18 @@ NS_IMETHODIMP nsExternalAppHandler::OnSt
   mMimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
   if (alwaysAsk)
   {
     // But we *don't* ask if this mimeInfo didn't come from
     // our mimeTypes.rdf data source and the user has said
     // at some point in the distant past that they don't
     // want to be asked.  The latter fact would have been
     // stored in pref strings back in the old days.
-    NS_ASSERTION(sSrv, "Service gone away!?");
-    if (!sSrv->MIMETypeIsInDataSource(MIMEType.get()))
+    NS_ASSERTION(gExtProtSvc, "Service gone away!?");
+    if (!gExtProtSvc->MIMETypeIsInDataSource(MIMEType.get()))
     {
       if (!GetNeverAskFlagFromPref(NEVER_ASK_FOR_SAVE_TO_DISK_PREF, MIMEType.get()))
       {
         // Don't need to ask after all.
         alwaysAsk = PR_FALSE;
         // Make sure action matches pref (save to disk).
         mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
       }
@@ -2143,17 +2187,17 @@ nsresult nsExternalAppHandler::ExecuteDe
     else // Various unknown actions go here too
     {
       // XXX Put progress dialog in barber-pole mode
       //     and change text to say "Copying from:".
       rv = MoveFile(mFinalFileDestination);
       if (NS_SUCCEEDED(rv) && action == nsIMIMEInfo::saveToDisk)
       {
         nsCOMPtr<nsILocalFile> destfile(do_QueryInterface(mFinalFileDestination));
-        sSrv->FixFilePermissions(destfile);
+        gExtProtSvc->FixFilePermissions(destfile);
       }
     }
     
     // Notify dialog that download is complete.
     // By waiting till this point, it ensures that the progress dialog doesn't indicate
     // success until we're really done.
     if(mWebProgressListener)
     {
@@ -2449,18 +2493,18 @@ nsresult nsExternalAppHandler::OpenWithA
           // app exit - they don't like it - but we'll continue to do this on
           // other platforms for now.
         deleteTempFileOnExit = PR_TRUE;
 #else
         deleteTempFileOnExit = PR_FALSE;
 #endif
       }
       if (deleteTempFileOnExit) {
-        NS_ASSERTION(sSrv, "Service gone away!?");
-        sSrv->DeleteTemporaryFileOnExit(mFinalFileDestination);
+        NS_ASSERTION(gExtProtSvc, "Service gone away!?");
+        gExtProtSvc->DeleteTemporaryFileOnExit(mFinalFileDestination);
       }
     }
   }
 
   return rv;
 }
 
 // LaunchWithApplication should only be called by the helper app dialog which allows
--- a/uriloader/exthandler/nsExternalHelperAppService.h
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -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>
  *   Christian Biesinger <cbiesinger@web.de>
+ *   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
@@ -186,16 +187,26 @@ public:
                                        nsIFile ** aFile);
 
   /**
    * Helper routine used to test whether a given mime type is in our
    * 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);
+
+  virtual NS_HIDDEN_(nsresult) OSProtocolHandlerExists(const char *aScheme,
+                                                       PRBool *aExists) = 0;
+
 protected:
   /**
    * Pointer to the datasource that contains the user override information.
    * @see InitDataSource
    */
 #ifdef MOZ_RDF
   nsCOMPtr<nsIRDFDataSource> mOverRideDataSource;
 
@@ -507,9 +518,11 @@ protected:
 
   /**
    * The request that's being loaded. Not used after OnStopRequest, so a weak
    * reference suffices. Initialized in OnStartRequest.
    */
   nsIRequest*  mRequest;
 };
 
+extern NS_HIDDEN_(nsExternalHelperAppService*) gExtProtSvc;
+
 #endif // nsExternalHelperAppService_h__
--- a/uriloader/exthandler/nsExternalProtocolHandler.cpp
+++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp
@@ -18,16 +18,17 @@
  *
  * 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):
  *   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
@@ -47,45 +48,51 @@
 #include "nsIServiceManager.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIStringBundle.h"
 #include "nsIPrefService.h"
 #include "nsIPrompt.h"
 #include "nsNetUtil.h"
+#include "nsIChannelEventSink.h"
+#include "nsThreadUtils.h"
+#include "nsEscape.h"
+#include "nsExternalHelperAppService.h"
 
 // used to dispatch urls to default protocol handlers
 #include "nsCExternalHandlerService.h"
 #include "nsIExternalProtocolService.h"
 
 ////////////////////////////////////////////////////////////////////////
 // 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;
+
 public:
-
     NS_DECL_ISUPPORTS
     NS_DECL_NSICHANNEL
     NS_DECL_NSIREQUEST
 
     nsExtProtocolChannel();
     virtual ~nsExtProtocolChannel();
 
     nsresult SetURI(nsIURI*);
 
 private:
     nsresult OpenURL();
 
     nsCOMPtr<nsIURI> mUrl;
     nsCOMPtr<nsIURI> mOriginalURI;
     nsresult mStatus;
+    nsLoadFlags mLoadFlags;
 
     nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
     nsCOMPtr<nsILoadGroup> mLoadGroup;
 };
 
 NS_IMPL_THREADSAFE_ADDREF(nsExtProtocolChannel)
 NS_IMPL_THREADSAFE_RELEASE(nsExtProtocolChannel)
 
@@ -153,17 +160,17 @@ NS_IMETHODIMP nsExtProtocolChannel::GetU
   return NS_OK; 
 }
  
 nsresult nsExtProtocolChannel::SetURI(nsIURI* aURI)
 {
   mUrl = aURI;
   return NS_OK; 
 }
- 
+
 nsresult nsExtProtocolChannel::OpenURL()
 {
   nsresult rv = NS_ERROR_FAILURE;
   nsCOMPtr<nsIExternalProtocolService> extProtService (do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID));
 
   if (extProtService)
   {
 #ifdef DEBUG
@@ -187,30 +194,162 @@ nsresult nsExtProtocolChannel::OpenURL()
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::Open(nsIInputStream **_retval)
 {
   OpenURL();
   return NS_ERROR_NO_CONTENT; // force caller to abort.
 }
 
+class nsWebProtocolRedirect : public nsRunnable {
+  public:
+    nsWebProtocolRedirect(nsIURI *aURI, const nsACString & aUriTemplate,
+                          nsIStreamListener *aListener, nsISupports *aContext,
+                          nsExtProtocolChannel *aOriginalChannel)
+      : mURI(aURI), mUriTemplate(aUriTemplate), mListener(aListener), 
+        mContext(aContext), mOriginalChannel(aOriginalChannel) {}
+
+    NS_IMETHOD Run() 
+    {
+      // get the URI spec so we can escape it for insertion into the template 
+      nsCAutoString uriSpecToHandle;
+      nsresult rv = mURI->GetSpec(uriSpecToHandle);
+      if (NS_FAILED(rv)) {
+        AbandonOriginalChannel(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...
+
+      // XXX this doesn't exactly match how the HTML5 draft is requesting us to
+      // escape; at the very least, it should be escaping @ signs, and there
+      // may well be more issues.  However, this code will probably be thrown
+      // out when we do the front-end work, as we'll be using a refactored 
+      // nsIWebContentConverterInfo to do this work for us
+      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);
+  
+      // 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);
+      if (NS_FAILED(rv)) {
+        AbandonOriginalChannel(rv);
+        return NS_OK; 
+      }
+
+      // create a channel
+      nsCOMPtr<nsIChannel> newChannel;
+      rv = NS_NewChannel(getter_AddRefs(newChannel), uriToSend, nsnull,
+                         mOriginalChannel->mLoadGroup,
+                         mOriginalChannel->mCallbacks,
+                         mOriginalChannel->mLoadFlags);
+      if (NS_FAILED(rv)) {
+        AbandonOriginalChannel(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);
+          return NS_OK;
+        }
+      }
+
+      rv = newChannel->AsyncOpen(mListener, mContext);
+      if (NS_FAILED(rv)) {
+        AbandonOriginalChannel(rv);
+        return NS_OK; 
+      }
+      
+      mOriginalChannel->mStatus = NS_BINDING_REDIRECTED;
+      return NS_OK;
+    }
+
+  private:
+    nsCOMPtr<nsIURI> mURI;
+    nsCString mUriTemplate;
+    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
+  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_SUCCEEDED(rv)) {
+
+    // 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);
+    rv = NS_DispatchToCurrentThread(event);
+    if (NS_SUCCEEDED(rv)) {
+      return rv;
+    }
+  }
+
+  // try for an OS-provided handler
   OpenURL();
   return NS_ERROR_NO_CONTENT; // force caller to abort.
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
 {
-  *aLoadFlags = 0;
+  *aLoadFlags = mLoadFlags;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
 {
+  mLoadFlags = aLoadFlags;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::GetContentType(nsACString &aContentType)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
@@ -255,18 +394,17 @@ NS_IMETHODIMP nsExtProtocolChannel::SetO
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // From nsIRequest
 ////////////////////////////////////////////////////////////////////////////////
 
 NS_IMETHODIMP nsExtProtocolChannel::GetName(nsACString &result)
 {
-  NS_NOTREACHED("nsExtProtocolChannel::GetName");
-  return NS_ERROR_NOT_IMPLEMENTED;
+  return mUrl->GetSpec(result);
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::IsPending(PRBool *result)
 {
   *result = PR_TRUE;
   return NS_OK; 
 }
 
@@ -296,17 +434,16 @@ NS_IMETHODIMP nsExtProtocolChannel::Resu
 
 ///////////////////////////////////////////////////////////////////////
 // the default protocol handler implementation
 //////////////////////////////////////////////////////////////////////
 
 nsExternalProtocolHandler::nsExternalProtocolHandler()
 {
   m_schemeName = "default";
-  m_extProtService = do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
 }
 
 
 nsExternalProtocolHandler::~nsExternalProtocolHandler()
 {}
 
 NS_IMPL_THREADSAFE_ADDREF(nsExternalProtocolHandler)
 NS_IMPL_THREADSAFE_RELEASE(nsExternalProtocolHandler)
@@ -333,25 +470,25 @@ NS_IMETHODIMP nsExternalProtocolHandler:
 NS_IMETHODIMP 
 nsExternalProtocolHandler::AllowPort(PRInt32 port, const char *scheme, PRBool *_retval)
 {
     // don't override anything.  
     *_retval = PR_FALSE;
     return NS_OK;
 }
 // returns TRUE if the OS can handle this protocol scheme and false otherwise.
-PRBool nsExternalProtocolHandler::HaveProtocolHandler(nsIURI * aURI)
+PRBool nsExternalProtocolHandler::HaveOSProtocolHandler(nsIURI * aURI)
 {
   PRBool haveHandler = PR_FALSE;
   if (aURI)
   {
     nsCAutoString scheme;
     aURI->GetScheme(scheme);
-    if (m_extProtService)
-      m_extProtService->ExternalProtocolHandlerExists(scheme.get(), &haveHandler);
+    if (gExtProtSvc)
+      gExtProtSvc->OSProtocolHandlerExists(scheme.get(), &haveHandler);
   }
 
   return haveHandler;
 }
 
 NS_IMETHODIMP nsExternalProtocolHandler::GetProtocolFlags(PRUint32 *aUritype)
 {
     // Make it norelative since it is a simple uri
@@ -375,18 +512,18 @@ NS_IMETHODIMP nsExternalProtocolHandler:
   NS_ADDREF(*_retval = uri);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsExternalProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
 {
   // only try to return a channel if we have a protocol handler for the url
 
-  PRBool haveHandler = HaveProtocolHandler(aURI);
-  if (haveHandler)
+  PRBool haveOSHandler = HaveOSProtocolHandler(aURI);
+  if (haveOSHandler)
   {
     nsCOMPtr<nsIChannel> channel;
     NS_NEWXPCOM(channel, nsExtProtocolChannel);
     if (!channel) return NS_ERROR_OUT_OF_MEMORY;
 
     ((nsExtProtocolChannel*) channel.get())->SetURI(aURI);
     channel->SetOriginalURI(aURI);
 
@@ -401,18 +538,19 @@ NS_IMETHODIMP nsExternalProtocolHandler:
   return NS_ERROR_UNKNOWN_PROTOCOL;
 }
 
 ///////////////////////////////////////////////////////////////////////
 // External protocol handler interface implementation
 //////////////////////////////////////////////////////////////////////
 NS_IMETHODIMP nsExternalProtocolHandler::ExternalAppExistsForScheme(const nsACString& aScheme, PRBool *_retval)
 {
-  if (m_extProtService)
-    return m_extProtService->ExternalProtocolHandlerExists(PromiseFlatCString(aScheme).get(), _retval);
+  if (gExtProtSvc)
+    return gExtProtSvc->ExternalProtocolHandlerExists(
+      PromiseFlatCString(aScheme).get(), _retval);
 
   // In case we don't have external protocol service.
   *_retval = PR_FALSE;
   return NS_OK;
 }
 
 nsBlockedExternalProtocolHandler::nsBlockedExternalProtocolHandler()
 {
--- a/uriloader/exthandler/nsExternalProtocolHandler.h
+++ b/uriloader/exthandler/nsExternalProtocolHandler.h
@@ -17,16 +17,17 @@
  *
  * 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):
  *   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
@@ -56,17 +57,17 @@ public:
 	NS_DECL_NSIPROTOCOLHANDLER
 	NS_DECL_NSIEXTERNALPROTOCOLHANDLER
 
 	nsExternalProtocolHandler();
 	~nsExternalProtocolHandler();
 
 protected:
   // helper function
-  PRBool HaveProtocolHandler(nsIURI * aURI);
+  PRBool HaveOSProtocolHandler(nsIURI * aURI);
 	nsCString	m_schemeName;
   nsCOMPtr<nsIExternalProtocolService> m_extProtService;
 };
 
 class nsBlockedExternalProtocolHandler: public nsExternalProtocolHandler
 {
 public:
   nsBlockedExternalProtocolHandler();
--- a/uriloader/exthandler/nsIExternalProtocolService.idl
+++ b/uriloader/exthandler/nsIExternalProtocolService.idl
@@ -40,17 +40,18 @@
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIFile;
 interface nsIPrompt;
 
 /**
  * The external protocol service is used for finding and launching
- * platform specific applications for particular protocols.
+ * web handlers (a la registerProtocolHandler in the HTML5 draft) or 
+ * platform-specific applications for handling particular protocols.
  *
  * You can ask the external protocol service if it has an external
  * handler for a given protocol scheme. And you can ask it to load 
  * the url using the default handler.
  */
 [scriptable, uuid(a49813a4-98b7-4bdb-998c-8bd9704af0c0)]
 interface nsIExternalProtocolService : nsISupports
 {
--- a/uriloader/exthandler/os2/nsOSHelperAppService.cpp
+++ b/uriloader/exthandler/os2/nsOSHelperAppService.cpp
@@ -1235,19 +1235,19 @@ nsOSHelperAppService::GetApplicationAndP
 
   /* application string in INI was empty */
   if (app[0] == '\0')
     return NS_ERROR_FAILURE;
 
   return NS_OK;
 }
 
-NS_IMETHODIMP nsOSHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists)
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists)
 {
-  LOG(("-- nsOSHelperAppService::ExternalProtocolHandlerExists for '%s'\n",
+  LOG(("-- nsOSHelperAppService::OSProtocolHandlerExists for '%s'\n",
        aProtocolScheme));
   *aHandlerExists = PR_FALSE;
 
   /* if applications.protocol is in prefs, then we have an external protocol handler */
   nsresult rv;
   nsCAutoString prefName;
   prefName = NS_LITERAL_CSTRING("applications.") + nsDependentCString(aProtocolScheme);
 
--- a/uriloader/exthandler/os2/nsOSHelperAppService.h
+++ b/uriloader/exthandler/os2/nsOSHelperAppService.h
@@ -59,20 +59,20 @@ public:
   virtual ~nsOSHelperAppService();
 
   // method overrides for mime.types and mime.info look up steps
   already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMimeType,
                                                   const nsACString& aFileExt,
                                                   PRBool     *aFound);
 
   // override nsIExternalProtocolService methods
-  NS_IMETHOD ExternalProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists);
   nsresult LoadUriInternal(nsIURI * aURL);
   NS_IMETHODIMP GetApplicationDescription(const nsACString& aScheme, nsAString& _retval);
 
+  nsresult OSProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists);
 protected:
   already_AddRefed<nsMIMEInfoOS2> GetFromType(const nsCString& aMimeType);
   already_AddRefed<nsMIMEInfoOS2> GetFromExtension(const nsCString& aFileExt);
 
 private:
   nsresult GetApplicationAndParametersFromINI(const nsACString& aProtocol,
                                               char * app, unsigned long appLength,
                                               char * param, unsigned long paramLength);
--- a/uriloader/exthandler/unix/nsOSHelperAppService.cpp
+++ b/uriloader/exthandler/unix/nsOSHelperAppService.cpp
@@ -1228,19 +1228,19 @@ nsOSHelperAppService::GetHandlerAppFromP
     }
     NS_RELEASE(*aApp);
   }
 
   // Thirdly, search the path
   return GetFileTokenForPath(utf16AppPath.get(), aApp);
 }
 
-NS_IMETHODIMP nsOSHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists)
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists)
 {
-  LOG(("-- nsOSHelperAppService::ExternalProtocolHandlerExists for '%s'\n",
+  LOG(("-- nsOSHelperAppService::OSProtocolHandlerExists for '%s'\n",
        aProtocolScheme));
   *aHandlerExists = PR_FALSE;
 
   nsCOMPtr<nsIFile> app;
   nsresult rv = GetHandlerAppFromPrefs(aProtocolScheme, getter_AddRefs(app));
   if (NS_SUCCEEDED(rv)) {
     PRBool isExecutable = PR_FALSE, exists = PR_FALSE;
     nsresult rv1 = app->Exists(&exists);
--- a/uriloader/exthandler/unix/nsOSHelperAppService.h
+++ b/uriloader/exthandler/unix/nsOSHelperAppService.h
@@ -58,17 +58,17 @@ public:
   virtual ~nsOSHelperAppService();
 
   // method overrides for mime.types and mime.info look up steps
   already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMimeType,
                                                   const nsACString& aFileExt,
                                                   PRBool     *aFound);
 
   // override nsIExternalProtocolService methods
-  NS_IMETHOD ExternalProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists);
+  nsresult OSProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists);
   nsresult LoadUriInternal(nsIURI * aURL);
   NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval);
 
   // GetFileTokenForPath must be implemented by each platform. 
   // platformAppPath --> a platform specific path to an application that we got out of the 
   //                     rdf data source. This can be a mac file spec, a unix path or a windows path depending on the platform
   // aFile --> an nsIFile representation of that platform application path.
   virtual nsresult GetFileTokenForPath(const PRUnichar * platformAppPath, nsIFile ** aFile);
--- a/uriloader/exthandler/win/nsOSHelperAppService.cpp
+++ b/uriloader/exthandler/win/nsOSHelperAppService.cpp
@@ -132,17 +132,17 @@ static nsresult GetExtensionFrom4xRegist
     // we have a comma separated list of types...
     // truncate everything after the first comma (including the comma)
     aFileExtension.Truncate(pos); 
   }
    
   return NS_OK;
 }
 
-NS_IMETHODIMP nsOSHelperAppService::ExternalProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists)
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists)
 {
   // look up the protocol scheme in the windows registry....if we find a match then we have a handler for it...
   *aHandlerExists = PR_FALSE;
   if (aProtocolScheme && *aProtocolScheme)
   {
      HKEY hKey;
      LONG err = ::RegOpenKeyEx(HKEY_CLASSES_ROOT, aProtocolScheme, 0,
                                KEY_QUERY_VALUE, &hKey);
--- a/uriloader/exthandler/win/nsOSHelperAppService.h
+++ b/uriloader/exthandler/win/nsOSHelperAppService.h
@@ -52,17 +52,17 @@ class nsMIMEInfoWin;
 
 class nsOSHelperAppService : public nsExternalHelperAppService
 {
 public:
   nsOSHelperAppService();
   virtual ~nsOSHelperAppService();
 
   // override nsIExternalProtocolService methods
-  NS_IMETHOD ExternalProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists);
+  nsresult OSProtocolHandlerExists(const char * aProtocolScheme, PRBool * aHandlerExists);
   nsresult LoadUriInternal(nsIURI * aURL);
   NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval);
 
   // method overrides for windows registry look up steps....
   already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, PRBool *aFound);
 
   /** Get the string value of a registry value and store it in result.
    * @return PR_TRUE on success, PR_FALSE on failure