/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: ft=cpp tw=78 sw=2 et ts=2
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK *****
 *
 * This Original Code has been modified by IBM Corporation.
 * Modifications made by IBM described herein are Copyright (c)
 * International Business Machines Corporation, 2000.  Modifications
 * to Mozilla code or documentation identified per MPL Section 3.3
 *
 * Date             Modified by     Description of modification
 * 04/20/2000       IBM Corp.      OS/2 VisualAge build.
 */

/* loading of CSS style sheets using the network APIs */

#include "mozilla/css/Loader.h"
#include "nsIRunnable.h"
#include "nsIUnicharStreamLoader.h"
#include "nsSyncLoadService.h"
#include "nsCOMPtr.h"
#include "nsCOMArray.h"
#include "nsString.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIDOMNode.h"
#include "nsIDOMDocument.h"
#include "nsIDOMWindow.h"
#include "nsICharsetAlias.h"
#include "nsHashtable.h"
#include "nsIURI.h"
#include "nsIServiceManager.h"
#include "nsNetUtil.h"
#include "nsContentUtils.h"
#include "nsCRT.h"
#include "nsIScriptSecurityManager.h"
#include "nsContentPolicyUtils.h"
#include "nsIHttpChannel.h"
#include "nsIScriptError.h"
#include "nsMimeTypes.h"
#include "nsIAtom.h"
#include "nsCSSStyleSheet.h"
#include "nsIStyleSheetLinkingElement.h"
#include "nsICSSLoaderObserver.h"
#include "nsCSSParser.h"
#include "mozilla/css/ImportRule.h"
#include "nsThreadUtils.h"
#include "nsGkAtoms.h"
#include "nsDocShellCID.h"
#include "nsIThreadInternal.h"

#ifdef MOZ_XUL
#include "nsXULPrototypeCache.h"
#endif

#include "nsIMediaList.h"
#include "nsIDOMStyleSheet.h"
#include "nsIDOMCSSStyleSheet.h"
#include "nsContentErrors.h"

#include "nsIChannelPolicy.h"
#include "nsIContentSecurityPolicy.h"

#include "mozilla/FunctionTimer.h"

/**
 * OVERALL ARCHITECTURE
 *
 * The CSS Loader gets requests to load various sorts of style sheets:
 * inline style from <style> elements, linked style, @import-ed child
 * sheets, non-document sheets.  The loader handles the following tasks:
 *
 * 1) Checking whether the load is allowed: CheckLoadAllowed()
 * 2) Creation of the actual style sheet objects: CreateSheet()
 * 3) setting of the right media, title, enabled state, etc on the
 *    sheet: PrepareSheet()
 * 4) Insertion of the sheet in the proper cascade order:
 *    InsertSheetInDoc() and InsertChildSheet()
 * 5) Load of the sheet: LoadSheet()
 * 6) Parsing of the sheet: ParseSheet()
 * 7) Cleanup: SheetComplete()
 *
 * The detailed documentation for these functions is found with the
 * function implementations.
 *
 * The following helper object is used:
 *    SheetLoadData -- a small class that is used to store all the
 *                     information needed for the loading of a sheet;
 *                     this class handles listening for the stream
 *                     loader completion and also handles charset
 *                     determination.
 */

