Can't pass a function from one window to another in IE
Asked Answered
E

4

9

I have a two windows, one is opened from another, so, I have an opener property in "child" window.

Parent window has some function in global scope, which must be called with a function as a first argument (it will be used as a callback).

Both pages are opened from same domain, so, I haven't any Same Origin Policy restrictions (I hope so)...

In a child window I have code like this

if(window.opener) {
    window.opener.myFunction(function() { ... });
}

Everything works fine, until I try to run it in IE. In this browser an argument, received by myFunction, is ALWAYS of type Object (checked with typeof). Code of myFunction is something like this:

window.myFunction = function(cb) {
    alert('Callback type is ' + (typeof cb));
    if(typeof cb == 'function') 
        cb();
    else
        alert('Not a function!');
}

Live demo: http://elifantiev.ru/ie-opener-issue/first.html

The questions is:

  1. is this a standarts compliant behaviour?
  2. is there some workaround for this issue?
Enzyme answered 8/5, 2012 at 13:30 Comment(1)
Please specify the version(s) of IE in which you encounter this problem.Newfashioned
K
6

Even though typeof returns "object" the function still works as expected. Calling cb() will execute the function.

An alternative to using typeof to determine if the parameter is a function is testing for the call property which all functions are expected to have:

if (cb && cb.call) { cb(); }

If you are passing along to something that expects a function it can be wrapped like this:

function newCb() {
    return cb.apply(object, arguments);
}

Also note when passing a function from parent to the child the typeof is also object. Comparing original function to function-as-an-object (after the round trip) returns true. This is important if for some reason you need a reference to the original function (for example when unsubscribing).

Ketonuria answered 9/5, 2012 at 21:42 Comment(3)
+1 for if (cb && cb.call)... best to stay away from typeofSlowly
Wrapping is nice, but can't be used, cause' I need to add wrapping to any function of framework, which can receive a function as an argument...Enzyme
I don't like them but you can prototype Function so you don't have to worry about rewriting your entire framework, just your client-side calls. I'll update my answer in a minute.Sinewy
S
2

It seems this is by design.

Here's an article about it

https://bugzilla.mozilla.org/show_bug.cgi?id=475038

Because each window can have different prototype chains, functions cannot be passed as expected. Try doing something like this to confirm:

   var popup = window.open('second.html');

   window.myFunction = function(cb) {
            alert(cb instanceof Function);       //false
            alert(cb instanceof popup.Function); //true
   }

So here's how I would do proper function validation if there were numerous functions to worry about on the parent side (I hate prototype but this time I think you're stuck):

Parent Window:

<html>
<script type="text/javascript">
    /* give all our functions the validation check at once */
    Function.prototype.remote = function(cb) {
        if(cb instanceof popup.Function) {
            this(cb);
        } else {
            alert('Not a function!');
        }
    };

    var popup,
    myFunction = function(cb) {
        cb();
    },
    test = function(cb) {
        cb();
    }

</script>   
<body>
    <a href="#" onclick="popup = window.open('second.html'); return false;">Open window</a>
</body>
</html>

Child Window:

<html>
<body>
    <script type="text/javascript">
        var Parent = window.opener;
        if(Parent) {
            Parent.myFunction.remote(function(){
                alert('callback!');
            });

            Parent.test.remote(function() {
               alert('callback again');
            });
        }
    </script>
    This is a second page!
</body>
</html>

And here's a working example: http://dl.dropbox.com/u/169857/first.html

Sinewy answered 15/5, 2012 at 4:3 Comment(0)
C
2

Even if you can get this to work for now, I wouldn't count on the ability for two browser windows to directly invoke functions on each other to be around much longer. There are just too many security concerns, even if you remove the cross-domain problems from the scenario.

The only future-proof way to do this, and ensure that it works across all browsers, is to use cross-document messaging APIs that are supported in all modern browsers, including IE8 and above.

The most detailed example I could find when I needed to solve this problem was this window.postMessage article on MDN.

What it boils down to is a call on one side to post the message, e.g.:

otherWindow.postMessage(message, targetOrigin);

And then an event handler on the other side:

window.addEventListener("message", receiveMessage, false);

function receiveMessage(event)
{
  alert("I got it!\n" + event.data);
}

IE6 and IE7 might allow you to make those cross-window calls for now if the windows are from the same domain, but IE8 and above will likely expect you to use this API, and I'm guessing other browsers will eventually revert fully back to this much safer communication mechanism.

Given the situation, I would strongly recommend using a shared library to share your code, (using something like LAB.js or require.js or even just Jquery's getScript() function), and then use the cross-document messaging system to send the events, instead of the callback function you're trying to use today.

UPDATE


There are polyfills available to add postMessage support for older browsers. Since you're not doing cross-domain calls, I'd start with Ben Alman's Jquery postMessage plugin. I've not used it, but I've used Ben's excellent Jquery BBQ plugins. It should fall back to the built-in methods if supported, and only shim in alternative methods in older browsers. It claims to work in IE7...

If Ben's plugin doesn't do the trick, then you could try easyXDM, but it looks a bit more convoluted to get set up.

Coequal answered 17/5, 2012 at 18:23 Comment(3)
I think this is a best, standart compliant workaraound. But this is a problem for me what it is not supported in IE7. Anyway, I think you are the winner.Enzyme
I didn't think to look before (I was up against the time deadline), but there are some polyfills to provide this method in IE7. I updated the answer to include them.Coequal
Nice lib, but it uses location.hash to pass messages... In my environment hash is highly used for internal needs (such as navigation, etc...). But it can be modified to fill my needs and not to broke anything what is working already. Thx.Enzyme
R
-1

After opening the child window, you can register a function created by the parent into its window context. You can see the following code running in jsFiddle:

<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>

// will be called by the child window
function MyExportedCallback(x) {
    $("#my-callback-result").text("this callback was run with '" + x + "'.");
}

// will open a child window and create content for testing
function MyLinkHandler() {
    var wnd = window.open("about:blank");

    // IMPORTANT: this is where the parent defined function is "registered"
    // into child window scope
    wnd["myExternal"] = MyExportedCallback;

    // auxiliary code to generate some HTML to test the external function
    var put = function(x) { wnd.document.write(x); };
    put("<html><body>");
    put("<a href='#' onclick='window.myExternal(123);'>");
    put("click me to run external function");
    put("</a>");
    put("</body></html>");
}

// attach events
$.ready(function() {
    $("#my-link").click(MyLinkHandler);
});

</script>
<body>
<p><a id="my-link" href="#">Open window</a></p>
<p id="my-callback-result">waitting callback...</p>
</body>
</html>
Rousing answered 17/5, 2012 at 15:30 Comment(4)
The main problem is how to pass a function, created in child window, as an argument to parent's window function.Enzyme
@Olegas, your original post says: "Parent window has some function in global scope, which must be called with a function as a first argument (it will be used as a callback)." So I expected the problem to be calling a function defined in parent window from child window. Passing a function in Javascript, for practical matters is just like passing any other argument, and that's why I didn't explored it. But nevermind, it as seems you already solved your problem you don't any further help.Rousing
To call - is not a problem. To pass a function - is the problem. In IE it is changing it's typeof to Object when passed through the window boundary.Enzyme
For IE6 ​typeof(new Function("x", "return x*2;")) == 'object', but, although it's wierd, it behaves just like a regular function.Rousing

© 2022 - 2024 — McMap. All rights reserved.