Fix for protocol handling dialog throwing exception when a web handler is selected (bug 392957), r=biesi, sr=mconnor, a=bz
authordmose@mozilla.org
Sun, 02 Sep 2007 21:44:25 -0700
changeset 5588 74d91b7faf50549cb5c4fff1063e8312239df864
parent 5587 bb90c48d2cad297b34e1d2a717284b8c08ba7b4c
child 5589 dafc8871586084392b297db77a0ef0b61c3f2f83
push id1
push usersledru@mozilla.com
push dateThu, 04 Dec 2014 17:57:20 +0000
reviewersbiesi, mconnor, bz
bugs392957
milestone1.9a8pre
Fix for protocol handling dialog throwing exception when a web handler is selected (bug 392957), r=biesi, sr=mconnor, a=bz
docshell/base/nsWebShell.cpp
netwerk/mime/public/nsIMIMEInfo.idl
toolkit/mozapps/handling/content/dialog.js
toolkit/mozapps/handling/src/nsContentDispatchChooser.js
uriloader/exthandler/mac/nsMIMEInfoMac.cpp
uriloader/exthandler/mac/nsMIMEInfoMac.h
uriloader/exthandler/nsExternalHelperAppService.cpp
uriloader/exthandler/nsExternalProtocolHandler.cpp
uriloader/exthandler/nsIExternalProtocolService.idl
uriloader/exthandler/nsMIMEInfoImpl.cpp
uriloader/exthandler/nsMIMEInfoImpl.h
uriloader/exthandler/os2/nsMIMEInfoOS2.cpp
uriloader/exthandler/os2/nsMIMEInfoOS2.h
--- a/docshell/base/nsWebShell.cpp
+++ b/docshell/base/nsWebShell.cpp
@@ -813,17 +813,17 @@ nsWebShell::OnLinkClickSync(nsIContent *
       nsCAutoString scheme;
       aURI->GetScheme(scheme);
       if (!scheme.IsEmpty()) {
         // if the URL scheme does not correspond to an exposed protocol, then we
         // need to hand this link click over to the external protocol handler.
         PRBool isExposed;
         nsresult rv = extProtService->IsExposedProtocol(scheme.get(), &isExposed);
         if (NS_SUCCEEDED(rv) && !isExposed) {
-          return extProtService->LoadUrl(aURI);
+          return extProtService->LoadURI(aURI, this); 
         }
       }
     }
   }
 
   nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aContent));
   NS_ENSURE_TRUE(node, NS_ERROR_UNEXPECTED);
 
--- a/netwerk/mime/public/nsIMIMEInfo.idl
+++ b/netwerk/mime/public/nsIMIMEInfo.idl
@@ -38,24 +38,25 @@
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIFile;
 interface nsIUTF8StringEnumerator;
 interface nsIHandlerApp;
 interface nsIMutableArray;
+interface nsIInterfaceRequestor;
 
 typedef long nsHandlerInfoAction;
 
 /**
  * nsIHandlerInfo gives access to the information about how a given protocol
  * scheme or MIME-type is handled.
  */