namespace mozilla {
namespace css {

/*********************************************
 * Data needed to properly load a stylesheet *
 *********************************************/

class SheetLoadData : public nsIRunnable,
                      public nsIUnicharStreamLoaderObserver,
                      public nsIThreadObserver
{
public:
  virtual ~SheetLoadData(void);
  // Data for loading a sheet linked from a document
  SheetLoadData(Loader* aLoader,
                const nsSubstring& aTitle,
                nsIURI* aURI,
                nsCSSStyleSheet* aSheet,
                nsIStyleSheetLinkingElement* aOwningElement,
                bool aIsAlternate,
                nsICSSLoaderObserver* aObserver,
                nsIPrincipal* aLoaderPrincipal);

  // Data for loading a sheet linked from an @import rule
  SheetLoadData(Loader* aLoader,
                nsIURI* aURI,
                nsCSSStyleSheet* aSheet,
                SheetLoadData* aParentData,
                nsICSSLoaderObserver* aObserver,
                nsIPrincipal* aLoaderPrincipal);

  // Data for loading a non-document sheet
  SheetLoadData(Loader* aLoader,
                nsIURI* aURI,
                nsCSSStyleSheet* aSheet,
                bool aSyncLoad,
                bool aAllowUnsafeRules,
                bool aUseSystemPrincipal,
                const nsCString& aCharset,
                nsICSSLoaderObserver* aObserver,
                nsIPrincipal* aLoaderPrincipal);

  already_AddRefed<nsIURI> GetReferrerURI();

  void ScheduleLoadEventIfNeeded(nsresult aStatus);

  NS_DECL_ISUPPORTS
  NS_DECL_NSIRUNNABLE
  NS_DECL_NSITHREADOBSERVER
  NS_DECL_NSIUNICHARSTREAMLOADEROBSERVER

  // Hold a ref to the CSSLoader so we can call back to it to let it
  // know the load finished
  Loader*                    mLoader; // strong ref

  // Title needed to pull datas out of the pending datas table when
  // the preferred title is changed
  nsString                   mTitle;

  // Charset we decided to use for the sheet
  nsCString                  mCharset;

  // URI we're loading.  Null for inline sheets
  nsCOMPtr<nsIURI>           mURI;

  // Should be 1 for non-inline sheets.
  PRUint32                   mLineNumber;

  // The sheet we're loading data for
  nsRefPtr<nsCSSStyleSheet>  mSheet;

  // Linked list of datas for the same URI as us
  SheetLoadData*             mNext;  // strong ref

  // Load data for the sheet that @import-ed us if we were @import-ed
  // during the parse
  SheetLoadData*             mParentData;  // strong ref

  // Number of sheets we @import-ed that are still loading
  PRUint32                   mPendingChildren;

  // mSyncLoad is true when the load needs to be synchronous -- right
  // now only for LoadSheetSync and children of sync loads.
  bool                       mSyncLoad : 1;

  // mIsNonDocumentSheet is true if the load was triggered by LoadSheetSync or
  // LoadSheet or an @import from such a sheet.  Non-document sheet loads can
  // proceed even if we have no document.
  bool                       mIsNonDocumentSheet : 1;

  // mIsLoading is true from the moment we are placed in the loader's
  // "loading datas" table (right after the async channel is opened)
  // to the moment we are removed from said table (due to the load
  // completing or being cancelled).
  bool                       mIsLoading : 1;

  // mIsCancelled is set to true when a sheet load is stopped by
  // Stop() or StopLoadingSheet() (which was removed in Bug 556446).
  // SheetLoadData::OnStreamComplete() checks this to avoid parsing
  // sheets that have been cancelled and such.
  bool                       mIsCancelled : 1;

  // mMustNotify is true if the load data is being loaded async and
  // the original function call that started the load has returned.
  // This applies only to observer notifications; load/error events
  // are fired for any SheetLoadData that has a non-null
  // mOwningElement.
  bool                       mMustNotify : 1;

  // mWasAlternate is true if the sheet was an alternate when the load data was
  // created.
  bool                       mWasAlternate : 1;

  // mAllowUnsafeRules is true if we should allow unsafe rules to be parsed
  // in the loaded sheet.
  bool                       mAllowUnsafeRules : 1;

  // mUseSystemPrincipal is true if the system principal should be used for
  // this sheet, no matter what the channel principal is.  Only true for sync
  // loads.
  bool                       mUseSystemPrincipal : 1;

  // If true, this SheetLoadData is being used as a way to handle
  // async observer notification for an already-complete sheet.
  bool                       mSheetAlreadyComplete : 1;

  // This is the element that imported the sheet.  Needed to get the
  // charset set on it and to fire load/error events.
  nsCOMPtr<nsIStyleSheetLinkingElement> mOwningElement;

  // The observer that wishes to be notified of load completion
  nsCOMPtr<nsICSSLoaderObserver>        mObserver;

  // The principal that identifies who started loading us.
  nsCOMPtr<nsIPrincipal>                mLoaderPrincipal;

  // The charset to use if the transport and sheet don't indicate one.
  // May be empty.  Must be empty if mOwningElement is non-null.
  nsCString                             mCharsetHint;

  // The status our load ended up with; this determines whether we
  // should fire error events or load events.  This gets initialized
  // by ScheduleLoadEventIfNeeded, and is only used after that has
  // been called.
  nsresult                              mStatus;

private:
  void FireLoadEvent(nsIThreadInternal* aThread);
};

#ifdef MOZ_LOGGING
// #define FORCE_PR_LOG /* Allow logging in the release build */
#endif /* MOZ_LOGGING */
#include "prlog.h"

#ifdef PR_LOGGING
static PRLogModuleInfo *gLoaderLog = PR_NewLogModule("nsCSSLoader");
#endif /* PR_LOGGING */

#define LOG_FORCE(args) PR_LOG(gLoaderLog, PR_LOG_ALWAYS, args)
#define LOG_ERROR(args) PR_LOG(gLoaderLog, PR_LOG_ERROR, args)
#define LOG_WARN(args) PR_LOG(gLoaderLog, PR_LOG_WARNING, args)
#define LOG_DEBUG(args) PR_LOG(gLoaderLog, PR_LOG_DEBUG, args)
#define LOG(args) LOG_DEBUG(args)

#define LOG_FORCE_ENABLED() PR_LOG_TEST(gLoaderLog, PR_LOG_ALWAYS)
#define LOG_ERROR_ENABLED() PR_LOG_TEST(gLoaderLog, PR_LOG_ERROR)
#define LOG_WARN_ENABLED() PR_LOG_TEST(gLoaderLog, PR_LOG_WARNING)
#define LOG_DEBUG_ENABLED() PR_LOG_TEST(gLoaderLog, PR_LOG_DEBUG)
#define LOG_ENABLED() LOG_DEBUG_ENABLED()

#ifdef PR_LOGGING
#define LOG_URI(format, uri)                        \
  PR_BEGIN_MACRO                                    \
    NS_ASSERTION(uri, "Logging null uri");          \
    if (LOG_ENABLED()) {                            \
      nsCAutoString _logURISpec;                    \
      uri->GetSpec(_logURISpec);                    \
      LOG((format, _logURISpec.get()));             \
    }                                               \
  PR_END_MACRO
#else // PR_LOGGING
#define LOG_URI(format, uri)
#endif // PR_LOGGING

// And some convenience strings...
#ifdef PR_LOGGING
static const char* const gStateStrings[] = {
  "eSheetStateUnknown",
  "eSheetNeedsParser",
  "eSheetPending",
  "eSheetLoading",
  "eSheetComplete"
};
#endif

/********************************
 * SheetLoadData implementation *
 ********************************/
NS_IMPL_ISUPPORTS3(SheetLoadData, nsIUnicharStreamLoaderObserver, nsIRunnable,
                   nsIThreadObserver)

SheetLoadData::SheetLoadData(Loader* aLoader,
                             const nsSubstring& aTitle,
                             nsIURI* aURI,
                             nsCSSStyleSheet* aSheet,
                             nsIStyleSheetLinkingElement* aOwningElement,
                             bool aIsAlternate,
                             nsICSSLoaderObserver* aObserver,
                             nsIPrincipal* aLoaderPrincipal)
  : mLoader(aLoader),
    mTitle(aTitle),
    mURI(aURI),
    mLineNumber(1),
    mSheet(aSheet),
    mNext(nsnull),
    mParentData(nsnull),
    mPendingChildren(0),
    mSyncLoad(PR_FALSE),
    mIsNonDocumentSheet(PR_FALSE),
    mIsLoading(PR_FALSE),
    mIsCancelled(PR_FALSE),
    mMustNotify(PR_FALSE),
    mWasAlternate(aIsAlternate),
    mAllowUnsafeRules(PR_FALSE),
    mUseSystemPrincipal(PR_FALSE),
    mSheetAlreadyComplete(PR_FALSE),
    mOwningElement(aOwningElement),
    mObserver(aObserver),
    mLoaderPrincipal(aLoaderPrincipal)
{
  NS_PRECONDITION(mLoader, "Must have a loader!");
  NS_ADDREF(mLoader);
}

SheetLoadData::SheetLoadData(Loader* aLoader,
                             nsIURI* aURI,
                             nsCSSStyleSheet* aSheet,
                             SheetLoadData* aParentData,
                             nsICSSLoaderObserver* aObserver,
                             nsIPrincipal* aLoaderPrincipal)
  : mLoader(aLoader),
    mURI(aURI),
    mLineNumber(1),
    mSheet(aSheet),
    mNext(nsnull),
    mParentData(aParentData),
    mPendingChildren(0),
    mSyncLoad(PR_FALSE),
    mIsNonDocumentSheet(PR_FALSE),
    mIsLoading(PR_FALSE),
    mIsCancelled(PR_FALSE),
    mMustNotify(PR_FALSE),
    mWasAlternate(PR_FALSE),
    mAllowUnsafeRules(PR_FALSE),
    mUseSystemPrincipal(PR_FALSE),
    mSheetAlreadyComplete(PR_FALSE),
    mOwningElement(nsnull),
    mObserver(aObserver),
    mLoaderPrincipal(aLoaderPrincipal)
{
  NS_PRECONDITION(mLoader, "Must have a loader!");
  NS_ADDREF(mLoader);
  if (mParentData) {
    NS_ADDREF(mParentData);
    mSyncLoad = mParentData->mSyncLoad;
    mIsNonDocumentSheet = mParentData->mIsNonDocumentSheet;
    mAllowUnsafeRules = mParentData->mAllowUnsafeRules;
    mUseSystemPrincipal = mParentData->mUseSystemPrincipal;
    ++(mParentData->mPendingChildren);
  }

  NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
                   "Shouldn't use system principal for async loads");
}

SheetLoadData::SheetLoadData(Loader* aLoader,
                             nsIURI* aURI,
                             nsCSSStyleSheet* aSheet,
                             bool aSyncLoad,
                             bool aAllowUnsafeRules,
                             bool aUseSystemPrincipal,
                             const nsCString& aCharset,
                             nsICSSLoaderObserver* aObserver,
                             nsIPrincipal* aLoaderPrincipal)
  : mLoader(aLoader),
    mURI(aURI),
    mLineNumber(1),
    mSheet(aSheet),
    mNext(nsnull),
    mParentData(nsnull),
    mPendingChildren(0),
    mSyncLoad(aSyncLoad),
    mIsNonDocumentSheet(PR_TRUE),
    mIsLoading(PR_FALSE),
    mIsCancelled(PR_FALSE),
    mMustNotify(PR_FALSE),
    mWasAlternate(PR_FALSE),
    mAllowUnsafeRules(aAllowUnsafeRules),
    mUseSystemPrincipal(aUseSystemPrincipal),
    mSheetAlreadyComplete(PR_FALSE),
    mOwningElement(nsnull),
    mObserver(aObserver),
    mLoaderPrincipal(aLoaderPrincipal),
    mCharsetHint(aCharset)
{
  NS_PRECONDITION(mLoader, "Must have a loader!");
  NS_ADDREF(mLoader);

  NS_POSTCONDITION(!mUseSystemPrincipal || mSyncLoad,
                   "Shouldn't use system principal for async loads");
}

SheetLoadData::~SheetLoadData()
{
  NS_RELEASE(mLoader);
  NS_IF_RELEASE(mParentData);
  NS_IF_RELEASE(mNext);
}

NS_IMETHODIMP
SheetLoadData::Run()
{
  mLoader->HandleLoadEvent(this);
  return NS_OK;
}

NS_IMETHODIMP
SheetLoadData::OnDispatchedEvent(nsIThreadInternal* aThread)
{
  return NS_OK;
}

NS_IMETHODIMP
SheetLoadData::OnProcessNextEvent(nsIThreadInternal* aThread,
                                  bool aMayWait,
                                  PRUint32 aRecursionDepth)
{
  // We want to fire our load even before or after event processing,
  // whichever comes first.
  FireLoadEvent(aThread);
  return NS_OK;
}

NS_IMETHODIMP
SheetLoadData::AfterProcessNextEvent(nsIThreadInternal* aThread,
                                     PRUint32 aRecursionDepth)
{
  // We want to fire our load even before or after event processing,
  // whichever comes first.
  FireLoadEvent(aThread);
  return NS_OK;
}

void
SheetLoadData::FireLoadEvent(nsIThreadInternal* aThread)
{
  
  // First remove ourselves as a thread observer.  But we need to keep
  // ourselves alive while doing that!
  nsRefPtr<SheetLoadData> kungFuDeathGrip(this);
  aThread->RemoveObserver(this);

  // Now fire the event
  nsCOMPtr<nsINode> node = do_QueryInterface(mOwningElement);
  NS_ASSERTION(node, "How did that happen???");

  nsContentUtils::DispatchTrustedEvent(node->GetOwnerDoc(),
                                       node,
                                       NS_SUCCEEDED(mStatus) ?
                                         NS_LITERAL_STRING("load") :
                                         NS_LITERAL_STRING("error"),
                                       PR_FALSE, PR_FALSE);

  // And unblock onload
  if (mLoader->mDocument) {
    mLoader->mDocument->UnblockOnload(PR_TRUE);
  }  
}

void
SheetLoadData::ScheduleLoadEventIfNeeded(nsresult aStatus)
{
  if (!mOwningElement) {
    return;
  }

  mStatus = aStatus;

  nsCOMPtr<nsIThread> thread = do_GetMainThread();
  nsCOMPtr<nsIThreadInternal> internalThread = do_QueryInterface(thread);
  if (NS_SUCCEEDED(internalThread->AddObserver(this))) {
    // Make sure to block onload here
    if (mLoader->mDocument) {
      mLoader->mDocument->BlockOnload();
    }
  }
}

/*************************
 * Loader Implementation *
 *************************/

Loader::Loader(void)
  : mDocument(nsnull)
  , mDatasToNotifyOn(0)
  , mCompatMode(eCompatibility_FullStandards)
  , mEnabled(PR_TRUE)
#ifdef DEBUG
  , mSyncCallback(PR_FALSE)
#endif
{
}

Loader::Loader(nsIDocument* aDocument)
  : mDocument(aDocument)
  , mDatasToNotifyOn(0)
  , mCompatMode(eCompatibility_FullStandards)
  , mEnabled(PR_TRUE)
#ifdef DEBUG
  , mSyncCallback(PR_FALSE)
#endif
{
  // We can just use the preferred set, since there are no sheets in the
  // document yet (if there are, how did they get there? _we_ load the sheets!)
  // and hence the selected set makes no sense at this time.
  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
  if (domDoc) {
    domDoc->GetPreferredStyleSheetSet(mPreferredSheet);
  }
}

Loader::~Loader()
{
  NS_ASSERTION((!mLoadingDatas.IsInitialized()) || mLoadingDatas.Count() == 0,
               "How did we get destroyed when there are loading data?");
  NS_ASSERTION((!mPendingDatas.IsInitialized()) || mPendingDatas.Count() == 0,
               "How did we get destroyed when there are pending data?");
  // Note: no real need to revoke our stylesheet loaded events -- they
  // hold strong references to us, so if we're going away that means
  // they're all done.
}

NS_IMPL_ADDREF(Loader)
NS_IMPL_RELEASE(Loader)

void
Loader::DropDocumentReference(void)
{
  mDocument = nsnull;
  // Flush out pending datas just so we don't leak by accident.  These
  // loads should short-circuit through the mDocument check in
  // LoadSheet and just end up in SheetComplete immediately
  if (mPendingDatas.IsInitialized()) {
    StartAlternateLoads();
  }
}

static PLDHashOperator
CollectNonAlternates(URIAndPrincipalHashKey *aKey,
                     SheetLoadData* &aData,
                     void* aClosure)
{
  NS_PRECONDITION(aData, "Must have a data");
  NS_PRECONDITION(aClosure, "Must have an array");

  // Note that we don't want to affect what the selected style set is,
  // so use PR_TRUE for aHasAlternateRel.
  if (aData->mLoader->IsAlternate(aData->mTitle, PR_TRUE)) {
    return PL_DHASH_NEXT;
  }

  static_cast<Loader::LoadDataArray*>(aClosure)->AppendElement(aData);
  return PL_DHASH_REMOVE;
}

nsresult
Loader::SetPreferredSheet(const nsAString& aTitle)
{
#ifdef DEBUG
  nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(mDocument);
  if (doc) {
    nsAutoString currentPreferred;
    doc->GetLastStyleSheetSet(currentPreferred);
    if (DOMStringIsNull(currentPreferred)) {
      doc->GetPreferredStyleSheetSet(currentPreferred);
    }
    NS_ASSERTION(currentPreferred.Equals(aTitle),
                 "Unexpected argument to SetPreferredSheet");
  }
#endif

  mPreferredSheet = aTitle;

  // start any pending alternates that aren't alternates anymore
  if (mPendingDatas.IsInitialized()) {
    LoadDataArray arr(mPendingDatas.Count());
    mPendingDatas.Enumerate(CollectNonAlternates, &arr);

    mDatasToNotifyOn += arr.Length();
    for (PRUint32 i = 0; i < arr.Length(); ++i) {
      --mDatasToNotifyOn;
      LoadSheet(arr[i], eSheetNeedsParser);
    }
  }

  return NS_OK;
}

static const char kCharsetSym[] = "@charset \"";

static nsresult GetCharsetFromData(const unsigned char* aStyleSheetData,
                                   PRUint32 aDataLength,
                                   nsACString& aCharset)
{
  aCharset.Truncate();
  if (aDataLength <= sizeof(kCharsetSym) - 1)
    return NS_ERROR_NOT_AVAILABLE;
  PRUint32 step = 1;
  PRUint32 pos = 0;
  bool bigEndian = false;
  // Determine the encoding type.  If we have a BOM, set aCharset to the
  // charset listed for that BOM in http://www.w3.org/TR/REC-xml#sec-guessing;
  // that way even if we don't have a valid @charset rule we can use the BOM to
  // get a reasonable charset.  If we do have an @charset rule, the string from
  // that will override this fallback setting of aCharset.
  if (*aStyleSheetData == 0x40 && *(aStyleSheetData+1) == 0x63 /* '@c' */ ) {
    // 1-byte ASCII-based encoding (ISO-8859-*, UTF-8, etc), no BOM
    step = 1;
    pos = 0;
  }
  else if (nsContentUtils::CheckForBOM(aStyleSheetData,
                                       aDataLength, aCharset, &bigEndian)) {
    if (aCharset.Equals("UTF-8")) {
      step = 1;
      pos = 3;
    }
    else if (aCharset.Equals("UTF-16")) {
      step = 2;
      pos = bigEndian ? 3 : 2;
    }
  }
  else if (aStyleSheetData[0] == 0x00 &&
           aStyleSheetData[1] == 0x40 &&
           aStyleSheetData[2] == 0x00 &&
           aStyleSheetData[3] == 0x63) {
    // 2-byte big-endian encoding, no BOM
    step = 2;
    pos = 1;
  }
  else if (aStyleSheetData[0] == 0x40 &&
           aStyleSheetData[1] == 0x00 &&
           aStyleSheetData[2] == 0x63 &&
           aStyleSheetData[3] == 0x00) {
    // 2-byte little-endian encoding, no BOM
    step = 2;
    pos = 0;
  }
  else {
    // no clue what this is
    return NS_ERROR_UNEXPECTED;
  }

  PRUint32 index = 0;
  while (pos < aDataLength && index < sizeof(kCharsetSym) - 1) {
    if (aStyleSheetData[pos] != kCharsetSym[index]) {
      // If we have a guess as to the charset based on the BOM, then
      // we can just return NS_OK even if there is no valid @charset
      // rule.
      return aCharset.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK;
    }
    ++index;
    pos += step;
  }

  nsCAutoString charset;
  while (pos < aDataLength) {
    if (aStyleSheetData[pos] == '"') {
      break;
    }

    // casting to avoid ambiguities
    charset.Append(char(aStyleSheetData[pos]));
    pos += step;
  }

  // Check for the ending ';'
  pos += step;
  if (pos >= aDataLength || aStyleSheetData[pos] != ';') {
    return aCharset.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK;
  }

  aCharset = charset;
  return NS_OK;
}

NS_IMETHODIMP
SheetLoadData::OnDetermineCharset(nsIUnicharStreamLoader* aLoader,
                                  nsISupports* aContext,
                                  nsACString const& aSegment,
                                  nsACString& aCharset)
{
  NS_PRECONDITION(!mOwningElement || mCharsetHint.IsEmpty(),
                  "Can't have element _and_ charset hint");

  LOG_URI("SheetLoadData::OnDetermineCharset for '%s'", mURI);
  nsCOMPtr<nsIChannel> channel;
  nsresult result = aLoader->GetChannel(getter_AddRefs(channel));
  if (NS_FAILED(result))
    channel = nsnull;

  aCharset.Truncate();

  /*
   * First determine the charset (if one is indicated)
   * 1)  Check nsIChannel::contentCharset
   * 2)  Check @charset rules in the data
   * 3)  Check "charset" attribute of the <LINK> or <?xml-stylesheet?>
   *
   * If all these fail to give us a charset, fall back on our default
   * (parent sheet charset, document charset or ISO-8859-1 in that order)
   */
  if (channel) {
    channel->GetContentCharset(aCharset);
  }

  result = NS_ERROR_NOT_AVAILABLE;

#ifdef PR_LOGGING
  if (! aCharset.IsEmpty()) {
    LOG(("  Setting from HTTP to: %s", PromiseFlatCString(aCharset).get()));
  }
#endif

  if (aCharset.IsEmpty()) {
    //  We have no charset
    //  Try @charset rule and BOM
    result = GetCharsetFromData((const unsigned char*)aSegment.BeginReading(),
                                aSegment.Length(), aCharset);
#ifdef PR_LOGGING
    if (NS_SUCCEEDED(result)) {
      LOG(("  Setting from @charset rule or BOM: %s",
           PromiseFlatCString(aCharset).get()));
    }
#endif
  }

  if (aCharset.IsEmpty()) {
    // Now try the charset on the <link> or processing instruction
    // that loaded us
    if (mOwningElement) {
      nsAutoString elementCharset;
      mOwningElement->GetCharset(elementCharset);
      LossyCopyUTF16toASCII(elementCharset, aCharset);
#ifdef PR_LOGGING
      if (! aCharset.IsEmpty()) {
        LOG(("  Setting from property on element: %s",
             PromiseFlatCString(aCharset).get()));
      }
#endif
    } else {
      // If mCharsetHint is empty, that's ok; aCharset is known empty here
      aCharset = mCharsetHint;
    }
  }

  if (aCharset.IsEmpty() && mParentData) {
    aCharset = mParentData->mCharset;
#ifdef PR_LOGGING
    if (! aCharset.IsEmpty()) {
      LOG(("  Setting from parent sheet: %s",
           PromiseFlatCString(aCharset).get()));
    }
#endif
  }

  if (aCharset.IsEmpty() && mLoader->mDocument) {
    // no useful data on charset.  Try the document charset.
    aCharset = mLoader->mDocument->GetDocumentCharacterSet();
#ifdef PR_LOGGING
    LOG(("  Set from document: %s", PromiseFlatCString(aCharset).get()));
#endif
  }

  if (aCharset.IsEmpty()) {
    NS_WARNING("Unable to determine charset for sheet, using ISO-8859-1!");
#ifdef PR_LOGGING
    LOG_WARN(("  Falling back to ISO-8859-1"));
#endif
    aCharset.AssignLiteral("ISO-8859-1");
  }

  mCharset = aCharset;
  return NS_OK;
}

already_AddRefed<nsIURI>
SheetLoadData::GetReferrerURI()
{
  nsCOMPtr<nsIURI> uri;
  if (mParentData)
    uri = mParentData->mSheet->GetSheetURI();
  if (!uri && mLoader->mDocument)
    uri = mLoader->mDocument->GetDocumentURI();
  return uri.forget();
}

/*
 * Here we need to check that the load did not give us an http error
 * page and check the mimetype on the channel to make sure we're not
 * loading non-text/css data in standards mode.
 */
NS_IMETHODIMP
SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader,
                                nsISupports* aContext,
                                nsresult aStatus,
                                const nsAString& aBuffer)
{
  LOG(("SheetLoadData::OnStreamComplete"));
  NS_ASSERTION(!mLoader->mSyncCallback, "Synchronous callback from necko");

  if (mIsCancelled) {
    // Just return.  Don't call SheetComplete -- it's already been
    // called and calling it again will lead to an extra NS_RELEASE on
    // this data and a likely crash.
    return NS_OK;
  }

  if (!mLoader->mDocument && !mIsNonDocumentSheet) {
    // Sorry, we don't care about this load anymore
    LOG_WARN(("  No document and not non-document sheet; dropping load"));
    mLoader->SheetComplete(this, NS_BINDING_ABORTED);
    return NS_OK;
  }

  if (NS_FAILED(aStatus)) {
    LOG_WARN(("  Load failed: status 0x%x", aStatus));
    mLoader->SheetComplete(this, aStatus);
    return NS_OK;
  }

  nsCOMPtr<nsIChannel> channel;
  nsresult result = aLoader->GetChannel(getter_AddRefs(channel));
  if (NS_FAILED(result)) {
    LOG_WARN(("  No channel from loader"));
    mLoader->SheetComplete(this, result);
    return NS_OK;
  }

  nsCOMPtr<nsIURI> originalURI;
  channel->GetOriginalURI(getter_AddRefs(originalURI));

  // If the channel's original URI is "chrome:", we want that, since
  // the observer code in nsXULPrototypeCache depends on chrome stylesheets
  // having a chrome URI.  (Whether or not chrome stylesheets come through
  // this codepath seems nondeterministic.)
  // Otherwise we want the potentially-HTTP-redirected URI.
  nsCOMPtr<nsIURI> channelURI;
  NS_GetFinalChannelURI(channel, getter_AddRefs(channelURI));

  if (!channelURI || !originalURI) {
    NS_ERROR("Someone just violated the nsIRequest contract");
    LOG_WARN(("  Channel without a URI.  Bad!"));
    mLoader->SheetComplete(this, NS_ERROR_UNEXPECTED);
    return NS_OK;
  }

  nsCOMPtr<nsIPrincipal> principal;
  nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
  result = NS_ERROR_NOT_AVAILABLE;
  if (secMan) {  // Could be null if we already shut down
    if (mUseSystemPrincipal) {
      result = secMan->GetSystemPrincipal(getter_AddRefs(principal));
    } else {
      result = secMan->GetChannelPrincipal(channel, getter_AddRefs(principal));
    }
  }

  if (NS_FAILED(result)) {
    LOG_WARN(("  Couldn't get principal"));
    mLoader->SheetComplete(this, result);
    return NS_OK;
  }

  mSheet->SetPrincipal(principal);

  // If it's an HTTP channel, we want to make sure this is not an
  // error document we got.
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
  if (httpChannel) {
    bool requestSucceeded;
    result = httpChannel->GetRequestSucceeded(&requestSucceeded);
    if (NS_SUCCEEDED(result) && !requestSucceeded) {
      LOG(("  Load returned an error page"));
      mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE);
      return NS_OK;
    }
  }

  nsCAutoString contentType;
  if (channel) {
    channel->GetContentType(contentType);
  }

  // In standards mode, a style sheet must have one of these MIME
  // types to be processed at all.  In quirks mode, we accept any
  // MIME type, but only if the style sheet is same-origin with the
  // requesting document or parent sheet.  See bug 524223.

  bool validType = contentType.EqualsLiteral("text/css") ||
    contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
    contentType.IsEmpty();

  if (!validType) {
    const char *errorMessage;
    PRUint32 errorFlag;
    bool sameOrigin = true;

    if (mLoaderPrincipal) {
      bool subsumed;
      result = mLoaderPrincipal->Subsumes(principal, &subsumed);
      if (NS_FAILED(result) || !subsumed) {
        sameOrigin = PR_FALSE;
      }
    }

    if (sameOrigin && mLoader->mCompatMode == eCompatibility_NavQuirks) {
      errorMessage = "MimeNotCssWarn";
      errorFlag = nsIScriptError::warningFlag;
    } else {
      errorMessage = "MimeNotCss";
      errorFlag = nsIScriptError::errorFlag;
    }

    nsCAutoString spec;
    channelURI->GetSpec(spec);

    const nsAFlatString& specUTF16 = NS_ConvertUTF8toUTF16(spec);
    const nsAFlatString& ctypeUTF16 = NS_ConvertASCIItoUTF16(contentType);
    const PRUnichar *strings[] = { specUTF16.get(), ctypeUTF16.get() };

    nsCOMPtr<nsIURI> referrer = GetReferrerURI();
    nsContentUtils::ReportToConsole(nsContentUtils::eCSS_PROPERTIES,
                                    errorMessage,
                                    strings, NS_ARRAY_LENGTH(strings),
                                    referrer, EmptyString(), 0, 0, errorFlag,
                                    "CSS Loader", mLoader->mDocument);

    if (errorFlag == nsIScriptError::errorFlag) {
      LOG_WARN(("  Ignoring sheet with improper MIME type %s",
                contentType.get()));
      mLoader->SheetComplete(this, NS_ERROR_NOT_AVAILABLE);
      return NS_OK;
    }
  }

  // Enough to set the URIs on mSheet, since any sibling datas we have share
  // the same mInner as mSheet and will thus get the same URI.
  mSheet->SetURIs(channelURI, originalURI, channelURI);

  bool completed;
  result = mLoader->ParseSheet(aBuffer, this, completed);
  NS_ASSERTION(completed || !mSyncLoad, "sync load did not complete");
  return result;
}

