Closed Bug 206687 Opened 18 years ago Closed 4 years ago

Implement ElementXBL

Categories

(Core :: XBL, defect)

x86
All
defect
Not set
normal

Tracking

()

RESOLVED WONTFIX

People

(Reporter: WeirdAl, Unassigned)

References

()

Details

Attachments

(1 file)

A quick search of LXR shows we don't have a full implementation of DocumentXBL,
or any implementation of ElementXBL.  We don't even have IDL files for them.

Bugzilla shows no open bugs against XBL for that purpose, so this bug is for
tracking DocumentXBL and ElementXBL.
Dup of bug 206586?

/be
I'm not cleared for that bug... ?
I'll let bsmedberg decide what's a dup.  This may be the right single-issue (or
dual-issue?  Maybe we need a bug for each of DocumentXBL and ElementXBL?) bug
that the other bug (which seems kind of "meta" to me) should depend on.

I'm not sure the other bug should be security-sensitive any longer.  Benjamin?

/be
OK, this bug is going to be about the ElementXBL interface. Bug 206586 (now
open) will be a tracking bug for the general architecture issues. I will open
other dependent bugs (DocumentXBL and the security architecture) as needed.

This is probably going to happen on a branch, because a security review may be
required.
Assignee: hyatt → bsmedberg
Blocks: 206586
OS: Windows 98 → All
Summary: Implement DocumentXBL, ElementXBL → Implement ElementXBL
This is a first-crack at the ElementXBL interface. Note that this there are
significant differences between what's here and the 1.0 specification on the
mozilla.org website.

I've kept addBinding() and removeBinding() even though they are currently (and
probably permanently) unimplemented. Neil says they cannot be implemented until
JS2.0 is avilable, because you need multiple-inheritence.
Hixie/hyatt please let me know what you think of this
This would be the interface on what? Anonymous nodes, or explicit nodes that are
children of a bound element?
Ian, this interface would be implemented on every DOM node. e.g. on content
nodes without XBL bindings, childNodesXBL would return the same NodeList as
nsIDOMNode::childNodes returns.

However, depending on the security discussions in bug 206586, the methods might
throw exceptions for anonymous content that the page is not allowed to acccess.
Let me rephrase my question.

If you have the following case:

   <a><b/></a>

...and:

   a { -moz-binding: url(...); }

...which creates:

   <a><foo:c><b/></foo:c></a>

...and:

   foo|c { -moz-binding: url(...); }

...which creates:

   <a><foo:c><b/><foo:bar/></foo:c></a>

...what would b.xblNextSibling return?

In other words, what scopes does this interface cross?
This interface does not distinguish scope (it crosses all scope). So
b.nextSiblingXBL returns the <foo:bar/> element.
Then I don't really like it. I'd rather have something like:

IDL Definition:

   interface ElementXBL {
     readonly attribute NodeList xblChildNodes; 
     readonly attribute Element bindingOwner;
     readonly attribute Element anonymousParent;
     
     void addBinding(in DOMString bindingURI);
     void removeBinding(in DOMString bindingURI);
     boolean hasBinding(in DOMString bindingURI);
   };

Properties

    xblChildNodes
        The xblChildNodes property is a NodeList that represents the true 
        children of the element in the default view after insertion points and 
        element tags have been applied. This property can be used to navigate 
        the content model according to XBL after bindings have moved explicit 
        and anonymous children using children tags. The property's value is the 
        same as childNodes if the element is not bound in the default view.

    bindingOwner
        The bindingOwner property is used to obtain the bound element with the 
        binding attached that is responsible for the generation of the specified 
        anonymous node. This property enables an author to determine the scope 
        of any content node. For content at the document-level scope, the 
        property's value is null.

    anonymousParent
        The anonymousParent property's value is the anonymous parent in the 
        default view for an element that was placed underneath an insertion 
        point using the children element. The property's value is null if the 
        element was not repositioned.

