Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Internet Explorer 7/8 and window functions are empty objects

In Internet Explorer 8 (works also in IE9 in IE7/8 modes) the following code alerts object and undefined instead of expected function and something like function() { [native code] }.

alert("typeof window.setTimeout = " + typeof window.setTimeout);  // object
alert("window.setTimeout.apply  = " + window.setTimeout.apply );  // undefined

Try it: http://jsfiddle.net/BsvZw/5/

Why is this happening? What would be a workaround to get the actual setTimeout?

Update

I am trying to create a wrapper around setTimeout:

var _oldSetTimeout = window.setTimeout;
window.setTimeout = function ()
{
    // ...

    return _oldSetTimeout.apply(this, arguments);    // this is place where IE 7/8 says 'Object doesn't support this property or method'
                                                // and _oldSetTimeout looks like an empty object
};
like image 615
Loki Kriasus Avatar asked Dec 13 '22 00:12

Loki Kriasus


2 Answers

Why is this happening?

Basically, because IE hates web developers and is messing with you.

More seriously, things provided by the browser implementation that are not part of the core Javascript language may be classified as host objects. When it comes to host objects all bets are off and they are basically allowed to do anything they want[1] without needing to respect usual Javascript semantics.

What would be a workaround to get the actual setTimeout?

I know its really ugly, but you could do an if-else-if chain up to a predefined number of arguments. In setTimeout's case this shouldn't be a big problem since you shouldn't ever need more then 2 or 3 arguments for it.

var _oldSetTimeout = window.setTimeout;
window.setTimeout = function (a1, a2, a3)
{
   switch(arguments.length){
       case 0:  return _oldSetTimeout();
       case 1:  return _oldSetTimeout(a1);
       case 2:  return _oldSetTimeout(a1, a2);
       default: return _oldSetTimeout(a1, a2, a3);
   }
};

While this is a very ugly solution, sometimes its the only way. For example, there is no way to invoke constructors with variadic arguments either.


[1] To give you an idea of how evil host objects can be, the other day I had to do feature detection for XPath methods in DOM nodes/documents. Instead of the usual if(node.selectNodes) test I had to use if("selectNodes" in node) because nodes are host objects in in IE and just accessing the selectNodes property would actually call it, giving me an "incorrect number of arguments" exception!

like image 59
hugomg Avatar answered Dec 28 '22 23:12

hugomg


When you call "apply" on a function, the function object itself is the "this" of the call to "apply", so you can do this:

function test(s) { alert(""+s); }
Function.prototype.apply.call(setTimeout, null, [test, 0, 'abc']);
// Note: same as "setTimeout.apply(null, [test, 0, 'abc']);" in supported browsers.

Basically, we are forcing the use of Function.prototype.apply instead of trying to look for a non-existing setTimeout.apply. In the parameters to call, the first argument is the function we want to run using apply (the object context, which is setTimeout in this case). What follows next are the parameters that get pass to apply, the first of which is the expect this the function will be run under ('null' in this case, since setTimeout does not allow a this context value), and the following being the array of arguments to be passed to the function we want to run.

This works in IE7+, except IE7 doesn't pass on the custom parameters (i.e 'abc' in this example, which will prompt "undefined" instead).

Here's a TypeScript implementation:

/** Helps support cases where 'apply' is missing for a host function object (i.e. IE7 'setTimeout', etc.).  This function
* will attempt to call '.apply()' on the specified function, and fall back to a work around if missing.
* @param {Function} func The function to call '.apply()' on.
* @param {Object} _this The calling object, which is the 'this' reference in the called function (the 'func' argument).
* Note: This must be null for special host functions, such as 'setTimeout' in IE7.
* @param {any} args The arguments to apply to given function reference (the 'func' argument).
*/
function apply(func: Function, _this: Object, args: any[]): any {
    if (func.apply) {
        return func.apply(_this, args);
    } else {
        return Function.prototype.apply.call(func, _this, args);
    }
}

... and a basic JavaScript one:

function apply(func, _this, args) {
    if (func.apply) {
        return func.apply(_this, args);
    } else {
        return Function.prototype.apply.call(func, _this, args);
    }
}
like image 28
James Wilkins Avatar answered Dec 29 '22 01:12

James Wilkins