blob: c73283a8a1b4e04414a2684881fe7a2c1c6b271e [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @constructor
* @extends {WebInspector.View}
* @param {WebInspector.FlameChartDataProvider} dataProvider
*/
WebInspector.FlameChart = function(dataProvider)
{
WebInspector.View.call(this);
this.registerRequiredCSS("flameChart.css");
this.element.className = "fill";
this.element.id = "cpu-flame-chart";
this._overviewPane = new WebInspector.FlameChart.OverviewPane(dataProvider);
this._overviewPane.show(this.element);
this._mainPane = new WebInspector.FlameChart.MainPane(dataProvider, this._overviewPane);
this._mainPane.show(this.element);
this._mainPane.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
this._overviewPane._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
if (!WebInspector.FlameChart._colorGenerator)
WebInspector.FlameChart._colorGenerator = new WebInspector.FlameChart.ColorGenerator();
}
WebInspector.FlameChart.prototype = {
/*
* @param {WebInspector.Event} event
*/
_onWindowChanged: function(event)
{
this._mainPane.changeWindow(this._overviewPane._overviewGrid.windowLeft(), this._overviewPane._overviewGrid.windowRight());
},
/**
* @param {!number} timeLeft
* @param {!number} timeRight
*/
selectRange: function(timeLeft, timeRight)
{
this._overviewPane._selectRange(timeLeft, timeRight);
},
/*
* @param {WebInspector.Event} event
*/
_onEntrySelected: function(event)
{
this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, event.data);
},
update: function()
{
this._overviewPane.update();
this._mainPane.update();
},
__proto__: WebInspector.View.prototype
};
/**
* @interface
*/
WebInspector.FlameChartDataProvider = function()
{
}
WebInspector.FlameChartDataProvider.prototype = {
/**
* @param {WebInspector.FlameChart.ColorGenerator} colorGenerator
* @return {Object}
*/
timelineData: function(colorGenerator) { return null; },
/**
* @param {number} entryIndex
*/
prepareHighlightedEntryInfo: function(entryIndex) { },
/**
* @param {number} entryIndex
* @return {boolean}
*/
canJumpToEntry: function(entryIndex) { return false; },
/**
* @param {number} entryIndex
* @return {Object}
*/
entryData: function(entryIndex) { return null; }
}
/**
* @constructor
* @implements {WebInspector.TimelineGrid.Calculator}
*/
WebInspector.FlameChart.Calculator = function()
{
}
WebInspector.FlameChart.Calculator.prototype = {
/**
* @param {WebInspector.FlameChart.MainPane} mainPane
*/
_updateBoundaries: function(mainPane)
{
function log10(x)
{
return Math.log(x) / Math.LN10;
}
this._decimalDigits = Math.max(0, -Math.floor(log10(mainPane._timelineGrid.gridSliceTime * 1.01)));
var totalTime = mainPane._timelineData().totalTime;
this._minimumBoundaries = mainPane._windowLeft * totalTime;
this._maximumBoundaries = mainPane._windowRight * totalTime;
this.paddingLeft = mainPane._paddingLeft;
this._width = mainPane._canvas.width - this.paddingLeft;
this._timeToPixel = this._width / this.boundarySpan();
},
/**
* @param {number} time
* @return {number}
*/
computePosition: function(time)
{
return (time - this._minimumBoundaries) * this._timeToPixel + this.paddingLeft;
},
/**
* @param {number} value
* @return {string}
*/
formatTime: function(value)
{
var format = "%." + this._decimalDigits + "f\u2009ms";
return WebInspector.UIString(format, value + this._minimumBoundaries);
},
/**
* @return {number}
*/
maximumBoundary: function()
{
return this._maximumBoundaries;
},
/**
* @return {number}
*/
minimumBoundary: function()
{
return this._minimumBoundaries;
},
/**
* @return {number}
*/
zeroTime: function()
{
return 0;
},
/**
* @return {number}
*/
boundarySpan: function()
{
return this._maximumBoundaries - this._minimumBoundaries;
}
}
/**
* @constructor
* @implements {WebInspector.TimelineGrid.Calculator}
*/
WebInspector.FlameChart.OverviewCalculator = function()
{
}
WebInspector.FlameChart.OverviewCalculator.prototype = {
/**
* @param {WebInspector.FlameChart.OverviewPane} overviewPane
*/
_updateBoundaries: function(overviewPane)
{
this._minimumBoundaries = 0;
var totalTime = overviewPane._timelineData().totalTime;
this._maximumBoundaries = totalTime;
this._xScaleFactor = overviewPane._overviewCanvas.width / totalTime;
},
/**
* @param {number} time
* @return {number}
*/
computePosition: function(time)
{
return (time - this._minimumBoundaries) * this._xScaleFactor;
},
/**
* @param {number} value
* @return {string}
*/
formatTime: function(value)
{
return Number.secondsToString((value + this._minimumBoundaries) / 1000);
},
/**
* @return {number}
*/
maximumBoundary: function()
{
return this._maximumBoundaries;
},
/**
* @return {number}
*/
minimumBoundary: function()
{
return this._minimumBoundaries;
},
/**
* @return {number}
*/
zeroTime: function()
{
return this._minimumBoundaries;
},
/**
* @return {number}
*/
boundarySpan: function()
{
return this._maximumBoundaries - this._minimumBoundaries;
}
}
WebInspector.FlameChart.Events = {
EntrySelected: "EntrySelected"
}
/**
* @constructor
*/
WebInspector.FlameChart.ColorGenerator = function()
{
this._colorPairs = {};
this._colorIndexes = [];
this._currentColorIndex = 0;
this._colorPairForID("(idle)::0", 50);
this._colorPairForID("(program)::0", 50);
this._colorPairForID("(garbage collector)::0", 50);
}
WebInspector.FlameChart.ColorGenerator.prototype = {
/**
* @param {!string} id
* @param {number=} sat
*/
_colorPairForID: function(id, sat)
{
if (typeof sat !== "number")
sat = 100;
var colorPairs = this._colorPairs;
var colorPair = colorPairs[id];
if (!colorPair) {
colorPairs[id] = colorPair = this._createPair(this._currentColorIndex++, sat);
this._colorIndexes[colorPair.index] = colorPair;
}
return colorPair;
},
/**
* @param {!number} index
*/
_colorPairForIndex: function(index)
{
return this._colorIndexes[index];
},
/**
* @param {!number} index
* @param {!number} sat
*/
_createPair: function(index, sat)
{
var hue = (index * 7 + 12 * (index % 2)) % 360;
return {index: index, highlighted: "hsla(" + hue + ", " + sat + "%, 33%, 0.7)", normal: "hsla(" + hue + ", " + sat + "%, 66%, 0.7)"}
}
}
/**
* @interface
*/
WebInspector.FlameChart.OverviewPaneInterface = function()
{
}
WebInspector.FlameChart.OverviewPaneInterface.prototype = {
/*
* @param {number} zoom
* @param {number} referencePoint
*/
zoom: function(zoom, referencePoint) { },
/*
* @param {number} windowLeft
* @param {number} windowRight
*/
setWindow: function(windowLeft, windowRight) { },
}
/**
* @constructor
* @extends {WebInspector.View}
* @implements {WebInspector.FlameChart.OverviewPaneInterface}
* @param {WebInspector.FlameChartDataProvider} dataProvider
*/
WebInspector.FlameChart.OverviewPane = function(dataProvider)
{
WebInspector.View.call(this);
this._overviewContainer = this.element.createChild("div", "overview-container");
this._overviewGrid = new WebInspector.OverviewGrid("flame-chart");
this._overviewGrid.element.addStyleClass("fill");
this._overviewCanvas = this._overviewContainer.createChild("canvas", "flame-chart-overview-canvas");
this._overviewContainer.appendChild(this._overviewGrid.element);
this._overviewCalculator = new WebInspector.FlameChart.OverviewCalculator();
this._dataProvider = dataProvider;
}
WebInspector.FlameChart.OverviewPane.prototype = {
/*
* @param {number} zoom
* @param {number} referencePoint
*/
zoom: function(zoom, referencePoint)
{
this._overviewGrid.zoom(zoom, referencePoint);
},
/*
* @param {number} windowLeft
* @param {number} windowRight
*/
setWindow: function(windowLeft, windowRight)
{
this._overviewGrid.setWindow(windowLeft, windowRight);
},
/**
* @param {!number} timeLeft
* @param {!number} timeRight
*/
_selectRange: function(timeLeft, timeRight)
{
var timelineData = this._timelineData();
if (!timelineData)
return;
this._overviewGrid.setWindow(timeLeft / timelineData._totalTime, timeRight / timelineData._totalTime);
},
_timelineData: function()
{
return this._dataProvider.timelineData(WebInspector.FlameChart._colorGenerator);
},
onResize: function()
{
this._scheduleUpdate();
},
_scheduleUpdate: function()
{
if (this._updateTimerId)
return;
this._updateTimerId = setTimeout(this.update.bind(this), 10);
},
update: function()
{
this._updateTimerId = 0;
var timelineData = this._timelineData();
if (!timelineData)
return;
this._overviewCalculator._updateBoundaries(this);
this._overviewGrid.updateDividers(this._overviewCalculator);
this._resetCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20);
WebInspector.FlameChart.OverviewPane.drawOverviewCanvas(
timelineData,
this._overviewCanvas.getContext("2d"),
this._overviewContainer.clientWidth,
this._overviewContainer.clientHeight - 20
);
},
/*
* @param {!number} width
* @param {!number} height
*/
_resetCanvas: function(width, height)
{
var ratio = window.devicePixelRatio;
this._overviewCanvas.width = width * ratio;
this._overviewCanvas.height = height * ratio;
},
__proto__: WebInspector.View.prototype
}
/**
* @param {!Object} timelineData
* @param {!number} width
*/
WebInspector.FlameChart.OverviewPane.calculateDrawData = function(timelineData, width)
{
var entryOffsets = timelineData.entryOffsets;
var entryTotalTimes = timelineData.entryTotalTimes;
var entryLevels = timelineData.entryLevels;
var length = entryOffsets.length;
var drawData = new Uint8Array(width);
var scaleFactor = width / timelineData.totalTime;
for (var entryIndex = 0; entryIndex < length; ++entryIndex) {
var start = Math.floor(entryOffsets[entryIndex] * scaleFactor);
var finish = Math.floor((entryOffsets[entryIndex] + entryTotalTimes[entryIndex]) * scaleFactor);
for (var x = start; x <= finish; ++x)
drawData[x] = Math.max(drawData[x], entryLevels[entryIndex] + 1);
}
return drawData;
}
/**
* @param {!Object} timelineData
* @param {!Object} context
* @param {!number} width
* @param {!number} height
*/
WebInspector.FlameChart.OverviewPane.drawOverviewCanvas = function(timelineData, context, width, height)
{
var drawData = WebInspector.FlameChart.OverviewPane.calculateDrawData(timelineData, width);
if (!drawData)
return;
var ratio = window.devicePixelRatio;
var canvasWidth = width * ratio;
var canvasHeight = height * ratio;
var yScaleFactor = canvasHeight / (timelineData.maxStackDepth * 1.1);
context.lineWidth = 1;
context.translate(0.5, 0.5);
context.strokeStyle = "rgba(20,0,0,0.4)";
context.fillStyle = "rgba(214,225,254,0.8)";
context.moveTo(-1, canvasHeight - 1);
if (drawData)
context.lineTo(-1, Math.round(height - drawData[0] * yScaleFactor - 1));
var value;
for (var x = 0; x < width; ++x) {
value = Math.round(canvasHeight - drawData[x] * yScaleFactor - 1);
context.lineTo(x * ratio, value);
}
context.lineTo(canvasWidth + 1, value);
context.lineTo(canvasWidth + 1, canvasHeight - 1);
context.fill();
context.stroke();
context.closePath();
}
/**
* @constructor
* @extends {WebInspector.View}
* @param {WebInspector.FlameChartDataProvider} dataProvider
* @param {WebInspector.FlameChart.OverviewPaneInterface} overviewPane
*/
WebInspector.FlameChart.MainPane = function(dataProvider, overviewPane)
{
WebInspector.View.call(this);
this._overviewPane = overviewPane;
this._chartContainer = this.element.createChild("div", "chart-container");
this._timelineGrid = new WebInspector.TimelineGrid();
this._chartContainer.appendChild(this._timelineGrid.element);
this._calculator = new WebInspector.FlameChart.Calculator();
this._canvas = this._chartContainer.createChild("canvas");
this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this));
this._canvas.addEventListener("mousewheel", this._onMouseWheel.bind(this), false);
this._canvas.addEventListener("click", this._onClick.bind(this), false);
WebInspector.installDragHandle(this._canvas, this._startCanvasDragging.bind(this), this._canvasDragging.bind(this), this._endCanvasDragging.bind(this), "col-resize");
this._entryInfo = this._chartContainer.createChild("div", "entry-info");
this._dataProvider = dataProvider;
this._windowLeft = 0.0;
this._windowRight = 1.0;
this._windowWidth = 1.0;
this._barHeight = 15;
this._minWidth = 1;
this._paddingLeft = 15;
this._highlightedEntryIndex = -1;
}
WebInspector.FlameChart.MainPane.prototype = {
_timelineData: function()
{
return this._dataProvider.timelineData(WebInspector.FlameChart._colorGenerator);
},
/*
* @param {!number} windowLeft
* @param {!number} windowRight
*/
changeWindow: function(windowLeft, windowRight)
{
this._windowLeft = windowLeft;
this._windowRight = windowRight;
this._windowWidth = this._windowRight - this._windowLeft;
this._scheduleUpdate();
},
/*
* @param {WebInspector.Event} event
*/
_startCanvasDragging: function(event)
{
if (!this._timelineData())
return false;
this._isDragging = true;
this._wasDragged = false;
this._dragStartPoint = event.pageX;
this._dragStartWindowLeft = this._windowLeft;
this._dragStartWindowRight = this._windowRight;
return true;
},
/*
* @param {WebInspector.Event} event
*/
_canvasDragging: function(event)
{
var pixelShift = this._dragStartPoint - event.pageX;
var windowShift = pixelShift / this._totalPixels;
var windowLeft = Math.max(0, this._dragStartWindowLeft + windowShift);
if (windowLeft === this._windowLeft)
return;
windowShift = windowLeft - this._dragStartWindowLeft;
var windowRight = Math.min(1, this._dragStartWindowRight + windowShift);
if (windowRight === this._windowRight)
return;
windowShift = windowRight - this._dragStartWindowRight;
this._overviewPane.setWindow(this._dragStartWindowLeft + windowShift, this._dragStartWindowRight + windowShift);
this._wasDragged = true;
},
_endCanvasDragging: function()
{
this._isDragging = false;
},
/*
* @param {WebInspector.Event} event
*/
_onMouseMove: function(event)
{
if (this._isDragging)
return;
var entryIndex = this._coordinatesToEntryIndex(event.offsetX, event.offsetY);
if (this._highlightedEntryIndex === entryIndex)
return;
if (entryIndex === -1 || !this._dataProvider.canJumpToEntry(entryIndex))
this._canvas.style.cursor = "default";
else
this._canvas.style.cursor = "pointer";
this._highlightedEntryIndex = entryIndex;
this._scheduleUpdate();
},
_onClick: function(e)
{
// onClick comes after dragStart and dragEnd events.
// So if there was drag (mouse move) in the middle of that events
// we skip the click. Otherwise we jump to the sources.
if (this._wasDragged)
return;
if (this._highlightedEntryIndex === -1)
return;
var data = this._dataProvider.entryData(this._highlightedEntryIndex);
this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, data);
},
_onMouseWheel: function(e)
{
if (e.wheelDeltaY) {
const zoomFactor = 1.1;
const mouseWheelZoomSpeed = 1 / 120;
var zoom = Math.pow(zoomFactor, -e.wheelDeltaY * mouseWheelZoomSpeed);
var referencePoint = (this._pixelWindowLeft + e.offsetX - this._paddingLeft) / this._totalPixels;
this._overviewPane.zoom(zoom, referencePoint);
} else {
var shift = Number.constrain(-1 * this._windowWidth / 4 * e.wheelDeltaX / 120, -this._windowLeft, 1 - this._windowRight);
this._overviewPane.setWindow(this._windowLeft + shift, this._windowRight + shift);
}
},
/**
* @param {!number} x
* @param {!number} y
*/
_coordinatesToEntryIndex: function(x, y)
{
var timelineData = this._timelineData();
if (!timelineData)
return -1;
var cursorTime = (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTime;
var cursorLevel = Math.floor((this._canvas.height / window.devicePixelRatio - y) / this._barHeight);
var entryOffsets = timelineData.entryOffsets;
var entryTotalTimes = timelineData.entryTotalTimes;
var entryLevels = timelineData.entryLevels;
var length = entryOffsets.length;
for (var i = 0; i < length; ++i) {
if (cursorTime < entryOffsets[i])
return -1;
if (cursorTime < (entryOffsets[i] + entryTotalTimes[i])
&& cursorLevel === entryLevels[i])
return i;
}
return -1;
},
/**
* @param {!number} height
* @param {!number} width
*/
draw: function(width, height)
{
var timelineData = this._timelineData();
if (!timelineData)
return;
var ratio = window.devicePixelRatio;
this._canvas.width = width * ratio;
this._canvas.height = height * ratio;
this._canvas.style.width = width + "px";
this._canvas.style.height = height + "px";
var context = this._canvas.getContext("2d");
context.scale(ratio, ratio);
var timeWindowRight = this._timeWindowRight;
var timeToPixel = this._timeToPixel;
var pixelWindowLeft = this._pixelWindowLeft;
var paddingLeft = this._paddingLeft;
var minWidth = this._minWidth;
var entryTotalTimes = timelineData.entryTotalTimes;
var entryOffsets = timelineData.entryOffsets;
var entryLevels = timelineData.entryLevels;
var colorEntryIndexes = timelineData.colorEntryIndexes;
var entryTitles = timelineData.entryTitles;
var entryDeoptFlags = timelineData.entryDeoptFlags;
var colorGenerator = WebInspector.FlameChart._colorGenerator;
var titleIndexes = new Uint32Array(timelineData.entryTotalTimes);
var lastTitleIndex = 0;
var dotsWidth = context.measureText("\u2026").width;
var textPaddingLeft = 2;
this._minTextWidth = context.measureText("\u2026").width + textPaddingLeft;
var minTextWidth = this._minTextWidth;
var marksField = [];
for (var i = 0; i < timelineData.maxStackDepth; ++i)
marksField.push(new Uint16Array(width));
var barHeight = this._barHeight;
var barX = 0;
var barWidth = 0;
var barRight = 0;
var barLevel = 0;
var bHeight = height - barHeight;
context.strokeStyle = "black";
var colorPair;
var entryIndex = 0;
var entryOffset = 0;
for (var colorIndex = 0; colorIndex < colorEntryIndexes.length; ++colorIndex) {
colorPair = colorGenerator._colorPairForIndex(colorIndex);
context.fillStyle = colorPair.normal;
var indexes = colorEntryIndexes[colorIndex];
if (!indexes)
continue;
context.beginPath();
for (var i = 0; i < indexes.length; ++i) {
entryIndex = indexes[i];
entryOffset = entryOffsets[entryIndex];
if (entryOffset > timeWindowRight)
break;
barX = Math.ceil(entryOffset * timeToPixel) - pixelWindowLeft + paddingLeft;
if (barX >= width)
continue;
barRight = Math.floor((entryOffset + entryTotalTimes[entryIndex]) * timeToPixel) - pixelWindowLeft + paddingLeft;
if (barRight < 0)
continue;
barWidth = (barRight - barX) || minWidth;
barLevel = entryLevels[entryIndex];
var marksRow = marksField[barLevel];
if (barWidth <= marksRow[barX])
continue;
marksRow[barX] = barWidth;
if (entryIndex === this._highlightedEntryIndex) {
context.fill();
context.beginPath();
context.fillStyle = colorPair.highlighted;
}
context.rect(barX, bHeight - barLevel * barHeight, barWidth, barHeight);
if (entryIndex === this._highlightedEntryIndex) {
context.fill();
context.beginPath();
context.fillStyle = colorPair.normal;
}
if (barWidth > minTextWidth)
titleIndexes[lastTitleIndex++] = entryIndex;
}
context.fill();
}
var font = (barHeight - 4) + "px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
var boldFont = "bold " + font;
var isBoldFontSelected = false;
context.font = font;
context.textBaseline = "alphabetic";
context.fillStyle = "#333";
this._dotsWidth = context.measureText("\u2026").width;
var textBaseHeight = bHeight + barHeight - 4;
for (var i = 0; i < lastTitleIndex; ++i) {
entryIndex = titleIndexes[i];
if (isBoldFontSelected) {
if (!entryDeoptFlags[entryIndex]) {
context.font = font;
isBoldFontSelected = false;
}
} else {
if (entryDeoptFlags[entryIndex]) {
context.font = boldFont;
isBoldFontSelected = true;
}
}
entryOffset = entryOffsets[entryIndex];
barX = Math.floor(entryOffset * timeToPixel) - pixelWindowLeft + paddingLeft;
barRight = Math.ceil((entryOffset + entryTotalTimes[entryIndex]) * timeToPixel) - pixelWindowLeft + paddingLeft;
barWidth = (barRight - barX) || minWidth;
var xText = Math.max(0, barX);
var widthText = barWidth - textPaddingLeft + barX - xText;
var title = this._prepareText(context, entryTitles[entryIndex], widthText);
if (title)
context.fillText(title, xText + textPaddingLeft, textBaseHeight - entryLevels[entryIndex] * barHeight);
}
this._entryInfo.removeChildren();
if (!this._isDragging) {
var entryInfo = this._dataProvider.prepareHighlightedEntryInfo(this._highlightedEntryIndex);
if (entryInfo)
this._entryInfo.appendChild(this._buildEntryInfo(entryInfo));
}
},
_buildEntryInfo: function(entryInfo)
{
var infoTable = document.createElement("table");
infoTable.className = "info-table";
for (var i = 0; i < entryInfo.length; ++i) {
var row = infoTable.createChild("tr");
var titleCell = row.createChild("td");
titleCell.textContent = entryInfo[i].title;
titleCell.className = "title";
var textCell = row.createChild("td");
textCell.textContent = entryInfo[i].text;
}
return infoTable;
},
_prepareText: function(context, title, maxSize)
{
if (maxSize < this._dotsWidth)
return null;
var titleWidth = context.measureText(title).width;
if (maxSize > titleWidth)
return title;
maxSize -= this._dotsWidth;
var dotRegExp=/[\.\$]/g;
var match = dotRegExp.exec(title);
if (!match) {
var visiblePartSize = maxSize / titleWidth;
var newTextLength = Math.floor(title.length * visiblePartSize) + 1;
var minTextLength = 4;
if (newTextLength < minTextLength)
return null;
var substring;
do {
--newTextLength;
substring = title.substring(0, newTextLength);
} while (context.measureText(substring).width > maxSize);
return title.substring(0, newTextLength) + "\u2026";
}
while (match) {
var substring = title.substring(match.index + 1);
var width = context.measureText(substring).width;
if (maxSize > width)
return "\u2026" + substring;
match = dotRegExp.exec(title);
}
var i = 0;
do {
++i;
} while (context.measureText(title.substring(0, i)).width < maxSize);
return title.substring(0, i - 1) + "\u2026";
},
_updateBoundaries: function()
{
this._totalTime = this._timelineData().totalTime;
this._timeWindowLeft = this._windowLeft * this._totalTime;
this._timeWindowRight = this._windowRight * this._totalTime;
this._pixelWindowWidth = this._chartContainer.clientWidth - this._paddingLeft;
this._totalPixels = Math.floor(this._pixelWindowWidth / this._windowWidth);
this._pixelWindowLeft = Math.floor(this._totalPixels * this._windowLeft);
this._pixelWindowRight = Math.floor(this._totalPixels * this._windowRight);
this._timeToPixel = this._totalPixels / this._totalTime;
this._pixelToTime = this._totalTime / this._totalPixels;
this._paddingLeftTime = this._paddingLeft / this._timeToPixel;
},
onResize: function()
{
this._scheduleUpdate();
},
_scheduleUpdate: function()
{
if (this._updateTimerId)
return;
this._updateTimerId = setTimeout(this.update.bind(this), 10);
},
update: function()
{
this._updateTimerId = 0;
if (!this._timelineData())
return;
this._updateBoundaries();
this.draw(this._chartContainer.clientWidth, this._chartContainer.clientHeight);
this._calculator._updateBoundaries(this);
this._timelineGrid.element.style.width = this.element.clientWidth;
this._timelineGrid.updateDividers(this._calculator);
},
__proto__: WebInspector.View.prototype
}