|
home / bulletins / 7 / page 5
Re-Synchronizing Menus in Opera 7
In previous release bulletins, we've documented issues relating to
Opera 7's ability to reload a previously loaded page from history without calling
the load of the retrieved document or the unload of the current
document. We initially noted this behavior in our
release bulletin for HM 5.0,
and later revisited it by discussing its impact
on HM 5.1's ability to reload the navigation page of a frameset. Feel free to refresh your memory
by referring to those bulletins, but in summary, menus can become "out-of-sync" in Opera 7
cross-frames scenarios; the handler code that is being used by menus that are displayed in the
content frame can, in some contexts, point to code that no longer exists within the navigation
frame.
While experimenting with potential workarounds for the event-timing issue
described on the previous page, we discovered this tidbit of information
that appears to allow us to conclusively know when the menus have become out-of-sync.
With this information in hand, we can do something with those menus--other than allow
them to spawn errors or attempt to continue accessing code that was loaded from a different,
no longer visible page.
Specifically, in Opera, referring to a variable via the self or
window objects always retrieves the value of that variable from within
the currently displayed window within the frame; as opposed to the instance of the
variable as it existed in the previously loaded page, where the handler was originally assigned
(this, of course, is assuming that the original handler function was defined within the same
window for which you wish to interrogate the current variable setting). Accessing a
function directly by name, however, (and not using an explicit self or
window reference) refers not to the function in the currently displayed page,
but the function as it was defined in the original page that defined the handler code
in the first place. This is a confusing point, so let's look at an example to see if
we can sort it out. Again, this example will open in a new window and is designed to
be meaningful only in Opera 7.
Opera 7 and self
The initial top frame of the example page contains JavaScript
code as follows:
function theHandler() {
alert("localFunction==window.localFunction: "+
(localFunction==window.localFunction)+
"\ntypeof(localFunction): "+
(typeof(localFunction))+
"\ntypeof(window.localFunction): "+
(typeof(window.localFunction)));
alert("localFunction==self.localFunction: "+
(localFunction==self.localFunction)+
"\ntypeof(localFunction): "+
(typeof(localFunction))+
"\ntypeof(self.localFunction): "+
(typeof(self.localFunction)));
alert("localFunction() = "+
localFunction());
alert("self.localFunction() = "+
self.localFunction());
alert("window.localFunction() = "+
window.localFunction());
}
function localFunction() {
return "Hello from Nav Page 1";
}
And in the lower page is code that sets the mouseover of a
link defined within that page to the function theHandler from the top navigation
page. When you intially roll over the link, all the responses are as you expect them
to be: localFunction is declared to be equal to both window.localFunction
and self.localFunction, and calling the function with or without the window
or self prefix results in the string "Hello from Nav Page 1".
Rolling over the link after clicking through to the second nav
page, however, produces differing results in each of the major browsers. The code on the
second navigation page looks like this:
function theHandler() {
alert("theHandler in Nav 2");
}
function localFunction() {
return "Hello from Nav Page 2";
}
None of the browsers attempt to execute the new theHandler code
from the second navigation page (i.e., none of the browsers displays the "theHandler in Nav 2"
message), and this is to be expected. As we explained in
Bulletin 5, since
the assignment of the handler is based on a simple address, the browsers all
know that the address of the new theHandler code differs from that of the originally
assigned handler from the first navigation page.
What happens next, however, depends on the browser. IE doesn't do
anything; presumably the handler was removed when the page that defined it was removed
as part of the navigation page swap. Gecko browsers execute the handler code as it existed
in the initial page, using the currently displayed window to resolve function and variable
references. In other words, in Gecko browsers the second rollover states that all of the
localFunction references, whether prefixed with self/window or
not, resolve to the localFunction as defined in the second navigation page, and
not the first. Thus, when rolling over the main content page link after loading nav page
2, the "Hello from Nav Page 2" message is displayed and not the "Hello from Nav Page 1"
message.
Opera 7 produces the most unique result of all. In Opera 7, the
localFunction comparison in the second nav page is false; whether
it is compared to window.localFunction or self.localFunction. And
calling localFunction by itself results in the original "Hello from Nav Page 1"
message; while calling the function through the self or window
prefixes results in the "Hello from Nav Page 2" message in the second nav page.
Beginning with HM 5.2, we intend to use this unique behavior
to our advantage. Since we have a way of knowing when a called menu is out-of-sync, we
can check for this fact and then destroy or hide the menus in the content page when
they are no longer pointing to the code being used in the main navigation frame. To
accomplish this, we first set a marker in the content page each time it is
initialized:
function HM_f_InitIt(reloadmain){
...
HM_MenusTarget.HM_SyncMarker = HM_f_Return;
HM_MenusTarget.HM_Initialized=true;
return true;
}
And then we strategically check that marker each time any of our
handlers fire to ensure that it is still what we think it should be:
function HM_f_CheckMenuSync() {
try{var CheckWindow=self;}
catch(e){return false;}
if(HM_MenusTarget.HM_SyncMarker!=self.HM_f_Return) {
// "Un" initialize the content page here
}
else return true;
}
We'll discuss the first try..catch block on the next page,
but for now note the key line:
if(HM_MenusTarget.HM_SyncMarker!=self.HM_f_Return) {
As described above, this expression will be true only when the
HM_SyncMarker dropped as part of the initialization routine is no longer
identical to the HM_f_Return function that it was set to originally. If
the menus are determined to be out-of-sync, then four things happen:
All visible menus on the page are hidden. To know which menus
have been created within a page, the HM_a_TopMenus array has been moved into
the content page for Opera. In other words, each content page will have its own
array of top level menu trees that have been successfully created within that
page, and HM can use it to know which menus it can (or should) work with within
the page.
The topmost menu in each tree is destroyed, effectively making
direct access to any menu tree impossible.
The generic handlers of the content page (unload, resize,
etc.) are reset to null or the previously existing handlers, depending on
what the HM_f_CheckMenuSync function can sucessfully locate.
The HM_a_TopMenus array, HM_Initialized sentinel,
and the HM_NavWindow variables of the content frame are cleared.
The net result is that the menu system of the content
page is uninitialized, and the next time a user rolls over a link in the navigation page
it will be reinitialized in the same manner that it would if the user had visited a page
outside of the site's domain and then returned.
This menu synchronization check appears to address all of the remaining
stability problems we've encountered as a result of leaving the menus in place between
page loads in Opera, but it does admittedly create a couple of minor annoyances. First,
any permanently visible menus that existed in the page will disappear when they are first
rolled over in an unsynchronized state, and then not reappear until the user rolls over
a link in the navigation page. Similarly, any popped up menus which were not hidden when
the navigation page originally changed will be immediately hidden when rolled over. Keep
in mind, however, that both of these situations will only occur when retrieving a
historical page that was created with a different version of the navigation page that is
currently being displayed, a fairly rare situation. In addition, the disappearing menus
are much less annoying than the JavaScript errors that you would otherwise encounter.
Clearly, relying on a browser-specific feature such as this is
not a desirable coding practice, and we don't make the change lightly. We'll
keep an eye out for a more elegant solution, as well as watch future Opera
versions to confirm the behavior is still present.
We close by discussing the
remaining notable changes in HM 5.2, including
one more minor Opera gotcha, a stability improvement for Safari, and variable-width menu
corrections that affect nearly all browser platforms.
     
[previous] [next]
|