Methods

    addBinding
        The addBinding method attaches the specified binding (and any bindings 
        that the binding inherits from) to an element. This call is not 
        necessarily synchronous. The binding may not be attached yet when the 
        call completes. The binding is attached in all views.

        Parameters
            bindingURI of type DOMString
                A URI that specifies the location of a specific binding to 
                attach.
        No Return Value
        No Exceptions

    removeBinding
        The removeBinding method detaches the specified binding (and any 
        bindings that the binding inherits from explicitly using the extends 
        attribute) from the element. This method can only detach bindings that 
        were attached using addBinding. If the binding in question is not 
        attached to this element (or was attached through CSS ), then the method 
        does nothing.

        Parameters
            bindingURI of type DOMString
                A URI that specifies the location of a specific binding to 
                detach.
        No Return Value
        No Exceptions

    hasBinding
        The hasBinding method walks up the bindings applied to the element in 
        the default view and compares each binding's URI with the parameter 
        passed. This can be used to check if an element has been bound to a 
        particular binding in in order to ensure that the expected methods and 
        properties are available. For example widgets may walk up their 
        ancestors looking for an element that has been bound to the 
        form-container binding in order to locate their scope (so that radio 
        buttons may properly be mutually exclusive, or so that a submit button 
        can properly submit a form).

        Parameters
            bindingURI of type DOMString
                A URI that specifies the location of a specific binding for 
                which to look.
        Returns
            boolean
                true if any of the bindings match the parameter, false 
                otherwise.
        No Exceptions


This allows you to get from an element to its anonymous children, if you need to
crawl them, while avoiding the problem of crossing scopes. The ElementXBL
properties are thus all explicit scope-crossing properties.

If you want to walk from an anonymous node to an explicit one you can do so
using the Node properties nextSibling, etc.
Ian, see bug 206586, I'm looking for a interface that allows modifications of
the anonymous content structure, not just read-walking.
I'm confused. How does the DOM Node interface not already provide this?

e.g.: http://www.hixie.ch/tests/adhoc/xbl/019.html
Ian, your testcase is exactly what I'm trying to avoid. You are moving the
explicit children, and they are becoming anonymous. If you try to submit that
form, you won't get anything!
I think that's a bug (they shouldn't turn into anonymous nodes).

I'll talk this over with hyatt and let you know what the conclusion is. If I
haven't gotten back to you by the weekend, ping me again.
If I may offer my two bits:

firstChildXBL, lastChildXBL, previousSiblingXBL, nextSiblingXBL seem like a
touch of features not needed; ditto for insertBeforeXBL, replaceChildXBL,
removeChildXBL.  They aren't in the spec, and we should keep discussion of these
to bug 206586.

I'll comment more on the extensions in that bug; for now I propose this bug
remain for implementing ElementXBL as given in the XBL 1.0 spec.  (This bug is
blocking that one, anyway.)

Should I file a new bug for adding the bindingDocuments property of DocumentXBL
to our current DocumentXBL implementation?
Ian, the elements should become anonymous.  If you use normal DOM calls, you are
inserting the nodes in a tree at the same scope level as the parent.  Thus
moving explicit children using normal DOM calls to a position underneath an
anonymous element would result in them becoming anonymous.  Having some sort of
scope level invariant that is preserved across DOM operations is nonsensical and
needlessly complex IMHO.

I agree with Alex that a whole set of XBL-mirrored DOM operations is overkill. 
I also agree, though, that it should be possible to create and manipulate
insertion points.  I would, however, recommend that this manipulation be
performed using a createInsertionPoint method on ElementXBL that hands back a
new interface, XBLInsertionPoint.

Similarly, you could expose an array of XBLInsertionPoints (e.g.,
insertionPoints) on the binding and have a removeInsertionPoint function.  It is
IMO not intuitive to have methods like xblAppendChild, because that really isn't
how you should think of it.  The insertion points have rules that must be
matched dynamically, so objects could constantly be shifting around as they
matched different XPath selectors on the <children>.  

The binding can even have its anonymous content dynamically destroyed if
insertion points suddenly can't accommodate all children.  Therefore any
manipulation of insertion points needs to be done using specialized methods that
manipulate only insertion points, and that don't attempt to actually explicitly
place children under specific insertion points (in potential violation of the
very rules that the insertion point is supposed to adhere to).
It is worth noting that I didn't specify insertion point routines on ElementXBL
mainly because without rich XPath selection capabilities, it's not going to be
all that useful yet.  (Insertion points in general aren't terribly useful as
implemented in XUL because they can only filter by tag name.)
In Ian's proposal anonymousParent could then be replaced with insertionParent,
which could return an XBLInsertionPoint.  I suppose this property could be
settable and throw an exception if the element doesn't match the constraints of
the insertion point.  Elements could then stay locked to an insertion point as
long as they continue to match that insertion point, so you could then create
any number of totally generic insertion points (e.g., <children/>) and place
kids under whichever one you choose by setting their insertionParent properties.

