views:

425

answers:

1

I'm trying to build an nsIProtocolHandler implementation in Delphi. (I've done an IInternetProtocol before with success, and want to have in FireFox what I've got in Internet Explorer.) Thanks to the d-gecko project, that somehow links the TInterfacedObject magic to the nsISupports magic, I'm able to make a DLL that provides an nsIModule when asked, which provides an nsIFactory when asked, which provides one of my nsIProtocolHandler's when asked, which provides one of my nsIChannel/nsIHttpChannel's when asked.

When debugging using firefox.exe as host process, I can see my library gets loaded, NewURI gets called three times, NewChannel gets called, and I pass an object that implements nsIChannel and nsIHttpChannel.

This is where I'm troubled. I'm not supposed to call OnStartRequest and OnDataAvailable on the nsIStreamListener I get, until I return control from AsyncOpen, but I don't seem to get control back in the thread that AsyncOpen was called in.

I've tried debugging with a self-made wrapper around a default http handler (gotten with CreateInstanceByContractID('@mozilla.org/network/protocol;1?name=http',...). I also wrapped the listener passed. Oddly enough, I see OnStartRequest and OnDataAvailable get called after my channel wrapper dies, in the same thread. But who's calling? If it's the http-channel I was trying to wrap, how does it survive (in the same thread) and how does it get control to call the listener? I'm baffled. And stuck.

I've tried to contact the main developer of the d-gecko project, but got no response.

(Also, did someone ever notice my blurb at the bottom of the talk page on MDC on nsIProtocolHandler?)

(Oh one more thing, yes I know "life would be simpler" if I would just inherit from nsBaseChannel in C++. But the point is to add a FireFox protocol-handler to an existing Delphi project core.)

Update: I've done some more reading, It's mentioned here as well: "The stream listener's methods are called on the thread that calls asyncOpen [...]" but how that is possible without being called from the 'hosting application' first, isn't clear to me. Is that an XPCOM trick? I guess I'll have to read (a lot) more firefox source before I get it.

+1  A: 

I have no idea about Mozilla coding, but here it goes.

According to nsIChannel::asyncOpen(),

Asynchronously open this channel. Data is fed to the specified stream listener as it becomes available. The stream listener's methods are called on the thread that calls asyncOpen and are not called until after asyncOpen returns. If asyncOpen returns successfully, the channel promises to call at least onStartRequest and onStopRequest.

So as the protocol hander, you implement a channel object on your own or redirect it to a channel object, the consumer of the channel calls your channel up using asyncOpen(). Since it is an async call, the idea is to return the control back to the consumer immediately, and it is suppose to call the callbacks as it loads in the data.

I am not sure if I understand what you mean by "but I don't seem to get control back in the thread that AsyncOpen was called in." The thread is created by the consumer of your protocol, and it opens the channel.

Also from nsIChannel::asyncOpen():

If asyncOpen returns successfully, the channel is responsible for keeping itself alive until it has called onStopRequest on aListener or called onChannelRedirect.

Since asyncOpen returns the control right back, the channel itself needs to keep itself alive somewhere.

If you are looking for example code, I found codase to be very useful. See nsIProtocolHandler and nsIChannel. Using that I came across view-source protocol (this implementation may be older, but doesn't matter).

nsViewSourceHandler implements custom channel.

nsViewSourceHandler::NewChannel(nsIURI* uri, nsIChannel* *result)
{
 nsresult rv;

 nsViewSourceChannel* channel;
 rv = nsViewSourceChannel::Create(nsnull, NS_GET_IID(nsIChannel), (void**)&channel);
 if (NS_FAILED(rv)) return rv;

 rv = channel->Init(uri);
 if (NS_FAILED(rv)) {
  NS_RELEASE(channel);
  return rv;
 }

 *result = NS_STATIC_CAST(nsIViewSourceChannel*, channel);
 return NS_OK;
}

Here's the nsViewSourceChannel's AsyncOpen:

nsViewSourceChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *ctxt)
{
 NS_ENSURE_TRUE(mChannel, NS_ERROR_FAILURE);

 mListener = aListener;

 /*
  * We want to add ourselves to the loadgroup before opening
  * mChannel, since we want to make sure we're in the loadgroup
  * when mChannel finishes and fires OnStopRequest()
  */

 nsCOMPtr<nsILoadGroup> loadGroup;
 mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
 if (loadGroup)
  loadGroup->AddRequest(NS_STATIC_CAST(nsIViewSourceChannel*,
            this), nsnull);

 nsresult rv = mChannel->AsyncOpen(this, ctxt);

 if (NS_FAILED(rv) && loadGroup)
  loadGroup->RemoveRequest(NS_STATIC_CAST(nsIViewSourceChannel*,
            this),
         nsnull, rv);

 if (NS_SUCCEEDED(rv)) {
  mOpened = PR_TRUE;
 }

 return rv;
}

Anyway, this is a long and winding way of asking, how are you creating your channel?

eed3si9n
Congratulations, you've earned 300 points. I was silently hoping that noone would answer, and I would get the points back when then bounty expires, but hey, thanks for the answer. It's clarifying. And I learned about codase.com (I was using www.google.com/codesearch and mxr.mozilla.org/firefox before).In the mean time since posting this question, I learned about NS_DispatchToMainThread which answers my question. I was also doing something wrong in the nsACString conversions, so I'm no longer stuck... If you're interested: http://xxm.svn.sourceforge.net/viewvc/xxm/trunk/Delphi/gecko/
Stijn Sanders