Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Memory leak with Javascript + DOM menu in IE

21 views
Skip to first unread message

Pyrox

unread,
Jun 17, 2003, 4:45:03 PM6/17/03
to
Hello all you js gods! As you can tell from the subject i'm having a
memory leak with IE when i try and use my DOM menu system. (i got it
from here .... http://www.mojavelinux.com/forum/viewtopic.php?t=141 )

I've been reading alot about this problem (well what i can find, most
of it right in this group). Could someone maybe help me figure out why
the menu keeps eating up memory? The memory doesn't get cleared until
the browser is closed, or minimized (heard something about IE taking
pictures of the page to use for smooth scrolling?).

If i get this correctly... When javascript calls a reference to a DOM
object in IE javascript can't "unload" that object when
garbagecllector goes. (right?) So is there a work around to fix this?
Any help is appreciated! If you need more info, let me know! THANKS
AGAIN!!!
Paul

Douglas Crockford

unread,
Jun 17, 2003, 5:32:19 PM6/17/03
to
> I've been reading alot about this problem (well what i can find, most
> of it right in this group). Could someone maybe help me figure out why
> the menu keeps eating up memory? The memory doesn't get cleared until
> the browser is closed, or minimized (heard something about IE taking
> pictures of the page to use for smooth scrolling?).
>
> If i get this correctly... When javascript calls a reference to a DOM
> object in IE javascript can't "unload" that object when
> garbagecllector goes. (right?) So is there a work around to fix this?
> Any help is appreciated! If you need more info, let me know! THANKS

What evidence do you have of a memory leak? Slow execution? Out of memory
alerts? System failures?

Paul Lemke

unread,
Jun 17, 2003, 6:18:07 PM6/17/03
to
Yea, that would be important now wouldn't it...
Well after working with it a few days, i noticed that the browser would
start to slow down if i kept working on that page. So... i checked the
mem usuage in taskmanager, and lo and behold, IE had about 45mb used!!!
So i shut down IE, relaunched it, then we back to the site. I kept
hitting the refresh button (about 20 times and i hit 25mb of mem usuage)
As i kept using it, the mem usuage would go up about 700k each time i
refreshed the page. Yea, i dont' want that for my users. What do you
think?
Pyrox

*** Sent via Developersdex http://www.developersdex.com ***
Don't just participate in USENET...get rewarded for it!

Douglas Crockford

unread,
Jun 17, 2003, 9:05:04 PM6/17/03
to
> Yea, that would be important now wouldn't it...
> Well after working with it a few days, i noticed that the browser would
> start to slow down if i kept working on that page. So... i checked the
> mem usuage in taskmanager, and lo and behold, IE had about 45mb used!!!
> So i shut down IE, relaunched it, then we back to the site. I kept
> hitting the refresh button (about 20 times and i hit 25mb of mem usuage)
> As i kept using it, the mem usuage would go up about 700k each time i
> refreshed the page. Yea, i dont' want that for my users. What do you
> think?

If you were using a competent operating system, you could have more confidence
in those resource utilization statistics and in system memory management in
general. You can't fix major OS design errors in applications, and especially
not in untrusted scripts on webpages. If your users think that Windows and IE
are the best thing for them, then there is not a lot more you can do to help
them. It is a Microsoft World after all.

http://www.crockford.com

Dom Leonard

unread,
Jun 17, 2003, 9:10:37 PM6/17/03
to
Paul Lemke wrote:
> Yea, that would be important now wouldn't it...
> Well after working with it a few days, i noticed that the browser would
> start to slow down if i kept working on that page. So... i checked the
> mem usuage in taskmanager, and lo and behold, IE had about 45mb used!!!
> So i shut down IE, relaunched it, then we back to the site. I kept
> hitting the refresh button (about 20 times and i hit 25mb of mem usuage)
> As i kept using it, the mem usuage would go up about 700k each time i
> refreshed the page. Yea, i dont' want that for my users. What do you
> think?

Definitely reproducable (I get approx 700 k added for each reload of
DHTML page), and have been noticing IE slowing down more and more for
each reload during testing. Didn't know extra mem got discarded when
minimising (thanks for that). I would treat it as a memory leak, but...
for pages which do not reload themselves, I think it unlikely that users
will sit there and reload the same page - I am not taking any action
about it ("IE Sucks" seems to be enough).

Cheers,
Dom

Richard Cornford

unread,
Jun 17, 2003, 9:53:37 PM6/17/03
to
"Paul Lemke" <lem...@gmx.net> wrote in message
news:3eef939e$0$200$7586...@news.frii.net...

> Yea, that would be important now wouldn't it...
<snip>

Given the number of times that people post a supposed cause of a problem
here that turns out to be something else entirely it is always worth
stating the reasons you have for your beliefs. Alternatively posting a
minimal demonstration/test HTML+JavaScript that exhibits the problem
removes the need for both questions and justification.

Still, the symptoms that you describe correspond exactly with symptoms
described by Steve van Dongen when he bought the problem of circular
JavaScript - ActiveX/DOM element references in IE to my attention.
Although Steve did think that the problem was more significant with
ActiveX objects than with DOM elements. Your post implies that your
problem is specifically with DOM elements so it would have been nice to
see an example of code that caused the problem.

Since Steve raised the issue I have been looking into providing a
general 'finalize' mechanism for my DHTML objects. A mechanism that will
call any number of registered functions onunload so that those functions
can null any DOM element/ActiveX references that are being held by their
associated objects and any references to those objects that have been
assigned to DOM elements/ActiveX objects (event handlers and expando
properties).

