Closed Bug 127418 Opened 23 years ago Closed 21 years ago

[FIXr]Adding XBL binding with method to HTML BODY element breaks DOM


(Core :: XBL, defect, P1)






(Reporter: matthew, Assigned: bzbarsky)




(3 files, 6 obsolete files)

From Bugzilla Helper:
User-Agent: Mozilla/5.0 (Windows; U; Win98; en-US; rv:0.9.8) Gecko/20020204
BuildID:    2002020406

If I add an XBL binding to the BODY of an HTML document using ('-moz-binding', ...), and if that XBL binding has a
<method> in its <implementation>, then many (all?) other DOM operations on nodes
in the HTML document fail with an error

Exception: Illegal operation on WrappedNative prototype object nsresult:

Reproducible: Always
Steps to Reproduce:
Load the test case (I will attach this shortly). It tries to attach the binding
to a BODY, H1, H2 and H3 element in turn.

Actual Results:  Operations on the BODY succeed.
The binding is successfully set on the H1 element, but afterwards, retrieving
the nodetype fails with the above error.

Expected Results:  Mozilla should successfully set bindings and report the node
type for each element in turn.

If the attempt to change the body is removed from the test case, then all
operations succeed.
Attached file Test case: XBL bindings (obsolete) —
Attached file Test case: HTML file (obsolete) —
For the test case, you'll need to download both files into the same directory
and rename the bindings file to a .xml extension, then adjust the HTML file so
that the pathnames point to the XML file.

Just discovered that it looks as though the problem goes away if the HTML file
uses a relative URL to refer to the bindings file.
It seems that referring to the XBL bindings document via an http:// URL also
works.	So http:// succeeds, relative URLs succeed, file:// fails, and
chrome:// fails.
Do we need to fix this for mozilla1.0/xbl1.0?

DOM and XPConnect gurus cc'd -- I hope something obvious leaps out at you guys
and there's an easy fix.

Looks like there's nothing special about the HTML BODY - the test fails in the
same way if you add a DIV and manipulate that instead.

Also you can remove the <content> from the XBL bindings without changing the
behaviour - it's the <implementation> with a <method> that causes the problem.
The testcase worksforme from both HTTP and local disk.  Is this still an issue?
In Mozilla 1.5 and Firebird 0.7 I still see failure in the following case:

1. save attachments 71072 and 71073 to local disk
2. edit the HTML file to specify the location of the XBL file as an absolute
file:// URL
3. load the HTML file

In this case the last non-error alert I see is
[after] h1 is [object HTMLHeadingElement]

followed by
[Exception... "Illegal operation on WrappedNative prototype object" <etc.> ]
This should even work (as in, demonstrate the bug) directly from bugzilla, I
hope... if not, just save it to your hard drive.
Attachment #71072 - Attachment is obsolete: true
Attachment #71073 - Attachment is obsolete: true
Attachment #71075 - Attachment is obsolete: true
So this is a longstanding (ever since the original "make inheritance work" code
landed) bug in nsXBLBinding::DoInitJSClass (which wasn't called that at the
time).  The problematic code in that function looks like this:

1092   if ((!::JS_LookupProperty(cx, global, aClassName.get(), &val)) ||
1093       JSVAL_IS_PRIMITIVE(val)) {
          // initialize proto here, using the current proto of "obj" as the
          // parent proto.  This sets the aClassName property of the global to
          // the proto we create
1166   }
1167   else {
1168     proto = JSVAL_TO_OBJECT(val);
1169   }
1171   // Set the prototype of our object to be the new class.
1172   if (!::JS_SetPrototype(cx, obj, proto)) {
1173     return NS_ERROR_FAILURE;
1174   }

In the attached testcase, the same exact binding is attached to two different
types of objects -- an <h1> and a <div>.  The aClassName is a property of the
_binding_.  So the first time through this code, we will create a new class with
the <h1>'s proto as it's proto and set the proto of the <h1> to that new class.
 The second time through, we will find the aClassName property of the window
object set (as the testcase shows) and will just get the value of that property
and use it as the proto for the <div>.

So now the proto chains look like

h1 -> XBL proto -> PrototypeHeaderElement (or whatever)
div -> XBL proto -> PrototypeHeaderElement (!)

In other words, we've screwed up the proto chain for the div.  This bug follows
(since XPConnect gets all unhappy).

