Monday, February 9th, 2009

Building an isNative method

Category: JavaScript

<p>If you want to test if a method is “native” in that the host provided the methods, versus a third party method (including one that has overridden a native function) it can be tricky.

Kangax posted on the issue where he took a note Diego Perini on an impl:

javascript
< view plain text >
  1. // detect native method in object
  2. // not same scope of isHostObject
  3. var isNative = function(object, method) {
  4.   return object && method in object &&
  5.     typeof object[method] != 'string' &&
  6.     // IE &amp; W3C browser return "[native code]"
  7.     // Safari < = 2.0.4 will return "[function]"
  8.     (/{\s*[native code]\s*}|^[function]$/).test(object[method]);
  9. }

and then shows how it can easily create false positives.

Kangax then shows one solution:

The solution I came up with is not perfect. It doesn’t rely on any specified behavior either; That’s because there doesn’t seem to be any standard, describing what built-in host method should be like / act like / represent itself like. The most robust solution seems to be to test for a presence of a method in a different DOM context. For example, to check whether native window.addEventListener is present, we could use something along these lines:

javascript
< view plain text >
  1. var isNativeWindowAddEventListenerPresent = (function(global){
  2.   // feature test following methods as needed
  3.   var el = document.createElement('iframe');
  4.   var root = document.body || document.documentElement;
  5.   root.appendChild(el);
  6.   var frame = global.frames[global.frames.length-1];
  7.   var result = (typeof frame.addEventListener != "undefined");
  8.   root.removeChild(el);
  9.   el = null;
  10.   return result;
  11. })(this);

In all browsers I tested, same-named methods from any of two non-modified DOM contexts behave identically. This means that if window.addEventListener is present in one non-modified context, it will be present in another non-modified context. It will also function identically. This seems to be a de-facto standard; unlike string representation, all browsers seem to be pretty consistent in this regard.

Related Content:

14 Comments »

Comments feed TrackBack URI

bleh iframes

Comment by V1 — February 9, 2009

ok, I tried to post directly in kangax blog but for some reason I was the post did not appear … in these cases I prefer the delete keyword and my tests did not cause troubles:

function isNativeMethod(object, method){
var r = !!object && !/undefined|string/.test(typeof object[method]);
if(r){
var toString = (method = object[method]).toString;
try{ // necessary for native functions (alert, as example)
delete method.toString;
r = method.toString !== Object.prototype.toString && /\[.+?\]/.test(method);
method.toString = toString;
}catch(e){
r = true;
}
};
return r;
};

// test cases
function fake(){};
fake.toString = function(){return '[native]'};
window.test = {
};
alert([
isNativeMethod(this, "fake"),
isNativeMethod(this, "test"),
isNativeMethod(this, "attachEvent"),
isNativeMethod(this, "addEventListener")
].join("\n"));

Comment by WebReflection — February 9, 2009

for isntance, above code is not parsed perfectly as well …
if(method.toString != toString)method.toString = toString;

Comment by WebReflection — February 9, 2009

I’ve never yet needed an “isNative” method. What kind of crazy cool things are you people doing that the need arises?

Comment by Nosredna — February 9, 2009

I think sometimes you would like to trust your environment or simply you would like to know if you can use that method in a certain way.

As example, I created a XMLHttpRequest wrapper to be able to create Ajax prototypes but maybe another library would like to know if the function is native or a fake (both security problems plus possible problems with the wrapper itself)

Comment by WebReflection — February 9, 2009

@Andrea,
you are correct the initial necessity was inferred by the fact some of my code had to fight with frameworks overwriting native methods trying to fix issues but still not providing a fully functional alternative implementation.

My explicit cases were with “hasAttribute/getAttribute” and “getElementsByClassName” but I believe any of those “supposed” built-in fixes may have given similar results and needs similar tricks.

@kangax, yes functional testing is sometime superior, but when writing the above I had two objectives be concise and have something generic.
It will be more difficult to reach both objectives with functional testing also feasible if the concise part is an option.

As a side note, I am not sure every browser has IFRAMES either.

Thank you for your good work & continuous investigation in this field.

Diego

Comment by dperini — February 9, 2009

@dperini

Any browser I could find (and test) supports iframes. The only one that doesn’t is proprietary Blackberry’s one. I understand that using iframes is not always possible/desirable, but the goal was to point out that this is the most reliable (although somewhat unlikable) solution at the moment.

Anyone, of course, can go ahead and use `toString` or `delete`-based versions, but the fact remains – there’s *no guarantee* that some random browser will support this behavior. You can use it, but at your own risk, understanding that you rely on unspecified behavior – behavior similar to that of `navigator.userAgent` : )

Comment by kangax — February 9, 2009

The iframe technique is what I use on my frameworks page, which now highlights overridden native functions using a simple toString comparison.
http://www.mankz.com/code/globalcheck.htm