This is the code that I am currently experimenting with:-

var global = this;
var finalizeMe = function(){
var base;
var safe = false;
var svType = (global.addEventListener && 2)||
(global.attachEvent && 1)|| 0;
function addFnc(next, f){
function t(ev){
if(next)next(ev);
f(ev);
};
t.addItem = function(d){
if(f != d.getFunc()){ //don't add duplicates!
if(next){
next.addItem(d);
}else{
next = d;
}
}
return this;
};
t.remove = function(d){
if(f == d){
f = null;
return next;
}else if(next){
next = next.remove(d);
}
return this;
};
t.getFunc = function(){return f;};
t.finalize = function(){
if(next)next = next.finalize();
return (f = null);
};
return t;
};
function addFunction(f){
if(base){
base = base.addItem(addFnc(null, f));
}else{
base = addFnc(null, f);
}
};
function ulQue(f){
addFunction(f);
if(!safe){
switch(svType){
case 1:
global.attachEvent("onunload", base);
safe = true;
break;
case 2:
global.addEventListener("unload", base, false);
safe = true;
break;
default:
if(global.onunload != base){
if(global.onunload)addFunction(global.onunload);
global.onunload = base;
}
break;
}
}
};
ulQue.remove = function(f){
if(base)base.remove(f);
};
function finalize(){
if(base){
base.finalize();
switch(svType){
case 1:
global.detachEvent("onunload", base);
break;
case 2:
global.removeEventListener("unload", base, false);
break;
default:
global.onunload = null;
break;
}
base = null;
}
safe = false;
};
ulQue(finalize);
return ulQue;
}();

- so far I have not encountered any problems with it, except on
Konqueror 2 which does not understand inner function definitions (and so
does not comply with any ECMA script specification), Konqueror 3 is OK
with the script.

This finalize mechanism is used by calling the function - finalizeMe -
and passing it a function reference as its only argument. E.g. -
finalizeMe(funcRef); -. This has the drawback that the - funcRef -
function will be called in the global context and not as a method of any
JavaScript object instance. I get round that by passing it references to
private instance methods (as described by Douglas Crockford at:-

<URL: http://www.crockford.com/javascript/private.html >

-) or private class methods (as described by me in various posts to this
group) of classes that track all of their object instances. That allows
the functions called by the - finalizeMe - construct to be associated
with particular object instances so that they can have their DOM
element/ActiveX references nulled.

Of course there may be ways of achieving what your code does without
forming any circular references in the first place, but that is just
speculation without seeing the code.

Richard.


Jim Ley

unread,
Jun 18, 2003, 5:13:43 AM6/18/03
to
On Wed, 18 Jun 2003 02:53:37 +0100, "Richard Cornford"
<Ric...@litotes.demon.co.uk> wrote:

>Although Steve did think that the problem was more significant with
>ActiveX objects than with DOM elements. Your post implies that your
>problem is specifically with DOM elements so it would have been nice to
>see an example of code that caused the problem.

Since it's just reload, the GC doesn't get called, and the scripts
aren't re-compiled, navigating away from the page will release the
memory, IE is not designed for repeatedly viewing the same document,
or for hours on end, it's optimised to be a browser and browse around.
On almost all pages you'll see memory rise on reload

visit:
about:FRED
and hit reload _a lot_ you'll see the memory rise in the task manager.


Jim.
--
comp.lang.javascript FAQ - http://jibbering.com/faq/

Richard Cornford

unread,
Jun 18, 2003, 6:48:10 AM6/18/03
to

"Jim Ley" <j...@jibbering.com> wrote in message
news:3ef02c42...@news.cis.dfn.de...

>>Although Steve did think that the problem was more significant with
>>ActiveX objects than with DOM elements. Your post implies that your
>>problem is specifically with DOM elements so it would have been nice
>> to see an example of code that caused the problem.
>
>Since it's just reload, the GC doesn't get called, and the scripts
>aren't re-compiled, navigating away from the page will release the
>memory, IE is not designed for repeatedly viewing the same document,
>or for hours on end, it's optimised to be a browser and browse
>around. On almost all pages you'll see memory rise on reload

So would calling IE's (5.0+) global CollectGarbage method onunload
prevent the apparent memory leak? (While being completely pointless (or
counterproductive) if the user was actually navigating away from the
current page.)

That would imply that a test script that attempted to demonstrate a
genuine memory leak on IE should be required to cycle between at least
two HTML pages before being considered a valid demonstration of a
problem.

Richard.


Jim Ley

unread,
Jun 18, 2003, 7:06:08 AM6/18/03
to
On Wed, 18 Jun 2003 11:48:10 +0100, "Richard Cornford"
<Ric...@litotes.demon.co.uk> wrote:

>So would calling IE's (5.0+) global CollectGarbage method onunload
>prevent the apparent memory leak?

I doubt it, the leak happens without script.

>That would imply that a test script that attempted to demonstrate a
>genuine memory leak on IE should be required to cycle between at least
>two HTML pages before being considered a valid demonstration of a
>problem.

Definately.

Paul Lemke

unread,
Jun 18, 2003, 9:18:10 AM6/18/03
to
j...@jibbering.com (Jim Ley) wrote in message
news:<3ef02c42...@news.cis.dfn.de>...

