5

I am building an app that performs a lot of client side data downloading and processing. The data processing is isolated from the main app by being processed in an iframe that resides on a sub domain. It is this iframe that downloads the data. Communication is via postMessage.

Everything works fine, except it could be better.

If the user opens extra tabs/windows, the app currently reloads all the data and may even do duplicate processing work, which isn't a problem other than that it slows everything down and pages take longer to load.

What I would like to do is have each top level tab/window communicate with just the one processing iframe, which could be reinstated if the original window is closed. The trouble is, these are not opened via javascript, but via the normal browser methods to open links in tabs so I can't get a reference to the iframe that is needed to send a message.

Is there anyway I can communicate the window reference for the iframe to the other tabs so that they can communicate with it via a postMessage? Could this in someway be achieved using shared workers?

I realize I could use shared workers for the whole processing task, but this would have it's own problems as the the data comes from third party domains, which can't be accessed from within a worker.

Only compatibility with the latest versions of all major browsers is needed.

Edit: I've just discovered that SharedWorker is not yet implemented in firefox, so I guess that is not going to work. Any other way I could achieve this?

Edit 2: I've discovered that you can use :

var win = window.open('', 'my_window_name'); 

to capture a reference to an iframe from any other window. However, if the iframe does not already exist then it will open it as a window. Even if it is closed immediately, it causes a flicker and causes the 'popup blocked' messages, making it unusable.

SystemicPlural
  • 5,629
  • 9
  • 49
  • 74
  • I suppose web workers is not an option as it will only work in IE 10 ;-) – Ja͢ck Jun 12 '12 at 12:34
  • use html5 localstorage, may be? – pahnin Jun 12 '12 at 12:36
  • @pahnin That's an idea. I'll do an experiment. – SystemicPlural Jun 12 '12 at 12:46
  • @pahnin I can't see a way to convert the window reference to a string and back again. This would also be a problem if SharedWorkers were used. – SystemicPlural Jun 12 '12 at 13:19
  • @SystemicPlural it's probably easier to simply store the results and/or progress of the long-running process in `localStorage` (I think that's what pahnin might have meant, too). Then you can just check its status periodically, and display the results when it's done. The window-to-string-reference-to-window stuff sounds like it'd be fragile and tricky. – Flambino Jun 12 '12 at 16:17

1 Answers1

3

In case any one else finds this, I've come up with a solution. It is somewhat hacky and requires further testing. But so far it is working. It works cross domain if that is needed.

It uses a combination of two tricks.

The first is to use

remote_window = window.open("", "remote_window_name");

to fetch a reference to the window. This works because if a window is already open with the given name then a reference is returned to it rather than opening a new window.

It does however have the problem that if the iframe does not exist then a new window will pop up. Local storage is used in order to prevent this. When a window/tab loads, it checks localStorage to see if there is another page already with a shared iframe. If not it inserts the the iframe and sets a flag in local storage to say that it is available.

As a last ditched resort, if the window still opens, a try block is used to close the newly opened window. The try block prevents cross domain errors. This means that the worst that will happen is the user sees a window pop up and disappear or they will see the 'enable pop-ups' message. I've yet to manage to trigger this in testing - it is only an edge case fall back.

try {
    if(store_window.location.href === "about:blank" ){
        remote_window.close();
        remote_window = insertIfame();
    }
} catch(err) {
}

An onunload event is added which removes the flag should the page be closed.

Also a setInterval is created that constantly refreshes a timeout flag. I have it running 4 times a second; when a second window/tab is loaded it checks that the iframe flag has not timed out before trying to communicate with it. This is a small overhead, but far less than the cost to me of having that second iframe loading. This serves the purpose of preventing stale data if the browser crashes or the the onunload does not fire for any reason. I include a small leeway when checking the timeout - currently 1 second - in case the main window is stuck in a loop. The leeway is only on the timeout, not the unload event which removes the flag entirely.

The flag needs to be checked every time a message is sent in case the original window with the iframe has closed. When this happens the iframe is reinserted in the first open window that requires it and the flag is reset.

Sending messages back is easy. Just use the event.source property of the receiveMessage -this points to the sending window.

One final edge case to account for is if the primary window closes whilst it's iframe is mid process for a secondary window. Theoretically this could be dealt with by using an onunload event in the iframe to send a message back to any windows with data in process. But I've yet to implement it and it may not finish before the page unloads. Another way of dealing with it would be by having a timeout in the secondary window which checks the flag and retries, I'll probably go this route as the messages already have timeouts attached.

SystemicPlural
  • 5,629
  • 9
  • 49
  • 74