summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/catapult/tracing/tracing/ui/extras
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/catapult/tracing/tracing/ui/extras')
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/about_tracing.html26
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/common.css25
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/devtools_stream.html99
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_connection.html115
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_tracing_controller_client.html216
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_tracing_controller_client_test.html396
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/mock_tracing_controller_client.html88
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view.html372
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view_test.html76
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller.html187
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller_test.html57
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog.html689
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog_test.html426
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/tracing_controller_client.html34
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/xhr_based_tracing_controller_client.html115
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/cc.html14
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger.html451
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger_test.html134
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_list_item.html134
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_list_view.html60
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/images/input-event.pngbin0 -> 3344 bytes
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/images/input-event.svg114
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_picker.html336
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view.html142
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view_test.html37
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view.html1200
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view_test.html113
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view.html165
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view_test.html55
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_debugger.html455
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_debugger_test.html32
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_chart_summary_view.html458
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_chart_view.html505
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_list_view.html261
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_list_view_test.html56
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_view.html62
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_selection.html140
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_selection_test.html39
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view.html222
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view_test.html70
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/selection.html304
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/tile_view.html56
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/codesearch.html49
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/gpu.html10
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/images/checkerboard.pngbin0 -> 245 bytes
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/state_view.css15
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/state_view.html48
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/layout_tree_sub_view.html229
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/chrome_config.html33
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/html_results.html123
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/main.html65
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/scalar_value.html43
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comment_element.html84
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comments_side_panel.html185
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comments_side_panel_test.html71
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/drive/drive_comment_provider.html99
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/drive/index.html463
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/full_config.html19
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/lean_config.html21
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/alerts_side_panel.html172
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/alerts_side_panel_test.html61
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/frame_data_side_panel.html347
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/frame_data_side_panel_test.html165
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/input_latency_side_panel.html334
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/input_latency_side_panel_test.html148
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats.html12
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track.css15
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track.html451
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track_test.html116
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_snapshot_view.css28
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_snapshot_view.html84
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/systrace_config.html18
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/gc_objects_stats_table.html728
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/gc_objects_stats_table_test.html198
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/ic_stats_table.html181
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_gc_stats_thread_slice_sub_view.html45
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_ic_stats_thread_slice_sub_view.html43
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_thread_slice_sub_view.html44
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_thread_slice_sub_view_test.html87
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/runtime_call_stats_table.html197
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/runtime_call_stats_table_test.html236
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_gc_stats_thread_slice_sub_view.html43
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_ic_stats_thread_slice_sub_view.html42
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_thread_slice_sub_view.html43
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_thread_slice_sub_view_test.html114
-rw-r--r--chromium/third_party/catapult/tracing/tracing/ui/extras/v8_config.html17
86 files changed, 14062 insertions, 0 deletions
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/about_tracing.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/about_tracing.html
new file mode 100644
index 00000000000..8e579d90921
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/about_tracing.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+<link rel="import" href="/tracing/ui/base/base.html" data-suppress-import-order>
+
+<link rel="stylesheet" href="/tracing/ui/extras/about_tracing/common.css">
+<link rel="import" href="/tracing/ui/extras/about_tracing/profiling_view.html">
+<link rel="import" href="/tracing/ui/extras/full_config.html">
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.about_tracing', function() {
+ window.profilingView = undefined; // Made global for debugging purposes only.
+
+ document.addEventListener('DOMContentLoaded', function() {
+ window.profilingView = new tr.ui.e.about_tracing.ProfilingView();
+ profilingView.timelineView.globalMode = true;
+ Polymer.dom(document.body).appendChild(profilingView);
+ });
+
+ return {};
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/common.css b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/common.css
new file mode 100644
index 00000000000..3db21b67ffa
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/common.css
@@ -0,0 +1,25 @@
+/* Copyright (c) 2012 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.
+ */
+html,
+body {
+ height: 100%;
+}
+
+body {
+ flex-direction: column;
+ display: flex;
+ margin: 0;
+ padding: 0;
+}
+
+body > x-profiling-view {
+ flex: 1 1 auto;
+ min-height: 0;
+}
+
+body > x-profiling-view > x-timeline-view:focus {
+ outline: 0
+}
+
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/devtools_stream.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/devtools_stream.html
new file mode 100644
index 00000000000..fa348a7b661
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/devtools_stream.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<!--
+Copyright 2017 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.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<link rel="import" href="/tracing/base/base64.html">
+<script>
+
+'use strict';
+
+/**
+ * A devtools protocol stream object.
+ *
+ * This reads a stream of data over the remote debugging connection.
+ */
+tr.exportTo('tr.ui.e.about_tracing', function() {
+ class DevtoolsStream {
+ constructor(connection, streamHandle) {
+ this.connection_ = connection;
+ this.streamHandle_ = streamHandle;
+ this.closed_ = false;
+ }
+
+ async read() {
+ if (this.closed_) {
+ throw new Error('stream is closed');
+ }
+
+ const pendingRequests = [];
+
+ const READ_REQUEST_BYTES = 32768;
+ const makeRequest = () => {
+ pendingRequests.push(this.connection_.req(
+ 'IO.read',
+ {
+ handle: this.streamHandle_,
+ size: READ_REQUEST_BYTES,
+ }));
+ };
+
+ const MAX_CONCURRENT_REQUESTS = 2;
+ for (let i = 0; i < MAX_CONCURRENT_REQUESTS; ++i) {
+ makeRequest();
+ }
+
+ const chunks = [];
+ let base64 = false;
+ while (true) {
+ const request = pendingRequests.shift();
+ const response = await request;
+
+ chunks.push(response.data);
+ if (response.base64Encoded) {
+ base64 = true;
+ }
+ if (response.eof) {
+ break;
+ }
+
+ makeRequest();
+ }
+
+ if (base64) {
+ let totalSize = 0;
+ for (const chunk of chunks) {
+ totalSize += tr.b.Base64.getDecodedBufferLength(chunk);
+ }
+ const buffer = new ArrayBuffer(totalSize);
+ let offset = 0;
+ for (const chunk of chunks) {
+ offset += tr.b.Base64.DecodeToTypedArray(
+ chunk,
+ new DataView(buffer, offset));
+ }
+ return buffer;
+ }
+
+ return chunks.join('');
+ }
+
+ close() {
+ this.closed_ = true;
+ return this.connection_.req('IO.close', { handle: this.streamHandle_ });
+ }
+
+ async readAndClose() {
+ const data = await this.read();
+ this.close();
+ return data;
+ }
+ }
+
+ return {
+ DevtoolsStream,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_connection.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_connection.html
new file mode 100644
index 00000000000..791f5e77705
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_connection.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+<link rel="import" href="/tracing/base/base.html">
+<script>
+
+'use strict';
+
+/**
+ * Contains connection code that inspector's embedding framework calls on
+ * tracing, and that tracing can use to talk to inspector.
+ */
+tr.exportTo('tr.ui.e.about_tracing', function() {
+ class InspectorConnection {
+ constructor(windowGlobal) {
+ if (!windowGlobal.DevToolsHost) {
+ throw new Error('Requires window.DevToolsHost');
+ }
+ this.devToolsHost_ = windowGlobal.DevToolsHost;
+ this.installDevToolsAPI_(windowGlobal);
+
+ this.nextRequestId_ = 1;
+ this.pendingRequestResolversId_ = {};
+
+ this.notificationListenersByMethodName_ = {};
+ }
+
+ req(method, params) {
+ const id = this.nextRequestId_++;
+ const msg = JSON.stringify({
+ id,
+ method,
+ params
+ });
+ const devtoolsMessageStr = JSON.stringify(
+ {id, 'method': 'dispatchProtocolMessage', 'params': [msg]});
+ this.devToolsHost_.sendMessageToEmbedder(devtoolsMessageStr);
+
+ return new Promise(function(resolve, reject) {
+ this.pendingRequestResolversId_[id] = {
+ resolve,
+ reject
+ };
+ }.bind(this));
+ }
+
+ setNotificationListener(method, listener) {
+ this.notificationListenersByMethodName_[method] = listener;
+ }
+
+ dispatchMessage_(payload) {
+ const isStringPayload = typeof payload === 'string';
+ // Special handling for Tracing.dataCollected because it is high
+ // bandwidth.
+ const isDataCollectedMessage = isStringPayload ?
+ payload.includes('"method": "Tracing.dataCollected"') :
+ payload.method === 'Tracing.dataCollected';
+ if (isDataCollectedMessage) {
+ const listener = this.notificationListenersByMethodName_[
+ 'Tracing.dataCollected'];
+ if (listener) {
+ // FIXME(loislo): trace viewer should be able to process
+ // raw message object because string based version a few times
+ // slower on the browser side.
+ // see https://codereview.chromium.org/784513002.
+ listener(isStringPayload ? payload : JSON.stringify(payload));
+ return;
+ }
+ }
+
+ const message = isStringPayload ? JSON.parse(payload) : payload;
+ if (message.id) {
+ const resolver = this.pendingRequestResolversId_[message.id];
+ if (resolver === undefined) {
+ return;
+ }
+ if (message.error) {
+ resolver.reject(message.error);
+ return;
+ }
+ resolver.resolve(message.result);
+ return;
+ }
+
+ if (message.method) {
+ const listener = this.notificationListenersByMethodName_[
+ message.method];
+ if (listener === undefined) return;
+ listener(message.params);
+ return;
+ }
+ }
+
+ installDevToolsAPI_(windowGlobal) {
+ // Interface used by inspector when it hands data to us from the backend.
+ windowGlobal.DevToolsAPI = {
+ setToolbarColors() { },
+ addExtensions() { },
+ setInspectedPageId() { },
+ dispatchMessage: this.dispatchMessage_.bind(this),
+ };
+
+ // Temporary until inspector backend switches to DevToolsAPI.
+ windowGlobal.InspectorFrontendAPI = windowGlobal.DevToolsAPI;
+ }
+ }
+
+ return {
+ InspectorConnection,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_tracing_controller_client.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_tracing_controller_client.html
new file mode 100644
index 00000000000..ac5afabae12
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_tracing_controller_client.html
@@ -0,0 +1,216 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/ui/extras/about_tracing/devtools_stream.html">
+<link rel="import" href="/tracing/ui/extras/about_tracing/inspector_connection.html">
+<link rel="import"
+ href="/tracing/ui/extras/about_tracing/tracing_controller_client.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.about_tracing', function() {
+ function createResolvedPromise(data) {
+ const promise = new Promise(function(resolve, reject) {
+ if (data) {
+ resolve(data);
+ } else {
+ resolve();
+ }
+ });
+ return promise;
+ }
+
+ function appendTraceChunksTo(chunks, messageString) {
+ if (typeof messageString !== 'string') {
+ throw new Error('Invalid data');
+ }
+ const re = /"params":\s*\{\s*"value":\s*\[([^]+)\]\s*\}\s*\}/;
+ const m = re.exec(messageString);
+ if (!m) {
+ throw new Error('Malformed response');
+ }
+
+ if (chunks.length > 1) {
+ chunks.push(',');
+ }
+ chunks.push(m[1]);
+ }
+
+ /**
+ * Controls tracing using the inspector's FrontendAgentHost APIs.
+ */
+ class InspectorTracingControllerClient extends
+ tr.ui.e.about_tracing.TracingControllerClient {
+ constructor(connection) {
+ super();
+ this.recording_ = false;
+ this.bufferUsage_ = 0;
+ this.conn_ = connection;
+ this.currentTraceTextChunks_ = undefined;
+ }
+
+ beginMonitoring(monitoringOptions) {
+ throw new Error('Not implemented');
+ }
+
+ endMonitoring() {
+ throw new Error('Not implemented');
+ }
+
+ captureMonitoring() {
+ throw new Error('Not implemented');
+ }
+
+ getMonitoringStatus() {
+ return createResolvedPromise({
+ isMonitoring: false,
+ categoryFilter: '',
+ useSystemTracing: false,
+ useContinuousTracing: false,
+ useSampling: false
+ });
+ }
+
+ getCategories() {
+ const res = this.conn_.req('Tracing.getCategories', {});
+ return res.then(function(result) {
+ return result.categories;
+ }, function(err) {
+ return [];
+ });
+ }
+
+ beginRecording(recordingOptions) {
+ if (this.recording_) {
+ throw new Error('Already recording');
+ }
+ this.recording_ = 'starting';
+
+ // The devtools and tracing endpoints have slightly different parameter
+ // configurations. Noteably, recordMode has different spelling
+ // requirements.
+ function RewriteRecordMode(recordMode) {
+ if (recordMode === 'record-until-full') {
+ return 'recordUntilFull';
+ }
+ if (recordMode === 'record-continuously') {
+ return 'recordContinuously';
+ }
+ if (recordMode === 'record-as-much-as-possible') {
+ return 'recordAsMuchAsPossible';
+ }
+ return 'unsupported record mode';
+ }
+
+ const traceConfigStr = {
+ includedCategories: recordingOptions.included_categories,
+ excludedCategories: recordingOptions.excluded_categories,
+ recordMode: RewriteRecordMode(recordingOptions.record_mode),
+ enableSystrace: recordingOptions.enable_systrace
+ };
+ if ('memory_dump_config' in recordingOptions) {
+ traceConfigStr.memoryDumpConfig = recordingOptions.memory_dump_config;
+ }
+ let res = this.conn_.req(
+ 'Tracing.start',
+ {
+ traceConfig: traceConfigStr,
+ transferMode: 'ReturnAsStream',
+ streamCompression: 'gzip',
+ bufferUsageReportingInterval: 1000
+ });
+ res = res.then(
+ function ok() {
+ this.conn_.setNotificationListener(
+ 'Tracing.bufferUsage',
+ this.onBufferUsageUpdateFromInspector_.bind(this));
+ this.recording_ = true;
+ }.bind(this),
+ function error() {
+ this.recording_ = false;
+ }.bind(this));
+ return res;
+ }
+
+ onBufferUsageUpdateFromInspector_(params) {
+ this.bufferUsage_ = params.value || params.percentFull;
+ }
+
+ beginGetBufferPercentFull() {
+ return tr.b.timeout(100).then(() => this.bufferUsage_);
+ }
+
+ onDataCollected_(messageString) {
+ appendTraceChunksTo(this.currentTraceTextChunks_, messageString);
+ }
+
+ async endRecording() {
+ if (this.recording_ === false) {
+ return createResolvedPromise();
+ }
+
+ if (this.recording_ !== true) {
+ throw new Error('Cannot end');
+ }
+
+ this.currentTraceTextChunks_ = ['['];
+ const clearListeners = () => {
+ this.conn_.setNotificationListener(
+ 'Tracing.bufferUsage', undefined);
+ this.conn_.setNotificationListener(
+ 'Tracing.tracingComplete', undefined);
+ this.conn_.setNotificationListener(
+ 'Tracing.dataCollected', undefined);
+ };
+
+ try {
+ this.conn_.setNotificationListener(
+ 'Tracing.dataCollected', this.onDataCollected_.bind(this));
+
+ const tracingComplete = new Promise((resolve, reject) => {
+ this.conn_.setNotificationListener(
+ 'Tracing.tracingComplete', resolve);
+ });
+
+ this.recording_ = 'stopping';
+ await this.conn_.req('Tracing.end', {});
+ const params = await tracingComplete;
+
+ this.traceName_ = 'trace.json';
+ if ('stream' in params) {
+ const stream = new tr.ui.e.about_tracing.DevtoolsStream(
+ this.conn_, params.stream);
+ const streamCompression = params.streamCompression || 'none';
+ if (streamCompression === 'gzip') {
+ this.traceName_ = 'trace.json.gz';
+ }
+
+ return await stream.readAndClose();
+ }
+
+ this.currentTraceTextChunks_.push(']');
+ const traceText = this.currentTraceTextChunks_.join('');
+ this.currentTraceTextChunks_ = undefined;
+ return traceText;
+ } finally {
+ clearListeners();
+ this.recording_ = false;
+ }
+ }
+
+ defaultTraceName() {
+ return this.traceName_;
+ }
+ }
+
+ return {
+ InspectorTracingControllerClient,
+ appendTraceChunksTo,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_tracing_controller_client_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_tracing_controller_client_test.html
new file mode 100644
index 00000000000..4a6585ac9e8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/inspector_tracing_controller_client_test.html
@@ -0,0 +1,396 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import"
+ href="/tracing/ui/extras/about_tracing/inspector_connection.html">
+<link rel="import"
+ href="/tracing/ui/extras/about_tracing/inspector_tracing_controller_client.html">
+
+<script>
+'use strict';
+
+function makeController() {
+ const controller =
+ new tr.ui.e.about_tracing.InspectorTracingControllerClient();
+ controller.conn_ = new (function() {
+ this.req = function(method, params) {
+ const msg = JSON.stringify({
+ id: 1,
+ method,
+ params
+ });
+ return new (function() {
+ this.msg = msg;
+ this.then = function(m1, m2) {
+ return this;
+ };
+ })();
+ };
+ this.setNotificationListener = function(method, listener) {
+ };
+ })();
+ return controller;
+}
+
+tr.b.unittest.testSuite(function() {
+ test('beginRecording_sendCategoriesAndOptions', function() {
+ const controller = makeController();
+
+ const recordingOptions = {
+ included_categories: ['a', 'b', 'c'],
+ excluded_categories: ['e'],
+ enable_systrace: false,
+ record_mode: 'record-until-full',
+ };
+
+ const result = JSON.parse(controller.beginRecording(recordingOptions).msg);
+ assert.deepEqual(
+ result.params.traceConfig.includedCategories, ['a', 'b', 'c']);
+ assert.deepEqual(
+ result.params.traceConfig.excludedCategories, ['e']);
+ assert.strictEqual(
+ result.params.traceConfig.recordMode, 'recordUntilFull');
+ assert.isFalse(
+ result.params.traceConfig.enableSystrace);
+ assert.isFalse('memoryDumpConfig' in result.params.traceConfig);
+ });
+
+ test('beginRecording_sendCategoriesAndOptionsWithMemoryInfra', function() {
+ const controller = makeController();
+
+ const memoryConfig = { triggers: [] };
+ memoryConfig.triggers.push(
+ {'mode': 'detailed', 'periodic_interval_ms': 10000});
+ const recordingOptions = {
+ included_categories: ['c', 'disabled-by-default-memory-infra', 'a'],
+ excluded_categories: ['e'],
+ enable_systrace: false,
+ record_mode: 'test-mode',
+ memory_dump_config: memoryConfig,
+ };
+
+ const result = JSON.parse(controller.beginRecording(recordingOptions).msg);
+ assert.isTrue(
+ result.params.traceConfig.memoryDumpConfig.triggers.length === 1);
+ assert.strictEqual(result.params.traceConfig.memoryDumpConfig.
+ triggers[0].mode, 'detailed');
+ assert.strictEqual(result.params.traceConfig.memoryDumpConfig.
+ triggers[0].periodic_interval_ms, 10000);
+ });
+
+ test('oldFormat', function() {
+ const chunks = [];
+ tr.ui.e.about_tracing.appendTraceChunksTo(chunks, '"{ "method": "Tracing.dataCollected", "params": { "value": [ {"cat":"__metadata","pid":28871,"tid":0,"ts":0,"ph":"M","name":"num_cpus","args":{"number":4}},{"cat":"__metadata","pid":28871,"tid":28911,"ts":0,"ph":"M","name":"process_sort_index","args":{"sort_index":-5}},{"cat":"__metadata","pid":28871,"tid":28911,"ts":0,"ph":"M","name":"process_name","args":{"name":"Renderer"}},{"cat":"__metadata","pid":28871,"tid":28911,"ts":0,"ph":"M","name":"process_labels","args":{"labels":"JS Bin"}},{"cat":"__metadata","pid":28871,"tid":28908,"ts":0,"ph":"M","name":"thread_sort_index","args":{"sort_index":-1}},{"cat":"__metadata","pid":28871,"tid":28917,"ts":0,"ph":"M","name":"thread_name","args":{"name":"Compositor"}},{"cat":"__metadata","pid":28871,"tid":28911,"ts":0,"ph":"M","name":"thread_name","args":{"name":"Chrome_ChildIOThread"}},{"cat":"__metadata","pid":28871,"tid":28919,"ts":0,"ph":"M","name":"thread_name","args":{"name":"CompositorRasterWorker1/28919"}},{"cat":"__metadata","pid":28871,"tid":28908,"ts":0,"ph":"M","name":"thread_name","args":{"name":"CrRendererMain"}},{"cat":"ipc,toplevel","pid":28871,"tid":28911,"ts":22000084746,"ph":"X","name":"ChannelReader::DispatchInputData","args":{"class":64,"line":25},"tdur":0,"tts":1853064},{"cat":"__metadata","pid":28871,"tid":28911,"ts":0,"ph":"M","name":"overhead","args":{"average_overhead":0.015}} ] } }"'); // @suppress longLineCheck
+ assert.strictEqual(chunks.length, 1);
+ JSON.parse('[' + chunks.join('') + ']');
+ });
+
+ test('newFormat', function() {
+ const chunks = [];
+ tr.ui.e.about_tracing.appendTraceChunksTo(chunks, '"{ "method": "Tracing.dataCollected", "params": { "value": [{"cat":"__metadata","pid":28871,"tid":0,"ts":0,"ph":"M","name":"num_cpus","args":{"number":4}},{"cat":"__metadata","pid":28871,"tid":28911,"ts":0,"ph":"M","name":"process_sort_index","args":{"sort_index":-5}},{"cat":"__metadata","pid":28871,"tid":28911,"ts":0,"ph":"M","name":"process_name","args":{"name":"Renderer"}},{"cat":"__metadata","pid":28871,"tid":28911,"ts":0,"ph":"M","name":"process_labels","args":{"labels":"JS Bin"}},{"cat":"__metadata","pid":28871,"tid":28908,"ts":0,"ph":"M","name":"thread_sort_index","args":{"sort_index":-1}},{"cat":"__metadata","pid":28871,"tid":28917,"ts":0,"ph":"M","name":"thread_name","args":{"name":"Compositor"}},{"cat":"__metadata","pid":28871,"tid":28911,"ts":0,"ph":"M","name":"thread_name","args":{"name":"Chrome_ChildIOThread"}},{"cat":"__metadata","pid":28871,"tid":28919,"ts":0,"ph":"M","name":"thread_name","args":{"name":"CompositorRasterWorker1/28919"}},{"cat":"__metadata","pid":28871,"tid":28908,"ts":0,"ph":"M","name":"thread_name","args":{"name":"CrRendererMain"}},{"cat":"ipc,toplevel","pid":28871,"tid":28911,"ts":22000084746,"ph":"X","name":"ChannelReader::DispatchInputData","args":{"class":64,"line":25},"tdur":0,"tts":1853064},{"cat":"__metadata","pid":28871,"tid":28911,"ts":0,"ph":"M","name":"overhead","args":{"average_overhead":0.015}}] } }"'); // @suppress longLineCheck
+ assert.strictEqual(chunks.length, 1);
+ JSON.parse('[' + chunks.join('') + ']');
+ });
+
+ test('stringAndObjectPayload', function() {
+ const connection =
+ new tr.ui.e.about_tracing.InspectorConnection({DevToolsHost: {}});
+ connection.setNotificationListener('Tracing.dataCollected',
+ function(message) {
+ assert.typeOf(message, 'string');
+ JSON.parse(message);
+ }
+ );
+ connection.dispatchMessage_('{ "method": "Tracing.dataCollected", "params": { "value": [] } }'); // @suppress longLineCheck
+ connection.dispatchMessage_({'method': 'Tracing.dataCollected', 'params': {'value': [] } }); // @suppress longLineCheck
+ });
+
+ // Makes a fake version of DevToolsHost, which is the object injected
+ // by the chrome inspector to allow tracing a remote instance of chrome.
+ //
+ // The fake host doesn't do much by itself - you have to install
+ // callbacks for incoming messages via handleMessage().
+ function makeFakeDevToolsHost() {
+ return new (function() {
+ this.pendingMethods_ = [];
+ this.messageHandlers_ = [];
+
+ // Sends a message to DevTools host. This is used by
+ // InspectorTracingControllerClient to communicate with the remote
+ // debugging tracing backend.
+ this.sendMessageToEmbedder = function(devtoolsMessageStr) {
+ this.pendingMethods_.push(JSON.parse(devtoolsMessageStr));
+ this.tryMessageHandlers_();
+ };
+
+ // Runs remote debugging message handlers. Handlers are installed
+ // by test code via handleMessage().
+ this.tryMessageHandlers_ = function() {
+ while (this.pendingMethods_.length !== 0) {
+ const message = this.pendingMethods_[0];
+ const params = JSON.parse(message.params);
+ let handled = false;
+ const handlersToRemove = [];
+
+ // Try to find a handler for this method.
+ for (const handler of this.messageHandlers_) {
+ if (handler(params, () => handlersToRemove.push(handler))) {
+ handled = true;
+ break;
+ }
+ }
+
+ // Remove any handlers that requested removal.
+ this.messageHandlers_ = this.messageHandlers_.filter(
+ (handler) => !handlersToRemove.includes(handler));
+
+ // Remove any handled messages.
+ if (handled) {
+ this.pendingMethods_.shift();
+ } else {
+ return; // Methods must be handled in order.
+ }
+ }
+ };
+
+ // Installs a message handler that will be invoked for each
+ // incoming message from InspectorTracingControllerClient.
+ //
+ // handleMessage((message, removeSelf) => {
+ // // Try to handle |message|.
+ // // Call |removeSelf| to remove this handler for future messages.
+ // // Return whether |message| was handled. Otherwise other handlers
+ // // will be run until one of them succeeds.
+ // }
+ this.handleMessage = function(handler) {
+ this.messageHandlers_.push(handler);
+ this.tryMessageHandlers_();
+ };
+
+ // Installs a message handler that will handle the first call to the named
+ // method. Returns a promise for the parameters passed to the method.
+ this.handleMethod = function(method) {
+ const result = new Promise((resolve, reject) => {
+ this.handleMessage(
+ (requestParams, removeHandler) => {
+ if (requestParams.method === method) {
+ removeHandler();
+ resolve(requestParams);
+ return true;
+ }
+ return false;
+ });
+ });
+ return result;
+ };
+
+ // Sends a response to a remote debugging method call (i.e.,
+ // "return") to InspectorTracingControllerClient.
+ this.respondToMethod = function(id, params) {
+ this.devToolsAPI_.dispatchMessage(JSON.stringify({
+ id,
+ result: params,
+ }));
+ };
+
+ // Sets the object used to send messages back to
+ // InspectorTracingControllerClient.
+ this.setDevToolsAPI = function(api) {
+ this.devToolsAPI_ = api;
+ };
+
+ // Sends a notification to InspectorTracingControllerClient.
+ this.sendNotification = function(method, params) {
+ this.devToolsAPI_.dispatchMessage(JSON.stringify({ method, params }));
+ };
+ })();
+ }
+
+ test('shouldUseLegacyTraceFormatIfNoStreamId', async function() {
+ const fakeDevToolsHost = makeFakeDevToolsHost();
+ const fakeWindow = {
+ DevToolsHost: fakeDevToolsHost,
+ };
+ const controller =
+ new tr.ui.e.about_tracing.InspectorTracingControllerClient(
+ new tr.ui.e.about_tracing.InspectorConnection(fakeWindow));
+ fakeDevToolsHost.setDevToolsAPI(fakeWindow.DevToolsAPI);
+
+ const runHost = (async() => {
+ const startParams = await fakeDevToolsHost.handleMethod('Tracing.start');
+ fakeDevToolsHost.respondToMethod(startParams.id, {});
+ const endParams = await fakeDevToolsHost.handleMethod('Tracing.end');
+ fakeDevToolsHost.respondToMethod(endParams.id, {});
+ fakeDevToolsHost.sendNotification('Tracing.tracingComplete', {});
+ })();
+
+ await controller.beginRecording({});
+ const traceData = await controller.endRecording();
+ await runHost;
+
+ assert.strictEqual('[]', traceData);
+ });
+
+ test('shouldReassembleTextDataChunks', async function() {
+ const fakeDevToolsHost = makeFakeDevToolsHost();
+ const fakeWindow = {
+ DevToolsHost: fakeDevToolsHost,
+ };
+ const controller =
+ new tr.ui.e.about_tracing.InspectorTracingControllerClient(
+ new tr.ui.e.about_tracing.InspectorConnection(fakeWindow));
+ fakeDevToolsHost.setDevToolsAPI(fakeWindow.DevToolsAPI);
+
+ const STREAM_HANDLE = 7;
+
+ const streamChunks = [
+ '[',
+ ']',
+ '\n',
+ ];
+
+ let streamClosed = false;
+
+ const handleIoRead = (index, params) => {
+ if (params.params.handle !== STREAM_HANDLE) {
+ throw new Error('Invalid stream handle');
+ }
+ if (streamClosed) {
+ throw new Error('stream is closed');
+ }
+ let data = '';
+ if (index < streamChunks.length) {
+ data = streamChunks[index];
+ }
+ const eof = (index >= streamChunks.length - 1);
+ fakeDevToolsHost.respondToMethod(params.id, {
+ eof,
+ base64Encoded: false,
+ data,
+ });
+ const nextIndex = eof ? streamChunks.length : index + 1;
+ return (async() =>
+ handleIoRead(nextIndex, await fakeDevToolsHost.handleMethod('IO.read'))
+ )();
+ };
+
+ const runHost = (async() => {
+ const startParams = await fakeDevToolsHost.handleMethod('Tracing.start');
+ fakeDevToolsHost.respondToMethod(startParams.id, {});
+ const endParams = await fakeDevToolsHost.handleMethod('Tracing.end');
+ fakeDevToolsHost.respondToMethod(endParams.id, {});
+ fakeDevToolsHost.sendNotification('Tracing.tracingComplete', {
+ 'stream': STREAM_HANDLE,
+ });
+
+ const closePromise = (async() => {
+ const closeParams = await fakeDevToolsHost.handleMethod('IO.close');
+ assert.strictEqual(closeParams.params.handle, STREAM_HANDLE);
+ streamClosed = true;
+ })();
+
+ const readPromise = (async() =>
+ handleIoRead(0, await fakeDevToolsHost.handleMethod('IO.read'))
+ )();
+
+ await Promise.race([closePromise, readPromise]);
+ await closePromise;
+ })();
+
+ await controller.beginRecording({});
+ const traceData = await controller.endRecording();
+ await runHost;
+
+ assert.strictEqual(traceData, '[]\n');
+ });
+
+ test('shouldReassembleBase64TraceDataChunks', async function() {
+ const fakeDevToolsHost = makeFakeDevToolsHost();
+ const fakeWindow = {
+ DevToolsHost: fakeDevToolsHost,
+ };
+ const controller =
+ new tr.ui.e.about_tracing.InspectorTracingControllerClient(
+ new tr.ui.e.about_tracing.InspectorConnection(fakeWindow));
+ fakeDevToolsHost.setDevToolsAPI(fakeWindow.DevToolsAPI);
+
+ const STREAM_HANDLE = 7;
+
+ // This is the empty trace ('[]') gzip compressed and chunked to make
+ // sure reassembling base64 strings works properly.
+ const streamChunks = [
+ 'Hw==',
+ 'iwg=',
+ 'ALg4',
+ 'L1oAA4uOBQApu0wNAgAAAA==',
+ ];
+
+ let streamClosed = false;
+
+ const handleIoRead = (index, params) => {
+ if (params.params.handle !== STREAM_HANDLE) {
+ throw new Error('Invalid stream handle');
+ }
+ if (streamClosed) {
+ throw new Error('stream is closed');
+ }
+ let data = '';
+ if (index < streamChunks.length) {
+ data = streamChunks[index];
+ }
+ const eof = (index >= streamChunks.length - 1);
+ fakeDevToolsHost.respondToMethod(params.id, {
+ eof,
+ base64Encoded: true,
+ data,
+ });
+ const nextIndex = eof ? streamChunks.length : index + 1;
+ return (async() => {
+ handleIoRead(nextIndex, await fakeDevToolsHost.handleMethod('IO.read'));
+ })();
+ };
+
+ const runHost = (async() => {
+ const startParams = await fakeDevToolsHost.handleMethod('Tracing.start');
+ fakeDevToolsHost.respondToMethod(startParams.id, {});
+ const endParams = await fakeDevToolsHost.handleMethod('Tracing.end');
+ fakeDevToolsHost.respondToMethod(endParams.id, {});
+ fakeDevToolsHost.sendNotification('Tracing.tracingComplete', {
+ 'stream': STREAM_HANDLE,
+ 'streamCompression': 'gzip'
+ });
+ const closePromise = (async() => {
+ const closeParams = await fakeDevToolsHost.handleMethod('IO.close');
+ assert.strictEqual(closeParams.params.handle, STREAM_HANDLE);
+ streamClosed = true;
+ })();
+
+ const readPromise = (async() => {
+ handleIoRead(0, await fakeDevToolsHost.handleMethod('IO.read'));
+ })();
+
+ await Promise.race([closePromise, readPromise]);
+ await closePromise;
+ })();
+
+ await controller.beginRecording({});
+ const traceData = await controller.endRecording();
+ await runHost;
+
+ const dataArray = new Uint8Array(traceData);
+ const expectedArray = new Uint8Array([
+ 0x1f, 0x8b, 0x8, 0x0, 0xb8, 0x38, 0x2f, 0x5a, 0x0, 0x3, 0x8b, 0x8e,
+ 0x5, 0x0, 0x29, 0xbb, 0x4c, 0xd, 0x2, 0x0, 0x0, 0x0]);
+
+ assert.strictEqual(dataArray.length, expectedArray.length);
+
+ for (let i = 0; i < dataArray.length; ++i) {
+ assert.strictEqual(dataArray[i], expectedArray[i]);
+ }
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/mock_tracing_controller_client.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/mock_tracing_controller_client.html
new file mode 100644
index 00000000000..cfefdc05cc7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/mock_tracing_controller_client.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import"
+ href="/tracing/ui/extras/about_tracing/tracing_controller_client.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.about_tracing', function() {
+ function MockTracingControllerClient() {
+ this.requests = [];
+ this.nextRequestIndex = 0;
+ this.allowLooping = false;
+ }
+
+ MockTracingControllerClient.prototype = {
+ __proto__: tr.ui.e.about_tracing.TracingControllerClient.prototype,
+
+ expectRequest(method, generateResponse) {
+ let generateResponseCb;
+ if (typeof generateResponse === 'function') {
+ generateResponseCb = generateResponse;
+ } else {
+ generateResponseCb = function() {
+ return generateResponse;
+ };
+ }
+
+ this.requests.push({
+ method,
+ generateResponseCb});
+ },
+
+ _request(method, args) {
+ return new Promise(function(resolve) {
+ const requestIndex = this.nextRequestIndex;
+ if (requestIndex >= this.requests.length) {
+ throw new Error('Unhandled request');
+ }
+ if (!this.allowLooping) {
+ this.nextRequestIndex++;
+ } else {
+ this.nextRequestIndex = (this.nextRequestIndex + 1) %
+ this.requests.length;
+ }
+
+ const req = this.requests[requestIndex];
+ assert.strictEqual(method, req.method);
+ const resp = req.generateResponseCb(args);
+ resolve(resp);
+ }.bind(this));
+ },
+
+ assertAllRequestsHandled() {
+ if (this.allowLooping) {
+ throw new Error('Incompatible with allowLooping');
+ }
+ assert.strictEqual(this.requests.length, this.nextRequestIndex);
+ },
+
+ getCategories() {
+ return this._request('getCategories');
+ },
+
+ beginRecording(recordingOptions) {
+ return this._request('beginRecording', recordingOptions);
+ },
+
+ beginGetBufferPercentFull() {
+ return this._request('beginGetBufferPercentFull');
+ },
+
+ endRecording() {
+ return this._request('endRecording');
+ }
+ };
+
+ return {
+ MockTracingControllerClient,
+ };
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view.html
new file mode 100644
index 00000000000..77c0e80af12
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view.html
@@ -0,0 +1,372 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/base64.html">
+<link rel="import" href="/tracing/importer/import.html">
+<link rel="import" href="/tracing/ui/base/file.html">
+<link rel="import" href="/tracing/ui/base/hotkey_controller.html">
+<link rel="import" href="/tracing/ui/base/info_bar_group.html">
+<link rel="import" href="/tracing/ui/base/overlay.html">
+<link rel="import" href="/tracing/ui/base/utils.html">
+<link rel="import"
+ href="/tracing/ui/extras/about_tracing/inspector_tracing_controller_client.html">
+<link rel="import"
+ href="/tracing/ui/extras/about_tracing/record_controller.html">
+<link rel="import"
+ href="/tracing/ui/extras/about_tracing/xhr_based_tracing_controller_client.html">
+<link rel="import" href="/tracing/ui/timeline_view.html">
+
+<style>
+x-profiling-view {
+ flex-direction: column;
+ display: flex;
+ padding: 0;
+}
+
+x-profiling-view .controls #save-button {
+ margin-left: 64px !important;
+}
+
+x-profiling-view > tr-ui-timeline-view {
+ flex: 1 1 auto;
+ min-height: 0;
+}
+
+.report-id-message {
+ -webkit-user-select: text;
+}
+
+x-timeline-view-buttons {
+ display: flex;
+ align-items: center;
+}
+</style>
+
+<template id="profiling-view-template">
+ <tr-ui-b-info-bar-group></tr-ui-b-info-bar-group>
+ <x-timeline-view-buttons>
+ <button id="record-button">Record</button>
+ <button id="save-button">Save</button>
+ <button id="load-button">Load</button>
+ </x-timeline-view-buttons>
+ <tr-ui-timeline-view>
+ <track-view-container id='track_view_container'></track-view-container>
+ </tr-ui-timeline-view>
+</template>
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview ProfilingView glues the View control to
+ * TracingController.
+ */
+tr.exportTo('tr.ui.e.about_tracing', function() {
+ /**
+ * ProfilingView
+ * @constructor
+ * @extends {HTMLDivElement}
+ */
+ const ProfilingView = tr.ui.b.define('x-profiling-view');
+ const THIS_DOC = document.currentScript.ownerDocument;
+
+ ProfilingView.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ decorate(tracingControllerClient) {
+ Polymer.dom(this).appendChild(
+ tr.ui.b.instantiateTemplate('#profiling-view-template', THIS_DOC));
+
+ this.timelineView_ =
+ Polymer.dom(this).querySelector('tr-ui-timeline-view');
+ this.infoBarGroup_ =
+ Polymer.dom(this).querySelector('tr-ui-b-info-bar-group');
+
+ // Detach the buttons. We will reattach them to the timeline view.
+ // TODO(nduca): Make timeline-view have a content select="x-buttons"
+ // that pulls in any buttons.
+ this.recordButton_ = Polymer.dom(this).querySelector('#record-button');
+ this.loadButton_ = Polymer.dom(this).querySelector('#load-button');
+ this.saveButton_ = Polymer.dom(this).querySelector('#save-button');
+
+ const buttons = Polymer.dom(this).querySelector(
+ 'x-timeline-view-buttons');
+ Polymer.dom(buttons.parentElement).removeChild(buttons);
+ Polymer.dom(this.timelineView_.leftControls).appendChild(buttons);
+ this.initButtons_();
+
+ this.timelineView_.hotkeyController.addHotKey(new tr.ui.b.HotKey({
+ eventType: 'keypress',
+ keyCode: 'r'.charCodeAt(0),
+ callback(e) {
+ this.beginRecording();
+ event.stopPropagation();
+ },
+ thisArg: this
+ }));
+
+ this.initDragAndDrop_();
+
+ if (tracingControllerClient) {
+ this.tracingControllerClient_ = tracingControllerClient;
+ } else if (window.DevToolsHost !== undefined) {
+ this.tracingControllerClient_ =
+ new tr.ui.e.about_tracing.InspectorTracingControllerClient(
+ new tr.ui.e.about_tracing.InspectorConnection(window));
+ } else {
+ this.tracingControllerClient_ =
+ new tr.ui.e.about_tracing.XhrBasedTracingControllerClient();
+ }
+
+ this.isRecording_ = false;
+ this.activeTrace_ = undefined;
+
+ this.updateTracingControllerSpecificState_();
+ },
+
+ // Detach all document event listeners. Without this the tests can get
+ // confused as the element may still be listening when the next test runs.
+ detach_() {
+ this.detachDragAndDrop_();
+ },
+
+ get isRecording() {
+ return this.isRecording_;
+ },
+
+ set tracingControllerClient(tracingControllerClient) {
+ this.tracingControllerClient_ = tracingControllerClient;
+ this.updateTracingControllerSpecificState_();
+ },
+
+ updateTracingControllerSpecificState_() {
+ const isInspector = this.tracingControllerClient_ instanceof
+ tr.ui.e.about_tracing.InspectorTracingControllerClient;
+
+ if (isInspector) {
+ this.infoBarGroup_.addMessage(
+ 'This about:tracing is connected to a remote device...',
+ [{buttonText: 'Wow!', onClick() {}}]);
+ }
+ },
+
+ beginRecording() {
+ if (this.isRecording_) {
+ throw new Error('Already recording');
+ }
+ this.isRecording_ = true;
+ const resultPromise = tr.ui.e.about_tracing.beginRecording(
+ this.tracingControllerClient_);
+ resultPromise.then(
+ function(data) {
+ this.isRecording_ = false;
+ const traceName = tr.ui.e.about_tracing.defaultTraceName(
+ this.tracingControllerClient_);
+ this.setActiveTrace(traceName, data, false);
+ }.bind(this),
+ function(err) {
+ this.isRecording_ = false;
+ if (err instanceof tr.ui.e.about_tracing.UserCancelledError) {
+ return;
+ }
+ tr.ui.b.Overlay.showError('Error while recording', err);
+ }.bind(this));
+ return resultPromise;
+ },
+
+ get timelineView() {
+ return this.timelineView_;
+ },
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ clearActiveTrace() {
+ this.saveButton_.disabled = true;
+ this.activeTrace_ = undefined;
+ },
+
+ setActiveTrace(filename, data) {
+ this.activeTrace_ = {
+ filename,
+ data
+ };
+
+ this.infoBarGroup_.clearMessages();
+ this.updateTracingControllerSpecificState_();
+ this.saveButton_.disabled = false;
+ this.timelineView_.viewTitle = filename;
+
+ const m = new tr.Model();
+ const i = new tr.importer.Import(m);
+ const p = i.importTracesWithProgressDialog([data]);
+ p.then(
+ function() {
+ this.timelineView_.model = m;
+ this.timelineView_.updateDocumentFavicon();
+ }.bind(this),
+ function(err) {
+ tr.ui.b.Overlay.showError('While importing: ', err);
+ }.bind(this));
+ },
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ initButtons_() {
+ this.recordButton_.addEventListener(
+ 'click', function(event) {
+ event.stopPropagation();
+ this.beginRecording();
+ }.bind(this));
+
+ this.loadButton_.addEventListener(
+ 'click', function(event) {
+ event.stopPropagation();
+ this.onLoadClicked_();
+ }.bind(this));
+
+ this.saveButton_.addEventListener('click',
+ this.onSaveClicked_.bind(this));
+ this.saveButton_.disabled = true;
+ },
+
+ requestFilename_() {
+ // unsafe filename patterns:
+ const illegalRe = /[\/\?<>\\:\*\|":]/g;
+ const controlRe = /[\x00-\x1f\x80-\x9f]/g;
+ const reservedRe = /^\.+$/;
+
+ const defaultName = this.activeTrace_.filename;
+ let fileExtension = '.json';
+ let fileRegex = /\.json$/;
+ if (/[.]gz$/.test(defaultName)) {
+ fileExtension += '.gz';
+ fileRegex = /\.json\.gz$/;
+ } else if (/[.]zip$/.test(defaultName)) {
+ fileExtension = '.zip';
+ fileRegex = /\.zip$/;
+ }
+
+ const custom = prompt('Filename? (' + fileExtension +
+ ' appended) Or leave blank:');
+ if (custom === null) {
+ return undefined;
+ }
+
+ let name;
+ if (custom) {
+ name = ' ' + custom;
+ } else {
+ const date = new Date();
+ const dateText = ' ' + date.toDateString() +
+ ' ' + date.toLocaleTimeString();
+ name = dateText;
+ }
+
+ const filename = defaultName.replace(fileRegex, name) + fileExtension;
+
+ return filename
+ .replace(illegalRe, '.')
+ .replace(controlRe, '\u2022')
+ .replace(reservedRe, '')
+ .replace(/\s+/g, '_');
+ },
+
+ onSaveClicked_() {
+ // Create a blob URL from the binary array.
+ const blob = new Blob([this.activeTrace_.data],
+ {type: 'application/octet-binary'});
+ const blobUrl = window.webkitURL.createObjectURL(blob);
+
+ // Create a link and click on it. BEST API EVAR!
+ const link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
+ link.href = blobUrl;
+ const filename = this.requestFilename_();
+ if (filename) {
+ link.download = filename;
+ link.click();
+ }
+ },
+
+ onLoadClicked_() {
+ const inputElement = document.createElement('input');
+ inputElement.type = 'file';
+ inputElement.multiple = false;
+
+ let changeFired = false;
+ inputElement.addEventListener(
+ 'change',
+ function(e) {
+ if (changeFired) return;
+ changeFired = true;
+
+ const file = inputElement.files[0];
+ tr.ui.b.readFile(file).then(
+ function(data) {
+ this.setActiveTrace(file.name, data);
+ }.bind(this),
+ function(err) {
+ tr.ui.b.Overlay.showError('Error while loading file: ' + err);
+ });
+ }.bind(this), false);
+ inputElement.click();
+ },
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ initDragAndDrop_() {
+ this.dropHandler_ = this.dropHandler_.bind(this);
+ this.ignoreDragEvent_ = this.ignoreDragEvent_.bind(this);
+ document.addEventListener('dragstart', this.ignoreDragEvent_, false);
+ document.addEventListener('dragend', this.ignoreDragEvent_, false);
+ document.addEventListener('dragenter', this.ignoreDragEvent_, false);
+ document.addEventListener('dragleave', this.ignoreDragEvent_, false);
+ document.addEventListener('dragover', this.ignoreDragEvent_, false);
+ document.addEventListener('drop', this.dropHandler_, false);
+ },
+
+ detachDragAndDrop_() {
+ document.removeEventListener('dragstart', this.ignoreDragEvent_);
+ document.removeEventListener('dragend', this.ignoreDragEvent_);
+ document.removeEventListener('dragenter', this.ignoreDragEvent_);
+ document.removeEventListener('dragleave', this.ignoreDragEvent_);
+ document.removeEventListener('dragover', this.ignoreDragEvent_);
+ document.removeEventListener('drop', this.dropHandler_);
+ },
+
+ ignoreDragEvent_(e) {
+ e.preventDefault();
+ return false;
+ },
+
+ dropHandler_(e) {
+ if (this.isAnyDialogUp_) return;
+
+ e.stopPropagation();
+ e.preventDefault();
+
+ const files = e.dataTransfer.files;
+ if (files.length !== 1) {
+ tr.ui.b.Overlay.showError('1 file supported at a time.');
+ return;
+ }
+
+ tr.ui.b.readFile(files[0]).then(
+ function(data) {
+ this.setActiveTrace(files[0].name, data);
+ }.bind(this),
+ function(err) {
+ tr.ui.b.Overlay.showError('Error while loading file: ' + err);
+ });
+ return false;
+ }
+ };
+
+ return {
+ ProfilingView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view_test.html
new file mode 100644
index 00000000000..f52c491207f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/profiling_view_test.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+<link rel="import" href="/tracing/base/base64.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import"
+ href="/tracing/ui/extras/about_tracing/mock_tracing_controller_client.html">
+<link rel="import" href="/tracing/ui/extras/about_tracing/profiling_view.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const Base64 = tr.b.Base64;
+ const testData = [
+ {name: 'a', args: {}, pid: 52, ts: 15000, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 19000, cat: 'foo', tid: 53, ph: 'E'},
+ {name: 'b', args: {}, pid: 52, ts: 32000, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'b', args: {}, pid: 52, ts: 54000, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+
+ const monitoringOptions = {
+ isMonitoring: false,
+ categoryFilter: '*',
+ useSystemTracing: false,
+ useContinuousTracing: false,
+ useSampling: false
+ };
+
+ const ProfilingView = tr.ui.e.about_tracing.ProfilingView;
+
+ test('recording', function() {
+ const mock = new tr.ui.e.about_tracing.MockTracingControllerClient();
+ mock.allowLooping = true;
+ mock.expectRequest('endRecording', function() {
+ return '';
+ });
+ mock.expectRequest('getCategories', function() {
+ return ['a', 'b', 'c'];
+ });
+ mock.expectRequest('beginRecording', function(data) {
+ return '';
+ });
+ mock.expectRequest('endRecording', function(data) {
+ return JSON.stringify(testData);
+ });
+
+ const view = new ProfilingView(mock);
+ view.style.height = '400px';
+ view.style.border = '1px solid black';
+ this.addHTMLOutput(view);
+
+ const recordingPromise = view.beginRecording();
+
+ let didAbort = false;
+
+ tr.b.timeout(60).then(() => {
+ if (didAbort) return;
+ recordingPromise.selectionDlg.clickRecordButton();
+ }).then(() => tr.b.timeout(60)).then(() => {
+ recordingPromise.progressDlg.clickStopButton();
+ });
+
+ return recordingPromise.then(null, err => {
+ didAbort = true;
+ assert.fail(err);
+ });
+ });
+});
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller.html
new file mode 100644
index 00000000000..a9b42b589d8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller.html
@@ -0,0 +1,187 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/ui/extras/about_tracing/record_selection_dialog.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.about_tracing', function() {
+ function beginRecording(tracingControllerClient) {
+ let finalPromiseResolver;
+ const finalPromise = new Promise(function(resolve, reject) {
+ finalPromiseResolver = {
+ resolve,
+ reject
+ };
+ });
+ finalPromise.selectionDlg = undefined;
+ finalPromise.progressDlg = undefined;
+
+ function beginRecordingError(err) {
+ finalPromiseResolver.reject(err);
+ }
+
+ // Step 0: End recording. This is necessary when the user reloads the
+ // about:tracing page when we are recording. Window.onbeforeunload is not
+ // reliable to end recording on reload.
+ endRecording(tracingControllerClient).then(
+ getCategories,
+ getCategories); // Ignore error.
+
+ // But just in case, bind onbeforeunload anyway.
+ window.onbeforeunload = function(e) {
+ endRecording(tracingControllerClient);
+ };
+
+ // Step 1: Get categories.
+ function getCategories() {
+ const p = tracingControllerClient.getCategories().then(
+ showTracingDialog,
+ beginRecordingError);
+ p.catch(function(err) {
+ beginRecordingError(err);
+ });
+ }
+
+ // Step 2: Show tracing dialog.
+ let selectionDlg;
+ function showTracingDialog(categories) {
+ selectionDlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ selectionDlg.categories = categories;
+ selectionDlg.settings_key =
+ 'tr.ui.e.about_tracing.record_selection_dialog';
+ selectionDlg.addEventListener('recordclick', startTracing);
+ selectionDlg.addEventListener('closeclick', cancelRecording);
+ selectionDlg.visible = true;
+
+ finalPromise.selectionDlg = selectionDlg;
+ }
+
+ function cancelRecording() {
+ finalPromise.selectionDlg = undefined;
+ finalPromiseResolver.reject(new UserCancelledError());
+ }
+
+ // Step 2: Do the actual tracing dialog.
+ let progressDlg;
+ let bufferPercentFullDiv;
+ function startTracing() {
+ progressDlg = new tr.ui.b.Overlay();
+ Polymer.dom(progressDlg).textContent = 'Recording...';
+ progressDlg.userCanClose = false;
+
+ bufferPercentFullDiv = document.createElement('div');
+ Polymer.dom(progressDlg).appendChild(bufferPercentFullDiv);
+
+ const stopButton = document.createElement('button');
+ Polymer.dom(stopButton).textContent = 'Stop';
+ progressDlg.clickStopButton = function() {
+ stopButton.click();
+ };
+ Polymer.dom(progressDlg).appendChild(stopButton);
+
+ const categories = selectionDlg.includedAndExcludedCategories();
+ const recordingOptions = {
+ included_categories: categories.included,
+ excluded_categories: categories.excluded,
+ enable_systrace: selectionDlg.useSystemTracing,
+ record_mode: selectionDlg.tracingRecordMode,
+ };
+ if (categories.included.indexOf(
+ 'disabled-by-default-memory-infra') !== -1) {
+ const memoryConfig = { triggers: [] };
+ memoryConfig.triggers.push(
+ {'mode': 'detailed', 'periodic_interval_ms': 10000});
+ recordingOptions.memory_dump_config = memoryConfig;
+ }
+
+ const requestPromise = tracingControllerClient.beginRecording(
+ recordingOptions);
+ requestPromise.then(
+ function() {
+ progressDlg.visible = true;
+ stopButton.focus();
+ updateBufferPercentFull('0');
+ },
+ recordFailed);
+
+ stopButton.addEventListener('click', function() {
+ // TODO(chrishenry): Currently, this only dismiss the progress
+ // dialog when tracingComplete event is received. When performing
+ // remote debugging, the tracingComplete event may be delayed
+ // considerable. We should indicate to user that we are waiting
+ // for tracingComplete event instead of being unresponsive. (For
+ // now, I disable the "stop" button, since clicking on the button
+ // again now cause exception.)
+ const recordingPromise = endRecording(tracingControllerClient);
+ recordingPromise.then(
+ recordFinished,
+ recordFailed);
+ stopButton.disabled = true;
+ bufferPercentFullDiv = undefined;
+ });
+ finalPromise.progressDlg = progressDlg;
+ }
+
+ function recordFinished(tracedData) {
+ progressDlg.visible = false;
+ finalPromise.progressDlg = undefined;
+ finalPromiseResolver.resolve(tracedData);
+ }
+
+ function recordFailed(err) {
+ progressDlg.visible = false;
+ finalPromise.progressDlg = undefined;
+ finalPromiseResolver.reject(err);
+ }
+
+ function getBufferPercentFull() {
+ if (!bufferPercentFullDiv) return;
+
+ tracingControllerClient.beginGetBufferPercentFull().then(
+ updateBufferPercentFull);
+ }
+
+ function updateBufferPercentFull(percentFull) {
+ if (!bufferPercentFullDiv) return;
+
+ percentFull = Math.round(100 * parseFloat(percentFull));
+ const newText = 'Buffer usage: ' + percentFull + '%';
+ if (Polymer.dom(bufferPercentFullDiv).textContent !== newText) {
+ Polymer.dom(bufferPercentFullDiv).textContent = newText;
+ }
+
+ window.setTimeout(getBufferPercentFull, 500);
+ }
+
+ // Thats it! We're done.
+ return finalPromise;
+ }
+
+ function endRecording(tracingControllerClient) {
+ return tracingControllerClient.endRecording();
+ }
+
+ function defaultTraceName(tracingControllerClient) {
+ return tracingControllerClient.defaultTraceName();
+ }
+
+ function UserCancelledError() {
+ Error.apply(this, arguments);
+ }
+ UserCancelledError.prototype = {
+ __proto__: Error.prototype
+ };
+
+ return {
+ beginRecording,
+ UserCancelledError,
+ defaultTraceName,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller_test.html
new file mode 100644
index 00000000000..e3e0438f3a2
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_controller_test.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import"
+ href="/tracing/ui/extras/about_tracing/mock_tracing_controller_client.html">
+<link rel="import"
+ href="/tracing/ui/extras/about_tracing/record_controller.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const testData = [
+ {name: 'a', args: {}, pid: 52, ts: 15000, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'a', args: {}, pid: 52, ts: 19000, cat: 'foo', tid: 53, ph: 'E'},
+ {name: 'b', args: {}, pid: 52, ts: 32000, cat: 'foo', tid: 53, ph: 'B'},
+ {name: 'b', args: {}, pid: 52, ts: 54000, cat: 'foo', tid: 53, ph: 'E'}
+ ];
+
+ test('fullRecording', function() {
+ const mock = new tr.ui.e.about_tracing.MockTracingControllerClient();
+ mock.expectRequest('endRecording', function() {
+ return '';
+ });
+ mock.expectRequest('getCategories', function() {
+ tr.b.timeout(20).then(() =>
+ recordingPromise.selectionDlg.clickRecordButton());
+ return ['a', 'b', 'c'];
+ });
+ mock.expectRequest('beginRecording', function(recordingOptions) {
+ assert.typeOf(recordingOptions.included_categories, 'array');
+ assert.typeOf(recordingOptions.excluded_categories, 'array');
+ assert.typeOf(recordingOptions.enable_systrace, 'boolean');
+ assert.typeOf(recordingOptions.record_mode, 'string');
+ tr.b.timeout(10).then(() =>
+ recordingPromise.progressDlg.clickStopButton());
+ return '';
+ });
+ mock.expectRequest('endRecording', function(data) {
+ return JSON.stringify(testData);
+ });
+
+ const recordingPromise = tr.ui.e.about_tracing.beginRecording(mock);
+
+ return recordingPromise.then(function(data) {
+ mock.assertAllRequestsHandled();
+ assert.strictEqual(data, JSON.stringify(testData));
+ }, function(error) {
+ assert.fail(error);
+ });
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog.html
new file mode 100644
index 00000000000..a5383973a80
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog.html
@@ -0,0 +1,689 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+<link rel="import" href="/tracing/core/filter.html">
+<link rel="import" href="/tracing/ui/base/dom_helpers.html">
+<link rel="import" href="/tracing/ui/base/info_bar_group.html">
+<link rel="import" href="/tracing/ui/base/overlay.html">
+<link rel="import" href="/tracing/ui/base/utils.html">
+
+<template id="record-selection-dialog-template">
+ <style>
+ .categories-column-view {
+ display: flex;
+ flex-direction: column;
+ font-family: sans-serif;
+ max-width: 640px;
+ min-height: 0;
+ min-width: 0;
+ opacity: 1;
+ transition: max-height 1s ease, max-width 1s ease, opacity 1s ease;
+ will-change: opacity;
+ }
+
+ .categories-column-view-hidden {
+ max-height: 0;
+ max-width: 0;
+ opacity: 0;
+ overflow: hidden;
+ display: none;
+ }
+
+ .categories-selection {
+ display: flex;
+ flex-direction: row;
+ }
+
+ .category-presets {
+ padding: 4px;
+ }
+
+ .category-description {
+ color: #aaa;
+ font-size: small;
+ max-height: 1em;
+ opacity: 1;
+ padding-left: 4px;
+ padding-right: 4px;
+ text-align: right;
+ transition: max-height 1s ease, opacity 1s ease;
+ will-change: opacity;
+ }
+
+ .category-description-hidden {
+ max-height: 0;
+ opacity: 0;
+ }
+
+ .default-enabled-categories,
+ .default-disabled-categories {
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: column;
+ padding: 4px;
+ width: 300px;
+ }
+
+ .default-enabled-categories > div,
+ .default-disabled-categories > div {
+ padding: 4px;
+ }
+
+ .tracing-modes {
+ flex: 1 0 auto;
+ display: flex;
+ flex-direction: reverse;
+ padding: 4px;
+ border-bottom: 2px solid #ddd;
+ border-top: 2px solid #ddd;
+ }
+
+ .default-disabled-categories {
+ border-left: 2px solid #ddd;
+ }
+
+ .warning-default-disabled-categories {
+ display: inline-block;
+ font-weight: bold;
+ text-align: center;
+ color: #BD2E2E;
+ width: 2.0ex;
+ height: 2.0ex;
+ border-radius: 2.0ex;
+ border: 1px solid #BD2E2E;
+ }
+
+ .categories {
+ font-size: 80%;
+ padding: 10px;
+ flex: 1 1 auto;
+ }
+
+ .group-selectors {
+ font-size: 80%;
+ border-bottom: 1px solid #ddd;
+ padding-bottom: 6px;
+ flex: 0 0 auto;
+ }
+
+ .group-selectors button {
+ padding: 1px;
+ }
+
+ .record-selection-dialog .labeled-option-group {
+ flex: 0 0 auto;
+ flex-direction: column;
+ display: flex;
+ }
+
+ .record-selection-dialog .labeled-option {
+ border-top: 5px solid white;
+ border-bottom: 5px solid white;
+ }
+
+ .record-selection-dialog .edit-categories {
+ padding-left: 6px;
+ }
+
+ .record-selection-dialog .edit-categories:after {
+ padding-left: 15px;
+ font-size: 125%;
+ }
+
+ .record-selection-dialog .labeled-option-group:not(.categories-expanded)
+ .edit-categories:after {
+ content: '\25B8'; /* Right triangle */
+ }
+
+ .record-selection-dialog .labeled-option-group.categories-expanded
+ .edit-categories:after {
+ content: '\25BE'; /* Down triangle */
+ }
+
+ </style>
+
+ <div class="record-selection-dialog">
+ <tr-ui-b-info-bar-group></tr-ui-b-info-bar-group>
+ <div class="category-presets">
+ </div>
+ <div class="category-description"></div>
+ <div class="categories-column-view">
+ <div class="tracing-modes"></div>
+ <div class="categories-selection">
+ <div class="default-enabled-categories">
+ <div>Record&nbsp;Categories</div>
+ <div class="group-selectors">
+ Select
+ <button class="all-btn">All</button>
+ <button class="none-btn">None</button>
+ </div>
+ <div class="categories"></div>
+ </div>
+ <div class="default-disabled-categories">
+ <div>Disabled&nbsp;by&nbsp;Default&nbsp;Categories
+ <a class="warning-default-disabled-categories">!</a>
+ </div>
+ <div class="group-selectors">
+ Select
+ <button class="all-btn">All</button>
+ <button class="none-btn">None</button>
+ </div>
+ <div class="categories"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview RecordSelectionDialog presents the available categories
+ * to be enabled/disabled during tr.c.
+ */
+tr.exportTo('tr.ui.e.about_tracing', function() {
+ const THIS_DOC = document.currentScript.ownerDocument;
+ const RecordSelectionDialog = tr.ui.b.define('div');
+
+ const DEFAULT_PRESETS = [
+ {title: 'Web developer',
+ categoryFilter: ['blink', 'cc', 'netlog', 'renderer.scheduler',
+ 'sequence_manager', 'toplevel', 'v8']},
+ {title: 'Input latency',
+ categoryFilter: ['benchmark', 'input', 'evdev', 'renderer.scheduler',
+ 'sequence_manager', 'toplevel']},
+ {title: 'Rendering',
+ categoryFilter: ['blink', 'cc', 'gpu', 'toplevel', 'viz']},
+ {title: 'Javascript and rendering',
+ categoryFilter: ['blink', 'cc', 'gpu', 'renderer.scheduler',
+ 'sequence_manager', 'v8', 'toplevel', 'viz']},
+ {title: 'Frame Viewer',
+ categoryFilter: ['blink', 'cc', 'gpu', 'renderer.scheduler',
+ 'sequence_manager', 'v8', 'toplevel',
+ 'disabled-by-default-blink.invalidation',
+ 'disabled-by-default-cc.debug',
+ 'disabled-by-default-cc.debug.picture',
+ 'disabled-by-default-cc.debug.display_items']},
+ {title: 'Manually select settings',
+ categoryFilter: []}
+ ];
+ const RECORDING_MODES = [
+ {'label': 'Record until full',
+ 'value': 'record-until-full'},
+ {'label': 'Record continuously',
+ 'value': 'record-continuously'},
+ {'label': 'Record as much as possible',
+ 'value': 'record-as-much-as-possible'}];
+ const DEFAULT_RECORD_MODE = 'record-until-full';
+ const DEFAULT_CONTINUOUS_TRACING = true;
+ const DEFAULT_SYSTEM_TRACING = true;
+ const DEFAULT_SAMPLING_TRACING = false;
+
+ RecordSelectionDialog.prototype = {
+ __proto__: tr.ui.b.Overlay.prototype,
+
+ decorate() {
+ tr.ui.b.Overlay.prototype.decorate.call(this);
+ this.title = 'Record a new trace...';
+
+ Polymer.dom(this).classList.add('record-dialog-overlay');
+
+ const node =
+ tr.ui.b.instantiateTemplate('#record-selection-dialog-template',
+ THIS_DOC);
+ Polymer.dom(this).appendChild(node);
+
+ this.recordButtonEl_ = document.createElement('button');
+ Polymer.dom(this.recordButtonEl_).textContent = 'Record';
+ this.recordButtonEl_.addEventListener(
+ 'click',
+ this.onRecordButtonClicked_.bind(this));
+ this.recordButtonEl_.style.fontSize = '110%';
+ Polymer.dom(this.buttons).appendChild(this.recordButtonEl_);
+
+ this.categoriesView_ = Polymer.dom(this).querySelector(
+ '.categories-column-view');
+ this.presetsEl_ = Polymer.dom(this).querySelector('.category-presets');
+ Polymer.dom(this.presetsEl_).appendChild(tr.ui.b.createOptionGroup(
+ this, 'currentlyChosenPreset',
+ 'about_tracing.record_selection_dialog_preset',
+ DEFAULT_PRESETS[0].categoryFilter,
+ DEFAULT_PRESETS.map(function(p) {
+ return { label: p.title, value: p.categoryFilter };
+ })));
+
+ this.tracingRecordModeSltr_ = tr.ui.b.createSelector(
+ this, 'tracingRecordMode',
+ 'recordSelectionDialog.tracingRecordMode',
+ DEFAULT_RECORD_MODE, RECORDING_MODES);
+
+ this.systemTracingBn_ = tr.ui.b.createCheckBox(
+ undefined, undefined,
+ 'recordSelectionDialog.useSystemTracing', DEFAULT_SYSTEM_TRACING,
+ 'System tracing');
+ this.samplingTracingBn_ = tr.ui.b.createCheckBox(
+ undefined, undefined,
+ 'recordSelectionDialog.useSampling', DEFAULT_SAMPLING_TRACING,
+ 'State sampling');
+ this.tracingModesContainerEl_ = Polymer.dom(this).querySelector(
+ '.tracing-modes');
+ Polymer.dom(this.tracingModesContainerEl_).appendChild(
+ this.tracingRecordModeSltr_);
+ Polymer.dom(this.tracingModesContainerEl_).appendChild(
+ this.systemTracingBn_);
+ Polymer.dom(this.tracingModesContainerEl_).appendChild(
+ this.samplingTracingBn_);
+
+ this.enabledCategoriesContainerEl_ =
+ Polymer.dom(this).querySelector(
+ '.default-enabled-categories .categories');
+
+ this.disabledCategoriesContainerEl_ =
+ Polymer.dom(this).querySelector(
+ '.default-disabled-categories .categories');
+
+ this.createGroupSelectButtons_(
+ Polymer.dom(this).querySelector('.default-enabled-categories'));
+ this.createGroupSelectButtons_(
+ Polymer.dom(this).querySelector('.default-disabled-categories'));
+ this.createDefaultDisabledWarningDialog_(
+ Polymer.dom(this).querySelector(
+ '.warning-default-disabled-categories'));
+ this.editCategoriesOpened_ = false;
+
+ // TODO(chrishenry): When used with tr.ui.b.Overlay (such as in
+ // chrome://tracing, this does not yet look quite right due to
+ // the 10px overlay content padding (but it's good enough).
+ this.infoBarGroup_ = Polymer.dom(this).querySelector(
+ 'tr-ui-b-info-bar-group');
+
+ this.addEventListener('visible-change', this.onVisibleChange_.bind(this));
+ },
+
+ set supportsSystemTracing(s) {
+ if (s) {
+ this.systemTracingBn_.style.display = undefined;
+ } else {
+ this.systemTracingBn_.style.display = 'none';
+ this.useSystemTracing = false;
+ }
+ },
+
+ get tracingRecordMode() {
+ return this.tracingRecordModeSltr_.selectedValue;
+ },
+ set tracingRecordMode(value) {
+ this.tracingRecordMode_ = value;
+ },
+
+ get useSystemTracing() {
+ return this.systemTracingBn_.checked;
+ },
+ set useSystemTracing(value) {
+ this.systemTracingBn_.checked = !!value;
+ },
+
+ get useSampling() {
+ return this.samplingTracingBn_.checked;
+ },
+ set useSampling(value) {
+ this.samplingTracingBn_.checked = !!value;
+ },
+
+ set categories(c) {
+ if (!(c instanceof Array)) {
+ throw new Error('categories must be an array');
+ }
+ this.categories_ = c;
+
+ for (let i = 0; i < this.categories_.length; i++) {
+ const split = this.categories_[i].split(',');
+ this.categories_[i] = split.shift();
+ if (split.length > 0) {
+ this.categories_ = this.categories_.concat(split);
+ }
+ }
+ },
+
+ set settings_key(k) {
+ this.settings_key_ = k;
+ },
+
+ set settings(s) {
+ throw new Error('Dont use this!');
+ },
+
+ usingPreset_() {
+ return this.currentlyChosenPreset_.length > 0;
+ },
+
+ get currentlyChosenPreset() {
+ return this.currentlyChosenPreset_;
+ },
+
+ set currentlyChosenPreset(preset) {
+ if (!(preset instanceof Array)) {
+ throw new Error('RecordSelectionDialog.currentlyChosenPreset:' +
+ ' preset must be an array.');
+ }
+ this.currentlyChosenPreset_ = preset;
+
+ if (this.usingPreset_()) {
+ this.changeEditCategoriesState_(false);
+ } else {
+ this.updateCategoryColumnView_(true);
+ this.changeEditCategoriesState_(true);
+ }
+ this.updateManualSelectionView_();
+ this.updatePresetDescription_();
+ },
+
+ updateManualSelectionView_() {
+ const classList = Polymer.dom(this.categoriesView_).classList;
+ if (!this.usingPreset_()) {
+ classList.remove('categories-column-view-hidden');
+ } else {
+ if (this.editCategoriesOpened_) {
+ classList.remove('categories-column-view-hidden');
+ } else {
+ classList.add('categories-column-view-hidden');
+ }
+ }
+ },
+
+ updateCategoryColumnView_(shouldReadFromSettings) {
+ const categorySet = Polymer.dom(this).querySelectorAll('.categories');
+ for (let i = 0; i < categorySet.length; ++i) {
+ const categoryGroup = categorySet[i].children;
+ for (let j = 0; j < categoryGroup.length; ++j) {
+ const categoryEl = categoryGroup[j].children[0];
+ categoryEl.checked = shouldReadFromSettings ?
+ tr.b.Settings.get(categoryEl.value, false, this.settings_key_) :
+ false;
+ }
+ }
+ },
+
+ onClickEditCategories() {
+ if (!this.usingPreset_()) return;
+
+ if (!this.editCategoriesOpened_) {
+ this.updateCategoryColumnView_(false);
+ for (let i = 0; i < this.currentlyChosenPreset_.length; ++i) {
+ const categoryEl = this.querySelector('#' +
+ this.currentlyChosenPreset_[i]);
+ if (!categoryEl) continue;
+ categoryEl.checked = true;
+ }
+ }
+
+ this.changeEditCategoriesState_(!this.editCategoriesOpened_);
+ this.updateManualSelectionView_();
+ this.recordButtonEl_.focus();
+ },
+
+ changeEditCategoriesState_(editCategoriesState) {
+ const presetOptionsGroup = Polymer.dom(this).querySelector(
+ '.labeled-option-group');
+ if (!presetOptionsGroup) return;
+
+ this.editCategoriesOpened_ = editCategoriesState;
+ if (this.editCategoriesOpened_) {
+ Polymer.dom(presetOptionsGroup).classList.add('categories-expanded');
+ } else {
+ Polymer.dom(presetOptionsGroup).classList.remove(
+ 'categories-expanded');
+ }
+ },
+
+ updatePresetDescription_() {
+ const description = Polymer.dom(this).querySelector(
+ '.category-description');
+ if (this.usingPreset_()) {
+ description.innerText = this.currentlyChosenPreset_;
+ Polymer.dom(description).classList.remove(
+ 'category-description-hidden');
+ } else {
+ description.innerText = '';
+ if (!Polymer.dom(description).classList.contains(
+ 'category-description-hidden')) {
+ Polymer.dom(description).classList.add('category-description-hidden');
+ }
+ }
+ },
+
+ includedAndExcludedCategories() {
+ let includedCategories = [];
+ let excludedCategories = [];
+ if (this.usingPreset_()) {
+ const allCategories = this.allCategories_();
+ for (const category in allCategories) {
+ const disabledByDefault =
+ category.indexOf('disabled-by-default-') === 0;
+ if (this.currentlyChosenPreset_.indexOf(category) >= 0) {
+ if (disabledByDefault) {
+ includedCategories.push(category);
+ }
+ } else {
+ if (!disabledByDefault) {
+ excludedCategories.push(category);
+ }
+ }
+ }
+ return {
+ included: includedCategories,
+ excluded: excludedCategories
+ };
+ }
+
+ excludedCategories = this.unselectedCategories_();
+ includedCategories = this.enabledDisabledByDefaultCategories_();
+ return {
+ included: includedCategories,
+ excluded: excludedCategories
+ };
+ },
+
+ clickRecordButton() {
+ this.recordButtonEl_.click();
+ },
+
+ onRecordButtonClicked_() {
+ this.visible = false;
+ tr.b.dispatchSimpleEvent(this, 'recordclick');
+ return false;
+ },
+
+ collectInputs_(inputs, isChecked) {
+ const inputsLength = inputs.length;
+ const categories = [];
+ for (let i = 0; i < inputsLength; ++i) {
+ const input = inputs[i];
+ if (input.checked === isChecked) {
+ categories.push(input.value);
+ }
+ }
+ return categories;
+ },
+
+ unselectedCategories_() {
+ const inputs =
+ Polymer.dom(this.enabledCategoriesContainerEl_).querySelectorAll(
+ 'input');
+ return this.collectInputs_(inputs, false);
+ },
+
+ enabledDisabledByDefaultCategories_() {
+ const inputs =
+ Polymer.dom(this.disabledCategoriesContainerEl_).querySelectorAll(
+ 'input');
+ return this.collectInputs_(inputs, true);
+ },
+
+ onVisibleChange_() {
+ if (this.visible) {
+ this.updateForm_();
+ }
+ },
+
+ buildInputs_(inputs, checkedDefault, parent) {
+ const inputsLength = inputs.length;
+ for (let i = 0; i < inputsLength; i++) {
+ const category = inputs[i];
+
+ const inputEl = document.createElement('input');
+ inputEl.type = 'checkbox';
+ inputEl.id = category;
+ inputEl.value = category;
+
+ inputEl.checked = tr.b.Settings.get(
+ category, checkedDefault, this.settings_key_);
+ inputEl.onclick = this.updateSetting_.bind(this);
+
+ const labelEl = document.createElement('label');
+ Polymer.dom(labelEl).textContent =
+ category.replace('disabled-by-default-', '');
+ Polymer.dom(labelEl).setAttribute('for', category);
+
+ const divEl = document.createElement('div');
+ Polymer.dom(divEl).appendChild(inputEl);
+ Polymer.dom(divEl).appendChild(labelEl);
+
+ Polymer.dom(parent).appendChild(divEl);
+ }
+ },
+
+ allCategories_() {
+ // Dedup the categories. We may have things in settings that are also
+ // returned when we query the category list.
+ const categorySet = {};
+ const allCategories =
+ this.categories_.concat(tr.b.Settings.keys(this.settings_key_));
+ const allCategoriesLength = allCategories.length;
+ for (let i = 0; i < allCategoriesLength; ++i) {
+ categorySet[allCategories[i]] = true;
+ }
+ return categorySet;
+ },
+
+ updateForm_() {
+ function ignoreCaseCompare(a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ }
+
+ // Clear old categories
+ Polymer.dom(this.enabledCategoriesContainerEl_).innerHTML = '';
+ Polymer.dom(this.disabledCategoriesContainerEl_).innerHTML = '';
+
+ this.recordButtonEl_.focus();
+
+ const allCategories = this.allCategories_();
+ let categories = [];
+ let disabledCategories = [];
+ for (const category in allCategories) {
+ if (category.indexOf('disabled-by-default-') === 0) {
+ disabledCategories.push(category);
+ } else {
+ categories.push(category);
+ }
+ }
+ disabledCategories = disabledCategories.sort(ignoreCaseCompare);
+ categories = categories.sort(ignoreCaseCompare);
+
+ if (this.categories_.length === 0) {
+ this.infoBarGroup_.addMessage(
+ 'No categories found; recording will use default categories.');
+ }
+
+ this.buildInputs_(categories, true, this.enabledCategoriesContainerEl_);
+
+ if (disabledCategories.length > 0) {
+ this.disabledCategoriesContainerEl_.hidden = false;
+ this.buildInputs_(disabledCategories, false,
+ this.disabledCategoriesContainerEl_);
+ }
+ },
+
+ updateSetting_(e) {
+ const checkbox = e.target;
+ tr.b.Settings.set(checkbox.value, checkbox.checked, this.settings_key_);
+
+ // Change the current record mode to 'Manually select settings' from
+ // preset mode if and only if currently user is in preset record mode
+ // and user selects/deselects any category in 'Edit Categories' mode.
+ if (this.usingPreset_()) {
+ this.currentlyChosenPreset_ = []; /* manually select settings */
+ const categoryEl = this.querySelector(
+ '#category-preset-Manually-select-settings');
+ categoryEl.checked = true;
+ const description = Polymer.dom(this).querySelector(
+ '.category-description');
+ description.innerText = '';
+ Polymer.dom(description).classList.add('category-description-hidden');
+ }
+ },
+
+ createGroupSelectButtons_(parent) {
+ const flipInputs = function(dir) {
+ const inputs = Polymer.dom(parent).querySelectorAll('input');
+ for (let i = 0; i < inputs.length; i++) {
+ if (inputs[i].checked === dir) continue;
+ // click() is used so the settings will be correclty stored. Setting
+ // checked does not trigger the onclick (or onchange) callback.
+ inputs[i].click();
+ }
+ };
+
+ const allBtn = Polymer.dom(parent).querySelector('.all-btn');
+ allBtn.onclick = function(evt) {
+ flipInputs(true);
+ evt.preventDefault();
+ };
+
+ const noneBtn = Polymer.dom(parent).querySelector('.none-btn');
+ noneBtn.onclick = function(evt) {
+ flipInputs(false);
+ evt.preventDefault();
+ };
+ },
+
+ setWarningDialogOverlayText_(messages) {
+ const contentDiv = document.createElement('div');
+
+ for (let i = 0; i < messages.length; ++i) {
+ const messageDiv = document.createElement('div');
+ Polymer.dom(messageDiv).textContent = messages[i];
+ Polymer.dom(contentDiv).appendChild(messageDiv);
+ }
+ Polymer.dom(this.warningOverlay_).textContent = '';
+ Polymer.dom(this.warningOverlay_).appendChild(contentDiv);
+ },
+
+ createDefaultDisabledWarningDialog_(warningLink) {
+ function onClickHandler(evt) {
+ this.warningOverlay_ = tr.ui.b.Overlay();
+ this.warningOverlay_.parentEl_ = this;
+ this.warningOverlay_.title = 'Warning...';
+ this.warningOverlay_.userCanClose = true;
+ this.warningOverlay_.visible = true;
+
+ this.setWarningDialogOverlayText_([
+ 'Enabling the default disabled categories may have',
+ 'performance and memory impact while tr.c.'
+ ]);
+
+ evt.preventDefault();
+ }
+ warningLink.onclick = onClickHandler.bind(this);
+ }
+ };
+
+ return {
+ RecordSelectionDialog,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog_test.html
new file mode 100644
index 00000000000..7c62b487305
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/record_selection_dialog_test.html
@@ -0,0 +1,426 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/settings.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/ui/extras/about_tracing/record_selection_dialog.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('instantitate', function() {
+ const showButton = document.createElement('button');
+ Polymer.dom(showButton).textContent = 'Show record selection dialog';
+ this.addHTMLOutput(showButton);
+
+ showButton.addEventListener('click', function(e) {
+ e.stopPropagation();
+
+ const categories = [];
+ for (let i = 0; i < 30; i++) {
+ categories.push('cat-' + i);
+ }
+ for (let i = 0; i < 20; i++) {
+ categories.push('disabled-by-default-cat-' + i);
+ }
+ categories.push(
+ 'really-really-really-really-really-really-very-loong-cat');
+ categories.push('first,second,third');
+ categories.push('cc,disabled-by-default-cc.debug');
+
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.categories = categories;
+ dlg.settings_key = 'key';
+ dlg.visible = true;
+ });
+ });
+
+ test('recordSelectionDialog_splitCategories', function() {
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.categories =
+ ['cc,disabled-by-default-one,cc.debug', 'two,three', 'three'];
+ dlg.settings_key = 'key';
+ dlg.currentlyChosenPreset = [];
+ dlg.updateForm_();
+
+ const expected =
+ ['"cc"', '"cc.debug"', '"disabled-by-default-one"', '"three"', '"two"'];
+
+ const labels = Polymer.dom(dlg).querySelectorAll('.categories input');
+ let results = [];
+ for (let i = 0; i < labels.length; i++) {
+ results.push('"' + labels[i].value + '"');
+ }
+ results = results.sort();
+
+ assert.deepEqual(results, expected);
+ });
+
+ test('recordSelectionDialog_UpdateForm_NoSettings', function() {
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.categories = ['disabled-by-default-one', 'two', 'three'];
+ dlg.settings_key = 'key';
+ dlg.currentlyChosenPreset = [];
+ dlg.updateForm_();
+
+ const checkboxes = Polymer.dom(dlg).querySelectorAll('.categories input');
+ assert.strictEqual(checkboxes.length, 3);
+ assert.strictEqual(checkboxes[0].id, 'three');
+ assert.strictEqual(checkboxes[0].value, 'three');
+ assert.isTrue(checkboxes[0].checked);
+ assert.strictEqual(checkboxes[1].id, 'two');
+ assert.strictEqual(checkboxes[1].value, 'two');
+ assert.isTrue(checkboxes[1].checked);
+ assert.strictEqual(checkboxes[2].id, 'disabled-by-default-one');
+ assert.strictEqual(checkboxes[2].value, 'disabled-by-default-one');
+ assert.isFalse(checkboxes[2].checked);
+
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded, []);
+
+ const labels = Polymer.dom(dlg).querySelectorAll('.categories label');
+ assert.strictEqual(labels.length, 3);
+ assert.strictEqual(Polymer.dom(labels[0]).textContent, 'three');
+ assert.strictEqual(Polymer.dom(labels[1]).textContent, 'two');
+ assert.strictEqual(Polymer.dom(labels[2]).textContent, 'one');
+ });
+
+ test('recordSelectionDialog_UpdateForm_Settings', function() {
+ tr.b.Settings.set('two', true, 'categories');
+ tr.b.Settings.set('three', false, 'categories');
+
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.categories = ['disabled-by-default-one'];
+ dlg.settings_key = 'categories';
+ dlg.currentlyChosenPreset = [];
+ dlg.updateForm_();
+
+ const checkboxes = Polymer.dom(dlg).querySelectorAll('.categories input');
+ assert.strictEqual(checkboxes.length, 3);
+ assert.strictEqual(checkboxes[0].id, 'three');
+ assert.strictEqual(checkboxes[0].value, 'three');
+ assert.isFalse(checkboxes[0].checked);
+ assert.strictEqual(checkboxes[1].id, 'two');
+ assert.strictEqual(checkboxes[1].value, 'two');
+ assert.isTrue(checkboxes[1].checked);
+ assert.strictEqual(checkboxes[2].id, 'disabled-by-default-one');
+ assert.strictEqual(checkboxes[2].value, 'disabled-by-default-one');
+ assert.isFalse(checkboxes[2].checked);
+
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded, ['three']);
+
+ const labels = Polymer.dom(dlg).querySelectorAll('.categories label');
+ assert.strictEqual(labels.length, 3);
+ assert.strictEqual(Polymer.dom(labels[0]).textContent, 'three');
+ assert.strictEqual(Polymer.dom(labels[1]).textContent, 'two');
+ assert.strictEqual(Polymer.dom(labels[2]).textContent, 'one');
+ });
+
+ test('recordSelectionDialog_UpdateForm_DisabledByDefault', function() {
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.categories = ['disabled-by-default-bar', 'baz'];
+ dlg.settings_key = 'categories';
+ dlg.currentlyChosenPreset = [];
+ dlg.updateForm_();
+
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded, []);
+
+ const inputs =
+ Polymer.dom(dlg).querySelector('input#disabled-by-default-bar').click();
+
+ assert.deepEqual(dlg.includedAndExcludedCategories().included,
+ ['disabled-by-default-bar']);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded, []);
+
+ assert.isFalse(
+ tr.b.Settings.get('disabled-by-default-foo', false, 'categories'));
+ });
+
+ test('selectAll', function() {
+ tr.b.Settings.set('two', true, 'categories');
+ tr.b.Settings.set('three', false, 'categories');
+
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.categories = ['disabled-by-default-one'];
+ dlg.settings_key = 'categories';
+ dlg.currentlyChosenPreset = [];
+ dlg.updateForm_();
+ });
+
+ test('selectNone', function() {
+ tr.b.Settings.set('two', true, 'categories');
+ tr.b.Settings.set('three', false, 'categories');
+
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.categories = ['disabled-by-default-one'];
+ dlg.settings_key = 'categories';
+ dlg.currentlyChosenPreset = [];
+ dlg.updateForm_();
+
+ // Enables the three option, two already enabled.
+ Polymer.dom(dlg).querySelector('.default-enabled-categories .all-btn')
+ .click();
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded, []);
+ assert.isTrue(tr.b.Settings.get('three', false, 'categories'));
+
+ // Disables three and two.
+ Polymer.dom(dlg).querySelector('.default-enabled-categories .none-btn')
+ .click();
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded,
+ ['three', 'two']);
+ assert.isFalse(tr.b.Settings.get('two', false, 'categories'));
+ assert.isFalse(tr.b.Settings.get('three', false, 'categories'));
+
+ // Turn categories back on so they can be ignored.
+ Polymer.dom(dlg).querySelector('.default-enabled-categories .all-btn')
+ .click();
+
+ // Enables disabled category.
+ Polymer.dom(dlg).querySelector('.default-disabled-categories .all-btn')
+ .click();
+ assert.deepEqual(dlg.includedAndExcludedCategories().included,
+ ['disabled-by-default-one']);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded, []);
+ assert.isTrue(
+ tr.b.Settings.get('disabled-by-default-one', false, 'categories'));
+
+ // Turn disabled by default back off.
+ Polymer.dom(dlg).querySelector('.default-disabled-categories .none-btn')
+ .click();
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded, []);
+ assert.isFalse(
+ tr.b.Settings.get('disabled-by-default-one', false, 'categories'));
+ });
+
+ test('recordSelectionDialog_noPreset', function() {
+ tr.b.Settings.set('about_tracing.record_selection_dialog_preset', []);
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ assert.isFalse(dlg.usingPreset_());
+ });
+
+ test('recordSelectionDialog_defaultPreset', function() {
+ tr.b.Settings.set('two', true, 'categories');
+ tr.b.Settings.set('three', false, 'categories');
+
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.categories = ['disabled-by-default-one'];
+ dlg.settings_key = 'categories';
+ // Note: currentlyChosenPreset is not set here, so the default is used.
+ dlg.updateForm_();
+
+ // Make sure the default filter is returned.
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded,
+ ['three', 'two']);
+
+ // Make sure the default tracing types are returned.
+ assert.strictEqual(dlg.tracingRecordMode, 'record-until-full');
+ assert.isTrue(dlg.useSystemTracing);
+ assert.isFalse(dlg.useSampling);
+
+ // Make sure the manual settings are not visible.
+ const classList = Polymer.dom(dlg.categoriesView_).classList;
+ assert.isTrue(classList.contains('categories-column-view-hidden'));
+
+ // Verify manual settings do not modify the checkboxes.
+ const checkboxes = Polymer.dom(dlg).querySelectorAll('.categories input');
+ assert.strictEqual(checkboxes.length, 3);
+ assert.strictEqual(checkboxes[0].id, 'three');
+ assert.strictEqual(checkboxes[0].value, 'three');
+ assert.isFalse(checkboxes[0].checked);
+ assert.strictEqual(checkboxes[1].id, 'two');
+ assert.strictEqual(checkboxes[1].value, 'two');
+ assert.isTrue(checkboxes[1].checked);
+ assert.strictEqual(checkboxes[2].id, 'disabled-by-default-one');
+ assert.strictEqual(checkboxes[2].value, 'disabled-by-default-one');
+ assert.isFalse(checkboxes[2].checked);
+ });
+
+ test('recordSelectionDialog_editPreset', function() {
+ function createDialog() {
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.categories = ['one', 'two', 'disabled-by-default-three'];
+ dlg.settings_key = 'categories';
+ // Note: currentlyChosenPreset is not set here, so the default is used.
+ dlg.updateForm_();
+ return dlg;
+ }
+
+ // After the dialog is created, it should be using the default preset.
+ let dlg = createDialog();
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded,
+ ['one', 'two']);
+ assert.isTrue(dlg.usingPreset_());
+ assert.isFalse(
+ dlg.querySelector('#category-preset-Manually-select-settings').checked);
+
+ // After clicking on "Edit Categories", the default preset should still be
+ // used.
+ dlg.onClickEditCategories();
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded,
+ ['one', 'two']);
+ assert.isTrue(dlg.usingPreset_());
+ assert.isFalse(
+ dlg.querySelector('#category-preset-Manually-select-settings').checked);
+
+ // After clicking on category checkbox(es), the mode should be changed to
+ // "Manually select settings".
+ Array.prototype.forEach.call(dlg.querySelectorAll('.categories input'),
+ checkbox => checkbox.click());
+ assert.deepEqual(dlg.includedAndExcludedCategories().included,
+ ['disabled-by-default-three']);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded, []);
+ assert.isFalse(dlg.usingPreset_());
+ assert.isTrue(
+ dlg.querySelector('#category-preset-Manually-select-settings').checked);
+
+ // After the dialog is opened again, it should be using the default preset.
+ // More importantly, the default preset should NOT be modified.
+ dlg = createDialog();
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded,
+ ['one', 'two']);
+ assert.isTrue(dlg.usingPreset_());
+ assert.isFalse(
+ dlg.querySelector('#category-preset-Manually-select-settings').checked);
+ });
+
+ test('recordSelectionDialog_changePresets', function() {
+ tr.b.Settings.set('two', true, 'categories');
+ tr.b.Settings.set('three', false, 'categories');
+ tr.b.Settings.set('disabled-by-default-cc.debug', true, 'categories');
+ tr.b.Settings.set('recordSelectionDialog.tracingRecordMode',
+ 'record-as-much-as-possible');
+ tr.b.Settings.set('recordSelectionDialog.useSystemTracing', true);
+ tr.b.Settings.set('recordSelectionDialog.useSampling', false);
+
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.categories = ['disabled-by-default-one'];
+ dlg.settings_key = 'categories';
+ // Note: currentlyChosenPreset is not set here, so the default is used.
+ dlg.updateForm_();
+
+ // Preset mode is on.
+ assert.isTrue(dlg.usingPreset_());
+
+ // Make sure the default filter is returned.
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded,
+ ['three', 'two']);
+
+ // Make sure the default tracing types are returned.
+ assert.strictEqual(dlg.tracingRecordMode, 'record-as-much-as-possible');
+ assert.isTrue(dlg.useSystemTracing);
+ assert.isFalse(dlg.useSampling);
+
+ // Make sure the manual settings are not visible.
+ const classList = Polymer.dom(dlg.categoriesView_).classList;
+ assert.isTrue(classList.contains('categories-column-view-hidden'));
+
+ // Switch to manual settings and verify the default values are not returned.
+ dlg.currentlyChosenPreset = [];
+
+ // Preset mode is off.
+ assert.isFalse(dlg.usingPreset_());
+
+ // Make sure the default filter is returned.
+ assert.deepEqual(dlg.includedAndExcludedCategories().included,
+ ['disabled-by-default-cc.debug']);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded, ['three']);
+
+ // Make sure the tracing types set by catalog are returned.
+ assert.strictEqual(dlg.tracingRecordMode, 'record-as-much-as-possible');
+ assert.isTrue(dlg.useSystemTracing);
+ assert.isFalse(dlg.useSampling);
+ assert.isFalse(classList.contains('categories-column-view-hidden'));
+
+ // Switch to the graphics, rendering, and rasterization preset.
+ dlg.currentlyChosenPreset = ['blink', 'cc', 'renderer',
+ 'disabled-by-default-cc.debug'];
+ assert.deepEqual(dlg.includedAndExcludedCategories().included,
+ ['disabled-by-default-cc.debug']);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded,
+ ['three', 'two']);
+ });
+
+ test('recordSelectionDialog_savedPreset', function() {
+ tr.b.Settings.set('two', true, 'categories');
+ tr.b.Settings.set('three', false, 'categories');
+ tr.b.Settings.set('recordSelectionDialog.tracingRecordMode',
+ 'record-continuously');
+ tr.b.Settings.set('recordSelectionDialog.useSystemTracing', true);
+ tr.b.Settings.set('recordSelectionDialog.useSampling', true);
+ tr.b.Settings.set('tr.ui.e.about_tracing.record_selection_dialog_preset',
+ ['blink', 'cc', 'renderer', 'cc.debug']);
+
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.categories = ['disabled-by-default-one'];
+ dlg.settings_key = 'categories';
+ dlg.updateForm_();
+
+ // Make sure the correct filter is returned.
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded,
+ ['three', 'two']);
+
+ // Make sure the correct tracing types are returned.
+ assert.strictEqual(dlg.tracingRecordMode, 'record-continuously');
+ assert.isTrue(dlg.useSystemTracing);
+ assert.isTrue(dlg.useSampling);
+
+ // Make sure the manual settings are not visible.
+ const classList = Polymer.dom(dlg.categoriesView_).classList;
+ assert.isTrue(classList.contains('categories-column-view-hidden'));
+
+ // Switch to manual settings and verify the default values are not returned.
+ dlg.currentlyChosenPreset = [];
+ assert.deepEqual(dlg.includedAndExcludedCategories().included, []);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded, ['three']);
+ assert.strictEqual(dlg.tracingRecordMode, 'record-continuously');
+ assert.isTrue(dlg.useSystemTracing);
+ assert.isTrue(dlg.useSampling);
+ assert.isFalse(classList.contains('categories-column-view-hidden'));
+ });
+
+ test('recordSelectionDialog_categoryFilters', function() {
+ tr.b.Settings.set('default1', true, 'categories');
+ tr.b.Settings.set('disabled1', false, 'categories');
+ tr.b.Settings.set('disabled-by-default-cc.disabled2', false, 'categories');
+ tr.b.Settings.set('input', true, 'categories');
+ tr.b.Settings.set('blink', true, 'categories');
+ tr.b.Settings.set('cc', false, 'categories');
+ tr.b.Settings.set('disabled-by-default-cc.debug', true, 'categories');
+
+ const dlg = new tr.ui.e.about_tracing.RecordSelectionDialog();
+ dlg.settings_key = 'categories';
+ dlg.categories = [];
+ dlg.currentlyChosenPreset = [];
+ dlg.updateForm_();
+
+ assert.deepEqual(dlg.includedAndExcludedCategories().included,
+ ['disabled-by-default-cc.debug']);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded,
+ ['cc', 'disabled1']);
+
+ // Switch to the graphics, rendering, and rasterization preset.
+ dlg.currentlyChosenPreset = ['blink', 'cc', 'renderer',
+ 'disabled-by-default-cc.debug'];
+ assert.deepEqual(dlg.includedAndExcludedCategories().included,
+ ['disabled-by-default-cc.debug']);
+ assert.deepEqual(dlg.includedAndExcludedCategories().excluded,
+ ['default1', 'disabled1', 'input']);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/tracing_controller_client.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/tracing_controller_client.html
new file mode 100644
index 00000000000..c00bbe915e4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/tracing_controller_client.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.about_tracing', function() {
+ /**
+ * Communicates with content/browser/tracing_controller_impl.cc
+ *
+ * @constructor
+ */
+ class TracingControllerClient {
+ beginMonitoring(monitoringOptions) { }
+ endMonitoring() { }
+ captureMonitoring() { }
+ getMonitoringStatus() { }
+ getCategories() { }
+ beginRecording(recordingOptions) { }
+ beginGetBufferPercentFull() { }
+ endRecording() { }
+ defaultTraceName() { }
+ }
+
+ return {
+ TracingControllerClient,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/xhr_based_tracing_controller_client.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/xhr_based_tracing_controller_client.html
new file mode 100644
index 00000000000..d2c6adcac2a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing/xhr_based_tracing_controller_client.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/base64.html">
+<link rel="import"
+ href="/tracing/ui/extras/about_tracing/tracing_controller_client.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.about_tracing', function() {
+ const Base64 = tr.b.Base64;
+
+ function beginXhr(method, path, data) {
+ if (data === undefined) data = null;
+
+ return new Promise(function(resolve, reject) {
+ const req = new XMLHttpRequest();
+ if (method !== 'POST' && data !== null) {
+ throw new Error('Non-POST should have data==null');
+ }
+ req.open(method, path, true);
+ req.onreadystatechange = function(e) {
+ if (req.readyState === 4) {
+ window.setTimeout(function() {
+ if (req.status === 200 && req.responseText !== '##ERROR##') {
+ resolve(req.responseText);
+ } else {
+ reject(new Error('Error occured at ' + path));
+ }
+ }, 0);
+ }
+ };
+ req.send(data);
+ });
+ }
+
+ /**
+ * @constructor
+ */
+ function XhrBasedTracingControllerClient() { }
+
+ XhrBasedTracingControllerClient.prototype = {
+ __proto__: tr.ui.e.about_tracing.TracingControllerClient.prototype,
+
+ beginMonitoring(monitoringOptions) {
+ const monitoringOptionsB64 = Base64.btoa(JSON.stringify(
+ monitoringOptions));
+ return beginXhr('GET', '/json/begin_monitoring?' + monitoringOptionsB64);
+ },
+
+ endMonitoring() {
+ return beginXhr('GET', '/json/end_monitoring');
+ },
+
+ captureMonitoring() {
+ return beginXhr('GET', '/json/capture_monitoring_compressed').then(
+ function(data) {
+ const decodedSize = Base64.getDecodedBufferLength(data);
+ const buffer = new ArrayBuffer(decodedSize);
+ Base64.DecodeToTypedArray(data, new DataView(buffer));
+ return buffer;
+ }
+ );
+ },
+
+ getMonitoringStatus() {
+ return beginXhr('GET', '/json/get_monitoring_status').then(
+ function(monitoringOptionsB64) {
+ return JSON.parse(Base64.atob(monitoringOptionsB64));
+ });
+ },
+
+ getCategories() {
+ return beginXhr('GET', '/json/categories').then(
+ function(json) {
+ return JSON.parse(json);
+ });
+ },
+
+ beginRecording(recordingOptions) {
+ const recordingOptionsB64 = Base64.btoa(JSON.stringify(recordingOptions));
+ return beginXhr('GET', '/json/begin_recording?' +
+ recordingOptionsB64);
+ },
+
+ beginGetBufferPercentFull() {
+ return beginXhr('GET', '/json/get_buffer_percent_full');
+ },
+
+ endRecording() {
+ return beginXhr('GET', '/json/end_recording_compressed').then(
+ function(data) {
+ const decodedSize = Base64.getDecodedBufferLength(data);
+ const buffer = new ArrayBuffer(decodedSize);
+ Base64.DecodeToTypedArray(data, new DataView(buffer));
+ return buffer;
+ }
+ );
+ },
+
+ defaultTraceName() {
+ return 'trace.json.gz';
+ }
+ };
+
+ return {
+ XhrBasedTracingControllerClient,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/cc.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/cc.html
new file mode 100644
index 00000000000..79ba7e593c0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/cc.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 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.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/cc.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/display_item_list_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/picture_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/raster_task_selection.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/raster_task_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/tile_view.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger.html
new file mode 100644
index 00000000000..f8bfd671355
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger.html
@@ -0,0 +1,451 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 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.
+-->
+
+<link rel="import" href="/tracing/base/base64.html">
+<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
+<link rel="import" href="/tracing/ui/analysis/generic_object_view.html">
+<link rel="import" href="/tracing/ui/base/drag_handle.html">
+<link rel="import" href="/tracing/ui/base/hotkey_controller.html">
+<link rel="import" href="/tracing/ui/base/info_bar.html">
+<link rel="import" href="/tracing/ui/base/list_view.html">
+<link rel="import" href="/tracing/ui/base/mouse_mode_selector.html">
+<link rel="import" href="/tracing/ui/base/overlay.html">
+<link rel="import" href="/tracing/ui/base/utils.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/display_item_list_item.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/picture_ops_list_view.html">
+
+<template id="tr-ui-e-chrome-cc-display-item-debugger-template">
+ <left-panel>
+ <display-item-info>
+ <header>
+ <span class='title'>Display Item List</span>
+ <span class='size'></span>
+ <div class='export'>
+ <input class='dlfilename' type='text' value='displayitemlist.json' />
+ <button class='dlexport'>Export display item list</button>
+ </div>
+ <div class='export'>
+ <input class='skpfilename' type='text' value='skpicture.skp' />
+ <button class='skpexport'>Export list as SkPicture</button>
+ </div>
+ </header>
+ </display-item-info>
+ </left-panel>
+ <right-panel>
+ <raster-area>
+ <canvas-scroller>
+ <canvas></canvas>
+ </canvas-scroller>
+ </raster-area>
+ </right-panel>
+</template>
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ const THIS_DOC = document.currentScript.ownerDocument;
+
+ /**
+ * DisplayItemDebugger is a view of a DisplayItemListSnapshot for inspecting
+ * a display item list and the pictures within it.
+ *
+ * @constructor
+ */
+ const DisplayItemDebugger = tr.ui.b.define(
+ 'tr-ui-e-chrome-cc-display-item-debugger');
+
+ DisplayItemDebugger.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ decorate() {
+ const node = tr.ui.b.instantiateTemplate(
+ '#tr-ui-e-chrome-cc-display-item-debugger-template', THIS_DOC);
+
+ Polymer.dom(this).appendChild(node);
+ this.style.flexGrow = 1;
+ this.style.flexShrink = 1;
+ this.style.flexBasis = 'auto';
+ this.style.display = 'flex';
+ this.style.minWidth = 0;
+
+ this.pictureAsImageData_ = undefined;
+ this.zoomScaleValue_ = 1;
+
+ this.sizeInfo_ = Polymer.dom(this).querySelector('.size');
+ this.rasterArea_ = Polymer.dom(this).querySelector('raster-area');
+ this.rasterArea_.style.flexGrow = 1;
+ this.rasterArea_.style.flexShrink = 1;
+ this.rasterArea_.style.flexBasis = 'auto';
+ this.rasterArea_.style.backgroundColor = '#ddd';
+ this.rasterArea_.style.minHeight = '200px';
+ this.rasterArea_.style.minWidth = '200px';
+ this.rasterArea_.style.paddingLeft = '5px';
+ this.rasterArea_.style.display = 'flex';
+ this.rasterArea_.style.flexDirection = 'column';
+ this.rasterCanvas_ =
+ Polymer.dom(this.rasterArea_).querySelector('canvas');
+ this.rasterCtx_ = this.rasterCanvas_.getContext('2d');
+
+ const canvasScroller = Polymer.dom(this).querySelector('canvas-scroller');
+ canvasScroller.style.flexGrow = 1;
+ canvasScroller.style.flexShrink = 1;
+ canvasScroller.style.flexBasis = 'auto';
+ canvasScroller.style.minWidth = 0;
+ canvasScroller.style.minHeight = 0;
+ canvasScroller.style.overflow = 'auto';
+
+ this.trackMouse_();
+
+ this.displayItemInfo_ =
+ Polymer.dom(this).querySelector('display-item-info');
+ this.displayItemInfo_.addEventListener(
+ 'click', this.onDisplayItemInfoClick_.bind(this), false);
+
+ this.displayItemListView_ = new tr.ui.b.ListView();
+ this.displayItemListView_.addEventListener('selection-changed',
+ this.onDisplayItemListSelection_.bind(this));
+ Polymer.dom(this.displayItemInfo_).appendChild(this.displayItemListView_);
+
+ this.displayListFilename_ =
+ Polymer.dom(this).querySelector('.dlfilename');
+ this.displayListExportButton_ =
+ Polymer.dom(this).querySelector('.dlexport');
+ this.displayListExportButton_.addEventListener(
+ 'click', this.onExportDisplayListClicked_.bind(this));
+
+ this.skpFilename_ = Polymer.dom(this).querySelector('.skpfilename');
+ this.skpExportButton_ = Polymer.dom(this).querySelector('.skpexport');
+ this.skpExportButton_.addEventListener(
+ 'click', this.onExportSkPictureClicked_.bind(this));
+
+ const leftPanel = Polymer.dom(this).querySelector('left-panel');
+ leftPanel.style.flexGrow = 0;
+ leftPanel.style.flexShrink = 0;
+ leftPanel.style.flexBasis = 'auto';
+ leftPanel.style.minWidth = '200px';
+ leftPanel.style.overflow = 'auto';
+
+ leftPanel.children[0].paddingTop = '2px';
+ leftPanel.children[0].children[0].style.borderBottom = '1px solid #555';
+
+ const leftPanelTitle = leftPanel.querySelector('.title');
+ leftPanelTitle.style.fontWeight = 'bold';
+ leftPanelTitle.style.marginLeft = '5px';
+ leftPanelTitle.style.marginright = '5px';
+
+ for (const div of leftPanel.querySelectorAll('.export')) {
+ div.style.margin = '5px';
+ }
+
+ const middleDragHandle = document.createElement('tr-ui-b-drag-handle');
+ middleDragHandle.style.flexGrow = 0;
+ middleDragHandle.style.flexShrink = 0;
+ middleDragHandle.style.flexBasis = 'auto';
+ middleDragHandle.horizontal = false;
+ middleDragHandle.target = leftPanel;
+
+ const rightPanel = Polymer.dom(this).querySelector('right-panel');
+ rightPanel.style.display = 'flex';
+ rightPanel.style.flexGrow = 1;
+ rightPanel.style.flexShrink = 1;
+ rightPanel.style.flexBasis = 'auto';
+ rightPanel.style.minWidth = 0;
+
+ this.infoBar_ = document.createElement('tr-ui-b-info-bar');
+ Polymer.dom(this.rasterArea_).insertBefore(this.infoBar_, canvasScroller);
+
+ Polymer.dom(this).insertBefore(middleDragHandle, rightPanel);
+
+ this.picture_ = undefined;
+
+ this.pictureOpsListView_ = new tr.ui.e.chrome.cc.PictureOpsListView();
+ this.pictureOpsListView_.style.flexGrow = 0;
+ this.pictureOpsListView_.style.flexShrink = 0;
+ this.pictureOpsListView_.style.flexBasis = 'auto';
+ this.pictureOpsListView_.style.overflow = 'auto';
+ this.pictureOpsListView_.style.minWidth = '100px';
+ Polymer.dom(rightPanel).insertBefore(
+ this.pictureOpsListView_, this.rasterArea_);
+
+ this.pictureOpsListDragHandle_ =
+ document.createElement('tr-ui-b-drag-handle');
+ this.pictureOpsListDragHandle_.horizontal = false;
+ this.pictureOpsListDragHandle_.target = this.pictureOpsListView_;
+ Polymer.dom(rightPanel).insertBefore(
+ this.pictureOpsListDragHandle_, this.rasterArea_);
+ },
+
+ get picture() {
+ return this.picture_;
+ },
+
+ set displayItemList(displayItemList) {
+ this.displayItemList_ = displayItemList;
+ this.picture = this.displayItemList_;
+
+ this.displayItemListView_.clear();
+ this.displayItemList_.items.forEach(function(item) {
+ const listItem = document.createElement(
+ 'tr-ui-e-chrome-cc-display-item-list-item');
+ listItem.data = item;
+ Polymer.dom(this.displayItemListView_).appendChild(listItem);
+ }.bind(this));
+ },
+
+ set picture(picture) {
+ this.picture_ = picture;
+
+ // Hide the ops list if we are showing the "main" display item list.
+ const showOpsList = picture && picture !== this.displayItemList_;
+ this.updateDrawOpsList_(showOpsList);
+
+ if (picture) {
+ const size = this.getRasterCanvasSize_();
+ this.rasterCanvas_.width = size.width;
+ this.rasterCanvas_.height = size.height;
+ }
+
+ const bounds = this.rasterArea_.getBoundingClientRect();
+ const selectorBounds = this.mouseModeSelector_.getBoundingClientRect();
+ this.mouseModeSelector_.pos = {
+ x: (bounds.right - selectorBounds.width - 10),
+ y: bounds.top
+ };
+
+ this.rasterize_();
+
+ this.scheduleUpdateContents_();
+ },
+
+ getRasterCanvasSize_() {
+ const style = window.getComputedStyle(this.rasterArea_);
+ let width = parseInt(style.width);
+ let height = parseInt(style.height);
+ if (this.picture_) {
+ width = Math.max(width, this.picture_.layerRect.width);
+ height = Math.max(height, this.picture_.layerRect.height);
+ }
+
+ return {
+ width,
+ height
+ };
+ },
+
+ scheduleUpdateContents_() {
+ if (this.updateContentsPending_) return;
+
+ this.updateContentsPending_ = true;
+ tr.b.requestAnimationFrameInThisFrameIfPossible(
+ this.updateContents_.bind(this)
+ );
+ },
+
+ updateContents_() {
+ this.updateContentsPending_ = false;
+
+ if (this.picture_) {
+ Polymer.dom(this.sizeInfo_).textContent = '(' +
+ this.picture_.layerRect.width + ' x ' +
+ this.picture_.layerRect.height + ')';
+ }
+
+ // Return if picture hasn't finished rasterizing.
+ if (!this.pictureAsImageData_) return;
+
+ this.infoBar_.visible = false;
+ this.infoBar_.removeAllButtons();
+ if (this.pictureAsImageData_.error) {
+ this.infoBar_.message = 'Cannot rasterize...';
+ this.infoBar_.addButton('More info...', function(e) {
+ const overlay = new tr.ui.b.Overlay();
+ Polymer.dom(overlay).textContent = this.pictureAsImageData_.error;
+ overlay.visible = true;
+ e.stopPropagation();
+ return false;
+ }.bind(this));
+ this.infoBar_.visible = true;
+ }
+
+ this.drawPicture_();
+ },
+
+ drawPicture_() {
+ const size = this.getRasterCanvasSize_();
+ if (size.width !== this.rasterCanvas_.width) {
+ this.rasterCanvas_.width = size.width;
+ }
+ if (size.height !== this.rasterCanvas_.height) {
+ this.rasterCanvas_.height = size.height;
+ }
+
+ this.rasterCtx_.clearRect(0, 0, size.width, size.height);
+
+ if (!this.picture_ || !this.pictureAsImageData_.imageData) return;
+
+ const imgCanvas = this.pictureAsImageData_.asCanvas();
+ const w = imgCanvas.width;
+ const h = imgCanvas.height;
+ this.rasterCtx_.drawImage(imgCanvas, 0, 0, w, h,
+ 0, 0, w * this.zoomScaleValue_,
+ h * this.zoomScaleValue_);
+ },
+
+ rasterize_() {
+ if (this.picture_) {
+ this.picture_.rasterize(
+ {
+ showOverdraw: false
+ },
+ this.onRasterComplete_.bind(this));
+ }
+ },
+
+ onRasterComplete_(pictureAsImageData) {
+ this.pictureAsImageData_ = pictureAsImageData;
+ this.scheduleUpdateContents_();
+ },
+
+ onDisplayItemListSelection_(e) {
+ const selected = this.displayItemListView_.selectedElement;
+
+ if (!selected) {
+ this.picture = this.displayItemList_;
+ return;
+ }
+
+ const index = Array.prototype.indexOf.call(
+ this.displayItemListView_.children, selected);
+ const displayItem = this.displayItemList_.items[index];
+ if (displayItem && displayItem.skp64) {
+ this.picture = new tr.e.cc.Picture(
+ displayItem.skp64, this.displayItemList_.layerRect);
+ } else {
+ this.picture = undefined;
+ }
+ },
+
+ onDisplayItemInfoClick_(e) {
+ if (e && e.target === this.displayItemInfo_) {
+ this.displayItemListView_.selectedElement = undefined;
+ }
+ },
+
+ updateDrawOpsList_(showOpsList) {
+ if (showOpsList) {
+ this.pictureOpsListView_.picture = this.picture_;
+ if (this.pictureOpsListView_.numOps > 0) {
+ this.pictureOpsListView_.style.display = 'block';
+ this.pictureOpsListDragHandle_.style.display = 'block';
+ }
+ } else {
+ this.pictureOpsListView_.style.display = 'none';
+ this.pictureOpsListDragHandle_.style.display = 'none';
+ }
+ },
+
+ trackMouse_() {
+ this.mouseModeSelector_ = document.createElement(
+ 'tr-ui-b-mouse-mode-selector');
+ this.mouseModeSelector_.targetElement = this.rasterArea_;
+ Polymer.dom(this.rasterArea_).appendChild(this.mouseModeSelector_);
+
+ this.mouseModeSelector_.supportedModeMask =
+ tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;
+ this.mouseModeSelector_.mode = tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;
+ this.mouseModeSelector_.defaultMode = tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;
+ this.mouseModeSelector_.settingsKey = 'pictureDebugger.mouseModeSelector';
+
+ this.mouseModeSelector_.addEventListener('beginzoom',
+ this.onBeginZoom_.bind(this));
+ this.mouseModeSelector_.addEventListener('updatezoom',
+ this.onUpdateZoom_.bind(this));
+ this.mouseModeSelector_.addEventListener('endzoom',
+ this.onEndZoom_.bind(this));
+ },
+
+ onBeginZoom_(e) {
+ this.isZooming_ = true;
+
+ this.lastMouseViewPos_ = this.extractRelativeMousePosition_(e);
+
+ e.preventDefault();
+ },
+
+ onUpdateZoom_(e) {
+ if (!this.isZooming_) return;
+
+ const currentMouseViewPos = this.extractRelativeMousePosition_(e);
+
+ // Take the distance the mouse has moved and we want to zoom at about
+ // 1/1000th of that speed. 0.01 feels jumpy. This could possibly be tuned
+ // more if people feel it's too slow.
+ this.zoomScaleValue_ +=
+ ((this.lastMouseViewPos_.y - currentMouseViewPos.y) * 0.001);
+ this.zoomScaleValue_ = Math.max(this.zoomScaleValue_, 0.1);
+
+ this.drawPicture_();
+
+ this.lastMouseViewPos_ = currentMouseViewPos;
+ },
+
+ onEndZoom_(e) {
+ this.lastMouseViewPos_ = undefined;
+ this.isZooming_ = false;
+ e.preventDefault();
+ },
+
+ extractRelativeMousePosition_(e) {
+ return {
+ x: e.clientX - this.rasterArea_.offsetLeft,
+ y: e.clientY - this.rasterArea_.offsetTop
+ };
+ },
+
+ saveFile_(filename, rawData) {
+ if (!rawData) return;
+
+ // Convert this String into an Uint8Array
+ const length = rawData.length;
+ const arrayBuffer = new ArrayBuffer(length);
+ const uint8Array = new Uint8Array(arrayBuffer);
+ for (let c = 0; c < length; c++) {
+ uint8Array[c] = rawData.charCodeAt(c);
+ }
+
+ // Create a blob URL from the binary array.
+ const blob = new Blob([uint8Array], {type: 'application/octet-binary'});
+ const blobUrl = window.URL.createObjectURL(blob);
+
+ // Create a link and click on it.
+ const link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
+ link.href = blobUrl;
+ link.download = filename;
+ const event = document.createEvent('MouseEvents');
+ event.initMouseEvent(
+ 'click', true, false, window, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null);
+ link.dispatchEvent(event);
+ },
+
+ onExportDisplayListClicked_() {
+ const rawData = JSON.stringify(this.displayItemList_.items);
+ this.saveFile_(this.displayListFilename_.value, rawData);
+ },
+
+ onExportSkPictureClicked_() {
+ const rawData = tr.b.Base64.atob(this.picture_.getBase64SkpData());
+ this.saveFile_(this.skpFilename_.value, rawData);
+ }
+ };
+
+ return {
+ DisplayItemDebugger,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger_test.html
new file mode 100644
index 00000000000..c10d6995db3
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_debugger_test.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 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.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/display_item_list.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/display_item_debugger.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('instantiate', function() {
+ const displayItemList = new tr.e.cc.DisplayItemListSnapshot(
+ {id: '31415'},
+ 10,
+ {
+ 'params': {
+ 'layer_rect': [-15, -15, 46, 833],
+ 'items': [
+ 'BeginClipDisplayItem',
+ 'EndClipDisplayItem'
+ ]
+ },
+ 'skp64': '[another skia picture in base64]'});
+ displayItemList.preInitialize();
+ displayItemList.initialize();
+
+ const dbg = new tr.ui.e.chrome.cc.DisplayItemDebugger();
+ this.addHTMLOutput(dbg);
+ assert.isUndefined(dbg.displayItemList_);
+ assert.isUndefined(dbg.picture_);
+ dbg.displayItemList = displayItemList;
+ assert.isDefined(dbg.displayItemList_);
+ assert.isDefined(dbg.picture_);
+ assert.strictEqual(dbg.displayItemList_.items.length, 2);
+ dbg.style.border = '1px solid black';
+ });
+
+ test('selections', function() {
+ const displayItemList = new tr.e.cc.DisplayItemListSnapshot(
+ {id: '31415'},
+ 10,
+ {
+ 'params': {
+ 'layer_rect': [-15, -15, 46, 833],
+ 'items': [
+ 'BeginClipDisplayItem',
+ 'TransformDisplayItem',
+ {
+ 'name': 'DrawingDisplayItem',
+ 'skp64': '[skia picture in base64]',
+ },
+ 'EndTransformDisplayItem',
+ 'EndClipDisplayItem'
+ ]
+ },
+ 'skp64': '[another skia picture in base64]'});
+ displayItemList.preInitialize();
+ displayItemList.initialize();
+
+ const dbg = new tr.ui.e.chrome.cc.DisplayItemDebugger();
+ this.addHTMLOutput(dbg);
+ dbg.displayItemList = displayItemList;
+ assert.isDefined(dbg.displayItemList_);
+ assert.isDefined(dbg.picture_);
+ assert.strictEqual(dbg.displayItemList_.items.length, 5);
+
+ const initialPicture = dbg.picture_;
+ assert.isAbove(initialPicture.guid, 0);
+
+ // Select the drawing display item and make sure the picture updates.
+ const listView = dbg.displayItemListView_;
+ listView.selectedElement = listView.getElementByIndex(3);
+ let updatedPicture = dbg.picture_;
+ assert.isAbove(updatedPicture.guid, 0);
+ assert.notEqual(initialPicture.guid, updatedPicture.guid);
+
+ // Select the TransformDisplayItem and make sure the picture is blank.
+ listView.selectedElement = listView.getElementByIndex(2);
+ assert.isUndefined(dbg.picture_);
+
+ // Deselect a list item and make sure the picture is reset to the original.
+ listView.selectedElement = undefined;
+ updatedPicture = dbg.picture_;
+ assert.isAbove(updatedPicture.guid, 0);
+ assert.strictEqual(initialPicture.guid, updatedPicture.guid);
+
+ dbg.style.border = '1px solid black';
+ });
+
+ test('export', function() {
+ const displayItemList = new tr.e.cc.DisplayItemListSnapshot(
+ {id: '31415'},
+ 10,
+ {
+ 'params': {
+ 'layer_rect': [-15, -15, 46, 833],
+ 'items': [
+ 'BeginClipDisplayItem',
+ 'EndClipDisplayItem'
+ ]
+ },
+ 'skp64': 'c2twaWN0dXJl'});
+ displayItemList.preInitialize();
+ displayItemList.initialize();
+
+ const dbg = new tr.ui.e.chrome.cc.DisplayItemDebugger();
+ this.addHTMLOutput(dbg);
+ dbg.displayItemList = displayItemList;
+
+ let onSaveDisplayListCalled = false;
+ dbg.saveFile_ = function(filename, rawData) {
+ onSaveDisplayListCalled = true;
+ assert.strictEqual(filename, 'displayitemlist.json');
+ assert.strictEqual(
+ rawData, '["BeginClipDisplayItem","EndClipDisplayItem"]');
+ };
+ dbg.onExportDisplayListClicked_();
+ assert(onSaveDisplayListCalled);
+
+ let onSaveSkPictureCalled = false;
+ dbg.saveFile_ = function(filename, rawData) {
+ onSaveSkPictureCalled = true;
+ assert.strictEqual(filename, 'skpicture.skp');
+ assert.strictEqual(rawData, 'skpicture');
+ };
+ dbg.onExportSkPictureClicked_();
+ assert(onSaveSkPictureCalled);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_list_item.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_list_item.html
new file mode 100644
index 00000000000..3024e8d2d22
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_list_item.html
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 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.
+-->
+
+<!--
+An element displaying basic information about a display item in a list view.
+-->
+<dom-module id='tr-ui-e-chrome-cc-display-item-list-item'>
+ <template>
+ <style>
+ :host {
+ border-bottom: 1px solid #555;
+ display: block;
+ font-size: 12px;
+ padding: 3px 5px;
+ }
+
+ :host(:hover) {
+ background-color: #f0f0f0;
+ cursor: pointer;
+ }
+
+ .header {
+ font-weight: bold;
+ margin: 2px 0;
+ }
+
+ .header > .extra {
+ background-color: #777;
+ border-radius: 4px;
+ color: white;
+ margin: 0 6px;
+ text-decoration: none;
+ padding: 2px 4px;
+ }
+
+ .raw-details {
+ white-space: pre-wrap;
+ }
+
+ .details > dl {
+ margin: 0;
+ }
+
+ :host(:not([selected])) .details {
+ display: none;
+ }
+ </style>
+ <div class="header">
+ {{name}}
+ <template is="dom-if" if="{{_computeIfSKP(richDetails)}}">
+ <a class="extra" href$="{{_computeHref(richDetails)}}"
+ download="drawing.skp" on-click="{{stopPropagation}}">SKP</a>
+ </template>
+ </div>
+ <div class="details">
+ <template is="dom-if" if="{{rawDetails}}">
+ <div class="raw-details">{{rawDetails}}</div>
+ </template>
+ <template is="dom-if" if="{{richDetails}}">
+ <dl>
+ <template is="dom-if" if="{{richDetails.visualRect}}">
+ <dt>Visual rect</dt>
+ <dd>{{richDetails.visualRect.x}},{{richDetails.visualRect.y}}
+ {{richDetails.visualRect.width}}&times;{{richDetails.visualRect.height}}
+ </dd>
+ </template>
+ </dl>
+ </template>
+ </div>
+ </template>
+<script>
+'use strict';
+(function() {
+ // Extracts the "type" and "details" parts of the unstructured (plaintext)
+ // display item format, even if the details span multiple lines.
+ // For example, given "FooDisplayItem type=hello\nworld", produces
+ // "FooDisplayItem" as the first capture and "type=hello\nworld" as the
+ // second. Either capture could be the empty string, but this regex will
+ // still successfully match.
+ const DETAILS_SPLIT_REGEX = /^(\S*)\s*([\S\s]*)$/;
+
+ Polymer({
+ is: 'tr-ui-e-chrome-cc-display-item-list-item',
+
+ created() {
+ // TODO(charliea): Why is setAttribute necessary here but not below? We
+ // should reach out to the Polymer team to figure out.
+ Polymer.dom(this).setAttribute('name', '');
+ Polymer.dom(this).setAttribute('rawDetails', '');
+ Polymer.dom(this).setAttribute('richDetails', undefined);
+ Polymer.dom(this).setAttribute('data_', undefined);
+ },
+
+ get data() {
+ return this.data_;
+ },
+
+ set data(data) {
+ this.data_ = data;
+
+ if (!data) {
+ this.name = 'DATA MISSING';
+ this.rawDetails = '';
+ this.richDetails = undefined;
+ } else if (typeof data === 'string') {
+ const match = data.match(DETAILS_SPLIT_REGEX);
+ this.name = match[1];
+ this.rawDetails = match[2];
+ this.richDetails = undefined;
+ } else {
+ this.name = data.name;
+ this.rawDetails = '';
+ this.richDetails = data;
+ }
+ },
+
+ stopPropagation(e) {
+ e.stopPropagation();
+ },
+
+ _computeIfSKP(richDetails) {
+ return richDetails && richDetails.skp64;
+ },
+
+ _computeHref(richDetails) {
+ return 'data:application/octet-stream;base64,' + richDetails.skp64;
+ }
+ });
+})();
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_list_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_list_view.html
new file mode 100644
index 00000000000..97598aaf3a7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/display_item_list_view.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 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.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/display_item_list.html">
+<link rel="import" href="/tracing/ui/analysis/generic_object_view.html">
+<link rel="import" href="/tracing/ui/analysis/object_snapshot_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/display_item_debugger.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ /*
+ * Displays a display item snapshot in a human readable form.
+ * @constructor
+ */
+ const DisplayItemSnapshotView = tr.ui.b.define(
+ 'tr-ui-e-chrome-cc-display-item-list-view',
+ tr.ui.analysis.ObjectSnapshotView);
+
+ DisplayItemSnapshotView.prototype = {
+ __proto__: tr.ui.analysis.ObjectSnapshotView.prototype,
+
+ decorate() {
+ this.style.display = 'flex';
+ this.style.flexGrow = 1;
+ this.style.flexShrink = 1;
+ this.style.flexBasis = 'auto';
+ this.style.minWidth = 0;
+ this.displayItemDebugger_ = new tr.ui.e.chrome.cc.DisplayItemDebugger();
+ this.displayItemDebugger_.style.flexGrow = 1;
+ this.displayItemDebugger_.style.flexShrink = 1;
+ this.displayItemDebugger_.style.flexBasis = 'auto';
+ this.displayItemDebugger_.style.minWidth = 0;
+ Polymer.dom(this).appendChild(this.displayItemDebugger_);
+ },
+
+ updateContents() {
+ if (this.objectSnapshot_ && this.displayItemDebugger_) {
+ this.displayItemDebugger_.displayItemList = this.objectSnapshot_;
+ }
+ }
+ };
+
+ tr.ui.analysis.ObjectSnapshotView.register(
+ DisplayItemSnapshotView,
+ {
+ typeNames: ['cc::DisplayItemList'],
+ showInstances: false
+ });
+
+ return {
+ DisplayItemSnapshotView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/images/input-event.png b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/images/input-event.png
new file mode 100644
index 00000000000..a2b7710d3c4
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/images/input-event.png
Binary files differ
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/images/input-event.svg b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/images/input-event.svg
new file mode 100644
index 00000000000..00531ac68d7
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/images/input-event.svg
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="744.09448819"
+ height="1052.3622047"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="New document 1">
+ <defs
+ id="defs4">
+ <filter
+ inkscape:collect="always"
+ id="filter3791">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="2.7246316"
+ id="feGaussianBlur3793" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.8"
+ inkscape:cx="195.13782"
+ inkscape:cy="982.30556"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1215"
+ inkscape:window-height="860"
+ inkscape:window-x="2219"
+ inkscape:window-y="113"
+ inkscape:window-maximized="0" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g3882"
+ style="opacity:0.5"
+ inkscape:export-filename="/tmp/input-event.png"
+ inkscape:export-xdpi="82.07"
+ inkscape:export-ydpi="82.07">
+ <path
+ transform="matrix(1.0152631,0,0,1.0152631,-0.71357503,0.46150497)"
+ sodipodi:type="arc"
+ style="opacity:0.50934604000000006;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter3791);enable-background:accumulate"
+ id="path3755"
+ sodipodi:cx="177.78685"
+ sodipodi:cy="100.79848"
+ sodipodi:rx="42.426407"
+ sodipodi:ry="42.426407"
+ d="m 220.21326,100.79848 a 42.426407,42.426407 0 1 1 -84.85282,0 42.426407,42.426407 0 1 1 84.85282,0 z" />
+ <path
+ transform="translate(-2,-2)"
+ d="m 220.21326,100.79848 a 42.426407,42.426407 0 1 1 -84.85282,0 42.426407,42.426407 0 1 1 84.85282,0 z"
+ sodipodi:ry="42.426407"
+ sodipodi:rx="42.426407"
+ sodipodi:cy="100.79848"
+ sodipodi:cx="177.78685"
+ id="path2985"
+ style="color:#000000;fill:#d4d4d4;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ sodipodi:type="arc" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3853"
+ d="m 175.28125,96.03125 0,8.46875 1,0 0,-8.46875 -1,0 z"
+ style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3859"
+ d="m 171.53125,99.75 0,1 8.46875,0 0,-1 -8.46875,0 z"
+ style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" />
+ </g>
+ <path
+ transform="matrix(1.2923213,0,0,1.2923213,-53.970887,-31.465544)"
+ d="m 220.21326,100.79848 a 42.426407,42.426407 0 1 1 -84.85282,0 42.426407,42.426407 0 1 1 84.85282,0 z"
+ sodipodi:ry="42.426407"
+ sodipodi:rx="42.426407"
+ sodipodi:cy="100.79848"
+ sodipodi:cx="177.78685"
+ id="path3867"
+ style="color:#000000;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ sodipodi:type="arc"
+ inkscape:export-filename="/tmp/input-event.png"
+ inkscape:export-xdpi="82.07"
+ inkscape:export-ydpi="82.07" />
+ </g>
+</svg>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_picker.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_picker.html
new file mode 100644
index 00000000000..9f81199e358
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_picker.html
@@ -0,0 +1,336 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/extras/chrome/cc/constants.html">
+<link rel="import" href="/tracing/extras/chrome/cc/layer_tree_host_impl.html">
+<link rel="import" href="/tracing/extras/chrome/cc/util.html">
+<link rel="import" href="/tracing/model/event.html">
+<link rel="import" href="/tracing/ui/analysis/generic_object_view.html">
+<link rel="import" href="/tracing/ui/base/dom_helpers.html">
+<link rel="import" href="/tracing/ui/base/drag_handle.html">
+<link rel="import" href="/tracing/ui/base/list_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/selection.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ const constants = tr.e.cc.constants;
+ const RENDER_PASS_QUADS =
+ Math.max(constants.ACTIVE_TREE, constants.PENDING_TREE) + 1;
+
+ /**
+ * @constructor
+ */
+ const LayerPicker = tr.ui.b.define('tr-ui-e-chrome-cc-layer-picker');
+
+ LayerPicker.prototype = {
+ __proto__: HTMLUnknownElement.prototype,
+
+ decorate() {
+ this.lthi_ = undefined;
+ this.controls_ = document.createElement('top-controls');
+ this.renderPassQuads_ = false;
+
+ this.style.display = 'flex';
+ this.style.flexDirection = 'column';
+ this.controls_.style.flexGrow = 0;
+ this.controls_.style.flexShrink = 0;
+ this.controls_.style.flexBasis = 'auto';
+ this.controls_.style.backgroundImage =
+ '-webkit-gradient(linear, 0 0, 100% 0, from(#E5E5E5), to(#D1D1D1))';
+ this.controls_.style.borderBottom = '1px solid #8e8e8e';
+ this.controls_.style.borderTop = '1px solid white';
+ this.controls_.style.display = 'inline';
+ this.controls_.style.fontSize = '14px';
+ this.controls_.style.paddingLeft = '2px';
+
+ this.itemList_ = new tr.ui.b.ListView();
+ this.itemList_.style.flexGrow = 1;
+ this.itemList_.style.flexShrink = 1;
+ this.itemList_.style.flexBasis = 'auto';
+ this.itemList_.style.fontFamily = 'monospace';
+ this.itemList_.style.overflow = 'auto';
+ Polymer.dom(this).appendChild(this.controls_);
+
+ Polymer.dom(this).appendChild(this.itemList_);
+
+ this.itemList_.addEventListener(
+ 'selection-changed', this.onItemSelectionChanged_.bind(this));
+
+ Polymer.dom(this.controls_).appendChild(tr.ui.b.createSelector(
+ this, 'whichTree',
+ 'layerPicker.whichTree', constants.ACTIVE_TREE,
+ [{label: 'Active tree', value: constants.ACTIVE_TREE},
+ {label: 'Pending tree', value: constants.PENDING_TREE},
+ {label: 'Render pass quads', value: RENDER_PASS_QUADS}]));
+
+ this.showPureTransformLayers_ = false;
+ const showPureTransformLayers = tr.ui.b.createCheckBox(
+ this, 'showPureTransformLayers',
+ 'layerPicker.showPureTransformLayers', false,
+ 'Transform layers');
+ Polymer.dom(showPureTransformLayers).classList.add(
+ 'show-transform-layers');
+ showPureTransformLayers.title =
+ 'When checked, pure transform layers are shown';
+ Polymer.dom(this.controls_).appendChild(showPureTransformLayers);
+ },
+
+ get lthiSnapshot() {
+ return this.lthiSnapshot_;
+ },
+
+ set lthiSnapshot(lthiSnapshot) {
+ this.lthiSnapshot_ = lthiSnapshot;
+ this.updateContents_();
+ },
+
+ get whichTree() {
+ return this.renderPassQuads_ ? constants.ACTIVE_TREE : this.whichTree_;
+ },
+
+ set whichTree(whichTree) {
+ this.whichTree_ = whichTree;
+ this.renderPassQuads_ = (whichTree === RENDER_PASS_QUADS);
+ this.updateContents_();
+ tr.b.dispatchSimpleEvent(this, 'selection-change', false);
+ },
+
+ get layerTreeImpl() {
+ if (this.lthiSnapshot === undefined) return undefined;
+
+ return this.lthiSnapshot.getTree(this.whichTree);
+ },
+
+ get isRenderPassQuads() {
+ return this.renderPassQuads_;
+ },
+
+ get showPureTransformLayers() {
+ return this.showPureTransformLayers_;
+ },
+
+ set showPureTransformLayers(show) {
+ if (this.showPureTransformLayers_ === show) return;
+
+ this.showPureTransformLayers_ = show;
+ this.updateContents_();
+ },
+
+ getRenderPassInfos_() {
+ if (!this.lthiSnapshot_) return [];
+
+ const renderPassInfo = [];
+ if (!this.lthiSnapshot_.args.frame ||
+ !this.lthiSnapshot_.args.frame.renderPasses) {
+ return renderPassInfo;
+ }
+
+ const renderPasses = this.lthiSnapshot_.args.frame.renderPasses;
+ for (let i = 0; i < renderPasses.length; ++i) {
+ const info = {renderPass: renderPasses[i],
+ depth: 0,
+ id: i,
+ name: 'cc::RenderPass'};
+ renderPassInfo.push(info);
+ }
+ return renderPassInfo;
+ },
+
+ getLayerInfos_() {
+ if (!this.lthiSnapshot_) return [];
+
+ const tree = this.lthiSnapshot_.getTree(this.whichTree_);
+ if (!tree) return [];
+
+ const layerInfos = [];
+
+ const showPureTransformLayers = this.showPureTransformLayers_;
+
+ const visitedLayers = {};
+ function visitLayer(layer, depth, isMask, isReplica) {
+ if (visitedLayers[layer.layerId]) return;
+
+ visitedLayers[layer.layerId] = true;
+ const info = {layer,
+ depth};
+
+ if (layer.args.drawsContent) {
+ info.name = layer.objectInstance.name;
+ } else {
+ info.name = 'cc::LayerImpl';
+ }
+
+ if (layer.usingGpuRasterization) {
+ info.name += ' (G)';
+ }
+
+ info.isMaskLayer = isMask;
+ info.replicaLayer = isReplica;
+
+ if (showPureTransformLayers || layer.args.drawsContent) {
+ layerInfos.push(info);
+ }
+ }
+ tree.iterLayers(visitLayer);
+ return layerInfos;
+ },
+
+ updateContents_() {
+ if (this.renderPassQuads_) {
+ this.updateRenderPassContents_();
+ } else {
+ this.updateLayerContents_();
+ }
+ },
+
+ updateRenderPassContents_() {
+ this.itemList_.clear();
+
+ let selectedRenderPassId;
+ if (this.selection_ && this.selection_.associatedRenderPassId) {
+ selectedRenderPassId = this.selection_.associatedRenderPassId;
+ }
+
+ const renderPassInfos = this.getRenderPassInfos_();
+ renderPassInfos.forEach(function(renderPassInfo) {
+ const renderPass = renderPassInfo.renderPass;
+ const id = renderPassInfo.id;
+
+ const item = this.createElementWithDepth_(renderPassInfo.depth);
+ const labelEl = Polymer.dom(item).appendChild(tr.ui.b.createSpan());
+
+ Polymer.dom(labelEl).textContent = renderPassInfo.name + ' ' + id;
+ item.renderPass = renderPass;
+ item.renderPassId = id;
+ Polymer.dom(this.itemList_).appendChild(item);
+
+ if (id === selectedRenderPassId) {
+ renderPass.selectionState =
+ tr.model.SelectionState.SELECTED;
+ }
+ }, this);
+ },
+
+ updateLayerContents_() {
+ this.changingItemSelection_ = true;
+ try {
+ this.itemList_.clear();
+
+ let selectedLayerId;
+ if (this.selection_ && this.selection_.associatedLayerId) {
+ selectedLayerId = this.selection_.associatedLayerId;
+ }
+
+ const layerInfos = this.getLayerInfos_();
+ layerInfos.forEach(function(layerInfo) {
+ const layer = layerInfo.layer;
+ const id = layer.layerId;
+
+ const item = this.createElementWithDepth_(layerInfo.depth);
+ const labelEl = Polymer.dom(item).appendChild(tr.ui.b.createSpan());
+
+ Polymer.dom(labelEl).textContent = layerInfo.name + ' ' + id;
+
+ const notesEl = Polymer.dom(item).appendChild(tr.ui.b.createSpan());
+ if (layerInfo.isMaskLayer) {
+ Polymer.dom(notesEl).textContent += '(mask)';
+ }
+ if (layerInfo.isReplicaLayer) {
+ Polymer.dom(notesEl).textContent += '(replica)';
+ }
+
+ if ((layer.gpuMemoryUsageInBytes !== undefined) &&
+ (layer.gpuMemoryUsageInBytes > 0)) {
+ const gpuUsageStr = tr.b.Unit.byName.sizeInBytes.format(
+ layer.gpuMemoryUsageInBytes);
+ Polymer.dom(notesEl).textContent += ' (' + gpuUsageStr + ' MiB)';
+ }
+
+ item.layer = layer;
+ Polymer.dom(this.itemList_).appendChild(item);
+
+ if (layer.layerId === selectedLayerId) {
+ layer.selectionState = tr.model.SelectionState.SELECTED;
+ item.selected = true;
+ }
+ }, this);
+ } finally {
+ this.changingItemSelection_ = false;
+ }
+ },
+
+ createElementWithDepth_(depth) {
+ const item = document.createElement('div');
+
+ const indentEl = Polymer.dom(item).appendChild(tr.ui.b.createSpan());
+ indentEl.style.whiteSpace = 'pre';
+ for (let i = 0; i < depth; i++) {
+ Polymer.dom(indentEl).textContent =
+ Polymer.dom(indentEl).textContent + ' ';
+ }
+ return item;
+ },
+
+ onItemSelectionChanged_(e) {
+ if (this.changingItemSelection_) return;
+ if (this.renderPassQuads_) {
+ this.onRenderPassSelected_(e);
+ } else {
+ this.onLayerSelected_(e);
+ }
+ tr.b.dispatchSimpleEvent(this, 'selection-change', false);
+ },
+
+ onRenderPassSelected_(e) {
+ let selectedRenderPass;
+ let selectedRenderPassId;
+ if (this.itemList_.selectedElement) {
+ selectedRenderPass = this.itemList_.selectedElement.renderPass;
+ selectedRenderPassId =
+ this.itemList_.selectedElement.renderPassId;
+ }
+
+ if (selectedRenderPass) {
+ this.selection_ = new tr.ui.e.chrome.cc.RenderPassSelection(
+ selectedRenderPass, selectedRenderPassId);
+ } else {
+ this.selection_ = undefined;
+ }
+ },
+
+ onLayerSelected_(e) {
+ let selectedLayer;
+ if (this.itemList_.selectedElement) {
+ selectedLayer = this.itemList_.selectedElement.layer;
+ }
+
+ if (selectedLayer) {
+ this.selection_ = new tr.ui.e.chrome.cc.LayerSelection(selectedLayer);
+ } else {
+ this.selection_ = undefined;
+ }
+ },
+
+ get selection() {
+ return this.selection_;
+ },
+
+ set selection(selection) {
+ if (this.selection_ === selection) return;
+ this.selection_ = selection;
+ this.updateContents_();
+ }
+ };
+
+ return {
+ LayerPicker,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view.html
new file mode 100644
index 00000000000..1aaee9d7fbc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view.html
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/layer_tree_host_impl.html">
+<link rel="import" href="/tracing/extras/chrome/cc/tile.html">
+<link rel="import" href="/tracing/ui/analysis/object_snapshot_view.html">
+<link rel="import" href="/tracing/ui/base/drag_handle.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/layer_picker.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/layer_view.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ /*
+ * Displays a LayerTreeHostImpl snapshot in a human readable form.
+ * @constructor
+ */
+ const LayerTreeHostImplSnapshotView = tr.ui.b.define(
+ 'tr-ui-e-chrome-cc-layer-tree-host-impl-snapshot-view',
+ tr.ui.analysis.ObjectSnapshotView);
+
+ LayerTreeHostImplSnapshotView.prototype = {
+ __proto__: tr.ui.analysis.ObjectSnapshotView.prototype,
+
+ decorate() {
+ Polymer.dom(this).classList.add('tr-ui-e-chrome-cc-lthi-s-view');
+ this.style.display = 'flex';
+ this.style.flexDirection = 'row';
+ this.style.flexGrow = 1;
+ this.style.flexShrink = 1;
+ this.style.flexBasis = 'auto';
+ this.style.minWidth = 0;
+
+ this.selection_ = undefined;
+
+ this.layerPicker_ = new tr.ui.e.chrome.cc.LayerPicker();
+ this.layerPicker_.style.flexGrow = 0;
+ this.layerPicker_.style.flexShrink = 0;
+ this.layerPicker_.style.flexBasis = 'auto';
+ this.layerPicker_.style.minWidth = '200px';
+ this.layerPicker_.addEventListener(
+ 'selection-change',
+ this.onLayerPickerSelectionChanged_.bind(this));
+
+ this.layerView_ = new tr.ui.e.chrome.cc.LayerView();
+ this.layerView_.addEventListener(
+ 'selection-change',
+ this.onLayerViewSelectionChanged_.bind(this));
+ this.layerView_.style.flexGrow = 1;
+ this.layerView_.style.flexShrink = 1;
+ this.layerView_.style.flexBasis = 'auto';
+ this.layerView_.style.minWidth = 0;
+
+ this.dragHandle_ = document.createElement('tr-ui-b-drag-handle');
+ this.dragHandle_.style.flexGrow = 0;
+ this.dragHandle_.style.flexShrink = 0;
+ this.dragHandle_.style.flexBasis = 'auto';
+ this.dragHandle_.horizontal = false;
+ this.dragHandle_.target = this.layerPicker_;
+
+ Polymer.dom(this).appendChild(this.layerPicker_);
+ Polymer.dom(this).appendChild(this.dragHandle_);
+ Polymer.dom(this).appendChild(this.layerView_);
+
+ // Make sure we have the current values from layerView_ and layerPicker_,
+ // since those might have been created before we added the listener.
+ this.onLayerViewSelectionChanged_();
+ this.onLayerPickerSelectionChanged_();
+ },
+
+ get objectSnapshot() {
+ return this.objectSnapshot_;
+ },
+
+ set objectSnapshot(objectSnapshot) {
+ this.objectSnapshot_ = objectSnapshot;
+
+ const lthi = this.objectSnapshot;
+ let layerTreeImpl;
+ if (lthi) {
+ layerTreeImpl = lthi.getTree(this.layerPicker_.whichTree);
+ }
+
+ this.layerPicker_.lthiSnapshot = lthi;
+ this.layerView_.layerTreeImpl = layerTreeImpl;
+ this.layerView_.regenerateContent();
+
+ if (!this.selection_) return;
+
+ this.selection = this.selection_.findEquivalent(lthi);
+ },
+
+ get selection() {
+ return this.selection_;
+ },
+
+ set selection(selection) {
+ if (this.selection_ === selection) return;
+
+ this.selection_ = selection;
+ this.layerPicker_.selection = selection;
+ this.layerView_.selection = selection;
+ tr.b.dispatchSimpleEvent(this, 'cc-selection-change');
+ },
+
+ onLayerPickerSelectionChanged_() {
+ this.selection_ = this.layerPicker_.selection;
+ this.layerView_.selection = this.selection;
+ this.layerView_.layerTreeImpl = this.layerPicker_.layerTreeImpl;
+ this.layerView_.isRenderPassQuads = this.layerPicker_.isRenderPassQuads;
+ this.layerView_.regenerateContent();
+ tr.b.dispatchSimpleEvent(this, 'cc-selection-change');
+ },
+
+ onLayerViewSelectionChanged_() {
+ this.selection_ = this.layerView_.selection;
+ this.layerPicker_.selection = this.selection;
+ tr.b.dispatchSimpleEvent(this, 'cc-selection-change');
+ },
+
+ get extraHighlightsByLayerId() {
+ return this.layerView_.extraHighlightsByLayerId;
+ },
+
+ set extraHighlightsByLayerId(extraHighlightsByLayerId) {
+ this.layerView_.extraHighlightsByLayerId = extraHighlightsByLayerId;
+ }
+ };
+
+ tr.ui.analysis.ObjectSnapshotView.register(
+ LayerTreeHostImplSnapshotView, {typeName: 'cc::LayerTreeHostImpl'});
+
+ return {
+ LayerTreeHostImplSnapshotView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view_test.html
new file mode 100644
index 00000000000..1831be24618
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view_test.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/cc/layer_tree_host_impl.html">
+<link rel="import" href="/tracing/extras/chrome/cc/raster_task.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view.html">
+
+<script src="/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js">
+</script>
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('instantiate', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = Object.values(m.processes)[0];
+
+ const instance = p.objects.getAllInstancesNamed('cc::LayerTreeHostImpl')[0];
+ const snapshot = instance.snapshots[0];
+
+ const view = new tr.ui.e.chrome.cc.LayerTreeHostImplSnapshotView();
+ view.style.width = '900px';
+ view.style.height = '400px';
+ view.objectSnapshot = snapshot;
+
+ this.addHTMLOutput(view);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view.html
new file mode 100644
index 00000000000..2a7e5666f8b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view.html
@@ -0,0 +1,1200 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 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.
+-->
+
+<link rel="import" href="/tracing/base/color.html">
+<link rel="import" href="/tracing/base/math/quad.html">
+<link rel="import" href="/tracing/base/math/range.html">
+<link rel="import" href="/tracing/base/raf.html">
+<link rel="import" href="/tracing/base/unit_scale.html">
+<link rel="import" href="/tracing/extras/chrome/cc/debug_colors.html">
+<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
+<link rel="import" href="/tracing/extras/chrome/cc/render_pass.html">
+<link rel="import" href="/tracing/extras/chrome/cc/tile.html">
+<link rel="import" href="/tracing/extras/chrome/cc/util.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/ui/analysis/analysis_link.html">
+<link rel="import" href="/tracing/ui/base/info_bar.html">
+<link rel="import" href="/tracing/ui/base/quad_stack_view.html">
+<link rel="import" href="/tracing/ui/base/utils.html">
+
+<template id='tr-ui-e-chrome-cc-layer-tree-quad-stack-view-template'>
+ <style>
+ #input-event {
+ background-image: url('./images/input-event.png');
+ display: none;
+ }
+ </style>
+ <img id='input-event'/>
+</template>
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview Graphical view of LayerTreeImpl, with controls for
+ * type of layer content shown and info bar for content-loading warnings.
+ */
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ const ColorScheme = tr.b.ColorScheme;
+
+ const THIS_DOC = document.currentScript.ownerDocument;
+ const TILE_HEATMAP_TYPE = {};
+ TILE_HEATMAP_TYPE.NONE = 'none';
+ TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY = 'scheduledPriority';
+ TILE_HEATMAP_TYPE.USING_GPU_MEMORY = 'usingGpuMemory';
+
+ const cc = tr.ui.e.chrome.cc;
+
+ function createTileRectsSelectorBaseOptions() {
+ return [{label: 'None', value: 'none'},
+ {label: 'Coverage Rects', value: 'coverage'}];
+ }
+
+
+ /**
+ * @constructor
+ */
+ const LayerTreeQuadStackView =
+ tr.ui.b.define('tr-ui-e-chrome-cc-layer-tree-quad-stack-view');
+
+ LayerTreeQuadStackView.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ decorate() {
+ this.style.flexGrow = 1;
+ this.style.flexShrink = 1;
+ this.style.flexBasis = 'auto';
+ this.style.flexDirection = 'column';
+ this.style.minHeight = 0;
+ this.style.display = 'flex';
+
+ this.isRenderPassQuads_ = false;
+ this.pictureAsImageData_ = {}; // Maps picture.guid to PictureAsImageData.
+ this.messages_ = [];
+ this.controls_ = document.createElement('top-controls');
+ this.controls_.style.flexGrow = 0;
+ this.controls_.style.flexShrink = 0;
+ this.controls_.style.flexBasis = 'auto';
+ this.controls_.style.backgroundImage =
+ '-webkit-gradient(linear, 0 0, 100% 0, from(#E5E5E5), to(#D1D1D1))';
+ this.controls_.style.borderBottom = '1px solid #8e8e8e';
+ this.controls_.style.borderTop = '1px solid white';
+ this.controls_.style.display = 'flex';
+ this.controls_.style.flexDirection = 'row';
+ this.controls_.style.flexWrap = 'wrap';
+ this.controls_.style.fontSize = '14px';
+ this.controls_.style.paddingLeft = '2px';
+ this.controls_.style.overflow = 'hidden';
+ this.infoBar_ = document.createElement('tr-ui-b-info-bar');
+ this.quadStackView_ = new tr.ui.b.QuadStackView();
+ this.quadStackView_.addEventListener(
+ 'selectionchange', this.onQuadStackViewSelectionChange_.bind(this));
+ this.quadStackView_.style.flexGrow = 1;
+ this.quadStackView_.style.flexShrink = 1;
+ this.quadStackView_.style.flexBasis = 'auto';
+ this.quadStackView_.style.minWidth = '200px';
+
+ this.extraHighlightsByLayerId_ = undefined;
+ this.inputEventImageData_ = undefined;
+
+ const m = tr.ui.b.MOUSE_SELECTOR_MODE;
+ const mms = this.quadStackView_.mouseModeSelector;
+ mms.settingsKey = 'tr.e.cc.layerTreeQuadStackView.mouseModeSelector';
+ mms.setKeyCodeForMode(m.SELECTION, 'Z'.charCodeAt(0));
+ mms.setKeyCodeForMode(m.PANSCAN, 'X'.charCodeAt(0));
+ mms.setKeyCodeForMode(m.ZOOM, 'C'.charCodeAt(0));
+ mms.setKeyCodeForMode(m.ROTATE, 'V'.charCodeAt(0));
+
+ const node = tr.ui.b.instantiateTemplate(
+ '#tr-ui-e-chrome-cc-layer-tree-quad-stack-view-template', THIS_DOC);
+ Polymer.dom(this).appendChild(node);
+ Polymer.dom(this).appendChild(this.controls_);
+ Polymer.dom(this).appendChild(this.infoBar_);
+ Polymer.dom(this).appendChild(this.quadStackView_);
+
+ this.tileRectsSelector_ = tr.ui.b.createSelector(
+ this, 'howToShowTiles',
+ 'layerView.howToShowTiles', 'none',
+ createTileRectsSelectorBaseOptions());
+ Polymer.dom(this.controls_).appendChild(this.tileRectsSelector_);
+
+ const tileHeatmapText = tr.ui.b.createSpan({
+ textContent: 'Tile heatmap:'
+ });
+ Polymer.dom(this.controls_).appendChild(tileHeatmapText);
+
+ const tileHeatmapSelector = tr.ui.b.createSelector(
+ this, 'tileHeatmapType',
+ 'layerView.tileHeatmapType', TILE_HEATMAP_TYPE.NONE,
+ [{label: 'None',
+ value: TILE_HEATMAP_TYPE.NONE},
+ {label: 'Scheduled Priority',
+ value: TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY},
+ {label: 'Is using GPU memory',
+ value: TILE_HEATMAP_TYPE.USING_GPU_MEMORY}
+ ]);
+ Polymer.dom(this.controls_).appendChild(tileHeatmapSelector);
+
+ const showOtherLayersCheckbox = tr.ui.b.createCheckBox(
+ this, 'showOtherLayers',
+ 'layerView.showOtherLayers', true,
+ 'Other layers/passes');
+ showOtherLayersCheckbox.title =
+ 'When checked, show all layers, selected or not.';
+ Polymer.dom(this.controls_).appendChild(showOtherLayersCheckbox);
+
+ const showInvalidationsCheckbox = tr.ui.b.createCheckBox(
+ this, 'showInvalidations',
+ 'layerView.showInvalidations', true,
+ 'Invalidations');
+ showInvalidationsCheckbox.title =
+ 'When checked, compositing invalidations are highlighted in red';
+ Polymer.dom(this.controls_).appendChild(showInvalidationsCheckbox);
+
+ const showUnrecordedRegionCheckbox = tr.ui.b.createCheckBox(
+ this, 'showUnrecordedRegion',
+ 'layerView.showUnrecordedRegion', true,
+ 'Unrecorded area');
+ showUnrecordedRegionCheckbox.title =
+ 'When checked, unrecorded areas are highlighted in yellow';
+ Polymer.dom(this.controls_).appendChild(showUnrecordedRegionCheckbox);
+
+ const showBottlenecksCheckbox = tr.ui.b.createCheckBox(
+ this, 'showBottlenecks',
+ 'layerView.showBottlenecks', true,
+ 'Bottlenecks');
+ showBottlenecksCheckbox.title =
+ 'When checked, scroll bottlenecks are highlighted';
+ Polymer.dom(this.controls_).appendChild(showBottlenecksCheckbox);
+
+ const showLayoutRectsCheckbox = tr.ui.b.createCheckBox(
+ this, 'showLayoutRects',
+ 'layerView.showLayoutRects', false,
+ 'Layout rects');
+ showLayoutRectsCheckbox.title =
+ 'When checked, shows rects for regions where layout happened';
+ Polymer.dom(this.controls_).appendChild(showLayoutRectsCheckbox);
+
+ const showContentsCheckbox = tr.ui.b.createCheckBox(
+ this, 'showContents',
+ 'layerView.showContents', true,
+ 'Contents');
+ showContentsCheckbox.title =
+ 'When checked, show the rendered contents inside the layer outlines';
+ Polymer.dom(this.controls_).appendChild(showContentsCheckbox);
+
+ const showAnimationBoundsCheckbox = tr.ui.b.createCheckBox(
+ this, 'showAnimationBounds',
+ 'layerView.showAnimationBounds', false,
+ 'Animation Bounds');
+ showAnimationBoundsCheckbox.title = 'When checked, show a border around' +
+ ' a layer showing the extent of its animation.';
+ Polymer.dom(this.controls_).appendChild(showAnimationBoundsCheckbox);
+
+ const showInputEventsCheckbox = tr.ui.b.createCheckBox(
+ this, 'showInputEvents',
+ 'layerView.showInputEvents', true,
+ 'Input events');
+ showInputEventsCheckbox.title = 'When checked, input events are ' +
+ 'displayed as circles.';
+ Polymer.dom(this.controls_).appendChild(showInputEventsCheckbox);
+
+ this.whatRasterizedLink_ = document.createElement(
+ 'tr-ui-a-analysis-link');
+ this.whatRasterizedLink_.style.position = 'absolute';
+ this.whatRasterizedLink_.style.bottom = '15px';
+ this.whatRasterizedLink_.style.left = '10px';
+ this.whatRasterizedLink_.selection =
+ this.getWhatRasterizedEventSet_.bind(this);
+ Polymer.dom(this.quadStackView_).appendChild(this.whatRasterizedLink_);
+ },
+
+ get layerTreeImpl() {
+ return this.layerTreeImpl_;
+ },
+
+ set isRenderPassQuads(newValue) {
+ this.isRenderPassQuads_ = newValue;
+ },
+
+ set layerTreeImpl(layerTreeImpl) {
+ if (this.layerTreeImpl_ === layerTreeImpl) return;
+
+ // FIXME(pdr): We may want to clear pictureAsImageData_ here to save
+ // memory at the cost of performance. Note that
+ // pictureAsImageData_ will be cleared when this is
+ // destructed, but this view might live for several
+ // layerTreeImpls.
+ this.layerTreeImpl_ = layerTreeImpl;
+ this.selection = undefined;
+ },
+
+ get extraHighlightsByLayerId() {
+ return this.extraHighlightsByLayerId_;
+ },
+
+ set extraHighlightsByLayerId(extraHighlightsByLayerId) {
+ this.extraHighlightsByLayerId_ = extraHighlightsByLayerId;
+ this.scheduleUpdateContents_();
+ },
+
+ get showOtherLayers() {
+ return this.showOtherLayers_;
+ },
+
+ set showOtherLayers(show) {
+ this.showOtherLayers_ = show;
+ this.updateContents_();
+ },
+
+ get showAnimationBounds() {
+ return this.showAnimationBounds_;
+ },
+
+ set showAnimationBounds(show) {
+ this.showAnimationBounds_ = show;
+ this.updateContents_();
+ },
+
+ get showInputEvents() {
+ return this.showInputEvents_;
+ },
+
+ set showInputEvents(show) {
+ this.showInputEvents_ = show;
+ this.updateContents_();
+ },
+
+ get showContents() {
+ return this.showContents_;
+ },
+
+ set showContents(show) {
+ this.showContents_ = show;
+ this.updateContents_();
+ },
+
+ get showInvalidations() {
+ return this.showInvalidations_;
+ },
+
+ set showInvalidations(show) {
+ this.showInvalidations_ = show;
+ this.updateContents_();
+ },
+
+ get showUnrecordedRegion() {
+ return this.showUnrecordedRegion_;
+ },
+
+ set showUnrecordedRegion(show) {
+ this.showUnrecordedRegion_ = show;
+ this.updateContents_();
+ },
+
+ get showBottlenecks() {
+ return this.showBottlenecks_;
+ },
+
+ set showBottlenecks(show) {
+ this.showBottlenecks_ = show;
+ this.updateContents_();
+ },
+
+ get showLayoutRects() {
+ return this.showLayoutRects_;
+ },
+
+ set showLayoutRects(show) {
+ this.showLayoutRects_ = show;
+ this.updateContents_();
+ },
+
+ get howToShowTiles() {
+ return this.howToShowTiles_;
+ },
+
+ set howToShowTiles(val) {
+ // Make sure val is something we expect.
+ if (val !== 'none' && val !== 'coverage' && isNaN(parseFloat(val))) {
+ throw new Error(
+ 'howToShowTiles requires "none" or "coverage" or a number');
+ }
+
+ this.howToShowTiles_ = val;
+ this.updateContents_();
+ },
+
+ get tileHeatmapType() {
+ return this.tileHeatmapType_;
+ },
+
+ set tileHeatmapType(val) {
+ this.tileHeatmapType_ = val;
+ this.updateContents_();
+ },
+
+ get selection() {
+ return this.selection_;
+ },
+
+ set selection(selection) {
+ if (this.selection === selection) return;
+
+ this.selection_ = selection;
+ tr.b.dispatchSimpleEvent(this, 'selection-change');
+ this.updateContents_();
+ },
+
+ regenerateContent() {
+ this.updateTilesSelector_();
+ this.updateContents_();
+ },
+
+ loadDataForImageElement_(image, callback) {
+ const imageContent = window.getComputedStyle(image).backgroundImage;
+ if (!imageContent) {
+ // The style has not been applied because the view has not been added
+ // into the DOM tree yet. Try again in another cycle.
+ this.scheduleUpdateContents_();
+ return;
+ }
+ image.src = tr.ui.b.extractUrlString(imageContent);
+ image.onload = function() {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ canvas.width = image.width;
+ canvas.height = image.height;
+ ctx.drawImage(image, 0, 0);
+ const imageData = ctx.getImageData(
+ 0, 0, canvas.width, canvas.height);
+ callback(imageData);
+ };
+ },
+
+ onQuadStackViewSelectionChange_(e) {
+ const selectableQuads = e.quads.filter(function(q) {
+ return q.selectionToSetIfClicked !== undefined;
+ });
+ if (selectableQuads.length === 0) {
+ this.selection = undefined;
+ return;
+ }
+
+ // Sort the quads low to high on stackingGroupId.
+ selectableQuads.sort(function(x, y) {
+ const z = x.stackingGroupId - y.stackingGroupId;
+ if (z !== 0) return z;
+
+ return x.selectionToSetIfClicked.specicifity -
+ y.selectionToSetIfClicked.specicifity;
+ });
+
+ // TODO(nduca): Support selecting N things at once.
+ const quadToSelect = selectableQuads[selectableQuads.length - 1];
+ this.selection = quadToSelect.selectionToSetIfClicked;
+ },
+
+ scheduleUpdateContents_() {
+ if (this.updateContentsPending_) return;
+
+ this.updateContentsPending_ = true;
+ tr.b.requestAnimationFrameInThisFrameIfPossible(
+ this.updateContents_, this);
+ },
+
+ updateContents_() {
+ if (!this.layerTreeImpl_) {
+ this.quadStackView_.headerText = 'No tree';
+ this.quadStackView_.quads = [];
+ return;
+ }
+
+
+ const status = this.computePictureLoadingStatus_();
+ if (!status.picturesComplete) return;
+
+ const lthi = this.layerTreeImpl_.layerTreeHostImpl;
+ const lthiInstance = lthi.objectInstance;
+ const worldViewportRect = tr.b.math.Rect.fromXYWH(
+ 0, 0,
+ lthi.deviceViewportSize.width, lthi.deviceViewportSize.height);
+ this.quadStackView_.deviceRect = worldViewportRect;
+ if (this.isRenderPassQuads_) {
+ this.quadStackView_.quads = this.generateRenderPassQuads();
+ } else {
+ this.quadStackView_.quads = this.generateLayerQuads();
+ }
+
+ this.updateWhatRasterizedLinkState_();
+
+ let message = '';
+ if (lthi.tilesHaveGpuMemoryUsageInfo) {
+ const thisTreeUsageInBytes = this.layerTreeImpl_.gpuMemoryUsageInBytes;
+ const otherTreeUsageInBytes = lthi.gpuMemoryUsageInBytes -
+ thisTreeUsageInBytes;
+ message +=
+ tr.b.convertUnit(thisTreeUsageInBytes,
+ tr.b.UnitPrefixScale.BINARY.NONE,
+ tr.b.UnitPrefixScale.BINARY.MEBI).toFixed(1) +
+ ' MiB on this tree';
+ if (otherTreeUsageInBytes) {
+ message += ', ' +
+ tr.b.convertUnit(otherTreeUsageInBytes,
+ tr.b.UnitPrefixScale.BINARY.NONE,
+ tr.b.UnitPrefixScale.BINARY.MEBI).toFixed(1) +
+ ' MiB on the other tree';
+ }
+ } else {
+ if (this.layerTreeImpl_) {
+ const thisTreeUsageInBytes =
+ this.layerTreeImpl_.gpuMemoryUsageInBytes;
+ message +=
+ tr.b.convertUnit(thisTreeUsageInBytes,
+ tr.b.UnitPrefixScale.BINARY.NONE,
+ tr.b.UnitPrefixScale.BINARY.MEBI).toFixed(1) +
+ ' MiB on this tree';
+
+ if (this.layerTreeImpl_.otherTree) {
+ // Older Chromes don't report enough data to know how much memory is
+ // being used across both trees. We know the memory consumed by each
+ // tree, but there is resource sharing *between the trees* so we
+ // can't simply sum up the per-tree costs. We need either the total
+ // plus one tree, to guess the unique on the other tree, etc. Newer
+ // chromes report memory per tile, which allows LTHI to compute the
+ // total tile memory usage, letting us figure things out properly.
+ message += ', ??? MiB on other tree. ';
+ }
+ }
+ }
+
+ if (lthi.args.tileManagerBasicState) {
+ const tmgs = lthi.args.tileManagerBasicState.globalState;
+ message += ' (softMax=' +
+ tr.b.convertUnit(tmgs.softMemoryLimitInBytes,
+ tr.b.UnitPrefixScale.BINARY.NONE,
+ tr.b.UnitPrefixScale.BINARY.MEBI).toFixed(1) +
+ ' MiB, hardMax=' +
+ tr.b.convertUnit(tmgs.hardMemoryLimitInBytes,
+ tr.b.UnitPrefixScale.BINARY.NONE,
+ tr.b.UnitPrefixScale.BINARY.MEBI).toFixed(1) + ' MiB, ' +
+ tmgs.memoryLimitPolicy + ')';
+ } else {
+ // Old Chromes do not have a globalState on the LTHI dump.
+ // But they do issue a DidManage event wiht the globalstate. Find that
+ // event so that we show some global state.
+ const thread = lthi.snapshottedOnThread;
+ const didManageTilesSlices = thread.sliceGroup.slices.filter(s => {
+ if (s.category !== 'tr.e.cc') return false;
+
+ if (s.title !== 'DidManage') return false;
+
+ if (s.end > lthi.ts) return false;
+
+ return true;
+ });
+ didManageTilesSlices.sort(function(x, y) {
+ return x.end - y.end;
+ });
+ if (didManageTilesSlices.length > 0) {
+ const newest = didManageTilesSlices[didManageTilesSlices.length - 1];
+ const tmgs = newest.args.state.global_state;
+ message += ' (softMax=' +
+ tr.b.convertUnit(tmgs.softMemoryLimitInBytes,
+ tr.b.UnitPrefixScale.BINARY.NONE,
+ tr.b.UnitPrefixScale.BINARY.MEBI).toFixed(1) +
+ ' MiB, hardMax=' +
+ tr.b.convertUnit(tmgs.hardMemoryLimitInBytes,
+ tr.b.UnitPrefixScale.BINARY.NONE,
+ tr.b.UnitPrefixScale.BINARY.MEBI).toFixed(1) + ' MiB, ' +
+ tmgs.memoryLimitPolicy + ')';
+ }
+ }
+
+ if (this.layerTreeImpl_.otherTree) {
+ message += ' (Another tree exists)';
+ }
+
+ if (message.length) {
+ this.quadStackView_.headerText = message;
+ } else {
+ this.quadStackView_.headerText = undefined;
+ }
+
+ this.updateInfoBar_(status.messages);
+ },
+
+ updateTilesSelector_() {
+ const data = createTileRectsSelectorBaseOptions();
+
+ if (this.layerTreeImpl_) {
+ // First get all of the scales information from LTHI.
+ const lthi = this.layerTreeImpl_.layerTreeHostImpl;
+ const scaleNames = lthi.getContentsScaleNames();
+ for (const scale in scaleNames) {
+ data.push({
+ label: 'Scale ' + scale + ' (' + scaleNames[scale] + ')',
+ value: scale
+ });
+ }
+ }
+
+ // Then create a new selector and replace the old one.
+ const newSelector = tr.ui.b.createSelector(
+ this, 'howToShowTiles',
+ 'layerView.howToShowTiles', 'none',
+ data);
+ this.controls_.replaceChild(newSelector, this.tileRectsSelector_);
+ this.tileRectsSelector_ = newSelector;
+ },
+
+ computePictureLoadingStatus_() {
+ // Figure out if we can draw the quads yet. While we're at it, figure out
+ // if we have any warnings we need to show.
+ const layers = this.layers;
+ const status = {
+ messages: [],
+ picturesComplete: true
+ };
+ if (this.showContents) {
+ let hasPendingRasterizeImage = false;
+ let firstPictureError = undefined;
+ let hasMissingLayerRect = false;
+ let hasUnresolvedPictureRef = false;
+ for (let i = 0; i < layers.length; i++) {
+ const layer = layers[i];
+ for (let ir = 0; ir < layer.pictures.length; ++ir) {
+ const picture = layer.pictures[ir];
+
+ if (picture.idRef) {
+ hasUnresolvedPictureRef = true;
+ continue;
+ }
+ if (!picture.layerRect) {
+ hasMissingLayerRect = true;
+ continue;
+ }
+
+ const pictureAsImageData = this.pictureAsImageData_[picture.guid];
+ if (!pictureAsImageData) {
+ hasPendingRasterizeImage = true;
+ this.pictureAsImageData_[picture.guid] =
+ tr.e.cc.PictureAsImageData.Pending(this);
+ picture.rasterize(
+ {stopIndex: undefined},
+ function(pictureImageData) {
+ const picture_ = pictureImageData.picture;
+ this.pictureAsImageData_[picture_.guid] = pictureImageData;
+ this.scheduleUpdateContents_();
+ }.bind(this));
+ continue;
+ }
+ if (pictureAsImageData.isPending()) {
+ hasPendingRasterizeImage = true;
+ continue;
+ }
+ if (pictureAsImageData.error) {
+ if (!firstPictureError) {
+ firstPictureError = pictureAsImageData.error;
+ }
+ break;
+ }
+ }
+ }
+ if (hasPendingRasterizeImage) {
+ status.picturesComplete = false;
+ } else {
+ if (hasUnresolvedPictureRef) {
+ status.messages.push({
+ header: 'Missing picture',
+ details: 'Your trace didn\'t have pictures for every layer. ' +
+ 'Old chrome versions had this problem'});
+ }
+ if (hasMissingLayerRect) {
+ status.messages.push({
+ header: 'Missing layer rect',
+ details: 'Your trace may be corrupt or from a very old ' +
+ 'Chrome revision.'});
+ }
+ if (firstPictureError) {
+ status.messages.push({
+ header: 'Cannot rasterize',
+ details: firstPictureError});
+ }
+ }
+ }
+ if (this.showInputEvents && this.layerTreeImpl.tracedInputLatencies &&
+ this.inputEventImageData_ === undefined) {
+ const image = Polymer.dom(this).querySelector('#input-event');
+ if (!image.src) {
+ this.loadDataForImageElement_(image, function(imageData) {
+ this.inputEventImageData_ = imageData;
+ this.updateContentsPending_ = false;
+ this.scheduleUpdateContents_();
+ }.bind(this));
+ }
+ status.picturesComplete = false;
+ }
+ return status;
+ },
+
+ get selectedRenderPass() {
+ if (this.selection) {
+ return this.selection.renderPass_;
+ }
+ },
+
+ get selectedLayer() {
+ if (this.selection) {
+ const selectedLayerId = this.selection.associatedLayerId;
+ return this.layerTreeImpl_.findLayerWithId(selectedLayerId);
+ }
+ },
+
+ get renderPasses() {
+ let renderPasses =
+ this.layerTreeImpl.layerTreeHostImpl.args.frame.renderPasses;
+ if (!this.showOtherLayers) {
+ const selectedRenderPass = this.selectedRenderPass;
+ if (selectedRenderPass) {
+ renderPasses = [selectedRenderPass];
+ }
+ }
+ return renderPasses;
+ },
+
+ get layers() {
+ let layers = this.layerTreeImpl.renderSurfaceLayerList;
+ if (!this.showOtherLayers) {
+ const selectedLayer = this.selectedLayer;
+ if (selectedLayer) {
+ layers = [selectedLayer];
+ }
+ }
+ return layers;
+ },
+
+ appendImageQuads_(quads, layer, layerQuad) {
+ // Generate image quads for the layer
+ for (let ir = 0; ir < layer.pictures.length; ++ir) {
+ const picture = layer.pictures[ir];
+ if (!picture.layerRect) continue;
+
+ const unitRect = picture.layerRect.asUVRectInside(layer.bounds);
+ const iq = layerQuad.projectUnitRect(unitRect);
+
+ const pictureData = this.pictureAsImageData_[picture.guid];
+ if (this.showContents && pictureData && pictureData.imageData) {
+ iq.imageData = pictureData.imageData;
+ iq.borderColor = 'rgba(0,0,0,0)';
+ } else {
+ iq.imageData = undefined;
+ }
+
+ iq.stackingGroupId = layerQuad.stackingGroupId;
+ quads.push(iq);
+ }
+ },
+
+ appendAnimationQuads_(quads, layer, layerQuad) {
+ if (!layer.animationBoundsRect) return;
+
+ const rect = layer.animationBoundsRect;
+ const abq = tr.b.math.Quad.fromRect(rect);
+
+ abq.backgroundColor = 'rgba(164,191,48,0.5)';
+ abq.borderColor = 'rgba(205,255,0,0.75)';
+ abq.borderWidth = 3.0;
+ abq.stackingGroupId = layerQuad.stackingGroupId;
+ abq.selectionToSetIfClicked = new cc.AnimationRectSelection(
+ layer, rect);
+ quads.push(abq);
+ },
+
+ appendInvalidationQuads_(quads, layer, layerQuad) {
+ if (layer.layerTreeImpl.hasSourceFrameBeenDrawnBefore) return;
+
+ // Generate the invalidation rect quads.
+ for (const rect of layer.invalidation.rects) {
+ const unitRect = rect.asUVRectInside(layer.bounds);
+ const iq = layerQuad.projectUnitRect(unitRect);
+ iq.backgroundColor = 'rgba(0, 255, 0, 0.1)';
+ if (rect.reason === 'appeared') {
+ iq.backgroundColor = 'rgba(0, 255, 128, 0.1)';
+ }
+ iq.borderColor = 'rgba(0, 255, 0, 1)';
+ iq.stackingGroupId = layerQuad.stackingGroupId;
+
+ let message = 'Invalidation rect';
+ if (rect.reason) {
+ message += ' (' + rect.reason + ')';
+ }
+ if (rect.client) {
+ message += ' for ' + rect.client;
+ }
+
+ iq.selectionToSetIfClicked = new cc.LayerRectSelection(
+ layer, message, rect, rect);
+ quads.push(iq);
+ }
+ },
+
+ appendUnrecordedRegionQuads_(quads, layer, layerQuad) {
+ // Generate the unrecorded region quads.
+ for (let ir = 0; ir < layer.unrecordedRegion.rects.length; ir++) {
+ const rect = layer.unrecordedRegion.rects[ir];
+ const unitRect = rect.asUVRectInside(layer.bounds);
+ const iq = layerQuad.projectUnitRect(unitRect);
+ iq.backgroundColor = 'rgba(240, 230, 140, 0.3)';
+ iq.borderColor = 'rgba(240, 230, 140, 1)';
+ iq.stackingGroupId = layerQuad.stackingGroupId;
+ iq.selectionToSetIfClicked = new cc.LayerRectSelection(
+ layer, 'Unrecorded area', rect, rect);
+ quads.push(iq);
+ }
+ },
+
+ appendBottleneckQuads_(quads, layer, layerQuad, stackingGroupId) {
+ function processRegion(region, label, borderColor) {
+ const backgroundColor = borderColor.clone();
+ backgroundColor.a = 0.4 * (borderColor.a || 1.0);
+
+ if (!region || !region.rects) return;
+
+ for (let ir = 0; ir < region.rects.length; ir++) {
+ const rect = region.rects[ir];
+ const unitRect = rect.asUVRectInside(layer.bounds);
+ const iq = layerQuad.projectUnitRect(unitRect);
+ iq.backgroundColor = backgroundColor.toString();
+ iq.borderColor = borderColor.toString();
+ iq.borderWidth = 4.0;
+ iq.stackingGroupId = stackingGroupId;
+ iq.selectionToSetIfClicked = new cc.LayerRectSelection(
+ layer, label, rect, rect);
+ quads.push(iq);
+ }
+ }
+
+ processRegion(layer.touchEventHandlerRegion, 'Touch listener',
+ tr.b.Color.fromString('rgb(228, 226, 27)'));
+ processRegion(layer.wheelEventHandlerRegion, 'Wheel listener',
+ tr.b.Color.fromString('rgb(176, 205, 29)'));
+ processRegion(layer.nonFastScrollableRegion, 'Repaints on scroll',
+ tr.b.Color.fromString('rgb(213, 134, 32)'));
+ },
+
+ appendTileCoverageRectQuads_(
+ quads, layer, layerQuad, heatmapType) {
+ if (!layer.tileCoverageRects) return;
+
+ const tiles = [];
+ for (let ct = 0; ct < layer.tileCoverageRects.length; ++ct) {
+ const tile = layer.tileCoverageRects[ct].tile;
+ if (tile !== undefined) tiles.push(tile);
+ }
+
+ const lthi = this.layerTreeImpl_.layerTreeHostImpl;
+ const minMax =
+ this.getMinMaxForHeatmap_(lthi.activeTiles, heatmapType);
+ const heatmapResult =
+ this.computeHeatmapColors_(tiles, minMax, heatmapType);
+ let heatIndex = 0;
+
+ for (let ct = 0; ct < layer.tileCoverageRects.length; ++ct) {
+ let rect = layer.tileCoverageRects[ct].geometryRect;
+ rect = rect.scale(1.0 / layer.geometryContentsScale);
+
+ const tile = layer.tileCoverageRects[ct].tile;
+
+ const unitRect = rect.asUVRectInside(layer.bounds);
+ const quad = layerQuad.projectUnitRect(unitRect);
+
+ quad.backgroundColor = 'rgba(0, 0, 0, 0)';
+ quad.stackingGroupId = layerQuad.stackingGroupId;
+ let type = tr.e.cc.tileTypes.missing;
+ if (tile) {
+ type = tile.getTypeForLayer(layer);
+ quad.backgroundColor = heatmapResult[heatIndex].color;
+ ++heatIndex;
+ }
+
+ quad.borderColor = tr.e.cc.tileBorder[type].color;
+ quad.borderWidth = tr.e.cc.tileBorder[type].width;
+ let label;
+ if (tile) {
+ label = 'coverageRect';
+ } else {
+ label = 'checkerboard coverageRect';
+ }
+ quad.selectionToSetIfClicked = new cc.LayerRectSelection(
+ layer, label, rect, layer.tileCoverageRects[ct]);
+
+ quads.push(quad);
+ }
+ },
+
+ appendLayoutRectQuads_(quads, layer, layerQuad) {
+ if (!layer.layoutRects) {
+ return;
+ }
+
+ for (let ct = 0; ct < layer.layoutRects.length; ++ct) {
+ let rect = layer.layoutRects[ct].geometryRect;
+ rect = rect.scale(1.0 / layer.geometryContentsScale);
+
+ const unitRect = rect.asUVRectInside(layer.bounds);
+ const quad = layerQuad.projectUnitRect(unitRect);
+
+ quad.backgroundColor = 'rgba(0, 0, 0, 0)';
+ quad.stackingGroupId = layerQuad.stackingGroupId;
+
+ quad.borderColor = 'rgba(0, 0, 200, 0.7)';
+ quad.borderWidth = 2;
+ const label = 'Layout rect';
+ quad.selectionToSetIfClicked = new cc.LayerRectSelection(
+ layer, label, rect);
+
+ quads.push(quad);
+ }
+ },
+
+ getValueForHeatmap_(tile, heatmapType) {
+ if (heatmapType === TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY) {
+ return tile.scheduledPriority === 0 ?
+ undefined :
+ tile.scheduledPriority;
+ } else if (heatmapType === TILE_HEATMAP_TYPE.USING_GPU_MEMORY) {
+ if (tile.isSolidColor) return 0.5;
+ return tile.isUsingGpuMemory ? 0 : 1;
+ }
+ },
+
+ getMinMaxForHeatmap_(tiles, heatmapType) {
+ const range = new tr.b.math.Range();
+ if (heatmapType === TILE_HEATMAP_TYPE.USING_GPU_MEMORY) {
+ range.addValue(0);
+ range.addValue(1);
+ return range;
+ }
+
+ for (let i = 0; i < tiles.length; ++i) {
+ const value = this.getValueForHeatmap_(tiles[i], heatmapType);
+ if (value === undefined) continue;
+ range.addValue(value);
+ }
+ if (range.range === 0) {
+ range.addValue(1);
+ }
+ return range;
+ },
+
+ computeHeatmapColors_(tiles, minMax, heatmapType) {
+ const min = minMax.min;
+ const max = minMax.max;
+
+ const color = function(value) {
+ let hue = 120 * (1 - (value - min) / (max - min));
+ if (hue < 0) hue = 0;
+ return 'hsla(' + hue + ', 100%, 50%, 0.5)';
+ };
+
+ const values = [];
+ for (let i = 0; i < tiles.length; ++i) {
+ const tile = tiles[i];
+ const value = this.getValueForHeatmap_(tile, heatmapType);
+ const res = {
+ value,
+ color: value !== undefined ? color(value) : undefined
+ };
+ values.push(res);
+ }
+
+ return values;
+ },
+
+ appendTilesWithScaleQuads_(
+ quads, layer, layerQuad, scale, heatmapType) {
+ const lthi = this.layerTreeImpl_.layerTreeHostImpl;
+
+ const tiles = [];
+ for (let i = 0; i < lthi.activeTiles.length; ++i) {
+ const tile = lthi.activeTiles[i];
+
+ if (Math.abs(tile.contentsScale - scale) > 1e-6) {
+ continue;
+ }
+
+ // TODO(vmpstr): Make the stiching of tiles and layers a part of
+ // tile construction (issue 346)
+ if (layer.layerId !== tile.layerId) continue;
+
+ tiles.push(tile);
+ }
+
+ const minMax =
+ this.getMinMaxForHeatmap_(lthi.activeTiles, heatmapType);
+ const heatmapResult =
+ this.computeHeatmapColors_(tiles, minMax, heatmapType);
+
+ for (let i = 0; i < tiles.length; ++i) {
+ const tile = tiles[i];
+ const rect = tile.layerRect;
+ if (!tile.layerRect) continue;
+
+ const unitRect = rect.asUVRectInside(layer.bounds);
+ const quad = layerQuad.projectUnitRect(unitRect);
+
+ quad.backgroundColor = 'rgba(0, 0, 0, 0)';
+ quad.stackingGroupId = layerQuad.stackingGroupId;
+
+ const type = tile.getTypeForLayer(layer);
+ quad.borderColor = tr.e.cc.tileBorder[type].color;
+ quad.borderWidth = tr.e.cc.tileBorder[type].width;
+
+ quad.backgroundColor = heatmapResult[i].color;
+ const data = {
+ tileType: type
+ };
+ if (heatmapType !== TILE_HEATMAP_TYPE.NONE) {
+ data[heatmapType] = heatmapResult[i].value;
+ }
+ quad.selectionToSetIfClicked = new cc.TileSelection(tile, data);
+ quads.push(quad);
+ }
+ },
+
+ appendHighlightQuadsForLayer_(
+ quads, layer, layerQuad, highlights) {
+ highlights.forEach(function(highlight) {
+ const rect = highlight.rect;
+
+ const unitRect = rect.asUVRectInside(layer.bounds);
+ const quad = layerQuad.projectUnitRect(unitRect);
+
+ let colorId = ColorScheme.getColorIdForGeneralPurposeString(
+ highlight.colorKey);
+ const offset = ColorScheme.properties.brightenedOffsets[0];
+ colorId = ColorScheme.getVariantColorId(colorId, offset);
+
+ const color = ColorScheme.colors[colorId];
+
+ const quadForDrawing = quad.clone();
+ quadForDrawing.backgroundColor = color.withAlpha(0.5).toString();
+ quadForDrawing.borderColor = color.withAlpha(1.0).darken().toString();
+ quadForDrawing.stackingGroupId = layerQuad.stackingGroupId;
+ quads.push(quadForDrawing);
+ }, this);
+ },
+
+ generateRenderPassQuads() {
+ if (!this.layerTreeImpl.layerTreeHostImpl.args.frame) return [];
+ const renderPasses = this.renderPasses;
+ if (!renderPasses) return [];
+
+ const quads = [];
+ for (let i = 0; i < renderPasses.length; ++i) {
+ const quadList = renderPasses[i].quadList;
+ for (let j = 0; j < quadList.length; ++j) {
+ const drawQuad = quadList[j];
+ const quad = drawQuad.rectAsTargetSpaceQuad.clone();
+ quad.borderColor = 'rgb(170, 204, 238)';
+ quad.borderWidth = 2;
+ quad.stackingGroupId = i;
+ quads.push(quad);
+ }
+ }
+ return quads;
+ },
+
+ generateLayerQuads() {
+ this.updateContentsPending_ = false;
+
+ // Generate the quads for the view.
+ const layers = this.layers;
+ const quads = [];
+ let nextStackingGroupId = 0;
+ const alreadyVisitedLayerIds = {};
+
+
+ let selectionHighlightsByLayerId;
+ if (this.selection) {
+ selectionHighlightsByLayerId = this.selection.highlightsByLayerId;
+ } else {
+ selectionHighlightsByLayerId = {};
+ }
+
+ const extraHighlightsByLayerId = this.extraHighlightsByLayerId || {};
+
+ for (let i = 1; i <= layers.length; i++) {
+ // Generate quads back-to-front.
+ const layer = layers[layers.length - i];
+ alreadyVisitedLayerIds[layer.layerId] = true;
+ if (layer.objectInstance.name === 'cc::NinePatchLayerImpl') {
+ continue;
+ }
+
+ const layerQuad = layer.layerQuad.clone();
+ if (layer.usingGpuRasterization) {
+ const pixelRatio = window.devicePixelRatio || 1;
+ layerQuad.borderWidth = 2.0 * pixelRatio;
+ layerQuad.borderColor = 'rgba(154,205,50,0.75)';
+ } else {
+ layerQuad.borderColor = 'rgba(0,0,0,0.75)';
+ }
+ layerQuad.stackingGroupId = nextStackingGroupId++;
+ layerQuad.selectionToSetIfClicked = new cc.LayerSelection(layer);
+ layerQuad.layer = layer;
+ if (this.showOtherLayers && this.selectedLayer === layer) {
+ layerQuad.upperBorderColor = 'rgb(156,189,45)';
+ }
+
+ if (this.showAnimationBounds) {
+ this.appendAnimationQuads_(quads, layer, layerQuad);
+ }
+
+ this.appendImageQuads_(quads, layer, layerQuad);
+ quads.push(layerQuad);
+
+
+ if (this.showInvalidations) {
+ this.appendInvalidationQuads_(quads, layer, layerQuad);
+ }
+ if (this.showUnrecordedRegion) {
+ this.appendUnrecordedRegionQuads_(quads, layer, layerQuad);
+ }
+ if (this.showBottlenecks) {
+ this.appendBottleneckQuads_(quads, layer, layerQuad,
+ layerQuad.stackingGroupId);
+ }
+ if (this.showLayoutRects) {
+ this.appendLayoutRectQuads_(quads, layer, layerQuad);
+ }
+
+ if (this.howToShowTiles === 'coverage') {
+ this.appendTileCoverageRectQuads_(
+ quads, layer, layerQuad, this.tileHeatmapType);
+ } else if (this.howToShowTiles !== 'none') {
+ this.appendTilesWithScaleQuads_(
+ quads, layer, layerQuad,
+ this.howToShowTiles, this.tileHeatmapType);
+ }
+
+ let highlights;
+ highlights = extraHighlightsByLayerId[layer.layerId];
+ if (highlights) {
+ this.appendHighlightQuadsForLayer_(
+ quads, layer, layerQuad, highlights);
+ }
+
+ highlights = selectionHighlightsByLayerId[layer.layerId];
+ if (highlights) {
+ this.appendHighlightQuadsForLayer_(
+ quads, layer, layerQuad, highlights);
+ }
+ }
+
+ this.layerTreeImpl.iterLayers(function(layer, depth, isMask, isReplica) {
+ if (!this.showOtherLayers && this.selectedLayer !== layer) return;
+ if (alreadyVisitedLayerIds[layer.layerId]) return;
+
+ const layerQuad = layer.layerQuad;
+ const stackingGroupId = nextStackingGroupId++;
+ if (this.showBottlenecks) {
+ this.appendBottleneckQuads_(quads, layer, layerQuad, stackingGroupId);
+ }
+ }, this);
+
+ const tracedInputLatencies = this.layerTreeImpl.tracedInputLatencies;
+ if (this.showInputEvents && tracedInputLatencies) {
+ for (let i = 0; i < tracedInputLatencies.length; i++) {
+ const coordinatesArray =
+ tracedInputLatencies[i].args.data.coordinates;
+ for (let j = 0; j < coordinatesArray.length; j++) {
+ const inputQuad = tr.b.math.Quad.fromXYWH(
+ coordinatesArray[j].x - 25,
+ coordinatesArray[j].y - 25,
+ 50,
+ 50);
+ inputQuad.borderColor = 'rgba(0, 0, 0, 0)';
+ inputQuad.imageData = this.inputEventImageData_;
+ quads.push(inputQuad);
+ }
+ }
+ }
+
+ return quads;
+ },
+
+ updateInfoBar_(infoBarMessages) {
+ if (infoBarMessages.length) {
+ this.infoBar_.removeAllButtons();
+ this.infoBar_.message = 'Some problems were encountered...';
+ this.infoBar_.addButton('More info...', function(e) {
+ const overlay = new tr.ui.b.Overlay();
+ Polymer.dom(overlay).textContent = '';
+ infoBarMessages.forEach(function(message) {
+ const title = document.createElement('h3');
+ Polymer.dom(title).textContent = message.header;
+
+ const details = document.createElement('div');
+ Polymer.dom(details).textContent = message.details;
+
+ Polymer.dom(overlay).appendChild(title);
+ Polymer.dom(overlay).appendChild(details);
+ });
+ overlay.visible = true;
+
+ e.stopPropagation();
+ return false;
+ });
+ this.infoBar_.visible = true;
+ } else {
+ this.infoBar_.removeAllButtons();
+ this.infoBar_.message = '';
+ this.infoBar_.visible = false;
+ }
+ },
+
+ getWhatRasterized_() {
+ const lthi = this.layerTreeImpl_.layerTreeHostImpl;
+ const renderProcess = lthi.objectInstance.parent;
+ const tasks = [];
+ for (const event of renderProcess.getDescendantEvents()) {
+ if (!(event instanceof tr.model.Slice)) continue;
+
+ const tile = tr.e.cc.getTileFromRasterTaskSlice(event);
+ if (tile === undefined) continue;
+
+ if (tile.containingSnapshot === lthi) {
+ tasks.push(event);
+ }
+ }
+ return tasks;
+ },
+
+ updateWhatRasterizedLinkState_() {
+ const tasks = this.getWhatRasterized_();
+ if (tasks.length) {
+ Polymer.dom(this.whatRasterizedLink_).textContent =
+ tasks.length + ' raster tasks';
+ this.whatRasterizedLink_.style.display = '';
+ } else {
+ Polymer.dom(this.whatRasterizedLink_).textContent = '';
+ this.whatRasterizedLink_.style.display = 'none';
+ }
+ },
+
+ getWhatRasterizedEventSet_() {
+ return new tr.model.EventSet(this.getWhatRasterized_());
+ }
+ };
+
+ return {
+ LayerTreeQuadStackView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view_test.html
new file mode 100644
index 00000000000..66932ae785f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view_test.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/cc.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view.html">
+
+<script src="/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js">
+</script>
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('tileCoverageRectCount', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = m.processes[1];
+
+ const instance = p.objects.getAllInstancesNamed('cc::LayerTreeHostImpl')[0];
+ const lthi = instance.snapshots[0];
+
+ const view = new tr.ui.e.chrome.cc.LayerTreeQuadStackView();
+ view.layerTreeImpl = lthi.activeTree;
+ view.howToShowTiles = 'none';
+ view.showInvalidations = false;
+ view.showContents = false;
+
+ // There should be some quads drawn with all "show" checkboxes off,
+ // but that number can change with new features added.
+ const aQuads = view.generateLayerQuads();
+ view.howToShowTiles = 'coverage';
+ const bQuads = view.generateLayerQuads();
+ const numCoverageRects = bQuads.length - aQuads.length;
+
+ // We know we have 5 coverage rects in lthi cats.
+ assert.strictEqual(numCoverageRects, 5);
+ });
+
+ test('inputEvent', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = m.processes[1];
+
+ const instance = p.objects.getAllInstancesNamed('cc::LayerTreeHostImpl')[0];
+ const lthi = instance.snapshots[0];
+ lthi.activeTree.tracedInputLatencies =
+ [{args: {data: {coordinates: [{x: 10, y: 20}, {x: 30, y: 40}]}}}];
+
+ const view = new tr.ui.e.chrome.cc.LayerTreeQuadStackView();
+ view.layerTreeImpl = lthi.activeTree;
+ view.showInputEvents = false;
+
+ const aQuads = view.generateLayerQuads();
+ view.showInputEvents = true;
+ const bQuads = view.generateLayerQuads();
+ const numInputEventRects = bQuads.length - aQuads.length;
+
+ assert.strictEqual(numInputEventRects, 2);
+
+ // We should not start loading the image until the view is added into the
+ // DOM tree.
+ const image = Polymer.dom(view).querySelector('#input-event');
+ assert.strictEqual(getComputedStyle(image).backgroundImage, '');
+ assert.strictEqual(image.src, '');
+
+ document.body.appendChild(view);
+ view.updateContents_();
+ assert.notEqual(getComputedStyle(image).backgroundImage, '');
+ assert.notEqual(image.src, '');
+ view.remove();
+ });
+
+ test('invalidation', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = m.processes[1];
+
+ const instance = p.objects.getAllInstancesNamed('cc::LayerTreeHostImpl')[0];
+ const lthi = instance.snapshots[0];
+
+ const view = new tr.ui.e.chrome.cc.LayerTreeQuadStackView();
+ view.layerTreeImpl = lthi.activeTree;
+ view.showInvalidations = false;
+
+ const aQuads = view.generateLayerQuads();
+ view.showInvalidations = true;
+ const bQuads = view.generateLayerQuads();
+ const numInvalidationRects = bQuads.length - aQuads.length;
+
+ // We know we have 3 invalidation rects.
+ assert.strictEqual(numInvalidationRects, 3);
+
+ const expectedRectTypes = [
+ 'Invalidation rect (appeared) for client1',
+ 'Invalidation rect (disappeared) for client2',
+ 'Invalidation rect' // The non-annotated rect.
+ ];
+ const found = [];
+ for (const quad of bQuads) {
+ const i = expectedRectTypes.indexOf(quad.selectionToSetIfClicked &&
+ quad.selectionToSetIfClicked.rectType_);
+ if (i !== -1) {
+ found[i] = true;
+ }
+ }
+ assert.deepEqual(found, [true, true, true]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view.html
new file mode 100644
index 00000000000..56ecf770ec1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/raf.html">
+<link rel="import" href="/tracing/base/settings.html">
+<link rel="import" href="/tracing/extras/chrome/cc/constants.html">
+<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/ui/base/drag_handle.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/layer_tree_quad_stack_view.html">
+
+<script>
+'use strict';
+
+/**
+ * @fileoverview LayerView coordinates graphical and analysis views of layers.
+ */
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ const constants = tr.e.cc.constants;
+
+ /**
+ * @constructor
+ */
+ const LayerView = tr.ui.b.define('tr-ui-e-chrome-cc-layer-view');
+
+ LayerView.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ decorate() {
+ this.style.flexDirection = 'column';
+ this.style.display = 'flex';
+
+ this.layerTreeQuadStackView_ =
+ new tr.ui.e.chrome.cc.LayerTreeQuadStackView();
+ this.dragBar_ = document.createElement('tr-ui-b-drag-handle');
+ this.analysisEl_ =
+ document.createElement('tr-ui-e-chrome-cc-layer-view-analysis');
+ this.analysisEl_.style.flexGrow = 0;
+ this.analysisEl_.style.flexShrink = 0;
+ this.analysisEl_.style.flexBasis = 'auto';
+ this.analysisEl_.style.height = '150px';
+ this.analysisEl_.style.overflow = 'auto';
+ this.analysisEl_.addEventListener('requestSelectionChange',
+ this.onRequestSelectionChangeFromAnalysisEl_.bind(this));
+
+ this.dragBar_.target = this.analysisEl_;
+
+ Polymer.dom(this).appendChild(this.layerTreeQuadStackView_);
+ Polymer.dom(this).appendChild(this.dragBar_);
+ Polymer.dom(this).appendChild(this.analysisEl_);
+
+ this.layerTreeQuadStackView_.addEventListener('selection-change',
+ function() {
+ this.layerTreeQuadStackViewSelectionChanged_();
+ }.bind(this));
+ this.layerTreeQuadStackViewSelectionChanged_();
+ },
+
+ get layerTreeImpl() {
+ return this.layerTreeQuadStackView_.layerTreeImpl;
+ },
+
+ set layerTreeImpl(newValue) {
+ return this.layerTreeQuadStackView_.layerTreeImpl = newValue;
+ },
+
+ set isRenderPassQuads(newValue) {
+ return this.layerTreeQuadStackView_.isRenderPassQuads = newValue;
+ },
+
+ get selection() {
+ return this.layerTreeQuadStackView_.selection;
+ },
+
+ set selection(newValue) {
+ this.layerTreeQuadStackView_.selection = newValue;
+ },
+
+ regenerateContent() {
+ this.layerTreeQuadStackView_.regenerateContent();
+ },
+
+ layerTreeQuadStackViewSelectionChanged_() {
+ const selection = this.layerTreeQuadStackView_.selection;
+ if (selection) {
+ this.dragBar_.style.display = '';
+ this.analysisEl_.style.display = '';
+ Polymer.dom(this.analysisEl_).textContent = '';
+
+ const layer = selection.layer;
+ if (tr.e.cc.PictureSnapshot.CanDebugPicture() &&
+ layer &&
+ layer.args &&
+ layer.args.pictures &&
+ layer.args.pictures.length) {
+ Polymer.dom(this.analysisEl_).appendChild(
+ this.createPictureBtn_(layer.args.pictures));
+ }
+
+ const analysis = selection.createAnalysis();
+ Polymer.dom(this.analysisEl_).appendChild(analysis);
+ for (const child of this.analysisEl_.children) {
+ child.style.userSelect = 'text';
+ }
+ } else {
+ this.dragBar_.style.display = 'none';
+ this.analysisEl_.style.display = 'none';
+ const analysis = Polymer.dom(this.analysisEl_).firstChild;
+ if (analysis) {
+ Polymer.dom(this.analysisEl_).removeChild(analysis);
+ }
+ this.layerTreeQuadStackView_.style.height =
+ window.getComputedStyle(this).height;
+ }
+ tr.b.dispatchSimpleEvent(this, 'selection-change');
+ },
+
+ createPictureBtn_(pictures) {
+ if (!(pictures instanceof Array)) {
+ pictures = [pictures];
+ }
+
+ const link = document.createElement('tr-ui-a-analysis-link');
+ link.selection = function() {
+ const layeredPicture = new tr.e.cc.LayeredPicture(pictures);
+ const snapshot = new tr.e.cc.PictureSnapshot(layeredPicture);
+ snapshot.picture = layeredPicture;
+
+ const selection = new tr.model.EventSet();
+ selection.push(snapshot);
+ return selection;
+ };
+ Polymer.dom(link).textContent = 'View in Picture Debugger';
+ return link;
+ },
+
+ onRequestSelectionChangeFromAnalysisEl_(e) {
+ if (!(e.selection instanceof tr.ui.e.chrome.cc.Selection)) {
+ return;
+ }
+
+ e.stopPropagation();
+ this.selection = e.selection;
+ },
+
+ get extraHighlightsByLayerId() {
+ return this.layerTreeQuadStackView_.extraHighlightsByLayerId;
+ },
+
+ set extraHighlightsByLayerId(extraHighlightsByLayerId) {
+ this.layerTreeQuadStackView_.extraHighlightsByLayerId =
+ extraHighlightsByLayerId;
+ }
+ };
+
+ return {
+ LayerView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view_test.html
new file mode 100644
index 00000000000..ed3de7b87e1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/layer_view_test.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/cc.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/layer_view.html">
+
+<script src="/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js">
+</script>
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('instantiate', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = m.processes[1];
+
+ const instance = p.objects.getAllInstancesNamed('cc::LayerTreeHostImpl')[0];
+ const lthi = instance.snapshots[0];
+ const numLayers = lthi.activeTree.renderSurfaceLayerList.length;
+ const layer = lthi.activeTree.renderSurfaceLayerList[numLayers - 1];
+
+ const view = new tr.ui.e.chrome.cc.LayerView();
+ view.style.height = '500px';
+ view.layerTreeImpl = lthi.activeTree;
+ view.selection = new tr.ui.e.chrome.cc.LayerSelection(layer);
+
+ this.addHTMLOutput(view);
+ });
+
+ test('instantiate_withTileHighlight', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = m.processes[1];
+
+ const instance = p.objects.getAllInstancesNamed('cc::LayerTreeHostImpl')[0];
+ const lthi = instance.snapshots[0];
+ const numLayers = lthi.activeTree.renderSurfaceLayerList.length;
+ const layer = lthi.activeTree.renderSurfaceLayerList[numLayers - 1];
+ const tile = lthi.activeTiles[0];
+
+ const view = new tr.ui.e.chrome.cc.LayerView();
+ view.style.height = '500px';
+ view.layerTreeImpl = lthi.activeTree;
+ view.selection = new tr.ui.e.chrome.cc.TileSelection(tile);
+ this.addHTMLOutput(view);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_debugger.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_debugger.html
new file mode 100644
index 00000000000..5dc62b1b4f6
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_debugger.html
@@ -0,0 +1,455 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/base64.html">
+<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
+<link rel="import" href="/tracing/ui/analysis/generic_object_view.html">
+<link rel="import" href="/tracing/ui/base/drag_handle.html">
+<link rel="import" href="/tracing/ui/base/hotkey_controller.html">
+<link rel="import" href="/tracing/ui/base/info_bar.html">
+<link rel="import" href="/tracing/ui/base/list_view.html">
+<link rel="import" href="/tracing/ui/base/mouse_mode_selector.html">
+<link rel="import" href="/tracing/ui/base/overlay.html">
+<link rel="import" href="/tracing/ui/base/utils.html">
+<link rel="import"
+ href="/tracing/ui/extras/chrome/cc/picture_ops_chart_summary_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/picture_ops_chart_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/picture_ops_list_view.html">
+
+<template id="tr-ui-e-chrome-cc-picture-debugger-template">
+ <left-panel>
+ <picture-info>
+ <div>
+ <span class='title'>Skia Picture</span>
+ <span class='size'></span>
+ </div>
+ <div>
+ <input class='filename' type='text' value='skpicture.skp' />
+ <button class='export'>Export</button>
+ </div>
+ </picture-info>
+ </left-panel>
+ <right-panel>
+ <tr-ui-e-chrome-cc-picture-ops-chart-view>
+ </tr-ui-e-chrome-cc-picture-ops-chart-view>
+ <raster-area><canvas></canvas></raster-area>
+ </right-panel>
+</template>
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ const THIS_DOC = document.currentScript.ownerDocument;
+
+ /**
+ * PictureDebugger is a view of a PictureSnapshot for inspecting
+ * the picture in detail. (e.g., timing information, etc.)
+ *
+ * @constructor
+ */
+ const PictureDebugger = tr.ui.b.define('tr-ui-e-chrome-cc-picture-debugger');
+
+ PictureDebugger.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ decorate() {
+ const node = tr.ui.b.instantiateTemplate(
+ '#tr-ui-e-chrome-cc-picture-debugger-template', THIS_DOC);
+
+ Polymer.dom(this).appendChild(node);
+
+ this.style.display = 'flex';
+ this.style.flexDirection = 'row';
+
+ const title = this.querySelector('.title');
+ title.style.fontWeight = 'bold';
+ title.style.marginLeft = '5px';
+ title.style.marginRight = '5px';
+
+ this.pictureAsImageData_ = undefined;
+ this.showOverdraw_ = false;
+ this.zoomScaleValue_ = 1;
+
+ this.sizeInfo_ = Polymer.dom(this).querySelector('.size');
+ this.rasterArea_ = Polymer.dom(this).querySelector('raster-area');
+ this.rasterArea_.style.backgroundColor = '#ddd';
+ this.rasterArea_.style.minHeight = '100px';
+ this.rasterArea_.style.minWidth = '200px';
+ this.rasterArea_.style.overflow = 'auto';
+ this.rasterArea_.style.paddingLeft = '5px';
+ this.rasterCanvas_ = Polymer.dom(this.rasterArea_)
+ .querySelector('canvas');
+ this.rasterCtx_ = this.rasterCanvas_.getContext('2d');
+
+ this.filename_ = Polymer.dom(this).querySelector('.filename');
+ this.filename_.style.userSelect = 'text';
+ this.filename_.style.marginLeft = '5px';
+
+ this.drawOpsChartSummaryView_ =
+ new tr.ui.e.chrome.cc.PictureOpsChartSummaryView();
+ this.drawOpsChartView_ = new tr.ui.e.chrome.cc.PictureOpsChartView();
+ this.drawOpsChartView_.addEventListener(
+ 'selection-changed', this.onChartBarClicked_.bind(this));
+
+ this.exportButton_ = Polymer.dom(this).querySelector('.export');
+ this.exportButton_.addEventListener(
+ 'click', this.onSaveAsSkPictureClicked_.bind(this));
+
+ this.trackMouse_();
+
+ const overdrawCheckbox = tr.ui.b.createCheckBox(
+ this, 'showOverdraw',
+ 'pictureView.showOverdraw', false,
+ 'Show overdraw');
+
+ const chartCheckbox = tr.ui.b.createCheckBox(
+ this, 'showSummaryChart',
+ 'pictureView.showSummaryChart', false,
+ 'Show timing summary');
+
+ const pictureInfo = Polymer.dom(this).querySelector('picture-info');
+ pictureInfo.style.flexGrow = 0;
+ pictureInfo.style.flexShrink = 0;
+ pictureInfo.style.flexBasis = 'auto';
+ pictureInfo.style.paddingTop = '2px';
+ Polymer.dom(pictureInfo).appendChild(overdrawCheckbox);
+ Polymer.dom(pictureInfo).appendChild(chartCheckbox);
+
+ this.drawOpsView_ = new tr.ui.e.chrome.cc.PictureOpsListView();
+ this.drawOpsView_.flexGrow = 1;
+ this.drawOpsView_.flexShrink = 1;
+ this.drawOpsView_.flexBasis = 'auto';
+ this.drawOpsView_.addEventListener(
+ 'selection-changed', this.onChangeDrawOps_.bind(this));
+
+ const leftPanel = Polymer.dom(this).querySelector('left-panel');
+ leftPanel.style.flexDirection = 'column';
+ leftPanel.style.display = 'flex';
+ leftPanel.style.flexGrow = 0;
+ leftPanel.style.flexShrink = 0;
+ leftPanel.style.flexBasis = 'auto';
+ leftPanel.style.minWidth = '200px';
+ leftPanel.style.overflow = 'auto';
+ Polymer.dom(leftPanel).appendChild(this.drawOpsChartSummaryView_);
+ Polymer.dom(leftPanel).appendChild(this.drawOpsView_);
+
+ const middleDragHandle = document.createElement('tr-ui-b-drag-handle');
+ middleDragHandle.style.flexGrow = 0;
+ middleDragHandle.style.flexShrink = 0;
+ middleDragHandle.style.flexBasis = 'auto';
+ middleDragHandle.horizontal = false;
+ middleDragHandle.target = leftPanel;
+
+ const rightPanel = Polymer.dom(this).querySelector('right-panel');
+ rightPanel.style.flexGrow = 1;
+ rightPanel.style.flexShrink = 1;
+ rightPanel.style.flexBasis = 'auto';
+ rightPanel.style.minWidth = 0;
+ rightPanel.style.flexDirection = 'column';
+ rightPanel.style.display = 'flex';
+
+ const chartView = Polymer.dom(rightPanel).querySelector(
+ 'tr-ui-e-chrome-cc-picture-ops-chart-view');
+ this.drawOpsChartView_.style.flexGrow = 0;
+ this.drawOpsChartView_.style.flexShrink = 0;
+ this.drawOpsChartView_.style.flexBasis = 'auto';
+ this.drawOpsChartView_.style.minWidth = 0;
+ this.drawOpsChartView_.style.overflowX = 'auto';
+ this.drawOpsChartView_.style.overflowY = 'hidden';
+ rightPanel.replaceChild(this.drawOpsChartView_, chartView);
+
+ this.infoBar_ = document.createElement('tr-ui-b-info-bar');
+ Polymer.dom(this.rasterArea_).appendChild(this.infoBar_);
+
+ Polymer.dom(this).insertBefore(middleDragHandle, rightPanel);
+
+ this.picture_ = undefined;
+
+ const hkc = document.createElement('tv-ui-b-hotkey-controller');
+ hkc.addHotKey(new tr.ui.b.HotKey({
+ eventType: 'keypress',
+ thisArg: this,
+ keyCode: 'h'.charCodeAt(0),
+ callback(e) {
+ this.moveSelectedOpBy(-1);
+ e.stopPropagation();
+ }
+ }));
+ hkc.addHotKey(new tr.ui.b.HotKey({
+ eventType: 'keypress',
+ thisArg: this,
+ keyCode: 'l'.charCodeAt(0),
+ callback(e) {
+ this.moveSelectedOpBy(1);
+ e.stopPropagation();
+ }
+ }));
+ Polymer.dom(this).appendChild(hkc);
+ },
+
+ onSaveAsSkPictureClicked_() {
+ // Decode base64 data into a String
+ const rawData = tr.b.Base64.atob(this.picture_.getBase64SkpData());
+
+ // Convert this String into an Uint8Array
+ const length = rawData.length;
+ const arrayBuffer = new ArrayBuffer(length);
+ const uint8Array = new Uint8Array(arrayBuffer);
+ for (let c = 0; c < length; c++) {
+ uint8Array[c] = rawData.charCodeAt(c);
+ }
+
+ // Create a blob URL from the binary array.
+ const blob = new Blob([uint8Array], {type: 'application/octet-binary'});
+ const blobUrl = window.webkitURL.createObjectURL(blob);
+
+ // Create a link and click on it. BEST API EVAR!
+ const link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
+ link.href = blobUrl;
+ link.download = this.filename_.value;
+ const event = document.createEvent('MouseEvents');
+ event.initMouseEvent(
+ 'click', true, false, window, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null);
+ link.dispatchEvent(event);
+ },
+
+ get picture() {
+ return this.picture_;
+ },
+
+ set picture(picture) {
+ this.drawOpsView_.picture = picture;
+ this.drawOpsChartView_.picture = picture;
+ this.drawOpsChartSummaryView_.picture = picture;
+ this.picture_ = picture;
+
+ this.exportButton_.disabled = !this.picture_.canSave;
+
+ if (picture) {
+ const size = this.getRasterCanvasSize_();
+ this.rasterCanvas_.width = size.width;
+ this.rasterCanvas_.height = size.height;
+ }
+
+ const bounds = this.rasterArea_.getBoundingClientRect();
+ const selectorBounds = this.mouseModeSelector_.getBoundingClientRect();
+ this.mouseModeSelector_.pos = {
+ x: (bounds.right - selectorBounds.width - 10),
+ y: bounds.top
+ };
+
+ this.rasterize_();
+
+ this.scheduleUpdateContents_();
+ },
+
+ getRasterCanvasSize_() {
+ const style = window.getComputedStyle(this.rasterArea_);
+ const width =
+ Math.max(parseInt(style.width), this.picture_.layerRect.width);
+ const height =
+ Math.max(parseInt(style.height), this.picture_.layerRect.height);
+
+ return {
+ width,
+ height
+ };
+ },
+
+ scheduleUpdateContents_() {
+ if (this.updateContentsPending_) return;
+
+ this.updateContentsPending_ = true;
+ tr.b.requestAnimationFrameInThisFrameIfPossible(
+ this.updateContents_.bind(this)
+ );
+ },
+
+ updateContents_() {
+ this.updateContentsPending_ = false;
+
+ if (this.picture_) {
+ Polymer.dom(this.sizeInfo_).textContent = '(' +
+ this.picture_.layerRect.width + ' x ' +
+ this.picture_.layerRect.height + ')';
+ }
+
+ this.drawOpsChartView_.updateChartContents();
+ this.drawOpsChartView_.scrollSelectedItemIntoViewIfNecessary();
+
+ // Return if picture hasn't finished rasterizing.
+ if (!this.pictureAsImageData_) return;
+
+ this.infoBar_.visible = false;
+ this.infoBar_.removeAllButtons();
+ if (this.pictureAsImageData_.error) {
+ this.infoBar_.message = 'Cannot rasterize...';
+ this.infoBar_.addButton('More info...', function(e) {
+ const overlay = new tr.ui.b.Overlay();
+ Polymer.dom(overlay).textContent = this.pictureAsImageData_.error;
+ overlay.visible = true;
+ e.stopPropagation();
+ return false;
+ }.bind(this));
+ this.infoBar_.visible = true;
+ }
+
+ this.drawPicture_();
+ },
+
+ drawPicture_() {
+ const size = this.getRasterCanvasSize_();
+ if (size.width !== this.rasterCanvas_.width) {
+ this.rasterCanvas_.width = size.width;
+ }
+ if (size.height !== this.rasterCanvas_.height) {
+ this.rasterCanvas_.height = size.height;
+ }
+
+ this.rasterCtx_.clearRect(0, 0, size.width, size.height);
+
+ if (!this.pictureAsImageData_.imageData) return;
+
+ const imgCanvas = this.pictureAsImageData_.asCanvas();
+ const w = imgCanvas.width;
+ const h = imgCanvas.height;
+ this.rasterCtx_.drawImage(imgCanvas, 0, 0, w, h,
+ 0, 0, w * this.zoomScaleValue_,
+ h * this.zoomScaleValue_);
+ },
+
+ rasterize_() {
+ if (this.picture_) {
+ this.picture_.rasterize(
+ {
+ stopIndex: this.drawOpsView_.selectedOpIndex,
+ showOverdraw: this.showOverdraw_
+ },
+ this.onRasterComplete_.bind(this));
+ }
+ },
+
+ onRasterComplete_(pictureAsImageData) {
+ this.pictureAsImageData_ = pictureAsImageData;
+ this.scheduleUpdateContents_();
+ },
+
+ moveSelectedOpBy(increment) {
+ if (this.selectedOpIndex === undefined) {
+ this.selectedOpIndex = 0;
+ return;
+ }
+ this.selectedOpIndex = tr.b.math.clamp(
+ this.selectedOpIndex + increment,
+ 0, this.numOps);
+ },
+
+ get numOps() {
+ return this.drawOpsView_.numOps;
+ },
+
+ get selectedOpIndex() {
+ return this.drawOpsView_.selectedOpIndex;
+ },
+
+ set selectedOpIndex(index) {
+ this.drawOpsView_.selectedOpIndex = index;
+ this.drawOpsChartView_.selectedOpIndex = index;
+ },
+
+ onChartBarClicked_(e) {
+ this.drawOpsView_.selectedOpIndex =
+ this.drawOpsChartView_.selectedOpIndex;
+ },
+
+ onChangeDrawOps_(e) {
+ this.rasterize_();
+ this.scheduleUpdateContents_();
+
+ this.drawOpsChartView_.selectedOpIndex =
+ this.drawOpsView_.selectedOpIndex;
+ },
+
+ set showOverdraw(v) {
+ this.showOverdraw_ = v;
+ this.rasterize_();
+ },
+
+ set showSummaryChart(chartShouldBeVisible) {
+ if (chartShouldBeVisible) {
+ this.drawOpsChartSummaryView_.show();
+ } else {
+ this.drawOpsChartSummaryView_.hide();
+ }
+ },
+
+ trackMouse_() {
+ this.mouseModeSelector_ = document.createElement(
+ 'tr-ui-b-mouse-mode-selector');
+ this.mouseModeSelector_.targetElement = this.rasterArea_;
+ Polymer.dom(this.rasterArea_).appendChild(this.mouseModeSelector_);
+
+ this.mouseModeSelector_.supportedModeMask =
+ tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;
+ this.mouseModeSelector_.mode = tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;
+ this.mouseModeSelector_.defaultMode = tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;
+ this.mouseModeSelector_.settingsKey = 'pictureDebugger.mouseModeSelector';
+
+ this.mouseModeSelector_.addEventListener('beginzoom',
+ this.onBeginZoom_.bind(this));
+ this.mouseModeSelector_.addEventListener('updatezoom',
+ this.onUpdateZoom_.bind(this));
+ this.mouseModeSelector_.addEventListener('endzoom',
+ this.onEndZoom_.bind(this));
+ },
+
+ onBeginZoom_(e) {
+ this.isZooming_ = true;
+
+ this.lastMouseViewPos_ = this.extractRelativeMousePosition_(e);
+
+ e.preventDefault();
+ },
+
+ onUpdateZoom_(e) {
+ if (!this.isZooming_) return;
+
+ const currentMouseViewPos = this.extractRelativeMousePosition_(e);
+
+ // Take the distance the mouse has moved and we want to zoom at about
+ // 1/1000th of that speed. 0.01 feels jumpy. This could possibly be tuned
+ // more if people feel it's too slow.
+ this.zoomScaleValue_ +=
+ ((this.lastMouseViewPos_.y - currentMouseViewPos.y) * 0.001);
+ this.zoomScaleValue_ = Math.max(this.zoomScaleValue_, 0.1);
+
+ this.drawPicture_();
+
+ this.lastMouseViewPos_ = currentMouseViewPos;
+ },
+
+ onEndZoom_(e) {
+ this.lastMouseViewPos_ = undefined;
+ this.isZooming_ = false;
+ e.preventDefault();
+ },
+
+ extractRelativeMousePosition_(e) {
+ return {
+ x: e.clientX - this.rasterArea_.offsetLeft,
+ y: e.clientY - this.rasterArea_.offsetTop
+ };
+ }
+ };
+
+ return {
+ PictureDebugger,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_debugger_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_debugger_test.html
new file mode 100644
index 00000000000..e89e6b355e1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_debugger_test.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/picture_debugger.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('instantiate', function() {
+ const picture = new tr.e.cc.PictureSnapshot({id: '31415'}, 10, {
+ 'params': {
+ 'opaque_rect': [-15, -15, 0, 0],
+ 'layer_rect': [-15, -15, 46, 833]
+ },
+ 'skp64': 'DAAAAHYEAADzAQAABwAAAAFkYWVy8AAAAAgAAB4DAAAADAAAIAAAgD8AAIA/CAAAHgMAAAAcAAADAAAAAAAAAAAAwI5EAID5QwEAAADoAAAACAAAHgMAAAAMAAAjAAAAAAAAAAAMAAAjAAAAAAAAAAAcAAADAAAAAAAAAAAAwI5EAID5QwEAAADkAAAAGAAAFQEAAAAAAAAAAAAAAADAjkQAgPlDGAAAFQIAAAAAAAAAAAAAAADAjkQAgPlDCAAAHgMAAAAcAAADAAAAAAAAAAAAwI5EAID5QwEAAADgAAAAGAAAFQMAAAAAAKBAAACgQAAAgEIAAIBCBAAAHAQAABwEAAAcBAAAHHRjYWYBAAAADVNrU3JjWGZlcm1vZGVjZnB0AAAAAHlhcmGgAAAAIHRucAMAAAAAAEBBAACAPwAAAAAAAIA/AAAAAAAAgEAAAP//ADABAAAAAAAAAEBBAACAPwAAAAAAAIA/AAAAAAAAgED/////AjABAAAAAAAAAAAAAAAAAAEAAAAEAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEEAAIA/AAAAAAAAgD8AAAAAAACAQP8AAP8AMAEAAAAAACBmb2U=' // @suppress longLineCheck
+ });
+ picture.preInitialize();
+ picture.initialize();
+
+ const dbg = new tr.ui.e.chrome.cc.PictureDebugger();
+ this.addHTMLOutput(dbg);
+ dbg.picture = picture;
+ dbg.style.border = '1px solid black';
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_chart_summary_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_chart_summary_view.html
new file mode 100644
index 00000000000..55a8685aee0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_chart_summary_view.html
@@ -0,0 +1,458 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/ui/base/ui.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ const OPS_TIMING_ITERATIONS = 3;
+ const CHART_PADDING_LEFT = 65;
+ const CHART_PADDING_RIGHT = 40;
+ const AXIS_PADDING_LEFT = 60;
+ const AXIS_PADDING_RIGHT = 35;
+ const AXIS_PADDING_TOP = 25;
+ const AXIS_PADDING_BOTTOM = 45;
+ const AXIS_LABEL_PADDING = 5;
+ const AXIS_TICK_SIZE = 10;
+ const LABEL_PADDING = 5;
+ const LABEL_INTERLEAVE_OFFSET = 15;
+ const BAR_PADDING = 5;
+ const VERTICAL_TICKS = 5;
+ const HUE_CHAR_CODE_ADJUSTMENT = 5.7;
+
+ /**
+ * Provides a chart showing the cumulative time spent in Skia operations
+ * during picture rasterization.
+ *
+ * @constructor
+ */
+ const PictureOpsChartSummaryView = tr.ui.b.define(
+ 'tr-ui-e-chrome-cc-picture-ops-chart-summary-view');
+
+ PictureOpsChartSummaryView.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ decorate() {
+ this.style.flexGrow = 0;
+ this.style.flexShrink = 0;
+ this.style.flexBasis = 'auto';
+ this.style.fontSize = 0;
+ this.style.margin = 0;
+ this.style.minHeight = '200px';
+ this.style.minWidth = '200px';
+ this.style.overflow = 'hidden';
+ this.style.padding = 0;
+
+ this.picture_ = undefined;
+ this.pictureDataProcessed_ = false;
+
+ this.chartScale_ = window.devicePixelRatio;
+
+ this.chart_ = document.createElement('canvas');
+ this.chartCtx_ = this.chart_.getContext('2d');
+ Polymer.dom(this).appendChild(this.chart_);
+
+ this.opsTimingData_ = [];
+
+ this.chartWidth_ = 0;
+ this.chartHeight_ = 0;
+ this.requiresRedraw_ = true;
+
+ this.currentBarMouseOverTarget_ = null;
+
+ this.chart_.addEventListener('mousemove', this.onMouseMove_.bind(this));
+ new ResizeObserver(this.onResize_.bind(this)).observe(this);
+ },
+
+ get requiresRedraw() {
+ return this.requiresRedraw_;
+ },
+
+ set requiresRedraw(requiresRedraw) {
+ this.requiresRedraw_ = requiresRedraw;
+ },
+
+ get picture() {
+ return this.picture_;
+ },
+
+ set picture(picture) {
+ this.picture_ = picture;
+ this.pictureDataProcessed_ = false;
+
+ if (Polymer.dom(this).classList.contains('hidden')) return;
+
+ this.processPictureData_();
+ this.requiresRedraw = true;
+ this.updateChartContents();
+ },
+
+ hide() {
+ Polymer.dom(this).classList.add('hidden');
+ this.style.display = 'none';
+ },
+
+ show() {
+ Polymer.dom(this).classList.remove('hidden');
+ this.style.display = '';
+
+ if (!this.pictureDataProcessed_) {
+ this.processPictureData_();
+ }
+ this.requiresRedraw = true;
+ this.updateChartContents();
+ },
+
+ onMouseMove_(e) {
+ const lastBarMouseOverTarget = this.currentBarMouseOverTarget_;
+ this.currentBarMouseOverTarget_ = null;
+
+ const x = e.offsetX;
+ const y = e.offsetY;
+
+ const chartLeft = CHART_PADDING_LEFT;
+ const chartRight = this.chartWidth_ - CHART_PADDING_RIGHT;
+ const chartTop = AXIS_PADDING_TOP;
+ const chartBottom = this.chartHeight_ - AXIS_PADDING_BOTTOM;
+ const chartInnerWidth = chartRight - chartLeft;
+
+ if (x > chartLeft && x < chartRight && y > chartTop && y < chartBottom) {
+ this.currentBarMouseOverTarget_ = Math.floor(
+ (x - chartLeft) / chartInnerWidth * this.opsTimingData_.length);
+
+ this.currentBarMouseOverTarget_ = tr.b.math.clamp(
+ this.currentBarMouseOverTarget_, 0, this.opsTimingData_.length - 1);
+ }
+
+ if (this.currentBarMouseOverTarget_ === lastBarMouseOverTarget) return;
+
+ this.drawChartContents_();
+ },
+
+ onResize_() {
+ this.requiresRedraw = true;
+ this.updateChartContents();
+ },
+
+ updateChartContents() {
+ if (this.requiresRedraw) {
+ this.updateChartDimensions_();
+ }
+
+ this.drawChartContents_();
+ },
+
+ updateChartDimensions_() {
+ this.chartWidth_ = this.offsetWidth;
+ this.chartHeight_ = this.offsetHeight;
+
+ // Scale up the canvas according to the devicePixelRatio, then reduce it
+ // down again via CSS. Finally we apply a scale to the canvas so that
+ // things are drawn at the correct size.
+ this.chart_.width = this.chartWidth_ * this.chartScale_;
+ this.chart_.height = this.chartHeight_ * this.chartScale_;
+
+ this.chart_.style.width = this.chartWidth_ + 'px';
+ this.chart_.style.height = this.chartHeight_ + 'px';
+
+ this.chartCtx_.scale(this.chartScale_, this.chartScale_);
+ },
+
+ processPictureData_() {
+ this.resetOpsTimingData_();
+ this.pictureDataProcessed_ = true;
+
+ if (!this.picture_) return;
+
+ let ops = this.picture_.getOps();
+ if (!ops) return;
+
+ ops = this.picture_.tagOpsWithTimings(ops);
+
+ // Check that there are valid times.
+ if (ops[0].cmd_time === undefined) return;
+
+ this.collapseOpsToTimingBuckets_(ops);
+ },
+
+ drawChartContents_() {
+ this.clearChartContents_();
+
+ if (this.opsTimingData_.length === 0) {
+ this.showNoTimingDataMessage_();
+ return;
+ }
+
+ this.drawChartAxes_();
+ this.drawBars_();
+ this.drawLineAtBottomOfChart_();
+
+ if (this.currentBarMouseOverTarget_ === null) return;
+
+ this.drawTooltip_();
+ },
+
+ drawLineAtBottomOfChart_() {
+ this.chartCtx_.strokeStyle = '#AAA';
+ this.chartCtx_.moveTo(0, this.chartHeight_ - 0.5);
+ this.chartCtx_.lineTo(this.chartWidth_, this.chartHeight_ - 0.5);
+ this.chartCtx_.stroke();
+ },
+
+ drawTooltip_() {
+ const tooltipData = this.opsTimingData_[this.currentBarMouseOverTarget_];
+ const tooltipTitle = tooltipData.cmd_string;
+ const tooltipTime = tooltipData.cmd_time.toFixed(4);
+
+ const tooltipWidth = 110;
+ const tooltipHeight = 40;
+ const chartInnerWidth = this.chartWidth_ - CHART_PADDING_RIGHT -
+ CHART_PADDING_LEFT;
+ const barWidth = chartInnerWidth / this.opsTimingData_.length;
+ const tooltipOffset = Math.round((tooltipWidth - barWidth) * 0.5);
+
+ const left = CHART_PADDING_LEFT + this.currentBarMouseOverTarget_ *
+ barWidth - tooltipOffset;
+ const top = Math.round((this.chartHeight_ - tooltipHeight) * 0.5);
+
+ this.chartCtx_.save();
+
+ this.chartCtx_.shadowOffsetX = 0;
+ this.chartCtx_.shadowOffsetY = 5;
+ this.chartCtx_.shadowBlur = 4;
+ this.chartCtx_.shadowColor = 'rgba(0,0,0,0.4)';
+
+ this.chartCtx_.strokeStyle = '#888';
+ this.chartCtx_.fillStyle = '#EEE';
+ this.chartCtx_.fillRect(left, top, tooltipWidth, tooltipHeight);
+
+ this.chartCtx_.shadowColor = 'transparent';
+ this.chartCtx_.translate(0.5, 0.5);
+ this.chartCtx_.strokeRect(left, top, tooltipWidth, tooltipHeight);
+
+ this.chartCtx_.restore();
+
+ this.chartCtx_.fillStyle = '#222';
+ this.chartCtx_.textBaseline = 'top';
+ this.chartCtx_.font = '800 12px Arial';
+ this.chartCtx_.fillText(tooltipTitle, left + 8, top + 8);
+
+ this.chartCtx_.fillStyle = '#555';
+ this.chartCtx_.textBaseline = 'top';
+ this.chartCtx_.font = '400 italic 10px Arial';
+ this.chartCtx_.fillText('Total: ' + tooltipTime + 'ms',
+ left + 8, top + 22);
+ },
+
+ drawBars_() {
+ const len = this.opsTimingData_.length;
+ const max = this.opsTimingData_[0].cmd_time;
+ const min = this.opsTimingData_[len - 1].cmd_time;
+
+ const width = this.chartWidth_ - CHART_PADDING_LEFT - CHART_PADDING_RIGHT;
+ const height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
+ const barWidth = Math.floor(width / len);
+
+ let opData;
+ let opTiming;
+ let opHeight;
+ let opLabel;
+ let barLeft;
+
+ for (let b = 0; b < len; b++) {
+ opData = this.opsTimingData_[b];
+ opTiming = opData.cmd_time / max;
+
+ opHeight = Math.round(Math.max(1, opTiming * height));
+ opLabel = opData.cmd_string;
+ barLeft = CHART_PADDING_LEFT + b * barWidth;
+
+ this.chartCtx_.fillStyle = this.getOpColor_(opLabel);
+
+ this.chartCtx_.fillRect(barLeft + BAR_PADDING, AXIS_PADDING_TOP +
+ height - opHeight, barWidth - 2 * BAR_PADDING, opHeight);
+ }
+ },
+
+ getOpColor_(opName) {
+ const characters = opName.split('');
+ const hue = characters.reduce(this.reduceNameToHue, 0) % 360;
+
+ return 'hsl(' + hue + ', 30%, 50%)';
+ },
+
+ reduceNameToHue(previousValue, currentValue, index, array) {
+ // Get the char code and apply a magic adjustment value so we get
+ // pretty colors from around the rainbow.
+ return Math.round(previousValue + currentValue.charCodeAt(0) *
+ HUE_CHAR_CODE_ADJUSTMENT);
+ },
+
+ drawChartAxes_() {
+ const len = this.opsTimingData_.length;
+ const max = this.opsTimingData_[0].cmd_time;
+ const min = this.opsTimingData_[len - 1].cmd_time;
+
+ const width = this.chartWidth_ - AXIS_PADDING_LEFT - AXIS_PADDING_RIGHT;
+ const height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
+
+ const totalBarWidth = this.chartWidth_ - CHART_PADDING_LEFT -
+ CHART_PADDING_RIGHT;
+ const barWidth = Math.floor(totalBarWidth / len);
+ const tickYInterval = height / (VERTICAL_TICKS - 1);
+ let tickYPosition = 0;
+ const tickValInterval = (max - min) / (VERTICAL_TICKS - 1);
+ let tickVal = 0;
+
+ this.chartCtx_.fillStyle = '#333';
+ this.chartCtx_.strokeStyle = '#777';
+ this.chartCtx_.save();
+
+ // Translate half a pixel to avoid blurry lines.
+ this.chartCtx_.translate(0.5, 0.5);
+
+ // Sides.
+
+ this.chartCtx_.save();
+
+ this.chartCtx_.translate(AXIS_PADDING_LEFT, AXIS_PADDING_TOP);
+ this.chartCtx_.moveTo(0, 0);
+ this.chartCtx_.lineTo(0, height);
+ this.chartCtx_.lineTo(width, height);
+
+ // Y-axis ticks.
+ this.chartCtx_.font = '10px Arial';
+ this.chartCtx_.textAlign = 'right';
+ this.chartCtx_.textBaseline = 'middle';
+
+ for (let t = 0; t < VERTICAL_TICKS; t++) {
+ tickYPosition = Math.round(t * tickYInterval);
+ tickVal = (max - t * tickValInterval).toFixed(4);
+
+ this.chartCtx_.moveTo(0, tickYPosition);
+ this.chartCtx_.lineTo(-AXIS_TICK_SIZE, tickYPosition);
+ this.chartCtx_.fillText(tickVal,
+ -AXIS_TICK_SIZE - AXIS_LABEL_PADDING, tickYPosition);
+ }
+
+ this.chartCtx_.stroke();
+
+ this.chartCtx_.restore();
+
+
+ // Labels.
+
+ this.chartCtx_.save();
+
+ this.chartCtx_.translate(CHART_PADDING_LEFT + Math.round(barWidth * 0.5),
+ AXIS_PADDING_TOP + height + LABEL_PADDING);
+
+ this.chartCtx_.font = '10px Arial';
+ this.chartCtx_.textAlign = 'center';
+ this.chartCtx_.textBaseline = 'top';
+
+ let labelTickLeft;
+ let labelTickBottom;
+ for (let l = 0; l < len; l++) {
+ labelTickLeft = Math.round(l * barWidth);
+ labelTickBottom = l % 2 * LABEL_INTERLEAVE_OFFSET;
+
+ this.chartCtx_.save();
+ this.chartCtx_.moveTo(labelTickLeft, -LABEL_PADDING);
+ this.chartCtx_.lineTo(labelTickLeft, labelTickBottom);
+ this.chartCtx_.stroke();
+ this.chartCtx_.restore();
+
+ this.chartCtx_.fillText(this.opsTimingData_[l].cmd_string,
+ labelTickLeft, labelTickBottom);
+ }
+
+ this.chartCtx_.restore();
+
+ this.chartCtx_.restore();
+ },
+
+ clearChartContents_() {
+ this.chartCtx_.clearRect(0, 0, this.chartWidth_, this.chartHeight_);
+ },
+
+ showNoTimingDataMessage_() {
+ this.chartCtx_.font = '800 italic 14px Arial';
+ this.chartCtx_.fillStyle = '#333';
+ this.chartCtx_.textAlign = 'center';
+ this.chartCtx_.textBaseline = 'middle';
+ this.chartCtx_.fillText('No timing data available.',
+ this.chartWidth_ * 0.5, this.chartHeight_ * 0.5);
+ },
+
+ collapseOpsToTimingBuckets_(ops) {
+ const opsTimingDataIndexHash_ = {};
+ const timingData = this.opsTimingData_;
+ let op;
+ let opIndex;
+
+ for (let i = 0; i < ops.length; i++) {
+ op = ops[i];
+
+ if (op.cmd_time === undefined) continue;
+
+ // Try to locate the entry for the current operation
+ // based on its name. If that fails, then create one for it.
+ opIndex = opsTimingDataIndexHash_[op.cmd_string] || null;
+
+ if (opIndex === null) {
+ timingData.push({
+ cmd_time: 0,
+ cmd_string: op.cmd_string
+ });
+
+ opIndex = timingData.length - 1;
+ opsTimingDataIndexHash_[op.cmd_string] = opIndex;
+ }
+
+ timingData[opIndex].cmd_time += op.cmd_time;
+ }
+
+ timingData.sort(this.sortTimingBucketsByOpTimeDescending_);
+
+ this.collapseTimingBucketsToOther_(4);
+ },
+
+ collapseTimingBucketsToOther_(count) {
+ const timingData = this.opsTimingData_;
+ const otherSource = timingData.splice(count, timingData.length - count);
+ let otherDestination = null;
+
+ if (!otherSource.length) return;
+
+ timingData.push({
+ cmd_time: 0,
+ cmd_string: 'Other'
+ });
+
+ otherDestination = timingData[timingData.length - 1];
+ for (let i = 0; i < otherSource.length; i++) {
+ otherDestination.cmd_time += otherSource[i].cmd_time;
+ }
+ },
+
+ sortTimingBucketsByOpTimeDescending_(a, b) {
+ return b.cmd_time - a.cmd_time;
+ },
+
+ resetOpsTimingData_() {
+ this.opsTimingData_.length = 0;
+ }
+ };
+
+ return {
+ PictureOpsChartSummaryView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_chart_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_chart_view.html
new file mode 100644
index 00000000000..413998847aa
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_chart_view.html
@@ -0,0 +1,505 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/ui/base/dom_helpers.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ const BAR_PADDING = 1;
+ const BAR_WIDTH = 5;
+ const CHART_PADDING_LEFT = 65;
+ const CHART_PADDING_RIGHT = 30;
+ const CHART_PADDING_BOTTOM = 35;
+ const CHART_PADDING_TOP = 20;
+ const AXIS_PADDING_LEFT = 55;
+ const AXIS_PADDING_RIGHT = 30;
+ const AXIS_PADDING_BOTTOM = 35;
+ const AXIS_PADDING_TOP = 20;
+ const AXIS_TICK_SIZE = 5;
+ const AXIS_LABEL_PADDING = 5;
+ const VERTICAL_TICKS = 5;
+ const HUE_CHAR_CODE_ADJUSTMENT = 5.7;
+
+ /**
+ * Provides a chart showing the cumulative time spent in Skia operations
+ * during picture rasterization.
+ *
+ * @constructor
+ */
+ const PictureOpsChartView =
+ tr.ui.b.define('tr-ui-e-chrome-cc-picture-ops-chart-view');
+
+ PictureOpsChartView.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ decorate() {
+ this.style.display = 'block';
+ this.style.height = '180px';
+ this.style.margin = 0;
+ this.style.padding = 0;
+ this.style.position = 'relative';
+
+ this.picture_ = undefined;
+ this.pictureOps_ = undefined;
+ this.opCosts_ = undefined;
+
+ this.chartScale_ = window.devicePixelRatio;
+
+ this.chart_ = document.createElement('canvas');
+ this.chartCtx_ = this.chart_.getContext('2d');
+ Polymer.dom(this).appendChild(this.chart_);
+
+ this.selectedOpIndex_ = undefined;
+ this.chartWidth_ = 0;
+ this.chartHeight_ = 0;
+ this.dimensionsHaveChanged_ = true;
+
+ this.currentBarMouseOverTarget_ = undefined;
+
+ this.ninetyFifthPercentileCost_ = 0;
+ this.totalOpCost_ = 0;
+
+ this.chart_.addEventListener('click', this.onClick_.bind(this));
+ this.chart_.addEventListener('mousemove', this.onMouseMove_.bind(this));
+ new ResizeObserver(this.onResize_.bind(this)).observe(this);
+
+ this.usePercentileScale_ = false;
+ this.usePercentileScaleCheckbox_ = tr.ui.b.createCheckBox(
+ this, 'usePercentileScale',
+ 'PictureOpsChartView.usePercentileScale', false,
+ 'Limit to 95%-ile');
+ Polymer.dom(this.usePercentileScaleCheckbox_).classList.add(
+ 'use-percentile-scale');
+ this.usePercentileScaleCheckbox_.style.position = 'absolute';
+ this.usePercentileScaleCheckbox_.style.left = 0;
+ this.usePercentileScaleCheckbox_.style.top = 0;
+ Polymer.dom(this).appendChild(this.usePercentileScaleCheckbox_);
+ },
+
+ get dimensionsHaveChanged() {
+ return this.dimensionsHaveChanged_;
+ },
+
+ set dimensionsHaveChanged(dimensionsHaveChanged) {
+ this.dimensionsHaveChanged_ = dimensionsHaveChanged;
+ },
+
+ get usePercentileScale() {
+ return this.usePercentileScale_;
+ },
+
+ set usePercentileScale(usePercentileScale) {
+ this.usePercentileScale_ = usePercentileScale;
+ this.drawChartContents_();
+ },
+
+ get numOps() {
+ return this.opCosts_.length;
+ },
+
+ get selectedOpIndex() {
+ return this.selectedOpIndex_;
+ },
+
+ set selectedOpIndex(selectedOpIndex) {
+ if (selectedOpIndex < 0) throw new Error('Invalid index');
+ if (selectedOpIndex >= this.numOps) throw new Error('Invalid index');
+
+ this.selectedOpIndex_ = selectedOpIndex;
+ },
+
+ get picture() {
+ return this.picture_;
+ },
+
+ set picture(picture) {
+ this.picture_ = picture;
+ this.pictureOps_ = picture.tagOpsWithTimings(picture.getOps());
+ this.currentBarMouseOverTarget_ = undefined;
+ this.processPictureData_();
+ this.dimensionsHaveChanged = true;
+ },
+
+ processPictureData_() {
+ if (this.pictureOps_ === undefined) return;
+
+ let totalOpCost = 0;
+
+ // Take a copy of the picture ops data for sorting.
+ this.opCosts_ = this.pictureOps_.map(function(op) {
+ totalOpCost += op.cmd_time;
+ return op.cmd_time;
+ });
+ this.opCosts_.sort();
+
+ const ninetyFifthPercentileCostIndex = Math.floor(
+ this.opCosts_.length * 0.95);
+ this.ninetyFifthPercentileCost_ =
+ this.opCosts_[ninetyFifthPercentileCostIndex];
+ this.maxCost_ = this.opCosts_[this.opCosts_.length - 1];
+
+ this.totalOpCost_ = totalOpCost;
+ },
+
+ extractBarIndex_(e) {
+ let index = undefined;
+
+ if (this.pictureOps_ === undefined ||
+ this.pictureOps_.length === 0) {
+ return index;
+ }
+
+ const x = e.offsetX;
+ const y = e.offsetY;
+
+ const totalBarWidth = (BAR_WIDTH + BAR_PADDING) * this.pictureOps_.length;
+
+ const chartLeft = CHART_PADDING_LEFT;
+ const chartTop = 0;
+ const chartBottom = this.chartHeight_ - CHART_PADDING_BOTTOM;
+ const chartRight = chartLeft + totalBarWidth;
+
+ if (x < chartLeft || x > chartRight || y < chartTop || y > chartBottom) {
+ return index;
+ }
+
+ index = Math.floor((x - chartLeft) / totalBarWidth *
+ this.pictureOps_.length);
+
+ index = tr.b.math.clamp(index, 0, this.pictureOps_.length - 1);
+
+ return index;
+ },
+
+ onClick_(e) {
+ const barClicked = this.extractBarIndex_(e);
+
+ if (barClicked === undefined) return;
+
+ // If we click on the already selected item we should deselect.
+ if (barClicked === this.selectedOpIndex) {
+ this.selectedOpIndex = undefined;
+ } else {
+ this.selectedOpIndex = barClicked;
+ }
+
+ e.preventDefault();
+
+ tr.b.dispatchSimpleEvent(this, 'selection-changed', false);
+ },
+
+ onMouseMove_(e) {
+ const lastBarMouseOverTarget = this.currentBarMouseOverTarget_;
+ this.currentBarMouseOverTarget_ = this.extractBarIndex_(e);
+
+ if (this.currentBarMouseOverTarget_ === lastBarMouseOverTarget) {
+ return;
+ }
+
+ this.drawChartContents_();
+ },
+
+ onResize_() {
+ this.dimensionsHaveChanged = true;
+ this.updateChartContents();
+ },
+
+ scrollSelectedItemIntoViewIfNecessary() {
+ if (this.selectedOpIndex === undefined) {
+ return;
+ }
+
+ const width = this.offsetWidth;
+ const left = this.scrollLeft;
+ const right = left + width;
+ const targetLeft = CHART_PADDING_LEFT +
+ (BAR_WIDTH + BAR_PADDING) * this.selectedOpIndex;
+
+ if (targetLeft > left && targetLeft < right) {
+ return;
+ }
+
+ this.scrollLeft = (targetLeft - width * 0.5);
+ },
+
+ updateChartContents() {
+ if (this.dimensionsHaveChanged) {
+ this.updateChartDimensions_();
+ }
+
+ this.drawChartContents_();
+ },
+
+ updateChartDimensions_() {
+ if (!this.pictureOps_) return;
+
+ let width = CHART_PADDING_LEFT + CHART_PADDING_RIGHT +
+ ((BAR_WIDTH + BAR_PADDING) * this.pictureOps_.length);
+
+ if (width < this.offsetWidth) {
+ width = this.offsetWidth;
+ }
+
+ // Allow the element to be its natural size as set by flexbox, then lock
+ // the width in before we set the width of the canvas.
+ this.chartWidth_ = width;
+ this.chartHeight_ = this.getBoundingClientRect().height;
+
+ // Scale up the canvas according to the devicePixelRatio, then reduce it
+ // down again via CSS. Finally we apply a scale to the canvas so that
+ // things are drawn at the correct size.
+ this.chart_.width = this.chartWidth_ * this.chartScale_;
+ this.chart_.height = this.chartHeight_ * this.chartScale_;
+
+ this.chart_.style.width = this.chartWidth_ + 'px';
+ this.chart_.style.height = this.chartHeight_ + 'px';
+
+ this.chartCtx_.scale(this.chartScale_, this.chartScale_);
+
+ this.dimensionsHaveChanged = false;
+ },
+
+ drawChartContents_() {
+ this.clearChartContents_();
+
+ if (this.pictureOps_ === undefined ||
+ this.pictureOps_.length === 0 ||
+ this.pictureOps_[0].cmd_time === undefined) {
+ this.showNoTimingDataMessage_();
+ return;
+ }
+
+ this.drawSelection_();
+ this.drawBars_();
+ this.drawChartAxes_();
+ this.drawLinesAtTickMarks_();
+ this.drawLineAtBottomOfChart_();
+
+ if (this.currentBarMouseOverTarget_ === undefined) {
+ return;
+ }
+
+ this.drawTooltip_();
+ },
+
+ drawSelection_() {
+ if (this.selectedOpIndex === undefined) {
+ return;
+ }
+
+ const width = (BAR_WIDTH + BAR_PADDING) * this.selectedOpIndex;
+ this.chartCtx_.fillStyle = 'rgb(223, 235, 230)';
+ this.chartCtx_.fillRect(CHART_PADDING_LEFT, CHART_PADDING_TOP,
+ width, this.chartHeight_ - CHART_PADDING_TOP - CHART_PADDING_BOTTOM);
+ },
+
+ drawChartAxes_() {
+ const min = this.opCosts_[0];
+ const max = this.opCosts_[this.opCosts_.length - 1];
+ const height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
+
+ const tickYInterval = height / (VERTICAL_TICKS - 1);
+ let tickYPosition = 0;
+ const tickValInterval = (max - min) / (VERTICAL_TICKS - 1);
+ let tickVal = 0;
+
+ this.chartCtx_.fillStyle = '#333';
+ this.chartCtx_.strokeStyle = '#777';
+ this.chartCtx_.save();
+
+ // Translate half a pixel to avoid blurry lines.
+ this.chartCtx_.translate(0.5, 0.5);
+
+ // Sides.
+ this.chartCtx_.beginPath();
+ this.chartCtx_.moveTo(AXIS_PADDING_LEFT, AXIS_PADDING_TOP);
+ this.chartCtx_.lineTo(AXIS_PADDING_LEFT, this.chartHeight_ -
+ AXIS_PADDING_BOTTOM);
+ this.chartCtx_.lineTo(this.chartWidth_ - AXIS_PADDING_RIGHT,
+ this.chartHeight_ - AXIS_PADDING_BOTTOM);
+ this.chartCtx_.stroke();
+ this.chartCtx_.closePath();
+
+ // Y-axis ticks.
+ this.chartCtx_.translate(AXIS_PADDING_LEFT, AXIS_PADDING_TOP);
+
+ this.chartCtx_.font = '10px Arial';
+ this.chartCtx_.textAlign = 'right';
+ this.chartCtx_.textBaseline = 'middle';
+
+ this.chartCtx_.beginPath();
+ for (let t = 0; t < VERTICAL_TICKS; t++) {
+ tickYPosition = Math.round(t * tickYInterval);
+ tickVal = (max - t * tickValInterval).toFixed(4);
+
+ this.chartCtx_.moveTo(0, tickYPosition);
+ this.chartCtx_.lineTo(-AXIS_TICK_SIZE, tickYPosition);
+ this.chartCtx_.fillText(tickVal,
+ -AXIS_TICK_SIZE - AXIS_LABEL_PADDING, tickYPosition);
+ }
+
+ this.chartCtx_.stroke();
+ this.chartCtx_.closePath();
+
+ this.chartCtx_.restore();
+ },
+
+ drawLinesAtTickMarks_() {
+ const height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
+ const width = this.chartWidth_ - AXIS_PADDING_LEFT - AXIS_PADDING_RIGHT;
+ const tickYInterval = height / (VERTICAL_TICKS - 1);
+ let tickYPosition = 0;
+
+ this.chartCtx_.save();
+
+ this.chartCtx_.translate(AXIS_PADDING_LEFT + 0.5, AXIS_PADDING_TOP + 0.5);
+ this.chartCtx_.beginPath();
+ this.chartCtx_.strokeStyle = 'rgba(0,0,0,0.05)';
+
+ for (let t = 0; t < VERTICAL_TICKS; t++) {
+ tickYPosition = Math.round(t * tickYInterval);
+
+ this.chartCtx_.moveTo(0, tickYPosition);
+ this.chartCtx_.lineTo(width, tickYPosition);
+ this.chartCtx_.stroke();
+ }
+
+ this.chartCtx_.restore();
+ this.chartCtx_.closePath();
+ },
+
+ drawLineAtBottomOfChart_() {
+ this.chartCtx_.strokeStyle = '#AAA';
+ this.chartCtx_.beginPath();
+ this.chartCtx_.moveTo(0, this.chartHeight_ - 0.5);
+ this.chartCtx_.lineTo(this.chartWidth_, this.chartHeight_ - 0.5);
+ this.chartCtx_.stroke();
+ this.chartCtx_.closePath();
+ },
+
+ drawTooltip_() {
+ const tooltipData = this.pictureOps_[this.currentBarMouseOverTarget_];
+ const tooltipTitle = tooltipData.cmd_string;
+ const tooltipTime = tooltipData.cmd_time.toFixed(4);
+ const toolTipTimePercentage =
+ ((tooltipData.cmd_time / this.totalOpCost_) * 100).toFixed(2);
+
+ const tooltipWidth = 120;
+ const tooltipHeight = 40;
+ const chartInnerWidth = this.chartWidth_ - CHART_PADDING_RIGHT -
+ CHART_PADDING_LEFT;
+ const barWidth = BAR_WIDTH + BAR_PADDING;
+ const tooltipOffset = Math.round((tooltipWidth - barWidth) * 0.5);
+
+ const left = CHART_PADDING_LEFT + this.currentBarMouseOverTarget_ *
+ barWidth - tooltipOffset;
+ const top = Math.round((this.chartHeight_ - tooltipHeight) * 0.5);
+
+ this.chartCtx_.save();
+
+ this.chartCtx_.shadowOffsetX = 0;
+ this.chartCtx_.shadowOffsetY = 5;
+ this.chartCtx_.shadowBlur = 4;
+ this.chartCtx_.shadowColor = 'rgba(0,0,0,0.4)';
+
+ this.chartCtx_.strokeStyle = '#888';
+ this.chartCtx_.fillStyle = '#EEE';
+ this.chartCtx_.fillRect(left, top, tooltipWidth, tooltipHeight);
+
+ this.chartCtx_.shadowColor = 'transparent';
+ this.chartCtx_.translate(0.5, 0.5);
+ this.chartCtx_.strokeRect(left, top, tooltipWidth, tooltipHeight);
+
+ this.chartCtx_.restore();
+
+ this.chartCtx_.fillStyle = '#222';
+ this.chartCtx_.textAlign = 'left';
+ this.chartCtx_.textBaseline = 'top';
+ this.chartCtx_.font = '800 12px Arial';
+ this.chartCtx_.fillText(tooltipTitle, left + 8, top + 8);
+
+ this.chartCtx_.fillStyle = '#555';
+ this.chartCtx_.font = '400 italic 10px Arial';
+ this.chartCtx_.fillText(tooltipTime + 'ms (' +
+ toolTipTimePercentage + '%)', left + 8, top + 22);
+ },
+
+ drawBars_() {
+ let op;
+ let opColor = 0;
+ let opHeight = 0;
+ const opWidth = BAR_WIDTH + BAR_PADDING;
+ let opHover = false;
+
+ const bottom = this.chartHeight_ - CHART_PADDING_BOTTOM;
+ const maxHeight = this.chartHeight_ - CHART_PADDING_BOTTOM -
+ CHART_PADDING_TOP;
+
+ let maxValue;
+ if (this.usePercentileScale) {
+ maxValue = this.ninetyFifthPercentileCost_;
+ } else {
+ maxValue = this.maxCost_;
+ }
+
+ for (let b = 0; b < this.pictureOps_.length; b++) {
+ op = this.pictureOps_[b];
+ opHeight = Math.round(
+ (op.cmd_time / maxValue) * maxHeight);
+ opHeight = Math.max(opHeight, 1);
+ opHover = (b === this.currentBarMouseOverTarget_);
+ opColor = this.getOpColor_(op.cmd_string, opHover);
+
+ if (b === this.selectedOpIndex) {
+ this.chartCtx_.fillStyle = '#FFFF00';
+ } else {
+ this.chartCtx_.fillStyle = opColor;
+ }
+
+ this.chartCtx_.fillRect(CHART_PADDING_LEFT + b * opWidth,
+ bottom - opHeight, BAR_WIDTH, opHeight);
+ }
+ },
+
+ getOpColor_(opName, hover) {
+ const characters = opName.split('');
+
+ const hue = characters.reduce(this.reduceNameToHue, 0) % 360;
+ const saturation = 30;
+ const lightness = hover ? '75%' : '50%';
+
+ return 'hsl(' + hue + ', ' + saturation + '%, ' + lightness + '%)';
+ },
+
+ reduceNameToHue(previousValue, currentValue, index, array) {
+ // Get the char code and apply a magic adjustment value so we get
+ // pretty colors from around the rainbow.
+ return Math.round(previousValue + currentValue.charCodeAt(0) *
+ HUE_CHAR_CODE_ADJUSTMENT);
+ },
+
+ clearChartContents_() {
+ this.chartCtx_.clearRect(0, 0, this.chartWidth_, this.chartHeight_);
+ },
+
+ showNoTimingDataMessage_() {
+ this.chartCtx_.font = '800 italic 14px Arial';
+ this.chartCtx_.fillStyle = '#333';
+ this.chartCtx_.textAlign = 'center';
+ this.chartCtx_.textBaseline = 'middle';
+ this.chartCtx_.fillText('No timing data available.',
+ this.chartWidth_ * 0.5, this.chartHeight_ * 0.5);
+ }
+ };
+
+ return {
+ PictureOpsChartView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_list_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_list_view.html
new file mode 100644
index 00000000000..2e45be58c33
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_list_view.html
@@ -0,0 +1,261 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/constants.html">
+<link rel="import" href="/tracing/ui/base/dom_helpers.html">
+<link rel="import" href="/tracing/ui/base/list_view.html">
+<link rel="import" href="/tracing/ui/base/utils.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/selection.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ const OPS_TIMING_ITERATIONS = 3; // Iterations to average op timing info over.
+ const ANNOTATION = 'Comment';
+ const BEGIN_ANNOTATION = 'BeginCommentGroup';
+ const END_ANNOTATION = 'EndCommentGroup';
+ const ANNOTATION_ID = 'ID: ';
+ const ANNOTATION_CLASS = 'CLASS: ';
+ const ANNOTATION_TAG = 'TAG: ';
+
+ const constants = tr.e.cc.constants;
+
+ /**
+ * @constructor
+ */
+ const PictureOpsListView =
+ tr.ui.b.define('tr-ui-e-chrome-cc-picture-ops-list-view');
+
+ PictureOpsListView.prototype = {
+ __proto__: HTMLDivElement.prototype,
+
+ decorate() {
+ this.style.borderTop = '1px solid grey';
+ this.style.overflow = 'auto';
+ this.opsList_ = new tr.ui.b.ListView();
+ Polymer.dom(this).appendChild(this.opsList_);
+
+ this.selectedOp_ = undefined;
+ this.selectedOpIndex_ = undefined;
+ this.opsList_.addEventListener(
+ 'selection-changed', this.onSelectionChanged_.bind(this));
+
+ this.picture_ = undefined;
+ },
+
+ get picture() {
+ return this.picture_;
+ },
+
+ set picture(picture) {
+ this.picture_ = picture;
+ this.updateContents_();
+ },
+
+ updateContents_() {
+ this.opsList_.clear();
+
+ if (!this.picture_) return;
+
+ let ops = this.picture_.getOps();
+ if (!ops) return;
+
+ ops = this.picture_.tagOpsWithTimings(ops);
+
+ ops = this.opsTaggedWithAnnotations_(ops);
+
+ for (let i = 0; i < ops.length; i++) {
+ const op = ops[i];
+ const item = document.createElement('div');
+ item.opIndex = op.opIndex;
+ Polymer.dom(item).textContent = i + ') ' + op.cmd_string;
+
+ // Display the element info associated with the op, if available.
+ if (op.elementInfo.tag || op.elementInfo.id || op.elementInfo.class) {
+ const elementInfo = document.createElement('span');
+ Polymer.dom(elementInfo).classList.add('elementInfo');
+ elementInfo.style.color = 'purple';
+ elementInfo.style.fontSize = 'small';
+ elementInfo.style.fontWeight = 'bold';
+ elementInfo.style.color = '#777';
+ const tag = op.elementInfo.tag ? op.elementInfo.tag : 'unknown';
+ const id = op.elementInfo.id ? 'id=' + op.elementInfo.id : undefined;
+ const className = op.elementInfo.class ? 'class=' +
+ op.elementInfo.class : undefined;
+ Polymer.dom(elementInfo).textContent =
+ '<' + tag + (id ? ' ' : '') +
+ (id ? id : '') + (className ? ' ' : '') +
+ (className ? className : '') + '>';
+ Polymer.dom(item).appendChild(elementInfo);
+ }
+
+ // Display the Skia params.
+ // FIXME: now that we have structured data, we should format it.
+ // (https://github.com/google/trace-viewer/issues/782)
+ if (op.info.length > 0) {
+ const infoItem = document.createElement('div');
+ Polymer.dom(infoItem).textContent = JSON.stringify(op.info);
+ infoItem.style.fontSize = 'x-small';
+ infoItem.style.color = '#777';
+ Polymer.dom(item).appendChild(infoItem);
+ }
+
+ // Display the op timing, if available.
+ if (op.cmd_time && op.cmd_time >= 0.0001) {
+ const time = document.createElement('span');
+ Polymer.dom(time).classList.add('time');
+ const rounded = op.cmd_time.toFixed(4);
+ Polymer.dom(time).textContent = '(' + rounded + 'ms)';
+ time.style.fontSize = 'x-small';
+ time.style.color = 'rgb(136, 0, 0)';
+ Polymer.dom(item).appendChild(time);
+ }
+
+ item.style.borderBottom = '1px solid #555';
+ item.style.fontSize = 'small';
+ item.style.fontWeight = 'bold';
+ item.style.paddingBottom = '5px';
+ item.style.paddingLeft = '5px';
+ item.style.cursor = 'pointer';
+
+ for (const child of item.children) {
+ child.style.fontWeight = 'normal';
+ child.style.marginLeft = '1em';
+ child.style.maxWidth = '300px';
+ }
+
+ Polymer.dom(this.opsList_).appendChild(item);
+ }
+ },
+
+ onSelectionChanged_(e) {
+ let beforeSelectedOp = true;
+
+ // Deselect on re-selection.
+ if (this.opsList_.selectedElement === this.selectedOp_) {
+ this.opsList_.selectedElement = undefined;
+ beforeSelectedOp = false;
+ this.selectedOpIndex_ = undefined;
+ }
+
+ this.selectedOp_ = this.opsList_.selectedElement;
+
+ // Set selection on all previous ops.
+ const ops = this.opsList_.children;
+ for (let i = 0; i < ops.length; i++) {
+ const op = ops[i];
+ if (op === this.selectedOp_) {
+ beforeSelectedOp = false;
+ this.selectedOpIndex_ = op.opIndex;
+ } else if (beforeSelectedOp) {
+ Polymer.dom(op).setAttribute('beforeSelection', 'beforeSelection');
+ op.style.backgroundColor = 'rgb(103, 199, 165)';
+ } else {
+ Polymer.dom(op).removeAttribute('beforeSelection');
+ op.style.backgroundColor = '';
+ }
+ }
+
+ tr.b.dispatchSimpleEvent(this, 'selection-changed', false);
+ },
+
+ get numOps() {
+ return this.opsList_.children.length;
+ },
+
+ get selectedOpIndex() {
+ return this.selectedOpIndex_;
+ },
+
+ set selectedOpIndex(s) {
+ this.selectedOpIndex_ = s;
+
+ if (s === undefined) {
+ this.opsList_.selectedElement = this.selectedOp_;
+ this.onSelectionChanged_();
+ } else {
+ if (s < 0) throw new Error('Invalid index');
+ if (s >= this.numOps) throw new Error('Invalid index');
+ this.opsList_.selectedElement = this.opsList_.getElementByIndex(s + 1);
+ tr.ui.b.scrollIntoViewIfNeeded(this.opsList_.selectedElement);
+ }
+ },
+
+ /**
+ * Return Skia operations tagged by annotation.
+ *
+ * The ops returned from Picture.getOps() contain both Skia ops and
+ * annotations threaded together. This function removes all annotations
+ * from the list and tags each op with the associated annotations.
+ * Additionally, the last {tag, id, class} is stored as elementInfo on
+ * each op.
+ *
+ * @param {Array} ops Array of Skia operations and annotations.
+ * @return {Array} Skia ops where op.annotations contains the associated
+ * annotations for a given op.
+ */
+ opsTaggedWithAnnotations_(ops) {
+ // This algorithm works by walking all the ops and pushing any
+ // annotations onto a stack. When a non-annotation op is found, the
+ // annotations stack is traversed and stored with the op.
+ const annotationGroups = [];
+ const opsWithoutAnnotations = [];
+ for (let opIndex = 0; opIndex < ops.length; opIndex++) {
+ const op = ops[opIndex];
+ op.opIndex = opIndex;
+ switch (op.cmd_string) {
+ case BEGIN_ANNOTATION:
+ annotationGroups.push([]);
+ break;
+ case END_ANNOTATION:
+ annotationGroups.pop();
+ break;
+ case ANNOTATION:
+ annotationGroups[annotationGroups.length - 1].push(op);
+ break;
+ default: {
+ const annotations = [];
+ let elementInfo = {};
+ annotationGroups.forEach(function(annotationGroup) {
+ elementInfo = {};
+ annotationGroup.forEach(function(annotation) {
+ annotation.info.forEach(function(info) {
+ if (info.includes(ANNOTATION_TAG)) {
+ elementInfo.tag = info.substring(
+ info.indexOf(ANNOTATION_TAG) +
+ ANNOTATION_TAG.length).toLowerCase();
+ } else if (info.includes(ANNOTATION_ID)) {
+ elementInfo.id = info.substring(
+ info.indexOf(ANNOTATION_ID) +
+ ANNOTATION_ID.length);
+ } else if (info.includes(ANNOTATION_CLASS)) {
+ elementInfo.class = info.substring(
+ info.indexOf(ANNOTATION_CLASS) +
+ ANNOTATION_CLASS.length);
+ }
+
+ annotations.push(info);
+ });
+ });
+ });
+ op.annotations = annotations;
+ op.elementInfo = elementInfo;
+ opsWithoutAnnotations.push(op);
+ }
+ }
+ }
+
+ return opsWithoutAnnotations;
+ }
+ };
+
+ return {
+ PictureOpsListView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_list_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_list_view_test.html
new file mode 100644
index 00000000000..b58c1568f4f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_ops_list_view_test.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/picture_ops_list_view.html">
+
+<script src="/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js">
+</script>
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const PictureOpsListView = tr.ui.e.chrome.cc.PictureOpsListView;
+
+ test('instantiate', function() {
+ if (!tr.e.cc.PictureSnapshot.CanRasterize()) return;
+
+ const m = new tr.Model(g_catLTHIEvents);
+ const p = Object.values(m.processes)[0];
+
+ const instance = p.objects.getAllInstancesNamed('cc::Picture')[0];
+ const snapshot = instance.snapshots[0];
+
+ const view = new PictureOpsListView();
+ view.picture = snapshot;
+ assert.strictEqual(view.opsList_.children.length, 142);
+ });
+
+ test('selection', function() {
+ if (!tr.e.cc.PictureSnapshot.CanRasterize()) return;
+
+ const m = new tr.Model(g_catLTHIEvents);
+ const p = Object.values(m.processes)[0];
+
+ const instance = p.objects.getAllInstancesNamed('cc::Picture')[0];
+ const snapshot = instance.snapshots[0];
+
+ const view = new PictureOpsListView();
+ view.picture = snapshot;
+ let didSelectionChange = 0;
+ view.addEventListener('selection-changed', function() {
+ didSelectionChange = true;
+ });
+ assert.isFalse(didSelectionChange);
+ view.opsList_.selectedElement = view.opsList_.children[3];
+ assert.isTrue(didSelectionChange);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_view.html
new file mode 100644
index 00000000000..a9db575773f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/picture_view.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
+<link rel="import" href="/tracing/ui/analysis/generic_object_view.html">
+<link rel="import" href="/tracing/ui/analysis/object_snapshot_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/picture_debugger.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ /*
+ * Displays a picture snapshot in a human readable form.
+ * @constructor
+ */
+ const PictureSnapshotView = tr.ui.b.define(
+ 'tr-ui-e-chrome-cc-picture-snapshot-view',
+ tr.ui.analysis.ObjectSnapshotView);
+
+ PictureSnapshotView.prototype = {
+ __proto__: tr.ui.analysis.ObjectSnapshotView.prototype,
+
+ decorate() {
+ Polymer.dom(this).classList.add(
+ 'tr-ui-e-chrome-cc-picture-snapshot-view');
+ this.style.display = 'flex';
+ this.style.flexGrow = 1;
+ this.style.flexShrink = 1;
+ this.style.flexBasis = 'auto';
+ this.style.minWidth = 0;
+ this.pictureDebugger_ = new tr.ui.e.chrome.cc.PictureDebugger();
+ this.pictureDebugger_.style.flexGrow = 1;
+ this.pictureDebugger_.style.flexShrink = 1;
+ this.pictureDebugger_.style.flexBasis = 'auto';
+ this.pictureDebugger_.style.minWidth = 0;
+ Polymer.dom(this).appendChild(this.pictureDebugger_);
+ },
+
+ updateContents() {
+ if (this.objectSnapshot_ && this.pictureDebugger_) {
+ this.pictureDebugger_.picture = this.objectSnapshot_;
+ }
+ }
+ };
+
+ tr.ui.analysis.ObjectSnapshotView.register(
+ PictureSnapshotView,
+ {
+ typeNames: ['cc::Picture', 'cc::LayeredPicture'],
+ showInstances: false
+ });
+
+ return {
+ PictureSnapshotView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_selection.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_selection.html
new file mode 100644
index 00000000000..6b1a7cb7df0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_selection.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+<link rel="import" href="/tracing/extras/chrome/cc/raster_task.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/ui/analysis/single_event_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/raster_task_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/selection.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ /**
+ * @constructor
+ */
+ function RasterTaskSelection(selection) {
+ tr.ui.e.chrome.cc.Selection.call(this);
+ const whySupported = RasterTaskSelection.whySuported(selection);
+ if (!whySupported.ok) {
+ throw new Error('Fail: ' + whySupported.why);
+ }
+ this.slices_ = Array.from(selection);
+ this.tiles_ = this.slices_.map(function(slice) {
+ const tile = tr.e.cc.getTileFromRasterTaskSlice(slice);
+ if (tile === undefined) {
+ throw new Error('This should never happen due to .supports check.');
+ }
+ return tile;
+ });
+ }
+
+ RasterTaskSelection.whySuported = function(selection) {
+ if (!(selection instanceof tr.model.EventSet)) {
+ return {ok: false, why: 'Must be selection'};
+ }
+
+ if (selection.length === 0) {
+ return {ok: false, why: 'Selection must be non empty'};
+ }
+
+ let referenceSnapshot = undefined;
+ for (const event of selection) {
+ if (!(event instanceof tr.model.Slice)) {
+ return {ok: false, why: 'Not a slice'};
+ }
+
+ const tile = tr.e.cc.getTileFromRasterTaskSlice(event);
+ if (tile === undefined) {
+ return {ok: false, why: 'No tile found'};
+ }
+
+ if (!referenceSnapshot) {
+ referenceSnapshot = tile.containingSnapshot;
+ } else {
+ if (tile.containingSnapshot !== referenceSnapshot) {
+ return {
+ ok: false,
+ why: 'Raster tasks are from different compositor instances'
+ };
+ }
+ }
+ }
+ return {ok: true};
+ };
+
+ RasterTaskSelection.supports = function(selection) {
+ return RasterTaskSelection.whySuported(selection).ok;
+ };
+
+ RasterTaskSelection.prototype = {
+ __proto__: tr.ui.e.chrome.cc.Selection.prototype,
+
+ get specicifity() {
+ return 3;
+ },
+
+ get associatedLayerId() {
+ const tile0 = this.tiles_[0];
+ const allSameLayer = this.tiles_.every(function(tile) {
+ tile.layerId === tile0.layerId;
+ });
+ if (allSameLayer) {
+ return tile0.layerId;
+ }
+ return undefined;
+ },
+
+ get extraHighlightsByLayerId() {
+ const highlights = {};
+ this.tiles_.forEach(function(tile, i) {
+ if (highlights[tile.layerId] === undefined) {
+ highlights[tile.layerId] = [];
+ }
+ const slice = this.slices_[i];
+ highlights[tile.layerId].push({
+ colorKey: slice.title,
+ rect: tile.layerRect
+ });
+ }, this);
+ return highlights;
+ },
+
+ createAnalysis() {
+ const sel = new tr.model.EventSet();
+ this.slices_.forEach(function(slice) {
+ sel.push(slice);
+ });
+
+ let analysis;
+ if (sel.length === 1) {
+ analysis = document.createElement('tr-ui-a-single-event-sub-view');
+ } else {
+ analysis = document.createElement('tr-ui-e-chrome-cc-raster-task-view');
+ }
+ analysis.selection = sel;
+ return analysis;
+ },
+
+ findEquivalent(lthi) {
+ // Raster tasks are only valid in one LTHI.
+ return undefined;
+ },
+
+ // RasterTaskSelection specific stuff follows.
+ get containingSnapshot() {
+ return this.tiles_[0].containingSnapshot;
+ }
+ };
+
+ return {
+ RasterTaskSelection,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_selection_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_selection_test.html
new file mode 100644
index 00000000000..d95a7135d37
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_selection_test.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/raster_task_selection.html">
+
+<script src="/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js">
+</script>
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('basic', function() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = m.processes[1];
+ const rasterTasks = p.threads[1].sliceGroup.slices.filter(function(slice) {
+ return slice.title === 'RasterTask';
+ });
+
+ let selection = new tr.model.EventSet();
+ selection.push(rasterTasks[0]);
+ selection.push(rasterTasks[1]);
+
+ assert.isTrue(tr.ui.e.chrome.cc.RasterTaskSelection.supports(selection));
+ selection = new tr.ui.e.chrome.cc.RasterTaskSelection(selection);
+ const highlights = selection.extraHighlightsByLayerId;
+ assert.lengthOf(Object.keys(highlights), 1);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view.html
new file mode 100644
index 00000000000..a5f7f5d806c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view.html
@@ -0,0 +1,222 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/extras/chrome/cc/raster_task.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html">
+<link rel="import" href="/tracing/ui/base/table.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/selection.html">
+<link rel="import" href="/tracing/value/ui/scalar_span.html">
+
+<dom-module id='tr-ui-e-chrome-cc-raster-task-view'>
+ <template>
+ <style>
+ :host {
+ display: flex;
+ flex-direction: column;
+ }
+ #heading {
+ flex: 0 0 auto;
+ }
+ tr-ui-b-table {
+ font-size: 12px;
+ }
+ </style>
+
+ <div id="heading">
+ Rasterization costs in
+ <tr-ui-a-analysis-link id="link"></tr-ui-a-analysis-link>
+ </div>
+ <tr-ui-b-table id="content"></tr-ui-b-table>
+ </template>
+</dom-module>
+<script>
+'use strict';
+Polymer({
+ is: 'tr-ui-e-chrome-cc-raster-task-view',
+
+ created() {
+ this.selection_ = undefined;
+ },
+
+ set selection(selection) {
+ this.selection_ = selection;
+
+ this.updateContents_();
+ },
+
+ updateColumns_(hadCpuDurations) {
+ const timeSpanConfig = {
+ unit: tr.b.Unit.byName.timeDurationInMs,
+ ownerDocument: this.ownerDocument
+ };
+
+ const columns = [
+ {
+ title: 'Layer',
+ value(row) {
+ if (row.isTotals) return 'Totals';
+ if (row.layer) {
+ const linkEl = document.createElement('tr-ui-a-analysis-link');
+ linkEl.setSelectionAndContent(
+ function() {
+ return new tr.ui.e.chrome.cc.LayerSelection(row.layer);
+ },
+ 'Layer ' + row.layerId);
+ return linkEl;
+ }
+ return 'Layer ' + row.layerId;
+ },
+ width: '250px'
+ },
+ {
+ title: 'Num Tiles',
+ value(row) { return row.numTiles; },
+ cmp(a, b) { return a.numTiles - b.numTiles; }
+ },
+ {
+ title: 'Num Analysis Tasks',
+ value(row) { return row.numAnalysisTasks; },
+ cmp(a, b) {
+ return a.numAnalysisTasks - b.numAnalysisTasks;
+ }
+ },
+ {
+ title: 'Num Raster Tasks',
+ value(row) { return row.numRasterTasks; },
+ cmp(a, b) { return a.numRasterTasks - b.numRasterTasks; }
+ },
+ {
+ title: 'Wall Duration (ms)',
+ value(row) {
+ return tr.v.ui.createScalarSpan(row.duration, timeSpanConfig);
+ },
+ cmp(a, b) { return a.duration - b.duration; }
+ }
+ ];
+
+ if (hadCpuDurations) {
+ columns.push({
+ title: 'CPU Duration (ms)',
+ value(row) {
+ return tr.v.ui.createScalarSpan(row.cpuDuration, timeSpanConfig);
+ },
+ cmp(a, b) { return a.cpuDuration - b.cpuDuration; }
+ });
+ }
+
+ let colWidthPercentage;
+ if (columns.length === 1) {
+ colWidthPercentage = '100%';
+ } else {
+ colWidthPercentage = (100 / (columns.length - 1)).toFixed(3) + '%';
+ }
+
+ for (let i = 1; i < columns.length; i++) {
+ columns[i].width = colWidthPercentage;
+ }
+
+ this.$.content.tableColumns = columns;
+ this.$.content.sortColumnIndex = columns.length - 1;
+ },
+
+ updateContents_() {
+ const table = this.$.content;
+
+ if (this.selection_.length === 0) {
+ this.$.link.setSelectionAndContent(undefined, '');
+ table.tableRows = [];
+ table.footerRows = [];
+ table.rebuild();
+ return;
+ }
+ // LTHI link.
+ const lthi = tr.e.cc.getTileFromRasterTaskSlice(
+ tr.b.getFirstElement(this.selection_)).containingSnapshot;
+ this.$.link.setSelectionAndContent(function() {
+ return new tr.model.EventSet(lthi);
+ }, lthi.userFriendlyName);
+
+ // Get costs by layer.
+ const costsByLayerId = {};
+ function getCurrentCostsForLayerId(tile) {
+ const layerId = tile.layerId;
+ const lthi = tile.containingSnapshot;
+ let layer;
+ if (lthi.activeTree) {
+ layer = lthi.activeTree.findLayerWithId(layerId);
+ }
+ if (layer === undefined && lthi.pendingTree) {
+ layer = lthi.pendingTree.findLayerWithId(layerId);
+ }
+ if (costsByLayerId[layerId] === undefined) {
+ costsByLayerId[layerId] = {
+ layerId,
+ layer,
+ numTiles: 0,
+ numAnalysisTasks: 0,
+ numRasterTasks: 0,
+ duration: 0,
+ cpuDuration: 0
+ };
+ }
+ return costsByLayerId[layerId];
+ }
+
+ let totalDuration = 0;
+ let totalCpuDuration = 0;
+ let totalNumAnalyzeTasks = 0;
+ let totalNumRasterizeTasks = 0;
+ let hadCpuDurations = false;
+
+ const tilesThatWeHaveSeen = {};
+
+ this.selection_.forEach(function(slice) {
+ const tile = tr.e.cc.getTileFromRasterTaskSlice(slice);
+ const curCosts = getCurrentCostsForLayerId(tile);
+
+ if (!tilesThatWeHaveSeen[tile.objectInstance.id]) {
+ tilesThatWeHaveSeen[tile.objectInstance.id] = true;
+ curCosts.numTiles += 1;
+ }
+
+ if (tr.e.cc.isSliceDoingAnalysis(slice)) {
+ curCosts.numAnalysisTasks += 1;
+ totalNumAnalyzeTasks += 1;
+ } else {
+ curCosts.numRasterTasks += 1;
+ totalNumRasterizeTasks += 1;
+ }
+ curCosts.duration += slice.duration;
+ totalDuration += slice.duration;
+ if (slice.cpuDuration !== undefined) {
+ curCosts.cpuDuration += slice.cpuDuration;
+ totalCpuDuration += slice.cpuDuration;
+ hadCpuDurations = true;
+ }
+ });
+
+ // Apply to the table.
+ this.updateColumns_(hadCpuDurations);
+ table.tableRows = Object.values(costsByLayerId);
+ table.rebuild();
+
+ // Footer.
+ table.footerRows = [
+ {
+ isTotals: true,
+ numTiles: Object.keys(tilesThatWeHaveSeen).length,
+ numAnalysisTasks: totalNumAnalyzeTasks,
+ numRasterTasks: totalNumRasterizeTasks,
+ duration: totalDuration,
+ cpuDuration: totalCpuDuration
+ }
+ ];
+ }
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view_test.html
new file mode 100644
index 00000000000..56767cfcc89
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/raster_task_view_test.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/base/event_target.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/ui/analysis/analysis_view.html">
+<link rel="import" href="/tracing/ui/base/deep_utils.html">
+<link rel="import" href="/tracing/ui/brushing_state_controller.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/layer_tree_host_impl_view.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/raster_task_selection.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/raster_task_view.html">
+
+<script src="/tracing/extras/chrome/cc/layer_tree_host_impl_test_data.js">
+</script>
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function createSelection() {
+ const m = tr.c.TestUtils.newModelWithEvents([g_catLTHIEvents]);
+ const p = m.processes[1];
+ const rasterTasks = p.threads[1].sliceGroup.slices.filter(function(slice) {
+ return slice.title === 'RasterTask' || slice.title === 'AnalyzeTask';
+ });
+
+ const selection = new tr.model.EventSet();
+ selection.model = m;
+
+ selection.push(rasterTasks[0]);
+ selection.push(rasterTasks[1]);
+ return selection;
+ }
+
+ test('basic', function() {
+ const selection = createSelection();
+ const view = document.createElement('tr-ui-e-chrome-cc-raster-task-view');
+ view.selection = selection;
+ this.addHTMLOutput(view);
+ });
+
+ test('analysisViewIntegration', function() {
+ const selection = createSelection();
+
+ const timelineView = {model: selection.model};
+ const brushingStateController =
+ new tr.c.BrushingStateController(timelineView);
+
+ const analysisEl = document.createElement('tr-ui-a-analysis-view');
+ analysisEl.brushingStateController = brushingStateController;
+ brushingStateController.changeSelectionFromTimeline(selection);
+
+ assert.isDefined(Polymer.dom(analysisEl).querySelector(
+ 'tr-ui-e-chrome-cc-raster-task-view'));
+
+ const sv = tr.ui.b.findDeepElementMatching(
+ analysisEl, 'tr-ui-a-multi-thread-slice-sub-view');
+ assert.isTrue(sv.requiresTallView);
+ this.addHTMLOutput(analysisEl);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/selection.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/selection.html
new file mode 100644
index 00000000000..2794540e115
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/selection.html
@@ -0,0 +1,304 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/ui/analysis/generic_object_view.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ function Selection() {
+ this.selectionToSetIfClicked = undefined;
+ }
+ Selection.prototype = {
+ /**
+ * When two things are picked in the UI, one must occasionally tie-break
+ * between them to decide what was really clicked. Things with higher
+ * specicifity will win.
+ */
+ get specicifity() {
+ throw new Error('Not implemented');
+ },
+
+ /**
+ * If a selection is related to a specific layer, then this returns the
+ * layerId of that layer. If the selection is not related to a layer, for
+ * example if the device viewport is selected, then this returns undefined.
+ */
+ get associatedLayerId() {
+ throw new Error('Not implemented');
+ },
+
+ /**
+ * If a selection is related to a specific render pass, then this returns
+ * the layerId of that layer. If the selection is not related to a layer,
+ * for example if the device viewport is selected, then this returns
+ * undefined.
+ */
+ get associatedRenderPassId() {
+ throw new Error('Not implemented');
+ },
+
+
+ get highlightsByLayerId() {
+ return {};
+ },
+
+ /**
+ * Called when the selection is made active in the layer view. Must return
+ * an HTMLElement that explains this selection in detail.
+ */
+ createAnalysis() {
+ throw new Error('Not implemented');
+ },
+
+ /**
+ * Should try to create the equivalent selection in the provided LTHI,
+ * or undefined if it can't be done.
+ */
+ findEquivalent(lthi) {
+ throw new Error('Not implemented');
+ }
+ };
+
+ /**
+ * @constructor
+ */
+ function RenderPassSelection(renderPass, renderPassId) {
+ if (!renderPass || (renderPassId === undefined)) {
+ throw new Error('Render pass (with id) is required');
+ }
+ this.renderPass_ = renderPass;
+ this.renderPassId_ = renderPassId;
+ }
+
+ RenderPassSelection.prototype = {
+ __proto__: Selection.prototype,
+
+ get specicifity() {
+ return 1;
+ },
+
+ get associatedLayerId() {
+ return undefined;
+ },
+
+ get associatedRenderPassId() {
+ return this.renderPassId_;
+ },
+
+ get renderPass() {
+ return this.renderPass_;
+ },
+
+ createAnalysis() {
+ const dataView = document.createElement(
+ 'tr-ui-a-generic-object-view-with-label');
+ dataView.label = 'RenderPass ' + this.renderPassId_;
+ dataView.object = this.renderPass_.args;
+ return dataView;
+ },
+
+ get title() {
+ return this.renderPass_.objectInstance.typeName;
+ }
+ };
+
+ /**
+ * @constructor
+ */
+ function LayerSelection(layer) {
+ if (!layer) {
+ throw new Error('Layer is required');
+ }
+ this.layer_ = layer;
+ }
+
+ LayerSelection.prototype = {
+ __proto__: Selection.prototype,
+
+ get specicifity() {
+ return 1;
+ },
+
+ get associatedLayerId() {
+ return this.layer_.layerId;
+ },
+
+ get associatedRenderPassId() {
+ return undefined;
+ },
+
+ get layer() {
+ return this.layer_;
+ },
+
+ createAnalysis() {
+ const dataView = document.createElement(
+ 'tr-ui-a-generic-object-view-with-label');
+ dataView.label = 'Layer ' + this.layer_.layerId;
+ if (this.layer_.usingGpuRasterization) {
+ dataView.label += ' (GPU-rasterized)';
+ }
+ dataView.object = this.layer_.args;
+ return dataView;
+ },
+
+ get title() {
+ return this.layer_.objectInstance.typeName;
+ },
+
+ findEquivalent(lthi) {
+ const layer = lthi.activeTree.findLayerWithId(this.layer_.layerId) ||
+ lthi.pendingTree.findLayerWithId(this.layer_.layerId);
+ if (!layer) return undefined;
+ return new LayerSelection(layer);
+ }
+ };
+
+ /**
+ * @constructor
+ */
+ function TileSelection(tile, opt_data) {
+ this.tile_ = tile;
+ this.data_ = opt_data || {};
+ }
+
+ TileSelection.prototype = {
+ __proto__: Selection.prototype,
+
+ get specicifity() {
+ return 2;
+ },
+
+ get associatedLayerId() {
+ return this.tile_.layerId;
+ },
+
+ get highlightsByLayerId() {
+ const highlights = {};
+ highlights[this.tile_.layerId] = [
+ {
+ colorKey: this.tile_.objectInstance.typeName,
+ rect: this.tile_.layerRect
+ }
+ ];
+ return highlights;
+ },
+
+ createAnalysis() {
+ const analysis = document.createElement(
+ 'tr-ui-a-generic-object-view-with-label');
+ analysis.label = 'Tile ' + this.tile_.objectInstance.id + ' on layer ' +
+ this.tile_.layerId;
+ if (this.data_) {
+ analysis.object = {
+ moreInfo: this.data_,
+ tileArgs: this.tile_.args
+ };
+ } else {
+ analysis.object = this.tile_.args;
+ }
+ return analysis;
+ },
+
+ findEquivalent(lthi) {
+ const tileInstance = this.tile_.tileInstance;
+ if (lthi.ts < tileInstance.creationTs ||
+ lthi.ts >= tileInstance.deletionTs) {
+ return undefined;
+ }
+ const tileSnapshot = tileInstance.getSnapshotAt(lthi.ts);
+ if (!tileSnapshot) return undefined;
+ return new TileSelection(tileSnapshot);
+ }
+ };
+
+ /**
+ * @constructor
+ */
+ function LayerRectSelection(layer, rectType, rect, opt_data) {
+ this.layer_ = layer;
+ this.rectType_ = rectType;
+ this.rect_ = rect;
+ this.data_ = opt_data !== undefined ? opt_data : rect;
+ }
+
+ LayerRectSelection.prototype = {
+ __proto__: Selection.prototype,
+
+ get specicifity() {
+ return 2;
+ },
+
+ get associatedLayerId() {
+ return this.layer_.layerId;
+ },
+
+
+ get highlightsByLayerId() {
+ const highlights = {};
+ highlights[this.layer_.layerId] = [
+ {
+ colorKey: this.rectType_,
+ rect: this.rect_
+ }
+ ];
+ return highlights;
+ },
+
+ createAnalysis() {
+ const analysis = document.createElement(
+ 'tr-ui-a-generic-object-view-with-label');
+ analysis.label = this.rectType_ + ' on layer ' + this.layer_.layerId;
+ analysis.object = this.data_;
+ return analysis;
+ },
+
+ findEquivalent(lthi) {
+ return undefined;
+ }
+ };
+
+ /**
+ * @constructor
+ */
+ function AnimationRectSelection(layer, rect) {
+ this.layer_ = layer;
+ this.rect_ = rect;
+ }
+
+ AnimationRectSelection.prototype = {
+ __proto__: Selection.prototype,
+
+ get specicifity() {
+ return 0;
+ },
+
+ get associatedLayerId() {
+ return this.layer_.layerId;
+ },
+
+ createAnalysis() {
+ const analysis = document.createElement(
+ 'tr-ui-a-generic-object-view-with-label');
+ analysis.label = 'Animation Bounds of layer ' + this.layer_.layerId;
+ analysis.object = this.rect_;
+ return analysis;
+ }
+ };
+
+ return {
+ Selection,
+ RenderPassSelection,
+ LayerSelection,
+ TileSelection,
+ LayerRectSelection,
+ AnimationRectSelection,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/tile_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/tile_view.html
new file mode 100644
index 00000000000..ad1f633f334
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/cc/tile_view.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/cc/tile.html">
+<link rel="import" href="/tracing/ui/analysis/generic_object_view.html">
+<link rel="import" href="/tracing/ui/analysis/object_snapshot_view.html">
+
+<script>
+
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.cc', function() {
+ /*
+ * Displays a tile in a human readable form.
+ * @constructor
+ */
+ const TileSnapshotView = tr.ui.b.define(
+ 'tr-ui-e-chrome-cc-tile-snapshot-view',
+ tr.ui.analysis.ObjectSnapshotView);
+
+ TileSnapshotView.prototype = {
+ __proto__: tr.ui.analysis.ObjectSnapshotView.prototype,
+
+ decorate() {
+ Polymer.dom(this).classList.add('tr-ui-e-chrome-cc-tile-snapshot-view');
+ this.layerTreeView_ =
+ new tr.ui.e.chrome.cc.LayerTreeHostImplSnapshotView();
+ Polymer.dom(this).appendChild(this.layerTreeView_);
+ },
+
+ updateContents() {
+ const tile = this.objectSnapshot_;
+ const layerTreeHostImpl = tile.containingSnapshot;
+ if (!layerTreeHostImpl) return;
+
+ this.layerTreeView_.objectSnapshot = layerTreeHostImpl;
+ this.layerTreeView_.selection = new tr.ui.e.chrome.cc.TileSelection(tile);
+ }
+ };
+
+ tr.ui.analysis.ObjectSnapshotView.register(
+ TileSnapshotView,
+ {
+ typeName: 'cc::Tile',
+ showInTrackView: false
+ });
+
+ return {
+ TileSnapshotView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/codesearch.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/codesearch.html
new file mode 100644
index 00000000000..af6c79447bc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/codesearch.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+Copyright 2018 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.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+
+<dom-module id='tr-ui-e-chrome-codesearch'>
+ <template>
+ <style>
+ :host {
+ white-space: nowrap;
+ }
+ #codesearchLink {
+ font-size: x-small;
+ margin-left: 20px;
+ text-decoration: none;
+ }
+ </style>
+ <a id="codesearchLink" target=_blank on-click="onClick">&#x1F50D;</a>
+ </template>
+</dom-module>
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome', function() {
+ Polymer({
+ is: 'tr-ui-e-chrome-codesearch',
+
+ set searchPhrase(phrase) {
+ const link = Polymer.dom(this.$.codesearchLink);
+ const codeSearchURL =
+ 'https://cs.chromium.org/search/?sq=package:chromium&type=cs&q=';
+ link.setAttribute('href', codeSearchURL + encodeURIComponent(phrase));
+ },
+
+ onClick(clickEvent) {
+ // Let the event trigger the default action of following the link. Stop
+ // the propagation of the event here, so that subsequent handlers do not
+ // intercept the clicks.
+ clickEvent.stopPropagation();
+ }
+ });
+
+ return {};
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/gpu.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/gpu.html
new file mode 100644
index 00000000000..ec7991b6640
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/gpu.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/gpu/gpu_async_slice.html">
+<link rel="import" href="/tracing/extras/chrome/gpu/state.html">
+<link rel="import" href="/tracing/ui/extras/chrome/gpu/state_view.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/images/checkerboard.png b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/images/checkerboard.png
new file mode 100644
index 00000000000..8ea9bc726bb
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/images/checkerboard.png
Binary files differ
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/state_view.css b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/state_view.css
new file mode 100644
index 00000000000..7c2c34787dc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/state_view.css
@@ -0,0 +1,15 @@
+/* Copyright (c) 2013 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.
+ */
+
+.tr-ui-e-chrome-gpu-state-snapshot-view {
+ background: url('./images/checkerboard.png');
+ display: flex;
+ overflow: auto;
+}
+
+.tr-ui-e-chrome-gpu-state-snapshot-view img {
+ display: block;
+ margin: 16px auto 16px auto;
+}
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/state_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/state_view.html
new file mode 100644
index 00000000000..ba6c345be5f
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/gpu/state_view.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="stylesheet" href="/tracing/ui/extras/chrome/gpu/state_view.css">
+
+<link rel="import" href="/tracing/ui/analysis/object_snapshot_view.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.chrome.gpu', function() {
+ /*
+ * Displays a GPU state snapshot in a human readable form.
+ * @constructor
+ */
+ const StateSnapshotView = tr.ui.b.define(
+ 'tr-ui-e-chrome-gpu-state-snapshot-view',
+ tr.ui.analysis.ObjectSnapshotView);
+
+ StateSnapshotView.prototype = {
+ __proto__: tr.ui.analysis.ObjectSnapshotView.prototype,
+
+ decorate() {
+ Polymer.dom(this).classList.add('tr-ui-e-chrome-gpu-state-snapshot-view');
+ this.screenshotImage_ = document.createElement('img');
+ Polymer.dom(this).appendChild(this.screenshotImage_);
+ },
+
+ updateContents() {
+ if (this.objectSnapshot_ && this.objectSnapshot_.screenshot) {
+ this.screenshotImage_.src = 'data:image/png;base64,' +
+ this.objectSnapshot_.screenshot;
+ }
+ }
+ };
+ tr.ui.analysis.ObjectSnapshotView.register(
+ StateSnapshotView,
+ {typeName: 'gpu::State'});
+
+ return {
+ StateSnapshotView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/layout_tree_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/layout_tree_sub_view.html
new file mode 100644
index 00000000000..db80ef75afa
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome/layout_tree_sub_view.html
@@ -0,0 +1,229 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 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.
+-->
+
+<link rel="import" href="/tracing/extras/chrome/layout_tree.html">
+<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html">
+
+<dom-module id='tr-ui-a-layout-tree-sub-view'>
+ <template>
+ <style>
+ tr-ui-b-table {
+ font-size: 12px;
+ }
+ </style>
+ <div id="content"></div>
+ </template>
+</dom-module>
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.analysis', function() {
+ Polymer({
+ is: 'tr-ui-a-layout-tree-sub-view',
+ behaviors: ['tr-ui-a-sub-view'],
+
+ set selection(selection) {
+ this.currentSelection_ = selection;
+ this.updateContents_();
+ },
+
+ get selection() {
+ return this.currentSelection_;
+ },
+
+ updateContents_() {
+ this.set('$.content.textContent', '');
+ if (!this.currentSelection_) return;
+
+ const columns = [
+ {
+ title: 'Tag/Name',
+ value(layoutObject) {
+ return layoutObject.tag || ':' + layoutObject.name;
+ }
+ },
+
+ {
+ title: 'htmlId',
+ value(layoutObject) {
+ return layoutObject.htmlId || '';
+ }
+ },
+
+ {
+ title: 'classNames',
+ value(layoutObject) {
+ return layoutObject.classNames || '';
+ }
+ },
+
+ {
+ title: 'reasons',
+ value(layoutObject) {
+ return layoutObject.needsLayoutReasons.join(', ');
+ }
+ },
+
+ {
+ title: 'width',
+ value(layoutObject) {
+ return layoutObject.absoluteRect.width;
+ }
+ },
+
+ {
+ title: 'height',
+ value(layoutObject) {
+ return layoutObject.absoluteRect.height;
+ }
+ },
+
+ {
+ title: 'absX',
+ value(layoutObject) {
+ return layoutObject.absoluteRect.left;
+ }
+ },
+
+ {
+ title: 'absY',
+ value(layoutObject) {
+ return layoutObject.absoluteRect.top;
+ }
+ },
+
+ {
+ title: 'relX',
+ value(layoutObject) {
+ return layoutObject.relativeRect.left;
+ }
+ },
+
+ {
+ title: 'relY',
+ value(layoutObject) {
+ return layoutObject.relativeRect.top;
+ }
+ },
+
+ {
+ title: 'float',
+ value(layoutObject) {
+ return layoutObject.isFloat ? 'float' : '';
+ }
+ },
+
+ {
+ title: 'positioned',
+ value(layoutObject) {
+ return layoutObject.isPositioned ? 'positioned' : '';
+ }
+ },
+
+ {
+ title: 'relative',
+ value(layoutObject) {
+ return layoutObject.isRelativePositioned ? 'relative' : '';
+ }
+ },
+
+ {
+ title: 'sticky',
+ value(layoutObject) {
+ return layoutObject.isStickyPositioned ? 'sticky' : '';
+ }
+ },
+
+ {
+ title: 'anonymous',
+ value(layoutObject) {
+ return layoutObject.isAnonymous ? 'anonymous' : '';
+ }
+ },
+
+ {
+ title: 'row',
+ value(layoutObject) {
+ if (layoutObject.tableRow === undefined) {
+ return '';
+ }
+ return layoutObject.tableRow;
+ }
+ },
+
+ {
+ title: 'col',
+ value(layoutObject) {
+ if (layoutObject.tableCol === undefined) {
+ return '';
+ }
+ return layoutObject.tableCol;
+ }
+ },
+
+ {
+ title: 'rowSpan',
+ value(layoutObject) {
+ if (layoutObject.tableRowSpan === undefined) {
+ return '';
+ }
+ return layoutObject.tableRowSpan;
+ }
+ },
+
+ {
+ title: 'colSpan',
+ value(layoutObject) {
+ if (layoutObject.tableColSpan === undefined) {
+ return '';
+ }
+ return layoutObject.tableColSpan;
+ }
+ },
+
+ {
+ title: 'address',
+ value(layoutObject) {
+ return layoutObject.id.toString(16);
+ }
+ }
+ ];
+
+ const table = this.ownerDocument.createElement('tr-ui-b-table');
+ table.defaultExpansionStateCallback = function(
+ layoutObject, parentLayoutObject) {
+ return true;
+ };
+ table.subRowsPropertyName = 'childLayoutObjects';
+ table.tableColumns = columns;
+ table.tableRows = this.currentSelection_.map(function(snapshot) {
+ return snapshot.rootLayoutObject;
+ });
+ table.rebuild();
+ Polymer.dom(this.$.content).appendChild(table);
+ },
+ });
+
+ return {};
+});
+tr.ui.analysis.AnalysisSubView.register(
+ 'tr-ui-a-layout-tree-sub-view',
+ tr.e.chrome.LayoutTreeSnapshot,
+ {
+ multi: false,
+ title: 'Layout Tree',
+ });
+
+tr.ui.analysis.AnalysisSubView.register(
+ 'tr-ui-a-layout-tree-sub-view',
+ tr.e.chrome.LayoutTreeSnapshot,
+ {
+ multi: true,
+ title: 'Layout Trees',
+ });
+</script>
+
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome_config.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome_config.html
new file mode 100644
index 00000000000..cea42a2d78c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/chrome_config.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<!--
+The chrome config is heavily used:
+ - chrome://tracing,
+ - trace2html, which in turn implies
+ - adb_profile_chrome
+ - telemetry
+-->
+
+<!--
+TODO(charliea): Make all UI files depend on tracing/ui/base/base.html in the
+same way that all non-UI files depend on tracing/base/base.html. Enforce this
+dependency with a presubmit.
+-->
+<link rel="import" href="/tracing/ui/base/base.html" data-suppress-import-order>
+
+<link rel="import" href="/tracing/extras/chrome_config.html">
+<link rel="import" href="/tracing/ui/base/ui.html">
+<link rel="import" href="/tracing/ui/extras/chrome/cc/cc.html">
+<link rel="import" href="/tracing/ui/extras/chrome/codesearch.html">
+<link rel="import" href="/tracing/ui/extras/chrome/gpu/gpu.html">
+<link rel="import" href="/tracing/ui/extras/chrome/layout_tree_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/side_panel/frame_data_side_panel.html">
+<link rel="import" href="/tracing/ui/extras/side_panel/input_latency_side_panel.html">
+<link rel="import" href="/tracing/ui/extras/system_stats/system_stats.html">
+<link rel="import" href="/tracing/ui/extras/v8_config.html">
+<link rel="import" href="/tracing/ui/timeline_view.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/html_results.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/html_results.html
new file mode 100644
index 00000000000..cb3d41a88a0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/html_results.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 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.
+-->
+
+<link rel="import" href="/tracing/ui/base/table.html">
+
+<!--
+This class tries to (simply) copy the telemetry Results object, but outputs
+directly to an HTML table. It takes things that look like Telemetry values,
+and updates the table internally.
+-->
+<dom-module id='tr-ui-e-deep-reports-html-results'>
+ <template>
+ <style>
+ :host {
+ display: flex;
+ font-size: 12px;
+ }
+ </style>
+ <tr-ui-b-table id="table"></tr-ui-b-table>
+ </template>
+</dom-module>
+
+<script>
+'use strict';
+
+Polymer({
+ is: 'tr-ui-e-deep-reports-html-results',
+
+ created() {
+ this.hasColumnNamed_ = {};
+ this.pageToRowMap_ = new WeakMap();
+ },
+
+ ready() {
+ const table = this.$.table;
+ table.tableColumns = [
+ {
+ title: 'Label',
+ value(row) { return row.label; },
+ width: '350px'
+ }
+ ];
+ this.clear();
+ },
+
+ clear() {
+ this.$.table.tableRows = [];
+ },
+
+ addColumnIfNeeded_(columnName) {
+ if (this.hasColumnNamed_[columnName]) return;
+
+ this.hasColumnNamed_[columnName] = true;
+
+ const column = {
+ title: columnName,
+ value(row) {
+ if (row[columnName] === undefined) return '';
+ return row[columnName];
+ }
+ };
+
+ const columns = this.$.table.tableColumns;
+ columns.push(column);
+
+ // Update widths.
+ let colWidthPercentage;
+ if (columns.length === 1) {
+ colWidthPercentage = '100%';
+ } else {
+ colWidthPercentage = (100 / (columns.length - 1)).toFixed(3) + '%';
+ }
+
+ for (let i = 1; i < columns.length; i++) {
+ columns[i].width = colWidthPercentage;
+ }
+
+ this.$.table.tableColumns = columns;
+ },
+
+ getRowForPage_(page) {
+ if (!this.pageToRowMap_.has(page)) {
+ const i = page.url.lastIndexOf('/');
+ const baseName = page.url.substring(i + 1);
+
+ const link = document.createElement('a');
+ link.href = 'trace_viewer.html#' + page.url;
+ Polymer.dom(link).textContent = baseName;
+
+ const row = {
+ label: link,
+ value: '',
+ subRows: [],
+ isExpanded: true
+ };
+ this.$.table.tableRows.push(row);
+ this.pageToRowMap_.set(page, row);
+
+ // Kick table rebuild.
+ this.$.table.tableRows = this.$.table.tableRows;
+ }
+ return this.pageToRowMap_.get(page);
+ },
+
+ addValue(value) {
+ /* Value is expected to be a scalar telemetry-style Value. */
+ if (value.type !== 'scalar') {
+ throw new Error('wat');
+ }
+
+ this.addColumnIfNeeded_(value.name);
+ const rowForPage = this.getRowForPage_(value.page);
+ rowForPage[value.name] = value.value;
+
+ // Kick table rebuild.
+ this.$.table.tableRows = this.$.table.tableRows;
+ }
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/main.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/main.html
new file mode 100644
index 00000000000..9cf2d7f6c79
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/main.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 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.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/base/xhr.html">
+<link rel="import" href="/tracing/ui/extras/deep_reports/scalar_value.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.deep_reports', function() {
+ /**
+ * Runs deep reports on the provided files, and pushes telemetry-style
+ * values to the results object.
+ */
+ function main(results, filesInDir) {
+ let lastP = new Promise(function(resolve) { resolve(); });
+
+ filesInDir.forEach(function(filename) {
+ // TODO(nduca): Make this like telemetry page.
+ const page = {
+ url: filename
+ };
+ lastP = lastP.then(function() {
+ return loadModelFromFileAsync(filename);
+ });
+ lastP = lastP.then(function(model) {
+ processModel(results, page, model);
+ });
+ });
+ return lastP;
+ }
+
+ function loadModelFromFileAsync(filename) {
+ return tr.b.getAsync(filename).then(function(trace) {
+ const io = new tr.ImportOptions();
+ io.shiftWorldToZero = true;
+ io.pruneEmptyContainers = false;
+
+ const m = new tr.Model();
+ try {
+ m.importTraces([trace], io);
+ } catch (e) {
+ throw new Error('While loading ' + filename + ' got: ' + e.toString());
+ }
+ return m;
+ });
+ }
+
+ function processModel(results, page, model) {
+ results.addValue(
+ new tr.ui.e.deep_reports.ScalarValue(
+ page, 'numRailIRs', 'ms', model.userModel.expectations.length));
+ }
+
+ return {
+ main
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/scalar_value.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/scalar_value.html
new file mode 100644
index 00000000000..cb550c35b4b
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/deep_reports/scalar_value.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 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.
+-->
+
+<link rel="import" href="/tracing/base/base.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.deep_reports', function() {
+ function ScalarValue(page, name, units, value,
+ opt_important, opt_description) {
+ this.type = 'scalar';
+ this.page = page;
+ this.name = name;
+ this.units = units;
+ this.value = value;
+ this.important = opt_important !== undefined ? opt_important : false;
+ this.description = opt_description || '';
+ }
+ ScalarValue.fromDict = function(page, dict) {
+ if (dict.type !== 'scalar') {
+ throw new Error('wat');
+ }
+ const v = new ScalarValue(page, dict.name, dict.units, dict.value);
+ v.important = dict.important;
+ v.description = dict.description;
+ v.value = dict.value;
+ return v;
+ };
+
+ ScalarValue.prototype = {
+
+ };
+
+ return {
+ ScalarValue,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comment_element.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comment_element.html
new file mode 100644
index 00000000000..1748dd12b88
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comment_element.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 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.
+-->
+
+<dom-module id='tr-ui-e-drive-comment-element'>
+ <template>
+ <style>
+ :host {
+ display: block;
+ }
+ #comment-area {
+ display: flex;
+ flex-direction: column;
+ border-top: 1px solid #e8e8e8;
+ background-color: white;
+ padding: 6px;
+ margin-bottom: 4px;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.3);
+ border-radius: 2px;
+ font-size: small;
+ }
+ #comment-header {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin-bottom: 8px;
+ }
+ #comment-header-text {
+ display: flex;
+ flex-direction: column;
+ padding-left: 10px;
+ }
+ #comment-img {
+ width: 32px;
+ height: 32px;
+ }
+ #comment-text-author {
+ padding-bottom: 2px;
+ }
+ #comment-date {
+ color: #777;
+ font-size: 11px;
+ }
+ #comment-content {
+ word-wrap: break-word;
+ }
+ </style>
+ <div id="comment-area">
+ <div id="comment-header">
+ <img id="comment-img" src="{{ comment.author.picture.url }}" />
+ <div id="comment-header-text">
+ <div id="comment-text-author">{{ comment.author.displayName }}</div>
+ <div id="comment-date">{{ createdDate }}</div>
+ </div>
+ </div>
+ <div id="comment-content">{{_computeCommentContentPrefix( comment)}}
+ {{ comment.content }}</div>
+ </div>
+ </template>
+</dom-module>
+<script>
+'use strict';
+Polymer({
+ is: 'tr-ui-e-drive-comment-element',
+
+ properties: {
+ comment: {
+ type: String,
+ observer: '_commentChanged'
+ }
+ },
+
+ _commentChanged() {
+ this.createdDate = new Date(this.comment.createdDate).toLocaleString();
+ },
+
+ _computeCommentContentPrefix(comment) {
+ return comment.anchor ? '&#9875;&nbsp;' : '';
+ }
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comments_side_panel.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comments_side_panel.html
new file mode 100644
index 00000000000..8f52839029d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comments_side_panel.html
@@ -0,0 +1,185 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 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.
+-->
+
+<link rel="import" href="/tracing/ui/extras/drive/comment_element.html">
+<link rel="import" href="/tracing/ui/side_panel/side_panel_registry.html">
+
+<dom-module id='tr-ui-e-drive-comments-side-panel'>
+ <template>
+ <style>
+ :host {
+ flex-direction: column;
+ display: flex;
+ width: 290px;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ background-color: #eee;
+ }
+ toolbar {
+ flex: 0 0 auto;
+ border-bottom: 1px solid black;
+ display: flex;
+ }
+ result-area {
+ flex: 1 1 auto;
+ display: block;
+ min-height: 0;
+ padding: 4px;
+ }
+ #comments-textarea-container {
+ display: flex;
+ }
+ #commentinput {
+ width: 100%;
+ }
+ </style>
+
+ <toolbar id='toolbar'></toolbar>
+ <result-area id='result_area'>
+ <template is="dom-repeat" items="{{comments_}}" repeat="{{ comment in comments_ }}">
+ <tr-ui-e-drive-comment-element comment="{{comment}}"
+ on-click="commentClick">
+ </tr-ui-e-drive-comment-element>
+ </template>
+ <div id="comments-textarea-container">
+ <textarea id="commentinput" on-focus='textAreaFocus'
+ on-blur='textAreaBlur'
+ on-keypress="textareaKeypress"></textarea>
+ </div>
+ </result-area>
+ </template>
+</dom-module>
+<script>
+'use strict';
+
+Polymer({
+ is: 'tr-ui-e-drive-comments-side-panel',
+ behaviors: [tr.ui.behaviors.SidePanel],
+
+ ready() {
+ this.rangeOfInterest_ = new tr.b.math.Range();
+ this.selection_ = undefined;
+ this.comments_ = [];
+ this.annotationFromComment_ = undefined;
+ this.textAreaFocused = false;
+ },
+
+ setCommentProvider(commentProvider) {
+ this.commentProvider_ = commentProvider;
+ },
+
+ attached() {
+ if (this.commentProvider_ === undefined) {
+ this.commentProvider_ =
+ new tr.ui.e.drive.analysis.DefaultCommentProvider();
+ }
+ this.commentProvider_.attachToElement(this);
+ },
+
+ detached() {
+ this.commentProvider_.detachFromElement();
+ },
+
+ commentClick(event) {
+ const anchor = event.currentTarget.comment.anchor;
+ if (!anchor) return;
+
+ const uiState =
+ JSON.parse(anchor).a[0][tr.ui.e.drive.constants.ANCHOR_NAME];
+
+ const myEvent = new CustomEvent('navigateToUIState', { detail:
+ new tr.ui.b.UIState(new tr.model.Location(uiState.location.xWorld,
+ uiState.location.yComponents),
+ uiState.scaleX)
+ });
+ document.dispatchEvent(myEvent);
+
+ if (this.annotationFromComment_) {
+ this.model.removeAnnotation(this.annotationFromComment_);
+ }
+ const loc = new tr.model.Location(uiState.location.xWorld,
+ uiState.location.yComponents);
+
+ const text = sender.comment.author.displayName + ': ' +
+ sender.comment.content;
+ this.annotationFromComment_ =
+ new tr.model.CommentBoxAnnotation(loc, text);
+ this.model.addAnnotation(this.annotationFromComment_);
+ },
+
+ textareaKeypress(event) {
+ // Check for return key.
+ if (event.keyCode === 13 && !event.ctrlKey) {
+ this.commentProvider_.addComment(this.$.commentinput.value);
+ this.$.commentinput.value = '';
+ }
+ event.stopPropagation();
+ return true;
+ },
+
+ textAreaFocus(event) {
+ this.textAreaFocused = true;
+ },
+
+ textAreaBlur(event) {
+ this.textAreaFocused = false;
+ },
+
+ get rangeOfInterest() {
+ return this.rangeOfInterest_;
+ },
+
+ set rangeOfInterest(rangeOfInterest) {
+ this.rangeOfInterest_ = rangeOfInterest;
+ this.updateContents_();
+ },
+
+ get currentRangeOfInterest() {
+ if (this.rangeOfInterest_.isEmpty) {
+ return this.model_.bounds;
+ }
+ return this.rangeOfInterest_;
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ set model(model) {
+ this.model_ = model;
+ this.updateContents_();
+ },
+
+ set selection(selection) {
+ this.selection_ = selection;
+ },
+
+ updateContents_() {
+ this.commentProvider_.updateComments();
+ },
+
+ supportsModel(m) {
+ if (m === undefined) {
+ return {
+ supported: false,
+ reason: 'Unknown tracing model'
+ };
+ }
+ return {
+ supported: true
+ };
+ },
+
+ get textLabel() {
+ return 'Comments';
+ }
+});
+
+tr.ui.side_panel.SidePanelRegistry.register(function() {
+ return document.createElement('tr-ui-e-drive-comments-side-panel');
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comments_side_panel_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comments_side_panel_test.html
new file mode 100644
index 00000000000..639c1b9b597
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/comments_side_panel_test.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/ui/extras/drive/comments_side_panel.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ function StubCommentProvider() {
+ this.addDummyComment('Lorem ipsum dolor sit amet');
+ this.addDummyComment('consectetur adipiscing elit');
+ this.addDummyComment('sed do eiusmod tempor incididunt ut labore et ' +
+ 'dolore magna aliqua. Ut enim ad minim veniam, quis nostrud ' +
+ 'exercitation ullamco laboris nisi ut aliquip ex ea commodo ' +
+ 'consequat. Duis aute irure dolor in reprehenderit in voluptate ' +
+ 'velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint ' +
+ 'occaecat cupidatat non proident, sunt in culpa qui officia deserunt ' +
+ 'mollit anim id est laborum.');
+ }
+
+ StubCommentProvider.prototype = {
+ comments_: [],
+
+ attachToElement(attachedElement) {
+ this.attachedElement_ = attachedElement;
+ this.updateComments();
+ },
+
+ detachFromElement() {
+ },
+
+ updateComments() {
+ this.attachedElement_.comments_ = this.comments_;
+ },
+
+ addDummyComment(content) {
+ const newComment = {
+ author: {
+ displayName: 'Casper the Friendly Ghost',
+ picture: {
+ url: 'https://lh3.googleusercontent.com/-XdUIqdMkCWA/' +
+ 'AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/s128/photo.jpg'
+ }
+ },
+ createdDate: Date.now(),
+ anchor: (this.comments_.length) % 2 ? 1 : 0,
+ content
+ };
+
+ this.comments_.push(newComment);
+ },
+
+ addComment(body) {
+ this.addDummyComment(body);
+ this.updateComments();
+ }
+ };
+
+ test('instantiate', function() {
+ const panel = document.createElement('tr-ui-e-drive-comments-side-panel');
+ panel.setCommentProvider(new StubCommentProvider);
+ this.addHTMLOutput(panel);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/drive_comment_provider.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/drive_comment_provider.html
new file mode 100644
index 00000000000..42d3706f854
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/drive_comment_provider.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 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.
+-->
+
+<link rel="import" href="/tracing/model/comment_box_annotation.html">
+
+<link rel="import" href="/tracing/ui/extras/drive/comments_side_panel.html">
+<link rel="import" href="/tracing/ui/side_panel/side_panel.html">
+
+<script>
+'use strict';
+
+(function() {
+ function addDriveCommentWithUIState_(text, uiState) {
+ gapi.client.load('drive', 'v2', function() {
+ const request = gapi.client.drive.revisions.get({
+ 'fileId': tr.ui.e.drive.getDriveFileId(),
+ 'revisionId': 'head'
+ });
+ request.execute(function(resp) {
+ const anchorObject = {};
+ anchorObject[tr.ui.e.drive.constants.ANCHOR_NAME] = uiState;
+ let anchor = {
+ 'r': resp.id,
+ 'a': [anchorObject]
+ };
+ anchor = JSON.stringify(anchor);
+ gapi.client.load('drive', 'v2', function() {
+ const request = gapi.client.drive.comments.insert({
+ 'fileId': tr.ui.e.drive.getDriveFileId(),
+ 'resource': {'content': text, anchor}
+ });
+ request.execute();
+ });
+ });
+ });
+ }
+
+ function onCommentWithUIState(e) {
+ addDriveCommentWithUIState_(e.detail.name, e.detail.location);
+ }
+
+ document.addEventListener('commentWithUIState',
+ onCommentWithUIState.bind(this));
+}());
+
+tr.exportTo('tr.ui.e.drive.analysis', function() {
+ function DefaultCommentProvider() { }
+
+ DefaultCommentProvider.prototype = {
+ attachToElement(attachedElement) {
+ this.attachedElement_ = attachedElement;
+ this.commentsCheckTimer_ = setTimeout(this.checkForComments_.bind(this),
+ 5000);
+ },
+
+ detachFromElement() {
+ clearTimeout(this.commentsCheckTimer_);
+ },
+
+ checkForComments_() {
+ this.updateComments();
+ this.commentsCheckTimer_ = setTimeout(this.checkForComments_.bind(this),
+ 5000);
+ },
+
+ updateComments() {
+ gapi.client.load('drive', 'v2', () => {
+ const request = gapi.client.drive.comments.list({
+ 'fileId': tr.ui.e.drive.getDriveFileId()
+ });
+ request.execute(results => {
+ this.attachedElement_.comments_ = results.items;
+ });
+ });
+ },
+
+ addComment(body) {
+ gapi.client.load('drive', 'v2', () => {
+ const request = gapi.client.drive.comments.insert({
+ 'fileId': tr.ui.e.drive.getDriveFileId(),
+ 'resource': {'content': body}
+ });
+ request.execute(resp => {
+ this.updateComments();
+ });
+ });
+ }
+ };
+
+ return {
+ DefaultCommentProvider,
+ };
+});
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/index.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/index.html
new file mode 100644
index 00000000000..270dcccf2fc
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/drive/index.html
@@ -0,0 +1,463 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 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.
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+ <script type="text/javascript" src="https://apis.google.com/js/api.js"></script>
+
+ <link rel="import" href="/components/polymer/polymer.html">
+ <link rel="import" href="/tracing/ui/extras/drive/drive_comment_provider.html">
+ <link rel="import" href="/tracing/ui/extras/full_config.html">
+ <link rel="import" href="/tracing/ui/timeline_view.html">
+
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ }
+ body > x-timeline-view {
+ flex: 1 1 auto;
+ overflow: hidden;
+ position: absolute;
+ top: 0px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ }
+ body > x-timeline-view:focus {
+ outline: none;
+ }
+ nav {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ }
+ #navbar button {
+ height: 24px;
+ padding-bottom: 3px;
+ vertical-align: middle;
+ box-shadow: none;
+ background-color: #4d90fe;
+ background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed);
+ border: 1px solid #3079ed;
+ color: #fff;
+ border-radius: 2px;
+ cursor: default;
+ font-size: 11px;
+ font-weight: bold;
+ text-align: center;
+ white-space: nowrap;
+ line-height: 27px;
+ min-width: 54px;
+ outline: 0px;
+ padding: 0 8px;
+ font: normal 13px arial,sans-serif;
+ margin: 5px;
+ }
+ #collabs {
+ display: flex;
+ flex-direction: row;
+ }
+ .collaborator-div {
+ display: inline-block;
+ vertical-align: middle;
+ min-height: 0;
+ width: 100px;
+ font-size: 11px;
+ font-weight: bold;
+ font: normal 13px arial,sans-serif;
+ margin: 10px;
+ }
+ .collaborator-img {
+ margin: 2px;
+ }
+ .collaborator-tooltip {
+ z-index: 10000;
+ transition: visibility 0,opacity .13s ease-in;
+ background-color: #2a2a2a;
+ border: 1px solid #fff;
+ color: #fff;
+ cursor: default;
+ display: block;
+ font-family: arial, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ margin-left: -1px;
+ opacity: 1;
+ padding: 7px 9px;
+ word-break: break-word;
+ position: absolute;
+ }
+ .collaborator-tooltip-content {
+ color: #fff;
+ }
+ .collaborator-tooltip-arrow {
+ position: absolute;
+ top: -6px;
+ }
+ .collaborator-tooltip-arrow-before {
+ border-color: #fff transparent !important;
+ left: -6px;
+ border: 6px solid;
+ border-top-width: 0;
+ content: '';
+ display: block;
+ height: 0;
+ position: absolute;
+ width: 0;
+ }
+ .collaborator-tooltip-arrow-after {
+ top: 1px;
+ border-color: #2a2a2a transparent !important;
+ left: -5px;
+ border: 5px solid;
+ border-top-width: 0;
+ content: '';
+ display: block;
+ height: 0;
+ position: absolute;
+ width: 0;
+ }
+
+ </style>
+ <title>Trace Viewer</title>
+</head>
+<body>
+ <nav id="navbar">
+ <div id="collabs"></div>
+ <button id="x-drive-save-to-disk">Save to disk</button>
+ <button id="x-drive-save-to-drive">Save to Drive</button>
+ <button id="x-drive-load-from-drive">Load from Drive</button>
+ <button id="x-drive-share">Share</button>
+ </nav>
+ <x-timeline-view>
+ </x-timeline-view>
+
+ <script>
+ 'use strict';
+
+ // Needs to be global as it's passed through the Google API as a
+ // GET parameter.
+ let onAPIClientLoaded_ = null;
+
+ (function() {
+ tr.exportTo('tr.ui.e.drive', function() {
+ const appId = '239864068844';
+ const constants = {
+ APP_ID: appId,
+ ANCHOR_NAME: appId + '.trace_viewer',
+ DEVELOPER_KEY: 'AIzaSyDR-6_wL9vHg1_oz4JHk8IQAkv2_Y0Y8-M',
+ CLIENT_ID: '239864068844-c7gefbfdcp0j6grltulh2r88tsvl18c1.apps.' +
+ 'googleusercontent.com',
+ SCOPE: [
+ 'https://www.googleapis.com/auth/drive',
+ 'https://www.googleapis.com/auth/drive.install',
+ 'https://www.googleapis.com/auth/drive.file',
+ 'profile'
+ ]
+ };
+
+ return {
+ getDriveFileId() { return driveFileId_; },
+ constants
+ };
+ });
+
+
+ let pickerApiLoaded_ = false;
+ let oauthToken_ = null;
+
+ let timelineViewEl_ = null;
+ let driveDocument_ = null;
+ let shareClient_ = null;
+ let fileIdToLoad_ = null;
+ let driveFileId_ = null;
+
+ function parseGETParameter(val) {
+ let result = null;
+ let tmp = [];
+ location.search.substr(1).split('&').forEach(function(item) {
+ tmp = item.split('=');
+ if (tmp[0] === val) {
+ result = decodeURIComponent(tmp[1]);
+ }
+ });
+ return result;
+ }
+
+ // Use the Google API Loader script to load the google.picker script.
+ onAPIClientLoaded_ = function() {
+ const driveState = parseGETParameter('state');
+ if (driveState !== null) {
+ const driveStateJson = JSON.parse(driveState);
+ fileIdToLoad_ = String(driveStateJson.ids);
+ }
+
+ gapi.load('picker', {'callback': onPickerApiLoad});
+ gapi.load('auth', {'callback'() {
+ onAuthApiLoad(true, onAuthResultSuccess);
+ return tr.b.timeout(30e3)
+ .then(() => onAuthApiLoad(true, function() {}))
+ .then(() => tr.b.timeout(30e3))
+ .then(() => onRepeatAuthApiLoad);
+ }});
+ };
+
+ function onAuthApiLoad(tryImmediate, resultCallback) {
+ window.gapi.auth.authorize(
+ {'client_id': tr.ui.e.drive.constants.CLIENT_ID,
+ 'scope': tr.ui.e.drive.constants.SCOPE, 'immediate': tryImmediate},
+ function(authResult) {
+ handleAuthResult(authResult, tryImmediate, resultCallback);
+ });
+ }
+
+ function onPickerApiLoad() {
+ pickerApiLoaded_ = true;
+ if (fileIdToLoad_ === null) {
+ createPicker();
+ }
+ }
+
+ function onAuthResultSuccess() {
+ if (fileIdToLoad_ === null) {
+ createPicker();
+ } else {
+ loadFileFromDrive(fileIdToLoad_);
+ }
+ }
+
+ function handleAuthResult(authResult, wasImmediate, resultCallback) {
+ if (authResult && !authResult.error) {
+ oauthToken_ = authResult.access_token;
+ resultCallback();
+ } else if (wasImmediate) {
+ onAuthApiLoad(false);
+ }
+ }
+
+ function createPicker() {
+ if (pickerApiLoaded_ && oauthToken_) {
+ const view = new google.picker.View(google.picker.ViewId.DOCS);
+ view.setMimeTypes('application/json,application/octet-stream');
+ const picker = new google.picker.PickerBuilder()
+ .enableFeature(google.picker.Feature.NAV_HIDDEN)
+ .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
+ .setAppId(tr.ui.e.drive.constants.APP_ID)
+ .setOAuthToken(oauthToken_)
+ .addView(view)
+ .addView(new google.picker.DocsUploadView())
+ .setDeveloperKey(tr.ui.e.drive.constants.DEVELOPER_KEY)
+ .setCallback(pickerCallback)
+ .build();
+ picker.setVisible(true);
+ }
+ }
+
+ function pickerCallback(data) {
+ if (data.action === google.picker.Action.PICKED) {
+ loadFileFromDrive(data.docs[0].id);
+ }
+ }
+
+ function initShareButton() {
+ shareClient_ = new gapi.drive.share.ShareClient(
+ tr.ui.e.drive.constants.APP_ID);
+ shareClient_.setItemIds([driveFileId_]);
+ }
+
+ function loadFileFromDrive(fileId) {
+ gapi.client.load('drive', 'v2', function() {
+ const request = gapi.client.drive.files.get({fileId});
+ request.execute(function(resp) { downloadFile(resp); });
+ driveFileId_ = fileId;
+ gapi.load('drive-share', initShareButton);
+ });
+ }
+
+ function downloadFile(file) {
+ if (file.downloadUrl) {
+ const downloadingOverlay = tr.ui.b.Overlay();
+ downloadingOverlay.title = 'Downloading...';
+ downloadingOverlay.userCanClose = false;
+ downloadingOverlay.msgEl = document.createElement('div');
+ Polymer.dom(downloadingOverlay).appendChild(downloadingOverlay.msgEl);
+ downloadingOverlay.msgEl.style.margin = '20px';
+ downloadingOverlay.update = function(msg) {
+ Polymer.dom(this.msgEl).textContent = msg;
+ };
+ downloadingOverlay.visible = true;
+
+ const accessToken = gapi.auth.getToken().access_token;
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', file.downloadUrl);
+ xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
+ xhr.onload = function() {
+ downloadingOverlay.visible = false;
+ onDownloaded(file.title, xhr.responseText);
+ };
+ xhr.onprogress = function(evt) {
+ downloadingOverlay.update(
+ Math.floor(evt.position * 100 / file.fileSize) + '% complete');
+ };
+ xhr.onerror = function() { alert('Failed downloading!'); };
+ xhr.send();
+ } else {
+ alert('No URL!');
+ }
+ }
+
+ function displayAllCollaborators() {
+ const allCollaborators = driveDocument_.getCollaborators();
+ const collaboratorCount = allCollaborators.length;
+ const collabspan = document.getElementById('collabs');
+ Polymer.dom(collabspan).innerHTML = '';
+ const imageList = [];
+ for (let i = 0; i < collaboratorCount; i++) {
+ const user = allCollaborators[i];
+
+ const img = document.createElement('img');
+ img.src = user.photoUrl;
+ img.alt = user.displayName;
+ img.height = 30;
+ img.width = 30;
+ img.className = 'collaborator-img';
+ Polymer.dom(collabspan).appendChild(img);
+ imageList.push({'image': img, 'name': user.displayName});
+ }
+ for (i = 0; i < imageList.length; i++) {
+ const collabTooltip = tr.ui.b.createDiv({
+ className: 'collaborator-tooltip'
+ });
+ const collabTooltipContent = tr.ui.b.createDiv({
+ className: 'collaborator-tooltip-content'
+ });
+ Polymer.dom(collabTooltipContent).textContent = imageList[i].name;
+ Polymer.dom(collabTooltip).appendChild(collabTooltipContent);
+ Polymer.dom(collabspan).appendChild(collabTooltip);
+ const collabTooltipArrow = tr.ui.b.createDiv({
+ className: 'collaborator-tooltip-arrow'});
+ Polymer.dom(collabTooltip).appendChild(collabTooltipArrow);
+ const collabTooltipArrowBefore = tr.ui.b.createDiv({
+ className: 'collaborator-tooltip-arrow-before'});
+ Polymer.dom(collabTooltipArrow).appendChild(collabTooltipArrowBefore);
+ const collabTooltipArrowAfter = tr.ui.b.createDiv({
+ className: 'collaborator-tooltip-arrow-after'});
+ Polymer.dom(collabTooltipArrow).appendChild(collabTooltipArrowAfter);
+
+ const rect = imageList[i].image.getBoundingClientRect();
+ collabTooltip.style.top = (rect.bottom - 6) + 'px';
+ collabTooltip.style.left =
+ (rect.left + 16 - (collabTooltip.offsetWidth / 2)) + 'px';
+ collabTooltipArrow.style.left = (collabTooltip.offsetWidth / 2) + 'px';
+ collabTooltip.style.visibility = 'hidden';
+ function visibilityDelegate(element, visibility) {
+ return function() {
+ element.style.visibility = visibility;
+ };
+ }
+ imageList[i].image.addEventListener(
+ 'mouseover', visibilityDelegate(collabTooltip, 'visible'));
+ imageList[i].image.addEventListener(
+ 'mouseout', visibilityDelegate(collabTooltip, 'hidden'));
+ }
+ }
+
+ function onRealtimeFileLoaded(doc) {
+ if (driveDocument_) {
+ driveDocument_.close();
+ }
+ driveDocument_ = doc;
+ doc.addEventListener(gapi.drive.realtime.EventType.COLLABORATOR_JOINED,
+ displayAllCollaborators);
+ doc.addEventListener(gapi.drive.realtime.EventType.COLLABORATOR_LEFT,
+ displayAllCollaborators);
+
+ displayAllCollaborators(doc);
+ }
+
+ function onRealtimeError(e) {
+ alert('Error loading realtime: ' + e);
+ }
+
+ function onDownloaded(filename, content) {
+ gapi.load('auth:client,drive-realtime,drive-share', function() {
+ gapi.drive.realtime.load(driveFileId_,
+ onRealtimeFileLoaded,
+ null,
+ onRealtimeError);
+ });
+
+ const traces = [];
+ const filenames = [];
+ filenames.push(filename);
+ traces.push(content);
+ createViewFromTraces(filenames, traces);
+ }
+
+ function createViewFromTraces(filenames, traces) {
+ const m = new tr.Model();
+ const i = new tr.importer.Import(m);
+ const p = i.importTracesWithProgressDialog(traces);
+ p.then(
+ function() {
+ timelineViewEl_.model = m;
+ timelineViewEl_.updateDocumentFavicon();
+ timelineViewEl_.globalMode = true;
+ timelineViewEl_.viewTitle = '';
+ },
+ function(err) {
+ const downloadingOverlay = new tr.ui.b.Overlay();
+ Polymer.dom(downloadingOverlay).textContent =
+ tr.b.normalizeException(err).message;
+ downloadingOverlay.title = 'Import error';
+ downloadingOverlay.visible = true;
+ });
+ }
+
+ function onSaveToDiskClicked() {
+ throw new Error('Not implemented');
+ }
+
+ function onSaveToDriveClicked() {
+ throw new Error('Not implemented');
+ }
+
+ function onLoadFromDriveClicked() {
+ createPicker();
+ }
+
+ function onLoad() {
+ timelineViewEl_ = Polymer.dom(document).querySelector('x-timeline-view');
+ timelineViewEl_.globalMode = true;
+ const navbar = document.getElementById('navbar');
+ timelineViewEl_.style.top = navbar.offsetHeight + 'px';
+ tr.ui.b.decorate(timelineViewEl_, tr.ui.TimelineView);
+ }
+
+ window.addEventListener('load', onLoad);
+
+ document.getElementById('x-drive-save-to-disk').onclick =
+ onSaveToDiskClicked;
+ document.getElementById('x-drive-save-to-drive').onclick =
+ onSaveToDriveClicked;
+ document.getElementById('x-drive-load-from-drive').onclick =
+ onLoadFromDriveClicked;
+ document.getElementById('x-drive-share').onclick = function() {
+ shareClient_.showSettingsDialog();
+ };
+ }());
+
+ </script>
+ <script type="text/javascript"
+ src="https://apis.google.com/js/client.js?onload=onAPIClientLoaded_">
+ </script>
+</body>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/full_config.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/full_config.html
new file mode 100644
index 00000000000..6d1e29d4e20
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/full_config.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<!--
+TODO(charliea): Make all UI files depend on tracing/ui/base/base.html in the
+same way that all non-UI files depend on tracing/base/base.html. Enforce this
+dependency with a presubmit.
+-->
+<link rel="import" href="/tracing/ui/base/base.html" data-suppress-import-order>
+
+<!-- The full config is all the configs slammed together. -->
+<link rel="import" href="/tracing/extras/importer/gcloud_trace/gcloud_trace_importer.html">
+<link rel="import" href="/tracing/ui/extras/chrome_config.html">
+<link rel="import" href="/tracing/ui/extras/lean_config.html">
+<link rel="import" href="/tracing/ui/extras/systrace_config.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/lean_config.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/lean_config.html
new file mode 100644
index 00000000000..8d66352f140
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/lean_config.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<!--
+TODO(charliea): Make all UI files depend on tracing/ui/base/base.html in the same way that
+all non-UI files depend on tracing/base/base.html. Enforce this dependency with a presubmit.
+-->
+<link rel="import" href="/tracing/ui/base/base.html" data-suppress-import-order>
+
+<link rel="import" href="/tracing/extras/lean_config.html" data-suppress-import-order>
+
+<!--
+The lean config is just enough to import uncompressed, trace-event-formatted
+json blobs.
+-->
+<link rel="import" href="/tracing/ui/side_panel/file_size_stats_side_panel.html">
+<link rel="import" href="/tracing/ui/side_panel/metrics_side_panel.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/alerts_side_panel.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/alerts_side_panel.html
new file mode 100644
index 00000000000..0971d7f5409
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/alerts_side_panel.html
@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<!--
+Copyright 2015 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.
+-->
+
+<link rel="import" href="/tracing/base/math/statistics.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/ui/base/dom_helpers.html">
+<link rel="import" href="/tracing/ui/base/line_chart.html">
+<link rel="import" href="/tracing/ui/base/table.html">
+<link rel="import" href="/tracing/ui/side_panel/side_panel.html">
+<link rel="import" href="/tracing/ui/side_panel/side_panel_registry.html">
+
+<dom-module id='tr-ui-e-s-alerts-side-panel'>
+ <template>
+ <style>
+ :host {
+ display: block;
+ width: 250px;
+ }
+ #content {
+ flex-direction: column;
+ display: flex;
+ }
+ tr-ui-b-table {
+ font-size: 12px;
+ }
+ </style>
+
+ <div id='content'>
+ <toolbar id='toolbar'></toolbar>
+ <result-area id='result_area'></result-area>
+ </div>
+ </template>
+</dom-module>
+<script>
+'use strict';
+
+Polymer({
+ is: 'tr-ui-e-s-alerts-side-panel',
+ behaviors: [tr.ui.behaviors.SidePanel],
+
+
+ ready() {
+ this.rangeOfInterest_ = new tr.b.math.Range();
+ this.selection_ = undefined;
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ set model(model) {
+ this.model_ = model;
+ this.updateContents_();
+ },
+
+ set selection(selection) {
+ },
+
+ set rangeOfInterest(rangeOfInterest) {
+ },
+
+ /**
+ * Fires a selection event selecting all alerts of the specified
+ * type.
+ */
+ selectAlertsOfType(alertTypeString) {
+ const alertsOfType = this.model_.alerts.filter(function(alert) {
+ return alert.title === alertTypeString;
+ });
+
+ const event = new tr.model.RequestSelectionChangeEvent();
+ event.selection = new tr.model.EventSet(alertsOfType);
+ this.dispatchEvent(event);
+ },
+
+ /**
+ * Returns a map for the specified alerts where each key is the
+ * alert type string and each value is a list of alerts with that
+ * type.
+ */
+ alertsByType_(alerts) {
+ const alertsByType = {};
+ alerts.forEach(function(alert) {
+ if (!alertsByType[alert.title]) {
+ alertsByType[alert.title] = [];
+ }
+
+ alertsByType[alert.title].push(alert);
+ });
+ return alertsByType;
+ },
+
+ alertsTableRows_(alertsByType) {
+ return Object.keys(alertsByType).map(function(key) {
+ return {
+ alertType: key,
+ count: alertsByType[key].length
+ };
+ });
+ },
+
+ alertsTableColumns_() {
+ return [
+ {
+ title: 'Alert type',
+ value(row) { return row.alertType; },
+ width: '180px'
+ },
+ {
+ title: 'Count',
+ width: '100%',
+ value(row) { return row.count; }
+ }
+ ];
+ },
+
+ createAlertsTable_(alerts) {
+ const alertsByType = this.alertsByType_(alerts);
+
+ const table = document.createElement('tr-ui-b-table');
+ table.tableColumns = this.alertsTableColumns_();
+ table.tableRows = this.alertsTableRows_(alertsByType);
+ table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW;
+ table.addEventListener('selection-changed', function(e) {
+ const row = table.selectedTableRow;
+ if (row) {
+ this.selectAlertsOfType(row.alertType);
+ }
+ }.bind(this));
+
+ return table;
+ },
+
+ updateContents_() {
+ Polymer.dom(this.$.result_area).textContent = '';
+ if (this.model_ === undefined) return;
+
+ const panel = this.createAlertsTable_(this.model_.alerts);
+ Polymer.dom(this.$.result_area).appendChild(panel);
+ },
+
+ supportsModel(m) {
+ if (m === undefined) {
+ return {
+ supported: false,
+ reason: 'Unknown tracing model'
+ };
+ } else if (m.alerts.length === 0) {
+ return {
+ supported: false,
+ reason: 'No alerts in tracing model'
+ };
+ }
+
+ return {
+ supported: true
+ };
+ },
+
+ get textLabel() {
+ return 'Alerts';
+ }
+});
+
+tr.ui.side_panel.SidePanelRegistry.register(function() {
+ return document.createElement('tr-ui-e-s-alerts-side-panel');
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/alerts_side_panel_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/alerts_side_panel_test.html
new file mode 100644
index 00000000000..c4cb9825d1e
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/alerts_side_panel_test.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/ui/extras/side_panel/alerts_side_panel.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const ALERT_INFO_1 = new tr.model.EventInfo(
+ 'Alert 1', 'Critical alert');
+ const ALERT_INFO_2 = new tr.model.EventInfo(
+ 'Alert 2', 'Warning alert');
+
+ test('instantiate', function() {
+ const panel = document.createElement('tr-ui-e-s-alerts-side-panel');
+ panel.model = createModelWithAlerts([
+ new tr.model.Alert(ALERT_INFO_1, 5),
+ new tr.model.Alert(ALERT_INFO_2, 35)
+ ]);
+ panel.style.height = '100px';
+
+ this.addHTMLOutput(panel);
+ });
+
+ test('selectAlertsOfType', function() {
+ const panel = document.createElement('tr-ui-e-s-alerts-side-panel');
+ const alerts = [
+ new tr.model.Alert(ALERT_INFO_1, 1),
+ new tr.model.Alert(ALERT_INFO_1, 2),
+ new tr.model.Alert(ALERT_INFO_2, 3)
+ ];
+
+ const predictedAlerts = new tr.model.EventSet([alerts[0], alerts[1]]);
+ panel.model = createModelWithAlerts(alerts);
+ panel.style.height = '100px';
+ this.addHTMLOutput(panel);
+
+ let selectionChanged = false;
+ panel.addEventListener('requestSelectionChange', function(e) {
+ selectionChanged = true;
+ assert.isTrue(e.selection.equals(predictedAlerts));
+ });
+ panel.selectAlertsOfType(ALERT_INFO_1.title);
+
+ assert.isTrue(selectionChanged);
+ });
+
+ function createModelWithAlerts(alerts) {
+ const m = new tr.Model();
+ m.alerts = alerts;
+ return m;
+ }
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/frame_data_side_panel.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/frame_data_side_panel.html
new file mode 100644
index 00000000000..e5fd7689479
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/frame_data_side_panel.html
@@ -0,0 +1,347 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 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.
+-->
+
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/frame_tree_node.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/render_frame.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/top_level.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/ui/base/table.html">
+<link rel="import" href="/tracing/ui/side_panel/side_panel.html">
+<link rel="import" href="/tracing/ui/side_panel/side_panel_registry.html">
+<link rel="import" href="/tracing/value/ui/scalar_span.html">
+
+<dom-module id='tr-ui-e-s-frame-data-side-panel'>
+ <template>
+ <style>
+ :host {
+ display: flex;
+ width: 600px;
+ flex-direction: column;
+ }
+ table-container {
+ display: flex;
+ overflow: auto;
+ font-size: 12px;
+ }
+ </style>
+ <div>
+ Organize by:
+ <select id="select">
+ <option value="none">None</option>
+ <option value="tree">Frame Tree</option>
+ </select>
+ </div>
+ <table-container>
+ <tr-ui-b-table id="table"></tr-ui-b-table>
+ </table-container>
+ </template>
+</dom-module>
+
+<script>
+'use strict';
+tr.exportTo('tr.ui.e.s', function() {
+ const BlameContextSnapshot = tr.e.chrome.BlameContextSnapshot;
+ const FrameTreeNodeSnapshot = tr.e.chrome.FrameTreeNodeSnapshot;
+ const RenderFrameSnapshot = tr.e.chrome.RenderFrameSnapshot;
+ const TopLevelSnapshot = tr.e.chrome.TopLevelSnapshot;
+
+ const BlameContextInstance = tr.e.chrome.BlameContextInstance;
+ const FrameTreeNodeInstance = tr.e.chrome.FrameTreeNodeInstance;
+ const RenderFrameInstance = tr.e.chrome.RenderFrameInstance;
+ const TopLevelInstance = tr.e.chrome.TopLevelInstance;
+
+ /**
+ * @constructor
+ * If |context| is provided, creates a row for the given context.
+ * Otherwise, creates an empty Row template which can be used for aggregating
+ * data from a group of subrows.
+ */
+ function Row(context) {
+ this.subRows = undefined;
+ this.contexts = [];
+ this.type = undefined;
+ this.renderer = 'N/A';
+ this.url = undefined;
+ this.time = 0;
+ this.eventsOfInterest = new tr.model.EventSet();
+
+ if (context === undefined) return;
+
+ this.type = context.objectInstance.blameContextType;
+ this.contexts.push(context);
+ if (context instanceof FrameTreeNodeSnapshot) {
+ if (context.renderFrame) {
+ this.contexts.push(context.renderFrame);
+ this.renderer = context.renderFrame.objectInstance.parent.pid;
+ }
+ } else if (context instanceof RenderFrameSnapshot) {
+ if (context.frameTreeNode) {
+ this.contexts.push(context.frameTreeNode);
+ }
+ this.renderer = context.objectInstance.parent.pid;
+ } else if (context instanceof TopLevelSnapshot) {
+ this.renderer = context.objectInstance.parent.pid;
+ } else {
+ throw new Error('Unknown context type');
+ }
+ this.eventsOfInterest.addEventSet(this.contexts);
+
+ // TODO(xiaochengh): Handle the case where a subframe has a trivial url
+ // (e.g., about:blank), but inherits the origin of its parent. This is not
+ // needed now, but will be required if we want to group rows by origin.
+ this.url = context.url;
+ }
+
+ const groupFunctions = {
+ none: rows => rows,
+
+ // Group the rows according to the frame tree structure.
+ // Example: consider frame tree a(b, c(d)), where each frame has 1ms time
+ // attributed to it. The resulting table should look like:
+ // Type | Time | URL
+ // --------------+------+-----
+ // Frame Tree | 4 | a
+ // +- Frame | 1 | a
+ // +- Subframe | 1 | b
+ // +- Frame Tree | 2 | c
+ // +- Frame | 1 | c
+ // +- Subframe | 1 | d
+ tree(rows, rowMap) {
+ // Finds the parent of a specific row. When there is conflict between the
+ // browser's dump of the frame tree and the renderers', use the browser's.
+ const getParentRow = function(row) {
+ let pivot;
+ row.contexts.forEach(function(context) {
+ if (context instanceof tr.e.chrome.FrameTreeNodeSnapshot) {
+ pivot = context;
+ }
+ });
+ if (pivot && pivot.parentContext) {
+ return rowMap[pivot.parentContext.guid];
+ }
+ return undefined;
+ };
+
+ const rootRows = [];
+ rows.forEach(function(row) {
+ const parentRow = getParentRow(row);
+ if (parentRow === undefined) {
+ rootRows.push(row);
+ return;
+ }
+ if (parentRow.subRows === undefined) {
+ parentRow.subRows = [];
+ }
+ parentRow.subRows.push(row);
+ });
+
+ const aggregateAllDescendants = function(row) {
+ if (!row.subRows) {
+ if (getParentRow(row)) {
+ row.type = 'Subframe';
+ }
+ return row;
+ }
+ const result = new Row();
+ result.type = 'Frame Tree';
+ result.renderer = row.renderer;
+ result.url = row.url;
+ result.subRows = [row];
+ row.subRows.forEach(
+ subRow => result.subRows.push(aggregateAllDescendants(subRow)));
+ result.subRows.forEach(function(subRow) {
+ result.time += subRow.time;
+ result.eventsOfInterest.addEventSet(subRow.eventsOfInterest);
+ });
+ row.subRows = undefined;
+ return result;
+ };
+
+ return rootRows.map(rootRow => aggregateAllDescendants(rootRow));
+ }
+
+ // TODO(xiaochengh): Add grouping by site and probably more...
+ };
+
+ Polymer({
+ is: 'tr-ui-e-s-frame-data-side-panel',
+ behaviors: [tr.ui.behaviors.SidePanel],
+
+ ready() {
+ this.model_ = undefined;
+ this.rangeOfInterest_ = new tr.b.math.Range();
+
+ this.$.table.showHeader = true;
+ this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW;
+ this.$.table.tableColumns = this.createFrameDataTableColumns_();
+
+ this.$.table.addEventListener('selection-changed', function(e) {
+ this.selectEventSet_(this.$.table.selectedTableRow.eventsOfInterest);
+ }.bind(this));
+
+ this.$.select.addEventListener('change', function(e) {
+ this.updateContents_();
+ }.bind(this));
+ },
+
+ selectEventSet_(eventSet) {
+ const event = new tr.model.RequestSelectionChangeEvent();
+ event.selection = eventSet;
+ this.dispatchEvent(event);
+ },
+
+ createFrameDataTableColumns_() {
+ return [
+ {
+ title: 'Renderer',
+ value: row => row.renderer,
+ cmp: (a, b) => a.renderer - b.renderer
+ },
+ {
+ title: 'Type',
+ value: row => row.type
+ },
+ // TODO(xiaochengh): Decide what details to show in the table:
+ // - URL seems necessary, but we may also want origin instead/both.
+ // - Distinguish between browser time and renderer time?
+ // - Distinguish between CPU time and wall clock time?
+ // - Memory? Network? ...
+ {
+ title: 'Time',
+ value: row => tr.v.ui.createScalarSpan(row.time, {
+ unit: tr.b.Unit.byName.timeStampInMs,
+ ownerDocument: this.ownerDocument
+ }),
+ cmp: (a, b) => a.time - b.time
+ },
+ {
+ title: 'URL',
+ value: row => row.url,
+ cmp: (a, b) => (a.url || '').localeCompare(b.url || '')
+ }
+ ];
+ },
+
+ createFrameDataTableRows_() {
+ if (!this.model_) return [];
+
+ // Gather contexts into skeletons of rows.
+ const rows = [];
+ const rowMap = {};
+ for (const proc of Object.values(this.model_.processes)) {
+ proc.objects.iterObjectInstances(function(objectInstance) {
+ if (!(objectInstance instanceof BlameContextInstance)) {
+ return;
+ }
+ objectInstance.snapshots.forEach(function(snapshot) {
+ if (rowMap[snapshot.guid]) return;
+
+ const row = new Row(snapshot);
+ row.contexts.forEach(context => rowMap[context.guid] = row);
+ rows.push(row);
+ }, this);
+ }, this);
+ }
+
+ // Find slices attributed to each row.
+ // TODO(xiaochengh): We should implement a getter
+ // BlameContextSnapshot.attributedEvents, instead of process the model in
+ // a UI component.
+ for (const proc of Object.values(this.model_.processes)) {
+ for (const thread of Object.values(proc.threads)) {
+ thread.sliceGroup.iterSlicesInTimeRange(function(topLevelSlice) {
+ topLevelSlice.contexts.forEach(function(context) {
+ if (!context.snapshot.guid || !rowMap[context.snapshot.guid]) {
+ return;
+ }
+ const row = rowMap[context.snapshot.guid];
+ row.eventsOfInterest.push(topLevelSlice);
+ row.time += topLevelSlice.selfTime || 0;
+ });
+ }, this.currentRangeOfInterest.min, this.currentRangeOfInterest.max);
+ }
+ }
+
+ // Apply grouping to rows.
+ const select = this.$.select;
+ const groupOption = select.options[select.selectedIndex].value;
+ const groupFunction = groupFunctions[groupOption];
+ return groupFunction(rows, rowMap);
+ },
+
+ updateContents_() {
+ this.$.table.tableRows = this.createFrameDataTableRows_();
+ this.$.table.rebuild();
+ },
+
+ supportsModel(m) {
+ if (!m) {
+ return {
+ supported: false,
+ reason: 'No model available.'
+ };
+ }
+
+ const ans = {supported: false};
+ for (const proc of Object.values(m.processes)) {
+ proc.objects.iterObjectInstances(function(instance) {
+ if (instance instanceof BlameContextInstance) {
+ ans.supported = true;
+ }
+ });
+ }
+
+ if (!ans.supported) {
+ ans.reason = 'No frame data available';
+ }
+ return ans;
+ },
+
+ get currentRangeOfInterest() {
+ if (this.rangeOfInterest_.isEmpty) {
+ return this.model_.bounds;
+ }
+ return this.rangeOfInterest_;
+ },
+
+ get rangeOfInterest() {
+ return this.rangeOfInterest_;
+ },
+
+ set rangeOfInterest(rangeOfInterest) {
+ this.rangeOfInterest_ = rangeOfInterest;
+ this.updateContents_();
+ },
+
+ get selection() {
+ // Not applicable.
+ },
+
+ set selection(_) {
+ // Not applicable.
+ },
+
+ get textLabel() {
+ return 'Frame Data';
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ set model(model) {
+ this.model_ = model;
+ this.updateContents_();
+ }
+ });
+
+ tr.ui.side_panel.SidePanelRegistry.register(function() {
+ return document.createElement('tr-ui-e-s-frame-data-side-panel');
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/frame_data_side_panel_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/frame_data_side_panel_test.html
new file mode 100644
index 00000000000..298afe05d42
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/frame_data_side_panel_test.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<!--
+Copyright 2016 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/frame_tree_node.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/render_frame.html">
+<link rel="import" href="/tracing/extras/chrome/blame_context/top_level.html">
+<link rel="import" href="/tracing/ui/extras/side_panel/frame_data_side_panel.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const TestUtils = tr.c.TestUtils;
+
+ function topLevelOptions(pid, id) {
+ return {
+ pid,
+ id,
+ cat: 'blink',
+ scope: 'PlatformThread',
+ name: 'TopLevel'
+ };
+ }
+
+ function renderFrameOptions(pid, id, parent) {
+ return {
+ pid,
+ id,
+ cat: 'blink',
+ scope: 'RenderFrame',
+ name: 'RenderFrame',
+ args: {parent: {
+ id_ref: parent.id,
+ scope: parent.scope
+ }}
+ };
+ }
+
+ function frameTreeNodeOptions(pid, id, opt_renderFrame, opt_parentId) {
+ const ans = {
+ pid,
+ id,
+ cat: 'navigation',
+ scope: 'FrameTreeNode',
+ name: 'FrameTreeNode',
+ args: {}
+ };
+ if (opt_renderFrame) {
+ ans.args.renderFrame = {
+ id_ref: opt_renderFrame.id,
+ pid_ref: opt_renderFrame.pid,
+ scope: 'RenderFrame'
+ };
+ }
+ if (opt_parentId) {
+ ans.args.parent = {
+ id_ref: opt_parentId,
+ scope: 'FrameTreeNode'
+ };
+ }
+ return ans;
+ }
+
+ /**
+ * Creates some independent contexts. Checks if all are present in the panel.
+ */
+ test('basic', function() {
+ const panel = document.createElement('tr-ui-e-s-frame-data-side-panel');
+ panel.model = TestUtils.newModel(function(model) {
+ TestUtils.newSnapshot(model, topLevelOptions(1, '0x1'));
+ TestUtils.newSnapshot(model, renderFrameOptions(
+ 1, '0x2', {id: '0x1', scope: 'PlatformThread'}));
+ TestUtils.newSnapshot(model, frameTreeNodeOptions(
+ 2, '0x3'));
+ });
+ assert.lengthOf(panel.$.table.tableRows, 3);
+
+ this.addHTMLOutput(panel);
+ });
+
+ /**
+ * Creates a FrameTreeNode in the browser process and a RenderFrame in a
+ * renderer process that are the same frame. Checks if they are merged into
+ * one row in the panel.
+ */
+ test('mergeCrossProcessFrameBlameContexts', function() {
+ const panel = document.createElement('tr-ui-e-s-frame-data-side-panel');
+ panel.model = TestUtils.newModel(function(model) {
+ TestUtils.newSnapshot(model, topLevelOptions(1, '0x1'));
+ TestUtils.newSnapshot(model, renderFrameOptions(
+ 1, '0x2', {id: '0x1', scope: 'PlatformThread'}));
+ TestUtils.newSnapshot(model, frameTreeNodeOptions(
+ 2, '0x3', {id: '0x2', pid: 1}));
+ });
+ assert.lengthOf(panel.$.table.tableRows, 2);
+
+ this.addHTMLOutput(panel);
+ });
+
+ function newAttributedSlice(model, pid, start, duration, context) {
+ const slice = TestUtils.newSliceEx({start, duration});
+ slice.contexts = [{type: 'FrameBlameContext', snapshot: context}];
+ model.getOrCreateProcess(pid).getOrCreateThread(1).sliceGroup.pushSlice(
+ slice);
+ return slice;
+ }
+
+ /**
+ * Changes the range of interest. Checks if the panel updates correspondingly.
+ */
+ test('respondToRangeOfInterest', function() {
+ let topLevel;
+ let slice1;
+ let slice2;
+ const panel = document.createElement('tr-ui-e-s-frame-data-side-panel');
+ panel.model = TestUtils.newModel(function(model) {
+ topLevel = TestUtils.newSnapshot(model, topLevelOptions(1, '0x1'));
+ slice1 = newAttributedSlice(model, 1, 1500, 500, topLevel);
+ slice2 = newAttributedSlice(model, 1, 2500, 500, topLevel);
+ });
+
+ // The default range of interest contains both slices.
+ assert.isTrue(panel.$.table.tableRows[0].eventsOfInterest.equals(
+ new tr.model.EventSet([topLevel, slice1, slice2])));
+
+ // The new range of interest contains only slice2.
+ panel.rangeOfInterest = tr.b.math.Range.fromExplicitRange(slice2.start,
+ slice2.end);
+ assert.isTrue(panel.$.table.tableRows[0].eventsOfInterest.equals(
+ new tr.model.EventSet([topLevel, slice2])));
+
+ this.addHTMLOutput(panel);
+ });
+
+ /**
+ * Selects a row in the panel. Checks if the context(s) of the row and the
+ * slices attributed to the row are selected.
+ */
+ test('selectAttributedEvents', function() {
+ let topLevel;
+ let slice;
+ const panel = document.createElement('tr-ui-e-s-frame-data-side-panel');
+ panel.model = TestUtils.newModel(function(model) {
+ topLevel = TestUtils.newSnapshot(model, topLevelOptions(1, '0x1'));
+ slice = newAttributedSlice(model, 1, 1500, 500, topLevel);
+ });
+
+ let selectionChanged = false;
+ panel.addEventListener('requestSelectionChange', function(e) {
+ selectionChanged = true;
+ assert.isTrue(
+ e.selection.equals(new tr.model.EventSet([topLevel, slice])));
+ });
+ panel.$.table.selectedTableRow = panel.$.table.tableRows[0];
+ assert.isTrue(selectionChanged);
+
+ this.addHTMLOutput(panel);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/input_latency_side_panel.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/input_latency_side_panel.html
new file mode 100644
index 00000000000..14e33919922
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/input_latency_side_panel.html
@@ -0,0 +1,334 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+<link rel="import" href="/tracing/base/math/statistics.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/model/helpers/chrome_model_helper.html">
+<link rel="import" href="/tracing/ui/base/dom_helpers.html">
+<link rel="import" href="/tracing/ui/base/line_chart.html">
+<link rel="import" href="/tracing/ui/side_panel/side_panel.html">
+<link rel="import" href="/tracing/ui/side_panel/side_panel_registry.html">
+
+<dom-module id='tr-ui-e-s-input-latency-side-panel'>
+ <template>
+ <style>
+ :host {
+ flex-direction: column;
+ display: flex;
+ }
+ toolbar {
+ flex: 0 0 auto;
+ border-bottom: 1px solid black;
+ display: flex;
+ }
+ result-area {
+ flex: 1 1 auto;
+ display: block;
+ min-height: 0;
+ overflow-y: auto;
+ }
+ </style>
+
+ <toolbar id='toolbar'></toolbar>
+ <result-area id='result_area'></result-area>
+ </template>
+</dom-module>
+<script>
+'use strict';
+
+Polymer({
+ is: 'tr-ui-e-s-input-latency-side-panel',
+ behaviors: [tr.ui.behaviors.SidePanel],
+
+
+ ready() {
+ this.rangeOfInterest_ = new tr.b.math.Range();
+ this.frametimeType_ = tr.model.helpers.IMPL_FRAMETIME_TYPE;
+ this.latencyChart_ = undefined;
+ this.frametimeChart_ = undefined;
+ this.selectedProcessId_ = undefined;
+ this.mouseDownIndex_ = undefined;
+ this.curMouseIndex_ = undefined;
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ set model(model) {
+ this.model_ = model;
+ if (this.model_) {
+ this.modelHelper_ = this.model_.getOrCreateHelper(
+ tr.model.helpers.ChromeModelHelper);
+ } else {
+ this.modelHelper_ = undefined;
+ }
+
+ this.updateToolbar_();
+ this.updateContents_();
+ },
+
+ get frametimeType() {
+ return this.frametimeType_;
+ },
+
+ set frametimeType(type) {
+ if (this.frametimeType_ === type) return;
+
+ this.frametimeType_ = type;
+ this.updateContents_();
+ },
+
+ get selectedProcessId() {
+ return this.selectedProcessId_;
+ },
+
+ set selectedProcessId(process) {
+ if (this.selectedProcessId_ === process) return;
+
+ this.selectedProcessId_ = process;
+ this.updateContents_();
+ },
+
+ set selection(selection) {
+ if (this.latencyChart_ === undefined) return;
+
+ this.latencyChart_.brushedRange = selection.bounds;
+ },
+
+ // This function is for testing purpose.
+ setBrushedIndices(mouseDownIndex, curIndex) {
+ this.mouseDownIndex_ = mouseDownIndex;
+ this.curMouseIndex_ = curIndex;
+ this.updateBrushedRange_();
+ },
+
+ updateBrushedRange_() {
+ if (this.latencyChart_ === undefined) return;
+
+ let r = new tr.b.math.Range();
+ if (this.mouseDownIndex_ === undefined) {
+ this.latencyChart_.brushedRange = r;
+ return;
+ }
+ r = this.latencyChart_.computeBrushRangeFromIndices(
+ this.mouseDownIndex_, this.curMouseIndex_);
+ this.latencyChart_.brushedRange = r;
+
+ // Based on the brushed range, update the selection of LatencyInfo in
+ // the timeline view by sending a selectionChange event.
+ let latencySlices = [];
+ for (const thread of this.model_.getAllThreads()) {
+ for (const event of thread.getDescendantEvents()) {
+ if (event.title.indexOf('InputLatency:') === 0) {
+ latencySlices.push(event);
+ }
+ }
+ }
+ latencySlices = tr.model.helpers.getSlicesIntersectingRange(
+ r, latencySlices);
+
+ const event = new tr.model.RequestSelectionChangeEvent();
+ event.selection = new tr.model.EventSet(latencySlices);
+ this.latencyChart_.dispatchEvent(event);
+ },
+
+ registerMouseEventForLatencyChart_() {
+ this.latencyChart_.addEventListener('item-mousedown', function(e) {
+ this.mouseDownIndex_ = e.index;
+ this.curMouseIndex_ = e.index;
+ this.updateBrushedRange_();
+ }.bind(this));
+
+ this.latencyChart_.addEventListener('item-mousemove', function(e) {
+ if (e.button === undefined) return;
+
+ this.curMouseIndex_ = e.index;
+ this.updateBrushedRange_();
+ }.bind(this));
+
+ this.latencyChart_.addEventListener('item-mouseup', function(e) {
+ this.curMouseIndex = e.index;
+ this.updateBrushedRange_();
+ }.bind(this));
+ },
+
+ updateToolbar_() {
+ const browserProcess = this.modelHelper_.browserProcess;
+ const labels = [];
+
+ if (browserProcess !== undefined) {
+ const labelStr = 'Browser: ' + browserProcess.pid;
+ labels.push({label: labelStr, value: browserProcess.pid});
+ }
+
+ for (const rendererHelper of
+ Object.values(this.modelHelper_.rendererHelpers)) {
+ const rendererProcess = rendererHelper.process;
+ const labelStr = 'Renderer: ' + rendererProcess.userFriendlyName;
+ labels.push({label: labelStr, value: rendererProcess.userFriendlyName});
+ }
+
+ if (labels.length === 0) return;
+
+ this.selectedProcessId_ = labels[0].value;
+ const toolbarEl = this.$.toolbar;
+ Polymer.dom(toolbarEl).appendChild(tr.ui.b.createSelector(
+ this, 'frametimeType',
+ 'inputLatencySidePanel.frametimeType', this.frametimeType_,
+ [{label: 'Main Thread Frame Times',
+ value: tr.model.helpers.MAIN_FRAMETIME_TYPE},
+ {label: 'Impl Thread Frame Times',
+ value: tr.model.helpers.IMPL_FRAMETIME_TYPE}
+ ]));
+ Polymer.dom(toolbarEl).appendChild(tr.ui.b.createSelector(
+ this, 'selectedProcessId',
+ 'inputLatencySidePanel.selectedProcessId',
+ this.selectedProcessId_,
+ labels));
+ },
+
+ // TODO(charliea): Delete this function in favor of rangeOfInterest.
+ get currentRangeOfInterest() {
+ if (this.rangeOfInterest_.isEmpty) {
+ return this.model_.bounds;
+ }
+ return this.rangeOfInterest_;
+ },
+
+ createLatencyLineChart(data, title, parentNode) {
+ const chart = new tr.ui.b.LineChart();
+ Polymer.dom(parentNode).appendChild(chart);
+ let width = 600;
+ if (document.body.clientWidth !== undefined) {
+ width = document.body.clientWidth * 0.5;
+ }
+ chart.graphWidth = width;
+ chart.chartTitle = title;
+ chart.data = data;
+ return chart;
+ },
+
+ updateContents_() {
+ const resultArea = this.$.result_area;
+ this.latencyChart_ = undefined;
+ this.frametimeChart_ = undefined;
+ Polymer.dom(resultArea).textContent = '';
+
+ if (this.modelHelper_ === undefined) return;
+
+ const rangeOfInterest = this.currentRangeOfInterest;
+
+ let chromeProcess;
+ if (this.modelHelper_.rendererHelpers[this.selectedProcessId_]) {
+ chromeProcess = this.modelHelper_.rendererHelpers[
+ this.selectedProcessId_
+ ];
+ } else {
+ chromeProcess = this.modelHelper_.browserHelper;
+ }
+
+ const frameEvents = chromeProcess.getFrameEventsInRange(
+ this.frametimeType, rangeOfInterest);
+
+ const frametimeData = tr.model.helpers.getFrametimeDataFromEvents(
+ frameEvents);
+ const averageFrametime = tr.b.math.Statistics.mean(frametimeData, d =>
+ d.frametime
+ );
+
+ const latencyEvents = this.modelHelper_.browserHelper.
+ getLatencyEventsInRange(
+ rangeOfInterest);
+
+ const latencyData = [];
+ latencyEvents.forEach(function(event) {
+ if (event.inputLatency === undefined) return;
+
+ latencyData.push({
+ x: event.start,
+ latency: event.inputLatency / 1000
+ });
+ });
+
+ const averageLatency = tr.b.math.Statistics.mean(latencyData, function(d) {
+ return d.latency;
+ });
+
+ // Create summary.
+ const latencySummaryText = document.createElement('div');
+ Polymer.dom(latencySummaryText).appendChild(tr.ui.b.createSpan({
+ textContent: 'Average Latency ' + averageLatency + ' ms',
+ bold: true}));
+ Polymer.dom(resultArea).appendChild(latencySummaryText);
+
+ const frametimeSummaryText = document.createElement('div');
+ Polymer.dom(frametimeSummaryText).appendChild(tr.ui.b.createSpan({
+ textContent: 'Average Frame Time ' + averageFrametime + ' ms',
+ bold: true}));
+ Polymer.dom(resultArea).appendChild(frametimeSummaryText);
+
+ if (latencyData.length !== 0) {
+ this.latencyChart_ = this.createLatencyLineChart(
+ latencyData, 'Latency Over Time', resultArea);
+ this.registerMouseEventForLatencyChart_();
+ }
+
+ if (frametimeData.length !== 0) {
+ this.frametimeChart_ = this.createLatencyLineChart(
+ frametimeData, 'Frame Times', resultArea);
+ }
+ },
+
+ get rangeOfInterest() {
+ return this.rangeOfInterest_;
+ },
+
+ set rangeOfInterest(rangeOfInterest) {
+ this.rangeOfInterest_ = rangeOfInterest;
+ this.updateContents_();
+ },
+
+ supportsModel(m) {
+ if (m === undefined) {
+ return {
+ supported: false,
+ reason: 'Unknown tracing model'
+ };
+ }
+
+ if (!tr.model.helpers.ChromeModelHelper.supportsModel(m)) {
+ return {
+ supported: false,
+ reason: 'No Chrome browser or renderer process found'
+ };
+ }
+
+ const modelHelper = m.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
+ if (modelHelper.browserHelper &&
+ modelHelper.browserHelper.hasLatencyEvents) {
+ return {
+ supported: true
+ };
+ }
+
+ return {
+ supported: false,
+ reason: 'No InputLatency events trace. Consider enabling ' +
+ 'benchmark" and "input" category when recording the trace'
+ };
+ },
+
+ get textLabel() {
+ return 'Input Latency';
+ }
+});
+
+tr.ui.side_panel.SidePanelRegistry.register(function() {
+ return document.createElement('tr-ui-e-s-input-latency-side-panel');
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/input_latency_side_panel_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/input_latency_side_panel_test.html
new file mode 100644
index 00000000000..de225416faa
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/side_panel/input_latency_side_panel_test.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/chrome/cc/input_latency_async_slice.html">
+<link rel="import" href="/tracing/extras/importer/trace_event_importer.html">
+<link rel="import" href="/tracing/model/model.html">
+<link rel="import" href="/tracing/ui/extras/side_panel/input_latency_side_panel.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ test('basic', function() {
+ const latencyData = [
+ {
+ x: 1000,
+ latency: 16
+ },
+ {
+ x: 2000,
+ latency: 17
+ },
+ {
+ x: 3000,
+ latency: 14
+ },
+ {
+ x: 4000,
+ latency: 23
+ }
+ ];
+ let lc = document.createElement('tr-ui-e-s-input-latency-side-panel');
+ let container = document.createElement('div');
+ this.addHTMLOutput(container);
+ const latencyChart = lc.createLatencyLineChart(
+ latencyData, 'latency', container);
+
+ const frametimeData = [
+ {
+ x: 1000,
+ frametime: 16
+ },
+ {
+ x: 2000,
+ frametime: 17
+ },
+ {
+ x: 3000,
+ frametime: 14
+ },
+ {
+ x: 4000,
+ frametime: 23
+ }
+ ];
+ lc = document.createElement('tr-ui-e-s-input-latency-side-panel');
+ container = document.createElement('div');
+ this.addHTMLOutput(container);
+ const frametimeChart = lc.createLatencyLineChart(
+ frametimeData, 'frametime', container);
+ });
+
+ test('brushedRangeChange', function() {
+ const events = [];
+ for (let i = 0; i < 10; i++) {
+ const startTs = i * 10000;
+ const endTs = startTs + 1000 * (i % 2);
+ events.push(
+ {
+ 'cat': 'benchmark',
+ 'pid': 3507,
+ 'tid': 3507,
+ 'ts': startTs,
+ 'ph': 'S',
+ 'name': 'InputLatency',
+ 'id': i
+ });
+ events.push(
+ {
+ 'cat': 'benchmark',
+ 'pid': 3507,
+ 'tid': 3507,
+ 'ts': endTs,
+ 'ph': 'T',
+ 'name': 'InputLatency',
+ 'args': {'step': 'GestureScrollUpdate'},
+ 'id': i
+ });
+ events.push(
+ {
+ 'cat': 'benchmark',
+ 'pid': 3507,
+ 'tid': 3507,
+ 'ts': endTs,
+ 'ph': 'F',
+ 'name': 'InputLatency',
+ 'args': {
+ 'data': {
+ 'INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT': {
+ 'time': startTs
+ },
+ 'INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT': {
+ 'time': endTs
+ }
+ }
+ },
+ 'id': i
+ });
+ }
+ events.push({'cat': '__metadata',
+ 'pid': 3507,
+ 'tid': 3507,
+ 'ts': 0,
+ 'ph': 'M',
+ 'name': 'thread_name',
+ 'args': {'name': 'CrBrowserMain'}});
+
+ const panel = document.createElement('tr-ui-e-s-input-latency-side-panel');
+ this.addHTMLOutput(panel);
+
+ let selectionChanged = false;
+
+ panel.model = tr.c.TestUtils.newModelWithEvents([events]);
+ function listener(e) {
+ selectionChanged = true;
+ assert.strictEqual(e.selection.length, 3);
+ const predictedStarts = [20, 31, 40];
+ let i = 0;
+ for (const event of e.selection) {
+ assert.strictEqual(event.start, predictedStarts[i++]);
+ }
+ }
+ panel.ownerDocument.addEventListener('requestSelectionChange', listener);
+ try {
+ panel.setBrushedIndices(2, 4);
+ } finally {
+ panel.ownerDocument.removeEventListener(
+ 'requestSelectionChange', listener);
+ }
+ assert.isTrue(selectionChanged);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats.html
new file mode 100644
index 00000000000..31bc1dbd997
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/extras/system_stats/system_stats_snapshot.html">
+<link rel="import"
+ href="/tracing/ui/extras/system_stats/system_stats_instance_track.html">
+<link rel="import"
+ href="/tracing/ui/extras/system_stats/system_stats_snapshot_view.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track.css b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track.css
new file mode 100644
index 00000000000..40096f5497c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track.css
@@ -0,0 +1,15 @@
+/* Copyright (c) 2013 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.
+ */
+
+.tr-ui-e-system-stats-instance-track {
+ height: 500px;
+}
+
+.tr-ui-e-system-stats-instance-track ul {
+ list-style: none;
+ list-style-position: outside;
+ margin: 0;
+ overflow: hidden;
+}
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track.html
new file mode 100644
index 00000000000..7695086660c
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track.html
@@ -0,0 +1,451 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="stylesheet"
+ href="/tracing/ui/extras/system_stats/system_stats_instance_track.css">
+
+<link rel="import" href="/tracing/base/unit.html">
+<link rel="import" href="/tracing/base/utils.html">
+<link rel="import" href="/tracing/ui/base/event_presenter.html">
+<link rel="import" href="/tracing/ui/base/ui.html">
+<link rel="import" href="/tracing/ui/tracks/object_instance_track.html">
+<link rel="import" href="/tracing/ui/tracks/stacked_bars_track.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.system_stats', function() {
+ const EventPresenter = tr.ui.b.EventPresenter;
+
+ let statCount;
+
+ const excludedStats = {'meminfo': {
+ 'pswpin': 0,
+ 'pswpout': 0,
+ 'pgmajfault': 0},
+ 'diskinfo': {
+ 'io': 0,
+ 'io_time': 0,
+ 'read_time': 0,
+ 'reads': 0,
+ 'reads_merged': 0,
+ 'sectors_read': 0,
+ 'sectors_written': 0,
+ 'weighted_io_time': 0,
+ 'write_time': 0,
+ 'writes': 0,
+ 'writes_merged': 0},
+ 'swapinfo': {},
+ 'perfinfo': {
+ 'idle_time': 0,
+ 'read_transfer_count': 0,
+ 'write_transfer_count': 0,
+ 'other_transfer_count': 0,
+ 'read_operation_count': 0,
+ 'write_operation_count': 0,
+ 'other_operation_count': 0,
+ 'pagefile_pages_written': 0,
+ 'pagefile_pages_write_ios': 0,
+ 'available_pages': 0,
+ 'pages_read': 0,
+ 'page_read_ios': 0}
+ };
+
+ /**
+ * Tracks that display system stats data.
+ *
+ * @constructor
+ * @extends {StackedBarsTrack}
+ */
+
+ const SystemStatsInstanceTrack = tr.ui.b.define(
+ 'tr-ui-e-system-stats-instance-track', tr.ui.tracks.StackedBarsTrack);
+
+ const kPageSizeWindows = 4096;
+
+ SystemStatsInstanceTrack.prototype = {
+
+ __proto__: tr.ui.tracks.StackedBarsTrack.prototype,
+
+ decorate(viewport) {
+ tr.ui.tracks.StackedBarsTrack.prototype.decorate.call(this, viewport);
+ Polymer.dom(this).classList.add('tr-ui-e-system-stats-instance-track');
+ this.objectInstance_ = null;
+ },
+
+ set objectInstances(objectInstances) {
+ if (!objectInstances) {
+ this.objectInstance_ = [];
+ return;
+ }
+ if (objectInstances.length !== 1) {
+ throw new Error('Bad object instance count.');
+ }
+ this.objectInstance_ = objectInstances[0];
+ if (this.objectInstance_ !== null) {
+ this.computeRates_(this.objectInstance_.snapshots);
+ this.maxStats_ = this.computeMaxStats_(
+ this.objectInstance_.snapshots);
+ }
+ },
+
+ computeRates_(snapshots) {
+ for (let i = 0; i < snapshots.length; i++) {
+ const snapshot = snapshots[i];
+ const stats = snapshot.getStats();
+ let prevSnapshot;
+
+ if (i === 0) {
+ // Deltas will be zero.
+ prevSnapshot = snapshots[0];
+ } else {
+ prevSnapshot = snapshots[i - 1];
+ }
+ const prevStats = prevSnapshot.getStats();
+ let timeIntervalSeconds = (snapshot.ts - prevSnapshot.ts) / 1000;
+ // Prevent divide by zero.
+ if (timeIntervalSeconds === 0) {
+ timeIntervalSeconds = 1;
+ }
+
+ this.computeRatesRecursive_(prevStats, stats,
+ timeIntervalSeconds);
+ }
+ },
+
+ computeRatesRecursive_(prevStats, stats,
+ timeIntervalSeconds) {
+ for (const statName in stats) {
+ if (stats[statName] instanceof Object) {
+ this.computeRatesRecursive_(prevStats[statName],
+ stats[statName],
+ timeIntervalSeconds);
+ } else {
+ if (statName === 'sectors_read') {
+ stats.bytes_read_per_sec = (stats.sectors_read -
+ prevStats.sectors_read) *
+ 512 / timeIntervalSeconds;
+ }
+ if (statName === 'sectors_written') {
+ stats.bytes_written_per_sec =
+ (stats.sectors_written -
+ prevStats.sectors_written) *
+ 512 / timeIntervalSeconds;
+ }
+ if (statName === 'pgmajfault') {
+ stats.pgmajfault_per_sec = (stats.pgmajfault -
+ prevStats.pgmajfault) /
+ timeIntervalSeconds;
+ }
+ if (statName === 'pswpin') {
+ stats.bytes_swpin_per_sec = (stats.pswpin -
+ prevStats.pswpin) *
+ 1000 / timeIntervalSeconds;
+ }
+ if (statName === 'pswpout') {
+ stats.bytes_swpout_per_sec = (stats.pswpout -
+ prevStats.pswpout) *
+ 1000 / timeIntervalSeconds;
+ }
+
+ // All the stats below are available only on Windows:
+
+ if (statName === 'idle_time') {
+ // Total amount of idle_time, in unit of 100 nanoseconds.
+ const units = tr.b.convertUnit(100.,
+ tr.b.UnitScale.TIME.NANO_SEC, tr.b.UnitScale.TIME.SEC);
+ const idleTile = (stats.idle_time - prevStats.idle_time) * units;
+ stats.idle_time_per_sec = idleTile / timeIntervalSeconds;
+ }
+ if (statName === 'read_transfer_count') {
+ const bytesRead = stats.read_transfer_count -
+ prevStats.read_transfer_count;
+ stats.bytes_read_per_sec = bytesRead / timeIntervalSeconds;
+ }
+ if (statName === 'write_transfer_count') {
+ const bytesWritten = stats.write_transfer_count -
+ prevStats.write_transfer_count;
+ stats.bytes_written_per_sec = bytesWritten / timeIntervalSeconds;
+ }
+ if (statName === 'other_transfer_count') {
+ const bytesTransfer = stats.other_transfer_count -
+ prevStats.other_transfer_count;
+ stats.bytes_other_per_sec = bytesTransfer / timeIntervalSeconds;
+ }
+ if (statName === 'read_operation_count') {
+ const readOperation = stats.read_operation_count -
+ prevStats.read_operation_count;
+ stats.read_operation_per_sec = readOperation / timeIntervalSeconds;
+ }
+ if (statName === 'write_operation_count') {
+ const writeOperation = stats.write_operation_count -
+ prevStats.write_operation_count;
+ stats.write_operation_per_sec =
+ writeOperation / timeIntervalSeconds;
+ }
+ if (statName === 'other_operation_count') {
+ const otherOperation = stats.other_operation_count -
+ prevStats.other_operation_count;
+ stats.other_operation_per_sec =
+ otherOperation / timeIntervalSeconds;
+ }
+ if (statName === 'pagefile_pages_written') {
+ const pageFileBytesWritten =
+ (stats.pagefile_pages_written -
+ prevStats.pagefile_pages_written) * kPageSizeWindows;
+ stats.pagefile_bytes_written_per_sec =
+ pageFileBytesWritten / timeIntervalSeconds;
+ }
+ if (statName === 'pagefile_pages_write_ios') {
+ const pagefileWriteOperation =
+ stats.pagefile_pages_write_ios -
+ prevStats.pagefile_pages_write_ios;
+ stats.pagefile_write_operation_per_sec =
+ pagefileWriteOperation / timeIntervalSeconds;
+ }
+ if (statName === 'available_pages') {
+ // Nothing to do here for now.
+ stats.available_pages_in_bytes =
+ stats.available_pages * kPageSizeWindows;
+ // TODO(sebmarchand): Add a available_pages_field that tracks the
+ // variation of this metric?
+ }
+ if (statName === 'pages_read') {
+ const pagesBytesRead =
+ (stats.pages_read - prevStats.pages_read) * kPageSizeWindows;
+ stats.bytes_read_per_sec = pagesBytesRead / timeIntervalSeconds;
+ }
+ if (statName === 'page_read_ios') {
+ const pagesBytesReadOperations =
+ stats.page_read_ios - prevStats.page_read_ios;
+ stats.pagefile_write_operation_per_sec =
+ pagesBytesReadOperations / timeIntervalSeconds;
+ }
+ }
+ }
+ },
+
+ computeMaxStats_(snapshots) {
+ const maxStats = {};
+ statCount = 0;
+
+ for (let i = 0; i < snapshots.length; i++) {
+ const snapshot = snapshots[i];
+ const stats = snapshot.getStats();
+
+ this.computeMaxStatsRecursive_(stats, maxStats,
+ excludedStats);
+ }
+
+ return maxStats;
+ },
+
+ computeMaxStatsRecursive_(stats, maxStats, excludedStats) {
+ for (const statName in stats) {
+ if (stats[statName] instanceof Object) {
+ if (!(statName in maxStats)) {
+ maxStats[statName] = {};
+ }
+
+ let excludedNested;
+ if (excludedStats && statName in excludedStats) {
+ excludedNested = excludedStats[statName];
+ } else {
+ excludedNested = null;
+ }
+
+ this.computeMaxStatsRecursive_(stats[statName],
+ maxStats[statName],
+ excludedNested);
+ } else {
+ if (excludedStats && statName in excludedStats) {
+ continue;
+ }
+ if (!(statName in maxStats)) {
+ maxStats[statName] = 0;
+ statCount++;
+ }
+ if (stats[statName] > maxStats[statName]) {
+ maxStats[statName] = stats[statName];
+ }
+ }
+ }
+ },
+
+ get height() {
+ return window.getComputedStyle(this).height;
+ },
+
+ set height(height) {
+ this.style.height = height;
+ },
+
+ draw(type, viewLWorld, viewRWorld, viewHeight) {
+ switch (type) {
+ case tr.ui.tracks.DrawType.GENERAL_EVENT:
+ this.drawStatBars_(viewLWorld, viewRWorld);
+ break;
+ }
+ },
+
+ drawStatBars_(viewLWorld, viewRWorld) {
+ const ctx = this.context();
+ const pixelRatio = window.devicePixelRatio || 1;
+
+ const bounds = this.getBoundingClientRect();
+ const width = bounds.width * pixelRatio;
+ const height = (bounds.height * pixelRatio) / statCount;
+
+ // Culling parameters.
+ const vp = this.viewport.currentDisplayTransform;
+
+ // Scale by the size of the largest snapshot.
+ const maxStats = this.maxStats_;
+
+ const objectSnapshots = this.objectInstance_.snapshots;
+ let lowIndex = tr.b.findLowIndexInSortedArray(
+ objectSnapshots,
+ function(snapshot) {
+ return snapshot.ts;
+ },
+ viewLWorld);
+
+ // Assure that the stack with the left edge off screen still gets drawn
+ if (lowIndex > 0) lowIndex -= 1;
+
+ for (let i = lowIndex; i < objectSnapshots.length; ++i) {
+ const snapshot = objectSnapshots[i];
+ const trace = snapshot.getStats();
+ const currentY = height;
+
+ const left = snapshot.ts;
+ if (left > viewRWorld) break;
+
+ let leftView = vp.xWorldToView(left);
+ if (leftView < 0) leftView = 0;
+
+ // Compute the edges for the column graph bar.
+ let right;
+ if (i !== objectSnapshots.length - 1) {
+ right = objectSnapshots[i + 1].ts;
+ } else {
+ // If this is the last snapshot of multiple snapshots, use the width
+ // of the previous snapshot for the width.
+ if (objectSnapshots.length > 1) {
+ right = objectSnapshots[i].ts + (objectSnapshots[i].ts -
+ objectSnapshots[i - 1].ts);
+ } else {
+ // If there's only one snapshot, use max bounds as the width.
+ right = this.objectInstance_.parent.model.bounds.max;
+ }
+ }
+
+ let rightView = vp.xWorldToView(right);
+ if (rightView > width) {
+ rightView = width;
+ }
+
+ // Floor the bounds to avoid a small gap between stacks.
+ leftView = Math.floor(leftView);
+ rightView = Math.floor(rightView);
+
+ // Descend into nested stats.
+ this.drawStatBarsRecursive_(snapshot,
+ leftView,
+ rightView,
+ height,
+ trace,
+ maxStats,
+ currentY);
+
+ if (i === lowIndex) {
+ this.drawStatNames_(leftView, height, currentY, '', maxStats);
+ }
+ }
+ ctx.lineWidth = 1;
+ },
+
+ drawStatBarsRecursive_(snapshot,
+ leftView,
+ rightView,
+ height,
+ stats,
+ maxStats,
+ currentY) {
+ const ctx = this.context();
+
+ for (const statName in maxStats) {
+ if (stats[statName] instanceof Object) {
+ // Use the y-position returned from the recursive call.
+ currentY = this.drawStatBarsRecursive_(snapshot,
+ leftView,
+ rightView,
+ height,
+ stats[statName],
+ maxStats[statName],
+ currentY);
+ } else {
+ const maxStat = maxStats[statName];
+
+ // Draw a bar for the stat. The height of the bar is scaled
+ // against the largest value of the stat across all snapshots.
+ ctx.fillStyle = EventPresenter.getBarSnapshotColor(
+ snapshot, Math.round(currentY / height));
+
+ let barHeight;
+ if (maxStat > 0) {
+ barHeight = height * Math.max(stats[statName], 0) / maxStat;
+ } else {
+ barHeight = 0;
+ }
+
+ ctx.fillRect(leftView, currentY - barHeight,
+ Math.max(rightView - leftView, 1), barHeight);
+
+ currentY += height;
+ }
+ }
+
+ // Return the updated y-position.
+ return currentY;
+ },
+
+ drawStatNames_(leftView, height, currentY, prefix, maxStats) {
+ const ctx = this.context();
+
+ ctx.textAlign = 'end';
+ ctx.font = '12px Arial';
+ ctx.fillStyle = '#000000';
+ for (const statName in maxStats) {
+ if (maxStats[statName] instanceof Object) {
+ currentY = this.drawStatNames_(leftView, height, currentY,
+ statName, maxStats[statName]);
+ } else {
+ let fullname = statName;
+
+ if (prefix !== '') {
+ fullname = prefix + ' :: ' + statName;
+ }
+
+ ctx.fillText(fullname, leftView - 10, currentY - height / 4);
+ currentY += height;
+ }
+ }
+
+ return currentY;
+ }
+ };
+
+ tr.ui.tracks.ObjectInstanceTrack.register(
+ SystemStatsInstanceTrack,
+ {typeName: 'base::TraceEventSystemStatsMonitor::SystemStats'});
+
+ return {
+ SystemStatsInstanceTrack,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track_test.html
new file mode 100644
index 00000000000..8dc4bc28264
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_instance_track_test.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/ui/extras/system_stats/system_stats.html">
+<link rel="import" href="/tracing/ui/timeline_viewport.html">
+<link rel="import" href="/tracing/ui/tracks/drawing_container.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const SystemStatsInstanceTrack =
+ tr.ui.e.system_stats.SystemStatsInstanceTrack;
+ const Viewport = tr.ui.TimelineViewport;
+
+ const createObjects = function() {
+ const objectInstance = new tr.model.ObjectInstance({});
+ const snapshots = [];
+
+ const stats1 = {};
+ const stats2 = {};
+
+ stats1.committed_memory = 2000000;
+ stats2.committed_memory = 3000000;
+
+ stats1.meminfo = {};
+ stats1.meminfo.free = 10000;
+ stats2.meminfo = {};
+ stats2.meminfo.free = 20000;
+
+ stats1.perfinfo = {};
+ stats1.perfinfo.idle_time = 10;
+ stats1.perfinfo.read_transfer_count = 20;
+ stats1.perfinfo.write_transfer_count = 30;
+ stats1.perfinfo.other_transfer_count = 40;
+ stats1.perfinfo.read_operation_count = 2;
+ stats1.perfinfo.write_operation_count = 3;
+ stats1.perfinfo.other_operation_count = 4;
+ stats1.perfinfo.pagefile_pages_written = 5;
+ stats1.perfinfo.pagefile_pages_write_ios = 6;
+
+ stats2.perfinfo = {};
+ stats2.perfinfo.idle_time = 110;
+ stats2.perfinfo.read_transfer_count = 120;
+ stats2.perfinfo.write_transfer_count = 130;
+ stats2.perfinfo.other_transfer_count = 140;
+ stats2.perfinfo.read_operation_count = 102;
+ stats2.perfinfo.write_operation_count = 103;
+ stats2.perfinfo.other_operation_count = 104;
+ stats2.perfinfo.pagefile_pages_written = 105;
+ stats2.perfinfo.pagefile_pages_write_ios = 106;
+
+ snapshots.push(new tr.e.system_stats.SystemStatsSnapshot(objectInstance,
+ 10, stats1));
+ snapshots.push(new tr.e.system_stats.SystemStatsSnapshot(objectInstance,
+ 20, stats2));
+
+ objectInstance.snapshots = snapshots;
+
+ return objectInstance;
+ };
+
+ test('instantiate', function() {
+ const objectInstances = [];
+ objectInstances.push(createObjects());
+
+ const div = document.createElement('div');
+ const viewport = new Viewport(div);
+ const drawingContainer = new tr.ui.tracks.DrawingContainer(viewport);
+ Polymer.dom(div).appendChild(drawingContainer);
+
+ const track = new SystemStatsInstanceTrack(viewport);
+ track.objectInstances = objectInstances;
+ Polymer.dom(drawingContainer).appendChild(track);
+
+ const snapshot1 = track.objectInstance_.snapshots[1];
+ const stats1 = snapshot1.getStats();
+
+ // Raw counters should not move.
+ assert.strictEqual(stats1.perfinfo.idle_time, 110);
+ assert.strictEqual(stats1.perfinfo.read_operation_count, 102);
+ assert.strictEqual(stats1.perfinfo.write_operation_count, 103);
+ assert.strictEqual(stats1.perfinfo.other_operation_count, 104);
+ assert.strictEqual(stats1.perfinfo.read_transfer_count, 120);
+ assert.strictEqual(stats1.perfinfo.write_transfer_count, 130);
+ assert.strictEqual(stats1.perfinfo.other_transfer_count, 140);
+ assert.strictEqual(stats1.perfinfo.pagefile_pages_written, 105);
+ assert.strictEqual(stats1.perfinfo.pagefile_pages_write_ios, 106);
+
+ // Rates should be computed.
+ assert.strictEqual(stats1.perfinfo.idle_time_per_sec, 0.001);
+ assert.strictEqual(stats1.perfinfo.bytes_read_per_sec, 10000);
+ assert.strictEqual(stats1.perfinfo.bytes_written_per_sec, 10000);
+ assert.strictEqual(stats1.perfinfo.bytes_other_per_sec, 10000);
+ assert.strictEqual(stats1.perfinfo.read_operation_per_sec, 10000);
+ assert.strictEqual(stats1.perfinfo.write_operation_per_sec, 10000);
+ assert.strictEqual(stats1.perfinfo.other_operation_per_sec, 10000);
+ assert.strictEqual(stats1.perfinfo.pagefile_bytes_written_per_sec,
+ 40960000);
+ assert.strictEqual(stats1.perfinfo.pagefile_write_operation_per_sec, 10000);
+
+ this.addHTMLOutput(div);
+ drawingContainer.invalidate();
+
+ track.heading = 'testBasic';
+ const dt = new tr.ui.TimelineDisplayTransform();
+ dt.xSetWorldBounds(0, 50, track.clientWidth);
+ track.viewport.setDisplayTransformImmediately(dt);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_snapshot_view.css b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_snapshot_view.css
new file mode 100644
index 00000000000..e698b15aa70
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_snapshot_view.css
@@ -0,0 +1,28 @@
+/* Copyright (c) 2013 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.
+ */
+
+.tr-ui-e-system-stats-snapshot-view .subhead {
+ font-size: small;
+ padding-bottom: 10px;
+}
+
+.tr-ui-e-system-stats-snapshot-view ul {
+ background-position: 0 5px;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ font-family: monospace;
+ list-style: none;
+ margin: 0;
+ padding-left: 15px;
+}
+
+.tr-ui-e-system-stats-snapshot-view li {
+ background-position: 0 5px;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ list-style: none;
+ margin: 0;
+ padding-left: 15px;
+}
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_snapshot_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_snapshot_view.html
new file mode 100644
index 00000000000..54f4b869f4a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/system_stats/system_stats_snapshot_view.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<link rel="stylesheet"
+ href="/tracing/ui/extras/system_stats/system_stats_snapshot_view.css">
+
+<link rel="import" href="/tracing/ui/analysis/object_snapshot_view.html">
+
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.system_stats', function() {
+ /*
+ * Displays a system stats snapshot in a human readable form. @constructor
+ */
+ const SystemStatsSnapshotView = tr.ui.b.define(
+ 'tr-ui-e-system-stats-snapshot-view', tr.ui.analysis.ObjectSnapshotView);
+
+ SystemStatsSnapshotView.prototype = {
+ __proto__: tr.ui.analysis.ObjectSnapshotView.prototype,
+
+ decorate() {
+ Polymer.dom(this).classList.add('tr-ui-e-system-stats-snapshot-view');
+ },
+
+ updateContents() {
+ const snapshot = this.objectSnapshot_;
+ if (!snapshot || !snapshot.getStats()) {
+ Polymer.dom(this).textContent = 'No system stats snapshot found.';
+ return;
+ }
+ // Clear old snapshot view.
+ Polymer.dom(this).textContent = '';
+
+ const stats = snapshot.getStats();
+ Polymer.dom(this).appendChild(this.buildList_(stats));
+ },
+
+ isFloat(n) {
+ return typeof n === 'number' && n % 1 !== 0;
+ },
+
+ /**
+ * Creates nested lists.
+ *
+ * @param {Object} stats The current trace system stats entry.
+ * @return {Element} A ul list element.
+ */
+ buildList_(stats) {
+ const statList = document.createElement('ul');
+
+ for (const statName in stats) {
+ const statText = document.createElement('li');
+ Polymer.dom(statText).textContent = '' + statName + ': ';
+ Polymer.dom(statList).appendChild(statText);
+
+ if (stats[statName] instanceof Object) {
+ Polymer.dom(statList).appendChild(this.buildList_(stats[statName]));
+ } else {
+ if (this.isFloat(stats[statName])) {
+ Polymer.dom(statText).textContent += stats[statName].toFixed(2);
+ } else {
+ Polymer.dom(statText).textContent += stats[statName];
+ }
+ }
+ }
+
+ return statList;
+ }
+ };
+
+ tr.ui.analysis.ObjectSnapshotView.register(
+ SystemStatsSnapshotView,
+ {typeName: 'base::TraceEventSystemStatsMonitor::SystemStats'});
+
+ return {
+ SystemStatsSnapshotView,
+ };
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/systrace_config.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/systrace_config.html
new file mode 100644
index 00000000000..fcc410b754a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/systrace_config.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+<!--
+TODO(charliea): Make all UI files depend on tracing/ui/base/base.html in the
+same way that all non-UI files depend on tracing/base/base.html. Enforce this
+dependency with a presubmit.
+-->
+<link rel="import" href="/tracing/ui/base/base.html" data-suppress-import-order>
+
+<link rel="import" href="/tracing/extras/systrace_config.html">
+<link rel="import" href="/tracing/ui/base/ui.html">
+<link rel="import" href="/tracing/ui/extras/side_panel/alerts_side_panel.html">
+<link rel="import" href="/tracing/ui/timeline_view.html">
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/gc_objects_stats_table.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/gc_objects_stats_table.html
new file mode 100644
index 00000000000..bc3247d9289
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/gc_objects_stats_table.html
@@ -0,0 +1,728 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/extras/v8/v8_gc_stats_thread_slice.html">
+<link rel="import" href="/tracing/ui/base/table.html">
+
+<dom-module id='tr-ui-e-v8-gc-objects-stats-table'>
+ <template>
+ <style>
+ tr-ui-b-table {
+ flex: 0 0 auto;
+ align-self: stretch;
+ margin-top: 1em;
+ font-size: 12px;
+ }
+ .diff {
+ display: inline-block;
+ margin-top: 1em;
+ margin-left: 0.8em;
+ }
+ </style>
+ <div class="diff" id="diffOption">
+ Diff
+ </div>
+ <tr-ui-b-table id="diffTable"></tr-ui-b-table>
+ <tr-ui-b-table id="table"></tr-ui-b-table>
+ </template>
+</dom-module>
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.v8', function() {
+ // Instance types that should not be part of the overview as they are either
+ // double-attributed (e.g. also part of some other instance type) or do not
+ // make any sense in memory profiling.
+ const IGNORED_ENTRIES = {
+ // Ignore code aging entries as they are already accounted in their
+ // respective code instance types.
+ match: full => full.startsWith('*CODE_AGE_')
+ };
+
+ // Groups are matched on a first-matched basis, i.e., once a group matches we
+ // are done with an entry.
+ // Requires properties:
+ // - match(full): Return true iff |full| should be part of the group and
+ // false otherwise.
+ // - keyToName(key): Returns the human readable name for |key|.
+ // - nameToKey(name): Returns the key for |name|.
+ // Optional properties:
+ // - realEntry: A string representing the actual entry in the trace. If this
+ // entry is present an additional entry UNKNOWN will be created holding all
+ // the unaccounted data.
+ const INSTANCE_TYPE_GROUPS = {
+ FIXED_ARRAY_TYPE: {
+ match: full => full.startsWith('*FIXED_ARRAY_'),
+ realEntry: 'FIXED_ARRAY_TYPE',
+ keyToName: key => key.slice('*FIXED_ARRAY_'.length)
+ .slice(0, -('_SUB_TYPE'.length)),
+ nameToKey: name => '*FIXED_ARRAY_' + name + '_SUB_TYPE'
+ },
+ CODE_TYPE: {
+ match: full => full.startsWith('*CODE_'),
+ realEntry: 'CODE_TYPE',
+ keyToName: key => key.slice('*CODE_'.length),
+ nameToKey: name => '*CODE_' + name
+ },
+ JS_OBJECTS: {
+ match: full => full.startsWith('JS_'),
+ keyToName: key => key,
+ nameToKey: name => name
+ },
+ Strings: {
+ match: full => full.endsWith('STRING_TYPE'),
+ keyToName: key => key,
+ nameToKey: name => name
+ }
+ };
+
+ const DIFF_COLOR = {
+ GREEN: '#64DD17',
+ RED: '#D50000'
+ };
+
+ function computePercentage(valueA, valueB) {
+ if (valueA === 0) return 0;
+ return valueA / valueB * 100;
+ }
+
+ class DiffEntry {
+ constructor(originalEntry, diffEntry) {
+ this.originalEntry_ = originalEntry;
+ this.diffEntry_ = diffEntry;
+ }
+ get title() {
+ return this.diffEntry_.title;
+ }
+ get overall() {
+ return this.diffEntry_.overall;
+ }
+ get overAllocated() {
+ return this.diffEntry_.overAllocated;
+ }
+ get count() {
+ return this.diffEntry_.count;
+ }
+ get overallPercent() {
+ return this.diffEntry_.overallPercent;
+ }
+ get overAllocatedPercent() {
+ return this.diffEntry_.overAllocatedPercent;
+ }
+ get origin() {
+ return this.originalEntry_;
+ }
+ get diff() {
+ return this.diffEntry_;
+ }
+ get subRows() {
+ return this.diffEntry_.subRows;
+ }
+ }
+
+ class Entry {
+ constructor(title, count, overall, overAllocated, histogram,
+ overAllocatedHistogram) {
+ this.title_ = title;
+ this.overall_ = overall;
+ this.count_ = count;
+ this.overAllocated_ = overAllocated;
+ this.histogram_ = histogram;
+ this.overAllocatedHistogram_ = overAllocatedHistogram;
+ this.bucketSize_ = this.histogram_.length;
+ this.overallPercent_ = 100;
+ this.overAllocatedPercent_ = 100;
+ }
+
+ get title() {
+ return this.title_;
+ }
+
+ get overall() {
+ return this.overall_;
+ }
+
+ get count() {
+ return this.count_;
+ }
+
+ get overAllocated() {
+ return this.overAllocated_;
+ }
+
+ get histogram() {
+ return this.histogram_;
+ }
+
+ get overAllocatedHistogram() {
+ return this.overAllocatedHistogram_;
+ }
+
+ get bucketSize() {
+ return this.bucketSize_;
+ }
+
+ get overallPercent() {
+ return this.overallPercent_;
+ }
+
+ set overallPercent(value) {
+ this.overallPercent_ = value;
+ }
+
+ get overAllocatedPercent() {
+ return this.overAllocatedPercent_;
+ }
+
+ set overAllocatedPercent(value) {
+ this.overAllocatedPercent_ = value;
+ }
+
+ setFromObject(obj) {
+ this.count_ = obj.count;
+ // Calculate memory in KB.
+ this.overall_ = obj.overall / 1024;
+ this.overAllocated_ = obj.over_allocated / 1024;
+ this.histogram_ = obj.histogram;
+ this.overAllocatedHistogram_ = obj.over_allocated_histogram;
+ }
+
+ diff(other) {
+ const entry = new Entry(this.title_, other.count_ - this.count,
+ other.overall_ - this.overall,
+ other.overAllocated_ - this.overAllocated, [], []);
+ entry.overallPercent = computePercentage(entry.overall, this.overall);
+ entry.overAllocatedPercent = computePercentage(entry.overAllocated,
+ this.overAllocated);
+ return new DiffEntry(this, entry);
+ }
+ }
+
+ class GroupedEntry extends Entry {
+ constructor(title, count, overall, overAllocated, histogram,
+ overAllocatedHistogram) {
+ super(title, count, overall, overAllocated, histogram,
+ overAllocatedHistogram);
+ this.histogram_.fill(0);
+ this.overAllocatedHistogram_.fill(0);
+ this.entries_ = new Map();
+ }
+
+ get title() {
+ return this.title_;
+ }
+
+ set title(value) {
+ this.title_ = value;
+ }
+
+ get subRows() {
+ return Array.from(this.entries_.values());
+ }
+
+ getEntryFromTitle(title) {
+ return this.entries_.get(title);
+ }
+
+ add(entry) {
+ this.count_ += entry.count;
+ this.overall_ += entry.overall;
+ this.overAllocated_ += entry.overAllocated;
+ if (this.bucketSize_ === entry.bucketSize) {
+ for (let i = 0; i < this.bucketSize_; ++i) {
+ this.histogram_[i] += entry.histogram[i];
+ this.overAllocatedHistogram_[i] += entry.overAllocatedHistogram[i];
+ }
+ }
+ this.entries_.set(entry.title, entry);
+ }
+
+ accumulateUnknown(title) {
+ let unknownCount = this.count_;
+ let unknownOverall = this.overall_;
+ let unknownOverAllocated = this.overAllocated_;
+ const unknownHistogram = tr.b.deepCopy(this.histogram_);
+ const unknownOverAllocatedHistogram =
+ tr.b.deepCopy(this.overAllocatedHistogram_);
+ for (const entry of this.entries_.values()) {
+ unknownCount -= entry.count;
+ unknownOverall -= entry.overall;
+ unknownOverAllocated -= entry.overAllocated;
+ for (let i = 0; i < this.bucketSize_; ++i) {
+ unknownHistogram[i] -= entry.histogram[i];
+ unknownOverAllocatedHistogram[i] -= entry.overAllocatedHistogram[i];
+ }
+ }
+ unknownOverAllocated =
+ unknownOverAllocated < 0 ? 0 : unknownOverAllocated;
+ this.entries_.set(title, new Entry(title, unknownCount, unknownOverall,
+ unknownOverAllocated, unknownHistogram,
+ unknownOverAllocatedHistogram));
+ }
+
+ calculatePercentage() {
+ for (const entry of this.entries_.values()) {
+ entry.overallPercent = computePercentage(entry.overall, this.overall_);
+ entry.overAllocatedPercent =
+ computePercentage(entry.overAllocated, this.overAllocated_);
+
+ if (entry instanceof GroupedEntry) entry.calculatePercentage();
+ }
+ }
+
+ diff(other) {
+ let newTitle = '';
+ if (this.title_.startsWith('Isolate')) {
+ newTitle = 'Total';
+ } else {
+ newTitle = this.title_;
+ }
+ const result = new GroupedEntry(newTitle, 0, 0, 0, [], []);
+ for (const entry of this.entries_) {
+ const otherEntry = other.getEntryFromTitle(entry[0]);
+ if (otherEntry === undefined) continue;
+ result.add(entry[1].diff(otherEntry));
+ }
+ result.overallPercent = computePercentage(result.overall, this.overall);
+ result.overAllocatedPercent = computePercentage(result.overAllocated,
+ this.overAllocated);
+ return new DiffEntry(this, result);
+ }
+ }
+
+ function createSelector(targetEl, defaultValue, items, callback) {
+ const selectorEl = document.createElement('select');
+ selectorEl.addEventListener('change', callback.bind(targetEl));
+ const defaultOptionEl = document.createElement('option');
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ const optionEl = document.createElement('option');
+ Polymer.dom(optionEl).textContent = item.label;
+ optionEl.targetPropertyValue = item.value;
+ optionEl.item = item;
+ Polymer.dom(selectorEl).appendChild(optionEl);
+ }
+ selectorEl.__defineGetter__('selectedValue', function(v) {
+ if (selectorEl.children[selectorEl.selectedIndex] === undefined) {
+ return undefined;
+ }
+ return selectorEl.children[selectorEl.selectedIndex].targetPropertyValue;
+ });
+ selectorEl.__defineGetter__('selectedItem', function(v) {
+ if (selectorEl.children[selectorEl.selectedIndex] === undefined) {
+ return undefined;
+ }
+ return selectorEl.children[selectorEl.selectedIndex].item;
+ });
+ selectorEl.__defineSetter__('selectedValue', function(v) {
+ for (let i = 0; i < selectorEl.children.length; i++) {
+ const value = selectorEl.children[i].targetPropertyValue;
+ if (value === v) {
+ const changed = selectorEl.selectedIndex !== i;
+ if (changed) {
+ selectorEl.selectedIndex = i;
+ callback();
+ }
+ return;
+ }
+ }
+ throw new Error('Not a valid value');
+ });
+ selectorEl.selectedIndex = -1;
+
+ return selectorEl;
+ }
+
+ function plusMinus(value, toFixed = 3) {
+ return (value > 0 ? '+' : '') + value.toFixed(toFixed);
+ }
+
+ function addArrow(value) {
+ if (value === 0) return value;
+ if (value === Number.NEGATIVE_INFINITY) return '\u2193\u221E';
+ if (value === Number.POSITIVE_INFINITY) return '\u2191\u221E';
+ return (value > 0 ? '\u2191' : '\u2193') + Math.abs(value.toFixed(3));
+ }
+
+ Polymer({
+ is: 'tr-ui-e-v8-gc-objects-stats-table',
+
+ ready() {
+ this.$.diffOption.style.display = 'none';
+ this.isolateEntries_ = [];
+ this.selector1_ = undefined;
+ this.selector2_ = undefined;
+ },
+
+ constructDiffTable_(table) {
+ this.$.diffTable.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW;
+ this.$.diffTable.tableColumns = [
+ {
+ title: 'Component',
+ value(row) {
+ const typeEl = document.createElement('span');
+ typeEl.innerText = row.title;
+ return typeEl;
+ },
+ showExpandButtons: true
+ },
+ {
+ title: 'Overall Memory(KB)',
+ value(row) {
+ const spanEl = tr.ui.b.createSpan();
+ spanEl.innerText = row.origin.overall.toFixed(3);
+ return spanEl;
+ },
+ cmp(a, b) {
+ return a.origin.overall - b.origin.overall;
+ }
+ },
+ {
+ title: 'diff(KB)',
+ value(row) {
+ const spanEl = tr.ui.b.createSpan();
+ spanEl.innerText = plusMinus(row.overall);
+ if (row.overall > 0) {
+ spanEl.style.color = DIFF_COLOR.RED;
+ } else if (row.overall < 0) {
+ spanEl.style.color = DIFF_COLOR.GREEN;
+ }
+ return spanEl;
+ },
+ cmp(a, b) {
+ return a.overall - b.overall;
+ }
+ },
+ {
+ title: 'diff(%)',
+ value(row) {
+ const spanEl = tr.ui.b.createSpan();
+ spanEl.innerText = addArrow(row.overallPercent);
+ if (row.overall > 0) {
+ spanEl.style.color = DIFF_COLOR.RED;
+ } else if (row.overall < 0) {
+ spanEl.style.color = DIFF_COLOR.GREEN;
+ }
+ return spanEl;
+ },
+ cmp(a, b) {
+ return a.overall - b.overall;
+ }
+ },
+ {
+ title: 'Over Allocated Memory(KB)',
+ value(row) {
+ const spanEl = tr.ui.b.createSpan();
+ spanEl.innerText = row.origin.overAllocated.toFixed(3);
+ return spanEl;
+ },
+ cmp(a, b) {
+ return a.origin.overAllocated - b.origin.overAllocated;
+ }
+ },
+ {
+ title: 'diff(KB)',
+ value(row) {
+ const spanEl = tr.ui.b.createSpan();
+ spanEl.innerText = plusMinus(row.overAllocated);
+ if (row.overAllocated > 0) {
+ spanEl.style.color = DIFF_COLOR.RED;
+ } else if (row.overAllocated < 0) {
+ spanEl.style.color = DIFF_COLOR.GREEN;
+ }
+ return spanEl;
+ },
+ cmp(a, b) {
+ return a.overAllocated - b.overAllocated;
+ }
+ },
+ {
+ title: 'diff(%)',
+ value(row) {
+ const spanEl = tr.ui.b.createSpan();
+ spanEl.innerText = addArrow(row.overAllocatedPercent);
+ if (row.overAllocated > 0) {
+ spanEl.style.color = DIFF_COLOR.RED;
+ } else if (row.overAllocated < 0) {
+ spanEl.style.color = DIFF_COLOR.GREEN;
+ }
+ return spanEl;
+ },
+ cmp(a, b) {
+ return a.overAllocated - b.overAllocated;
+ }
+ },
+ {
+ title: 'Count',
+ value(row) {
+ const spanEl = tr.ui.b.createSpan();
+ spanEl.innerText = row.origin.count;
+ return spanEl;
+ },
+ cmp(a, b) {
+ return a.origin.count - b.origin.count;
+ }
+ },
+ {
+ title: 'diff',
+ value(row) {
+ const spanEl = tr.ui.b.createSpan();
+ spanEl.innerText = plusMinus(row.count, 0);
+ if (row.count > 0) {
+ spanEl.style.color = DIFF_COLOR.RED;
+ } else if (row.count < 0) {
+ spanEl.style.color = DIFF_COLOR.GREEN;
+ }
+ return spanEl;
+ },
+ cmp(a, b) {
+ return a.count - b.count;
+ }
+ },
+ ];
+ },
+
+ buildOptions_() {
+ const items = [];
+ for (const isolateEntry of this.isolateEntries_) {
+ items.push({
+ label: isolateEntry.title,
+ value: isolateEntry
+ });
+ }
+ this.$.diffOption.style.display = 'inline-block';
+ this.selector1_ = createSelector(
+ this, '', items, this.diffOptionChanged_);
+ Polymer.dom(this.$.diffOption).appendChild(this.selector1_);
+ const spanEl = tr.ui.b.createSpan();
+ spanEl.innerText = ' VS ';
+ Polymer.dom(this.$.diffOption).appendChild(spanEl);
+ this.selector2_ = createSelector(
+ this, '', items, this.diffOptionChanged_);
+ Polymer.dom(this.$.diffOption).appendChild(this.selector2_);
+ },
+
+ diffOptionChanged_() {
+ const isolateEntry1 = this.selector1_.selectedValue;
+ const isolateEntry2 = this.selector2_.selectedValue;
+ if (isolateEntry1 === undefined || isolateEntry2 === undefined) {
+ return;
+ }
+ if (isolateEntry1 === isolateEntry2) {
+ this.$.diffTable.tableRows = [];
+ this.$.diffTable.rebuild();
+ return;
+ }
+ this.$.diffTable.tableRows = [isolateEntry1.diff(isolateEntry2)];
+ this.$.diffTable.rebuild();
+ },
+
+ constructTable_() {
+ this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW;
+ this.$.table.tableColumns = [
+ {
+ title: 'Component',
+ value(row) {
+ const typeEl = document.createElement('span');
+ typeEl.innerText = row.title;
+ return typeEl;
+ },
+ showExpandButtons: true
+ },
+ {
+ title: 'Overall Memory (KB)',
+ value(row) {
+ const typeEl = document.createElement('span');
+ typeEl.innerText = row.overall.toFixed(3);
+ return typeEl;
+ },
+ cmp(a, b) {
+ return a.overall - b.overall;
+ }
+ },
+ {
+ title: 'Over Allocated Memory (KB)',
+ value(row) {
+ const typeEl = document.createElement('span');
+ typeEl.innerText = row.overAllocated.toFixed(3);
+ return typeEl;
+ },
+ cmp(a, b) {
+ return a.overAllocated - b.overAllocated;
+ }
+ },
+ {
+ title: 'Overall Count',
+ value(row) {
+ const typeEl = document.createElement('span');
+ typeEl.innerText = row.count;
+ return typeEl;
+ },
+ cmp(a, b) {
+ return a.count - b.count;
+ }
+ },
+ {
+ title: 'Overall Memory Percent',
+ value(row) {
+ const typeEl = document.createElement('span');
+ typeEl.innerText = row.overallPercent.toFixed(3) + '%';
+ return typeEl;
+ },
+ cmp(a, b) {
+ return a.overall - b.overall;
+ }
+ },
+ {
+ title: 'Overall Allocated Memory Percent',
+ value(row) {
+ const typeEl = document.createElement('span');
+ typeEl.innerText = row.overAllocatedPercent.toFixed(3) + '%';
+ return typeEl;
+ },
+ cmp(a, b) {
+ return a.overAllocated - b.overAllocated;
+ }
+ }
+ ];
+
+ this.$.table.sortColumnIndex = 1;
+ this.$.table.sortDescending = true;
+ },
+
+ buildSubEntry_(objects, groupEntry, keyToName) {
+ const typeGroup = INSTANCE_TYPE_GROUPS[groupEntry.title];
+ for (const instanceType of typeGroup) {
+ const e = objects[instanceType];
+ if (e === undefined) continue;
+ delete objects[instanceType];
+ let title = instanceType;
+ if (keyToName !== undefined) title = keyToName(title);
+ // Represent memery in KB unit.
+ groupEntry.add(new Entry(title, e.count, e.overall / 1024,
+ e.over_allocated / 1024, e.histogram,
+ e.over_allocated_histogram));
+ }
+ },
+
+ buildUnGroupedEntries_(objects, objectEntry, bucketSize) {
+ for (const title of Object.getOwnPropertyNames(objects)) {
+ const obj = objects[title];
+ const groupedEntry = new GroupedEntry(title, 0, 0, 0,
+ new Array(bucketSize),
+ new Array(bucketSize));
+ groupedEntry.setFromObject(obj);
+ objectEntry.add(groupedEntry);
+ }
+ },
+
+ createGroupEntries_(groupEntries, objects, bucketSize) {
+ for (const groupName of Object.getOwnPropertyNames(
+ INSTANCE_TYPE_GROUPS)) {
+ const groupEntry = new GroupedEntry(groupName, 0, 0, 0,
+ new Array(bucketSize),
+ new Array(bucketSize));
+ if (INSTANCE_TYPE_GROUPS[groupName].realEntry !== undefined) {
+ groupEntry.savedRealEntry =
+ objects[INSTANCE_TYPE_GROUPS[groupName].realEntry];
+ delete objects[INSTANCE_TYPE_GROUPS[groupName].realEntry];
+ }
+ groupEntries[groupName] = groupEntry;
+ }
+ },
+
+ buildGroupEntries_(groupEntries, objectEntry) {
+ for (const groupName of Object.getOwnPropertyNames(groupEntries)) {
+ const groupEntry = groupEntries[groupName];
+ if (groupEntry.savedRealEntry !== undefined) {
+ groupEntry.setFromObject(groupEntry.savedRealEntry);
+ groupEntry.accumulateUnknown('UNKNOWN');
+ delete groupEntry.savedRealEntry;
+ }
+ objectEntry.add(groupEntry);
+ }
+ },
+
+ buildSubEntriesForGroups_(groupEntries, objects) {
+ for (const instanceType of Object.getOwnPropertyNames(objects)) {
+ if (IGNORED_ENTRIES.match(instanceType)) {
+ delete objects[instanceType];
+ continue;
+ }
+ const e = objects[instanceType];
+ for (const name of Object.getOwnPropertyNames(INSTANCE_TYPE_GROUPS)) {
+ const group = INSTANCE_TYPE_GROUPS[name];
+ if (group.match(instanceType)) {
+ groupEntries[name].add(new Entry(
+ group.keyToName(instanceType), e.count, e.overall / 1024,
+ e.over_allocated / 1024, e.histogram,
+ e.over_allocated_histogram));
+ delete objects[instanceType];
+ }
+ }
+ }
+ },
+
+ build_(objects, objectEntry, bucketSize) {
+ delete objects.END;
+ const groupEntries = {};
+ this.createGroupEntries_(groupEntries, objects, bucketSize);
+ this.buildSubEntriesForGroups_(groupEntries, objects);
+ this.buildGroupEntries_(groupEntries, objectEntry);
+ this.buildUnGroupedEntries_(objects, objectEntry, bucketSize);
+ },
+
+ set selection(slices) {
+ slices.sortEvents(function(a, b) {
+ return b.start - a.start;
+ });
+ const previous = undefined;
+ for (const slice of slices) {
+ if (!slice instanceof tr.e.v8.V8GCStatsThreadSlice) continue;
+ const liveObjects = slice.liveObjects;
+ const deadObjects = slice.deadObjects;
+ const isolate = liveObjects.isolate;
+
+ const isolateEntry =
+ new GroupedEntry(
+ 'Isolate_' + isolate + ' at ' + slice.start.toFixed(3) + ' ms',
+ 0, 0, 0, [], []);
+ const liveEntry = new GroupedEntry('live objects', 0, 0, 0, [], []);
+ const deadEntry = new GroupedEntry('dead objects', 0, 0, 0, [], []);
+
+ const liveBucketSize = liveObjects.bucket_sizes.length;
+ const deadBucketSize = deadObjects.bucket_sizes.length;
+
+ this.build_(tr.b.deepCopy(liveObjects.type_data), liveEntry,
+ liveBucketSize);
+ isolateEntry.add(liveEntry);
+
+ this.build_(tr.b.deepCopy(deadObjects.type_data), deadEntry,
+ deadBucketSize);
+ isolateEntry.add(deadEntry);
+
+ isolateEntry.calculatePercentage();
+ this.isolateEntries_.push(isolateEntry);
+ }
+ this.updateTable_();
+
+ if (slices.length > 1) {
+ this.buildOptions_();
+ this.constructDiffTable_();
+ }
+ },
+
+ updateTable_() {
+ this.constructTable_();
+ this.$.table.tableRows = this.isolateEntries_;
+ this.$.table.rebuild();
+ },
+ });
+
+ return {};
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/gc_objects_stats_table_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/gc_objects_stats_table_test.html
new file mode 100644
index 00000000000..42d904d94c8
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/gc_objects_stats_table_test.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/v8/v8_gc_stats_thread_slice.html">
+<link rel="import" href="/tracing/ui/extras/v8/gc_objects_stats_table.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+
+ function createModel() {
+ const m = tr.c.TestUtils.newModel(function(m) {
+ m.p1 = m.getOrCreateProcess(1);
+ m.t2 = m.p1.getOrCreateThread(2);
+
+ m.s1 = m.t2.sliceGroup.pushSlice(
+ newSliceEx({
+ title: 'V8.GC_Objects_Stats',
+ start: 1,
+ end: 1,
+ type: tr.e.v8.V8GCStatsThreadSlice,
+ cat: 'disabled-by-default-v8.gc_stats',
+ args: {
+ // eslint-disable-next-line
+ live:'{"isolate":"0x00000000001","id":1,"time":111,"bucket_sizes":[32,64,128,256],"type_data":{"STRING_TYPE":{"type":1,"overall":2,"count":3,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]},"FIXED_ARRAY_TYPE":{"type":2,"overall":5,"count":6,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]},"*FIXED_ARRAY_CONTEXT_SUB_TYPE":{"type":3,"overall":1,"count":1,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]},"JS_OBJECT_TYPE":{"type":4,"overall":5,"count":1,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]},"JS_TYPED_ARRAY_TYPE":{"type":5,"overall":5,"count":1,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]},"CODE_TYPE":{"type":4,"overall":6,"count":6,"over_allocated":0,"histogram":[6,0,0,0],"over_allocated_histogram":[0,0,0,0]},"*CODE_BYTECODE_HANDLER":{"type":7,"overall":5,"count":6,"over_allocated":0,"histogram":[6,0,0,0],"over_allocated_histogram":[0,0,0,0]},"*CODE_AGE_Quadragenarian":{"type":8,"overall":1,"count":1,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]}}}',
+ // eslint-disable-next-line
+ dead:'{"isolate":"0x00000000001","id":2,"time":112,"bucket_sizes":[32,64,128,256],"type_data":{"STRING_TYPE":{"type":1,"overall":1,"count":1,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]},"FIXED_ARRAY_TYPE":{"type":2,"overall":3,"count":3,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]},"*FIXED_ARRAY_CONTEXT_SUB_TYPE":{"type":3,"overall":1,"count":1,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]}}}'
+ }
+ })
+ );
+ m.s2 = m.t2.sliceGroup.pushSlice(
+ newSliceEx({
+ title: 'V8.GC_Objects_Stats',
+ start: 2,
+ end: 2,
+ type: tr.e.v8.V8GCStatsThreadSlice,
+ cat: 'disabled-by-default-v8.gc_stats',
+ args: {
+ // eslint-disable-next-line
+ live:'{"isolate":"0x00000000001","id":1,"time":113,"bucket_sizes":[32,64,128,256],"type_data":{"STRING_TYPE":{"type":1,"overall":3,"count":4,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]},"FIXED_ARRAY_TYPE":{"type":2,"overall":6,"count":7,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]},"*FIXED_ARRAY_CONTEXT_SUB_TYPE":{"type":3,"overall":2,"count":2,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]}}}',
+ // eslint-disable-next-line
+ dead:'{"isolate":"0x00000000001","id":2,"time":114,"bucket_sizes":[32,64,128,256],"type_data":{"STRING_TYPE":{"type":1,"overall":2,"count":2,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]},"FIXED_ARRAY_TYPE":{"type":2,"overall":4,"count":4,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]},"*FIXED_ARRAY_CONTEXT_SUB_TYPE":{"type":3,"overall":2,"count":2,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]}}}'
+ }
+ })
+ );
+ m.s3 = m.t2.sliceGroup.pushSlice(
+ newSliceEx({
+ title: 'V8.GC_Objects_Stats',
+ start: 3,
+ end: 3,
+ type: tr.e.v8.V8GCStatsThreadSlice,
+ cat: 'disabled-by-default-v8.gc_stats',
+ args: {
+ // eslint-disable-next-line
+ live:'{"isolate":"0x00000000001","id":1,"time":115,"bucket_sizes":[32,64,128,256],"type_data":{"FIXED_ARRAY_TYPE":{"type":2,"overall":5,"count":6,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]}, "TYPE_DONT_HAVE_GROUP1":{"type":1,"overall":2,"count":3,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]}, "TYPE_DONT_HAVE_GROUP2":{"type":1,"overall":2,"count":3,"over_allocated":0,"histogram":[0,1,0,0],"over_allocated_histogram":[0,0,0,0]}}}',
+ // eslint-disable-next-line
+ dead:'{"isolate":"0x00000000001","id":2,"time":116,"bucket_sizes":[32,64,128,256],"type_data":{"FIXED_ARRAY_TYPE":{"type":2,"overall":5,"count":6,"over_allocated":0,"histogram":[1,0,0,0],"over_allocated_histogram":[0,0,0,0]}}}'
+ }
+ })
+ );
+ });
+ return m;
+ }
+
+ test('GCObjectTableSingleSelection', function() {
+ const m = createModel();
+
+ const viewEl = document.createElement('tr-ui-e-v8-gc-objects-stats-table');
+ const eventSet = new tr.model.EventSet();
+ eventSet.push(m.s1);
+ viewEl.selection = eventSet;
+ this.addHTMLOutput(viewEl);
+ tr.b.forceAllPendingTasksToRunForTest();
+ const rows = viewEl.$.table.tableRows;
+ assert.lengthOf(rows, 1);
+ const row = rows[0];
+ assert.strictEqual(row.overall, 0.0263671875);
+ assert.strictEqual(row.count, 21);
+ assert.strictEqual(row.overAllocated, 0);
+ const subRows = row.subRows;
+ const live = subRows[0];
+ assert.strictEqual(live.overall, 0.0224609375);
+ assert.strictEqual(live.count, 17);
+ assert.strictEqual(live.overAllocated, 0);
+ const dead = subRows[1];
+ assert.strictEqual(dead.overall, 0.00390625);
+ assert.strictEqual(dead.count, 4);
+ assert.strictEqual(dead.overAllocated, 0);
+ });
+
+ test('GCObjectTableMultiSelection', function() {
+ const m = createModel();
+
+ const viewEl = document.createElement('tr-ui-e-v8-gc-objects-stats-table');
+ const eventSet = new tr.model.EventSet();
+ eventSet.push(m.s1);
+ eventSet.push(m.s2);
+ viewEl.selection = eventSet;
+ this.addHTMLOutput(viewEl);
+ tr.b.forceAllPendingTasksToRunForTest();
+ const rows = viewEl.$.table.tableRows;
+ assert.lengthOf(rows, 2);
+
+ let row = rows[0];
+ assert.strictEqual(row.overall, 0.0146484375);
+ assert.strictEqual(row.count, 17);
+ assert.strictEqual(row.overAllocated, 0);
+ let subRows = row.subRows;
+ let live = subRows[0];
+ assert.strictEqual(live.overall, 0.0087890625);
+ assert.strictEqual(live.count, 11);
+ assert.strictEqual(live.overAllocated, 0);
+ let dead = subRows[1];
+ assert.strictEqual(dead.overall, 0.005859375);
+ assert.strictEqual(dead.count, 6);
+ assert.strictEqual(dead.overAllocated, 0);
+
+ row = rows[1];
+ assert.strictEqual(row.overall, 0.0263671875);
+ assert.strictEqual(row.count, 21);
+ assert.strictEqual(row.overAllocated, 0);
+ subRows = row.subRows;
+ live = subRows[0];
+ assert.strictEqual(live.overall, 0.0224609375);
+ assert.strictEqual(live.count, 17);
+ assert.strictEqual(live.overAllocated, 0);
+ dead = subRows[1];
+ assert.strictEqual(dead.overall, 0.00390625);
+ assert.strictEqual(dead.count, 4);
+ assert.strictEqual(dead.overAllocated, 0);
+ });
+
+ test('GCObjectTableDiff', function() {
+ const m = createModel();
+
+ const viewEl = document.createElement('tr-ui-e-v8-gc-objects-stats-table');
+ const eventSet = new tr.model.EventSet();
+ eventSet.push(m.s1);
+ eventSet.push(m.s2);
+ viewEl.selection = eventSet;
+ this.addHTMLOutput(viewEl);
+ tr.b.forceAllPendingTasksToRunForTest();
+ const rows = viewEl.$.table.tableRows;
+ assert.lengthOf(rows, 2);
+ const diffEntry = rows[0].diff(rows[1]);
+
+ assert.strictEqual(diffEntry.origin.overall.toFixed(3), '0.015');
+ assert.strictEqual(diffEntry.origin.overAllocated, 0);
+ assert.strictEqual(diffEntry.overall.toFixed(3), '-0.004');
+ assert.strictEqual(diffEntry.overAllocated, 0);
+ assert.strictEqual(diffEntry.overallPercent.toFixed(3), '-26.667');
+ assert.strictEqual(diffEntry.overAllocatedPercent, 0);
+ });
+
+ test('GCObjectTableGroupEntryWithoutGroupDefined', function() {
+ const m = createModel();
+
+ const viewEl = document.createElement('tr-ui-e-v8-gc-objects-stats-table');
+ const eventSet = new tr.model.EventSet();
+ eventSet.push(m.s3);
+ viewEl.selection = eventSet;
+ this.addHTMLOutput(viewEl);
+ tr.b.forceAllPendingTasksToRunForTest();
+ const rows = viewEl.$.table.tableRows;
+ assert.lengthOf(rows, 1);
+ const row = rows[0];
+ assert.strictEqual(row.overall, 0.013671875);
+ assert.strictEqual(row.count, 18);
+ assert.strictEqual(row.overAllocated, 0);
+ const subRows = row.subRows;
+
+ const live = subRows[0];
+ assert.strictEqual(live.overall, 0.0087890625);
+ assert.strictEqual(live.count, 12);
+ assert.strictEqual(live.overAllocated, 0);
+
+ // ungrouped entry should be top level entry.
+ const unGrouped1 = live.getEntryFromTitle('TYPE_DONT_HAVE_GROUP1');
+ assert.isDefined(unGrouped1);
+ assert.strictEqual(unGrouped1.overall, 0.001953125);
+ assert.strictEqual(unGrouped1.count, 3);
+ assert.strictEqual(unGrouped1.overAllocated, 0);
+
+ const unGrouped2 = live.getEntryFromTitle('TYPE_DONT_HAVE_GROUP2');
+ assert.isDefined(unGrouped2);
+ assert.strictEqual(unGrouped2.overall, 0.001953125);
+ assert.strictEqual(unGrouped2.count, 3);
+ assert.strictEqual(unGrouped2.overAllocated, 0);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/ic_stats_table.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/ic_stats_table.html
new file mode 100644
index 00000000000..e19a367cfca
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/ic_stats_table.html
@@ -0,0 +1,181 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/extras/v8/ic_stats_entry.html">
+<link rel="import" href="/tracing/extras/v8/v8_ic_stats_thread_slice.html">
+<link rel="import" href="/tracing/ui/base/table.html">
+
+<dom-module id='tr-ui-e-v8-ic-stats-table'>
+ <template>
+ <style>
+ tr-ui-b-table {
+ flex: 0 0 auto;
+ align-self: stretch;
+ margin-top: 1em;
+ font-size: 12px;
+ }
+ #total {
+ margin-top: 1em;
+ margin-left: 0.8em;
+ }
+ #groupOption {
+ display: inline-block;
+ margin-top: 1em;
+ margin-left: 0.8em;
+ }
+ </style>
+ <div style="padding-right: 200px">
+ <div style="float:right; border-style: solid; border-width: 1px; padding:20px">
+ 0 uninitialized<br>
+ . premonomorphic<br>
+ 1 monomorphic<br>
+ ^ recompute handler<br>
+ P polymorphic<br>
+ N megamorphic<br>
+ G generic
+ </div>
+ </div>
+ <div id="total">
+ </div>
+ <div id="groupOption">
+ Group Key
+ </div>
+ <tr-ui-b-table id="table"></tr-ui-b-table>
+ </template>
+</dom-module>
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.v8', function() {
+ const PROPERTIES = tr.e.v8.IC_STATS_PROPERTIES.map(
+ x => {return {label: x, value: x};});
+ const ICStatsEntry = tr.e.v8.ICStatsEntry;
+ const ICStatsEntryGroup = tr.e.v8.ICStatsEntryGroup;
+ const ICStatsCollection = tr.e.v8.ICStatsCollection;
+
+ Polymer({
+ is: 'tr-ui-e-v8-ic-stats-table',
+
+ ready() {
+ this.icStatsCollection_ = new ICStatsCollection();
+ this.groupKey_ = PROPERTIES[0].value;
+ this.selector_ = tr.ui.b.createSelector(this, 'groupKey',
+ 'v8ICStatsGroupKey',
+ this.groupKey_, PROPERTIES);
+ Polymer.dom(this.$.groupOption).appendChild(this.selector_);
+ },
+
+ get groupKey() {
+ return this.groupKey_;
+ },
+
+ set groupKey(key) {
+ this.groupKey_ = key;
+ if (this.icStatsCollection_.length === 0) return;
+ this.updateTable_(this.groupKey_);
+ },
+
+ constructTable_(table, groupKey) {
+ table.tableColumns = [
+ {
+ title: '',
+ value: row => {
+ let expanded = false;
+ const buttonEl = tr.ui.b.createButton('details', function() {
+ const previousSibling = Polymer.dom(this).parentNode.parentNode;
+ const parentNode = previousSibling.parentNode;
+ if (expanded) {
+ const trEls = parentNode.getElementsByClassName('subTable');
+ Array.from(trEls).map(x => x.parentNode.removeChild(x));
+ expanded = false;
+ return;
+ }
+ expanded = true;
+ const subGroups = row.createSubGroup();
+ const tr = document.createElement('tr');
+ tr.classList.add('subTable');
+ tr.appendChild(document.createElement('td'));
+ const td = document.createElement('td');
+ td.colSpan = 3;
+ for (const subGroup of subGroups) {
+ const property = subGroup[0];
+ const all = Array.from(subGroup[1].values());
+ const group = all.slice(0, 20);
+ const divEl = document.createElement('div');
+ const spanEl = document.createElement('span');
+ const subTableEl = document.createElement('tr-ui-b-table');
+
+ spanEl.innerText = `Top 20 out of ${all.length}`;
+ spanEl.style.fontWeight = 'bold';
+ spanEl.style.fontSize = '14px';
+ divEl.appendChild(spanEl);
+
+ this.constructTable_(subTableEl, property);
+ subTableEl.tableRows = group;
+ subTableEl.rebuild();
+ divEl.appendChild(subTableEl);
+ td.appendChild(divEl);
+ }
+ tr.appendChild(td);
+ parentNode.insertBefore(tr, previousSibling.nextSibling);
+ });
+ return buttonEl;
+ }
+ },
+ {
+ title: 'Percentage',
+ value(row) {
+ const spanEl = document.createElement('span');
+ spanEl.innerText = (row.percentage * 100).toFixed(3) + '%';
+ return spanEl;
+ },
+ cmp: (a, b) => a.percentage - b.percentage
+ },
+ {
+ title: 'Count',
+ value(row) {
+ const spanEl = document.createElement('span');
+ spanEl.innerText = row.length;
+ return spanEl;
+ },
+ cmp: (a, b) => a.length - b.length
+ },
+ {
+ title: groupKey,
+ value(row) {
+ const spanEl = document.createElement('span');
+ spanEl.innerText = row.key ? row.key : '';
+ return spanEl;
+ }
+ }
+ ];
+
+ table.sortColumnIndex = 1;
+ table.sortDescending = true;
+ },
+
+ updateTable_(groupKey) {
+ this.constructTable_(this.$.table, groupKey);
+ this.$.table.tableRows = this.icStatsCollection_.groupBy(groupKey);
+ this.$.table.rebuild();
+ },
+
+ set selection(slices) {
+ for (const slice of slices) {
+ for (const icStatsObj of slice.icStats) {
+ const entry = new ICStatsEntry(icStatsObj);
+ this.icStatsCollection_.add(entry);
+ }
+ }
+ this.$.total.innerText = 'Total items: ' + this.icStatsCollection_.length;
+ this.updateTable_(this.selector_.selectedValue);
+ }
+ });
+
+ return {};
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_gc_stats_thread_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_gc_stats_thread_slice_sub_view.html
new file mode 100644
index 00000000000..eded2c44a66
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_gc_stats_thread_slice_sub_view.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/v8/gc_objects_stats_table.html">
+
+<dom-module id='tr-ui-e-multi-v8-gc-stats-thread-slice-sub-view'>
+ <template>
+ <style>
+ </style>
+ <tr-ui-e-v8-gc-objects-stats-table id="gcObjectsStats">
+ </tr-ui-e-v8-gc-objects-stats-table>
+ </template>
+</dom-module>
+
+<script>
+'use strict';
+
+Polymer({
+ is: 'tr-ui-e-multi-v8-gc-stats-thread-slice-sub-view',
+ behaviors: [tr.ui.analysis.AnalysisSubView],
+
+ get selection() {
+ return this.$.content.selection;
+ },
+
+ set selection(selection) {
+ this.$.gcObjectsStats.selection = selection;
+ }
+});
+
+tr.ui.analysis.AnalysisSubView.register(
+ 'tr-ui-e-multi-v8-gc-stats-thread-slice-sub-view',
+ tr.e.v8.V8GCStatsThreadSlice,
+ {
+ multi: true,
+ title: 'V8 GC Stats slices'
+ }
+);
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_ic_stats_thread_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_ic_stats_thread_slice_sub_view.html
new file mode 100644
index 00000000000..36b14cb7be0
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_ic_stats_thread_slice_sub_view.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/v8/ic_stats_table.html">
+
+<dom-module id='tr-ui-e-multi-v8-ic-stats-thread-slice-sub-view'>
+ <template>
+ <tr-ui-e-v8-ic-stats-table id="table">
+ </tr-ui-e-v8-ic-stats-table>
+ </template>
+</dom-module>
+
+<script>
+'use strict';
+
+Polymer({
+ is: 'tr-ui-e-multi-v8-ic-stats-thread-slice-sub-view',
+ behaviors: [tr.ui.analysis.AnalysisSubView],
+
+ get selection() {
+ return this.$.content.selection;
+ },
+
+ set selection(selection) {
+ this.$.table.selection = selection;
+ }
+});
+
+tr.ui.analysis.AnalysisSubView.register(
+ 'tr-ui-e-multi-v8-ic-stats-thread-slice-sub-view',
+ tr.e.v8.V8ICStatsThreadSlice,
+ {
+ multi: true,
+ title: 'V8 IC stats slices'
+ }
+);
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_thread_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_thread_slice_sub_view.html
new file mode 100644
index 00000000000..48c1f05b274
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_thread_slice_sub_view.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/v8/runtime_call_stats_table.html">
+
+<dom-module id='tr-ui-e-multi-v8-thread-slice-sub-view'>
+ <template>
+ <tr-ui-a-multi-thread-slice-sub-view id="content"></tr-ui-a-multi-thread-slice-sub-view>
+ <tr-ui-e-v8-runtime-call-stats-table id="runtimeCallStats"></tr-ui-e-v8-runtime-call-stats-table>
+ </template>
+</dom-module>
+
+<script>
+'use strict';
+
+Polymer({
+ is: 'tr-ui-e-multi-v8-thread-slice-sub-view',
+ behaviors: [tr.ui.analysis.AnalysisSubView],
+
+ get selection() {
+ return this.$.content.selection;
+ },
+
+ set selection(selection) {
+ this.$.runtimeCallStats.slices = selection;
+ this.$.content.selection = selection;
+ }
+});
+
+tr.ui.analysis.AnalysisSubView.register(
+ 'tr-ui-e-multi-v8-thread-slice-sub-view',
+ tr.e.v8.V8ThreadSlice,
+ {
+ multi: true,
+ title: 'V8 slices'
+ }
+);
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_thread_slice_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_thread_slice_sub_view_test.html
new file mode 100644
index 00000000000..d0d62bed87d
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/multi_v8_thread_slice_sub_view_test.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/v8/v8_thread_slice.html">
+<link rel="import" href="/tracing/ui/extras/v8/multi_v8_thread_slice_sub_view.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+
+ function createModel() {
+ const m = tr.c.TestUtils.newModel(function(m) {
+ m.p1 = m.getOrCreateProcess(1);
+ m.t2 = m.p1.getOrCreateThread(2);
+
+ m.s1 = m.t2.sliceGroup.pushSlice(
+ newSliceEx(
+ {title: 'V8.Execute',
+ start: 0,
+ end: 10,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {'runtime-call-stats':
+ {
+ CompileFullCode: [3, 345],
+ LoadIC_Miss: [5, 567],
+ ParseLazy: [8, 890]
+ }}}));
+ m.s2 = m.t2.sliceGroup.pushSlice(
+ newSliceEx(
+ {title: 'V8.Execute',
+ start: 11,
+ end: 15,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {'runtime-call-stats':
+ {
+ HandleApiCall: [1, 123],
+ OptimizeCode: [7, 789]
+ }}}));
+ });
+ return m;
+ }
+
+ test('selectMultiV8ThreadSlices', function() {
+ const m = createModel();
+
+ const viewEl =
+ document.createElement('tr-ui-e-multi-v8-thread-slice-sub-view');
+ const selection = new tr.model.EventSet();
+ selection.push(m.s1);
+ selection.push(m.s2);
+ viewEl.selection = selection;
+ this.addHTMLOutput(viewEl);
+ const rows = viewEl.$.runtimeCallStats.$.table.tableRows;
+ assert.lengthOf(rows, 19);
+ assert.deepEqual(rows.map(r => r.time), [
+ 2714,
+ 567,
+ 0,
+ 789,
+ 0,
+ 345,
+ 0,
+ 890,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 123
+ ]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/runtime_call_stats_table.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/runtime_call_stats_table.html
new file mode 100644
index 00000000000..27bf0f5ef72
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/runtime_call_stats_table.html
@@ -0,0 +1,197 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/extras/v8/runtime_stats_entry.html">
+<link rel="import" href="/tracing/extras/v8/v8_thread_slice.html">
+<link rel="import" href="/tracing/ui/base/table.html">
+
+<dom-module id='tr-ui-e-v8-runtime-call-stats-table'>
+ <template>
+ <style>
+ #table, #blink_rcs_table {
+ flex: 0 0 auto;
+ align-self: stretch;
+ margin-top: 1em;
+ font-size: 12px;
+ }
+
+ #v8_rcs_heading, #blink_rcs_heading {
+ padding-top: 1em;
+ font-size: 18px;
+ }
+ </style>
+ <h1 id="v8_rcs_heading"></h1>
+ <tr-ui-b-table id="table"></tr-ui-b-table>
+ <h1 id="blink_rcs_heading"></h1>
+ <tr-ui-b-table id="blink_rcs_table"></tr-ui-b-table>
+ </template>
+</dom-module>
+<script>
+'use strict';
+
+tr.exportTo('tr.ui.e.v8', function() {
+ const codeSearchURL_ = 'https://cs.chromium.org/search/?sq=package:chromium&type=cs&q=';
+
+ function removeBlinkPrefix_(name) {
+ if (name.startsWith('Blink_')) name = name.substring(6);
+ return name;
+ }
+
+ function handleCodeSearchForV8_(event) {
+ if (event.target.parentNode === undefined) return;
+ let name = event.target.parentNode.entryName;
+ if (name.startsWith('API_')) name = name.substring(4);
+ const url = codeSearchURL_ + encodeURIComponent(name) + '+file:src/v8/src';
+ window.open(url, '_blank');
+ }
+
+ function handleCodeSearchForBlink_(event) {
+ if (event.target.parentNode === undefined) return;
+ const name = event.target.parentNode.entryName;
+ const url = codeSearchURL_ +
+ encodeURIComponent('RuntimeCallStats::CounterId::k' + name) +
+ '+file:src/third_party/WebKit/|src/out/Debug/';
+ window.open(url, '_blank');
+ }
+
+ function createCodeSearchEl_(handleCodeSearch) {
+ const codeSearchEl = document.createElement('span');
+ codeSearchEl.innerText = '?';
+ codeSearchEl.style.float = 'right';
+ codeSearchEl.style.borderRadius = '5px';
+ codeSearchEl.style.backgroundColor = '#EEE';
+ codeSearchEl.addEventListener('click',
+ handleCodeSearch.bind(this));
+ return codeSearchEl;
+ }
+
+ const timeColumn_ = {
+ title: 'Time',
+ value(row) {
+ const typeEl = document.createElement('span');
+ typeEl.innerText = (row.time / 1000.0).toFixed(3) + ' ms';
+ return typeEl;
+ },
+ width: '100px',
+ cmp(a, b) {
+ return a.time - b.time;
+ }
+ };
+
+ const countColumn_ = {
+ title: 'Count',
+ value(row) {
+ const typeEl = document.createElement('span');
+ typeEl.innerText = row.count;
+ return typeEl;
+ },
+ width: '100px',
+ cmp(a, b) {
+ return a.count - b.count;
+ }
+ };
+
+ function percentColumn_(title, totalTime) {
+ return {
+ title,
+ value(row) {
+ const typeEl = document.createElement('span');
+ typeEl.innerText = (row.time / totalTime * 100).toFixed(3) + '%';
+ return typeEl;
+ },
+ width: '100px',
+ cmp(a, b) {
+ return a.time - b.time;
+ }
+ };
+ }
+
+ function nameColumn_(handleCodeSearch, modifyName) {
+ return {
+ title: 'Name',
+ value(row) {
+ const typeEl = document.createElement('span');
+ let name = row.name;
+ if (modifyName) name = modifyName(name);
+ typeEl.innerText = name;
+ if (!(row instanceof tr.e.v8.RuntimeStatsGroup)) {
+ typeEl.title = 'click ? for code search';
+ typeEl.entryName = name;
+ const codeSearchEl = createCodeSearchEl_(handleCodeSearch);
+ typeEl.appendChild(codeSearchEl);
+ }
+ return typeEl;
+ },
+ width: '200px',
+ showExpandButtons: true
+ };
+ }
+
+ function initializeCommonOptions_(table) {
+ table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW;
+ table.sortColumnIndex = 1;
+ table.sortDescending = true;
+ table.subRowsPropertyName = 'values';
+ }
+
+ Polymer({
+ is: 'tr-ui-e-v8-runtime-call-stats-table',
+
+ ready() {
+ this.table_ = this.$.table;
+ this.blink_rcs_table_ = this.$.blink_rcs_table;
+ this.totalTime_ = 0;
+ },
+
+ constructV8RCSTable_(totalTime) {
+ this.table_.tableColumns = [
+ nameColumn_(handleCodeSearchForV8_),
+ timeColumn_,
+ countColumn_,
+ percentColumn_('Percent', totalTime)
+ ];
+
+ initializeCommonOptions_(this.table_);
+ },
+
+ constructBlinkRCSTable_(blinkCppTotalTime) {
+ this.blink_rcs_table_.tableColumns = [
+ nameColumn_(handleCodeSearchForBlink_, removeBlinkPrefix_),
+ timeColumn_,
+ countColumn_,
+ percentColumn_('Percent (of \'Blink C++\' + \'API\')',
+ blinkCppTotalTime)
+ ];
+
+ initializeCommonOptions_(this.blink_rcs_table_);
+ },
+
+ set slices(slices) {
+ const runtimeGroupCollection = new tr.e.v8.RuntimeStatsGroupCollection();
+ runtimeGroupCollection.addSlices(slices);
+ if (runtimeGroupCollection.totalTime > 0) {
+ this.$.v8_rcs_heading.textContent = 'V8 Runtime Call Stats';
+ this.constructV8RCSTable_(runtimeGroupCollection.totalTime);
+ this.table_.tableRows = runtimeGroupCollection.runtimeGroups;
+ this.table_.rebuild();
+ }
+
+ const blinkRCSGroupCollection =
+ runtimeGroupCollection.blinkRCSGroupCollection;
+ if (runtimeGroupCollection.blinkCppTotalTime > 0 &&
+ blinkRCSGroupCollection.totalTime > 0) {
+ this.$.blink_rcs_heading.textContent = 'Blink Runtime Call Stats';
+ this.constructBlinkRCSTable_(runtimeGroupCollection.blinkCppTotalTime);
+ this.blink_rcs_table_.tableRows = blinkRCSGroupCollection.runtimeGroups;
+ this.blink_rcs_table_.rebuild();
+ }
+ }
+ });
+
+ return {};
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/runtime_call_stats_table_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/runtime_call_stats_table_test.html
new file mode 100644
index 00000000000..06698d03c4a
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/runtime_call_stats_table_test.html
@@ -0,0 +1,236 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/v8/v8_thread_slice.html">
+<link rel="import" href="/tracing/ui/extras/v8/runtime_call_stats_table.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+ const apiObjectGet = [1, 123];
+ const functionCallback = [2, 234];
+ const compileFullCode = [3, 345];
+ const allocateInTargetSpace = [4, 456];
+ const loadIcMiss = [5, 567];
+ const jsExecution = [6, 678];
+ const optimizeCode = [7, 789];
+ const parseLazy = [8, 890];
+ const handleApiCall = [9, 901];
+ const compileBackground = [1, 101];
+ const parseBackground = [2, 202];
+ const optimizeCodeBackground = [3, 303];
+
+ function createModel() {
+ const m = tr.c.TestUtils.newModel(function(m) {
+ m.p1 = m.getOrCreateProcess(1);
+ m.t2 = m.p1.getOrCreateThread(2);
+
+ m.s1 = m.t2.sliceGroup.pushSlice(
+ newSliceEx(
+ {title: 'V8.Execute',
+ start: 0,
+ end: 10,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {'runtime-call-stats':
+ {
+ JS_Execution: jsExecution,
+ HandleApiCall: handleApiCall,
+ CompileFullCode: compileFullCode,
+ LoadIC_Miss: loadIcMiss,
+ ParseLazy: parseLazy,
+ RecompileConcurrent: optimizeCode,
+ OptimizeCode: optimizeCode,
+ FunctionCallback: functionCallback,
+ AllocateInTargetSpace: allocateInTargetSpace,
+ API_Object_Get: apiObjectGet,
+ CompileBackgroundIgnition: compileBackground,
+ ParseBackgroundFunctionLiteral: parseBackground,
+ RecompileConcurrent: optimizeCodeBackground
+ }}}));
+ m.s2 = m.t2.sliceGroup.pushSlice(
+ newSliceEx(
+ {title: 'V8.Execute',
+ start: 11,
+ end: 15,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {'runtime-call-stats':
+ {
+ JS_Execution: jsExecution,
+ HandleApiCall: handleApiCall,
+ CompileFullCode: compileFullCode,
+ LoadIC_Miss: loadIcMiss,
+ ParseLazy: parseLazy,
+ OptimizeCode: optimizeCode,
+ FunctionCallback: functionCallback,
+ AllocateInTargetSpace: allocateInTargetSpace,
+ API_Object_Get: apiObjectGet
+ }}}));
+ m.s3 = m.t2.sliceGroup.pushSlice(
+ newSliceEx(
+ {title: 'V8.Execute',
+ start: 11,
+ end: 15,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {'runtime-call-stats':
+ {
+ LoadIC_LoadCallback: [1, 111],
+ StoreIC_StoreCallback: [2, 222],
+ }}}));
+ });
+ return m;
+ }
+
+ test('SingleSliceSelection', function() {
+ const m = createModel();
+
+ const viewEl = document.createElement(
+ 'tr-ui-e-v8-runtime-call-stats-table');
+ viewEl.slices = [m.s1];
+ this.addHTMLOutput(viewEl);
+ tr.b.forceAllPendingTasksToRunForTest();
+ const rows = viewEl.$.table.tableRows;
+ assert.lengthOf(rows, 19);
+ assert.deepEqual(rows.map(r => r.time), [
+ 5589,
+ loadIcMiss[1],
+ optimizeCodeBackground[1],
+ optimizeCode[1],
+ compileBackground[1],
+ compileFullCode[1],
+ parseBackground[1],
+ parseLazy[1],
+ functionCallback[1],
+ apiObjectGet[1],
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ allocateInTargetSpace[1],
+ jsExecution[1],
+ handleApiCall[1]
+ ]);
+ });
+
+ test('MultiSliceSelection', function() {
+ const m = createModel();
+
+ const viewEl = document.createElement(
+ 'tr-ui-e-v8-runtime-call-stats-table');
+ viewEl.slices = [m.s1, m.s2];
+ this.addHTMLOutput(viewEl);
+ tr.b.forceAllPendingTasksToRunForTest();
+ const rows = viewEl.$.table.tableRows;
+ assert.lengthOf(rows, 19);
+ assert.deepEqual(rows.map(r => r.time), [
+ 10572,
+ loadIcMiss[1] * 2,
+ optimizeCodeBackground[1],
+ optimizeCode[1] * 2,
+ compileBackground[1],
+ compileFullCode[1] * 2,
+ parseBackground[1],
+ parseLazy[1] * 2,
+ functionCallback[1] * 2,
+ apiObjectGet[1] * 2,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ allocateInTargetSpace[1] * 2,
+ jsExecution[1] * 2,
+ handleApiCall[1] * 2
+ ]);
+
+ assert.deepEqual(rows.map(r => r.entries_.size), [
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1
+ ]);
+ });
+
+ test('groupCorrectly', function() {
+ const m = createModel();
+
+ const viewEl = document.createElement(
+ 'tr-ui-e-v8-runtime-call-stats-table');
+ viewEl.slices = [m.s3];
+ this.addHTMLOutput(viewEl);
+ tr.b.forceAllPendingTasksToRunForTest();
+ const rows = viewEl.$.table.tableRows;
+ assert.lengthOf(rows, 19);
+ assert.deepEqual(rows.map(r => r.time), [
+ 333,
+ 333,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]);
+
+ assert.deepEqual(rows.map(r => r.entries_.size), [
+ 0,
+ 2,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_gc_stats_thread_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_gc_stats_thread_slice_sub_view.html
new file mode 100644
index 00000000000..6a8d5f15b73
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_gc_stats_thread_slice_sub_view.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/v8/gc_objects_stats_table.html">
+
+<dom-module id='tr-ui-e-single-v8-gc-stats-thread-slice-sub-view'>
+ <template>
+ <tr-ui-a-single-event-sub-view id="content"></tr-ui-a-single-event-sub-view>
+ <tr-ui-e-v8-gc-objects-stats-table id="gcObjectsStats"></tr-ui-e-v8-gc-objects-stats-table>
+ </template>
+</dom-module>
+
+<script>
+'use strict';
+Polymer({
+ is: 'tr-ui-e-single-v8-gc-stats-thread-slice-sub-view',
+ behaviors: [tr.ui.analysis.AnalysisSubView],
+
+ get selection() {
+ return this.$.content.selection;
+ },
+
+ set selection(selection) {
+ this.$.content.selection = selection;
+ this.$.gcObjectsStats.selection = selection;
+ }
+});
+
+tr.ui.analysis.AnalysisSubView.register(
+ 'tr-ui-e-single-v8-gc-stats-thread-slice-sub-view',
+ tr.e.v8.V8GCStatsThreadSlice,
+ {
+ multi: false,
+ title: 'V8 GC stats slice'
+ }
+);
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_ic_stats_thread_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_ic_stats_thread_slice_sub_view.html
new file mode 100644
index 00000000000..eeaf407eab1
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_ic_stats_thread_slice_sub_view.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/v8/ic_stats_table.html">
+
+<dom-module id='tr-ui-e-single-v8-ic-stats-thread-slice-sub-view'>
+ <template>
+ <tr-ui-e-v8-ic-stats-table id="table">
+ </tr-ui-e-v8-ic-stats-table>
+ </template>
+</dom-module>
+
+<script>
+'use strict';
+Polymer({
+ is: 'tr-ui-e-single-v8-ic-stats-thread-slice-sub-view',
+ behaviors: [tr.ui.analysis.AnalysisSubView],
+
+ get selection() {
+ return this.$.content.selection;
+ },
+
+ set selection(selection) {
+ this.$.table.selection = selection;
+ }
+});
+
+tr.ui.analysis.AnalysisSubView.register(
+ 'tr-ui-e-single-v8-ic-stats-thread-slice-sub-view',
+ tr.e.v8.V8ICStatsThreadSlice,
+ {
+ multi: false,
+ title: 'V8 IC stats slice'
+ }
+);
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_thread_slice_sub_view.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_thread_slice_sub_view.html
new file mode 100644
index 00000000000..a9b1189fc76
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_thread_slice_sub_view.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/ui/analysis/analysis_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/v8/runtime_call_stats_table.html">
+
+<dom-module id='tr-ui-e-single-v8-thread-slice-sub-view'>
+ <template>
+ <tr-ui-a-single-thread-slice-sub-view id="content"></tr-ui-a-single-thread-slice-sub-view>
+ <tr-ui-e-v8-runtime-call-stats-table id="runtimeCallStats"></tr-ui-e-v8-runtime-call-stats-table>
+ </template>
+</dom-module>
+
+<script>
+'use strict';
+Polymer({
+ is: 'tr-ui-e-single-v8-thread-slice-sub-view',
+ behaviors: [tr.ui.analysis.AnalysisSubView],
+
+ get selection() {
+ return this.$.content.selection;
+ },
+
+ set selection(selection) {
+ this.$.runtimeCallStats.slices = selection;
+ this.$.content.selection = selection;
+ }
+});
+
+tr.ui.analysis.AnalysisSubView.register(
+ 'tr-ui-e-single-v8-thread-slice-sub-view',
+ tr.e.v8.V8ThreadSlice,
+ {
+ multi: false,
+ title: 'V8 slice'
+ }
+);
+
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_thread_slice_sub_view_test.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_thread_slice_sub_view_test.html
new file mode 100644
index 00000000000..9e12aa6e044
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8/single_v8_thread_slice_sub_view_test.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2016 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.
+-->
+
+<link rel="import" href="/tracing/core/test_utils.html">
+<link rel="import" href="/tracing/extras/v8/v8_thread_slice.html">
+<link rel="import" href="/tracing/model/event_set.html">
+<link rel="import" href="/tracing/ui/extras/v8/single_v8_thread_slice_sub_view.html">
+
+<script>
+'use strict';
+
+tr.b.unittest.testSuite(function() {
+ const newSliceEx = tr.c.TestUtils.newSliceEx;
+
+ function createModel() {
+ const m = tr.c.TestUtils.newModel(function(m) {
+ m.p1 = m.getOrCreateProcess(1);
+ m.t2 = m.p1.getOrCreateThread(2);
+
+ m.s1 = m.t2.sliceGroup.pushSlice(
+ newSliceEx(
+ {title: 'V8.Execute',
+ start: 0,
+ end: 10,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {'runtime-call-stats':
+ {
+ CompileFullCode: [3, 345],
+ LoadIC_Miss: [5, 567],
+ ParseLazy: [8, 890]
+ }}}));
+ m.s2 = m.t2.sliceGroup.pushSlice(
+ newSliceEx(
+ {title: 'V8.Execute',
+ start: 11,
+ end: 15,
+ type: tr.e.v8.V8ThreadSlice,
+ cat: 'v8',
+ args: {'runtime-call-stats':
+ {
+ HandleApiCall: [1, 123],
+ OptimizeCode: [7, 789]
+ }}}));
+ });
+ return m;
+ }
+
+ test('selectV8ThreadSlice', function() {
+ const m = createModel();
+
+ const viewEl =
+ document.createElement('tr-ui-e-single-v8-thread-slice-sub-view');
+ const selection1 = new tr.model.EventSet();
+ selection1.push(m.s1);
+ viewEl.selection = selection1;
+ this.addHTMLOutput(viewEl);
+ let rows = viewEl.$.runtimeCallStats.$.table.tableRows;
+ assert.lengthOf(rows, 19);
+ assert.deepEqual(rows.map(r => r.time), [
+ 1802,
+ 567,
+ 0,
+ 0,
+ 0,
+ 345,
+ 0,
+ 890,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]);
+
+ const selection2 = new tr.model.EventSet();
+ selection2.push(m.s2);
+ viewEl.selection = selection2;
+ rows = viewEl.$.runtimeCallStats.$.table.tableRows;
+ assert.lengthOf(rows, 19);
+ assert.deepEqual(rows.map(r => r.time), [
+ 912,
+ 0,
+ 0,
+ 789,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 123
+ ]);
+ });
+});
+</script>
diff --git a/chromium/third_party/catapult/tracing/tracing/ui/extras/v8_config.html b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8_config.html
new file mode 100644
index 00000000000..f78005c2b54
--- /dev/null
+++ b/chromium/third_party/catapult/tracing/tracing/ui/extras/v8_config.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2013 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.
+-->
+
+
+<link rel="import" href="/tracing/extras/v8_config.html">
+<link rel="import" href="/tracing/ui/extras/v8/gc_objects_stats_table.html">
+<link rel="import" href="/tracing/ui/extras/v8/multi_v8_gc_stats_thread_slice_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/v8/multi_v8_ic_stats_thread_slice_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/v8/multi_v8_thread_slice_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/v8/runtime_call_stats_table.html">
+<link rel="import" href="/tracing/ui/extras/v8/single_v8_gc_stats_thread_slice_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/v8/single_v8_ic_stats_thread_slice_sub_view.html">
+<link rel="import" href="/tracing/ui/extras/v8/single_v8_thread_slice_sub_view.html">