#ifdef MOZ_XUL
static bool IsChromeURI(nsIURI* aURI)
{
  NS_ASSERTION(aURI, "Have to pass in a URI");
  bool isChrome = false;
  aURI->SchemeIs("chrome", &isChrome);
  return isChrome;
}
#endif

bool
Loader::IsAlternate(const nsAString& aTitle, bool aHasAlternateRel)
{
  // A sheet is alternate if it has a nonempty title that doesn't match the
  // currently selected style set.  But if there _is_ no currently selected
  // style set, the sheet wasn't marked as an alternate explicitly, and aTitle
  // is nonempty, we should select the style set corresponding to aTitle, since
  // that's a preferred sheet.
  if (aTitle.IsEmpty()) {
    return PR_FALSE;
  }

  if (!aHasAlternateRel && mDocument && mPreferredSheet.IsEmpty()) {
    // There's no preferred set yet, and we now have a sheet with a title.
    // Make that be the preferred set.
    mDocument->SetHeaderData(nsGkAtoms::headerDefaultStyle, aTitle);
    // We're definitely not an alternate
    return PR_FALSE;
  }

  return !aTitle.Equals(mPreferredSheet);
}

/**
 * CheckLoadAllowed will return success if the load is allowed,
 * failure otherwise.
 *
 * @param aSourcePrincipal the principal of the node or document or parent
 *                         sheet loading the sheet
 * @param aTargetURI the uri of the sheet to be loaded
 * @param aContext the node owning the sheet.  This is the element or document
 *                 owning the stylesheet (possibly indirectly, for child sheets)
 */