Comment by mankz — February 9, 2009

@kangax,
stealing other iframe object methods for consumption or for testing purpose was firstly touched in this discussion (2 years ago):

http://dean.edwards.name/weblog/2006/11/hooray/#comment11342

this is to reiterate I know very well the strong argumentations you are bringing about using feature testing on iframes (fresh native scopes).

As I told you in my projects I extensively use feature testing but I have production code using object detection that has lived for years with no problems at all. Yes we can improve !!! But throwing that old knowledge away is not an improvement in my way of view things.

However it seems that now that you discovered FT you want everything going through that path forgetting and telling everything else is wrong and that you are able to fold those old testing easily.

Testing was not meant to prevent humans (programmer) from breaking it, they were built to test against browsers bugs or misbehavior that are quite static and predictable. Security is another issue that should be handled by browsers.

Diego

PS: the isNative() method showed here and in your blog have lost the escape characters in front of each square bracket in the regexp, so it doesn’t actually work by just cutting & pasting it. BE AWARE of that…

Comment by dperini — February 9, 2009

@kangax, I told you iframe is not that good stuff specially in https environments. At the same time the usage of frames is not necessary in your code:

var isNativeWindowAddEventListenerPresent = (function(global){
var el = document.documentElement.appendChild(document.createElement('iframe')),
result = typeof el.contentWindow != "undefined";
el.parentNode.removeChild(el);
el = null;
return result;
})(this);

he main problem is that how you said there is no guarantee that delete and toString trick works, but there is no guarantee that iframes are supported as well. In this JavaScript Web era guarantees are myths because of the nature of the language itself and its implementation, too different for every environment (browser,server) to be trusted 100%
// silly example
Number.prototype.toString =
Number.prototype.valueOf = function(){
return "0";
};

var i = new Number(123);
alert(i); // 0

So, as summary, sometimes we have to trust the test case, and for this reason I would like you try to break my proposal, cause to break your one I need a blackberry or an https environment surfed via IE ;-)

function isNativeMethod(object, method){
var r = !!object && !/undefined|string/.test(typeof object[method]);
if(r){
var toString = (method = object[method]).toString;
try{ // necessary for native functions (alert, as example)
delete method.toString;
r = method.toString !== Object.prototype.toString && /\[.+?\]/.test(method);
if(method.toString != toString)
method.toString = toString;
}catch(e){
r = true;
}
};
return r;
};

Comment by WebReflection — February 10, 2009

I have tweaked the RegExp in my isNative() snippet to cover other edge cases you pointed out (blackberry) and added a cast to boolean on object:


// detect native method in object
// not same scope of isHostObject
var isNative = function(object, method) {
return !!object && method in object &&
typeof object[method] != 'string' &&
// IE and W3C browser return "[native code]"
// Safari older than 3.0 will return "[function]"
(/\{\s*\[native code[^\]]*\]\s*\}|^\[function\]$/).
test(object[method]);
};

I hope the above code can go through the parser…

The first two conditions are just arguments checking, the regexp does the check I need, I don’t use this to overload native methods but for the exact contrary thus avoid using methods that seems overloaded, and in for the failing cases I provide valid and working alternatives.

Diego

Comment by dperini — February 10, 2009

kangax,
there are problems with IFRAMES when used inside an SSL environment, in IE6 your test will bring up a dialog box warning the users that the underlying communication protocol is going to change and user intervention is required…

Moreover there are no IFRAMES in Rhino and Spidermonkey and most other server side Javascript engines. This doesn’t mean your method is broken just that we have to make sure where to use it proficuously and in which situations.

In my previous research with the “doScroll()” trick for IE I also came across problems assuming that whatever behavior in the primary chrome window will be the same in an IFRAME (chromeless window) the behavior of several method properties is not the same in several cases.
In fact “doScroll()” will not throw any error in an IFRAME and there is still no explanation about that…

Also, when comparing and/or feature testing objects/properties between hosting environments I suggest you keep valid just the “types” or the “strings” the references will probably never be comparable in many cases.

Diego

Comment by dperini — February 10, 2009

@WebReflection

I just tested your version a little.
The falling test cases are:

`window.eval` – `false` in Chrome (since `eval` seems to be declared as a “user-defined” function)
`window.XMLHttpRequest` – `false` in Webkit (no idea why)
`window.XMLHttpRequest` errors out in IE (since accessing its `toString` throws error)

: )

Comment by kangax — February 11, 2009

@kangax
if you move the var toString inside the try catch IE is fine. Chrome has a user like defined function indeed so there is no way to know if that eval is the real one or not. WebKit? Probably XMLHttpRequest is an object and it inherits Object.prototype.toString (which is weird enough for me … )
thanks for the test in any case ;-)

Comment by WebReflection — February 11, 2009

Leave a comment

You must be logged in to post a comment.