Back to index

lightning-sunbird  0.9+nobinonly
imgLoader.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 2001
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Stuart Parmenter <pavlov@netscape.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include "imgLoader.h"
00041 
00042 #include "nsCOMPtr.h"
00043 
00044 #include "nsNetUtil.h"
00045 #include "nsIHttpChannel.h"
00046 #include "nsICachingChannel.h"
00047 #include "nsIProxyObjectManager.h"
00048 #include "nsIServiceManager.h"
00049 #include "nsXPIDLString.h"
00050 #include "nsCRT.h"
00051 
00052 #include "netCore.h"
00053 
00054 #include "imgCache.h"
00055 #include "imgRequest.h"
00056 #include "imgRequestProxy.h"
00057 
00058 #include "ImageErrors.h"
00059 #include "ImageLogging.h"
00060 
00061 #include "nsIComponentRegistrar.h"
00062 
00063 // we want to explore making the document own the load group
00064 // so we can associate the document URI with the load group.
00065 // until this point, we have an evil hack:
00066 #include "nsIHttpChannelInternal.h"  
00067 
00068 #if defined(DEBUG_pavlov) || defined(DEBUG_timeless)
00069 #include "nsISimpleEnumerator.h"
00070 #include "nsXPCOM.h"
00071 #include "nsISupportsPrimitives.h"
00072 #include "nsXPIDLString.h"
00073 #include "nsComponentManagerUtils.h"
00074 
00075 static void PrintImageDecoders()
00076 {
00077   nsCOMPtr<nsIComponentRegistrar> compMgr;
00078   if (NS_FAILED(NS_GetComponentRegistrar(getter_AddRefs(compMgr))) || !compMgr)
00079     return;
00080   nsCOMPtr<nsISimpleEnumerator> enumer;
00081   if (NS_FAILED(compMgr->EnumerateContractIDs(getter_AddRefs(enumer))) || !enumer)
00082     return;
00083   
00084   nsCString str;
00085   nsCOMPtr<nsISupports> s;
00086   PRBool more = PR_FALSE;
00087   while (NS_SUCCEEDED(enumer->HasMoreElements(&more)) && more) {
00088     enumer->GetNext(getter_AddRefs(s));
00089     if (s) {
00090       nsCOMPtr<nsISupportsCString> ss(do_QueryInterface(s));
00091 
00092       nsCAutoString xcs;
00093       ss->GetData(xcs);
00094 
00095       NS_NAMED_LITERAL_CSTRING(decoderContract, "@mozilla.org/image/decoder;2?type=");
00096 
00097       if (StringBeginsWith(xcs, decoderContract)) {
00098         printf("Have decoder for mime type: %s\n", xcs.get()+decoderContract.Length());
00099       }
00100     }
00101   }
00102 }
00103 #endif
00104 
00105 NS_IMPL_ISUPPORTS2(imgLoader, imgILoader, nsIContentSniffer)
00106 
00107 imgLoader::imgLoader()
00108 {
00109   /* member initializers and constructor code */
00110 #ifdef DEBUG_pavlov
00111   PrintImageDecoders();
00112 #endif
00113 }
00114 
00115 imgLoader::~imgLoader()
00116 {
00117   /* destructor code */
00118 }
00119 
00120 #define LOAD_FLAGS_CACHE_MASK    (nsIRequest::LOAD_BYPASS_CACHE | \
00121                                   nsIRequest::LOAD_FROM_CACHE)
00122 
00123 #define LOAD_FLAGS_VALIDATE_MASK (nsIRequest::VALIDATE_ALWAYS |   \
00124                                   nsIRequest::VALIDATE_NEVER |    \
00125                                   nsIRequest::VALIDATE_ONCE_PER_SESSION)
00126 
00127 
00128 static PRBool RevalidateEntry(nsICacheEntryDescriptor *aEntry,
00129                               nsLoadFlags aFlags,
00130                               PRBool aHasExpired)
00131 {
00132   PRBool bValidateEntry = PR_FALSE;
00133 
00134   NS_ASSERTION(!(aFlags & nsIRequest::LOAD_BYPASS_CACHE),
00135                "MUST not revalidate when BYPASS_CACHE is specified.");
00136 
00137   if (aFlags & nsIRequest::VALIDATE_ALWAYS) {
00138     bValidateEntry = PR_TRUE;
00139   }
00140   //
00141   // The cache entry has expired...  Determine whether the stale cache
00142   // entry can be used without validation...
00143   //
00144   else if (aHasExpired) {
00145     //
00146     // VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow stale cache
00147     // entries to be used unless they have been explicitly marked to
00148     // indicate that revalidation is necessary.
00149     //
00150     if (aFlags & (nsIRequest::VALIDATE_NEVER | 
00151                   nsIRequest::VALIDATE_ONCE_PER_SESSION)) 
00152     {
00153       nsXPIDLCString value;
00154 
00155       aEntry->GetMetaDataElement("MustValidateIfExpired",
00156                                  getter_Copies(value));
00157       if (PL_strcmp(value, "true")) {
00158         bValidateEntry = PR_TRUE;
00159       }
00160     }
00161     //
00162     // LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise,
00163     // the entry must be revalidated.
00164     //
00165     else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) {
00166       bValidateEntry = PR_TRUE;
00167     }
00168   }
00169 
00170   return bValidateEntry;
00171 }
00172 
00173 
00174 static nsresult NewImageChannel(nsIChannel **aResult,
00175                                 nsIURI *aURI,
00176                                 nsIURI *aInitialDocumentURI,
00177                                 nsIURI *aReferringURI,
00178                                 nsILoadGroup *aLoadGroup,
00179                                 nsLoadFlags aLoadFlags)
00180 {
00181   nsresult rv;
00182   nsCOMPtr<nsIChannel> newChannel;
00183   nsCOMPtr<nsIHttpChannel> newHttpChannel;
00184  
00185   nsCOMPtr<nsIInterfaceRequestor> callbacks;
00186 
00187   if (aLoadGroup) {
00188     // Get the notification callbacks from the load group for the new channel.
00189     //
00190     // XXX: This is not exactly correct, because the network request could be
00191     //      referenced by multiple windows...  However, the new channel needs
00192     //      something.  So, using the 'first' notification callbacks is better
00193     //      than nothing...
00194     //
00195     aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
00196   }
00197 
00198   // Pass in a NULL loadgroup because this is the underlying network request.
00199   // This request may be referenced by several proxy image requests (psossibly
00200   // in different documents).
00201   // If all of the proxy requests are canceled then this request should be
00202   // canceled too.
00203   //
00204   rv = NS_NewChannel(aResult,
00205                      aURI,        // URI 
00206                      nsnull,      // Cached IOService
00207                      nsnull,      // LoadGroup
00208                      callbacks,   // Notification Callbacks
00209                      aLoadFlags);
00210   if (NS_FAILED(rv))
00211     return rv;
00212 
00213   // Initialize HTTP-specific attributes
00214   newHttpChannel = do_QueryInterface(*aResult);
00215   if (newHttpChannel) {
00216     newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
00217                                      NS_LITERAL_CSTRING("image/png,*/*;q=0.5"),
00218                                      PR_FALSE);
00219 
00220     nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = do_QueryInterface(newHttpChannel);
00221     NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED);
00222     httpChannelInternal->SetDocumentURI(aInitialDocumentURI);
00223     newHttpChannel->SetReferrer(aReferringURI);
00224   }
00225 
00226   // Image channels are loaded by default with reduced priority.
00227   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(*aResult);
00228   if (p) {
00229     PRUint32 priority = nsISupportsPriority::PRIORITY_LOW;
00230 
00231     if (aLoadFlags & nsIRequest::LOAD_BACKGROUND)
00232       ++priority; // further reduce priority for background loads
00233 
00234     p->AdjustPriority(priority);
00235   }
00236 
00237   return NS_OK;
00238 }
00239 
00240 /* imgIRequest loadImage (in nsIURI aURI, in nsIURI initialDocumentURI, in nsILoadGroup aLoadGroup, in imgIDecoderObserver aObserver, in nsISupports aCX, in nsLoadFlags aLoadFlags, in nsISupports cacheKey, in imgIRequest aRequest); */
00241 
00242 NS_IMETHODIMP imgLoader::LoadImage(nsIURI *aURI, 
00243                                    nsIURI *aInitialDocumentURI,
00244                                    nsIURI *aReferrerURI,
00245                                    nsILoadGroup *aLoadGroup,
00246                                    imgIDecoderObserver *aObserver,
00247                                    nsISupports *aCX,
00248                                    nsLoadFlags aLoadFlags,
00249                                    nsISupports *cacheKey,
00250                                    imgIRequest *aRequest,
00251                                    imgIRequest **_retval)
00252 {
00253   NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer");
00254 
00255   // CreateNewProxyForRequest treats _retval as inout - null out
00256   // to make sure the passed value doesn't affect the behavior of
00257   // this method
00258   *_retval = nsnull;
00259 
00260   if (!aURI)
00261     return NS_ERROR_NULL_POINTER;
00262 
00263 #if defined(PR_LOGGING)
00264   nsCAutoString spec;
00265   aURI->GetAsciiSpec(spec);
00266   LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::LoadImage", "aURI", spec.get());
00267 #endif
00268 
00269   // This is an owning reference that must be released.
00270   imgRequest *request = nsnull;
00271 
00272   nsresult rv;
00273   nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
00274 
00275   // Get the default load flags from the loadgroup (if possible)...
00276   if (aLoadGroup) {
00277     aLoadGroup->GetLoadFlags(&requestFlags);
00278   }
00279   //
00280   // Merge the default load flags with those passed in via aLoadFlags.
00281   // Currently, *only* the caching, validation and background load flags
00282   // are merged...
00283   //
00284   // The flags in aLoadFlags take precidence over the default flags!
00285   //
00286   if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) {
00287     // Override the default caching flags...
00288     requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) |
00289                    (aLoadFlags & LOAD_FLAGS_CACHE_MASK);
00290   }
00291   if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) {
00292     // Override the default validation flags...
00293     requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) |
00294                    (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK);
00295   }
00296   if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
00297     // Propagate background loading...
00298     requestFlags |= nsIRequest::LOAD_BACKGROUND;
00299   }
00300 
00301   nsCOMPtr<nsICacheEntryDescriptor> entry;
00302   PRBool bCanCacheRequest = PR_TRUE;
00303   PRBool bHasExpired      = PR_FALSE;
00304   PRBool bValidateRequest = PR_FALSE;
00305 
00306   // XXX For now ignore the cache key. We will need it in the future
00307   // for correctly dealing with image load requests that are a result
00308   // of post data.
00309   imgCache::Get(aURI, &bHasExpired,
00310                 &request, getter_AddRefs(entry)); // addrefs request
00311 
00312   if (request && entry) {
00313 
00314     // request's null out their mCacheEntry when all proxy's are removed.
00315     // If we are about to add a new one back, go ahead and re-set the cache
00316     // entry so it can be used.
00317     if (!request->mCacheEntry) {
00318       request->mCacheEntry = entry;
00319     }
00320 
00321     // If the request's loadId is the same as the aCX, then it is ok to use
00322     // this one because it has already been validated for this context.
00323     //
00324     // XXX: nsnull seems to be a 'special' key value that indicates that NO
00325     //      validation is required.
00326     //
00327     void *key = (void*)aCX;
00328     if (request->mLoadId != key) {
00329 
00330       // LOAD_BYPASS_CACHE - Always re-fetch
00331       if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
00332         // doom cache entry; be sure to break the reference cycle between the
00333         // request and cache entry.  NOTE: the request might not own the cache
00334         // entry at this point, so we explicitly Doom |entry| just in case.
00335         entry->Doom();
00336         entry = nsnull;
00337         request->RemoveFromCache();
00338         NS_RELEASE(request);
00339       } else {
00340         // Determine whether the cache entry must be revalidated...
00341         bValidateRequest = RevalidateEntry(entry, requestFlags, bHasExpired);
00342 
00343         PR_LOG(gImgLog, PR_LOG_DEBUG,
00344                ("imgLoader::LoadImage validating cache entry. " 
00345                 "bValidateRequest = %d", bValidateRequest));
00346       }
00347 
00348     }
00349 #if defined(PR_LOGGING)
00350     else if (!key) {
00351       PR_LOG(gImgLog, PR_LOG_DEBUG,
00352              ("imgLoader::LoadImage BYPASSING cache validation for %s " 
00353               "because of NULL LoadID", spec.get()));
00354     }
00355 #endif
00356   }
00357 
00358   //
00359   // Get the current EventQueue...  This is used as a cacheId to prevent
00360   // sharing requests which are being loaded across multiple event queues...
00361   //
00362   nsCOMPtr<nsIEventQueueService> eventQService;
00363   nsCOMPtr<nsIEventQueue> activeQ;
00364 
00365   eventQService = do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv);
00366   if (NS_FAILED(rv)) {
00367     NS_IF_RELEASE(request);
00368     return rv;
00369   }
00370 
00371   rv = eventQService->ResolveEventQueue(NS_CURRENT_EVENTQ,
00372                                         getter_AddRefs(activeQ));
00373   if (NS_FAILED(rv)) {
00374     NS_IF_RELEASE(request);
00375     return rv;
00376   }
00377 
00378   void *cacheId = activeQ.get();
00379   if (request && !request->IsReusable(cacheId)) {
00380     //
00381     // The current request is still being loaded and lives on a different
00382     // event queue.
00383     //
00384     // Since its event queue is NOT active, do not reuse this imgRequest !!
00385     // Instead, force a new request to be created but DO NOT allow it to be
00386     // cached!
00387     //
00388     PR_LOG(gImgLog, PR_LOG_DEBUG,
00389            ("[this=%p] imgLoader::LoadImage -- DANGER!! Unable to use cached "
00390             "imgRequest [request=%p]\n", this, request));
00391 
00392     entry = nsnull;
00393     NS_RELEASE(request);
00394 
00395     bCanCacheRequest = PR_FALSE;
00396   }
00397 
00398   //
00399   // Time to load the request... There are 3 possible cases:
00400   // =======================================================
00401   //   1. There is no cached request (ie. nothing was found in the cache).
00402   //
00403   //   2. There is a cached request that must be validated.
00404   //
00405   //   3. There is a valid cached request.
00406   //
00407   if (request && bValidateRequest) {
00408     /* Case #2: the cache request cache must be revalidated. */
00409     LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache hit| must validate");
00410 
00411     // now we need to insert a new channel request object inbetween the real
00412     // request and the proxy that basically delays loading the image until it
00413     // gets a 304 or figures out that this needs to be a new request
00414 
00415     if (request->mValidator) {
00416       rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
00417                                     requestFlags, aRequest, _retval);
00418 
00419       if (*_retval)
00420         request->mValidator->AddProxy(NS_STATIC_CAST(imgRequestProxy*, *_retval));
00421 
00422       NS_RELEASE(request);
00423       return rv;
00424 
00425     } else {
00426       nsCOMPtr<nsIChannel> newChannel;
00427       rv = NewImageChannel(getter_AddRefs(newChannel),
00428                            aURI,
00429                            aInitialDocumentURI,
00430                            aReferrerURI,
00431                            aLoadGroup,
00432                            requestFlags);
00433       if (NS_FAILED(rv)) {
00434         NS_RELEASE(request);
00435         return NS_ERROR_FAILURE;
00436       }
00437 
00438       nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(newChannel));
00439 
00440       if (cacheChan) {
00441         // since this channel supports nsICachingChannel, we can ask it
00442         // to only stream us data if the data comes off the net.
00443         PRUint32 loadFlags;
00444         if (NS_SUCCEEDED(newChannel->GetLoadFlags(&loadFlags)))
00445             newChannel->SetLoadFlags(loadFlags | nsICachingChannel::LOAD_ONLY_IF_MODIFIED);
00446 
00447       }
00448       nsCOMPtr<imgIRequest> req;
00449       rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
00450                                     requestFlags, aRequest, getter_AddRefs(req));
00451       if (NS_FAILED(rv)) {
00452         NS_RELEASE(request);
00453         return rv;
00454       }
00455 
00456       imgCacheValidator *hvc = new imgCacheValidator(request, aCX);
00457       if (!hvc) {
00458         NS_RELEASE(request);
00459         return NS_ERROR_OUT_OF_MEMORY;
00460       }
00461 
00462       NS_ADDREF(hvc);
00463       request->mValidator = hvc;
00464 
00465       hvc->AddProxy(NS_STATIC_CAST(imgRequestProxy*,
00466                                    NS_STATIC_CAST(imgIRequest*, req.get())));
00467 
00468       rv = newChannel->AsyncOpen(NS_STATIC_CAST(nsIStreamListener *, hvc), nsnull);
00469       if (NS_SUCCEEDED(rv))
00470         NS_ADDREF(*_retval = req.get());
00471 
00472       NS_RELEASE(hvc);
00473 
00474       NS_RELEASE(request);
00475 
00476       return rv;
00477     }
00478   } else if (!request) {
00479     /* Case #1: no request from the cache.  do a new load */
00480     LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache miss|");
00481 
00482     nsCOMPtr<nsIChannel> newChannel;
00483     rv = NewImageChannel(getter_AddRefs(newChannel),
00484                          aURI,
00485                          aInitialDocumentURI,
00486                          aReferrerURI,
00487                          aLoadGroup,
00488                          requestFlags);
00489     if (NS_FAILED(rv))
00490       return NS_ERROR_FAILURE;
00491 
00492     NS_NEWXPCOM(request, imgRequest);
00493     if (!request) return NS_ERROR_OUT_OF_MEMORY;
00494 
00495     NS_ADDREF(request);
00496 
00497     PR_LOG(gImgLog, PR_LOG_DEBUG,
00498            ("[this=%p] imgLoader::LoadImage -- Created new imgRequest [request=%p]\n", this, request));
00499 
00500     // Add the new request into the imgCache if its cachable...
00501     if (bCanCacheRequest) {
00502       imgCache::Put(aURI, request, getter_AddRefs(entry));
00503     }
00504 
00505     request->Init(newChannel, entry, cacheId, aCX);
00506 
00507     // create the proxy listener
00508     ProxyListener *pl = new ProxyListener(NS_STATIC_CAST(nsIStreamListener *, request));
00509     if (!pl) {
00510       request->Cancel(NS_ERROR_OUT_OF_MEMORY);
00511       NS_RELEASE(request);
00512       return NS_ERROR_OUT_OF_MEMORY;
00513     }
00514 
00515     NS_ADDREF(pl);
00516 
00517     PR_LOG(gImgLog, PR_LOG_DEBUG,
00518            ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n", this));
00519 
00520     nsresult openRes;
00521     openRes = newChannel->AsyncOpen(NS_STATIC_CAST(nsIStreamListener *, pl), nsnull);
00522 
00523     NS_RELEASE(pl);
00524 
00525     if (NS_FAILED(openRes)) {
00526       PR_LOG(gImgLog, PR_LOG_DEBUG,
00527              ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%x\n",
00528               this, openRes));
00529       request->Cancel(openRes);
00530       NS_RELEASE(request);
00531       return openRes;
00532     }
00533 
00534   } else {
00535     /* Case #3: request found in cache.  use it */
00536     // XXX: Should this be executed if an expired cache entry does not have a caching channel??
00537     LOG_MSG_WITH_PARAM(gImgLog, 
00538                        "imgLoader::LoadImage |cache hit|", "request", request);
00539 
00540     // Update the request's LoadId
00541     request->SetLoadId(aCX);
00542   }
00543 
00544   LOG_MSG(gImgLog, "imgLoader::LoadImage", "creating proxy request.");
00545 
00546   rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
00547                                 requestFlags, aRequest, _retval);
00548 
00549   imgRequestProxy *proxy = (imgRequestProxy *)*_retval;
00550 
00551   // Note that it's OK to add here even if the request is done.  If it is,
00552   // it'll send a OnStopRequest() to the proxy in NotifyProxyListener and the
00553   // proxy will be removed from the loadgroup.
00554   proxy->AddToLoadGroup();
00555 
00556   // if we have to validate the request, then we will send the
00557   // notifications later.
00558   if (!bValidateRequest) {
00559     request->NotifyProxyListener(proxy);
00560   }
00561 
00562   NS_RELEASE(request);
00563 
00564   return rv;
00565 }
00566 
00567 /* imgIRequest loadImageWithChannel(in nsIChannel channel, in imgIDecoderObserver aObserver, in nsISupports cx, out nsIStreamListener); */
00568 NS_IMETHODIMP imgLoader::LoadImageWithChannel(nsIChannel *channel, imgIDecoderObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval)
00569 {
00570   NS_ASSERTION(channel, "imgLoader::LoadImageWithChannel -- NULL channel pointer");
00571 
00572   // CreateNewProxyForRequest treats _retval as inout - null out
00573   // to make sure the passed value doesn't affect the behavior of
00574   // this method
00575   *_retval = nsnull;
00576 
00577   nsresult rv;
00578   imgRequest *request = nsnull;
00579 
00580   nsCOMPtr<nsIURI> uri;
00581   channel->GetURI(getter_AddRefs(uri));
00582 
00583   nsCOMPtr<nsICacheEntryDescriptor> entry;
00584   PRBool bHasExpired;
00585 
00586   imgCache::Get(uri, &bHasExpired, &request, getter_AddRefs(entry)); // addrefs request
00587 
00588   nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
00589 
00590   channel->GetLoadFlags(&requestFlags);
00591 
00592   if (request) {
00593     PRBool bUseCacheCopy = PR_TRUE;
00594 
00595     // LOAD_BYPASS_CACHE - Always re-fetch
00596     if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
00597       bUseCacheCopy = PR_FALSE;
00598     }
00599     else if (RevalidateEntry(entry, requestFlags, bHasExpired)) {
00600       nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(channel));
00601       if (cacheChan) {
00602         cacheChan->IsFromCache(&bUseCacheCopy);
00603       } else {
00604         bUseCacheCopy = PR_FALSE;
00605       }
00606     }
00607 
00608     if (!bUseCacheCopy) {
00609       // doom cache entry; be sure to break the reference cycle between the
00610       // request and cache entry.  NOTE: the request might not own the cache
00611       // entry at this point, so we explicitly Doom |entry| just in case.
00612       entry->Doom();
00613       entry = nsnull;
00614       request->RemoveFromCache();
00615       NS_RELEASE(request);
00616     }
00617   }
00618 
00619   nsCOMPtr<nsILoadGroup> loadGroup;
00620   channel->GetLoadGroup(getter_AddRefs(loadGroup));
00621 
00622   if (request) {
00623     // we have this in our cache already.. cancel the current (document) load
00624 
00625     /* XXX If |*listener| is null when we return here, the caller should 
00626        probably cancel the channel instead of us doing it here.
00627     */
00628     channel->Cancel(NS_IMAGELIB_ERROR_LOAD_ABORTED); // this should fire an OnStopRequest
00629 
00630     *listener = nsnull; // give them back a null nsIStreamListener
00631   } else {
00632     //
00633     // Get the current EventQueue...  This is used as a cacheId to prevent
00634     // sharing requests which are being loaded across multiple event queues...
00635     //
00636     nsCOMPtr<nsIEventQueueService> eventQService;
00637     nsCOMPtr<nsIEventQueue> activeQ;
00638 
00639     eventQService = do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv);
00640     if (NS_FAILED(rv)) 
00641       return rv;
00642         
00643     rv = eventQService->ResolveEventQueue(NS_CURRENT_EVENTQ, getter_AddRefs(activeQ));
00644     if (NS_FAILED(rv))
00645       return rv;
00646 
00647     NS_NEWXPCOM(request, imgRequest);
00648     if (!request) return NS_ERROR_OUT_OF_MEMORY;
00649 
00650     NS_ADDREF(request);
00651 
00652     imgCache::Put(uri, request, getter_AddRefs(entry));
00653 
00654     request->Init(channel, entry, activeQ.get(), aCX);
00655 
00656     ProxyListener *pl = new ProxyListener(NS_STATIC_CAST(nsIStreamListener *, request));
00657     if (!pl) {
00658       NS_RELEASE(request);
00659       return NS_ERROR_OUT_OF_MEMORY;
00660     }
00661 
00662     NS_ADDREF(pl);
00663 
00664     *listener = NS_STATIC_CAST(nsIStreamListener*, pl);
00665     NS_ADDREF(*listener);
00666 
00667     NS_RELEASE(pl);
00668   }
00669 
00670   // XXX: It looks like the wrong load flags are being passed in...
00671   requestFlags &= 0xFFFF;
00672 
00673   rv = CreateNewProxyForRequest(request, loadGroup, aObserver,
00674                                 requestFlags, nsnull, _retval);
00675   request->NotifyProxyListener(NS_STATIC_CAST(imgRequestProxy*, *_retval));
00676 
00677   NS_RELEASE(request);
00678 
00679   return rv;
00680 }
00681 
00682 
00683 nsresult
00684 imgLoader::CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup,
00685                                     imgIDecoderObserver *aObserver,
00686                                     nsLoadFlags aLoadFlags, imgIRequest *aProxyRequest,
00687                                     imgIRequest **_retval)
00688 {
00689   LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest", "imgRequest", aRequest);
00690 
00691   /* XXX If we move decoding onto separate threads, we should save off the
00692      calling thread here and pass it off to |proxyRequest| so that it call
00693      proxy calls to |aObserver|.
00694    */
00695 
00696   imgRequestProxy *proxyRequest;
00697   if (aProxyRequest) {
00698     proxyRequest = NS_STATIC_CAST(imgRequestProxy *, aProxyRequest);
00699   } else {
00700     NS_NEWXPCOM(proxyRequest, imgRequestProxy);
00701     if (!proxyRequest) return NS_ERROR_OUT_OF_MEMORY;
00702   }
00703   NS_ADDREF(proxyRequest);
00704 
00705   /* It is important to call |SetLoadFlags()| before calling |Init()| because
00706      |Init()| adds the request to the loadgroup.
00707    */
00708   proxyRequest->SetLoadFlags(aLoadFlags);
00709 
00710   // init adds itself to imgRequest's list of observers
00711   nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, aObserver);
00712   if (NS_FAILED(rv)) {
00713     NS_RELEASE(proxyRequest);
00714     return rv;
00715   }
00716 
00717   if (*_retval) {
00718     (*_retval)->Cancel(NS_IMAGELIB_ERROR_LOAD_ABORTED);
00719     NS_RELEASE(*_retval);
00720   }
00721   // transfer reference to caller
00722   *_retval = NS_STATIC_CAST(imgIRequest*, proxyRequest);
00723 
00724   return NS_OK;
00725 }
00726 
00727 NS_IMETHODIMP imgLoader::SupportImageWithMimeType(const char* aMimeType, PRBool *_retval)
00728 {
00729   *_retval = PR_FALSE;
00730   nsCOMPtr<nsIComponentRegistrar> reg;
00731   nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(reg));
00732   if (NS_FAILED(rv))
00733     return rv;
00734   nsCAutoString mimeType(aMimeType);
00735   ToLowerCase(mimeType);
00736   nsCAutoString decoderId(NS_LITERAL_CSTRING("@mozilla.org/image/decoder;2?type=") + mimeType);
00737   return reg->IsContractIDRegistered(decoderId.get(),  _retval);
00738 }
00739 
00740 NS_IMETHODIMP imgLoader::GetMIMETypeFromContent(const PRUint8* aContents, PRUint32 aLength, nsACString& aContentType)
00741 {
00742   return GetMimeTypeFromContent((const char*)aContents, aLength, aContentType);
00743 }
00744 
00745 /* static */
00746 nsresult imgLoader::GetMimeTypeFromContent(const char* aContents, PRUint32 aLength, nsACString& aContentType)
00747 {
00748   /* Is it a GIF? */
00749   if (aLength >= 4 && !nsCRT::strncmp(aContents, "GIF8", 4))  {
00750     aContentType.AssignLiteral("image/gif");
00751   }
00752 
00753   /* or a PNG? */
00754   else if (aLength >= 4 && ((unsigned char)aContents[0]==0x89 &&
00755                    (unsigned char)aContents[1]==0x50 &&
00756                    (unsigned char)aContents[2]==0x4E &&
00757                    (unsigned char)aContents[3]==0x47))
00758   { 
00759     aContentType.AssignLiteral("image/png");
00760   }
00761 
00762   /* maybe a JPEG (JFIF)? */
00763   /* JFIF files start with SOI APP0 but older files can start with SOI DQT
00764    * so we test for SOI followed by any marker, i.e. FF D8 FF
00765    * this will also work for SPIFF JPEG files if they appear in the future.
00766    *
00767    * (JFIF is 0XFF 0XD8 0XFF 0XE0 <skip 2> 0X4A 0X46 0X49 0X46 0X00)
00768    */
00769   else if (aLength >= 3 &&
00770      ((unsigned char)aContents[0])==0xFF &&
00771      ((unsigned char)aContents[1])==0xD8 &&
00772      ((unsigned char)aContents[2])==0xFF)
00773   {
00774     aContentType.AssignLiteral("image/jpeg");
00775   }
00776 
00777   /* or how about ART? */
00778   /* ART begins with JG (4A 47). Major version offset 2.
00779    * Minor version offset 3. Offset 4 must be NULL.
00780    */
00781   else if (aLength >= 5 &&
00782    ((unsigned char) aContents[0])==0x4a &&
00783    ((unsigned char) aContents[1])==0x47 &&
00784    ((unsigned char) aContents[4])==0x00 )
00785   {
00786     aContentType.AssignLiteral("image/x-jg");
00787   }
00788 
00789   else if (aLength >= 2 && !nsCRT::strncmp(aContents, "BM", 2)) {
00790     aContentType.AssignLiteral("image/bmp");
00791   }
00792 
00793   // ICOs always begin with a 2-byte 0 followed by a 2-byte 1.
00794   // CURs begin with 2-byte 0 followed by 2-byte 2.
00795   else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) ||
00796                             !memcmp(aContents, "\000\000\002\000", 4))) {
00797     aContentType.AssignLiteral("image/x-icon");
00798   }
00799 
00800   else if (aLength >= 8 && !nsCRT::strncmp(aContents, "#define ", 8)) {
00801     aContentType.AssignLiteral("image/x-xbitmap");
00802   }
00803   else {
00804     /* none of the above?  I give up */
00805     return NS_ERROR_NOT_AVAILABLE;
00806   }
00807 
00808   return NS_OK;
00809 }
00810 
00815 #include "nsIRequest.h"
00816 #include "nsIStreamConverterService.h"
00817 #include "nsXPIDLString.h"
00818 
00819 NS_IMPL_ISUPPORTS2(ProxyListener, nsIStreamListener, nsIRequestObserver)
00820 
00821 ProxyListener::ProxyListener(nsIStreamListener *dest) :
00822   mDestListener(dest)
00823 {
00824   /* member initializers and constructor code */
00825 }
00826 
00827 ProxyListener::~ProxyListener()
00828 {
00829   /* destructor code */
00830 }
00831 
00832 
00835 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
00836 NS_IMETHODIMP ProxyListener::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
00837 {
00838   if (!mDestListener)
00839     return NS_ERROR_FAILURE;
00840 
00841   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
00842   if (channel) {
00843     nsCAutoString contentType;
00844     nsresult rv = channel->GetContentType(contentType);
00845 
00846     if (!contentType.IsEmpty()) {
00847      /* If multipart/x-mixed-replace content, we'll insert a MIME decoder
00848         in the pipeline to handle the content and pass it along to our
00849         original listener.
00850       */
00851       if (NS_LITERAL_CSTRING("multipart/x-mixed-replace").Equals(contentType)) {
00852 
00853         nsCOMPtr<nsIStreamConverterService> convServ(do_GetService("@mozilla.org/streamConverters;1", &rv));
00854         if (NS_SUCCEEDED(rv)) {
00855           nsCOMPtr<nsIStreamListener> toListener(mDestListener);
00856           nsCOMPtr<nsIStreamListener> fromListener;
00857 
00858           rv = convServ->AsyncConvertData("multipart/x-mixed-replace",
00859                                           "*/*",
00860                                           toListener,
00861                                           nsnull,
00862                                           getter_AddRefs(fromListener));
00863           if (NS_SUCCEEDED(rv))
00864             mDestListener = fromListener;
00865         }
00866       }
00867     }
00868   }
00869 
00870   return mDestListener->OnStartRequest(aRequest, ctxt);
00871 }
00872 
00873 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
00874 NS_IMETHODIMP ProxyListener::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
00875 {
00876   if (!mDestListener)
00877     return NS_ERROR_FAILURE;
00878 
00879   return mDestListener->OnStopRequest(aRequest, ctxt, status);
00880 }
00881 
00884 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long sourceOffset, in unsigned long count); */
00885 NS_IMETHODIMP ProxyListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count)
00886 {
00887   if (!mDestListener)
00888     return NS_ERROR_FAILURE;
00889 
00890   return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
00891 }
00892 
00893 
00894 
00895 
00896 
00901 NS_IMPL_ISUPPORTS2(imgCacheValidator, nsIStreamListener, nsIRequestObserver)
00902 
00903 imgCacheValidator::imgCacheValidator(imgRequest *request, void *aContext) :
00904   mContext(aContext)
00905 {
00906   /* member initializers and constructor code */
00907 
00908   mRequest = request;
00909   NS_ADDREF(mRequest);
00910 }
00911 
00912 imgCacheValidator::~imgCacheValidator()
00913 {
00914   /* destructor code */
00915   if (mRequest) {
00916     mRequest->mValidator = nsnull;
00917     NS_RELEASE(mRequest);
00918   }
00919 }
00920 
00921 void imgCacheValidator::AddProxy(imgRequestProxy *aProxy)
00922 {
00923   // aProxy needs to be in the loadgroup since we're validating from
00924   // the network.
00925   aProxy->AddToLoadGroup();
00926 
00927   mProxies.AppendObject(aProxy);
00928 }
00929 
00932 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
00933 NS_IMETHODIMP imgCacheValidator::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
00934 {
00935   nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(aRequest));
00936   if (cacheChan) {
00937     PRBool isFromCache;
00938     if (NS_SUCCEEDED(cacheChan->IsFromCache(&isFromCache)) && isFromCache) {
00939 
00940       PRUint32 count = mProxies.Count();
00941       for (PRInt32 i = count-1; i>=0; i--) {
00942         imgRequestProxy *proxy = NS_STATIC_CAST(imgRequestProxy *, mProxies[i]);
00943         mRequest->NotifyProxyListener(proxy);
00944       }
00945 
00946       mRequest->SetLoadId(mContext);
00947       mRequest->mValidator = nsnull;
00948 
00949       NS_RELEASE(mRequest); // assigns null
00950 
00951       return NS_OK;
00952     }
00953   }
00954   // fun stuff.
00955   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
00956   nsCOMPtr<nsICacheEntryDescriptor> entry;
00957   nsCOMPtr<nsIURI> uri;
00958 
00959   // Doom the old request's cache entry
00960   mRequest->RemoveFromCache();
00961 
00962   mRequest->GetURI(getter_AddRefs(uri));
00963 
00964   mRequest->mValidator = nsnull;
00965   NS_RELEASE(mRequest); // assigns null
00966 
00967   nsresult rv;
00968   nsCOMPtr<nsIEventQueueService> eventQService = do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv);
00969   if (NS_FAILED(rv)) return rv;
00970 
00971   nsCOMPtr<nsIEventQueue> activeQ;
00972   rv = eventQService->ResolveEventQueue(NS_CURRENT_EVENTQ, getter_AddRefs(activeQ));
00973   if (NS_FAILED(rv)) return rv;
00974 
00975   imgRequest *request;
00976   NS_NEWXPCOM(request, imgRequest);
00977   if (!request) return NS_ERROR_OUT_OF_MEMORY;
00978   NS_ADDREF(request);
00979 
00980   imgCache::Put(uri, request, getter_AddRefs(entry));
00981 
00982   request->Init(channel, entry, activeQ.get(), mContext);
00983 
00984   ProxyListener *pl = new ProxyListener(NS_STATIC_CAST(nsIStreamListener *, request));
00985   if (!pl) {
00986     NS_RELEASE(request);
00987     return NS_ERROR_OUT_OF_MEMORY;
00988   }
00989 
00990   mDestListener = NS_STATIC_CAST(nsIStreamListener*, pl);
00991 
00992   PRUint32 count = mProxies.Count();
00993   for (PRInt32 i = count-1; i>=0; i--) {
00994     imgRequestProxy *proxy = NS_STATIC_CAST(imgRequestProxy *, mProxies[i]);
00995     proxy->ChangeOwner(request);
00996     request->NotifyProxyListener(proxy);
00997   }
00998 
00999   NS_RELEASE(request);
01000 
01001   if (!mDestListener)
01002     return NS_OK;
01003 
01004   return mDestListener->OnStartRequest(aRequest, ctxt);
01005 }
01006 
01007 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
01008 NS_IMETHODIMP imgCacheValidator::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
01009 {
01010   if (!mDestListener)
01011     return NS_OK;
01012 
01013   return mDestListener->OnStopRequest(aRequest, ctxt, status);
01014 }
01015 
01019 // XXX see bug 113959
01020 static NS_METHOD dispose_of_data(nsIInputStream* in, void* closure,
01021                                  const char* fromRawSegment, PRUint32 toOffset,
01022                                  PRUint32 count, PRUint32 *writeCount)
01023 {
01024   *writeCount = count;
01025   return NS_OK;
01026 }
01027 
01028 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long sourceOffset, in unsigned long count); */
01029 NS_IMETHODIMP imgCacheValidator::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count)
01030 {
01031 #ifdef DEBUG
01032   nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(aRequest));
01033   if (cacheChan) {
01034     PRBool isFromCache;
01035     if (NS_SUCCEEDED(cacheChan->IsFromCache(&isFromCache)) && isFromCache)
01036       NS_ERROR("OnDataAvailable not suppressed by LOAD_ONLY_IF_MODIFIED load flag");
01037   }
01038 #endif
01039 
01040   if (!mDestListener) {
01041     // XXX see bug 113959
01042     PRUint32 _retval;
01043     inStr->ReadSegments(dispose_of_data, nsnull, count, &_retval);
01044     return NS_OK;
01045   }
01046 
01047   return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
01048 }