So if you had some tab box with an anonymous |box|, and you then made that |box|
contain row children, you could create a second row like this:

var secondRowContainer = document.createElementNS("box", "xulns");
box.appendChild(secondRowContainer);
var secondRowInsertionPoint = this.createInsertionPoint(secondRowContainer, 0,
""); // Parent, index, XPath selector
for (var i = 11; i < 20; i++) // Put tabs 11-20 on second row.
  firstRowContainer.xblChildNodes[i].insertionParent = secondRowInsertionPoint;

BTW, addBinding and removeBinding are implementable without JS2.0.  They're just
hard.
interface XBLInsertionPoint {
  readonly Element element;
  readonly attribute unsigned int index;
  attribute DOMString includes;
}

and...

interface ElementXBL {
  readonly attribute NodeList xblChildNodes; 
  readonly attribute Element bindingOwner;
  attribute XBLInsertionPoint insertionParent;
  
  void addBinding(in DOMString bindingURL);
  void removeBinding(in DOMString bindingURL);

  XBLInsertionPoint createInsertionPoint(in Element parent, 
     in unsigned int index, in DOMString includes);
  void removeInsertionPoint(in XBLInsertionPoint point);

  // Not sure how to represent a collection that isn't of Nodes.
  // Might have to link insertionPoints with next pointers.  
  readonly Array(?) insertionPoints;
};
D'oh.  Insertion points also have a type (explicit vs. inherited), so add a
fourth argument (type) to createInsertionPoint, and add a readonly boolean type
field to XBLInsertionPoint.
David, I've again redrafted my proposal. Instead of "insertion points" (the
<element> and <children> tags) I have documented a concept of "connection
points". An insertion point is expanded into a connection point at
binding-attachment.

The problem with the interface you posted is that you cannot move explicit items
around.

