prototype.js breaks treemenus from pear HTML_TreeMenu in safari

2 views
Skip to first unread message

mr shintla

unread,
Jul 11, 2008, 3:07:55 PM7/11/08
to Prototype & script.aculo.us
hi all

i see a very strange behavior in safari when using prototype.js and
the pear HTML_TreeMenu

to reproduce get prototype.js (http://www.prototypejs.org/assets/
2008/1/25/prototype-1.6.0.2.js) and treemenu.js (http://cvs.php.net/
viewvc.cgi/pear/HTML_TreeMenu/TreeMenu.js?revision=1.22).

and test the following html with safari on mac or windows (http://
www.apple.com/safari/download/)

<html>
<head>
<script src="TreeMenu.js" type="text/javascript"></script>
<script type="text/javascript" src="prototype.js"></script>
</head>
<body>
<script type="text/javascript">
//<![CDATA[
objTreeMenu_1 = new TreeMenu("TMimages",
"objTreeMenu_1","_self", "", true, false);
newNode = objTreeMenu_1.addItem(new TreeNode('a', null,
'',true, true, '', '', null));
newNode_1 = newNode.addItem(new TreeNode('b', null, '',
true,true, '', '', null));
newNode_1_1 = newNode_1.addItem(new TreeNode('c', null,
'',false, true, '', '', null));
objTreeMenu_1.drawMenu();
objTreeMenu_1.writeOutput();
objTreeMenu_1.resetBranches();
// ]]>
</script>
</body>
</html>

when prototype.js is not loaded all works well. when its loaded the
menu breaks. somehow the js-code from the treemenu is not executed,
but shown. this happens only in safari!

does anyone know a solution? it is also ok for me if i adapt the
treemenu.js. but as i'm not that much in js, i would be glad for any
help on this.

thank you and greetings.ivo

kangax

unread,
Jul 11, 2008, 5:25:13 PM7/11/08
to Prototype & script.aculo.us
First of all, it's a good idea to declare a doctype for a document.
Second, it is safer to declare variables using "var" keyword.
Finally, TreeMenu.js seems to be of a poor quality : )
It "breaks with prototype.js" due to a for/in enumeration of arrays.

-- kangax

mr shintla

unread,
Jul 14, 2008, 4:08:43 AM7/14/08
to Prototype & script.aculo.us
thank you for your answer

> First of all, it's a good idea to declare a doctype for a document.

you're dead right. this was my testfile to track down the error, so i
tried to keep it as simple as possible. but you're right.

> Second, it is safer to declare variables using "var" keyword.

these declarations actually come from the pear package, it might be
poor quality, but it is still the easiest way i know to build such
trees.

> Finally, TreeMenu.js seems to be of a poor quality : )
> It "breaks with prototype.js" due to a for/in enumeration of arrays.

what do you mean by this? this happens only in safari. is it a common
problem with safari? or do you know a workaround for safari?

thank you very much.ivo

kangax

unread,
Jul 14, 2008, 11:29:41 AM7/14/08
to Prototype & script.aculo.us
Iterating with for/in over arrays should actually "break" in any
modern browser.
I remember Safari had issues with document being served with content-
type: xhtml/application

-- kangax

RobG

unread,
Jul 14, 2008, 8:41:53 PM7/14/08
to Prototype & script.aculo.us


On Jul 12, 7:25 am, kangax <kan...@gmail.com> wrote:
[...]
> Finally, TreeMenu.js seems to be of a poor quality : )
> It "breaks with prototype.js" due to a for/in enumeration of arrays.

I think you have that backwards: prototype.js breaks TreeMenu.js's
enumeration of arrays by extending Array.prototype. One fix is to
include a hasOwnProperty test when iterating over an array using
for..in.


--
Rob

kangax

unread,
Jul 14, 2008, 9:30:13 PM7/14/08
to Prototype & script.aculo.us
On Jul 14, 8:41 pm, RobG <rg...@iinet.net.au> wrote:
> I think you have that backwards: prototype.js breaks TreeMenu.js's
> enumeration of arrays by extending Array.prototype. One fix is to
> include a hasOwnProperty test when iterating over an array using
> for..in.

