Opened 8 years ago

Closed 7 years ago

Last modified 4 years ago

#13112 closed defect (patchwelcome)

TabContainer: very slow tab changed, if content is big

Reported by: Sergey Kravchenko Owned by:
Priority: high Milestone: future
Component: Dijit Version: 1.6.1
Keywords: Cc:
Blocked By: Blocking:

Description (last modified by bill)

TabContainer is very slow tab changed, if content is big.

showcase:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <!--
  Firebug profile:
      from Tab1 to Tab2 = (4467.993ms, 280806 calls)
      from Tab2 to Tab1 = (43.511ms, 808 calls)
  -->
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <link rel="stylesheet" href="http://yandex.st/dojo/1.6.1/dojo/resources/dojo.css"/>
  <link rel="stylesheet" href="http://yandex.st/dojo/1.6.1/dijit/themes/claro/claro.css"/>
  <script type="text/javascript" src="http://yandex.st/dojo/1.6.1/dojo/dojo.xd.js.uncompressed.js"></script>
</head>
<body class="claro">
<div style="width: 350px; height: 290px"><div id="tc1-prog"></div></div>

<script type="text/javascript">
  dojo.require("dijit.layout.TabContainer");
  dojo.require("dijit.layout.ContentPane");
  dojo.addOnLoad(function() {
    var tc = new dijit.layout.TabContainer({
      style: "height: 100%; width: 100%;"
    }, "tc1-prog");
    var cp1 = new dijit.layout.ContentPane({
      title: "Tab1",
      content: "from Tab1 to Tab2 = slow<br>from Tab2 to Tab1 = fast"
    });
    tc.addChild(cp1);
    var cp2 = new dijit.layout.ContentPane({
      title: "Tab2",
      content: "very big...."
    });
    tc.addChild(cp2);
    //// big content...
    var ss = '';
    for (var i = 0; i < 20000; i++) {
      ss += '<span class="dijit dijitReset dijitInline dijitToolbarText">label-' + i + '</span>';
    }
    cp2.set("content", ss);
    ////
    tc.startup();
  });
</script>
</body>
</html>

Attachments (1)

attemptedFixes.patch (2.9 KB) - added by bill 8 years ago.
avoiding DOM computation but still slow

Download all attachments as: .zip

Change History (9)

comment:1 Changed 8 years ago by bill

Component: GeneralDijit
Description: modified (diff)

Hmm, the 280806 calls does sounds suspicious.

comment:2 Changed 8 years ago by Sergey Kravchenko

Reducing the number of calls to be corrected setting top-level node attribute "widgetId" (to simulate a widget). However, the total time is not appreciably diminished. Methods "_getPadExtents" and "_getMarginExtents" called each time you switch to the tab "Tab2" and executed a very long time.

new version showcase:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <!--
  Firebug profile:
      from Tab1 to Tab2 = (2186.261ms, 827 calls):
          _getPadExtents	    2	49.98%	1092.665ms
          _getMarginExtents	    2	49.36%	1079.093ms

      from Tab2 to Tab1 = (56.874ms, 949 calls)
  -->
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <link rel="stylesheet" href="http://yandex.st/dojo/1.6.1/dojo/resources/dojo.css"/>
  <link rel="stylesheet" href="http://yandex.st/dojo/1.6.1/dijit/themes/claro/claro.css"/>
  <script type="text/javascript" src="http://yandex.st/dojo/1.6.1/dojo/dojo.xd.js.uncompressed.js"></script>
</head>
<body class="claro">
<div style="width: 350px; height: 290px"><div id="tc1-prog"></div></div>

<script type="text/javascript">
  dojo.require("dijit.layout.TabContainer");
  dojo.require("dijit.layout.ContentPane");
  dojo.addOnLoad(function() {
    var tc = new dijit.layout.TabContainer({
      style: "height: 100%; width: 100%;"
    }, "tc1-prog");
    var cp1 = new dijit.layout.ContentPane({
      title: "Tab1",
      content: "from Tab1 to Tab2 = slow<br>from Tab2 to Tab1 = fast"
    });
    tc.addChild(cp1);
    var cp2 = new dijit.layout.ContentPane({
      title: "Tab2",
      content: "very big...."
    });
    tc.addChild(cp2);
    //// big content...
    var ss = '<div widgetId="__imitate_widget__">';
    for (var i = 0; i < 20000; i++) {
      ss += '<span class="dijit dijitReset dijitInline dijitToolbarText">label-' + i + '</span>';
    }
    ss += '</div>';
    cp2.set("content", ss);
    ////
    tc.startup();
  });
</script>
</body>
</html>

comment:3 Changed 8 years ago by bill

Milestone: tbdfuture

OK, well your results make sense.

When you switch tabs, ContentPaneResizeMixin is computing the content-box size of the new tab (this._contentBox), and that's taking a long time since the tab has so much data in it. Dijit is waiting for the browser.

I can defer the calculation of this._contentBox until/unless it's needed (and it isn't needed in this case), but it still takes a long time to switch tabs because TabContainer calls ContentPaneResizeMixin.resize(...) which needs to compute margins etc. in order to do the dojo.marginBox() call.

