Section B. Cross-window communication


B. Cross-window communication

How do you establish cross-window communication? We've glanced at the opener property of popups and the availability of functions and variables in other windows, but in general, communication between two windows is possible only if two conditions are met:

  • Both windows contain pages that come from the same domain.

  • One window contains a reference to the other.

The first condition is a result of JavaScript's same-source policy, as discussed in 1B. You are not allowed to spy on windows that contain pages from another domain, since that would invade the user's privacy.

The second condition is more complicated: essentially, one of the windows must already know of the other's existence. If neither of them is aware of the other, communication is impossible.

If a user opens two browser windows and loads your site in both of them, JavaScript's same-source policy is satisfied, but the windows don't know of each other's existence, and it is impossible to establish communication between them.

In practice, cross-window communication is possible only if one window opens the other, or, in other words, if you create a popup window.

Opening popup windows

You open a popup window with the window.open() method. It works as follows:

window.open('page.html','popup','width=300,height=400'); 


Window.Open() and Document.Open()

Although we've seen that you don't have to use window. when calling methods or properties of the window, window.open() is always written in full. That serves to distinguish this method from the document.open() method we'll discuss in 6F. Since an incorrect use of document.open() may wipe the entire page, this distinction is important.


As you see, this method takes three arguments:

  • The first argument tells the browser which page to load in the new window. If you don't specify which page to load in the popup, the browser opens a blank window.

  • The second argument sets the name property of the new window. We already discussed that property in 6A.

  • The third argument contains instructions on the new window's features: its width and height, does it contain a location bar, etc.

In old browsers, the developer could control exactly how the popup would look and behave. The user could not resize it, and if the Web developer did not allow scrollbars, users would never see them, even if they were necessary. Basically the only thing the user could do was close the popup.

Popup Features

See http://www.quirksmode.org/js/popups.html for a complete overview of all possible popup features and their browser compatibility.


Modern browsers have given the user progressively more control over the popup. At this point, most browsers allow the user to resize popups, even if resizable is set to 'no', and they always show scrollbars when they're necessary, even if scrollbars is set to 'no'.

Therefore, it's best to assume that the user can and will treat your popup as if it's a real window (which, in the end, it is).

References to other windows

The previous code example opened a popup window, but it didn't initiate cross-window communication. In order to do that, we must store the return value of the window.open() method, which is a reference to the newly created window.

Site Survey opens the popup as follows:

[Site Survey/survey.js, lines 30 and 62]

var ST_newWindow; // more stuff ST_newWindow = window.open('survey/popup.html','ST_window',[arguments]); 


It opens a new window as described above, and it assigns the reference to the new window to variable ST_newWindow. Now this variable refers to the other window, and cross-window communication has been established.

opener

Remember, the popup window automatically creates an opener property that refers back to the main window, and if a new page is loaded into the popup (for instance, because the user clicks on a link), the opener property remains available. It's one of the very few properties that is not removed when a window object is wiped clean.

Therefore, even if the main page doesn't contain a variable like ST_newWindow, cross-window communication is still possible: You just use the popup's opener property.

Re-establishing contact

Unfortunately, the ST_newWindow variable in the main window is destroyed when the user loads a new page, which means that the new page in the main window cannot contact the popup.

That's a problem to Site Survey, since the ST_exit() function wants to call a function in the popup. When the main page unloads, it does this:

[Site Survey/survey.js, lines 67-73]

function ST_exit() {    if (readCookie('ST_popup') == 'opened' && ST_newWindow)           ST_newWindow.trackMain(location.href);    else           createCookie('ST_temp_store',location.href,1); } 


The trackMain() function stores the URLs of the pages the user views in the main window, but in order to call it, the main window needs access to the popup: ST_newWindow.trackMain().

Fortunately, the popup contains its opener property, which refers to the main window; thus cross-window communication is still possible. Thus the popup can restore the ST_newWindow variable in the main window:

[Site Survey/popup.js, line 30]

// in popup opener.ST_newWindow = window; 


