PageRenderTime 87ms CodeModel.GetById 16ms app.highlight 62ms RepoModel.GetById 1ms app.codeStats 0ms

/image/src/imgRequest.cpp

http://github.com/zpao/v8monkey
C++ | 1247 lines | 818 code | 222 blank | 207 comment | 144 complexity | 040f609e986a801b571f645aaac4b1f4 MD5 | raw file
   1/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
   2 *
   3 * ***** BEGIN LICENSE BLOCK *****
   4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
   5 *
   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/
  10 *
  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.
  15 *
  16 * The Original Code is mozilla.org code.
  17 *
  18 * The Initial Developer of the Original Code is
  19 * Netscape Communications Corporation.
  20 * Portions created by the Initial Developer are Copyright (C) 2001
  21 * the Initial Developer. All Rights Reserved.
  22 *
  23 * Contributor(s):
  24 *   Stuart Parmenter <stuart@mozilla.com>
  25 *   Bobby Holley <bobbyholley@gmail.com>
  26 *
  27 * Alternatively, the contents of this file may be used under the terms of
  28 * either the GNU General Public License Version 2 or later (the "GPL"), or
  29 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30 * in which case the provisions of the GPL or the LGPL are applicable instead
  31 * of those above. If you wish to allow use of your version of this file only
  32 * under the terms of either the GPL or the LGPL, and not to allow others to
  33 * use your version of this file under the terms of the MPL, indicate your
  34 * decision by deleting the provisions above and replace them with the notice
  35 * and other provisions required by the GPL or the LGPL. If you do not delete
  36 * the provisions above, a recipient may use your version of this file under
  37 * the terms of any one of the MPL, the GPL or the LGPL.
  38 *
  39 * ***** END LICENSE BLOCK ***** */
  40
  41#include "imgRequest.h"
  42#include "ImageLogging.h"
  43
  44/* We end up pulling in windows.h because we eventually hit gfxWindowsSurface;
  45 * windows.h defines LoadImage, so we have to #undef it or imgLoader::LoadImage
  46 * gets changed.
  47 * This #undef needs to be in multiple places because we don't always pull
  48 * headers in in the same order.
  49 */
  50#undef LoadImage
  51
  52#include "imgLoader.h"
  53#include "imgRequestProxy.h"
  54#include "RasterImage.h"
  55#include "VectorImage.h"
  56
  57#include "imgILoader.h"
  58
  59#include "netCore.h"
  60
  61#include "nsIChannel.h"
  62#include "nsICachingChannel.h"
  63#include "nsILoadGroup.h"
  64#include "nsIInputStream.h"
  65#include "nsIMultiPartChannel.h"
  66#include "nsIHttpChannel.h"
  67
  68#include "nsIComponentManager.h"
  69#include "nsIInterfaceRequestorUtils.h"
  70#include "nsIServiceManager.h"
  71#include "nsISupportsPrimitives.h"
  72#include "nsIScriptSecurityManager.h"
  73
  74#include "nsICacheVisitor.h"
  75
  76#include "nsString.h"
  77#include "nsXPIDLString.h"
  78#include "plstr.h" // PL_strcasestr(...)
  79#include "nsNetUtil.h"
  80#include "nsIProtocolHandler.h"
  81
  82#include "mozilla/Preferences.h"
  83
  84#include "DiscardTracker.h"
  85#include "nsAsyncRedirectVerifyHelper.h"
  86
  87#define SVG_MIMETYPE "image/svg+xml"
  88
  89using namespace mozilla;
  90using namespace mozilla::image;
  91
  92static bool gInitializedPrefCaches = false;
  93static bool gDecodeOnDraw = false;
  94static bool gDiscardable = false;
  95
  96static void
  97InitPrefCaches()
  98{
  99  Preferences::AddBoolVarCache(&gDiscardable, "image.mem.discardable");
 100  Preferences::AddBoolVarCache(&gDecodeOnDraw, "image.mem.decodeondraw");
 101  gInitializedPrefCaches = true;
 102}
 103
 104#if defined(PR_LOGGING)
 105PRLogModuleInfo *gImgLog = PR_NewLogModule("imgRequest");
 106#endif
 107
 108NS_IMPL_ISUPPORTS8(imgRequest,
 109                   imgIDecoderObserver, imgIContainerObserver,
 110                   nsIStreamListener, nsIRequestObserver,
 111                   nsISupportsWeakReference,
 112                   nsIChannelEventSink,
 113                   nsIInterfaceRequestor,
 114                   nsIAsyncVerifyRedirectCallback)
 115
 116imgRequest::imgRequest() : 
 117  mValidator(nsnull), mImageSniffers("image-sniffing-services"),
 118  mInnerWindowId(0), mCORSMode(imgIRequest::CORS_NONE),
 119  mDecodeRequested(false), mIsMultiPartChannel(false), mGotData(false),
 120  mIsInCache(false)
 121{
 122  // Register our pref observers if we haven't yet.
 123  if (NS_UNLIKELY(!gInitializedPrefCaches)) {
 124    InitPrefCaches();
 125  }
 126}
 127
 128imgRequest::~imgRequest()
 129{
 130  if (mURI) {
 131    nsCAutoString spec;
 132    mURI->GetSpec(spec);
 133    LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::~imgRequest()", "keyuri", spec.get());
 134  } else
 135    LOG_FUNC(gImgLog, "imgRequest::~imgRequest()");
 136}
 137
 138nsresult imgRequest::Init(nsIURI *aURI,
 139                          nsIURI *aCurrentURI,
 140                          nsIRequest *aRequest,
 141                          nsIChannel *aChannel,
 142                          imgCacheEntry *aCacheEntry,
 143                          void *aLoadId,
 144                          nsIPrincipal* aLoadingPrincipal,
 145                          PRInt32 aCORSMode)
 146{
 147  LOG_FUNC(gImgLog, "imgRequest::Init");
 148
 149  NS_ABORT_IF_FALSE(!mImage, "Multiple calls to init");
 150  NS_ABORT_IF_FALSE(aURI, "No uri");
 151  NS_ABORT_IF_FALSE(aCurrentURI, "No current uri");
 152  NS_ABORT_IF_FALSE(aRequest, "No request");
 153  NS_ABORT_IF_FALSE(aChannel, "No channel");
 154
 155  mProperties = do_CreateInstance("@mozilla.org/properties;1");
 156
 157  mStatusTracker = new imgStatusTracker(nsnull);
 158
 159  mURI = aURI;
 160  mCurrentURI = aCurrentURI;
 161  mRequest = aRequest;
 162  mChannel = aChannel;
 163  mTimedChannel = do_QueryInterface(mChannel);
 164
 165  mLoadingPrincipal = aLoadingPrincipal;
 166  mCORSMode = aCORSMode;
 167
 168  mChannel->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink));
 169
 170  NS_ASSERTION(mPrevChannelSink != this,
 171               "Initializing with a channel that already calls back to us!");
 172
 173  mChannel->SetNotificationCallbacks(this);
 174
 175  mCacheEntry = aCacheEntry;
 176
 177  SetLoadId(aLoadId);
 178
 179  return NS_OK;
 180}
 181
 182imgStatusTracker&
 183imgRequest::GetStatusTracker()
 184{
 185  if (mImage) {
 186    NS_ABORT_IF_FALSE(!mStatusTracker,
 187                      "Should have given mStatusTracker to mImage");
 188    return mImage->GetStatusTracker();
 189  } else {
 190    NS_ABORT_IF_FALSE(mStatusTracker,
 191                      "Should have mStatusTracker until we create mImage");
 192    return *mStatusTracker;
 193  }
 194}
 195
 196void imgRequest::SetCacheEntry(imgCacheEntry *entry)
 197{
 198  mCacheEntry = entry;
 199}
 200
 201bool imgRequest::HasCacheEntry() const
 202{
 203  return mCacheEntry != nsnull;
 204}
 205
 206nsresult imgRequest::AddProxy(imgRequestProxy *proxy)
 207{
 208  NS_PRECONDITION(proxy, "null imgRequestProxy passed in");
 209  LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::AddProxy", "proxy", proxy);
 210
 211  // If we're empty before adding, we have to tell the loader we now have
 212  // proxies.
 213  if (mObservers.IsEmpty()) {
 214    NS_ABORT_IF_FALSE(mURI, "Trying to SetHasProxies without key uri.");
 215    imgLoader::SetHasProxies(mURI);
 216  }
 217
 218  // If we don't have any current observers, we should restart any animation.
 219  if (mImage && !HaveProxyWithObserver(proxy) && proxy->HasObserver()) {
 220    LOG_MSG(gImgLog, "imgRequest::AddProxy", "resetting animation");
 221
 222    mImage->ResetAnimation();
 223  }
 224
 225  proxy->SetPrincipal(mPrincipal);
 226
 227  return mObservers.AppendElementUnlessExists(proxy) ?
 228    NS_OK : NS_ERROR_OUT_OF_MEMORY;
 229}
 230
 231nsresult imgRequest::RemoveProxy(imgRequestProxy *proxy, nsresult aStatus, bool aNotify)
 232{
 233  LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy", "proxy", proxy);
 234
 235  // This will remove our animation consumers, so after removing
 236  // this proxy, we don't end up without proxies with observers, but still
 237  // have animation consumers.
 238  proxy->ClearAnimationConsumers();
 239
 240  if (!mObservers.RemoveElement(proxy)) {
 241    // Not one of our proxies; we're done
 242    return NS_OK;
 243  }
 244
 245  // Let the status tracker do its thing before we potentially call Cancel()
 246  // below, because Cancel() may result in OnStopRequest being called back
 247  // before Cancel() returns, leaving the image in a different state then the
 248  // one it was in at this point.
 249
 250  imgStatusTracker& statusTracker = GetStatusTracker();
 251  statusTracker.EmulateRequestFinished(proxy, aStatus, !aNotify);
 252
 253  if (mObservers.IsEmpty()) {
 254    // If we have no observers, there's nothing holding us alive. If we haven't
 255    // been cancelled and thus removed from the cache, tell the image loader so
 256    // we can be evicted from the cache.
 257    if (mCacheEntry) {
 258      NS_ABORT_IF_FALSE(mURI, "Removing last observer without key uri.");
 259
 260      imgLoader::SetHasNoProxies(mURI, mCacheEntry);
 261    } 
 262#if defined(PR_LOGGING)
 263    else {
 264      nsCAutoString spec;
 265      mURI->GetSpec(spec);
 266      LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy no cache entry", "uri", spec.get());
 267    }
 268#endif
 269
 270    /* If |aStatus| is a failure code, then cancel the load if it is still in progress.
 271       Otherwise, let the load continue, keeping 'this' in the cache with no observers.
 272       This way, if a proxy is destroyed without calling cancel on it, it won't leak
 273       and won't leave a bad pointer in mObservers.
 274     */
 275    if (statusTracker.IsLoading() && NS_FAILED(aStatus)) {
 276      LOG_MSG(gImgLog, "imgRequest::RemoveProxy", "load in progress.  canceling");
 277
 278      this->Cancel(NS_BINDING_ABORTED);
 279    }
 280
 281    /* break the cycle from the cache entry. */
 282    mCacheEntry = nsnull;
 283  }
 284
 285  // If a proxy is removed for a reason other than its owner being
 286  // changed, remove the proxy from the loadgroup.
 287  if (aStatus != NS_IMAGELIB_CHANGING_OWNER)
 288    proxy->RemoveFromLoadGroup(true);
 289
 290  return NS_OK;
 291}
 292
 293void imgRequest::CancelAndAbort(nsresult aStatus)
 294{
 295  LOG_SCOPE(gImgLog, "imgRequest::CancelAndAbort");
 296
 297  Cancel(aStatus);
 298
 299  // It's possible for the channel to fail to open after we've set our
 300  // notification callbacks. In that case, make sure to break the cycle between
 301  // the channel and us, because it won't.
 302  if (mChannel) {
 303    mChannel->SetNotificationCallbacks(mPrevChannelSink);
 304    mPrevChannelSink = nsnull;
 305  }
 306}
 307
 308void imgRequest::Cancel(nsresult aStatus)
 309{
 310  /* The Cancel() method here should only be called by this class. */
 311
 312  LOG_SCOPE(gImgLog, "imgRequest::Cancel");
 313
 314  imgStatusTracker& statusTracker = GetStatusTracker();
 315  statusTracker.RecordCancel();
 316
 317  RemoveFromCache();
 318
 319  if (mRequest && statusTracker.IsLoading())
 320    mRequest->Cancel(aStatus);
 321}
 322
 323nsresult imgRequest::GetURI(nsIURI **aURI)
 324{
 325  LOG_FUNC(gImgLog, "imgRequest::GetURI");
 326
 327  if (mURI) {
 328    *aURI = mURI;
 329    NS_ADDREF(*aURI);
 330    return NS_OK;
 331  }
 332
 333  return NS_ERROR_FAILURE;
 334}
 335
 336nsresult imgRequest::GetSecurityInfo(nsISupports **aSecurityInfo)
 337{
 338  LOG_FUNC(gImgLog, "imgRequest::GetSecurityInfo");
 339
 340  // Missing security info means this is not a security load
 341  // i.e. it is not an error when security info is missing
 342  NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
 343  return NS_OK;
 344}
 345
 346void imgRequest::RemoveFromCache()
 347{
 348  LOG_SCOPE(gImgLog, "imgRequest::RemoveFromCache");
 349
 350  if (mIsInCache) {
 351    // mCacheEntry is nulled out when we have no more observers.
 352    if (mCacheEntry)
 353      imgLoader::RemoveFromCache(mCacheEntry);
 354    else
 355      imgLoader::RemoveFromCache(mURI);
 356  }
 357
 358  mCacheEntry = nsnull;
 359}
 360
 361bool imgRequest::HaveProxyWithObserver(imgRequestProxy* aProxyToIgnore) const
 362{
 363  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 364  imgRequestProxy* proxy;
 365  while (iter.HasMore()) {
 366    proxy = iter.GetNext();
 367    if (proxy == aProxyToIgnore) {
 368      continue;
 369    }
 370    
 371    if (proxy->HasObserver()) {
 372      return true;
 373    }
 374  }
 375  
 376  return false;
 377}
 378
 379PRInt32 imgRequest::Priority() const
 380{
 381  PRInt32 priority = nsISupportsPriority::PRIORITY_NORMAL;
 382  nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);
 383  if (p)
 384    p->GetPriority(&priority);
 385  return priority;
 386}
 387
 388void imgRequest::AdjustPriority(imgRequestProxy *proxy, PRInt32 delta)
 389{
 390  // only the first proxy is allowed to modify the priority of this image load.
 391  //
 392  // XXX(darin): this is probably not the most optimal algorithm as we may want
 393  // to increase the priority of requests that have a lot of proxies.  the key
 394  // concern though is that image loads remain lower priority than other pieces
 395  // of content such as link clicks, CSS, and JS.
 396  //
 397  if (mObservers.SafeElementAt(0, nsnull) != proxy)
 398    return;
 399
 400  nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);
 401  if (p)
 402    p->AdjustPriority(delta);
 403}
 404
 405void imgRequest::SetIsInCache(bool incache)
 406{
 407  LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::SetIsCacheable", "incache", incache);
 408  mIsInCache = incache;
 409}
 410
 411void imgRequest::UpdateCacheEntrySize()
 412{
 413  if (mCacheEntry) {
 414    mCacheEntry->SetDataSize(mImage->GetDataSize());
 415
 416#ifdef DEBUG_joe
 417    nsCAutoString url;
 418    mURI->GetSpec(url);
 419    printf("CACHEPUT: %d %s %d\n", time(NULL), url.get(), imageSize);
 420#endif
 421  }
 422}
 423
 424void imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, nsIRequest* aRequest)
 425{
 426  /* get the expires info */
 427  if (aCacheEntry) {
 428    nsCOMPtr<nsICachingChannel> cacheChannel(do_QueryInterface(aRequest));
 429    if (cacheChannel) {
 430      nsCOMPtr<nsISupports> cacheToken;
 431      cacheChannel->GetCacheToken(getter_AddRefs(cacheToken));
 432      if (cacheToken) {
 433        nsCOMPtr<nsICacheEntryInfo> entryDesc(do_QueryInterface(cacheToken));
 434        if (entryDesc) {
 435          PRUint32 expiration;
 436          /* get the expiration time from the caching channel's token */
 437          entryDesc->GetExpirationTime(&expiration);
 438
 439          // Expiration time defaults to 0. We set the expiration time on our
 440          // entry if it hasn't been set yet.
 441          if (aCacheEntry->GetExpiryTime() == 0)
 442            aCacheEntry->SetExpiryTime(expiration);
 443        }
 444      }
 445    }
 446
 447    // Determine whether the cache entry must be revalidated when we try to use it.
 448    // Currently, only HTTP specifies this information...
 449    nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
 450    if (httpChannel) {
 451      bool bMustRevalidate = false;
 452
 453      httpChannel->IsNoStoreResponse(&bMustRevalidate);
 454
 455      if (!bMustRevalidate) {
 456        httpChannel->IsNoCacheResponse(&bMustRevalidate);
 457      }
 458
 459      if (!bMustRevalidate) {
 460        nsCAutoString cacheHeader;
 461
 462        httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Cache-Control"),
 463                                            cacheHeader);
 464        if (PL_strcasestr(cacheHeader.get(), "must-revalidate")) {
 465          bMustRevalidate = true;
 466        }
 467      }
 468
 469      // Cache entries default to not needing to validate. We ensure that
 470      // multiple calls to this function don't override an earlier decision to
 471      // validate by making validation a one-way decision.
 472      if (bMustRevalidate)
 473        aCacheEntry->SetMustValidate(bMustRevalidate);
 474    }
 475
 476    // We always need to validate file URIs.
 477    nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
 478    if (channel) {
 479      nsCOMPtr<nsIURI> uri;
 480      channel->GetURI(getter_AddRefs(uri));
 481      bool isfile = false;
 482      uri->SchemeIs("file", &isfile);
 483      if (isfile)
 484        aCacheEntry->SetMustValidate(isfile);
 485    }
 486  }
 487}
 488
 489nsresult
 490imgRequest::LockImage()
 491{
 492  return mImage->LockImage();
 493}
 494
 495nsresult
 496imgRequest::UnlockImage()
 497{
 498  return mImage->UnlockImage();
 499}
 500
 501nsresult
 502imgRequest::RequestDecode()
 503{
 504  // If we've initialized our image, we can request a decode.
 505  if (mImage) {
 506    return mImage->RequestDecode();
 507  }
 508
 509  // Otherwise, flag to do it when we get the image
 510  mDecodeRequested = true;
 511
 512  return NS_OK;
 513}
 514
 515/** imgIContainerObserver methods **/
 516
 517/* [noscript] void frameChanged (in imgIRequest request,
 518                                 in imgIContainer container,
 519                                 in nsIntRect dirtyRect); */
 520NS_IMETHODIMP imgRequest::FrameChanged(imgIRequest *request,
 521                                       imgIContainer *container,
 522                                       const nsIntRect *dirtyRect)
 523{
 524  LOG_SCOPE(gImgLog, "imgRequest::FrameChanged");
 525  NS_ABORT_IF_FALSE(mImage,
 526                    "FrameChanged callback before we've created our image");
 527
 528  mImage->GetStatusTracker().RecordFrameChanged(container, dirtyRect);
 529
 530  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 531  while (iter.HasMore()) {
 532    mImage->GetStatusTracker().SendFrameChanged(iter.GetNext(), container, dirtyRect);
 533  }
 534
 535  return NS_OK;
 536}
 537
 538/** imgIDecoderObserver methods **/
 539
 540/* void onStartDecode (in imgIRequest request); */
 541NS_IMETHODIMP imgRequest::OnStartDecode(imgIRequest *request)
 542{
 543  LOG_SCOPE(gImgLog, "imgRequest::OnStartDecode");
 544  NS_ABORT_IF_FALSE(mImage,
 545                    "OnStartDecode callback before we've created our image");
 546
 547
 548  mImage->GetStatusTracker().RecordStartDecode();
 549
 550  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 551  while (iter.HasMore()) {
 552    mImage->GetStatusTracker().SendStartDecode(iter.GetNext());
 553  }
 554
 555  /* In the case of streaming jpegs, it is possible to get multiple OnStartDecodes which
 556     indicates the beginning of a new decode.
 557     The cache entry's size therefore needs to be reset to 0 here.  If we do not do this,
 558     the code in imgRequest::OnStopFrame will continue to increase the data size cumulatively.
 559   */
 560  if (mCacheEntry)
 561    mCacheEntry->SetDataSize(0);
 562
 563  return NS_OK;
 564}
 565
 566NS_IMETHODIMP imgRequest::OnStartRequest(imgIRequest *aRequest)
 567{
 568  NS_NOTREACHED("imgRequest(imgIDecoderObserver)::OnStartRequest");
 569  return NS_OK;
 570}
 571
 572/* void onStartContainer (in imgIRequest request, in imgIContainer image); */
 573NS_IMETHODIMP imgRequest::OnStartContainer(imgIRequest *request, imgIContainer *image)
 574{
 575  LOG_SCOPE(gImgLog, "imgRequest::OnStartContainer");
 576
 577  NS_ASSERTION(image, "imgRequest::OnStartContainer called with a null image!");
 578  if (!image) return NS_ERROR_UNEXPECTED;
 579
 580  NS_ABORT_IF_FALSE(mImage,
 581                    "OnStartContainer callback before we've created our image");
 582  NS_ABORT_IF_FALSE(image == mImage,
 583                    "OnStartContainer callback from an image we don't own");
 584  mImage->GetStatusTracker().RecordStartContainer(image);
 585
 586  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 587  while (iter.HasMore()) {
 588    mImage->GetStatusTracker().SendStartContainer(iter.GetNext(), image);
 589  }
 590
 591  return NS_OK;
 592}
 593
 594/* void onStartFrame (in imgIRequest request, in unsigned long frame); */
 595NS_IMETHODIMP imgRequest::OnStartFrame(imgIRequest *request,
 596                                       PRUint32 frame)
 597{
 598  LOG_SCOPE(gImgLog, "imgRequest::OnStartFrame");
 599  NS_ABORT_IF_FALSE(mImage,
 600                    "OnStartFrame callback before we've created our image");
 601
 602  mImage->GetStatusTracker().RecordStartFrame(frame);
 603
 604  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 605  while (iter.HasMore()) {
 606    mImage->GetStatusTracker().SendStartFrame(iter.GetNext(), frame);
 607  }
 608
 609  return NS_OK;
 610}
 611
 612/* [noscript] void onDataAvailable (in imgIRequest request, in boolean aCurrentFrame, [const] in nsIntRect rect); */
 613NS_IMETHODIMP imgRequest::OnDataAvailable(imgIRequest *request,
 614                                          bool aCurrentFrame,
 615                                          const nsIntRect * rect)
 616{
 617  LOG_SCOPE(gImgLog, "imgRequest::OnDataAvailable");
 618  NS_ABORT_IF_FALSE(mImage,
 619                    "OnDataAvailable callback before we've created our image");
 620
 621  mImage->GetStatusTracker().RecordDataAvailable(aCurrentFrame, rect);
 622
 623  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 624  while (iter.HasMore()) {
 625    mImage->GetStatusTracker().SendDataAvailable(iter.GetNext(), aCurrentFrame, rect);
 626  }
 627
 628  return NS_OK;
 629}
 630
 631/* void onStopFrame (in imgIRequest request, in unsigned long frame); */
 632NS_IMETHODIMP imgRequest::OnStopFrame(imgIRequest *request,
 633                                      PRUint32 frame)
 634{
 635  LOG_SCOPE(gImgLog, "imgRequest::OnStopFrame");
 636  NS_ABORT_IF_FALSE(mImage,
 637                    "OnStopFrame callback before we've created our image");
 638
 639  mImage->GetStatusTracker().RecordStopFrame(frame);
 640
 641  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 642  while (iter.HasMore()) {
 643    mImage->GetStatusTracker().SendStopFrame(iter.GetNext(), frame);
 644  }
 645
 646  return NS_OK;
 647}
 648
 649/* void onStopContainer (in imgIRequest request, in imgIContainer image); */
 650NS_IMETHODIMP imgRequest::OnStopContainer(imgIRequest *request,
 651                                          imgIContainer *image)
 652{
 653  LOG_SCOPE(gImgLog, "imgRequest::OnStopContainer");
 654  NS_ABORT_IF_FALSE(mImage,
 655                    "OnDataContainer callback before we've created our image");
 656
 657  mImage->GetStatusTracker().RecordStopContainer(image);
 658
 659  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 660  while (iter.HasMore()) {
 661    mImage->GetStatusTracker().SendStopContainer(iter.GetNext(), image);
 662  }
 663
 664  return NS_OK;
 665}
 666
 667/* void onStopDecode (in imgIRequest request, in nsresult status, in wstring statusArg); */
 668NS_IMETHODIMP imgRequest::OnStopDecode(imgIRequest *aRequest,
 669                                       nsresult aStatus,
 670                                       const PRUnichar *aStatusArg)
 671{
 672  LOG_SCOPE(gImgLog, "imgRequest::OnStopDecode");
 673  NS_ABORT_IF_FALSE(mImage,
 674                    "OnDataDecode callback before we've created our image");
 675
 676  // We finished the decode, and thus have the decoded frames. Update the cache
 677  // entry size to take this into account.
 678  UpdateCacheEntrySize();
 679
 680  mImage->GetStatusTracker().RecordStopDecode(aStatus, aStatusArg);
 681
 682  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 683  while (iter.HasMore()) {
 684    mImage->GetStatusTracker().SendStopDecode(iter.GetNext(), aStatus,
 685                                              aStatusArg);
 686  }
 687
 688  // RasterImage and everything below it is completely correct and
 689  // bulletproof about its handling of decoder notifications.
 690  // Unfortunately, here and above we have to make some gross and
 691  // inappropriate use of things to get things to work without
 692  // completely overhauling the decoder observer interface (this will,
 693  // thankfully, happen in bug 505385). From imgRequest and above (for
 694  // the time being), OnStopDecode is just a companion to OnStopRequest
 695  // that signals success or failure of the _load_ (not the _decode_).
 696  // Within imgStatusTracker, we ignore OnStopDecode notifications from the
 697  // decoder and RasterImage and generate our own every time we send
 698  // OnStopRequest. From within SendStopDecode, we actually send
 699  // OnStopContainer.  For more information, see bug 435296.
 700
 701  return NS_OK;
 702}
 703
 704NS_IMETHODIMP imgRequest::OnStopRequest(imgIRequest *aRequest,
 705                                        bool aLastPart)
 706{
 707  NS_NOTREACHED("imgRequest(imgIDecoderObserver)::OnStopRequest");
 708  return NS_OK;
 709}
 710
 711/* void onDiscard (in imgIRequest request); */
 712NS_IMETHODIMP imgRequest::OnDiscard(imgIRequest *aRequest)
 713{
 714  NS_ABORT_IF_FALSE(mImage,
 715                    "OnDiscard callback before we've created our image");
 716
 717  mImage->GetStatusTracker().RecordDiscard();
 718
 719  // Update the cache entry size, since we just got rid of frame data
 720  UpdateCacheEntrySize();
 721
 722  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 723  while (iter.HasMore()) {
 724    mImage->GetStatusTracker().SendDiscard(iter.GetNext());
 725  }
 726
 727  return NS_OK;
 728}
 729
 730NS_IMETHODIMP imgRequest::OnImageIsAnimated(imgIRequest *aRequest)
 731{
 732  NS_ABORT_IF_FALSE(mImage,
 733                    "OnImageIsAnimated callback before we've created our image");
 734  mImage->GetStatusTracker().RecordImageIsAnimated();
 735
 736  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 737  while (iter.HasMore()) {
 738    mImage->GetStatusTracker().SendImageIsAnimated(iter.GetNext());
 739  }
 740
 741  return NS_OK;
 742}
 743
 744/** nsIRequestObserver methods **/
 745
 746/* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
 747NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
 748{
 749  LOG_SCOPE(gImgLog, "imgRequest::OnStartRequest");
 750
 751  // Figure out if we're multipart
 752  nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
 753  if (mpchan)
 754      mIsMultiPartChannel = true;
 755
 756  // If we're not multipart, we shouldn't have an image yet
 757  NS_ABORT_IF_FALSE(mIsMultiPartChannel || !mImage,
 758                    "Already have an image for non-multipart request");
 759
 760  // If we're multipart, and our image is initialized, fix things up for another round
 761  if (mIsMultiPartChannel && mImage) {
 762    if (mImage->GetType() == imgIContainer::TYPE_RASTER) {
 763      // Inform the RasterImage that we have new source data
 764      static_cast<RasterImage*>(mImage.get())->NewSourceData();
 765    } else {  // imageType == imgIContainer::TYPE_VECTOR
 766      nsCOMPtr<nsIStreamListener> imageAsStream = do_QueryInterface(mImage);
 767      NS_ABORT_IF_FALSE(imageAsStream,
 768                        "SVG-typed Image failed QI to nsIStreamListener");
 769      imageAsStream->OnStartRequest(aRequest, ctxt);
 770    }
 771  }
 772
 773  /*
 774   * If mRequest is null here, then we need to set it so that we'll be able to
 775   * cancel it if our Cancel() method is called.  Note that this can only
 776   * happen for multipart channels.  We could simply not null out mRequest for
 777   * non-last parts, if GetIsLastPart() were reliable, but it's not.  See
 778   * https://bugzilla.mozilla.org/show_bug.cgi?id=339610
 779   */
 780  if (!mRequest) {
 781    NS_ASSERTION(mpchan,
 782                 "We should have an mRequest here unless we're multipart");
 783    nsCOMPtr<nsIChannel> chan;
 784    mpchan->GetBaseChannel(getter_AddRefs(chan));
 785    mRequest = chan;
 786  }
 787
 788  imgStatusTracker& statusTracker = GetStatusTracker();
 789  statusTracker.RecordStartRequest();
 790
 791  nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
 792  if (channel)
 793    channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
 794
 795  nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 796  while (iter.HasMore()) {
 797    statusTracker.SendStartRequest(iter.GetNext());
 798  }
 799
 800  /* Get our principal */
 801  nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
 802  if (chan) {
 803    nsCOMPtr<nsIScriptSecurityManager> secMan =
 804      do_GetService("@mozilla.org/scriptsecuritymanager;1");
 805    if (secMan) {
 806      nsresult rv = secMan->GetChannelPrincipal(chan,
 807                                                getter_AddRefs(mPrincipal));
 808      if (NS_FAILED(rv)) {
 809        return rv;
 810      }
 811
 812      // Tell all of our proxies that we have a principal.
 813      nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 814      while (iter.HasMore()) {
 815        iter.GetNext()->SetPrincipal(mPrincipal);
 816      }
 817    }
 818  }
 819
 820  SetCacheValidation(mCacheEntry, aRequest);
 821
 822  // Shouldn't we be dead already if this gets hit?  Probably multipart/x-mixed-replace...
 823  if (mObservers.IsEmpty()) {
 824    this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
 825  }
 826
 827  return NS_OK;
 828}
 829
 830/* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
 831NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
 832{
 833  LOG_FUNC(gImgLog, "imgRequest::OnStopRequest");
 834
 835  bool lastPart = true;
 836  nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
 837  if (mpchan)
 838    mpchan->GetIsLastPart(&lastPart);
 839
 840  // XXXldb What if this is a non-last part of a multipart request?
 841  // xxx before we release our reference to mRequest, lets
 842  // save the last status that we saw so that the
 843  // imgRequestProxy will have access to it.
 844  if (mRequest) {
 845    mRequest = nsnull;  // we no longer need the request
 846  }
 847
 848  // stop holding a ref to the channel, since we don't need it anymore
 849  if (mChannel) {
 850    mChannel->SetNotificationCallbacks(mPrevChannelSink);
 851    mPrevChannelSink = nsnull;
 852    mChannel = nsnull;
 853  }
 854
 855  // Tell the image that it has all of the source data. Note that this can
 856  // trigger a failure, since the image might be waiting for more non-optional
 857  // data and this is the point where we break the news that it's not coming.
 858  if (mImage) {
 859    nsresult rv;
 860    if (mImage->GetType() == imgIContainer::TYPE_RASTER) {
 861      // Notify the image
 862      rv = static_cast<RasterImage*>(mImage.get())->SourceDataComplete();
 863    } else { // imageType == imgIContainer::TYPE_VECTOR
 864      nsCOMPtr<nsIStreamListener> imageAsStream = do_QueryInterface(mImage);
 865      NS_ABORT_IF_FALSE(imageAsStream,
 866                        "SVG-typed Image failed QI to nsIStreamListener");
 867      rv = imageAsStream->OnStopRequest(aRequest, ctxt, status);
 868    }
 869
 870    // If we got an error in the SourceDataComplete() / OnStopRequest() call,
 871    // we don't want to proceed as if nothing bad happened. However, we also
 872    // want to give precedence to failure status codes from necko, since
 873    // presumably they're more meaningful.
 874    if (NS_FAILED(rv) && NS_SUCCEEDED(status))
 875      status = rv;
 876  }
 877
 878  imgStatusTracker& statusTracker = GetStatusTracker();
 879  statusTracker.RecordStopRequest(lastPart, status);
 880
 881  // If the request went through, update the cache entry size. Otherwise,
 882  // cancel the request, which removes us from the cache.
 883  if (mImage && NS_SUCCEEDED(status)) {
 884    // We update the cache entry size here because this is where we finish
 885    // loading compressed source data, which is part of our size calculus.
 886    UpdateCacheEntrySize();
 887  }
 888  else {
 889    // stops animations, removes from cache
 890    this->Cancel(status);
 891  }
 892
 893  /* notify the kids */
 894  nsTObserverArray<imgRequestProxy*>::ForwardIterator srIter(mObservers);
 895  while (srIter.HasMore()) {
 896    statusTracker.SendStopRequest(srIter.GetNext(), lastPart, status);
 897  }
 898
 899  mTimedChannel = nsnull;
 900  return NS_OK;
 901}
 902
 903/* prototype for these defined below */
 904static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, void* closure, const char* fromRawSegment,
 905                                         PRUint32 toOffset, PRUint32 count, PRUint32 *writeCount);
 906
 907/** nsIStreamListener methods **/
 908
 909/* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long sourceOffset, in unsigned long count); */
 910NS_IMETHODIMP imgRequest::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count)
 911{
 912  LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::OnDataAvailable", "count", count);
 913
 914  NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!");
 915
 916  nsresult rv;
 917
 918  PRUint16 imageType;
 919  if (mGotData) {
 920    imageType = mImage->GetType();
 921  } else {
 922    LOG_SCOPE(gImgLog, "imgRequest::OnDataAvailable |First time through... finding mimetype|");
 923
 924    mGotData = true;
 925
 926    /* look at the first few bytes and see if we can tell what the data is from that
 927     * since servers tend to lie. :(
 928     */
 929    PRUint32 out;
 930    inStr->ReadSegments(sniff_mimetype_callback, this, count, &out);
 931
 932#ifdef NS_DEBUG
 933    /* NS_WARNING if the content type from the channel isn't the same if the sniffing */
 934#endif
 935
 936    nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
 937    if (mContentType.IsEmpty()) {
 938      LOG_SCOPE(gImgLog, "imgRequest::OnDataAvailable |sniffing of mimetype failed|");
 939
 940      rv = NS_ERROR_FAILURE;
 941      if (chan) {
 942        rv = chan->GetContentType(mContentType);
 943      }
 944
 945      if (NS_FAILED(rv)) {
 946        PR_LOG(gImgLog, PR_LOG_ERROR,
 947               ("[this=%p] imgRequest::OnDataAvailable -- Content type unavailable from the channel\n",
 948                this));
 949
 950        this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
 951
 952        return NS_BINDING_ABORTED;
 953      }
 954
 955      LOG_MSG(gImgLog, "imgRequest::OnDataAvailable", "Got content type from the channel");
 956    }
 957
 958    /* now we have mimetype, so we can infer the image type that we want */
 959    if (mContentType.EqualsLiteral(SVG_MIMETYPE)) {
 960      mImage = new VectorImage(mStatusTracker.forget());
 961    } else {
 962      mImage = new RasterImage(mStatusTracker.forget());
 963    }
 964    mImage->SetInnerWindowID(mInnerWindowId);
 965    imageType = mImage->GetType();
 966
 967    // Notify any imgRequestProxys that are observing us that we have an Image.
 968    nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
 969    while (iter.HasMore()) {
 970      iter.GetNext()->SetImage(mImage);
 971    }
 972
 973    /* set our mimetype as a property */
 974    nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1"));
 975    if (contentType) {
 976      contentType->SetData(mContentType);
 977      mProperties->Set("type", contentType);
 978    }
 979
 980    /* set our content disposition as a property */
 981    nsCAutoString disposition;
 982    if (chan) {
 983      chan->GetContentDispositionHeader(disposition);
 984    }
 985    if (!disposition.IsEmpty()) {
 986      nsCOMPtr<nsISupportsCString> contentDisposition(do_CreateInstance("@mozilla.org/supports-cstring;1"));
 987      if (contentDisposition) {
 988        contentDisposition->SetData(disposition);
 989        mProperties->Set("content-disposition", contentDisposition);
 990      }
 991    }
 992
 993    LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::OnDataAvailable", "content type", mContentType.get());
 994
 995    //
 996    // Figure out our Image initialization flags
 997    //
 998
 999    // We default to the static globals
1000    bool isDiscardable = gDiscardable;
1001    bool doDecodeOnDraw = gDecodeOnDraw;
1002
1003    // We want UI to be as snappy as possible and not to flicker. Disable discarding
1004    // and decode-on-draw for chrome URLS
1005    bool isChrome = false;
1006    rv = mURI->SchemeIs("chrome", &isChrome);
1007    if (NS_SUCCEEDED(rv) && isChrome)
1008      isDiscardable = doDecodeOnDraw = false;
1009
1010    // We don't want resources like the "loading" icon to be discardable or
1011    // decode-on-draw either.
1012    bool isResource = false;
1013    rv = mURI->SchemeIs("resource", &isResource);
1014    if (NS_SUCCEEDED(rv) && isResource)
1015      isDiscardable = doDecodeOnDraw = false;
1016
1017    // For multipart/x-mixed-replace, we basically want a direct channel to the
1018    // decoder. Disable both for this case as well.
1019    if (mIsMultiPartChannel)
1020      isDiscardable = doDecodeOnDraw = false;
1021
1022    // We have all the information we need
1023    PRUint32 imageFlags = Image::INIT_FLAG_NONE;
1024    if (isDiscardable)
1025      imageFlags |= Image::INIT_FLAG_DISCARDABLE;
1026    if (doDecodeOnDraw)
1027      imageFlags |= Image::INIT_FLAG_DECODE_ON_DRAW;
1028    if (mIsMultiPartChannel)
1029      imageFlags |= Image::INIT_FLAG_MULTIPART;
1030
1031    // Get our URI string
1032    nsCAutoString uriString;
1033    rv = mURI->GetSpec(uriString);
1034    if (NS_FAILED(rv))
1035      uriString.Assign("<unknown image URI>");
1036
1037    // Initialize the image that we created above. For RasterImages, this
1038    // instantiates a decoder behind the scenes, so if we don't have a decoder
1039    // for this mimetype we'll find out about it here.
1040    rv = mImage->Init(this, mContentType.get(), uriString.get(), imageFlags);
1041    if (NS_FAILED(rv)) { // Probably bad mimetype
1042
1043      this->Cancel(rv);
1044      return NS_BINDING_ABORTED;
1045    }
1046
1047    if (imageType == imgIContainer::TYPE_RASTER) {
1048      /* Use content-length as a size hint for http channels. */
1049      nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
1050      if (httpChannel) {
1051        nsCAutoString contentLength;
1052        rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-length"),
1053                                            contentLength);
1054        if (NS_SUCCEEDED(rv)) {
1055          PRInt32 len = contentLength.ToInteger(&rv);
1056
1057          // Pass anything usable on so that the RasterImage can preallocate
1058          // its source buffer
1059          if (len > 0) {
1060            PRUint32 sizeHint = (PRUint32) len;
1061            sizeHint = NS_MIN<PRUint32>(sizeHint, 20000000); /* Bound by something reasonable */
1062            RasterImage* rasterImage = static_cast<RasterImage*>(mImage.get());
1063            rv = rasterImage->SetSourceSizeHint(sizeHint);
1064            if (NS_FAILED(rv)) {
1065              // Flush memory, try to get some back, and try again
1066              rv = nsMemory::HeapMinimize(true);
1067              rv |= rasterImage->SetSourceSizeHint(sizeHint);
1068              // If we've still failed at this point, things are going downhill
1069              if (NS_FAILED(rv)) {
1070                NS_WARNING("About to hit OOM in imagelib!");
1071              }
1072            }
1073          }
1074        }
1075      }
1076    }
1077
1078    if (imageType == imgIContainer::TYPE_RASTER) {
1079      // If we were waiting on the image to do something, now's our chance.
1080      if (mDecodeRequested) {
1081        mImage->RequestDecode();
1082      }
1083    } else { // imageType == imgIContainer::TYPE_VECTOR
1084      nsCOMPtr<nsIStreamListener> imageAsStream = do_QueryInterface(mImage);
1085      NS_ABORT_IF_FALSE(imageAsStream,
1086                        "SVG-typed Image failed QI to nsIStreamListener");
1087      imageAsStream->OnStartRequest(aRequest, nsnull);
1088    }
1089  }
1090
1091  if (imageType == imgIContainer::TYPE_RASTER) {
1092    // WriteToRasterImage always consumes everything it gets
1093    // if it doesn't run out of memory
1094    PRUint32 bytesRead;
1095    rv = inStr->ReadSegments(RasterImage::WriteToRasterImage,
1096                             static_cast<void*>(mImage),
1097                             count, &bytesRead);
1098    NS_ABORT_IF_FALSE(bytesRead == count || mImage->HasError(),
1099  "WriteToRasterImage should consume everything or the image must be in error!");
1100  } else { // imageType == imgIContainer::TYPE_VECTOR
1101    nsCOMPtr<nsIStreamListener> imageAsStream = do_QueryInterface(mImage);
1102    rv = imageAsStream->OnDataAvailable(aRequest, ctxt, inStr,
1103                                        sourceOffset, count);
1104  }
1105  if (NS_FAILED(rv)) {
1106    PR_LOG(gImgLog, PR_LOG_WARNING,
1107           ("[this=%p] imgRequest::OnDataAvailable -- "
1108            "copy to RasterImage failed\n", this));
1109    this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
1110    return NS_BINDING_ABORTED;
1111  }
1112
1113  return NS_OK;
1114}
1115
1116static NS_METHOD sniff_mimetype_callback(nsIInputStream* in,
1117                                         void* closure,
1118                                         const char* fromRawSegment,
1119                                         PRUint32 toOffset,
1120                                         PRUint32 count,
1121                                         PRUint32 *writeCount)
1122{
1123  imgRequest *request = static_cast<imgRequest*>(closure);
1124
1125  NS_ASSERTION(request, "request is null!");
1126
1127  if (count > 0)
1128    request->SniffMimeType(fromRawSegment, count);
1129
1130  *writeCount = 0;
1131  return NS_ERROR_FAILURE;
1132}
1133
1134void
1135imgRequest::SniffMimeType(const char *buf, PRUint32 len)
1136{
1137  imgLoader::GetMimeTypeFromContent(buf, len, mContentType);
1138
1139  // The vast majority of the time, imgLoader will find a gif/jpeg/png image
1140  // and fill mContentType with the sniffed MIME type.
1141  if (!mContentType.IsEmpty())
1142    return;
1143
1144  // When our sniffing fails, we want to query registered image decoders
1145  // to see if they can identify the image. If we always trusted the server
1146  // to send the right MIME, images sent as text/plain would not be rendered.
1147  const nsCOMArray<nsIContentSniffer>& sniffers = mImageSniffers.GetEntries();
1148  PRUint32 length = sniffers.Count();
1149  for (PRUint32 i = 0; i < length; ++i) {
1150    nsresult rv =
1151      sniffers[i]->GetMIMETypeFromContent(nsnull, (const PRUint8 *) buf, len, mContentType);
1152    if (NS_SUCCEEDED(rv) && !mContentType.IsEmpty()) {
1153      return;
1154    }
1155  }
1156}
1157
1158
1159/** nsIInterfaceRequestor methods **/
1160
1161NS_IMETHODIMP
1162imgRequest::GetInterface(const nsIID & aIID, void **aResult)
1163{
1164  if (!mPrevChannelSink || aIID.Equals(NS_GET_IID(nsIChannelEventSink)))
1165    return QueryInterface(aIID, aResult);
1166
1167  NS_ASSERTION(mPrevChannelSink != this, 
1168               "Infinite recursion - don't keep track of channel sinks that are us!");
1169  return mPrevChannelSink->GetInterface(aIID, aResult);
1170}
1171
1172/** nsIChannelEventSink methods **/
1173NS_IMETHODIMP
1174imgRequest::AsyncOnChannelRedirect(nsIChannel *oldChannel,
1175                                   nsIChannel *newChannel, PRUint32 flags,
1176                                   nsIAsyncVerifyRedirectCallback *callback)
1177{
1178  NS_ASSERTION(mRequest && mChannel, "Got a channel redirect after we nulled out mRequest!");
1179  NS_ASSERTION(mChannel == oldChannel, "Got a channel redirect for an unknown channel!");
1180  NS_ASSERTION(newChannel, "Got a redirect to a NULL channel!");
1181
1182  SetCacheValidation(mCacheEntry, oldChannel);
1183
1184  // Prepare for callback
1185  mRedirectCallback = callback;
1186  mNewRedirectChannel = newChannel;
1187
1188  nsCOMPtr<nsIChannelEventSink> sink(do_GetInterface(mPrevChannelSink));
1189  if (sink) {
1190    nsresult rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags,
1191                                               this);
1192    if (NS_FAILED(rv)) {
1193        mRedirectCallback = nsnull;
1194        mNewRedirectChannel = nsnull;
1195    }
1196    return rv;
1197  }
1198
1199  (void) OnRedirectVerifyCallback(NS_OK);
1200  return NS_OK;
1201}
1202
1203NS_IMETHODIMP
1204imgRequest::OnRedirectVerifyCallback(nsresult result)
1205{
1206  NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
1207  NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
1208
1209  if (NS_FAILED(result)) {
1210      mRedirectCallback->OnRedirectVerifyCallback(result);
1211      mRedirectCallback = nsnull;
1212      mNewRedirectChannel = nsnull;
1213      return NS_OK;
1214  }
1215
1216  mChannel = mNewRedirectChannel;
1217  mTimedChannel = do_QueryInterface(mChannel);
1218  mNewRedirectChannel = nsnull;
1219
1220#if defined(PR_LOGGING)
1221  nsCAutoString oldspec;
1222  if (mCurrentURI)
1223    mCurrentURI->GetSpec(oldspec);
1224  LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::OnChannelRedirect", "old", oldspec.get());
1225#endif
1226
1227  // make sure we have a protocol that returns data rather than opens
1228  // an external application, e.g. mailto:
1229  mChannel->GetURI(getter_AddRefs(mCurrentURI));
1230  bool doesNotReturnData = false;
1231  nsresult rv =
1232    NS_URIChainHasFlags(mCurrentURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
1233                        &doesNotReturnData);
1234
1235  if (NS_SUCCEEDED(rv) && doesNotReturnData)
1236    rv = NS_ERROR_ABORT;
1237
1238  if (NS_FAILED(rv)) {
1239    mRedirectCallback->OnRedirectVerifyCallback(rv);
1240    mRedirectCallback = nsnull;
1241    return NS_OK;
1242  }
1243
1244  mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
1245  mRedirectCallback = nsnull;
1246  return NS_OK;
1247}