As things stand, you simply can't have two different node types with the same
binding attached to them in the same document and have things work.  Hasn't come
up as a problem in XUL, since in XUL there is only one node type...
Attached patch Possible fix (obsolete) — Splinter Review
This fixes the bug on at least these testcases by making the new protoclass
name depend on the classname of obj.  The extra string munging seems to be the
only reasonable way to do that....  Luckily, we will still hit the cached proto
all the time for XUL (since there is only one XULElement).

Other options include not using the cached proto off the global at all (this
may cause noticeable problems with Ts/Txul) and declaring this a "feature"
instead of a bug... ;)

Anyone have any other ideas on how to fix this?
Attachment #137037 - Flags: superreview?(brendan)
Attachment #137037 - Flags: review?(jst)
The first approach has one more drawback -- it breaks for cases when objects of
the same class happen to have different prototypes (for example <applet>
elements with different applets, since applets muck with the prototype chain). 
This patch enforces the invariant we _really_ care about, which is that the the
proto of the cached val is the same as the proto of the incoming object.

The drawback of this patch is that it will end up "trashing" (always missing
out cached proto) if the same binding is attached to nodes of different types
alternately, but I doubt that's a common scenario.

A second (possible) drawback is that this will create different classes with
identical classnames.  Is that an issue?
This is actually a known issue with XBL and is closely related to the prototype forking problem (see 
the prototype forking bug).  

I think the class names should probably be unique, but perhaps just munged using integers, e.g., 
BindingClass1, BindingClass2, etc.  I dislike including the name of a prototype, since the chain can 
be dynamically mucked with later anyway (as is the case when a chain of bindings is installed).

If you really want to kick ass on this bug, a prototype forking system should be able to look up a 
cached class based on the desired prototype object for that class (which may or may not be the 
current immediate prototype of the bound element's js object).  This system should share cached 
classes for any objects or classes that share the same prototype.

This is all reasonably straightforward, but what makes the problem difficult is when the prototype 
chain gets mucked with dynamically at a later date, e.g., if/when we support the ability to add/
remove bindings using the DOM.  That problem could be spun off into a separate bug though.
> This should even work (as in, demonstrate the bug) directly from bugzilla, I
> hope... if not, just save it to your hard drive.

Actually I get a crash either way (in Firebird 0.7/WinNT).

Thanks for looking at this by the way.
> Actually I get a crash either way (in Firebird 0.7/WinNT).

Time to upgrade?  ;) A recent nightly does not crash, and I recall some sort of
crash with that setup in 1.5.

The forking bug hyatt refers to is bug 41428. hyatt, why would the desired
prototype for the binding ever not be the current prototype of the element's
JSObject?  Note that right now, the element's prototype is used as the binding's
prototype, so in fact my second patch covers what needs to happen as far as I
can tell...
Depends on: 41428
bz: identical class names for differeng bindings means last class initialized
wins, and defines the last binding's prototype under the given name.  Still,
patch 2 looks better to me.  It makes progress, AFAICT -- does it make anything
worse for a hard case?

> and defines the last binding's prototype under the given name

Right.  But all that means is that the corresponding property of the global will
be set to whatever.  As long as no one is depending on those properties on the
global (except for this code, of course), that should be fine...

I've been thinking some more about patch 2, and as far as I can tell its only
drawback is that it will thrash the one-element cache.  Comment 13 paragraph 3
is talking about a way to have a more-than-one-element cache here.

As for what comment 13 paragraph 4, is saying...  Adding bindings should be safe
with the "patch 2" approach.  Removing bindings, I've not fully thought through
yet.  I'm not sure when the 

Munging as suggested in comment 13 paragraph 2 makes it nontrivial to look up
the cached class (how do you know what integer it got munged with?).

Anyway, I'd like to know hyatt's answer to comment 15 before I say too many more
foolish things.  :)
What we need here (this will make prototype forking work too) is to mangle the class name 
somehow, possibly just with integers, e.g., classname1, classname2, etc.  Then for some JSObject 
with a current prototype class of C and for a binding with a classname of B, we'd need to have a 
hash from B*C -> Bn, creating the class Bn as needed.

This would avoid collisions, allow for caching still, and ensure that any unique prototypes end up 
forking their own copy of the class.
So...  Is the pointer to a prototype a stable thing?  That is, given binding B
and an object with prototype C, can we just take the JSObject* that is C, cast
to int, and append to the B URI?  That would do exactly what we want as far as I
can tell.

