Annoying NN/Firefox Highlight Bug

Discussion in 'Javascript' started by 50295@web.de, Apr 17, 2005.

  1. Guest

    Hi everyone,

    This one is better experienced than explained, so I'm including a code
    sample below. Please and save (as an html file) and view with NN or
    Firefox (or maybe even Mozilla), and then view. When loaded:

    (1.) Place the mouse over "Top Menu" item.
    (The menu opens)
    (2.) Move to any of the sub-menu items.
    (3.) Click on the left or right (but NOT on the text) of the current
    sub-item
    (The menu collapses)
    (4.) Now place the mose over the top-menu
    -> The menu opens as expected, but the an annoying highlight
    appears, and follows the mouse.

    Please tell me there's a work-around.

    ------------------------ H T M L ------------------------

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html;
    charset=iso-8859-1">

    <script type="text/javascript">

    function getIndex(){

    if (navigator.appName == "Microsoft Internet Explorer"){
    return 2;
    }
    else if (navigator.appName == "Netscape"){
    return 3;
    }
    }


    function bindListeners(){

    var anchors = document.getElementsByTagName("A");
    for (i = 0; i < anchors.length; i++){

    anchors.onmousedown = function() {
    this.className = "mouseDown";
    }

    anchors.onmouseup = function() {
    this.className = "mouseUp";

    var object = this.parentNode.parentNode.parentNode;
    var object1 = object.parentNode.parentNode;


    if (object.tagName.toUpperCase() == 'BODY'){
    // top-menu

    this.parentNode.childNodes[getIndex()].style.display = 'none';
    }
    else if( object1.tagName.toUpperCase() == 'BODY' ){
    // sub-menu
    this.parentNode.parentNode.style.display =
    'none';
    }

    }


    anchors.onmouseover = function(){
    var object = this.parentNode.parentNode.parentNode;
    var object1 = object.parentNode.parentNode;


    if (object.tagName.toUpperCase() == 'BODY'){
    // top-menu

    this.parentNode.childNodes[getIndex()].style.display = 'block';
    }
    else if(
    object.parentNode.parentNode.tagName.toUpperCase() == 'BODY' ){
    // sub-menu
    this.parentNode.parentNode.style.display =
    'block';;
    }


    }

    }


    }
    </script>

    <style type="text/css">

    li {
    background-color: #FFFFFF;
    font-weight: normal;
    font-family: arial;
    font-size: 12px;

    text-align: center;
    margin: 0;
    padding: 0;
    height: 1%;
    }

    ul{
    margin: 0;
    padding: 0;
    list-style: none;
    }

    a {
    display: block;
    width: 120px;
    text-decoration: none;
    color: #000000;
    padding: 2px 15px 2px 15px;
    }

    li:hover ul {
    display: block;
    }

    li ul {
    display: none;
    }

    body li{
    width: 150px;
    }

    li.collapse {
    display: none;
    }

    ul li ul li {
    border-top: 1px solid black;
    }

    li.topMenu {
    border: 1px solid black;
    }

    </style>

    <title>test</title>
    </head>

    <body onLoad="bindListeners()">

    <ul><li class="topMenu">
    <a href="#">Top Menu</a>
    <ul>
    <li><a href="#">Sub-menu 1</a></li>
    <li><a href="#">Sub-menu 2</a></li>
    <li><a href="#">Sub-menu 3</a></li>
    <li><a href="#">Sub-menu 4</a></li>
    </ul>
    </li></ul>

    </body>
    </html>


    ------------------------ H T M L ------------------------

    I wonder if this is the same bug that this guy is talking about?
    http://groups.google.co.uk/groups?selm=&output=gplain

    Thanks,

    - Olumide
     
    , Apr 17, 2005
    #1
    1. Advertising

  2. fox Guest

    wrote:
    > Hi everyone,
    >
    > This one is better experienced than explained, so I'm including a code
    > sample below. Please and save (as an html file) and view with NN or
    > Firefox (or maybe even Mozilla), and then view. When loaded:
    >
    > (1.) Place the mouse over "Top Menu" item.
    > (The menu opens)
    > (2.) Move to any of the sub-menu items.
    > (3.) Click on the left or right (but NOT on the text) of the current
    > sub-item
    > (The menu collapses)
    > (4.) Now place the mose over the top-menu
    > -> The menu opens as expected, but the an annoying highlight
    > appears, and follows the mouse.
    >
    > Please tell me there's a work-around.


    for items in which you do not want text selection, simply add:

    li {
    -moz-user-select: none;
    }

    to your css

    problem solved...


    >
    > ------------------------ H T M L ------------------------
    >
    > <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    > "http://www.w3.org/TR/html4/loose.dtd">
    > <html>
    > <head>
    > <meta http-equiv="Content-Type" content="text/html;
    > charset=iso-8859-1">
    >
    > <script type="text/javascript">
    >
    > function getIndex(){
    >
    > if (navigator.appName == "Microsoft Internet Explorer"){
    > return 2;
    > }
    > else if (navigator.appName == "Netscape"){
    > return 3;
    > }
    > }
    >
    >
    > function bindListeners(){
    >
    > var anchors = document.getElementsByTagName("A");
    > for (i = 0; i < anchors.length; i++){
    >
    > anchors.onmousedown = function() {
    > this.className = "mouseDown";
    > }
    >
    > anchors.onmouseup = function() {
    > this.className = "mouseUp";
    >
    > var object = this.parentNode.parentNode.parentNode;
    > var object1 = object.parentNode.parentNode;
    >
    >
    > if (object.tagName.toUpperCase() == 'BODY'){
    > // top-menu
    >
    > this.parentNode.childNodes[getIndex()].style.display = 'none';
    > }
    > else if( object1.tagName.toUpperCase() == 'BODY' ){
    > // sub-menu
    > this.parentNode.parentNode.style.display =
    > 'none';
    > }
    >
    > }
    >
    >
    > anchors.onmouseover = function(){
    > var object = this.parentNode.parentNode.parentNode;
    > var object1 = object.parentNode.parentNode;
    >
    >
    > if (object.tagName.toUpperCase() == 'BODY'){
    > // top-menu
    >
    > this.parentNode.childNodes[getIndex()].style.display = 'block';
    > }
    > else if(
    > object.parentNode.parentNode.tagName.toUpperCase() == 'BODY' ){
    > // sub-menu
    > this.parentNode.parentNode.style.display =
    > 'block';;
    > }
    >
    >
    > }
    >
    > }
    >
    >
    > }
    > </script>
    >
    > <style type="text/css">
    >
    > li {
    > background-color: #FFFFFF;
    > font-weight: normal;
    > font-family: arial;
    > font-size: 12px;
    >
    > text-align: center;
    > margin: 0;
    > padding: 0;
    > height: 1%;
    > }
    >
    > ul{
    > margin: 0;
    > padding: 0;
    > list-style: none;
    > }
    >
    > a {
    > display: block;
    > width: 120px;
    > text-decoration: none;
    > color: #000000;
    > padding: 2px 15px 2px 15px;
    > }
    >
    > li:hover ul {
    > display: block;
    > }
    >
    > li ul {
    > display: none;
    > }
    >
    > body li{
    > width: 150px;
    > }
    >
    > li.collapse {
    > display: none;
    > }
    >
    > ul li ul li {
    > border-top: 1px solid black;
    > }
    >
    > li.topMenu {
    > border: 1px solid black;
    > }
    >
    > </style>
    >
    > <title>test</title>
    > </head>
    >
    > <body onLoad="bindListeners()">
    >
    > <ul><li class="topMenu">
    > <a href="#">Top Menu</a>
    > <ul>
    > <li><a href="#">Sub-menu 1</a></li>
    > <li><a href="#">Sub-menu 2</a></li>
    > <li><a href="#">Sub-menu 3</a></li>
    > <li><a href="#">Sub-menu 4</a></li>
    > </ul>
    > </li></ul>
    >
    > </body>
    > </html>
    >
    >
    > ------------------------ H T M L ------------------------
    >
    > I wonder if this is the same bug that this guy is talking about?
    > http://groups.google.co.uk/groups?selm=&output=gplain
    >
    > Thanks,
    >
    > - Olumide
    >
     
    fox, Apr 17, 2005
    #2
    1. Advertising

  3. RobG Guest

    wrote:
    > Hi everyone,
    >
    > This one is better experienced than explained, so I'm including a code
    > sample below. Please and save (as an html file) and view with NN or
    > Firefox (or maybe even Mozilla), and then view. When loaded:
    >
    > (1.) Place the mouse over "Top Menu" item.
    > (The menu opens)
    > (2.) Move to any of the sub-menu items.
    > (3.) Click on the left or right (but NOT on the text) of the current
    > sub-item
    > (The menu collapses)
    > (4.) Now place the mose over the top-menu
    > -> The menu opens as expected, but the an annoying highlight
    > appears, and follows the mouse.
    >
    > Please tell me there's a work-around.
    >


    Please don't post code with tabs. Use 2 or 4 spaces for indenting.
    Manually wrap code to prevent wrapping errors.

    [...]
    >
    > function getIndex(){
    >
    > if (navigator.appName == "Microsoft Internet Explorer"){
    > return 2;
    > }
    > else if (navigator.appName == "Netscape"){
    > return 3;
    > }
    > }


    The concept behind this function is flawed, see below.

    >
    >
    > function bindListeners(){
    >
    > var anchors = document.getElementsByTagName("A");
    > for (i = 0; i < anchors.length; i++){
    >
    > anchors.onmousedown = function() {
    > this.className = "mouseDown";
    > }
    >
    > anchors.onmouseup = function() {
    > this.className = "mouseUp";
    >
    > var object = this.parentNode.parentNode.parentNode;
    > var object1 = object.parentNode.parentNode;


    You seem to be guessing at how many parentNodes will get you to the
    <body> tag, then based on that, how many will take to get to the <ul>
    tag at the start of the list. See below for a more efficient
    solution.

    >
    >
    > if (object.tagName.toUpperCase() == 'BODY'){


    A more efficient test here is:

    if ( /body/i.test(object.tagName) ){

    > // top-menu
    >
    > this.parentNode.childNodes[getIndex()].style.display = 'none';
    > }
    > else if( object1.tagName.toUpperCase() == 'BODY' ){
    > // sub-menu
    > this.parentNode.parentNode.style.display =
    > 'none';
    > }
    >
    > }
    >
    >
    > anchors.onmouseover = function(){
    > var object = this.parentNode.parentNode.parentNode;
    > var object1 = object.parentNode.parentNode;
    >
    >
    > if (object.tagName.toUpperCase() == 'BODY'){
    > // top-menu
    >
    > this.parentNode.childNodes[getIndex()].style.display = 'block';
    > }
    > else if(
    > object.parentNode.parentNode.tagName.toUpperCase() == 'BODY' ){
    > // sub-menu
    > this.parentNode.parentNode.style.display =
    > 'block';;
    > }
    >
    >
    > }
    >
    > }
    >
    >
    > }
    > </script>
    >


    The way your script works is very prone to failure. Your getIndex()
    function will only ever work with browsers that identify as
    'Netscape' or 'Microsoft Internet Explorer'.

    You also make assumptions about the number of nodes based on your
    guess of browser.

    It seems that what you are trying to do is find the <ul> tag at the
    start of the list that was clicked on. If that is so, the best way
    to find it is to go up through the parentNodes until you get there.

    If the following function is passed a reference to a node, it will go
    up the DOM tree until it finds a UL node, then return a reference to
    it:

    function getULtag(x){
    while (!/ul/i.test(x.nodeName) && x.parentNode ){
    x = x.parentNode;
    }
    return x;
    }

    Below is a more concise version, but it will fail if x is the UL tag
    you were after:

    function getULtag(x){
    while ( (x = x.parentNode) && !/ul/i.test(x.nodeName)){}
    return x;
    }

    Both the above functions will work in any browser supporting
    JavaScript and the W3C DOM without regard for whatever they chose to
    report as their 'appName' (which includes IE and Netscape, as well as
    many others).

    A sample implementation is below.

    <script type="text/javascript">
    function getULtag(x){
    while ( (x = x.parentNode) && !/ul/i.test(x.nodeName)){}
    return x;
    }
    </script>
    <ul id="ulA">
    <li onclick="alert(getULtag(this).id);">hi</li>
    <li onclick="alert(getULtag(this).id);">hi</li>
    <li onclick="alert(getULtag(this).id);">hi</li>
    <li onclick="alert(getULtag(this).id);">hi</li>
    </ul>

    [...]

    --
    Rob
     
    RobG, Apr 18, 2005
    #3
  4. RobG wrote:
    > wrote:

    <snip>
    >> if (object.tagName.toUpperCase() == 'BODY'){

    >
    > A more efficient test here is:
    >
    > if ( /body/i.test(object.tagName) ){

    <snip>

    Is it a more efficient test? Have you read the ECMA 262 section on
    Regular Expressions and thought about what it must take to implement
    that? String comparison is not that complex; maybe compare the length
    and return false if they don't match, then compare each corresponding
    character in turn, returning false at the first non-match (equality
    testing between 16 bit integers is trivial for CPUs to do directly), and
    return true if you get to the end of the strings without having returned
    false.

    Richard.
     
    Richard Cornford, Apr 18, 2005
    #4
  5. RobG Guest

    Richard Cornford wrote:
    > RobG wrote:
    >
    >> wrote:

    >
    > <snip>
    >
    >>> if (object.tagName.toUpperCase() == 'BODY'){

    >>
    >> A more efficient test here is:
    >>
    >> if ( /body/i.test(object.tagName) ){

    >
    > <snip>
    >
    > Is it a more efficient test? Have you read the ECMA 262 section on
    > Regular Expressions and thought about what it must take to implement
    > that? String comparison is not that complex; maybe compare the length
    > and return false if they don't match, then compare each corresponding
    > character in turn, returning false at the first non-match (equality
    > testing between 16 bit integers is trivial for CPUs to do directly), and
    > return true if you get to the end of the strings without having returned
    > false.


    I'm on lunch, so let's compare:

    A. /body/i.test(object.tagName)

    to

    B. object.tagName.toUpperCase() == 'BODY'

    I'll define efficiency as providing the same result while consuming
    fewer resources. You may wish to offer some modification of that.

    In this case, resources are:

    1. Programmer time in keystrokes - A: 27 B: 36
    A has 30% fewer keystrokes, therefore it's more efficient for this
    criterion.

    2. Download bandwidth in characters - A: 27 B: 36
    A has 30% fewer characters and is more efficient for this
    criterion.

    3. Browser execution -
    I could compare the two algorithms and see which *should* be
    quicker, but that that would just be my opinion of an
    implementation and may not have any basis in fact. So I'll do the
    pragmatic (and considerably less intellectual) thing and just
    write a test and run it through a few of browsers.

    Just for fun, I put in a case C that uses a compiled RegExp - the
    full script is below.

    Results vary by browser - Firefox, Netscape & Mozilla were
    virtually identical, so I've just reported Firefox:

    A B C
    Firefox 360 770 360
    IE 950 310 360
    Opera 530 820 530

    Make what you will of that! For Geko-based browsers and Opera,
    the RegExp method is about twice as fast always: for IE, the
    string method is nearly 3 times faster unless a compiled RegExp is
    used (this points to the others as compiling and caching the
    RegExp regardless, but that is pure conjecture).

    Since we are talking 100,000 iterations here, it likely makes no
    practical difference in most cases. In those cases where it does
    matter, a compiled RegExp is just as fast in IE as the string
    method and twice as fast in other browsers.

    And the number of keystrokes for C is less than for either of the
    other two methods - 26 characters (the relevant part is below):

    var z = /BODY/i;
    z.test(t)

    So the worst-case-scenario (using IE of course) is that a compiled
    RegExp is about the same speed as using a string with toLoweCase()
    (or toUpperCase() as the 'case' may be). And in at least Opera and
    Geko browsers you about halve the time of execution.

    So given the above, I stand by my statement regarding efficiency.
    Over to you.


    <script type="text/javascript">
    function doTest(t){
    var r = 100000;
    var i = r, start, end, timeA, timeB, timeC;
    var start = new Date().getTime();

    while ( /BODY/i.test(t) && i-- ){}
    var end = new Date().getTime();
    timeA = end - start;

    i = r;
    start = new Date().getTime();
    while ( t.toUpperCase() == 'BODY' && i-- ){}
    end = new Date().getTime();
    timeB = end - start;

    i = r;
    start = new Date().getTime();
    var z = /BODY/i;
    while ( z.test(t) && i-- ){}
    var end = new Date().getTime();
    timeC = end - start;

    document.getElementById('result').innerHTML =
    'timeA: ' + timeA + '<br>' +
    'timeB: ' + timeB + '<br>' +
    'timeC: ' + timeC + '<br>';
    }
    </script>

    <input type="button" value="Do test" onclick="doTest('BODY')"><br>
    <span id="result"></span>


    --
    Rob
     
    RobG, Apr 18, 2005
    #5
  6. RobG Guest

    RobG wrote:
    [...]
    >
    > And the number of keystrokes for C is less than for either of the
    > other two methods - 26 characters (the relevant part is below):
    >
    > var z = /BODY/i;
    > z.test(t)


    Ooops, the correct comparison here should have been:

    var z = /BODY/i;
    z.test(object.tagName)

    Which is 38, or the same as the string method.

    >
    > So the worst-case-scenario (using IE of course) is that a compiled
    > RegExp is about the same speed as using a string with toLoweCase()
    > (or toUpperCase() as the 'case' may be). And in at least Opera and
    > Geko browsers you about halve the time of execution.
    >
    > So given the above, I stand by my statement regarding efficiency.
    > Over to you.
    >


    This does not allow for the overhead of creating the RegExp, which is
    distributed over the 100,000 iterations. If challenged I'll go the
    extra yard, but not right now.


    --
    Rob
     
    RobG, Apr 18, 2005
    #6
  7. Mick White Guest

    RobG wrote:
    {snip}
    >
    > <script type="text/javascript">
    > function doTest(t){
    > var r = 100000;
    > var i = r, start, end, timeA, timeB, timeC;
    > var start = new Date().getTime();
    >
    > while ( /BODY/i.test(t) && i-- ){}


    I'm curious, Rob, why:
    while ( /BODY/i.test(t) && i-- ){}
    ?
    Why the curly brackets?
    Mick



    > var end = new Date().getTime();
    > timeA = end - start;
    >
    > i = r;
    > start = new Date().getTime();
    > while ( t.toUpperCase() == 'BODY' && i-- ){}
    > end = new Date().getTime();
    > timeB = end - start;
    >
    > i = r;
    > start = new Date().getTime();
    > var z = /BODY/i;
    > while ( z.test(t) && i-- ){}
    > var end = new Date().getTime();
    > timeC = end - start;
    >
    > document.getElementById('result').innerHTML =
    > 'timeA: ' + timeA + '<br>' +
    > 'timeB: ' + timeB + '<br>' +
    > 'timeC: ' + timeC + '<br>';
    > }
    > </script>
    >
    > <input type="button" value="Do test" onclick="doTest('BODY')"><br>
    > <span id="result"></span>
    >
    >
     
    Mick White, Apr 18, 2005
    #7
  8. Guest

    Thanx Foxy :)
     
    , Apr 18, 2005
    #8
  9. Guest

    RobG wrote:

    > Please don't post code with tabs. Use 2 or 4 spaces for indenting.
    > Manually wrap code to prevent wrapping errors.
    >


    I DID remove the tabs but replaced them with 6 spaces.
     
    , Apr 18, 2005
    #9
  10. JRS: In article <fgN8e.1793$>, dated Mon,
    18 Apr 2005 11:56:59, seen in news:comp.lang.javascript, Mick White
    <> posted :
    >RobG wrote:


    >> while ( /BODY/i.test(t) && i-- ){}

    >
    >I'm curious, Rob, why:
    > while ( /BODY/i.test(t) && i-- ){}
    >?
    >Why the curly brackets?


    If omitted, the while would or could extend onto the next line. But a
    test in IE4 suggests that a semicolon is enough, saving two key
    depressions.

    --
    © John Stockton, Surrey, UK. ?@merlyn.demon.co.uk Turnpike v4.00 IE 4 ©
    <URL:http://www.jibbering.com/faq/> JL/RC: FAQ of news:comp.lang.javascript
    <URL:http://www.merlyn.demon.co.uk/js-index.htm> jscr maths, dates, sources.
    <URL:http://www.merlyn.demon.co.uk/> TP/BP/Delphi/jscr/&c, FAQ items, links.
     
    Dr John Stockton, Apr 18, 2005
    #10
  11. RobG Guest

    Mick White wrote:
    > RobG wrote:
    > {snip}
    >
    >>
    >> <script type="text/javascript">
    >> function doTest(t){
    >> var r = 100000;
    >> var i = r, start, end, timeA, timeB, timeC;
    >> var start = new Date().getTime();
    >>
    >> while ( /BODY/i.test(t) && i-- ){}

    >
    >
    > I'm curious, Rob, why:
    > while ( /BODY/i.test(t) && i-- ){}
    > ?
    > Why the curly brackets?
    > Mick


    It makes it obvious to me that the while has no body, others may
    use a semi-colon:

    while ( /BODY/i.test(t) && i-- );

    but there you go. Either the braces or a semi-colon are required in
    this case. Is one way better than the other? I don't think it
    should matter, but I'm no guru here! :)


    [...]


    --
    Rob
     
    RobG, Apr 18, 2005
    #11
  12. Mick White Guest

    RobG wrote:

    > Mick White wrote:
    >
    >> RobG wrote:
    >> {snip}
    >>
    >>>
    >>> <script type="text/javascript">
    >>> function doTest(t){
    >>> var r = 100000;
    >>> var i = r, start, end, timeA, timeB, timeC;
    >>> var start = new Date().getTime();
    >>>
    >>> while ( /BODY/i.test(t) && i-- ){}

    >>
    >>
    >>
    >> I'm curious, Rob, why:
    >> while ( /BODY/i.test(t) && i-- ){}
    >> ?
    >> Why the curly brackets?
    >> Mick

    >
    >
    > It makes it obvious to me that the while has no body, others may
    > use a semi-colon:
    >
    > while ( /BODY/i.test(t) && i-- );
    >
    > but there you go. Either the braces or a semi-colon are required in
    > this case. Is one way better than the other? I don't think it
    > should matter, but I'm no guru here! :)
    >

    I see. "timeB" slowest for me. Mac(FF and Safari).
    Mick
     
    Mick White, Apr 18, 2005
    #12
  13. RobG wrote:
    > Richard Cornford wrote:
    >> RobG wrote:
    >>
    >>> wrote:

    >>
    >> <snip>
    >>
    >>>> if (object.tagName.toUpperCase() == 'BODY'){
    >>>
    >>> A more efficient test here is:
    >>>
    >>> if ( /body/i.test(object.tagName) ){

    >>
    >> <snip>
    >>
    >> Is it a more efficient test? ...

    <snip>
    > I'm on lunch, so let's compare:
    >
    > A. /body/i.test(object.tagName)
    >
    > to
    >
    > B. object.tagName.toUpperCase() == 'BODY'


    Yes, that is the idea; test the proposition.

    <snip>
    > Just for fun, I put in a case C that uses a compiled
    > RegExp - the full script is below.


    Worth observing the results (particularly in IE) but not part of the
    original proposition.

    > Results vary by browser - Firefox, Netscape
    > & Mozilla were virtually identical,


    As you would expect given that they all use the same JS engine.

    > so I've just reported Firefox:
    >
    > A B C
    > Firefox 360 770 360
    > IE 950 310 360
    > Opera 530 820 530
    >
    > Make what you will of that! For Geko-based browsers and Opera,
    > the RegExp method is about twice as fast always: for IE, the
    > string method is nearly 3 times faster unless a compiled RegExp
    > is used (this points to the others as compiling and caching the
    > RegExp regardless, but that is pure conjecture).

    <snip>
    > So given the above, I stand by my statement regarding
    > efficiency. Over to you.


    My figures don't agree with yours. Taking string comparison as 100% I
    get the following (fairly consistently):-

    IE 6 Mozilla 1.6 Opera 7.54

    (/body/i.test(S)) 328.9% 195.4% 54.2%
    ("BODY".toUpperCase() == S) 100% 100% 100%

    Opera's regular expressions looks fast, or its string manipulation is
    appalling, but IE and Mozilla come down in favour of string comparison.

    <snip>
    > while ( /BODY/i.test(t) && i-- ){}


    I thought we were comparing - /body/i - not - /BODY/i -.

    <snip>
    > while ( t.toUpperCase() == 'BODY' && i-- ){}

    <snip>
    > <input ... onclick="doTest('BODY')"><br>


    Good choice of string for the test ;) String comparisons that match
    being the worst-case as the entire string needs to be processed in order
    to determine that there is a match, while most non-matching strings will
    be reveals by the first character comparison.

    Some additional points worth mentioning are that - /body/i - will
    produced a false positive if it encounters "TBODY", so the regular
    expression - /^body$/i - would be preferable.

    Most of the processing in - "BODY".toUpperCase() == S - is in the
    implied creation of a String object for the method call, and the
    subsequent method call. A comparison of - "BODY" == S - with (case
    sensitive) - /^BODY$/.test(S) - demonstrates the extent to which this is
    significant:-

    IE 6 Mozilla 1.6 Opera 7.54

    (/^BODY$/.test(S)) 3002.9% 24257.4% 1176.4%
    ("BODY" == S) 100% 100% 100%

    - But because in HTML DOMs (in reality and by specification) the -
    tagName - property is uppercase the overhead in trying to be case
    insensitive is futile.

    And finally, the most efficient formulation will be:-

    if(object == document.body){ ...

    My test page, for comparison and verification:-

    <html>
    <head>
    <title></title>
    <script type="text/javascript">
    var frm = null;
    var fncts = ['emptyL','regularExpression','stringComparison'];
    var running = false;
    function setButtons(bl){
    frm['loopLimit'].disabled = bl;
    frm["string"].disabled = bl
    var sw = frm['bt'];
    if(typeof sw.length == 'undefined'){
    sw = [sw];
    }
    for(var c = 0;c < sw.length;c++){
    sw[c].disabled = bl;
    }
    }
    function doTests(){
    if(!running){
    frm = document.forms['f'].elements;
    setButtons(true);
    frm["Dur0"].value = '';frm["Avr0"].value = '';
    for(var c = 1;c < fncts.length;c++){
    frm["Dur"+c].value = '';
    frm["Avr"+c].value = '';
    frm["CAvr"+c].value = '';
    frm["PAvr"+c].value = '';
    frm["Res"+c].value = '';
    }
    running = true;
    act(0);
    }
    }

    function act(p){
    /* setTimeout is used to minimise the occurrences
    of 'a script on this page is running slow' dialogs. */
    if(p >= fncts.length){
    setTimeout('report()',100);
    }else{
    setTimeout((fncts[p]+'('+p+');'),200);
    }
    }

    function report(){
    var lim = +frm['loopLimit'].value;
    var emDur = +frm["Dur0"].value
    var unaC = (frm["Dur"+(fncts.length-1)].value - emDur) / lim;
    frm["CAvr"+(fncts.length-1)].value = unaC;
    frm["PAvr"+(fncts.length-1)].value = '100';
    for(var c = 1;c < (fncts.length-1);c++){
    var evaC = (frm["Dur"+c].value - emDur) / lim;
    frm["CAvr"+c].value = evaC;
    frm["PAvr"+c].value = ((evaC/unaC)*100);
    }
    setButtons(false);
    running = false;
    }

    function emptyL(p){
    var lim = +frm['loopLimit'].value;
    var N, S = frm["string"].value;
    var totTime,stTime = new Date().getTime();
    for(var c = 0;c < lim;c++){
    N = true;
    }
    totTime = (new Date().getTime() - stTime)
    frm["Dur0"].value = totTime;
    frm["Avr0"].value = (totTime/lim);
    act(p+1);
    }

    function stringComparison(p){
    var lim = +frm['loopLimit'].value;
    var N, S = frm["string"].value;
    var totTime,stTime = new Date().getTime();
    for(var c = 0;c < lim;c++){
    N = ("BODY".toUpperCase() == S)
    }
    totTime = (new Date().getTime() - stTime)
    frm["Dur"+p].value = totTime;
    frm["Avr"+p].value = (totTime/lim);
    frm["Res"+p].value = N;
    act(p+1);
    }

    function regularExpression(p){
    var lim = +frm['loopLimit'].value;
    var N, S = frm["string"].value;
    var totTime,stTime = new Date().getTime();
    for(var c = 0;c < lim;c++){
    N = (/body/i.test(S));
    }
    totTime = (new Date().getTime() - stTime)
    frm["Dur"+p].value = totTime;
    frm["Avr"+p].value = (totTime/lim);
    frm["Res"+p].value = N;
    act(p+1);
    }

    </script>
    </head>
    <body>
    <p>
    <form name="f" action="#">
    Loop Length = <input type="text" value="1300000"
    name="loopLimit"> Some browsers will put up an &quot;A script on
    this page is making the browser run slowly&quot; dialog. If this
    happens the results for the test will be invalid and a shorter loop
    will be needed. However, JavaScript Date objects do not tend to be
    accurate to less than 10 milliseconds so duration results that are
    not different by at least 20 milliseconds (and preferably 100+) are
    not necessarily meaningful and a longer loop may be needed to acquire
    useful results.<br><br>
    Test Value = <input type="text" value="INPUT" name="string"> <br>
    <input type="button" value="Test" name="bt" onclick="doTests();">
    Repeat tests to reduce/expose the influence of background tasks.
    <br><br>
    Empty Loop Duration (milliseconds) = <input type="text" value="X"
    name="Dur0"><br>
    Empty Loop Average (milliseconds) = <input type="text" value="X"
    name="Avr0" size="22"><br><br>

    <code>(/body/i.test(S))</code> Duration (milliseconds) =
    <input type="text" value="X" name="Dur1"><br>
    <code>(/body/i.test(S))</code> Average (milliseconds) =
    <input type="text" value="X" name="Avr1" size="22"><br>
    (result = <input type="text" value="X" name="Res1" size="22">)<br><br>

    <code>("BODY".toUpperCase() == S)</code> Duration (milliseconds) =
    <input type="text" value="X" name="Dur2"><br>
    <code>("BODY".toUpperCase() == S)</code> Average (milliseconds) =
    <input type="text" value="X" name="Avr2" size="22"><br>
    (result = <input type="text" value="X" name="Res2" size="22">)<br><br>
    <br>

    <input type="button" value="Test" name="bt" onclick="doTests();">
    Repeat tests to reduce/expose the influence of background tasks.
    <br><br>

    Results: (duration of test - duration of empty loop) / loop length<br>

    <code>(/body/i.test(S))</code> Average (milliseconds) =
    <input type="text" value="X" name="CAvr1" size="22"><br>
    <code>("BODY".toUpperCase() == S)</code> Average (milliseconds) =
    <input type="text" value="X" name="CAvr2" size="22"><br>

    <br>
    Differences (<code>("BODY".toUpperCase() == S)</code> = 100%)<br>

    <code>(/body/i.test(S))</code>
    <input type="text" value="X" name="PAvr1" size="22">%<br>
    <code>("BODY".toUpperCase() == S)</code>
    <input type="text" value="X" name="PAvr2" size="22">%<br>
    <br>
    </form>
    </p>
    </body>
    </html>

    Richard.
     
    Richard Cornford, Apr 19, 2005
    #13
  14. RobG Guest

    Richard Cornford wrote:
    > RobG wrote:

    [...]
    >> A B C
    >> Firefox 360 770 360
    >> IE 950 310 360
    >> Opera 530 820 530
    >>
    >>Make what you will of that! For Geko-based browsers and Opera,
    >>the RegExp method is about twice as fast always: for IE, the
    >>string method is nearly 3 times faster unless a compiled RegExp
    >>is used (this points to the others as compiling and caching the
    >>RegExp regardless, but that is pure conjecture).

    >
    > <snip>
    >
    >>So given the above, I stand by my statement regarding
    >>efficiency. Over to you.

    >
    >
    > My figures don't agree with yours. Taking string comparison as 100% I
    > get the following (fairly consistently):-
    >
    > IE 6 Mozilla 1.6 Opera 7.54
    >
    > (/body/i.test(S)) 328.9% 195.4% 54.2%
    > ("BODY".toUpperCase() == S) 100% 100% 100%


    Using your page, my results are:

    IE 6 Mozilla 1.6 Opera 7.54

    (/body/i.test(S)) 300.5% 34.0% 24.6%
    ("BODY".toUpperCase() == S) 100% 100% 100%


    I have no idea why my results vary so much from yours for the non-IE
    browsers.

    >
    > Opera's regular expressions looks fast, or its string manipulation is
    > appalling, but IE and Mozilla come down in favour of string comparison.


    Based on both results, Opera's RegExp is ordinary but string
    comparison is awful.

    >
    > <snip>
    >
    >> while ( /BODY/i.test(t) && i-- ){}

    >
    >
    > I thought we were comparing - /body/i - not - /BODY/i -.
    >
    > <snip>
    >
    >> while ( t.toUpperCase() == 'BODY' && i-- ){}

    >
    > <snip>
    >
    >><input ... onclick="doTest('BODY')"><br>

    >
    >
    > Good choice of string for the test ;) String comparisons that match
    > being the worst-case as the entire string needs to be processed in order
    > to determine that there is a match, while most non-matching strings will
    > be reveals by the first character comparison.


    In practice it appears to make little difference - a bit of testing
    showed it's impossible to split the results using 'BODY' or 'body'.

    I imagine there are a small number of highly refined algorithms used
    in comparison operations that make such differences irrelevant.

    >
    > Some additional points worth mentioning are that - /body/i - will
    > produced a false positive if it encounters "TBODY", so the regular
    > expression - /^body$/i - would be preferable.


    Yes, my bad there.

    >
    > Most of the processing in - "BODY".toUpperCase() == S - is in the
    > implied creation of a String object for the method call, and the
    > subsequent method call. A comparison of - "BODY" == S - with (case
    > sensitive) - /^BODY$/.test(S) - demonstrates the extent to which this is
    > significant:-
    >
    > IE 6 Mozilla 1.6 Opera 7.54
    >
    > (/^BODY$/.test(S)) 3002.9% 24257.4% 1176.4%
    > ("BODY" == S) 100% 100% 100%
    >
    > - But because in HTML DOMs (in reality and by specification) the -
    > tagName - property is uppercase the overhead in trying to be case
    > insensitive is futile.


    You're right about tagName, but I don't think the exercise is futile.

    The DOM level 2 spec encourages case-insensitivity for element name
    comparisons (or conversion and testing with lower case) where
    compatibility is required between XHTML and HTML DOM. I guess that
    suggestion stuck (Section 1.3).

    My other excuse is that there are lots of things that the spec
    requires of browsers that aren't fully or universally implemented, so
    a bit of "belt 'n braces" seems OK.

    Having said all that, removing toUpperCase() from the string
    comparison reduces the time by 80% for IE and a staggering 95% for
    the 'zillas, making them about 5 times faster than IE. The RegExp
    method gets no such benefit if the 'i' is removed.

    So if performance is an issue and XHTML compatibility isn't, use a
    string comparison without toUpper/LowerCase().

    >
    > And finally, the most efficient formulation will be:-
    >
    > if(object == document.body){ ...
    >


    HEY! That's cheating! The exercise was string comparison, not
    finding DOM elements!

    But I guess you're just winding me up. ;-)


    [...]


    --
    Rob
     
    RobG, Apr 19, 2005
    #14
  15. RobG wrote:
    > Richard Cornford wrote:

    <snip>
    >> IE 6 Mozilla 1.6 Opera 7.54
    >>
    >> (/body/i.test(S)) 328.9% 195.4% 54.2%
    >> ("BODY".toUpperCase() == S) 100% 100% 100%

    >
    > Using your page, my results are:
    >
    > IE 6 Mozilla 1.6 Opera 7.54
    >
    > (/body/i.test(S)) 300.5% 34.0% 24.6%
    > ("BODY".toUpperCase() == S) 100% 100% 100%
    >
    >
    > I have no idea why my results vary so much from yours for
    > the non-IE browsers.


    I would suspect the CPU and/or the OS could potentially make a big
    difference. And in this case it looks like the OS makes the difference
    as earlier in the week I had a chance to repeat the test on a Windows
    2000 machine with otherwise broadly similar hardware (same type of CPU)
    and got results similar to yours (Mozilla did much better on that
    machine).

    <snip>
    >> Most of the processing in - "BODY".toUpperCase() == S - is
    >> in the implied creation of a String object for the method
    >> call, and the subsequent method call. A comparison of -
    >> "BODY" == S - with (case sensitive) - /^BODY$/.test(S) -
    >> demonstrates the extent to which this is significant:-
    >>
    >> IE 6 Mozilla 1.6 Opera 7.54
    >>
    >> (/^BODY$/.test(S)) 3002.9% 24257.4% 1176.4%
    >> ("BODY" == S) 100% 100% 100%
    >>
    >> - But because in HTML DOMs (in reality and by specification)
    >> the - tagName - property is uppercase the overhead in trying
    >> to be case insensitive is futile.

    >
    > You're right about tagName, but I don't think the exercise is
    > futile.
    >
    > The DOM level 2 spec encourages case-insensitivity for element
    > name comparisons (or conversion and testing with lower case)
    > where compatibility is required between XHTML and HTML DOM.
    > I guess that suggestion stuck (Section 1.3).


    To say "encourage" is a matter of interpretation. The spec states that
    if scripts are to be written to work for both HTML and XHTML then such
    testing needs to be case insensitive.

    > My other excuse is that there are lots of things that the spec
    > requires of browsers that aren't fully or universally implemented,
    > so a bit of "belt 'n braces" seems OK.


    Cautious scripting is generally advisable but 10-30 times worse
    performance in exchange is not necessarily a good idea. In this case, I
    have not yet encountered (or heard of) an HTML DOM implementation that
    has a - tagName - property and does not follow the standard in being
    uppercase (including IE 4 which introduced the property, pre-dating the
    standard), and planning for the consequences of the browser not
    implementing the property at all necessary regardless of its case.

    <snip>
    > So if performance is an issue and XHTML compatibility isn't,
    > use a string comparison without toUpper/LowerCase().


    When would XHML compatibility be an issue? A document may be either HTML
    or XHTML when it gets to the client. The script that accompanies it only
    needs to be capable of interacting with one type of DOM.

    And do you see people trying to write scripts that function in both
    types of DOM? Consider the namespace qualified method on the DOM (the
    ones with the "NS" prefix), they should be used in XMTML. How often have
    you seen a script that tests for the NS methods in preference to the
    non-NS versions and uses those in preference? And if the NS method are
    being used in DOMs that may be HTML or XHTML it becomes necessary to
    determine which namespace to use with those methods prior to using them.

    That is a lot of extra authoring effort, and an overhead in execution,
    in a world where XHTML is normally never served to UAs in a commercial
    context. (The very few sites that use content negotiation, get it right,
    and server variable document types, don't have to serve the same scripts
    with each type). So with only a limited need for XHTML client-side
    scripts, and then only outside of a public, commercial context,
    consequences that follow only form attempting to write cross-DOM scripts
    are not worth the consideration of javascript authors. We would be
    ill-advised to be attempting to write cross-DOM scripts in the first
    place.

    >> And finally, the most efficient formulation will be:-
    >>
    >> if(object == document.body){ ...

    >
    > HEY! That's cheating! The exercise was string comparison,
    > not finding DOM elements!


    The context was finding DOM elements (or at least confirming that they
    had been found).

    > But I guess you're just winding me up. ;-)


    Stirring you up, maybe, not winding. ;)

    Richard.
     
    Richard Cornford, Apr 24, 2005
    #15
  16. Richard Cornford wrote:
    <snip>
    > ..., and planning for the consequences of the browser
    > not implementing the property at all necessary regardless of
    > its case.

    <snip>

    Well that is gibberish. It should have read:-

    "..., and planning for the consequences of the browser not implementing
    the property at all is necessary regardless, in any case."

    Richard.
     
    Richard Cornford, Apr 24, 2005
    #16
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. nospam

    Annoying flaw / bug in ASP.NET

    nospam, Jun 6, 2005, in forum: ASP .Net
    Replies:
    5
    Views:
    773
    Kevin Spencer
    Jun 6, 2005
  2. =?Utf-8?B?QW5kcsOpIFNpbHZh?=

    ASP.NET 2.0 / VS.NET 2005 ANNOYING BUG

    =?Utf-8?B?QW5kcsOpIFNpbHZh?=, Feb 25, 2006, in forum: ASP .Net
    Replies:
    2
    Views:
    767
    =?Utf-8?B?QW5kcsOpIFNpbHZh?=
    Apr 8, 2006
  3. =?ISO-8859-1?Q?Elmo_M=E4ntynen?=
    Replies:
    1
    Views:
    282
  4. GaryDean

    annoying FireFox Issues

    GaryDean, Jun 16, 2007, in forum: ASP .Net
    Replies:
    3
    Views:
    427
    GaryDean
    Jun 17, 2007
  5. Howard Kaikow

    Bug in my Javascript or bug in Firefox

    Howard Kaikow, Nov 28, 2004, in forum: Javascript
    Replies:
    9
    Views:
    151
    Howard Kaikow
    Dec 1, 2004
Loading...

Share This Page