nsresult
Loader::CheckLoadAllowed(nsIPrincipal* aSourcePrincipal,
                         nsIURI* aTargetURI,
                         nsISupports* aContext)
{
  LOG(("css::Loader::CheckLoadAllowed"));

  nsresult rv;

  if (aSourcePrincipal) {
    // Check with the security manager
    nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
    rv =
      secMan->CheckLoadURIWithPrincipal(aSourcePrincipal, aTargetURI,
                                        nsIScriptSecurityManager::ALLOW_CHROME);
    if (NS_FAILED(rv)) { // failure is normal here; don't warn
      return rv;
    }

    LOG(("  Passed security check"));

    // Check with content policy

    PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
    rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_STYLESHEET,
                                   aTargetURI,
                                   aSourcePrincipal,
                                   aContext,
                                   NS_LITERAL_CSTRING("text/css"),
                                   nsnull,                     //extra param
                                   &shouldLoad,
                                   nsContentUtils::GetContentPolicy(),
                                   nsContentUtils::GetSecurityManager());

    if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
      LOG(("  Load blocked by content policy"));
      return NS_ERROR_CONTENT_BLOCKED;
    }
  }

  return NS_OK;
}

/**
 * CreateSheet() creates an nsCSSStyleSheet object for the given URI,
 * if any.  If there is no URI given, we just create a new style sheet
 * object.  Otherwise, we check for an existing style sheet object for
 * that uri in various caches and clone it if we find it.  Cloned
 * sheets will have the title/media/enabled state of the sheet they
 * are clones off; make sure to call PrepareSheet() on the result of
 * CreateSheet().
 */
nsresult
Loader::CreateSheet(nsIURI* aURI,
                    nsIContent* aLinkingContent,
                    nsIPrincipal* aLoaderPrincipal,
                    bool aSyncLoad,
                    StyleSheetState& aSheetState,
                    nsCSSStyleSheet** aSheet)
{
  LOG(("css::Loader::CreateSheet"));
  NS_PRECONDITION(aSheet, "Null out param!");

  NS_ENSURE_TRUE((mCompleteSheets.IsInitialized() || mCompleteSheets.Init()) &&
                   (mLoadingDatas.IsInitialized() || mLoadingDatas.Init()) &&
                   (mPendingDatas.IsInitialized() || mPendingDatas.Init()),
                 NS_ERROR_OUT_OF_MEMORY);

  nsresult rv = NS_OK;
  *aSheet = nsnull;
  aSheetState = eSheetStateUnknown;

  if (aURI) {
    aSheetState = eSheetComplete;
    nsRefPtr<nsCSSStyleSheet> sheet;

    // First, the XUL cache
#ifdef MOZ_XUL
    if (IsChromeURI(aURI)) {
      nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
      if (cache) {
        if (cache->IsEnabled()) {
          sheet = cache->GetStyleSheet(aURI);
          LOG(("  From XUL cache: %p", sheet.get()));
        }
      }
    }
#endif

    if (!sheet) {
      // Then our per-document complete sheets.
      URIAndPrincipalHashKey key(aURI, aLoaderPrincipal);

      mCompleteSheets.Get(&key, getter_AddRefs(sheet));
      LOG(("  From completed: %p", sheet.get()));
    }

    if (sheet) {
      // This sheet came from the XUL cache or our per-document hashtable; it
      // better be a complete sheet.
      NS_ASSERTION(sheet->IsComplete(),
                   "Sheet thinks it's not complete while we think it is");

      // Make sure it hasn't been modified; if it has, we can't use it
      if (sheet->IsModified()) {
        LOG(("  Not cloning completed sheet %p because it's been modified",
             sheet.get()));
        sheet = nsnull;
      }
    }

    // Then loading sheets
    if (!sheet && !aSyncLoad) {
      aSheetState = eSheetLoading;
      SheetLoadData* loadData = nsnull;
      URIAndPrincipalHashKey key(aURI, aLoaderPrincipal);
      mLoadingDatas.Get(&key, &loadData);
      if (loadData) {
        sheet = loadData->mSheet;
        LOG(("  From loading: %p", sheet.get()));

#ifdef DEBUG
        bool debugEqual;
        NS_ASSERTION((!aLoaderPrincipal && !loadData->mLoaderPrincipal) ||
                     (aLoaderPrincipal && loadData->mLoaderPrincipal &&
                      NS_SUCCEEDED(aLoaderPrincipal->
                                   Equals(loadData->mLoaderPrincipal,
                                          &debugEqual)) && debugEqual),
                     "Principals should be the same");
#endif
      }

      // Then alternate sheets
      if (!sheet) {
        aSheetState = eSheetPending;
        SheetLoadData* loadData = nsnull;
        mPendingDatas.Get(&key, &loadData);
        if (loadData) {
          sheet = loadData->mSheet;
          LOG(("  From pending: %p", sheet.get()));

#ifdef DEBUG
          bool debugEqual;
          NS_ASSERTION((!aLoaderPrincipal && !loadData->mLoaderPrincipal) ||
                       (aLoaderPrincipal && loadData->mLoaderPrincipal &&
                        NS_SUCCEEDED(aLoaderPrincipal->
                                     Equals(loadData->mLoaderPrincipal,
                                            &debugEqual)) && debugEqual),
                       "Principals should be the same");
#endif
        }
      }
    }

    if (sheet) {
      // The sheet we have now should be either incomplete or unmodified
      NS_ASSERTION(!sheet->IsModified() || !sheet->IsComplete(),
                   "Unexpected modified complete sheet");
      NS_ASSERTION(sheet->IsComplete() || aSheetState != eSheetComplete,
                   "Sheet thinks it's not complete while we think it is");

      *aSheet = sheet->Clone(nsnull, nsnull, nsnull, nsnull).get();
    }
  }

  if (!*aSheet) {
    aSheetState = eSheetNeedsParser;
    nsIURI *sheetURI;
    nsCOMPtr<nsIURI> baseURI;
    nsIURI* originalURI;
    if (!aURI) {
      // Inline style.  Use the document's base URL so that @import in
      // the inline sheet picks up the right base.
      NS_ASSERTION(aLinkingContent, "Inline stylesheet without linking content?");
      baseURI = aLinkingContent->GetBaseURI();
      sheetURI = aLinkingContent->GetDocument()->GetDocumentURI();
      originalURI = nsnull;
    } else {
      baseURI = aURI;
      sheetURI = aURI;
      originalURI = aURI;
    }

    rv = NS_NewCSSStyleSheet(aSheet);
    NS_ENSURE_SUCCESS(rv, rv);
    (*aSheet)->SetURIs(sheetURI, originalURI, baseURI);
  }

  NS_ASSERTION(*aSheet, "We should have a sheet by now!");
  NS_ASSERTION(aSheetState != eSheetStateUnknown, "Have to set a state!");
  LOG(("  State: %s", gStateStrings[aSheetState]));

  return NS_OK;
}

/**
 * PrepareSheet() handles setting the media and title on the sheet, as
 * well as setting the enabled state based on the title and whether
 * the sheet had "alternate" in its rel.
 */
nsresult
Loader::PrepareSheet(nsCSSStyleSheet* aSheet,
                     const nsSubstring& aTitle,
                     const nsSubstring& aMediaString,
                     nsMediaList* aMediaList,
                     bool aHasAlternateRel,
                     bool *aIsAlternate)
{
  NS_PRECONDITION(aSheet, "Must have a sheet!");

  nsresult rv;
  nsRefPtr<nsMediaList> mediaList(aMediaList);

  if (!aMediaString.IsEmpty()) {
    NS_ASSERTION(!aMediaList,
                 "must not provide both aMediaString and aMediaList");
    mediaList = new nsMediaList();
    NS_ENSURE_TRUE(mediaList, NS_ERROR_OUT_OF_MEMORY);

    nsCSSParser mediumParser(this);

    // We have aMediaString only when linked from link elements, style
    // elements, or PIs, so pass PR_TRUE.
    rv = mediumParser.ParseMediaList(aMediaString, nsnull, 0, mediaList,
                                     PR_TRUE);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  aSheet->SetMedia(mediaList);

  aSheet->SetTitle(aTitle);
  bool alternate = IsAlternate(aTitle, aHasAlternateRel);
  aSheet->SetEnabled(! alternate);
  if (aIsAlternate) {
    *aIsAlternate = alternate;
  }
  return NS_OK;
}

/**
 * InsertSheetInDoc handles ordering of sheets in the document.  Here
 * we have two types of sheets -- those with linking elements and
 * those without.  The latter are loaded by Link: headers.
 * The following constraints are observed:
 * 1) Any sheet with a linking element comes after all sheets without
 *    linking elements
 * 2) Sheets without linking elements are inserted in the order in
 *    which the inserting requests come in, since all of these are
 *    inserted during header data processing in the content sink
 * 3) Sheets with linking elements are ordered based on document order
 *    as determined by CompareDocumentPosition.
 */
nsresult
Loader::InsertSheetInDoc(nsCSSStyleSheet* aSheet,
                         nsIContent* aLinkingContent,
                         nsIDocument* aDocument)
{
  LOG(("css::Loader::InsertSheetInDoc"));
  NS_PRECONDITION(aSheet, "Nothing to insert");
  NS_PRECONDITION(aDocument, "Must have a document to insert into");

  // XXX Need to cancel pending sheet loads for this element, if any

  PRInt32 sheetCount = aDocument->GetNumberOfStyleSheets();

  /*
   * Start the walk at the _end_ of the list, since in the typical
   * case we'll just want to append anyway.  We want to break out of
   * the loop when insertionPoint points to just before the index we
   * want to insert at.  In other words, when we leave the loop
   * insertionPoint is the index of the stylesheet that immediately
   * precedes the one we're inserting.
   */
  PRInt32 insertionPoint;
  for (insertionPoint = sheetCount - 1; insertionPoint >= 0; --insertionPoint) {
    nsIStyleSheet *curSheet = aDocument->GetStyleSheetAt(insertionPoint);
    NS_ASSERTION(curSheet, "There must be a sheet here!");
    nsCOMPtr<nsIDOMStyleSheet> domSheet = do_QueryInterface(curSheet);
    NS_ASSERTION(domSheet, "All the \"normal\" sheets implement nsIDOMStyleSheet");
    nsCOMPtr<nsIDOMNode> sheetOwner;
    domSheet->GetOwnerNode(getter_AddRefs(sheetOwner));
    if (sheetOwner && !aLinkingContent) {
      // Keep moving; all sheets with a sheetOwner come after all
      // sheets without a linkingNode
      continue;
    }

    if (!sheetOwner) {
      // Aha!  The current sheet has no sheet owner, so we want to
      // insert after it no matter whether we have a linkingNode
      break;
    }

    nsCOMPtr<nsINode> sheetOwnerNode = do_QueryInterface(sheetOwner);
    NS_ASSERTION(aLinkingContent != sheetOwnerNode,
                 "Why do we still have our old sheet?");

    // Have to compare
    if (nsContentUtils::PositionIsBefore(sheetOwnerNode, aLinkingContent)) {
      // The current sheet comes before us, and it better be the first
      // such, because now we break
      break;
    }
  }

  ++insertionPoint; // adjust the index to the spot we want to insert in

  // XXX <meta> elements do not implement nsIStyleSheetLinkingElement;
  // need to fix this for them to be ordered correctly.
  nsCOMPtr<nsIStyleSheetLinkingElement>
    linkingElement = do_QueryInterface(aLinkingContent);
  if (linkingElement) {
    linkingElement->SetStyleSheet(aSheet); // This sets the ownerNode on the sheet
  }

  aDocument->BeginUpdate(UPDATE_STYLE);
  aDocument->InsertStyleSheetAt(aSheet, insertionPoint);
  aDocument->EndUpdate(UPDATE_STYLE);
  LOG(("  Inserting into document at position %d", insertionPoint));

  return NS_OK;
}

/**
 * InsertChildSheet handles ordering of @import-ed sheet in their
 * parent sheets.  Here we want to just insert based on order of the
 * @import rules that imported the sheets.  In theory we can't just
 * append to the end because the CSSOM can insert @import rules.  In
 * practice, we get the call to load the child sheet before the CSSOM
 * has finished inserting the @import rule, so we have no idea where
 * to put it anyway.  So just append for now.
 */
nsresult
Loader::InsertChildSheet(nsCSSStyleSheet* aSheet,
                         nsCSSStyleSheet* aParentSheet,
                         ImportRule* aParentRule)
{
  LOG(("css::Loader::InsertChildSheet"));
  NS_PRECONDITION(aSheet, "Nothing to insert");
  NS_PRECONDITION(aParentSheet, "Need a parent to insert into");
  NS_PRECONDITION(aParentSheet, "How did we get imported?");

  // child sheets should always start out enabled, even if they got
  // cloned off of top-level sheets which were disabled
  aSheet->SetEnabled(PR_TRUE);

  aParentSheet->AppendStyleSheet(aSheet);
  aParentRule->SetSheet(aSheet); // This sets the ownerRule on the sheet

  LOG(("  Inserting into parent sheet"));
  //  LOG(("  Inserting into parent sheet at position %d", insertionPoint));

  return NS_OK;
}

/**
 * LoadSheet handles the actual load of a sheet.  If the load is
 * supposed to be synchronous it just opens a channel synchronously
 * using the given uri, wraps the resulting stream in a converter
 * stream and calls ParseSheet.  Otherwise it tries to look for an
 * existing load for this URI and piggyback on it.  Failing all that,
 * a new load is kicked off asynchronously.
 */
nsresult
Loader::LoadSheet(SheetLoadData* aLoadData, StyleSheetState aSheetState)
{
  LOG(("css::Loader::LoadSheet"));
  NS_PRECONDITION(aLoadData, "Need a load data");
  NS_PRECONDITION(aLoadData->mURI, "Need a URI to load");
  NS_PRECONDITION(aLoadData->mSheet, "Need a sheet to load into");
  NS_PRECONDITION(aSheetState != eSheetComplete, "Why bother?");
  NS_PRECONDITION(!aLoadData->mUseSystemPrincipal || aLoadData->mSyncLoad,
                  "Shouldn't use system principal for async loads");
  NS_ASSERTION(mLoadingDatas.IsInitialized(), "mLoadingDatas should be initialized by now.");

#ifdef NS_FUNCTION_TIMER
  nsCAutoString spec__("N/A");
  if (aLoadData->mURI) aLoadData->mURI->GetSpec(spec__);
  NS_TIME_FUNCTION_FMT("Loading stylesheet (url: %s, %ssync)",
                       spec__.get(), aLoadData->mSyncLoad ? "" : "a");
#endif

  LOG_URI("  Load from: '%s'", aLoadData->mURI);

  nsresult rv = NS_OK;

  if (!mDocument && !aLoadData->mIsNonDocumentSheet) {
    // No point starting the load; just release all the data and such.
    LOG_WARN(("  No document and not non-document sheet; pre-dropping load"));
    SheetComplete(aLoadData, NS_BINDING_ABORTED);
    return NS_BINDING_ABORTED;
  }

  if (aLoadData->mSyncLoad) {
    LOG(("  Synchronous load"));
    NS_ASSERTION(!aLoadData->mObserver, "Observer for a sync load?");
    NS_ASSERTION(aSheetState == eSheetNeedsParser,
                 "Sync loads can't reuse existing async loads");

    // Create a nsIUnicharStreamLoader instance to which we will feed
    // the data from the sync load.  Do this before creating the
    // channel to make error recovery simpler.
    nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
    rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
    if (NS_FAILED(rv)) {
      LOG_ERROR(("  Failed to create stream loader for sync load"));
      SheetComplete(aLoadData, rv);
      return rv;
    }

    // Just load it
    nsCOMPtr<nsIInputStream> stream;
    nsCOMPtr<nsIChannel> channel;
    rv = NS_OpenURI(getter_AddRefs(stream), aLoadData->mURI, nsnull,
                    nsnull, nsnull, nsIRequest::LOAD_NORMAL,
                    getter_AddRefs(channel));
    if (NS_FAILED(rv)) {
      LOG_ERROR(("  Failed to open URI synchronously"));
      SheetComplete(aLoadData, rv);
      return rv;
    }

    NS_ASSERTION(channel, "NS_OpenURI lied?");

    // Force UA sheets to be UTF-8.
    // XXX this is only necessary because the default in
    // SheetLoadData::OnDetermineCharset is wrong (bug 521039).
    channel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));

    // Manually feed the streamloader the contents of the stream we
    // got from NS_OpenURI.  This will call back into OnStreamComplete
    // and thence to ParseSheet.  Regardless of whether this fails,
    // SheetComplete has been called.
    return nsSyncLoadService::PushSyncStreamToListener(stream,
                                                       streamLoader,
                                                       channel);
  }

  SheetLoadData* existingData = nsnull;

  URIAndPrincipalHashKey key(aLoadData->mURI, aLoadData->mLoaderPrincipal);
  if (aSheetState == eSheetLoading) {
    mLoadingDatas.Get(&key, &existingData);
    NS_ASSERTION(existingData, "CreateSheet lied about the state");
  }
  else if (aSheetState == eSheetPending){
    mPendingDatas.Get(&key, &existingData);
    NS_ASSERTION(existingData, "CreateSheet lied about the state");
  }

  if (existingData) {
    LOG(("  Glomming on to existing load"));
    SheetLoadData* data = existingData;
    while (data->mNext) {
      data = data->mNext;
    }
    data->mNext = aLoadData; // transfer ownership
    if (aSheetState == eSheetPending && !aLoadData->mWasAlternate) {
      // Kick the load off; someone cares about it right away

#ifdef DEBUG
      SheetLoadData* removedData;
      NS_ASSERTION(mPendingDatas.Get(&key, &removedData) &&
                   removedData == existingData,
                   "Bad pending table.");
#endif

      mPendingDatas.Remove(&key);

      LOG(("  Forcing load of pending data"));
      return LoadSheet(existingData, eSheetNeedsParser);
    }
    // All done here; once the load completes we'll be marked complete
    // automatically
    return NS_OK;
  }