Sure, we can put it this way : )
Too bad Safari <2.0.2 does not support hasOwnProperty (and is one of
the reasons why prototype.js does not use it internally)

-- kangax

RobG

unread,
Jul 14, 2008, 9:46:14 PM7/14/08
to Prototype & script.aculo.us


On Jul 15, 1:29 am, kangax <kan...@gmail.com> wrote:
> Iterating with for/in over arrays should actually "break" in any
> modern browser.

I can't believe you posted that. :-(

Expecting behaviour specified in ECMA-262 to break in a compliant
implementation is not a healthy sentiment, even if you believe (with
some justification) that it is poor coding practice.

For a long time, the authors of Prototype.js ignored complaints about
breaking for..in iteration of objects by extending Object.prototype.
They stopped it when they decided to use for..in with Objects within
Prototype.js itself. Self interest has a way of setting priorities
ahead of other motivating factors. :-)

I agree with your sentiment that treemenu.js is crap, e.g.:

typeof(input[i]) == 'array'

Incidentally, jQuery had a similar test until very recently.

Also "Ultimate client-side JavaScript client sniff" (apparently last
updated in 2001) seems to think there are only 5 browser families,
none of which are Safari. A good example of why browser sniffing is a
flawed strategy.


> I remember Safari had issues with document being served with content-
> type: xhtml/application

That the browser is Safari is relevant in that the "Ultimate ...
client sniff" doesn't attempt to detect it (since the script appears
to have been last updated a couple of years before the first Safari
public beta), so it probably thinks Safari something else and responds
accordingly.

All browsers have their foibles: early versions of Safari certainly
had their fair share (and then some) but it's getting better in leaps
and bounds. In regard to serving XHTML as application/xhtml, any
browser that doesn't know what XHTML is will have a problem with that
- try it with IE. :-)

Similarly, any document served as XHTML should also use:

<script type="application/javascript">


but I don't know anyone who would recommend it for general use on the
web. Nor would it be sensible to use an XHTML document with a script
that uses document.write.


--
Rob
Message has been deleted

kangax

unread,
Jul 15, 2008, 1:36:41 AM7/15/08
to Prototype & script.aculo.us
On Jul 14, 9:46 pm, RobG <rg...@iinet.net.au> wrote:
> On Jul 15, 1:29 am, kangax <kan...@gmail.com> wrote:
>
> > Iterating with for/in over arrays should actually "break" in any
> > modern browser.
>
> I can't believe you posted that. :-(
>
> Expecting behaviour specified in ECMA-262 to break in a compliant
> implementation is not a healthy sentiment, even if you believe (with
> some justification) that it is poor coding practice.
>
> For a long time, the authors of Prototype.js ignored complaints about
> breaking for..in iteration of objects by extending Object.prototype.
> They stopped it when they decided to use for..in with Objects within
> Prototype.js itself. Self interest has a way of setting priorities
> ahead of other motivating factors. :-)

Rob, I didn't feel like going into details of what's going on under
the hood. I don't think the author wanted to delve into the way for/in
works. I agree that claiming perfectly valid ECMA behavior as
"breakage" is foolish, but the fact remains - Prototype.js augments
native objects and people have problems with third-party scripts
breaking unexpectedly. Instead of putting accent on whose fault it is,
it might make sense to just get rid of the script with worse quality.

>
> I agree with your sentiment that treemenu.js is crap, e.g.:
>
> typeof(input[i]) == 'array'
>
> Incidentally, jQuery had a similar test until very recently.

I'm yet to find a reliable method for determining if an object is an
array. Even the time-proven Object.isArray from prototype.js does
"naive" decisions based on object members. There's no guarantee that
object implements [[Put]] and has an expected "length" behavior.

