gecko-dev/layout/style/nsCSSLoader.cpp

2389 строки
75 KiB
C++

/* -*- 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 "nsCSSLoader.h"
#include "nsIContent.h"
#include "nsIDOMNode.h"
#include "nsIDOMWindow.h"
#include "nsIDocument.h"
#include "nsIDOMNSDocumentStyle.h"
#include "nsIUnicharInputStream.h"
#include "nsIConverterInputStream.h"
#include "nsICharsetAlias.h"
#include "nsUnicharUtils.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 "nsITimelineService.h"
#include "nsIHttpChannel.h"
#include "nsIScriptError.h"
#include "nsMimeTypes.h"
#include "nsIAtom.h"
#include "nsIDOM3Node.h"
#include "nsICSSStyleSheet.h"
#include "nsIStyleSheetLinkingElement.h"
#include "nsICSSLoaderObserver.h"
#include "nsICSSLoader.h"
#include "nsICSSParser.h"
#include "nsICSSImportRule.h"
#include "nsThreadUtils.h"
#include "nsGkAtoms.h"
#include "nsDocShellCID.h"
#include "nsIChannelClassifier.h"
#ifdef MOZ_XUL
#include "nsXULPrototypeCache.h"
#endif
#include "nsIMediaList.h"
#include "nsIDOMStyleSheet.h"
#include "nsIDOMCSSStyleSheet.h"
#include "nsIDOMCSSImportRule.h"
#include "nsContentErrors.h"
#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_ISUPPORTS2(SheetLoadData, nsIUnicharStreamLoaderObserver, nsIRunnable)
SheetLoadData::SheetLoadData(CSSLoaderImpl* aLoader,
const nsSubstring& aTitle,
nsIURI* aURI,
nsICSSStyleSheet* aSheet,
nsIStyleSheetLinkingElement* aOwningElement,
PRBool 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),
mOwningElement(aOwningElement),
mObserver(aObserver),
mLoaderPrincipal(aLoaderPrincipal)
{
NS_PRECONDITION(mLoader, "Must have a loader!");
NS_ADDREF(mLoader);
}
SheetLoadData::SheetLoadData(CSSLoaderImpl* aLoader,
nsIURI* aURI,
nsICSSStyleSheet* 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),
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(CSSLoaderImpl* aLoader,
nsIURI* aURI,
nsICSSStyleSheet* aSheet,
PRBool aSyncLoad,
PRBool aAllowUnsafeRules,
PRBool 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),
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;
}
/*************************
* Loader Implementation *
*************************/
// static
nsCOMArray<nsICSSParser>* CSSLoaderImpl::gParsers = nsnull;
CSSLoaderImpl::CSSLoaderImpl(void)
: mDocument(nsnull),
mEnabled(PR_TRUE),
mCompatMode(eCompatibility_FullStandards),
mDatasToNotifyOn(0)
{
}
CSSLoaderImpl::~CSSLoaderImpl(void)
{
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_ISUPPORTS1(CSSLoaderImpl, nsICSSLoader)
void
CSSLoaderImpl::Shutdown()
{
delete gParsers;
gParsers = nsnull;
}
NS_IMETHODIMP
CSSLoaderImpl::Init(nsIDocument* aDocument)
{
NS_ASSERTION(! mDocument, "already initialized");
mDocument = aDocument;
// 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<nsIDOMNSDocumentStyle> domDoc(do_QueryInterface(mDocument));
if (domDoc) {
domDoc->GetPreferredStyleSheetSet(mPreferredSheet);
}
return NS_OK;
}
NS_IMETHODIMP
CSSLoaderImpl::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();
}
return NS_OK;
}
NS_IMETHODIMP
CSSLoaderImpl::SetCompatibilityMode(nsCompatibility aCompatMode)
{
mCompatMode = aCompatMode;
return NS_OK;
}
static PLDHashOperator
CollectNonAlternates(nsURIAndPrincipalHashKey *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<CSSLoaderImpl::LoadDataArray*>(aClosure)->AppendElement(aData);
return PL_DHASH_REMOVE;
}
NS_IMETHODIMP
CSSLoaderImpl::SetPreferredSheet(const nsAString& aTitle)
{
#ifdef DEBUG
nsCOMPtr<nsIDOMNSDocumentStyle> 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;
}
NS_IMETHODIMP
CSSLoaderImpl::GetPreferredSheet(nsAString& aTitle)
{
aTitle.Assign(mPreferredSheet);
return NS_OK;
}
NS_IMETHODIMP
CSSLoaderImpl::GetParserFor(nsICSSStyleSheet* aSheet,
nsICSSParser** aParser)
{
NS_PRECONDITION(aParser, "Null out param");
*aParser = nsnull;
if (!gParsers) {
gParsers = new nsCOMArray<nsICSSParser>;
if (!gParsers) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
PRInt32 count = gParsers->Count();
if (0 < count--) {
*aParser = gParsers->ObjectAt(count);
NS_ADDREF(*aParser);
gParsers->RemoveObjectAt(count);
}
nsresult result = NS_OK;
if (! *aParser) {
result = NS_NewCSSParser(aParser);
}
if (*aParser) {
(*aParser)->SetQuirkMode(mCompatMode == eCompatibility_NavQuirks);
if (aSheet) {
(*aParser)->SetStyleSheet(aSheet);
}
(*aParser)->SetChildLoader(this);
}
return result;
}
NS_IMETHODIMP
CSSLoaderImpl::RecycleParser(nsICSSParser* aParser)
{
NS_PRECONDITION(aParser, "Recycle only good parsers, please");
NS_ENSURE_TRUE(gParsers, NS_ERROR_UNEXPECTED);
if (!gParsers->AppendObject(aParser)) {
return NS_ERROR_FAILURE;
}
// Make sure the parser doesn't keep the last sheet it parsed alive
aParser->SetStyleSheet(nsnull);
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;
PRBool bigEndian = PR_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;
}
// Check for a 4-byte encoding BOM before checking for a 2-byte one,
// since the latter can be a proper subset of the former.
else if (aStyleSheetData[0] == 0x00 &&
aStyleSheetData[1] == 0x00 &&
aStyleSheetData[2] == 0xFF &&
aStyleSheetData[3] == 0xFE) {
// 4-byte encoding BOM in 2143 order
NS_WARNING("Our unicode decoders aren't likely to deal with this one");
step = 4;
pos = 6;
aCharset = "UTF-32";
}
else if (aStyleSheetData[0] == 0xFE &&
aStyleSheetData[1] == 0xFF &&
aStyleSheetData[2] == 0x00 &&
aStyleSheetData[3] == 0x00) {
// 4-byte encoding BOM in 3412 order
NS_WARNING("Our unicode decoders aren't likely to deal with this one");
step = 4;
pos = 5;
aCharset = "UTF-32";
}
else if (nsContentUtils::CheckForBOM(aStyleSheetData,
aDataLength, aCharset, &bigEndian)) {
if (aCharset.Equals("UTF-8")) {
step = 1;
pos = 3;
}
else if (aCharset.Equals("UTF-32")) {
step = 4;
pos = bigEndian ? 7 : 4;
}
else if (aCharset.Equals("UTF-16")) {
step = 2;
pos = bigEndian ? 3 : 2;
}
}
else if (aStyleSheetData[0] == 0x00 &&
aStyleSheetData[1] == 0x00 &&
aStyleSheetData[2] == 0x00 &&
aStyleSheetData[3] == 0x40) {
// big-endian 4-byte encoding, no BOM
step = 4;
pos = 3;
}
else if (aStyleSheetData[0] == 0x40 &&
aStyleSheetData[1] == 0x00 &&
aStyleSheetData[2] == 0x00 &&
aStyleSheetData[3] == 0x00) {
// little-endian 4-byte encoding, no BOM
step = 4;
pos = 0;
}
else if (aStyleSheetData[0] == 0x00 &&
aStyleSheetData[1] == 0x00 &&
aStyleSheetData[2] == 0x40 &&
aStyleSheetData[3] == 0x00) {
// 4-byte encoding in 2143 order, no BOM
step = 4;
pos = 2;
}
else if (aStyleSheetData[0] == 0x00 &&
aStyleSheetData[1] == 0x40 &&
aStyleSheetData[2] == 0x00 &&
aStyleSheetData[3] == 0x00) {
// 4-byte encoding in 3412 order, no BOM
step = 4;
pos = 1;
}
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,
const char* aData,
PRUint32 aDataLength,
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*)aData,
aDataLength, 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()
{
nsIURI* uri = nsnull;
if (mParentData)
mParentData->mSheet->GetSheetURI(&uri);
if (!uri && mLoader->mDocument)
NS_IF_ADDREF(uri = mLoader->mDocument->GetDocumentURI());
return uri;
}
/*
* 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,
nsIUnicharInputStream* aDataStream)
{
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
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);
#ifdef MOZ_TIMELINE
NS_TIMELINE_OUTDENT();
NS_TIMELINE_MARK_CHANNEL("SheetLoadData::OnStreamComplete(%s)", channel);
#endif // MOZ_TIMELINE
// 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) {
PRBool 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;
}
}
if (aDataStream) {
nsCAutoString contentType;
if (channel) {
channel->GetContentType(contentType);
}
PRBool validType = contentType.EqualsLiteral("text/css") ||
contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
contentType.IsEmpty();
if (!validType) {
nsCAutoString spec;
channelURI->GetSpec(spec);
const nsAFlatString& specUTF16 = NS_ConvertUTF8toUTF16(spec);
const nsAFlatString& ctypeUTF16 = NS_ConvertASCIItoUTF16(contentType);
const PRUnichar *strings[] = { specUTF16.get(), ctypeUTF16.get() };
const char *errorMessage;
PRUint32 errorFlag;
if (mLoader->mCompatMode == eCompatibility_NavQuirks) {
errorMessage = "MimeNotCssWarn";
errorFlag = nsIScriptError::warningFlag;
} else {
// Drop the data stream so that we do not load it
aDataStream = nsnull;
errorMessage = "MimeNotCss";
errorFlag = nsIScriptError::errorFlag;
}
nsCOMPtr<nsIURI> referrer = GetReferrerURI();
nsContentUtils::ReportToConsole(nsContentUtils::eCSS_PROPERTIES,
errorMessage,
strings, NS_ARRAY_LENGTH(strings),
referrer, EmptyString(), 0, 0, errorFlag,
"CSS Loader");
}
}
if (!aDataStream) {
LOG_WARN((" No data stream; bailing"));
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);
PRBool completed;
return mLoader->ParseSheet(aDataStream, this, completed);
}
#ifdef MOZ_XUL
static PRBool IsChromeURI(nsIURI* aURI)
{
NS_ASSERTION(aURI, "Have to pass in a URI");
PRBool isChrome = PR_FALSE;
aURI->SchemeIs("chrome", &isChrome);
return isChrome;
}
#endif
PRBool
CSSLoaderImpl::IsAlternate(const nsAString& aTitle, PRBool 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
CSSLoaderImpl::CheckLoadAllowed(nsIPrincipal* aSourcePrincipal,
nsIURI* aTargetURI,
nsISupports* aContext)
{
LOG(("CSSLoaderImpl::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 nsICSSStyleSheet 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
CSSLoaderImpl::CreateSheet(nsIURI* aURI,
nsIContent* aLinkingContent,
nsIPrincipal* aLoaderPrincipal,
PRBool aSyncLoad,
StyleSheetState& aSheetState,
nsICSSStyleSheet** aSheet)
{
LOG(("CSSLoaderImpl::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;
nsCOMPtr<nsICSSStyleSheet> 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.
nsURIAndPrincipalHashKey key(aURI, aLoaderPrincipal);
mCompleteSheets.Get(&key, getter_AddRefs(sheet));
LOG((" From completed: %p", sheet.get()));
}
if (sheet) {
#ifdef DEBUG
// This sheet came from the XUL cache or our per-document hashtable; it
// better be a complete sheet.
PRBool complete = PR_FALSE;
sheet->GetComplete(complete);
NS_ASSERTION(complete,
"Sheet thinks it's not complete while we think it is");
#endif
// Make sure it hasn't been modified; if it has, we can't use it
PRBool modified = PR_TRUE;
sheet->IsModified(&modified);
if (modified) {
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;
nsURIAndPrincipalHashKey key(aURI, aLoaderPrincipal);
mLoadingDatas.Get(&key, &loadData);
if (loadData) {
sheet = loadData->mSheet;
LOG((" From loading: %p", sheet.get()));
#ifdef DEBUG
PRBool 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
PRBool 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
#ifdef DEBUG
PRBool modified = PR_TRUE;
sheet->IsModified(&modified);
PRBool complete = PR_FALSE;
sheet->GetComplete(complete);
NS_ASSERTION(!modified || !complete,
"Unexpected modified complete sheet");
NS_ASSERTION(complete || aSheetState != eSheetComplete,
"Sheet thinks it's not complete while we think it is");
#endif
rv = sheet->Clone(nsnull, nsnull, nsnull, nsnull, aSheet);
NS_ENSURE_SUCCESS(rv, rv);
}
}
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
CSSLoaderImpl::PrepareSheet(nsICSSStyleSheet* aSheet,
const nsSubstring& aTitle,
const nsSubstring& aMediaString,
nsMediaList* aMediaList,
PRBool aHasAlternateRel,
PRBool *aIsAlternate)
{
NS_PRECONDITION(aSheet, "Must have a sheet!");
nsresult rv;
nsCOMPtr<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);
nsCOMPtr<nsICSSParser> mediumParser;
nsresult rv = GetParserFor(nsnull, getter_AddRefs(mediumParser));
NS_ENSURE_SUCCESS(rv, rv);
// 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);
RecycleParser(mediumParser);
}
rv = aSheet->SetMedia(mediaList);
NS_ENSURE_SUCCESS(rv, rv);
aSheet->SetTitle(aTitle);
PRBool 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
CSSLoaderImpl::InsertSheetInDoc(nsICSSStyleSheet* aSheet,
nsIContent* aLinkingContent,
nsIDocument* aDocument)
{
LOG(("CSSLoaderImpl::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
CSSLoaderImpl::InsertChildSheet(nsICSSStyleSheet* aSheet,
nsICSSStyleSheet* aParentSheet,
nsICSSImportRule* aParentRule)
{
LOG(("CSSLoaderImpl::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
CSSLoaderImpl::LoadSheet(SheetLoadData* aLoadData, StyleSheetState aSheetState)
{
LOG(("CSSLoaderImpl::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.");
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");
// 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?");
// Get the principal for this sheet
nsCOMPtr<nsIPrincipal> principal;
if (aLoadData->mUseSystemPrincipal) {
rv = nsContentUtils::GetSecurityManager()->
GetSystemPrincipal(getter_AddRefs(principal));
} else {
rv = nsContentUtils::GetSecurityManager()->
GetChannelPrincipal(channel, getter_AddRefs(principal));
}
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to get a principal for the sheet"));
SheetComplete(aLoadData, rv);
return rv;
}
aLoadData->mSheet->SetPrincipal(principal);
nsCOMPtr<nsIConverterInputStream> converterStream =
do_CreateInstance("@mozilla.org/intl/converter-input-stream;1", &rv);
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to create converter stream"));
SheetComplete(aLoadData, rv);
return rv;
}
// This forces UA sheets to be UTF-8. We should really look for
// @charset rules here via ReadSegments on the raw stream...
// 8192 is a nice magic number that happens to be what a lot of
// other things use for buffer sizes.
rv = converterStream->Init(stream, "UTF-8",
8192,
nsIConverterInputStream::
DEFAULT_REPLACEMENT_CHARACTER);
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to initialize converter stream"));
SheetComplete(aLoadData, rv);
return rv;
}
PRBool completed;
rv = ParseSheet(converterStream, aLoadData, completed);
NS_ASSERTION(completed, "sync load did not complete");
return rv;
}
SheetLoadData* existingData = nsnull;
nsURIAndPrincipalHashKey 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;
if (mDocument) {
loadGroup = mDocument->GetDocumentLoadGroup();
NS_ASSERTION(loadGroup,
"No loadgroup for stylesheet; onload will fire early");
}
#ifdef MOZ_TIMELINE
NS_TIMELINE_MARK_URI("Loading style sheet: %s", aLoadData->mURI);
NS_TIMELINE_INDENT();
#endif
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannel(getter_AddRefs(channel),
aLoadData->mURI, nsnull, loadGroup,
nsnull, nsIChannel::LOAD_NORMAL);
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) {
PRBool 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;
}
// Check the load against the URI classifier
nsCOMPtr<nsIChannelClassifier> classifier =
do_CreateInstance(NS_CHANNELCLASSIFIER_CONTRACTID);
if (classifier) {
rv = classifier->Start(channel, PR_TRUE);
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to classify URI"));
aLoadData->mIsCancelled = PR_TRUE;
channel->Cancel(rv);
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
CSSLoaderImpl::ParseSheet(nsIUnicharInputStream* aStream,
SheetLoadData* aLoadData,
PRBool& aCompleted)
{
LOG(("CSSLoaderImpl::ParseSheet"));
NS_PRECONDITION(aStream, "Must have data to parse");
NS_PRECONDITION(aLoadData, "Must have load data");
NS_PRECONDITION(aLoadData->mSheet, "Must have sheet to parse into");
aCompleted = PR_FALSE;
nsCOMPtr<nsICSSParser> parser;
nsresult rv = GetParserFor(aLoadData->mSheet, getter_AddRefs(parser));
if (NS_FAILED(rv)) {
LOG_ERROR((" Failed to get CSS parser"));
SheetComplete(aLoadData, rv);
return rv;
}
// Push our load data on the stack so any kids can pick it up
mParsingDatas.AppendElement(aLoadData);
nsCOMPtr<nsIURI> sheetURI, baseURI;
aLoadData->mSheet->GetSheetURI(getter_AddRefs(sheetURI));
aLoadData->mSheet->GetBaseURI(getter_AddRefs(baseURI));
rv = parser->Parse(aStream, sheetURI, baseURI,
aLoadData->mSheet->Principal(), aLoadData->mLineNumber,
aLoadData->mAllowUnsafeRules);
mParsingDatas.RemoveElementAt(mParsingDatas.Length() - 1);
RecycleParser(parser);
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
CSSLoaderImpl::SheetComplete(SheetLoadData* aLoadData, nsresult aStatus)
{
LOG(("CSSLoaderImpl::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
CSSLoaderImpl::DoSheetComplete(SheetLoadData* aLoadData, nsresult aStatus,
LoadDataArray& aDatasToNotify)
{
LOG(("CSSLoaderImpl::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) {
nsURIAndPrincipalHashKey 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) {
data->mSheet->SetModified(PR_FALSE); // it's clean
data->mSheet->SetComplete();
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 (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
nsURIAndPrincipalHashKey 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
}
NS_IMETHODIMP
CSSLoaderImpl::LoadInlineStyle(nsIContent* aElement,
nsIUnicharInputStream* aStream,
PRUint32 aLineNumber,
const nsSubstring& aTitle,
const nsSubstring& aMedia,
nsICSSLoaderObserver* aObserver,
PRBool* aCompleted,
PRBool* aIsAlternate)
{
LOG(("CSSLoaderImpl::LoadInlineStyle"));
NS_PRECONDITION(aStream, "Must have a stream to parse!");
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;
nsCOMPtr<nsICSSStyleSheet> 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);
if (!data) {
sheet->SetComplete();
return NS_ERROR_OUT_OF_MEMORY;
}
// 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(aStream, 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;
}
NS_IMETHODIMP
CSSLoaderImpl::LoadStyleLink(nsIContent* aElement,
nsIURI* aURL,
const nsSubstring& aTitle,
const nsSubstring& aMedia,
PRBool aHasAlternateRel,
nsICSSLoaderObserver* aObserver,
PRBool* aIsAlternate)
{
LOG(("CSSLoaderImpl::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;
nsCOMPtr<nsICSSStyleSheet> 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);
if (state == eSheetComplete) {
LOG((" Sheet already complete: 0x%p",
static_cast<void*>(sheet.get())));
if (aObserver) {
rv = PostLoadEvent(aURL, sheet, aObserver, *aIsAlternate);
return rv;
}
return NS_OK;
}
nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement));
// Now we need to actually load it
SheetLoadData* data = new SheetLoadData(this, aTitle, aURL, sheet,
owningElement, *aIsAlternate,
aObserver, principal);
if (!data) {
sheet->SetComplete();
return NS_ERROR_OUT_OF_MEMORY;
}
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"));
nsURIAndPrincipalHashKey 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;
}
NS_IMETHODIMP
CSSLoaderImpl::LoadChildSheet(nsICSSStyleSheet* aParentSheet,
nsIURI* aURL,
nsMediaList* aMedia,
nsICSSImportRule* aParentRule)
{
LOG(("CSSLoaderImpl::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
nsCOMPtr<nsIDocument> owningDoc;
nsresult rv = aParentSheet->GetOwningDocument(*getter_AddRefs(owningDoc));
if (NS_SUCCEEDED(rv) && owningDoc) {
nsCOMPtr<nsIDOMStyleSheet> nextParentSheet(do_QueryInterface(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();
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
SheetLoadData* data = parentData;
while (data && data->mURI) {
PRBool equal;
if (NS_SUCCEEDED(data->mURI->Equals(aURL, &equal)) && equal) {
// Houston, we have a loop, blow off this child and pretend this never
// happened
LOG_ERROR((" @import cycle detected, dropping load"));
return NS_OK;
}
data = data->mParentData;
}
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 = do_QueryInterface(aParentSheet);
}
// Now that we know it's safe to load this (passes security check and not a
// loop) do so
nsCOMPtr<nsICSSStyleSheet> 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);
if (!data) {
sheet->SetComplete();
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(data);
PRBool 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;
}
NS_IMETHODIMP
CSSLoaderImpl::LoadSheetSync(nsIURI* aURL, PRBool aAllowUnsafeRules,
PRBool aUseSystemPrincipal,
nsICSSStyleSheet** aSheet)
{
LOG(("CSSLoaderImpl::LoadSheetSync"));
return InternalLoadNonDocumentSheet(aURL, aAllowUnsafeRules,
aUseSystemPrincipal, nsnull,
EmptyCString(), aSheet, nsnull);
}
NS_IMETHODIMP
CSSLoaderImpl::LoadSheet(nsIURI* aURL,
nsIPrincipal* aOriginPrincipal,
const nsCString& aCharset,
nsICSSLoaderObserver* aObserver,
nsICSSStyleSheet** aSheet)
{
LOG(("CSSLoaderImpl::LoadSheet(aURL, aObserver, aSheet) api call"));
NS_PRECONDITION(aSheet, "aSheet is null");
return InternalLoadNonDocumentSheet(aURL, PR_FALSE, PR_FALSE,
aOriginPrincipal, aCharset,
aSheet, aObserver);
}
NS_IMETHODIMP
CSSLoaderImpl::LoadSheet(nsIURI* aURL,
nsIPrincipal* aOriginPrincipal,
const nsCString& aCharset,
nsICSSLoaderObserver* aObserver)
{
LOG(("CSSLoaderImpl::LoadSheet(aURL, aObserver) api call"));
return InternalLoadNonDocumentSheet(aURL, PR_FALSE, PR_FALSE,
aOriginPrincipal, aCharset,
nsnull, aObserver);
}
nsresult
CSSLoaderImpl::InternalLoadNonDocumentSheet(nsIURI* aURL,
PRBool aAllowUnsafeRules,
PRBool aUseSystemPrincipal,
nsIPrincipal* aOriginPrincipal,
const nsCString& aCharset,
nsICSSStyleSheet** 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;
nsCOMPtr<nsICSSStyleSheet> sheet;
PRBool 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) {
rv = PostLoadEvent(aURL, sheet, aObserver, PR_FALSE);
}
if (aSheet) {
sheet.swap(*aSheet);
}
return rv;
}
SheetLoadData* data =
new SheetLoadData(this, aURL, sheet, syncLoad, aAllowUnsafeRules,
aUseSystemPrincipal, aCharset, aObserver,
aOriginPrincipal);
if (!data) {
sheet->SetComplete();
return NS_ERROR_OUT_OF_MEMORY;
}
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
CSSLoaderImpl::PostLoadEvent(nsIURI* aURI,
nsICSSStyleSheet* aSheet,
nsICSSLoaderObserver* aObserver,
PRBool aWasAlternate)
{
LOG(("nsCSSLoader::PostLoadEvent"));
NS_PRECONDITION(aSheet, "Must have sheet");
NS_PRECONDITION(aObserver, "Must have observer");
nsRefPtr<SheetLoadData> evt =
new SheetLoadData(this, EmptyString(), // title doesn't matter here
aURI,
aSheet,
nsnull, // owning element doesn't matter here
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;
}
return rv;
}
void
CSSLoaderImpl::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");
if (!aEvent->mIsCancelled) {
// SheetComplete will call Release(), so give it a reference to do
// that with.
NS_ADDREF(aEvent);
SheetComplete(aEvent, NS_OK);
}
mPostedEvents.RemoveElement(aEvent);
if (mDocument) {
mDocument->UnblockOnload(PR_TRUE);
}
}
nsresult NS_NewCSSLoader(nsIDocument* aDocument, nsICSSLoader** aLoader)
{
CSSLoaderImpl* it = new CSSLoaderImpl();
NS_ENSURE_TRUE(it, NS_ERROR_OUT_OF_MEMORY);
it->Init(aDocument);
return CallQueryInterface(it, aLoader);
}
nsresult NS_NewCSSLoader(nsICSSLoader** aLoader)
{
CSSLoaderImpl* it = new CSSLoaderImpl();
NS_ENSURE_TRUE(it, NS_ERROR_OUT_OF_MEMORY);
return CallQueryInterface(it, aLoader);
}
static PLDHashOperator
StopLoadingSheetCallback(nsURIAndPrincipalHashKey* 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<CSSLoaderImpl::LoadDataArray*>(aClosure)->AppendElement(aData);
return PL_DHASH_REMOVE;
}
NS_IMETHODIMP
CSSLoaderImpl::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;
}
struct StopLoadingSheetsByURIClosure {
StopLoadingSheetsByURIClosure(nsIURI* aURI,
CSSLoaderImpl::LoadDataArray& aArray) :
uri(aURI), array(aArray)
{}
nsIURI* uri;
CSSLoaderImpl::LoadDataArray& array;
};
static PLDHashOperator
StopLoadingSheetByURICallback(nsURIAndPrincipalHashKey* aKey,
SheetLoadData*& aData,
void* aClosure)
{
NS_PRECONDITION(aData, "Must have a data!");
NS_PRECONDITION(aClosure, "Must have a loader");
StopLoadingSheetsByURIClosure* closure =
static_cast<StopLoadingSheetsByURIClosure*>(aClosure);
PRBool equal;
if (NS_SUCCEEDED(aData->mURI->Equals(closure->uri, &equal)) &&
equal) {
aData->mIsLoading = PR_FALSE; // we will handle the removal right here
aData->mIsCancelled = PR_TRUE;
closure->array.AppendElement(aData);
return PL_DHASH_REMOVE;
}
return PL_DHASH_NEXT;
}
NS_IMETHODIMP
CSSLoaderImpl::StopLoadingSheet(nsIURI* aURL)
{
NS_ENSURE_TRUE(aURL, NS_ERROR_NULL_POINTER);
PRUint32 pendingCount =
mPendingDatas.IsInitialized() ? mPendingDatas.Count() : 0;
PRUint32 loadingCount =
mLoadingDatas.IsInitialized() ? mLoadingDatas.Count() : 0;
LoadDataArray arr(pendingCount + loadingCount + mPostedEvents.Length());
StopLoadingSheetsByURIClosure closure(aURL, arr);
if (pendingCount) {
mPendingDatas.Enumerate(StopLoadingSheetByURICallback, &closure);
}
if (loadingCount) {
mLoadingDatas.Enumerate(StopLoadingSheetByURICallback, &closure);
}
PRUint32 i;
for (i = 0; i < mPostedEvents.Length(); ++i) {
SheetLoadData* curData = mPostedEvents[i];
PRBool equal;
if (curData->mURI && NS_SUCCEEDED(curData->mURI->Equals(aURL, &equal)) &&
equal) {
curData->mIsCancelled = PR_TRUE;
if (arr.AppendElement(curData)) {
// SheetComplete() calls Release(), so give this an extra ref.
NS_ADDREF(curData);
}
#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;
}
NS_IMETHODIMP
CSSLoaderImpl::GetEnabled(PRBool *aEnabled)
{
NS_ENSURE_ARG_POINTER(aEnabled);
*aEnabled = mEnabled;
return NS_OK;
}
NS_IMETHODIMP
CSSLoaderImpl::SetEnabled(PRBool aEnabled)
{
mEnabled = aEnabled;
return NS_OK;
}
NS_IMETHODIMP_(PRBool)
CSSLoaderImpl::HasPendingLoads()
{
return
(mLoadingDatas.IsInitialized() && mLoadingDatas.Count() != 0) ||
(mPendingDatas.IsInitialized() && mPendingDatas.Count() != 0) ||
mPostedEvents.Length() != 0 ||
mDatasToNotifyOn != 0;
}
NS_IMETHODIMP
CSSLoaderImpl::AddObserver(nsICSSLoaderObserver* aObserver)
{
NS_PRECONDITION(aObserver, "Must have observer");
if (mObservers.AppendElementUnlessExists(aObserver)) {
return NS_OK;
}
return NS_ERROR_OUT_OF_MEMORY;
}
NS_IMETHODIMP_(void)
CSSLoaderImpl::RemoveObserver(nsICSSLoaderObserver* aObserver)
{
mObservers.RemoveElement(aObserver);
}
static PLDHashOperator
CollectLoadDatas(nsURIAndPrincipalHashKey *aKey,
SheetLoadData* &aData,
void* aClosure)
{
static_cast<CSSLoaderImpl::LoadDataArray*>(aClosure)->AppendElement(aData);
return PL_DHASH_REMOVE;
}
void
CSSLoaderImpl::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);
}
}