简体   繁体   中英

Find what is causing a memory leak in this Windows Gadget

SCENARIO

I'm using the windows gadget platform with this gadget:

http://win7gadgets.com/pc-system/sushis_driveinfo.html

PROBLEM

The gadget has a memory leak, If I keep running this gadget +24h. it can increase the RAM consumption up to 1 GB, while other similar gadgets does not procude this, so I discarded that this is a sidebar.exe memory management, not, is an script bug.

When more time is running the gadget, more unresponsiveness becomes the gadget (on click).

My knowledges about JavaScript are NULL, but anyways I can understand the syntax and try to understand what the developer is doing in this code, I think that the problem is when managging the image objects, but at my point of view those objets seems to be properly disposed after each operation.

QUESTION

This is the gadget source.

Someone could help me to discover and fix what is causing the memory leak in this gadget?

sushi_driveinfo.html (main window):

<html>
  <head>
    <title>Drive Info</title>
    <style>
      body { margin: 0; padding: 0; width: 156px; height: 200px; background-image: url(images\canvas.png); color: #ffffff; font-family: 'Segoe UI'; }
      #targets { position: absolute; top: 0; left: 0; }
      .target { position: absolute; width: 156px; height: 48; left: 0; cursor: hand; }
    </style>
    <script type="text/javascript">
      var lst = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
      var timeout = null;
      var drives = new Array(26);
      var drvchk = new Array(26);
      var drvspc = new Array(26);
      var vizchg = false;
      var current_y = 0;
      var background,theme,remove,local,network,media,show_pc,show_net;
      var item_height=48;
      var icon_offset=20;
      var text_offset=72;
      var meter_offset=24;

      function convertBytes(b)
      {
        var i = 0, u = Array(' MB', ' GB', ' TB');
        while (b >= 1024 && (b /= 1024) >= 1) i++;
        return (Math.round(b * 100) / 100) + u[i];
      }

      function openDrive()
      {        
        var d = window.event.srcElement.getAttribute('drive');    
        System.Shell.execute(d + ':\\');
        return;
      }

      function openNetwork()
      {        
        System.Shell.execute("Explorer", "/N,::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}");
        return;
      }

      function openComputer()
      {        
        System.Shell.execute("Explorer", "/N,::{20D04FE0-3AEA-1069-A2D8-08002B30309D}");
        return;
      }

      function recheckDrives() {
       for(var i = 0; i < 26; i++)
        {
          if (!drives[i]) {
            drives[i] = System.Shell.drive(lst.charAt(i));
            if (drives[i]) { vizchg = true; drvchk[i] = true; }
          } else {
          if (drives[i].isReady != drvchk[i]) { drvchk[i] = !drvchk[i]; vizchg = true; }
          if (drives[i].isReady && drives[i].freeSpace != drvspc[i]) { drvspc[i] = drives[i].freeSpace; vizchg = true; }
          }
        }
      }

      function calcHeight(h) {
        var y=0;
        if(show_pc==2) y+=h;
        if(show_net==2) y+=h;
        for(var i=0;i<26;i++)
            if(isDriveVisible(i)) y+=h;
        return y;
      }

      function isDriveVisible(i) {
        if(drvchk[i]) {
           if      (drives[i].driveType == 2 && remove == 1)  ;
           else if (drives[i].driveType == 3 && local == 1)   ;
           else if (drives[i].driveType == 4 && network == 1) ;
           else if (drives[i].driveType == 5 && media == 1)   ;
           else if (drives[i].driveType == 1 || drives[i].driveType == 6) ;
           else 
            return true;
        }
        return false;
      }

      function paintPC() {
        if (show_pc == 2) {
            canvas.addImageObject('images/backgrounds/background' + background + 's.png', 0, current_y);  
            var di=canvas.addImageObject('images/drives/pc'+ theme +'.png', icon_offset, current_y);
            di.width*=0.8;
            di.height*=0.8;
            canvas.addTextObject('Computer', 'Segoe UI', 11, 'white', text_offset, current_y + 5);
            var b = document.createElement('DIV');
            b.className = 'target';
            b.style.posTop = current_y;
            b.onclick = openComputer;
            targets.appendChild(b);
            current_y+=item_height;
        }
        return;
      }

      function paintNET() {
        if (show_net == 2) {
            canvas.addImageObject('images/backgrounds/background' + background + 's.png', 0, current_y);  
            var di=canvas.addImageObject('images/drives/net'+ theme +'.png', icon_offset, current_y);
            di.width*=0.8;
            di.height*=0.8;
            canvas.addTextObject('Network', 'Segoe UI', 11, 'white', text_offset, current_y + 5);
            var b = document.createElement('DIV');
            b.className = 'target';
            b.style.posTop = current_y;
            b.onclick = openNetwork;
            targets.appendChild(b);
            current_y+=item_height;
        }
        return;
      }

      function paintGadget()
      {       
      try {
        recheckDrives();
        if (!vizchg) return;

        var total_height=calcHeight(item_height);
        System.Gadget.beginTransition();

        document.body.style.height=total_height;
        canvas.style.height=total_height;
        canvas.removeObjects();
        targets.innerHtml = '';

        current_y = 0;
        paintPC();
        paintNET();
        for(i = 0; i < 26; i++)
        {
            if(isDriveVisible(i)) {
              if (drives[i].freeSpace != 0) {
               canvas.addImageObject('images/backgrounds/background' + background + '.png', 0, current_y);  
               var f = Math.round(drives[i].freeSpace / drives[i].totalSize * 100);
               var u = (100 - f); 
               canvas.addTextObject(convertBytes(drives[i].freeSpace) + ' / ' + f + '%', 'Segoe UI', 10, 'white', text_offset, current_y + 17);
               var m = canvas.addImageObject('images/meter' + (u < 90 ? 'blue': (u < 98 ? 'orange': 'red')) + '.png', meter_offset, current_y + 34);   
               m.width = Math.floor((u * 128 / 100));
               m.left = 24 - Math.floor(((128 - m.width) / 2));
              } else {
               canvas.addImageObject('images/backgrounds/background' + background + 's.png', 0, current_y);  
               canvas.addTextObject(convertBytes(drives[i].totalSize), 'Segoe UI', 10, 'white', text_offset, current_y + 17);
              }

              var di=canvas.addImageObject('images/drives/drive' + drives[i].driveType + theme + '.png', icon_offset, current_y-5);
              di.width*=0.8;
              di.height*=0.8;
              canvas.addTextObject(drives[i].volumeLabel + ' (' + drives[i].driveLetter + ':)', 'Segoe UI', 11, 'white', text_offset, current_y + 5);
              var o = document.createElement('DIV');
              o.className = 'target';
              o.style.posTop = current_y;
              o.setAttribute('drive', drives[i].driveLetter);
              o.onclick = openDrive;
              targets.appendChild(o);

              current_y += item_height;
           }
        System.Gadget.endTransition(System.Gadget.TransitionType.morph,0.1);
        window.setTimeout(fixCanvasBackground, 600);
        }
        } finally {
         vizchg = false;
         return;
        }
      }

      function fixCanvasBackground() {
        canvas.src = canvas.src;
      } 

      function initDrives()
      {
       for(var i = 0; i < 26; i++)  {
            drives[i] = System.Shell.drive(lst.charAt(i));
            if (drives[i] && drives[i].isReady)
            { drvchk[i] = true ; drvspc[i] = drives[i].freeSpace; }
            else  { drvchk[i] = false; }
        }
        return;
      }

      function onShowSettings() {
        window.clearInterval(timeout);
        System.Gadget.beginTransition();
        window.setTimeout(endTransitionFast, 400);
      }

      function onSettingsClosed() {
        readSettings();
        timeout=window.setInterval(paintGadget, 2500);
        vizchg=true;
        paintGadget();
      }

      function endTransitionFast() {
        System.Gadget.endTransition(System.Gadget.TransitionType.morph, 0.1);
        fixCanvasBackground();
      }

    function readSettings() {
        background=System.Gadget.Settings.read("background");
        if(background==0) { background=2; System.Gadget.Settings.write("background",2); }
        theme=System.Gadget.Settings.read("theme");
        if(theme==0) { theme=1; System.Gadget.Settings.write("theme",1); }
        show_pc=System.Gadget.Settings.read("showpc");
        if(show_pc==0) { show_pc=1; System.Gadget.Settings.write("showpc",1); }
        show_net=System.Gadget.Settings.read("shownet");
        if(show_net==0) { show_net=1; System.Gadget.Settings.write("shownet",1); }
        local=System.Gadget.Settings.read("local");
        if(local==0) { local=2; System.Gadget.Settings.write("local",2); }
        media=System.Gadget.Settings.read("media");
        if(media==0) { media=2; System.Gadget.Settings.write("media",2); }
        network=System.Gadget.Settings.read("network");
        if(network==0) { network=2; System.Gadget.Settings.write("network",2); }
        remove=System.Gadget.Settings.read("remove");
        if(remove==0) { remove=2; System.Gadget.Settings.write("remove",2); }
      }

      function onLoad()
      {
        System.Gadget.settingsUI = "settings.html";
        System.Gadget.onSettingsClosed = onSettingsClosed;
        System.Gadget.onShowSettings = onShowSettings;

        readSettings();
        initDrives();
        timeout = window.setInterval(paintGadget, 2500);
        vizchg = true;
        paintGadget();
        return;
      }
    </script>
  </head>
  <body onload="onLoad()">
    <div id="targets"></div>
    <g:background id="canvas" src="images/canvas.png" style="position: absolute; top: 0; left: 0; width: 156; height: 200; z-index: -999;" opacity="0" />
  </body>
</html>

settings.html (settings window):

<html>
  <head>
    <style>
      body { width: 250px; height: 800px; padding: 0px; margin: 0px; font-family: Tahoma; }
      body,p,div,span,td { font-size: 9pt; }
      label { font-weight: bold; }
      input,select { font: Arial; font-size: 9pt; }
      table { width: 100%; }
    </style>
    <script>
      var background, maxBackgrounds = 3, theme = 1, maxThemes = 7;

      function updateBackground()
      {
        var x = 84, y = 47, m;
        canvas.removeObjects();

        canvas.addImageObject('images/backgrounds/background' + background + '.png', x, y); 
        m = canvas.addImageObject('images/meterblue.png', x + 24, y + 34);   
        m.width = (0.25 * 128);
        m.left = x + 24 - ((128 - m.width) / 2);

        canvas.addImageObject('images/drives/drive3' + theme + '.png', x, y);
        canvas.addTextObject('Vista (C:)', 'Segoe UI', 11, 'white', x + 58, y + 5);
        canvas.addTextObject('40GB / 75%', 'Segoe UI', 10, 'white', x + 58, y + 17);

        //y -= 20;

        //canvas.addImageObject('images/backgrounds/background' + background + '.png', x, y); 
        //m = canvas.addImageObject('images/meterorange.png', x + 24, y + 34);  
        //m.width = (0.937 * 128);
        //m.left = x + 24 - ((128 - m.width) / 2);

        //canvas.addImageObject('images/drives/drive3.png', x, y);
        //canvas.addTextObject('Apps (D:)', 'Segoe UI', 11, 'white', x + 58, y + 5);
        //canvas.addTextObject('10GB / 6.3%', 'Segoe UI', 10, 'white', x + 58, y + 17);

        canvas.addImageObject('images/drives/drive3' + theme + '.png', x-85, y+130);
        canvas.addImageObject('images/drives/drive2' + theme + '.png', x-85, y+172);
        canvas.addImageObject('images/drives/drive4' + theme + '.png', x-85, y+215);
        canvas.addImageObject('images/drives/drive5' + theme + '.png', x-85, y+258);
      }

      function onBackground()
      {
        var e = window.event, o = e.srcElement, b = o.getAttribute('base');

        o.src = 'images/settings/' + b + (e.type == 'mouseover' || e.type == 'mouseup' ? 'hover': (e.type == 'mousedown' ? 'pressed': '')) + '.png';

        if (e.type == 'mouseup') 
        {
          if (b == 'next') background++; else background--;
          if (background < 1) background = maxBackgrounds;
          if (background > maxBackgrounds) background = 1;

          updateBackground();        
        }
      }

      function onTheme()
      {
        var e = window.event, o = e.srcElement, b = o.getAttribute('base');

        o.src = 'images/settings/' + b + (e.type == 'mouseover' || e.type == 'mouseup' ? 'hover': (e.type == 'mousedown' ? 'pressed': '')) + '.png';

        if (e.type == 'mouseup') 
        {
          if (b == 'next') theme++; else theme--;
          if (theme < 1) theme = maxThemes;
          if (theme > maxThemes) theme = 1;

          updateBackground();        
        }
      }


      function onClose(event)
      {
        if (event.closeAction == event.Action.commit) 
        {
          System.Gadget.Settings.write("background", background);
          System.Gadget.Settings.write("theme",      theme);
          System.Gadget.Settings.write("showpc",     document.boxes.mypc.checked ? 2 : 1);
          System.Gadget.Settings.write("shownet",    document.boxes.netw.checked ? 2 : 1);

          System.Gadget.Settings.write("remove",     document.boxes.remove.checked ? 2 : 1);
          System.Gadget.Settings.write("local",      document.boxes.local.checked ? 2 : 1);
          System.Gadget.Settings.write("network",    document.boxes.network.checked ? 2 : 1);
          System.Gadget.Settings.write("media",      document.boxes.media.checked ? 2 : 1);
        }

        event.cancel = false;

//      System.Gadget.beginTransition();
//      window.setTimeout(endtransit, 400); 
      }

/*    function endtransit() {
        System.Gadget.endTransition(System.Gadget.TransitionType.morph, 0.1);
      }*/


      function onLoad() 
      {
        var box;
        System.Gadget.onSettingsClosing = onClose;

        background = System.Gadget.Settings.read("background");
        if (background == 0) background = 2;

        theme = System.Gadget.Settings.read("theme");
        if (theme == 0) theme = 1;

        System.Gadget.Settings.read("remove")  == 2 ? document.boxes.remove.checked  = true : false;
        System.Gadget.Settings.read("local")   == 2 ? document.boxes.local.checked   = true : false;
        System.Gadget.Settings.read("network") == 2 ? document.boxes.network.checked = true : false;
        System.Gadget.Settings.read("media")   == 2 ? document.boxes.media.checked   = true : false;

        System.Gadget.Settings.read("showpc")  == 2 ? document.boxes.mypc.checked   = true : false;
        System.Gadget.Settings.read("shownet") == 2 ? document.boxes.netw.checked   = true : false;

        updateBackground();
      }
    </script>
  </head>
  <body onload="onLoad()">
    <g:background id="canvas" src="images/settings/desktop.png" style="position: absolute; left: 1; top: 1; z-index: -999;" />
    <div style="position: absolute; left: 0; top: 147px;">
      <table cellspacing="0" cellpadding="0">
        <tr>
          <td style="width: 33%; padding-right: 10px;" align="right"><img src="images/settings/previous.png" base="previous" style="cursor: hand;" onmouseover="onBackground();" onmouseout="onBackground();" onmousedown="onBackground();" onmouseup="onBackground();" /></td>
          <td style="width: 33%;" align="center"><label>Backgrounds</label></td>
          <td style="width: 33%; padding-left: 10px;" align="left"><img src="images/settings/next.png" base="next" style="cursor: hand;" onmouseover="onBackground();" onmouseout="onBackground();" onmousedown="onBackground();" onmouseup="onBackground();" /></td>
         </tr>
         <tr>
          <td style="width: 33%; padding-right: 10px;" align="right"><img src="images/settings/previous.png" base="previous" style="cursor: hand;" onmouseover="onTheme();" onmouseout="onTheme();" onmousedown="onTheme();" onmouseup="onTheme();" /></td>
          <td style="width: 33%;" align="center"><label>Icon Theme</label></td>
          <td style="width: 33%; padding-left: 10px;" align="left"><img src="images/settings/next.png" base="next" style="cursor: hand;" onmouseover="onTheme();" onmouseout="onTheme();" onmousedown="onTheme();" onmouseup="onTheme();" /></td>
        </tr>
      </table>
      <table cellspacing="0" cellpadding="0" style="margin-top: 15px;margin-left:60px;">
        <tr><td>
          <form name="boxes">
            <input type="checkbox" name="local">
                <font style="font-size: 8pt;">Local Drives</font><p>
            <input type="checkbox" name="remove">
                <font style="font-size: 8pt;">Removable Drives</font><p>
            <input type="checkbox" name="network">
                <font style="font-size: 8pt;">Network Drives</font><p>
            <input type="checkbox" name="media">
                <font style="font-size: 8pt;">Media Drives</font><p>
            <input type="checkbox" name="mypc">
                <font style="font-size: 8pt;">My Computer link</font><br>
            <input type="checkbox" name="netw">
                <font style="font-size: 8pt;">Network Link</font>
            </form>
        </td></tr>
      </table>
    </div>
  </body>
</html>

UPDATE:

Here is the full gadget source if it helps:

https://www.mediafire.com/?c8h1271714sp6tz

There is always a chance that their is a leak in one of the drivers installed on your system that causes this. However, when looking at that javascript code there is a pattern that caused issues in the past and has a resolution now.

The main loop of the Gadget looks like this:

function paintGadget() {
    // repaint/rebuild all UI elelments
    // remove all elements
    targets.innerHtml = '';
    // buildup
     var o = document.createElement('DIV');
     o.onclick = openDrive;
     targets.appendChild(o);
}

function openDrive() {
}

window.setInterval(paintGadget, 2500);

which basically means: call paintGadget every 2.5 seconds, for ever

This should be fine if the javascript engine and its resources are garbage collected when they are no longer in any scope. And this where things might go wromg due to sloppy programming.

Based on the answer from user dsg we learn that eventlisteners are a root cause for garbage collection to fail.

To overcome this problem we have to replace the line targets.innerHtml = ''; in the function paintGadget with an implementation that removes the eventhandlers on every element before removing the element it self, like so:

while(targets.firstChild) {
    var ch = targets.firstChild;
    ch.onclick = null;
    targets.removeChild(ch);              
}

As said in the introduction, paintGadget does more in particular with the canvas where it follows a similar pattern, remove everything and recreate. If there is a leak in there a reimplementation of that is needed as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM