Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find out what is referencing a detached DOM tree using Chrome Dev Tools

I'm trying to figure out exactly how to get which variable is referencing something in a detached DOM tree. I've isolated the problem down to two simple views, and I'm trying to use Chrome Dev Tools (in the comparison view) to find out what is referencing the detached nodes. I've attached an image of dev tools...

Chrome dev tools snapshot

The bottom part of dev tools shows that el of HomeView has created a div that became detached. But I'm not sure where to go from there.

I've read through a bunch of stack overflow posts and blog posts on pinpointing memory leaks, but I still can't figure this one out. I know that Backbone is especially likely to cause memory leaks, so I've implemented the "zombie-killing" techniques, but the memory leak is still there. Here are my views:

Help View

    // Generated by CoffeeScript 1.6.3
    (function() {
      var __hasProp = {}.hasOwnProperty,
        __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };

      define(['jquery', 'jquerymobile', 'underscore', 'backbone'], function($, Mobile, _, Backbone) {
        var HelpView, _ref;
        return HelpView = (function(_super) {
          __extends(HelpView, _super);

          function HelpView() {
            _ref = HelpView.__super__.constructor.apply(this, arguments);
            return _ref;
          }

          HelpView.prototype.initialize = function() {
            return _.bindAll(this, "render", "jqdisplay", "close");
          };

          HelpView.prototype.render = function() {
            $(this.el).html("Help View");
            return this;
          };

          HelpView.prototype.jqdisplay = function() {};

          HelpView.prototype.close = function() {
            console.log('THIS', this);
            console.log($(this.el)[0].parentNode);
            $(this.el)[0].parentNode.removeChild($(this.el)[0]);
            this.undelegateEvents();
            $(this.el).removeData().unbind();
            this.remove();
            this.unbind();
            Backbone.View.prototype.remove.call(this);
            return delete this;
          };

          return HelpView;

        })(Backbone.View);
      });

    }).call(this);

Home View

    // Generated by CoffeeScript 1.6.3
    (function() {
      var __hasProp = {}.hasOwnProperty,
        __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };

      define(['jquery', 'jquerymobile', 'underscore', 'backbone'], function($, Mobile, _, Backbone) {
        var HomeView, _ref;
        return HomeView = (function(_super) {
          __extends(HomeView, _super);

          function HomeView() {
            _ref = HomeView.__super__.constructor.apply(this, arguments);
            return _ref;
          }

          HomeView.prototype.initialize = function() {
            return _.bindAll(this, "render", "jqdisplay", "close");
          };

          HomeView.prototype.render = function() {
            $(this.el).html("Home View");
            return this;
          };

          HomeView.prototype.jqdisplay = function() {};

          HomeView.prototype.close = function() {
            console.log('THIS', this);
            console.log($(this.el)[0].parentNode);
            $(this.el)[0].parentNode.removeChild($(this.el)[0]);
            this.undelegateEvents();
            $(this.el).removeData().unbind();
            this.remove();
            this.unbind();
            Backbone.View.prototype.remove.call(this);
            return delete this;
          };

          return HomeView;

        })(Backbone.View);
      });

    }).call(this);

...and then I call the "close" method of each view in a method in my router...

  MyRouter.prototype.showView = function(view) {
    console.log('THIS', this);
    console.log("next view", view);
    console.log(this.currentView);
    if (this.currentView) {
      console.log('closing the current view...', this.currentView);
      console.log('starting', $('[data-role="content"]').html());
      this.currentView.close();
      delete this.currentView;
      console.log('remaining', $('[data-role="content"]').html());
      console.log('should be empty', this.currentView);
    }
    this.currentView = view;
    this.currentView.render();
    $('[data-role="content"]').html(this.currentView.el);
    if (this.currentView.jqdisplay) {
      return this.currentView.jqdisplay();
    }
  };

A live demo of the leak is here: http://bit.ly/15xPrW7. The leak-triggering behavior is using the menu to navigate between the two pages.

Any help would be appreciated! Thank you!

like image 658
Jacquerie Avatar asked Jul 20 '13 04:07

Jacquerie


1 Answers

Ug coffeescript.

That aside, anytime you're memory leak hunting with jquery on the page you need to disable the jquery dom cache. In my brief playing with the example site you've linked to, I'm pretty sure some of the detached nodes I'm seeing are in that cache.

$.expr.cacheLength = 1;

This is very poorly documented, but should help you hunt down where your actual leaks are coming from.

like image 109
Will Shaver Avatar answered Sep 17 '22 12:09

Will Shaver