> On Wed, 18 Jun 2003 02:53:37 +0100, "Richard Cornford"
> <Ric...@litotes.demon.co.uk> wrote:
>
> >Although Steve did think that the problem was more significant with
> >ActiveX objects than with DOM elements. Your post implies that your
> >problem is specifically with DOM elements so it would have been nice
to
> >see an example of code that caused the problem.
>
> Since it's just reload, the GC doesn't get called, and the scripts
> aren't re-compiled, navigating away from the page will release the
> memory, IE is not designed for repeatedly viewing the same document,
> or for hours on end, it's optimised to be a browser and browse around.
> On almost all pages you'll see memory rise on reload

Actually, just moving around on the site creates the memory leak.
Refreshing and actually clicking through on the site create the same
problem.

Paul

Pyrox

unread,
Jun 18, 2003, 9:50:43 AM6/18/03
to
"Richard Cornford" <Ric...@litotes.demon.co.uk> wrote in message news:<bcogn2$8ua$1$8300...@news.demon.co.uk>...

> "Paul Lemke" <lem...@gmx.net> wrote in message
> news:3eef939e$0$200$7586...@news.frii.net...
> > Yea, that would be important now wouldn't it...
> <snip>
>
>
> Still, the symptoms that you describe correspond exactly with symptoms
> described by Steve van Dongen when he bought the problem of circular
> JavaScript - ActiveX/DOM element references in IE to my attention.
> Although Steve did think that the problem was more significant with
> ActiveX objects than with DOM elements. Your post implies that your
> problem is specifically with DOM elements so it would have been nice to
> see an example of code that caused the problem.
>

Yea, i've read his posts on that and figured that was my problem too.

> Since Steve raised the issue I have been looking into providing a
> general 'finalize' mechanism for my DHTML objects. A mechanism that will
> call any number of registered functions onunload so that those functions
> can null any DOM element/ActiveX references that are being held by their
> associated objects and any references to those objects that have been
> assigned to DOM elements/ActiveX objects (event handlers and expando
> properties).
>

I"m very interested in your function. So basically i would call the
finalizeme method from the "onunload" event and pass it a refrence to
a function? (i'm not a noob, but some of this stuff is going way over
my head)

> This is the code that I am currently experimenting with:-

> Of course there may be ways of achieving what your code does without
> forming any circular references in the first place, but that is just
> speculation without seeing the code.
>
> Richard.

You can actually download the code for yourself right now... here is
the freshmeat link:
http://freshmeat.net/projects/dommenu/
A demo of the menu is here:
http://www.mojavelinux.com/cooker/demos/domMenu/example1.html

Could you guys look at the domMenu.js file and point out a circular
reference? Then maybe i can fix it myself or ask someone who knows how
to fix it for help. Thanks alot! This has been a big help so far! i
feel the wealth of knowledge just growing inside me! Thanks again!!!

Richard Cornford

unread,
Jun 18, 2003, 1:11:18 PM6/18/03
to
"Jim Ley" <j...@jibbering.com> wrote in message
news:3ef04741...@news.cis.dfn.de...
<snip>

>>That would imply that a test script that attempted to demonstrate a
>>genuine memory leak on IE should be required to cycle between at least
>>two HTML pages before being considered a valid demonstration of a
>>problem.
>
> Definately.

OK, before spending too much time experimenting with generalised DHTML
object finalizing I thought it would be a good idea to gauge the extent
of the problem for myself. I tried a two-page test and a three-page test
with identical results. I will post the two-page test for brevity.

The first page creates a JavaScript object for each element in IE's
document.all collection and has that object hold a reference to the DOM
element, assign an inner function to an event handler of that element
and assign a reference to itself to and expando property on that
element. That should be sufficiently circular to expose potential
problems caused by circular references.

The second page just uses a timeout to re-load the first.

---------------- memLeakTest1.html START -------------------------
<html>
<head>
<title>Memory leak test page 1</title>
<script type="text/javascript">
var susp = false;
var clrObjects = [];
function CurcularRefObject(el, instArray){
this.el = el;
this.instAr = instArray;
this.el.crlArRef = instArray;
this.onclkFunc = function(){
return;
}
this.el.onmouseover = this.onclkFunc;
}
function doOnLoad(){
var el;
for(var c = document.all.length;c--;){
el = document.all[c]
//Comment out the following statement for a comparison of
//memory use without the JavaScript objects.
clrObjects[clrObjects.length] =
(el.expandoRef = new CurcularRefObject(el, clrObjects));
}
if(location.search.length < 2){
alert('read the initial memory use!');
}
setTimeout(swapPages, 1000)
}
function swapPages(){
if(!susp){
location = 'memLeakTest2.html';
}else{
setTimeout(swapPages, 1000);
}
}
swapPages.toString = function(){return 'swapPages();';}
</script>
</head>
<body onload=doOnLoad();>
<p>Memory leak test page</p>
<form name="f" action="">
<input type="button" value="Suspend"
onclick="this.value = (susp = !susp)?'Resume':'Suspend';"
</form>

<!-- insert sufficient valid HTML body contents here to give a dramatic
effect. I was using about 1600 lines -->

</body>
</html>
---------------- memLeakTest1.html END -------------------------
---------------- memLeakTest2.html START-------------------------
<html>
<head>
<title>Memory leak test page 2</title>
<script type="text/javascript">
function doLoad(){
setTimeout('location=\'memLeakTest1.html?noAlert=true;\'',
500);
}
</script>
</head>
<body onload=doLoad();>
<p>Alternative page!</p>
</body>
</html>
---------------- memLeakTest2.html END -------------------------

