/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ // vim:cindent:tabstop=2:expandtab:shiftwidth=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 * Daniel Glazman * * * 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 "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 "nsIDOMCSSRule.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 "nsStyleUtil.h" #include "nsQuickSort.h" #include "nsContentUtils.h" #include "nsIJSContextStack.h" #include "nsIScriptSecurityManager.h" struct RuleValue { /** * |RuleValue|s are constructed before they become part of the * |RuleHash|, to act as rule/selector pairs. |Add| is called when * they are added to the |RuleHash|, and can be considered the second * half of the constructor. */ RuleValue(nsICSSStyleRule* aRule, nsCSSSelector* aSelector) : mRule(aRule), mSelector(aSelector) {} RuleValue* Add(PRInt32 aIndex, RuleValue *aNext) { mIndex = aIndex; mNext = aNext; return this; } // 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; nsCSSSelector* mSelector; // which of |mRule|'s selectors 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 char *match_str, *entry_str; match_atom->GetUTF8String(&match_str); entry_atom->GetUTF8String(&entry_str); return (nsCRT::strcasecmp(entry_str, match_str) == 0); } 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->mSelector->mTag; } PR_STATIC_CALLBACK(const void*) RuleHash_ClassTable_GetKey(PLDHashTable *table, PLDHashEntryHdr *hdr) { RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, hdr); return entry->mRules->mSelector->mClassList->mAtom; } PR_STATIC_CALLBACK(const void*) RuleHash_IdTable_GetKey(PLDHashTable *table, PLDHashEntryHdr *hdr) { RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, hdr); return entry->mRules->mSelector->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->mSelector->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->mSelector->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, nsCSSSelector* aSelector, void *aData); class RuleHash { public: RuleHash(PRBool aQuirksMode); ~RuleHash(); void PrependRule(RuleValue *aRuleInfo); void EnumerateAllRules(PRInt32 aNameSpace, nsIAtom* aTag, nsIAtom* aID, const nsVoidArray& aClassList, RuleEnumFunc aFunc, void* aData); void EnumerateTagRules(nsIAtom* aTag, RuleEnumFunc aFunc, void* aData); PLArenaPool& Arena() { return mArena; } protected: void PrependRuleToTable(PLDHashTable* aTable, const void* aKey, RuleValue* aRuleInfo); void PrependUniversalRule(RuleValue* aRuleInfo); // 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(); nsCOMPtr sheet; value->mRule->GetStyleSheet(*getter_AddRefs(sheet)); nsCOMPtr cssSheet = do_QueryInterface(sheet); value->mSelector->ToString(selectorText, cssSheet); 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, RuleValue* aRuleInfo) { // Get a new or existing entry. RuleHashTableEntry *entry = NS_STATIC_CAST(RuleHashTableEntry*, PL_DHashTableOperate(aTable, aKey, PL_DHASH_ADD)); if (!entry) return; entry->mRules = aRuleInfo->Add(mRuleCount++, entry->mRules); } void RuleHash::PrependUniversalRule(RuleValue *aRuleInfo) { mUniversalRules = aRuleInfo->Add(mRuleCount++, mUniversalRules); } void RuleHash::PrependRule(RuleValue *aRuleInfo) { nsCSSSelector *selector = aRuleInfo->mSelector; if (nsnull != selector->mIDList) { PrependRuleToTable(&mIdTable, selector->mIDList->mAtom, aRuleInfo); RULE_HASH_STAT_INCREMENT(mIdSelectors); } else if (nsnull != selector->mClassList) { PrependRuleToTable(&mClassTable, selector->mClassList->mAtom, aRuleInfo); RULE_HASH_STAT_INCREMENT(mClassSelectors); } else if (nsnull != selector->mTag) { PrependRuleToTable(&mTagTable, selector->mTag, aRuleInfo); RULE_HASH_STAT_INCREMENT(mTagSelectors); } else if (kNameSpaceID_Unknown != selector->mNameSpace) { PrependRuleToTable(&mNameSpaceTable, NS_INT32_TO_PTR(selector->mNameSpace), aRuleInfo); RULE_HASH_STAT_INCREMENT(mNameSpaceSelectors); } else { // universal tag selector PrependUniversalRule(aRuleInfo); 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; } } RuleValue *cur = mEnumList[valueIndex]; (*aFunc)(cur->mRule, cur->mSelector, aData); RuleValue *next = cur->mNext; mEnumList[valueIndex] = next ? next : mEnumList[--valueCount]; } // Fast loop over single value. RuleValue* value = mEnumList[0]; do { (*aFunc)(value->mRule, value->mSelector, 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, tagValue->mSelector, aData); tagValue = tagValue->mNext; } while (tagValue); } } //-------------------------------- // Attribute selectors hash table. struct AttributeSelectorEntry : public PLDHashEntryHdr { nsIAtom *mAttribute; nsVoidArray *mSelectors; }; PR_STATIC_CALLBACK(void) AttributeSelectorClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr) { AttributeSelectorEntry *entry = NS_STATIC_CAST(AttributeSelectorEntry*, hdr); delete entry->mSelectors; memset(entry, 0, table->entrySize); } static PLDHashTableOps AttributeSelectorOps = { PL_DHashAllocTable, PL_DHashFreeTable, PL_DHashGetKeyStub, PL_DHashVoidPtrKeyStub, PL_DHashMatchEntryStub, PL_DHashMoveEntryStub, AttributeSelectorClearEntry, PL_DHashFinalizeStub, NULL }; //-------------------------------- struct RuleCascadeData { RuleCascadeData(nsIAtom *aMedium, PRBool aQuirksMode) : mRuleHash(aQuirksMode), mStateSelectors(), mMedium(aMedium), mNext(nsnull) { PL_DHashTableInit(&mAttributeSelectors, &AttributeSelectorOps, nsnull, sizeof(AttributeSelectorEntry), 16); } ~RuleCascadeData(void) { PL_DHashTableFinish(&mAttributeSelectors); } RuleHash mRuleHash; nsVoidArray mStateSelectors; PLDHashTable mAttributeSelectors; // nsIAtom* -> nsVoidArray* // Looks up or creates the appropriate list in |mAttributeSelectors|. // Returns null only on allocation failure. nsVoidArray* AttributeListFor(nsIAtom* aAttribute); nsCOMPtr mMedium; RuleCascadeData* mNext; // for a different medium }; nsVoidArray* RuleCascadeData::AttributeListFor(nsIAtom* aAttribute) { AttributeSelectorEntry *entry = NS_STATIC_CAST(AttributeSelectorEntry*, PL_DHashTableOperate(&mAttributeSelectors, aAttribute, PL_DHASH_ADD)); if (!entry) return nsnull; if (!entry->mSelectors) { if (!(entry->mSelectors = new nsVoidArray)) { PL_DHashTableRawRemove(&mAttributeSelectors, entry); return nsnull; } entry->mAttribute = aAttribute; } return entry->mSelectors; } // ------------------------------- // 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); NS_IMETHOD HasAttributeDependentStyle(AttributeRuleProcessorData* aData, nsIAtom* aMedium, PRBool* aResult); 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); nsAutoVoidArray mSheets; nsCOMPtr mURL; nsISupportsArray* mOrderedRules; nsCOMPtr mNameSpace; 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 SetURL(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_(PRBool) HasRules() const; NS_IMETHOD DeleteRuleFromGroup(nsICSSGroupRule* aGroup, PRUint32 aIndex); NS_IMETHOD InsertRuleIntoGroup(const nsAString& aRule, nsICSSGroupRule* aGroup, PRUint32 aIndex, PRUint32* _retval); NS_IMETHOD ReplaceRuleInGroup(nsICSSGroupRule* aGroup, nsICSSRule* aOld, nsICSSRule* aNew); 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 GetOwnerRule(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); // XXX do these belong here or are they generic? NS_IMETHOD PrependStyleRule(nsICSSRule* aRule); NS_IMETHOD AppendStyleRule(nsICSSRule* aRule); NS_IMETHOD ReplaceStyleRule(nsICSSRule* aOld, nsICSSRule* aNew); 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 Clone(nsICSSStyleSheet* aCloneParent, nsICSSImportRule* aCloneOwnerRule, nsIDocument* aCloneDocument, nsIDOMNode* aCloneOwningNode, 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; #endif // nsIDOMStyleSheet interface NS_DECL_NSIDOMSTYLESHEET // nsIDOMCSSStyleSheet interface NS_DECL_NSIDOMCSSSTYLESHEET private: CSSStyleSheetImpl(const CSSStyleSheetImpl& aCopy, nsICSSStyleSheet* aParentToUse, nsICSSImportRule* aOwnerRuleToUse, nsIDocument* aDocumentToUse, nsIDOMNode* aOwningNodeToUse); // 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; // weak ref; parents maintain this for their children 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) { // 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 rule; result = mStyleSheet->GetStyleRuleAt(aIndex, *getter_AddRefs(rule)); if (rule) { result = rule->GetDOMRule(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 Delete(const nsAString & aOldMedium); nsresult Append(const nsAString & aOldMedium); nsCOMPtr 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_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 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 medium; QueryElementAt(index++, NS_GET_IID(nsIAtom), getter_AddRefs(medium)); NS_ENSURE_TRUE(medium, NS_ERROR_FAILURE); nsAutoString buffer; medium->ToString(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); } // "sheet" should be an CSSStyleSheetImpl and "doc" should be an // nsCOMPtr #define BEGIN_MEDIA_CHANGE(sheet, doc) \ if (sheet) { \ rv = sheet->GetOwningDocument(*getter_AddRefs(doc)); \ NS_ENSURE_SUCCESS(rv, rv); \ } \ mozAutoDocUpdate updateBatch(doc, UPDATE_STYLE, PR_TRUE); \ if (sheet) { \ rv = sheet->WillDirty(); \ NS_ENSURE_SUCCESS(rv, rv); \ } #define END_MEDIA_CHANGE(sheet, doc) \ if (sheet) { \ sheet->DidDirty(); \ } \ /* XXXldb Pass something meaningful? */ \ if (doc) { \ doc->StyleRuleChanged(sheet, nsnull, nsnull); \ } NS_IMETHODIMP DOMMediaListImpl::SetMediaText(const nsAString& aMediaText) { nsresult rv = NS_OK; nsCOMPtr doc; BEGIN_MEDIA_CHANGE(mStyleSheet, doc) rv = SetText(aMediaText); if (NS_FAILED(rv)) return rv; END_MEDIA_CHANGE(mStyleSheet, doc) 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 tmp(dont_AddRef(ElementAt(aIndex))); if (tmp) { nsCOMPtr medium(do_QueryInterface(tmp)); NS_ENSURE_TRUE(medium, NS_ERROR_FAILURE); nsAutoString buffer; medium->ToString(buffer); aReturn.Assign(buffer); } else { aReturn.Truncate(); } return NS_OK; } NS_IMETHODIMP DOMMediaListImpl::DeleteMedium(const nsAString& aOldMedium) { nsresult rv = NS_OK; nsCOMPtr doc; BEGIN_MEDIA_CHANGE(mStyleSheet, doc) rv = Delete(aOldMedium); if (NS_FAILED(rv)) return rv; END_MEDIA_CHANGE(mStyleSheet, doc) return rv; } NS_IMETHODIMP DOMMediaListImpl::AppendMedium(const nsAString& aNewMedium) { nsresult rv = NS_OK; nsCOMPtr doc; BEGIN_MEDIA_CHANGE(mStyleSheet, doc) rv = Append(aNewMedium); if (NS_FAILED(rv)) return rv; END_MEDIA_CHANGE(mStyleSheet, doc) return rv; } nsresult DOMMediaListImpl::Delete(const nsAString& aOldMedium) { if (aOldMedium.IsEmpty()) return NS_ERROR_DOM_NOT_FOUND_ERR; nsCOMPtr old = do_GetAtom(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 media = do_GetAtom(aNewMedium); NS_ENSURE_TRUE(media, NS_ERROR_OUT_OF_MEMORY); PRInt32 indx = IndexOf(media); if (indx >= 0) { RemoveElementAt(indx); } AppendElement(media); 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) { // 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 (mStyleSheet) { nsCOMPtr sheet; result = mStyleSheet->GetStyleSheetAt(aIndex, *getter_AddRefs(sheet)); if (NS_SUCCEEDED(result)) { result = CallQueryInterface(sheet, aReturn); } } 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(), mOrderedRules(nsnull), mNameSpace(nsnull), 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; } CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheetInner& aCopy, nsICSSStyleSheet* aParentSheet) : mSheets(), mURL(aCopy.mURL), mNameSpace(nsnull), mComplete(aCopy.mComplete) { MOZ_COUNT_CTOR(CSSStyleSheetInner); mSheets.AppendElement(aParentSheet); if (aCopy.mOrderedRules) { NS_NewISupportsArray(&mOrderedRules); if (mOrderedRules) { aCopy.mOrderedRules->EnumerateForwards(CloneRuleInto, mOrderedRules); mOrderedRules->EnumerateForwards(SetStyleSheetReference, aParentSheet); } } else { mOrderedRules = nsnull; } RebuildNameSpaces(); } CSSStyleSheetInner::~CSSStyleSheetInner(void) { MOZ_COUNT_DTOR(CSSStyleSheetInner); 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 (mOrderedRules) { mOrderedRules->EnumerateForwards(CreateNameSpace, address_of(mNameSpace)); } } // ------------------------------- // 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) { mInner = new CSSStyleSheetInner(this); } CSSStyleSheetImpl::CSSStyleSheetImpl(const CSSStyleSheetImpl& aCopy, nsICSSStyleSheet* aParentToUse, nsICSSImportRule* aOwnerRuleToUse, nsIDocument* aDocumentToUse, nsIDOMNode* aOwningNodeToUse) : nsICSSStyleSheet(), mRefCnt(0), mTitle(aCopy.mTitle), mMedia(nsnull), mFirstChild(nsnull), mNext(nsnull), mParent(aParentToUse), mOwnerRule(aOwnerRuleToUse), mImportsCollection(nsnull), // re-created lazily mRuleCollection(nsnull), // re-created lazily mDocument(aDocumentToUse), mOwningNode(aOwningNodeToUse), mDisabled(aCopy.mDisabled), mDirty(PR_FALSE), mInner(aCopy.mInner), mRuleProcessors(nsnull) { 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 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 { // XXX This is wrong; we should be keeping @import rules and // sheets in sync! CSSStyleSheetImpl* child = new CSSStyleSheetImpl(*otherChild, this, nsnull, aDocumentToUse, nsnull); 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->mDocument = 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::SetURL(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->mOrderedRules && !mInner->mComplete, "Can't call SetURL on sheets that are complete or have rules"); mInner->mURL = aURL; return NS_OK; } NS_IMETHODIMP CSSStyleSheetImpl::GetURL(nsIURI*& aURL) const { NS_IF_ADDREF(aURL = (mInner ? mInner->mURL.get() : nsnull)); 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 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_(PRBool) CSSStyleSheetImpl::HasRules() const { PRInt32 count; StyleRuleCount(count); return count != 0; } 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->BeginUpdate(UPDATE_STYLE); mDocument->SetStyleSheetApplicableState(this, !mDisabled); mDocument->EndUpdate(UPDATE_STYLE); } 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->BeginUpdate(UPDATE_STYLE); mDocument->SetStyleSheetApplicableState(this, PR_TRUE); mDocument->EndUpdate(UPDATE_STYLE); } 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 { aDocument = mDocument; NS_IF_ADDREF(aDocument); return NS_OK; } NS_IMETHODIMP CSSStyleSheetImpl::SetOwningDocument(nsIDocument* aDocument) { // not ref counted mDocument = aDocument; // Now set the same document on all our child sheets.... for (CSSStyleSheetImpl* child = mFirstChild; child; child = child->mNext) { child->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::GetOwnerRule(nsICSSImportRule** aOwnerRule) { *aOwnerRule = mOwnerRule; NS_IF_ADDREF(*aOwnerRule); return NS_OK; } NS_IMETHODIMP CSSStyleSheetImpl::ContainsStyleSheet(nsIURI* aURL, PRBool& aContains, nsIStyleSheet** aTheChild /*=nsnull*/) { NS_PRECONDITION(nsnull != aURL, "null arg"); if (!mInner || !mInner->mURL) { // We're not yet far enough along in our load to know what our URL is (we // may still get redirected and such). Assert (caller should really not be // calling this on us at this stage) and return. NS_ERROR("ContainsStyleSheet called on a sheet that's still loading"); aContains = PR_FALSE; return NS_OK; } // 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; sheet->mDocument = mDocument; 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; sheet->mDocument = mDocument; DidDirty(); } return result; } 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(); } } } 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 nameSpaceRule(do_QueryInterface(aRule)); nsCOMPtr newNameSpace; nsCOMPtr prefix; nsAutoString urlSpec; nameSpaceRule->GetPrefix(*getter_AddRefs(prefix)); nameSpaceRule->GetURLSpec(urlSpec); mInner->mNameSpace-> CreateChildNameSpace(prefix, urlSpec, getter_AddRefs(newNameSpace)); if (newNameSpace) { mInner->mNameSpace = newNameSpace; } } } } } return NS_OK; } NS_IMETHODIMP CSSStyleSheetImpl::ReplaceStyleRule(nsICSSRule* aOld, nsICSSRule* aNew) { NS_PRECONDITION(mInner->mOrderedRules, "can't have old rule"); NS_PRECONDITION(mInner && mInner->mComplete, "No replacing in an incomplete sheet!"); if (NS_SUCCEEDED(WillDirty())) { PRInt32 index = mInner->mOrderedRules->IndexOf(aOld); NS_ENSURE_TRUE(index != -1, NS_ERROR_UNEXPECTED); mInner->mOrderedRules->ReplaceElementAt(aNew, index); aNew->SetStyleSheet(this); aOld->SetStyleSheet(nsnull); DidDirty(); #ifdef DEBUG PRInt32 type = nsICSSRule::UNKNOWN_RULE; aNew->GetType(type); NS_ASSERTION(nsICSSRule::NAMESPACE_RULE != type, "not yet implemented"); aOld->GetType(type); NS_ASSERTION(nsICSSRule::NAMESPACE_RULE != type, "not yet implemented"); #endif } 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::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* aCloneParent, nsICSSImportRule* aCloneOwnerRule, nsIDocument* aCloneDocument, nsIDOMNode* aCloneOwningNode, nsICSSStyleSheet** aClone) const { NS_PRECONDITION(aClone, "Null out param!"); CSSStyleSheetImpl* clone = new CSSStyleSheetImpl(*this, aCloneParent, aCloneOwnerRule, aCloneDocument, aCloneOwningNode); if (clone) { *aClone = NS_STATIC_CAST(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 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 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); } #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->BeginUpdate(UPDATE_STYLE); mDocument->SetStyleSheetApplicableState(this, !mDisabled); mDocument->EndUpdate(UPDATE_STYLE); } 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) { nsCAutoString str; if (mInner && mInner->mURL) { mInner->mURL->GetSpec(str); } CopyUTF8toUTF16(str, aHref); 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 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 mOwnerRule->GetDOMRule(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 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 rv = nsContentUtils::GetSecurityManager()->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 loader; nsCOMPtr css; nsCOMPtr 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; mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, PR_TRUE); nsCOMPtr 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 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 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 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 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 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 nameSpaceRule(do_QueryInterface(cssRule)); nsCOMPtr newNameSpace; nsCOMPtr prefix; nsAutoString urlSpec; nameSpaceRule->GetPrefix(*getter_AddRefs(prefix)); nameSpaceRule->GetURLSpec(urlSpec); mInner->mNameSpace->CreateChildNameSpace(prefix, urlSpec, getter_AddRefs(newNameSpace)); if (newNameSpace) { mInner->mNameSpace = newNameSpace; } } // 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 importRule(do_QueryInterface(cssRule)); NS_ASSERTION(importRule, "Rule which has type IMPORT_RULE and does not implement nsIDOMCSSImportRule!"); nsCOMPtr childSheet; importRule->GetStyleSheet(getter_AddRefs(childSheet)); if (!childSheet) { notify = PR_FALSE; } } if (mDocument && notify) { mDocument->StyleRuleAdded(this, cssRule); } } 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) { mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, PR_TRUE); result = WillDirty(); if (NS_SUCCEEDED(result)) { PRUint32 count; mInner->mOrderedRules->Count(&count); if (aIndex >= count) return NS_ERROR_DOM_INDEX_SIZE_ERR; nsCOMPtr rule = dont_AddRef((nsICSSRule*)mInner->mOrderedRules->ElementAt(aIndex)); if (rule) { mInner->mOrderedRules->RemoveElementAt(aIndex); rule->SetStyleSheet(nsnull); DidDirty(); if (mDocument) { mDocument->StyleRuleRemoved(this, rule); } } } } 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 rule; result = aGroup->GetStyleRuleAt(aIndex, *getter_AddRefs(rule)); NS_ENSURE_SUCCESS(result, result); // check that the rule actually belongs to this sheet! nsCOMPtr ruleSheet; rule->GetStyleSheet(*getter_AddRefs(ruleSheet)); if (this != ruleSheet) { return NS_ERROR_INVALID_ARG; } mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, PR_TRUE); result = WillDirty(); NS_ENSURE_SUCCESS(result, result); result = aGroup->DeleteStyleRuleAt(aIndex); NS_ENSURE_SUCCESS(result, result); rule->SetStyleSheet(nsnull); DidDirty(); if (mDocument) { mDocument->StyleRuleRemoved(this, rule); } 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 groupSheet; aGroup->GetStyleSheet(*getter_AddRefs(groupSheet)); if (this != groupSheet) { return NS_ERROR_INVALID_ARG; } // get the css parser nsCOMPtr loader; nsCOMPtr css; nsCOMPtr 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 mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, PR_TRUE); result = WillDirty(); NS_ENSURE_SUCCESS(result, result); nsCOMPtr 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 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)); if (mDocument) { mDocument->StyleRuleAdded(this, rule); } } if (loader) { loader->RecycleParser(css); } *_retval = aIndex; return NS_OK; } NS_IMETHODIMP CSSStyleSheetImpl::ReplaceRuleInGroup(nsICSSGroupRule* aGroup, nsICSSRule* aOld, nsICSSRule* aNew) { nsresult result; NS_PRECONDITION(mInner && mInner->mComplete, "No replacing in an incomplete sheet!"); #ifdef DEBUG { nsCOMPtr groupSheet; aGroup->GetStyleSheet(*getter_AddRefs(groupSheet)); NS_ASSERTION(this == groupSheet, "group doesn't belong to this sheet"); } #endif result = WillDirty(); NS_ENSURE_SUCCESS(result, result); result = aGroup->ReplaceStyleRule(aOld, aNew); DidDirty(); return result; } // nsICSSLoaderObserver implementation NS_IMETHODIMP CSSStyleSheetImpl::StyleSheetLoaded(nsICSSStyleSheet*aSheet, PRBool aNotify) { #ifdef DEBUG nsCOMPtr styleSheet(do_QueryInterface(aSheet)); NS_ASSERTION(styleSheet, "Sheet not implementing nsIStyleSheet!\n"); nsCOMPtr parentSheet; aSheet->GetParentSheet(*getter_AddRefs(parentSheet)); nsCOMPtr 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 ownerRule; aSheet->GetOwnerRule(getter_AddRefs(ownerRule)); mozAutoDocUpdate updateBatch(mDocument, UPDATE_STYLE, PR_TRUE); // XXXldb @import rules shouldn't even implement nsIStyleRule (but // they do)! nsCOMPtr styleRule(do_QueryInterface(ownerRule)); mDocument->StyleRuleAdded(this, styleRule); } return NS_OK; } // XXX for backwards compatibility and convenience 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->SetURL(aURL))) { NS_RELEASE(sheet); return rv; } *aInstancePtrResult = sheet; return NS_OK; } 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) { } 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); mParentContent = aContent->GetParent(); // 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 mHasAttributes = aContent->GetAttrCount() > 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 optEl = do_QueryInterface(mContent); optEl->GetSelected(&isChecked); } else if (mContentTag == nsHTMLAtoms::input) { nsCOMPtr 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(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; for (nsIContent* content = mContent; content; content = content->GetParent()) { if (content->GetAttrCount() > 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; } } } } 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 nsCSSPseudoClasses::active == aAtom || nsCSSPseudoClasses::mozDragOver == aAtom || nsCSSPseudoClasses::focus == aAtom || nsCSSPseudoClasses::hover == aAtom || nsCSSPseudoClasses::target == 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) { nsCOMPtr tag; aChild->GetTag(getter_AddRefs(tag)); // skip text & comments if ((tag != nsLayoutAtoms::textTagName) && (tag != nsLayoutAtoms::commentTagName) && (tag != nsLayoutAtoms::processingInstructionTagName)) { 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) { return PR_TRUE; } } } } 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 { if (aCaseSensitive) result = StringBeginsWith(aAttributeValue, aSelectorValue); else result = StringBeginsWith(aAttributeValue, aSelectorValue, nsCaseInsensitiveStringComparator()); } } return result; } // This function is to be called once we have fetched a value for an attribute // whose namespace and name match those of aAttrSelector. This function // performs comparisons on the value only, based on aAttrSelector->mFunction. static PRBool AttrMatchesValue(const nsAttrSelector* aAttrSelector, const nsString& aValue) { NS_PRECONDITION(aAttrSelector, "Must have an attribute selector"); const PRBool isCaseSensitive = aAttrSelector->mCaseSensitive; switch (aAttrSelector->mFunction) { case NS_ATTR_FUNC_EQUALS: if (isCaseSensitive) { return aValue.Equals(aAttrSelector->mValue); } else { return aValue.Equals(aAttrSelector->mValue, nsCaseInsensitiveStringComparator()); } case NS_ATTR_FUNC_INCLUDES: return ValueIncludes(aValue, aAttrSelector->mValue, isCaseSensitive); case NS_ATTR_FUNC_DASHMATCH: return DashMatchCompare(aValue, aAttrSelector->mValue, isCaseSensitive); case NS_ATTR_FUNC_ENDSMATCH: if (isCaseSensitive) { return StringEndsWith(aValue, aAttrSelector->mValue); } else { return StringEndsWith(aValue, aAttrSelector->mValue, nsCaseInsensitiveStringComparator()); } case NS_ATTR_FUNC_BEGINSMATCH: if (isCaseSensitive) { return StringBeginsWith(aValue, aAttrSelector->mValue); } else { return StringBeginsWith(aValue, aAttrSelector->mValue, nsCaseInsensitiveStringComparator()); } case NS_ATTR_FUNC_CONTAINSMATCH: return FindInReadable(aAttrSelector->mValue, aValue, nsCaseInsensitiveStringComparator()); default: NS_NOTREACHED("Shouldn't be ending up here"); return PR_FALSE; } } // 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 nsIAtom* aAttribute, // attribute NOT to test PRInt8 aNegationIndex) { // if we are dealing with negations, reverse the values of PR_TRUE and PR_FALSE PRBool localFalse = 0 < aNegationIndex; PRBool localTrue = 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; // test for pseudo class match // first-child, root, lang, active, focus, hover, link, visited... // XXX disabled, enabled, selected, selection for (nsAtomStringList* pseudoClass = aSelector->mPseudoClassList; pseudoClass && result; pseudoClass = pseudoClass->mNext) { if ((nsCSSPseudoClasses::firstChild == pseudoClass->mAtom) || (nsCSSPseudoClasses::firstNode == pseudoClass->mAtom) ) { nsIContent *firstChild = nsnull; nsIContent *parent = data.mParentContent; if (parent) { PRBool acceptNonWhitespace = nsCSSPseudoClasses::firstNode == pseudoClass->mAtom; PRInt32 index = -1; do { firstChild = parent->GetChildAt(++index); // stop at first non-comment and non-whitespace node (and // non-text node for firstChild) } while (firstChild && !IsSignificantChild(firstChild, acceptNonWhitespace)); } result = localTrue == (data.mContent == firstChild); } else if ((nsCSSPseudoClasses::lastChild == pseudoClass->mAtom) || (nsCSSPseudoClasses::lastNode == pseudoClass->mAtom)) { nsIContent *lastChild = nsnull; nsIContent *parent = data.mParentContent; if (parent) { PRBool acceptNonWhitespace = nsCSSPseudoClasses::lastNode == pseudoClass->mAtom; PRUint32 index = parent->GetChildCount(); do { lastChild = parent->GetChildAt(--index); // stop at first non-comment and non-whitespace node (and // non-text node for lastChild) } while (lastChild && !IsSignificantChild(lastChild, acceptNonWhitespace)); } result = localTrue == (data.mContent == lastChild); } else if (nsCSSPseudoClasses::empty == pseudoClass->mAtom) { nsIContent *child = nsnull; nsIContent *element = data.mContent; PRInt32 index = -1; do { child = element->GetChildAt(++index); // stop at first non-comment and non-whitespace node } while (child && !IsSignificantChild(child, PR_TRUE)); result = localTrue == (child == nsnull); } else if (nsCSSPseudoClasses::root == pseudoClass->mAtom) { if (data.mParentContent) { result = localFalse; } else { result = localTrue; } } else if (nsCSSPseudoClasses::mozBoundElement == pseudoClass->mAtom) { // XXXldb How do we know where the selector came from? And what // if there are multiple bindings, and we should be matching the // outer one? 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 { nsIDocument* doc = data.mContent->GetDocument(); 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; 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 (but don't check .class): !aSelector->mTag && !aSelector->mIDList && !aSelector->mAttrList && // This (or the other way around) both make :not() asymmetric // in quirks mode (and it's hard to work around since we're // testing the current mNegations, not the first // (unnegated)). This at least makes it closer to the spec. aNegationIndex == 0 && // :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 (nsCSSPseudoClasses::target == pseudoClass->mAtom) { result = (aStateMask & NS_EVENT_STATE_URLTARGET) || (localTrue == (0 != (data.mEventState & NS_EVENT_STATE_URLTARGET))); } } } else if (IsLinkPseudo(pseudoClass->mAtom)) { if (data.mIsHTMLLink || data.mIsSimpleXLink) { if (nsCSSPseudoClasses::mozAnyLink == pseudoClass->mAtom) { result = localTrue; } else if (nsCSSPseudoClasses::link == pseudoClass->mAtom) { result = localTrue == (eLinkState_Unvisited == data.mLinkState); } else if (nsCSSPseudoClasses::visited == pseudoClass->mAtom) { result = 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: //