Bug 779084 - Move querySelector stuff out of nsGenericElement and into nsINode r=bz

This commit is contained in:
David Zbarsky 2012-08-30 13:10:13 -04:00
parent a7f4964f72
commit 0793e3afda
6 changed files with 217 additions and 227 deletions

View File

@ -16,7 +16,6 @@
#include "nsCOMPtr.h" // member
#include "nsCycleCollectionParticipant.h" // NS_DECL_CYCLE_*
#include "nsIContent.h" // base class
#include "nsIDOMNodeSelector.h" // base class
#include "nsIDOMTouchEvent.h" // base class (nsITouchEventReceiver)
#include "nsIDOMXPathNSResolver.h" // base class
#include "nsIInlineEventHandlers.h" // base class
@ -154,29 +153,6 @@ private:
nsCOMPtr<nsINode> mNode;
};
/**
* A tearoff class for FragmentOrElement to implement NodeSelector
*/
class nsNodeSelectorTearoff MOZ_FINAL : public nsIDOMNodeSelector
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSIDOMNODESELECTOR
NS_DECL_CYCLE_COLLECTION_CLASS(nsNodeSelectorTearoff)
nsNodeSelectorTearoff(nsINode *aNode) : mNode(aNode)
{
}
private:
~nsNodeSelectorTearoff() {}
private:
nsCOMPtr<nsINode> mNode;
};
// Forward declare to allow being a friend
class nsTouchEventReceiverTearoff;
class nsInlineEventHandlersTearoff;
@ -307,15 +283,6 @@ public:
nsINode* aParent,
nsTArray<nsCOMPtr<nsIContent> >& aNodes);
/**
* Helper methods for implementing querySelector/querySelectorAll
*/
static nsIContent* doQuerySelector(nsINode* aRoot, const nsAString& aSelector,
nsresult *aResult);
static nsresult doQuerySelectorAll(nsINode* aRoot,
const nsAString& aSelector,
nsIDOMNodeList **aReturn);
NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(FragmentOrElement)
/**

View File

@ -9,6 +9,7 @@
#include "nsCOMPtr.h" // for member, local
#include "nsGkAtoms.h" // for nsGkAtoms::baseURIProperty
#include "nsIDOMEventTarget.h" // for base class
#include "nsIDOMNodeSelector.h" // base class
#include "nsINodeInfo.h" // member (in nsCOMPtr)
#include "nsIVariant.h" // for use in GetUserData()
#include "nsNodeInfoManager.h" // for use in NodePrincipal()
@ -1051,6 +1052,14 @@ public:
return NS_OK;
}
/**
* Helper methods for implementing querySelector/querySelectorAll
*/
nsIContent* QuerySelector(const nsAString& aSelector,
nsresult *aResult);
nsresult QuerySelectorAll(const nsAString& aSelector,
nsIDOMNodeList **aReturn);
/**
* Associate an object aData to aKey on this node. If aData is null any
* previously registered object and UserDataHandler associated to aKey on
@ -1571,6 +1580,28 @@ inline nsINode* NODE_FROM(C& aContent, D& aDocument)
return static_cast<nsINode*>(aDocument);
}
/**
* A tearoff class for FragmentOrElement to implement NodeSelector
*/
class nsNodeSelectorTearoff MOZ_FINAL : public nsIDOMNodeSelector
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSIDOMNODESELECTOR
NS_DECL_CYCLE_COLLECTION_CLASS(nsNodeSelectorTearoff)
nsNodeSelectorTearoff(nsINode *aNode) : mNode(aNode)
{
}
private:
~nsNodeSelectorTearoff() {}
private:
nsCOMPtr<nsINode> mNode;
};
extern const nsIID kThisPtrOffsetsSID;

View File