So, filling the body of memLeakTest2.html with about 1600 lines of
nested tables and forms copied froms other bigger HTML pages, and
executing it in IE 5.0 on Windows NT4 and IE 6.0 on Windows 98 I got
steady increases in memory use with each cycle of loading the page.

I figured that rapidly cycling through the pages might not be giving to
garbage collector time to catch up (although CPU usage did not exceed
75% during the experiment) but I had included a suspend/resume button on
the page. It puts the page into a repeated half second timeout.
Suspending the reloading cycle should have freed IE up enough for it to
catch up with background garbage collection for previous pages, but the
memory use figure just stopped going up it did not go down (and I went
and made some tea so it had plenty of time to take advantage of the
opportunity to catch up).

Memory use did not go down again until I closed the browser.

Commenting out the statement that creates the JavaScript objects and
re-running the page for comparison produced an oscillating but generally
constant level of memory use.

From this I would conclude that Steve van Dongen is correct and circular
references between IE DOM objects and JavaScript objects does bar
garbage collection of one or both of them. It looks like it is
definitely a potential problem that needs to be recognised by DHTML
authors. (although how significant it is will depend on how much memory
any given page/site/browsing session will tie-up. Extreme tests will
always produce extreme results).

The next question is whether my finalizing strategy can be used to
effectively break the circular references as the page unloads and what
effect that has on memory consumption.

So a second set of test pages, importing the script from my earlier post
as Finalizer.js.

---------------- finalizeLeakTest1.html START-------------------------
<html>
<head>
<title>Memory leak test page 1</title>
<script type="text/javascript" src="Finalizer.js"></script>
<script type="text/javascript">
var susp = false;
var clrObjects = [];
var CurcularRefObject = function(){
var classFnlzSetUp = false;
var instArray = [];
function finalize(){
var itm;
for(var c = instArray.length;c--;){
itm = instArray[c];
itm.el.crlArRef = null;
itm.el.onmouseover = null;
itm.instAr = null;
itm.el.expandoRef = null;
itm.el = null;
}
instArray = null;
}
function constructor(el, instArr){
this.el = el;
this.instAr = instArr;
this.el.crlArRef = instArr;
instArray[instArray.length] = this;
this.onclkFunc = function(){
return;
}
this.el.onmouseover = this.onclkFunc;
if(!classFnlzSetUp){
finalizeMe(finalize);
classFnlzSetUp = true;
}
}
return constructor;
}();
function doOnLoad(){
var el;
for(var c = document.all.length;c--;){
el = document.all[c]
clrObjects[clrObjects.length] =
(el.expandoRef = new CurcularRefObject(el, clrObjects));
}
if(location.search.length < 2){
alert('read the initial memory use!');
}
setTimeout(swapPages, 1000)
}
function swapPages(){
if(!susp){
location = 'finalizeLeakTest2.html';
}else{
setTimeout(swapPages, 1000);
}
}
swapPages.toString = function(){return 'swapPages();';}
</script>
</head>
<body onload=doOnLoad();>
<p>Memory leak test page</p>
<form name="f" action="">
<input type="button" value="Suspend"
onclick="this.value = (susp = !susp)?'Resume':'Suspend';"
</form>

<!-- insert sufficient valid HTML body contents here to give a dramatic
effect. I was using about 1600 lines -->

</body>
</html>
---------------- finalizeLeakTest1.html END --------------------------
---------------- finalizeLeakTest2.html START-------------------------
Same as memLeakTest2.html except the URL assigned to the location object
is finalizeLeakTest1.html instead of memLeakTest1.html
---------------- finalizeLeakTest2.html END --------------------------

The result was that nulling the references from the DOM elements to the
JavaScript objects and from those objects to the DOM elements when the
page was unloaded had the effect of rendering the memory use constant
(well, oscillating but relatively constant). Garbage collection seemed
to be working again.

I did discover that my finalizing function produces a stack overflow
error if asked to finalize more than about 1000 objects, but that was
easily solved by only asking it to execute one private static function
instead of one for each object instance and would not be a problem in
less extreme use.

Richard.


Richard Cornford

unread,
Jun 18, 2003, 1:11:21 PM6/18/03
to
"Pyrox" <lem...@gmx.net> wrote in message
news:7d5a62a5.0306...@posting.google.com...
<snip>

>I"m very interested in your function. So basically i would call the
>finalizeme method from the "onunload" event and pass it a refrence to
>a function? (i'm not a noob, but some of this stuff is going way over
>my head)

Not exactly, the finalizeMe function is designed to handle arranging
that its function argument is called onunload itself. So it should be
invoked before the onunload event. I designed it for use with JavaScript
objects that did DHTML interaction so I was expecting it to be called
from the object's constructor as the objects are created.

I have just posted an example of using it in one of my replies to my
conversation with Jim Ley in this thread.

You could always just design your own circular reference breaking
function and assign it to onunload yourself.

<snip>


>You can actually download the code for yourself right now...
>here is the freshmeat link:
>http://freshmeat.net/projects/dommenu/
>A demo of the menu is here:
>http://www.mojavelinux.com/cooker/demos/domMenu/example1.html

OK, I will try and have a look later.

Richard.


Steve Heron

unread,
Jun 18, 2003, 2:34:33 PM6/18/03
to
I'm not sure the circular reference is even necessary, although I
haven't specifically put it to the test. I went through these
shenanigans with my own js framework. IE was leaking memory like a sieve
as long as references to dom elements were not explicitly set to null -
with no circular references involved, just one way references from JS
objects to dom elements.