>
> Also "Ultimate client-side JavaScript client sniff" (apparently last
> updated in 2001) seems to think there are only 5 browser families,
> none of which are Safari. A good example of why browser sniffing is a
> flawed strategy.
>
> > I remember Safari had issues with document being served with content-
> > type: xhtml/application
>
> That the browser is Safari is relevant in that the "Ultimate ...
> client sniff" doesn't attempt to detect it (since the script appears
> to have been last updated a couple of years before the first Safari
> public beta), so it probably thinks Safari something else and responds
> accordingly.
>
> All browsers have their foibles: early versions of Safari certainly
> had their fair share (and then some) but it's getting better in leaps
> and bounds. In regard to serving XHTML as application/xhtml, any
> browser that doesn't know what XHTML is will have a problem with that
> - try it with IE. :-)

Here's a relevant ticket that we tried to investigate some time ago:
http://dev.rubyonrails.org/ticket/9798
Here's an actual WebKit bug mentioned in a ticket:
https://bugs.webkit.org/show_bug.cgi?id=15467

-- kangax

RobG

unread,
Jul 15, 2008, 9:57:13 PM7/15/08
to Prototype & script.aculo.us


On Jul 15, 3:36 pm, kangax <kan...@gmail.com> wrote:
> On Jul 14, 9:46 pm, RobG <rg...@iinet.net.au> wrote:
> > On Jul 15, 1:29 am, kangax <kan...@gmail.com> wrote:
[...]
> > I agree with your sentiment that treemenu.js is crap, e.g.:
>
> > typeof(input[i]) == 'array'
>
> > Incidentally, jQuery had a similar test until very recently.
>
> I'm yet to find a reliable method for determining if an object is an
> array.

The test is absolutely reliable for native objects - it will always
return false. The point is that typeof will never return the string
'array' for native objects, it demonstrates that the author of
treemenu.js doesn't know much about ECMA-262. The jQuery reference is
a bit of a snipe. ;-D

You might get a chuckle from this if you haven't seen it already:

<URL:
http://groups.google.com.au/group/comp.lang.javascript/tree/browse_frm/thread/6ea0ec35f290dd1b/249604095daa1a20?hl=en&rnum=81&q=typeof+array+jquery&_done=%2Fgroup%2Fcomp.lang.javascript%2Fbrowse_frm%2Fthread%2F6ea0ec35f290dd1b%2Ff814db369484353c%3Fhl%3Den%26lnk%3Dgst%26q%3Dtypeof%2Barray%2Bjquery%26#doc_f814db369484353c
>

The use of () around input[i] is pointless and may lead some to assume
that typeof is a function. Putting brackets around the unary
expression following an operator is a silly habit as it can lead to
script errors, try:

++(1);


> Even the time-proven Object.isArray from prototype.js does
> "naive" decisions based on object members.

"Time proven"? Yes, it's pretty simplistic and probably provides a
warm fuzzy feeling, but what does it actually prove?


> There's no guarantee that
> object implements [[Put]]

Internal methods can't be accessed directly using javascript and even
if they could, there'd have to be a way to distinguish between an
Array's special [[Put]] method and the one that might be implemented
by other objects.

The notion of internal methods is adopted by the specification purely
for the sake of describing behaviour (ECMA-262 8.6.2). It doesn't
mean that such a method exists in any implementation, only that
objects that are specified as having it behave as if they do.


> and has an expected "length" behavior.

The special length behaviour is a consequence of the special [[Put]]
(ECMA-262 15.4.5.1). Testing the behaviour of the length property
requires use of other methods that implement [[Put]] such as push, so
they would have to be tested first.

This leads to the strategy of testing those methods you are going to
use, implementing alternatives for those that are missing and ignoring
what you don't need.

In the case of treemenu.js, a simple test would be for the object's
constructor property:

if (x.constructor === Array) {
// do stuff...
}

which is not suitable in a general case but is is likely sufficient in
this one.


