Bug 574454 - Cleanup nsNativeThemeWin's GetMinimumWidgetSize a bit. r=roc.
[mozilla-central.git] / uriloader / base / nsURILoader.cpp
blobccb7ff3684a4b30b81d6fcdbb18e5201a47cb00c
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sts=2 sw=2 et cin: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is mozilla.org code.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1999
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsURILoader.h"
40 #include "nsAutoPtr.h"
41 #include "nsIURIContentListener.h"
42 #include "nsIContentHandler.h"
43 #include "nsILoadGroup.h"
44 #include "nsIDocumentLoader.h"
45 #include "nsIWebProgress.h"
46 #include "nsIWebProgressListener.h"
47 #include "nsIIOService.h"
48 #include "nsIServiceManager.h"
49 #include "nsIStreamListener.h"
50 #include "nsIURI.h"
51 #include "nsIChannel.h"
52 #include "nsIInterfaceRequestor.h"
53 #include "nsIInterfaceRequestorUtils.h"
54 #include "nsIProgressEventSink.h"
55 #include "nsIInputStream.h"
56 #include "nsIStreamConverterService.h"
57 #include "nsWeakReference.h"
58 #include "nsIHttpChannel.h"
59 #include "nsIMultiPartChannel.h"
60 #include "netCore.h"
61 #include "nsCRT.h"
62 #include "nsIDocShell.h"
63 #include "nsIDocShellTreeItem.h"
64 #include "nsIDocShellTreeOwner.h"
66 #include "nsXPIDLString.h"
67 #include "nsString.h"
68 #include "nsNetUtil.h"
69 #include "nsIDOMWindowInternal.h"
70 #include "nsReadableUtils.h"
71 #include "nsDOMError.h"
73 #include "nsICategoryManager.h"
74 #include "nsCExternalHandlerService.h" // contains contractids for the helper app service
76 #include "nsIMIMEHeaderParam.h"
77 #include "nsNetCID.h"
79 #include "nsMimeTypes.h"
81 #include "nsDocLoader.h"
83 #include "mozilla/FunctionTimer.h"
84 #ifdef NS_FUNCTION_TIMER
85 #define TIME_URILOADER_FUNCTION(req) \
86 nsCAutoString name__("N/A"); \
87 (req)->GetName(name__); \
88 NS_TIME_FUNCTION_FMT("%s (line %d) (request: %s)", \
89 MOZ_FUNCTION_NAME, \
90 __LINE__, \
91 name__.get())
92 #else
93 #define TIME_URILOADER_FUNCTION(req) do {} while(0)
94 #endif
96 #ifdef PR_LOGGING
97 PRLogModuleInfo* nsURILoader::mLog = nsnull;
98 #endif
100 #define LOG(args) PR_LOG(nsURILoader::mLog, PR_LOG_DEBUG, args)
101 #define LOG_ERROR(args) PR_LOG(nsURILoader::mLog, PR_LOG_ERROR, args)
102 #define LOG_ENABLED() PR_LOG_TEST(nsURILoader::mLog, PR_LOG_DEBUG)
105 * The nsDocumentOpenInfo contains the state required when a single
106 * document is being opened in order to discover the content type...
107 * Each instance remains alive until its target URL has been loaded
108 * (or aborted).
110 class nsDocumentOpenInfo : public nsIStreamListener
112 public:
113 // Needed for nsCOMPtr to work right... Don't call this!
114 nsDocumentOpenInfo();
116 // Real constructor
117 // aFlags is a combination of the flags on nsIURILoader
118 nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
119 PRUint32 aFlags,
120 nsURILoader* aURILoader);
122 NS_DECL_ISUPPORTS
125 * Prepares this object for receiving data. The stream
126 * listener methods of this class must not be called before calling this
127 * method.
129 nsresult Prepare();
131 // Call this (from OnStartRequest) to attempt to find an nsIStreamListener to
132 // take the data off our hands.
133 nsresult DispatchContent(nsIRequest *request, nsISupports * aCtxt);
135 // Call this if we need to insert a stream converter from aSrcContentType to
136 // aOutContentType into the StreamListener chain. DO NOT call it if the two
137 // types are the same, since no conversion is needed in that case.
138 nsresult ConvertData(nsIRequest *request,
139 nsIURIContentListener *aListener,
140 const nsACString & aSrcContentType,
141 const nsACString & aOutContentType);
144 * Function to attempt to use aListener to handle the load. If
145 * PR_TRUE is returned, nothing else needs to be done; if PR_FALSE
146 * is returned, then a different way of handling the load should be
147 * tried.
149 PRBool TryContentListener(nsIURIContentListener* aListener,
150 nsIChannel* aChannel);
152 // nsIRequestObserver methods:
153 NS_DECL_NSIREQUESTOBSERVER
155 // nsIStreamListener methods:
156 NS_DECL_NSISTREAMLISTENER
158 protected:
159 ~nsDocumentOpenInfo();
161 protected:
163 * The first content listener to try dispatching data to. Typically
164 * the listener associated with the entity that originated the load.
166 nsCOMPtr<nsIURIContentListener> m_contentListener;
169 * The stream listener to forward nsIStreamListener notifications
170 * to. This is set once the load is dispatched.
172 nsCOMPtr<nsIStreamListener> m_targetStreamListener;
175 * A pointer to the entity that originated the load. We depend on getting
176 * things like nsIURIContentListeners, nsIDOMWindows, etc off of it.
178 nsCOMPtr<nsIInterfaceRequestor> m_originalContext;
181 * IS_CONTENT_PREFERRED is used for the boolean to pass to CanHandleContent
182 * (also determines whether we use CanHandleContent or IsPreferred).
183 * DONT_RETARGET means that we will only try m_originalContext, no other
184 * listeners.
186 PRUint32 mFlags;
189 * The type of the data we will be trying to dispatch.
191 nsCString mContentType;
194 * Reference to the URILoader service so we can access its list of
195 * nsIURIContentListeners.
197 nsRefPtr<nsURILoader> mURILoader;
200 NS_IMPL_THREADSAFE_ADDREF(nsDocumentOpenInfo)
201 NS_IMPL_THREADSAFE_RELEASE(nsDocumentOpenInfo)
203 NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo)
204 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
205 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
206 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
207 NS_INTERFACE_MAP_END_THREADSAFE
209 nsDocumentOpenInfo::nsDocumentOpenInfo()
211 NS_NOTREACHED("This should never be called\n");
214 nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
215 PRUint32 aFlags,
216 nsURILoader* aURILoader)
217 : m_originalContext(aWindowContext),
218 mFlags(aFlags),
219 mURILoader(aURILoader)
223 nsDocumentOpenInfo::~nsDocumentOpenInfo()
227 nsresult nsDocumentOpenInfo::Prepare()
229 LOG(("[0x%p] nsDocumentOpenInfo::Prepare", this));
231 nsresult rv;
233 // ask our window context if it has a uri content listener...
234 m_contentListener = do_GetInterface(m_originalContext, &rv);
235 return rv;
238 NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
240 TIME_URILOADER_FUNCTION(request);
242 LOG(("[0x%p] nsDocumentOpenInfo::OnStartRequest", this));
244 nsresult rv = NS_OK;
247 // Deal with "special" HTTP responses:
249 // - In the case of a 204 (No Content) or 205 (Reset Content) response, do
250 // not try to find a content handler. Return NS_BINDING_ABORTED to cancel
251 // the request. This has the effect of ensuring that the DocLoader does
252 // not try to interpret this as a real request.
254 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request, &rv));
256 if (NS_SUCCEEDED(rv)) {
257 PRUint32 responseCode = 0;
259 rv = httpChannel->GetResponseStatus(&responseCode);
261 if (NS_FAILED(rv)) {
262 LOG_ERROR((" Failed to get HTTP response status"));
264 // behave as in the canceled case
265 return NS_OK;
268 LOG((" HTTP response status: %d", responseCode));
270 if (204 == responseCode || 205 == responseCode) {
271 return NS_BINDING_ABORTED;
276 // Make sure that the transaction has succeeded, so far...
278 nsresult status;
280 rv = request->GetStatus(&status);
282 NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!");
283 if (NS_FAILED(rv)) return rv;
285 if (NS_FAILED(status)) {
286 LOG_ERROR((" Request failed, status: 0x%08X", rv));
289 // The transaction has already reported an error - so it will be torn
290 // down. Therefore, it is not necessary to return an error code...
292 return NS_OK;
295 rv = DispatchContent(request, aCtxt);
297 LOG((" After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08X", m_targetStreamListener.get(), rv));
299 NS_ASSERTION(NS_SUCCEEDED(rv) || !m_targetStreamListener,
300 "Must not have an m_targetStreamListener with a failure return!");
302 NS_ENSURE_SUCCESS(rv, rv);
304 if (m_targetStreamListener)
305 rv = m_targetStreamListener->OnStartRequest(request, aCtxt);
307 LOG((" OnStartRequest returning: 0x%08X", rv));
309 return rv;
312 NS_IMETHODIMP nsDocumentOpenInfo::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
313 nsIInputStream * inStr, PRUint32 sourceOffset, PRUint32 count)
315 TIME_URILOADER_FUNCTION(request);
317 // if we have retarged to the end stream listener, then forward the call....
318 // otherwise, don't do anything
320 nsresult rv = NS_OK;
322 if (m_targetStreamListener)
323 rv = m_targetStreamListener->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
324 return rv;
327 NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
328 nsresult aStatus)
330 TIME_URILOADER_FUNCTION(request);
332 LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this));
334 if ( m_targetStreamListener)
336 nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener);
338 // If this is a multipart stream, we could get another
339 // OnStartRequest after this... reset state.
340 m_targetStreamListener = 0;
341 mContentType.Truncate();
342 listener->OnStopRequest(request, aCtxt, aStatus);
345 // Remember...
346 // In the case of multiplexed streams (such as multipart/x-mixed-replace)
347 // these stream listener methods could be called again :-)
349 return NS_OK;
352 nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest *request, nsISupports * aCtxt)
354 TIME_URILOADER_FUNCTION(request);
356 LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this, mContentType.get()));
358 NS_PRECONDITION(!m_targetStreamListener,
359 "Why do we already have a target stream listener?");
361 nsresult rv;
362 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
363 if (!aChannel) {
364 LOG_ERROR((" Request is not a channel. Bailing."));
365 return NS_ERROR_FAILURE;
368 NS_NAMED_LITERAL_CSTRING(anyType, "*/*");
369 if (mContentType.IsEmpty() || mContentType == anyType) {
370 rv = aChannel->GetContentType(mContentType);
371 if (NS_FAILED(rv)) return rv;
372 LOG((" Got type from channel: '%s'", mContentType.get()));
375 PRBool isGuessFromExt =
376 mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT);
377 if (isGuessFromExt) {
378 // Reset to application/octet-stream for now; no one other than the
379 // external helper app service should see APPLICATION_GUESS_FROM_EXT.
380 mContentType = APPLICATION_OCTET_STREAM;
381 aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
384 // Check whether the data should be forced to be handled externally. This
385 // could happen because the Content-Disposition header is set so, or, in the
386 // future, because the user has specified external handling for the MIME
387 // type.
388 PRBool forceExternalHandling = PR_FALSE;
389 nsCAutoString disposition;
390 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
391 nsCOMPtr<nsIURI> uri;
392 if (httpChannel)
394 rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-disposition"),
395 disposition);
396 httpChannel->GetURI(getter_AddRefs(uri));
398 else
400 nsCOMPtr<nsIMultiPartChannel> multipartChannel(do_QueryInterface(request));
401 if (multipartChannel)
403 rv = multipartChannel->GetContentDisposition(disposition);
404 } else {
405 // Soon-to-be common way to get Disposition: right now only JARChannel
406 rv = NS_GetContentDisposition(request, disposition);
410 LOG((" Disposition header: '%s'", disposition.get()));
412 if (NS_SUCCEEDED(rv) && !disposition.IsEmpty())
414 nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
415 if (NS_SUCCEEDED(rv))
417 nsCAutoString fallbackCharset;
418 if (uri)
419 uri->GetOriginCharset(fallbackCharset);
420 nsAutoString dispToken;
421 // Get the disposition type
422 rv = mimehdrpar->GetParameter(disposition, "", fallbackCharset,
423 PR_TRUE, nsnull, dispToken);
424 // RFC 2183, section 2.8 says that an unknown disposition
425 // value should be treated as "attachment"
426 // XXXbz this code is duplicated in GetFilenameAndExtensionFromChannel in
427 // nsExternalHelperAppService. Factor it out!
428 if (NS_FAILED(rv) ||
429 (!dispToken.IsEmpty() &&
430 !dispToken.LowerCaseEqualsLiteral("inline") &&
431 // Broken sites just send
432 // Content-Disposition: filename="file"
433 // without a disposition token... screen those out.
434 !dispToken.EqualsIgnoreCase("filename", 8) &&
435 // Also in use is Content-Disposition: name="file"
436 !dispToken.EqualsIgnoreCase("name", 4)))
437 // We have a content-disposition of "attachment" or unknown
438 forceExternalHandling = PR_TRUE;
442 LOG((" forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
444 // We're going to try to find a contentListener that can handle our data
445 nsCOMPtr<nsIURIContentListener> contentListener;
446 // The type or data the contentListener wants.
447 nsXPIDLCString desiredContentType;
449 if (!forceExternalHandling)
452 // First step: See whether m_contentListener wants to handle this
453 // content type.
455 if (m_contentListener && TryContentListener(m_contentListener, aChannel)) {
456 LOG((" Success! Our default listener likes this type"));
457 // All done here
458 return NS_OK;
461 // If we aren't allowed to try other listeners, just skip through to
462 // trying to convert the data.
463 if (!(mFlags & nsIURILoader::DONT_RETARGET)) {
466 // Second step: See whether some other registered listener wants
467 // to handle this content type.
469 PRInt32 count = mURILoader->m_listeners.Count();
470 nsCOMPtr<nsIURIContentListener> listener;
471 for (PRInt32 i = 0; i < count; i++) {
472 listener = do_QueryReferent(mURILoader->m_listeners[i]);
473 if (listener) {
474 if (TryContentListener(listener, aChannel)) {
475 LOG((" Found listener registered on the URILoader"));
476 return NS_OK;
478 } else {
479 // remove from the listener list, reset i and update count
480 mURILoader->m_listeners.RemoveObjectAt(i--);
481 --count;
486 // Third step: Try to find a content listener that has not yet had
487 // the chance to register, as it is contained in a not-yet-loaded
488 // module, but which has registered a contract ID.
490 nsCOMPtr<nsICategoryManager> catman =
491 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
492 if (catman) {
493 nsXPIDLCString contractidString;
494 rv = catman->GetCategoryEntry(NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY,
495 mContentType.get(),
496 getter_Copies(contractidString));
497 if (NS_SUCCEEDED(rv) && !contractidString.IsEmpty()) {
498 LOG((" Listener contractid for '%s' is '%s'",
499 mContentType.get(), contractidString.get()));
501 listener = do_CreateInstance(contractidString);
502 LOG((" Listener from category manager: 0x%p", listener.get()));
504 if (listener && TryContentListener(listener, aChannel)) {
505 LOG((" Listener from category manager likes this type"));
506 return NS_OK;
512 // Fourth step: try to find an nsIContentHandler for our type.
514 nsCAutoString handlerContractID (NS_CONTENT_HANDLER_CONTRACTID_PREFIX);
515 handlerContractID += mContentType;
517 nsCOMPtr<nsIContentHandler> contentHandler =
518 do_CreateInstance(handlerContractID.get());
519 if (contentHandler) {
520 LOG((" Content handler found"));
521 rv = contentHandler->HandleContent(mContentType.get(),
522 m_originalContext, request);
523 // XXXbz returning an error code to represent handling the
524 // content is just bizarre!
525 if (rv != NS_ERROR_WONT_HANDLE_CONTENT) {
526 if (NS_FAILED(rv)) {
527 // The content handler has unexpectedly failed. Cancel the request
528 // just in case the handler didn't...
529 LOG((" Content handler failed. Aborting load"));
530 request->Cancel(rv);
532 #ifdef PR_LOGGING
533 else {
534 LOG((" Content handler taking over load"));
536 #endif
538 return rv;
541 } else {
542 LOG((" DONT_RETARGET flag set, so skipped over random other content "
543 "listeners and content handlers"));
547 // Fifth step: If no listener prefers this type, see if any stream
548 // converters exist to transform this content type into
549 // some other.
551 // Don't do this if the server sent us a MIME type of "*/*" because they saw
552 // it in our Accept header and got confused.
553 // XXXbz have to be careful here; may end up in some sort of bizarre infinite
554 // decoding loop.
555 if (mContentType != anyType) {
556 rv = ConvertData(request, m_contentListener, mContentType, anyType);
557 if (NS_FAILED(rv)) {
558 m_targetStreamListener = nsnull;
559 } else if (m_targetStreamListener) {
560 // We found a converter for this MIME type. We'll just pump data into it
561 // and let the downstream nsDocumentOpenInfo handle things.
562 LOG((" Converter taking over now"));
563 return NS_OK;
568 NS_ASSERTION(!m_targetStreamListener,
569 "If we found a listener, why are we not using it?");
571 if (mFlags & nsIURILoader::DONT_RETARGET) {
572 LOG((" External handling forced or (listener not interested and no "
573 "stream converter exists), and retargeting disallowed -> aborting"));
574 return NS_ERROR_WONT_HANDLE_CONTENT;
577 // Before dispatching to the external helper app service, check for an HTTP
578 // error page. If we got one, we don't want to handle it with a helper app,
579 // really.
580 if (httpChannel) {
581 PRBool requestSucceeded;
582 httpChannel->GetRequestSucceeded(&requestSucceeded);
583 if (!requestSucceeded) {
584 // returning error from OnStartRequest will cancel the channel
585 return NS_ERROR_FILE_NOT_FOUND;
589 // Sixth step:
591 // All attempts to dispatch this content have failed. Just pass it off to
592 // the helper app service.
594 nsCOMPtr<nsIExternalHelperAppService> helperAppService =
595 do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
596 if (helperAppService) {
597 LOG((" Passing load off to helper app service"));
599 // Set these flags to indicate that the channel has been targeted and that
600 // we are not using the original consumer.
601 nsLoadFlags loadFlags = 0;
602 request->GetLoadFlags(&loadFlags);
603 request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI
604 | nsIChannel::LOAD_TARGETED);
606 if (isGuessFromExt) {
607 mContentType = APPLICATION_GUESS_FROM_EXT;
608 aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_GUESS_FROM_EXT));
611 rv = helperAppService->DoContent(mContentType,
612 request,
613 m_originalContext,
614 PR_FALSE,
615 getter_AddRefs(m_targetStreamListener));
616 if (NS_FAILED(rv)) {
617 request->SetLoadFlags(loadFlags);
618 m_targetStreamListener = nsnull;
622 NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv),
623 "There is no way we should be successful at this point without a m_targetStreamListener");
624 return rv;
627 nsresult
628 nsDocumentOpenInfo::ConvertData(nsIRequest *request,
629 nsIURIContentListener* aListener,
630 const nsACString& aSrcContentType,
631 const nsACString& aOutContentType)
633 TIME_URILOADER_FUNCTION(request);
635 LOG(("[0x%p] nsDocumentOpenInfo::ConvertData from '%s' to '%s'", this,
636 PromiseFlatCString(aSrcContentType).get(),
637 PromiseFlatCString(aOutContentType).get()));
639 NS_PRECONDITION(aSrcContentType != aOutContentType,
640 "ConvertData called when the two types are the same!");
641 nsresult rv = NS_OK;
643 nsCOMPtr<nsIStreamConverterService> StreamConvService =
644 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
645 if (NS_FAILED(rv)) return rv;
647 LOG((" Got converter service"));
649 // When applying stream decoders, it is necessary to "insert" an
650 // intermediate nsDocumentOpenInfo instance to handle the targeting of
651 // the "final" stream or streams.
653 // For certain content types (ie. multi-part/x-mixed-replace) the input
654 // stream is split up into multiple destination streams. This
655 // intermediate instance is used to target these "decoded" streams...
657 nsCOMPtr<nsDocumentOpenInfo> nextLink =
658 new nsDocumentOpenInfo(m_originalContext, mFlags, mURILoader);
659 if (!nextLink) return NS_ERROR_OUT_OF_MEMORY;
661 LOG((" Downstream DocumentOpenInfo would be: 0x%p", nextLink.get()));
663 // Make sure nextLink starts with the contentListener that said it wanted the
664 // results of this decode.
665 nextLink->m_contentListener = aListener;
666 // Also make sure it has to look for a stream listener to pump data into.
667 nextLink->m_targetStreamListener = nsnull;
669 // Make sure that nextLink treats the data as aOutContentType when
670 // dispatching; that way even if the stream converters don't change the type
671 // on the channel we will still do the right thing. If aOutContentType is
672 // */*, that's OK -- that will just indicate to nextLink that it should get
673 // the type off the channel.
674 nextLink->mContentType = aOutContentType;
676 // The following call sets m_targetStreamListener to the input end of the
677 // stream converter and sets the output end of the stream converter to
678 // nextLink. As we pump data into m_targetStreamListener the stream
679 // converter will convert it and pass the converted data to nextLink.
680 return StreamConvService->AsyncConvertData(PromiseFlatCString(aSrcContentType).get(),
681 PromiseFlatCString(aOutContentType).get(),
682 nextLink,
683 request,
684 getter_AddRefs(m_targetStreamListener));
687 PRBool
688 nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener,
689 nsIChannel* aChannel)
691 TIME_URILOADER_FUNCTION(aChannel);
693 LOG(("[0x%p] nsDocumentOpenInfo::TryContentListener; mFlags = 0x%x",
694 this, mFlags));
696 NS_PRECONDITION(aListener, "Must have a non-null listener");
697 NS_PRECONDITION(aChannel, "Must have a channel");
699 PRBool listenerWantsContent = PR_FALSE;
700 nsXPIDLCString typeToUse;
702 if (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) {
703 aListener->IsPreferred(mContentType.get(),
704 getter_Copies(typeToUse),
705 &listenerWantsContent);
706 } else {
707 aListener->CanHandleContent(mContentType.get(), PR_FALSE,
708 getter_Copies(typeToUse),
709 &listenerWantsContent);
711 if (!listenerWantsContent) {
712 LOG((" Listener is not interested"));
713 return PR_FALSE;
716 if (!typeToUse.IsEmpty() && typeToUse != mContentType) {
717 // Need to do a conversion here.
719 nsresult rv = ConvertData(aChannel, aListener, mContentType, typeToUse);
721 if (NS_FAILED(rv)) {
722 // No conversion path -- we don't want this listener, if we got one
723 m_targetStreamListener = nsnull;
726 LOG((" Found conversion: %s", m_targetStreamListener ? "yes" : "no"));
728 // m_targetStreamListener is now the input end of the converter, and we can
729 // just pump the data in there, if it exists. If it does not, we need to
730 // try other nsIURIContentListeners.
731 return m_targetStreamListener != nsnull;
734 // At this point, aListener wants data of type mContentType. Let 'em have
735 // it. But first, if we are retargeting, set an appropriate flag on the
736 // channel
737 nsLoadFlags loadFlags = 0;
738 aChannel->GetLoadFlags(&loadFlags);
740 // Set this flag to indicate that the channel has been targeted at a final
741 // consumer. This load flag is tested in nsDocLoader::OnProgress.
742 nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED;
744 nsCOMPtr<nsIURIContentListener> originalListener =
745 do_GetInterface(m_originalContext);
746 if (originalListener != aListener) {
747 newLoadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI;
749 aChannel->SetLoadFlags(loadFlags | newLoadFlags);
751 PRBool abort = PR_FALSE;
752 PRBool isPreferred = (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) != 0;
753 nsresult rv = aListener->DoContent(mContentType.get(),
754 isPreferred,
755 aChannel,
756 getter_AddRefs(m_targetStreamListener),
757 &abort);
759 if (NS_FAILED(rv)) {
760 LOG_ERROR((" DoContent failed"));
762 // Unset the RETARGETED_DOCUMENT_URI flag if we set it...
763 aChannel->SetLoadFlags(loadFlags);
764 m_targetStreamListener = nsnull;
765 return PR_FALSE;
768 if (abort) {
769 // Nothing else to do here -- aListener is handling it all. Make
770 // sure m_targetStreamListener is null so we don't do anything
771 // after this point.
772 LOG((" Listener has aborted the load"));
773 m_targetStreamListener = nsnull;
776 NS_ASSERTION(abort || m_targetStreamListener, "DoContent returned no listener?");
778 // aListener is handling the load from this point on.
779 return PR_TRUE;
783 ///////////////////////////////////////////////////////////////////////////////////////////////
784 // Implementation of nsURILoader
785 ///////////////////////////////////////////////////////////////////////////////////////////////
787 nsURILoader::nsURILoader()
789 #ifdef PR_LOGGING
790 if (!mLog) {
791 mLog = PR_NewLogModule("URILoader");
793 #endif
796 nsURILoader::~nsURILoader()
800 NS_IMPL_ADDREF(nsURILoader)
801 NS_IMPL_RELEASE(nsURILoader)
803 NS_INTERFACE_MAP_BEGIN(nsURILoader)
804 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURILoader)
805 NS_INTERFACE_MAP_ENTRY(nsIURILoader)
806 NS_INTERFACE_MAP_END
808 NS_IMETHODIMP nsURILoader::RegisterContentListener(nsIURIContentListener * aContentListener)
810 nsresult rv = NS_OK;
812 nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
813 NS_ASSERTION(weakListener, "your URIContentListener must support weak refs!\n");
815 if (weakListener)
816 m_listeners.AppendObject(weakListener);
818 return rv;
821 NS_IMETHODIMP nsURILoader::UnRegisterContentListener(nsIURIContentListener * aContentListener)
823 nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
824 if (weakListener)
825 m_listeners.RemoveObject(weakListener);
827 return NS_OK;
831 NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel *channel,
832 PRBool aIsContentPreferred,
833 nsIInterfaceRequestor *aWindowContext)
835 NS_ENSURE_ARG_POINTER(channel);
837 TIME_URILOADER_FUNCTION(channel);
839 #ifdef PR_LOGGING
840 if (LOG_ENABLED()) {
841 nsCOMPtr<nsIURI> uri;
842 channel->GetURI(getter_AddRefs(uri));
843 nsCAutoString spec;
844 uri->GetAsciiSpec(spec);
845 LOG(("nsURILoader::OpenURI for %s", spec.get()));
847 #endif
849 nsCOMPtr<nsIStreamListener> loader;
850 nsresult rv = OpenChannel(channel,
851 aIsContentPreferred ? IS_CONTENT_PREFERRED : 0,
852 aWindowContext,
853 PR_FALSE,
854 getter_AddRefs(loader));
856 if (NS_SUCCEEDED(rv)) {
857 // this method is not complete!!! Eventually, we should first go
858 // to the content listener and ask them for a protocol handler...
859 // if they don't give us one, we need to go to the registry and get
860 // the preferred protocol handler.
862 // But for now, I'm going to let necko do the work for us....
863 rv = channel->AsyncOpen(loader, nsnull);
865 // no content from this load - that's OK.
866 if (rv == NS_ERROR_NO_CONTENT) {
867 LOG((" rv is NS_ERROR_NO_CONTENT -- doing nothing"));
868 rv = NS_OK;
870 } else if (rv == NS_ERROR_WONT_HANDLE_CONTENT) {
871 // Not really an error, from this method's point of view
872 rv = NS_OK;
874 return rv;
877 nsresult nsURILoader::OpenChannel(nsIChannel* channel,
878 PRUint32 aFlags,
879 nsIInterfaceRequestor* aWindowContext,
880 PRBool aChannelIsOpen,
881 nsIStreamListener** aListener)
883 NS_ASSERTION(channel, "Trying to open a null channel!");
884 NS_ASSERTION(aWindowContext, "Window context must not be null");
886 TIME_URILOADER_FUNCTION(channel);
888 #ifdef PR_LOGGING
889 if (LOG_ENABLED()) {
890 nsCOMPtr<nsIURI> uri;
891 channel->GetURI(getter_AddRefs(uri));
892 nsCAutoString spec;
893 uri->GetAsciiSpec(spec);
894 LOG(("nsURILoader::OpenChannel for %s", spec.get()));
896 #endif
898 // Let the window context's uriListener know that the open is starting. This
899 // gives that window a chance to abort the load process.
900 nsCOMPtr<nsIURIContentListener> winContextListener(do_GetInterface(aWindowContext));
901 if (winContextListener) {
902 nsCOMPtr<nsIURI> uri;
903 channel->GetURI(getter_AddRefs(uri));
904 if (uri) {
905 PRBool doAbort = PR_FALSE;
906 winContextListener->OnStartURIOpen(uri, &doAbort);
908 if (doAbort) {
909 LOG((" OnStartURIOpen aborted load"));
910 return NS_ERROR_WONT_HANDLE_CONTENT;
915 // we need to create a DocumentOpenInfo object which will go ahead and open
916 // the url and discover the content type....
917 nsCOMPtr<nsDocumentOpenInfo> loader =
918 new nsDocumentOpenInfo(aWindowContext, aFlags, this);
920 if (!loader) return NS_ERROR_OUT_OF_MEMORY;
922 // Set the correct loadgroup on the channel
923 nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext));
925 if (!loadGroup) {
926 // XXXbz This context is violating what we'd like to be the new uriloader
927 // api.... Set up a nsDocLoader to handle the loadgroup for this context.
928 // This really needs to go away!
929 nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(aWindowContext));
930 if (listener) {
931 nsCOMPtr<nsISupports> cookie;
932 listener->GetLoadCookie(getter_AddRefs(cookie));
933 if (!cookie) {
934 nsRefPtr<nsDocLoader> newDocLoader = new nsDocLoader();
935 if (!newDocLoader)
936 return NS_ERROR_OUT_OF_MEMORY;
937 nsresult rv = newDocLoader->Init();
938 if (NS_FAILED(rv))
939 return rv;
940 rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader);
941 if (NS_FAILED(rv))
942 return rv;
943 cookie = nsDocLoader::GetAsSupports(newDocLoader);
944 listener->SetLoadCookie(cookie);
946 loadGroup = do_GetInterface(cookie);
950 // If the channel is pending, then we need to remove it from its current
951 // loadgroup
952 nsCOMPtr<nsILoadGroup> oldGroup;
953 channel->GetLoadGroup(getter_AddRefs(oldGroup));
954 if (aChannelIsOpen && !SameCOMIdentity(oldGroup, loadGroup)) {
955 // It is important to add the channel to the new group before
956 // removing it from the old one, so that the load isn't considered
957 // done as soon as the request is removed.
958 loadGroup->AddRequest(channel, nsnull);
960 if (oldGroup) {
961 oldGroup->RemoveRequest(channel, nsnull, NS_BINDING_RETARGETED);
965 channel->SetLoadGroup(loadGroup);
967 // prepare the loader for receiving data
968 nsresult rv = loader->Prepare();
969 if (NS_SUCCEEDED(rv))
970 NS_ADDREF(*aListener = loader);
971 return rv;
974 NS_IMETHODIMP nsURILoader::OpenChannel(nsIChannel* channel,
975 PRUint32 aFlags,
976 nsIInterfaceRequestor* aWindowContext,
977 nsIStreamListener** aListener)
979 PRBool pending;
980 if (NS_FAILED(channel->IsPending(&pending))) {
981 pending = PR_FALSE;
984 return OpenChannel(channel, aFlags, aWindowContext, pending, aListener);
987 NS_IMETHODIMP nsURILoader::Stop(nsISupports* aLoadCookie)
989 nsresult rv;
990 nsCOMPtr<nsIDocumentLoader> docLoader;
992 NS_ENSURE_ARG_POINTER(aLoadCookie);
994 docLoader = do_GetInterface(aLoadCookie, &rv);
995 if (docLoader) {
996 rv = docLoader->Stop();
998 return rv;