@ -96,7 +96,6 @@
#include "nsIScrollableFrame.h"
#include "nsXBLInsertionPoint.h"
#include "mozilla/css/StyleRule.h" /* For nsCSSSelectorList */
#include "nsCSSRuleProcessor.h"
#include "nsRuleProcessorData.h"
#include "nsAsyncDOMEvent.h"
#include "nsTextNode.h"
@ -111,7 +110,6 @@
#include "mozAutoDocUpdate.h"
#include "nsCSSParser.h"
#include "prprf.h"
#include "nsDOMMutationObserver.h"
#include "nsSVGFeatures.h"
@ -519,33 +517,6 @@ nsNodeSupportsWeakRefTearoff::GetWeakReference(nsIWeakReference** aInstancePtr)
//----------------------------------------------------------------------
NS_IMPL_CYCLE_COLLECTION_1(nsNodeSelectorTearoff, mNode)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNodeSelectorTearoff)
NS_INTERFACE_MAP_ENTRY(nsIDOMNodeSelector)
NS_INTERFACE_MAP_END_AGGREGATED(mNode)
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNodeSelectorTearoff)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNodeSelectorTearoff)
NS_IMETHODIMP
nsNodeSelectorTearoff::QuerySelector(const nsAString& aSelector,
nsIDOMElement **aReturn)
{
nsresult rv;
nsIContent* result = FragmentOrElement::doQuerySelector(mNode, aSelector, &rv);
return result ? CallQueryInterface(result, aReturn) : rv;
}
NS_IMETHODIMP
nsNodeSelectorTearoff::QuerySelectorAll(const nsAString& aSelector,
nsIDOMNodeList **aReturn)
{
return FragmentOrElement::doQuerySelectorAll(mNode, aSelector, aReturn);
}
//----------------------------------------------------------------------
NS_IMPL_CYCLE_COLLECTION_1(nsTouchEventReceiverTearoff, mElement)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTouchEventReceiverTearoff)
@ -1976,164 +1947,6 @@ FragmentOrElement::FireNodeRemovedForChildren()
}
}
// NOTE: The aPresContext pointer is NOT addrefed.
// *aSelectorList might be null even if NS_OK is returned; this
// happens when all the selectors were pseudo-element selectors.
static nsresult
ParseSelectorList(nsINode* aNode,
const nsAString& aSelectorString,
nsCSSSelectorList** aSelectorList)
{
NS_ENSURE_ARG(aNode);
nsIDocument* doc = aNode->OwnerDoc();
nsCSSParser parser(doc->CSSLoader());
nsCSSSelectorList* selectorList;
nsresult rv = parser.ParseSelectorString(aSelectorString,
doc->GetDocumentURI(),
0, // XXXbz get the line number!
&selectorList);
NS_ENSURE_SUCCESS(rv, rv);
// Filter out pseudo-element selectors from selectorList
nsCSSSelectorList** slot = &selectorList;
do {
nsCSSSelectorList* cur = *slot;
if (cur->mSelectors->IsPseudoElement()) {
*slot = cur->mNext;
cur->mNext = nullptr;
delete cur;
} else {
slot = &cur->mNext;
}
} while (*slot);
*aSelectorList = selectorList;
return NS_OK;
}
// Actually find elements matching aSelectorList (which must not be
// null) and which are descendants of aRoot and put them in aList. If
// onlyFirstMatch, then stop once the first one is found.
template<bool onlyFirstMatch, class T>
inline static nsresult FindMatchingElements(nsINode* aRoot,
const nsAString& aSelector,
T &aList)
{
nsAutoPtr<nsCSSSelectorList> selectorList;
nsresult rv = ParseSelectorList(aRoot, aSelector,
getter_Transfers(selectorList));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(selectorList, NS_OK);
NS_ASSERTION(selectorList->mSelectors,
"How can we not have any selectors?");
nsIDocument* doc = aRoot->OwnerDoc();
TreeMatchContext matchingContext(false, nsRuleWalker::eRelevantLinkUnvisited,
doc, TreeMatchContext::eNeverMatchVisited);
doc->FlushPendingLinkUpdates();
// Fast-path selectors involving IDs. We can only do this if aRoot
// is in the document and the document is not in quirks mode, since
// ID selectors are case-insensitive in quirks mode. Also, only do
// this if selectorList only has one selector, because otherwise
// ordering the elements correctly is a pain.
NS_ASSERTION(aRoot->IsElement() || aRoot->IsNodeOfType(nsINode::eDOCUMENT) ||
!aRoot->IsInDoc(),
"The optimization below to check ContentIsDescendantOf only for "
"elements depends on aRoot being either an element or a "
"document if it's in the document.");
if (aRoot->IsInDoc() &&
doc->GetCompatibilityMode() != eCompatibility_NavQuirks &&
!selectorList->mNext &&
selectorList->mSelectors->mIDList) {
nsIAtom* id = selectorList->mSelectors->mIDList->mAtom;
const nsSmallVoidArray* elements =
doc->GetAllElementsForId(nsDependentAtomString(id));
// XXXbz: Should we fall back to the tree walk if aRoot is not the
// document and |elements| is long, for some value of "long"?
if (elements) {
for (int32_t i = 0; i < elements->Count(); ++i) {
Element *element = static_cast<Element*>(elements->ElementAt(i));
if (!aRoot->IsElement() ||
(element != aRoot &&
nsContentUtils::ContentIsDescendantOf(element, aRoot))) {
// We have an element with the right id and it's a strict descendant
// of aRoot. Make sure it really matches the selector.
if (nsCSSRuleProcessor::SelectorListMatches(element, matchingContext,
selectorList)) {
aList.AppendElement(element);
if (onlyFirstMatch) {
return NS_OK;
}
}
}
}
}
// No elements with this id, or none of them are our descendants,
// or none of them match. We're done here.
return NS_OK;
}
for (nsIContent* cur = aRoot->GetFirstChild();
cur;
cur = cur->GetNextNode(aRoot)) {
if (cur->IsElement() &&
nsCSSRuleProcessor::SelectorListMatches(cur->AsElement(),
matchingContext,
selectorList)) {
aList.AppendElement(cur->AsElement());
if (onlyFirstMatch) {
return NS_OK;
}
}
}
return NS_OK;
}
struct ElementHolder {
ElementHolder() : mElement(nullptr) {}
void AppendElement(Element* aElement) {
NS_ABORT_IF_FALSE(!mElement, "Should only get one element");
mElement = aElement;
}
Element* mElement;
};
/* static */
nsIContent*
FragmentOrElement::doQuerySelector(nsINode* aRoot, const nsAString& aSelector,
nsresult *aResult)
{
NS_PRECONDITION(aResult, "Null out param?");
ElementHolder holder;
*aResult = FindMatchingElements<true>(aRoot, aSelector, holder);
return holder.mElement;
}
/* static */
nsresult
FragmentOrElement::doQuerySelectorAll(nsINode* aRoot,
const nsAString& aSelector,
nsIDOMNodeList **aReturn)
{
NS_PRECONDITION(aReturn, "Null out param?");
nsSimpleContentList* contentList = new nsSimpleContentList(aRoot);
NS_ENSURE_TRUE(contentList, NS_ERROR_OUT_OF_MEMORY);
NS_ADDREF(*aReturn = contentList);
return FindMatchingElements<false>(aRoot, aSelector, *contentList);
}
size_t
FragmentOrElement::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const
{

View File

@ -97,7 +97,8 @@
#include "nsXBLPrototypeBinding.h"
#include "prprf.h"
#include "xpcpublic.h"
#include "nsCSSRuleProcessor.h"
#include "nsCSSParser.h"
#include "nsWrapperCacheInlines.h"
using namespace mozilla;
@ -2053,3 +2054,184 @@ nsINode::Length() const
return GetChildCount();
}
}
NS_IMPL_CYCLE_COLLECTION_1(nsNodeSelectorTearoff, mNode)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNodeSelectorTearoff)
NS_INTERFACE_MAP_ENTRY(nsIDOMNodeSelector)
NS_INTERFACE_MAP_END_AGGREGATED(mNode)
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNodeSelectorTearoff)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNodeSelectorTearoff)
NS_IMETHODIMP
nsNodeSelectorTearoff::QuerySelector(const nsAString& aSelector,
nsIDOMElement **aReturn)
{
nsresult rv;
nsIContent* result = mNode->QuerySelector(aSelector, &rv);
return result ? CallQueryInterface(result, aReturn) : rv;
}
NS_IMETHODIMP
nsNodeSelectorTearoff::QuerySelectorAll(const nsAString& aSelector,
nsIDOMNodeList **aReturn)
{
return mNode->QuerySelectorAll(aSelector, aReturn);
}
// NOTE: The aPresContext pointer is NOT addrefed.
// *aSelectorList might be null even if NS_OK is returned; this
// happens when all the selectors were pseudo-element selectors.
static nsresult
ParseSelectorList(nsINode* aNode,
const nsAString& aSelectorString,
nsCSSSelectorList** aSelectorList)
{
NS_ENSURE_ARG(aNode);
nsIDocument* doc = aNode->OwnerDoc();
nsCSSParser parser(doc->CSSLoader());
nsCSSSelectorList* selectorList;
nsresult rv = parser.ParseSelectorString(aSelectorString,
doc->GetDocumentURI(),
0, // XXXbz get the line number!
&selectorList);
NS_ENSURE_SUCCESS(rv, rv);
// Filter out pseudo-element selectors from selectorList
nsCSSSelectorList** slot = &selectorList;
do {
nsCSSSelectorList* cur = *slot;
if (cur->mSelectors->IsPseudoElement()) {
*slot = cur->mNext;
cur->mNext = nullptr;
delete cur;
} else {
slot = &cur->mNext;
}
} while (*slot);
*aSelectorList = selectorList;
return NS_OK;
}
// Actually find elements matching aSelectorList (which must not be
// null) and which are descendants of aRoot and put them in aList. If
// onlyFirstMatch, then stop once the first one is found.
template<bool onlyFirstMatch, class T>
inline static nsresult FindMatchingElements(nsINode* aRoot,
const nsAString& aSelector,
T &aList)
{
nsAutoPtr<nsCSSSelectorList> selectorList;
nsresult rv = ParseSelectorList(aRoot, aSelector,
getter_Transfers(selectorList));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(selectorList, NS_OK);
NS_ASSERTION(selectorList->mSelectors,
"How can we not have any selectors?");
nsIDocument* doc = aRoot->OwnerDoc();
TreeMatchContext matchingContext(false, nsRuleWalker::eRelevantLinkUnvisited,
doc, TreeMatchContext::eNeverMatchVisited);
doc->FlushPendingLinkUpdates();
// Fast-path selectors involving IDs. We can only do this if aRoot
// is in the document and the document is not in quirks mode, since
// ID selectors are case-insensitive in quirks mode. Also, only do
// this if selectorList only has one selector, because otherwise
// ordering the elements correctly is a pain.
NS_ASSERTION(aRoot->IsElement() || aRoot->IsNodeOfType(nsINode::eDOCUMENT) ||
!aRoot->IsInDoc(),
"The optimization below to check ContentIsDescendantOf only for "
"elements depends on aRoot being either an element or a "
"document if it's in the document.");
if (aRoot->IsInDoc() &&
doc->GetCompatibilityMode() != eCompatibility_NavQuirks &&
!selectorList->mNext &&
selectorList->mSelectors->mIDList) {
nsIAtom* id = selectorList->mSelectors->mIDList->mAtom;
const nsSmallVoidArray* elements =
doc->GetAllElementsForId(nsDependentAtomString(id));
// XXXbz: Should we fall back to the tree walk if aRoot is not the
// document and |elements| is long, for some value of "long"?
if (elements) {
for (int32_t i = 0; i < elements->Count(); ++i) {
Element *element = static_cast<Element*>(elements->ElementAt(i));
if (!aRoot->IsElement() ||
(element != aRoot &&
nsContentUtils::ContentIsDescendantOf(element, aRoot))) {
// We have an element with the right id and it's a strict descendant
// of aRoot. Make sure it really matches the selector.
if (nsCSSRuleProcessor::SelectorListMatches(element, matchingContext,
selectorList)) {
aList.AppendElement(element);
if (onlyFirstMatch) {
return NS_OK;
}
}
}
}
}
// No elements with this id, or none of them are our descendants,
// or none of them match. We're done here.
return NS_OK;
}
for (nsIContent* cur = aRoot->GetFirstChild();
cur;
cur = cur->GetNextNode(aRoot)) {
if (cur->IsElement() &&
nsCSSRuleProcessor::SelectorListMatches(cur->AsElement(),
matchingContext,
selectorList)) {
aList.AppendElement(cur->AsElement());
if (onlyFirstMatch) {
return NS_OK;
}
}
}
return NS_OK;
}
struct ElementHolder {
ElementHolder() : mElement(nullptr) {}
void AppendElement(Element* aElement) {
NS_ABORT_IF_FALSE(!mElement, "Should only get one element");
mElement = aElement;
}
Element* mElement;
};
nsIContent*
nsINode::QuerySelector(const nsAString& aSelector,
nsresult *aResult)
{
NS_PRECONDITION(aResult, "Null out param?");
ElementHolder holder;
*aResult = FindMatchingElements<true>(this, aSelector, holder);
return holder.mElement;
}
/* static */
nsresult
nsINode::QuerySelectorAll(const nsAString& aSelector,
nsIDOMNodeList **aReturn)
{
NS_PRECONDITION(aReturn, "Null out param?");
nsSimpleContentList* contentList = new nsSimpleContentList(this);
NS_ENSURE_TRUE(contentList, NS_ERROR_OUT_OF_MEMORY);
NS_ADDREF(*aReturn = contentList);
return FindMatchingElements<false>(this, aSelector, *contentList);
}

