Bug 705877 part 3. Hang an optional Bloom filter and some methods for managing it off the TreeMatchContext. r=dbaron

This commit is contained in:
Boris Zbarsky 2012-03-12 22:54:15 -05:00
parent e9f9272eeb
commit f593cd5e83
2 changed files with 189 additions and 0 deletions

View File

@ -3242,3 +3242,99 @@ nsCSSRuleProcessor::SelectorListMatches(Element* aElement,
return false;
}
// AncestorFilter out of line methods
void
AncestorFilter::Init(Element *aElement)
{
MOZ_ASSERT(!mFilter);
MOZ_ASSERT(mHashes.IsEmpty());
mFilter = new Filter();
if (NS_LIKELY(aElement)) {
MOZ_ASSERT(aElement->IsInDoc(),
"aElement must be in the document for the assumption that "
"GetNodeParent() is non-null on all element ancestors of "
"aElement to be true");
// Collect up the ancestors
nsAutoTArray<Element*, 50> ancestors;
Element* cur = aElement;
do {
ancestors.AppendElement(cur);
nsINode* parent = cur->GetNodeParent();
if (!parent->IsElement()) {
break;
}
cur = parent->AsElement();
} while (true);
// Now push them in reverse order.
for (PRUint32 i = ancestors.Length(); i-- != 0; ) {
PushAncestor(ancestors[i]);
}
}
}
void
AncestorFilter::PushAncestor(Element *aElement)
{
MOZ_ASSERT(mFilter);
PRUint32 oldLength = mHashes.Length();
mPopTargets.AppendElement(oldLength);
#ifdef DEBUG
mElements.AppendElement(aElement);
#endif
mHashes.AppendElement(aElement->Tag()->hash());
nsIAtom *id = aElement->GetID();
if (id) {
mHashes.AppendElement(id->hash());
}
const nsAttrValue *classes = aElement->GetClasses();
if (classes) {
PRUint32 classCount = classes->GetAtomCount();
for (PRUint32 i = 0; i < classCount; ++i) {
mHashes.AppendElement(classes->AtomAt(i)->hash());
}
}
PRUint32 newLength = mHashes.Length();
for (PRUint32 i = oldLength; i < newLength; ++i) {
mFilter->add(mHashes[i]);
}
}
void
AncestorFilter::PopAncestor()
{
MOZ_ASSERT(!mPopTargets.IsEmpty());
MOZ_ASSERT(mPopTargets.Length() == mElements.Length());
PRUint32 popTargetLength = mPopTargets.Length();
PRUint32 newLength = mPopTargets[popTargetLength-1];
mPopTargets.TruncateLength(popTargetLength-1);
#ifdef DEBUG
mElements.TruncateLength(popTargetLength-1);
#endif
PRUint32 oldLength = mHashes.Length();
for (PRUint32 i = newLength; i < oldLength; ++i) {
mFilter->remove(mHashes[i]);
}
mHashes.TruncateLength(newLength);
}
#ifdef DEBUG
void
AncestorFilter::AssertHasAllAncestors(Element *aElement) const
{
nsINode* cur = aElement->GetNodeParent();
while (cur && cur->IsElement()) {
MOZ_ASSERT(mElements.Contains(cur));
cur = cur->GetNodeParent();
}
}
#endif

View File

@ -51,12 +51,102 @@
#include "nsCSSPseudoElements.h"
#include "nsRuleWalker.h"
#include "nsNthIndexCache.h"
#include "mozilla/BloomFilter.h"
#include "mozilla/GuardObjects.h"
class nsIStyleSheet;
class nsIAtom;
class nsICSSPseudoComparator;
class nsAttrValue;
/**
* An AncestorFilter is used to keep track of ancestors so that we can
* quickly tell that a particular selector is not relevant to a given
* element.
*/
class NS_STACK_CLASS AncestorFilter {
public:
/**
* Initialize the filter. If aElement is not null, it and all its
* ancestors will be passed to PushAncestor, starting from the root
* and going down the tree.
*/
void Init(mozilla::dom::Element *aElement);
/* Maintenance of our ancestor state */
void PushAncestor(mozilla::dom::Element *aElement);
void PopAncestor();
/* Helper class for maintaining the ancestor state */
class NS_STACK_CLASS AutoAncestorPusher {
public:
AutoAncestorPusher(bool aDoPush,
AncestorFilter &aFilter,
mozilla::dom::Element *aElement
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mPushed(aDoPush && aElement), mFilter(aFilter)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (mPushed) {
mFilter.PushAncestor(aElement);
}
}
~AutoAncestorPusher() {
if (mPushed) {
mFilter.PopAncestor();
}
}
private:
bool mPushed;
AncestorFilter &mFilter;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
/* Check whether we might have an ancestor matching one of the given
atom hashes. |hashes| must have length hashListLength */
template<size_t hashListLength>
bool MightHaveMatchingAncestor(const uint32_t* aHashes) const
{
MOZ_ASSERT(mFilter);
for (size_t i = 0; i < hashListLength && aHashes[i]; ++i) {
if (!mFilter->mightContain(aHashes[i])) {
return false;
}
}
return true;
}
bool HasFilter() const { return mFilter; }
#ifdef DEBUG
void AssertHasAllAncestors(mozilla::dom::Element *aElement) const;
#endif
private:
// Using 2^12 slots makes the Bloom filter a nice round page in
// size, so let's do that. We get a false positive rate of 1% or
// less even with several hundred things in the filter. Note that
// we allocate the filter lazily, because not all tree match
// contexts can use one effectively.
typedef mozilla::BloomFilter<12, nsIAtom> Filter;
nsAutoPtr<Filter> mFilter;
// Stack of indices to pop to. These are indices into mHashes.
nsTArray<PRUint32> mPopTargets;
// List of hashes; this is what we pop using mPopTargets. We store
// hashes of our ancestor element tag names, ids, and classes in
// here.
nsTArray<uint32_t> mHashes;
// A debug-only stack of Elements for use in assertions
#ifdef DEBUG
nsTArray<mozilla::dom::Element*> mElements;
#endif
};
/**
* A |TreeMatchContext| has data about a matching operation. The
* data are not node-specific but are invariants of the DOM tree the
@ -128,6 +218,9 @@ struct NS_STACK_CLASS TreeMatchContext {
// The nth-index cache we should use
nsNthIndexCache mNthIndexCache;
// An ancestor filter
AncestorFilter mAncestorFilter;
// Constructor to use when creating a tree match context for styling
TreeMatchContext(bool aForStyling,
nsRuleWalker::VisitedHandlingType aVisitedHandling,