Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stack overflow at line 0

I have a form validation script that is unfortunately returning the Stack overflow at line 0 alert box shortly before crashing (IE7) and just straight up crashes in IE8 (It does work first, very slowly).

I have made a jsFiddle for your testing pleasure: http://jsfiddle.net/yuNXm/2/ the stack overflow occurs after you have entered a value into an input which requires validation and then lose it's focus. (the email field is ajax driven so won't function there).

The relevant Javascript:

jQuery(document).ready(function($) {

    var inputs = $('input[data-validation-method]');
    var fields = $();
    var classes = ['fail', 'win'];

    //Methods of validation, must return an object like so {result: [boolean], message: [string or false]} as a parameter of the callback() function;
    var methods = {

        'email' : function(field, dependancies, callback) {
            var value = field.val();
            var response = false;
            field.addClass("loading");
            $.post(
               ajaxData.url, 
               {
                  'action':'validate_form',
                  'value': value,
                  'method': field.data('method')
               }, 
               function(response){
                   return callback(response);
               }
            ).complete(function() {
                field.removeClass("loading");
            });
        },

        'password' : function(field, dependancies, callback) {
            var value = field.val();
            var response = {};
            if (value.length < 8) {
                response.result = false;
                response.message = 'Your password must be a minimum of 8 characters';
            } else {
                response.result = true;
                response.message = false;
            }
            return callback(response);
        },

        'verify_password' : function(field, dependancies, callback) {
            var value = field.val();
            var response = {};
            if (value != dependancies["password"].val()) {
                if (!dependancies["password"].val() || !value) {
                    return false;
                }
                response.result = false;
                response.message = 'Passwords do no match';
            } else {
                response.result = true;
                response.message = false;
            }
            return callback(response);
        }
    }

    // Prepare fields for validation
    inputs.each(function() {
        createField($(this));
    });

    function createField (field) {
        inputs = inputs.not(field);
        var method = field.attr('data-validation-method');
        var requires = field.attr('data-validation-requires');
        if (!!requires) {
            requires = requires.split(',');
            var dependancies = {};
            $.each(requires, function(key, value) {
                var element = $('#' + value);
                if(element.length) {
                    dependancies[element.attr('id')] = element;
                    if(inputs.find(element).length) {
                        createField(element);
                    }
                    if ($.isArray(element.data('linked_fields'))) {
                        element.data('linked_fields').push(field);
                    } else {
                        element.data('linked_fields', [field]);
                    }
                }
            });
        }
        if (methods[method]) {
            fields = fields.add('#' + field.attr('id'));
            field.data('method', method);
            field.data('dependancies', dependancies);
        }
    }

    function validate (field) {
        var callback = function(response) {
            field.data('response', response);
            if (response) {
                toggleFlag(field, 'show');
            } else {
                toggleFlag(field, 'remove');
            }
            if($.isArray(field.data('linked_fields'))) {
                $.each(field.data('linked_fields'), function(key, value) {
                    validate(value);
                });
            }
        }
        methods[field.data('method')](field, field.data('dependancies'), callback);
    }

    fields.focus(function() {
        var field = $(this);
        field.data("value", field.val());
        field.bind("propertychange keyup input paste", function(event){
            if(field.data("response") && (field.val() != field.data("value"))) {
                toggleFlag(field, "hide");
                if($.isArray(field.data('linked_fields'))) {
                    $.each(field.data('linked_fields'), function(key, value) {
                        toggleFlag(value, "hide");
                    });
                }
            }
        });
    });

    fields.blur(function() {
        var field = $(this);
        if (field.val().length) {
            if (field.val() != field.data("value")) {
                toggleFlag(field, "remove");
                validate(field);
            } else {
                toggleFlag(field, "show");
            }
        } else {
            toggleFlag(field, "remove");
        }
    });

    function toggleFlag (field, method) {
        var flag = field.data("flag");
        var response = field.data("response");
        if (response) {
            switch (method) {
                case "show":
                    if (response.message) {
                        if(!flag) {
                            flag = $('<span class="pie ' + classes[~~response.result] + '">' + response.message + '</span>').insertAfter(field);
                            field.data("flag", flag);
                            flag.hide();
                        }
                        if (!flag.data("active")) {
                            flag.data("active", true);
                            flag.stop(true, true).animate({height: "show", opacity: "show"}, 500);
                        }
                    }
                    field.addClass(classes[~~response.result]);
                    break;
                case "hide":
                    if (flag) {
                        if (flag.data("active")) {
                            flag.data("active", false);
                            flag.stop(true, true).animate({height: "hide", opacity: "hide"}, 500);
                        }
                    }
                    field.removeClass(classes[~~response.result]);
                    break;
                case "remove":
                    if (flag) {
                        field.removeData("flag");
                        if (flag.data("active")) {
                            flag.stop(true, true).animate({height: "hide", opacity: "hide"}, 100, function() {
                                flag.remove();
                            });
                        }
                    }
                    field.removeClass(classes[~~response.result]);
                    field.removeData("response");
                    break;
            }
        }
    }

});

