netwerk/protocol/wyciwyg/nsWyciwygChannel.cpp
author Jonathan Kingston <jkt@mozilla.com>
Wed, 27 Feb 2019 23:41:04 +0000
changeset 519440 b5f7ff4ca9b58d002c2bebe0a11b529680068b0c
parent 518718 d5643033fdd801efda790a2836a3e196e7ba442e
child 519441 c48f47f793c0f0d9b4acf2caf9324b8bd7a5a683
permissions -rw-r--r--
Bug 1525319 - Removing context from OnStartRequest r=valentin Differential Revision: https://phabricator.services.mozilla.com/D20769

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsWyciwyg.h"
#include "nsWyciwygChannel.h"
#include "nsILoadGroup.h"
#include "nsNetUtil.h"
#include "nsNetCID.h"
#include "LoadContextInfo.h"
#include "nsICacheService.h"  // only to initialize
#include "nsICacheStorageService.h"
#include "nsICacheStorage.h"
#include "nsICacheEntry.h"
#include "nsCharsetSource.h"
#include "nsProxyRelease.h"
#include "nsThreadUtils.h"
#include "nsIInputStream.h"
#include "nsIInputStreamPump.h"
#include "nsIOutputStream.h"
#include "nsIProgressEventSink.h"
#include "nsIURI.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Unused.h"
#include "mozilla/BasePrincipal.h"
#include "nsProxyRelease.h"
#include "nsContentSecurityManager.h"
#include "nsContentUtils.h"

typedef mozilla::net::LoadContextInfo LoadContextInfo;

// nsWyciwygChannel methods
nsWyciwygChannel::nsWyciwygChannel()
    : mMode(NONE),
      mStatus(NS_OK),
      mIsPending(false),
      mNeedToWriteCharset(false),
      mCharsetSource(kCharsetUninitialized),
      mContentLength(-1),
      mLoadFlags(LOAD_NORMAL),
      mNeedToSetSecurityInfo(false) {}

nsWyciwygChannel::~nsWyciwygChannel() {
  if (mLoadInfo) {
    NS_ReleaseOnMainThreadSystemGroup("nsWyciwygChannel::mLoadInfo",
                                      mLoadInfo.forget(), false);
  }
}

NS_IMPL_ISUPPORTS(nsWyciwygChannel, nsIChannel, nsIRequest, nsIStreamListener,
                  nsIRequestObserver, nsICacheEntryOpenCallback,
                  nsIWyciwygChannel, nsIPrivateBrowsingChannel)

nsresult nsWyciwygChannel::Init(nsIURI *uri) {
  NS_ENSURE_ARG_POINTER(uri);

  mURI = uri;
  mOriginalURI = uri;

  return NS_OK;
}

///////////////////////////////////////////////////////////////////////////////
// nsIRequest methods:
///////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP
nsWyciwygChannel::GetName(nsACString &aName) { return mURI->GetSpec(aName); }

