Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chrome sometimes calls incorrect constructor

We have a web site that uses extensively jQuery and it works fine in Firefox and IE. However in Chrome, we are getting frequently (and semi-randomly) Uncaught TypeError: Cannot call method 'apply' of undefined (also other jQuery methods appear in place of apply).

We managed to track down the problem to jQuery method pushStack().

Original source code (jQuery 1.7.1):

// Take an array of elements and push it onto the stack
// (returning the new matched element set)
pushStack: function( elems, name, selector ) {
   // Build a new jQuery matched element set
   var ret = this.constructor();

   // (etc.)
}

Instrumented code:

pushStack: function( elems, name, selector ) {
   if (!(this instanceof jQuery.fn.init)) throw this;

   // Build a new jQuery matched element set
   var ret = this.constructor();

   if (!(ret instanceof jQuery.fn.init)) {
          console.log("pushStack>this: " + this.constructor);
          console.log("pushStack>ret: " + ret.constructor);
          throw ret;
   }

   // (etc.)
}

In most cases pushStack() runs correctly. However sometimes Chrome constructs an object of type Object instead of jQuery.fn.init. Console output:

pushStack>this: function ( selector, context ) {
    // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init( selector, context, rootjQuery );
    }
pushStack>ret: function Object() { [native code] }
Uncaught #<Object>

Did anybody encounter similar problem? Is it a (known) bug of Chrome?

Update

I managed to simplify our page, so that it could be loaded on its own. I filled bug in Chromium project project, the page for reproducing the issue is attached there.

like image 677
Miroslav Bajtoš Avatar asked Apr 25 '12 11:04

Miroslav Bajtoš


2 Answers

The responses in Chromium bug tracker seem to confirm that this is a bug of Chrome browser.

The workaround solution is to "fix" pushStack() function in jQuery:

// Take an array of elements and push it onto the stack
// (returning the new matched element set)
pushStack: function( elems, name, selector ) {
   // Build a new jQuery matched element set
   var ret = this.constructor();

   // Workaround for Chrome bug
   if ((this instanceof jQuery.fn.init) && !(ret instanceof jQuery.fn.init)) {
       // console.log("applying pushStack fix");
       ret = new jQuery.fn.init();
   }

   // etc.
}
like image 152
Miroslav Bajtoš Avatar answered Oct 22 '22 05:10

Miroslav Bajtoš


Here is an "unobtrusive" solution (for example, if you're pulling jQuery from a CDN). Save this in a .js file and include it after pulling in jQuery.

(function ($) {
    var pushStackOrig, pushStackChrome;

    pushStackOrig = $.fn.pushStack;

    pushStackChrome = function ( elems, name, selector ) {
        // Build a new jQuery matched element set

        // Invoke the correct constructor directly when the bug manifests in Chrome.
        //var ret = this.constructor();
        var ret = new jQuery.fn.init(); 

        if ( jQuery.isArray( elems ) ) {
            push.apply( ret, elems );

        } else {
            jQuery.merge( ret, elems );
        }

        // Add the old object onto the stack (as a reference)
        ret.prevObject = this;

        ret.context = this.context;

        if ( name === "find" ) {
            ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
        } else if ( name ) {
            ret.selector = this.selector + "." + name + "(" + selector + ")";
        }

        // Return the newly-formed element set
        return ret;
    };

    $.fn.pushStack = function (elems, name, selector) {
        var ret;

        try {
            ret = pushStackOrig.call(this, elems, name, selector);
            return ret;
        } 
        catch (e) {
            if (e instanceof TypeError) {
                if (!(ret instanceof jQuery.fn.init)) {
                    ret = pushStackChrome.call(this, elems, name, selector);
                    return ret;
                }
            }

            throw e;
        }
    };

}).call(this, jQuery);
like image 24
davidzarlengo Avatar answered Oct 22 '22 04:10

davidzarlengo