Part of bug 382647 (move xpfe bookmarks to suite) and bug 393842 (move xpfe search...
[mozilla-central.git] / uriloader / base / nsURILoader.cpp
blobc18554e3568069452d40ab7311346cc0b465bebd
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"
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 #ifdef PR_LOGGING
84 PRLogModuleInfo* nsURILoader::mLog = nsnull;
85 #endif
87 #define LOG(args) PR_LOG(nsURILoader::mLog, PR_LOG_DEBUG, args)
88 #define LOG_ERROR(args) PR_LOG(nsURILoader::mLog, PR_LOG_ERROR, args)
89 #define LOG_ENABLED() PR_LOG_TEST(nsURILoader::mLog, PR_LOG_DEBUG)
91 /**
92 * The nsDocumentOpenInfo contains the state required when a single
93 * document is being opened in order to discover the content type...
94 * Each instance remains alive until its target URL has been loaded
95 * (or aborted).
97 class nsDocumentOpenInfo : public nsIStreamListener
99 public:
100 // Needed for nsCOMPtr to work right... Don't call this!
101 nsDocumentOpenInfo();
103 // Real constructor
104 // aFlags is a combination of the flags on nsIURILoader
105 nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
106 PRUint32 aFlags,
107 nsURILoader* aURILoader);
109 NS_DECL_ISUPPORTS
112 * Prepares this object for receiving data. The stream
113 * listener methods of this class must not be called before calling this
114 * method.
116 nsresult Prepare();
118 // Call this (from OnStartRequest) to attempt to find an nsIStreamListener to
119 // take the data off our hands.
120 nsresult DispatchContent(nsIRequest *request, nsISupports * aCtxt);
122 // Call this if we need to insert a stream converter from aSrcContentType to
123 // aOutContentType into the StreamListener chain. DO NOT call it if the two
124 // types are the same, since no conversion is needed in that case.
125 nsresult ConvertData(nsIRequest *request,
126 nsIURIContentListener *aListener,
127 const nsACString & aSrcContentType,
128 const nsACString & aOutContentType);
131 * Function to attempt to use aListener to handle the load. If
132 * PR_TRUE is returned, nothing else needs to be done; if PR_FALSE
133 * is returned, then a different way of handling the load should be
134 * tried.
136 PRBool TryContentListener(nsIURIContentListener* aListener,
137 nsIChannel* aChannel);
139 // nsIRequestObserver methods:
140 NS_DECL_NSIREQUESTOBSERVER
142 // nsIStreamListener methods:
143 NS_DECL_NSISTREAMLISTENER
145 protected:
146 ~nsDocumentOpenInfo();
148 protected:
150 * The first content listener to try dispatching data to. Typically
151 * the listener associated with the entity that originated the load.
153 nsCOMPtr<nsIURIContentListener> m_contentListener;
156 * The stream listener to forward nsIStreamListener notifications
157 * to. This is set once the load is dispatched.
159 nsCOMPtr<nsIStreamListener> m_targetStreamListener;
162 * A pointer to the entity that originated the load. We depend on getting
163 * things like nsIURIContentListeners, nsIDOMWindows, etc off of it.
165 nsCOMPtr<nsIInterfaceRequestor> m_originalContext;
168 * IS_CONTENT_PREFERRED is used for the boolean to pass to CanHandleContent
169 * (also determines whether we use CanHandleContent or IsPreferred).
170 * DONT_RETARGET means that we will only try m_originalContext, no other
171 * listeners.
173 PRUint32 mFlags;
176 * The type of the data we will be trying to dispatch.
178 nsCString mContentType;
181 * Reference to the URILoader service so we can access its list of
182 * nsIURIContentListeners.
184 nsRefPtr<nsURILoader> mURILoader;
187 NS_IMPL_THREADSAFE_ADDREF(nsDocumentOpenInfo)
188 NS_IMPL_THREADSAFE_RELEASE(nsDocumentOpenInfo)
190 NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo)
191 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
192 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
193 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
194 NS_INTERFACE_MAP_END_THREADSAFE
196 nsDocumentOpenInfo::nsDocumentOpenInfo()
198 NS_NOTREACHED("This should never be called\n");
201 nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
202 PRUint32 aFlags,
203 nsURILoader* aURILoader)
204 : m_originalContext(aWindowContext),
205 mFlags(aFlags),
206 mURILoader(aURILoader)
210 nsDocumentOpenInfo::~nsDocumentOpenInfo()
214 nsresult nsDocumentOpenInfo::Prepare()
216 LOG(("[0x%p] nsDocumentOpenInfo::Prepare", this));
218 nsresult rv;
220 // ask our window context if it has a uri content listener...
221 m_contentListener = do_GetInterface(m_originalContext, &rv);
222 return rv;
225 NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
227 LOG(("[0x%p] nsDocumentOpenInfo::OnStartRequest", this));
229 nsresult rv = NS_OK;
232 // Deal with "special" HTTP responses:
234 // - In the case of a 204 (No Content) or 205 (Reset Content) response, do
235 // not try to find a content handler. Return NS_BINDING_ABORTED to cancel
236 // the request. This has the effect of ensuring that the DocLoader does
237 // not try to interpret this as a real request.
239 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request, &rv));
241 if (NS_SUCCEEDED(rv)) {
242 PRUint32 responseCode = 0;
244 rv = httpChannel->GetResponseStatus(&responseCode);
246 if (NS_FAILED(rv)) {
247 LOG_ERROR((" Failed to get HTTP response status"));
249 // behave as in the canceled case
250 return NS_OK;
253 LOG((" HTTP response status: %d", responseCode));
255 if (204 == responseCode || 205 == responseCode) {
256 return NS_BINDING_ABORTED;
261 // Make sure that the transaction has succeeded, so far...
263 nsresult status;
265 rv = request->GetStatus(&status);
267 NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!");
268 if (NS_FAILED(rv)) return rv;
270 if (NS_FAILED(status)) {
271 LOG_ERROR((" Request failed, status: 0x%08X", rv));
274 // The transaction has already reported an error - so it will be torn
275 // down. Therefore, it is not necessary to return an error code...
277 return NS_OK;
280 rv = DispatchContent(request, aCtxt);
282 LOG((" After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08X", m_targetStreamListener.get(), rv));
284 NS_ASSERTION(NS_SUCCEEDED(rv) || !m_targetStreamListener,
285 "Must not have an m_targetStreamListener with a failure return!");
287 NS_ENSURE_SUCCESS(rv, rv);
289 if (m_targetStreamListener)
290 rv = m_targetStreamListener->OnStartRequest(request, aCtxt);
292 LOG((" OnStartRequest returning: 0x%08X", rv));
294 return rv;
297 NS_IMETHODIMP nsDocumentOpenInfo::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
298 nsIInputStream * inStr, PRUint32 sourceOffset, PRUint32 count)
300 // if we have retarged to the end stream listener, then forward the call....
301 // otherwise, don't do anything
303 nsresult rv = NS_OK;
305 if (m_targetStreamListener)
306 rv = m_targetStreamListener->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
307 return rv;
310 NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
311 nsresult aStatus)
313 LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this));
315 if ( m_targetStreamListener)
317 nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener);
319 // If this is a multipart stream, we could get another
320 // OnStartRequest after this... reset state.
321 m_targetStreamListener = 0;
322 mContentType.Truncate();
323 listener->OnStopRequest(request, aCtxt, aStatus);
326 // Remember...
327 // In the case of multiplexed streams (such as multipart/x-mixed-replace)
328 // these stream listener methods could be called again :-)
330 return NS_OK;
333 nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest *request, nsISupports * aCtxt)
335 LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this, mContentType.get()));
337 NS_PRECONDITION(!m_targetStreamListener,
338 "Why do we already have a target stream listener?");
340 nsresult rv;
341 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
342 if (!aChannel) {
343 LOG_ERROR((" Request is not a channel. Bailing."));
344 return NS_ERROR_FAILURE;
347 NS_NAMED_LITERAL_CSTRING(anyType, "*/*");
348 if (mContentType.IsEmpty() || mContentType == anyType) {
349 rv = aChannel->GetContentType(mContentType);
350 if (NS_FAILED(rv)) return rv;
351 LOG((" Got type from channel: '%s'", mContentType.get()));
354 PRBool isGuessFromExt =
355 mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT);
356 if (isGuessFromExt) {
357 // Reset to application/octet-stream for now; no one other than the
358 // external helper app service should see APPLICATION_GUESS_FROM_EXT.
359 mContentType = APPLICATION_OCTET_STREAM;
360 aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
363 // Check whether the data should be forced to be handled externally. This
364 // could happen because the Content-Disposition header is set so, or, in the
365 // future, because the user has specified external handling for the MIME
366 // type.
367 PRBool forceExternalHandling = PR_FALSE;
368 nsCAutoString disposition;
369 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
370 nsCOMPtr<nsIURI> uri;
371 if (httpChannel)
373 rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-disposition"),
374 disposition);
375 httpChannel->GetURI(getter_AddRefs(uri));
377 else
379 nsCOMPtr<nsIMultiPartChannel> multipartChannel(do_QueryInterface(request));
380 if (multipartChannel)
382 rv = multipartChannel->GetContentDisposition(disposition);
386 LOG((" Disposition header: '%s'", disposition.get()));
388 if (NS_SUCCEEDED(rv) && !disposition.IsEmpty())
390 nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar = do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
391 if (NS_SUCCEEDED(rv))
393 nsCAutoString fallbackCharset;
394 if (uri)
395 uri->GetOriginCharset(fallbackCharset);
396 nsAutoString dispToken;
397 // Get the disposition type
398 rv = mimehdrpar->GetParameter(disposition, "", fallbackCharset,
399 PR_TRUE, nsnull, dispToken);
400 // RFC 2183, section 2.8 says that an unknown disposition
401 // value should be treated as "attachment"
402 // XXXbz this code is duplicated in GetFilenameAndExtensionFromChannel in
403 // nsExternalHelperAppService. Factor it out!
404 if (NS_FAILED(rv) ||
405 (// Some broken sites just send
406 // Content-Disposition: ; filename="file"
407 // screen those out here.
408 !dispToken.IsEmpty() &&
409 !dispToken.LowerCaseEqualsLiteral("inline") &&
410 // Broken sites just send
411 // Content-Disposition: filename="file"
412 // without a disposition token... screen those out.
413 !dispToken.EqualsIgnoreCase("filename", 8)) &&
414 // Also in use is Content-Disposition: name="file"
415 !dispToken.EqualsIgnoreCase("name", 4))
416 // We have a content-disposition of "attachment" or unknown
417 forceExternalHandling = PR_TRUE;
421 LOG((" forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
423 // We're going to try to find a contentListener that can handle our data
424 nsCOMPtr<nsIURIContentListener> contentListener;
425 // The type or data the contentListener wants.
426 nsXPIDLCString desiredContentType;
428 if (!forceExternalHandling)
431 // First step: See whether m_contentListener wants to handle this
432 // content type.
434 if (m_contentListener && TryContentListener(m_contentListener, aChannel)) {
435 LOG((" Success! Our default listener likes this type"));
436 // All done here
437 return NS_OK;
440 // If we aren't allowed to try other listeners, we're done here.
441 if (!(mFlags & nsIURILoader::DONT_RETARGET)) {
444 // Second step: See whether some other registered listener wants
445 // to handle this content type.
447 PRInt32 count = mURILoader->m_listeners.Count();
448 nsCOMPtr<nsIURIContentListener> listener;
449 for (PRInt32 i = 0; i < count; i++) {
450 listener = do_QueryReferent(mURILoader->m_listeners[i]);
451 if (listener) {
452 if (TryContentListener(listener, aChannel)) {
453 LOG((" Found listener registered on the URILoader"));
454 return NS_OK;
456 } else {
457 // remove from the listener list, reset i and update count
458 mURILoader->m_listeners.RemoveObjectAt(i--);
459 --count;
464 // Third step: Try to find a content listener that has not yet had
465 // the chance to register, as it is contained in a not-yet-loaded
466 // module, but which has registered a contract ID.
468 nsCOMPtr<nsICategoryManager> catman =
469 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
470 if (catman) {
471 nsXPIDLCString contractidString;
472 rv = catman->GetCategoryEntry(NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY,
473 mContentType.get(),
474 getter_Copies(contractidString));
475 if (NS_SUCCEEDED(rv) && !contractidString.IsEmpty()) {
476 LOG((" Listener contractid for '%s' is '%s'",
477 mContentType.get(), contractidString.get()));
479 listener = do_CreateInstance(contractidString);
480 LOG((" Listener from category manager: 0x%p", listener.get()));
482 if (listener && TryContentListener(listener, aChannel)) {
483 LOG((" Listener from category manager likes this type"));
484 return NS_OK;
490 // Fourth step: try to find an nsIContentHandler for our type.
492 nsCAutoString handlerContractID (NS_CONTENT_HANDLER_CONTRACTID_PREFIX);
493 handlerContractID += mContentType;
495 nsCOMPtr<nsIContentHandler> contentHandler =
496 do_CreateInstance(handlerContractID.get());
497 if (contentHandler) {
498 LOG((" Content handler found"));
499 rv = contentHandler->HandleContent(mContentType.get(),
500 m_originalContext, request);
501 // XXXbz returning an error code to represent handling the
502 // content is just bizarre!
503 if (rv != NS_ERROR_WONT_HANDLE_CONTENT) {
504 if (NS_FAILED(rv)) {
505 // The content handler has unexpectedly failed. Cancel the request
506 // just in case the handler didn't...
507 LOG((" Content handler failed. Aborting load"));
508 request->Cancel(rv);
510 #ifdef PR_LOGGING
511 else {
512 LOG((" Content handler taking over load"));
514 #endif
516 return rv;
520 } else if (mFlags & nsIURILoader::DONT_RETARGET) {
521 // External handling was forced, but we must not retarget
522 // -> abort
523 LOG((" External handling forced, but not allowed to retarget -> aborting"));
524 return NS_ERROR_WONT_HANDLE_CONTENT;
527 NS_ASSERTION(!m_targetStreamListener,
528 "If we found a listener, why are we not using it?");
531 // Fifth step: If no listener prefers this type, see if any stream
532 // converters exist to transform this content type into
533 // some other.
536 // We always want to do this, since even content being forced to
537 // be handled externally may need decoding (eg via the unknown
538 // content decoder).
539 // Don't do this if the server sent us a MIME type of "*/*" because they saw
540 // it in our Accept header and got confused.
541 // XXXbz have to be careful here; may end up in some sort of bizarre infinite
542 // decoding loop.
543 if (mContentType != anyType) {
544 rv = ConvertData(request, m_contentListener, mContentType, anyType);
545 if (NS_FAILED(rv)) {
546 m_targetStreamListener = nsnull;
547 } else if (m_targetStreamListener) {
548 // We found a converter for this MIME type. We'll just pump data into it
549 // and let the downstream nsDocumentOpenInfo handle things.
550 LOG((" Converter taking over now"));
551 return NS_OK;
555 if (mFlags & nsIURILoader::DONT_RETARGET) {
556 LOG((" Listener not interested and no stream converter exists, and retargeting disallowed -> aborting"));
557 return NS_ERROR_WONT_HANDLE_CONTENT;
560 // Before dispatching to the external helper app service, check for an HTTP
561 // error page. If we got one, we don't want to handle it with a helper app,
562 // really.
563 if (httpChannel) {
564 PRBool requestSucceeded;
565 httpChannel->GetRequestSucceeded(&requestSucceeded);
566 if (!requestSucceeded) {
567 // returning error from OnStartRequest will cancel the channel
568 return NS_ERROR_FILE_NOT_FOUND;
572 // Sixth step:
574 // All attempts to dispatch this content have failed. Just pass it off to
575 // the helper app service.
577 nsCOMPtr<nsIExternalHelperAppService> helperAppService =
578 do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
579 if (helperAppService) {
580 LOG((" Passing load off to helper app service"));
582 // Set these flags to indicate that the channel has been targeted and that
583 // we are not using the original consumer.
584 nsLoadFlags loadFlags = 0;
585 request->GetLoadFlags(&loadFlags);
586 request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI
587 | nsIChannel::LOAD_TARGETED);
589 if (isGuessFromExt) {
590 mContentType = APPLICATION_GUESS_FROM_EXT;
591 aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_GUESS_FROM_EXT));
594 rv = helperAppService->DoContent(mContentType,
595 request,
596 m_originalContext,
597 getter_AddRefs(m_targetStreamListener));
598 if (NS_FAILED(rv)) {
599 request->SetLoadFlags(loadFlags);
600 m_targetStreamListener = nsnull;
604 NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv),
605 "There is no way we should be successful at this point without a m_targetStreamListener");
606 return rv;
609 nsresult
610 nsDocumentOpenInfo::ConvertData(nsIRequest *request,
611 nsIURIContentListener* aListener,
612 const nsACString& aSrcContentType,
613 const nsACString& aOutContentType)
615 LOG(("[0x%p] nsDocumentOpenInfo::ConvertData from '%s' to '%s'", this,
616 PromiseFlatCString(aSrcContentType).get(),
617 PromiseFlatCString(aOutContentType).get()));
619 NS_PRECONDITION(aSrcContentType != aOutContentType,
620 "ConvertData called when the two types are the same!");
621 nsresult rv = NS_OK;
623 nsCOMPtr<nsIStreamConverterService> StreamConvService =
624 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
625 if (NS_FAILED(rv)) return rv;
627 LOG((" Got converter service"));
629 // When applying stream decoders, it is necessary to "insert" an
630 // intermediate nsDocumentOpenInfo instance to handle the targeting of
631 // the "final" stream or streams.
633 // For certain content types (ie. multi-part/x-mixed-replace) the input
634 // stream is split up into multiple destination streams. This
635 // intermediate instance is used to target these "decoded" streams...
637 nsCOMPtr<nsDocumentOpenInfo> nextLink =
638 new nsDocumentOpenInfo(m_originalContext, mFlags, mURILoader);
639 if (!nextLink) return NS_ERROR_OUT_OF_MEMORY;
641 LOG((" Downstream DocumentOpenInfo would be: 0x%p", nextLink.get()));
643 // Make sure nextLink starts with the contentListener that said it wanted the
644 // results of this decode.
645 nextLink->m_contentListener = aListener;
646 // Also make sure it has to look for a stream listener to pump data into.
647 nextLink->m_targetStreamListener = nsnull;
649 // Make sure that nextLink treats the data as aOutContentType when
650 // dispatching; that way even if the stream converters don't change the type
651 // on the channel we will still do the right thing. If aOutContentType is
652 // */*, that's OK -- that will just indicate to nextLink that it should get
653 // the type off the channel.
654 nextLink->mContentType = aOutContentType;
656 // The following call sets m_targetStreamListener to the input end of the
657 // stream converter and sets the output end of the stream converter to
658 // nextLink. As we pump data into m_targetStreamListener the stream
659 // converter will convert it and pass the converted data to nextLink.
660 return StreamConvService->AsyncConvertData(PromiseFlatCString(aSrcContentType).get(),
661 PromiseFlatCString(aOutContentType).get(),
662 nextLink,
663 request,
664 getter_AddRefs(m_targetStreamListener));
667 PRBool
668 nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener,
669 nsIChannel* aChannel)
671 LOG(("[0x%p] nsDocumentOpenInfo::TryContentListener; mFlags = 0x%x",
672 this, mFlags));
674 NS_PRECONDITION(aListener, "Must have a non-null listener");
675 NS_PRECONDITION(aChannel, "Must have a channel");
677 PRBool listenerWantsContent = PR_FALSE;
678 nsXPIDLCString typeToUse;
680 if (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) {
681 aListener->IsPreferred(mContentType.get(),
682 getter_Copies(typeToUse),
683 &listenerWantsContent);
684 } else {
685 aListener->CanHandleContent(mContentType.get(), PR_FALSE,
686 getter_Copies(typeToUse),
687 &listenerWantsContent);
689 if (!listenerWantsContent) {
690 LOG((" Listener is not interested"));
691 return PR_FALSE;
694 if (!typeToUse.IsEmpty() && typeToUse != mContentType) {
695 // Need to do a conversion here.
697 nsresult rv = ConvertData(aChannel, aListener, mContentType, typeToUse);
699 if (NS_FAILED(rv)) {
700 // No conversion path -- we don't want this listener, if we got one
701 m_targetStreamListener = nsnull;
704 LOG((" Found conversion: %s", m_targetStreamListener ? "yes" : "no"));
706 // m_targetStreamListener is now the input end of the converter, and we can
707 // just pump the data in there, if it exists. If it does not, we need to
708 // try other nsIURIContentListeners.
709 return m_targetStreamListener != nsnull;
712 // At this point, aListener wants data of type mContentType. Let 'em have
713 // it. But first, if we are retargeting, set an appropriate flag on the
714 // channel
715 nsLoadFlags loadFlags = 0;
716 aChannel->GetLoadFlags(&loadFlags);
718 // Set this flag to indicate that the channel has been targeted at a final
719 // consumer. This load flag is tested in nsDocLoader::OnProgress.
720 nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED;
722 nsCOMPtr<nsIURIContentListener> originalListener =
723 do_GetInterface(m_originalContext);
724 if (originalListener != aListener) {
725 newLoadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI;
727 aChannel->SetLoadFlags(loadFlags | newLoadFlags);
729 PRBool abort = PR_FALSE;
730 PRBool isPreferred = (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) != 0;
731 nsresult rv = aListener->DoContent(mContentType.get(),
732 isPreferred,
733 aChannel,
734 getter_AddRefs(m_targetStreamListener),
735 &abort);
737 if (NS_FAILED(rv)) {
738 LOG_ERROR((" DoContent failed"));
740 // Unset the RETARGETED_DOCUMENT_URI flag if we set it...
741 aChannel->SetLoadFlags(loadFlags);
742 m_targetStreamListener = nsnull;
743 return PR_FALSE;
746 if (abort) {
747 // Nothing else to do here -- aListener is handling it all. Make
748 // sure m_targetStreamListener is null so we don't do anything
749 // after this point.
750 LOG((" Listener has aborted the load"));
751 m_targetStreamListener = nsnull;
754 NS_ASSERTION(abort || m_targetStreamListener, "DoContent returned no listener?");
756 // aListener is handling the load from this point on.
757 return PR_TRUE;
761 ///////////////////////////////////////////////////////////////////////////////////////////////
762 // Implementation of nsURILoader
763 ///////////////////////////////////////////////////////////////////////////////////////////////
765 nsURILoader::nsURILoader()
767 #ifdef PR_LOGGING
768 if (!mLog) {
769 mLog = PR_NewLogModule("URILoader");
771 #endif
774 nsURILoader::~nsURILoader()
778 NS_IMPL_ADDREF(nsURILoader)
779 NS_IMPL_RELEASE(nsURILoader)
781 NS_INTERFACE_MAP_BEGIN(nsURILoader)
782 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURILoader)
783 NS_INTERFACE_MAP_ENTRY(nsIURILoader)
784 NS_INTERFACE_MAP_END
786 NS_IMETHODIMP nsURILoader::RegisterContentListener(nsIURIContentListener * aContentListener)
788 nsresult rv = NS_OK;
790 nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
791 NS_ASSERTION(weakListener, "your URIContentListener must support weak refs!\n");
793 if (weakListener)
794 m_listeners.AppendObject(weakListener);
796 return rv;
799 NS_IMETHODIMP nsURILoader::UnRegisterContentListener(nsIURIContentListener * aContentListener)
801 nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
802 if (weakListener)
803 m_listeners.RemoveObject(weakListener);
805 return NS_OK;
809 NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel *channel,
810 PRBool aIsContentPreferred,
811 nsIInterfaceRequestor *aWindowContext)
813 NS_ENSURE_ARG_POINTER(channel);
815 #ifdef PR_LOGGING
816 if (LOG_ENABLED()) {
817 nsCOMPtr<nsIURI> uri;
818 channel->GetURI(getter_AddRefs(uri));
819 nsCAutoString spec;
820 uri->GetAsciiSpec(spec);
821 LOG(("nsURILoader::OpenURI for %s", spec.get()));
823 #endif
825 nsCOMPtr<nsIStreamListener> loader;
826 nsresult rv = OpenChannel(channel,
827 aIsContentPreferred ? IS_CONTENT_PREFERRED : 0,
828 aWindowContext,
829 PR_FALSE,
830 getter_AddRefs(loader));
832 if (NS_SUCCEEDED(rv)) {
833 // this method is not complete!!! Eventually, we should first go
834 // to the content listener and ask them for a protocol handler...
835 // if they don't give us one, we need to go to the registry and get
836 // the preferred protocol handler.
838 // But for now, I'm going to let necko do the work for us....
839 rv = channel->AsyncOpen(loader, nsnull);
841 // no content from this load - that's OK.
842 if (rv == NS_ERROR_NO_CONTENT) {
843 LOG((" rv is NS_ERROR_NO_CONTENT -- doing nothing"));
844 rv = NS_OK;
846 } else if (rv == NS_ERROR_WONT_HANDLE_CONTENT) {
847 // Not really an error, from this method's point of view
848 rv = NS_OK;
850 return rv;
853 nsresult nsURILoader::OpenChannel(nsIChannel* channel,
854 PRUint32 aFlags,
855 nsIInterfaceRequestor* aWindowContext,
856 PRBool aChannelIsOpen,
857 nsIStreamListener** aListener)
859 NS_ASSERTION(channel, "Trying to open a null channel!");
860 NS_ASSERTION(aWindowContext, "Window context must not be null");
862 #ifdef PR_LOGGING
863 if (LOG_ENABLED()) {
864 nsCOMPtr<nsIURI> uri;
865 channel->GetURI(getter_AddRefs(uri));
866 nsCAutoString spec;
867 uri->GetAsciiSpec(spec);
868 LOG(("nsURILoader::OpenChannel for %s", spec.get()));
870 #endif
872 // Let the window context's uriListener know that the open is starting. This
873 // gives that window a chance to abort the load process.
874 nsCOMPtr<nsIURIContentListener> winContextListener(do_GetInterface(aWindowContext));
875 if (winContextListener) {
876 nsCOMPtr<nsIURI> uri;
877 channel->GetURI(getter_AddRefs(uri));
878 if (uri) {
879 PRBool doAbort = PR_FALSE;
880 winContextListener->OnStartURIOpen(uri, &doAbort);
882 if (doAbort) {
883 LOG((" OnStartURIOpen aborted load"));
884 return NS_ERROR_WONT_HANDLE_CONTENT;
889 // we need to create a DocumentOpenInfo object which will go ahead and open
890 // the url and discover the content type....
891 nsCOMPtr<nsDocumentOpenInfo> loader =
892 new nsDocumentOpenInfo(aWindowContext, aFlags, this);
894 if (!loader) return NS_ERROR_OUT_OF_MEMORY;
896 // Set the correct loadgroup on the channel
897 nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext));
899 if (!loadGroup) {
900 // XXXbz This context is violating what we'd like to be the new uriloader
901 // api.... Set up a nsDocLoader to handle the loadgroup for this context.
902 // This really needs to go away!
903 nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(aWindowContext));
904 if (listener) {
905 nsCOMPtr<nsISupports> cookie;
906 listener->GetLoadCookie(getter_AddRefs(cookie));
907 if (!cookie) {
908 nsRefPtr<nsDocLoader> newDocLoader = new nsDocLoader();
909 if (!newDocLoader)
910 return NS_ERROR_OUT_OF_MEMORY;
911 nsresult rv = newDocLoader->Init();
912 if (NS_FAILED(rv))
913 return rv;
914 rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader);
915 if (NS_FAILED(rv))
916 return rv;
917 cookie = nsDocLoader::GetAsSupports(newDocLoader);
918 listener->SetLoadCookie(cookie);
920 loadGroup = do_GetInterface(cookie);
924 // If the channel is pending, then we need to remove it from its current
925 // loadgroup
926 nsCOMPtr<nsILoadGroup> oldGroup;
927 channel->GetLoadGroup(getter_AddRefs(oldGroup));
928 if (aChannelIsOpen && !SameCOMIdentity(oldGroup, loadGroup)) {
929 // It is important to add the channel to the new group before
930 // removing it from the old one, so that the load isn't considered
931 // done as soon as the request is removed.
932 loadGroup->AddRequest(channel, nsnull);
934 if (oldGroup) {
935 oldGroup->RemoveRequest(channel, nsnull, NS_BINDING_RETARGETED);
939 channel->SetLoadGroup(loadGroup);
941 // prepare the loader for receiving data
942 nsresult rv = loader->Prepare();
943 if (NS_SUCCEEDED(rv))
944 NS_ADDREF(*aListener = loader);
945 return rv;
948 NS_IMETHODIMP nsURILoader::OpenChannel(nsIChannel* channel,
949 PRUint32 aFlags,
950 nsIInterfaceRequestor* aWindowContext,
951 nsIStreamListener** aListener)
953 PRBool pending;
954 if (NS_FAILED(channel->IsPending(&pending))) {
955 pending = PR_FALSE;
958 return OpenChannel(channel, aFlags, aWindowContext, pending, aListener);
961 NS_IMETHODIMP nsURILoader::Stop(nsISupports* aLoadCookie)
963 nsresult rv;
964 nsCOMPtr<nsIDocumentLoader> docLoader;
966 NS_ENSURE_ARG_POINTER(aLoadCookie);
968 docLoader = do_GetInterface(aLoadCookie, &rv);
969 if (docLoader) {
970 rv = docLoader->Stop();
972 return rv;