I am trying to iterate over all the globals defined in a website, but in doing so I am also getting the native browser functions.
var numf=0; var nump=0; var numo=0;
for(var p in this) {
if(typeof(this[p]) === "function"){
numf+=1;
console.log(p+"()");
} else if(typeof p != 'undefined'){
nump+=1;
console.log(p);
} else {
numo+=1;
开发者_JAVA百科 console.log(p);
}
}
Is there a way to determine if a function is native to the browser or created in a script?
You can call the inherited .toString()
function on the methods and check the outcome. Native methods will have a block like [native code]
.
if( this[p].toString().indexOf('[native code]') > -1 ) {
// yep, native in the browser
}
Update because a lot of commentators want some clarification and people really have a requirement for such a detection. To make this check really save, we should probably use a line line this:
if( /\{\s+\[native code\]/.test( Function.prototype.toString.call( this[ p ] ) ) ) {
// yep, native
}
Now we're using the .toString
method from the prototype
of Function
which makes it very unlikely if not impossible some other script has overwritten the toString
method. Secondly we're checking with a regular expression so we can't get fooled by comments within the function body.
function isFuncNative(f) {
return !!f && (typeof f).toLowerCase() == 'function'
&& (f === Function.prototype
|| /^\s*function\s*(\b[a-z$_][a-z0-9$_]*\b)*\s*\((|([a-z$_][a-z0-9$_]*)(\s*,[a-z$_][a-z0-9$_]*)*)\)\s*{\s*\[native code\]\s*}\s*$/i.test(String(f)));
}
this should be good enough. this function does the following tests:
- null or undefined;
- the param is actually a function;
- the param is Function.prototype itself (this is a special case, where Function.prototype.toString gives
function Empty(){}
) - the function body is exactly
function <valid_function_name> (<valid_param_list>) { [native code] }
the regex is a little bit complicated, but it actually runs pretty decently fast in chrome on my 4GB lenovo laptop (duo core):
var n = (new Date).getTime();
for (var i = 0; i < 1000000; i++) {
i%2 ? isFuncNative(isFuncNative) :
isFuncNative(document.getElementById);
};
(new Date).getTime() - n;
3023ms. so the function takes somewhere around 3 micro-sec to run once all is JIT'ed.
It works in all browsers. Previously, I used Function.prototype.toString.call, this crashes IE, since in IE, the DOM element methods and window methods are NOT functions, but objects, and they don't have toString method. String constructor solves the problem elegantly.
Function.prototype.toString
can be spoofed, something kinda like this:
Function.prototype.toString = (function(_toString){
return function() {
if (shouldSpoof) return 'function() { [native code] }'
return _toString.apply(this, arguments)
}
})(Function.prototype.toString)
You can detect if Function.prototype.toString
is vandalized by trapping .apply()
, .call()
, .bind()
(and others).
And if it was, you can grab a "clean" version of Function.prototype.toString
from a newly injected IFRAME
.
2022 answer
Now that we have the Proxy API, there's no fail proof way to determine if a native function was overridden or not.
For example:
function isNativeFunction(f) {
return f.toString().includes("[native code]");
}
window.fetch = new Proxy(window.fetch, {
apply: function (target, thisArg, argumentsList) {
console.log("Fetch call intercepted:", ...argumentsList);
Reflect.apply(...arguments);
},
});
window.fetch.toString(); // → "function fetch() { [native code] }"
isNativeFunction(window.fetch); // → true
Following the specs, a proxied object should be indistinguishable from its target. Some runtimes (e.g., Node.js) offers some utilities to go against the spec and check if an object is proxied, but in the browser the only way to do so is by monkey patching the Proxy API itself before any proxy is applied.
So, back to the original question — I think the only available option nowadays is to hold a reference of the “clean” native function and, later on, compare your potentially monkey patched function with it:
<html>
<head>
<script>
// Store a reference of the original "clean" native function before any
// other script has a chance to modify it.
// In this case, we're just holding a reference of the original fetch API
// and hide it behind a closure. If you don't know in advance what API
// you'll want to check, you might need to store a reference to multiple
// `window` objects.
(function () {
const { fetch: originalFetch } = window;
window.__isFetchMonkeyPatched = function () {
return window.fetch !== originalFetch;
};
})();
// From now on, you can check if the fetch API has been monkey patched
// by invoking window.__isFetchMonkeyPatched().
//
// Example:
window.fetch = new Proxy(window.fetch, {
apply: function (target, thisArg, argumentsList) {
console.log("Fetch call intercepted:", ...argumentsList);
Reflect.apply(...arguments);
},
});
window.__isFetchMonkeyPatched(); // → true
</script>
</head>
</html>
By using a strict reference check, we avoid all toString()
loopholes. And it even works on proxies because they can’t trap equality comparisons.
The main drawback of this approach is that it can be impractical. It requires storing the original function reference before running any other code in your app (to ensure it’s still untouched), which sometimes you won’t be able to do (e.g., if you’re building a library).
For more info, I recently wrote an article going in-depth into the available approaches for determining if a native function was monkey patched. You can find it here.
For developers who want to use these detection methods to block user behavior (such as userscript), there are actually ways to bypass these methods.
For Example:
(The detection method for the isNative
function below comes from methods provided by others in the past.)
function isNative(f) {
if (!!/bound/.test(f.name)) return 1;
if (!!!/\{\s+\[native code\]/.test(Function.prototype.toString.call(f))) return 2;
if (!!!/\{\s+\[native code\]/.test(eval(f) + "")) return 3;
if ((typeof f).toLowerCase() !== 'function') return 4;
if (!/^\s*function\s*(\b[a-z$_][a-z0-9$_]*\b)*\s*\((|([a-z$_][a-z0-9$_]*)(\s*,[a-z$_][a-z0-9$_]*)*)\)\s*{\s*\[native code\]\s*}\s*$/i.test(String(f))) return 5;
return true;
}
function fakeStringify(value) {
return `Fake Stringify ${value}`;
};
const s = new Proxy(fakeStringify, {
get(target, prop, receiver) {
if (prop === "name") {
return "stringify";
} else if (prop === Symbol.toPrimitive) {
return function () {
return "function () { [native code] }";
};
}
}
});
const obj = {
a: 1
};
console.log("========= [native] =========");
console.log(isNative(JSON.stringify));
console.log(JSON.stringify(obj));
console.log(JSON.stringify.name);
JSON.stringify = s;
console.log("======== [override] ========");
console.log(isNative(JSON.stringify));
console.log(JSON.stringify(obj));
console.log(JSON.stringify.name);
After execution, it can be found that the isNative
detection can be successfully deceived after passing through the Proxy
.
As far as I know, there is no way to detect Proxy fucntion
in the browser environment. But if you know how to detect it, please provide it, thanks!
I tried a different approach. This is only tested for firefox, and chrome.
function isNative(obj){
//Is there a function?
//You may throw an exception instead if you want only functions to get in here.
if(typeof obj === 'function'){
//Check does this prototype appear as an object?
//Most natives will not have a prototype of [object Object]
//If not an [object Object] just skip to true.
if(Object.prototype.toString.call(obj.prototype) === '[object Object]'){
//Prototype was an object, but is the function Object?
//If it's not Object it is not native.
//This only fails if the Object function is assigned to prototype.constructor, or
//Object function is assigned to the prototype, but
//why you wanna do that?
if(String(obj.prototype.constructor) !== String(Object.prototype.constructor)){
return false;
}
}
}
return true;
}
function bla(){}
isNative(bla); //false
isNative(Number); //true
isNative(Object); //true
isNative(Function); //true
isNative(RegExp); //true
almost all of these will fail, because:
function notNative(){}
notNative.toString = String.bind(0, "function notNative() { [native code] }");
console.log( notNative.toString() );
instead:
Function.prototype.isNative = function(){
return Function.prototype.toString.call(this).slice(-14, -3) === "native code";
};
console.log(alert.isNative());
console.log(String.isNative());
function foo(){}
console.log(foo.isNative());
精彩评论