I can avoid that resize() call when we are simply sizing the pane to the same size it was before (since the TabContainer's size hasn't changed). However, then it still takes a long time, hanging in the ScrollingTabController._getScroll() method. Note that AFAICT _getScroll() isn't accessing anything to do with that big <div>.

Basically, at some point we end up doing a getComputedStyle() call that forces the browser to finish it's layout. So I think the delay is just inherent in having a <div> with so much data in it.

I'll leave this open for further consideration but I doubt there's anything dijit can do to make this faster.

Changed 8 years ago by bill

Attachment: attemptedFixes.patch added

avoiding DOM computation but still slow

comment:4 Changed 8 years ago by Sergey Kravchenko

Your patch did not help much, as you promised.

My experiments have shown that a simple 'div' with nested 'div', each stack-like panel and a simple switch "display: none / display: block" solves my problem.

May need to simplify StackContainer? (at least optionally) for this behavior?

showcase:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <link rel="stylesheet" href="http://yandex.st/dojo/1.6.1/dojo/resources/dojo.css"/>
  <link rel="stylesheet" href="http://yandex.st/dojo/1.6.1/dijit/themes/claro/claro.css"/>
  <script type="text/javascript" src="http://yandex.st/dojo/1.6.1/dojo/dojo.xd.js.uncompressed.js"></script>
</head>
<body class="claro">
<div id="bb">
  <div id="b1">show Tab1</div>

  <div id="b2">show Tab2</div>
  <div id="b3">grow size</div>
</div>
<div id="tp" style="width: 350px; height: 290px">
  <div id="cp1" class="dijitTabContent" style="width:100%;height:100%">
    Tab1
    <div class="dijitTabContent">Tab1 nested</div>
  </div>
  <div id="cp2" class="dijitTabContent" style="display:none;width:100%;height:100%;"></div>

</div>
<script type="text/javascript">
  dojo.require("dijit.layout.TabContainer");
  dojo.require("dijit.layout.ContentPane");
  dojo.ready(function() {
    var cp1 = new dijit.layout.ContentPane({
      title: "Tab1"
    }, "cp1");
    var cp2 = new dijit.layout.ContentPane({
      title: "Tab2",
      content: "very big...."
    }, "cp2");
    //// big content...
    var ss = '<div widgetId="__imitate_widget__">';
    for (var i = 0; i < 20000; i++) {
      ss += '<span class="dijit dijitReset dijitInline dijitToolbarText">label-' + i + '</span>';
    }
    ss += '</div>';
    cp2.set("content", ss);
    ////
    var b1 = new dijit.form.Button({onClick:function() {
      dojo.style(cp2.domNode, "display", "none");
      dojo.style(cp1.domNode, "display", "block");
    }}, "b1");
    var b2 = new dijit.form.Button({onClick:function() {
      dojo.style(cp1.domNode, "display", "none");
      dojo.style(cp2.domNode, "display", "block");
    }}, "b2");
    var b3 = new dijit.form.Button({onClick:function() {
      dojo.style("tp", {width:"700px", height:"500px"});
    }}, "b3");
  });
</script>
</body>
</html>

comment:5 Changed 8 years ago by tendrid

Has there been any work on this? I'm writing a chat client in 1.7.2, and im having the exact same issue. The longer the chat content, the longer it takes to switch to the tab.

comment:6 Changed 7 years ago by bill

Resolution: patchwelcome
Status: newclosed

Nope sorry, AFAIK there's nothing that can be done.

comment:7 in reply to:  6 Changed 4 years ago by Housik

Replying to bill:

Nope sorry, AFAIK there's nothing that can be done.

There is something, what can be done. Switching tab is slow, because it can have a (huge) content attached to DOM, even when Tab content is not visible. You can speed up things by temporarily removing not visible content of all not selected Tabs from DOM.

You can use tabContainer.watch("selectedChildWidget", function(property, notSelectedWidget, selectedWidget), then find all child nodes of notSelectedWidget by notSelectedWidget.getChildren() and use notSelectedWidget.removeChild(childNode) to temporarily remove it from DOM. Store childNode to notSelectedWidget.tempHiddenNodes[] = childNode.

Before removal a node from DOM, make sure it was fully initialized. You can postpone removing child nodes for some time (1 second), when it has sub widgets working with DOM (menus, etc) - to ensure a reference to DOM is available when tab was hidden before it was fully initialized. You may use tab.defer() method for deferred method run.

When notSelectedWidget became selectedWidget, use selectedWidget.addChild() for all selectedWidget.tempHiddenNodes to add hidden nodes to DOM. Do the same for notSelectedWidget, before notSelectedWidget is destroyed (use aspect.before for example), because some child Dijits can access DOM on destroy.

You can create new TabContainer? dijit implementing this and you will speed up whole Tab layout immediately. Just test is, so any sub widget has DOM available, when need it.

Would be nice to have it as an option directly in dijit/layout/TabContainer.

comment:8 Changed 4 years ago by bill

Hmm, when are you reattaching the content? You saw all the computations I listed in https://bugs.dojotoolkit.org/ticket/13112#comment:3. My guess is that in your benchmarks you delayed attaching the content until after those computations. But that will surely break something.

Note: See TracTickets for help on using tickets.