http://bdsmedberg.no-ip.org/xbl-proposal.html
(This DNS is blocked by some AOL DNS servers, so do a DNS lookup from an
authoratative native server if that's a problem and use the IP address.)
You can move explicit items around with Hyatt's proposal simply by changing an
element's insertionParent, as his example in comment 19.
One thing that isn't clear to me in hyatt's proposal is how the DOM would
represent sibling insertion points, as in:

   <content>
     <children/>
     <divider xmlns=""/>
     <children/>
   </content>

It seems it might be necessary to have the insertionPoints actually be Nodes
that contain the explicit children, although then we'd have the interesting
situation of the DOM not being what selectors match on.
OK, that makes some sense. Instead of the many Node-like methods, a single
xblParent = someNode; call would work.

I don't think you need the XBLInsertParent interface to make this work. If you
keep my separation of "insertion points" and "connection points", you have an
explicit place to insert new children, and you don't have to massage the DOM for
CSS selectors and other evil things.
IMO we should not allow the same element to be inserted in more than one place, so

<content>
  <children/>
  <xul:divider/>
  <children/>
</content>

would place the children in only one of the <children/> insertion points.
Mozilla currently uses the last matching insertion point, and that seems good to me.
The <children/> elements could have different filters, the problem still stands.
I retract. xblParent = something still isn't enough. How do you rearrange nodes
(or even insert them in the proper place)?

Ian, I don't see what the problem is with the multiple children elements... the
children are not children of the children element, they are merely connected at
the point the <children/> element indicates...

Do you mean that the <children/> element itself needs to be included in the
xblChild* attribute.
XBLInsertionPoints have indices, so in this example:

<content>
  <children/>
  <baz:foo/>
  <children/>
</content>

You have an insertion point with an index of 0, and a second insertion point
with an index of 1.  They are distinguishable.

There is a problem with my proposal though in that an element can be shoved
through an arbitrary number of insertion points before arriving at its final
destination, which means that - rather than having a single "insertionParent",
and element has a whole chain of insertion points that it potentially moved through.

Therefore "insertionParent" should be removed.  I think "xblParentNode" should
come back as the way of getting your parent that you finally end up shoved under
.  It can return a Node.

Instead of .insertionParent, we will need:

XBLInsertionPoint getInsertionPointFor(Element childElement);

and

void setInsertionPointFor(Element childElement, XBLInsertionPoint insertionPoint);
You don't have to rearrange nodes.  They stay in document order at a given
insertion point.  (There is some ambiguity regarding what happens when you mix
different scope levels at the same insertion point, so it might be worthwhile to
attempt to establish some total order of DOM nodes including scopes).
I oppose the ability to arrange children of an insertion point, because then you
make it totally ambiguous how to order children when new DOM nodes implicitly
arrive at an insertion point (e.g., if inserted dynamically into the document).
 If you scramble the order at all, then you make it impossible to determine an
accurate position for new elements.  I also haven't seen a use case yet for
being able to rearrange elements at an insertion point.

David,

Perhaps you don't like my proposal for separating "insertion points" and
"connection points", but I believe that it handles arbitrary ordering of
children without problem.
I think introducing the concept of connection points needlessly complicates
matters. Insertion points can do content reordering, just insert an insertion
point for each node if necessary. Since we say explicit children remember which
of the insertion points that match it they are currently bound to, there isn't a
problem.
The problem with your connection points proposal is that you're missing two key
aspects of how insertion points work.

(1) Insertion points are dynamic.  Elements implicitly move as they match new
filters.
(2) An element can have an arbitrary number of insertion points due to nested
bindings. 
David,

> (1) Insertion points are dynamic.  Elements implicitly move as they match new
> filters.
My proposal constructs the tree using the <children>, but at that point only
uses the <children> for new children, not existing children... which IMO is a
benefit (but you wrote the spec, so in the end I'll bow to your decision).

> (2) An element can have an arbitrary number of insertion points due to nested
> bindings. 
Which my spec handles with ease. Each binding keeps its insertion points, which
are used up the binding chain.

My other problem is this... I am pretty certain I can implement my spec, because
it does not involve an "extra" XBLInsertionPoint element. It may look like a
sledgehammer, but I think that in terms of element parenting it's fairly
streamlined. I'm afraid that an extra element will cause all sorts of DOM headaches.
interface ElementXBL {
  readonly attribute NodeList xblChildNodes;
   /* the true child list after all bindings are applied */

  readonly attribute Node xblParentNode;
   /* the true parent list after all bindings are applied */

  readonly attribute Element bindingOwner;
   /* the element that generated the scope for this anonymous node, if any */

  void addBinding(in DOMString bindingURL);
  void removeBinding(in DOMString bindingURL);

  XBLInsertionPoint insertInsertionPoint(
     in Element parent,
      /* must be the element, or an anonymous node with
         bindingOwner set to this element */
     in unsigned long index,
      /* the position in the given element at which to place
         the insertion point */
     in DOMString includes,
      /* the filter (a CSS selector) that elements must match
         in order to be placed in this insertion point */
     in boolean inherited
      /* the type of insertion point */
  );
   /* creates and inserts an insertion point */
  void removeInsertionPoint(in XBLInsertionPoint point);
   /* removes an insertion point. Nodes that used to be in the
      insertion point are redistributed in the remaining ones.
      If some nodes no longer apply to any insertion point, the
      anonymous content is removed. */

  readonly attribute XBLInsertionPointList insertionPoints;
   /* if the element is a bound element, returns the list of
      insertion points for this element. */

  XBLInsertionPoint getInsertionPointFor(Element child);
   /* if the element is a bound element, and child is an element in
      the same scope, or a higher scope, than the element itself,
      this returns the XBLInsertionPoint in which the element 
      was inserted. Otherwise, or if the bound element has no 
      anonymous content, returns null. */ 

  void changeInsertionPointFor(Element child, XBLInsertionPoint insertionPoint);
   /* changes the insertion point of the child element to the new
      insertion point. Raises an exception if the element is not a
      bound element, or if the child element is not in the same scope,
      or a higher scope, than the element itself, or if the element
      does not apply to the filter of that insertion point. */

};

interface XBLInsertionPoint {
  readonly Element element;
  readonly attribute unsigned long index;
  attribute DOMString includes;
  attribute boolean inherited;
  attribute readonly NodeList nodes;
   /* the list of nodes currently inserted in this insertion point */
}

interface XBLInsertionPointList {
  XBLInsertionPoint item(in unsigned long index);
  readonly attribute unsigned long length;
}

If two insertion points have the same parent, index, and filter, then the order
in which they were added determines the order of their elements.
oops, add the following after the removeBinding declaration:

  boolean hasBinding(in DOMString bindingURI);
...and change getInsertionPoint and changeInsertionPoint to use nodes instead of
Elements when it comes to children (we need to be able to cope with text nodes).
Benjamin, so you're proposing that the filters at insertion points not be
dynamic? What do you do then when a new element is added to a document and has
to be placed at an insertion point?  That case clearly has to be dynamic (it
occurs all the time in XUL), and if you allow screwing around with the node
order at insertion points, it then becomes ambiguous where to place the new child.

I'm all for entertaining new ideas, but they can't regress/break existing
behavior in XUL.
>> (1) Insertion points are dynamic.  Elements implicitly move as they match new
>> filters.
> My proposal constructs the tree using the <children>, but at that point only
> uses the <children> for new children, not existing children... which IMO is a
> benefit 

If the <children/> elements have filters, e.g.:

   <children includes="value[checked]"/>
   <children includes="value:not([checked])"/>

...and you change an element so that it no longer matches one of the filters, it
should change to the appropriate filter, whether or not it has been moved
manually in the meantime.


>> (2) An element can have an arbitrary number of insertion points due to nested
>> bindings. 
> Which my spec handles with ease. Each binding keeps its insertion points, 
> which are used up the binding chain.

The bindings in the middle can be removed and added dynamically, too.


> an "extra" XBLInsertionPoint element

The latest proposal (comment 37 to comment 39) doesn't either, the xbl insertion
points do not appear in the DOM tree, only in separate mutable lists.
Another problem is that <children/> cannot be in the DOM as an element.  You run
into all sorts of problems if you leave that node in the DOM (which is what XBL
used to do) and make life more complicated for scripts and bindings that are
crawling around in the DOM.

One idea might be to make connection points "sever" the insertion point
relationship, i.e., to stop caring about filters/placement, but that gets really
complicated in the presence of dynamic insertion points and nested bindings.
In the above proposal, forget everything that mentions inherited insertion
points. We've got a better way of doing that that doesn't involve this DOM.
Yes, the child node order is the fatal flaw in my proposal. OK, I await Ian's
next which doesn't use the DOM to manipulate insertion points.
How are you distinguishing between "active" insertion points (with or without
filters; new nodes are inserted into these) and "passive" ones (manually
inserted nodes, but no new child nodes go here)?
Ok, here's our current thinking:

interface ElementXBL {
  readonly attribute NodeList xblChildNodes;
   /* the true child list after all bindings are applied */

  readonly attribute Node xblParentNode;
   /* the true parent list after all bindings are applied */

  readonly attribute Element bindingOwner;
   /* the element that generated the scope for this anonymous node, if any */

  void addBinding(in DOMString bindingURL);
  void removeBinding(in DOMString bindingURL);
  boolean hasBinding(in DOMString bindingURI);

  XBLInsertionPoint insertInsertionPoint(
     in Element parent,
      /* must be the element, or an anonymous node with
         bindingOwner set to this element */
     in unsigned long index,
      /* the position in the given element at which to place
         the insertion point */
     in DOMString includes
      /* the filter (a CSS selector) that elements must match
         in order to be placed in this insertion point. Null
         values are treated like an empty string. */
  );
   /* creates and inserts an insertion point */
  void removeInsertionPoint(in XBLInsertionPoint point);
   /* removes an insertion point. Nodes that used to be in the
      insertion point are redistributed in the remaining ones.
      If some nodes no longer apply to any insertion point, the
      anonymous content is removed. */

  readonly attribute XBLInsertionPointList insertionPoints;
   /* if the element is a bound element, returns the list of
      insertion points for this element. */

  XBLInsertionPoint getInsertionPointFor(Node child);
   /* if the element is a bound element, and child is a node in
      the same scope, or a higher scope, than the element itself,
      this returns the XBLInsertionPoint in which the node 
      is inserted. Otherwise, or if the bound element has no 
      anonymous content, returns null. */ 

  void changeInsertionPointFor(Node child, XBLInsertionPoint insertionPoint);
   /* changes the insertion point of the child node to the new
      insertion point. Raises an exception if the element is not a
      bound element, or if the child node is not in the same scope,
      or a higher scope, than the element itself, or if the node
      does not apply to the filter of that insertion point.
      Nodes other than element nodes only apply when the filter
      string is empty (that's the "catch all" filter) */

};

interface XBLInsertionPoint {
  readonly Element element;
  readonly attribute unsigned long index;
  attribute DOMString includes; /* never null */
  attribute readonly NodeList nodes;
   /* the list of nodes currently inserted in this insertion point */
}

interface XBLInsertionPointList {
  XBLInsertionPoint item(in unsigned long index);
  readonly attribute unsigned long length;
}

If two insertion points have the same parent, index, and filter, then the order
in which they were added determines the order of their elements.

If an element is added to the DOM dynamically, its scope is that of its parent
element. (There is thus no way to add an anonymous child to a bound element,
although in practice that doesn't matter.)

Changes to a bound element's anonymous content only affects that bound element.
Other bindings are unaffected.

A node is inserted into the first insertion point that it matches. If it is
moved (using changeInsertionPoint), then it remains in the new insertion point
while it matches it. If it stops matching that new insertion point, it gets
placed into the first insertion point that it matches. (If it doesn't match any,
then the entire anonymous content is removed.) In other words, changes to the
insertion point are "sticky".

If an element is inserted into an insertion point with an empty filter
("includes" list) then it will always match it while that insertion point
exists, and will therefore never move to another insertion point unless the
insertion point is dynamically removed.
> How are you distinguishing between "active" insertion points (with or without
> filters; new nodes are inserted into these) and "passive" ones (manually
> inserted nodes, but no new child nodes go here)?

There is no distinction, except that if there are two identical insertion
points, the second will never be used by new nodes.
OK, I like this and it looks implementable. A few questions...

> There is no distinction, except that if there are two identical insertion
> points, the second will never be used by new nodes.

I would like to able to declare an insertion point which will never
automatically receive children. Take an automatically-overflowing toolbar, for
example:
<binding id="overflowing-toolbar">
  <content>
    <children id="tbMain" />
    <xul:button type="menubutton">
      <xul:menupopup>
        <children id="tbOverflow" disabled="true"/>
      </xul:menupopup>
    </xul:button>
  </content>
</binding>

While there is room on the toolbar, children should be placed in tbMain. But
when there are too many children, the binding will set disabled="true" (or
something similar) on tbMain, and set disabled="false" on tbOverflow. The same
sort of thing applies to multi-row tabboxes and most of the other applications I
can think of.

> interface XBLInsertionPoint {
>   readonly attribute unsigned long index;

What is this index? Is it needed?
Ian, I would use addInsertionPoint, since "insertInsertionPoint" just sounds clumsy.
Ben (do you go by Ben or Benjamin), index is an index into the childNodes array
of the <children>'s parent.

So for example:

<content>
  <xul:box>
     <children/>
     <xul:separator/>
     <children/>
  </xul:box>
</content>

The first insertion point above has an index of 0.  The second insertion point
has an index of 1.  Both have the same parent, a <xul:box>.

I don't follow the logic in disabling an insertion point.  What if someone uses
elt.insertBefore to put an element at the beginning?  Presumably that element
would then need to be on the first row of e.g., a toolbar and not in the overflow.

I believe some cleverness with CSS3 selectors could be used as filters on the
insertion points, e.g., nth-child stuff, in order to make sure elements
dynamically stay where they are supposed to.

It's also worth noting that box should be able to wrap to multiple rows/columns
in XUL, and that you shouldn't have to do a multi-row system using XBL.  I will
propose a new property, box-lines (with values of single/multiple) to cover this
deficiency in the XUL box model.

I've never seen a bug gather so many comments in a twenty-four hour period.  :)

When you guys finish defining what ElementXBL really should be, I'll write up a
bunch of testcases and/or documentation for you.

Hixie:  you probably understand better than I do the difference between a URL
and a URI; is the difference between the respective arguments of addBinding,
removeBinding and hasBinding intentional?
> I would like to able to declare an insertion point which will never
> automatically receive children. Take an automatically-overflowing toolbar,
> for example:
>
> <binding id="overflowing-toolbar">
>   <content>
>     <children id="tbMain" />
>     <xul:button type="menubutton">
>       <xul:menupopup>
>         <children id="tbOverflow" disabled="true"/>
>       </xul:menupopup>
>     </xul:button>
>   </content>
> </binding>

You don't need the disabled attribute. The tbOverflow <children/> element above
will never automatically receive children.

(Note that IDs on <children/> elements aren't useful in the current model. We
might want to introduce getInsertionPointById() or something, though.)


> While there is room on the toolbar, children should be placed in tbMain. But
> when there are too many children, the binding will set disabled="true" (or
> something similar) on tbMain, and set disabled="false" on tbOverflow. The same
> sort of thing applies to multi-row tabboxes and most of the other applications 
> I can think of.

You wouldn't want to implement it that way anyway, since it doesn't cope with
dynamic ordering changes, resizes, etc.


> Hixie: you probably understand better than I do the difference between a URL
> and a URI; is the difference between the respective arguments of addBinding,
> removeBinding and hasBinding intentional?

It was a mistake. It should be URI throughout.


> When you guys finish defining what ElementXBL really should be, I'll write up 
> a bunch of testcases and/or documentation for you.

Testcases would be great! Documentation should be already covered by a spec
hyatt and I are writing.
Update:

interface ElementXBL {
  readonly attribute NodeList xblChildNodes;
   /* the true child list after all bindings are applied */

  readonly attribute Node xblParentNode;
   /* the true parent list after all bindings are applied */

  readonly attribute Element bindingOwner;
   /* the element that generated the scope for this anonymous node, if any */

  void addBinding(in DOMString bindingURI);
  void removeBinding(in DOMString bindingURI);
  boolean hasBinding(in DOMString bindingURI);

  XBLInsertionPoint addInsertionPoint(
     in Element parent,
      /* must be the element, or an anonymous node with
         bindingOwner set to this element */
     in unsigned long index,
      /* the position in the given element at which to place
         the insertion point */
     in DOMString includes
      /* the filter (a CSS selector) that elements must match
         in order to be placed in this insertion point. Null
         values are treated like an empty string. */
  );
   /* creates and inserts an insertion point */
  void removeInsertionPoint(in XBLInsertionPoint point);
   /* removes an insertion point. Nodes that used to be in the
      insertion point are redistributed in the remaining ones.
      If some nodes no longer apply to any insertion point, the
      anonymous content is removed. */

  readonly attribute XBLInsertionPointList insertionPoints;
   /* if the element is a bound element, returns the list of
      insertion points for this element. */

  XBLInsertionPoint getInsertionPointFor(Node child);
   /* if the element is a bound element, and child is a node in
      the same scope, or a higher scope, than the element itself,
      this returns the XBLInsertionPoint in which the node 
      is inserted. Otherwise, or if the bound element has no 
      anonymous content, returns null. */ 

  XBLInsertionPoint getInsertionPointById(DOMString id);
   /* if the element is a bound element, and it has an insertion
      point with the given ID, this returns the relevant
      XBLInsertionPoint. Otherwise, or if the bound element has no 
      anonymous content, returns null. */ 

  void changeInsertionPointFor(Node child, XBLInsertionPoint insertionPoint);
   /* changes the insertion point of the child node to the new
      insertion point. Raises an exception if the element is not a
      bound element, or if the child node is not in the same scope,
      or a higher scope, than the element itself, or if the node
      does not apply to the filter of that insertion point.
      Nodes other than element nodes only apply when the filter
      string is empty (that's the "catch all" filter) */

};

interface XBLInsertionPoint {
  readonly Element element;
   /* the parent of the insertion point */
  readonly attribute unsigned long index;
   /* the index of the insertion point in the element */
  attribute DOMString includes; /* never null */
   /* the filter, a string representing a CSS selector */
  attribute readonly NodeList nodes;
   /* the list of nodes currently inserted in this insertion point */
}

interface XBLInsertionPointList {
  XBLInsertionPoint item(in unsigned long index);
  readonly attribute unsigned long length;
}

If two insertion points have the same parent, index, and filter, then the order
in which they were added determines the order of their elements.

If an element is added to the DOM dynamically, its scope is that of its parent
element. (There is thus no way to add an anonymous child to a bound element,
although in practice that doesn't matter.)

Changes to a bound element's anonymous content only affects that bound element.
Other bindings are unaffected.

A node is inserted into the first insertion point that it matches. If it is
moved (using changeInsertionPoint), then it remains in the new insertion point
while it matches it. If it stops matching that new insertion point, it gets
placed into the first insertion point that it matches. (If it doesn't match any,
then the entire anonymous content is removed.) In other words, changes to the
insertion point are "sticky".

If an element is inserted into an insertion point with an empty filter
("includes" list) then it will always match it while that insertion point
exists, and will therefore never move to another insertion point unless the
insertion point is dynamically removed.
Rather than having a getInsertionPointById, you could allow the insertionPoints
array to be accessed by id, e.g., 

elt.insertionPoints['someId']

I'm not sure how that's represented in IDL but we do that for other arrays.
That would be represented in the JS binding for the IDL. It isn't represented
directly in the IDL, as I understand it, as some languages don't support an
equivalent syntax.

But yes, in JS, we should allow that.
I think it is represented in the IDL actually, usually with a namedItem method
(like the item method but takes a string).
It's possible it might be represented in XPIDL, but not the IDL used in DOM
specs? I'm not familiar with XPIDL. I haven't seen it ever mentioned in the IDL
used by DOM specs.
http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/html.html#ID-75708506

HTMLCollection is what I was thinking of.  IMO we could copy this.
I would prefer that to a getInsertionPointById method.
There's nothing special about namedItem(). The reason it can be accessed by
subscript notation is that the ECMAScript binding says:

# Note: This object can also be dereferenced using square bracket notation (e.g. 
# obj["foo"]). Dereferencing using a string index is equivalent to invoking the 
# namedItem function with that index.

See the "Functions of objects that implement the HTMLCollection interface"
section in:
   http://www.w3.org/TR/DOM-Level-2-HTML/ecma-script-binding.html

We can easily say that getInsertionPointById() should be equivalent to using
string based subscript notation.
If I may point out, multiple bindings may have elements with the same "ID", if
they come from different source documents. So I think we should scrap the whole
GetById/named-item-access unless there is a way to differentiate between (valid)
multiple IDs.
There is never any ambiguity with multiple IDs as getInsertionPointById() always
operates in a single scope (you have to call it on the bound element itself).

The only possible problem would be inherited bindings, but that situation is
unlikely to occur (authors usually have control over the various bindings,
especially if they use <inherited/>) and in the unlikely case of a clash, the
method would return the first matching ID and authors wanting more careful
control over the insertion points can always walk the insertion point list.
a few proposed changes (in pseudo-patch format). childInsertionPoints makes
programming much easier. XBLInsertionPoint::element was ambiguous as to whether
it meant the immediate parent or the bound element... both are useful, so I
clarified that. And added XBLInsertionPointList::namedItem a la HTMLCollection.

interface ElementXBL
{
[snip]
+  readonly attribute XBLInsertionPointList childInsertionPoints;
+   /* if the element is a bound or binding element, returns the list of
+      insertion points that are children of this element. */

interface XBLInsertionPoint {
-  readonly Element element;
+  readonly Element parent;
   /* the parent of the insertion point */

+  readonly Element bindingOwner;
   /* matches ElementXBL.bindingOwner */

interface XBLInsertionPointList {
  XBLInsertionPoint item(in unsigned long index);
+  XBLInsertionPoint namedItem(in DOMString name);
  readonly attribute unsigned long length;
}
These make sense.

I'd shorten "childInsertionPoints" to just "insertionPoints".

I still think getInsertionPointById can just go once we have namedItem.
Dave,

Actually "insertionPoints" and "childInsertionPoints" are two different attributes.

* insertionPoints is all of the insertion points created by binding the current
element, and is only usable on bound elements.

* childInsertionPoints is all of the insertion points that are direct children
of the current node, whether it is a client node or a binding node.
I don't think childInsertionPoints is necessary.  It's the difference between:

anonContent.childInsertionPoints['foo']

and

anonContent.bindingOwner.insertionPoints['foo']

So I really don't think we need an extra property just for that.

The two are not equivalent...

anonElem.childInsertionPoints[0] retrieves the (first or only) insertion point
directly under this anonymous element.

anonElement.bindingParent.insertionPoints[0] retrieves the first insertion point
on the element, which could very well be a totally different insertion point.

If I dynamically create insertion points (for a multi-row tabbox) they won't
have IDs, so I can't used the "namedItem" method of accessing them. It's much
easier to access them by their location in the anonymous DOM.
Just store the insertion points as returned from the addInsertionPoint method.
Yeah.  Also, I still don't like the multi-row tabbox use case, since boxes
should be able to wrap to multiple lines.  You shouldn't have to build complex
XBL just to get wrapping.
How soon can we finalize the ElementXBL interface, so I can write testcases?
Assignee: benjamin → nobody
QA Contact: ian → xbl
Blocks: XBL2
Web Components \o/
Status: NEW → RESOLVED
Closed: 4 years ago
Resolution: --- → WONTFIX
You need to log in before you can comment on or make changes to this bug.