-[scriptable, uuid(4c7f5603-cfa9-4576-a769-c3343cb0135b)]
+[scriptable, uuid(325e56a7-3762-4312-aec7-f1fcf84b4145)]
 interface nsIHandlerInfo : nsISupports {
     /**
      * The type of this handler info.  For MIME handlers, this is the MIME type.
      * For protocol handlers, it's the scheme.
      * 
      * @return String representing the type.
      */
     readonly attribute ACString type;
@@ -101,22 +102,32 @@ interface nsIHandlerInfo : nsISupports {
      * useHelperApp or useSystemDefault.
      *  
      * @note Only the URI scheme is used to determine how to launch.  This is
      * essentially a pass-by-value operation.  This means that in the case of
      * a file: URI, the handler that is registered for file: will be launched
      * and our code will not make any decision based on the content-type or
      * extension, though the invoked file: handler is free to do so. 
      *
-     * @param aURI The URI to launch this application with
+     * @param aURI
+     *        The URI to launch this application with
      *
-     * @throw NS_ERROR_INVALID_ARG if action is not valid for this function.
-     * Other exceptions may be thrown.
+     * @param aWindowContext 
+     *        The window to parent the dialog against, and, if a web handler
+     *        is chosen, it is loaded in this window as well.  This parameter
+     *        may be ultimately passed nsIURILoader.openURI in the case of a
+     *        web handler, and aWindowContext is null or not present, web
+     *        handlers will fail.  We need to do better than that; bug 394483
+     *        filed in order to track.
+     *       
+     * @throw NS_ERROR_INVALID_ARG if preferredAction is not valid for this
+     * call. Other exceptions may be thrown.
      */
-    void launchWithURI(in nsIURI aURI);
+    void launchWithURI(in nsIURI aURI, 
+                       [optional] in nsIInterfaceRequestor aWindowContext);
 
     /**
      * 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;
--- a/toolkit/mozapps/handling/content/dialog.js
+++ b/toolkit/mozapps/handling/content/dialog.js
@@ -15,16 +15,17 @@
  *
  * The Initial Developer of the Original Code is
  *   Mozilla Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2007
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Shawn Wilsher <me@shawnwilsher.com> (original author)
+ *   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
@@ -50,41 +51,47 @@
  *   This is the text to be placed in the label for the checkbox.  If no text is
  *   passed (ie, it's an empty string), the checkbox will be hidden.
  * window.arguments[5]:
  *   This is the text that is displayed below the checkbox when it is checked.
  * window.arguments[6]:
  *   This is the nsIHandlerInfo that gives us all our precious information.
  * window.arguments[7]:
  *   This is the nsIURI that we are being brought up for in the first place.
+ * window.arguments[8]:
+ *   The nsIInterfaceRequestor of the parent window; may be null
  */
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 var dialog = {
   //////////////////////////////////////////////////////////////////////////////
   //// Member Variables
 
   _handlerInfo: null,
   _URI: null,
   _itemChoose: null,
   _okButton: null,
-
+  _windowCtxt: null,
+  
   //////////////////////////////////////////////////////////////////////////////
   //// Methods
 
  /**
   * This function initializes the content of the dialog.
   */
   initialize: function initialize()
   {
     this._handlerInfo = window.arguments[6].QueryInterface(Ci.nsIHandlerInfo);
     this._URI         = window.arguments[7].QueryInterface(Ci.nsIURI);
+    this._windowCtxt  = window.arguments[8];
+    if (this._windowCtxt)
+      this._windowCtxt.QueryInterface(Ci.nsIInterfaceRequestor);
     this._itemChoose  = document.getElementById("item-choose");
     this._okButton    = document.documentElement.getButton("accept");
 
     this.updateOKButton();
 
     var description = {
       image: document.getElementById("description-image"),
       text:  document.getElementById("description-text")
@@ -197,17 +204,17 @@ var dialog = {
         this._handlerInfo.preferredAction = Ci.nsIHandlerInfo.useSystemDefault;
     }
     this._handlerInfo.alwaysAskBeforeHandling = !checkbox.checked;
 
     var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
              getService(Ci.nsIHandlerService);
     hs.store(this._handlerInfo);
 
-    this._handlerInfo.launchWithURI(this._URI);
+    this._handlerInfo.launchWithURI(this._URI, this._windowCtxt);
 
     return true;
   },
 
  /**
   * Determines if the OK button should be disabled or not
   */
   updateOKButton: function updateOKButton()
--- a/toolkit/mozapps/handling/src/nsContentDispatchChooser.js
+++ b/toolkit/mozapps/handling/src/nsContentDispatchChooser.js
@@ -15,16 +15,17 @@
  *
  * The Initial Developer of the Original Code is
  * Mozilla Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2007
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *   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
@@ -94,17 +95,18 @@ nsContentDispatchChooser.prototype =
     for each (let text in arr) {
       let string = Cc["@mozilla.org/supports-string;1"].
                    createInstance(Ci.nsISupportsString);
       string.data = text;
       params.appendElement(string, false);
     }
     params.appendElement(aHandler, false);
     params.appendElement(aURI, false);
-
+    params.appendElement(aWindowContext, false);
+    
     var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
              getService(Ci.nsIWindowWatcher);
     ww.openWindow(window,
                   CONTENT_HANDLING_URL,
                   null,
                   "chrome,dialog=yes,resizable,centerscreen",
                   params);
   },
--- a/uriloader/exthandler/mac/nsMIMEInfoMac.cpp
+++ b/uriloader/exthandler/mac/nsMIMEInfoMac.cpp
@@ -95,34 +95,35 @@ nsMIMEInfoMac::LaunchWithFile(nsIFile *a
       return NS_ERROR_FAILURE;
     }
   }
   nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(aFile);
   return app->LaunchWithDoc(localFile, PR_FALSE); 
 }
 
 NS_IMETHODIMP
-nsMIMEInfoMac::LaunchWithURI(nsIURI* aURI)
+nsMIMEInfoMac::LaunchWithURI(nsIURI* aURI,
+                             nsIInterfaceRequestor* aWindowContext)
 {
   nsCOMPtr<nsIFile> application;
   nsresult rv;
 
   // for now, this is only being called with protocol handlers; that
   // will change once we get to more general registerContentHandler
   // support
   NS_ASSERTION(mClass == eProtocolInfo,
                "nsMIMEInfoBase should be a protocol handler");
 
   if (mPreferredAction == useHelperApp) {
 
     // check for and launch with web handler app
     nsCOMPtr<nsIWebHandlerApp> webHandlerApp =
       do_QueryInterface(mPreferredApplication, &rv);
     if (NS_SUCCEEDED(rv)) {
-      return LaunchWithWebHandler(webHandlerApp, aURI);         
+      return LaunchWithWebHandler(webHandlerApp, aURI, aWindowContext);         
     }
 
     // otherwise, get the application executable from the handler
     nsCOMPtr<nsILocalHandlerApp> localHandlerApp =
         do_QueryInterface(mPreferredApplication, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
     
     rv = localHandlerApp->GetExecutable(getter_AddRefs(application));
--- a/uriloader/exthandler/mac/nsMIMEInfoMac.h
+++ b/uriloader/exthandler/mac/nsMIMEInfoMac.h
@@ -41,17 +41,18 @@
 
 class nsMIMEInfoMac : public nsMIMEInfoImpl {
   public:
     nsMIMEInfoMac(const char* aMIMEType = "") : nsMIMEInfoImpl(aMIMEType) {}
     nsMIMEInfoMac(const nsACString& aMIMEType) : nsMIMEInfoImpl(aMIMEType) {}
     nsMIMEInfoMac(const nsACString& aType, HandlerClass aClass) :
       nsMIMEInfoImpl(aType, aClass) {}
 
-    NS_IMETHOD LaunchWithURI(nsIURI* aURI);
+    NS_IMETHOD LaunchWithURI(nsIURI* aURI,
+                             nsIInterfaceRequestor* aWindowContext);
     NS_IMETHOD LaunchWithFile(nsIFile* aFile);
     NS_IMETHOD GetHasDefaultHandler(PRBool *_retval);
   protected:
     virtual NS_HIDDEN_(nsresult) LoadUriInternal(nsIURI *aURI);
 #ifdef DEBUG
     virtual NS_HIDDEN_(nsresult) LaunchDefaultWithFile(nsIFile* aFile) {
       NS_NOTREACHED("do not call this method, use LaunchWithFile");
       return NS_ERROR_UNEXPECTED;
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -119,17 +119,16 @@
 #include "nsIGlobalHistory2.h" // to mark downloads as visited
 
 #include "nsIDOMWindow.h"
 #include "nsIDOMWindowInternal.h"
 #include "nsIDocShell.h"
 
 #include "nsCRT.h"
 
-#include "nsMIMEInfoImpl.h"
 #include "nsLocalHandlerApp.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
@@ -1334,17 +1333,17 @@ nsExternalHelperAppService::LoadURI(nsIU
   handler->GetAlwaysAskBeforeHandling(&alwaysAsk);
 
   // if we are not supposed to warn, we are not supposed to always ask, and
   // the preferred action is to use a helper app or the system default, we just
   // launch the URI.
   if (!warn &&
       !alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp ||
                      preferredAction == nsIHandlerInfo::useSystemDefault))
-    return handler->LaunchWithURI(uri);
+    return handler->LaunchWithURI(uri, aWindowContext);
   
   nsCOMPtr<nsIContentDispatchChooser> chooser =
     do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
   
   return chooser->Ask(handler, aWindowContext, uri,
                       nsIContentDispatchChooser::REASON_CANNOT_HANDLE);
 }
@@ -1402,20 +1401,16 @@ nsExternalHelperAppService::GetProtocolH
                                                    nsIHandlerInfo **aHandlerInfo)
 {
   // XXX Now that we've exposed this to the UI (bug 391150), is there anything
   // we need to do to make it compatible with 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.
   PRBool exists;
   *aHandlerInfo = GetProtocolInfoFromOS(aScheme, &exists).get();
   if (!(*aHandlerInfo)) {
     // Either it knows nothing, or we ran out of memory
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv = FillProtoInfoForSchemeFromDS(aScheme, *aHandlerInfo);     
--- a/uriloader/exthandler/nsExternalProtocolHandler.cpp
+++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp
@@ -48,34 +48,29 @@
 #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 nsProtocolRedirect;
-
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSICHANNEL
     NS_DECL_NSIREQUEST
 
     nsExtProtocolChannel();
     virtual ~nsExtProtocolChannel();
 
@@ -84,36 +79,32 @@ public:
 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), 
-                                               mIsPending(PR_FALSE),
                                                mWasOpened(PR_FALSE)
 {
 }
 
 nsExtProtocolChannel::~nsExtProtocolChannel()
 {}
 
 NS_IMETHODIMP nsExtProtocolChannel::GetLoadGroup(nsILoadGroup * *aLoadGroup)
@@ -183,243 +174,50 @@ nsresult nsExtProtocolChannel::OpenURL()
 #ifdef DEBUG
     nsCAutoString urlScheme;
     mUrl->GetScheme(urlScheme);
     PRBool haveHandler = PR_FALSE;
     extProtService->ExternalProtocolHandlerExists(urlScheme.get(), &haveHandler);
     NS_ASSERTION(haveHandler, "Why do we have a channel for this url if we don't support the protocol?");
 #endif
 
-    rv = extProtService->LoadURI(mUrl, mCallbacks);
+    nsCOMPtr<nsIInterfaceRequestor> aggCallbacks;
+    rv = NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
+                                                getter_AddRefs(aggCallbacks));
+    if (NS_FAILED(rv)) {
+      goto finish;
+    }
+                                                
+    rv = extProtService->LoadURI(mUrl, aggCallbacks);
+    if (NS_SUCCEEDED(rv)) {
+        // despite success, we need to abort this channel, at the very least 
+        // to make it clear to the caller that no on{Start,Stop}Request
+        // should be expected.
+        rv = NS_ERROR_NO_CONTENT;
+    }
   }
 
-  // Drop notification callbacks to prevent cycles.
+finish:
   mCallbacks = 0;
-
   return rv;
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::Open(nsIInputStream **_retval)
 {
-  OpenURL();
-  return NS_ERROR_NO_CONTENT; // force caller to abort.
+  return OpenURL();
 }
 
-class nsProtocolRedirect : public nsRunnable {
-  public:
-    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;
-      rv = mURI->GetSpec(uriSpecToHandle);
-      if (NS_FAILED(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...
-
-      // 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). 
-      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), uriTemplate);
-      if (NS_FAILED(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 
-                         | nsIChannel::LOAD_REPLACE);
-      if (NS_FAILED(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)) {
-          mOriginalChannel->Finish(rv);
-          return NS_OK;
-        }
-      }
-
-      rv = newChannel->AsyncOpen(mListener, mContext);
-      if (NS_FAILED(rv)) {
-        mOriginalChannel->Finish(rv);
-        return NS_OK; 
-      }
-      
-      mOriginalChannel->Finish(NS_BINDING_REDIRECTED);
-      return NS_OK;
-    }
-
-  private:
-    nsCOMPtr<nsIURI> mURI;
-    nsCOMPtr<nsIHandlerInfo> mHandlerInfo;
-    nsCOMPtr<nsIStreamListener> mListener;
-    nsCOMPtr<nsISupports> mContext;
-    nsCOMPtr<nsExtProtocolChannel> mOriginalChannel;
-};
-
 NS_IMETHODIMP nsExtProtocolChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
 {
   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);
-  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));
-  // TODO all this code should be moved to nsIHandlerInfo::LaunchWithURI
-  if (NS_SUCCEEDED(rv)) {
-    PRInt32 preferredAction;                                           
-    rv = handlerInfo->GetPreferredAction(&preferredAction);
-
-    if (preferredAction == nsIHandlerInfo::useHelperApp) {
-
-      nsCOMPtr<nsIHandlerApp> handler;
-      rv = handlerInfo->GetPreferredApplicationHandler(getter_AddRefs(handler));
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      // Now we check to see if this is a local handler or not
-      nsCOMPtr<nsILocalHandlerApp> localHandler =
-        do_QueryInterface(handler, &rv);
-      if (NS_SUCCEEDED(rv)) {
-        OpenURL();
-        return NS_ERROR_NO_CONTENT; // force caller to abort
-      }
-
-      // We must have a web handler
-      // 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;
-      }
-    }
-  }
-  
-  // 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;
+  return OpenURL();
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
 {
   *aLoadFlags = mLoadFlags;
   return NS_OK;
 }
 