The script goes to the main window (opener) and assigns to its ST_newWindow property (opener.ST_newWindow = ) a reference to itself, the popup window (window). Now the main page is once again able to access the popup.

This re-establishment of contact with the main window is a job of trackMain(), and it's useful to take a detailed look at this function.

Before we start, let's pay close attention to the context. trackMain() is called at the moment the main page unloads, and it receives the URL of that page as an argument. Directly afterwards, the unload event takes place, which means either that the main window starts to receive a new page (which may or may not come from another domain), or that the user has closed the main window.

If the new page comes from another domain, JavaScript's same-source policy prevents the popup from re-establishing contact. We don't want error messages to appear, so we have to proceed with caution.

[Site Survey/popup.js, lines 22-24]

// in popup function trackMain(url) {    if (url)           addPage(url); 


First the easy bit. If the function receives a URL, it is stored through the function addPage().

Now the hard work starts: finding out what has happened to the main window. Did the user leave the site? If so, I have to send the user on to the survey. If the user hasn't left the site, though, I should do nothing.

We're certain of one thing: the page that called trackMain() isn't there any more. But has the user requested a new page from the same domain, from a different domain, or has he closed the window altogether?

[Site Survey/popup.js, lines 26-27]

if (!opener || opener.closed || !checkCommunication())        startSurvey(); 


If the main window is closed or contains another site, the script immediately starts up the survey. To find out whether this is the case, I check three things:

  1. If the opener property returns null (if (!opener)), even the window object of the main window doesn't exist any more. This is a rare case, but better safe than sorry, so I check for it anyway.

  2. If opener's closed property is true, the main window has also been closed.

  3. If the main window has not been closed, but communication is impossible (!checkCommunication()), the user has loaded another site.

[Site Survey/popup.js, lines 29-32]

else if (checkCommunication() && opener.ST_loaded) {        opener.ST_newWindow = window;        tryCounter = 0; } 


Then the script checks for the opposite. It uses ST_loaded in the main window to indicate that it has been loaded completely and that all scripts are ready for action. If it is possible to communicate with the main window, and if ST_loaded is true, the script re-establishes contact by means of the opener.ST_newWindow = window; line, and it sets tryCounter back to zero.

Note that the script checks the possibility of communication first. If checkCommunication() returns false, JavaScript doesn't even try to execute the second expression, as we saw in 5G. Therefore, the script does not try to access opener.ST_loaded, and that's a good thing, because that action would give an error if the main page came from another domain.

[Site Survey/popup.js, lines 34-37]

else if (tryCounter < nrOfAttempts) {        setTimeout('trackMain()',waitingTime);        tryCounter++; } 


If neither is the case (for instance, because the main window is busy loading a new page), I set a timeout to try again in a few milliseconds. I have set an upper limit to the number of attempts, since I don't want the script to spend ages trying to determine the status of the main window.

Note that this new call to trackMain() does not include an argument. The URL of the old page has already been stored, after all, and I want to avoid duplicates. When trackMain runs again, it ignores the addPage(url).

[Site Survey/popup.js, lines 39-41]

else        startSurvey(); } 


If the status is unclear, or if the script already exceeded the maximum number of tries, it also starts the survey.

This is one way of re-establishing contact with the main window, and as you see, it can be pretty hard to find out the status of the main window.

Properties of closed windows

Warning

Browser incompatibilities ahead


As we saw, when the user closes a window, its window object remains available. In order to indicate that the window is closed, its closed property is set to true. I use this fact in trackMain().

Unfortunately, at the time of writing, the closed property is unreliable in Opera. For that reason, among others, Site Survey doesn't work properly in Opera.

Obviously, a closed window loses all variables and functions that were defined in it. It's wiped clean, after all. But what about the default properties of the window object, such as location and navigator? Do they remain available, or are they destroyed, too?

Unfortunately, the browsers disagree on their exact status. When you try to read, for instance, the navigator.userAgent property of a closed window, some browsers give the browser string, while others give an error. Therefore, it's advisable to assume that a closed window has no properties except for closed.

Attempting to communicate