#ifdef DEBUG
  mSyncCallback = PR_TRUE;
#endif
  nsCOMPtr<nsILoadGroup> loadGroup;
  // Content Security Policy information to pass into channel
  nsCOMPtr<nsIChannelPolicy> channelPolicy;
  if (mDocument) {
    loadGroup = mDocument->GetDocumentLoadGroup();
    NS_ASSERTION(loadGroup,
                 "No loadgroup for stylesheet; onload will fire early");
    nsCOMPtr<nsIContentSecurityPolicy> csp;
    rv = mDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
    NS_ENSURE_SUCCESS(rv, rv);
    if (csp) {
      channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1");
      channelPolicy->SetContentSecurityPolicy(csp);
      channelPolicy->SetLoadType(nsIContentPolicy::TYPE_STYLESHEET);
    }
  }

  nsCOMPtr<nsIChannel> channel;
  rv = NS_NewChannel(getter_AddRefs(channel),
                     aLoadData->mURI, nsnull, loadGroup, nsnull,
                     nsIChannel::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI,
                     channelPolicy);

  if (NS_FAILED(rv)) {
#ifdef DEBUG
    mSyncCallback = PR_FALSE;
#endif
    LOG_ERROR(("  Failed to create channel"));
    SheetComplete(aLoadData, rv);
    return rv;
  }

  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
  if (httpChannel) {
    // send a minimal Accept header for text/css
    httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
                                  NS_LITERAL_CSTRING("text/css,*/*;q=0.1"),
                                  PR_FALSE);
    nsCOMPtr<nsIURI> referrerURI = aLoadData->GetReferrerURI();
    if (referrerURI)
      httpChannel->SetReferrer(referrerURI);
  }

  // Now tell the channel we expect text/css data back....  We do
  // this before opening it, so it's only treated as a hint.
  channel->SetContentType(NS_LITERAL_CSTRING("text/css"));

  if (aLoadData->mLoaderPrincipal) {
    bool inherit;
    rv = NS_URIChainHasFlags(aLoadData->mURI,
                             nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
                             &inherit);
    if ((NS_SUCCEEDED(rv) && inherit) ||
        (nsContentUtils::URIIsLocalFile(aLoadData->mURI) &&
         NS_SUCCEEDED(aLoadData->mLoaderPrincipal->
                      CheckMayLoad(aLoadData->mURI, PR_FALSE)))) {
      channel->SetOwner(aLoadData->mLoaderPrincipal);
    }
  }

  // We don't have to hold on to the stream loader.  The ownership
  // model is: Necko owns the stream loader, which owns the load data,
  // which owns us
  nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
  rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);

  if (NS_SUCCEEDED(rv))
    rv = channel->AsyncOpen(streamLoader, nsnull);

#ifdef DEBUG
  mSyncCallback = PR_FALSE;
#endif

  if (NS_FAILED(rv)) {
    LOG_ERROR(("  Failed to create stream loader"));
    SheetComplete(aLoadData, rv);
    return rv;
  }

  if (!mLoadingDatas.Put(&key, aLoadData)) {
    LOG_ERROR(("  Failed to put data in loading table"));
    aLoadData->mIsCancelled = PR_TRUE;
    channel->Cancel(NS_ERROR_OUT_OF_MEMORY);
    SheetComplete(aLoadData, NS_ERROR_OUT_OF_MEMORY);
    return NS_ERROR_OUT_OF_MEMORY;
  }

  aLoadData->mIsLoading = PR_TRUE;

  return NS_OK;
}

/**
 * ParseSheet handles parsing the data stream.  The main idea here is
 * to push the current load data onto the parse stack before letting
 * the CSS parser at the data stream.  That lets us handle @import
 * correctly.
 */
nsresult
Loader::ParseSheet(const nsAString& aInput,
                   SheetLoadData* aLoadData,
                   bool& aCompleted)
{
  LOG(("css::Loader::ParseSheet"));
  NS_PRECONDITION(aLoadData, "Must have load data");
  NS_PRECONDITION(aLoadData->mSheet, "Must have sheet to parse into");

#ifdef NS_FUNCTION_TIMER
  nsCAutoString spec__("N/A");
  if (aLoadData->mURI) aLoadData->mURI->GetSpec(spec__);
  NS_TIME_FUNCTION_FMT("Parsing stylesheet (url: %s)", spec__.get());
#endif

  aCompleted = PR_FALSE;

  nsCSSParser parser(this, aLoadData->mSheet);

  // Push our load data on the stack so any kids can pick it up
  mParsingDatas.AppendElement(aLoadData);
  nsIURI* sheetURI = aLoadData->mSheet->GetSheetURI();
  nsIURI* baseURI = aLoadData->mSheet->GetBaseURI();
  nsresult rv = parser.ParseSheet(aInput, sheetURI, baseURI,
                                  aLoadData->mSheet->Principal(),
                                  aLoadData->mLineNumber,
                                  aLoadData->mAllowUnsafeRules);
  mParsingDatas.RemoveElementAt(mParsingDatas.Length() - 1);

  if (NS_FAILED(rv)) {
    LOG_ERROR(("  Low-level error in parser!"));
    SheetComplete(aLoadData, rv);
    return rv;
  }

  NS_ASSERTION(aLoadData->mPendingChildren == 0 || !aLoadData->mSyncLoad,
               "Sync load has leftover pending children!");

  if (aLoadData->mPendingChildren == 0) {
    LOG(("  No pending kids from parse"));
    aCompleted = PR_TRUE;
    SheetComplete(aLoadData, NS_OK);
  }
  // Otherwise, the children are holding strong refs to the data and
  // will call SheetComplete() on it when they complete.

  return NS_OK;
}

/**
 * SheetComplete is the do-it-all cleanup function.  It removes the
 * load data from the "loading" hashtable, adds the sheet to the
 * "completed" hashtable, massages the XUL cache, handles siblings of
 * the load data (other loads for the same URI), handles unblocking
 * blocked parent loads as needed, and most importantly calls
 * NS_RELEASE on the load data to destroy the whole mess.
 */
void
Loader::SheetComplete(SheetLoadData* aLoadData, nsresult aStatus)
{
  LOG(("css::Loader::SheetComplete"));

  // 8 is probably big enough for all our common cases.  It's not likely that
  // imports will nest more than 8 deep, and multiple sheets with the same URI
  // are rare.
  nsAutoTArray<nsRefPtr<SheetLoadData>, 8> datasToNotify;
  DoSheetComplete(aLoadData, aStatus, datasToNotify);

  // Now it's safe to go ahead and notify observers
  PRUint32 count = datasToNotify.Length();
  mDatasToNotifyOn += count;
  for (PRUint32 i = 0; i < count; ++i) {
    --mDatasToNotifyOn;

    SheetLoadData* data = datasToNotify[i];
    NS_ASSERTION(data && data->mMustNotify, "How did this data get here?");
    if (data->mObserver) {
      LOG(("  Notifying observer 0x%x for data 0x%x.  wasAlternate: %d",
           data->mObserver.get(), data, data->mWasAlternate));
      data->mObserver->StyleSheetLoaded(data->mSheet, data->mWasAlternate,
                                        aStatus);
    }

    nsTObserverArray<nsCOMPtr<nsICSSLoaderObserver> >::ForwardIterator iter(mObservers);
    nsCOMPtr<nsICSSLoaderObserver> obs;
    while (iter.HasMore()) {
      obs = iter.GetNext();
      LOG(("  Notifying global observer 0x%x for data 0x%s.  wasAlternate: %d",
           obs.get(), data, data->mWasAlternate));
      obs->StyleSheetLoaded(data->mSheet, data->mWasAlternate, aStatus);
    }
  }

  if (mLoadingDatas.Count() == 0 && mPendingDatas.Count() > 0) {
    LOG(("  No more loading sheets; starting alternates"));
    StartAlternateLoads();
  }
}

void
Loader::DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
                        LoadDataArray& aDatasToNotify)
{
  LOG(("css::Loader::DoSheetComplete"));
  NS_PRECONDITION(aLoadData, "Must have a load data!");
  NS_PRECONDITION(aLoadData->mSheet, "Must have a sheet");
  NS_ASSERTION(mLoadingDatas.IsInitialized(),"mLoadingDatas should be initialized by now.");

  LOG(("Load completed, status: 0x%x", aStatus));

  // Twiddle the hashtables
  if (aLoadData->mURI) {
    LOG_URI("  Finished loading: '%s'", aLoadData->mURI);
    // Remove the data from the list of loading datas
    if (aLoadData->mIsLoading) {
      URIAndPrincipalHashKey key(aLoadData->mURI,
                                 aLoadData->mLoaderPrincipal);
#ifdef DEBUG
      SheetLoadData *loadingData;
      NS_ASSERTION(mLoadingDatas.Get(&key, &loadingData) &&
                   loadingData == aLoadData,
                   "Bad loading table");
#endif

      mLoadingDatas.Remove(&key);
      aLoadData->mIsLoading = PR_FALSE;
    }
  }

  // Go through and deal with the whole linked list.
  SheetLoadData* data = aLoadData;
  while (data) {
    NS_ABORT_IF_FALSE(!data->mSheet->IsModified(),
                      "should not get marked modified during parsing");
    if (!data->mSheetAlreadyComplete) {
      data->mSheet->SetComplete();
      data->ScheduleLoadEventIfNeeded(aStatus);
    }
    if (data->mMustNotify && (data->mObserver || !mObservers.IsEmpty())) {
      // Don't notify here so we don't trigger script.  Remember the
      // info we need to notify, then do it later when it's safe.
      aDatasToNotify.AppendElement(data);

      // On append failure, just press on.  We'll fail to notify the observer,
      // but not much we can do about that....
    }

    NS_ASSERTION(!data->mParentData ||
                 data->mParentData->mPendingChildren != 0,
                 "Broken pending child count on our parent");

    // If we have a parent, our parent is no longer being parsed, and
    // we are the last pending child, then our load completion
    // completes the parent too.  Note that the parent _can_ still be
    // being parsed (eg if the child (us) failed to open the channel
    // or some such).
    if (data->mParentData &&
        --(data->mParentData->mPendingChildren) == 0 &&
        !mParsingDatas.Contains(data->mParentData)) {
      DoSheetComplete(data->mParentData, aStatus, aDatasToNotify);
    }

    data = data->mNext;
  }

  // Now that it's marked complete, put the sheet in our cache.
  // If we ever start doing this for failure aStatus, we'll need to
  // adjust the PostLoadEvent code that thinks anything already
  // complete must have loaded succesfully.
  if (NS_SUCCEEDED(aStatus) && aLoadData->mURI) {
#ifdef MOZ_XUL
    if (IsChromeURI(aLoadData->mURI)) {
      nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
      if (cache && cache->IsEnabled()) {
        if (!cache->GetStyleSheet(aLoadData->mURI)) {
          LOG(("  Putting sheet in XUL prototype cache"));
          cache->PutStyleSheet(aLoadData->mSheet);
        }
      }
    }
    else {
#endif
      URIAndPrincipalHashKey key(aLoadData->mURI,
                                 aLoadData->mLoaderPrincipal);
      mCompleteSheets.Put(&key, aLoadData->mSheet);
#ifdef MOZ_XUL
    }
#endif
  }

  NS_RELEASE(aLoadData);  // this will release parents and siblings and all that
}