I guess my current patch assumes that the prototype pointer is in fact a stable
thing, huh?  ;)
Attached patch So maybe like this (obsolete) — Splinter Review
Attachment #137037 - Attachment is obsolete: true
Attachment #137038 - Attachment is obsolete: true
Attachment #137037 - Flags: superreview?(brendan)
Attachment #137037 - Flags: review?(jst)
Comment on attachment 137069 [details] [diff] [review]
So maybe like this

Since hyatt is clearly reading this bug... ;)
Attachment #137069 - Flags: superreview?(brendan)
Attachment #137069 - Flags: review?(hyatt)
Comment on attachment 137069 [details] [diff] [review]
So maybe like this

If the JS GC becomes a copying GC, then the JSObject* will change.  No worries
for the foreseeable future, but something to comment.

If you didn't have a class name identifying the prototype object, then the
object might become garbage and be collected.  The class-named property of the
window object is a necessary strong reference.

Does this really implement forking in the way that bug 41428 wants it?

I'd like to sit down with Brendan in person so we can work out whether or not this implements 
prototype forking.
I'd need a clear explanation of what bug 41428 is talking about to know whether
this does it...  I can certainly add a comment regarding garbage collection
here.  If that gets implemented, we may need some sort of other way to uniquely
identify prototypes.
bz, can you use this for a little future proofing?  If it fails, return
NS_ERROR_OUT_OF_MEMORY.  If it succeeds, use a |jsid objid| local whose address
you pass as the |idp| formal as the cookie to sprintf at the end of the
classname (using %lx format, I suppose).

Attachment #137171 - Flags: review?(shaver)
I rolled his patch into this diff, since I was testing it and all...

This seems to work just dandy.	sr=me on the JS part, as if that's needed.. ;)
Attachment #137069 - Attachment is obsolete: true
Attachment #137069 - Flags: superreview?(brendan)
Attachment #137069 - Flags: review?(hyatt)
Attachment #137172 - Flags: superreview?(brendan)
Attachment #137172 - Flags: review?(hyatt)
Comment on attachment 137171 [details] [diff] [review]
proposed trivial jsapi extension: JS_GetObjectId

Aye, cap'n.
Attachment #137171 - Flags: review?(shaver) → review+
Comment on attachment 137172 [details] [diff] [review]
Patch using Brendan's suggestion

Great work, bz.  If hyatt likes it, and it tests ok, I say we get it in for
1.6.  Small patch, low risk.  Why wait?

Nit: preexisting code overparenthesizes here:

+  if ((!::JS_LookupProperty(cx, global, className.get(), &val)) ||
+      JSVAL_IS_PRIMITIVE(val)) {  

Fix that if you care.

Attachment #137172 - Flags: superreview?(brendan) → superreview+
Taking, so I don't lose this....
Assignee: hyatt → bz-vacation
OS: Windows 98 → All
Priority: -- → P1
Hardware: PC → Macintosh
Summary: Adding XBL binding with method to HTML BODY element breaks DOM → [FIX]Adding XBL binding with method to HTML BODY element breaks DOM
Target Milestone: --- → mozilla1.6final
Flags: blocking1.6?
It's getting pretty late in the game for 1.6. Is this going to have necessary
reviews and be of low enough risk that we should be evaluating it for inclusion
in 1.6?
It's low-risk, yes.  As for reviews, ask hyatt, not me.  I've done what I could
to get review, and hyatt is clearly reading the bug....  Then again, given the
number of outstanding review requests he has, I doubt he'll ever actually review
it.  <shrug>
Hyatt told me on IRC that he'd get to this, but not by 1.6final ship date.  I
don't think we need to hold 1.6 for it, it was more of a safe fix to ship there
(IMHO).  So bz, how about punting to 1.7a?

OK. not a 1.6 blocker.
Flags: blocking1.6? → blocking1.6-
All good with me; I wouldn't have been the one checking this in for 1.6 anyway... ;)
Target Milestone: mozilla1.6final → mozilla1.7alpha
I checked the jsapi.[ch] changes reviewed by shaver (attachment 137171 [details] [diff] [review]) into the
1.7a trunk.

hyatt says r=hyatt via email.
Summary: [FIX]Adding XBL binding with method to HTML BODY element breaks DOM → [FIXr]Adding XBL binding with method to HTML BODY element breaks DOM
Comment on attachment 137172 [details] [diff] [review]
Patch using Brendan's suggestion

Marking the r=hyatt
Attachment #137172 - Flags: review?(hyatt) → review+
Checked in for 1.7a.
Closed: 21 years ago
Resolution: --- → FIXED
You need to log in before you can comment on or make changes to this bug.