diff options
Diffstat (limited to 'chromium/third_party/WebKit/Source/devtools/front_end/timeline/TimelineUIUtilsImpl.js')
-rw-r--r-- | chromium/third_party/WebKit/Source/devtools/front_end/timeline/TimelineUIUtilsImpl.js | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/timeline/TimelineUIUtilsImpl.js b/chromium/third_party/WebKit/Source/devtools/front_end/timeline/TimelineUIUtilsImpl.js new file mode 100644 index 00000000000..79cca164d1b --- /dev/null +++ b/chromium/third_party/WebKit/Source/devtools/front_end/timeline/TimelineUIUtilsImpl.js @@ -0,0 +1,571 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @constructor + * @extends {WebInspector.TimelineUIUtils} + */ +WebInspector.TimelineUIUtilsImpl = function() +{ + WebInspector.TimelineUIUtils.call(this); +} + +WebInspector.TimelineUIUtilsImpl.prototype = { + /** + * @param {!WebInspector.TimelineModel.Record} record + * @return {boolean} + */ + isBeginFrame: function(record) + { + return record.type() === WebInspector.TimelineModel.RecordType.BeginFrame; + }, + /** + * @param {!WebInspector.TimelineModel.Record} record + * @return {boolean} + */ + isProgram: function(record) + { + return record.type() === WebInspector.TimelineModel.RecordType.Program; + }, + /** + * @param {string} recordType + * @return {boolean} + */ + isCoalescable: function(recordType) + { + return !!WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[recordType]; + }, + + /** + * @param {!WebInspector.TimelineModel.Record} record + * @return {boolean} + */ + isEventDivider: function(record) + { + return WebInspector.TimelineUIUtilsImpl.isEventDivider(record); + }, + + /** + * @param {!WebInspector.TimelineModel.Record} record + * @return {?Object} + */ + countersForRecord: function(record) + { + return record.type() === WebInspector.TimelineModel.RecordType.UpdateCounters ? record.data() : null; + }, + + /** + * @param {!WebInspector.TimelineModel.Record} record + * @return {?Object} + */ + highlightQuadForRecord: function(record) + { + var recordTypes = WebInspector.TimelineModel.RecordType; + switch(record.type()) { + case recordTypes.Layout: + return record.data().root; + case recordTypes.Paint: + return record.data().clip; + default: + return null; + } + }, + + /** + * @param {!WebInspector.TimelineModel.Record} record + * @return {string} + */ + titleForRecord: function(record) + { + return WebInspector.TimelineUIUtilsImpl.recordTitle(record); + }, + + /** + * @param {!WebInspector.TimelineModel.Record} record + * @param {!WebInspector.Linkifier} linkifier + * @param {boolean} loadedFromFile + * @return {?Node} + */ + buildDetailsNode: function(record, linkifier, loadedFromFile) + { + return WebInspector.TimelineUIUtilsImpl.buildDetailsNode(record, linkifier, loadedFromFile); + }, + + /** + * @param {!WebInspector.TimelineModel.Record} record + * @param {!WebInspector.TimelineModel} model + * @param {!WebInspector.Linkifier} linkifier + * @param {function(!DocumentFragment)} callback + * @param {boolean} loadedFromFile + */ + generateDetailsContent: function(record, model, linkifier, callback, loadedFromFile) + { + WebInspector.TimelineUIUtilsImpl.generateDetailsContent(record, model, linkifier, callback, loadedFromFile); + }, + + /** + * @return {!Element} + */ + createBeginFrameDivider: function() + { + return this.createEventDivider(WebInspector.TimelineModel.RecordType.BeginFrame); + }, + + /** + * @param {string} recordType + * @param {string=} title + * @return {!Element} + */ + createEventDivider: function(recordType, title) + { + return WebInspector.TimelineUIUtilsImpl._createEventDivider(recordType, title); + }, + + /** + * @param {!WebInspector.TimelineModel.Record} record + * @param {!RegExp} regExp + * @return {boolean} + */ + testContentMatching: function(record, regExp) + { + var tokens = [WebInspector.TimelineUIUtilsImpl.recordTitle(record)]; + var data = record.data(); + for (var key in data) + tokens.push(data[key]) + return regExp.test(tokens.join("|")); + }, + + __proto__: WebInspector.TimelineUIUtils.prototype +} + + +WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes = {}; +WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[WebInspector.TimelineModel.RecordType.Layout] = 1; +WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[WebInspector.TimelineModel.RecordType.Paint] = 1; +WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[WebInspector.TimelineModel.RecordType.Rasterize] = 1; +WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[WebInspector.TimelineModel.RecordType.DecodeImage] = 1; +WebInspector.TimelineUIUtilsImpl._coalescableRecordTypes[WebInspector.TimelineModel.RecordType.ResizeImage] = 1; + + +/** + * @param {!WebInspector.TimelineModel.Record} record + * @return {string} + */ +WebInspector.TimelineUIUtilsImpl.recordTitle = function(record) +{ + var recordData = record.data(); + if (record.type() === WebInspector.TimelineModel.RecordType.TimeStamp) + return recordData["message"]; + if (record.type() === WebInspector.TimelineModel.RecordType.JSFrame) + return recordData["functionName"]; + if (WebInspector.TimelineUIUtilsImpl.isEventDivider(record)) { + var startTime = Number.millisToString(record.startTime() - record._model.minimumRecordTime()); + return WebInspector.UIString("%s at %s", WebInspector.TimelineUIUtils.recordStyle(record).title, startTime, true); + } + return WebInspector.TimelineUIUtils.recordStyle(record).title; +} + +/** + * @param {!WebInspector.TimelineModel.Record} record + * @return {boolean} + */ +WebInspector.TimelineUIUtilsImpl.isEventDivider = function(record) +{ + var recordTypes = WebInspector.TimelineModel.RecordType; + if (record.type() === recordTypes.TimeStamp) + return true; + if (record.type() === recordTypes.MarkFirstPaint) + return true; + if (record.type() === recordTypes.MarkDOMContent || record.type() === recordTypes.MarkLoad) + return record.data()["isMainFrame"]; + return false; +} + +/** + * @param {!WebInspector.TimelineModel.Record} record + * @param {!WebInspector.Linkifier} linkifier + * @param {boolean} loadedFromFile + * @return {?Node} + */ +WebInspector.TimelineUIUtilsImpl.buildDetailsNode = function(record, linkifier, loadedFromFile) +{ + var details; + var detailsText; + var recordData = record.data(); + switch (record.type()) { + case WebInspector.TimelineModel.RecordType.GCEvent: + detailsText = WebInspector.UIString("%s collected", Number.bytesToString(recordData["usedHeapSizeDelta"])); + break; + case WebInspector.TimelineModel.RecordType.TimerFire: + detailsText = recordData["timerId"]; + break; + case WebInspector.TimelineModel.RecordType.FunctionCall: + details = linkifyLocation(recordData["scriptId"], recordData["scriptName"], recordData["scriptLine"], 0); + break; + case WebInspector.TimelineModel.RecordType.FireAnimationFrame: + detailsText = recordData["id"]; + break; + case WebInspector.TimelineModel.RecordType.EventDispatch: + detailsText = recordData ? recordData["type"] : null; + break; + case WebInspector.TimelineModel.RecordType.Paint: + var width = WebInspector.TimelineUIUtils._quadWidth(recordData.clip); + var height = WebInspector.TimelineUIUtils._quadHeight(recordData.clip); + if (width && height) + detailsText = WebInspector.UIString("%d\u2009\u00d7\u2009%d", width, height); + break; + case WebInspector.TimelineModel.RecordType.TimerInstall: + case WebInspector.TimelineModel.RecordType.TimerRemove: + details = linkifyTopCallFrame(); + detailsText = recordData["timerId"]; + break; + case WebInspector.TimelineModel.RecordType.RequestAnimationFrame: + case WebInspector.TimelineModel.RecordType.CancelAnimationFrame: + details = linkifyTopCallFrame(); + detailsText = recordData["id"]; + break; + case WebInspector.TimelineModel.RecordType.ParseHTML: + case WebInspector.TimelineModel.RecordType.RecalculateStyles: + details = linkifyTopCallFrame(); + break; + case WebInspector.TimelineModel.RecordType.EvaluateScript: + var url = recordData["url"]; + if (url) + details = linkifyLocation("", url, recordData["lineNumber"], 0); + break; + case WebInspector.TimelineModel.RecordType.XHRReadyStateChange: + case WebInspector.TimelineModel.RecordType.XHRLoad: + case WebInspector.TimelineModel.RecordType.ResourceSendRequest: + case WebInspector.TimelineModel.RecordType.DecodeImage: + case WebInspector.TimelineModel.RecordType.ResizeImage: + var url = recordData["url"]; + if (url) + detailsText = WebInspector.displayNameForURL(url); + break; + case WebInspector.TimelineModel.RecordType.ResourceReceivedData: + case WebInspector.TimelineModel.RecordType.ResourceReceiveResponse: + case WebInspector.TimelineModel.RecordType.ResourceFinish: + var initiator = record.initiator(); + if (initiator) { + var url = initiator.data()["url"]; + if (url) + detailsText = WebInspector.displayNameForURL(url); + } + break; + case WebInspector.TimelineModel.RecordType.ConsoleTime: + detailsText = recordData["message"]; + break; + case WebInspector.TimelineModel.RecordType.EmbedderCallback: + detailsText = recordData["callbackName"]; + break; + default: + details = linkifyTopCallFrame(); + break; + } + + if (!details && detailsText) + details = document.createTextNode(detailsText); + return details; + + /** + * @param {string} scriptId + * @param {string} url + * @param {number} lineNumber + * @param {number=} columnNumber + */ + function linkifyLocation(scriptId, url, lineNumber, columnNumber) + { + if (!loadedFromFile && scriptId !== "0") { + var location = new WebInspector.DebuggerModel.Location( + record.target(), + scriptId, + lineNumber - 1, + (columnNumber || 1) - 1); + return linkifier.linkifyRawLocation(location, "timeline-details"); + } + + if (!url) + return null; + + // FIXME(62725): stack trace line/column numbers are one-based. + columnNumber = columnNumber ? columnNumber - 1 : 0; + return linkifier.linkifyLocation(record.target(), url, lineNumber - 1, columnNumber, "timeline-details"); + } + + /** + * @param {!ConsoleAgent.CallFrame} callFrame + */ + function linkifyCallFrame(callFrame) + { + return linkifyLocation(callFrame.scriptId, callFrame.url, callFrame.lineNumber, callFrame.columnNumber); + } + + /** + * @return {?Element} + */ + function linkifyTopCallFrame() + { + if (record.stackTrace()) + return linkifyCallFrame(record.stackTrace()[0]); + if (record.callSiteStackTrace()) + return linkifyCallFrame(record.callSiteStackTrace()[0]); + return null; + } +} + +/** + * @param {string=} recordType + * @return {boolean} + */ +WebInspector.TimelineUIUtilsImpl._needsPreviewElement = function(recordType) +{ + if (!recordType) + return false; + const recordTypes = WebInspector.TimelineModel.RecordType; + switch (recordType) { + case recordTypes.ResourceSendRequest: + case recordTypes.ResourceReceiveResponse: + case recordTypes.ResourceReceivedData: + case recordTypes.ResourceFinish: + return true; + default: + return false; + } +} + +/** + * @param {!WebInspector.TimelineModel.Record} record + * @param {!WebInspector.TimelineModel} model + * @param {!WebInspector.Linkifier} linkifier + * @param {function(!DocumentFragment)} callback + * @param {boolean} loadedFromFile + */ +WebInspector.TimelineUIUtilsImpl.generateDetailsContent = function(record, model, linkifier, callback, loadedFromFile) +{ + var imageElement = /** @type {?Element} */ (record.getUserObject("TimelineUIUtils::preview-element") || null); + var relatedNode = null; + var recordData = record.data(); + var barrier = new CallbackBarrier(); + if (!imageElement && WebInspector.TimelineUIUtilsImpl._needsPreviewElement(record.type())) + WebInspector.DOMPresentationUtils.buildImagePreviewContents(record.target(), recordData["url"], false, barrier.createCallback(saveImage)); + if (recordData["backendNodeId"]) + record.target().domModel.pushNodesByBackendIdsToFrontend([recordData["backendNodeId"]], barrier.createCallback(setRelatedNode)); + barrier.callWhenDone(callbackWrapper); + + /** + * @param {!Element=} element + */ + function saveImage(element) + { + imageElement = element || null; + record.setUserObject("TimelineUIUtils::preview-element", element); + } + + /** + * @param {?Array.<!DOMAgent.NodeId>} nodeIds + */ + function setRelatedNode(nodeIds) + { + if (nodeIds) + relatedNode = record.target().domModel.nodeForId(nodeIds[0]); + } + + function callbackWrapper() + { + callback(WebInspector.TimelineUIUtilsImpl._generateDetailsContentSynchronously(record, model, linkifier, imageElement, relatedNode, loadedFromFile)); + } +} + +/** + * @param {!WebInspector.TimelineModel.Record} record + * @param {!WebInspector.TimelineModel} model + * @param {!WebInspector.Linkifier} linkifier + * @param {?Element} imagePreviewElement + * @param {?WebInspector.DOMNode} relatedNode + * @param {boolean} loadedFromFile + * @return {!DocumentFragment} + */ +WebInspector.TimelineUIUtilsImpl._generateDetailsContentSynchronously = function(record, model, linkifier, imagePreviewElement, relatedNode, loadedFromFile) +{ + var fragment = document.createDocumentFragment(); + if (record.children().length) + fragment.appendChild(WebInspector.TimelineUIUtils.generatePieChart(record.aggregatedStats(), record.category(), record.selfTime())); + else + fragment.appendChild(WebInspector.TimelineUIUtils.generatePieChart(record.aggregatedStats())); + + const recordTypes = WebInspector.TimelineModel.RecordType; + + // The messages may vary per record.type(); + var callSiteStackTraceLabel; + var callStackLabel; + var relatedNodeLabel; + + var contentHelper = new WebInspector.TimelineDetailsContentHelper(record.target(), linkifier, true); + contentHelper.appendTextRow(WebInspector.UIString("Self Time"), Number.millisToString(record.selfTime(), true)); + contentHelper.appendTextRow(WebInspector.UIString("Start Time"), Number.millisToString(record.startTime() - model.minimumRecordTime())); + var recordData = record.data(); + + switch (record.type()) { + case recordTypes.GCEvent: + contentHelper.appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(recordData["usedHeapSizeDelta"])); + break; + case recordTypes.TimerFire: + callSiteStackTraceLabel = WebInspector.UIString("Timer installed"); + // Fall-through intended. + + case recordTypes.TimerInstall: + case recordTypes.TimerRemove: + contentHelper.appendTextRow(WebInspector.UIString("Timer ID"), recordData["timerId"]); + if (record.type() === recordTypes.TimerInstall) { + contentHelper.appendTextRow(WebInspector.UIString("Timeout"), Number.millisToString(recordData["timeout"])); + contentHelper.appendTextRow(WebInspector.UIString("Repeats"), !recordData["singleShot"]); + } + break; + case recordTypes.FireAnimationFrame: + callSiteStackTraceLabel = WebInspector.UIString("Animation frame requested"); + contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), recordData["id"]); + break; + case recordTypes.FunctionCall: + if (recordData["scriptName"]) + contentHelper.appendLocationRow(WebInspector.UIString("Location"), recordData["scriptName"], recordData["scriptLine"]); + break; + case recordTypes.ResourceSendRequest: + case recordTypes.ResourceReceiveResponse: + case recordTypes.ResourceReceivedData: + case recordTypes.ResourceFinish: + var url; + if (record.type() === recordTypes.ResourceSendRequest) + url = recordData["url"]; + else if (record.initiator()) + url = record.initiator().data()["url"]; + if (url) + contentHelper.appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(url)); + if (imagePreviewElement) + contentHelper.appendElementRow(WebInspector.UIString("Preview"), imagePreviewElement); + if (recordData["requestMethod"]) + contentHelper.appendTextRow(WebInspector.UIString("Request Method"), recordData["requestMethod"]); + if (typeof recordData["statusCode"] === "number") + contentHelper.appendTextRow(WebInspector.UIString("Status Code"), recordData["statusCode"]); + if (recordData["mimeType"]) + contentHelper.appendTextRow(WebInspector.UIString("MIME Type"), recordData["mimeType"]); + if (recordData["encodedDataLength"]) + contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", recordData["encodedDataLength"])); + break; + case recordTypes.EvaluateScript: + var url = recordData["url"]; + if (url) + contentHelper.appendLocationRow(WebInspector.UIString("Script"), url, recordData["lineNumber"]); + break; + case recordTypes.Paint: + var clip = recordData["clip"]; + contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", clip[0], clip[1])); + var clipWidth = WebInspector.TimelineUIUtils._quadWidth(clip); + var clipHeight = WebInspector.TimelineUIUtils._quadHeight(clip); + contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d × %d", clipWidth, clipHeight)); + // Fall-through intended. + + case recordTypes.PaintSetup: + case recordTypes.Rasterize: + case recordTypes.ScrollLayer: + relatedNodeLabel = WebInspector.UIString("Layer root"); + break; + case recordTypes.DecodeImage: + case recordTypes.ResizeImage: + relatedNodeLabel = WebInspector.UIString("Image element"); + var url = recordData["url"]; + if (url) + contentHelper.appendElementRow(WebInspector.UIString("Image URL"), WebInspector.linkifyResourceAsNode(url)); + break; + case recordTypes.RecalculateStyles: // We don't want to see default details. + if (recordData["elementCount"]) + contentHelper.appendTextRow(WebInspector.UIString("Elements affected"), recordData["elementCount"]); + callStackLabel = WebInspector.UIString("Styles recalculation forced"); + break; + case recordTypes.Layout: + if (recordData["dirtyObjects"]) + contentHelper.appendTextRow(WebInspector.UIString("Nodes that need layout"), recordData["dirtyObjects"]); + if (recordData["totalObjects"]) + contentHelper.appendTextRow(WebInspector.UIString("Layout tree size"), recordData["totalObjects"]); + if (typeof recordData["partialLayout"] === "boolean") { + contentHelper.appendTextRow(WebInspector.UIString("Layout scope"), + recordData["partialLayout"] ? WebInspector.UIString("Partial") : WebInspector.UIString("Whole document")); + } + callSiteStackTraceLabel = WebInspector.UIString("Layout invalidated"); + callStackLabel = WebInspector.UIString("Layout forced"); + relatedNodeLabel = WebInspector.UIString("Layout root"); + break; + case recordTypes.ConsoleTime: + contentHelper.appendTextRow(WebInspector.UIString("Message"), recordData["message"]); + break; + case recordTypes.WebSocketCreate: + case recordTypes.WebSocketSendHandshakeRequest: + case recordTypes.WebSocketReceiveHandshakeResponse: + case recordTypes.WebSocketDestroy: + var initiatorData = record.initiator() ? record.initiator().data() : recordData; + if (typeof initiatorData["webSocketURL"] !== "undefined") + contentHelper.appendTextRow(WebInspector.UIString("URL"), initiatorData["webSocketURL"]); + if (typeof initiatorData["webSocketProtocol"] !== "undefined") + contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"), initiatorData["webSocketProtocol"]); + if (typeof recordData["message"] !== "undefined") + contentHelper.appendTextRow(WebInspector.UIString("Message"), recordData["message"]); + break; + case recordTypes.EmbedderCallback: + contentHelper.appendTextRow(WebInspector.UIString("Callback Function"), recordData["callbackName"]); + break; + default: + var detailsNode = WebInspector.TimelineUIUtilsImpl.buildDetailsNode(record, linkifier, loadedFromFile); + if (detailsNode) + contentHelper.appendElementRow(WebInspector.UIString("Details"), detailsNode); + break; + } + + if (relatedNode) + contentHelper.appendElementRow(relatedNodeLabel || WebInspector.UIString("Related node"), WebInspector.DOMPresentationUtils.linkifyNodeReference(relatedNode)); + + if (recordData["scriptName"] && record.type() !== recordTypes.FunctionCall) + contentHelper.appendLocationRow(WebInspector.UIString("Function Call"), recordData["scriptName"], recordData["scriptLine"]); + var callSiteStackTrace = record.callSiteStackTrace(); + if (callSiteStackTrace) + contentHelper.appendStackTrace(callSiteStackTraceLabel || WebInspector.UIString("Call Site stack"), callSiteStackTrace); + var recordStackTrace = record.stackTrace(); + if (recordStackTrace) + contentHelper.appendStackTrace(callStackLabel || WebInspector.UIString("Call Stack"), recordStackTrace); + + if (record.warnings()) { + var ul = document.createElement("ul"); + for (var i = 0; i < record.warnings().length; ++i) + ul.createChild("li").textContent = record.warnings()[i]; + contentHelper.appendElementRow(WebInspector.UIString("Warning"), ul); + } + fragment.appendChild(contentHelper.element); + return fragment; +} + +/** + * @param {string} recordType + * @param {string=} title + * @return {!Element} + */ +WebInspector.TimelineUIUtilsImpl._createEventDivider = function(recordType, title) +{ + var eventDivider = document.createElement("div"); + eventDivider.className = "resources-event-divider"; + var recordTypes = WebInspector.TimelineModel.RecordType; + + if (recordType === recordTypes.MarkDOMContent) + eventDivider.className += " resources-blue-divider"; + else if (recordType === recordTypes.MarkLoad) + eventDivider.className += " resources-red-divider"; + else if (recordType === recordTypes.MarkFirstPaint) + eventDivider.className += " resources-green-divider"; + else if (recordType === recordTypes.TimeStamp) + eventDivider.className += " resources-orange-divider"; + else if (recordType === recordTypes.BeginFrame) + eventDivider.className += " timeline-frame-divider"; + + if (title) + eventDivider.title = title; + + return eventDivider; +} |