var nodelist = document.getAnonymousNodes(node);
if (nodelist[0] == node.firstChild)
// !! DOM API gives anonymous nodes??
Am I mistaken? Can this be true? All we know about node is that it is
from a XUL document.
My goal is to enumerate over all of the nodes. I thought I just had
figured it out: walk over the anonymous nodes from the list above, then
the regular nodes. But I was assuming that the regular nodes start with
node.firstChild, and that does not seem to be the case.
jjb
> var nodelist = document.getAnonymousNodes(node);
> if (nodelist[0] == node.firstChild)
> // !! DOM API gives anonymous nodes??
I can't see how that could happen, although it would be interesting to
see what document.getBindingParent(node.firstChild) returns. I say that
because when you start using XBL heavily things get confusing very quickly.
Consider <foo><bar/></foo>. Now foo has an XBL binding whose contents
are <baz><children/></baz>. This means that an event targetted at <bar>
bubbles up through <baz> and then <foo>, which is confusing for <baz>,
as its hasChildNodes() returns false!
--
Warning: May contain traces of nuts.
It can't.
> var nodelist = document.getAnonymousNodes(node);
> if (nodelist[0] == node.firstChild)
> // !! DOM API gives anonymous nodes??
I think you were mislead by the name of the (sadly undocumented and
possibly mis-named) getAnonymousNodes method. I do apologize for this
mess of an API. I've documented getAnonymousNodes a bit, in hopes of
making its behavior more clear.
getAnonymousNodes returns non-null only if the element passed to it has
an XBL binding attached to it. In this case, it returns a nodelist
representing the children of this element in the flattened tree. This
list can include some of the element's actual DOM kids, as well as nodes
that come from the element's binding or nodes that come from bindings on
ancestors of the element.
For example, consider a binding with a <content> section that looks like
this:
<content>
<span xmlns="http://www.w3.org/1999/xhtml">Something before</span>
<children/>
</content>
If this binding is attached to an element, the getAnonymousNodes for
that element will be a nodelist containing the <span> followed by all
the element's kids (both its DOM kids, and kids coming from <children>
kids due to bindings on ancestors), in order.
> My goal is to enumerate over all of the nodes.
"all" in what sense?
> But I was assuming that the regular nodes start with node.firstChild
This assumption is correct.
-Boris
Where is it documented?
>
> getAnonymousNodes returns non-null only if the element passed to it has
> an XBL binding attached to it. In this case, it returns a nodelist
> representing the children of this element in the flattened tree. This
What is a "flattened tree"? You chopped it down or what? ;-)
> list can include some of the element's actual DOM kids, as well as nodes
Hmm..."some" is not a very encouraging word for an API.
> that come from the element's binding or nodes that come from bindings on
> ancestors of the element.
Huh?
>
> For example, consider a binding with a <content> section that looks like
> this:
>
> <content>
> <span xmlns="http://www.w3.org/1999/xhtml">Something before</span>
> <children/>
> </content>
>
> If this binding is attached to an element, the getAnonymousNodes for
> that element will be a nodelist containing the <span> followed by all
> the element's kids (both its DOM kids, and kids coming from <children>
> kids due to bindings on ancestors), in order.
Ok, so which of any of that would a xul developer want to see in Chromebug?
>
>> My goal is to enumerate over all of the nodes.
>
> "all" in what sense?
In the the sense of "every single one"? I didn't expect to have to also
add "and not a single extra one".
>
>> But I was assuming that the regular nodes start with node.firstChild
>
> This assumption is correct.
Ok, well that is one would be the only thing that makes sense here then.
jjb
> Boris Zbarsky wrote:
>
>> I've documented getAnonymousNodes a bit, in hopes of making its behavior
>> more clear.
>>
>
> Where is it documented?
>
bz meant this: http://hg.mozilla.org/mozilla-central/rev/8d546f1d4a84
But you could update
https://developer.mozilla.org/en/XBL/XBL_1.0_Reference/DOM_Interfaces#getAnonymousNodesas
well :)
>
>
>> getAnonymousNodes returns non-null only if the element passed to it has an
>> XBL binding attached to it. In this case, it returns a nodelist
>> representing the children of this element in the flattened tree. This
>>
>
> What is a "flattened tree"? You chopped it down or what? ;-)
>
Google it, the definition in the XBL2 spec ("final flattened tree") is
accurate, although it uses XBL2 terminology. This is the tree with both
anonymous and real nodes (with all bindings applied).
> list can include some of the element's actual DOM kids, as well as nodes
>>
>
> Hmm..."some" is not a very encouraging word for an API.
>
> that come from the element's binding or nodes that come from bindings on
>> ancestors of the element.
>>
>
> Huh?
>
I guess bz hints at <children>'s behavior, it has interesting @include attr
and can be non-direct child of the bound element.
>
>> For example, consider a binding with a <content> section that looks like
>> this:
>>
>> <content>
>> <span xmlns="http://www.w3.org/1999/xhtml">Something before</span>
>> <children/>
>> </content>
>>
>> If this binding is attached to an element, the getAnonymousNodes for that
>> element will be a nodelist containing the <span> followed by all the
>> element's kids (both its DOM kids, and kids coming from <children> kids due
>> to bindings on ancestors), in order.
>>
>
> Ok, so which of any of that would a xul developer want to see in Chromebug?
>
Obviously a rhetorical question that will be followed by bz trying to
determine what does a "xul developer" want and so on... :)
Why don't you copy what the DOM Inspector does?
Nickolay
So, getAnonymousNodes returns all of the nodes, that is the regular
nodes and the anonymous nodes, in one list, is that correct?
If I visit each node in the list returned by getAnonymousNodes, and call
getAnonymousNodes() on them, recursively, I will visit all of the nodes
once?
Specifically I would visit node.firstChild, node.firstChild.nextSibling,
etc?
I gather that there is no XBL equivalent to nextSibling? So given an
element that is a child, computing the sibling would require iterating
over the parent's nodelist from getAnonymousNodes(). (Firebug's HTML
panel uses nextSibling iteration).
jjb
> So, getAnonymousNodes returns all of the nodes, that is the regular
> nodes and the anonymous nodes, in one list, is that correct?
For elements with their own XBL anonymous nodes, yes. (As per my
previous post, elements can have anonymous children without necessarily
having an XBL binding.)
> If I visit each node in the list returned by getAnonymousNodes, and
> call getAnonymousNodes() on them, recursively, I will visit all of the
> nodes once?
You would not visit nodes more than once. Unfortunately you might not
visit some nodes at all.
> I gather that there is no XBL equivalent to nextSibling? So given an
> element that is a child, computing the sibling would require iterating
> over the parent's nodelist from getAnonymousNodes().
Worse, you would need to use the anonymous parent, which XBL1 doesn't
expose.
Note that when I tried to do that last night devmo currently responded
with timeouts to every single request for anything other than the front
page.
And when I tried is just now, it responds with a connection drop to
attempts to log in.
Makes it rather hard to update the docs.
-Boris
Nickolay covered this.
>> getAnonymousNodes returns non-null only if the element passed to it
>> has an XBL binding attached to it. In this case, it returns a nodelist
>> representing the children of this element in the flattened tree. This
>
> What is a "flattened tree"? You chopped it down or what? ;-)
Nickolay mostly covered this too. Basically, the flattened tree is the
dom-like tree one uses to actually construct CSS boxes in the presence
of XBL. Its parent/child relationships have been modified by XBL
binding insertion points.
>> list can include some of the element's actual DOM kids, as well as nodes
>
> Hmm..."some" is not a very encouraging word for an API.
Well, it depends on the binding's details. If your binding has:
<content>
<children includes="span"/>
<foobar>
<children/>
</foobar>
</content>
then getAnonymousNodes will return the "span" children of the node, in
order, then the foobar element. The other children of the node will be
kids of the foobar in the flattened tree. This is really not a function
of the getAnonymousNodes API but of the flattened tree construction model.
>> that come from the element's binding or nodes that come from bindings
>> on ancestors of the element.
>
> Huh?
Consider this document:
<div>
<span>
</div>
And a binding that's applied to the <div> which has:
<content>
<hbox>
<foo/>
<children/>
</hbox>
</content>
And a binding that's applied to the <hbox> which has:
<content>
<bar/>
<children/>
</content>
Then getAnonymousNodes on the <hbox> will return a list containing the
<bar>, <foo>, <span> in that order. The <bar> comes from the hbox
binding, the <foo> is a DOM child of the hbox, the <span> comes from a
binding on an ancestor (and the binding parent, in this case, but in
general it would be some binding ancestor) of the hbox.
None of this is magic about getAnonymousNodes; all the magic is in the
XBL flattened tree construction.
> Ok, so which of any of that would a xul developer want to see in Chromebug?
A XUL developer is most likely to want to see the flattened tree kids
for an element. That means the contents of getAnonymousNodes if it's
non-null. If its null, they'd want to see the kids with all insertion
points under the element resolved. I don't believe there's a JS API for
getting this nodelist directly. DOM Inspector uses the
inIDeepTreeWalker component to walk over the flattened tree.
>> "all" in what sense?
>
> In the the sense of "every single one"? I didn't expect to have to also
> add "and not a single extra one".
Yes, but all nodes in the document? In some subtree? In some child
list? Something else?
You should probably seriously consider just doing what DOM Inspector
does here.
-Boris
No. It returns exactly what it returns. The nodes that should look
like the kids of the given node while constructing the box model. And
only if the node has a binding attached to it. Sometimes this list
includes some of the element's DOM kids. Sometimes it does not.
> If I visit each node in the list returned by getAnonymousNodes, and call
> getAnonymousNodes() on them, recursively, I will visit all of the nodes
> once?
No. See my previous mail.
> Specifically I would visit node.firstChild, node.firstChild.nextSibling,
> etc?
No.
> I gather that there is no XBL equivalent to nextSibling?
Not at the moment, though there was a bug on implementing flattened tree
traversal methods at some point.
You should seriously look into inIDeepTreeWalker.
-Boris
I believe inIDeepTreeWalker exposes this fine.
-Boris
Thanks and thanks to Neil for all of your help. Unfortunately it sounds
like supporting anonymous nodes would be a much bigger effort than it
seemed when I started. I better re-assess the cost/benefit here.
jjb
To be clear, it's impossible to sanely debug chrome without this feature.
-Boris
And to be even more clear, there are 672 non-anonymous nodes in the
Firefox UI. There are 5000-ish nodes total (anonymous and not).
-Boris
Well there are 2M firebug users and 4 users of Chromebug. I have to be
realistic about where to invest a week's time. Adding anonymous node
support may or may not change that dynamic. We use Chromebug on Firebug
all of the time and never found a need for the anonymous info. I have no
way to tell what features are essential and what impact could result
from adding stuff beyond what we need for Firebug debugging.
On the plus side, I did look into your suggestion. inIDeepTreeWalker
extends nsIDOMTreeWalker. Neither of these have MDC pages. I found zero
uses of inIDeepTreeWalker in mozilla-central.
http://mxr.mozilla.org/mozilla-central/search?string=inIDeepTreeWalker
The IDL for nsIDOMTreeWalker as a cryptic comment
// Introduced in DOM Level 2:
http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/traversal/nsIDOMTreeWalker.idl#48
Eventually I discovered that the DOM Level 2 has an interface call
TreeWalker,
http://www.w3.org/TR/2000/REC-DOM-Level-2-Traversal-Range-20001113/traversal.html#TreeWalker
so that is what that comment must mean.
The method calls on TreeWalker look to be identical to the properties
used by Firebug for its HTML traversal. The only real difference is that
the state of the traversal is not carried in the model (node). To switch
means understanding the lifetime of the traversal in Firebug so that
the lifetime of the walker matches it, and understanding which flags to
set on the walker.
jjb
Cheers,
Shawn
And as far as "what would a XUL developer want", in my experience what
DOM Inspector does is more than sufficient. So making Firebug use the
same algorithm would be great...
Dan
I did. My reward:
"[JavaScript Error: "uncaught exception: [Exception... "Component
returned failure code: 0x80004001 (NS_ERROR_NOT_IMPLEMENTED)
[inIDeepTreeWalker.firstChild]"
http://mxr.mozilla.org/mozilla-central/source/layout/inspector/src/inDeepTreeWalker.cpp#181
jjb
Ah, hmm. It might only implement nextNode() and parentNode() traversal
at the moment. That shouldn't be all that hard to fix. File bugs on
any issues that block you?
As a note, this is a chicken and egg problem; I doubt you can get many
more chromebug users without having this working, and you're not
incentivised to get it working because chromebug is underused. In the
end, it all depends on what your goals for chromebug are, I guess.
-Boris
The bit of code the implements nextNode():
http://mxr.mozilla.org/mozilla-central/source/layout/inspector/src/inDeepTreeWalker.cpp#258
is a lot like the JS code I wrote when I thought I understood
getAnonymousNodes(). Are there any JS equivalents to:
(bindingManager = inLayoutUtils::GetBindingManagerFor(aNode))) {
bindingManager->GetAnonymousNodesFor(content,
getter_AddRefs(kids));
if (!kids)
bindingManager->GetContentListFor(content,
getter_AddRefs(kids));
?
>
> As a note, this is a chicken and egg problem; I doubt you can get many
> more chromebug users without having this working, and you're not
> incentivised to get it working because chromebug is underused. In the
> end, it all depends on what your goals for chromebug are, I guess.
Yes, though I think I've sorted out the issues with JS and with windows
in Chromebug enough for it to be useful for most extensions. I realize
that XBL binding is very important to mozilla application developers but
not so much for extensions. As for goals, I want Chromebug to be
Firebug for XUL, but that does not help me have enough time to achieve
that goal.
jjb
No.
> bindingManager->GetAnonymousNodesFor(content,
> getter_AddRefs(kids));
Yes. The getAnonymousNodes.
> if (!kids)
> bindingManager->GetContentListFor(content,
> getter_AddRefs(kids));
No, and this is the key problem that prevents a JS implementation not
using inIDeepTreeWalker from actually being able to walk all the nodes.
> Yes, though I think I've sorted out the issues with JS and with windows
> in Chromebug enough for it to be useful for most extensions. I realize
> that XBL binding is very important to mozilla application developers but
> not so much for extensions.
I'm not sure why that would be the case, exactly... Extensions use a
fair amount of XBL, not even including the built-in bindings they rely on.
-Boris
I looked into the DOM Inspector code so see if this was feasible. As far
as I can tell, the key bits, where we get the anonymous nodes, is down
in inIDOMView, a XUL tree view thing implemented in C++. The thing that
opens the view twisties to drill down seems to be selectElementInTree,
http://mxr.mozilla.org/comm-central/source/mozilla/extensions/inspector/resources/content/viewers/dom/dom.js#745
It uses the DOM Walker only to set up the selection. Then it uses
operations on inIDOMView to fill in, esp. ToggleOpenState which calls
ExpandNode, and that calls GetChildNodesFor(), then we arrive at
code very similar to what Boris already said was not accessible to JS:
http://mxr.mozilla.org/comm-central/source/mozilla/layout/inspector/src/inDOMView.cpp#1240
So I am afraid this too is a dead end, absent new C++ code.
jjb
Do we have reason to believe that such new C++ code would be unwelcome?
Mike
OK. Could we get a bug filed with a description of what you need to
make this work, whether it be more of the methods on inDeepTreeWalker
implemented or whether it be a getter for the flattened tree child list?
This should be trivial to fix on the Gecko end to give you the APIs
you need to make your end work.
-Boris
I'm not sure what you want, here is what I did:
https://bugzilla.mozilla.org/show_bug.cgi?id=522601
jjb
I wanted you to describe what would make it easiest for you to implement
showing anonymous content. That bug report is fine in that regard.
-Boris
And to be clear, I wanted you to decide for yourself whether what you
want is to add functionality to inIDeepTreeWalker or whether you wanted
a function that takes an element and returns the list of its kids in the
flattened tree (similar to getAnonymousNodes, but doing the right thing
for all cases, including all the kids being no-anonymous, etc).
-Boris
I don't actually understand the data model, so its hard for me to make
suggestions. From what I understand, this 'flattened' tree is the real
DOM tree, it has all of the nodes and they form a tree. But some of
these nodes are not HTML or XUL but some special kind 'anonymous'. In
the normal traversal, node.nextSibling will not return these special
'anonymous' nodes, but these special nodes can have non-special,
non-anonymous children, and those nodes have nextSibling, etc, creating
a little island of regular-treeness hiding behind the anonymous nodes.
The inIDeepTreeWalker grabs a node and unlocks its secrets, the special
nodes hiding in the tree, and travels through them including new areas
of normal nodes within the tree. A list method,
getAllChildrenAnonymousOrNot(), would also work if called consistently
down the tree. The only way for node.firstChild to work would be to have
a kind of 'god' mode that changes the property returned from
'regular-nodes-only' to 'all-kinds-please'.
I think the modal version would be horrible, the list adequate, and the
walker best. The walker, if its implementation were complete, would
actually follows the DOM standards, so the same code can be used for
regular and anonymous node and it makes the documentation easier.
jjb
Well, hold on. inIDeepTreeWalker is just an nsIDOMTreeWalker
implementation that walks the flattened tree, not the DOM tree.
Since inIDeepTreeWalker doesn't quite do what we need, we can fix it to
be closer to what you need. Or we can add methods along the lines of
.firstChild/.nextSibling but working on the flattened tree (though the
.nextSibling will be expensive; explicit iteration along a child
nodelist would be cheaper here).
> I don't actually understand the data model, so its hard for me to make
> suggestions. From what I understand, this 'flattened' tree is the real
> DOM tree, it has all of the nodes and they form a tree. But some of
> these nodes are not HTML or XUL but some special kind 'anonymous'.
OK, that last sentence is pretty much totally wrong. Up to there,
though, you were doing well. ;)
You have a DOM tree. Some of the nodes have XBL bindings applied. The
XBL bindings effectively perform a transformation to create a new DOM
tree called the flattened tree. See
http://www.mozilla.org/projects/xbl/xbl2.html#final0 for a description
with some examples. The transformation can basically add nodes into the
tree in various ways; the only invariant preserved by the transformation
that I can think of is that if nodes A and B are both in the DOM and A
is a descendant of B, then A will continue to be a descendant of B in
the flattened tree, if A is present in it at all.
> In the normal traversal, node.nextSibling will not return these special
> 'anonymous' nodes
The XBL model is that the original DOM tree and the flattened tree both
exist; DOM methods operate as normal on the original DOM tree. So
.nextSibling on a node in the original DOM tree will never return a node
added via XBL, yes.
> but these special nodes can have non-special, non-anonymous children, and those nodes have nextSibling, etc, creating
> a little island of regular-treeness hiding behind the anonymous nodes.
Correct, though this island is always reachable via
.firstChild/.nextSibling from the DOM.
That is, the anonymous nodes have these non-anonymous kids in the
flattented tree, but the parentNode of the non-anonymous kids is
whatever their DOM parent is, not the anonymous node that's the
flattened tree parent.
> The inIDeepTreeWalker grabs a node and unlocks its secrets, the special
> nodes hiding in the tree, and travels through them including new areas
> of normal nodes within the tree. A list method,
> getAllChildrenAnonymousOrNot(), would also work if called consistently
> down the tree.
Correct.
> The only way for node.firstChild to work would be to have
> a kind of 'god' mode that changes the property returned from
> 'regular-nodes-only' to 'all-kinds-please'.
Or by having a new document.getFirstFlattenedTreeChildFor() method or
something, yes.
> I think the modal version would be horrible, the list adequate, and the
> walker best. The walker, if its implementation were complete, would
> actually follows the DOM standards, so the same code can be used for
> regular and anonymous node and it makes the documentation easier.
OK, sounds good. Thanks for bearing with me; I know this stuff is messy
and underdocumented. And thanks for filing the bug on the walker!
-Boris, going off to fix the walker
But my point was that the DOM TreeWalker has firstChild()/nextSibling()
so it can stand-in for the link properties on a node.
>
> Since inIDeepTreeWalker doesn't quite do what we need, we can fix it to
> be closer to what you need. Or we can add methods along the lines of
> .firstChild/.nextSibling but working on the flattened tree (though the
> .nextSibling will be expensive; explicit iteration along a child
> nodelist would be cheaper here).
I guess this amounts to who holds the state of the iterator?
I don't think performance is such an issue for chromebug, the whole
point of Firebug's HTML panel code is to only walk any part of the tree
the user explicitly asks to see. The bad case is a tree with a very
widely branching node.
...
>
> That is, the anonymous nodes have these non-anonymous kids in the
> flattented tree, but the parentNode of the non-anonymous kids is
> whatever their DOM parent is, not the anonymous node that's the
> flattened tree parent.
Oh. So it's wackier than I thought.
Does/Should the niIDeepTreeWalker.parentNode() return the possibily
anonymous parent in the flattened tree? I hope yes; I guess we will
need it.
jjb
Yes.
>> Since inIDeepTreeWalker doesn't quite do what we need, we can fix it
>> to be closer to what you need. Or we can add methods along the lines
>> of .firstChild/.nextSibling but working on the flattened tree (though
>> the .nextSibling will be expensive; explicit iteration along a child
>> nodelist would be cheaper here).
>
> I guess this amounts to who holds the state of the iterator?
Yeah.
> I don't think performance is such an issue for chromebug, the whole
> point of Firebug's HTML panel code is to only walk any part of the tree
> the user explicitly asks to see. The bad case is a tree with a very
> widely branching node.
Indeed. I do agree that for chromebug this is probably a non-issue.
> Does/Should the niIDeepTreeWalker.parentNode() return the possibily
> anonymous parent in the flattened tree? I hope yes; I guess we will need
> it.
Yes. parentNode() on the treewalker will give the parent of the
currentNode in the flattened tree (in the deep-tree-walker case) or the
DOM tree (in the standard DOM treewalker case, modulo the filter stuff;
I'm pretty sure that the deep-tree-walker ignores the filter stuff and
always does SHOW_ALL, but that's what you'd want here from the normal
DOM walker too).
-Boris