nsresult
Loader::LoadInlineStyle(nsIContent* aElement,
                        const nsAString& aBuffer,
                        PRUint32 aLineNumber,
                        const nsAString& aTitle,
                        const nsAString& aMedia,
                        nsICSSLoaderObserver* aObserver,
                        bool* aCompleted,
                        bool* aIsAlternate)
{
  LOG(("css::Loader::LoadInlineStyle"));
  NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");

  *aCompleted = PR_TRUE;

  if (!mEnabled) {
    LOG_WARN(("  Not enabled"));
    return NS_ERROR_NOT_AVAILABLE;
  }

  NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);

  nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
  NS_ASSERTION(owningElement, "Element is not a style linking element!");

  // Since we're not planning to load a URI, no need to hand a principal to the
  // load data or to CreateSheet().
  StyleSheetState state;
  nsRefPtr<nsCSSStyleSheet> sheet;
  nsresult rv = CreateSheet(nsnull, aElement, nsnull, PR_FALSE, state,
                            getter_AddRefs(sheet));
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ASSERTION(state == eSheetNeedsParser,
               "Inline sheets should not be cached");

  rv = PrepareSheet(sheet, aTitle, aMedia, nsnull, PR_FALSE,
                    aIsAlternate);
  NS_ENSURE_SUCCESS(rv, rv);

  LOG(("  Sheet is alternate: %d", *aIsAlternate));

  rv = InsertSheetInDoc(sheet, aElement, mDocument);
  NS_ENSURE_SUCCESS(rv, rv);

  SheetLoadData* data = new SheetLoadData(this, aTitle, nsnull, sheet,
                                          owningElement, *aIsAlternate,
                                          aObserver, nsnull);

  // We never actually load this, so just set its principal directly
  sheet->SetPrincipal(aElement->NodePrincipal());

  NS_ADDREF(data);
  data->mLineNumber = aLineNumber;
  // Parse completion releases the load data
  rv = ParseSheet(aBuffer, data, *aCompleted);
  NS_ENSURE_SUCCESS(rv, rv);

  // If aCompleted is true, |data| may well be deleted by now.
  if (!*aCompleted) {
    data->mMustNotify = PR_TRUE;
  }
  return rv;
}

nsresult
Loader::LoadStyleLink(nsIContent* aElement,
                      nsIURI* aURL,
                      const nsAString& aTitle,
                      const nsAString& aMedia,
                      bool aHasAlternateRel,
                      nsICSSLoaderObserver* aObserver,
                      bool* aIsAlternate)
{
  LOG(("css::Loader::LoadStyleLink"));
  NS_PRECONDITION(aURL, "Must have URL to load");
  NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");

  LOG_URI("  Link uri: '%s'", aURL);
  LOG(("  Link title: '%s'", NS_ConvertUTF16toUTF8(aTitle).get()));
  LOG(("  Link media: '%s'", NS_ConvertUTF16toUTF8(aMedia).get()));
  LOG(("  Link alternate rel: %d", aHasAlternateRel));

  if (!mEnabled) {
    LOG_WARN(("  Not enabled"));
    return NS_ERROR_NOT_AVAILABLE;
  }

  NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);

  nsIPrincipal* principal =
    aElement ? aElement->NodePrincipal() : mDocument->NodePrincipal();

  nsISupports* context = aElement;
  if (!context) {
    context = mDocument;
  }
  nsresult rv = CheckLoadAllowed(principal, aURL, context);
  if (NS_FAILED(rv)) return rv;

  LOG(("  Passed load check"));

  StyleSheetState state;
  nsRefPtr<nsCSSStyleSheet> sheet;
  rv = CreateSheet(aURL, aElement, principal, PR_FALSE, state,
                   getter_AddRefs(sheet));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = PrepareSheet(sheet, aTitle, aMedia, nsnull, aHasAlternateRel,
                    aIsAlternate);
  NS_ENSURE_SUCCESS(rv, rv);

  LOG(("  Sheet is alternate: %d", *aIsAlternate));

  rv = InsertSheetInDoc(sheet, aElement, mDocument);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));

  if (state == eSheetComplete) {
    LOG(("  Sheet already complete: 0x%p",
         static_cast<void*>(sheet.get())));
    if (aObserver || !mObservers.IsEmpty() || owningElement) {
      rv = PostLoadEvent(aURL, sheet, aObserver, *aIsAlternate,
                         owningElement);
      return rv;
    }

    return NS_OK;
  }

  // Now we need to actually load it
  SheetLoadData* data = new SheetLoadData(this, aTitle, aURL, sheet,
                                          owningElement, *aIsAlternate,
                                          aObserver, principal);
  NS_ADDREF(data);

  // If we have to parse and it's an alternate non-inline, defer it
  if (aURL && state == eSheetNeedsParser && mLoadingDatas.Count() != 0 &&
      *aIsAlternate) {
    LOG(("  Deferring alternate sheet load"));
    URIAndPrincipalHashKey key(data->mURI, data->mLoaderPrincipal);
    if (!mPendingDatas.Put(&key, data)) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    data->mMustNotify = PR_TRUE;
    return NS_OK;
  }

  // Load completion will free the data
  rv = LoadSheet(data, state);
  NS_ENSURE_SUCCESS(rv, rv);

  data->mMustNotify = PR_TRUE;
  return rv;
}

static bool
HaveAncestorDataWithURI(SheetLoadData *aData, nsIURI *aURI)
{
  if (!aData->mURI) {
    // Inline style; this won't have any ancestors
    NS_ABORT_IF_FALSE(!aData->mParentData,
                      "How does inline style have a parent?");
    return PR_FALSE;
  }

  bool equal;
  if (NS_FAILED(aData->mURI->Equals(aURI, &equal)) || equal) {
    return PR_TRUE;
  }

  // Datas down the mNext chain have the same URI as aData, so we
  // don't have to compare to them.  But they might have different
  // parents, and we have to check all of those.
  while (aData) {
    if (aData->mParentData &&
        HaveAncestorDataWithURI(aData->mParentData, aURI)) {
      return PR_TRUE;
    }

    aData = aData->mNext;
  }

  return PR_FALSE;
}

nsresult
Loader::LoadChildSheet(nsCSSStyleSheet* aParentSheet,
                       nsIURI* aURL,
                       nsMediaList* aMedia,
                       ImportRule* aParentRule)
{
  LOG(("css::Loader::LoadChildSheet"));
  NS_PRECONDITION(aURL, "Must have a URI to load");
  NS_PRECONDITION(aParentSheet, "Must have a parent sheet");

  if (!mEnabled) {
    LOG_WARN(("  Not enabled"));
    return NS_ERROR_NOT_AVAILABLE;
  }

  LOG_URI("  Child uri: '%s'", aURL);

  nsCOMPtr<nsIDOMNode> owningNode;

  // check for an owning document: if none, don't bother walking up the parent
  // sheets
  if (aParentSheet->GetOwningDocument()) {
    nsCOMPtr<nsIDOMStyleSheet> nextParentSheet(aParentSheet);
    NS_ENSURE_TRUE(nextParentSheet, NS_ERROR_FAILURE); //Not a stylesheet!?

    nsCOMPtr<nsIDOMStyleSheet> topSheet;
    //traverse our way to the top-most sheet
    do {
      topSheet.swap(nextParentSheet);
      topSheet->GetParentStyleSheet(getter_AddRefs(nextParentSheet));
    } while (nextParentSheet);

    topSheet->GetOwnerNode(getter_AddRefs(owningNode));
  }

  nsISupports* context = owningNode;
  if (!context) {
    context = mDocument;
  }

  nsIPrincipal* principal = aParentSheet->Principal();
  nsresult rv = CheckLoadAllowed(principal, aURL, context);
  if (NS_FAILED(rv)) return rv;

  LOG(("  Passed load check"));

  SheetLoadData* parentData = nsnull;
  nsCOMPtr<nsICSSLoaderObserver> observer;

  PRInt32 count = mParsingDatas.Length();
  if (count > 0) {
    LOG(("  Have a parent load"));
    parentData = mParsingDatas.ElementAt(count - 1);
    // Check for cycles
    if (HaveAncestorDataWithURI(parentData, aURL)) {
      // Houston, we have a loop, blow off this child and pretend this never
      // happened
      LOG_ERROR(("  @import cycle detected, dropping load"));
      return NS_OK;
    }

    NS_ASSERTION(parentData->mSheet == aParentSheet,
                 "Unexpected call to LoadChildSheet");
  } else {
    LOG(("  No parent load; must be CSSOM"));
    // No parent load data, so the sheet will need to be notified when
    // we finish, if it can be, if we do the load asynchronously.
    observer = aParentSheet;
  }

  // Now that we know it's safe to load this (passes security check and not a
  // loop) do so
  nsRefPtr<nsCSSStyleSheet> sheet;
  StyleSheetState state;
  rv = CreateSheet(aURL, nsnull, principal,
                   parentData ? parentData->mSyncLoad : PR_FALSE,
                   state, getter_AddRefs(sheet));
  NS_ENSURE_SUCCESS(rv, rv);

  const nsSubstring& empty = EmptyString();
  rv = PrepareSheet(sheet, empty, empty, aMedia);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = InsertChildSheet(sheet, aParentSheet, aParentRule);
  NS_ENSURE_SUCCESS(rv, rv);

  if (state == eSheetComplete) {
    LOG(("  Sheet already complete"));
    // We're completely done.  No need to notify, even, since the
    // @import rule addition/modification will trigger the right style
    // changes automatically.
    return NS_OK;
  }

  SheetLoadData* data = new SheetLoadData(this, aURL, sheet, parentData,
                                          observer, principal);

  NS_ADDREF(data);
  bool syncLoad = data->mSyncLoad;

  // Load completion will release the data
  rv = LoadSheet(data, state);
  NS_ENSURE_SUCCESS(rv, rv);

  // If syncLoad is true, |data| will be deleted by now.
  if (!syncLoad) {
    data->mMustNotify = PR_TRUE;
  }
  return rv;
}

