|
home / bulletins / 7 / page 4
Cross-Frame Event Timing in Opera 7
While testing the new code additions described on the previous page,
we uncovered an unusual behavior specific to Opera 7 cross-frames implementations.
Specifically, while all top level menus displayed properly, child menus of those top-level
menus either displayed sporadically or refused to display at all. The problem seemed to
strike somewhat randomly, as a single menu could have both working and non-working child
menus attached to it.
Naturally, our first instinct was to check our newly added menu
creation and/or Keep In Window logic for discrepancies that may cause such a problem.
However, we soon found that even with the new logic "turned off" (i.e., leaving the
actual code in place but setting the menu parameters to create all menus on load
and allow the Keep In Window behavior to be applied to all top level menus) we
still saw the problem--even though the same test pages in previous HM versions did
not display the error. The existence of the additional logic itself seemed to be
enough to trigger the error; which often means there's some type of timing problem
involved. The additional logic "shifted" the processing within the code just
enough to trigger problems that we hadn't seen before.
After further diagnosis we were able to conclude that the problem
was due to the unique handling of events in Opera 7 cross frames scenarios. Unlike
the other major browsers, it appears as though Opera 7 will fire events that originate
in two separate frames simultaneously, rather then ensuring that the events are
processed sequentially. If you're skeptical of this point, have a look at the following
example page, which opens in a new window. (Note that this example page is crafted
specifically for Opera 7, other browsers will not get the same results and may produce
confusing and/or problematic displays):
Opera Events and Cross Frames
In this page, we present a simple two frame page, with all of the
JavaScript functions defined in the top frame. Four text input boxes in the top frame
serve as message windows, allowing us to see the processing of our target events.
Once loaded, rolling over a link in the top frame will fire a
function called mouseOver which is also defined in the top frame.
mouseOver does little more than execute a counting loop, displaying
its progress in the designated text input box of the top frame:
function mouseOver(desc,mdString,loopMax) {
var theMC=document.getElementById('mc');
theMC.value=desc;
var j=0;
var k=0;
for(var i=1;i<=loopMax;i++) {
var theMD=document.getElementById(mdString);
theMD.value=desc+": "+i;
j=i;
k=j-1;
}
return true;
}
Within the lower frame is an additional link that will execute the same
mouseOver function above when rolled over. This lower link tells mouseOver
to direct its counting display to the right hand input box. It also reduces the number of
iterations for the main loop, so the lower mouseOver will typically finish before
the top mouseOver. In Opera 7, the result is that both counters continue to
increment simultaneously--the same function is executed twice from two different
event triggers. Local variables for the function are properly separated/isolated between
the two calls; but any global variables accessed by the function (such as the theMC
value setting) are shared by both.
This simultaneous processing has a major effect on HierMenus processing,
since HM in several instances assumes that certain events will follow each other sequentially.
For example, We assume that the MenuOut and ItemOver (or MenuOver,
depending on the browser) handlers will be fired in succession as a user rolls over individual
items of a menu. When we set timers from within these events (such has hover timers for the
display of child menus in ItemOver, or hide timers in MenuOut), we can
assume that those timers won't fire until after the current event queue--specifically
the new MenuOver and ItemOver calls--have fired. As you can tell from the
above example, this assumption is invalid in Opera cross-frames scenarios, since the events
(the actual mouse activity, in this example) and the timers (for hover or hide times) are
triggered from two different windows and can thus occur simultaneously, or in a different
order than we expected. In the case of the child menus that wouldn't appear, we found that
the displayChild function, which is called from a timeOut in the navigation
frame, was, in some cases, executing between (or in tandem) with the ItemOver
and MenuOver calls generated by the menu item itself. The result is that no
menu is displayed--since the displayChild function first checks to make sure
that a menu is actually being rolled over at the time of the display, and the MenuOver
call that would tell it precisely that has not yet been processed.
A related problem occured in that it was possible to pop up the wrong
menu from a particular navigation page link. Again, this is due to the events of the
navigation page (the mouseover of the link that would spawn the menu) occurring
simultaneously with the events of the content page (in this case a queued MenuOver
call from rapidly rolling the mouse over multiple items in a displayed menu). The
MenuOver call resets the global HM_CurrentMenu parameter--the same
parameter used by the PopUp function--while the function is in the process of
displaying the selected menu.
Though not particularly pretty, our solution is to force all of the
relevant HierMenus events--including timeOuts--to occur from within the
content frame of the document. This will ensure that each of the events is processed
in the right order, the order that we expect based on the mouse activity within the
document.
To accomplish this, each of our timer events is re-routed to be set
(and if necessary, cleared) using the content frame window as a base. Additionally,
each of the event handlers that is called as the result of an event that is triggered
in the navigation frame is also re-routed to be called as a timer that is fired from
the content frame window. In this latter case, be careful to grab whatever you need
from the event object that is passed to the event in the first place, and pass those pieces
of information to the code that will be executed from the timer in the content page.
Our new HM_f_PopUp routine for Opera, for example, now looks like this
(line-wrapped for clarity):
// 5.2
function HM_f_PopUp(menuname,e){
if(!HM_AreLoaded) return true;
if(HM_IsReloading||!HM_f_DocumentCheck()) return true;
HM_MenusTarget.HM_NavWindow=window;
var eSrcId=e.srcElement.id;
if(!eSrcId) eSrcId=e.srcElement.id=menuname+"Clicker";
var eType=e.type;
var eClientX=e.clientX;
var eClientY=e.clientY;
var TimeoutCommand = "if (window.HM_NavWindow) "+
"HM_NavWindow.HM_f_PopUpReal('"+
menuname+"','"+
eType+"',"+
eClientX+","+
eClientY+",'"+
eSrcId+"')";
HM_MenusTarget.setTimeout(TimeoutCommand,1);
return true;
}
After setting a variable in the content frame that will point back to
our current window (HM_NavWindow), we then build a timeOut call that
will pass the necessary event parameters to the actual code that will display the
selected menu. And since that function call originates from the event queue
of the content page and not the navigation page, it honors the event processing
order that exists within the content page event structure itself.
Sometimes it pays to try unique experiments with your code. While
working through potential work arounds for the issue described above, we discovered an
interesting Opera trick that may help us solve the Opera 7 unsynchronized menu problem
for good...
     
[previous] [next]
|