summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/catapult/tracing/tracing/ui/extras/about_tracing')
-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
15 files changed, 2921 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>