nsresult
Loader::LoadSheetSync(nsIURI* aURL, bool aAllowUnsafeRules,
                      bool aUseSystemPrincipal,
                      nsCSSStyleSheet** aSheet)
{
  LOG(("css::Loader::LoadSheetSync"));
  return InternalLoadNonDocumentSheet(aURL, aAllowUnsafeRules,
                                      aUseSystemPrincipal, nsnull,
                                      EmptyCString(), aSheet, nsnull);
}

nsresult
Loader::LoadSheet(nsIURI* aURL,
                  nsIPrincipal* aOriginPrincipal,
                  const nsCString& aCharset,
                  nsICSSLoaderObserver* aObserver,
                  nsCSSStyleSheet** aSheet)
{
  LOG(("css::Loader::LoadSheet(aURL, aObserver, aSheet) api call"));
  NS_PRECONDITION(aSheet, "aSheet is null");
  return InternalLoadNonDocumentSheet(aURL, PR_FALSE, PR_FALSE,
                                      aOriginPrincipal, aCharset,
                                      aSheet, aObserver);
}

nsresult
Loader::LoadSheet(nsIURI* aURL,
                  nsIPrincipal* aOriginPrincipal,
                  const nsCString& aCharset,
                  nsICSSLoaderObserver* aObserver)
{
  LOG(("css::Loader::LoadSheet(aURL, aObserver) api call"));
  return InternalLoadNonDocumentSheet(aURL, PR_FALSE, PR_FALSE,
                                      aOriginPrincipal, aCharset,
                                      nsnull, aObserver);
}

nsresult
Loader::InternalLoadNonDocumentSheet(nsIURI* aURL,
                                     bool aAllowUnsafeRules,
                                     bool aUseSystemPrincipal,
                                     nsIPrincipal* aOriginPrincipal,
                                     const nsCString& aCharset,
                                     nsCSSStyleSheet** aSheet,
                                     nsICSSLoaderObserver* aObserver)
{
  NS_PRECONDITION(aURL, "Must have a URI to load");
  NS_PRECONDITION(aSheet || aObserver, "Sheet and observer can't both be null");
  NS_PRECONDITION(!aUseSystemPrincipal || !aObserver,
                  "Shouldn't load system-principal sheets async");
  NS_ASSERTION(mParsingDatas.Length() == 0, "We're in the middle of a parse?");

  LOG_URI("  Non-document sheet uri: '%s'", aURL);

  if (aSheet) {
    *aSheet = nsnull;
  }

  if (!mEnabled) {
    LOG_WARN(("  Not enabled"));
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsresult rv = CheckLoadAllowed(aOriginPrincipal, aURL, mDocument);
  if (NS_FAILED(rv)) {
    return rv;
  }

  StyleSheetState state;
  nsRefPtr<nsCSSStyleSheet> sheet;
  bool syncLoad = (aObserver == nsnull);

  rv = CreateSheet(aURL, nsnull, aOriginPrincipal, syncLoad, state,
                   getter_AddRefs(sheet));
  NS_ENSURE_SUCCESS(rv, rv);

  const nsSubstring& empty = EmptyString();
  rv = PrepareSheet(sheet, empty, empty, nsnull);
  NS_ENSURE_SUCCESS(rv, rv);

  if (state == eSheetComplete) {
    LOG(("  Sheet already complete"));
    if (aObserver || !mObservers.IsEmpty()) {
      rv = PostLoadEvent(aURL, sheet, aObserver, PR_FALSE, nsnull);
    }
    if (aSheet) {
      sheet.swap(*aSheet);
    }
    return rv;
  }

  SheetLoadData* data =
    new SheetLoadData(this, aURL, sheet, syncLoad, aAllowUnsafeRules,
                      aUseSystemPrincipal, aCharset, aObserver,
                      aOriginPrincipal);

  NS_ADDREF(data);
  rv = LoadSheet(data, state);
  NS_ENSURE_SUCCESS(rv, rv);

  if (aSheet) {
    sheet.swap(*aSheet);
  }
  if (aObserver) {
    data->mMustNotify = PR_TRUE;
  }

  return rv;
}

nsresult
Loader::PostLoadEvent(nsIURI* aURI,
                      nsCSSStyleSheet* aSheet,
                      nsICSSLoaderObserver* aObserver,
                      bool aWasAlternate,
                      nsIStyleSheetLinkingElement* aElement)
{
  LOG(("css::Loader::PostLoadEvent"));
  NS_PRECONDITION(aSheet, "Must have sheet");
  NS_PRECONDITION(aObserver || !mObservers.IsEmpty() || aElement,
                  "Must have observer or element");

  nsRefPtr<SheetLoadData> evt =
    new SheetLoadData(this, EmptyString(), // title doesn't matter here
                      aURI,
                      aSheet,
                      aElement,
                      aWasAlternate,
                      aObserver,
                      nsnull);
  NS_ENSURE_TRUE(evt, NS_ERROR_OUT_OF_MEMORY);

  if (!mPostedEvents.AppendElement(evt)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  nsresult rv = NS_DispatchToCurrentThread(evt);
  if (NS_FAILED(rv)) {
    NS_WARNING("failed to dispatch stylesheet load event");
    mPostedEvents.RemoveElement(evt);
  } else {
    // We'll unblock onload when we handle the event.
    if (mDocument) {
      mDocument->BlockOnload();
    }

    // We want to notify the observer for this data.
    evt->mMustNotify = PR_TRUE;
    evt->mSheetAlreadyComplete = PR_TRUE;

    // If we get to this code, aSheet loaded correctly at some point, so
    // we can just use NS_OK for the status.  Note that we do this here
    // and not from inside our SheetComplete so that we don't end up
    // running the load event async.
    evt->ScheduleLoadEventIfNeeded(NS_OK);
  }

  return rv;
}

void
Loader::HandleLoadEvent(SheetLoadData* aEvent)
{
  // XXXbz can't assert this yet.... May not have an observer because
  // we're unblocking the parser
  // NS_ASSERTION(aEvent->mObserver, "Must have observer");
  NS_ASSERTION(aEvent->mSheet, "Must have sheet");

  // Very important: this needs to come before the SheetComplete call
  // below, so that HasPendingLoads() will test true as needed under
  // notifications we send from that SheetComplete call.
  mPostedEvents.RemoveElement(aEvent);

  if (!aEvent->mIsCancelled) {
    // SheetComplete will call Release(), so give it a reference to do
    // that with.
    NS_ADDREF(aEvent);
    SheetComplete(aEvent, NS_OK);
  }

  if (mDocument) {
    mDocument->UnblockOnload(PR_TRUE);
  }
}

static PLDHashOperator
StopLoadingSheetCallback(URIAndPrincipalHashKey* aKey,
                         SheetLoadData*& aData,
                         void* aClosure)
{
  NS_PRECONDITION(aData, "Must have a data!");
  NS_PRECONDITION(aClosure, "Must have a loader");

  aData->mIsLoading = PR_FALSE; // we will handle the removal right here
  aData->mIsCancelled = PR_TRUE;

  static_cast<Loader::LoadDataArray*>(aClosure)->AppendElement(aData);

  return PL_DHASH_REMOVE;
}

nsresult
Loader::Stop()
{
  PRUint32 pendingCount =
    mPendingDatas.IsInitialized() ?  mPendingDatas.Count() : 0;
  PRUint32 loadingCount =
    mLoadingDatas.IsInitialized() ? mLoadingDatas.Count() : 0;
  LoadDataArray arr(pendingCount + loadingCount + mPostedEvents.Length());

  if (pendingCount) {
    mPendingDatas.Enumerate(StopLoadingSheetCallback, &arr);
  }
  if (loadingCount) {
    mLoadingDatas.Enumerate(StopLoadingSheetCallback, &arr);
  }

  PRUint32 i;
  for (i = 0; i < mPostedEvents.Length(); ++i) {
    SheetLoadData* data = mPostedEvents[i];
    data->mIsCancelled = PR_TRUE;
    if (arr.AppendElement(data)) {
      // SheetComplete() calls Release(), so give this an extra ref.
      NS_ADDREF(data);
    }
#ifdef DEBUG
    else {
      NS_NOTREACHED("We preallocated this memory... shouldn't really fail, "
                    "except we never check that preallocation succeeds.");
    }
#endif
  }
  mPostedEvents.Clear();

  mDatasToNotifyOn += arr.Length();
  for (i = 0; i < arr.Length(); ++i) {
    --mDatasToNotifyOn;
    SheetComplete(arr[i], NS_BINDING_ABORTED);
  }
  return NS_OK;
}

bool
Loader::HasPendingLoads()
{
  return
    (mLoadingDatas.IsInitialized() && mLoadingDatas.Count() != 0) ||
    (mPendingDatas.IsInitialized() && mPendingDatas.Count() != 0) ||
    mPostedEvents.Length() != 0 ||
    mDatasToNotifyOn != 0;
}

nsresult
Loader::AddObserver(nsICSSLoaderObserver* aObserver)
{
  NS_PRECONDITION(aObserver, "Must have observer");
  if (mObservers.AppendElementUnlessExists(aObserver)) {
    return NS_OK;
  }

  return NS_ERROR_OUT_OF_MEMORY;
}

void
Loader::RemoveObserver(nsICSSLoaderObserver* aObserver)
{
  mObservers.RemoveElement(aObserver);
}

static PLDHashOperator
CollectLoadDatas(URIAndPrincipalHashKey *aKey,
                 SheetLoadData* &aData,
                 void* aClosure)
{
  static_cast<Loader::LoadDataArray*>(aClosure)->AppendElement(aData);
  return PL_DHASH_REMOVE;
}

void
Loader::StartAlternateLoads()
{
  NS_PRECONDITION(mPendingDatas.IsInitialized(), "Don't call me!");
  LoadDataArray arr(mPendingDatas.Count());
  mPendingDatas.Enumerate(CollectLoadDatas, &arr);

  mDatasToNotifyOn += arr.Length();
  for (PRUint32 i = 0; i < arr.Length(); ++i) {
    --mDatasToNotifyOn;
    LoadSheet(arr[i], eSheetNeedsParser);
  }
}

} // namespace css
} // namespace mozilla
