views:

613

answers:

4

I'm seeing some strange behavior in IE trying to call functions in another page via function.apply().

Here's a simple test case:

test1.html:

<HTML>
<HEAD>
<script language="javascript" type="text/javascript">
  var opened = null;

  function applyNone() {
    opened.testFunc.apply(opened);
  }

  function applyArgs() {
    opened.testFunc.apply(opened, ["applied array"]);
  }

  function call() {
    opened.testFunc("called directly");
  }

  function remoteApply() {
    opened.testApply(["used remote apply"]);
  }

  function remoteApplyCopy() {
    opened.testApplyCopy(["used remote apply copy"]);
  }

  function openPopup() {
    opened = window.open("test2.html", "_blank");
  }
</script>
</HEAD>
<BODY>
  <a href="#" onclick="openPopup()">OPEN</a>
  <hr>
  <a href="#" onclick="applyNone()">applyNone</a>
  <a href="#" onclick="applyArgs()">applyArgs</a>
  <a href="#" onclick="call()">call</a>
  <a href="#" onclick="remoteApply()">remoteApply</a>
  <a href="#" onclick="remoteApplyCopy()">remoteApplyCopy</a>
</BODY>
</HTML>

test2.html:

<HTML>
<HEAD>
<script language="javascript" type="text/javascript">
  function testApply(args) {
    testFunc.apply(this, args);
  }

  function testApplyCopy(args) {
    var a = [];
    for(var i = 0; i < args.length; i++) {
      a.push(args[i]);
    }
    testFunc.apply(this, a);
  }

  function testFunc() {
    var s = "Got: ";
    for(var i = 0; i < arguments.length; i++) {
      s += arguments[i] + " ";
    }
    document.getElementById("output").innerHTML += s + "<BR>";
  }
</script>
</HEAD>
<BODY>
  Hi there
  <div id="output"/>
</BODY>
</HTML>

In firefox and chrome all methods work properly.

In IE (tested in 6, 7, and 8) all but the applyArgs() and remoteApply() methods work as expected.

applyArgs() gives a "JScript object expected" error when it tries calling apply (test1.html line 11).

remoteApply() gives the same "JScript object expected" error when it tries calling apply (test2.html line 5).

Problem is, I need to be able to use apply(). I can get around the issue by doing something like the remoteApplyCopy() mechanism, but I'm trying to avoid that. Why doesn't apply() just work?

A: 

I have no idea why this works, but I was playing around with your code and stumbled across one solution... put test2's functions inside of test1 and it works:

<HTML>
<HEAD>
<script language="javascript" type="text/javascript">
  var opened = null;

  function applyArgs() {
    testFunc.apply(opened, ["applied array"]);
  }

  function openPopup() {
    opened = window.open("test2.html", "_blank");
  }

  function testFunc() {
    var s = "Got: ";
    for(var i = 0; i < arguments.length; i++) {
      s += arguments[i] + " ";
    }
    this.document.getElementById("output").innerHTML += s + "<BR>";
  }
</script>
</HEAD>
<BODY>
  <a href="#" onclick="openPopup()">OPEN</a>
  <hr>
  <a href="#" onclick="applyArgs()">applyArgs</a>
</BODY>
</HTML>

I'll let you know if I can figure out any more (IE is weird like that). Like I said, I was just toying with the code.

Daniel Lew
Yea, it looks like the problem is that the arrays are somehow losing their array type info when passed between pages (thus why remoteApply fails but remoteApplyCopy works).
Herms
Well, the "arguments" variable in functions is not actually an array; it's an array-like object with a length attribute. I don't think that's the core problem, either; the IE error comes from test1.html, when it tries to call the function in the first place with an array.
Daniel Lew
The two tests that fail are passing an array across page boundaries and using that to call apply. Since everything else works, it seems like that's the core issue. It's like IE is trying to verify the 2nd argument to apply() and is seeing something it doesn't think is a valid object.
Herms
Well, if the array really is the problem, could you use call() instead of apply()? I realize that apply() is probably more useful, but call() works for me because it gets rid of the array. You could call() an object with parameters instead of passing an array parameters, too.
Daniel Lew
The problem is I need to do an apply. In the actual code that I'm using the arguments are coming from elsewhere as an array, and I need to be able to apply that array to a function.
Herms
You could pass the array as a single argument to your function then unwrap it there. It's messy but people have had to use ugly hacks for years due to IE.
Daniel Lew
I already have an ugly hack in remoteApplyCopy(), and it doesn't require me to rewrite the actual functions. It's just annoying cause I would prefer a way to do it without needing to include helper functions in the target document. I mainly just want to know *why* IE is failing here.
Herms
A: 

If you change test2.html testApply() function as follows:

function testApply() {
    testFunc.apply(this, arguments);
}

remoteApply() works. But, applyArgs() still failed.

antreality
that changes the behavior though. Your change will cause testFunc to get an array as its argument, not the string that's in the array.
Herms
+1  A: 

You need to have the arrays created in the other window, because each window has its own Array constructor. I think this will work.

Add this function to test2.html:

function getEmptyArray() {
    return new Array();
}

And this function to test1.html:

Array.prototype.cloneToRemote = function (win) {
    var newArray = win.getEmptyArray();
    for (var i = 0; i < this.length; i++)
    {
     newArray.push(this[i]);
    }
    return newArray;
}

Then do this:

function applyArgs() {
    opened.testFunc.apply(opened, ["applied array"].cloneToRemote(opened));
}

Note, it seems like you should be able to do

var newArray = new win.Array();

within the test1.html cloneToRemote() function, but I couldn't make that work. If you could do that, you could get rid of the new getEmptyArray() function in test2.html.

edsoverflow
Interesting. I hadn't considered that the windows would each have their own Array constructor. Makes sense after thinking about it, and it does explain the behavior a bit (though I think it should still work fine, as it does in Firefox and Chrome). Don't have time to test this out right now, but I'll keep it in mind.
Herms
A: 

"... applyArgs() gives a "JScript object expected" error when it tries calling apply (test1.html line 11). remoteApply() gives the same "JScript object expected" error when it tries calling apply (test2.html line 5). ..."

Which exact object is not "JScript object" as "expected" ?

(hint: use debugger)

--DBJ

DBJDBJ