NS_IMETHODIMP
nsWyciwygChannel::IsPending(bool *aIsPending) {
  *aIsPending = mIsPending;
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::GetStatus(nsresult *aStatus) {
  if (NS_SUCCEEDED(mStatus) && mPump)
    mPump->GetStatus(aStatus);
  else
    *aStatus = mStatus;
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::Cancel(nsresult status) {
  mStatus = status;
  if (mPump) mPump->Cancel(status);
  // else we're waiting for OnCacheEntryAvailable
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::Suspend() {
  if (mPump) mPump->Suspend();
  // XXX else, we'll ignore this ... and that's probably bad!
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::Resume() {
  if (mPump) mPump->Resume();
  // XXX else, we'll ignore this ... and that's probably bad!
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::GetLoadGroup(nsILoadGroup **aLoadGroup) {
  *aLoadGroup = mLoadGroup;
  NS_IF_ADDREF(*aLoadGroup);
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::SetLoadGroup(nsILoadGroup *aLoadGroup) {
  if (!CanSetLoadGroup(aLoadGroup)) {
    return NS_ERROR_FAILURE;
  }

  mLoadGroup = aLoadGroup;
  NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
                                NS_GET_IID(nsIProgressEventSink),
                                getter_AddRefs(mProgressSink));
  UpdatePrivateBrowsing();
  NS_GetOriginAttributes(this, mOriginAttributes);

  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::SetLoadFlags(uint32_t aLoadFlags) {
  mLoadFlags = aLoadFlags;
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::GetLoadFlags(uint32_t *aLoadFlags) {
  *aLoadFlags = mLoadFlags;
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::GetIsDocument(bool *aIsDocument) {
  return NS_GetIsDocumentChannel(this, aIsDocument);
}

////////////////////////////////////////////////////////////////////////////////
// nsIChannel methods:
///////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP
nsWyciwygChannel::GetOriginalURI(nsIURI **aURI) {
  *aURI = mOriginalURI;
  NS_ADDREF(*aURI);
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::SetOriginalURI(nsIURI *aURI) {
  NS_ENSURE_ARG_POINTER(aURI);
  mOriginalURI = aURI;
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::GetURI(nsIURI **aURI) {
  *aURI = mURI;
  NS_IF_ADDREF(*aURI);
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::GetOwner(nsISupports **aOwner) {
  NS_IF_ADDREF(*aOwner = mOwner);
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::SetOwner(nsISupports *aOwner) {
  mOwner = aOwner;
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::GetLoadInfo(nsILoadInfo **aLoadInfo) {
  NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::SetLoadInfo(nsILoadInfo *aLoadInfo) {
  mLoadInfo = aLoadInfo;
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) {
  *aCallbacks = mCallbacks.get();
  NS_IF_ADDREF(*aCallbacks);
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::SetNotificationCallbacks(
    nsIInterfaceRequestor *aNotificationCallbacks) {
  if (!CanSetCallbacks(aNotificationCallbacks)) {
    return NS_ERROR_FAILURE;
  }

  mCallbacks = aNotificationCallbacks;
  NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
                                NS_GET_IID(nsIProgressEventSink),
                                getter_AddRefs(mProgressSink));

  UpdatePrivateBrowsing();
  NS_GetOriginAttributes(this, mOriginAttributes);

  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::GetSecurityInfo(nsISupports **aSecurityInfo) {
  NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);

  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::GetContentType(nsACString &aContentType) {
  aContentType.AssignLiteral(WYCIWYG_TYPE);
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::SetContentType(const nsACString &aContentType) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsWyciwygChannel::GetContentCharset(nsACString &aContentCharset) {
  aContentCharset.AssignLiteral("UTF-16LE");
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::SetContentCharset(const nsACString &aContentCharset) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsWyciwygChannel::GetContentDisposition(uint32_t *aContentDisposition) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsWyciwygChannel::SetContentDisposition(uint32_t aContentDisposition) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsWyciwygChannel::GetContentDispositionFilename(
    nsAString &aContentDispositionFilename) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsWyciwygChannel::SetContentDispositionFilename(
    const nsAString &aContentDispositionFilename) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsWyciwygChannel::GetContentDispositionHeader(
    nsACString &aContentDispositionHeader) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsWyciwygChannel::GetContentLength(int64_t *aContentLength) {
  *aContentLength = mContentLength;
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::SetContentLength(int64_t aContentLength) {
  mContentLength = aContentLength;

  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::Open(nsIInputStream **aStream) {
  nsCOMPtr<nsIStreamListener> listener;
  nsresult rv =
      nsContentSecurityManager::doContentSecurityCheck(this, listener);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsWyciwygChannel::AsyncOpen(nsIStreamListener *aListener) {
  nsCOMPtr<nsIStreamListener> listener = aListener;
  nsresult rv =
      nsContentSecurityManager::doContentSecurityCheck(this, listener);
  if (NS_FAILED(rv)) {
    mIsPending = false;
    mCallbacks = nullptr;
    return rv;
  }

  LOG(("nsWyciwygChannel::AsyncOpen [this=%p]\n", this));
  MOZ_ASSERT(mMode == NONE, "nsWyciwygChannel already open");

  NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
  NS_ENSURE_TRUE(mMode == NONE, NS_ERROR_IN_PROGRESS);
  NS_ENSURE_ARG_POINTER(listener);

  mMode = READING;

  // open a cache entry for this channel...
  // mIsPending set to true since OnCacheEntryAvailable may be called
  // synchronously and fails when mIsPending found false.
  mIsPending = true;
  rv = OpenCacheEntryForReading(mURI);
  if (NS_FAILED(rv)) {
    LOG(("nsWyciwygChannel::OpenCacheEntryForReading failed [rv=%" PRIx32 "]\n",
         static_cast<uint32_t>(rv)));
    mIsPending = false;
    mCallbacks = nullptr;
    return rv;
  }

  // There is no code path that would invoke the listener sooner than
  // we get to this line in case OnCacheEntryAvailable is invoked
  // synchronously.
  mListener = listener;

  if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);

  return NS_OK;
}

//////////////////////////////////////////////////////////////////////////////
// nsIWyciwygChannel
//////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP
nsWyciwygChannel::WriteToCacheEntry(const nsAString &aData) {
  LOG(("nsWyciwygChannel::WriteToCacheEntry [this=%p]", this));

  nsresult rv;

  if (mMode == READING) {
    LOG(("nsWyciwygChannel::WriteToCacheEntry already open for reading"));
    MOZ_ASSERT(false);
    return NS_ERROR_UNEXPECTED;
  }

  mMode = WRITING;

  if (!mCacheEntry) {
    nsresult rv = OpenCacheEntryForWriting(mURI);
    if (NS_FAILED(rv) || !mCacheEntry) {
      LOG(("  could not synchronously open cache entry for write!"));
      return NS_ERROR_FAILURE;
    }
  }

  if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
    rv = mCacheEntry->SetMetaDataElement("inhibit-persistent-caching", "1");
    if (NS_FAILED(rv)) return rv;
  }

  if (mNeedToSetSecurityInfo) {
    mCacheEntry->SetSecurityInfo(mSecurityInfo);
    mNeedToSetSecurityInfo = false;
  }

  if (mNeedToWriteCharset) {
    WriteCharsetAndSourceToCache(mCharsetSource, mCharset);
    mNeedToWriteCharset = false;
  }

  uint32_t out;
  if (!mCacheOutputStream) {
    // Get the outputstream from the cache entry.
    rv = mCacheEntry->OpenOutputStream(0, -1,
                                       getter_AddRefs(mCacheOutputStream));
    if (NS_FAILED(rv)) return rv;

    // Write out a Byte Order Mark, so that we'll know if the data is
    // BE or LE when we go to read it.
    char16_t bom = 0xFEFF;
    rv = mCacheOutputStream->Write((char *)&bom, sizeof(bom), &out);
    if (NS_FAILED(rv)) return rv;
  }

  return mCacheOutputStream->Write((const char *)PromiseFlatString(aData).get(),
                                   aData.Length() * sizeof(char16_t), &out);
}

NS_IMETHODIMP
nsWyciwygChannel::CloseCacheEntry(nsresult reason) {
  if (mCacheEntry) {
    LOG(("nsWyciwygChannel::CloseCacheEntry [this=%p ]", this));
    mCacheOutputStream = nullptr;

    if (NS_FAILED(reason)) {
      mCacheEntry->AsyncDoom(nullptr);
    }

    mCacheEntry = nullptr;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::SetSecurityInfo(nsISupports *aSecurityInfo) {
  if (mMode == READING) {
    MOZ_ASSERT(false);
    return NS_ERROR_UNEXPECTED;
  }

  mSecurityInfo = aSecurityInfo;

  if (mCacheEntry) {
    return mCacheEntry->SetSecurityInfo(mSecurityInfo);
  }

  mNeedToSetSecurityInfo = true;

  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::SetCharsetAndSource(int32_t aSource,
                                      const nsACString &aCharset) {
  NS_ENSURE_ARG(!aCharset.IsEmpty());

  if (mCacheEntry) {
    WriteCharsetAndSourceToCache(mCharsetSource, mCharset);
  } else {
    MOZ_ASSERT(mMode != WRITING, "We must have an entry!");
    if (mMode == READING) {
      return NS_ERROR_NOT_AVAILABLE;
    }
    mNeedToWriteCharset = true;
    mCharsetSource = aSource;
    mCharset = aCharset;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::GetCharsetAndSource(int32_t *aSource, nsACString &aCharset) {
  MOZ_ASSERT(mMode == READING);

  if (!mCacheEntry) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsCString data;
  mCacheEntry->GetMetaDataElement("charset", getter_Copies(data));

  if (data.IsEmpty()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsCString sourceStr;
  mCacheEntry->GetMetaDataElement("charset-source", getter_Copies(sourceStr));

  int32_t source;
  nsresult err;
  source = sourceStr.ToInteger(&err);
  if (NS_FAILED(err) || source == 0) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  *aSource = source;
  aCharset = data;
  return NS_OK;
}

//////////////////////////////////////////////////////////////////////////////
// nsICacheEntryOpenCallback
//////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP
nsWyciwygChannel::OnCacheEntryCheck(nsICacheEntry *entry,
                                    nsIApplicationCache *appCache,
                                    uint32_t *aResult) {
  *aResult = ENTRY_WANTED;
  return NS_OK;
}

NS_IMETHODIMP
nsWyciwygChannel::OnCacheEntryAvailable(nsICacheEntry *aCacheEntry, bool aNew,
                                        nsIApplicationCache *aAppCache,
                                        nsresult aStatus) {
  LOG(
      ("nsWyciwygChannel::OnCacheEntryAvailable [this=%p entry=%p "
       "new=%d status=%" PRIx32 "]\n",
       this, aCacheEntry, aNew, static_cast<uint32_t>(aStatus)));

  MOZ_RELEASE_ASSERT(!aNew,
                     "New entry must not be returned when flag "
                     "OPEN_READONLY is used!");

  // if the channel's already fired onStopRequest,
  // then we should ignore this event.
  if (!mIsPending) return NS_OK;

  if (NS_SUCCEEDED(mStatus)) {
    if (NS_SUCCEEDED(aStatus)) {
      MOZ_ASSERT(aCacheEntry);
      mCacheEntry = aCacheEntry;
      nsresult rv = ReadFromCache();
      if (NS_FAILED(rv)) {
        mStatus = rv;
      }
    } else {
      mStatus = aStatus;
    }
  }

  if (NS_FAILED(mStatus)) {
    LOG(("channel was canceled [this=%p status=%" PRIx32 "]\n", this,
         static_cast<uint32_t>(mStatus)));
    // Since OnCacheEntryAvailable can be called directly from AsyncOpen
    // we must dispatch.
    NS_DispatchToCurrentThread(
        mozilla::NewRunnableMethod("nsWyciwygChannel::NotifyListener", this,
                                   &nsWyciwygChannel::NotifyListener));
  }

  return NS_OK;
}

//-----------------------------------------------------------------------------
// nsWyciwygChannel::nsIStreamListener
//-----------------------------------------------------------------------------

NS_IMETHODIMP
nsWyciwygChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctx,
                                  nsIInputStream *input, uint64_t offset,
                                  uint32_t count) {
  LOG(("nsWyciwygChannel::OnDataAvailable [this=%p request=%p offset=%" PRIu64
       " count=%u]\n",
       this, request, offset, count));

  nsresult rv;

  nsCOMPtr<nsIStreamListener> listener = mListener;

  if (listener) {
    rv = listener->OnDataAvailable(this, nullptr, input, offset, count);
  } else {
    MOZ_ASSERT(false, "We must have a listener!");
    rv = NS_ERROR_UNEXPECTED;
  }

  // XXX handle 64-bit stuff for real
  if (mProgressSink && NS_SUCCEEDED(rv)) {
    mProgressSink->OnProgress(this, nullptr, offset + count, mContentLength);
  }

  return rv;  // let the pump cancel on failure
}

//////////////////////////////////////////////////////////////////////////////
// nsIRequestObserver
//////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP
nsWyciwygChannel::OnStartRequest(nsIRequest *request) {
  LOG(("nsWyciwygChannel::OnStartRequest [this=%p request=%p]\n", this,
       request));

  nsCOMPtr<nsIStreamListener> listener = mListener;

  if (listener) {
    return listener->OnStartRequest(this);
  }

  MOZ_ASSERT(false, "We must have a listener!");
  return NS_ERROR_UNEXPECTED;
}

NS_IMETHODIMP
nsWyciwygChannel::OnStopRequest(nsIRequest *request, nsISupports *ctx,
                                nsresult status) {
  LOG(("nsWyciwygChannel::OnStopRequest [this=%p request=%p status=%" PRIu32
       "]\n",
       this, request, static_cast<uint32_t>(status)));

  if (NS_SUCCEEDED(mStatus)) mStatus = status;

  mIsPending = false;

  nsCOMPtr<nsIStreamListener> listener;
  listener.swap(mListener);

  if (listener) {
    listener->OnStopRequest(this, nullptr, mStatus);
  } else {
    MOZ_ASSERT(false, "We must have a listener!");
  }

  if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);

  CloseCacheEntry(mStatus);
  mPump = nullptr;

  // Drop notification callbacks to prevent cycles.
  mCallbacks = nullptr;
  mProgressSink = nullptr;

  return NS_OK;
}

//////////////////////////////////////////////////////////////////////////////
// Helper functions
//////////////////////////////////////////////////////////////////////////////

nsresult nsWyciwygChannel::GetCacheStorage(nsICacheStorage **_retval) {
  nsresult rv;

  nsCOMPtr<nsICacheStorageService> cacheService =
      do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  bool anonymous = mLoadFlags & LOAD_ANONYMOUS;
  mOriginAttributes.SyncAttributesWithPrivateBrowsing(mPrivateBrowsing);
  RefPtr<LoadContextInfo> loadInfo =
      mozilla::net::GetLoadContextInfo(anonymous, mOriginAttributes);

  if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
    return cacheService->MemoryCacheStorage(loadInfo, _retval);
  }

  return cacheService->DiskCacheStorage(loadInfo, false, _retval);
}

nsresult nsWyciwygChannel::OpenCacheEntryForReading(nsIURI *aURI) {
  nsresult rv;

  nsCOMPtr<nsICacheStorage> cacheStorage;
  rv = GetCacheStorage(getter_AddRefs(cacheStorage));
  NS_ENSURE_SUCCESS(rv, rv);

  return cacheStorage->AsyncOpenURI(
      aURI, EmptyCString(),
      nsICacheStorage::OPEN_READONLY | nsICacheStorage::CHECK_MULTITHREADED,
      this);
}

nsresult nsWyciwygChannel::OpenCacheEntryForWriting(nsIURI *aURI) {
  nsresult rv;

  nsCOMPtr<nsICacheStorage> cacheStorage;
  rv = GetCacheStorage(getter_AddRefs(cacheStorage));
  NS_ENSURE_SUCCESS(rv, rv);

  return cacheStorage->OpenTruncate(aURI, EmptyCString(),
                                    getter_AddRefs(mCacheEntry));
}

nsresult nsWyciwygChannel::ReadFromCache() {
  LOG(("nsWyciwygChannel::ReadFromCache [this=%p] ", this));

  NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
  nsresult rv;

  // Get the stored security info
  mCacheEntry->GetSecurityInfo(getter_AddRefs(mSecurityInfo));

  nsAutoCString tmpStr;
  rv = mCacheEntry->GetMetaDataElement("inhibit-persistent-caching",
                                       getter_Copies(tmpStr));
  if (NS_SUCCEEDED(rv) && tmpStr.EqualsLiteral("1"))
    mLoadFlags |= INHIBIT_PERSISTENT_CACHING;

  // Get a transport to the cached data...
  nsCOMPtr<nsIInputStream> inputStream;
  rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
  if (NS_FAILED(rv)) return rv;
  NS_ENSURE_TRUE(inputStream, NS_ERROR_UNEXPECTED);

  rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream.forget());
  if (NS_FAILED(rv)) return rv;

  // Pump the cache data downstream
  return mPump->AsyncRead(this, nullptr);
}

void nsWyciwygChannel::WriteCharsetAndSourceToCache(int32_t aSource,
                                                    const nsCString &aCharset) {
  MOZ_ASSERT(mCacheEntry, "Better have cache entry!");

  mCacheEntry->SetMetaDataElement("charset", aCharset.get());

  nsAutoCString source;
  source.AppendInt(aSource);
  mCacheEntry->SetMetaDataElement("charset-source", source.get());
}

void nsWyciwygChannel::NotifyListener() {
  nsCOMPtr<nsIStreamListener> listener;

  listener.swap(mListener);

  if (listener) {
    listener->OnStartRequest(this);
    mIsPending = false;
    listener->OnStopRequest(this, nullptr, mStatus);
  } else {
    MOZ_ASSERT(false, "We must have the listener!");
    mIsPending = false;
  }

  CloseCacheEntry(mStatus);

  // Remove ourselves from the load group.
  if (mLoadGroup) {
    mLoadGroup->RemoveRequest(this, nullptr, mStatus);
  }
}

// vim: ts=2 sw=2