If a window still exists but contains a page from a different domain, you are not allowed to access any properties of its window object. As we saw in 1B, this is a security feature: without it, malicious site owners could gather data on the surfing habits of their visitors.

Site Survey needs to know whether the main window still contains a page from the original site, or whether the user has surfed on to another site. In the first case, it re-establishes cross-window communication; in the second, it starts the survey.

How does it find out? We could try to read the location.href of the other window, but if it contains a page from another server, this property, too, is inaccessible. In fact, when a page comes from another server, we cannot glean any information whatsoever from that other window.

The solution to this conundrum is to try to set a variable in the other window. If an error occurs, the window contains a page from another domain. This is what the checkCommunication function does. Since "Can I communicate with the other window?" is a simple Yes/No question, the function returns a boolean value true or false.

[Site Survey/popup.js, lines 77-86]

function checkCommunication() {    if (!opener) return false;    try {           opener.testVar = true;    }    catch (e) {           return false;    }    return true; } 


First the function checks if the opener exists at all. If not, communication is obviously impossible, and the function returns false.

Then the function enters a try/catch statement, as discussed in 5H. It tries to set a variable in the other window (opener.testVar = true). If this causes an error, the catch statement catches it and returns false: communication is not possible.

If the function survives without error messages, it returns true: communication is possible.

Closing windows

Every window object has a close() method: calling it closes the window. Nonetheless, most browsers have a built-in safety catch that offers the user some control. Although every window can be closed by this method, in the case of windows that were opened by the user, some browsers refuse to execute window.close(), while others open a dialog box that asks the user if it's OK to close the window. If the user denies permission, the window stays open.

Figure 6.1. If you try to close a window that's opened by the user, Explorer shows this dialog box.


It is not possible to circumvent this security measure.

Windows that were opened by JavaScript can be closed without encountering this dialog box.

Popup blockers

Popup windows have a bad reputation, and not undeservedly. When purveyors of frontal nudity and similar undesirable types discovered that it was possible to confront any user with their products, popups became very popular overnight. Additionally, designers who were unable to create liquid layouts were determined to force users to view their sites at a certain width and height, and opening a popup was the simplest way of ensuring that.

In fact, the situation deteriorated to a point that the browser vendors decided to intervene by adding popup blockers to their browsers. Unfortunately, now that they have been around for a while, it has become clear that these popup blockers are insufficient. Sure, they block most of the old popups written by people who hardly know JavaScript, but they are in fact easy to circumvent, and Site Survey does so.

To understand how popup circumvention works, we must first understand how popup blockers work. Browser vendors did not simply disable the window.open() method, since popups can have legitimate uses. Instead, they blocked only unrequested popups.

Figure 6.2. Like most other browsers, Mozilla allows you to block unrequested popups.


But how do you define an unrequested popup? The definition the browser vendors seem to have chosen is that any popup that is opened after a user click is a requested popup, and any other is an unrequested one.

So this simple script circumvents popup blockers:

var popup; document.onclick = function () {    popup = window.open('url','name','arguments'); } 


Whenever the user clicks anywhere in the document, the popup pops up, because the browser interprets the click as a user command to open the popup. Site Survey uses this principle.

It's only a matter of time before the dregs of the Internet catch up with this technique, at which point all current popup blockers will become worthless. And yes, the fact that this book mentions the technique will probably help speed up that process. I thought about leaving it out, but in the end decided not to. After all, depending on the circumstances, popups can be a valid part of JavaScript.

Besides, the fundamental problem is not one or another circumvention method, but the definition of unrequested popups, and this problem will not go away if we just refuse to look at it. Although the present definition is clearly insufficient, I can't think of a better one, and even if browser vendors create a new definition, this will only instigate a new round of the arms race, in which the browser vendors will operate at a disadvantage, since they have to leave a loophole for proper, valid popups to be opened.

Therefore I predict a popup-blocker arms race, the outcome of which will probably favor the malicious popup farms, unless browser vendors take the drastic step of disabling window.open() altogether.



ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ISBN: N/A
EAN: N/A
Year: 2005
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net