Incidentally, I found calls to CollectGarbage made no difference
whatsoever.
The problem did not exist in Mozilla.

Richard Cornford

unread,
Jun 18, 2003, 4:26:57 PM6/18/03
to
"Steve Heron" <st...@heswell.com> wrote in message
news:105596097...@damia.uk.clara.net...

>I'm not sure the circular reference is even necessary,
>although I haven't specifically put it to the test. I went
>through these shenanigans with my own js framework. IE was
>leaking memory like a sieve as long as references to dom
>elements were not explicitly set to null - with no circular
>references involved, just one way references from JS objects
>to dom elements.
<snip>

Memory leaks due to one way references in either direction did not stand
up to testing. Taking the test page that I posted in response to Jim Ley
earlier and changing the object constructor to:-

function CurcularRefObject(el){
this.el = el;
}

- and the call to the constructor to just:-

for(var c = document.all.length;c--;){
el = document.all[c]

new CurcularRefObject(el);
}

- so that the JavaScript object held a reference to the DOM element but
the element did not hold a reference to the object, I found no memory
leak. The memory consumption oscillated up and down a bit as the pages
changed but overall usage remained constant, as opposed to the steady
rise in consumption that the circular reference test produced.

Also reversing the relationship so the JavaScript object held no
references to the element but the element held a reference to the
object:-

function CurcularRefObject(el){
this.onclkFunc = function(){
return;
}
el.onmouseover = this.onclkFunc;
el = null;
}

- also did not produce any evidence of a memory leak.

I have to conclude that your code was forming circular references with
the DOM elements but that you were not aware that you were doing so.
(Probably assigning inner functions to event handlers and leaving
references back to DOM elements contained in the resulting closures.
Difficult to say for sure without seeing the code.)

Richard.


Steve Heron

unread,
Jun 18, 2003, 4:47:34 PM6/18/03
to
you are quite right, I do use inner function event handlers, I had
overlooked this as a circular reference scenario.

Richard Cornford

unread,
Jun 18, 2003, 7:24:27 PM6/18/03
to
"Pyrox" <lem...@gmx.net> wrote in message
news:7d5a62a5.0306...@posting.google.com...
<snip>

>You can actually download the code for yourself right now...
>here is the freshmeat link:
> http://freshmeat.net/projects/dommenu/
>A demo of the menu is here:
> http://www.mojavelinux.com/cooker/demos/domMenu/example1.html
>
>Could you guys look at the domMenu.js file and point out
>a circular reference? Then maybe i can fix it myself or ask
>someone who knows how to fix it for help. Thanks alot! This
>has been a big help so far! i feel the wealth of knowledge
>just growing inside me! Thanks again!!!

domMenu.js is a big script (some of which is quoted below), still 1000+
lines is only to be expected for a DHTML menu system. There are plenty
of potential circular references to DOM elements so pointing them all
out will not be practical, it would virtually mean re-writing the script
until the memory leak stopped and as this is not my style at all I would
rather spend my time writing my own and take a bit more effort over
graceful degradation and accessibility.

However, if you point out the memory leak problem to the author he might
want to do something about it himself.

> * Maintainer: Dan Allen <d...@mojavelinux.com>
> *
> * License: LGPL - however, if you use this library, please post to my
> * forum where you use it so that I get a chance to see my
> * baby in action. ...

I do have some comments on the script and can immediately point out a
few circular references but unravelling the all of the relationships and
adding an appropriate finalizing mechanism/function would take too long.

>var domMenu_userAgent = navigator.userAgent.toLowerCase();
>var domMenu_isOpera =
domMenu_userAgent.indexOf('opera 7') != -1 ? 1 : 0;
>var domMenu_isKonq = domMenu_userAgent.indexOf('konq') != -1 ? 1 : 0;
>var domMenu_isIE =
!domMenu_isKonq && !domMenu_isOpera && document.all ? 1 : 0;
>var domMenu_isIE50 =
domMenu_isIE && domMenu_userAgent.indexOf('msie 5.0') != -1;
>var domMenu_isIE55 =
domMenu_isIE && domMenu_userAgent.indexOf('msie 5.5') != -1;
>var domMenu_isIE5 = domMenu_isIE50 || domMenu_isIE55;
>var domMenu_isGecko =
domMenu_userAgent.indexOf('gecko') != -1 ? 1 : 0;

This is a very bad start. Browser detecting is not encouraged at all but
browser detecting based on the navigator.userAgent string is the very
worst approach available.

There is a valued regular contributor to this group who has stated that
his initial quality assessment for a script is to do a text search for
navigator.userAgen and dismiss the script out of hand if the string is
found. I would not recommend using a script that took this approach.

Looking at the code above I wonder how IE 6 is to be treated, it is not
IE 5.0 or IE 5.5 so these tests are going to categorise it with IE 4.
Does that make sense?

Then there is IceBrowser (a DOM level two dynamic visual browser), its
userAgent string is:-

Mozilla/4.0 (compatible; MSIE 5.0; Windows 98)

- so it is going to be treated like IE 5.0. That will probably be OK
because it has very similar capabilities, but how about Web Browser 2.0
on the palm OS, userAgent String:-

Mozilla/4.0 (compatible; MSIE 6.0; Windows 95; PalmSource) NetFront/3.0

- and that will have it treated as IE 6.0 by this script. It is of
course not capable of anything that this script will ask of it.