The relevant HTML:

<form action="" method="post" class="user-data">
<div class="fields">
    <label for="email">Email:</label>
    <input type="text" name="email" id="email" data-validation-method="email" class="text" value="" placeholder="[email protected]" />
    <span class="info">We won\'t do anything cheeky with your email... promise.</span>
    <label for="password">Choose a password:</label>
    <input type="password" name="password" id="password" data-validation-method="password" class="text" value="" />
    <label for="verify_password">Retype your password:</label>
    <input type="password" name="verify_password" id="verify_password" class="text" data-validation-method="verify_password" data-validation-requires="password" value="" />
    <input type="checkbox" name="mailing_list" value="true" /> <label for="mailing_list">I would like to recieve email updates about new features</label>
    <span class="info">We won\'t spam your inbox, emails will be infrequent.</span>
</div>
<input type="submit" id="submitbtn" class="button omega" name="submit" value="Create your account" />
</form>

Now I know this is normally due to recursion, and I use recursion in two areas of the script.

Recurring function number 1:

function createField (field) {
    inputs = inputs.not(field);
    var method = field.attr('data-validation-method');
    var requires = field.attr('data-validation-requires');
    if (!!requires) {
        requires = requires.split(',');
        var dependancies = {};
        $.each(requires, function(key, value) {
            var element = $('#' + value);
            if(element.length) {
                dependancies[element.attr('id')] = element;
                if(inputs.find(element).length) {
                    createField(element);
                }
                if ($.isArray(element.data('linked_fields'))) {
                    element.data('linked_fields').push(field);
                } else {
                    element.data('linked_fields', [field]);
                }
            }
        });
    }
    if (methods[method]) {
        fields = fields.add('#' + field.attr('id'));
        field.data('method', method);
        field.data('dependancies', dependancies);
    }
}

Because the stack overflow occurs only when you interact with an input that needs validation, and the createField function is only used as an initialisation function I don't think it is this one.

Recurring function number 2:

function validate (field) {
    var callback = function(response) {
        field.data('response', response);
        if (response) {
            toggleFlag(field, 'show');
        } else {
            toggleFlag(field, 'remove');
        }
        if($.isArray(field.data('linked_fields'))) {
            $.each(field.data('linked_fields'), function(key, value) {
                validate(value);
            });
        }
    }
    methods[field.data('method')](field, field.data('dependancies'), callback);
}

I don't have access to any other external programs to debug this (corporate environment), can anyone lead me in the right direction here?

like image 499
George Reith Avatar asked Jul 19 '12 13:07

George Reith


1 Answers

Internet Explorer is firing the event propertychange whenever you use jQuery to addClass or removeClass. The problem code is here:

     var field = $(this);
        field.data("value", field.val());
        field.bind("propertychange keyup input paste", function(event){
            if(field.data("response") && (field.val() != field.data("value"))) {
                toggleFlag(field, "hide");
                if($.isArray(field.data('linked_fields'))) {
                    $.each(field.data('linked_fields'), function(key, value) {
                        toggleFlag(value, "hide");
                    });
                }
            }
      });

In your toggleFlag function you call jQuery's addClass and removeClass. That created the infinite recursion loop that led to the stack overflow.

If you take out the propertychange it works great on Internet Explorer as well as all other browsers.

Working example: http://jsfiddle.net/yuNXm/9/

The reason you were having this problem only on Internet Explorer is that onpropertychange is a proprietary event implemented by Microsoft for Internet Explorer. It is not implemented by other browsers.

Debugging stack overflows with IE6-8:

A good method you can use in the future to diagnose these types of stack overflows is to:

  1. Identify one of the functions involved with the infinite recursion loop. If you're stuck with IE6-8 with no debugging capability, then this entails placing alerts in various functions till you find an infinitely looping function.

  2. Place this line of code at the top of the function:

    alert(arguments.callee.caller.toString());

This alert will tell you which function is calling the infinitely looping function. Then by tracing back which functions are involved in the infinite recursion loop, you can isolate the parts of your code you need to closely examine for the cause of the infinite loop.

Of course if you have a modern web browser with proper debugging tools, this isn't necessary--you would just step through the code.

On a side note, I feel your pain. Part of my job also involves coding JavaScript for corporate clients where IE6-8 is often the browser imposed by their IT Dept. No debug tools, just alerts and comments; not even a line number to work with when you're troubleshooting stack overflows.

like image 63
Elliot B. Avatar answered Nov 18 '22 06:11

Elliot B.