Bug 1277028 - Make external protocol handlers work with e10s redirects, r=jduell
authorHonza Bambas <honzab.moz@firemni.cz>
Fri, 01 Jul 2016 05:16:00 +0200
changeset 343510 6ca8b9d8abd15b6ea71281f94309e91e965a18d0
parent 343509 d19e8687583ef3af3c48b73fb783fbc0fe55f61e
child 343511 ce5c5ebc122bf9d776b30dfa359dfa79ee50ec21
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell
bugs1277028
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1277028 - Make external protocol handlers work with e10s redirects, r=jduell
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
uriloader/exthandler/nsExternalProtocolHandler.cpp
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -137,16 +137,17 @@
 #include "nsIIdleService.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMozBrowserFrame.h"
 #include "nsIMutable.h"
 #include "nsINSSU2FToken.h"
 #include "nsIObserverService.h"
+#include "nsIParentChannel.h"
 #include "nsIPresShell.h"
 #include "nsIRemoteWindowContext.h"
 #include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISiteSecurityService.h"
 #include "nsISpellChecker.h"
 #include "nsISupportsPrimitives.h"
 #include "nsISystemMessagesInternal.h"
@@ -4415,16 +4416,42 @@ ContentParent::RecvLoadURIExternal(const
 
   RefPtr<RemoteWindowContext> context =
     new RemoteWindowContext(static_cast<TabParent*>(windowContext));
   extProtService->LoadURI(ourURI, context);
   return true;
 }
 
 bool
+ContentParent::RecvExtProtocolChannelConnectParent(const uint32_t& registrarId)
+{
+  nsresult rv;
+
+  // First get the real channel created before redirect on the parent.
+  nsCOMPtr<nsIChannel> channel;
+  rv = NS_LinkRedirectChannels(registrarId, nullptr, getter_AddRefs(channel));
+  NS_ENSURE_SUCCESS(rv, true);
+
+  nsCOMPtr<nsIParentChannel> parent = do_QueryInterface(channel, &rv);
+  NS_ENSURE_SUCCESS(rv, true);
+
+  // The channel itself is its own (faked) parent, link it.
+  rv = NS_LinkRedirectChannels(registrarId, parent, getter_AddRefs(channel));
+  NS_ENSURE_SUCCESS(rv, true);
+
+  // Signal the parent channel that it's a redirect-to parent.  This will
+  // make AsyncOpen on it do nothing (what we want).
+  // Yes, this is a bit of a hack, but I don't think it's necessary to invent
+  // a new interface just to set this flag on the channel.
+  parent->SetParentListener(nullptr);
+
+  return true;
+}
+
+bool
 ContentParent::HasNotificationPermission(const IPC::Principal& aPrincipal)
 {
 #ifdef MOZ_CHILD_PERMISSIONS
   uint32_t permission = mozilla::CheckPermission(this, aPrincipal,
                                                  "desktop-notification");
   if (permission != nsIPermissionManager::ALLOW_ACTION) {
     return false;
   }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -999,16 +999,17 @@ private:
                               const IPC::Principal& aPrincipal) override;
 
   virtual bool RecvDisableNotifications(const IPC::Principal& aPrincipal) override;
 
   virtual bool RecvOpenNotificationSettings(const IPC::Principal& aPrincipal) override;
 
   virtual bool RecvLoadURIExternal(const URIParams& uri,
                                    PBrowserParent* windowContext) override;
+  virtual bool RecvExtProtocolChannelConnectParent(const uint32_t& registrarId) override;
 
   virtual bool RecvSyncMessage(const nsString& aMsg,
                                const ClonedMessageData& aData,
                                InfallibleTArray<CpowEntry>&& aCpows,
                                const IPC::Principal& aPrincipal,
                                nsTArray<StructuredCloneData>* aRetvals) override;
 
   virtual bool RecvRpcMessage(const nsString& aMsg,
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -828,16 +828,17 @@ parent:
 
     // Services remoting
 
     async StartVisitedQuery(URIParams uri);
     async VisitURI(URIParams uri, OptionalURIParams referrer, uint32_t flags);
     async SetURITitle(URIParams uri, nsString title);
 
     async LoadURIExternal(URIParams uri, PBrowser windowContext);
+    async ExtProtocolChannelConnectParent(uint32_t registrarId);
 
     // PrefService message
     sync ReadPrefsArray() returns (PrefSetting[] prefs) verify;
 
     sync ReadFontList() returns (FontListEntry[] retValue);
 
     sync ReadDataStorageArray(nsString aFilename)
       returns (DataStorageItem[] retValue);
--- a/uriloader/exthandler/nsExternalProtocolHandler.cpp
+++ b/uriloader/exthandler/nsExternalProtocolHandler.cpp
@@ -21,65 +21,83 @@
 #include "nsIPrompt.h"
 #include "nsNetUtil.h"
 #include "nsContentSecurityManager.h"
 #include "nsExternalHelperAppService.h"
 
 // used to dispatch urls to default protocol handlers
 #include "nsCExternalHandlerService.h"
 #include "nsIExternalProtocolService.h"
+#include "nsIChildChannel.h"
+#include "nsIParentChannel.h"
 
 class nsILoadInfo;
 
 ////////////////////////////////////////////////////////////////////////
 // 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
+class nsExtProtocolChannel : public nsIChannel,
+                             public nsIChildChannel,
+                             public nsIParentChannel
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSICHANNEL
+    NS_DECL_NSIREQUESTOBSERVER
+    NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSIREQUEST
+    NS_DECL_NSICHILDCHANNEL
+    NS_DECL_NSIPARENTCHANNEL
 
     nsExtProtocolChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo);
 
 private:
     virtual ~nsExtProtocolChannel();
 
     nsresult OpenURL();
     void Finish(nsresult aResult);
     
     nsCOMPtr<nsIURI> mUrl;
     nsCOMPtr<nsIURI> mOriginalURI;
     nsresult mStatus;
     nsLoadFlags mLoadFlags;
     bool mWasOpened;