>function domMenu_Hash() {
> var argIndex = 0;
> this.length = 0;
> this.numericLength = 0;
> this.items = new Array();
> while (arguments.length > argIndex) {
> this.items[arguments[argIndex]] = arguments[argIndex + 1];
> if (arguments[argIndex] == parseInt(arguments[argIndex])) {
> this.numericLength++;
> }
>
> this.length++;
> argIndex += 2;
> }
>
> this.removeItem = function(in_key)
> {
> var tmp_value;
> if (typeof(this.items[in_key]) != 'undefined') {
> this.length--;
> if (in_key == parseInt(in_key)) {
> this.numericLength--;
> }
>
> tmp_value = this.items[in_key];
> delete this.items[in_key];
> }
>
> return tmp_value;
> }
>
> this.getItem = function(in_key)
> {
> return this.items[in_key];
> }
>
> this.setItem = function(in_key, in_value)
> {
> if (typeof(this.items[in_key]) == 'undefined') {
> this.length++;
> if (in_key == parseInt(in_key)) {
> this.numericLength++;
> }
> }
>
> this.items[in_key] = in_value;
> }
>
> this.hasItem = function(in_key)
> {
> return typeof(this.items[in_key]) != 'undefined';
> }
>
> this.merge = function(in_hash)
> {
> for (var tmp_key in in_hash.items) {
> if (typeof(this.items[tmp_key]) == 'undefined') {
> this.length++;
> if (tmp_key == parseInt(tmp_key)) {
> this.numericLength++;
> }
> }
>
> this.items[tmp_key] = in_hash.items[tmp_key];
> }
> }
>
> this.compare = function(in_hash)
> {
> if (this.length != in_hash.length) {
> return false;
> }
>
> for (var tmp_key in this.items) {
> if (this.items[tmp_key] != in_hash.items[tmp_key]) {
> return false;
> }
> }
>
> return true;
> }
>}

This constructor is a potential memory hog. It assigns all of its object
methods as inner functions of the constructor. Potentially that would
mean that each method of each object has its own unique function object.
While there can be very good reasons for assigning an inner function as
on instance method (so that the result is unique to the object instance)
this code does not exploit any of the additional features of inner
functions. Exactly the same functionality could be achieved by defining
the instance methods as properties of the object's prototype and that
would have the advantage that only one function object would exist for
each method instead of potentially one function object for each method
of each object instance. And there a lot of these object instances used
in the code.

It is the case that the ECMA Script specification does allow (but not
require) implementations to re-use existing function objects rather than
creating new ones if it can determine that the result would be
identical. There is no guarantee that any implementation will make that
optimisation or that it would be possible for it to determine that a
function object in one closure is identical to a function object in
another.

This following function forms some of the circular references that will
cause the memory leak:-

