home *** CD-ROM | disk | FTP | other *** search
/ Computer Active Guide 2009 July / CAG7.ISO / Internetas / SafariSetup.exe / AppleApplicationSupport.msi / WebKit.resources_inspector_AbstractTimelinePanel.js < prev    next >
Encoding:
Text File  |  2010-06-03  |  16.0 KB  |  525 lines

  1. /*
  2.  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
  3.  * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
  4.  * Copyright (C) 2009 Google Inc. All rights reserved.
  5.  *
  6.  * Redistribution and use in source and binary forms, with or without
  7.  * modification, are permitted provided that the following conditions
  8.  * are met:
  9.  *
  10.  * 1.  Redistributions of source code must retain the above copyright
  11.  *     notice, this list of conditions and the following disclaimer.
  12.  * 2.  Redistributions in binary form must reproduce the above copyright
  13.  *     notice, this list of conditions and the following disclaimer in the
  14.  *     documentation and/or other materials provided with the distribution.
  15.  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
  16.  *     its contributors may be used to endorse or promote products derived
  17.  *     from this software without specific prior written permission.
  18.  *
  19.  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
  20.  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  21.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  22.  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  23.  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  24.  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  25.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  26.  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  28.  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29.  */
  30.  
  31. WebInspector.AbstractTimelinePanel = function()
  32. {
  33.     WebInspector.Panel.call(this);
  34.     this._items = [];
  35.     this._staleItems = [];
  36. }
  37.  
  38. WebInspector.AbstractTimelinePanel.prototype = {
  39.     get categories()
  40.     {
  41.         // Should be implemented by the concrete subclasses.
  42.         return {};
  43.     },
  44.  
  45.     populateSidebar: function()
  46.     {
  47.         // Should be implemented by the concrete subclasses.
  48.     },
  49.  
  50.     createItemTreeElement: function(item)
  51.     {
  52.         // Should be implemented by the concrete subclasses.
  53.     },
  54.  
  55.     createItemGraph: function(item)
  56.     {
  57.         // Should be implemented by the concrete subclasses.
  58.     },
  59.  
  60.     get items()
  61.     {
  62.         return this._items;
  63.     },
  64.  
  65.     createInterface: function()
  66.     {
  67.         this.containerElement = document.createElement("div");
  68.         this.containerElement.id = "resources-container";
  69.         this.containerElement.addEventListener("scroll", this._updateDividersLabelBarPosition.bind(this), false);
  70.         this.element.appendChild(this.containerElement);
  71.  
  72.         this.createSidebar(this.containerElement, this.element);
  73.         this.sidebarElement.id = "resources-sidebar";
  74.         this.populateSidebar();
  75.  
  76.         this._containerContentElement = document.createElement("div");
  77.         this._containerContentElement.id = "resources-container-content";
  78.         this.containerElement.appendChild(this._containerContentElement);
  79.  
  80.         this.summaryBar = new WebInspector.SummaryBar(this.categories);
  81.         this.summaryBar.element.id = "resources-summary";
  82.         this._containerContentElement.appendChild(this.summaryBar.element);
  83.  
  84.         this._timelineGrid = new WebInspector.TimelineGrid();
  85.         this._containerContentElement.appendChild(this._timelineGrid.element);
  86.         this.itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
  87.     },
  88.  
  89.     createFilterPanel: function()
  90.     {
  91.         this.filterBarElement = document.createElement("div");
  92.         this.filterBarElement.id = "resources-filter";
  93.         this.filterBarElement.className = "scope-bar";
  94.         this.element.appendChild(this.filterBarElement);
  95.  
  96.         function createFilterElement(category)
  97.         {
  98.             if (category === "all")
  99.                 var label = WebInspector.UIString("All");
  100.             else if (this.categories[category])
  101.                 var label = this.categories[category].title;
  102.  
  103.             var categoryElement = document.createElement("li");
  104.             categoryElement.category = category;
  105.             categoryElement.addStyleClass(category);
  106.             categoryElement.appendChild(document.createTextNode(label));
  107.             categoryElement.addEventListener("click", this._updateFilter.bind(this), false);
  108.             this.filterBarElement.appendChild(categoryElement);
  109.  
  110.             return categoryElement;
  111.         }
  112.  
  113.         this.filterAllElement = createFilterElement.call(this, "all");
  114.  
  115.         // Add a divider
  116.         var dividerElement = document.createElement("div");
  117.         dividerElement.addStyleClass("divider");
  118.         this.filterBarElement.appendChild(dividerElement);
  119.  
  120.         for (var category in this.categories)
  121.             createFilterElement.call(this, category);
  122.     },
  123.  
  124.     showCategory: function(category)
  125.     {
  126.         var filterClass = "filter-" + category.toLowerCase();
  127.         this.itemsGraphsElement.addStyleClass(filterClass);
  128.         this.itemsTreeElement.childrenListElement.addStyleClass(filterClass);
  129.     },
  130.  
  131.     hideCategory: function(category)
  132.     {
  133.         var filterClass = "filter-" + category.toLowerCase();
  134.         this.itemsGraphsElement.removeStyleClass(filterClass);
  135.         this.itemsTreeElement.childrenListElement.removeStyleClass(filterClass);
  136.     },
  137.  
  138.     filter: function(target, selectMultiple)
  139.     {
  140.         function unselectAll()
  141.         {
  142.             for (var i = 0; i < this.filterBarElement.childNodes.length; ++i) {
  143.                 var child = this.filterBarElement.childNodes[i];
  144.                 if (!child.category)
  145.                     continue;
  146.  
  147.                 child.removeStyleClass("selected");
  148.                 this.hideCategory(child.category);
  149.             }
  150.         }
  151.  
  152.         if (target === this.filterAllElement) {
  153.             if (target.hasStyleClass("selected")) {
  154.                 // We can't unselect All, so we break early here
  155.                 return;
  156.             }
  157.  
  158.             // If All wasn't selected, and now is, unselect everything else.
  159.             unselectAll.call(this);
  160.         } else {
  161.             // Something other than All is being selected, so we want to unselect All.
  162.             if (this.filterAllElement.hasStyleClass("selected")) {
  163.                 this.filterAllElement.removeStyleClass("selected");
  164.                 this.hideCategory("all");
  165.             }
  166.         }
  167.  
  168.         if (!selectMultiple) {
  169.             // If multiple selection is off, we want to unselect everything else
  170.             // and just select ourselves.
  171.             unselectAll.call(this);
  172.  
  173.             target.addStyleClass("selected");
  174.             this.showCategory(target.category);
  175.             return;
  176.         }
  177.  
  178.         if (target.hasStyleClass("selected")) {
  179.             // If selectMultiple is turned on, and we were selected, we just
  180.             // want to unselect ourselves.
  181.             target.removeStyleClass("selected");
  182.             this.hideCategory(target.category);
  183.         } else {
  184.             // If selectMultiple is turned on, and we weren't selected, we just
  185.             // want to select ourselves.
  186.             target.addStyleClass("selected");
  187.             this.showCategory(target.category);
  188.         }
  189.     },
  190.  
  191.     _updateFilter: function(e)
  192.     {
  193.         var isMac = WebInspector.isMac();
  194.         var selectMultiple = false;
  195.         if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey)
  196.             selectMultiple = true;
  197.         if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
  198.             selectMultiple = true;
  199.  
  200.         this.filter(e.target, selectMultiple);
  201.  
  202.         // When we are updating our filtering, scroll to the top so we don't end up
  203.         // in blank graph under all the resources.
  204.         this.containerElement.scrollTop = 0;
  205.     },
  206.  
  207.     updateGraphDividersIfNeeded: function(force)
  208.     {
  209.         if (!this.visible) {
  210.             this.needsRefresh = true;
  211.             return false;
  212.         }
  213.         return this._timelineGrid.updateDividers(force, this.calculator);
  214.     },
  215.  
  216.     _updateDividersLabelBarPosition: function()
  217.     {
  218.         const scrollTop = this.containerElement.scrollTop;
  219.         const offsetHeight = this.summaryBar.element.offsetHeight;
  220.         const dividersTop = (scrollTop < offsetHeight ? offsetHeight : scrollTop);
  221.         this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop);
  222.     },
  223.  
  224.     get needsRefresh()
  225.     {
  226.         return this._needsRefresh;
  227.     },
  228.  
  229.     set needsRefresh(x)
  230.     {
  231.         if (this._needsRefresh === x)
  232.             return;
  233.  
  234.         this._needsRefresh = x;
  235.  
  236.         if (x) {
  237.             if (this.visible && !("_refreshTimeout" in this))
  238.                 this._refreshTimeout = setTimeout(this.refresh.bind(this), 500);
  239.         } else {
  240.             if ("_refreshTimeout" in this) {
  241.                 clearTimeout(this._refreshTimeout);
  242.                 delete this._refreshTimeout;
  243.             }
  244.         }
  245.     },
  246.  
  247.     refreshIfNeeded: function()
  248.     {
  249.         if (this.needsRefresh)
  250.             this.refresh();
  251.     },
  252.  
  253.     show: function()
  254.     {
  255.         WebInspector.Panel.prototype.show.call(this);
  256.  
  257.         this._updateDividersLabelBarPosition();
  258.         this.refreshIfNeeded();
  259.     },
  260.  
  261.     resize: function()
  262.     {
  263.         WebInspector.Panel.prototype.resize.call(this);
  264.  
  265.         this.updateGraphDividersIfNeeded();
  266.     },
  267.  
  268.     updateMainViewWidth: function(width)
  269.     {
  270.         this._containerContentElement.style.left = width + "px";
  271.         this.resize();
  272.     },
  273.  
  274.     invalidateAllItems: function()
  275.     {
  276.         this._staleItems = this._items.slice();
  277.     },
  278.  
  279.     refresh: function()
  280.     {
  281.         this.needsRefresh = false;
  282.  
  283.         var staleItemsLength = this._staleItems.length;
  284.  
  285.         var boundariesChanged = false;
  286.  
  287.         for (var i = 0; i < staleItemsLength; ++i) {
  288.             var item = this._staleItems[i];
  289.             if (!item._itemsTreeElement) {
  290.                 // Create the timeline tree element and graph.
  291.                 item._itemsTreeElement = this.createItemTreeElement(item);
  292.                 item._itemsTreeElement._itemGraph = this.createItemGraph(item);
  293.  
  294.                 this.itemsTreeElement.appendChild(item._itemsTreeElement);
  295.                 this.itemsGraphsElement.appendChild(item._itemsTreeElement._itemGraph.graphElement);
  296.             }
  297.  
  298.             if (item._itemsTreeElement.refresh)
  299.                 item._itemsTreeElement.refresh();
  300.  
  301.             if (this.calculator.updateBoundaries(item))
  302.                 boundariesChanged = true;
  303.         }
  304.  
  305.         if (boundariesChanged) {
  306.             // The boundaries changed, so all item graphs are stale.
  307.             this._staleItems = this._items.slice();
  308.             staleItemsLength = this._staleItems.length;
  309.         }
  310.  
  311.  
  312.         const isBarOpaqueAtLeft = this.sidebarTree.selectedTreeElement && this.sidebarTree.selectedTreeElement.isBarOpaqueAtLeft;
  313.         for (var i = 0; i < staleItemsLength; ++i)
  314.             this._staleItems[i]._itemsTreeElement._itemGraph.refresh(this.calculator, isBarOpaqueAtLeft);
  315.  
  316.         this._staleItems = [];
  317.  
  318.         this.updateGraphDividersIfNeeded();
  319.     },
  320.  
  321.     reset: function()
  322.     {
  323.         this.containerElement.scrollTop = 0;
  324.  
  325.         if (this._calculator)
  326.             this._calculator.reset();
  327.  
  328.         if (this._items) {
  329.             var itemsLength = this._items.length;
  330.             for (var i = 0; i < itemsLength; ++i) {
  331.                 var item = this._items[i];
  332.                 delete item._itemsTreeElement;
  333.             }
  334.         }
  335.  
  336.         this._items = [];
  337.         this._staleItems = [];
  338.  
  339.         this.itemsTreeElement.removeChildren();
  340.         this.itemsGraphsElement.removeChildren();
  341.  
  342.         this.updateGraphDividersIfNeeded(true);
  343.     },
  344.  
  345.     get calculator()
  346.     {
  347.         return this._calculator;
  348.     },
  349.  
  350.     set calculator(x)
  351.     {
  352.         if (!x || this._calculator === x)
  353.             return;
  354.  
  355.         this._calculator = x;
  356.         this._calculator.reset();
  357.  
  358.         this._staleItems = this._items.slice();
  359.         this.refresh();
  360.     },
  361.  
  362.     addItem: function(item)
  363.     {
  364.         this._items.push(item);
  365.         this.refreshItem(item);
  366.     },
  367.  
  368.     removeItem: function(item)
  369.     {
  370.         this._items.remove(item, true);
  371.  
  372.         if (item._itemsTreeElement) {
  373.             this.itemsTreeElement.removeChild(item._itemsTreeElement);
  374.             this.itemsGraphsElement.removeChild(item._itemsTreeElement._itemGraph.graphElement);
  375.         }
  376.  
  377.         delete item._itemsTreeElement;
  378.         this.adjustScrollPosition();
  379.     },
  380.  
  381.     refreshItem: function(item)
  382.     {
  383.         this._staleItems.push(item);
  384.         this.needsRefresh = true;
  385.     },
  386.  
  387.     revealAndSelectItem: function(item)
  388.     {
  389.         if (item._itemsTreeElement) {
  390.             item._itemsTreeElement.reveal();
  391.             item._itemsTreeElement.select(true);
  392.         }
  393.     },
  394.  
  395.     sortItems: function(sortingFunction)
  396.     {
  397.         var sortedElements = [].concat(this.itemsTreeElement.children);
  398.         sortedElements.sort(sortingFunction);
  399.  
  400.         var sortedElementsLength = sortedElements.length;
  401.         for (var i = 0; i < sortedElementsLength; ++i) {
  402.             var treeElement = sortedElements[i];
  403.             if (treeElement === this.itemsTreeElement.children[i])
  404.                 continue;
  405.  
  406.             var wasSelected = treeElement.selected;
  407.             this.itemsTreeElement.removeChild(treeElement);
  408.             this.itemsTreeElement.insertChild(treeElement, i);
  409.             if (wasSelected)
  410.                 treeElement.select(true);
  411.  
  412.             var graphElement = treeElement._itemGraph.graphElement;
  413.             this.itemsGraphsElement.insertBefore(graphElement, this.itemsGraphsElement.children[i]);
  414.         }
  415.     },
  416.  
  417.     adjustScrollPosition: function()
  418.     {
  419.         // Prevent the container from being scrolled off the end.
  420.         if ((this.containerElement.scrollTop + this.containerElement.offsetHeight) > this.sidebarElement.offsetHeight)
  421.             this.containerElement.scrollTop = (this.sidebarElement.offsetHeight - this.containerElement.offsetHeight);
  422.     },
  423.  
  424.     addEventDivider: function(divider)
  425.     {
  426.         this._timelineGrid.addEventDivider(divider);
  427.     },
  428.  
  429.     hideEventDividers: function()
  430.     {
  431.         this._timelineGrid.hideEventDividers();
  432.     },
  433.  
  434.     showEventDividers: function()
  435.     {
  436.         this._timelineGrid.showEventDividers();
  437.     }
  438. }
  439.  
  440. WebInspector.AbstractTimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype;
  441.  
  442. WebInspector.AbstractTimelineCalculator = function()
  443. {
  444. }
  445.  
  446. WebInspector.AbstractTimelineCalculator.prototype = {
  447.     computeSummaryValues: function(items)
  448.     {
  449.         var total = 0;
  450.         var categoryValues = {};
  451.  
  452.         var itemsLength = items.length;
  453.         for (var i = 0; i < itemsLength; ++i) {
  454.             var item = items[i];
  455.             var value = this._value(item);
  456.             if (typeof value === "undefined")
  457.                 continue;
  458.             if (!(item.category.name in categoryValues))
  459.                 categoryValues[item.category.name] = 0;
  460.             categoryValues[item.category.name] += value;
  461.             total += value;
  462.         }
  463.  
  464.         return {categoryValues: categoryValues, total: total};
  465.     },
  466.  
  467.     computeBarGraphPercentages: function(item)
  468.     {
  469.         return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100};
  470.     },
  471.  
  472.     computeBarGraphLabels: function(item)
  473.     {
  474.         const label = this.formatValue(this._value(item));
  475.         return {left: label, right: label, tooltip: label};
  476.     },
  477.  
  478.     get boundarySpan()
  479.     {
  480.         return this.maximumBoundary - this.minimumBoundary;
  481.     },
  482.  
  483.     updateBoundaries: function(item)
  484.     {
  485.         this.minimumBoundary = 0;
  486.  
  487.         var value = this._value(item);
  488.         if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) {
  489.             this.maximumBoundary = value;
  490.             return true;
  491.         }
  492.         return false;
  493.     },
  494.  
  495.     reset: function()
  496.     {
  497.         delete this.minimumBoundary;
  498.         delete this.maximumBoundary;
  499.     },
  500.  
  501.     _value: function(item)
  502.     {
  503.         return 0;
  504.     },
  505.  
  506.     formatValue: function(value)
  507.     {
  508.         return value.toString();
  509.     }
  510. }
  511.  
  512. WebInspector.AbstractTimelineCategory = function(name, title, color)
  513. {
  514.     this.name = name;
  515.     this.title = title;
  516.     this.color = color;
  517. }
  518.  
  519. WebInspector.AbstractTimelineCategory.prototype = {
  520.     toString: function()
  521.     {
  522.         return this.title;
  523.     }
  524. }
  525.