text: //{"duration":8,"time":"2012-02-25T10:55:01","success":false,"cycleCount":6,"flushCount":5,"calcCount":7,"orphans":0,"layouts":[{"allDone":true,"done":true,"id":"autocomponent-1193","type":"autocomponent","name":"headercontainer-1115<autocomponent>","blocks":[],"boxParent":null,"isBoxParent":null,"triggers":[],"orphan":false,"heightModel":"configured","widthModel":"calculated","children":[],"duration":0,"totalTime":0,"count":1},{"allDone":false,"done":false,"id":"gridcolumn-1194","type":"gridcolumn","name":"headercontainer-1115<gridcolumn>","blocks":[],"boxParent":null,"isBoxParent":null,"triggers":[{"name":"gridview-1118.height","prop":"height","value":null,"dirty":false,"missing":true,"setBy":"??"},{"name":"gridcolumn-1116.height","prop":"height","value":22,"dirty":false,"missing":false,"setBy":"gridcolumn-1116<columncomponent>"},{"name":"headercontainer-1115.contentWidth","prop":"contentWidth","value":335,"dirty":false,"missing":false,"setBy":"headercontainer-1115<gridcolumn>"},{"name":"headercontainer-1115.height","prop":"height","value":0,"dirty":false,"missing":false,"setBy":"headercontainer-1115<autocomponent>"},{"name":"headercontainer-1115.width","prop":"width","value":335,"dirty":false,"missing":false,"setBy":"headercontainer-1115<autocomponent>"},{"name":"numbercolumn-1117.height","prop":"height","value":22,"dirty":false,"missing":false,"setBy":"numbercolumn-1117<columncomponent>"}],"orphan":false,"heightModel":"configured","widthModel":"calculated","children":[{"allDone":true,"done":true,"id":"columncomponent-1195","type":"columncomponent","name":"gridcolumn-1116<columncomponent>","blocks":[],"boxParent":null,"isBoxParent":true,"triggers":[{"name":"gridcolumn-1116.containerChildrenSizeDone:dom","prop":"containerChildrenSizeDone","value":true,"dirty":false,"missing":false,"setBy":"??"},{"name":"gridcolumn-1116.width:dom","prop":"width","value":168,"dirty":false,"missing":false,"setBy":"headercontainer-1115<gridcolumn>"}],"orphan":false,"heightModel":"shrinkWrap (calculatedFromShrinkWrap)","widthModel":"calculated","children":[],"duration":0,"totalTime":0,"count":2},{"allDone":true,"done":true,"id":"columncomponent-1197","type":"columncomponent","name":"numbercolumn-1117<columncomponent>","blocks":[],"boxParent":null,"isBoxParent":true,"triggers":[{"name":"numbercolumn-1117.containerChildrenSizeDone:dom","prop":"containerChildrenSizeDone","value":true,"dirty":false,"missing":false,"setBy":"??"},{"name":"numbercolumn-1117.width:dom","prop":"width","value":167,"dirty":false,"missing":false,"setBy":"headercontainer-1115<gridcolumn>"}],"orphan":false,"heightModel":"shrinkWrap (calculatedFromShrinkWrap)","widthModel":"calculated","children":[],"duration":2,"totalTime":2,"count":2}],"duration":4,"totalTime":6,"count":2}],"statsById":{"columncomponent-1195":{"duration":0,"count":2},"columncomponent-1197":{"duration":2,"count":2},"autocomponent-1193":{"duration":0,"count":1},"gridcolumn-1194":{"duration":4,"count":2}},"statsByType":{"columncomponent":{"duration":2,"layoutCount":2,"count":4},"autocomponent":{"duration":0,"layoutCount":1,"count":1},"gridcolumn":{"duration":4,"layoutCount":1,"count":2}},"flushTime":1,"flushInvalidateTime":1,"flushInvalidateCount":1,"flushLayoutStats":{"completeLayout":{"count":2,"time":0}},"totalTime":8,"num":5}
  Ext.Loader.setConfig({
      enabled: true
  });
  
  Ext.Loader.setPath({
      'Ext.ux': '../ux/',
      'PageAnalyzer': './'
  });
  
  Ext.require([
      'Ext.data.*',
      'Ext.tree.*',
      'Ext.container.Viewport',
      'Ext.layout.container.Border',
      'Ext.History',
      'Ext.tab.Panel',
      'Ext.grid.column.Action',
      'Ext.form.field.TextArea',
      'Ext.form.field.Number',
      'Ext.toolbar.TextItem',
      'Ext.layout.container.Table',
      'PageAnalyzer.models.LayoutTreeNode',
      'PageAnalyzer.models.ComponentTreeNode',
      'PageAnalyzer.Console',
      'PageAnalyzer.Summary',
      'Ext.ux.IFrame',
      'Ext.state.Manager',
      'Ext.state.CookieProvider'
  ]);
  
  Ext.define('PageAnalyzer.MainForm', {
      statsGatherCount: 0,
  
      layoutTpl: [
          '<tpl if="isBoxParent">',
              '<img class="x-tree-icon" src="resources/images/boxParent.gif">',
          '</tpl>',
          '{name:this.encode}',
          {
              encode: function (value) {
                  return Ext.htmlEncode(value);
              }
          }
      ],
  
      runTpl: [
          'Run #{num} ({time:this.date})',
          {
              date: function (value) {
                  return Ext.Date.format(value, "Y-m-d H:i:s");
              }
          }
      ],
  
      triggerTpl: [
          '<div class="pgan-{[values.missing ? "missing" : "available"]}-value">',
              '{name} (={[String(values.value) || "?"]}) - dirty: {dirty} - setBy: {setBy:this.encode}',
          '</div>',
          {
              encode: function (value) {
                  return Ext.htmlEncode(value);
              }
          }
      ],
  
      constructor: function (config) {
          Ext.state.Manager.setProvider(Ext.create('Ext.state.CookieProvider'));
  
          var me = this;
  
          Ext.apply(me, config);
  
          me.noCharts = me.getOption('nocharts');
          me.runNumber = 0;
  
          me.viewport = Ext.widget(me.createViewport());
          me.target = me.targetFrame.getWin();
  
          me.reloadBtn = me.viewport.down('#reloadBtn');
          me.stateText = me.viewport.down('#stateText');
  
          if (me.target) {
              setInterval(function() {
                  me.updateConnectedState();
              }, 100);
          } else {
              me.reloadBtn.setDisabled(true);
          }
  
          Ext.History.init(function () {
              Ext.History.on({
                  change: 'onHistoryChange',
                  scope: me
              });
  
              me.onHistoryChange(Ext.History.getToken());
          });
      },
  
      roundTimestamp: function(num) {
          return Math.round(num * 100) / 100;
      },
  
      createLayoutTree: function() {
          var me = this,
              store = new Ext.data.TreeStore({
                  proxy: 'memory',
                  model: 'PageAnalyzer.models.LayoutTreeNode'
              }),
              tree = {
                  xtype: 'treepanel',
                  store: store,
                  rootVisible: false,
                  useArrows: true,
                  title: 'Layouts',
                  region: 'center',
                  viewConfig: {
                      getRowClass: function (record) {
                          return 'pgan-' + record.data.type;
                      }
                  },
                  columns: [
                      {
                          xtype: 'treecolumn',
                          text: 'Layout',
                          flex: 1,
                          hideable: false,
                          draggable: false,
                          dataIndex: 'text'
                      },
                      {
                          text: 'Triggers',
                          width: 200,
                          dataIndex: 'triggers'
                      },
                      {
                          text: 'Blocks',
                          width: 125,
                          hidden: true,
                          dataIndex: 'blocks',
                          id: 'blocksCol'
                      },
                      {
                          text: 'Width',
                          width: 90,
                          dataIndex: 'widthModel'
                      },
                      {
                          text: 'Height',
                          width: 90,
                          dataIndex: 'heightModel'
                      },
                      {
                          text: 'Box Parent',
                          width: 100,
                          hidden: true,
                          dataIndex: 'boxParent',
                          id: 'boxParentCol'
                      },
                      {
                          text: 'Time',
                          width: 70,
                          dataIndex: 'duration',
                          id: 'durationCol',
                          renderer: me.roundTimestamp
                      },
                      {
                          text: 'Calls',
                          width: 50,
                          dataIndex: 'count',
                          id: 'countCol'
                      },
                      {
                          text: 'Avg Time',
                          width: 70,
                          id: 'avgCol',
                          renderer: function(value, metadata, rec) {
                              return me.roundTimestamp(rec.data.duration / rec.data.count);
                          }
                      },
                      {
                          text: 'Tot Time',
                          width: 70,
                          dataIndex: 'totalTime',
                          id: 'totalTimeCol',
                          renderer: me.roundTimestamp
                      },
                      {
                          menuDisabled: true,
                          sortable: false,
                          xtype: 'actioncolumn',
                          hideable: false,
                          width: 40,
                          items: [{
                              icon: 'resources/images/delete.gif',
                              iconCls: 'pgan-delete-row',
                              tooltip: 'Delete this run',
                              handler: this.onDeleteLayoutRun,
                              scope: this
                          }, {
                              icon: 'resources/images/info.gif',
                              iconCls: 'pgan-show-row-data',
                              tooltip: 'Show raw data for this run',
                              handler: this.onShowLayoutRunRawData,
                              scope: this
                          }]
                      }
                  ]
              };
  
          this.layoutTree = Ext.widget(tree);
  
          this.layoutTree.getSelectionModel().on({
              selectionchange: this.onLayoutSelectionChange,
              scope: this
          });
  
          return this.layoutTree;
      },
  
      createComponentTree: function() {
          var me = this,
              store = new Ext.data.TreeStore({
                  proxy: 'memory',
                  model: 'PageAnalyzer.models.ComponentTreeNode'
              }),
              compTree = {
                  xtype: 'treepanel',
                  store: store,
                  rootVisible: false,
                  useArrows: true,
                  title: 'Components',
                  region: 'center',
                  viewConfig: {
                      getRowClass: function (record) {
                          var data = record.data;
  
                          if (data.isComponent) {
                             return 'pgan-component';
                          } else {
                             return 'pgan-noncomponent';
                          }
                      }
                  },
                  columns: [
                      {
                          xtype: 'treecolumn',
                          text: 'Component',
                          flex: 1,
                          hideable: false,
                          draggable: false,
                          width: 450,
                          dataIndex: 'text'
                      },
                      {
                          text: 'Width',
                          width: 90,
                          hidden: true,
                          dataIndex: 'width'
                      },
                      {
                          text: 'Height',
                          width: 90,
                          hidden: true,
                          dataIndex: 'height'
                      },
                      {
                          text: 'X',
                          width: 90,
                          hidden: true,
                          dataIndex: 'x'
                      },
                      {
                          text: 'Y',
                          width: 90,
                          hidden: true,
                          dataIndex: 'y'
                      },
                      {
                          text: 'CSS Class',
                          width: 100,
                          dataIndex: 'cssClass',
                          id: 'cssClass'
                      },
                      {
                          text: 'XType',
                          width: 100,
                          dataIndex: 'xtype',
                          id: 'xtypeCol'
                      },
                      {
                          text: 'Rendered',
                          width: 100,
                          dataIndex: 'rendered',
                          id: 'renderedCol',
                          hidden: true
                      },
                      {
                          text: 'Hidden',
                          width: 100,
                          dataIndex: 'hidden',
                          id: 'hiddenCol',
                          hidden: true
                      },
                      {
                          text: 'IsContainer',
                          width: 100,
                          dataIndex: 'isContainer',
                          id: 'isContainerCol',
                          hidden: true
                      },
                      {
                          menuDisabled: true,
                          sortable: false,
                          xtype: 'actioncolumn',
                          hideable: false,
                          width: 20,
                          items: [{
                              icon: 'resources/images/info.gif',
                              iconCls: 'pgan-display-comp-spec',
                              tooltip: 'Display Component Test Spec',
                              handler: this.onDisplayLayoutSpec,
                              scope: this
  
                          }]
                      }
                  ]
              };
  
          me.componentTree = Ext.widget(compTree);
          return me.componentTree;
      },
  
      createPerfPanel: function() {
          this.perfPanel = this.noCharts ? null : (Ext.widget({
              xtype: 'panel',
              title: 'Performance',
              layout: 'fit',
              border: false,
              tbar: [
                  {
                      text: 'Clear',
                      handler: this.onClearStats,
                      scope: this
                  }
              ],
              items: [
                  new PageAnalyzer.Console({
                      itemId: 'perfcon'
                  })
              ]
          }));
  
          return this.perfPanel;
      },
  
      createLayoutPanel: function() {
          var me = this;
          me.layoutPanel = Ext.widget({
              xtype: 'panel',
              title: 'Layout',
              layout: 'border',
              tbar: [
                  {
                      text: 'Load Run',
                      iconCls: 'pgan-load-run',
                      handler: me.onLoadRun,
                      scope: me
                  },
                  {
                      text: 'Show All Triggers',
                      enableToggle: true,
                      handler: me.onShowAllTriggers,
                      scope: me
                  },
                  {
                      text: 'Clear',
                      handler: me.onClearLayouts,
                      scope: me
                  },
                  '->',
                  {
                      text: 'Capture Layout Spec',
                      handler: me.onDisplayLayoutSpec,
                      scope: me
                  }
              ],
              items: [
                  me.createLayoutTree(),
                  this.typeSummary = (this.noCharts ? null : new PageAnalyzer.Summary({
                      title: 'Summaries',
                      region: 'south',
                      height: '50%',
                      collapsible: true,
                      split: true
                  }))
              ]
          });
          return me.layoutPanel;
      },
  
      createComponentPanel: function() {
          var me = this;
          me.componentPanel = Ext.widget({
              xtype: 'panel',
              title: 'Components',
              layout: 'border',
              tbar: [
                  {
                      text: 'Generate Component Tree',
                      handler: me.loadComponentTree,
                      scope: me
                  }
              ],
              items: [
                  me.createComponentTree()
              ]
          });
          return me.componentPanel;
      },
  
      createTestPagePanel: function() {
          return this.targetFrame = Ext.create("Ext.ux.IFrame", {
              title: 'Page Under Test',
              hideMode: 'offsets'
          });
      },
  
      createViewport: function() {
          this.build = 1;
          var me = this,
              ret = {
                  xtype: 'viewport',
                  layout: 'fit',
                  items: [
                  {
                      xtype: 'panel',
                      layout: 'fit',
                      tbar: {
                          items: [
                              {
                                  xtype: 'tbtext',
                                  itemId: 'stateText',
                                  tdAttrs: {
                                      width: '100px'
                                  }
                              },
                              {
                                  xtype: 'tbtext',
                                  text: 'Test URL:',
                                  itemId: 'titleLbl',
                                  tdAttrs: {
                                      width: '100px'
                                  }
                              },
                              me.targetUrlField = Ext.widget({
                                  xtype: 'textfield',
                                  itemId: 'targetUrl',
                                  stateId: 'targetUrlField',
                                  selectOnFocus: true,
                                  flex: 1,
                                  enableKeyEvents: true,
                                  listeners: {
                                      keypress: function(f, e) {
                                          if (e.getKey() === 13) {
                                              me.onLoadPage();
                                          }
                                      }
                                  }
                              }),
                              {
                                  text: 'Load',
                                  itemId: 'reloadBtn',
                                  iconCls: 'pgan-refresh',
                                  handler: me.onLoadPage,
                                  scope: me,
                                  tdAttrs: {
                                      width: '100px'
                                  }
                              },
                              me.noCharts ? null : {
                                  xtype: 'numberfield',
                                  fieldLabel: 'Build',
                                  style: 'margin-right: 10px;',
                                  labelWidth: 30,
                                  width: 100,
                                  value: me.build,
                                  listeners: {
                                      change: function (field) {
                                          me.build = field.getValue();
                                      }
                                  }
                              },
                              me.noCharts ? null : {
                                  text: 'Update Stats',
                                  handler: me.onGatherStats,
                                  scope: me
                              },
                              me.noCharts ? null : {
                                  text: 'Reset',
                                  handler: this.onResetStats,
                                  scope: this
                              }
                          ]
                      },
                      items: [
                          Ext.create('Ext.tab.Panel', {
                              items:[
                                  me.createTestPagePanel(),
                                  me.createLayoutPanel(),
                                  me.createComponentPanel(),
                                  me.createPerfPanel()
                              ]
                          })
                      ]
                  }
              ]
          };
  
          return ret;
      },
  
      addLayoutChildren: function (parent, children) {
          var n = children.length,
              triggersTpl = Ext.XTemplate.getTpl(this, 'triggerTpl'),
              textTpl = Ext.XTemplate.getTpl(this, 'layoutTpl'),
              child, data, i, j, k, node, triggers;
  
          for (i = 0; i < n; ++i) {
              child = children[i];
              triggers = [];
              for (j = 0, k = child.triggers.length; j < k; ++j) {
                  triggers.push(triggersTpl.apply(child.triggers[j]));
              }
  
              data = {
                  text: textTpl.apply(child),
                  iconCls: 'pgan-layout' +
                      (child.allDone ? '' : (child.done ? '-partial' : '-not')) + '-done',
                  leaf: !child.children.length,
                  triggers: triggers.join(''),
                  blocks: child.blocks.join('<br>'),
                  boxParent: child.boxParent,
                  isBoxParent: child.isBoxParent,
                  heightModel: child.heightModel,
                  widthModel: child.widthModel,
                  type: 'layout',
                  duration: child.duration,
                  totalTime: child.totalTime,
                  count: child.count
              };
  
              if (data.boxParent) {
                  this.showBoxParentCol = true;
              }
  
              if (data.blocks) {
                  this.showBlocksCol = true;
              }
  
              node = new PageAnalyzer.models.LayoutTreeNode(data);
              parent.appendChild(node);
              this.addLayoutChildren(node, child.children);
          }
      },
  
      addLayoutRuns: function (run) {
          if (typeof run == 'string') {
              run = Ext.decode(run);
          }
          if (Ext.isArray(run)) {
              Ext.each(run, this.addLayoutRuns, this);
              return;
          }
          if (typeof run.time == 'string') {
              run.time = Ext.Date.parse(run.time, "Y-m-d\\TH:i:s");
          }
          if (typeof run.duration == 'string') {
              run.duration = parseInt(run.duration, 10);
          }
  
          //run.success = 0;
          run.num = ++this.runNumber;
  
          var node = new PageAnalyzer.models.LayoutTreeNode({
              text: Ext.XTemplate.getTpl(this, 'runTpl').apply(run),
              iconCls: 'pgan-' + (run.success ? 'good' : 'failed') + '-layout-run',
              type: 'layoutrun',
              duration: run.duration,
              totalTime: run.totalTime,
              count: 1
          });
  
          this.showBoxParentCol = this.showBlocksCol = false;
  
          node.rawData = run;
  
          node.appendChild(new PageAnalyzer.models.LayoutTreeNode({
              text: 'Flush',
              leaf: true,
              duration: run.flushTime,
              totalTime: run.flushTime,
              count: run.flushCount
          }));
  
          node.appendChild(new PageAnalyzer.models.LayoutTreeNode({
              text: 'Invalidate',
              leaf: true,
              duration: run.flushInvalidateTime,
              totalTime: run.flushInvalidateTime,
              count: run.flushInvalidateCount
          }));
  
          Ext.Object.each(run.flushLayoutStats, function(name, val) {
              node.appendChild(new PageAnalyzer.models.LayoutTreeNode({
                  text: (name == 'notifyOwner')
                      ? 'AfterLayout'
                      : Ext.String.capitalize(name),
                  leaf: true,
                  duration: val.time,
                  totalTime: val.time,
                  count: val.count
              }));
          });
  
          this.addLayoutChildren(node, run.layouts);
  
          this.layoutTree.getRootNode().appendChild(node);
  
          Ext.suspendLayouts();
          if (this.showBoxParentCol) {
              this.layoutTree.down('#boxParentCol').show();
          }
          if (this.showBlocksCol) {
              this.layoutTree.down('#blocksCol').show();
          }
          Ext.resumeLayouts(true);
      },
  
      getOption: function (opt) {
          var s = window.location.search;
          var re = new RegExp('(?:^|[&?])' + opt + '(?:[=]([^&]*))?(?:$|[&])', 'i');
          var m = re.exec(s);
  
          return m ? (m[1] === undefined ? true : m[1]) : false;
      },
  
      onDeleteLayoutRun: function (view, recordIndex, cellIndex, item, e, record) {
          record.parentNode.removeChild(record);
      },
  
      onHistoryChange: function (token) {
          this.targetUrlField.setValue(token);
      },
  
      onLayoutSelectionChange: function(selModel, records) {
          var run;
  
          if (records.length) {
              run = records[0];
              while (run && run.data.type != 'layoutrun') {
                  run = run.parentNode;
              }
  
              this.typeSummary.loadTypeSummary(run.rawData.statsByType);
          }
      },
  
      onShowLayoutRunRawData: function(view, recordIndex, cellIndex, item, e, record) {
          var text = Ext.JSON.encodeValue(record.rawData, '\n');
          Ext.widget({
              xtype: 'window',
              modal: true,
              title: 'Raw data for ' + record.data.text,
              //closeAction: 'close',
              autoShow: true,
              layout: 'fit',
              width: 500,
              height: 400,
              defaultFocus: 'rawData',
              items: [{
                  xtype: 'textarea',
                  value: text,
                  itemId: 'rawData',
                  readOnly: true,
                  selectOnFocus: true
              }]
          }).show();
          //this.rawDataTextArea.setValue(text);
      },
  
      onLoadRun: function() {
          var me = this;
  
          var window = new Ext.Window({
              title: 'Load Run',
              width: 400,
              height: 320,
              layout: 'fit',
              modal: true,
              items: [{
                  xtype: 'textarea'
              }],
              buttons: [{
                  text: 'OK',
                  handler: function() {
                      var text = window.down('textarea').getValue();
                      window.destroy();
  
                      me.addLayoutRuns(text);
                  }
              }, {
                  text: 'cancel',
                  handler: function() {
                      window.destroy();
                  }
              }]
          });
  
          window.show();
      },
  
      getCompNodeForComp: function(comp, refName) {
          return new PageAnalyzer.models.ComponentTreeNode({
              text: comp.id + (comp.itemId ? ' (' + comp.itemId  + ')' : ''),
              compId: comp.id || comp.itemId,
              xtype: comp.xtype,
              refName: refName || '',
              rendered: comp.rendered,
              hidden: comp.hidden,
              isContainer: comp.isContainer,
              isElement: false,
              isComponent: true,
              iconCls: 'pgan-' +
                  ((comp.rendered && !comp.hidden) ? 'rendered' : 'unrendered') +
                  (comp.isContainer ? '-container' : '-component')
          });
      },
  
      getCompNodeForElem: function(el, root, refName) {
          root = root ? root.el : el;
          return new PageAnalyzer.models.ComponentTreeNode({
              text: refName,
              width: el.getWidth(),
              height: el.getHeight(),
              x: root ? (el.getX() - root.getX()) : el.getX(),
              y: root ? (el.getY() - root.getY()) : el.getY(),
              cssClass: el.dom ? el.dom.className : undefined,
              refName: refName,
              isContainer: false,
              isComponent: false,
              isElement: true,
              hidden: !el.isVisible(true),
              iconCls: el.isVisible(true) ? 'pgan-visible-element' : 'pgan-hidden-element'
          });
      },
  
      getComponentTreeNodes: function(comps) {
          var me = this,
              compNodes = [],
              refName = '';
  
          if (!Ext.isArray(comps)) {
              comps = [comps];
          }
  
          Ext.each(comps, function(comp) {
              var container = comp.ownerCt,
                  node = me.getCompNodeForComp(comp, refName),
                  items = new PageAnalyzer.models.ComponentTreeNode({
                      refName: 'items',
                      iconCls: 'pgan-' + (comp.rendered ? 'rendered' : 'unrendered') + '-container',
                      text: 'items',
                      isComponent: false
                  }),
                  dockedItems = new PageAnalyzer.models.ComponentTreeNode({
                      refName: 'dockedItems',
                      iconCls: 'pgan-' + (comp.rendered ? 'rendered' : 'unrendered') + '-container',
                      text: 'dockedItems',
                      isComponent: false
                  }),
                  children;
  
              node.comp = comp;
  
              Ext.Object.each(comp, function(name, val){
                  if (name != 'container' &&
                      name != 'renderTo' &&
                      name != 'constrainTo' &&
                      name != 'focusEl' &&
                      val &&
                      val.dom &&
                      val.dom != me.target.document) {
                      var child = me.getCompNodeForElem(val, container, name);
                      node.appendChild(child);
                  }
              });
  
              if (comp.items && comp.items.items && comp.items.items.length) {
                  children = me.getComponentTreeNodes(comp.items.items);
                  Ext.each(children, function(child) {
                      items.appendChild(child);
                  });
                  node.appendChild(items);
              }
  
              if (comp.dockedItems && comp.dockedItems.items && comp.dockedItems.items.length) {
                  children = me.getComponentTreeNodes(comp.dockedItems.items);
                  Ext.each(children, function(child) {
                      dockedItems.appendChild(child);
                  });
                  node.appendChild(dockedItems);
              }
  
              compNodes.push(node);
          });
  
          return compNodes;
      },
  
      loadComponentTree: function() {
          var me = this,
              all = me.getTopLevelComponents(),
              nodes = me.getComponentTreeNodes(all),
              root = me.componentTree.getRootNode();
          root.removeAll();
          Ext.each(nodes, function(node){
              root.appendChild(node);
          });
      },
  
      getTopLevelComponents: function () {
          var me = this,
              all = me.target.Ext.ComponentManager.all.getArray(),
              top = [];
  
          Ext.each(all, function(comp){
              if(!comp.ownerCt) {
                  top.push(comp);
              }
          });
          return top;
      },
  
      onDisplayLayoutSpec: function (view, recordIndex, cellIndex, item, e, record) {
          var testSpec = record.getJasmineSpec(),
              window = new Ext.Window({
                  title: 'Component Test Spec',
                  width: 400,
                  height: 320,
                  layout: 'fit',
                  modal: true,
                  maximizable: true,
                  items: [{
                      xtype: 'textarea',
                      value: testSpec,
                      selectOnFocus: true
                  }],
                  buttons: [{
                      text: 'OK',
                      handler: function() {
                          window.destroy();
                      }
                  }]
              });
  
          window.show();
      },
  
      onClearLayouts: function() {
          this.layoutTree.getRootNode().removeAll();
      },
  
      onShowAllTriggers: function (button) {
          var comp = this.layoutTree;
  
          if (button.pressed) {
              comp.addCls('pgan-show-all-triggers');
          } else {
              comp.removeCls('pgan-show-all-triggers');
          }
  
          comp.updateLayout();
      },
  
      updateLayoutRuns: function() {
          var me = this,
              target = me.target,
              runs = target._layoutRuns;
  
          if (runs && runs.length) {
              target._layoutRuns = [];
              me.addLayoutRuns(runs);
          }
      },
  
      onGatherStats: function() {
          var me = this,
              target = me.target,
              perf = target.Ext.Perf,
              con = me.viewport.down('#perfcon');
  
          // Only gather page startup stats at each reload, not on each gather
          if (!me.statsGatherCount) {
              perf.get("Initialize").enter().leave(target.Ext._afterReadytime - target.Ext._beforeReadyTime);
              if (target.Ext._endTime) {
                  perf.get("Load").enter().leave(target.Ext._endTime - target.Ext._startTime);
                  perf.get("WaitForReady").enter().leave(target.Ext._readyTime - target.Ext._endTime);
              } else {
                  perf.get("BeforeReady").enter().leave(target.Ext._readyTime - target.Ext._startTime);
              }
          }
  
          perf.updateGC();
  
          me.statsGatherCount++;
          var data = perf.getData(),
              accCfg = perf.currentConfig;
  
          con.addSample({
              env: 'x',
              build: me.build,
              test: target.location.pathname,
              data: data
          });
  
          if (accCfg) {
              con.setAccumulators(accCfg);
          }
      },
  
      onClearStats: function() {
          this.viewport.down('#perfcon').clearSamples();
          this.onResetStats();
      },
  
      onResetStats: function(){
          this.target.Ext.Perf.reset();
      },
  
      getHrefMinusHash: function() {
          var href = location.href.replace(Ext.History.getHash(), '');
          return href;
      },
  
      //-------------------------------------------------------------------------
      // Target page mgmt
  
      states: {
          // the target page has not loaded Ext
          disconnected: {
              text: 'Disconnected',
              style: {
                  fontWeight: 'bold',
                  color: 'red'
              }
          },
  
          // the target page is loaded and has Ext.isReady
          loaded: {
              text: 'Loaded',
              style: {
                  fontWeight: 'bold',
                  color: 'orange'
              }
          },
  
          // the target page is loading the layout hooks
          hooking: {
              text: 'Loading hooks',
              style: {
                  fontWeight: 'bold',
                  color: 'yellow'
              }
          },
  
          ready: {
              text: 'Ready',
              style: {
                  fontWeight: 'bold',
                  color: 'green'
              }
          }
      },
  
      getState: function() {
          var states = this.states,
              target = this.target;
  
          if (target && target.Ext && target.Ext.isReady) {
  
              if (target._layoutRuns && target.Ext._readyTime) {
                  return states.ready;
              }
              if (target._hooking) {
                  return states.hooking;
              }
  
              return states.loaded;
          }
  
          return states.disconnected;
      },
  
      injectScript: function (scriptSrc) {
          var me = this,
              url = me.getHrefMinusHash().replace(/\/[^/]+$/, '/' + scriptSrc),
              target = me.targetFrame.getDoc(),
              head = target.head || target.getElementsByTagName('head')[0];
  
          var script = target.createElement('script');
  
          script.src = url;
          script.type = 'text/javascript';
          head.appendChild(script);
      },
  
      injectHooks: function() {
          var me = this,
              target = me.target,
              perfConsole = me.viewport.down('#perfcon'),
              accumulatorConfigs = perfConsole && perfConsole.getAccumulators();
  
          Ext.log('injecting script hooks');
          target._hooking = 3;
          if (accumulatorConfigs) {
              target._accumulatorCfg = accumulatorConfigs;
          }
          me.injectScript('hooks.js');
      },
  
      onLoadPage: function() {
          var me = this,
              target = me.targetFrame,
              doc = target.getDoc(),
              targetUrl = me.targetUrlField.getValue();
  
          if (targetUrl && targetUrl != '' && doc) {
              // add a query parameter the target page url so it will pause at onReady so that
              // we can inject our code and resume it to capture the initial layout run
              // by setting a global override flag (in updateConnectedState):
  
              Ext.log('resetting target document state');
              me.target._layoutRuns = null;
              me.target._syncComplete = false;
              if (me.target.Ext) {
                  me.target.Ext._readyTime = 0;
              }
              me.reloading = true;
  
              var hasPound = targetUrl.indexOf('#'),
                  hasQuest = targetUrl.indexOf('?') ,
                  qParm = (hasQuest > 0 ? '&' : '?') + 'ext-pauseReadyFire';
  
              if (targetUrl != Ext.History.getToken()) {
                  Ext.History.add(targetUrl);
              }
  
              if (hasPound > 0) {
                  targetUrl = targetUrl.substring(0, hasPound) + qParm + targetUrl.substring(hasPound + 1);
              } else {
                  targetUrl += qParm;
              }
  
              Ext.log('reloading target document');
              me.targetFrame.load(targetUrl);
              me.currUrl = targetUrl;
              me.updateConnectedState();
              me.statsGatherCount = 0;
          } else {
              me.targetFrame.load('about:blank');
          }
      },
  
      updateConnectedState: function() {
          var me = this,
              states = me.states,
              state = me.getState();
  
          if (me.lastState != state) {
              me.lastState = state;
              me.stateText.el.setStyle(state.style);
              me.stateText.setText(state.text);
          }
  
          if (state == states.loaded) {
              me.injectHooks();
          } else if (state == states.ready) {
              if (me.reloading) {
                  Ext.log('firing ready event in target document');
                  me.reloading = false;
                  me.target.Ext._continueFireReady = true;
                  me.target.Ext.EventManager.readyEvent.fire();
              }
              me.updateLayoutRuns();
          }
      }
  });
  
  (function() {
      function run () {
          Ext.QuickTips.init();
          new PageAnalyzer.MainForm({
              targetHref: window.opener
                  ? window.opener.location.href
                  : null
          });
      }
  
      Ext.onReady(run);
  })();
  