>function domMenu_activate(in_containerId)
>{
> var container;
> var data;
>
> // make sure we can use the menu system and this is a valid menu
> if (!domMenu_useLibrary || !(container =
> document.getElementById(in_containerId)) ||
!(data = domMenu_data.items[in_containerId])) {
> return;
> }
>
> // start with the global settings and merge in the local changes
> if (!domMenu_settings.hasItem(in_containerId)) {
> domMenu_settings.setItem(
> in_containerId, new domMenu_Hash());
> }
>
> var settings = domMenu_settings.items[in_containerId];
> for (var i in domMenu_settings.items['global'].items) {
> if (!settings.hasItem(i)) {
> settings.setItem(i,
domMenu_settings.items['global'].items[i]);
> }
> }
>
> // populate the zero level element
> container.data = new domMenu_Hash(
> 'parentElement', false,
> 'numChildren', data.numericLength,
> 'childElements', new domMenu_Hash(),
> 'level', 0,
> 'index', 1
> );
>
> // if we choose to distribute either height or width,
> // determine ratio of each cell
> var distributeRatio =
Math.round(100/container.data.items['numChildren']) + '%';
>
> // the first menu is the rootMenu, which is a child
> //of the zero level element
> var rootMenu = document.createElement('div');
> rootMenu.id = in_containerId + '[0]';
> rootMenu.className = settings.items['menuBarClass'];
> container.data.setItem('subMenu', rootMenu);
>
> var rootMenuTable = rootMenu.appendChild(
document.createElement('table'));
> if (domMenu_isKonq) {
> rootMenuTable.cellSpacing = 0;
> }
>
> rootMenuTable.style.border = 0;
> rootMenuTable.style.borderCollapse = 'collapse';
> rootMenuTable.style.width = settings.items['menuBarWidth'];
> var rootMenuTableBody = rootMenuTable.appendChild(
document.createElement('tbody'));
>
> var numSiblings = container.data.items['numChildren'];
> for (var index = 1; index <= numSiblings; index++) {
> // create a row the first time if horizontal
> //or each time if vertical
> if (index == 1 || settings.items['axis'] == 'vertical') {
> var rootMenuTableRow = rootMenuTableBody.appendChild(
document.createElement('tr'));
> }
>
> // create an instance of the root level menu element
> var rootMenuTableCell = rootMenuTableRow.appendChild(
document.createElement('td'));
> rootMenuTableCell.style.padding = 0;
> rootMenuTableCell.id = in_containerId + '[' + index + ']';
> // add element to list of parent children
> container.data.items['childElements'].setItem(
rootMenuTableCell.id, rootMenuTableCell);
>
> // assign the settings to the root level element
> // {!} this is a problem if two menus are
> //using the same data {!}
> rootMenuTableCell.data = data.items[index];
> rootMenuTableCell.data.merge(new domMenu_Hash(
> 'basename', in_containerId,
> 'parentElement', container,
> 'numChildren', rootMenuTableCell.data.numericLength,
> 'childElements', new domMenu_Hash(),
> 'offsets', new domMenu_Hash(),
> 'level', container.data.items['level'] + 1,
> 'index', index
> ));
>
> // assign the styles
> rootMenuTableCell.style.cursor = 'default';
> if (settings.items['axis'] == 'horizontal') {
> if (settings.items['distributeSpace']) {
> rootMenuTableCell.style.width = distributeRatio;
> }
> }
>
> var rootElement = rootMenuTableCell.appendChild(
> document.createElement('div'));
> rootElement.className =
> settings.items['menuElementClass'];
> // fill in the menu element contents
> rootElement.innerHTML = '<span>' +
> rootMenuTableCell.data.items['contents'] +
> '</span>' + (rootMenuTableCell.data.hasItem(
> 'contentsHover') ? '<span style="display: none;"> '
> + rootMenuTableCell.data.items['contentsHover']
> + '</span>' : '');
>
> // attach the events

These next lines are the cause of the problem. They assign an inner
function to event handlers. Inner functions always have the ability to
refer to the parameters and variables of their outer functions. To
achieve that the 'variables' object from the execution context of the
outer function must be preserved. This is referred to as a closure and
means that as this function exits the final state of all of its local
variables is preserved. Those local variables include: rootElement,
rootMenu, rootMenuTable, rootMenuTableBody, rootMenuTableRow,
rootMenuTableCell, and various other local variables including
references to other objects that are also holding references to DOM
elements created in this function.

The circular reference is caused because the event handling properties
refer to function objects from the current execution context and the
current execution context is full of un-nulled local variables that
refer back to the DOM elements. The function references on the DOM
elements preserve the local variables from the execution context and the
references to the DOM elements held in those local variables prevent the
garbage collection of the DOM elements.

> rootMenuTableCell.onmouseover =
> function(in_event) { domMenu_openEvent(
> this, in_event,
> settings.items['openMouseoverMenuDelay']); };
> rootMenuTableCell.onmouseout = function(in_event) {
> domMenu_closeEvent(this, in_event); };
>
> if (settings.items['openMousedownMenuDelay'] >= 0
> && rootMenuTableCell.data.items['numChildren']) {
> rootMenuTableCell.onmousedown =
> function(in_event) { domMenu_openEvent(this, in_event,
> settings.items['openMousedownMenuDelay']); };
> // cancel mouseup so that it doesn't
> //propogate to global mouseup event
> rootMenuTableCell.onmouseup =
> function(in_event) { var eventObj = domMenu_isIE ? event
> : in_event; eventObj.cancelBubble = true; };
> if (domMenu_isIE) {
> rootMenuTableCell.ondblclick = function(in_event) {
> domMenu_openEvent(this, in_event,
> settings.items['openMousedownMenuDelay']); };
> }
> }
> else if (rootMenuTableCell.data.items['uri']) {
> rootMenuTableCell.style.cursor = domMenu_pointerStyle;
> rootMenuTableCell.onclick =
> function(in_event) {
> domMenu_resolveLink(this, in_event); };
> }
>
> // prevent highlighting of text
> if (domMenu_isIE) {
> rootMenuTableCell.onselectstart =
> function() { return false; };
> }
>
> rootMenuTableCell.oncontextmenu =
> function() { return false; };
> }

There is nothing about these functions that requires that they be inner
functions of this function. In fact, as some of them are just
repetitions of each other, it would be more efficient if they were
defined outside this function and assigned by reference. And not using
inner functions would prevent the creation of the closure, which would
make clearing the DOM element references onunload easier because the
references in the JavaScript objects can be accessed while the
references preserved in this closure would only be accessible to inner
functions of this function that had been assigned as properties of
external objects.

> // add the menu rootMenu to the zero level element
> rootMenu = container.appendChild(rootMenu);
>
> // even though most cases the top level menu does not go away,
> // it could if this menu system is used by another process
> domMenu_detectCollisions(rootMenu);
>}

Much the same is true of the domMenu_activateSubMenu function.

>function domMenu_activateSubMenu(in_parentElement)
<snip>
> var pageYOffset = domMenu_isIE ?
> document.body.scrollTop : window.pageYOffset;
> var pageXOffset = domMenu_isIE ?
> document.body.scrollLeft : window.pageXOffset;

Current scrolling positions are not always read from document.body on IE
5.5+. If the page is - document.compatMode=='CSS1Compat' - scrolling
positions should be read from the document.documentElement property and
_not_ the body.

Richard.


Richard Cornford

unread,
Jun 18, 2003, 7:35:05 PM6/18/03
to
"Richard Cornford" <Ric...@litotes.demon.co.uk> wrote in message
news:bcq7a2$hcn$1$8300...@news.demon.co.uk...
<snip>

> I have just posted an example of using it in one of my
> replies to my conversation with Jim Ley in this thread.
<snip>

<URL:
http://groups.google.com/groups?selm=bcq6fn%24g53%241%248300dec7%40news.
demon.co.uk >

Richard.


Richard Cornford

unread,
Jun 19, 2003, 11:39:23 AM6/19/03
to
"Richard Cornford" <Ric...@litotes.demon.co.uk> wrote in message
news:bcqsbc$jq9$1$8300...@news.demon.co.uk...
<snip>

For a quick fix for the memory leak it is only necessary to strip the
DOM elements of their event handlers and expando properties onunload.
Inserting the following function definition and Array definition into
the domMenu.js file:-

