Bug 416317. Implement querySelector(All) API. r=dbaron, sr=jst
This commit is contained in:
@@ -126,6 +126,9 @@
|
||||
#include "nsIFocusController.h"
|
||||
#include "nsIControllers.h"
|
||||
#include "nsXBLInsertionPoint.h"
|
||||
#include "nsICSSStyleRule.h" /* For nsCSSSelectorList */
|
||||
#include "nsCSSRuleProcessor.h"
|
||||
|
||||
#ifdef MOZ_XUL
|
||||
#include "nsIXULDocument.h"
|
||||
#endif /* MOZ_XUL */
|
||||
@@ -1157,6 +1160,68 @@ nsDOMEventRTTearoff::AddEventListener(const nsAString& aType,
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_1(nsNodeSelectorTearoff, mContent)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNodeSelectorTearoff)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIDOMNodeSelector)
|
||||
NS_INTERFACE_MAP_END_AGGREGATED(mContent)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNodeSelectorTearoff)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNodeSelectorTearoff)
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNodeSelectorTearoff::QuerySelector(const nsAString& aSelector,
|
||||
nsIDOMElement **aReturn)
|
||||
{
|
||||
return nsGenericElement::doQuerySelector(mContent, aSelector, aReturn);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNodeSelectorTearoff::QuerySelectorAll(const nsAString& aSelector,
|
||||
nsIDOMNodeList **aReturn)
|
||||
{
|
||||
return nsGenericElement::doQuerySelectorAll(mContent, aSelector, aReturn);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(nsStaticContentList)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsStaticContentList)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mList)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsStaticContentList)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mList)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsStaticContentList)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsStaticContentList)
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsStaticContentList)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIDOMNodeList)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(NodeList)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsStaticContentList::GetLength(PRUint32* aLength)
|
||||
{
|
||||
*aLength = mList.Count();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsStaticContentList::Item(PRUint32 aIndex, nsIDOMNode** aReturn)
|
||||
{
|
||||
nsIContent* c = mList.SafeObjectAt(aIndex);
|
||||
if (!c) {
|
||||
*aReturn = nsnull;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return CallQueryInterface(c, aReturn);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
PRUint32 nsMutationGuard::sMutationCount = 0;
|
||||
|
||||
nsGenericElement::nsDOMSlots::nsDOMSlots(PtrBits aFlags)
|
||||
@@ -3583,6 +3648,8 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGenericElement)
|
||||
nsDOMEventRTTearoff::Create(this))
|
||||
NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference,
|
||||
new nsNodeSupportsWeakRefTearoff(this))
|
||||
NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIDOMNodeSelector,
|
||||
new nsNodeSelectorTearoff(this))
|
||||
// nsNodeSH::PreCreate() depends on the identity pointer being the
|
||||
// same as nsINode (which nsIContent inherits), so if you change the
|
||||
// below line, make sure nsNodeSH::PreCreate() still does the right
|
||||
@@ -4529,3 +4596,198 @@ nsGenericElement::GetLinkTarget(nsAString& aTarget)
|
||||
aTarget.Truncate();
|
||||
}
|
||||
|
||||
// NOTE: The aPresContext pointer is NOT addrefed.
|
||||
static nsresult
|
||||
ParseSelectorList(nsINode* aNode,
|
||||
const nsAString& aSelectorString,
|
||||
nsCSSSelectorList** aSelectorList,
|
||||
nsPresContext** aPresContext)
|
||||
{
|
||||
NS_ENSURE_ARG(aNode);
|
||||
|
||||
nsIDocument* doc = aNode->GetOwnerDoc();
|
||||
NS_ENSURE_STATE(doc);
|
||||
|
||||
nsCOMPtr<nsICSSParser> parser;
|
||||
nsresult rv = doc->CSSLoader()->GetParserFor(nsnull, getter_AddRefs(parser));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = parser->ParseSelectorString(aSelectorString,
|
||||
doc->GetDocumentURI(),
|
||||
0, // XXXbz get the right line number!
|
||||
aSelectorList);
|
||||
doc->CSSLoader()->RecycleParser(parser);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// It's not strictly necessary to have a prescontext here, but it's
|
||||
// a bit of an optimization for various stuff.
|
||||
*aPresContext = nsnull;
|
||||
nsIPresShell* shell = doc->GetPrimaryShell();
|
||||
if (shell) {
|
||||
*aPresContext = shell->GetPresContext();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback to be called as we iterate over the tree and match elements. If
|
||||
* the callbacks returns false, the iteration should be stopped.
|
||||
*/
|
||||
typedef PRBool
|
||||
(* PR_CALLBACK ElementMatchedCallback)(nsIContent* aMatchingElement,
|
||||
void* aClosure);
|
||||
|
||||
// returning false means stop iteration
|
||||
static PRBool
|
||||
TryMatchingElementsInSubtree(nsINode* aRoot,
|
||||
RuleProcessorData* aParentData,
|
||||
nsPresContext* aPresContext,
|
||||
nsCSSSelectorList* aSelectorList,
|
||||
ElementMatchedCallback aCallback,
|
||||
void* aClosure)
|
||||
{
|
||||
PRUint32 count = aRoot->GetChildCount();
|
||||
/* To improve the performance of '+' and '~' combinators and the :nth-*
|
||||
* selectors, we keep track of the immediately previous sibling data. That's
|
||||
* cheaper than heap-allocating all the datas and keeping track of them all,
|
||||
* and helps a good bit in the common cases. We also keep track of the whole
|
||||
* parent data chain, since we have those Around anyway */
|
||||
char databuf[2 * sizeof(RuleProcessorData)];
|
||||
RuleProcessorData* prevSibling = nsnull;
|
||||
RuleProcessorData* data = reinterpret_cast<RuleProcessorData*>(databuf);
|
||||
nsIContent * const * kidSlot = aRoot->GetChildArray();
|
||||
nsIContent * const * end = kidSlot + count;
|
||||
|
||||
PRBool continueIteration = PR_TRUE;
|
||||
for (; kidSlot != end; ++kidSlot) {
|
||||
nsIContent* kid = *kidSlot;
|
||||
if (!kid->IsNodeOfType(nsINode::eELEMENT)) {
|
||||
continue;
|
||||
}
|
||||
/* See whether we match */
|
||||
new (data) RuleProcessorData(aPresContext, kid, nsnull);
|
||||
NS_ASSERTION(!data->mParentData, "Shouldn't happen");
|
||||
NS_ASSERTION(!data->mPreviousSiblingData, "Shouldn't happen");
|
||||
data->mParentData = aParentData;
|
||||
data->mPreviousSiblingData = prevSibling;
|
||||
|
||||
if (nsCSSRuleProcessor::SelectorListMatches(*data, aSelectorList)) {
|
||||
continueIteration = (*aCallback)(kid, aClosure);
|
||||
}
|
||||
|
||||
if (continueIteration) {
|
||||
continueIteration =
|
||||
TryMatchingElementsInSubtree(kid, data, aPresContext, aSelectorList,
|
||||
aCallback, aClosure);
|
||||
}
|
||||
|
||||
/* Clear out the parent and previous sibling data if we set them, so that
|
||||
* ~RuleProcessorData won't try to delete a placement-new'd object. Make
|
||||
* sure this happens before our possible early break. Note that we can
|
||||
* have null aParentData but non-null data->mParentData if we're scoped to
|
||||
* an element. However, prevSibling and data->mPreviousSiblingData must
|
||||
* always match.
|
||||
*/
|
||||
NS_ASSERTION(!aParentData || data->mParentData == aParentData,
|
||||
"Unexpected parent");
|
||||
NS_ASSERTION(data->mPreviousSiblingData == prevSibling,
|
||||
"Unexpected prev sibling");
|
||||
data->mPreviousSiblingData = nsnull;
|
||||
if (prevSibling) {
|
||||
if (aParentData) {
|
||||
prevSibling->mParentData = nsnull;
|
||||
}
|
||||
prevSibling->~RuleProcessorData();
|
||||
} else {
|
||||
/* This is the first time through, so point |prevSibling| to the location
|
||||
we want to have |data| end up pointing to. */
|
||||
prevSibling = data + 1;
|
||||
}
|
||||
|
||||
/* Now swap |prevSibling| and |data|. Again, before the early break */
|
||||
RuleProcessorData* temp = prevSibling;
|
||||
prevSibling = data;
|
||||
data = temp;
|
||||
if (!continueIteration) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prevSibling) {
|
||||
if (aParentData) {
|
||||
prevSibling->mParentData = nsnull;
|
||||
}
|
||||
/* Make sure to clean this up */
|
||||
prevSibling->~RuleProcessorData();
|
||||
}
|
||||
return continueIteration;
|
||||
}
|
||||
|
||||
PR_STATIC_CALLBACK(PRBool)
|
||||
FindFirstMatchingElement(nsIContent* aMatchingElement,
|
||||
void* aClosure)
|
||||
{
|
||||
NS_PRECONDITION(aMatchingElement && aClosure, "How did that happen?");
|
||||
nsIContent** slot = static_cast<nsIContent**>(aClosure);
|
||||
*slot = aMatchingElement;
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsresult
|
||||
nsGenericElement::doQuerySelector(nsINode* aRoot, const nsAString& aSelector,
|
||||
nsIDOMElement **aReturn)
|
||||
{
|
||||
NS_PRECONDITION(aReturn, "Null out param?");
|
||||
|
||||
nsAutoPtr<nsCSSSelectorList> selectorList;
|
||||
nsPresContext* presContext;
|
||||
nsresult rv = ParseSelectorList(aRoot, aSelector,
|
||||
getter_Transfers(selectorList),
|
||||
&presContext);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsIContent* foundElement = nsnull;
|
||||
TryMatchingElementsInSubtree(aRoot, nsnull, presContext, selectorList,
|
||||
FindFirstMatchingElement, &foundElement);
|
||||
|
||||
if (foundElement) {
|
||||
return CallQueryInterface(foundElement, aReturn);
|
||||
}
|
||||
|
||||
*aReturn = nsnull;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
PR_STATIC_CALLBACK(PRBool)
|
||||
AppendAllMatchingElements(nsIContent* aMatchingElement,
|
||||
void* aClosure)
|
||||
{
|
||||
NS_PRECONDITION(aMatchingElement && aClosure, "How did that happen?");
|
||||
static_cast<nsStaticContentList*>(aClosure)->AppendContent(aMatchingElement);
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsresult
|
||||
nsGenericElement::doQuerySelectorAll(nsINode* aRoot,
|
||||
const nsAString& aSelector,
|
||||
nsIDOMNodeList **aReturn)
|
||||
{
|
||||
NS_PRECONDITION(aReturn, "Null out param?");
|
||||
|
||||
nsStaticContentList* contentList = new nsStaticContentList();
|
||||
NS_ENSURE_TRUE(contentList, NS_ERROR_OUT_OF_MEMORY);
|
||||
NS_ADDREF(*aReturn = contentList);
|
||||
|
||||
nsAutoPtr<nsCSSSelectorList> selectorList;
|
||||
nsPresContext* presContext;
|
||||
nsresult rv = ParseSelectorList(aRoot, aSelector,
|
||||
getter_Transfers(selectorList),
|
||||
&presContext);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
TryMatchingElementsInSubtree(aRoot, nsnull, presContext, selectorList,
|
||||
AppendAllMatchingElements, contentList);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user