View File

@ -604,7 +604,7 @@ nsSVGSVGElement::GetElementById(const nsAString & elementId, nsIDOMElement **_re
nsresult rv = NS_OK;
nsAutoString selector(NS_LITERAL_STRING("#"));
nsStyleUtil::AppendEscapedCSSIdent(PromiseFlatString(elementId), selector);
nsIContent* element = nsGenericElement::doQuerySelector(this, selector, &rv);
nsIContent* element = QuerySelector(selector, &rv);
if (NS_SUCCEEDED(rv) && element) {
return CallQueryInterface(element, _retval);
}

View File

@ -798,13 +798,10 @@ customMethodCalls = {
'nsIDOMNodeSelector_QuerySelector': {
'thisType': 'nsINode',
'code': ' nsIContent* result = '
'nsGenericElement::doQuerySelector(self, arg0, &rv);'
'self->QuerySelector(arg0, &rv);'
},
'nsIDOMNodeSelector_QuerySelectorAll': {
'thisType': 'nsINode',
'code': ' nsCOMPtr<nsIDOMNodeList> result;\n'
' rv = nsGenericElement::doQuerySelectorAll(self, '
'arg0, getter_AddRefs(result));'
'thisType': 'nsINode'
},
'nsIDOMNode_Normalize': {
'thisType': 'nsINode',