gecko-dev/layout/style/nsCSSStyleSheet.cpp
bzbarsky%mit.edu fe53f95e60 Make the CSSLoader correctly order stylesheets as they are dynamically added
and removed via the DOM.  Clean up the nsIDocument stylesheet accessors.  Clean
up nsIDocumentObserver stylesheet stuff a bit.  Make style sheets visible in
the CSSOM (though not completely accessible) from the moment the load is kicked
off.  Make us have sheet objects that can be manipulated via CSSOM even for
failed loads. Bug 107567, bug 47734, bug 57225, bug 178407.
r=sicking,  sr=peterv.
2002-12-03 05:48:14 +00:00

4634 lines
131 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Netscape 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/NPL/
*
* 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) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Pierre Phaneuf <pp@ludusdesign.com>
* Daniel Glazman <glazman@netscape.com>
*
*
* Alternatively, the contents of this file may be used under the terms of
* either 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 NPL, 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 NPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nscore.h"
#define PL_ARENA_CONST_ALIGN_MASK 7
#define NS_RULEHASH_ARENA_BLOCK_SIZE (256)
#include "plarena.h"
#include "nsICSSStyleSheet.h"
#include "nsIArena.h"
#include "nsCRT.h"
#include "nsIAtom.h"
#include "nsIURL.h"
#include "nsIServiceManager.h"
#include "nsISupportsArray.h"
#include "pldhash.h"
#include "nsHashtable.h"
#include "nsICSSPseudoComparator.h"
#include "nsICSSStyleRuleProcessor.h"
#include "nsICSSStyleRule.h"
#include "nsICSSNameSpaceRule.h"
#include "nsICSSMediaRule.h"
#include "nsIMediaList.h"
#include "nsIHTMLContent.h"
#include "nsIDocument.h"
#include "nsIHTMLContentContainer.h"
#include "nsIPresContext.h"
#include "nsIEventStateManager.h"
#include "nsHTMLAtoms.h"
#include "nsLayoutAtoms.h"
#include "nsIFrame.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsUnicharUtils.h"
#include "nsVoidArray.h"
#include "nsIUnicharInputStream.h"
#include "nsIDOMHTMLAnchorElement.h"
#include "nsIDOMHTMLLinkElement.h"
#include "nsIDOMHTMLAreaElement.h"
#include "nsIDOMHTMLInputElement.h"
#include "nsIDOMHTMLOptionElement.h"
#include "nsIDOMStyleSheetList.h"
#include "nsIDOMCSSStyleSheet.h"
#include "nsIDOMCSSStyleRule.h"
#include "nsIDOMCSSImportRule.h"
#include "nsIDOMCSSRuleList.h"
#include "nsIDOMMediaList.h"
#include "nsIDOMNode.h"
#include "nsDOMError.h"
#include "nsIPresShell.h"
#include "nsICSSParser.h"
#include "nsICSSLoader.h"
#include "nsICSSLoaderObserver.h"
#include "nsRuleWalker.h"
#include "nsCSSPseudoClasses.h"
#include "nsINameSpaceManager.h"
#include "nsINameSpace.h"
#include "nsITextContent.h"
#include "prlog.h"
#include "nsCOMPtr.h"
#include "nsIStyleSet.h"
#include "nsISizeOfHandler.h"
#include "nsStyleUtil.h"
#include "nsQuickSort.h"
#ifdef MOZ_XUL
#include "nsIXULContent.h"
#endif
#include "nsContentUtils.h"
#include "nsIJSContextStack.h"
#include "nsIScriptSecurityManager.h"
// An |AtomKey| is to be used for storage in the hashtable, and a
// |DependentAtomKey| should be used on the stack to avoid the performance
// cost of refcounting an atom that one is assured to own for the lifetime
// of the key.
class AtomKey_base : public nsHashKey {
public:
virtual PRUint32 HashCode(void) const;
virtual PRBool Equals(const nsHashKey *aKey) const;
nsIAtom* mAtom;
};
class AtomKey : public AtomKey_base {
public:
AtomKey(nsIAtom* aAtom);
AtomKey(const AtomKey_base& aKey);
virtual ~AtomKey();
virtual nsHashKey *Clone(void) const;
};
class DependentAtomKey : public AtomKey_base {
public:
DependentAtomKey(nsIAtom* aAtom)
{
mAtom = aAtom;
}
DependentAtomKey(const DependentAtomKey& aKey);
virtual ~DependentAtomKey(void);
virtual nsHashKey *Clone(void) const;
};
PRUint32 AtomKey_base::HashCode(void) const
{
return NS_PTR_TO_INT32(mAtom);
}
PRBool AtomKey_base::Equals(const nsHashKey* aKey) const
{
return NS_STATIC_CAST(const AtomKey_base*, aKey)->mAtom == mAtom;
}
AtomKey::AtomKey(nsIAtom* aAtom)
{
mAtom = aAtom;
NS_ADDREF(mAtom);
}
AtomKey::AtomKey(const AtomKey_base& aKey)
{
mAtom = aKey.mAtom;
NS_ADDREF(mAtom);
}
AtomKey::~AtomKey()
{
NS_RELEASE(mAtom);
}
nsHashKey* AtomKey::Clone(void) const
{
return new AtomKey(*this);
}
DependentAtomKey::DependentAtomKey(const DependentAtomKey& aKey)
{
NS_NOTREACHED("Should never clone to a dependent atom key.");
mAtom = aKey.mAtom;
}
DependentAtomKey::~DependentAtomKey()
{
}
nsHashKey* DependentAtomKey::Clone(void) const
{
return new AtomKey(*this); // return a non-dependent key
}
struct RuleValue {
RuleValue(nsICSSStyleRule* aRule, PRInt32 aIndex, RuleValue *aNext)
: mRule(aRule), mIndex(aIndex), mNext(aNext) {}
// CAUTION: ~RuleValue will never get called as RuleValues are arena
// allocated and arena cleanup will take care of deleting memory.
// Add code to RuleHash::~RuleHash to get it to call the destructor
// if any more cleanup needs to happen.
~RuleValue(void)
{
// Rule values are arena allocated. No need for any deletion.
}
// Placement new to arena allocate the RuleValues
void *operator new(size_t aSize, PLArenaPool &aArena) CPP_THROW_NEW {
void *mem;
PL_ARENA_ALLOCATE(mem, &aArena, aSize);
return mem;
}
nsICSSStyleRule* mRule;
PRInt32 mIndex; // High index means low weight/order.
RuleValue* mNext;
};
// ------------------------------
// Rule hash table
//
// Uses any of the sets of ops below.
struct RuleHashTableEntry : public PLDHashEntryHdr {
RuleValue *mRules; // linked list of |RuleValue|, null-terminated
};
PR_STATIC_CALLBACK(PLDHashNumber)
RuleHash_CIHashKey(PLDHashTable *table, const void *key)
{
nsIAtom *atom = NS_CONST_CAST(nsIAtom*, NS_STATIC_CAST(const nsIAtom*, key));
nsAutoString str;
atom->ToString(str);
ToUpperCase(str);
return HashString(str);
}
PR_STATIC_CALLBACK(PRBool)
RuleHash_CIMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr,
const void *key)
{
nsIAtom *match_atom = NS_CONST_CAST(nsIAtom*, NS_STATIC_CAST(const nsIAtom*,
key));
// Use the |getKey| callback to avoid code duplication.
// XXX Ugh! Why does |getKey| have different |const|-ness?
nsIAtom *entry_atom = NS_CONST_CAST(nsIAtom*, NS_STATIC_CAST(const nsIAtom*,
table->ops->getKey(table, NS_CONST_CAST(PLDHashEntryHdr*, hdr))));
// Check for case-sensitive match first.
if (match_atom == entry_atom)
return PR_TRUE;
const PRUnichar *match_str, *entry_str;
match_atom->GetUnicode(&match_str);
entry_atom->GetUnicode(&entry_str);
return nsDependentString(match_str).Equals(nsDependentString(entry_str),
nsCaseInsensitiveStringComparator());
}
PR_STATIC_CALLBACK(PRBool)
RuleHash_CSMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr,
const void *key)
{
nsIAtom *match_atom = NS_CONST_CAST(nsIAtom*, NS_STATIC_CAST(const nsIAtom*,
key));
// Use the |getKey| callback to avoid code duplication.
// XXX Ugh! Why does |getKey| have different |const|-ness?
nsIAtom *entry_atom = NS_CONST_CAST(nsIAtom*, NS_STATIC_CAST(const nsIAtom*,
table->ops->getKey(table, NS_CONST_CAST(PLDHashEntryHdr*, hdr))));
return match_atom == entry_atom;
}
PR_STATIC_CALLBACK(const void*)
RuleHash_TagTable_GetKey(PLDHashTable *table, PLDHashEntryHdr *hdr)
{
RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, hdr);
return entry->mRules->mRule->FirstSelector()->mTag;
}
PR_STATIC_CALLBACK(const void*)
RuleHash_ClassTable_GetKey(PLDHashTable *table, PLDHashEntryHdr *hdr)
{
RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, hdr);
return entry->mRules->mRule->FirstSelector()->mClassList->mAtom;
}
PR_STATIC_CALLBACK(const void*)
RuleHash_IdTable_GetKey(PLDHashTable *table, PLDHashEntryHdr *hdr)
{
RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, hdr);
return entry->mRules->mRule->FirstSelector()->mIDList->mAtom;
}
PR_STATIC_CALLBACK(const void*)
RuleHash_NameSpaceTable_GetKey(PLDHashTable *table, PLDHashEntryHdr *hdr)
{
RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, hdr);
return NS_INT32_TO_PTR(entry->mRules->mRule->FirstSelector()->mNameSpace);
}
PR_STATIC_CALLBACK(PLDHashNumber)
RuleHash_NameSpaceTable_HashKey(PLDHashTable *table, const void *key)
{
return NS_PTR_TO_INT32(key);
}
PR_STATIC_CALLBACK(PRBool)
RuleHash_NameSpaceTable_MatchEntry(PLDHashTable *table,
const PLDHashEntryHdr *hdr,
const void *key)
{
const RuleHashTableEntry *entry =
NS_STATIC_CAST(const RuleHashTableEntry*, hdr);
return NS_PTR_TO_INT32(key) ==
entry->mRules->mRule->FirstSelector()->mNameSpace;
}
static PLDHashTableOps RuleHash_TagTable_Ops = {
PL_DHashAllocTable,
PL_DHashFreeTable,
RuleHash_TagTable_GetKey,
PL_DHashVoidPtrKeyStub,
RuleHash_CSMatchEntry,
PL_DHashMoveEntryStub,
PL_DHashClearEntryStub,
PL_DHashFinalizeStub,
NULL
};
// Case-sensitive ops.
static PLDHashTableOps RuleHash_ClassTable_CSOps = {
PL_DHashAllocTable,
PL_DHashFreeTable,
RuleHash_ClassTable_GetKey,
PL_DHashVoidPtrKeyStub,
RuleHash_CSMatchEntry,
PL_DHashMoveEntryStub,
PL_DHashClearEntryStub,
PL_DHashFinalizeStub,
NULL
};
// Case-insensitive ops.
static PLDHashTableOps RuleHash_ClassTable_CIOps = {
PL_DHashAllocTable,
PL_DHashFreeTable,
RuleHash_ClassTable_GetKey,
RuleHash_CIHashKey,
RuleHash_CIMatchEntry,
PL_DHashMoveEntryStub,
PL_DHashClearEntryStub,
PL_DHashFinalizeStub,
NULL
};
// Case-sensitive ops.
static PLDHashTableOps RuleHash_IdTable_CSOps = {
PL_DHashAllocTable,
PL_DHashFreeTable,
RuleHash_IdTable_GetKey,
PL_DHashVoidPtrKeyStub,
RuleHash_CSMatchEntry,
PL_DHashMoveEntryStub,
PL_DHashClearEntryStub,
PL_DHashFinalizeStub,
NULL
};
// Case-insensitive ops.
static PLDHashTableOps RuleHash_IdTable_CIOps = {
PL_DHashAllocTable,
PL_DHashFreeTable,
RuleHash_IdTable_GetKey,
RuleHash_CIHashKey,
RuleHash_CIMatchEntry,
PL_DHashMoveEntryStub,
PL_DHashClearEntryStub,
PL_DHashFinalizeStub,
NULL
};
static PLDHashTableOps RuleHash_NameSpaceTable_Ops = {
PL_DHashAllocTable,
PL_DHashFreeTable,
RuleHash_NameSpaceTable_GetKey,
RuleHash_NameSpaceTable_HashKey,
RuleHash_NameSpaceTable_MatchEntry,
PL_DHashMoveEntryStub,
PL_DHashClearEntryStub,
PL_DHashFinalizeStub,
NULL
};
#undef RULE_HASH_STATS
#undef PRINT_UNIVERSAL_RULES
#ifdef DEBUG_dbaron
#define RULE_HASH_STATS
#define PRINT_UNIVERSAL_RULES
#endif
#ifdef RULE_HASH_STATS
#define RULE_HASH_STAT_INCREMENT(var_) PR_BEGIN_MACRO ++(var_); PR_END_MACRO
#else
#define RULE_HASH_STAT_INCREMENT(var_) PR_BEGIN_MACRO PR_END_MACRO
#endif
// Enumerator callback function.
typedef void (*RuleEnumFunc)(nsICSSStyleRule* aRule, void *aData);
class RuleHash {
public:
RuleHash(PRBool aQuirksMode);
~RuleHash();
void PrependRule(nsICSSStyleRule* aRule);
void EnumerateAllRules(PRInt32 aNameSpace, nsIAtom* aTag, nsIAtom* aID,
const nsVoidArray& aClassList,
RuleEnumFunc aFunc, void* aData);
void EnumerateTagRules(nsIAtom* aTag,
RuleEnumFunc aFunc, void* aData);
protected:
void PrependRuleToTable(PLDHashTable* aTable, const void* aKey,
nsICSSStyleRule* aRule);
void PrependUniversalRule(nsICSSStyleRule* aRule);
// All rule values in these hashtables are arena allocated
PRInt32 mRuleCount;
PLDHashTable mIdTable;
PLDHashTable mClassTable;
PLDHashTable mTagTable;
PLDHashTable mNameSpaceTable;
RuleValue *mUniversalRules;
RuleValue** mEnumList;
PRInt32 mEnumListSize;
PLArenaPool mArena;
#ifdef RULE_HASH_STATS
PRUint32 mUniversalSelectors;
PRUint32 mNameSpaceSelectors;
PRUint32 mTagSelectors;
PRUint32 mClassSelectors;
PRUint32 mIdSelectors;
PRUint32 mElementsMatched;
PRUint32 mPseudosMatched;
PRUint32 mElementUniversalCalls;
PRUint32 mElementNameSpaceCalls;
PRUint32 mElementTagCalls;
PRUint32 mElementClassCalls;
PRUint32 mElementIdCalls;
PRUint32 mPseudoTagCalls;
#endif // RULE_HASH_STATS
};
RuleHash::RuleHash(PRBool aQuirksMode)
: mRuleCount(0),
mUniversalRules(nsnull),
mEnumList(nsnull), mEnumListSize(0)
#ifdef RULE_HASH_STATS
,
mUniversalSelectors(0),
mNameSpaceSelectors(0),
mTagSelectors(0),
mClassSelectors(0),
mIdSelectors(0),
mElementsMatched(0),
mPseudosMatched(0),
mElementUniversalCalls(0),
mElementNameSpaceCalls(0),
mElementTagCalls(0),
mElementClassCalls(0),
mElementIdCalls(0),
mPseudoTagCalls(0)
#endif
{
// Initialize our arena
PL_INIT_ARENA_POOL(&mArena, "RuleHashArena", NS_RULEHASH_ARENA_BLOCK_SIZE);
PL_DHashTableInit(&mTagTable, &RuleHash_TagTable_Ops, nsnull,
sizeof(RuleHashTableEntry), 64);
PL_DHashTableInit(&mIdTable,
aQuirksMode ? &RuleHash_IdTable_CIOps
: &RuleHash_IdTable_CSOps,
nsnull, sizeof(RuleHashTableEntry), 16);
PL_DHashTableInit(&mClassTable,
aQuirksMode ? &RuleHash_ClassTable_CIOps
: &RuleHash_ClassTable_CSOps,
nsnull, sizeof(RuleHashTableEntry), 16);
PL_DHashTableInit(&mNameSpaceTable, &RuleHash_NameSpaceTable_Ops, nsnull,
sizeof(RuleHashTableEntry), 16);
}
RuleHash::~RuleHash(void)
{
#ifdef RULE_HASH_STATS
printf(
"RuleHash(%p):\n"
" Selectors: Universal (%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n"
" Content Nodes: Elements(%u) Pseudo-Elements(%u)\n"
" Element Calls: Universal(%u) NameSpace(%u) Tag(%u) Class(%u) Id(%u)\n"
" Pseudo-Element Calls: Tag(%u)\n",
NS_STATIC_CAST(void*, this),
mUniversalSelectors, mNameSpaceSelectors, mTagSelectors,
mClassSelectors, mIdSelectors,
mElementsMatched,
mPseudosMatched,
mElementUniversalCalls, mElementNameSpaceCalls, mElementTagCalls,
mElementClassCalls, mElementIdCalls,
mPseudoTagCalls);
#ifdef PRINT_UNIVERSAL_RULES
{
RuleValue* value = mUniversalRules;
if (value) {
printf(" Universal rules:\n");
do {
nsAutoString selectorText;
PRUint32 lineNumber = value->mRule->GetLineNumber();
value->mRule->GetSourceSelectorText(selectorText);
printf(" line %d, %s\n",
lineNumber, NS_ConvertUCS2toUTF8(selectorText).get());
value = value->mNext;
} while (value);
}
}
#endif // PRINT_UNIVERSAL_RULES
#endif // RULE_HASH_STATS
// Rule Values are arena allocated no need to delete them. Their destructor
// isn't doing any cleanup. So we dont even bother to enumerate through
// the hash tables and call their destructors.
if (nsnull != mEnumList) {
delete [] mEnumList;
}
// delete arena for strings and small objects
PL_DHashTableFinish(&mIdTable);
PL_DHashTableFinish(&mClassTable);
PL_DHashTableFinish(&mTagTable);
PL_DHashTableFinish(&mNameSpaceTable);
PL_FinishArenaPool(&mArena);
}
void RuleHash::PrependRuleToTable(PLDHashTable* aTable,
const void* aKey, nsICSSStyleRule* aRule)
{
// Get a new or existing entry.
RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*,
PL_DHashTableOperate(aTable, aKey, PL_DHASH_ADD));
if (!entry)
return;
entry->mRules = new (mArena) RuleValue(aRule, mRuleCount++, entry->mRules);
}
void RuleHash::PrependUniversalRule(nsICSSStyleRule* aRule)
{
mUniversalRules =
new (mArena) RuleValue(aRule, mRuleCount++, mUniversalRules);
}
void RuleHash::PrependRule(nsICSSStyleRule* aRule)
{
nsCSSSelector* selector = aRule->FirstSelector();
if (nsnull != selector->mIDList) {
PrependRuleToTable(&mIdTable, selector->mIDList->mAtom, aRule);
RULE_HASH_STAT_INCREMENT(mIdSelectors);
}
else if (nsnull != selector->mClassList) {
PrependRuleToTable(&mClassTable, selector->mClassList->mAtom, aRule);
RULE_HASH_STAT_INCREMENT(mClassSelectors);
}
else if (nsnull != selector->mTag) {
PrependRuleToTable(&mTagTable, selector->mTag, aRule);
RULE_HASH_STAT_INCREMENT(mTagSelectors);
}
else if (kNameSpaceID_Unknown != selector->mNameSpace) {
PrependRuleToTable(&mNameSpaceTable,
NS_INT32_TO_PTR(selector->mNameSpace), aRule);
RULE_HASH_STAT_INCREMENT(mNameSpaceSelectors);
}
else { // universal tag selector
PrependUniversalRule(aRule);
RULE_HASH_STAT_INCREMENT(mUniversalSelectors);
}
}
// this should cover practically all cases so we don't need to reallocate
#define MIN_ENUM_LIST_SIZE 8
#ifdef RULE_HASH_STATS
#define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \
do { ++(var_); (list_) = (list_)->mNext; } while (list_)
#else
#define RULE_HASH_STAT_INCREMENT_LIST_COUNT(list_, var_) \
PR_BEGIN_MACRO PR_END_MACRO
#endif
void RuleHash::EnumerateAllRules(PRInt32 aNameSpace, nsIAtom* aTag,
nsIAtom* aID, const nsVoidArray& aClassList,
RuleEnumFunc aFunc, void* aData)
{
PRInt32 classCount = aClassList.Count();
// assume 1 universal, tag, id, and namespace, rather than wasting
// time counting
PRInt32 testCount = classCount + 4;
if (mEnumListSize < testCount) {
delete [] mEnumList;
mEnumListSize = PR_MAX(testCount, MIN_ENUM_LIST_SIZE);
mEnumList = new RuleValue*[mEnumListSize];
}
PRInt32 valueCount = 0;
RULE_HASH_STAT_INCREMENT(mElementsMatched);
{ // universal rules
RuleValue* value = mUniversalRules;
if (nsnull != value) {
mEnumList[valueCount++] = value;
RULE_HASH_STAT_INCREMENT_LIST_COUNT(value, mElementUniversalCalls);
}
}
// universal rules within the namespace
if (kNameSpaceID_Unknown != aNameSpace) {
RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*,
PL_DHashTableOperate(&mNameSpaceTable, NS_INT32_TO_PTR(aNameSpace),
PL_DHASH_LOOKUP));
if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
RuleValue *value = entry->mRules;
mEnumList[valueCount++] = value;
RULE_HASH_STAT_INCREMENT_LIST_COUNT(value, mElementNameSpaceCalls);
}
}
if (nsnull != aTag) {
RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*,
PL_DHashTableOperate(&mTagTable, aTag, PL_DHASH_LOOKUP));
if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
RuleValue *value = entry->mRules;
mEnumList[valueCount++] = value;
RULE_HASH_STAT_INCREMENT_LIST_COUNT(value, mElementTagCalls);
}
}
if (nsnull != aID) {
RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*,
PL_DHashTableOperate(&mIdTable, aID, PL_DHASH_LOOKUP));
if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
RuleValue *value = entry->mRules;
mEnumList[valueCount++] = value;
RULE_HASH_STAT_INCREMENT_LIST_COUNT(value, mElementIdCalls);
}
}
{ // extra scope to work around compiler bugs with |for| scoping.
for (PRInt32 index = 0; index < classCount; ++index) {
nsIAtom* classAtom = (nsIAtom*)aClassList.ElementAt(index);
RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*,
PL_DHashTableOperate(&mClassTable, classAtom, PL_DHASH_LOOKUP));
if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
RuleValue *value = entry->mRules;
mEnumList[valueCount++] = value;
RULE_HASH_STAT_INCREMENT_LIST_COUNT(value, mElementClassCalls);
}
}
}
NS_ASSERTION(valueCount <= testCount, "values exceeded list size");
if (valueCount > 0) {
// Merge the lists while there are still multiple lists to merge.
while (valueCount > 1) {
PRInt32 valueIndex = 0;
PRInt32 highestRuleIndex = mEnumList[valueIndex]->mIndex;
for (PRInt32 index = 1; index < valueCount; ++index) {
PRInt32 ruleIndex = mEnumList[index]->mIndex;
if (ruleIndex > highestRuleIndex) {
valueIndex = index;
highestRuleIndex = ruleIndex;
}
}
(*aFunc)(mEnumList[valueIndex]->mRule, aData);
RuleValue *next = mEnumList[valueIndex]->mNext;
mEnumList[valueIndex] = next ? next : mEnumList[--valueCount];
}
// Fast loop over single value.
RuleValue* value = mEnumList[0];
do {
(*aFunc)(value->mRule, aData);
value = value->mNext;
} while (value);
}
}
void RuleHash::EnumerateTagRules(nsIAtom* aTag, RuleEnumFunc aFunc, void* aData)
{
RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*,
PL_DHashTableOperate(&mTagTable, aTag, PL_DHASH_LOOKUP));
RULE_HASH_STAT_INCREMENT(mPseudosMatched);
if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
RuleValue *tagValue = entry->mRules;
do {
RULE_HASH_STAT_INCREMENT(mPseudoTagCalls);
(*aFunc)(tagValue->mRule, aData);
tagValue = tagValue->mNext;
} while (tagValue);
}
}
//--------------------------------
struct RuleCascadeData {
RuleCascadeData(nsIAtom *aMedium, PRBool aQuirksMode)
: mWeightedRules(nsnull),
mRuleHash(aQuirksMode),
mStateSelectors(),
mMedium(aMedium),
mNext(nsnull)
{
NS_NewISupportsArray(&mWeightedRules);
}
~RuleCascadeData(void)
{
NS_IF_RELEASE(mWeightedRules);
}
nsISupportsArray* mWeightedRules;
RuleHash mRuleHash;
nsVoidArray mStateSelectors;
nsCOMPtr<nsIAtom> mMedium;
RuleCascadeData* mNext; // for a different medium
};
// -------------------------------
// CSS Style Rule processor
//
class CSSRuleProcessor: public nsICSSStyleRuleProcessor {
public:
CSSRuleProcessor(void);
virtual ~CSSRuleProcessor(void);
NS_DECL_ISUPPORTS
public:
// nsICSSStyleRuleProcessor
NS_IMETHOD AppendStyleSheet(nsICSSStyleSheet* aStyleSheet);
NS_IMETHOD ClearRuleCascades(void);
// nsIStyleRuleProcessor
NS_IMETHOD RulesMatching(ElementRuleProcessorData* aData,
nsIAtom* aMedium);
NS_IMETHOD RulesMatching(PseudoRuleProcessorData* aData,
nsIAtom* aMedium);
NS_IMETHOD HasStateDependentStyle(StateRuleProcessorData* aData,
nsIAtom* aMedium,
PRBool* aResult);
#ifdef DEBUG
virtual void SizeOf(nsISizeOfHandler *aSizeofHandler, PRUint32 &aSize);
#endif
protected:
RuleCascadeData* GetRuleCascade(nsIPresContext* aPresContext, nsIAtom* aMedium);
static PRBool CascadeSheetRulesInto(nsISupports* aSheet, void* aData);
nsISupportsArray* mSheets;
RuleCascadeData* mRuleCascades;
};
// -------------------------------
// CSS Style Sheet Inner Data Container
//
class CSSStyleSheetInner {
public:
CSSStyleSheetInner(nsICSSStyleSheet* aParentSheet);
CSSStyleSheetInner(CSSStyleSheetInner& aCopy, nsICSSStyleSheet* aParentSheet);
virtual ~CSSStyleSheetInner(void);
virtual CSSStyleSheetInner* CloneFor(nsICSSStyleSheet* aParentSheet);
virtual void AddSheet(nsICSSStyleSheet* aParentSheet);
virtual void RemoveSheet(nsICSSStyleSheet* aParentSheet);
virtual void RebuildNameSpaces(void);
#ifdef DEBUG
virtual void SizeOf(nsISizeOfHandler *aSizeofHandler, PRUint32 &aSize);
#endif
nsAutoVoidArray mSheets;
nsIURI* mURL;
nsISupportsArray* mOrderedRules;
nsCOMPtr<nsINameSpace> mNameSpace;
PRInt32 mDefaultNameSpaceID;
nsHashtable mRelevantAttributes;
PRPackedBool mComplete;
};
// -------------------------------
// CSS Style Sheet
//
class CSSImportsCollectionImpl;
class CSSRuleListImpl;
class DOMMediaListImpl;
class CSSStyleSheetImpl : public nsICSSStyleSheet,
public nsIDOMCSSStyleSheet,
public nsICSSLoaderObserver
{
public:
CSSStyleSheetImpl();
NS_DECL_ISUPPORTS
// basic style sheet data
NS_IMETHOD Init(nsIURI* aURL);
NS_IMETHOD GetURL(nsIURI*& aURL) const;
NS_IMETHOD GetTitle(nsString& aTitle) const;
NS_IMETHOD SetTitle(const nsAString& aTitle);
NS_IMETHOD GetType(nsString& aType) const;
NS_IMETHOD GetMediumCount(PRInt32& aCount) const;
NS_IMETHOD GetMediumAt(PRInt32 aIndex, nsIAtom*& aMedium) const;
NS_IMETHOD_(PRBool) UseForMedium(nsIAtom* aMedium) const;
NS_IMETHOD AppendMedium(nsIAtom* aMedium);
NS_IMETHOD ClearMedia(void);
NS_IMETHOD DeleteRuleFromGroup(nsICSSGroupRule* aGroup, PRUint32 aIndex);
NS_IMETHOD InsertRuleIntoGroup(const nsAString& aRule, nsICSSGroupRule* aGroup, PRUint32 aIndex, PRUint32* _retval);
NS_IMETHOD GetApplicable(PRBool& aApplicable) const;
NS_IMETHOD SetEnabled(PRBool aEnabled);
NS_IMETHOD GetComplete(PRBool& aComplete) const;
NS_IMETHOD SetComplete();
// style sheet owner info
NS_IMETHOD GetParentSheet(nsIStyleSheet*& aParent) const; // may be null
NS_IMETHOD GetOwningDocument(nsIDocument*& aDocument) const;
NS_IMETHOD SetOwningDocument(nsIDocument* aDocument);
NS_IMETHOD SetOwningNode(nsIDOMNode* aOwningNode);
NS_IMETHOD SetOwnerRule(nsICSSImportRule* aOwnerRule);
NS_IMETHOD GetStyleRuleProcessor(nsIStyleRuleProcessor*& aProcessor,
nsIStyleRuleProcessor* aPrevProcessor);
NS_IMETHOD DropRuleProcessorReference(nsICSSStyleRuleProcessor* aProcessor);
NS_IMETHOD ContainsStyleSheet(nsIURI* aURL, PRBool& aContains, nsIStyleSheet** aTheChild=nsnull);
NS_IMETHOD AppendStyleSheet(nsICSSStyleSheet* aSheet);
NS_IMETHOD InsertStyleSheetAt(nsICSSStyleSheet* aSheet, PRInt32 aIndex);
// If changing the given attribute cannot affect style context, aAffects
// will be PR_FALSE on return.
NS_IMETHOD AttributeAffectsStyle(nsIAtom *aAttribute, nsIContent *aContent,
PRBool &aAffects);
// Find attributes in selector for rule, for use with AttributeAffectsStyle
NS_IMETHOD CheckRuleForAttributes(nsICSSRule* aRule);
// XXX do these belong here or are they generic?
NS_IMETHOD PrependStyleRule(nsICSSRule* aRule);
NS_IMETHOD AppendStyleRule(nsICSSRule* aRule);
NS_IMETHOD StyleRuleCount(PRInt32& aCount) const;
NS_IMETHOD GetStyleRuleAt(PRInt32 aIndex, nsICSSRule*& aRule) const;
NS_IMETHOD StyleSheetCount(PRInt32& aCount) const;
NS_IMETHOD GetStyleSheetAt(PRInt32 aIndex, nsICSSStyleSheet*& aSheet) const;
NS_IMETHOD GetNameSpace(nsINameSpace*& aNameSpace) const;
NS_IMETHOD SetDefaultNameSpaceID(PRInt32 aDefaultNameSpaceID);
NS_IMETHOD Clone(nsICSSStyleSheet*& aClone) const;
NS_IMETHOD IsModified(PRBool* aSheetModified) const;
NS_IMETHOD SetModified(PRBool aModified);
NS_IMETHOD StyleSheetLoaded(nsICSSStyleSheet*aSheet, PRBool aNotify);
nsresult EnsureUniqueInner(void);
#ifdef DEBUG
virtual void List(FILE* out = stdout, PRInt32 aIndent = 0) const;
virtual void SizeOf(nsISizeOfHandler *aSizeofHandler, PRUint32 &aSize);
#endif
// nsIDOMStyleSheet interface
NS_DECL_NSIDOMSTYLESHEET
// nsIDOMCSSStyleSheet interface
NS_DECL_NSIDOMCSSSTYLESHEET
private:
// These are not supported and are not implemented!
CSSStyleSheetImpl(const CSSStyleSheetImpl& aCopy);
CSSStyleSheetImpl& operator=(const CSSStyleSheetImpl& aCopy);
protected:
virtual ~CSSStyleSheetImpl();
void ClearRuleCascades(void);
nsresult WillDirty(void);
void DidDirty(void);
protected:
nsString mTitle;
DOMMediaListImpl* mMedia;
CSSStyleSheetImpl* mFirstChild;
CSSStyleSheetImpl* mNext;
nsICSSStyleSheet* mParent; // weak ref
nsICSSImportRule* mOwnerRule; // weak ref
CSSImportsCollectionImpl* mImportsCollection;
CSSRuleListImpl* mRuleCollection;
nsIDocument* mDocument;
nsIDOMNode* mOwningNode; // weak ref
PRPackedBool mDisabled;
PRPackedBool mDirty; // has been modified
CSSStyleSheetInner* mInner;
nsAutoVoidArray* mRuleProcessors;
friend class CSSRuleProcessor;
friend class DOMMediaListImpl;
};
// -------------------------------
// Style Rule List for the DOM
//
class CSSRuleListImpl : public nsIDOMCSSRuleList
{
public:
CSSRuleListImpl(CSSStyleSheetImpl *aStyleSheet);
NS_DECL_ISUPPORTS
// nsIDOMCSSRuleList interface
NS_IMETHOD GetLength(PRUint32* aLength);
NS_IMETHOD Item(PRUint32 aIndex, nsIDOMCSSRule** aReturn);
void DropReference() { mStyleSheet = nsnull; }
protected:
virtual ~CSSRuleListImpl();
CSSStyleSheetImpl* mStyleSheet;
public:
PRBool mRulesAccessed;
};
CSSRuleListImpl::CSSRuleListImpl(CSSStyleSheetImpl *aStyleSheet)
{
NS_INIT_ISUPPORTS();
// Not reference counted to avoid circular references.
// The style sheet will tell us when its going away.
mStyleSheet = aStyleSheet;
mRulesAccessed = PR_FALSE;
}
CSSRuleListImpl::~CSSRuleListImpl()
{
}
// QueryInterface implementation for CSSRuleList
NS_INTERFACE_MAP_BEGIN(CSSRuleListImpl)
NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRuleList)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(CSSRuleList)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(CSSRuleListImpl);
NS_IMPL_RELEASE(CSSRuleListImpl);
NS_IMETHODIMP
CSSRuleListImpl::GetLength(PRUint32* aLength)
{
if (nsnull != mStyleSheet) {
PRInt32 count;
mStyleSheet->StyleRuleCount(count);
*aLength = (PRUint32)count;
}
else {
*aLength = 0;
}
return NS_OK;
}
NS_IMETHODIMP
CSSRuleListImpl::Item(PRUint32 aIndex, nsIDOMCSSRule** aReturn)
{
nsresult result = NS_OK;
*aReturn = nsnull;
if (mStyleSheet) {
result = mStyleSheet->EnsureUniqueInner(); // needed to ensure rules have correct parent
if (NS_SUCCEEDED(result)) {
nsCOMPtr<nsICSSRule> rule;
result = mStyleSheet->GetStyleRuleAt(aIndex, *getter_AddRefs(rule));
if (rule) {
result = CallQueryInterface(rule, aReturn);
mRulesAccessed = PR_TRUE; // signal to never share rules again
} else if (result == NS_ERROR_ILLEGAL_VALUE) {
result = NS_OK; // per spec: "Return Value ... null if ... not a valid index."
}
}
}
return result;
}
class DOMMediaListImpl : public nsIDOMMediaList,
public nsIMediaList
{
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMMEDIALIST
NS_FORWARD_NSISUPPORTSARRAY(mArray->)
NS_FORWARD_NSICOLLECTION(mArray->);
NS_FORWARD_NSISERIALIZABLE(mArray->); // XXXbe temporary
NS_IMETHOD_(PRBool) operator==(const nsISupportsArray& other) {
return PR_FALSE;
}
NS_IMETHOD_(nsISupports*) operator[](PRUint32 aIndex) {
return mArray->ElementAt(aIndex);
}
// nsIMediaList methods
NS_DECL_NSIMEDIALIST
DOMMediaListImpl(nsISupportsArray *aArray, CSSStyleSheetImpl *aStyleSheet);
virtual ~DOMMediaListImpl();
private:
nsresult BeginMediaChange(void);
nsresult EndMediaChange(void);
nsresult Delete(const nsAString & aOldMedium);
nsresult Append(const nsAString & aOldMedium);
nsCOMPtr<nsISupportsArray> mArray;
// not refcounted; sheet will let us know when it goes away
// mStyleSheet is the sheet that needs to be dirtied when this medialist
// changes
CSSStyleSheetImpl* mStyleSheet;
};
// QueryInterface implementation for DOMMediaListImpl
NS_INTERFACE_MAP_BEGIN(DOMMediaListImpl)
NS_INTERFACE_MAP_ENTRY(nsIDOMMediaList)
NS_INTERFACE_MAP_ENTRY(nsIMediaList)
NS_INTERFACE_MAP_ENTRY(nsISupportsArray)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMediaList)
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(MediaList)
NS_INTERFACE_MAP_ENTRY(nsISerializable)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(DOMMediaListImpl);
NS_IMPL_RELEASE(DOMMediaListImpl);
DOMMediaListImpl::DOMMediaListImpl(nsISupportsArray *aArray,
CSSStyleSheetImpl *aStyleSheet)
: mArray(aArray), mStyleSheet(aStyleSheet)
{
NS_INIT_ISUPPORTS();
NS_ABORT_IF_FALSE(mArray, "This can't be used without an array!!");
}
DOMMediaListImpl::~DOMMediaListImpl()
{
}
nsresult
NS_NewMediaList(nsIMediaList** aInstancePtrResult) {
return NS_NewMediaList(NS_LITERAL_STRING(""), aInstancePtrResult);
}
nsresult
NS_NewMediaList(const nsAString& aMediaText, nsIMediaList** aInstancePtrResult) {
nsresult rv;
NS_ASSERTION(aInstancePtrResult, "Null out param.");
nsCOMPtr<nsISupportsArray> array;
rv = NS_NewISupportsArray(getter_AddRefs(array));
if (NS_FAILED(rv)) {
return NS_ERROR_OUT_OF_MEMORY;
}
DOMMediaListImpl* medialist = new DOMMediaListImpl(array, nsnull);
*aInstancePtrResult = medialist;
NS_ENSURE_TRUE(medialist, NS_ERROR_OUT_OF_MEMORY);
NS_ADDREF(*aInstancePtrResult);
rv = medialist->SetMediaText(aMediaText);
if (NS_FAILED(rv)) {
NS_RELEASE(*aInstancePtrResult);
*aInstancePtrResult = nsnull;
}
return rv;
}
nsresult NS_NewMediaList(nsISupportsArray* aArray, nsICSSStyleSheet* aSheet, nsIMediaList** aInstancePtrResult)
{
NS_ASSERTION(aInstancePtrResult, "Null out param.");
DOMMediaListImpl* medialist = new DOMMediaListImpl(aArray, NS_STATIC_CAST(CSSStyleSheetImpl*, aSheet));
*aInstancePtrResult = medialist;
NS_ENSURE_TRUE(medialist, NS_ERROR_OUT_OF_MEMORY);
NS_ADDREF(*aInstancePtrResult);
return NS_OK;
}
NS_IMETHODIMP
DOMMediaListImpl::GetText(nsAString& aMediaText)
{
aMediaText.Truncate();
PRUint32 cnt;
nsresult rv = Count(&cnt);
if (NS_FAILED(rv)) return rv;
PRInt32 count = cnt, index = 0;
while (index < count) {
nsCOMPtr<nsIAtom> medium;
QueryElementAt(index++, NS_GET_IID(nsIAtom), getter_AddRefs(medium));
NS_ENSURE_TRUE(medium, NS_ERROR_FAILURE);
const PRUnichar *buffer;
medium->GetUnicode(&buffer);
aMediaText.Append(buffer);
if (index < count) {
aMediaText.Append(NS_LITERAL_STRING(", "));
}
}
return NS_OK;
}
NS_IMETHODIMP
DOMMediaListImpl::SetText(const nsAString& aMediaText)
{
nsresult rv = Clear();
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString buf(aMediaText);
PRInt32 n = buf.FindChar(',');
do {
if (n < 0)
n = buf.Length();
nsAutoString tmp;
buf.Left(tmp, n);
tmp.CompressWhitespace();
if (!tmp.IsEmpty()) {
rv = Append(tmp);
NS_ENSURE_SUCCESS(rv, rv);
}
buf.Cut(0, n + 1);
n = buf.FindChar(',');
} while (!buf.IsEmpty());
return rv;
}
/*
* aMatch is true when we contain the desired medium or contain the
* "all" medium or contain no media at all, which is the same as
* containing "all"
*/
NS_IMETHODIMP
DOMMediaListImpl::MatchesMedium(nsIAtom* aMedium, PRBool* aMatch)
{
NS_ENSURE_ARG_POINTER(aMatch);
*aMatch = PR_FALSE;
*aMatch = (-1 != IndexOf(aMedium)) ||
(-1 != IndexOf(nsLayoutAtoms::all));
if (*aMatch)
return NS_OK;
PRUint32 count;
nsresult rv = Count(&count);
if(NS_FAILED(rv))
return rv;
*aMatch = (count == 0);
return NS_OK;
}
NS_IMETHODIMP
DOMMediaListImpl::DropReference(void)
{
mStyleSheet = nsnull;
return NS_OK;
}
NS_IMETHODIMP
DOMMediaListImpl::GetMediaText(nsAString& aMediaText)
{
return GetText(aMediaText);
}
NS_IMETHODIMP
DOMMediaListImpl::SetMediaText(const nsAString& aMediaText)
{
nsresult rv;
rv = BeginMediaChange();
if (NS_FAILED(rv))
return rv;
rv = SetText(aMediaText);
if (NS_FAILED(rv))
return rv;
rv = EndMediaChange();
return rv;
}
NS_IMETHODIMP
DOMMediaListImpl::GetLength(PRUint32* aLength)
{
NS_ENSURE_ARG_POINTER(aLength);
PRUint32 cnt;
nsresult rv = Count(&cnt);
if (NS_FAILED(rv)) return rv;
*aLength = cnt;
return NS_OK;
}
NS_IMETHODIMP
DOMMediaListImpl::Item(PRUint32 aIndex, nsAString& aReturn)
{
nsCOMPtr<nsISupports> tmp(dont_AddRef(ElementAt(aIndex)));
if (tmp) {
nsCOMPtr<nsIAtom> medium(do_QueryInterface(tmp));
NS_ENSURE_TRUE(medium, NS_ERROR_FAILURE);
const PRUnichar *buffer;
medium->GetUnicode(&buffer);
aReturn.Assign(buffer);
} else {
aReturn.Truncate();
}
return NS_OK;
}
NS_IMETHODIMP
DOMMediaListImpl::DeleteMedium(const nsAString& aOldMedium)
{
nsresult rv;
rv = BeginMediaChange();
if (NS_FAILED(rv))
return rv;
rv = Delete(aOldMedium);
if (NS_FAILED(rv))
return rv;
rv = EndMediaChange();
return rv;
}
NS_IMETHODIMP
DOMMediaListImpl::AppendMedium(const nsAString& aNewMedium)
{
nsresult rv;
rv = BeginMediaChange();
if (NS_FAILED(rv))
return rv;
rv = Append(aNewMedium);
if (NS_FAILED(rv))
return rv;
rv = EndMediaChange();
return rv;
}
nsresult
DOMMediaListImpl::Delete(const nsAString& aOldMedium)
{
if (aOldMedium.IsEmpty())
return NS_ERROR_DOM_NOT_FOUND_ERR;
nsCOMPtr<nsIAtom> old(dont_AddRef(NS_NewAtom(aOldMedium)));
NS_ENSURE_TRUE(old, NS_ERROR_OUT_OF_MEMORY);
PRInt32 indx = IndexOf(old);
if (indx < 0) {
return NS_ERROR_DOM_NOT_FOUND_ERR;
}
RemoveElementAt(indx);
return NS_OK;
}
nsresult
DOMMediaListImpl::Append(const nsAString& aNewMedium)
{
if (aNewMedium.IsEmpty())
return NS_ERROR_DOM_NOT_FOUND_ERR;
nsCOMPtr<nsIAtom> media(dont_AddRef(NS_NewAtom(aNewMedium)));
NS_ENSURE_TRUE(media, NS_ERROR_OUT_OF_MEMORY);
PRInt32 indx = IndexOf(media);
if (indx >= 0) {
RemoveElementAt(indx);
}
AppendElement(media);
return NS_OK;
}
nsresult
DOMMediaListImpl::BeginMediaChange(void)
{
nsresult rv;
nsCOMPtr<nsIDocument> doc;
if (mStyleSheet) {
rv = mStyleSheet->GetOwningDocument(*getter_AddRefs(doc));
NS_ENSURE_SUCCESS(rv, rv);
rv = doc->BeginUpdate();
NS_ENSURE_SUCCESS(rv, rv);
rv = mStyleSheet->WillDirty();
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
DOMMediaListImpl::EndMediaChange(void)
{
nsresult rv;
nsCOMPtr<nsIDocument> doc;
if (mStyleSheet) {
mStyleSheet->DidDirty();
rv = mStyleSheet->GetOwningDocument(*getter_AddRefs(doc));
NS_ENSURE_SUCCESS(rv, rv);
rv = doc->StyleRuleChanged(mStyleSheet, nsnull, NS_STYLE_HINT_RECONSTRUCT_ALL);
NS_ENSURE_SUCCESS(rv, rv);
rv = doc->EndUpdate();
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
// -------------------------------
// Imports Collection for the DOM
//
class CSSImportsCollectionImpl : public nsIDOMStyleSheetList
{
public:
CSSImportsCollectionImpl(nsICSSStyleSheet *aStyleSheet);
NS_DECL_ISUPPORTS
// nsIDOMCSSStyleSheetList interface
NS_IMETHOD GetLength(PRUint32* aLength);
NS_IMETHOD Item(PRUint32 aIndex, nsIDOMStyleSheet** aReturn);
void DropReference() { mStyleSheet = nsnull; }
protected:
virtual ~CSSImportsCollectionImpl();
nsICSSStyleSheet* mStyleSheet;
};
CSSImportsCollectionImpl::CSSImportsCollectionImpl(nsICSSStyleSheet *aStyleSheet)
{
NS_INIT_ISUPPORTS();
// Not reference counted to avoid circular references.
// The style sheet will tell us when its going away.
mStyleSheet = aStyleSheet;
}
CSSImportsCollectionImpl::~CSSImportsCollectionImpl()
{
}
// QueryInterface implementation for CSSImportsCollectionImpl
NS_INTERFACE_MAP_BEGIN(CSSImportsCollectionImpl)
NS_INTERFACE_MAP_ENTRY(nsIDOMStyleSheetList)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(StyleSheetList)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(CSSImportsCollectionImpl);
NS_IMPL_RELEASE(CSSImportsCollectionImpl);
NS_IMETHODIMP
CSSImportsCollectionImpl::GetLength(PRUint32* aLength)
{
if (nsnull != mStyleSheet) {
PRInt32 count;
mStyleSheet->StyleSheetCount(count);
*aLength = (PRUint32)count;
}
else {
*aLength = 0;
}
return NS_OK;
}
NS_IMETHODIMP
CSSImportsCollectionImpl::Item(PRUint32 aIndex, nsIDOMStyleSheet** aReturn)
{
nsresult result = NS_OK;
*aReturn = nsnull;
if (nsnull != mStyleSheet) {
nsICSSStyleSheet *sheet;
result = mStyleSheet->GetStyleSheetAt(aIndex, sheet);
if (NS_OK == result) {
result = sheet->QueryInterface(NS_GET_IID(nsIDOMStyleSheet), (void **)aReturn);
}
NS_RELEASE(sheet);
}
return result;
}
// -------------------------------
// CSS Style Sheet Inner Data Container
//
static PRBool SetStyleSheetReference(nsISupports* aElement, void* aSheet)
{
nsICSSRule* rule = (nsICSSRule*)aElement;
if (nsnull != rule) {
rule->SetStyleSheet((nsICSSStyleSheet*)aSheet);
}
return PR_TRUE;
}
CSSStyleSheetInner::CSSStyleSheetInner(nsICSSStyleSheet* aParentSheet)
: mSheets(),
mURL(nsnull),
mOrderedRules(nsnull),
mNameSpace(nsnull),
mDefaultNameSpaceID(kNameSpaceID_None),
mRelevantAttributes(),
mComplete(PR_FALSE)
{
MOZ_COUNT_CTOR(CSSStyleSheetInner);
mSheets.AppendElement(aParentSheet);
}
static PRBool
CloneRuleInto(nsISupports* aRule, void* aArray)
{
nsICSSRule* rule = (nsICSSRule*)aRule;
nsICSSRule* clone = nsnull;
rule->Clone(clone);
if (clone) {
nsISupportsArray* array = (nsISupportsArray*)aArray;
array->AppendElement(clone);
NS_RELEASE(clone);
}
return PR_TRUE;
}
static PRBool PR_CALLBACK
CopyRelevantAttributes(nsHashKey *aAttrKey, void *aAtom, void *aTable)
{
nsHashtable *table = NS_STATIC_CAST(nsHashtable *, aTable);
AtomKey *key = NS_STATIC_CAST(AtomKey *, aAttrKey);
table->Put(key, key->mAtom);
// we don't need to addref the atom here, because we're also copying the
// rules when we clone, and that will add a ref for us
return PR_TRUE;
}
CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheetInner& aCopy,
nsICSSStyleSheet* aParentSheet)
: mSheets(),
mURL(aCopy.mURL),
mNameSpace(nsnull),
mDefaultNameSpaceID(aCopy.mDefaultNameSpaceID),
mRelevantAttributes(),
mComplete(aCopy.mComplete)
{
MOZ_COUNT_CTOR(CSSStyleSheetInner);
mSheets.AppendElement(aParentSheet);
NS_IF_ADDREF(mURL);
if (aCopy.mOrderedRules) {
NS_NewISupportsArray(&mOrderedRules);
if (mOrderedRules) {
aCopy.mOrderedRules->EnumerateForwards(CloneRuleInto, mOrderedRules);
mOrderedRules->EnumerateForwards(SetStyleSheetReference, aParentSheet);
}
}
else {
mOrderedRules = nsnull;
}
aCopy.mRelevantAttributes.Enumerate(CopyRelevantAttributes,
&mRelevantAttributes);
RebuildNameSpaces();
}
CSSStyleSheetInner::~CSSStyleSheetInner(void)
{
MOZ_COUNT_DTOR(CSSStyleSheetInner);
NS_IF_RELEASE(mURL);
if (mOrderedRules) {
mOrderedRules->EnumerateForwards(SetStyleSheetReference, nsnull);
NS_RELEASE(mOrderedRules);
}
}
CSSStyleSheetInner*
CSSStyleSheetInner::CloneFor(nsICSSStyleSheet* aParentSheet)
{
return new CSSStyleSheetInner(*this, aParentSheet);
}
void
CSSStyleSheetInner::AddSheet(nsICSSStyleSheet* aParentSheet)
{
mSheets.AppendElement(aParentSheet);
}
void
CSSStyleSheetInner::RemoveSheet(nsICSSStyleSheet* aParentSheet)
{
if (1 == mSheets.Count()) {
NS_ASSERTION(aParentSheet == (nsICSSStyleSheet*)mSheets.ElementAt(0), "bad parent");
delete this;
return;
}
if (aParentSheet == (nsICSSStyleSheet*)mSheets.ElementAt(0)) {
mSheets.RemoveElementAt(0);
NS_ASSERTION(mSheets.Count(), "no parents");
if (mOrderedRules) {
mOrderedRules->EnumerateForwards(SetStyleSheetReference,
(nsICSSStyleSheet*)mSheets.ElementAt(0));
}
}
else {
mSheets.RemoveElement(aParentSheet);
}
}
static PRBool
CreateNameSpace(nsISupports* aRule, void* aNameSpacePtr)
{
nsICSSRule* rule = (nsICSSRule*)aRule;
PRInt32 type = nsICSSRule::UNKNOWN_RULE;
rule->GetType(type);
if (nsICSSRule::NAMESPACE_RULE == type) {
nsICSSNameSpaceRule* nameSpaceRule = (nsICSSNameSpaceRule*)rule;
nsINameSpace** nameSpacePtr = (nsINameSpace**)aNameSpacePtr;
nsINameSpace* lastNameSpace = *nameSpacePtr;
nsINameSpace* newNameSpace;
nsIAtom* prefix = nsnull;
nsAutoString urlSpec;
nameSpaceRule->GetPrefix(prefix);
nameSpaceRule->GetURLSpec(urlSpec);
lastNameSpace->CreateChildNameSpace(prefix, urlSpec, newNameSpace);
NS_IF_RELEASE(prefix);
if (newNameSpace) {
NS_RELEASE(lastNameSpace);
(*nameSpacePtr) = newNameSpace; // takes ref
}
return PR_TRUE;
}
// stop if not namespace, import or charset because namespace can't follow anything else
return (((nsICSSRule::CHARSET_RULE == type) ||
(nsICSSRule::IMPORT_RULE)) ? PR_TRUE : PR_FALSE);
}
void
CSSStyleSheetInner::RebuildNameSpaces(void)
{
nsContentUtils::GetNSManagerWeakRef()->CreateRootNameSpace(*getter_AddRefs(mNameSpace));
if (kNameSpaceID_Unknown != mDefaultNameSpaceID) {
nsCOMPtr<nsINameSpace> defaultNameSpace;
mNameSpace->CreateChildNameSpace(nsnull, mDefaultNameSpaceID,
*getter_AddRefs(defaultNameSpace));
if (defaultNameSpace) {
mNameSpace = defaultNameSpace;
}
}
if (mOrderedRules) {
mOrderedRules->EnumerateForwards(CreateNameSpace, address_of(mNameSpace));
}
}
#ifdef DEBUG
/******************************************************************************
* SizeOf method:
*
* Self (reported as CSSStyleSheetInner's size):
* 1) sizeof(*this) + sizeof mSheets array (not contents though)
* + size of the mOrderedRules array (not the contents though)
*
* Contained / Aggregated data (not reported as CSSStyleSheetInner's size):
* 1) mSheets: each style sheet is sized seperately
* 2) mOrderedRules: each fule is sized seperately
*
* Children / siblings / parents:
* none
*
******************************************************************************/
void CSSStyleSheetInner::SizeOf(nsISizeOfHandler *aSizeOfHandler, PRUint32 &aSize)
{
NS_ASSERTION(aSizeOfHandler != nsnull, "SizeOf handler cannot be null");
// first get the unique items collection
UNIQUE_STYLE_ITEMS(uniqueItems);
if(! uniqueItems->AddItem((void*)this)){
// this style sheet is lared accounted for
return;
}
PRUint32 localSize=0;
PRBool rulesCounted=PR_FALSE;
// create a tag for this instance
nsCOMPtr<nsIAtom> tag;
tag = getter_AddRefs(NS_NewAtom("CSSStyleSheetInner"));
// get the size of an empty instance and add to the sizeof handler
aSize = sizeof(CSSStyleSheetInner);
// add in the size of the mSheets array itself
mSheets.SizeOf(aSizeOfHandler,&localSize);
aSize += localSize;
// and the mOrderedRules array (if there is one)
if(mOrderedRules && uniqueItems->AddItem(mOrderedRules)){
rulesCounted=PR_TRUE;
// no SizeOf method so we just get the basic object size
aSize += sizeof(*mOrderedRules);
}
aSizeOfHandler->AddSize(tag,aSize);
// delegate to the contained containers
// mSheets : nsVoidArray
{
PRUint32 sheetCount, sheetCur;
sheetCount = mSheets.Count();
for(sheetCur=0; sheetCur < sheetCount; sheetCur++){
nsICSSStyleSheet* sheet = (nsICSSStyleSheet*)mSheets.ElementAt(sheetCur);
if(sheet){
sheet->SizeOf(aSizeOfHandler, localSize);
}
}
}
// mOrderedRules : nsISupportsArray*
if(mOrderedRules && rulesCounted){
PRUint32 ruleCount, ruleCur;
mOrderedRules->Count(&ruleCount);
for(ruleCur=0; ruleCur < ruleCount; ruleCur++){
nsICSSRule* rule = (nsICSSRule*)mOrderedRules->ElementAt(ruleCur);
if(rule){
rule->SizeOf(aSizeOfHandler, localSize);
NS_IF_RELEASE(rule);
}
}
}
}
#endif
// -------------------------------
// CSS Style Sheet
//
MOZ_DECL_CTOR_COUNTER(CSSStyleSheetImpl)
CSSStyleSheetImpl::CSSStyleSheetImpl()
: nsICSSStyleSheet(),
mRefCnt(0),
mTitle(),
mMedia(nsnull),
mFirstChild(nsnull),
mNext(nsnull),
mParent(nsnull),
mOwnerRule(nsnull),
mImportsCollection(nsnull),
mRuleCollection(nsnull),
mDocument(nsnull),
mOwningNode(nsnull),
mDisabled(PR_FALSE),
mDirty(PR_FALSE),
mRuleProcessors(nsnull)
{
NS_INIT_ISUPPORTS();
mInner = new CSSStyleSheetInner(this);
}
CSSStyleSheetImpl::CSSStyleSheetImpl(const CSSStyleSheetImpl& aCopy)
: nsICSSStyleSheet(),
mRefCnt(0),
mTitle(aCopy.mTitle),
mMedia(nsnull),
mFirstChild(nsnull),
mNext(nsnull),
mParent(aCopy.mParent),
mOwnerRule(aCopy.mOwnerRule),
mImportsCollection(nsnull), // re-created lazily
mRuleCollection(nsnull), // re-created lazily
mDocument(aCopy.mDocument),
mOwningNode(aCopy.mOwningNode),
mDisabled(aCopy.mDisabled),
mDirty(PR_FALSE),
mInner(aCopy.mInner),
mRuleProcessors(nsnull)
{
NS_INIT_ISUPPORTS();
mInner->AddSheet(this);
if (aCopy.mRuleCollection &&
aCopy.mRuleCollection->mRulesAccessed) { // CSSOM's been there, force full copy now
NS_ASSERTION(mInner->mComplete, "Why have rules been accessed on an incomplete sheet?");
EnsureUniqueInner();
}
if (aCopy.mMedia) {
nsCOMPtr<nsISupportsArray> tmp;
(NS_STATIC_CAST(nsISupportsArray *, aCopy.mMedia))->Clone(getter_AddRefs(tmp));
mMedia = new DOMMediaListImpl(tmp, this);
NS_IF_ADDREF(mMedia);
}
if (aCopy.mFirstChild) {
CSSStyleSheetImpl* otherChild = aCopy.mFirstChild;
CSSStyleSheetImpl** ourSlot = &mFirstChild;
do {
CSSStyleSheetImpl* child = new CSSStyleSheetImpl(*otherChild);
if (child) {
NS_ADDREF(child);
(*ourSlot) = child;
ourSlot = &(child->mNext);
}
otherChild = otherChild->mNext;
}
while (otherChild && ourSlot);
}
}
CSSStyleSheetImpl::~CSSStyleSheetImpl()
{
if (mFirstChild) {
CSSStyleSheetImpl* child = mFirstChild;
do {
child->mParent = nsnull;
child = child->mNext;
} while (child);
NS_RELEASE(mFirstChild);
}
NS_IF_RELEASE(mNext);
if (nsnull != mRuleCollection) {
mRuleCollection->DropReference();
NS_RELEASE(mRuleCollection);
}
if (nsnull != mImportsCollection) {
mImportsCollection->DropReference();
NS_RELEASE(mImportsCollection);
}
if (mMedia) {
mMedia->DropReference();
NS_RELEASE(mMedia);
}
mInner->RemoveSheet(this);
// XXX The document reference is not reference counted and should
// not be released. The document will let us know when it is going
// away.
if (mRuleProcessors) {
NS_ASSERTION(mRuleProcessors->Count() == 0, "destucting sheet with rule processor reference");
delete mRuleProcessors; // weak refs, should be empty here anyway
}
}
// QueryInterface implementation for CSSStyleSheetImpl
NS_INTERFACE_MAP_BEGIN(CSSStyleSheetImpl)
NS_INTERFACE_MAP_ENTRY(nsICSSStyleSheet)
NS_INTERFACE_MAP_ENTRY(nsIStyleSheet)
NS_INTERFACE_MAP_ENTRY(nsIDOMStyleSheet)
NS_INTERFACE_MAP_ENTRY(nsIDOMCSSStyleSheet)
NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsICSSStyleSheet)
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(CSSStyleSheet)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(CSSStyleSheetImpl)
NS_IMPL_RELEASE(CSSStyleSheetImpl)
NS_IMETHODIMP
CSSStyleSheetImpl::GetStyleRuleProcessor(nsIStyleRuleProcessor*& aProcessor,
nsIStyleRuleProcessor* aPrevProcessor)
{
nsresult result = NS_OK;
nsICSSStyleRuleProcessor* cssProcessor = nsnull;
if (aPrevProcessor) {
result = aPrevProcessor->QueryInterface(NS_GET_IID(nsICSSStyleRuleProcessor), (void**)&cssProcessor);
}
if (! cssProcessor) {
CSSRuleProcessor* processor = new CSSRuleProcessor();
if (processor) {
result = processor->QueryInterface(NS_GET_IID(nsICSSStyleRuleProcessor), (void**)&cssProcessor);
if (NS_FAILED(result)) {
delete processor;
cssProcessor = nsnull;
}
}
else {
result = NS_ERROR_OUT_OF_MEMORY;
}
}
if (NS_SUCCEEDED(result) && cssProcessor) {
cssProcessor->AppendStyleSheet(this);
if (! mRuleProcessors) {
mRuleProcessors = new nsAutoVoidArray();
}
if (mRuleProcessors) {
NS_ASSERTION(-1 == mRuleProcessors->IndexOf(cssProcessor), "processor already registered");
mRuleProcessors->AppendElement(cssProcessor); // weak ref
}
}
aProcessor = cssProcessor;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::DropRuleProcessorReference(nsICSSStyleRuleProcessor* aProcessor)
{
NS_ASSERTION(mRuleProcessors, "no rule processors registered");
if (mRuleProcessors) {
NS_ASSERTION(-1 != mRuleProcessors->IndexOf(aProcessor), "not a registered processor");
mRuleProcessors->RemoveElement(aProcessor);
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::Init(nsIURI* aURL)
{
NS_PRECONDITION(aURL, "null ptr");
if (! aURL)
return NS_ERROR_NULL_POINTER;
if (! mInner) {
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ASSERTION(! mInner->mURL, "already initialized");
if (mInner->mURL)
return NS_ERROR_ALREADY_INITIALIZED;
if (mInner->mURL) {
#ifdef DEBUG
PRBool eq;
nsresult rv = mInner->mURL->Equals(aURL, &eq);
NS_ASSERTION(NS_SUCCEEDED(rv) && eq, "bad inner");
#endif
}
else {
mInner->mURL = aURL;
NS_ADDREF(mInner->mURL);
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetURL(nsIURI*& aURL) const
{
const nsIURI* url = ((mInner) ? mInner->mURL : nsnull);
aURL = (nsIURI*)url;
NS_IF_ADDREF(aURL);
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::SetTitle(const nsAString& aTitle)
{
mTitle = aTitle;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetType(nsString& aType) const
{
aType.Assign(NS_LITERAL_STRING("text/css"));
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetMediumCount(PRInt32& aCount) const
{
if (mMedia) {
PRUint32 cnt;
nsresult rv = mMedia->Count(&cnt);
if (NS_FAILED(rv)) return rv;
aCount = cnt;
}
else
aCount = 0;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetMediumAt(PRInt32 aIndex, nsIAtom*& aMedium) const
{
nsIAtom* medium = nsnull;
if (nsnull != mMedia) {
medium = (nsIAtom*)mMedia->ElementAt(aIndex);
}
if (nsnull != medium) {
aMedium = medium;
return NS_OK;
}
aMedium = nsnull;
return NS_ERROR_INVALID_ARG;
}
NS_IMETHODIMP_(PRBool)
CSSStyleSheetImpl::UseForMedium(nsIAtom* aMedium) const
{
if (mMedia) {
PRBool matches = PR_FALSE;
mMedia->MatchesMedium(aMedium, &matches);
return matches;
}
return PR_TRUE;
}
NS_IMETHODIMP
CSSStyleSheetImpl::AppendMedium(nsIAtom* aMedium)
{
nsresult result = NS_OK;
if (!mMedia) {
nsCOMPtr<nsISupportsArray> tmp;
result = NS_NewISupportsArray(getter_AddRefs(tmp));
NS_ENSURE_SUCCESS(result, result);
mMedia = new DOMMediaListImpl(tmp, this);
NS_ENSURE_TRUE(mMedia, NS_ERROR_OUT_OF_MEMORY);
NS_ADDREF(mMedia);
}
if (mMedia) {
mMedia->AppendElement(aMedium);
}
return result;
}
NS_IMETHODIMP
CSSStyleSheetImpl::ClearMedia(void)
{
if (mMedia) {
mMedia->Clear();
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetApplicable(PRBool& aApplicable) const
{
aApplicable = !mDisabled && mInner && mInner->mComplete;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::SetEnabled(PRBool aEnabled)
{
PRBool oldDisabled = mDisabled;
mDisabled = !aEnabled;
if (mDocument && mInner && mInner->mComplete && oldDisabled != mDisabled) {
mDocument->SetStyleSheetApplicableState(this, !mDisabled);
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetComplete(PRBool& aComplete) const
{
aComplete = mInner && mInner->mComplete;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::SetComplete()
{
if (!mInner)
return NS_ERROR_UNEXPECTED;
NS_ASSERTION(!mDirty, "Can't set a dirty sheet complete!");
mInner->mComplete = PR_TRUE;
if (mDocument && !mDisabled) {
// Let the document know
mDocument->SetStyleSheetApplicableState(this, PR_TRUE);
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetParentSheet(nsIStyleSheet*& aParent) const
{
aParent = mParent;
NS_IF_ADDREF(aParent);
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetOwningDocument(nsIDocument*& aDocument) const
{
nsIDocument* doc = mDocument;
CSSStyleSheetImpl* parent = (CSSStyleSheetImpl*)mParent;
while ((nsnull == doc) && (nsnull != parent)) {
doc = parent->mDocument;
parent = (CSSStyleSheetImpl*)(parent->mParent);
}
NS_IF_ADDREF(doc);
aDocument = doc;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::SetOwningDocument(nsIDocument* aDocument)
{ // not ref counted
mDocument = aDocument;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::SetOwningNode(nsIDOMNode* aOwningNode)
{ // not ref counted
mOwningNode = aOwningNode;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::SetOwnerRule(nsICSSImportRule* aOwnerRule)
{ // not ref counted
mOwnerRule = aOwnerRule;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::ContainsStyleSheet(nsIURI* aURL, PRBool& aContains, nsIStyleSheet** aTheChild /*=nsnull*/)
{
NS_PRECONDITION(nsnull != aURL, "null arg");
// first check ourself out
nsresult rv = mInner->mURL->Equals(aURL, &aContains);
if (NS_FAILED(rv)) aContains = PR_FALSE;
if (aContains) {
// if we found it and the out-param is there, set it and addref
if (aTheChild) {
rv = QueryInterface( NS_GET_IID(nsIStyleSheet), (void **)aTheChild);
}
} else {
CSSStyleSheetImpl* child = mFirstChild;
// now check the chil'ins out (recursively)
while ((PR_FALSE == aContains) && (nsnull != child)) {
child->ContainsStyleSheet(aURL, aContains, aTheChild);
if (aContains) {
break;
} else {
child = child->mNext;
}
}
}
// NOTE: if there are errors in the above we are handling them locally
// and not promoting them to the caller
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::AppendStyleSheet(nsICSSStyleSheet* aSheet)
{
NS_PRECONDITION(nsnull != aSheet, "null arg");
if (NS_SUCCEEDED(WillDirty())) {
NS_ADDREF(aSheet);
CSSStyleSheetImpl* sheet = (CSSStyleSheetImpl*)aSheet;
if (! mFirstChild) {
mFirstChild = sheet;
}
else {
CSSStyleSheetImpl* child = mFirstChild;
while (child->mNext) {
child = child->mNext;
}
child->mNext = sheet;
}
// This is not reference counted. Our parent tells us when
// it's going away.
sheet->mParent = this;
DidDirty();
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::InsertStyleSheetAt(nsICSSStyleSheet* aSheet, PRInt32 aIndex)
{
NS_PRECONDITION(nsnull != aSheet, "null arg");
nsresult result = WillDirty();
if (NS_SUCCEEDED(result)) {
NS_ADDREF(aSheet);
CSSStyleSheetImpl* sheet = (CSSStyleSheetImpl*)aSheet;
CSSStyleSheetImpl* child = mFirstChild;
if (aIndex && child) {
while ((0 < --aIndex) && child->mNext) {
child = child->mNext;
}
sheet->mNext = child->mNext;
child->mNext = sheet;
}
else {
sheet->mNext = mFirstChild;
mFirstChild = sheet;
}
// This is not reference counted. Our parent tells us when
// it's going away.
sheet->mParent = this;
DidDirty();
}
return result;
}
NS_IMETHODIMP
CSSStyleSheetImpl::AttributeAffectsStyle(nsIAtom *aAttribute,
nsIContent *aContent,
PRBool &aAffects)
{
DependentAtomKey key(aAttribute);
aAffects = !!mInner->mRelevantAttributes.Get(&key);
for (CSSStyleSheetImpl *child = mFirstChild;
child && !aAffects;
child = child->mNext) {
child->AttributeAffectsStyle(aAttribute, aContent, aAffects);
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::PrependStyleRule(nsICSSRule* aRule)
{
NS_PRECONDITION(nsnull != aRule, "null arg");
if (NS_SUCCEEDED(WillDirty())) {
if (! mInner->mOrderedRules) {
NS_NewISupportsArray(&(mInner->mOrderedRules));
}
if (mInner->mOrderedRules) {
mInner->mOrderedRules->InsertElementAt(aRule, 0);
aRule->SetStyleSheet(this);
DidDirty();
PRInt32 type = nsICSSRule::UNKNOWN_RULE;
aRule->GetType(type);
if (nsICSSRule::NAMESPACE_RULE == type) {
// no api to prepend a namespace (ugh), release old ones and re-create them all
mInner->RebuildNameSpaces();
} else {
CheckRuleForAttributes(aRule);
}
}
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::AppendStyleRule(nsICSSRule* aRule)
{
NS_PRECONDITION(nsnull != aRule, "null arg");
if (NS_SUCCEEDED(WillDirty())) {
if (! mInner->mOrderedRules) {
NS_NewISupportsArray(&(mInner->mOrderedRules));
}
if (mInner->mOrderedRules) {
mInner->mOrderedRules->AppendElement(aRule);
aRule->SetStyleSheet(this);
DidDirty();
PRInt32 type = nsICSSRule::UNKNOWN_RULE;
aRule->GetType(type);
if (nsICSSRule::NAMESPACE_RULE == type) {
if (! mInner->mNameSpace) {
nsContentUtils::GetNSManagerWeakRef()->CreateRootNameSpace(*getter_AddRefs(mInner->mNameSpace));
}
if (mInner->mNameSpace) {
nsCOMPtr<nsICSSNameSpaceRule> nameSpaceRule(do_QueryInterface(aRule));
nsCOMPtr<nsINameSpace> newNameSpace;
nsCOMPtr<nsIAtom> prefix;
nsAutoString urlSpec;
nameSpaceRule->GetPrefix(*getter_AddRefs(prefix));
nameSpaceRule->GetURLSpec(urlSpec);
mInner->mNameSpace->CreateChildNameSpace(prefix, urlSpec,
*getter_AddRefs(newNameSpace));
if (newNameSpace) {
mInner->mNameSpace = newNameSpace;
}
}
} else {
CheckRuleForAttributes(aRule);
}
}
}
return NS_OK;
}
static PRBool
CheckRuleForAttributesEnum(nsISupports *aRule, void *aData)
{
nsICSSRule *rule = NS_STATIC_CAST(nsICSSRule *, aRule);
CSSStyleSheetImpl *sheet = NS_STATIC_CAST(CSSStyleSheetImpl *, aData);
return NS_SUCCEEDED(sheet->CheckRuleForAttributes(rule));
}
NS_IMETHODIMP
CSSStyleSheetImpl::CheckRuleForAttributes(nsICSSRule *aRule)
{
PRInt32 ruleType = nsICSSRule::UNKNOWN_RULE;
aRule->GetType(ruleType);
switch (ruleType) {
case nsICSSRule::MEDIA_RULE: {
nsICSSMediaRule *mediaRule = (nsICSSMediaRule *)aRule;
return mediaRule->EnumerateRulesForwards(CheckRuleForAttributesEnum,
(void *)this);
}
case nsICSSRule::STYLE_RULE: {
nsICSSStyleRule *styleRule = NS_STATIC_CAST(nsICSSStyleRule *, aRule);
nsCSSSelector *iter;
for (iter = styleRule->FirstSelector(); iter; iter = iter->mNext) {
/* P.classname means we have to check the attribute "class" */
if (iter->mIDList) {
DependentAtomKey idKey(nsHTMLAtoms::id);
mInner->mRelevantAttributes.Put(&idKey, nsHTMLAtoms::id);
}
if (iter->mClassList) {
DependentAtomKey classKey(nsHTMLAtoms::kClass);
mInner->mRelevantAttributes.Put(&classKey, nsHTMLAtoms::kClass);
}
for (nsAttrSelector *sel = iter->mAttrList; sel; sel = sel->mNext) {
/* store it in this sheet's attributes-that-matter table */
/* XXX store tag name too, but handle collisions */
#ifdef DEBUG_shaver_off
nsAutoString str;
sel->mAttr->ToString(str);
char * chars = ToNewCString(str);
fprintf(stderr, "[%s@%p]", chars, this);
nsMemory::Free(chars);
#endif
DependentAtomKey key(sel->mAttr);
mInner->mRelevantAttributes.Put(&key, sel->mAttr);
}
// Search for the :lang() pseudo class. If it is there add
// the lang attribute to the relevant attribute list.
for (nsAtomStringList* p = iter->mPseudoClassList; p; p = p->mNext) {
if (p->mAtom == nsCSSPseudoClasses::lang) {
DependentAtomKey langKey(nsHTMLAtoms::lang);
mInner->mRelevantAttributes.Put(&langKey, nsHTMLAtoms::lang);
break;
}
}
}
} /* fall-through */
default:
return NS_OK;
}
}
NS_IMETHODIMP
CSSStyleSheetImpl::StyleRuleCount(PRInt32& aCount) const
{
aCount = 0;
if (mInner && mInner->mOrderedRules) {
PRUint32 cnt;
nsresult rv = ((CSSStyleSheetImpl*)this)->mInner->mOrderedRules->Count(&cnt); // XXX bogus cast -- this method should not be const
aCount = (PRInt32)cnt;
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetStyleRuleAt(PRInt32 aIndex, nsICSSRule*& aRule) const
{
// Important: If this function is ever made scriptable, we must add
// a security check here. See GetCSSRules below for an example.
nsresult result = NS_ERROR_ILLEGAL_VALUE;
if (mInner && mInner->mOrderedRules) {
aRule = (nsICSSRule*)(mInner->mOrderedRules->ElementAt(aIndex));
if (nsnull != aRule) {
result = NS_OK;
}
}
else {
aRule = nsnull;
}
return result;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetNameSpace(nsINameSpace*& aNameSpace) const
{
if (mInner) {
aNameSpace = mInner->mNameSpace;
NS_IF_ADDREF(aNameSpace);
}
else {
aNameSpace = nsnull;
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::SetDefaultNameSpaceID(PRInt32 aDefaultNameSpaceID)
{
if (mInner) {
mInner->mDefaultNameSpaceID = aDefaultNameSpaceID;
mInner->RebuildNameSpaces();
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::StyleSheetCount(PRInt32& aCount) const
{
// XXX Far from an ideal way to do this, but the hope is that
// it won't be done too often. If it is, we might want to
// consider storing the children in an array.
aCount = 0;
const CSSStyleSheetImpl* child = mFirstChild;
while (child) {
aCount++;
child = child->mNext;
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetStyleSheetAt(PRInt32 aIndex, nsICSSStyleSheet*& aSheet) const
{
// XXX Ughh...an O(n^2) method for doing iteration. Again, we hope
// that this isn't done too often. If it is, we need to change the
// underlying storage mechanism
aSheet = nsnull;
if (mFirstChild) {
const CSSStyleSheetImpl* child = mFirstChild;
while ((child) && (0 != aIndex)) {
--aIndex;
child = child->mNext;
}
aSheet = (nsICSSStyleSheet*)child;
NS_IF_ADDREF(aSheet);
}
return NS_OK;
}
nsresult
CSSStyleSheetImpl::EnsureUniqueInner(void)
{
if (! mInner) {
return NS_ERROR_NOT_INITIALIZED;
}
if (1 < mInner->mSheets.Count()) {
CSSStyleSheetInner* clone = mInner->CloneFor(this);
if (clone) {
mInner->RemoveSheet(this);
mInner = clone;
}
else {
return NS_ERROR_OUT_OF_MEMORY;
}
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::Clone(nsICSSStyleSheet*& aClone) const
{
// XXX no, really need to clone
CSSStyleSheetImpl* clone = new CSSStyleSheetImpl(*this);
if (clone) {
aClone = (nsICSSStyleSheet*)clone;
NS_ADDREF(aClone);
}
return NS_OK;
}
#ifdef DEBUG
static void
ListRules(nsISupportsArray* aRules, FILE* aOut, PRInt32 aIndent)
{
PRUint32 count;
PRInt32 index;
if (aRules) {
aRules->Count(&count);
for (index = count - 1; index >= 0; --index) {
nsCOMPtr<nsICSSRule> rule = dont_AddRef((nsICSSRule*)aRules->ElementAt(index));
rule->List(aOut, aIndent);
}
}
}
struct ListEnumData {
ListEnumData(FILE* aOut, PRInt32 aIndent)
: mOut(aOut),
mIndent(aIndent)
{
}
FILE* mOut;
PRInt32 mIndent;
};
#if 0
static PRBool ListCascade(nsHashKey* aKey, void* aValue, void* aClosure)
{
AtomKey* key = (AtomKey*)aKey;
RuleCascadeData* cascade = (RuleCascadeData*)aValue;
ListEnumData* data = (ListEnumData*)aClosure;
fputs("\nRules in cascade order for medium: \"", data->mOut);
nsAutoString buffer;
key->mAtom->ToString(buffer);
fputs(NS_LossyConvertUCS2toASCII(buffer).get(), data->mOut);
fputs("\"\n", data->mOut);
ListRules(cascade->mWeightedRules, data->mOut, data->mIndent);
return PR_TRUE;
}
#endif
void CSSStyleSheetImpl::List(FILE* out, PRInt32 aIndent) const
{
PRInt32 index;
// Indent
for (index = aIndent; --index >= 0; ) fputs(" ", out);
if (! mInner) {
fputs("CSS Style Sheet - without inner data storage - ERROR\n", out);
return;
}
fputs("CSS Style Sheet: ", out);
nsCAutoString urlSpec;
nsresult rv = mInner->mURL->GetSpec(urlSpec);
if (NS_SUCCEEDED(rv) && !urlSpec.IsEmpty()) {
fputs(urlSpec.get(), out);
}
if (mMedia) {
fputs(" media: ", out);
index = 0;
PRUint32 count;
mMedia->Count(&count);
nsAutoString buffer;
while (index < PRInt32(count)) {
nsCOMPtr<nsIAtom> medium = dont_AddRef((nsIAtom*)mMedia->ElementAt(index++));
medium->ToString(buffer);
fputs(NS_LossyConvertUCS2toASCII(buffer).get(), out);
if (index < PRInt32(count)) {
fputs(", ", out);
}
}
}
fputs("\n", out);
const CSSStyleSheetImpl* child = mFirstChild;
while (nsnull != child) {
child->List(out, aIndent + 1);
child = child->mNext;
}
fputs("Rules in source order:\n", out);
ListRules(mInner->mOrderedRules, out, aIndent);
}
/******************************************************************************
* SizeOf method:
*
* Self (reported as CSSStyleSheetImpl's size):
* 1) sizeof(*this) + sizeof the mImportsCollection + sizeof mCuleCollection)
*
* Contained / Aggregated data (not reported as CSSStyleSheetImpl's size):
* 1) mInner is delegated to be counted seperately
*
* Children / siblings / parents:
* 1) Recurse to mFirstChild
*
******************************************************************************/
void CSSStyleSheetImpl::SizeOf(nsISizeOfHandler *aSizeOfHandler, PRUint32 &aSize)
{
NS_ASSERTION(aSizeOfHandler != nsnull, "SizeOf handler cannot be null");
// first get the unique items collection
UNIQUE_STYLE_ITEMS(uniqueItems);
if(! uniqueItems->AddItem((void*)this)){
// this style sheet is already accounted for
return;
}
PRUint32 localSize=0;
// create a tag for this instance
nsCOMPtr<nsIAtom> tag;
tag = getter_AddRefs(NS_NewAtom("CSSStyleSheet"));
// get the size of an empty instance and add to the sizeof handler
aSize = sizeof(CSSStyleSheetImpl);
// add up the contained objects we won't delegate to:
// NOTE that we just add the sizeof the objects
// since the style data they contain is accounted for elsewhere
// - mImportsCollection
// - mRuleCollection
aSize += sizeof(mImportsCollection);
aSize += sizeof(mRuleCollection);
aSizeOfHandler->AddSize(tag,aSize);
// size the inner
if(mInner){
mInner->SizeOf(aSizeOfHandler, localSize);
}
// now travers the children (recursively, I'm sorry to say)
if(mFirstChild){
PRUint32 childSize=0;
mFirstChild->SizeOf(aSizeOfHandler, childSize);
}
}
#endif
static PRBool PR_CALLBACK
EnumClearRuleCascades(void* aProcessor, void* aData)
{
nsICSSStyleRuleProcessor* processor = (nsICSSStyleRuleProcessor*)aProcessor;
processor->ClearRuleCascades();
return PR_TRUE;
}
void
CSSStyleSheetImpl::ClearRuleCascades(void)
{
if (mRuleProcessors) {
mRuleProcessors->EnumerateForwards(EnumClearRuleCascades, nsnull);
}
if (mParent) {
CSSStyleSheetImpl* parent = (CSSStyleSheetImpl*)mParent;
parent->ClearRuleCascades();
}
}
nsresult
CSSStyleSheetImpl::WillDirty(void)
{
if (mInner && !mInner->mComplete) {
// Do nothing
return NS_OK;
}
return EnsureUniqueInner();
}
void
CSSStyleSheetImpl::DidDirty(void)
{
ClearRuleCascades();
mDirty = PR_TRUE;
}
NS_IMETHODIMP
CSSStyleSheetImpl::IsModified(PRBool* aSheetModified) const
{
*aSheetModified = mDirty;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::SetModified(PRBool aModified)
{
mDirty = aModified;
return NS_OK;
}
// nsIDOMStyleSheet interface
NS_IMETHODIMP
CSSStyleSheetImpl::GetType(nsAString& aType)
{
aType.Assign(NS_LITERAL_STRING("text/css"));
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetDisabled(PRBool* aDisabled)
{
*aDisabled = mDisabled;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::SetDisabled(PRBool aDisabled)
{
PRBool oldDisabled = mDisabled;
mDisabled = aDisabled;
if (mDocument && mInner && mInner->mComplete && oldDisabled != mDisabled) {
mDocument->SetStyleSheetApplicableState(this, !mDisabled);
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetOwnerNode(nsIDOMNode** aOwnerNode)
{
*aOwnerNode = mOwningNode;
NS_IF_ADDREF(*aOwnerNode);
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetParentStyleSheet(nsIDOMStyleSheet** aParentStyleSheet)
{
NS_ENSURE_ARG_POINTER(aParentStyleSheet);
nsresult rv = NS_OK;
if (mParent) {
rv = mParent->QueryInterface(NS_GET_IID(nsIDOMStyleSheet),
(void **)aParentStyleSheet);
} else {
*aParentStyleSheet = nsnull;
}
return rv;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetHref(nsAString& aHref)
{
if (mInner && mInner->mURL) {
nsCAutoString str;
mInner->mURL->GetSpec(str);
aHref.Assign(NS_ConvertUTF8toUCS2(str));
}
else {
aHref.Truncate();
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetTitle(nsString& aTitle) const
{
aTitle = mTitle;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetTitle(nsAString& aTitle)
{
aTitle.Assign(mTitle);
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetMedia(nsIDOMMediaList** aMedia)
{
NS_ENSURE_ARG_POINTER(aMedia);
*aMedia = nsnull;
if (!mMedia) {
nsCOMPtr<nsISupportsArray> tmp;
NS_NewISupportsArray(getter_AddRefs(tmp));
NS_ENSURE_TRUE(tmp, NS_ERROR_NULL_POINTER);
mMedia = new DOMMediaListImpl(tmp, this);
NS_IF_ADDREF(mMedia);
}
*aMedia = mMedia;
NS_IF_ADDREF(*aMedia);
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetOwnerRule(nsIDOMCSSRule** aOwnerRule)
{
if (mOwnerRule) {
return CallQueryInterface(mOwnerRule, aOwnerRule);
}
*aOwnerRule = nsnull;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::GetCssRules(nsIDOMCSSRuleList** aCssRules)
{
// No doing this on incomplete sheets!
PRBool complete;
GetComplete(complete);
if (!complete) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
//-- Security check: Only scripts from the same origin as the
// style sheet can access rule collections
// Get JSContext from stack
nsCOMPtr<nsIJSContextStack> stack =
do_GetService("@mozilla.org/js/xpc/ContextStack;1");
NS_ENSURE_TRUE(stack, NS_ERROR_FAILURE);
JSContext *cx = nsnull;
nsresult rv = NS_OK;
rv = stack->Peek(&cx);
NS_ENSURE_SUCCESS(rv, rv);
if (!cx)
return NS_ERROR_FAILURE;
// Get the security manager and do the same-origin check
nsCOMPtr<nsIScriptSecurityManager> secMan =
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = secMan->CheckSameOrigin(cx, mInner->mURL);
if (NS_FAILED(rv)) {
return rv;
}
// OK, security check passed, so get the rule collection
if (nsnull == mRuleCollection) {
mRuleCollection = new CSSRuleListImpl(this);
if (nsnull == mRuleCollection) {
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(mRuleCollection);
}
*aCssRules = mRuleCollection;
NS_ADDREF(mRuleCollection);
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::InsertRule(const nsAString& aRule,
PRUint32 aIndex,
PRUint32* aReturn)
{
NS_ENSURE_TRUE(mInner, NS_ERROR_FAILURE);
// No doing this if the sheet is not complete!
PRBool complete;
GetComplete(complete);
if (!complete) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
nsresult result;
result = WillDirty();
if (NS_FAILED(result))
return result;
if (! mInner->mOrderedRules) {
result = NS_NewISupportsArray(&(mInner->mOrderedRules));
}
if (NS_FAILED(result))
return result;
PRUint32 count;
mInner->mOrderedRules->Count(&count);
if (aIndex > count)
return NS_ERROR_DOM_INDEX_SIZE_ERR;
nsCOMPtr<nsICSSLoader> loader;
nsCOMPtr<nsICSSParser> css;
nsCOMPtr<nsIHTMLContentContainer> htmlContainer(do_QueryInterface(mDocument));
if (htmlContainer) {
htmlContainer->GetCSSLoader(*getter_AddRefs(loader));
}
NS_ASSERTION(loader || !mDocument, "Document with no CSS loader!");
if (loader) {
result = loader->GetParserFor(this, getter_AddRefs(css));
}
else {
result = NS_NewCSSParser(getter_AddRefs(css));
if (css) {
css->SetStyleSheet(this);
}
}
if (NS_FAILED(result))
return result;
if (mDocument) {
result = mDocument->BeginUpdate();
if (NS_FAILED(result))
return result;
}
nsCOMPtr<nsISupportsArray> rules;
result = css->ParseRule(aRule, mInner->mURL, getter_AddRefs(rules));
if (NS_FAILED(result))
return result;
PRUint32 rulecount = 0;
rules->Count(&rulecount);
if (rulecount == 0 && !aRule.IsEmpty()) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
// Hierarchy checking. Just check the first and last rule in the list.
// check that we're not inserting before a charset rule
nsCOMPtr<nsICSSRule> nextRule;
PRInt32 nextType = nsICSSRule::UNKNOWN_RULE;
nextRule = dont_AddRef((nsICSSRule*)mInner->mOrderedRules->ElementAt(aIndex));
if (nextRule) {
nextRule->GetType(nextType);
if (nextType == nsICSSRule::CHARSET_RULE) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
// check last rule in list
nsCOMPtr<nsICSSRule> lastRule = dont_AddRef((nsICSSRule*)rules->ElementAt(rulecount-1));
PRInt32 lastType = nsICSSRule::UNKNOWN_RULE;
lastRule->GetType(lastType);
if (nextType == nsICSSRule::IMPORT_RULE &&
lastType != nsICSSRule::CHARSET_RULE &&
lastType != nsICSSRule::IMPORT_RULE) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
if (nextType == nsICSSRule::NAMESPACE_RULE &&
lastType != nsICSSRule::CHARSET_RULE &&
lastType != nsICSSRule::IMPORT_RULE &&
lastType != nsICSSRule::NAMESPACE_RULE) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
}
// check first rule in list
nsCOMPtr<nsICSSRule> firstRule = dont_AddRef((nsICSSRule*)rules->ElementAt(0));
PRInt32 firstType = nsICSSRule::UNKNOWN_RULE;
firstRule->GetType(firstType);
if (aIndex != 0) {
if (firstType == nsICSSRule::CHARSET_RULE) { // no inserting charset at nonzero position
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
nsCOMPtr<nsICSSRule> prevRule = dont_AddRef((nsICSSRule*)mInner->mOrderedRules->ElementAt(aIndex-1));
PRInt32 prevType = nsICSSRule::UNKNOWN_RULE;
prevRule->GetType(prevType);
if (firstType == nsICSSRule::IMPORT_RULE &&
prevType != nsICSSRule::CHARSET_RULE &&
prevType != nsICSSRule::IMPORT_RULE) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
if (firstType == nsICSSRule::NAMESPACE_RULE &&
prevType != nsICSSRule::CHARSET_RULE &&
prevType != nsICSSRule::IMPORT_RULE &&
prevType != nsICSSRule::NAMESPACE_RULE) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
}
result = mInner->mOrderedRules->InsertElementsAt(rules, aIndex);
NS_ENSURE_SUCCESS(result, result);
DidDirty();
nsCOMPtr<nsICSSRule> cssRule;
PRUint32 counter;
for (counter = 0; counter < rulecount; counter++) {
cssRule = dont_AddRef((nsICSSRule*)rules->ElementAt(counter));
cssRule->SetStyleSheet(this);
PRInt32 type = nsICSSRule::UNKNOWN_RULE;
cssRule->GetType(type);
if (type == nsICSSRule::NAMESPACE_RULE) {
if (! mInner->mNameSpace) {
nsContentUtils::GetNSManagerWeakRef()->CreateRootNameSpace(*getter_AddRefs(mInner->mNameSpace));
}
NS_ENSURE_TRUE(mInner->mNameSpace, NS_ERROR_FAILURE);
nsCOMPtr<nsICSSNameSpaceRule> nameSpaceRule(do_QueryInterface(cssRule));
nsCOMPtr<nsINameSpace> newNameSpace;
nsCOMPtr<nsIAtom> prefix;
nsAutoString urlSpec;
nameSpaceRule->GetPrefix(*getter_AddRefs(prefix));
nameSpaceRule->GetURLSpec(urlSpec);
mInner->mNameSpace->CreateChildNameSpace(prefix, urlSpec,
*getter_AddRefs(newNameSpace));
if (newNameSpace) {
mInner->mNameSpace = newNameSpace;
}
}
else {
CheckRuleForAttributes(cssRule);
}
// We don't notify immediately for @import rules, but rather when
// the sheet the rule is importing is loaded
PRBool notify = PR_TRUE;
if (type == nsICSSRule::IMPORT_RULE) {
nsCOMPtr<nsIDOMCSSImportRule> importRule(do_QueryInterface(cssRule));
NS_ASSERTION(importRule, "Rule which has type IMPORT_RULE and does not implement nsIDOMCSSImportRule!");
nsCOMPtr<nsIDOMCSSStyleSheet> childSheet;
importRule->GetStyleSheet(getter_AddRefs(childSheet));
if (!childSheet) {
notify = PR_FALSE;
}
}
if (mDocument && notify) {
result = mDocument->StyleRuleAdded(this, cssRule);
NS_ENSURE_SUCCESS(result, result);
}
}
if (mDocument) {
result = mDocument->EndUpdate();
NS_ENSURE_SUCCESS(result, result);
}
if (loader) {
loader->RecycleParser(css);
}
*aReturn = aIndex;
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::DeleteRule(PRUint32 aIndex)
{
nsresult result = NS_ERROR_DOM_INDEX_SIZE_ERR;
// No doing this if the sheet is not complete!
PRBool complete;
GetComplete(complete);
if (!complete) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
// XXX TBI: handle @rule types
if (mInner && mInner->mOrderedRules) {
if (mDocument) {
result = mDocument->BeginUpdate();
if (NS_FAILED(result))
return result;
}
result = WillDirty();
if (NS_SUCCEEDED(result)) {
PRUint32 count;
mInner->mOrderedRules->Count(&count);
if (aIndex >= count)
return NS_ERROR_DOM_INDEX_SIZE_ERR;
nsCOMPtr<nsICSSRule> rule =
dont_AddRef((nsICSSRule*)mInner->mOrderedRules->ElementAt(aIndex));
if (rule) {
mInner->mOrderedRules->RemoveElementAt(aIndex);
rule->SetStyleSheet(nsnull);
DidDirty();
if (mDocument) {
result = mDocument->StyleRuleRemoved(this, rule);
NS_ENSURE_SUCCESS(result, result);
result = mDocument->EndUpdate();
NS_ENSURE_SUCCESS(result, result);
}
}
}
}
return result;
}
NS_IMETHODIMP
CSSStyleSheetImpl::DeleteRuleFromGroup(nsICSSGroupRule* aGroup, PRUint32 aIndex)
{
NS_ENSURE_ARG_POINTER(aGroup);
NS_ASSERTION(mInner && mInner->mComplete,
"No deleting from an incomplete sheet!");
nsresult result;
nsCOMPtr<nsICSSRule> rule;
result = aGroup->GetStyleRuleAt(aIndex, *getter_AddRefs(rule));
NS_ENSURE_SUCCESS(result, result);
// check that the rule actually belongs to this sheet!
nsCOMPtr<nsIDOMCSSRule> domRule(do_QueryInterface(rule));
nsCOMPtr<nsIDOMCSSStyleSheet> ruleSheet;
result = domRule->GetParentStyleSheet(getter_AddRefs(ruleSheet));
NS_ENSURE_SUCCESS(result, result);
nsCOMPtr<nsIDOMCSSStyleSheet> thisSheet;
this->QueryInterface(NS_GET_IID(nsIDOMCSSStyleSheet), getter_AddRefs(thisSheet));
if (thisSheet != ruleSheet) {
return NS_ERROR_INVALID_ARG;
}
if (mDocument) {
result = mDocument->BeginUpdate();
NS_ENSURE_SUCCESS(result, result);
}
result = WillDirty();
NS_ENSURE_SUCCESS(result, result);
result = aGroup->DeleteStyleRuleAt(aIndex);
NS_ENSURE_SUCCESS(result, result);
rule->SetStyleSheet(nsnull);
DidDirty();
if (mDocument) {
result = mDocument->StyleRuleRemoved(this, rule);
NS_ENSURE_SUCCESS(result, result);
result = mDocument->EndUpdate();
NS_ENSURE_SUCCESS(result, result);
}
return NS_OK;
}
NS_IMETHODIMP
CSSStyleSheetImpl::InsertRuleIntoGroup(const nsAString & aRule, nsICSSGroupRule* aGroup, PRUint32 aIndex, PRUint32* _retval)
{
nsresult result;
NS_ASSERTION(mInner && mInner->mComplete,
"No inserting into an incomplete sheet!");
// check that the group actually belongs to this sheet!
nsCOMPtr<nsIDOMCSSRule> domGroup(do_QueryInterface(aGroup));
nsCOMPtr<nsIDOMCSSStyleSheet> groupSheet;
result = domGroup->GetParentStyleSheet(getter_AddRefs(groupSheet));
NS_ENSURE_SUCCESS(result, result);
nsCOMPtr<nsIDOMCSSStyleSheet> thisSheet;
this->QueryInterface(NS_GET_IID(nsIDOMCSSStyleSheet), getter_AddRefs(thisSheet));
if (thisSheet != groupSheet) {
return NS_ERROR_INVALID_ARG;
}
// get the css parser
nsCOMPtr<nsICSSLoader> loader;
nsCOMPtr<nsICSSParser> css;
nsCOMPtr<nsIHTMLContentContainer> htmlContainer(do_QueryInterface(mDocument));
if (htmlContainer) {
htmlContainer->GetCSSLoader(*getter_AddRefs(loader));
}
NS_ASSERTION(loader || !mDocument, "Document with no CSS loader!");
if (loader) {
result = loader->GetParserFor(this, getter_AddRefs(css));
}
else {
result = NS_NewCSSParser(getter_AddRefs(css));
if (css) {
css->SetStyleSheet(this);
}
}
NS_ENSURE_SUCCESS(result, result);
// parse and grab the rule
if (mDocument) {
result = mDocument->BeginUpdate();
NS_ENSURE_SUCCESS(result, result);
}
result = WillDirty();
NS_ENSURE_SUCCESS(result, result);
nsCOMPtr<nsISupportsArray> rules;
result = css->ParseRule(aRule, mInner->mURL, getter_AddRefs(rules));
NS_ENSURE_SUCCESS(result, result);
PRUint32 rulecount = 0;
rules->Count(&rulecount);
if (rulecount == 0 && !aRule.IsEmpty()) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
PRUint32 counter;
nsCOMPtr<nsICSSRule> rule;
for (counter = 0; counter < rulecount; counter++) {
// Only rulesets are allowed in a group as of CSS2
PRInt32 type = nsICSSRule::UNKNOWN_RULE;
rule = dont_AddRef((nsICSSRule*)rules->ElementAt(counter));
rule->GetType(type);
if (type != nsICSSRule::STYLE_RULE) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
}
result = aGroup->InsertStyleRulesAt(aIndex, rules);
NS_ENSURE_SUCCESS(result, result);
DidDirty();
for (counter = 0; counter < rulecount; counter++) {
rule = dont_AddRef((nsICSSRule*)rules->ElementAt(counter));
CheckRuleForAttributes(rule);
if (mDocument) {
result = mDocument->StyleRuleAdded(this, rule);
NS_ENSURE_SUCCESS(result, result);
}
}
if (mDocument) {
result = mDocument->EndUpdate();
NS_ENSURE_SUCCESS(result, result);
}
if (loader) {
loader->RecycleParser(css);
}
*_retval = aIndex;
return NS_OK;
}
// nsICSSLoaderObserver implementation
NS_IMETHODIMP
CSSStyleSheetImpl::StyleSheetLoaded(nsICSSStyleSheet*aSheet, PRBool aNotify)
{
#ifdef DEBUG
nsCOMPtr<nsIStyleSheet> styleSheet(do_QueryInterface(aSheet));
NS_ASSERTION(styleSheet, "Sheet not implementing nsIStyleSheet!\n");
nsCOMPtr<nsIStyleSheet> parentSheet;
aSheet->GetParentSheet(*getter_AddRefs(parentSheet));
nsCOMPtr<nsIStyleSheet> thisSheet;
QueryInterface(NS_GET_IID(nsIStyleSheet), getter_AddRefs(thisSheet));
NS_ASSERTION(thisSheet == parentSheet, "We are being notified of a sheet load for a sheet that is not our child!\n");
#endif
if (mDocument && aNotify) {
nsCOMPtr<nsIDOMCSSStyleSheet> domSheet(do_QueryInterface(aSheet));
NS_ENSURE_TRUE(domSheet, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIDOMCSSRule> ownerRule;
domSheet->GetOwnerRule(getter_AddRefs(ownerRule));
NS_ENSURE_TRUE(ownerRule, NS_ERROR_UNEXPECTED);
nsresult rv = mDocument->BeginUpdate();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIStyleRule> styleRule(do_QueryInterface(ownerRule));
rv = mDocument->StyleRuleAdded(this, styleRule);
NS_ENSURE_SUCCESS(rv, rv);
rv = mDocument->EndUpdate();
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
// XXX for backwards compatibility and convenience
NS_EXPORT nsresult
NS_NewCSSStyleSheet(nsICSSStyleSheet** aInstancePtrResult, nsIURI* aURL)
{
nsICSSStyleSheet* sheet;
nsresult rv;
if (NS_FAILED(rv = NS_NewCSSStyleSheet(&sheet)))
return rv;
if (NS_FAILED(rv = sheet->Init(aURL))) {
NS_RELEASE(sheet);
return rv;
}
*aInstancePtrResult = sheet;
return NS_OK;
}
NS_EXPORT nsresult
NS_NewCSSStyleSheet(nsICSSStyleSheet** aInstancePtrResult)
{
if (aInstancePtrResult == nsnull) {
return NS_ERROR_NULL_POINTER;
}
CSSStyleSheetImpl *it = new CSSStyleSheetImpl();
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(it);
*aInstancePtrResult = it;
return NS_OK;
}
// -------------------------------
// CSS Style rule processor implementation
//
CSSRuleProcessor::CSSRuleProcessor(void)
: mSheets(nsnull),
mRuleCascades(nsnull)
{
NS_INIT_ISUPPORTS();
}
static PRBool
DropProcessorReference(nsISupports* aSheet, void* aProcessor)
{
nsICSSStyleSheet* sheet = (nsICSSStyleSheet*)aSheet;
nsICSSStyleRuleProcessor* processor = (nsICSSStyleRuleProcessor*)aProcessor;
sheet->DropRuleProcessorReference(processor);
return PR_TRUE;
}
CSSRuleProcessor::~CSSRuleProcessor(void)
{
if (mSheets) {
mSheets->EnumerateForwards(DropProcessorReference, this);
NS_RELEASE(mSheets);
}
ClearRuleCascades();
}
NS_IMPL_ADDREF(CSSRuleProcessor);
NS_IMPL_RELEASE(CSSRuleProcessor);
nsresult
CSSRuleProcessor::QueryInterface(REFNSIID aIID, void** aInstancePtrResult)
{
if (NULL == aInstancePtrResult) {
return NS_ERROR_NULL_POINTER;
}
static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
if (aIID.Equals(NS_GET_IID(nsICSSStyleRuleProcessor))) {
*aInstancePtrResult = (void*)this;
NS_ADDREF_THIS();
return NS_OK;
}
if (aIID.Equals(NS_GET_IID(nsIStyleRuleProcessor))) {
*aInstancePtrResult = (void*)this;
NS_ADDREF_THIS();
return NS_OK;
}
if (aIID.Equals(kISupportsIID)) {
*aInstancePtrResult = (void*)this;
NS_ADDREF_THIS();
return NS_OK;
}
return NS_NOINTERFACE;
}
NS_IMETHODIMP
CSSRuleProcessor::AppendStyleSheet(nsICSSStyleSheet* aStyleSheet)
{
nsresult result = NS_OK;
if (! mSheets) {
result = NS_NewISupportsArray(&mSheets);
}
if (mSheets) {
mSheets->AppendElement(aStyleSheet);
}
return result;
}
MOZ_DECL_CTOR_COUNTER(RuleProcessorData)
RuleProcessorData::RuleProcessorData(nsIPresContext* aPresContext,
nsIContent* aContent,
nsRuleWalker* aRuleWalker,
nsCompatibility* aCompat /*= nsnull*/)
{
MOZ_COUNT_CTOR(RuleProcessorData);
NS_PRECONDITION(aPresContext, "null pointer");
NS_ASSERTION(!aContent || aContent->IsContentOfType(nsIContent::eELEMENT),
"non-element leaked into SelectorMatches");
mPresContext = aPresContext;
mContent = aContent;
mParentContent = nsnull;
mRuleWalker = aRuleWalker;
mScopedRoot = nsnull;
mContentTag = nsnull;
mContentID = nsnull;
mStyledContent = nsnull;
mIsHTMLContent = PR_FALSE;
mIsHTMLLink = PR_FALSE;
mIsSimpleXLink = PR_FALSE;
mIsChecked = PR_FALSE;
mLinkState = eLinkState_Unknown;
mEventState = NS_EVENT_STATE_UNSPECIFIED;
mNameSpaceID = kNameSpaceID_Unknown;
mPreviousSiblingData = nsnull;
mParentData = nsnull;
mLanguage = nsnull;
// get the compat. mode (unless it is provided)
if(!aCompat) {
mPresContext->GetCompatibilityMode(&mCompatMode);
} else {
mCompatMode = *aCompat;
}
if(aContent){
// we hold no ref to the content...
mContent = aContent;
// get the namespace
aContent->GetNameSpaceID(mNameSpaceID);
// get the tag and parent
aContent->GetTag(mContentTag);
aContent->GetParent(mParentContent);
// get the event state
nsIEventStateManager* eventStateManager = nsnull;
mPresContext->GetEventStateManager(&eventStateManager);
if(eventStateManager) {
eventStateManager->GetContentState(aContent, mEventState);
NS_RELEASE(eventStateManager);
}
// get the styledcontent interface and the ID
if (NS_SUCCEEDED(aContent->QueryInterface(NS_GET_IID(nsIStyledContent), (void**)&mStyledContent))) {
NS_ASSERTION(mStyledContent, "Succeeded but returned null");
mStyledContent->GetID(mContentID);
}
// see if there are attributes for the content
PRInt32 attrCount = 0;
aContent->GetAttrCount(attrCount);
mHasAttributes = PRBool(attrCount > 0);
// check for HTMLContent and Link status
if (aContent->IsContentOfType(nsIContent::eHTML))
mIsHTMLContent = PR_TRUE;
// if HTML content and it has some attributes, check for an HTML link
// NOTE: optimization: cannot be a link if no attributes (since it needs an href)
if (mIsHTMLContent && mHasAttributes) {
// check if it is an HTML Link
if(nsStyleUtil::IsHTMLLink(aContent, mContentTag, mPresContext, &mLinkState)) {
mIsHTMLLink = PR_TRUE;
}
}
// if not an HTML link, check for a simple xlink (cannot be both HTML link and xlink)
// NOTE: optimization: cannot be an XLink if no attributes (since it needs an
if(!mIsHTMLLink &&
mHasAttributes &&
!(mIsHTMLContent || aContent->IsContentOfType(nsIContent::eXUL)) &&
nsStyleUtil::IsSimpleXlink(aContent, mPresContext, &mLinkState)) {
mIsSimpleXLink = PR_TRUE;
}
if (mIsHTMLContent) {
PRBool isChecked = PR_FALSE;
if (mContentTag == nsHTMLAtoms::option) {
nsCOMPtr<nsIDOMHTMLOptionElement> optEl = do_QueryInterface(mContent);
optEl->GetSelected(&isChecked);
} else if (mContentTag == nsHTMLAtoms::input) {
nsCOMPtr<nsIDOMHTMLInputElement> inputEl = do_QueryInterface(mContent);
inputEl->GetChecked(&isChecked);
}
mIsChecked = isChecked;
}
}
}
RuleProcessorData::~RuleProcessorData()
{
MOZ_COUNT_DTOR(RuleProcessorData);
if (mPreviousSiblingData)
mPreviousSiblingData->Destroy(mPresContext);
if (mParentData)
mParentData->Destroy(mPresContext);
NS_IF_RELEASE(mParentContent);
NS_IF_RELEASE(mContentTag);
NS_IF_RELEASE(mContentID);
NS_IF_RELEASE(mStyledContent);
delete mLanguage;
}
const nsString* RuleProcessorData::GetLang(void)
{
if (!mLanguage) {
mLanguage = new nsAutoString();
if (!mLanguage)
return nsnull;
nsCOMPtr<nsIContent> content = mContent;
while (content) {
PRInt32 attrCount = 0;
content->GetAttrCount(attrCount);
if (attrCount > 0) {
// xml:lang has precedence over lang on HTML elements (see
// XHTML1 section C.7).
nsAutoString value;
nsresult attrState = content->GetAttr(kNameSpaceID_XML,
nsHTMLAtoms::lang, value);
if (attrState != NS_CONTENT_ATTR_HAS_VALUE &&
content->IsContentOfType(nsIContent::eHTML)) {
attrState = content->GetAttr(kNameSpaceID_None,
nsHTMLAtoms::lang, value);
}
if (attrState == NS_CONTENT_ATTR_HAS_VALUE) {
*mLanguage = value;
break;
}
}
nsIContent *parent;
content->GetParent(parent);
content = dont_AddRef(parent);
}
}
return mLanguage;
}
static const PRUnichar kNullCh = PRUnichar('\0');
static PRBool ValueIncludes(const nsString& aValueList, const nsString& aValue, PRBool aCaseSensitive)
{
nsAutoString valueList(aValueList);
valueList.Append(kNullCh); // put an extra null at the end
PRUnichar* value = (PRUnichar*)(const PRUnichar*)aValue.get();
PRUnichar* start = (PRUnichar*)(const PRUnichar*)valueList.get();
PRUnichar* end = start;
while (kNullCh != *start) {
while ((kNullCh != *start) && nsCRT::IsAsciiSpace(*start)) { // skip leading space
start++;
}
end = start;
while ((kNullCh != *end) && (PR_FALSE == nsCRT::IsAsciiSpace(*end))) { // look for space or end
end++;
}
*end = kNullCh; // end string here
if (start < end) {
if (aCaseSensitive) {
if (!nsCRT::strcmp(value, start)) {
return PR_TRUE;
}
}
else {
if (nsDependentString(value).Equals(nsDependentString(start),
nsCaseInsensitiveStringComparator())) {
return PR_TRUE;
}
}
}
start = ++end;
}
return PR_FALSE;
}
inline PRBool IsEventPseudo(nsIAtom* aAtom)
{
return PRBool ((nsCSSPseudoClasses::active == aAtom) ||
(nsCSSPseudoClasses::mozDragOver == aAtom) ||
(nsCSSPseudoClasses::focus == aAtom) ||
(nsCSSPseudoClasses::hover == aAtom));
// XXX selected, enabled, disabled, selection?
}
inline PRBool IsLinkPseudo(nsIAtom* aAtom)
{
return PRBool ((nsCSSPseudoClasses::link == aAtom) ||
(nsCSSPseudoClasses::visited == aAtom) ||
(nsCSSPseudoClasses::mozAnyLink == aAtom));
}
// Return whether we should apply a "global" (i.e., universal-tag)
// selector for event states in quirks mode. Note that
// |data.mIsHTMLLink| is checked separately by the caller, so we return
// false for |nsHTMLAtoms::a|, which here means a named anchor.
inline PRBool IsQuirkEventSensitive(nsIAtom *aContentTag)
{
return PRBool ((nsHTMLAtoms::button == aContentTag) ||
(nsHTMLAtoms::img == aContentTag) ||
(nsHTMLAtoms::input == aContentTag) ||
(nsHTMLAtoms::label == aContentTag) ||
(nsHTMLAtoms::select == aContentTag) ||
(nsHTMLAtoms::textarea == aContentTag));
}
static PRBool IsSignificantChild(nsIContent* aChild, PRBool aAcceptNonWhitespaceText)
{
nsIAtom* tag;
aChild->GetTag(tag); // skip text & comments
if ((tag != nsLayoutAtoms::textTagName) &&
(tag != nsLayoutAtoms::commentTagName) &&
(tag != nsLayoutAtoms::processingInstructionTagName)) {
NS_IF_RELEASE(tag);
return PR_TRUE;
}
if (aAcceptNonWhitespaceText) {
if (tag == nsLayoutAtoms::textTagName) { // skip only whitespace text
nsITextContent* text = nsnull;
if (NS_SUCCEEDED(aChild->QueryInterface(NS_GET_IID(nsITextContent), (void**)&text))) {
PRBool isWhite;
text->IsOnlyWhitespace(&isWhite);
NS_RELEASE(text);
if (! isWhite) {
NS_RELEASE(tag);
return PR_TRUE;
}
}
}
}
NS_IF_RELEASE(tag);
return PR_FALSE;
}
static PRBool
DashMatchCompare(const nsAString& aAttributeValue,
const nsAString& aSelectorValue,
const PRBool aCaseSensitive)
{
PRBool result;
PRUint32 selectorLen = aSelectorValue.Length();
PRUint32 attributeLen = aAttributeValue.Length();
if (selectorLen > attributeLen) {
result = PR_FALSE;
}
else {
nsAString::const_iterator iter;
if (selectorLen != attributeLen &&
*aAttributeValue.BeginReading(iter).advance(selectorLen) !=
PRUnichar('-')) {
// to match, the aAttributeValue must have a dash after the end of
// the aSelectorValue's text (unless the aSelectorValue and the
// aAttributeValue have the same text)
result = PR_FALSE;
}
else {
const nsAString& attributeSubstring =
Substring(aAttributeValue, 0, selectorLen);
if (aCaseSensitive)
result = attributeSubstring.Equals(aSelectorValue);
else
result = attributeSubstring.Equals(aSelectorValue,
nsCaseInsensitiveStringComparator());
}
}
return result;
}
// NOTE: The |aStateMask| code isn't going to work correctly anymore if
// we start batching style changes, because if multiple states change in
// separate notifications then we might determine the style is not
// state-dependent when it really is (e.g., determining that a
// :hover:active rule no longer matches when both states are unset).
static PRBool SelectorMatches(RuleProcessorData &data,
nsCSSSelector* aSelector,
PRInt32 aStateMask, // states NOT to test
PRInt8 aNegationIndex)
{
// if we are dealing with negations, reverse the values of PR_TRUE and PR_FALSE
PRBool localFalse = PRBool(0 < aNegationIndex);
PRBool localTrue = PRBool(0 == aNegationIndex);
PRBool result = localTrue;
// Do not perform the test if aNegationIndex==1
// because it then contains only negated IDs, classes, attributes and pseudo-
// classes
if (1 != aNegationIndex) {
if (kNameSpaceID_Unknown != aSelector->mNameSpace) {
if (data.mNameSpaceID != aSelector->mNameSpace) {
result = localFalse;
}
}
if (localTrue == result) {
if ((nsnull != aSelector->mTag) && (aSelector->mTag != data.mContentTag)) {
result = localFalse;
}
}
// optimization : bail out early if we can
if (!result) {
return PR_FALSE;
}
}
result = PR_TRUE;
if (nsnull != aSelector->mPseudoClassList) { // test for pseudo class match
// first-child, root, lang, active, focus, hover, link, visited...
// XXX disabled, enabled, selected, selection
nsAtomStringList* pseudoClass = aSelector->mPseudoClassList;
while (result && (nsnull != pseudoClass)) {
if ((nsCSSPseudoClasses::firstChild == pseudoClass->mAtom) ||
(nsCSSPseudoClasses::firstNode == pseudoClass->mAtom) ) {
nsIContent* firstChild = nsnull;
nsIContent* parent = data.mParentContent;
if (parent) {
PRInt32 index = -1;
do {
parent->ChildAt(++index, firstChild);
if (firstChild) { // stop at first non-comment and non-whitespace node (and non-text node for firstChild)
if (IsSignificantChild(firstChild, (nsCSSPseudoClasses::firstNode == pseudoClass->mAtom))) {
break;
}
NS_RELEASE(firstChild);
}
else {
break;
}
} while (1 == 1);
}
result = PRBool(localTrue == (data.mContent == firstChild));
NS_IF_RELEASE(firstChild);
}
else if ((nsCSSPseudoClasses::lastChild == pseudoClass->mAtom) ||
(nsCSSPseudoClasses::lastNode == pseudoClass->mAtom)) {
nsIContent* lastChild = nsnull;
nsIContent* parent = data.mParentContent;
if (parent) {
PRInt32 index;
parent->ChildCount(index);
do {
parent->ChildAt(--index, lastChild);
if (lastChild) { // stop at first non-comment and non-whitespace node (and non-text node for lastChild)
if (IsSignificantChild(lastChild, (nsCSSPseudoClasses::lastNode == pseudoClass->mAtom))) {
break;
}
NS_RELEASE(lastChild);
}
else {
break;
}
} while (1 == 1);
}
result = PRBool(localTrue == (data.mContent == lastChild));
NS_IF_RELEASE(lastChild);
}
else if (nsCSSPseudoClasses::empty == pseudoClass->mAtom) {
nsIContent* child = nsnull;
nsIContent* element = data.mContent;
PRInt32 index = -1;
do {
element->ChildAt(++index, child);
if (child) { // stop at first non-comment and non-whitespace node
if (IsSignificantChild(child, PR_TRUE)) {
break;
}
NS_RELEASE(child);
}
else {
break;
}
} while (1 == 1);
result = PRBool(localTrue == (child == nsnull));
NS_IF_RELEASE(child);
}
else if (nsCSSPseudoClasses::root == pseudoClass->mAtom) {
if (data.mParentContent) {
result = localFalse;
}
else {
result = localTrue;
}
}
else if (nsCSSPseudoClasses::mozBoundElement == pseudoClass->mAtom) {
result = (data.mScopedRoot && data.mScopedRoot == data.mContent)
? localTrue : localFalse;
}
else if (nsCSSPseudoClasses::lang == pseudoClass->mAtom) {
NS_ASSERTION(nsnull != pseudoClass->mString, "null lang parameter");
result = localFalse;
if (pseudoClass->mString && *pseudoClass->mString) {
// We have to determine the language of the current element. Since
// this is currently no property and since the language is inherited
// from the parent we have to be prepared to look at all parent
// nodes. The language itself is encoded in the LANG attribute.
const nsString* lang = data.GetLang();
if (lang && !lang->IsEmpty()) { // null check for out-of-memory
result = localTrue == DashMatchCompare(*lang,
nsDependentString(pseudoClass->mString), PR_FALSE);
}
else {
nsCOMPtr<nsIDocument> doc;
data.mContent->GetDocument(*getter_AddRefs(doc));
if (doc) {
// Try to get the language from the HTTP header or if this
// is missing as well from the preferences.
// The content language can be a comma-separated list of
// language codes.
nsAutoString language;
if (NS_SUCCEEDED(doc->GetContentLanguage(language))) {
nsDependentString langString(pseudoClass->mString);
language.StripWhitespace();
PRInt32 begin = 0;
PRInt32 len = language.Length();
while (begin < len) {
PRInt32 end = language.FindChar(PRUnichar(','), begin);
if (end == kNotFound) {
end = len;
}
if (DashMatchCompare(Substring(language, begin, end-begin),
langString,
PR_FALSE)) {
result = localTrue;
break;
}
begin = end + 1;
}
}
}
}
}
}
else if (IsEventPseudo(pseudoClass->mAtom)) {
// check if the element is event-sensitive
if (data.mCompatMode == eCompatibility_NavQuirks &&
// global selector:
!aSelector->mTag && !aSelector->mClassList &&
!aSelector->mIDList && !aSelector->mAttrList &&
// :hover or :active
(nsCSSPseudoClasses::active == pseudoClass->mAtom ||
nsCSSPseudoClasses::hover == pseudoClass->mAtom) &&
// important for |IsQuirkEventSensitive|:
data.mIsHTMLContent && !data.mIsHTMLLink &&
!IsQuirkEventSensitive(data.mContentTag)) {
// In quirks mode, only make certain elements sensitive to
// selectors ":hover" and ":active".
// XXX Once we make ":active" work correctly (bug 65917) this
// quirk should apply only to ":hover" (if to anything at all).
result = localFalse;
} else {
if (nsCSSPseudoClasses::active == pseudoClass->mAtom) {
result = (aStateMask & NS_EVENT_STATE_ACTIVE) ||
(localTrue == (0 != (data.mEventState & NS_EVENT_STATE_ACTIVE)));
}
else if (nsCSSPseudoClasses::focus == pseudoClass->mAtom) {
result = (aStateMask & NS_EVENT_STATE_FOCUS) ||
(localTrue == (0 != (data.mEventState & NS_EVENT_STATE_FOCUS)));
}
else if (nsCSSPseudoClasses::hover == pseudoClass->mAtom) {
result = (aStateMask & NS_EVENT_STATE_HOVER) ||
(localTrue == (0 != (data.mEventState & NS_EVENT_STATE_HOVER)));
}
else if (nsCSSPseudoClasses::mozDragOver == pseudoClass->mAtom) {
result = (aStateMask & NS_EVENT_STATE_DRAGOVER) ||
(localTrue == (0 != (data.mEventState & NS_EVENT_STATE_DRAGOVER)));
}
}
}
else if (IsLinkPseudo(pseudoClass->mAtom)) {
if (data.mIsHTMLLink || data.mIsSimpleXLink) {
if (result) {
if (nsCSSPseudoClasses::mozAnyLink == pseudoClass->mAtom) {
result = localTrue;
}
else if (nsCSSPseudoClasses::link == pseudoClass->mAtom) {
result = PRBool(localTrue == (eLinkState_Unvisited == data.mLinkState));
}
else if (nsCSSPseudoClasses::visited == pseudoClass->mAtom) {
result = PRBool(localTrue == (eLinkState_Visited == data.mLinkState));
}
}
}
else {
result = localFalse; // not a link
}
}
else if (nsCSSPseudoClasses::checked == pseudoClass->mAtom) {
// This pseudoclass matches the selected state on the following elements:
// <option>
// <input type=checkbox>
// <input type=radio>
if (!(aStateMask & NS_EVENT_STATE_CHECKED))
result = data.mIsChecked ? localTrue : localFalse;
}
else {
result = localFalse; // unknown pseudo class
}
pseudoClass = pseudoClass->mNext;
}
}
// namespace/tag match
if (result && aSelector->mAttrList) { // test for attribute match
// if no attributes on the content, no match
if(!data.mHasAttributes) {
result = localFalse;
} else {
result = localTrue;
nsAttrSelector* attr = aSelector->mAttrList;
do {
if (!data.mContent->HasAttr(attr->mNameSpace, attr->mAttr)) {
result = localFalse;
}
else if (attr->mFunction != NS_ATTR_FUNC_SET) {
nsAutoString value;
#ifdef DEBUG
nsresult attrState =
#endif
data.mContent->GetAttr(attr->mNameSpace, attr->mAttr, value);
NS_ASSERTION(NS_SUCCEEDED(attrState) &&
NS_CONTENT_ATTR_NOT_THERE != attrState,
"HasAttr lied or GetAttr failed");
PRBool isCaseSensitive = attr->mCaseSensitive;
switch (attr->mFunction) {
case NS_ATTR_FUNC_EQUALS:
if (isCaseSensitive) {
result = localTrue == value.Equals(attr->mValue);
}
else {
result = localTrue == value.Equals(attr->mValue, nsCaseInsensitiveStringComparator());
}
break;
case NS_ATTR_FUNC_INCLUDES:
result = localTrue == ValueIncludes(value, attr->mValue, isCaseSensitive);
break;
case NS_ATTR_FUNC_DASHMATCH:
result = localTrue == DashMatchCompare(value, attr->mValue, isCaseSensitive);
break;
case NS_ATTR_FUNC_ENDSMATCH:
{
PRUint32 selLen = attr->mValue.Length();
PRUint32 valLen = value.Length();
if (selLen > valLen) {
result = localFalse;
} else {
if (isCaseSensitive)
result = localTrue == Substring(value, valLen - selLen, selLen).Equals(attr->mValue, nsDefaultStringComparator());
else
result = localTrue == Substring(value, valLen - selLen, selLen).Equals(attr->mValue, nsCaseInsensitiveStringComparator());
}
}
break;
case NS_ATTR_FUNC_BEGINSMATCH:
{
PRUint32 selLen = attr->mValue.Length();
PRUint32 valLen = value.Length();
if (selLen > valLen) {
result = localFalse;
} else {
if (isCaseSensitive)
result = localTrue == Substring(value, 0, selLen).Equals(attr->mValue, nsDefaultStringComparator());
else
result = localTrue == Substring(value, 0, selLen).Equals(attr->mValue, nsCaseInsensitiveStringComparator());
}
}
break;
case NS_ATTR_FUNC_CONTAINSMATCH:
result = localTrue == (FindInReadable(attr->mValue, value, nsCaseInsensitiveStringComparator()));
break;
}
}
attr = attr->mNext;
} while (result && (nsnull != attr));
}
}
if (result && (aSelector->mIDList || aSelector->mClassList)) {
// test for ID & class match
result = localFalse;
if (data.mStyledContent) {
// case sensitivity: bug 93371
PRBool isCaseSensitive = data.mCompatMode != eCompatibility_NavQuirks;
nsAtomList* IDList = aSelector->mIDList;
if (nsnull == IDList) {
result = PR_TRUE;
}
else if (nsnull != data.mContentID) {
result = PR_TRUE;
if (isCaseSensitive) {
do {
if (localTrue == (IDList->mAtom != data.mContentID)) {
result = PR_FALSE;
break;
}
IDList = IDList->mNext;
} while (IDList);
} else {
const PRUnichar* id1Str;
data.mContentID->GetUnicode(&id1Str);
nsDependentString id1(id1Str);
do {
const PRUnichar* id2Str;
IDList->mAtom->GetUnicode(&id2Str);
nsDependentString id2(id2Str);
if (localTrue !=
id1.Equals(id2, nsCaseInsensitiveStringComparator())) {
result = PR_FALSE;
break;
}
IDList = IDList->mNext;
} while (IDList);
}
}
if (result) {
nsAtomList* classList = aSelector->mClassList;
while (nsnull != classList) {
if (localTrue == (!data.mStyledContent->HasClass(classList->mAtom, isCaseSensitive))) {
result = PR_FALSE;
break;
}
classList = classList->mNext;
}
}
}
}
// apply SelectorMatches to the negated selectors in the chain
if (result && (nsnull != aSelector->mNegations)) {
result = SelectorMatches(data, aSelector->mNegations, aStateMask,
aNegationIndex+1);
}
return result;
}
static PRBool SelectorMatchesTree(RuleProcessorData &data,
nsCSSSelector* aSelector)
{
nsCSSSelector* selector = aSelector;
if (selector) {
nsIContent* content = nsnull;
nsIContent* lastContent = data.mContent;
NS_ADDREF(lastContent);
RuleProcessorData* curdata = &data;
while (nsnull != selector) { // check compound selectors
// Find the appropriate content (whether parent or previous sibling)
// to check next, and if we don't already have a RuleProcessorData
// for it, create one.
// for adjacent sibling combinators, the content to test against the
// selector is the previous sibling
nsCompatibility compat = curdata->mCompatMode;
RuleProcessorData* newdata;
if (PRUnichar('+') == selector->mOperator) {
newdata = curdata->mPreviousSiblingData;
if (!newdata) {
nsIContent* parent;
PRInt32 index;
lastContent->GetParent(parent);
if (parent) {
parent->IndexOf(lastContent, index);
while (0 <= --index) { // skip text & comment nodes
parent->ChildAt(index, content);
nsIAtom* tag;
content->GetTag(tag);
if ((tag != nsLayoutAtoms::textTagName) &&
(tag != nsLayoutAtoms::commentTagName)) {
NS_IF_RELEASE(tag);
newdata =
new (curdata->mPresContext) RuleProcessorData(curdata->mPresContext, content,
curdata->mRuleWalker, &compat);
curdata->mPreviousSiblingData = newdata;
break;
}
NS_RELEASE(content);
NS_IF_RELEASE(tag);
}
NS_RELEASE(parent);
}
} else {
content = newdata->mContent;
NS_ADDREF(content);
}
}
// for descendant combinators and child combinators, the content
// to test against is the parent
else {
newdata = curdata->mParentData;
if (!newdata) {
lastContent->GetParent(content);
if (content) {
newdata = new (curdata->mPresContext) RuleProcessorData(curdata->mPresContext, content,
curdata->mRuleWalker, &compat);
curdata->mParentData = newdata;
}
} else {
content = newdata->mContent;
NS_ADDREF(content);
}
}
if (! newdata) {
NS_ASSERTION(!content, "content must be null");
break;
}
if (SelectorMatches(*newdata, selector, 0, 0)) {
// to avoid greedy matching, we need to recurse if this is a
// descendant combinator and the next combinator is not
if ((NS_IS_GREEDY_OPERATOR(selector->mOperator)) &&
(selector->mNext) &&
(!NS_IS_GREEDY_OPERATOR(selector->mNext->mOperator))) {
// pretend the selector didn't match, and step through content
// while testing the same selector
// This approach is slightly strange is that when it recurses
// it tests from the top of the content tree, down. This
// doesn't matter much for performance since most selectors
// don't match. (If most did, it might be faster...)
if (SelectorMatchesTree(*newdata, selector)) {
selector = nsnull; // indicate success
break;
}
}
selector = selector->mNext;
}
else {
// for adjacent sibling and child combinators, if we didn't find
// a match, we're done
if (!NS_IS_GREEDY_OPERATOR(selector->mOperator)) {
NS_RELEASE(content);
break; // parent was required to match
}
}
NS_IF_RELEASE(lastContent);
lastContent = content; // take refcount
content = nsnull;
curdata = newdata;
}
NS_IF_RELEASE(lastContent);
}
return PRBool(nsnull == selector); // matches if ran out of selectors
}
static void ContentEnumFunc(nsICSSStyleRule* aRule, void* aData)
{
ElementRuleProcessorData* data = (ElementRuleProcessorData*)aData;
nsCSSSelector* selector = aRule->FirstSelector();
if (SelectorMatches(*data, selector, 0, 0)) {
selector = selector->mNext;
if (SelectorMatchesTree(*data, selector)) {
// for performance, require that every implementation of
// nsICSSStyleRule return the same pointer for nsIStyleRule (why
// would anything multiply inherit nsIStyleRule anyway?)
#ifdef DEBUG
nsCOMPtr<nsIStyleRule> iRule = do_QueryInterface(aRule);
NS_ASSERTION(NS_STATIC_CAST(nsIStyleRule*, aRule) == iRule.get(),
"Please fix QI so this performance optimization is valid");
#endif
data->mRuleWalker->Forward(NS_STATIC_CAST(nsIStyleRule*, aRule));
// nsStyleSet will deal with the !important rule
}
}
}
NS_IMETHODIMP
CSSRuleProcessor::RulesMatching(ElementRuleProcessorData *aData,
nsIAtom* aMedium)
{
NS_PRECONDITION(aData->mContent->IsContentOfType(nsIContent::eELEMENT),
"content must be element");
RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext, aMedium);
if (cascade) {
nsAutoVoidArray classArray;
nsIStyledContent* styledContent = aData->mStyledContent;
if (styledContent)
styledContent->GetClasses(classArray);
cascade->mRuleHash.EnumerateAllRules(aData->mNameSpaceID, aData->mContentTag, aData->mContentID, classArray, ContentEnumFunc, aData);
}
return NS_OK;
}
static void PseudoEnumFunc(nsICSSStyleRule* aRule, void* aData)
{
PseudoRuleProcessorData* data = (PseudoRuleProcessorData*)aData;
nsCSSSelector* selector = aRule->FirstSelector();
NS_ASSERTION(selector->mTag == data->mPseudoTag, "RuleHash failure");
PRBool matches = PR_TRUE;
if (data->mComparator)
data->mComparator->PseudoMatches(data->mPseudoTag, selector, &matches);
if (matches) {
selector = selector->mNext;
if (selector) { // test next selector specially
if (PRUnichar('+') == selector->mOperator) {
return; // not valid here, can't match
}
if (SelectorMatches(*data, selector, 0, 0)) {
selector = selector->mNext;
}
else {
if (PRUnichar('>') == selector->mOperator) {
return; // immediate parent didn't match
}
}
}
if (selector &&
(! SelectorMatchesTree(*data, selector))) {
return; // remaining selectors didn't match
}
// for performance, require that every implementation of
// nsICSSStyleRule return the same pointer for nsIStyleRule (why
// would anything multiply inherit nsIStyleRule anyway?)
#ifdef DEBUG
nsCOMPtr<nsIStyleRule> iRule = do_QueryInterface(aRule);
NS_ASSERTION(NS_STATIC_CAST(nsIStyleRule*, aRule) == iRule.get(),
"Please fix QI so this performance optimization is valid");
#endif
data->mRuleWalker->Forward(NS_STATIC_CAST(nsIStyleRule*, aRule));
// nsStyleSet will deal with the !important rule
}
}
NS_IMETHODIMP
CSSRuleProcessor::RulesMatching(PseudoRuleProcessorData* aData,
nsIAtom* aMedium)
{
NS_PRECONDITION(!aData->mContent ||
aData->mContent->IsContentOfType(nsIContent::eELEMENT),
"content (if present) must be element");
RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext, aMedium);
if (cascade) {
cascade->mRuleHash.EnumerateTagRules(aData->mPseudoTag,
PseudoEnumFunc, aData);
}
return NS_OK;
}
static
PRBool PR_CALLBACK StateEnumFunc(void* aSelector, void* aData)
{
StateRuleProcessorData* data = (StateRuleProcessorData*)aData;
nsCSSSelector* selector = (nsCSSSelector*)aSelector;
if (SelectorMatches(*data, selector, data->mStateMask, 0)) {
selector = selector->mNext;
if (SelectorMatchesTree(*data, selector)) {
return PR_FALSE;
}
}
return PR_TRUE;
}
NS_IMETHODIMP
CSSRuleProcessor::HasStateDependentStyle(StateRuleProcessorData* aData,
nsIAtom* aMedium,
PRBool* aResult)
{
NS_PRECONDITION(aData->mContent->IsContentOfType(nsIContent::eELEMENT),
"content must be element");
RuleCascadeData* cascade = GetRuleCascade(aData->mPresContext, aMedium);
// Look up the content node in the state rule list, which points to
// any (CSS2 definition) simple selector (whether or not it is the
// subject) that has a state pseudo-class on it. This means that this
// code will be matching selectors that aren't real selectors in any
// stylesheet (e.g., if there is a selector "body > p:hover > a", then
// "body > p:hover" will be in |cascade->mStateSelectors|). Note that
// |IsStateSelector| below determines which selectors are in
// |cascade->mStateSelectors|.
// The enumeration function signals the presence of state by stopping
// the enumeration, which is reflected in the return value from
// |EnumerateForwards|.
*aResult = cascade &&
!cascade->mStateSelectors.EnumerateForwards(StateEnumFunc, aData);
return NS_OK;
}
#ifdef DEBUG
struct CascadeSizeEnumData {
CascadeSizeEnumData(nsISizeOfHandler *aSizeOfHandler,
nsUniqueStyleItems *aUniqueStyleItem,
nsIAtom *aTag)
{
handler = aSizeOfHandler;
uniqueItems = aUniqueStyleItem;
tag = aTag;
}
// weak references all 'round
nsISizeOfHandler *handler;
nsUniqueStyleItems *uniqueItems;
nsIAtom *tag;
};
static
PRBool PR_CALLBACK StateSelectorsSizeEnumFunc( void *aSelector, void *aData )
{
nsCSSSelector* selector = (nsCSSSelector*)aSelector;
CascadeSizeEnumData *pData = (CascadeSizeEnumData *)aData;
NS_ASSERTION(selector && pData, "null arguments not supported");
if(! pData->uniqueItems->AddItem((void*)selector)){
return PR_TRUE;
}
// pass the call to the selector
PRUint32 localSize = 0;
selector->SizeOf(pData->handler, localSize);
return PR_TRUE;
}
static
PRBool WeightedRulesSizeEnumFunc( nsISupports *aRule, void *aData )
{
nsICSSStyleRule* rule = (nsICSSStyleRule*)aRule;
CascadeSizeEnumData *pData = (CascadeSizeEnumData *)aData;
NS_ASSERTION(rule && pData, "null arguments not supported");
if(! pData->uniqueItems->AddItem((void*)rule)){
return PR_TRUE;
}
PRUint32 localSize=0;
// pass the call to the rule
rule->SizeOf(pData->handler, localSize);
return PR_TRUE;
}
static
void CascadeSizeEnumFunc(RuleCascadeData *cascade, CascadeSizeEnumData *pData)
{
NS_ASSERTION(cascade && pData, "null arguments not supported");
// see if the cascade has already been counted
if(!(pData->uniqueItems->AddItem(cascade))){
return;
}
// record the size of the cascade data itself
PRUint32 localSize = sizeof(RuleCascadeData);
pData->handler->AddSize(pData->tag, localSize);
// next add up the selectors and the weighted rules for the cascade
nsCOMPtr<nsIAtom> stateSelectorSizeTag;
stateSelectorSizeTag = getter_AddRefs(NS_NewAtom("CascadeStateSelectors"));
CascadeSizeEnumData stateData(pData->handler,pData->uniqueItems,stateSelectorSizeTag);
cascade->mStateSelectors.EnumerateForwards(StateSelectorsSizeEnumFunc, &stateData);
if(cascade->mWeightedRules){
nsCOMPtr<nsIAtom> weightedRulesSizeTag;
weightedRulesSizeTag = getter_AddRefs(NS_NewAtom("CascadeWeightedRules"));
CascadeSizeEnumData stateData2(pData->handler,pData->uniqueItems,weightedRulesSizeTag);
cascade->mWeightedRules->EnumerateForwards(WeightedRulesSizeEnumFunc, &stateData2);
}
}
/******************************************************************************
* SizeOf method:
*
* Self (reported as CSSRuleProcessor's size):
* 1) sizeof(*this)
*
* Contained / Aggregated data (not reported as CSSRuleProcessor's size):
* 1) Delegate to the StyleSheets in the mSheets collection
* 2) Delegate to the Rules in the CascadeTable
*
* Children / siblings / parents:
* none
*
******************************************************************************/
void CSSRuleProcessor::SizeOf(nsISizeOfHandler *aSizeOfHandler, PRUint32 &aSize)
{
NS_ASSERTION(aSizeOfHandler != nsnull, "SizeOf handler cannot be null");
// first get the unique items collection
UNIQUE_STYLE_ITEMS(uniqueItems);
if(! uniqueItems->AddItem((void*)this)){
return;
}
// create a tag for this instance
nsCOMPtr<nsIAtom> tag;
tag = getter_AddRefs(NS_NewAtom("CSSRuleProcessor"));
// get the size of an empty instance and add to the sizeof handler
aSize = sizeof(CSSRuleProcessor);
// collect sizes for the data
// - mSheets
// - mRuleCascades
// sheets first
if(mSheets && uniqueItems->AddItem(mSheets)){
PRUint32 sheetCount, curSheet, localSize2;
mSheets->Count(&sheetCount);
for(curSheet=0; curSheet < sheetCount; curSheet++){
nsCOMPtr<nsICSSStyleSheet> pSheet =
dont_AddRef((nsICSSStyleSheet*)mSheets->ElementAt(curSheet));
if(pSheet && uniqueItems->AddItem((void*)pSheet)){
pSheet->SizeOf(aSizeOfHandler, localSize2);
// XXX aSize += localSize2;
}
}
}
// and for the medium cascade table we account for the hash table overhead,
// and then compute the sizeof each rule-cascade in the table
{
nsCOMPtr<nsIAtom> tag2 = getter_AddRefs(NS_NewAtom("RuleCascade"));
CascadeSizeEnumData data(aSizeOfHandler, uniqueItems, tag2);
for (RuleCascadeData *cascadeData = mRuleCascades;
cascadeData;
cascadeData = cascadeData->mNext) {
CascadeSizeEnumFunc(cascadeData, &data);
}
}
// now add the size of the RuleProcessor
aSizeOfHandler->AddSize(tag,aSize);
}
#endif
NS_IMETHODIMP
CSSRuleProcessor::ClearRuleCascades(void)
{
RuleCascadeData *data = mRuleCascades;
mRuleCascades = nsnull;
while (data) {
RuleCascadeData *next = data->mNext;
delete data;
data = next;
}
return NS_OK;
}
// This function should return true only for selectors that need to be
// checked by |HasStateDependentStyle|.
inline
PRBool IsStateSelector(nsCSSSelector& aSelector)
{
nsAtomStringList* pseudoClass = aSelector.mPseudoClassList;
while (pseudoClass) {
if ((pseudoClass->mAtom == nsCSSPseudoClasses::active) ||
(pseudoClass->mAtom == nsCSSPseudoClasses::checked) ||
(pseudoClass->mAtom == nsCSSPseudoClasses::mozDragOver) ||
(pseudoClass->mAtom == nsCSSPseudoClasses::focus) ||
(pseudoClass->mAtom == nsCSSPseudoClasses::hover)) {
return PR_TRUE;
}
pseudoClass = pseudoClass->mNext;
}
return PR_FALSE;
}
static PRBool
BuildRuleHashAndStateSelectors(nsISupports* aRule, void* aCascade)
{
nsICSSStyleRule* rule = NS_STATIC_CAST(nsICSSStyleRule*, aRule);
RuleCascadeData *cascade = NS_STATIC_CAST(RuleCascadeData*, aCascade);
cascade->mRuleHash.PrependRule(rule);
nsVoidArray* array = &cascade->mStateSelectors;
for (nsCSSSelector* selector = rule->FirstSelector();
selector; selector = selector->mNext)
if (IsStateSelector(*selector))
array->AppendElement(selector);
return PR_TRUE;
}
struct CascadeEnumData {
CascadeEnumData(nsIAtom* aMedium)
: mMedium(aMedium),
mRuleArrays(64)
{
}
nsIAtom* mMedium;
nsSupportsHashtable mRuleArrays; // of nsISupportsArray
};
static PRBool
InsertRuleByWeight(nsISupports* aRule, void* aData)
{
nsICSSRule* rule = (nsICSSRule*)aRule;
CascadeEnumData* data = (CascadeEnumData*)aData;
PRInt32 type = nsICSSRule::UNKNOWN_RULE;
rule->GetType(type);
if (nsICSSRule::STYLE_RULE == type) {
nsICSSStyleRule* styleRule = (nsICSSStyleRule*)rule;
PRInt32 weight = styleRule->GetWeight();
nsPRUint32Key key(weight);
nsCOMPtr<nsISupportsArray> rules(dont_AddRef(
NS_STATIC_CAST(nsISupportsArray*, data->mRuleArrays.Get(&key))));
if (!rules) {
NS_NewISupportsArray(getter_AddRefs(rules));
if (!rules) return PR_FALSE; // out of memory
data->mRuleArrays.Put(&key, rules);
}
rules->AppendElement(styleRule);
}
else if (nsICSSRule::MEDIA_RULE == type) {
nsICSSMediaRule* mediaRule = (nsICSSMediaRule*)rule;
if (mediaRule->UseForMedium(data->mMedium)) {
mediaRule->EnumerateRulesForwards(InsertRuleByWeight, aData);
}
}
return PR_TRUE;
}
PRBool
CSSRuleProcessor::CascadeSheetRulesInto(nsISupports* aSheet, void* aData)
{
nsICSSStyleSheet* iSheet = (nsICSSStyleSheet*)aSheet;
CSSStyleSheetImpl* sheet = (CSSStyleSheetImpl*)iSheet;
CascadeEnumData* data = (CascadeEnumData*)aData;
PRBool bSheetApplicable = PR_TRUE;
sheet->GetApplicable(bSheetApplicable);
if (bSheetApplicable && sheet->UseForMedium(data->mMedium)) {
CSSStyleSheetImpl* child = sheet->mFirstChild;
while (child) {
CascadeSheetRulesInto((nsICSSStyleSheet*)child, data);
child = child->mNext;
}
if (sheet->mInner && sheet->mInner->mOrderedRules) {
sheet->mInner->mOrderedRules->EnumerateForwards(InsertRuleByWeight, data);
}
}
return PR_TRUE;
}
struct RuleArrayData {
PRInt32 mWeight;
nsISupportsArray* mRuleArray;
};
PR_STATIC_CALLBACK(int) CompareArrayData(const void* aArg1, const void* aArg2,
void* closure)
{
const RuleArrayData* arg1 = NS_STATIC_CAST(const RuleArrayData*, aArg1);
const RuleArrayData* arg2 = NS_STATIC_CAST(const RuleArrayData*, aArg2);
return arg1->mWeight - arg2->mWeight; // put lower weight first
}
struct FillArrayData {
FillArrayData(RuleArrayData* aArrayData) :
mIndex(0),
mArrayData(aArrayData)
{
}
PRInt32 mIndex;
RuleArrayData* mArrayData;
};
PR_STATIC_CALLBACK(PRBool) FillArray(nsHashKey* aKey, void* aData,
void* aClosure)
{
nsPRUint32Key* key = NS_STATIC_CAST(nsPRUint32Key*, aKey);
nsISupportsArray* weightArray = NS_STATIC_CAST(nsISupportsArray*, aData);
FillArrayData* data = NS_STATIC_CAST(FillArrayData*, aClosure);
RuleArrayData& ruleData = data->mArrayData[data->mIndex++];
ruleData.mRuleArray = weightArray;
ruleData.mWeight = key->GetValue();
return PR_TRUE;
}
/**
* Takes the hashtable of arrays (keyed by weight, in order sort) and
* puts them all in one big array which has a primary sort by weight
* and secondary sort by order.
*/
static void PutRulesInList(nsSupportsHashtable* aRuleArrays,
nsISupportsArray* aWeightedRules)
{
PRInt32 arrayCount = aRuleArrays->Count();
RuleArrayData* arrayData = new RuleArrayData[arrayCount];
FillArrayData faData(arrayData);
aRuleArrays->Enumerate(FillArray, &faData);
NS_QuickSort(arrayData, arrayCount, sizeof(RuleArrayData),
CompareArrayData, nsnull);
for (PRInt32 i = 0; i < arrayCount; ++i)
aWeightedRules->AppendElements(arrayData[i].mRuleArray);
delete [] arrayData;
}
RuleCascadeData*
CSSRuleProcessor::GetRuleCascade(nsIPresContext* aPresContext, nsIAtom* aMedium)
{
RuleCascadeData **cascadep = &mRuleCascades;
RuleCascadeData *cascade;
while ((cascade = *cascadep)) {
if (cascade->mMedium == aMedium)
return cascade;
cascadep = &cascade->mNext;
}
if (mSheets) {
nsCompatibility quirkMode;
aPresContext->GetCompatibilityMode(&quirkMode);
cascade = new RuleCascadeData(aMedium,
eCompatibility_NavQuirks == quirkMode);
if (cascade) {
*cascadep = cascade;
CascadeEnumData data(aMedium);
mSheets->EnumerateForwards(CascadeSheetRulesInto, &data);
PutRulesInList(&data.mRuleArrays, cascade->mWeightedRules);
cascade->mWeightedRules->EnumerateBackwards(
BuildRuleHashAndStateSelectors, cascade);
}
}
return cascade;
}