> > Also "Ultimate client-side JavaScript client sniff" (apparently last
> > updated in 2001) seems to think there are only 5 browser families,
> > none of which are Safari. A good example of why browser sniffing is a
> > flawed strategy.
>
> > > I remember Safari had issues with document being served with content-
> > > type: xhtml/application
>
> > That the browser is Safari is relevant in that the "Ultimate ...
> > client sniff" doesn't attempt to detect it (since the script appears
> > to have been last updated a couple of years before the first Safari
> > public beta), so it probably thinks Safari something else and responds
> > accordingly.
>
> > All browsers have their foibles: early versions of Safari certainly
> > had their fair share (and then some) but it's getting better in leaps
> > and bounds. In regard to serving XHTML as application/xhtml, any
> > browser that doesn't know what XHTML is will have a problem with that
> > - try it with IE. :-)
>
> Here's a relevant ticket that we tried to investigate some time ago:http://dev.rubyonrails.org/ticket/9798
> Here's an actual WebKit bug mentioned in a ticket:https://bugs.webkit.org/show_bug.cgi?id=15467

I didn't intend starting a discussion of browser bugs but to point out
that the script is incompatible with XHTML documents, hence the
usefulness of mentioning bugs related to XHTML is moot. Further, the
script uses browser detection that doesn't even know Safari exists and
so is unlikely to make any accommodation for its eccentricities.


--
Rob

kangax

unread,
Jul 15, 2008, 11:52:54 PM7/15/08
to Prototype & script.aculo.us
On Jul 15, 9:57 pm, RobG <rg...@iinet.net.au> wrote:
> On Jul 15, 3:36 pm, kangax <kan...@gmail.com> wrote:
>
> > On Jul 14, 9:46 pm, RobG <rg...@iinet.net.au> wrote:
> > > On Jul 15, 1:29 am, kangax <kan...@gmail.com> wrote:
> [...]
> > > I agree with your sentiment that treemenu.js is crap, e.g.:
>
> > > typeof(input[i]) == 'array'
>
> > > Incidentally, jQuery had a similar test until very recently.
>
> > I'm yet to find a reliable method for determining if an object is an
> > array.
>
> The test is absolutely reliable for native objects - it will always
> return false. The point is that typeof will never return the string
> 'array' for native objects, it demonstrates that the author of
> treemenu.js doesn't know much about ECMA-262. The jQuery reference is
> a bit of a snipe. ;-D
>
> You might get a chuckle from this if you haven't seen it already:
>
> <URL:http://groups.google.com.au/group/comp.lang.javascript/tree/browse_fr...

Yes, I've read that thread. We all make mistakes.

> The use of () around input[i] is pointless and may lead some to assume
> that typeof is a function. Putting brackets around the unary

The way typeof operator is written is a pretty common argue point : )

> expression following an operator is a silly habit as it can lead to
> script errors, try:
>
> ++(1);
>
> > Even the time-proven Object.isArray from prototype.js does
> > "naive" decisions based on object members.
>
> "Time proven"? Yes, it's pretty simplistic and probably provides a
> warm fuzzy feeling, but what does it actually prove?
>
> > There's no guarantee that
> > object implements [[Put]]
>
> Internal methods can't be accessed directly using javascript and even
> if they could, there'd have to be a way to distinguish between an
> Array's special [[Put]] method and the one that might be implemented
> by other objects.
>
> The notion of internal methods is adopted by the specification purely
> for the sake of describing behaviour (ECMA-262 8.6.2). It doesn't
> mean that such a method exists in any implementation, only that
> objects that are specified as having it behave as if they do.

I'm not talking about being able to "access internal methods" - we are
not interested in that. By "object implements [[Put]]" I obviously
meant specified array's [[Put]] behavior - adding a property whose
name is an array index should change "length" property accordingly.
Isn't that what we care about most of the time?

> > and has an expected "length" behavior.
>
> The special length behaviour is a consequence of the special [[Put]]

Exactly. None of the "isArray" functions I've seen test for this, but
then the proper name would probably have to be something like
"actsAsArray" or "hasArrayLength" and its performance would stand no
chance against simpler (naive) techniques. Test used in prototype.js
actually works in many cases (within the subset of browsers supported
by the library).

It is of course possible to break it by doing something like:

Object.isArray({ splice: '', join: '' }); // true

Such "breakage" rarely happens in practice, though.

> (ECMA-262 15.4.5.1). Testing the behaviour of the length property
> requires use of other methods that implement [[Put]] such as push, so
> they would have to be tested first.