+    // Set true (as a result of ConnectParent invoked from child process)
+    // when this channel is on the parent process and is being used as
+    // a redirect target channel.  It turns AsyncOpen into a no-op since
+    // we do it on the child.
+    bool mConnectedParent;
     
     nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
     nsCOMPtr<nsILoadGroup> mLoadGroup;
     nsCOMPtr<nsILoadInfo> mLoadInfo;
 };
 
 NS_IMPL_ADDREF(nsExtProtocolChannel)
 NS_IMPL_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_ENTRY(nsIChildChannel)
+   NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
+   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
 NS_INTERFACE_MAP_END_THREADSAFE
 
 nsExtProtocolChannel::nsExtProtocolChannel(nsIURI* aURI,
                                            nsILoadInfo* aLoadInfo)
   : mUrl(aURI)
   , mOriginalURI(aURI)
   , mStatus(NS_OK)
   , mWasOpened(false)
+  , mConnectedParent(false)
   , mLoadInfo(aLoadInfo)
 {
 }
 
 nsExtProtocolChannel::~nsExtProtocolChannel()
 {}
 
 NS_IMETHODIMP nsExtProtocolChannel::GetLoadGroup(nsILoadGroup * *aLoadGroup)
@@ -179,16 +197,20 @@ NS_IMETHODIMP nsExtProtocolChannel::Open
   nsCOMPtr<nsIStreamListener> listener;
   nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
   NS_ENSURE_SUCCESS(rv, rv);
   return Open(aStream);
 }
 
 NS_IMETHODIMP nsExtProtocolChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
 {
+  if (mConnectedParent) {
+    return NS_OK;
+  }
+
   MOZ_ASSERT(!mLoadInfo ||
              mLoadInfo->GetSecurityMode() == 0 ||
              mLoadInfo->GetInitialSecurityCheckDone() ||
              (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
               nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
              "security flags in loadInfo but asyncOpen2() not called");
 
   NS_ENSURE_ARG_POINTER(listener);
@@ -334,16 +356,91 @@ NS_IMETHODIMP nsExtProtocolChannel::Susp
 
 NS_IMETHODIMP nsExtProtocolChannel::Resume()
 {
   NS_NOTREACHED("Resume");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 ///////////////////////////////////////////////////////////////////////
+// From nsIChildChannel
+//////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsExtProtocolChannel::ConnectParent(uint32_t registrarId)
+{
+  mozilla::dom::ContentChild::GetSingleton()->
+    SendExtProtocolChannelConnectParent(registrarId);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::CompleteRedirectSetup(nsIStreamListener *listener,
+                                                          nsISupports *context)
+{
+  // For redirects to external protocols we AsyncOpen on the child
+  // (not the parent) because child channel has the right docshell
+  // (which is needed for the select dialog).
+  return AsyncOpen(listener, context);
+}
+
+///////////////////////////////////////////////////////////////////////
+// From nsIParentChannel (derives from nsIStreamListener)
+//////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsExtProtocolChannel::SetParentListener(HttpChannelParentListener* aListener)
+{
+  // This is called as part of the connect parent operation from
+  // ContentParent::RecvExtProtocolChannelConnectParent.  Setting
+  // this flag tells this channel to not proceed and makes AsyncOpen
+  // just no-op.  Actual operation will happen from the child process
+  // via CompleteRedirectSetup call on the child channel.
+  mConnectedParent = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::NotifyTrackingProtectionDisabled()
+{
+  // nothing to do
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::Delete()
+{
+  // nothing to do
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::OnStartRequest(nsIRequest *aRequest,
+                                                   nsISupports *aContext)
+{
+  // no data is expected
+  MOZ_CRASH("No data expected from external protocol channel");
+  return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::OnStopRequest(nsIRequest *aRequest,
+                                                  nsISupports *aContext,
+                                                  nsresult aStatusCode)
+{
+  // no data is expected
+  MOZ_CRASH("No data expected from external protocol channel");
+  return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsExtProtocolChannel::OnDataAvailable(nsIRequest *aRequest,
+                                                    nsISupports *aContext,
+                                                    nsIInputStream *aInputStream,
+                                                    uint64_t aOffset,
+                                                    uint32_t aCount)
+{
+  // no data is expected
+  MOZ_CRASH("No data expected from external protocol channel");
+  return NS_ERROR_UNEXPECTED;
+}
+
+///////////////////////////////////////////////////////////////////////
 // the default protocol handler implementation
 //////////////////////////////////////////////////////////////////////
 
 nsExternalProtocolHandler::nsExternalProtocolHandler()
 {
   m_schemeName = "default";
 }