Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The Javascript "apply" function doesn't exist in window.external extension object

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

like image 853
Bagelzone Ha'bonè Avatar asked Jun 28 '11 23:06

Bagelzone Ha'bonè


2 Answers

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]);
    ...
  }
}
like image 162
Mike Samuel Avatar answered Nov 20 '22 16:11

Mike Samuel


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();
}
like image 1
shuangwhywhy Avatar answered Nov 20 '22 14:11

shuangwhywhy