Bug 1869043 assert that graph set access is main thread only r=padenot
[gecko.git] / uriloader / base / nsURILoader.cpp
blob0acd451c9dbfd506f30c9f5d63271d4ecf46a67e
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 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsURILoader.h"
8 #include "nsComponentManagerUtils.h"
9 #include "nsIURIContentListener.h"
10 #include "nsIContentHandler.h"
11 #include "nsILoadGroup.h"
12 #include "nsIDocumentLoader.h"
13 #include "nsIStreamListener.h"
14 #include "nsIURL.h"
15 #include "nsIChannel.h"
16 #include "nsIInterfaceRequestor.h"
17 #include "nsIInterfaceRequestorUtils.h"
18 #include "nsIInputStream.h"
19 #include "nsIStreamConverterService.h"
20 #include "nsIWeakReferenceUtils.h"
21 #include "nsIHttpChannel.h"
22 #include "netCore.h"
23 #include "nsCRT.h"
24 #include "nsIDocShell.h"
25 #include "nsIThreadRetargetableStreamListener.h"
26 #include "nsIChildChannel.h"
27 #include "nsExternalHelperAppService.h"
29 #include "nsString.h"
30 #include "nsThreadUtils.h"
31 #include "nsReadableUtils.h"
32 #include "nsError.h"
34 #include "nsICategoryManager.h"
35 #include "nsCExternalHandlerService.h"
37 #include "nsNetCID.h"
39 #include "nsMimeTypes.h"
41 #include "nsDocLoader.h"
42 #include "mozilla/Attributes.h"
43 #include "mozilla/IntegerPrintfMacros.h"
44 #include "mozilla/Preferences.h"
45 #include "mozilla/Unused.h"
46 #include "mozilla/StaticPrefs_browser.h"
47 #include "mozilla/StaticPrefs_dom.h"
48 #include "mozilla/StaticPrefs_general.h"
49 #include "nsContentUtils.h"
51 mozilla::LazyLogModule nsURILoader::mLog("URILoader");
53 #define LOG(args) MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Debug, args)
54 #define LOG_ERROR(args) \
55 MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Error, args)
56 #define LOG_ENABLED() MOZ_LOG_TEST(nsURILoader::mLog, mozilla::LogLevel::Debug)
58 NS_IMPL_ADDREF(nsDocumentOpenInfo)
59 NS_IMPL_RELEASE(nsDocumentOpenInfo)
61 NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo)
62 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
63 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
64 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
65 NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
66 NS_INTERFACE_MAP_END
68 nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
69 uint32_t aFlags, nsURILoader* aURILoader)
70 : m_originalContext(aWindowContext),
71 mFlags(aFlags),
72 mURILoader(aURILoader),
73 mDataConversionDepthLimit(
74 mozilla::StaticPrefs::
75 general_document_open_conversion_depth_limit()) {}
77 nsDocumentOpenInfo::nsDocumentOpenInfo(uint32_t aFlags,
78 bool aAllowListenerConversions)
79 : m_originalContext(nullptr),
80 mFlags(aFlags),
81 mURILoader(nullptr),
82 mDataConversionDepthLimit(
83 mozilla::StaticPrefs::general_document_open_conversion_depth_limit()),
84 mAllowListenerConversions(aAllowListenerConversions) {}
86 nsDocumentOpenInfo::~nsDocumentOpenInfo() {}
88 nsresult nsDocumentOpenInfo::Prepare() {
89 LOG(("[0x%p] nsDocumentOpenInfo::Prepare", this));
91 nsresult rv;
93 // ask our window context if it has a uri content listener...
94 m_contentListener = do_GetInterface(m_originalContext, &rv);
95 return rv;
98 NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest* request) {
99 LOG(("[0x%p] nsDocumentOpenInfo::OnStartRequest", this));
100 MOZ_ASSERT(request);
101 if (!request) {
102 return NS_ERROR_UNEXPECTED;
105 nsresult rv = NS_OK;
108 // Deal with "special" HTTP responses:
110 // - In the case of a 204 (No Content) or 205 (Reset Content) response, do
111 // not try to find a content handler. Return NS_BINDING_ABORTED to cancel
112 // the request. This has the effect of ensuring that the DocLoader does
113 // not try to interpret this as a real request.
115 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request, &rv));
117 if (NS_SUCCEEDED(rv)) {
118 uint32_t responseCode = 0;
120 rv = httpChannel->GetResponseStatus(&responseCode);
122 if (NS_FAILED(rv)) {
123 LOG_ERROR((" Failed to get HTTP response status"));
125 // behave as in the canceled case
126 return NS_OK;
129 LOG((" HTTP response status: %d", responseCode));
131 if (204 == responseCode || 205 == responseCode) {
132 return NS_BINDING_ABORTED;
137 // Make sure that the transaction has succeeded, so far...
139 nsresult status;
141 rv = request->GetStatus(&status);
143 NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!");
144 if (NS_FAILED(rv)) return rv;
146 if (NS_FAILED(status)) {
147 LOG_ERROR((" Request failed, status: 0x%08" PRIX32,
148 static_cast<uint32_t>(status)));
151 // The transaction has already reported an error - so it will be torn
152 // down. Therefore, it is not necessary to return an error code...
154 return NS_OK;
157 rv = DispatchContent(request);
159 LOG((" After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08" PRIX32,
160 m_targetStreamListener.get(), static_cast<uint32_t>(rv)));
162 NS_ASSERTION(
163 NS_SUCCEEDED(rv) || !m_targetStreamListener,
164 "Must not have an m_targetStreamListener with a failure return!");
166 NS_ENSURE_SUCCESS(rv, rv);
168 if (m_targetStreamListener)
169 rv = m_targetStreamListener->OnStartRequest(request);
171 LOG((" OnStartRequest returning: 0x%08" PRIX32, static_cast<uint32_t>(rv)));
173 return rv;
176 NS_IMETHODIMP
177 nsDocumentOpenInfo::CheckListenerChain() {
178 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
179 nsresult rv = NS_OK;
180 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
181 do_QueryInterface(m_targetStreamListener, &rv);
182 if (retargetableListener) {
183 rv = retargetableListener->CheckListenerChain();
185 LOG(
186 ("[0x%p] nsDocumentOpenInfo::CheckListenerChain %s listener %p rv "
187 "%" PRIx32,
188 this, (NS_SUCCEEDED(rv) ? "success" : "failure"),
189 (nsIStreamListener*)m_targetStreamListener, static_cast<uint32_t>(rv)));
190 return rv;
193 NS_IMETHODIMP
194 nsDocumentOpenInfo::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
195 uint64_t sourceOffset, uint32_t count) {
196 // if we have retarged to the end stream listener, then forward the call....
197 // otherwise, don't do anything
199 nsresult rv = NS_OK;
201 if (m_targetStreamListener)
202 rv = m_targetStreamListener->OnDataAvailable(request, inStr, sourceOffset,
203 count);
204 return rv;
207 NS_IMETHODIMP
208 nsDocumentOpenInfo::OnDataFinished(nsresult aStatus) {
209 if (!m_targetStreamListener) {
210 return NS_ERROR_FAILURE;
212 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
213 do_QueryInterface(m_targetStreamListener);
214 if (retargetableListener) {
215 return retargetableListener->OnDataFinished(aStatus);
218 return NS_OK;
221 NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest* request,
222 nsresult aStatus) {
223 LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this));
225 if (m_targetStreamListener) {
226 nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener);
228 // If this is a multipart stream, we could get another
229 // OnStartRequest after this... reset state.
230 m_targetStreamListener = nullptr;
231 mContentType.Truncate();
232 listener->OnStopRequest(request, aStatus);
234 mUsedContentHandler = false;
236 // Remember...
237 // In the case of multiplexed streams (such as multipart/x-mixed-replace)
238 // these stream listener methods could be called again :-)
240 return NS_OK;
243 nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest* request) {
244 LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this,
245 mContentType.get()));
247 MOZ_ASSERT(!m_targetStreamListener,
248 "Why do we already have a target stream listener?");
250 nsresult rv;
251 nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
252 if (!aChannel) {
253 LOG_ERROR((" Request is not a channel. Bailing."));
254 return NS_ERROR_FAILURE;
257 constexpr auto anyType = "*/*"_ns;
258 if (mContentType.IsEmpty() || mContentType == anyType) {
259 rv = aChannel->GetContentType(mContentType);
260 if (NS_FAILED(rv)) return rv;
261 LOG((" Got type from channel: '%s'", mContentType.get()));
264 bool isGuessFromExt =
265 mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT);
266 if (isGuessFromExt) {
267 // Reset to application/octet-stream for now; no one other than the
268 // external helper app service should see APPLICATION_GUESS_FROM_EXT.
269 mContentType = APPLICATION_OCTET_STREAM;
270 aChannel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
273 // Check whether the data should be forced to be handled externally. This
274 // could happen because the Content-Disposition header is set so, or, in the
275 // future, because the user has specified external handling for the MIME
276 // type.
277 bool forceExternalHandling = false;
278 uint32_t disposition;
279 rv = aChannel->GetContentDisposition(&disposition);
281 if (NS_SUCCEEDED(rv) && disposition == nsIChannel::DISPOSITION_ATTACHMENT) {
282 forceExternalHandling = true;
285 LOG((" forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
287 if (forceExternalHandling &&
288 mozilla::StaticPrefs::browser_download_open_pdf_attachments_inline()) {
289 // Check if this is a PDF which should be opened internally. We also handle
290 // octet-streams that look like they might be PDFs based on their extension.
291 bool isPDF = mContentType.LowerCaseEqualsASCII(APPLICATION_PDF);
292 if (!isPDF &&
293 (mContentType.LowerCaseEqualsASCII(APPLICATION_OCTET_STREAM) ||
294 mContentType.IsEmpty())) {
295 nsAutoString flname;
296 aChannel->GetContentDispositionFilename(flname);
297 isPDF = StringEndsWith(flname, u".pdf"_ns);
298 if (!isPDF) {
299 nsCOMPtr<nsIURI> uri;
300 aChannel->GetURI(getter_AddRefs(uri));
301 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
302 if (url) {
303 nsAutoCString ext;
304 url->GetFileExtension(ext);
305 isPDF = ext.EqualsLiteral("pdf");
310 // For a PDF, check if the preference is set that forces attachments to be
311 // opened inline. If so, treat it as a non-attachment by clearing
312 // 'forceExternalHandling' again. This allows it open a PDF directly
313 // instead of downloading it first. It may still end up being handled by
314 // a helper app depending anyway on the later checks.
315 if (isPDF) {
316 nsCOMPtr<nsILoadInfo> loadInfo;
317 aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
319 nsCOMPtr<nsIMIMEInfo> mimeInfo;
321 nsCOMPtr<nsIMIMEService> mimeSvc(
322 do_GetService(NS_MIMESERVICE_CONTRACTID));
323 NS_ENSURE_TRUE(mimeSvc, NS_ERROR_FAILURE);
324 mimeSvc->GetFromTypeAndExtension(nsLiteralCString(APPLICATION_PDF), ""_ns,
325 getter_AddRefs(mimeInfo));
327 if (mimeInfo) {
328 int32_t action = nsIMIMEInfo::saveToDisk;
329 mimeInfo->GetPreferredAction(&action);
331 bool alwaysAsk = true;
332 mimeInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
333 forceExternalHandling =
334 alwaysAsk || action != nsIMIMEInfo::handleInternally;
339 if (!forceExternalHandling) {
341 // First step: See whether m_contentListener wants to handle this
342 // content type.
344 if (TryDefaultContentListener(aChannel)) {
345 LOG((" Success! Our default listener likes this type"));
346 // All done here
347 return NS_OK;
350 // If we aren't allowed to try other listeners, just skip through to
351 // trying to convert the data.
352 if (!(mFlags & nsIURILoader::DONT_RETARGET)) {
354 // Second step: See whether some other registered listener wants
355 // to handle this content type.
357 int32_t count = mURILoader ? mURILoader->m_listeners.Count() : 0;
358 nsCOMPtr<nsIURIContentListener> listener;
359 for (int32_t i = 0; i < count; i++) {
360 listener = do_QueryReferent(mURILoader->m_listeners[i]);
361 if (listener) {
362 if (TryContentListener(listener, aChannel)) {
363 LOG((" Found listener registered on the URILoader"));
364 return NS_OK;
366 } else {
367 // remove from the listener list, reset i and update count
368 mURILoader->m_listeners.RemoveObjectAt(i--);
369 --count;
374 // Third step: Try to find an nsIContentHandler for our type.
376 nsAutoCString handlerContractID(NS_CONTENT_HANDLER_CONTRACTID_PREFIX);
377 handlerContractID += mContentType;
379 nsCOMPtr<nsIContentHandler> contentHandler =
380 do_CreateInstance(handlerContractID.get());
381 if (contentHandler) {
382 LOG((" Content handler found"));
383 // Note that m_originalContext can be nullptr when running this in
384 // the parent process on behalf on a docshell in the content process,
385 // and in that case we only support content handlers that don't need
386 // the context.
387 rv = contentHandler->HandleContent(mContentType.get(),
388 m_originalContext, request);
389 // XXXbz returning an error code to represent handling the
390 // content is just bizarre!
391 if (rv != NS_ERROR_WONT_HANDLE_CONTENT) {
392 if (NS_FAILED(rv)) {
393 // The content handler has unexpectedly failed. Cancel the request
394 // just in case the handler didn't...
395 LOG((" Content handler failed. Aborting load"));
396 request->Cancel(rv);
397 } else {
398 LOG((" Content handler taking over load"));
399 mUsedContentHandler = true;
402 return rv;
405 } else {
406 LOG(
407 (" DONT_RETARGET flag set, so skipped over random other content "
408 "listeners and content handlers"));
412 // Fourth step: If no listener prefers this type, see if any stream
413 // converters exist to transform this content type into
414 // some other.
416 // Don't do this if the server sent us a MIME type of "*/*" because they saw
417 // it in our Accept header and got confused.
418 // XXXbz have to be careful here; may end up in some sort of bizarre
419 // infinite decoding loop.
420 if (mContentType != anyType) {
421 rv = TryStreamConversion(aChannel);
422 if (NS_SUCCEEDED(rv)) {
423 return NS_OK;
428 NS_ASSERTION(!m_targetStreamListener,
429 "If we found a listener, why are we not using it?");
431 if (mFlags & nsIURILoader::DONT_RETARGET) {
432 LOG(
433 (" External handling forced or (listener not interested and no "
434 "stream converter exists), and retargeting disallowed -> aborting"));
435 return NS_ERROR_WONT_HANDLE_CONTENT;
438 // Before dispatching to the external helper app service, check for an HTTP
439 // error page. If we got one, we don't want to handle it with a helper app,
440 // really.
441 // The WPT a-download-click-404.html requires us to silently handle this
442 // without displaying an error page, so we just return early here.
443 // See bug 1604308 for discussion around what the ideal behaviour is.
444 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
445 if (httpChannel) {
446 bool requestSucceeded;
447 rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
448 if (NS_FAILED(rv) || !requestSucceeded) {
449 return NS_OK;
453 // Fifth step:
455 // All attempts to dispatch this content have failed. Just pass it off to
456 // the helper app service.
459 nsCOMPtr<nsIExternalHelperAppService> helperAppService =
460 do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
461 if (helperAppService) {
462 LOG((" Passing load off to helper app service"));
464 // Set these flags to indicate that the channel has been targeted and that
465 // we are not using the original consumer.
466 nsLoadFlags loadFlags = 0;
467 request->GetLoadFlags(&loadFlags);
468 request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI |
469 nsIChannel::LOAD_TARGETED);
471 if (isGuessFromExt || mContentType.IsEmpty()) {
472 mContentType = APPLICATION_GUESS_FROM_EXT;
473 aChannel->SetContentType(nsLiteralCString(APPLICATION_GUESS_FROM_EXT));
476 rv = TryExternalHelperApp(helperAppService, aChannel);
477 if (NS_FAILED(rv)) {
478 request->SetLoadFlags(loadFlags);
479 m_targetStreamListener = nullptr;
483 NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv),
484 "There is no way we should be successful at this point without "
485 "a m_targetStreamListener");
486 return rv;
489 nsresult nsDocumentOpenInfo::TryExternalHelperApp(
490 nsIExternalHelperAppService* aHelperAppService, nsIChannel* aChannel) {
491 return aHelperAppService->DoContent(mContentType, aChannel, m_originalContext,
492 false, nullptr,
493 getter_AddRefs(m_targetStreamListener));
496 nsresult nsDocumentOpenInfo::ConvertData(nsIRequest* request,
497 nsIURIContentListener* aListener,
498 const nsACString& aSrcContentType,
499 const nsACString& aOutContentType) {
500 LOG(("[0x%p] nsDocumentOpenInfo::ConvertData from '%s' to '%s'", this,
501 PromiseFlatCString(aSrcContentType).get(),
502 PromiseFlatCString(aOutContentType).get()));
504 if (mDataConversionDepthLimit == 0) {
505 LOG(
506 ("[0x%p] nsDocumentOpenInfo::ConvertData - reached the recursion "
507 "limit!",
508 this));
509 // This will fall back to external helper app handling.
510 return NS_ERROR_ABORT;
513 MOZ_ASSERT(aSrcContentType != aOutContentType,
514 "ConvertData called when the two types are the same!");
516 nsresult rv = NS_OK;
518 nsCOMPtr<nsIStreamConverterService> StreamConvService =
519 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
520 if (NS_FAILED(rv)) return rv;
522 LOG((" Got converter service"));
524 // When applying stream decoders, it is necessary to "insert" an
525 // intermediate nsDocumentOpenInfo instance to handle the targeting of
526 // the "final" stream or streams.
528 // For certain content types (ie. multi-part/x-mixed-replace) the input
529 // stream is split up into multiple destination streams. This
530 // intermediate instance is used to target these "decoded" streams...
532 RefPtr<nsDocumentOpenInfo> nextLink = Clone();
534 LOG((" Downstream DocumentOpenInfo would be: 0x%p", nextLink.get()));
536 // Decrease the conversion recursion limit by one to prevent infinite loops.
537 nextLink->mDataConversionDepthLimit = mDataConversionDepthLimit - 1;
539 // Make sure nextLink starts with the contentListener that said it wanted
540 // the results of this decode.
541 nextLink->m_contentListener = aListener;
542 // Also make sure it has to look for a stream listener to pump data into.
543 nextLink->m_targetStreamListener = nullptr;
545 // Make sure that nextLink treats the data as aOutContentType when
546 // dispatching; that way even if the stream converters don't change the type
547 // on the channel we will still do the right thing. If aOutContentType is
548 // */*, that's OK -- that will just indicate to nextLink that it should get
549 // the type off the channel.
550 nextLink->mContentType = aOutContentType;
552 // The following call sets m_targetStreamListener to the input end of the
553 // stream converter and sets the output end of the stream converter to
554 // nextLink. As we pump data into m_targetStreamListener the stream
555 // converter will convert it and pass the converted data to nextLink.
556 return StreamConvService->AsyncConvertData(
557 PromiseFlatCString(aSrcContentType).get(),
558 PromiseFlatCString(aOutContentType).get(), nextLink, request,
559 getter_AddRefs(m_targetStreamListener));
562 nsresult nsDocumentOpenInfo::TryStreamConversion(nsIChannel* aChannel) {
563 constexpr auto anyType = "*/*"_ns;
565 // A empty content type should be treated like the unknown content type.
566 nsCString srcContentType(mContentType);
567 if (srcContentType.IsEmpty()) {
568 srcContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
571 nsresult rv =
572 ConvertData(aChannel, m_contentListener, srcContentType, anyType);
573 if (NS_FAILED(rv)) {
574 m_targetStreamListener = nullptr;
575 } else if (m_targetStreamListener) {
576 // We found a converter for this MIME type. We'll just pump data into
577 // it and let the downstream nsDocumentOpenInfo handle things.
578 LOG((" Converter taking over now"));
580 return rv;
583 bool nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener,
584 nsIChannel* aChannel) {
585 LOG(("[0x%p] nsDocumentOpenInfo::TryContentListener; mFlags = 0x%x", this,
586 mFlags));
588 MOZ_ASSERT(aListener, "Must have a non-null listener");
589 MOZ_ASSERT(aChannel, "Must have a channel");
591 bool listenerWantsContent = false;
592 nsCString typeToUse;
594 if (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) {
595 aListener->IsPreferred(mContentType.get(), getter_Copies(typeToUse),
596 &listenerWantsContent);
597 } else {
598 aListener->CanHandleContent(mContentType.get(), false,
599 getter_Copies(typeToUse),
600 &listenerWantsContent);
602 if (!listenerWantsContent) {
603 LOG((" Listener is not interested"));
604 return false;
607 if (!typeToUse.IsEmpty() && typeToUse != mContentType) {
608 // Need to do a conversion here.
610 nsresult rv = NS_ERROR_NOT_AVAILABLE;
611 if (mAllowListenerConversions) {
612 rv = ConvertData(aChannel, aListener, mContentType, typeToUse);
615 if (NS_FAILED(rv)) {
616 // No conversion path -- we don't want this listener, if we got one
617 m_targetStreamListener = nullptr;
620 LOG((" Found conversion: %s", m_targetStreamListener ? "yes" : "no"));
622 // m_targetStreamListener is now the input end of the converter, and we can
623 // just pump the data in there, if it exists. If it does not, we need to
624 // try other nsIURIContentListeners.
625 return m_targetStreamListener != nullptr;
628 // At this point, aListener wants data of type mContentType. Let 'em have
629 // it. But first, if we are retargeting, set an appropriate flag on the
630 // channel
631 nsLoadFlags loadFlags = 0;
632 aChannel->GetLoadFlags(&loadFlags);
634 // Set this flag to indicate that the channel has been targeted at a final
635 // consumer. This load flag is tested in nsDocLoader::OnProgress.
636 nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED;
638 nsCOMPtr<nsIURIContentListener> originalListener =
639 do_GetInterface(m_originalContext);
640 if (originalListener != aListener) {
641 newLoadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI;
643 aChannel->SetLoadFlags(loadFlags | newLoadFlags);
645 bool abort = false;
646 bool isPreferred = (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) != 0;
647 nsresult rv =
648 aListener->DoContent(mContentType, isPreferred, aChannel,
649 getter_AddRefs(m_targetStreamListener), &abort);
651 if (NS_FAILED(rv)) {
652 LOG_ERROR((" DoContent failed"));
654 // Unset the RETARGETED_DOCUMENT_URI flag if we set it...
655 aChannel->SetLoadFlags(loadFlags);
656 m_targetStreamListener = nullptr;
657 return false;
660 if (abort) {
661 // Nothing else to do here -- aListener is handling it all. Make
662 // sure m_targetStreamListener is null so we don't do anything
663 // after this point.
664 LOG((" Listener has aborted the load"));
665 m_targetStreamListener = nullptr;
668 NS_ASSERTION(abort || m_targetStreamListener,
669 "DoContent returned no listener?");
671 // aListener is handling the load from this point on.
672 return true;
675 bool nsDocumentOpenInfo::TryDefaultContentListener(nsIChannel* aChannel) {
676 if (m_contentListener) {
677 return TryContentListener(m_contentListener, aChannel);
679 return false;
682 ///////////////////////////////////////////////////////////////////////////////////////////////
683 // Implementation of nsURILoader
684 ///////////////////////////////////////////////////////////////////////////////////////////////
686 nsURILoader::nsURILoader() {}
688 nsURILoader::~nsURILoader() {}
690 NS_IMPL_ADDREF(nsURILoader)
691 NS_IMPL_RELEASE(nsURILoader)
693 NS_INTERFACE_MAP_BEGIN(nsURILoader)
694 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURILoader)
695 NS_INTERFACE_MAP_ENTRY(nsIURILoader)
696 NS_INTERFACE_MAP_END
698 NS_IMETHODIMP nsURILoader::RegisterContentListener(
699 nsIURIContentListener* aContentListener) {
700 nsresult rv = NS_OK;
702 nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
703 NS_ASSERTION(weakListener,
704 "your URIContentListener must support weak refs!\n");
706 if (weakListener) m_listeners.AppendObject(weakListener);
708 return rv;
711 NS_IMETHODIMP nsURILoader::UnRegisterContentListener(
712 nsIURIContentListener* aContentListener) {
713 nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
714 if (weakListener) m_listeners.RemoveObject(weakListener);
716 return NS_OK;
719 NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel* channel, uint32_t aFlags,
720 nsIInterfaceRequestor* aWindowContext) {
721 NS_ENSURE_ARG_POINTER(channel);
723 if (LOG_ENABLED()) {
724 nsCOMPtr<nsIURI> uri;
725 channel->GetURI(getter_AddRefs(uri));
726 nsAutoCString spec;
727 uri->GetAsciiSpec(spec);
728 LOG(("nsURILoader::OpenURI for %s", spec.get()));
731 nsCOMPtr<nsIStreamListener> loader;
732 nsresult rv = OpenChannel(channel, aFlags, aWindowContext, false,
733 getter_AddRefs(loader));
734 if (NS_FAILED(rv)) {
735 if (rv == NS_ERROR_WONT_HANDLE_CONTENT) {
736 // Not really an error, from this method's point of view
737 return NS_OK;
741 // This method is not complete. Eventually, we should first go
742 // to the content listener and ask them for a protocol handler...
743 // if they don't give us one, we need to go to the registry and get
744 // the preferred protocol handler.
746 // But for now, I'm going to let necko do the work for us....
747 rv = channel->AsyncOpen(loader);
749 // no content from this load - that's OK.
750 if (rv == NS_ERROR_NO_CONTENT) {
751 LOG((" rv is NS_ERROR_NO_CONTENT -- doing nothing"));
752 return NS_OK;
754 return rv;
757 nsresult nsURILoader::OpenChannel(nsIChannel* channel, uint32_t aFlags,
758 nsIInterfaceRequestor* aWindowContext,
759 bool aChannelIsOpen,
760 nsIStreamListener** aListener) {
761 NS_ASSERTION(channel, "Trying to open a null channel!");
762 NS_ASSERTION(aWindowContext, "Window context must not be null");
764 if (LOG_ENABLED()) {
765 nsCOMPtr<nsIURI> uri;
766 channel->GetURI(getter_AddRefs(uri));
767 nsAutoCString spec;
768 uri->GetAsciiSpec(spec);
769 LOG(("nsURILoader::OpenChannel for %s", spec.get()));
772 // we need to create a DocumentOpenInfo object which will go ahead and open
773 // the url and discover the content type....
774 RefPtr<nsDocumentOpenInfo> loader =
775 new nsDocumentOpenInfo(aWindowContext, aFlags, this);
777 // Set the correct loadgroup on the channel
778 nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext));
780 if (!loadGroup) {
781 // XXXbz This context is violating what we'd like to be the new uriloader
782 // api.... Set up a nsDocLoader to handle the loadgroup for this context.
783 // This really needs to go away!
784 nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(aWindowContext));
785 if (listener) {
786 nsCOMPtr<nsISupports> cookie;
787 listener->GetLoadCookie(getter_AddRefs(cookie));
788 if (!cookie) {
789 RefPtr<nsDocLoader> newDocLoader = new nsDocLoader();
790 nsresult rv = newDocLoader->Init();
791 if (NS_FAILED(rv)) return rv;
792 rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader);
793 if (NS_FAILED(rv)) return rv;
794 cookie = nsDocLoader::GetAsSupports(newDocLoader);
795 listener->SetLoadCookie(cookie);
797 loadGroup = do_GetInterface(cookie);
801 // If the channel is pending, then we need to remove it from its current
802 // loadgroup
803 nsCOMPtr<nsILoadGroup> oldGroup;
804 channel->GetLoadGroup(getter_AddRefs(oldGroup));
805 if (aChannelIsOpen && !SameCOMIdentity(oldGroup, loadGroup)) {
806 // It is important to add the channel to the new group before
807 // removing it from the old one, so that the load isn't considered
808 // done as soon as the request is removed.
809 loadGroup->AddRequest(channel, nullptr);
811 if (oldGroup) {
812 oldGroup->RemoveRequest(channel, nullptr, NS_BINDING_RETARGETED);
816 channel->SetLoadGroup(loadGroup);
818 // prepare the loader for receiving data
819 nsresult rv = loader->Prepare();
820 if (NS_SUCCEEDED(rv)) NS_ADDREF(*aListener = loader);
821 return rv;
824 NS_IMETHODIMP nsURILoader::OpenChannel(nsIChannel* channel, uint32_t aFlags,
825 nsIInterfaceRequestor* aWindowContext,
826 nsIStreamListener** aListener) {
827 bool pending;
828 if (NS_FAILED(channel->IsPending(&pending))) {
829 pending = false;
832 return OpenChannel(channel, aFlags, aWindowContext, pending, aListener);
835 NS_IMETHODIMP nsURILoader::Stop(nsISupports* aLoadCookie) {
836 nsresult rv;
837 nsCOMPtr<nsIDocumentLoader> docLoader;
839 NS_ENSURE_ARG_POINTER(aLoadCookie);
841 docLoader = do_GetInterface(aLoadCookie, &rv);
842 if (docLoader) {
843 rv = docLoader->Stop();
845 return rv;