I'm not sure I understand what "push" has to do with Array's [[Put]].
Could you elaborate?

> This leads to the strategy of testing those methods you are going to
> use, implementing alternatives for those that are missing and ignoring
> what you don't need.
>
> In the case of treemenu.js, a simple test would be for the object's
> constructor property:
>
> if (x.constructor === Array) {
> // do stuff...
> }
>
> which is not suitable in a general case but is is likely sufficient in
> this one.

Agreed.

-- kangax

Brian Williams

unread,
Jul 16, 2008, 12:08:06 AM7/16/08
to prototype-s...@googlegroups.com
I wanted to use Pear TreeMenu as well, but found while it worked in everything it didn't in safari so I said pass

I did find a few replacements:

Yahoo's YUI - free

and

http://www.devmansion.com/Products/PHP/Menu/ - 49$ that is "A license for use of the product by a single user in development of an unlimited number of applications/web sites. Standard technical support services provided through e-mail and IM. "

good luck

RobG

unread,
Jul 16, 2008, 1:21:00 AM7/16/08
to Prototype & script.aculo.us


On Jul 16, 1:52 pm, kangax <kan...@gmail.com> wrote:
> On Jul 15, 9:57 pm, RobG <rg...@iinet.net.au> wrote:
> > On Jul 15, 3:36 pm, kangax <kan...@gmail.com> wrote:
[...]
> > > There's no guarantee that
> > > object implements [[Put]]
>
> > Internal methods can't be accessed directly using javascript and even
> > if they could, there'd have to be a way to distinguish between an
> > Array's special [[Put]] method and the one that might be implemented
> > by other objects.
[...]
> I'm not talking about being able to "access internal methods" - we are
> not interested in that. By "object implements [[Put]]" I obviously
> meant specified array's [[Put]] behavior - adding a property whose
> name is an array index should change "length" property accordingly.
> Isn't that what we care about most of the time?

That is one method to test for array-like behaviour, but it doesn't
tell you what other array methods are avaiable, you'd still have to
test for them.


> > > and has an expected "length" behavior.
>
> > The special length behaviour is a consequence of the special [[Put]]
>
> Exactly. None of the "isArray" functions I've seen test for this, but
> then the proper name would probably have to be something like
> "actsAsArray" or "hasArrayLength" and its performance would stand no
> chance against simpler (naive) techniques.

That is pretty much the conclusion that everyone comes to eventually -
even if it is possible to determine if something is a genuine native
javascript Array, does that really help? It doesn't tell you what
methods it has, they still have to be tested for.

As a result most scripts don't bother with any kind of isArray
function, they either do no testing at all and hope unit, module or
user testing picks up any errors or they feature test appropriately
within the code.


> Test used in prototype.js
> actually works in many cases (within the subset of browsers supported
> by the library).
>
> It is of course possible to break it by doing something like:
>
> Object.isArray({ splice: '', join: '' }); // true
>
> Such "breakage" rarely happens in practice, though.

That isn't the issue though. There are objects that can be iterated
over like an array (such a NodeList) and other objects that are arrays
in some browsers but not others (such as the arguments property). So
having any kind of isArray function is pretty pointless, it tends to
discourage usrs from testing explicitly for the properties they need.


> > (ECMA-262 15.4.5.1).  Testing the behaviour of the length property
> > requires use of other methods that implement [[Put]] such as push, so
> > they would have to be tested first.
>
> I'm not sure I understand what "push" has to do with Array's [[Put]].
> Could you elaborate?

It is a test that the push method causes the expected internal [[Put]]
behaviour. It may not be a built-in method at all (and hence the
effect on length may have been achieved some other way), but does that
matter? Using an assignment as you suggested tests the internal
[[Put]] more explicitly but requires more code, is likely slower and
still requires testing of methods that are to be used.

In the end, is either helpful if you aren't interested in those
properties of the object anyway?


> > This leads to the strategy of testing those methods you are going to
> > use, implementing alternatives for those that are missing and ignoring
> > what you don't need.


--
Rob
Reply all
Reply to author
Forward
0 new messages