@@ -480,17 +278,17 @@ NS_IMETHODIMP nsExtProtocolChannel::SetO
 
 NS_IMETHODIMP nsExtProtocolChannel::GetName(nsACString &result)
 {
   return mUrl->GetSpec(result);
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::IsPending(PRBool *result)
 {
-  *result = mIsPending;
+  *result = PR_FALSE;
   return NS_OK; 
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::GetStatus(nsresult *status)
 {
   *status = mStatus;
   return NS_OK;
 }
--- a/uriloader/exthandler/nsIExternalProtocolService.idl
+++ b/uriloader/exthandler/nsIExternalProtocolService.idl
@@ -18,16 +18,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>
  *   Myk Melez <myk@mozilla.org>
+ *   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
@@ -100,18 +101,30 @@ interface nsIExternalProtocolService : n
    * @deprecated Use LoadURI instead (See Bug 389565 for removal)
    */
    void loadUrl (in nsIURI aURL);
 
   /**
    * Used to load a URI via an external application. Might prompt the user for
    * permission to load the external application.
    *
-   * @param aURI           The URI to load
-   * @param aWindowContext The parent window to open the dialog with.
+   * @param aURI
+   *        The URI to load
+   *
+   * @param aWindowContext 
+   *        The window to parent the dialog against, and, if a web handler
+   *        is chosen, it is loaded in this window as well.  This parameter
+   *        may be ultimately passed nsIURILoader.openURI in the case of a
+   *        web handler, and aWindowContext is null or not present, web
+   *        handlers will fail.  We need to do better than that; bug 394483
+   *        filed in order to track.
+   * 
+   * @note  Embedders that do not expose the http protocol should not currently
+   *        use web-based protocol handlers, as handoff won't work correctly
+   *        (bug 394479).  
    */
   void loadURI(in nsIURI aURI,
                [optional] in nsIInterfaceRequestor aWindowContext);
 
   /**
    * Gets a human-readable description for the application responsible for
    * handling a specific protocol.
    *
--- a/uriloader/exthandler/nsMIMEInfoImpl.cpp
+++ b/uriloader/exthandler/nsMIMEInfoImpl.cpp
@@ -38,16 +38,20 @@
 
 #include "nsMIMEInfoImpl.h"
 #include "nsXPIDLString.h"
 #include "nsReadableUtils.h"
 #include "nsStringEnumerator.h"
 #include "nsIProcess.h"
 #include "nsILocalFile.h"
 #include "nsIFileURL.h"
+#include "nsEscape.h"
+#include "nsNetUtil.h"
+#include "nsIURILoader.h"
+#include "nsCURILoader.h"
 
 // nsISupports methods
 NS_IMPL_THREADSAFE_ADDREF(nsMIMEInfoBase)
 NS_IMPL_THREADSAFE_RELEASE(nsMIMEInfoBase)
 
 NS_INTERFACE_MAP_BEGIN(nsMIMEInfoBase)
     NS_INTERFACE_MAP_ENTRY(nsIHandlerInfo)
     // This is only an nsIMIMEInfo if it's a MIME handler.
@@ -375,17 +379,18 @@ nsMIMEInfoBase::LaunchWithFile(nsIFile* 
     aFile->GetNativePath(path);
     return LaunchWithIProcess(executable, path);
   }
 
   return NS_ERROR_INVALID_ARG;
 }
 
 NS_IMETHODIMP
-nsMIMEInfoBase::LaunchWithURI(nsIURI* aURI)
+nsMIMEInfoBase::LaunchWithURI(nsIURI* aURI,
+                              nsIInterfaceRequestor* aWindowContext)
 {
   nsresult rv;
 
   // for now, this is only being called with protocol handlers; that
   // will change once we get to more general registerContentHandler
   // support
   NS_ASSERTION(mClass == eProtocolInfo,
                "nsMIMEInfoBase should be a protocol handler");
@@ -397,17 +402,17 @@ nsMIMEInfoBase::LaunchWithURI(nsIURI* aU
   if (mPreferredAction == useHelperApp) {
     if (!mPreferredApplication)
       return NS_ERROR_FILE_NOT_FOUND;
 
     // check for and possibly launch with web application
     nsCOMPtr<nsIWebHandlerApp> webHandler = 
       do_QueryInterface(mPreferredApplication, &rv);
     if (NS_SUCCEEDED(rv)) {
-      return LaunchWithWebHandler(webHandler, aURI);         
+      return LaunchWithWebHandler(webHandler, aURI, aWindowContext);         
     }
 
     // ok, we must have a local handler app
     nsCOMPtr<nsILocalHandlerApp> localHandler = 
       do_QueryInterface(mPreferredApplication, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIFile> executable;
@@ -451,20 +456,76 @@ nsMIMEInfoBase::LaunchWithIProcess(nsIFi
   const char *string = aArg.get();
 
   PRUint32 pid;
   return process->Run(PR_FALSE, &string, 1, &pid);
 }
 
 /* static */
 nsresult
-nsMIMEInfoBase::LaunchWithWebHandler(nsIWebHandlerApp *aApp, nsIURI *aURI) 
+nsMIMEInfoBase::LaunchWithWebHandler(nsIWebHandlerApp *aApp, nsIURI *aURI,
+                                     nsIInterfaceRequestor *aWindowContext) 
 {
-  // we'll be implementing this Real Soon Now!
-  return NS_ERROR_NOT_IMPLEMENTED;
+  
+  nsCAutoString uriTemplate;
+  nsresult rv = aApp->GetUriTemplate(uriTemplate);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // get the URI spec so we can escape it for insertion into the template 
+  nsCAutoString uriSpecToHandle;
+  rv = aURI->GetSpec(uriSpecToHandle);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // 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 (bug 382019).
+  nsCAutoString escapedUriSpecToHandle;
+  NS_EscapeURL(uriSpecToHandle, esc_Minimal | esc_Forced | esc_Colon,
+               escapedUriSpecToHandle);
+
+  // XXX note that this replace all occurrences of %s with the URL to be
+  // handled, instead of just the first, as specified by the current draft
+  // of the spec.  Bug 394476 filed to track this.
+  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), uriTemplate);
+  if (NS_FAILED(rv))
+    return rv;
+
+  // create a channel
+  nsCOMPtr<nsIChannel> newChannel;
+  rv = NS_NewChannel(getter_AddRefs(newChannel), uriToSend, nsnull, nsnull,
+                     nsnull, nsIChannel::LOAD_DOCUMENT_URI);
+  if (NS_FAILED(rv))
+    return rv;
+
+  // load the URI
+  nsCOMPtr<nsIURILoader> uriLoader = do_GetService(NS_URI_LOADER_CONTRACTID, 
+                                                   &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // XXX ideally, aIsContentPreferred (the second param) should really be
+  // passed in from above.  Practically, PR_TRUE is probably a reasonable
+  // default since browsers don't care much, and link click is likely to be
+  // the more interesting case for non-browser apps.  See 
+  // <https://bugzilla.mozilla.org/show_bug.cgi?id=392957#c9> for details.
+  return uriLoader->OpenURI(newChannel, PR_TRUE, aWindowContext);
 }
 
 // nsMIMEInfoImpl implementation
 NS_IMETHODIMP
 nsMIMEInfoImpl::GetDefaultDescription(nsAString& aDefaultDescription)
 {
   if (mDefaultAppDescription.IsEmpty() && mDefaultApplication) {
     // Don't want to cache this, just in case someone resets the app
--- a/uriloader/exthandler/nsMIMEInfoImpl.h
+++ b/uriloader/exthandler/nsMIMEInfoImpl.h
@@ -84,17 +84,18 @@ class nsMIMEInfoBase : public nsIMIMEInf
     NS_IMETHOD GetMacCreator(PRUint32 *aMacCreator);
     NS_IMETHOD SetMacCreator(PRUint32 aMacCreator);
     NS_IMETHOD Equals(nsIMIMEInfo *aMIMEInfo, PRBool *_retval);
     NS_IMETHOD GetPreferredApplicationHandler(nsIHandlerApp * *aPreferredAppHandler);
     NS_IMETHOD SetPreferredApplicationHandler(nsIHandlerApp * aPreferredAppHandler);
     NS_IMETHOD GetPossibleApplicationHandlers(nsIMutableArray * *aPossibleAppHandlers);
     NS_IMETHOD GetDefaultDescription(nsAString & aDefaultDescription);
     NS_IMETHOD LaunchWithFile(nsIFile *aFile);
-    NS_IMETHOD LaunchWithURI(nsIURI *aURI);
+    NS_IMETHOD LaunchWithURI(nsIURI *aURI,
+                             nsIInterfaceRequestor *aWindowContext);
     NS_IMETHOD GetPreferredAction(nsHandlerInfoAction *aPreferredAction);
     NS_IMETHOD SetPreferredAction(nsHandlerInfoAction aPreferredAction);
     NS_IMETHOD GetAlwaysAskBeforeHandling(PRBool *aAlwaysAskBeforeHandling);
     NS_IMETHOD SetAlwaysAskBeforeHandling(PRBool aAlwaysAskBeforeHandling); 
 
     enum HandlerClass {
       eMIMEInfo,
       eProtocolInfo
@@ -153,19 +154,29 @@ class nsMIMEInfoBase : public nsIMIMEInf
      */
     static NS_HIDDEN_(nsresult) LaunchWithIProcess(nsIFile* aApp,
                                                    const nsCString &aArg);
 
     /**
      * Used to launch a web-based handler with this URI.
      * 
      * @param aURI  The URI to launch with.
+     * 
+     * @param aWindowContext 
+     *        The window to parent the dialog against, and, if a web handler
+     *        is chosen, it is loaded in this window as well.  This parameter
+     *        may be ultimately passed nsIURILoader.openURI in the case of a
+     *        web handler, and aWindowContext is null or not present, web
+     *        handlers will fail.  We need to do better than that; bug 394483
+     *        filed in order to track.
+     * 
      */
-    static NS_HIDDEN_(nsresult) LaunchWithWebHandler(nsIWebHandlerApp *aApp, 
-                                                     nsIURI *aURI);
+    static NS_HIDDEN_(nsresult) 
+        LaunchWithWebHandler(nsIWebHandlerApp *aApp, nsIURI *aURI,
+                             nsIInterfaceRequestor *aWindowContext);
 
     /**
      * Given a file: nsIURI, return the associated nsILocalFile
      *
      * @param  aURI      the file: URI in question
      * @param  aFile     the associated nsILocalFile (out param)
      */
     static NS_HIDDEN_(nsresult) GetLocalFileFromURI(nsIURI *aURI,
--- a/uriloader/exthandler/os2/nsMIMEInfoOS2.cpp
+++ b/uriloader/exthandler/os2/nsMIMEInfoOS2.cpp
@@ -54,17 +54,18 @@ static const PRUnichar table[] =
     'u','v','w','x','y','z','0','1','2','3',
     '4','5','6','7','8','9'};
 
 
 nsMIMEInfoOS2::~nsMIMEInfoOS2()
 {
 }
 
-NS_IMETHODIMP nsMIMEInfoOS2::LaunchWithURI(nsIURI* aURI)
+NS_IMETHODIMP nsMIMEInfoOS2::LaunchWithURI(nsIURI* aURI,
+                                           nsIInterfaceRequestor* aWindowContext)
 {
   nsresult rv = NS_OK;
 
   nsCOMPtr<nsILocalFile> docToLoad;
   rv = GetLocalFileFromURI(aURI, getter_AddRefs(docToLoad));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCAutoString path;
--- a/uriloader/exthandler/os2/nsMIMEInfoOS2.h
+++ b/uriloader/exthandler/os2/nsMIMEInfoOS2.h
@@ -59,17 +59,18 @@ class nsMIMEInfoOS2 : public nsMIMEInfoI
 {
   public:
     nsMIMEInfoOS2(const char* aType = "") : nsMIMEInfoImpl(aType) {}
     nsMIMEInfoOS2(const nsACString& aMIMEType) : nsMIMEInfoImpl(aMIMEType) {}
     nsMIMEInfoOS2(const nsACString& aType, HandlerClass aClass) :
       nsMIMEInfoImpl(aType, aClass) {}
     virtual ~nsMIMEInfoOS2();
 
-    NS_IMETHOD LaunchWithURI(nsIURI* aURI);
+    NS_IMETHOD LaunchWithURI(nsIURI* aURI,
+                             nsIInterfaceRequestor* aWindowContext);
   protected:
     virtual NS_HIDDEN_(nsresult) LoadUriInternal(nsIURI *aURI);
 #ifdef DEBUG
     virtual NS_HIDDEN_(nsresult) LaunchDefaultWithFile(nsIFile* aFile) {
       NS_NOTREACHED("Do not call this, use LaunchWithFile");
       return NS_ERROR_UNEXPECTED;
     }
 #endif