I'm using javascript extension (AKA window.external) on IE8 (might as well be any IE version) to expose certain functions.
I'm trying to call the apply function, which is (supposed to be, according to here) natively embedded in every JS function, on a window.external object's function, but the browser keep throwing exception that the apply function doesn't exist for that function.
For example, this code works:
function onDataReceived(url, success, status, data, errorMessage) {
alert(onDataReceived);
}
function innerTest() {
alert(arguments[0] + ", " + arguments[1]);
}
function outerTest() {
innerTest.apply(null, arguments);
}
outerTest("hello", "world");
// alerts "hello, world"
But This code throws exception:
function outerTest() {
window.external.innerTest.apply(null, arguments); // <-- exception
}
outerTest("hello", "world");
Bottom line is - I need to pass an unknown number of arguments to the external function, and so far i've reached a dead end...
Any ideas?
EDIT:
I accepted Mike Samuel's answer since (as far as i understand) the apply
function doesn't exist in the window.external
object, because it's not a native javascript object.
What Mike suggested as the "worst case" is what I ended up doing, for the moment.
thanks
If window.external
is a host object, or from some extension mechanism that doesn't want its prototype exposed to page logic, then it may be a function but may not have the usual call
and apply members. Luckily, you can call call
and apply apply
:
Function.prototype.apply.call(window.external, null, [argumentsToExtension])
or to be really meta,
Function.prototype.apply.apply(window.external, [null, [argumentsToExtension]])
where null
is what is passed as the value of this
which should be interpreted as window
by the usual call/apply rules.
EDIT:
If that doesn't work, you can always fall back to the triangle of hackery.
function triangleOfHackery(obj, methodName, args) {
switch (args.length) {
case 0: return obj[methodName]();
case 1: return obj[methodName](args[0]);
case 2: return obj[methodName](args[0], args[1]);
case 3: return obj[methodName](args[0], args[1], args[2]);
...
}
}
Actually there is a general solution. A simple example is like below:
function invoke (funcName, ...args) {
var argStr = '';
for (let i in args) {
argStr += ', this.args[' + i + ']';
}
return (new Function('return window.external[\'' + funcName + '\'](' + argStr.substring(2) + ')')).bind({args})();
}
// define a circular structure which cannot be stringified:
const a = {}; a.a = a;
// make a call:
invoke('foo', 10, 'abc', new Date(), function(){}, a);
// equivalent to:
window.external.foo(10, 'abc', new Date(), function(){}, a);
As you can see, there is no necessary to keep the parameters serializable.
The main idea is properly specifying the context, and mounting all the parameters onto that context.
I used ES6 syntax here just to make the code easier to understand. And of course, to make it work in IE8, you can translate it to ES5-compatible syntax and you might have ES5 shim included first to polyfill Function.prototype.bind
. OR, you just never need bind
at all:
EDIT: ES5 compatible version:
function invoke (funcName) {
var argStr = '';
for (var i=1; i<arguments.length; i++) {
argStr += ', this.args[' + i + ']';
}
return {
args: arguments,
func: new Function('return window.external[\'' + funcName + '\'](' + argStr.substring(2) + ')')
}.func();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With