This document describes the beginning of the document loading process. We start with the request to load a particular link in a particular window, and proceed up to the point at which the data stream is dispatched to the proper handler. The final goal is to find the correct stream listener to pump the data into when necko calls OnDataAvailable (e.g., we may find the HTML parser as the stream listener to give the data to).
This document focuses on the interaction of three classes with each other, but other Mozilla components are also involved.
nsDocShell/nsWebShellimplements too many interfaces to count; the ones that matter to us most are
- This is a service implementing
nsIURILoader. It keeps track of currently pending loads and registered content listeners. Some facilities are provided for starting loads, canceling loads, and other such micromanagement.
- This class encapsulates all the information related to a particular load. There is basically one of these per load (though see the section on stream converters).
nsIStreamListenerand proxies the
nsIStreamListenercalls over to the "real" stream listener once one is chosen. This is the class that handles deciding where the data from the load should go.
- This is the Mozilla networking library. This handles actually going out and getting the data. It also handles determining the MIME type of the data, which is used to decide who gets the data.
- Category Manager
- This is used in a last-ditch attempt to find a content listener.
- We try to find one of these which can handle data of the type we're looking at (that is, it can give us an
nsIStreamListenerto pump data into). Most often, the one we find is
nsDSURIContentListener, which corresponds to a docshell and handles most of the data types that Mozilla handles internally.
- If we can't do anything else with a load, we give it to the
nsIExternalHelperAppServiceand let it look for a helper app, put up the "what do I do now?" dialog, and so forth.
Bird's Eye View
<map id="loadDiagramMap"> <area alt="(13) DoContent()" coords="534,239,715,300" href="#nsIExternalHelperAppService::DoContent"> <area alt="(10) GetCategoryEntry()" coords="575,418,821,418,821,455,629,455,629,484,575,484" href="#CategoryManager" shape="poly"> <area alt="(12)" coords="539,133,583,163" href="#stream-converter"> <area alt="(11)" coords="485,133,537,163" href="#ContentHandler"> <area alt="(9)" coords="445,132,484,165" href="#nsDocumentOpenInfo::DispatchContent"> <area alt="(8)" coords="405,133,439,162" href="#OnStartRequest-innards"> <area alt="(7) OnStartRequest()" coords="639,129,703,129,703,165,833,165,833,204,639,204" href="#OnStartRequest" shape="poly"> <area alt="(6) AsyncOpen()" coords="637,121,709,121,709,96,783,96,783,58,637,58" href="#AsyncOpen" shape="poly"> <area alt="(5) Open()" coords="311,306,432,371" href="#Open"> <area alt="(4)" coords="90,384,127,417" href="#openURI-innards"> <area alt="(0) RegisterContentListener()" coords="37,474,346,474,346,505,88,505,88,535,37,535" href="#RegisterContentListener" shape="poly"> <area alt="(3) openURI() (nsURILoader)" coords="5,207,312,269" href="#openURI"> <area alt="(2)" coords="102,114,139,148" href="#InternalLoad"> <area alt="(1) loadURI/onLinkClick" coords="77,5,449,59" href="#loadURI"> <area alt="nsIExternalHelperAppService" coords="527,305,839,339" href="#nsIExternalHelperAppService"> <area alt="Category Manager" coords="683,467,807,527" href="#nsCategoryManager"> <area alt="nsDocumentOpenInfo" coords="371,71,635,185" href="#nsDocumentOpenInfo"> <area alt="Necko" coords="721,113,821,157" href="#necko"> <area alt="nsURILoader" coords="23,335,215,455" href="#nsURILoader"> <area coords="227,515,485,575" href="#nsIURIContentListener"> <area alt="nsDocShell" coords="47,83,203,153" href="#nsDocShell"> </map>
During startup and component initialization, components register themselves with the URILoader via
RegisterContentListener. These registered listeners are used later during content dispatch.
API calls to load a new URI. These can come in via
nsIWebNavigation(a scriptable embedding interface) or
nsILinkHandler(an internal interface used for link clicks). Both interfaces are implemented by
nsWebShell/nsDocShell. These API calls will typically pass in a URI string or object to load, and may include information like the name of the target frame (for
<a target="something">, e.g.).
nsDocShell::InternalLoad()handles targeting to the correct docshell (if the load has a target window associated with it), scrolling to anchors (if the load is an anchor within the current page), and session history issues. It calls
DoURILoad()creates a channel, massages it to have the right POST data, load flags, cache key, referrer, etc. It then passes the channel to
DoChannelLoad, which does some more flag massaging and then calls into the URILoader.
nsURILoader::OpenURIgets a channel to open, a boolean indicating whether this load is the result of a link click, and an
nsISupports"window context" (the docshell triggering the load, actually, but in drag and heavy makeup).
nsIURIContentListenerhanging off the window context, if any, of the start of the load; this gives embedders a chance to abort the load if this URI type is something they want to handle in the embedding app. If the load is not aborted, we create an
nsDocumentOpenInfoobject for this load, passing it the "this is a link click" boolean and the window context.
nsDocumentOpenInfo::Openis passed the channel to open. It calls
GetInterfaceon the window context to get and save that context's
nsDocumentOpenInfojust opens the channel, setting itself as the stream listener.
OnStartRequestnotification comes back from Necko
nsDocumentOpenInfo::OnStartRequestchecks the response status code on the channel for HTTP channels and drops the load on the floor if warranted (e.g. 204 or 205 responses). Then it calls
nsDocumentOpenInfo::DispatchContentstarts doing the real dirty work. First it checks whether the channel has "Content-Disposition: attachment" set. If so, it skips trying to find an internal viewer and goes right over to looking for a stream converter. Otherwise,
DispatchContentgoes through a three-step process to try to find the correct listener.
The basic idea is that
nsDocumentOpenInfo::TryContentListeneron various content listeners; if that returns true, we have found the right listener and we are done.
TryContentListenermakes use of the
DoContent()on the listener if it claims to handle the data, as well as hooking up a stream converter if the listener asks for one. If any of those steps fails, the function returns false so that another content listener will be looked for.
The first content listener we check is the content listener associated with our window context (the docshell that initiated the load). If this can't handle the content type, we loop over our stored list of possible listeners (previously registered via
RegisterContentListener) and ask each one in turn whether it can handle the data.
If we still have not found an
nsIURIContentListener, we ask the category manager whether it has an entry for the desired content type under the
NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRYkey. If it does, we instantiate that component using its contractid (via
CreateInstance) and call
If we find no content listener willing to handle the data, we look for a content handler (using
NS_CONTENT_HANDLER_CONTRACTID_PREFIX + aContentTypeand expecting back an
nsIContentHandler). If a handler is found, we call
HandleContenton it to give it a chance to take over the load. If it does so, we abort the entire dispatch process right here and cancel the necko request; we will not be getting a stream listener to stream data into, since the content handler has taken over completely.
If at this point we do not have an
nsIURIContentListener, we have failed to find a way to handle this content type. If that is the case,
DispatchContenttries to convert the data to some other content type by looking for a stream converter (implementing
nsIStreamConverter) that takes our content type as input and outputs the magic type "*/*" (which just means we'll take anything it can give us). If the type of the data is "application/x-unknown-content-type" (another magic type), this is where nsUnknownDecoder would be instantiated.
The conversion attempt is made by calling
ConvertData. This creates a new
nsDocumentOpenInfoobject and sets it as the output streamlistener of the converter. This way, once we know what type we've managed to get we can attempt to redispatch it.
If a converter is found, we hook that up as our stream listener and are done -- we just need to pump data into it and let the downstream
nsDocumentOpenInfohandle the final dispatch.
If we still do not have a stream listener, that means that we failed to find an
nsIContentHandlerfor this type. Give the load to the helper app service; this will return an
nsIStreamListenerthat we can use.