Question

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?
Was it helpful?

Solution

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).

OTHER TIPS

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.

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

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>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top