function fixCircleRefs(){
if(document.all){
var el;
for(var d = document.all.length;d--;){
el = document.all[d];
for(var c = cearElementProps.length;c--;){
el[cearElementProps[c]] = null;
}
}
}
}

var cearElementProps = [
'data',
'onmouseover',
'onmouseout',
'onmousedown',
'onmouseup',
'ondblclick',
'onclick',
'onselectstart',
'oncontextmenu'
];

- and calling it onunload as:-

<body onunload="fixCircleRefs();">

- gave me constant memory consumption when repeatedly re-loading and
cycling a page with the menu and another page (as in my other posts on
testing the memory leak).

The - fixCircleRefs - function is not very subtle as it nulls the
references for all of the elements on the page. I did try a recursive
version that started at the root element of the menu and progresses
through its children/descendants but it was not totally successful and
was obviously missing something. Hitting all of the elements in the
document.all collection worked consistently so I went with that.

Richard.


Dom Leonard

unread,
Jun 20, 2003, 1:11:22 AM6/20/03
to
Richard Cornford wrote:
> "Paul Lemke" <lem...@gmx.net> wrote in message

<snip>

Thanks for all the info Richard, and to all contributors as well!

Personally I ended up doing a DOM walk in an onbeforeunload handler to
remove circular DOM node references, similar to your solution for
domMenu.js (see thread discussion). In my case removing event handlers
proved uneccessary as there were no nested functions.

The circular reference that gave me the most problems, however, was
created on the document itself:

document.largeObject.documentPointer=document;

Of course in theory this has been well covered, but the nasty bit was
the page stabilised IE memory usage when loaded and reloaded outside a
frameset and so "passed" testing. The same page loaded within a frameset
leaks memory badly, either by reloading the frameset or navigating away
and back to the frameset. Don't ask me why but worth noting if anyone
gets hit with a similar problem.

Test pages show this particular error FYI, and demonstrate that
navigating away from a memory leak is not always a solution.

fs500.html frameset - load first or later.
tp500.html testpage does 500 things to create a large object.
load outside a frameset (in IE of course) to verify memory
usage is stable.
dummy.html test page for "navigating away".

Take links whilst monitoring memory and use back and forward buttons as
well. Leakage occurs when IE feels like it but is cummulative.


Dom

========= fs500.html =========
<html>
<head>
<title>fs500</title>

<frameset cols="230,*">

<frame name="frame0" src="tp500.html">
<frame name="frame1" src="about:blank">

</frameset>
</html>

=========== tp500.html ===========
<html>
<head>
<title>test500</title>

<script type="text/javascript">
function test()
{
// create large object

var o = new Object();
for(var i=0;i<500;++i)
{
( o["prop"+i]=new Object() ).value="a string to take up space";
}

// create circular ref on document

document.o=o;
o.ownerDoc=document;
}
window.onload=test;
</script>
</head>
<body>
This is test page tp500.html<br>
<a href="fs500.html" target="_top">frameset</a> ( _top)<br>
<a href="tp500.html" target="_top">tp500.html</a> (_top)<br>
<a href="dummy.html" target="_top">dummy.html</a> (_top)
</body>
</html>

========= dummy.html ===========

<html>
<head>
<title>dummy.html</title>


</head>
<body>
Dummy: use back to return to frameset
</body>
</html>

Richard Cornford

unread,
Jun 22, 2003, 11:15:59 PM6/22/03
to
"Dom Leonard" <doml.re...@senet.andthis.com.au> wrote in message
news:UHwIa.117$Ev4....@nnrp1.ozemail.com.au...
<snip>

>The circular reference that gave me the most problems,
>however, was created on the document itself:
>
> document.largeObject.documentPointer=document;
<snip>

As the DOM Document interface is supposed to inherit from the Node
interface I suppose it should be expected to exhibit the same features.

Another possible unexpected target for circular references might be
Image objects created with - new Image(); - as IE 5.5+ (at least)
implement then as new IMG elements contained in their own
DocumentFragment object. So assigning an onload/abort/error handler as
an inner function, while maintaining the reference to the image in the
resulting closure might bar garbage collection of both the Image and the
DocumentFragment.

Richard.


Pyrox

unread,
Jun 23, 2003, 9:23:02 AM6/23/03
to
OH MAN!!!! Thank you sooo much!!! This is exactly what i was looking
for! (Well actualy, i wasn't looking for anything, just answers to my
questions! BUT.... Richard! THank you soo very much!!! I have to
admit, i think this is the most response i got from a thread in a very
long time, and this is probably the one that has helped me the most
too! Thanks!!!

You do not know how much this helps me out! (and anyone that is also
haveing this problem!) It saves me the time of rewriting this code!!!
I mean really, are you some kind of Javascript genius or what?

I'm going to post this article over on the Menu's forum, and tell the
author the quick fix too. So yea, Now if only Microsoft would come in
and fix their little problem.... instead of us having to work around
it!!!

Thanks again Richard!! And anyone else who participated!!! Thanks!!!!
Paul

joelg...@gmail.com

unread,
Jan 3, 2005, 9:41:46 PM1/3/05
to
If anyone's still paying attention to this thread, I thought I would
add my $0.02, since people seem to keep having this problem over and
over again. The most likely cause is that you've got circular
references that involve both JavaScript and DOM objects (Not to say
it's your fault -- this is really common). I've posted a more detailed
explanation at
http://jgwebber.blogspot.com/2005/01/dhtml-leaks-like-sieve.html. Hope
this helps!

joel.

0 new messages