blob: e422e727590ae5ae944defc0dcb11f849553c805 [file] [log] [blame]
/**
// 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.
* WARNING: This file is generated by generate_standalone_timeline_view.py
*
* Do not edit directly.
*/
window.FLATTENED = {};
window.FLATTENED_RAW_SCRIPTS = {};
window.FLATTENED['base'] = true;
window.FLATTENED['tracing.importer.importer'] = true;
window.FLATTENED['base.event_target'] = true;
window.FLATTENED['base.events'] = true;
window.FLATTENED['base.interval_tree'] = true;
window.FLATTENED['base.range'] = true;
window.FLATTENED['tracing.filter'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/Promises/polyfill/src/Promise.js'] = true;
window.FLATTENED['base.promise'] = true;
window.FLATTENED['base.iteration_helpers'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/gl-matrix/src/gl-matrix/common.js'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/gl-matrix/src/gl-matrix/mat2d.js'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/gl-matrix/src/gl-matrix/mat4.js'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/gl-matrix/src/gl-matrix/vec2.js'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/gl-matrix/src/gl-matrix/vec3.js'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/gl-matrix/src/gl-matrix/vec4.js'] = true;
window.FLATTENED['base.gl_matrix'] = true;
window.FLATTENED['base.rect'] = true;
window.FLATTENED['base.utils'] = true;
window.FLATTENED['base.raf'] = true;
window.FLATTENED['tracing.importer.task'] = true;
window.FLATTENED['base.guid'] = true;
window.FLATTENED['base.sorted_array_utils'] = true;
window.FLATTENED['tracing.trace_model.event'] = true;
window.FLATTENED['tracing.trace_model.counter_sample'] = true;
window.FLATTENED['tracing.trace_model.counter_series'] = true;
window.FLATTENED['tracing.trace_model.counter'] = true;
window.FLATTENED['tracing.trace_model.timed_event'] = true;
window.FLATTENED['tracing.trace_model.slice'] = true;
window.FLATTENED['tracing.trace_model.cpu'] = true;
window.FLATTENED['tracing.trace_model.object_snapshot'] = true;
window.FLATTENED['tracing.trace_model.object_instance'] = true;
window.FLATTENED['tracing.trace_model.time_to_object_instance_map'] = true;
window.FLATTENED['tracing.trace_model.object_collection'] = true;
window.FLATTENED['tracing.trace_model.async_slice'] = true;
window.FLATTENED['tracing.trace_model.async_slice_group'] = true;
window.FLATTENED['tracing.trace_model.sample'] = true;
window.FLATTENED['tracing.color_scheme'] = true;
window.FLATTENED['tracing.trace_model.slice_group'] = true;
window.FLATTENED['tracing.trace_model.thread'] = true;
window.FLATTENED['base.settings'] = true;
window.FLATTENED['tracing.trace_model_settings'] = true;
window.FLATTENED['tracing.trace_model.process_base'] = true;
window.FLATTENED['tracing.trace_model.kernel'] = true;
window.FLATTENED['tracing.trace_model.process'] = true;
window.FLATTENED['base.properties'] = true;
window.FLATTENED['ui'] = true;
window.FLATTENED['ui.overlay'] = true;
window.FLATTENED['tracing.trace_model'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/jszip/jszip.js'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/jszip/jszip-inflate.js'] = true;
window.FLATTENED['tracing.importer.gzip_importer'] = true;
window.FLATTENED['tracing.importer.linux_perf.parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.android_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.bus_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.clock_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.cpufreq_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.disk_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.drm_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.exynos_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.gesture_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.i915_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.kfunc_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.mali_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.power_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.sched_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.sync_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf.workqueue_parser'] = true;
window.FLATTENED['tracing.importer.linux_perf_importer'] = true;
window.FLATTENED['base.quad'] = true;
window.FLATTENED['tracing.trace_model.flow_event'] = true;
window.FLATTENED['tracing.trace_model.instant_event'] = true;
window.FLATTENED['tracing.importer.trace_event_importer'] = true;
window.FLATTENED['tracing.importer.v8.splaytree'] = true;
window.FLATTENED['tracing.importer.v8.codemap'] = true;
window.FLATTENED['tracing.importer.v8.log_reader'] = true;
window.FLATTENED['tracing.importer.v8_log_importer'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/jszip/jszip.js'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/jszip/jszip-load.js'] = true;
window.FLATTENED_RAW_SCRIPTS['../third_party/jszip/jszip-inflate.js'] = true;
window.FLATTENED['tracing.importer.zip_importer'] = true;
window.FLATTENED['tracing.importer'] = true;
window.FLATTENED['tracing.analysis.util'] = true;
window.FLATTENED['tracing.selection'] = true;
window.FLATTENED['tracing.analysis.analysis_link'] = true;
window.FLATTENED['tracing.analysis.generic_object_view'] = true;
window.FLATTENED['tracing.analysis.analysis_results'] = true;
window.FLATTENED['tracing.analysis.analyze_counters'] = true;
window.FLATTENED['tracing.analysis.analyze_slices'] = true;
window.FLATTENED['tracing.analysis.analyze_selection'] = true;
window.FLATTENED['tracing.analysis.object_instance_view'] = true;
window.FLATTENED['tracing.analysis.object_snapshot_view'] = true;
window.FLATTENED['tracing.analysis.default_object_view'] = true;
window.FLATTENED['tracing.analysis.slice_view'] = true;
window.FLATTENED['tracing.analysis.analysis_view'] = true;
window.FLATTENED['tracing.analysis.cpu_slice_view'] = true;
window.FLATTENED['tracing.analysis.thread_time_slice_view'] = true;
window.FLATTENED['ui.animation'] = true;
window.FLATTENED['tracing.timeline_display_transform_animations'] = true;
window.FLATTENED['tracing.elided_cache'] = true;
window.FLATTENED['tracing.draw_helpers'] = true;
window.FLATTENED['tracing.timeline_display_transform'] = true;
window.FLATTENED['ui.animation_controller'] = true;
window.FLATTENED['tracing.timeline_viewport'] = true;
window.FLATTENED['tracing.constants'] = true;
window.FLATTENED['tracing.timing_tool'] = true;
window.FLATTENED['ui.container_that_decorates_its_children'] = true;
window.FLATTENED['tracing.tracks.track'] = true;
window.FLATTENED['tracing.tracks.drawing_container'] = true;
window.FLATTENED['tracing.tracks.heading_track'] = true;
window.FLATTENED['tracing.tracks.ruler_track'] = true;
window.FLATTENED['base.measuring_stick'] = true;
window.FLATTENED['tracing.tracks.container_track'] = true;
window.FLATTENED['tracing.fast_rect_renderer'] = true;
window.FLATTENED['tracing.tracks.slice_track'] = true;
window.FLATTENED['tracing.tracks.cpu_track'] = true;
window.FLATTENED['tracing.tracks.object_instance_track'] = true;
window.FLATTENED['tracing.tracks.stacked_bars_track'] = true;
window.FLATTENED['tcmalloc.heap_instance_track'] = true;
window.FLATTENED['tracing.tracks.counter_track'] = true;
window.FLATTENED['tracing.tracks.spacing_track'] = true;
window.FLATTENED['tracing.tracks.slice_group_track'] = true;
window.FLATTENED['tracing.tracks.async_slice_group_track'] = true;
window.FLATTENED['tracing.tracks.thread_track'] = true;
window.FLATTENED['ui.dom_helpers'] = true;
window.FLATTENED['tracing.tracks.process_track_base'] = true;
window.FLATTENED['tracing.tracks.kernel_track'] = true;
window.FLATTENED['tracing.tracks.process_track'] = true;
window.FLATTENED['tracing.tracks.trace_model_track'] = true;
window.FLATTENED['base.key_event_manager'] = true;
window.FLATTENED['ui.mouse_tracker'] = true;
window.FLATTENED['ui.mouse_mode_selector'] = true;
window.FLATTENED['tracing.timeline_track_view'] = true;
window.FLATTENED['tracing.find_control'] = true;
window.FLATTENED['ui.drag_handle'] = true;
window.FLATTENED['tracing.timeline_view'] = true;
window.FLATTENED['tracing.standalone_timeline_view'] = true;
var templateData_ = window.atob('PCEtLQpDb3B5cmlnaHQgKGMpIDIwMTMgVGhlIENocm9taXVtIEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuClVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGEgQlNELXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmUKZm91bmQgaW4gdGhlIExJQ0VOU0UgZmlsZS4KLS0+Cgo8dGVtcGxhdGUgaWQ9J3F1YWQtc3RhY2stdmlldy10ZW1wbGF0ZSc+CiAgPGNhbnZhcyBpZD0nY2FudmFzJz48L2NhbnZhcz4KICA8aW1nIGlkPSdjaHJvbWUtbGVmdCcvPgogIDxpbWcgaWQ9J2Nocm9tZS1taWQnLz4KICA8aW1nIGlkPSdjaHJvbWUtcmlnaHQnLz4KPC90ZW1wbGF0ZT4KPCEtLQpDb3B5cmlnaHQgKGMpIDIwMTMgVGhlIENocm9taXVtIEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuClVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGEgQlNELXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmUKZm91bmQgaW4gdGhlIExJQ0VOU0UgZmlsZS4KLS0+Cgo8dGVtcGxhdGUgaWQ9Im92ZXJsYXktdGVtcGxhdGUiPgogIDxzdHlsZT4KICAgIEBob3N0IHsKICAgICAgOnNjb3BlIHsKICAgICAgICBsZWZ0OiAwOwogICAgICAgIHBhZGRpbmc6IDhweDsKICAgICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgICAgdG9wOiAwOwogICAgICAgIHotaW5kZXg6IDEwMDA7CiAgICAgICAgZm9udC1mYW1pbHk6IHNhbnMtc2VyaWY7CiAgICAgIH0KICAgICAgOnNjb3BlOmZvY3VzIHsKICAgICAgICBvdXRsaW5lOiBub25lOwogICAgICB9CiAgICB9CiAgICBvdmVybGF5LW1hc2sgewogICAgICAtd2Via2l0LWp1c3RpZnktY29udGVudDogY2VudGVyOwogICAgICBiYWNrZ3JvdW5kOiByZ2JhKDAsIDAsIDAsIDAuOCk7CiAgICAgIGRpc3BsYXk6IC13ZWJraXQtZmxleDsKICAgICAgaGVpZ2h0OiAxMDAlOwogICAgICBsZWZ0OiAwOwogICAgICBwb3NpdGlvbjogZml4ZWQ7CiAgICAgIHRvcDogMDsKICAgICAgd2lkdGg6IDEwMCU7CiAgICB9CiAgICBvdmVybGF5LXZlcnRpY2FsLWNlbnRlcmluZy1jb250YWluZXIgewogICAgICAtd2Via2l0LWp1c3RpZnktY29udGVudDogY2VudGVyOwogICAgICAtd2Via2l0LWZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgIGRpc3BsYXk6IC13ZWJraXQtZmxleDsKICAgIH0KICAgIG92ZXJsYXktZnJhbWUgewogICAgICB6LWluZGV4OiAxMTAwOwogICAgICBiYWNrZ3JvdW5kOiByZ2IoMjU1LCAyNTUsIDI1NSk7CiAgICAgIGJvcmRlcjogMXB4IHNvbGlkICNjY2M7CiAgICAgIG1hcmdpbjogNzVweDsKICAgICAgZGlzcGxheTogLXdlYmtpdC1mbGV4OwogICAgICAtd2Via2l0LWZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICB9CiAgICB0aXRsZS1iYXIgewogICAgICAtd2Via2l0LWFsaWduLWl0ZW1zOiBjZW50ZXI7CiAgICAgIC13ZWJraXQtZmxleC1kaXJlY3Rpb246IHJvdzsKICAgICAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNjY2M7CiAgICAgIGJhY2tncm91bmQtY29sb3I6ICNkZGQ7CiAgICAgIGRpc3BsYXk6IC13ZWJraXQtZmxleDsKICAgICAgcGFkZGluZzogNXB4OwogICAgICAtd2Via2l0LWZsZXg6IDAgMCBhdXRvOwogICAgfQogICAgdGl0bGUgewogICAgICBkaXNwbGF5OiBpbmxpbmU7CiAgICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgICAtd2Via2l0LWJveC1mbGV4OiAxOwogICAgICAtd2Via2l0LWZsZXg6IDEgMSBhdXRvOwogICAgfQogICAgY2xvc2UtYnV0dG9uIHsKICAgICAgLXdlYmtpdC1hbGlnbi1zZWxmOiBmbGV4LWVuZDsKICAgICAgYm9yZGVyOiAxcHggc29saWQgI2VlZTsKICAgICAgYmFja2dyb3VuZC1jb2xvcjogIzk5OTsKICAgICAgZm9udC1zaXplOiAxMHB0OwogICAgICBmb250LXdlaWdodDogYm9sZDsKICAgICAgcGFkZGluZzogMnB4OwogICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7CiAgICAgIHdpZHRoOiAxNnB4OwogICAgfQogICAgY2xvc2UtYnV0dG9uOmhvdmVyIHsKICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2RkZDsKICAgICAgYm9yZGVyLWNvbG9yOiBibGFjazsKICAgICAgY3Vyc29yOiBwb2ludGVyOwogICAgfQogICAgb3ZlcmxheS1jb250ZW50IHsKICAgICAgZGlzcGxheTogLXdlYmtpdC1mbGV4OwogICAgICAtd2Via2l0LWZsZXg6IDEgMSBhdXRvOwogICAgICAtd2Via2l0LWZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgIG92ZXJmbG93LXk6IGF1dG87CiAgICAgIHBhZGRpbmc6IDEwcHg7CiAgICAgIG1pbi13aWR0aDogMzAwcHg7CiAgICB9CiAgICBidXR0b24tYmFyIHsKICAgICAgLXdlYmtpdC1mbGV4LWRpcmVjdGlvbjogcm93OwogICAgICBib3JkZXItdG9wOiAxcHggc29saWQgI2NjYzsKICAgICAgZGlzcGxheTogLXdlYmtpdC1mbGV4OwogICAgICBwYWRkaW5nOjRweDsKICAgICAgLXdlYmtpdC1hbGlnbi1pdGVtczogYmFzZWxpbmU7CiAgICAgIC13ZWJraXQtZmxleDogMCAwIGF1dG87CiAgICB9CiAgICBidXR0b24tYmFyID4gcGFkZGluZyB7CiAgICAgIC13ZWJraXQtYm94LWZsZXg6IDE7CiAgICAgIC13ZWJraXQtZmxleDogMSAxIGF1dG87CiAgICB9CiAgICBidXR0b24tYmFyID4gbGVmdC1idXR0b25zLAogICAgYnV0dG9uLWJhciA+IHJpZ2h0LWJ1dHRvbnMgewogICAgICBkaXNwbGF5OiAtd2Via2l0LWZsZXg7CiAgICAgIC13ZWJraXQtZmxleC1kaXJlY3Rpb246IHJvdzsKICAgIH0KICA8L3N0eWxlPgoKICA8b3ZlcmxheS1tYXNrPgogICAgPG92ZXJsYXktdmVydGljYWwtY2VudGVyaW5nLWNvbnRhaW5lcj4KICAgICAgPG92ZXJsYXktZnJhbWU+CiAgICAgICAgPHRpdGxlLWJhcj4KICAgICAgICAgIDx0aXRsZT48L3RpdGxlPgogICAgICAgICAgPGNsb3NlLWJ1dHRvbj4mI3gyNzE1PC9jbG9zZS1idXR0b24+CiAgICAgICAgPC90aXRsZS1iYXI+CiAgICAgICAgPG92ZXJsYXktY29udGVudD4KICAgICAgICAgIDxjb250ZW50PjwvY29udGVudD4KICAgICAgICA8L292ZXJsYXktY29udGVudD4KICAgICAgICA8YnV0dG9uLWJhcj4KICAgICAgICAgIDxsZWZ0LWJ1dHRvbnM+PC9sZWZ0LWJ1dHRvbnM+CiAgICAgICAgICA8cGFkZGluZz48L3BhZGRpbmc+CiAgICAgICAgICA8cmlnaHQtYnV0dG9ucz48L3JpZ2h0LWJ1dHRvbnM+CiAgICAgICAgPC9idXR0b24tYmFyPgogICAgICA8L292ZXJsYXktZnJhbWU+CiAgICA8L292ZXJsYXktdmVydGljYWwtY2VudGVyaW5nLWNvbnRhaW5lcj4KICA8L292ZXJsYXktbWFzaz4KPC90ZW1wbGF0ZT4KPCEtLQpDb3B5cmlnaHQgKGMpIDIwMTMgVGhlIENocm9taXVtIEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuClVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGEgQlNELXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmUKZm91bmQgaW4gdGhlIExJQ0VOU0UgZmlsZS4KLS0+Cgo8dGVtcGxhdGUgaWQ9Im1vdXNlLW1vZGUtc2VsZWN0b3ItdGVtcGxhdGUiPgogIDxkaXYgY2xhc3M9ImRyYWctaGFuZGxlIj48L2Rpdj4KICA8ZGl2IGNsYXNzPSJidXR0b25zIj4KICA8L2Rpdj4KPC90ZW1wbGF0ZT4KPCEtLQpDb3B5cmlnaHQgKGMpIDIwMTMgVGhlIENocm9taXVtIEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuClVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGEgQlNELXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmUKZm91bmQgaW4gdGhlIExJQ0VOU0UgZmlsZS4KLS0+Cgo8dGVtcGxhdGUgaWQ9InBpY3R1cmUtZGVidWdnZXItdGVtcGxhdGUiPgogIDxsZWZ0LXBhbmVsPgogICAgPHBpY3R1cmUtaW5mbz4KICAgICAgPGRpdj4KICAgICAgICA8c3BhbiBjbGFzcz0ndGl0bGUnPlNraWEgUGljdHVyZTwvc3Bhbj4KICAgICAgICA8c3BhbiBjbGFzcz0nc2l6ZSc+PC9zcGFuPgogICAgICA8L2Rpdj4KICAgICAgPGRpdj4KICAgICAgICA8aW5wdXQgY2xhc3M9J2ZpbGVuYW1lJyB0eXBlPSd0ZXh0JyB2YWx1ZT0nc2twaWN0dXJlLnNrcCcgLz4KICAgICAgICA8YnV0dG9uIGNsYXNzPSdleHBvcnQnPkV4cG9ydDwvYnV0dG9uPgogICAgICA8L2Rpdj4KICAgIDwvcGljdHVyZS1pbmZvPgogIDwvbGVmdC1wYW5lbD4KICA8cmlnaHQtcGFuZWw+CiAgICA8cGljdHVyZS1vcHMtY2hhcnQtdmlldz48L3BpY3R1cmUtb3BzLWNoYXJ0LXZpZXc+CiAgICA8cmFzdGVyLWFyZWE+PGNhbnZhcz48L2NhbnZhcz48L3Jhc3Rlci1hcmVhPgogIDwvcmlnaHQtcGFuZWw+CjwvdGVtcGxhdGU+CjwhLS0KQ29weXJpZ2h0IChjKSAyMDEzIFRoZSBDaHJvbWl1bSBBdXRob3JzLiBBbGwgcmlnaHRzIHJlc2VydmVkLgpVc2Ugb2YgdGhpcyBzb3VyY2UgY29kZSBpcyBnb3Zlcm5lZCBieSBhIEJTRC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlCmZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUuCi0tPgoKPHRlbXBsYXRlIGlkPSJ0aW1lbGluZS12aWV3LXRlbXBsYXRlIj4KICA8ZGl2IGNsYXNzPSJjb250cm9sIj4KICAgIDxkaXYgaWQ9ImxlZnQtY29udHJvbHMiIGNsYXNzPSJjb250cm9scyI+PC9kaXY+CiAgICA8ZGl2IGNsYXNzPSJ0aXRsZSI+Xl9ePC9kaXY+CiAgICA8ZGl2IGlkPSJyaWdodC1jb250cm9scyIgY2xhc3M9ImNvbnRyb2xzIj48L2Rpdj4KICA8L2Rpdj4KICA8ZGl2IGNsYXNzPSJjb250YWluZXIiPjwvZGl2Pgo8L3RlbXBsYXRlPgoKPHRlbXBsYXRlIGlkPSJoZWxwLWJ0bi10ZW1wbGF0ZSI+CiAgPGRpdiBjbGFzcz0iYnV0dG9uIHZpZXctaGVscC1idXR0b24iPj88L2Rpdj4KICA8ZGl2IGNsYXNzPSJ2aWV3LWhlbHAtdGV4dCI+CiAgICA8ZGl2IGNsYXNzPSJjb2x1bW4gbGVmdCI+CiAgICAgIDxoMj5OYXZpZ2F0aW9uPC9oMj4KICAgICAgPGRpdiBjbGFzcz0ncGFpcic+CiAgICAgICAgPGRpdiBjbGFzcz0nY29tbWFuZCc+dy9zPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0nYWN0aW9uJz5ab29tIGluL291dCAoK3NoaWZ0OiBmYXN0ZXIpPC9kaXY+CiAgICAgIDwvZGl2PgoKICAgICAgPGRpdiBjbGFzcz0ncGFpcic+CiAgICAgICAgPGRpdiBjbGFzcz0nY29tbWFuZCc+YS9kPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0nYWN0aW9uJz5QYW4gbGVmdC9yaWdodCAoK3NoaWZ0OiBmYXN0ZXIpPC9kaXY+CiAgICAgIDwvZGl2PgoKICAgICAgPGRpdiBjbGFzcz0ncGFpcic+CiAgICAgICAgPGRpdiBjbGFzcz0nY29tbWFuZCc+JnJhcnI7L3NoaWZ0LVRBQjwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9J2FjdGlvbic+U2VsZWN0IHByZXZpb3VzIGV2ZW50PC9kaXY+CiAgICAgIDwvZGl2PgoKICAgICAgPGRpdiBjbGFzcz0ncGFpcic+CiAgICAgICAgPGRpdiBjbGFzcz0nY29tbWFuZCc+JmxhcnI7L1RBQjwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9J2FjdGlvbic+U2VsZWN0IG5leHQgZXZlbnQ8L2Rpdj4KICAgICAgPC9kaXY+CgogICAgICA8aDI+TW91c2UgQ29udHJvbHM8L2gyPgogICAgICA8ZGl2IGNsYXNzPSdwYWlyJz4KICAgICAgICA8ZGl2IGNsYXNzPSdjb21tYW5kJz5jbGljazwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9J2FjdGlvbic+U2VsZWN0IGV2ZW50PC9kaXY+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSdwYWlyJz4KICAgICAgICA8ZGl2IGNsYXNzPSdjb21tYW5kJz5hbHQtbW91c2V3aGVlbDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9J2FjdGlvbic+Wm9vbSBpbi9vdXQ8L2Rpdj4KICAgICAgPC9kaXY+CgogICAgICA8aDM+CiAgICAgICAgPHNwYW4gY2xhc3M9J21vdXNlLW1vZGUtaWNvbiBzZWxlY3QtbW9kZSc+PC9zcGFuPgogICAgICAgIFNlbGVjdCBtb2RlCiAgICAgIDwvaDM+CiAgICAgIDxkaXYgY2xhc3M9J3BhaXInPgogICAgICAgIDxkaXYgY2xhc3M9J2NvbW1hbmQnPmRyYWc8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSdhY3Rpb24nPkJveCBzZWxlY3Q8L2Rpdj4KICAgICAgPC9kaXY+CgogICAgICA8ZGl2IGNsYXNzPSdwYWlyJz4KICAgICAgICA8ZGl2IGNsYXNzPSdjb21tYW5kJz5kb3VibGUgY2xpY2s8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSdhY3Rpb24nPlNlbGVjdCBhbGwgZXZlbnRzIHdpdGggc2FtZSB0aXRsZTwvZGl2PgogICAgICA8L2Rpdj4KCiAgICAgIDxoMz4KICAgICAgICA8c3BhbiBjbGFzcz0nbW91c2UtbW9kZS1pY29uIHBhbi1tb2RlJz48L3NwYW4+CiAgICAgICAgUGFuIG1vZGUKICAgICAgPC9oMz4KICAgICAgPGRpdiBjbGFzcz0ncGFpcic+CiAgICAgICAgPGRpdiBjbGFzcz0nY29tbWFuZCc+ZHJhZzwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9J2FjdGlvbic+UGFuIHRoZSB2aWV3PC9kaXY+CiAgICAgIDwvZGl2PgoKICAgICAgPGgzPgogICAgICAgIDxzcGFuIGNsYXNzPSdtb3VzZS1tb2RlLWljb24gem9vbS1tb2RlJz48L3NwYW4+CiAgICAgICAgWm9vbSBtb2RlCiAgICAgIDwvaDM+CiAgICAgIDxkaXYgY2xhc3M9J3BhaXInPgogICAgICAgIDxkaXYgY2xhc3M9J2NvbW1hbmQnPmRyYWc8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSdhY3Rpb24nPlpvb20gaW4vb3V0IGJ5IGRyYWdnaW5nIHVwL2Rvd248L2Rpdj4KICAgICAgPC9kaXY+CgogICAgICA8aDM+CiAgICAgICAgPHNwYW4gY2xhc3M9J21vdXNlLW1vZGUtaWNvbiB0aW1pbmctbW9kZSc+PC9zcGFuPgogICAgICAgIFRpbWluZyBtb2RlCiAgICAgIDwvaDM+CiAgICAgIDxkaXYgY2xhc3M9J3BhaXInPgogICAgICAgIDxkaXYgY2xhc3M9J2NvbW1hbmQnPmRyYWc8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSdhY3Rpb24nPkNyZWF0ZSBvciBtb3ZlIG1hcmtlcnM8L2Rpdj4KICAgICAgPC9kaXY+CgogICAgICA8ZGl2IGNsYXNzPSdwYWlyJz4KICAgICAgICA8ZGl2IGNsYXNzPSdjb21tYW5kJz5kb3VibGUgY2xpY2s8L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSdhY3Rpb24nPlNldCBtYXJrZXIgcmFuZ2UgdG8gc2xpY2U8L2Rpdj4KICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KCiAgICA8ZGl2IGNsYXNzPSJjb2x1bW4gcmlnaHQiPgogICAgICA8aDI+R2VuZXJhbDwvaDI+CiAgICAgIDxkaXYgY2xhc3M9J3BhaXInPgogICAgICAgIDxkaXYgY2xhc3M9J2NvbW1hbmQnPjEtNDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9J2FjdGlvbic+U3dpdGNoIG1vdXNlIG1vZGU8L2Rpdj4KICAgICAgPC9kaXY+CgogICAgICA8ZGl2IGNsYXNzPSdwYWlyJz4KICAgICAgICA8ZGl2IGNsYXNzPSdjb21tYW5kJz5zaGlmdDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9J2FjdGlvbic+SG9sZCBmb3IgdGVtcG9yYXJ5IHNlbGVjdDwvZGl2PgogICAgICA8L2Rpdj4KCiAgICAgIDxkaXYgY2xhc3M9J3BhaXInPgogICAgICAgIDxkaXYgY2xhc3M9J2NvbW1hbmQnPnNwYWNlPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0nYWN0aW9uJz5Ib2xkIGZvciB0ZW1wb3JhcnkgcGFuPC9kaXY+CiAgICAgIDwvZGl2PgoKICAgICAgPGRpdiBjbGFzcz0ncGFpcic+CiAgICAgICAgPGRpdiBjbGFzcz0nY29tbWFuZCc+PHNwYW4gY2xhc3M9J21vZCc+PC9zcGFuPjwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9J2FjdGlvbic+SG9sZCBmb3IgdGVtcG9yYXJ5IHpvb208L2Rpdj4KICAgICAgPC9kaXY+CgogICAgICA8ZGl2IGNsYXNzPSdwYWlyJz4KICAgICAgICA8ZGl2IGNsYXNzPSdjb21tYW5kJz4vPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0nYWN0aW9uJz5TZWFyY2g8L2Rpdj4KICAgICAgPC9kaXY+CgogICAgICA8ZGl2IGNsYXNzPSdwYWlyJz4KICAgICAgICA8ZGl2IGNsYXNzPSdjb21tYW5kJz5lbnRlcjwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9J2FjdGlvbic+U3RlcCB0aHJvdWdoIHNlYXJjaCByZXN1bHRzPC9kaXY+CiAgICAgIDwvZGl2PgoKICAgICAgPGRpdiBjbGFzcz0ncGFpcic+CiAgICAgICAgPGRpdiBjbGFzcz0nY29tbWFuZCc+ZjwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9J2FjdGlvbic+Wm9vbSBpbnRvIHNlbGVjdGlvbjwvZGl2PgogICAgICA8L2Rpdj4KCiAgICAgIDxkaXYgY2xhc3M9J3BhaXInPgogICAgICAgIDxkaXYgY2xhc3M9J2NvbW1hbmQnPnovMDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9J2FjdGlvbic+UmVzZXQgem9vbSBhbmQgcGFuPC9kaXY+CiAgICAgIDwvZGl2PgoKICAgICAgPGRpdiBjbGFzcz0ncGFpcic+CiAgICAgICAgPGRpdiBjbGFzcz0nY29tbWFuZCc+Zy9HPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0nYWN0aW9uJz5BZGQgZnJhbWUgbWFya2VycyB0byBldmVudDwvZGl2PgogICAgICA8L2Rpdj4KCiAgICAgIDxkaXYgY2xhc3M9J3BhaXInPgogICAgICAgIDxkaXYgY2xhc3M9J2NvbW1hbmQnPj88L2Rpdj4KICAgICAgICA8ZGl2IGNsYXNzPSdhY3Rpb24nPlNob3cgaGVscDwvZGl2PgogICAgICA8L2Rpdj4KICAgIDwvZGl2PgogIDwvZGl2Pgo8L3RlbXBsYXRlPgoKPHRlbXBsYXRlIGlkPSJtZXRhZGF0YS1idG4tdGVtcGxhdGUiPgogIDxkaXYgY2xhc3M9ImJ1dHRvbiB2aWV3LW1ldGFkYXRhLWJ1dHRvbiB2aWV3LWluZm8tYnV0dG9uIj5NZXRhZGF0YTwvZGl2PgogIDxkaXYgY2xhc3M9ImluZm8tYnV0dG9uLXRleHQgbWV0YWRhdGEtZGlhbG9nLXRleHQiPjwvZGl2Pgo8L3RlbXBsYXRlPgo8IS0tCkNvcHlyaWdodCAoYykgMjAxMyBUaGUgQ2hyb21pdW0gQXV0aG9ycy4gQWxsIHJpZ2h0cyByZXNlcnZlZC4KVXNlIG9mIHRoaXMgc291cmNlIGNvZGUgaXMgZ292ZXJuZWQgYnkgYSBCU0Qtc3R5bGUgbGljZW5zZSB0aGF0IGNhbiBiZQpmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlLgotLT4KCjx0ZW1wbGF0ZSBpZD0iZmluZC1jb250cm9sLXRlbXBsYXRlIj4KICA8c3R5bGU+CiAgICBAaG9zdCB7CiAgICAgIDpzY29wZSB7CiAgICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogbm9uZTsKICAgICAgICBkaXNwbGF5OiAtd2Via2l0LWJveDsKICAgICAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICAgIH0KICAgIH0KICAgIGlucHV0IHsKICAgICAgLXdlYmtpdC11c2VyLXNlbGVjdDogYXV0bzsKICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2Y4ZjhmODsKICAgICAgYm9yZGVyOiAxcHggc29saWQgcmdiYSgwLCAwLCAwLCAwLjUpOwogICAgICBib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgICBoZWlnaHQ6IDE5cHg7CiAgICAgIG1hcmdpbi1ib3R0b206IDFweDsKICAgICAgbWFyZ2luLWxlZnQ6IDA7CiAgICAgIG1hcmdpbi1yaWdodDogMDsKICAgICAgbWFyZ2luLXRvcDogMXB4OwogICAgICBwYWRkaW5nOiAwOwogICAgICB3aWR0aDogMTcwcHg7CiAgICB9CiAgICBpbnB1dDpmb2N1cyB7CiAgICAgIGJhY2tncm91bmQtY29sb3I6IHdoaXRlOwogICAgfQogICAgLmJ1dHRvbiB7CiAgICAgIGJvcmRlci1sZWZ0OiBub25lOwogICAgICBoZWlnaHQ6IDE3cHg7CiAgICAgIG1hcmdpbi1sZWZ0OiAwOwogICAgfQogICAgLmZpbmQtcHJldmlvdXMgewogICAgICBtYXJnaW4tcmlnaHQ6IDA7CiAgICB9CiAgICAuaGl0LWNvdW50LWxhYmVsIHsKICAgICAgaGVpZ2h0OiAxOXB4OwogICAgICBsZWZ0OiAwOwogICAgICBvcGFjaXR5OiAwLjI1OwogICAgICBwb2ludGVyLWV2ZW50czogbm9uZTsKICAgICAgcG9zaXRpb246IGFic29sdXRlOwogICAgICB0ZXh0LWFsaWduOiByaWdodDsKICAgICAgdG9wOiAycHg7CiAgICAgIHdpZHRoOiAxNzBweDsKICAgICAgei1pbmRleDogMTsKICAgIH0KICA8L3N0eWxlPgoKICA8aW5wdXQgdHlwZT0ndGV4dCcgaWQ9J2ZpbmQtY29udHJvbC1maWx0ZXInIC8+CiAgPGRpdiBjbGFzcz0iYnV0dG9uIGZpbmQtcHJldmlvdXMiPiZsYXJyOzwvZGl2PgogIDxkaXYgY2xhc3M9ImJ1dHRvbiBmaW5kLW5leHQiPiZyYXJyOzwvZGl2PgogIDxkaXYgY2xhc3M9ImhpdC1jb3VudC1sYWJlbCI+MSBvZiA3PC9kaXY+CjwvdGVtcGxhdGU+CjwhLS0KQ29weXJpZ2h0IChjKSAyMDEzIFRoZSBDaHJvbWl1bSBBdXRob3JzLiBBbGwgcmlnaHRzIHJlc2VydmVkLgpVc2Ugb2YgdGhpcyBzb3VyY2UgY29kZSBpcyBnb3Zlcm5lZCBieSBhIEJTRC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlCmZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUuCi0tPgoKPHRlbXBsYXRlIGlkPSJyZWNvcmQtc2VsZWN0aW9uLWRpYWxvZy10ZW1wbGF0ZSI+CiAgPHN0eWxlPgogICAgLnJlY29yZC1zZWxlY3Rpb24tZGlhbG9nIHsKICAgICAgZGlzcGxheTogLXdlYmtpdC1mbGV4OwogICAgICAtd2Via2l0LWZsZXgtZGlyZWN0aW9uOiByb3c7CiAgICAgIG1pbi1oZWlnaHQ6IDA7CiAgICAgIG1pbi13aWR0aDogMDsKICAgICAgZm9udC1mYW1pbHk6IHNhbnMtc2VyaWY7CiAgICB9CgogICAgLmRlZmF1bHQtZW5hYmxlZC1jYXRlZ29yaWVzLAogICAgLmRlZmF1bHQtZGlzYWJsZWQtY2F0ZWdvcmllcyB7CiAgICAgIC13ZWJraXQtZmxleDogMSAxIGF1dG87CiAgICAgIGRpc3BsYXk6IC13ZWJraXQtZmxleDsKICAgICAgLXdlYmtpdC1mbGV4LWRpcmVjdGlvbjogY29sdW1uOwogICAgICBwYWRkaW5nOiA0cHg7CiAgICAgIHdpZHRoOiAzMDBweDsKICAgIH0KCiAgICAuZGVmYXVsdC1kaXNhYmxlZC1jYXRlZ29yaWVzIHsKICAgICAgYm9yZGVyLWxlZnQ6IDJweCBzb2xpZCAjZGRkOwogICAgfQoKICAgIC5jYXRlZ29yaWVzIHsKICAgICAgZm9udC1zaXplOiA4MCU7CiAgICAgIHBhZGRpbmc6IDEwcHg7CiAgICAgIG92ZXJmbG93OiBhdXRvOwogICAgICBtYXgtaGVpZ2h0OiA0MDBweDsKICAgICAgLXdlYmtpdC1mbGV4OiAxIDEgYXV0bzsKICAgIH0KCiAgICAuZ3JvdXAtc2VsZWN0b3JzIHsKICAgICAgZm9udC1zaXplOiA4MCU7CiAgICAgIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjZGRkOwogICAgICBwYWRkaW5nLWJvdHRvbTogNnB4OwogICAgICAtd2Via2l0LWZsZXg6IDAgMCBhdXRvOwogICAgfQoKICAgIC5ncm91cC1zZWxlY3RvcnMgYnV0dG9uIHsKICAgICAgcGFkZGluZzogMXB4OwogICAgfQoKICA8L3N0eWxlPgogIDxkaXYgY2xhc3M9InJlY29yZC1zZWxlY3Rpb24tZGlhbG9nIj4KICAgIDxkaXYgY2xhc3M9ImRlZmF1bHQtZW5hYmxlZC1jYXRlZ29yaWVzIj4KICAgICAgPGRpdj5SZWNvcmQmbmJzcDtDYXRlZ29yaWVzPC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9Imdyb3VwLXNlbGVjdG9ycyI+CiAgICAgICAgU2VsZWN0CiAgICAgICAgPGJ1dHRvbiBjbGFzcz0iYWxsLWJ0biI+QWxsPC9idXR0b24+CiAgICAgICAgPGJ1dHRvbiBjbGFzcz0ibm9uZS1idG4iPk5vbmU8L2J1dHRvbj4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9ImNhdGVnb3JpZXMiPjwvZGl2PgogICAgPC9kaXY+CiAgICA8ZGl2IGNsYXNzPSJkZWZhdWx0LWRpc2FibGVkLWNhdGVnb3JpZXMiPgogICAgICA8ZGl2PkRpc2FibGVkJm5ic3A7YnkmbmJzcDtEZWZhdWx0Jm5ic3A7Q2F0ZWdvcmllczwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJncm91cC1zZWxlY3RvcnMiPgogICAgICAgIFNlbGVjdAogICAgICAgIDxidXR0b24gY2xhc3M9ImFsbC1idG4iPkFsbDwvYnV0dG9uPgogICAgICAgIDxidXR0b24gY2xhc3M9Im5vbmUtYnRuIj5Ob25lPC9idXR0b24+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJjYXRlZ29yaWVzIj48L2Rpdj4KICAgIDwvZGl2PgogIDwvZGl2Pgo8L3RlbXBsYXRlPgo8IS0tCkNvcHlyaWdodCAoYykgMjAxMyBUaGUgQ2hyb21pdW0gQXV0aG9ycy4gQWxsIHJpZ2h0cyByZXNlcnZlZC4KVXNlIG9mIHRoaXMgc291cmNlIGNvZGUgaXMgZ292ZXJuZWQgYnkgYSBCU0Qtc3R5bGUgbGljZW5zZSB0aGF0IGNhbiBiZQpmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlLgotLT4KCjx0ZW1wbGF0ZSBpZD0idGhyZWFkLXRpbWUtc2xpY2Utdmlldy10ZW1wbGF0ZSI+CiAgPHRhYmxlPgogICAgPHRyIGNsYXNzPSJhbmFseXNpcy10YWJsZS1yb3ciPgogICAgICA8dGQ+UnVubmluZyBwcm9jZXNzOjwvdGQ+PHRkIGlkPSJwcm9jZXNzLW5hbWUiPjwvdGQ+CiAgICA8L3RyPgogICAgPHRyIGNsYXNzPSJhbmFseXNpcy10YWJsZS1yb3ciPgogICAgICA8dGQ+UnVubmluZyB0aHJlYWQ6PC90ZD48dGQgaWQ9InRocmVhZC1uYW1lIj48L3RkPgogICAgPC90cj4KICAgIDx0ciBjbGFzcz0iYW5hbHlzaXMtdGFibGUtcm93Ij4KICAgICAgPHRkPlN0YXRlOjwvdGQ+CiAgICAgIDx0ZD48Yj48c3BhbiBpZD0ic3RhdGUiPjwvc3Bhbj48L2I+PC90ZD4KICAgIDwvdHI+CiAgICA8dHIgY2xhc3M9ImFuYWx5c2lzLXRhYmxlLXJvdyI+CiAgICAgIDx0ZD5TdGFydDo8L3RkPjx0ZCBpZD0ic3RhcnQiPjwvdGQ+CiAgICA8L3RyPgogICAgPHRyIGNsYXNzPSJhbmFseXNpcy10YWJsZS1yb3ciPgogICAgICA8dGQ+RHVyYXRpb246PC90ZD48dGQgaWQ9ImR1cmF0aW9uIj48L3RkPgogICAgPC90cj4KCiAgICA8dHIgY2xhc3M9ImFuYWx5c2lzLXRhYmxlLXJvdyI+CiAgICAgIDx0ZD5PbiBDUFU6PC90ZD48dGQgaWQ9Im9uLWNwdSI+PC90ZD4KICAgIDwvdHI+CgogICAgPHRyIGNsYXNzPSJhbmFseXNpcy10YWJsZS1yb3ciPgogICAgICA8dGQ+UnVubmluZyBpbnN0ZWFkOjwvdGQ+PHRkIGlkPSJydW5uaW5nLWluc3RlYWQiPjwvdGQ+CiAgICA8L3RyPgoKICAgIDx0ciBjbGFzcz0iYW5hbHlzaXMtdGFibGUtcm93Ij4KICAgICAgPHRkPkFyZ3M6PC90ZD48dGQgaWQ9ImFyZ3MiPjwvdGQ+CiAgICA8L3RyPgogIDwvdGFibGU+CjwvdGVtcGxhdGU+CjwhLS0KQ29weXJpZ2h0IChjKSAyMDEzIFRoZSBDaHJvbWl1bSBBdXRob3JzLiBBbGwgcmlnaHRzIHJlc2VydmVkLgpVc2Ugb2YgdGhpcyBzb3VyY2UgY29kZSBpcyBnb3Zlcm5lZCBieSBhIEJTRC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlCmZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUuCi0tPgoKPHRlbXBsYXRlIGlkPSJjcHUtc2xpY2Utdmlldy10ZW1wbGF0ZSI+CiAgPHRhYmxlPgogICAgPHRyIGNsYXNzPSJhbmFseXNpcy10YWJsZS1yb3ciPgogICAgICA8dGQgY29sc3Bhbj0yPjxiPkNQVSBUaW1lIFNsaWNlPC9iPjwvdGQ+CiAgICA8L3RyPgogICAgPHRyIGNsYXNzPSJhbmFseXNpcy10YWJsZS1yb3ciPgogICAgICA8dGQ+UnVubmluZyBwcm9jZXNzOjwvdGQ+PHRkIGlkPSJwcm9jZXNzLW5hbWUiPjwvdGQ+CiAgICA8L3RyPgogICAgPHRyIGNsYXNzPSJhbmFseXNpcy10YWJsZS1yb3ciPgogICAgICA8dGQ+UnVubmluZyB0aHJlYWQ6PC90ZD48dGQgaWQ9InRocmVhZC1uYW1lIj48L3RkPgogICAgPC90cj4KICAgIDx0ciBjbGFzcz0iYW5hbHlzaXMtdGFibGUtcm93Ij4KICAgICAgPHRkPlN0YXJ0OjwvdGQ+PHRkIGlkPSJzdGFydCI+PC90ZD4KICAgIDwvdHI+CiAgICA8dHIgY2xhc3M9ImFuYWx5c2lzLXRhYmxlLXJvdyI+CiAgICAgIDx0ZD5EdXJhdGlvbjo8L3RkPjx0ZCBpZD0iZHVyYXRpb24iPjwvdGQ+CiAgICA8L3RyPgogICAgPHRyIGNsYXNzPSJhbmFseXNpcy10YWJsZS1yb3ciPgogICAgICA8dGQ+QWN0aXZlIHNsaWNlczo8L3RkPjx0ZCBpZD0icnVubmluZy10aHJlYWQiPjwvdGQ+CiAgICA8L3RyPgogIDwvdGFibGU+CjwvdGVtcGxhdGU+Cg==');
var templateElem_ = document.createElement('div');
templateElem_.innerHTML = templateData_;
while (templateElem_.hasChildNodes()) {
document.head.appendChild(templateElem_.removeChild(templateElem_.firstChild));
}
// 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.
'use strict';
/**
* The global object.
* @type {!Object}
* @const
*/
var global = this;
/** Platform, package, object property, and Event support. */
this.base = (function() {
/**
* Base path for modules. Used to form URLs for module 'require' requests.
*/
var moduleBasePath = '.';
function setModuleBasePath(path) {
if (path[path.length - 1] == '/')
path = path.substring(0, path.length - 1);
moduleBasePath = path;
}
function mLog(text, opt_indentLevel) {
if (true)
return;
var spacing = '';
var indentLevel = opt_indentLevel || 0;
for (var i = 0; i < indentLevel; i++)
spacing += ' ';
console.log(spacing + text);
}
/**
* Builds an object structure for the provided namespace path,
* ensuring that names that already exist are not overwritten. For
* example:
* 'a.b.c' -> a = {};a.b={};a.b.c={};
* @param {string} name Name of the object that this file defines.
* @param {*=} opt_object The object to expose at the end of the path.
* @param {Object=} opt_objectToExportTo The object to add the path to;
* default is {@code global}.
* @private
*/
function exportPath(name, opt_object, opt_objectToExportTo) {
var parts = name.split('.');
var cur = opt_objectToExportTo || global;
for (var part; parts.length && (part = parts.shift());) {
if (!parts.length && opt_object !== undefined) {
// last part and we have an object; use it
cur[part] = opt_object;
} else if (part in cur) {
cur = cur[part];
} else {
cur = cur[part] = {};
}
}
return cur;
};
var didLoadModules = false;
var moduleDependencies = {};
var moduleStylesheets = {};
var moduleRawScripts = {};
function addModuleDependency(moduleName, dependentModuleName) {
if (!moduleDependencies[moduleName])
moduleDependencies[moduleName] = [];
var dependentModules = moduleDependencies[moduleName];
var found = false;
for (var i = 0; i < dependentModules.length; i++)
if (dependentModules[i] == dependentModuleName)
found = true;
if (!found)
dependentModules.push(dependentModuleName);
}
function addModuleRawScriptDependency(moduleName, rawScriptName) {
if (!moduleRawScripts[moduleName])
moduleRawScripts[moduleName] = [];
var dependentRawScripts = moduleRawScripts[moduleName];
var found = false;
for (var i = 0; i < moduleRawScripts.length; i++)
if (dependentRawScripts[i] == rawScriptName)
found = true;
if (!found)
dependentRawScripts.push(rawScriptName);
}
function addModuleStylesheet(moduleName, stylesheetName) {
if (!moduleStylesheets[moduleName])
moduleStylesheets[moduleName] = [];
var stylesheets = moduleStylesheets[moduleName];
var found = false;
for (var i = 0; i < stylesheets.length; i++)
if (stylesheets[i] == stylesheetName)
found = true;
if (!found)
stylesheets.push(stylesheetName);
}
function ensureDepsLoaded() {
if (window.FLATTENED)
return;
if (didLoadModules)
return;
didLoadModules = true;
var req = new XMLHttpRequest();
var src = '/deps.js';
req.open('GET', src, false);
req.send(null);
if (req.status != 200) {
var serverSideException = JSON.parse(req.responseText);
var msg = 'You have a module problem: ' +
serverSideException.message;
var baseWarningEl = document.createElement('div');
baseWarningEl.style.backgroundColor = 'white';
baseWarningEl.style.border = '3px solid red';
baseWarningEl.style.boxSizing = 'border-box';
baseWarningEl.style.color = 'black';
baseWarningEl.style.display = '-webkit-flex';
baseWarningEl.style.height = '100%';
baseWarningEl.style.left = 0;
baseWarningEl.style.padding = '8px';
baseWarningEl.style.position = 'fixed';
baseWarningEl.style.top = 0;
baseWarningEl.style.webkitFlexDirection = 'column';
baseWarningEl.style.width = '100%';
baseWarningEl.innerHTML =
'<h2>Module parsing problem</h2>' +
'<div id="message"></div>' +
'<pre id="details"></pre>';
baseWarningEl.querySelector('#message').textContent =
serverSideException.message;
var detailsEl = baseWarningEl.querySelector('#details');
detailsEl.textContent = serverSideException.details;
detailsEl.style.webkitFlex = '1 1 auto';
detailsEl.style.overflow = 'auto';
if (!document.body) {
setTimeout(function() {
document.body.appendChild(baseWarningEl);
}, 150);
} else {
document.body.appendChild(baseWarningEl);
}
throw new Error(msg);
}
base.addModuleDependency = addModuleDependency;
base.addModuleRawScriptDependency = addModuleRawScriptDependency;
base.addModuleStylesheet = addModuleStylesheet;
try {
// By construction, the deps should call addModuleDependency.
eval(req.responseText);
} catch (e) {
throw new Error('When loading deps, got ' +
e.stack ? e.stack : e.message);
}
delete base.addModuleStylesheet;
delete base.addModuleRawScriptDependency;
delete base.addModuleDependency;
}
// TODO(dsinclair): Remove this when HTML imports land as the templates
// will be pulled in by the requireTemplate calls.
var templatesLoaded_ = false;
function ensureTemplatesLoaded() {
if (templatesLoaded_ || window.FLATTENED)
return;
templatesLoaded_ = true;
var req = new XMLHttpRequest();
req.open('GET', '/templates', false);
req.send(null);
var elem = document.createElement('div');
elem.innerHTML = req.responseText;
while (elem.hasChildNodes())
document.head.appendChild(elem.removeChild(elem.firstChild));
}
var moduleLoadStatus = {};
var rawScriptLoadStatus = {};
function require(modules, opt_indentLevel) {
var indentLevel = opt_indentLevel || 0;
var dependentModules = modules;
if (!(modules instanceof Array))
dependentModules = [modules];
ensureDepsLoaded();
ensureTemplatesLoaded();
dependentModules.forEach(function(module) {
requireModule(module, indentLevel);
});
}
var modulesWaiting = [];
function requireModule(dependentModuleName, indentLevel) {
if (window.FLATTENED) {
if (!window.FLATTENED[dependentModuleName]) {
throw new Error('Somehow, module ' + dependentModuleName +
' didn\'t get stored in the flattened js file! ' +
'You may need to rerun ' +
'build/generate_about_tracing_contents.py');
}
return;
}
if (moduleLoadStatus[dependentModuleName] == 'APPENDED')
return;
if (moduleLoadStatus[dependentModuleName] == 'RESOLVING')
return;
mLog('require(' + dependentModuleName + ')', indentLevel);
moduleLoadStatus[dependentModuleName] = 'RESOLVING';
requireDependencies(dependentModuleName, indentLevel);
loadScript(dependentModuleName.replace(/\./g, '/') + '.js');
moduleLoadStatus[name] = 'APPENDED';
}
function requireDependencies(dependentModuleName, indentLevel) {
// Load the module's dependent scripts after.
var dependentModules = moduleDependencies[dependentModuleName] || [];
require(dependentModules, indentLevel + 1);
// Load the module stylesheet first.
var stylesheets = moduleStylesheets[dependentModuleName] || [];
for (var i = 0; i < stylesheets.length; i++)
requireStylesheet(stylesheets[i]);
// Load the module raw scripts next
var rawScripts = moduleRawScripts[dependentModuleName] || [];
for (var i = 0; i < rawScripts.length; i++) {
var rawScriptName = rawScripts[i];
if (rawScriptLoadStatus[rawScriptName])
continue;
loadScript(rawScriptName);
mLog('load(' + rawScriptName + ')', indentLevel);
rawScriptLoadStatus[rawScriptName] = 'APPENDED';
}
}
function loadScript(path) {
var scriptEl = document.createElement('script');
scriptEl.src = moduleBasePath + '/' + path;
scriptEl.type = 'text/javascript';
scriptEl.defer = true;
scriptEl.async = false;
base.doc.head.appendChild(scriptEl);
}
/**
* Adds a dependency on a raw javascript file, e.g. a third party
* library.
* @param {String} rawScriptName The path to the script file, relative to
* moduleBasePath.
*/
function requireRawScript(rawScriptPath) {
if (window.FLATTENED_RAW_SCRIPTS) {
if (!window.FLATTENED_RAW_SCRIPTS[rawScriptPath]) {
throw new Error('Somehow, ' + rawScriptPath +
' didn\'t get stored in the flattened js file! ' +
'You may need to rerun build/generate_about_tracing_contents.py');
}
return;
}
if (rawScriptLoadStatus[rawScriptPath])
return;
throw new Error(rawScriptPath + ' should already have been loaded.' +
' Did you forget to run build/generate_about_tracing_contents.py?');
}
var stylesheetLoadStatus = {};
function requireStylesheet(dependentStylesheetName) {
if (window.FLATTENED)
return;
if (stylesheetLoadStatus[dependentStylesheetName])
return;
stylesheetLoadStatus[dependentStylesheetName] = true;
var localPath = dependentStylesheetName.replace(/\./g, '/') + '.css';
var stylesheetPath = moduleBasePath + '/' + localPath;
var linkEl = document.createElement('link');
linkEl.setAttribute('rel', 'stylesheet');
linkEl.setAttribute('href', stylesheetPath);
base.doc.head.appendChild(linkEl);
}
var templateLoadStatus = {};
function requireTemplate(template) {
if (window.FLATTENED)
return;
if (templateLoadStatus[template])
return;
templateLoadStatus[template] = true;
var localPath = template.replace(/\./g, '/') + '.html';
var importPath = moduleBasePath + '/' + localPath;
var linkEl = document.createElement('link');
linkEl.setAttribute('rel', 'import');
linkEl.setAttribute('href', importPath);
// TODO(dsinclair): Enable when HTML imports are available.
//base.doc.head.appendChild(linkEl);
}
function exportTo(namespace, fn) {
var obj = exportPath(namespace);
try {
var exports = fn();
} catch (e) {
console.log('While running exports for ', namespace, ':');
console.log(e.stack || e);
return;
}
for (var propertyName in exports) {
// Maybe we should check the prototype chain here? The current usage
// pattern is always using an object literal so we only care about own
// properties.
var propertyDescriptor = Object.getOwnPropertyDescriptor(exports,
propertyName);
if (propertyDescriptor) {
Object.defineProperty(obj, propertyName, propertyDescriptor);
mLog(' +' + propertyName);
}
}
};
/**
* Initialization which must be deferred until run-time.
*/
function initialize() {
// If 'document' isn't defined, then we must be being pre-compiled,
// so set a trap so that we're initialized on first access at run-time.
if (!global.document) {
var originalBase = base;
Object.defineProperty(global, 'base', {
get: function() {
Object.defineProperty(global, 'base', {value: originalBase});
originalBase.initialize();
return originalBase;
},
configurable: true
});
return;
}
base.doc = document;
base.isMac = /Mac/.test(navigator.platform);
base.isWindows = /Win/.test(navigator.platform);
base.isChromeOS = /CrOS/.test(navigator.userAgent);
base.isLinux = /Linux/.test(navigator.userAgent);
base.isGTK = /GTK/.test(chrome.toolkit);
base.isViews = /views/.test(chrome.toolkit);
setModuleBasePath('/src');
}
return {
set moduleBasePath(path) {
setModuleBasePath(path);
},
get moduleBasePath() {
return moduleBasePath;
},
initialize: initialize,
require: require,
requireStylesheet: requireStylesheet,
requireRawScript: requireRawScript,
requireTemplate: requireTemplate,
exportTo: exportTo
};
})();
base.initialize();
// 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.
'use strict';
/**
* @fileoverview Base class for trace data importers.
*/
base.exportTo('tracing.importer', function() {
function Importer() {
}
Importer.prototype = {
__proto__: Object.prototype,
/**
* Called by the Model to extract one or more subtraces from the event data.
*/
extractSubtraces: function() {
return [];
},
/**
* Called to import events into the Model.
*/
importEvents: function() {
},
/**
* Called by the Model after all other importers have imported their
* events.
*/
finalizeImport: function() {
},
/**
* Called by the Model to join references between objects, after final
* model bounds have been computed.
*/
joinRefs: function() {
}
};
return {
Importer: Importer
};
});
// 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.
'use strict';
/**
* @fileoverview This contains an implementation of the EventTarget interface
* as defined by DOM Level 2 Events.
*/
base.exportTo('base', function() {
/**
* Creates a new EventTarget. This class implements the DOM level 2
* EventTarget interface and can be used wherever those are used.
* @constructor
*/
function EventTarget() {
}
EventTarget.prototype = {
/**
* Adds an event listener to the target.
* @param {string} type The name of the event.
* @param {!Function|{handleEvent:Function}} handler The handler for the
* event. This is called when the event is dispatched.
*/
addEventListener: function(type, handler) {
if (!this.listeners_)
this.listeners_ = Object.create(null);
if (!(type in this.listeners_)) {
this.listeners_[type] = [handler];
} else {
var handlers = this.listeners_[type];
if (handlers.indexOf(handler) < 0)
handlers.push(handler);
}
},
/**
* Removes an event listener from the target.
* @param {string} type The name of the event.
* @param {!Function|{handleEvent:Function}} handler The handler for the
* event.
*/
removeEventListener: function(type, handler) {
if (!this.listeners_)
return;
if (type in this.listeners_) {
var handlers = this.listeners_[type];
var index = handlers.indexOf(handler);
if (index >= 0) {
// Clean up if this was the last listener.
if (handlers.length == 1)
delete this.listeners_[type];
else
handlers.splice(index, 1);
}
}
},
/**
* Dispatches an event and calls all the listeners that are listening to
* the type of the event.
* @param {!cr.event.Event} event The event to dispatch.
* @return {boolean} Whether the default action was prevented. If someone
* calls preventDefault on the event object then this returns false.
*/
dispatchEvent: function(event) {
if (!this.listeners_)
return true;
// Since we are using DOM Event objects we need to override some of the
// properties and methods so that we can emulate this correctly.
var self = this;
event.__defineGetter__('target', function() {
return self;
});
var realPreventDefault = event.preventDefault;
event.preventDefault = function() {
realPreventDefault.call(this);
this.rawReturnValue = false;
};
var type = event.type;
var prevented = 0;
if (type in this.listeners_) {
// Clone to prevent removal during dispatch
var handlers = this.listeners_[type].concat();
for (var i = 0, handler; handler = handlers[i]; i++) {
if (handler.handleEvent)
prevented |= handler.handleEvent.call(handler, event) === false;
else
prevented |= handler.call(this, event) === false;
}
}
return !prevented && event.rawReturnValue;
},
hasEventListener: function(type) {
return this.listeners_[type] !== undefined;
}
};
var EventTargetHelper = {
decorate: function(target) {
for (var k in EventTargetHelper) {
if (k == 'decorate')
continue;
var v = EventTargetHelper[k];
if (typeof v !== 'function')
continue;
target[k] = v;
}
target.listenerCounts_ = {};
},
addEventListener: function(type, listener, useCapture) {
this.__proto__.addEventListener.call(
this, type, listener, useCapture);
if (this.listenerCounts_[type] === undefined)
this.listenerCounts_[type] = 0;
this.listenerCounts_[type]++;
},
removeEventListener: function(type, listener, useCapture) {
this.__proto__.removeEventListener.call(
this, type, listener, useCapture);
this.listenerCounts_[type]--;
},
hasEventListener: function(type) {
return this.listenerCounts_[type] > 0;
}
};
// Export
return {
EventTarget: EventTarget,
EventTargetHelper: EventTargetHelper
};
});
// 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.
'use strict';
base.require('base.event_target');
base.exportTo('base', function() {
/**
* Creates a new event to be used with base.EventTarget or DOM EventTarget
* objects.
* @param {string} type The name of the event.
* @param {boolean=} opt_bubbles Whether the event bubbles.
* Default is false.
* @param {boolean=} opt_preventable Whether the default action of the event
* can be prevented.
* @constructor
* @extends {Event}
*/
function Event(type, opt_bubbles, opt_preventable) {
var e = base.doc.createEvent('Event');
e.initEvent(type, !!opt_bubbles, !!opt_preventable);
e.__proto__ = global.Event.prototype;
return e;
};
Event.prototype = {
__proto__: global.Event.prototype
};
/**
* Dispatches a simple event on an event target.
* @param {!EventTarget} target The event target to dispatch the event on.
* @param {string} type The type of the event.
* @param {boolean=} opt_bubbles Whether the event bubbles or not.
* @param {boolean=} opt_cancelable Whether the default action of the event
* can be prevented.
* @return {boolean} If any of the listeners called {@code preventDefault}
* during the dispatch this will return false.
*/
function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
var e = new Event(type, opt_bubbles, opt_cancelable);
return target.dispatchEvent(e);
}
return {
Event: Event,
dispatchSimpleEvent: dispatchSimpleEvent
};
});
// 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.
'use strict';
base.exportTo('base', function() {
function max(a, b) {
if (a === undefined)
return b;
if (b === undefined)
return a;
return Math.max(a, b);
}
/**
* This class implements an interval tree.
* See: http://wikipedia.org/wiki/Interval_tree
*
* Internally the tree is a Red-Black tree. The insertion/colour is done using
* the Left-leaning Red-Black Trees algorithm as described in:
* http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf
*
* @param {function} beginPositionCb Callback to retrieve the begin position.
* @param {function} endPositionCb Callback to retrieve the end position.
*
* @constructor
*/
function IntervalTree(beginPositionCb, endPositionCb) {
this.beginPositionCb_ = beginPositionCb;
this.endPositionCb_ = endPositionCb;
this.root_ = undefined;
this.size_ = 0;
}
IntervalTree.prototype = {
/**
* Insert events into the interval tree.
*
* @param {Object} begin The left object.
* @param {Object=} opt_end The end object, optional. If not provided the
* begin object is assumed to also be the end object.
*/
insert: function(begin, opt_end) {
var startPosition = this.beginPositionCb_(begin);
var endPosition = this.endPositionCb_(opt_end || begin);
var node = new IntervalTreeNode(begin, opt_end || begin,
startPosition, endPosition);
this.size_++;
this.root_ = this.insertNode_(this.root_, node);
this.root_.colour = Colour.BLACK;
},
insertNode_: function(root, node) {
if (root === undefined)
return node;
if (root.leftNode && root.leftNode.isRed &&
root.rightNode && root.rightNode.isRed)
this.flipNodeColour_(root);
if (node.key < root.key)
root.leftNode = this.insertNode_(root.leftNode, node);
else if (node.key === root.key)
root.merge(node);
else
root.rightNode = this.insertNode_(root.rightNode, node);
if (root.rightNode && root.rightNode.isRed &&
(root.leftNode === undefined || !root.leftNode.isRed))
root = this.rotateLeft_(root);
if (root.leftNode && root.leftNode.isRed &&
root.leftNode.leftNode && root.leftNode.leftNode.isRed)
root = this.rotateRight_(root);
return root;
},
rotateRight_: function(node) {
var sibling = node.leftNode;
node.leftNode = sibling.rightNode;
sibling.rightNode = node;
sibling.colour = node.colour;
node.colour = Colour.RED;
return sibling;
},
rotateLeft_: function(node) {
var sibling = node.rightNode;
node.rightNode = sibling.leftNode;
sibling.leftNode = node;
sibling.colour = node.colour;
node.colour = Colour.RED;
return sibling;
},
flipNodeColour_: function(node) {
node.colour = this.flipColour_(node.colour);
node.leftNode.colour = this.flipColour_(node.leftNode.colour);
node.rightNode.colour = this.flipColour_(node.rightNode.colour);
},
flipColour_: function(colour) {
return colour === Colour.RED ? Colour.BLACK : Colour.RED;
},
/* The high values are used to find intersection. It should be called after
* all of the nodes are inserted. Doing it each insert is _slow_. */
updateHighValues: function() {
this.updateHighValues_(this.root_);
},
/* There is probably a smarter way to do this by starting from the inserted
* node, but need to handle the rotations correctly. Went the easy route
* for now. */
updateHighValues_: function(node) {
if (node === undefined)
return undefined;
node.maxHighLeft = this.updateHighValues_(node.leftNode);
node.maxHighRight = this.updateHighValues_(node.rightNode);
return max(max(node.maxHighLeft, node.highValue), node.maxHighRight);
},
/**
* Retrieve all overlapping intervals.
*
* @param {number} lowValue The low value for the intersection interval.
* @param {number} highValue The high value for the intersection interval.
* @return {Array} All [begin, end] pairs inside intersecting intervals.
*/
findIntersection: function(lowValue, highValue) {
if (lowValue === undefined || highValue === undefined)
throw new Error('lowValue and highValue must be defined');
if ((typeof lowValue !== 'number') || (typeof highValue !== 'number'))
throw new Error('lowValue and highValue must be numbers');
if (this.root_ === undefined)
return [];
return this.findIntersection_(this.root_, lowValue, highValue);
},
findIntersection_: function(node, lowValue, highValue) {
var ret = [];
/* This node starts has a start point at or further right then highValue
* so we know this node is out and all right children are out. Just need
* to check left */
if (node.lowValue >= highValue) {
if (!node.hasLeftNode)
return [];
return this.findIntersection_(node.leftNode, lowValue, highValue);
}
/* If we have a maximum left high value that is bigger then lowValue we
* need to check left for matches */
if (node.maxHighLeft > lowValue) {
ret = ret.concat(
this.findIntersection_(node.leftNode, lowValue, highValue));
}
/* We know that this node starts before highValue, if any of it's data
* ends after lowValue we need to add those nodes */
if (node.highValue > lowValue) {
for (var i = (node.data.length - 1); i >= 0; --i) {
/* data nodes are sorted by high value, so as soon as we see one
* before low value we're done. */
if (node.data[i].high < lowValue)
break;
ret.unshift([node.data[i].start, node.data[i].end]);
}
}
/* check for matches in the right tree */
if (node.hasRightNode) {
ret = ret.concat(
this.findIntersection_(node.rightNode, lowValue, highValue));
}
return ret;
},
/**
* Returns the number of nodes in the tree.
*/
get size() {
return this.size_;
},
/**
* Returns the root node in the tree.
*/
get root() {
return this.root_;
},
/**
* Dumps out the [lowValue, highValue] pairs for each node in depth-first
* order.
*/
dump_: function() {
if (this.root_ === undefined)
return [];
return this.dumpNode_(this.root_);
},
dumpNode_: function(node) {
var ret = {};
if (node.hasLeftNode)
ret['left'] = this.dumpNode_(node.leftNode);
ret['node'] = node.dump();
if (node.hasRightNode)
ret['right'] = this.dumpNode_(node.rightNode);
return ret;
}
};
var Colour = {
RED: 'red',
BLACK: 'black'
};
function IntervalTreeNode(startObject, endObject, lowValue, highValue) {
this.lowValue_ = lowValue;
this.data_ = [{
start: startObject,
end: endObject,
high: highValue,
low: lowValue
}];
this.colour_ = Colour.RED;
this.parentNode_ = undefined;
this.leftNode_ = undefined;
this.rightNode_ = undefined;
this.maxHighLeft_ = undefined;
this.maxHighRight_ = undefined;
}
IntervalTreeNode.prototype = {
get colour() {
return this.colour_;
},
set colour(colour) {
this.colour_ = colour;
},
get key() {
return this.lowValue_;
},
get lowValue() {
return this.lowValue_;
},
get highValue() {
return this.data_[this.data_.length - 1].high;
},
set leftNode(left) {
this.leftNode_ = left;
},
get leftNode() {
return this.leftNode_;
},
get hasLeftNode() {
return this.leftNode_ !== undefined;
},
set rightNode(right) {
this.rightNode_ = right;
},
get rightNode() {
return this.rightNode_;
},
get hasRightNode() {
return this.rightNode_ !== undefined;
},
set parentNode(parent) {
this.parentNode_ = parent;
},
get parentNode() {
return this.parentNode_;
},
get isRootNode() {
return this.parentNode_ === undefined;
},
set maxHighLeft(high) {
this.maxHighLeft_ = high;
},
get maxHighLeft() {
return this.maxHighLeft_;
},
set maxHighRight(high) {
this.maxHighRight_ = high;
},
get maxHighRight() {
return this.maxHighRight_;
},
get data() {
return this.data_;
},
get isRed() {
return this.colour_ === Colour.RED;
},
merge: function(node) {
this.data_ = this.data_.concat(node.data);
this.data_.sort(function(a, b) {
return a.high - b.high;
});
},
dump: function() {
if (this.data_.length === 1)
return [this.data_[0].low, this.data[0].high];
return this.data_.map(function(d) { return [d.low, d.high]; });
}
};
return {
IntervalTree: IntervalTree
};
});
// 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.
'use strict';
/**
* @fileoverview Quick range computations.
*/
base.exportTo('base', function() {
function Range() {
this.isEmpty_ = true;
this.min_ = undefined;
this.max_ = undefined;
};
Range.prototype = {
__proto__: Object.prototype,
reset: function() {
this.isEmpty_ = true;
this.min_ = undefined;
this.max_ = undefined;
},
get isEmpty() {
return this.isEmpty_;
},
addRange: function(range) {
if (range.isEmpty)
return;
this.addValue(range.min);
this.addValue(range.max);
},
addValue: function(value) {
if (this.isEmpty_) {
this.max_ = value;
this.min_ = value;
this.isEmpty_ = false;
return;
}
this.max_ = Math.max(this.max_, value);
this.min_ = Math.min(this.min_, value);
},
get min() {
if (this.isEmpty_)
return undefined;
return this.min_;
},
get max() {
if (this.isEmpty_)
return undefined;
return this.max_;
},
get range() {
if (this.isEmpty_)
return undefined;
return this.max_ - this.min_;
},
get center() {
return (this.min_ + this.max_) * 0.5;
}
};
Range.compareByMinTimes = function(a, b) {
if (!a.isEmpty && !b.isEmpty)
return a.min_ - b.min_;
if (a.isEmpty && !b.isEmpty)
return -1;
if (!a.isEmpty && b.isEmpty)
return 1;
return 0;
};
return {
Range: Range
};
});
// 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.
'use strict';
base.exportTo('tracing', function() {
/**
* @constructor The generic base class for filtering a TraceModel based on
* various rules. The base class returns true for everything.
*/
function Filter() {
}
Filter.prototype = {
__proto__: Object.prototype,
matchCounter: function(counter) {
return true;
},
matchCpu: function(cpu) {
return true;
},
matchProcess: function(process) {
return true;
},
matchSlice: function(slice) {
return true;
},
matchThread: function(thread) {
return true;
}
};
/**
* @constructor A filter that matches objects by their name case insensitive.
* .findAllObjectsMatchingFilter
*/
function TitleFilter(text) {
Filter.call(this);
this.text_ = text.toLowerCase();
if (!text.length)
throw new Error('Filter text is empty.');
}
TitleFilter.prototype = {
__proto__: Filter.prototype,
matchSlice: function(slice) {
if (slice.title === undefined)
return false;
return slice.title.toLowerCase().indexOf(this.text_) !== -1;
}
};
/**
* @constructor A filter that matches objects with the exact given title.
*/
function ExactTitleFilter(text) {
Filter.call(this);
this.text_ = text;
if (!text.length)
throw new Error('Filter text is empty.');
}
ExactTitleFilter.prototype = {
__proto__: Filter.prototype,
matchSlice: function(slice) {
return slice.title === this.text_;
}
};
return {
TitleFilter: TitleFilter,
ExactTitleFilter: ExactTitleFilter
};
});
// Copyright (C) 2013:
// Alex Russell <slightlyoff@chromium.org>
// Yehuda Katz
//
// Use of this source code is governed by
// http://www.apache.org/licenses/LICENSE-2.0
// FIXME(slightlyoff):
// - Document "npm test"
// - Change global name from "Promise" to something less conflicty
(function(global, browserGlobal, underTest) {
"use strict";
// FIXME(slighltyoff):
// * aggregates + tests
// * check on fast-forwarding
underTest = !!underTest;
//
// Async Utilities
//
// Borrowed from RSVP.js
var async;
var MutationObserver = browserGlobal.MutationObserver ||
browserGlobal.WebKitMutationObserver;
var Promise;
if (typeof process !== 'undefined' &&
{}.toString.call(process) === '[object process]') {
async = function(callback, binding) {
process.nextTick(function() {
callback.call(binding);
});
};
} else if (MutationObserver) {
var queue = [];
var observer = new MutationObserver(function() {
var toProcess = queue.slice();
queue = [];
toProcess.forEach(function(tuple) {
tuple[0].call(tuple[1]);
});
});
var element = document.createElement('div');
observer.observe(element, { attributes: true });
// Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661
window.addEventListener('unload', function(){
observer.disconnect();
observer = null;
});
async = function(callback, binding) {
queue.push([callback, binding]);
element.setAttribute('drainQueue', 'drainQueue');
};
} else {
async = function(callback, binding) {
setTimeout(function() {
callback.call(binding);
}, 1);
};
}
//
// Object Model Utilities
//
// defineProperties utilities
var _readOnlyProperty = function(v) {
return {
enumerable: true,
configurable: false,
get: v
};
};
var _method = function(v, e, c, w) {
return {
enumerable: !!(e || 0),
configurable: !!(c || 1),
writable: !!(w || 1),
value: v || function() {}
};
};
var _pseudoPrivate = function(v) { return _method(v, 0, 1, 0); };
var _public = function(v) { return _method(v, 1); };
//
// Promises Utilities
//
var isThenable = function(any) {
if (any === undefined)
return false;
try {
var f = any.then;
if (typeof f == "function") {
return true;
}
} catch (e) { /*squelch*/ }
return false;
};
var AlreadyResolved = function(name) {
Error.call(this, name);
};
AlreadyResolved.prototype = Object.create(Error.prototype);
var Backlog = function() {
var bl = [];
bl.pump = function(value) {
async(function() {
var l = bl.length;
var x = 0;
while(x < l) {
x++;
bl.shift()(value);
}
});
};
return bl;
};
//
// Resolver Constuctor
//
var Resolver = function(future,
fulfillCallbacks,
rejectCallbacks,
setValue,
setError,
setState) {
var isResolved = false;
var resolver = this;
var fulfill = function(value) {
// console.log("queueing fulfill with:", value);
async(function() {
setState("fulfilled");
setValue(value);
// console.log("fulfilling with:", value);
fulfillCallbacks.pump(value);
});
};
var reject = function(reason) {
// console.log("queuing reject with:", reason);
async(function() {
setState("rejected");
setError(reason);
// console.log("rejecting with:", reason);
rejectCallbacks.pump(reason);
});
};
var resolve = function(value) {
if (isThenable(value)) {
value.then(resolve, reject);
return;
}
fulfill(value);
};
var ifNotResolved = function(func, name) {
return function(value) {
if (!isResolved) {
isResolved = true;
func(value);
} else {
if (typeof console != "undefined") {
console.error("Cannot resolve a Promise multiple times.");
}
}
};
};
// Indirectly resolves the Promise, chaining any passed Promise's resolution
this.resolve = ifNotResolved(resolve, "resolve");
// Directly fulfills the future, no matter what value's type is
this.fulfill = ifNotResolved(fulfill, "fulfill");
// Rejects the future
this.reject = ifNotResolved(reject, "reject");
this.cancel = function() { resolver.reject(new Error("Cancel")); };
this.timeout = function() { resolver.reject(new Error("Timeout")); };
if (underTest) {
Object.defineProperties(this, {
_isResolved: _readOnlyProperty(function() { return isResolved; }),
});
}
setState("pending");
};
//
// Promise Constuctor
//
var Promise = function(init) {
var fulfillCallbacks = new Backlog();
var rejectCallbacks = new Backlog();
var value;
var error;
var state = "pending";
if (underTest) {
Object.defineProperties(this, {
_value: _readOnlyProperty(function() { return value; }),
_error: _readOnlyProperty(function() { return error; }),
_state: _readOnlyProperty(function() { return state; }),
});
}
Object.defineProperties(this, {
_addAcceptCallback: _pseudoPrivate(
function(cb) {
// console.log("adding fulfill callback:", cb);
fulfillCallbacks.push(cb);
if (state == "fulfilled") {
fulfillCallbacks.pump(value);
}
}
),
_addRejectCallback: _pseudoPrivate(
function(cb) {
// console.log("adding reject callback:", cb);
rejectCallbacks.push(cb);
if (state == "rejected") {
rejectCallbacks.pump(error);
}
}
),
});
var r = new Resolver(this,
fulfillCallbacks, rejectCallbacks,
function(v) { value = v; },
function(e) { error = e; },
function(s) { state = s; })
try {
if (init) { init(r); }
} catch(e) {
r.reject(e);
}
};
//
// Consructor
//
var isCallback = function(any) {
return (typeof any == "function");
};
// Used in .then()
var wrap = function(callback, resolver, disposition) {
if (!isCallback(callback)) {
// If we don't get a callback, we want to forward whatever resolution we get
return resolver[disposition].bind(resolver);
}
return function() {
try {
var r = callback.apply(null, arguments);
resolver.resolve(r);
} catch(e) {
// Exceptions reject the resolver
resolver.reject(e);
}
};
};
var addCallbacks = function(onfulfill, onreject, scope) {
if (isCallback(onfulfill)) {
scope._addAcceptCallback(onfulfill);
}
if (isCallback(onreject)) {
scope._addRejectCallback(onreject);
}
return scope;
};
//
// Prototype properties
//
Promise.prototype = Object.create(null, {
"then": _public(function(onfulfill, onreject) {
// The logic here is:
// We return a new Promise whose resolution merges with the return from
// onfulfill() or onerror(). If onfulfill() returns a Promise, we forward
// the resolution of that future to the resolution of the returned
// Promise.
var f = this;
return new Promise(function(r) {
addCallbacks(wrap(onfulfill, r, "resolve"),
wrap(onreject, r, "reject"), f);
});
}),
"catch": _public(function(onreject) {
var f = this;
return new Promise(function(r) {
addCallbacks(null, wrap(onreject, r, "reject"), f);
});
}),
});
//
// Statics
//
Promise.isThenable = isThenable;
var toPromiseList = function(list) {
return Array.prototype.slice.call(list).map(Promise.resolve);
};
Promise.any = function(/*...futuresOrValues*/) {
var futures = toPromiseList(arguments);
return new Promise(function(r) {
if (!futures.length) {
r.reject("No futures passed to Promise.any()");
} else {
var resolved = false;
var firstSuccess = function(value) {
if (resolved) { return; }
resolved = true;
r.resolve(value);
};
var firstFailure = function(reason) {
if (resolved) { return; }
resolved = true;
r.reject(reason);
};
futures.forEach(function(f, idx) {
f.then(firstSuccess, firstFailure);
});
}
});
};
Promise.every = function(/*...futuresOrValues*/) {
var futures = toPromiseList(arguments);
return new Promise(function(r) {
if (!futures.length) {
r.reject("No futures passed to Promise.every()");
} else {
var values = new Array(futures.length);
var count = 0;
var accumulate = function(idx, v) {
count++;
values[idx] = v;
if (count == futures.length) {
r.resolve(values);
}
};
futures.forEach(function(f, idx) {
f.then(accumulate.bind(null, idx), r.reject);
});
}
});
};
Promise.some = function() {
var futures = toPromiseList(arguments);
return new Promise(function(r) {
if (!futures.length) {
r.reject("No futures passed to Promise.some()");
} else {
var count = 0;
var accumulateFailures = function(e) {
count++;
if (count == futures.length) {
r.reject();
}
};
futures.forEach(function(f, idx) {
f.then(r.resolve, accumulateFailures);
});
}
});
};
Promise.fulfill = function(value) {
return new Promise(function(r) {
r.fulfill(value);
});
};
Promise.resolve = function(value) {
return new Promise(function(r) {
r.resolve(value);
});
};
Promise.reject = function(reason) {
return new Promise(function(r) {
r.reject(reason);
});
};
//
// Export
//
global.Promise = Promise;
})(this,
(typeof window !== 'undefined') ? window : {},
this.runningUnderTest||false);
// 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.
'use strict';
base.requireRawScript('../third_party/Promises/polyfill/src/Promise.js');
base.exportTo('base', function() {
var Promise = window.Promise;
return {
Promise: Promise
};
});
// 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.
'use strict';
base.exportTo('base', function() {
function asArray(arrayish) {
var values = [];
for (var i = 0; i < arrayish.length; i++)
values.push(arrayish[i]);
return values;
}
function compareArrays(x, y, elementCmp) {
var minLength = Math.min(x.length, y.length);
for (var i = 0; i < minLength; i++) {
var tmp = elementCmp(x[i], y[i]);
if (tmp)
return tmp;
}
if (x.length == y.length)
return 0;
if (x[i] === undefined)
return -1;
return 1;
}
/**
* Compares two values when one or both might be undefined. Undefined
* values are sorted after defined.
*/
function comparePossiblyUndefinedValues(x, y, cmp) {
if (x !== undefined && y !== undefined)
return cmp(x, y);
if (x !== undefined)
return -1;
if (y !== undefined)
return 1;
return 0;
}
function concatenateArrays(/*arguments*/) {
var values = [];
for (var i = 0; i < arguments.length; i++) {
if (!(arguments[i] instanceof Array))
throw new Error('Arguments ' + i + 'is not an array');
values.push.apply(values, arguments[i]);
}
return values;
}
function concatenateObjects(/*arguments*/) {
var result = {};
for (var i = 0; i < arguments.length; i++) {
var object = arguments[i];
for (var j in object) {
result[j] = object[j];
}
}
return result;
}
function dictionaryKeys(dict) {
var keys = [];
for (var key in dict)
keys.push(key);
return keys;
}
function dictionaryValues(dict) {
var values = [];
for (var key in dict)
values.push(dict[key]);
return values;
}
function iterItems(dict, fn, opt_this) {
opt_this = opt_this || this;
for (var key in dict)
fn.call(opt_this, key, dict[key]);
}
function iterObjectFieldsRecursively(object, func) {
if (!(object instanceof Object))
return;
if (object instanceof Array) {
for (var i = 0; i < object.length; i++) {
func(object, i, object[i]);
iterObjectFieldsRecursively(object[i], func);
}
return;
}
for (var key in object) {
var value = object[key];
func(object, key, value);
iterObjectFieldsRecursively(value, func);
}
}
return {
asArray: asArray,
concatenateArrays: concatenateArrays,
concatenateObjects: concatenateObjects,
compareArrays: compareArrays,
comparePossiblyUndefinedValues: comparePossiblyUndefinedValues,
dictionaryKeys: dictionaryKeys,
dictionaryValues: dictionaryValues,
iterItems: iterItems,
iterObjectFieldsRecursively: iterObjectFieldsRecursively
};
});
/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
if(!GLMAT_EPSILON) {
var GLMAT_EPSILON = 0.000001;
}
if(!GLMAT_ARRAY_TYPE) {
var GLMAT_ARRAY_TYPE = (typeof Float32Array !== 'undefined') ? Float32Array : Array;
}
/**
* @class Common utilities
* @name glMatrix
*/
var glMatrix = {};
/**
* Sets the type of array used when creating new vectors and matricies
*
* @param {Type} type Array type, such as Float32Array or Array
*/
glMatrix.setMatrixArrayType = function(type) {
GLMAT_ARRAY_TYPE = type;
}
if(typeof(exports) !== 'undefined') {
exports.glMatrix = glMatrix;
}
/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/**
* @class 2x3 Matrix
* @name mat2d
*
* @description
* A mat2d contains six elements defined as:
* <pre>
* [a, b,
* c, d,
* tx,ty]
* </pre>
* This is a short form for the 3x3 matrix:
* <pre>
* [a, b, 0
* c, d, 0
* tx,ty,1]
* </pre>
* The last column is ignored so the array is shorter and operations are faster.
*/
var mat2d = {};
/**
* Creates a new identity mat2d
*
* @returns {mat2d} a new 2x3 matrix
*/
mat2d.create = function() {
var out = new GLMAT_ARRAY_TYPE(6);
out[0] = 1;
out[1] = 0;
out[2] = 0;
out[3] = 1;
out[4] = 0;
out[5] = 0;
return out;
};
/**
* Creates a new mat2d initialized with values from an existing matrix
*
* @param {mat2d} a matrix to clone
* @returns {mat2d} a new 2x3 matrix
*/
mat2d.clone = function(a) {
var out = new GLMAT_ARRAY_TYPE(6);
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
out[3] = a[3];
out[4] = a[4];
out[5] = a[5];
return out;
};
/**
* Copy the values from one mat2d to another
*
* @param {mat2d} out the receiving matrix
* @param {mat2d} a the source matrix
* @returns {mat2d} out
*/
mat2d.copy = function(out, a) {
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
out[3] = a[3];
out[4] = a[4];
out[5] = a[5];
return out;
};
/**
* Set a mat2d to the identity matrix
*
* @param {mat2d} out the receiving matrix
* @returns {mat2d} out
*/
mat2d.identity = function(out) {
out[0] = 1;
out[1] = 0;
out[2] = 0;
out[3] = 1;
out[4] = 0;
out[5] = 0;
return out;
};
/**
* Inverts a mat2d
*
* @param {mat2d} out the receiving matrix
* @param {mat2d} a the source matrix
* @returns {mat2d} out
*/
mat2d.invert = function(out, a) {
var aa = a[0], ab = a[1], ac = a[2], ad = a[3],
atx = a[4], aty = a[5];
var det = aa * ad - ab * ac;
if(!det){
return null;
}
det = 1.0 / det;
out[0] = ad * det;
out[1] = -ab * det;
out[2] = -ac * det;
out[3] = aa * det;
out[4] = (ac * aty - ad * atx) * det;
out[5] = (ab * atx - aa * aty) * det;
return out;
};
/**
* Calculates the determinant of a mat2d
*
* @param {mat2d} a the source matrix
* @returns {Number} determinant of a
*/
mat2d.determinant = function (a) {
return a[0] * a[3] - a[1] * a[2];
};
/**
* Multiplies two mat2d's
*
* @param {mat2d} out the receiving matrix
* @param {mat2d} a the first operand
* @param {mat2d} b the second operand
* @returns {mat2d} out
*/
mat2d.multiply = function (out, a, b) {
var aa = a[0], ab = a[1], ac = a[2], ad = a[3],
atx = a[4], aty = a[5],
ba = b[0], bb = b[1], bc = b[2], bd = b[3],
btx = b[4], bty = b[5];
out[0] = aa*ba + ab*bc;
out[1] = aa*bb + ab*bd;
out[2] = ac*ba + ad*bc;
out[3] = ac*bb + ad*bd;
out[4] = ba*atx + bc*aty + btx;
out[5] = bb*atx + bd*aty + bty;
return out;
};
/**
* Alias for {@link mat2d.multiply}
* @function
*/
mat2d.mul = mat2d.multiply;
/**
* Rotates a mat2d by the given angle
*
* @param {mat2d} out the receiving matrix
* @param {mat2d} a the matrix to rotate
* @param {Number} rad the angle to rotate the matrix by
* @returns {mat2d} out
*/
mat2d.rotate = function (out, a, rad) {
var aa = a[0],
ab = a[1],
ac = a[2],
ad = a[3],
atx = a[4],
aty = a[5],
st = Math.sin(rad),
ct = Math.cos(rad);
out[0] = aa*ct + ab*st;
out[1] = -aa*st + ab*ct;
out[2] = ac*ct + ad*st;
out[3] = -ac*st + ct*ad;
out[4] = ct*atx + st*aty;
out[5] = ct*aty - st*atx;
return out;
};
/**
* Scales the mat2d by the dimensions in the given vec2
*
* @param {mat2d} out the receiving matrix
* @param {mat2d} a the matrix to translate
* @param {mat2d} v the vec2 to scale the matrix by
* @returns {mat2d} out
**/
mat2d.scale = function(out, a, v) {
var vx = v[0], vy = v[1];
out[0] = a[0] * vx;
out[1] = a[1] * vy;
out[2] = a[2] * vx;
out[3] = a[3] * vy;
out[4] = a[4] * vx;
out[5] = a[5] * vy;
return out;
};
/**
* Translates the mat2d by the dimensions in the given vec2
*
* @param {mat2d} out the receiving matrix
* @param {mat2d} a the matrix to translate
* @param {mat2d} v the vec2 to translate the matrix by
* @returns {mat2d} out
**/
mat2d.translate = function(out, a, v) {
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
out[3] = a[3];
out[4] = a[4] + v[0];
out[5] = a[5] + v[1];
return out;
};
/**
* Returns a string representation of a mat2d
*
* @param {mat2d} a matrix to represent as a string
* @returns {String} string representation of the matrix
*/
mat2d.str = function (a) {
return 'mat2d(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' +
a[3] + ', ' + a[4] + ', ' + a[5] + ')';
};
if(typeof(exports) !== 'undefined') {
exports.mat2d = mat2d;
}
/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/**
* @class 4x4 Matrix
* @name mat4
*/
var mat4 = {};
/**
* Creates a new identity mat4
*
* @returns {mat4} a new 4x4 matrix
*/
mat4.create = function() {
var out = new GLMAT_ARRAY_TYPE(16);
out[0] = 1;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = 1;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = 1;
out[11] = 0;
out[12] = 0;
out[13] = 0;
out[14] = 0;
out[15] = 1;
return out;
};
/**
* Creates a new mat4 initialized with values from an existing matrix
*
* @param {mat4} a matrix to clone
* @returns {mat4} a new 4x4 matrix
*/
mat4.clone = function(a) {
var out = new GLMAT_ARRAY_TYPE(16);
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
out[3] = a[3];
out[4] = a[4];
out[5] = a[5];
out[6] = a[6];
out[7] = a[7];
out[8] = a[8];
out[9] = a[9];
out[10] = a[10];
out[11] = a[11];
out[12] = a[12];
out[13] = a[13];
out[14] = a[14];
out[15] = a[15];
return out;
};
/**
* Copy the values from one mat4 to another
*
* @param {mat4} out the receiving matrix
* @param {mat4} a the source matrix
* @returns {mat4} out
*/
mat4.copy = function(out, a) {
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
out[3] = a[3];
out[4] = a[4];
out[5] = a[5];
out[6] = a[6];
out[7] = a[7];
out[8] = a[8];
out[9] = a[9];
out[10] = a[10];
out[11] = a[11];
out[12] = a[12];
out[13] = a[13];
out[14] = a[14];
out[15] = a[15];
return out;
};
/**
* Set a mat4 to the identity matrix
*
* @param {mat4} out the receiving matrix
* @returns {mat4} out
*/
mat4.identity = function(out) {
out[0] = 1;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = 1;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = 1;
out[11] = 0;
out[12] = 0;
out[13] = 0;
out[14] = 0;
out[15] = 1;
return out;
};
/**
* Transpose the values of a mat4
*
* @param {mat4} out the receiving matrix
* @param {mat4} a the source matrix
* @returns {mat4} out
*/
mat4.transpose = function(out, a) {
// If we are transposing ourselves we can skip a few steps but have to cache some values
if (out === a) {
var a01 = a[1], a02 = a[2], a03 = a[3],
a12 = a[6], a13 = a[7],
a23 = a[11];
out[1] = a[4];
out[2] = a[8];
out[3] = a[12];
out[4] = a01;
out[6] = a[9];
out[7] = a[13];
out[8] = a02;
out[9] = a12;
out[11] = a[14];
out[12] = a03;
out[13] = a13;
out[14] = a23;
} else {
out[0] = a[0];
out[1] = a[4];
out[2] = a[8];
out[3] = a[12];
out[4] = a[1];
out[5] = a[5];
out[6] = a[9];
out[7] = a[13];
out[8] = a[2];
out[9] = a[6];
out[10] = a[10];
out[11] = a[14];
out[12] = a[3];
out[13] = a[7];
out[14] = a[11];
out[15] = a[15];
}
return out;
};
/**
* Inverts a mat4
*
* @param {mat4} out the receiving matrix
* @param {mat4} a the source matrix
* @returns {mat4} out
*/
mat4.invert = function(out, a) {
var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],
b00 = a00 * a11 - a01 * a10,
b01 = a00 * a12 - a02 * a10,
b02 = a00 * a13 - a03 * a10,
b03 = a01 * a12 - a02 * a11,
b04 = a01 * a13 - a03 * a11,
b05 = a02 * a13 - a03 * a12,
b06 = a20 * a31 - a21 * a30,
b07 = a20 * a32 - a22 * a30,
b08 = a20 * a33 - a23 * a30,
b09 = a21 * a32 - a22 * a31,
b10 = a21 * a33 - a23 * a31,
b11 = a22 * a33 - a23 * a32,
// Calculate the determinant
det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
if (!det) {
return null;
}
det = 1.0 / det;
out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
return out;
};
/**
* Calculates the adjugate of a mat4
*
* @param {mat4} out the receiving matrix
* @param {mat4} a the source matrix
* @returns {mat4} out
*/
mat4.adjoint = function(out, a) {
var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
out[0] = (a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22));
out[1] = -(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22));
out[2] = (a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12));
out[3] = -(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12));
out[4] = -(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22));
out[5] = (a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22));
out[6] = -(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12));
out[7] = (a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12));
out[8] = (a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21));
out[9] = -(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21));
out[10] = (a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11));
out[11] = -(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11));
out[12] = -(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21));
out[13] = (a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21));
out[14] = -(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11));
out[15] = (a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11));
return out;
};
/**
* Calculates the determinant of a mat4
*
* @param {mat4} a the source matrix
* @returns {Number} determinant of a
*/
mat4.determinant = function (a) {
var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15],
b00 = a00 * a11 - a01 * a10,
b01 = a00 * a12 - a02 * a10,
b02 = a00 * a13 - a03 * a10,
b03 = a01 * a12 - a02 * a11,
b04 = a01 * a13 - a03 * a11,
b05 = a02 * a13 - a03 * a12,
b06 = a20 * a31 - a21 * a30,
b07 = a20 * a32 - a22 * a30,
b08 = a20 * a33 - a23 * a30,
b09 = a21 * a32 - a22 * a31,
b10 = a21 * a33 - a23 * a31,
b11 = a22 * a33 - a23 * a32;
// Calculate the determinant
return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
};
/**
* Multiplies two mat4's
*
* @param {mat4} out the receiving matrix
* @param {mat4} a the first operand
* @param {mat4} b the second operand
* @returns {mat4} out
*/
mat4.multiply = function (out, a, b) {
var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
// Cache only the current line of the second matrix
var b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
out[0] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
out[1] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
out[2] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
out[3] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
out[4] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
out[5] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
out[6] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
out[7] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
out[8] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
out[9] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
out[10] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
out[11] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
out[12] = b0*a00 + b1*a10 + b2*a20 + b3*a30;
out[13] = b0*a01 + b1*a11 + b2*a21 + b3*a31;
out[14] = b0*a02 + b1*a12 + b2*a22 + b3*a32;
out[15] = b0*a03 + b1*a13 + b2*a23 + b3*a33;
return out;
};
/**
* Alias for {@link mat4.multiply}
* @function
*/
mat4.mul = mat4.multiply;
/**
* Translate a mat4 by the given vector
*
* @param {mat4} out the receiving matrix
* @param {mat4} a the matrix to translate
* @param {vec3} v vector to translate by
* @returns {mat4} out
*/
mat4.translate = function (out, a, v) {
var x = v[0], y = v[1], z = v[2],
a00, a01, a02, a03,
a10, a11, a12, a13,
a20, a21, a22, a23;
if (a === out) {
out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
} else {
a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];
out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03;
out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13;
out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23;
out[12] = a00 * x + a10 * y + a20 * z + a[12];
out[13] = a01 * x + a11 * y + a21 * z + a[13];
out[14] = a02 * x + a12 * y + a22 * z + a[14];
out[15] = a03 * x + a13 * y + a23 * z + a[15];
}
return out;
};
/**
* Scales the mat4 by the dimensions in the given vec3
*
* @param {mat4} out the receiving matrix
* @param {mat4} a the matrix to scale
* @param {vec3} v the vec3 to scale the matrix by
* @returns {mat4} out
**/
mat4.scale = function(out, a, v) {
var x = v[0], y = v[1], z = v[2];
out[0] = a[0] * x;
out[1] = a[1] * x;
out[2] = a[2] * x;
out[3] = a[3] * x;
out[4] = a[4] * y;
out[5] = a[5] * y;
out[6] = a[6] * y;
out[7] = a[7] * y;
out[8] = a[8] * z;
out[9] = a[9] * z;
out[10] = a[10] * z;
out[11] = a[11] * z;
out[12] = a[12];
out[13] = a[13];
out[14] = a[14];
out[15] = a[15];
return out;
};
/**
* Rotates a mat4 by the given angle
*
* @param {mat4} out the receiving matrix
* @param {mat4} a the matrix to rotate
* @param {Number} rad the angle to rotate the matrix by
* @param {vec3} axis the axis to rotate around
* @returns {mat4} out
*/
mat4.rotate = function (out, a, rad, axis) {
var x = axis[0], y = axis[1], z = axis[2],
len = Math.sqrt(x * x + y * y + z * z),
s, c, t,
a00, a01, a02, a03,
a10, a11, a12, a13,
a20, a21, a22, a23,
b00, b01, b02,
b10, b11, b12,
b20, b21, b22;
if (Math.abs(len) < GLMAT_EPSILON) { return null; }
len = 1 / len;
x *= len;
y *= len;
z *= len;
s = Math.sin(rad);
c = Math.cos(rad);
t = 1 - c;
a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];
// Construct the elements of the rotation matrix
b00 = x * x * t + c; b01 = y * x * t + z * s; b02 = z * x * t - y * s;
b10 = x * y * t - z * s; b11 = y * y * t + c; b12 = z * y * t + x * s;
b20 = x * z * t + y * s; b21 = y * z * t - x * s; b22 = z * z * t + c;
// Perform rotation-specific matrix multiplication
out[0] = a00 * b00 + a10 * b01 + a20 * b02;
out[1] = a01 * b00 + a11 * b01 + a21 * b02;
out[2] = a02 * b00 + a12 * b01 + a22 * b02;
out[3] = a03 * b00 + a13 * b01 + a23 * b02;
out[4] = a00 * b10 + a10 * b11 + a20 * b12;
out[5] = a01 * b10 + a11 * b11 + a21 * b12;
out[6] = a02 * b10 + a12 * b11 + a22 * b12;
out[7] = a03 * b10 + a13 * b11 + a23 * b12;
out[8] = a00 * b20 + a10 * b21 + a20 * b22;
out[9] = a01 * b20 + a11 * b21 + a21 * b22;
out[10] = a02 * b20 + a12 * b21 + a22 * b22;
out[11] = a03 * b20 + a13 * b21 + a23 * b22;
if (a !== out) { // If the source and destination differ, copy the unchanged last row
out[12] = a[12];
out[13] = a[13];
out[14] = a[14];
out[15] = a[15];
}
return out;
};
/**
* Rotates a matrix by the given angle around the X axis
*
* @param {mat4} out the receiving matrix
* @param {mat4} a the matrix to rotate
* @param {Number} rad the angle to rotate the matrix by
* @returns {mat4} out
*/
mat4.rotateX = function (out, a, rad) {
var s = Math.sin(rad),
c = Math.cos(rad),
a10 = a[4],
a11 = a[5],
a12 = a[6],
a13 = a[7],
a20 = a[8],
a21 = a[9],
a22 = a[10],
a23 = a[11];
if (a !== out) { // If the source and destination differ, copy the unchanged rows
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
out[3] = a[3];
out[12] = a[12];
out[13] = a[13];
out[14] = a[14];
out[15] = a[15];
}
// Perform axis-specific matrix multiplication
out[4] = a10 * c + a20 * s;
out[5] = a11 * c + a21 * s;
out[6] = a12 * c + a22 * s;
out[7] = a13 * c + a23 * s;
out[8] = a20 * c - a10 * s;
out[9] = a21 * c - a11 * s;
out[10] = a22 * c - a12 * s;
out[11] = a23 * c - a13 * s;
return out;
};
/**
* Rotates a matrix by the given angle around the Y axis
*
* @param {mat4} out the receiving matrix
* @param {mat4} a the matrix to rotate
* @param {Number} rad the angle to rotate the matrix by
* @returns {mat4} out
*/
mat4.rotateY = function (out, a, rad) {
var s = Math.sin(rad),
c = Math.cos(rad),
a00 = a[0],
a01 = a[1],
a02 = a[2],
a03 = a[3],
a20 = a[8],
a21 = a[9],
a22 = a[10],
a23 = a[11];
if (a !== out) { // If the source and destination differ, copy the unchanged rows
out[4] = a[4];
out[5] = a[5];
out[6] = a[6];
out[7] = a[7];
out[12] = a[12];
out[13] = a[13];
out[14] = a[14];
out[15] = a[15];
}
// Perform axis-specific matrix multiplication
out[0] = a00 * c - a20 * s;
out[1] = a01 * c - a21 * s;
out[2] = a02 * c - a22 * s;
out[3] = a03 * c - a23 * s;
out[8] = a00 * s + a20 * c;
out[9] = a01 * s + a21 * c;
out[10] = a02 * s + a22 * c;
out[11] = a03 * s + a23 * c;
return out;
};
/**
* Rotates a matrix by the given angle around the Z axis
*
* @param {mat4} out the receiving matrix
* @param {mat4} a the matrix to rotate
* @param {Number} rad the angle to rotate the matrix by
* @returns {mat4} out
*/
mat4.rotateZ = function (out, a, rad) {
var s = Math.sin(rad),
c = Math.cos(rad),
a00 = a[0],
a01 = a[1],
a02 = a[2],
a03 = a[3],
a10 = a[4],
a11 = a[5],
a12 = a[6],
a13 = a[7];
if (a !== out) { // If the source and destination differ, copy the unchanged last row
out[8] = a[8];
out[9] = a[9];
out[10] = a[10];
out[11] = a[11];
out[12] = a[12];
out[13] = a[13];
out[14] = a[14];
out[15] = a[15];
}
// Perform axis-specific matrix multiplication
out[0] = a00 * c + a10 * s;
out[1] = a01 * c + a11 * s;
out[2] = a02 * c + a12 * s;
out[3] = a03 * c + a13 * s;
out[4] = a10 * c - a00 * s;
out[5] = a11 * c - a01 * s;
out[6] = a12 * c - a02 * s;
out[7] = a13 * c - a03 * s;
return out;
};
/**
* Creates a matrix from a quaternion rotation and vector translation
* This is equivalent to (but much faster than):
*
* mat4.identity(dest);
* mat4.translate(dest, vec);
* var quatMat = mat4.create();
* quat4.toMat4(quat, quatMat);
* mat4.multiply(dest, quatMat);
*
* @param {mat4} out mat4 receiving operation result
* @param {quat4} q Rotation quaternion
* @param {vec3} v Translation vector
* @returns {mat4} out
*/
mat4.fromRotationTranslation = function (out, q, v) {
// Quaternion math
var x = q[0], y = q[1], z = q[2], w = q[3],
x2 = x + x,
y2 = y + y,
z2 = z + z,
xx = x * x2,
xy = x * y2,
xz = x * z2,
yy = y * y2,
yz = y * z2,
zz = z * z2,
wx = w * x2,
wy = w * y2,
wz = w * z2;
out[0] = 1 - (yy + zz);
out[1] = xy + wz;
out[2] = xz - wy;
out[3] = 0;
out[4] = xy - wz;
out[5] = 1 - (xx + zz);
out[6] = yz + wx;
out[7] = 0;
out[8] = xz + wy;
out[9] = yz - wx;
out[10] = 1 - (xx + yy);
out[11] = 0;
out[12] = v[0];
out[13] = v[1];
out[14] = v[2];
out[15] = 1;
return out;
};
/**
* Calculates a 4x4 matrix from the given quaternion
*
* @param {mat4} out mat4 receiving operation result
* @param {quat} q Quaternion to create matrix from
*
* @returns {mat4} out
*/
mat4.fromQuat = function (out, q) {
var x = q[0], y = q[1], z = q[2], w = q[3],
x2 = x + x,
y2 = y + y,
z2 = z + z,
xx = x * x2,
xy = x * y2,
xz = x * z2,
yy = y * y2,
yz = y * z2,
zz = z * z2,
wx = w * x2,
wy = w * y2,
wz = w * z2;
out[0] = 1 - (yy + zz);
out[1] = xy + wz;
out[2] = xz - wy;
out[3] = 0;
out[4] = xy - wz;
out[5] = 1 - (xx + zz);
out[6] = yz + wx;
out[7] = 0;
out[8] = xz + wy;
out[9] = yz - wx;
out[10] = 1 - (xx + yy);
out[11] = 0;
out[12] = 0;
out[13] = 0;
out[14] = 0;
out[15] = 1;
return out;
};
/**
* Generates a frustum matrix with the given bounds
*
* @param {mat4} out mat4 frustum matrix will be written into
* @param {Number} left Left bound of the frustum
* @param {Number} right Right bound of the frustum
* @param {Number} bottom Bottom bound of the frustum
* @param {Number} top Top bound of the frustum
* @param {Number} near Near bound of the frustum
* @param {Number} far Far bound of the frustum
* @returns {mat4} out
*/
mat4.frustum = function (out, left, right, bottom, top, near, far) {
var rl = 1 / (right - left),
tb = 1 / (top - bottom),
nf = 1 / (near - far);
out[0] = (near * 2) * rl;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = (near * 2) * tb;
out[6] = 0;
out[7] = 0;
out[8] = (right + left) * rl;
out[9] = (top + bottom) * tb;
out[10] = (far + near) * nf;
out[11] = -1;
out[12] = 0;
out[13] = 0;
out[14] = (far * near * 2) * nf;
out[15] = 0;
return out;
};
/**
* Generates a perspective projection matrix with the given bounds
*
* @param {mat4} out mat4 frustum matrix will be written into
* @param {number} fovy Vertical field of view in radians
* @param {number} aspect Aspect ratio. typically viewport width/height
* @param {number} near Near bound of the frustum
* @param {number} far Far bound of the frustum
* @returns {mat4} out
*/
mat4.perspective = function (out, fovy, aspect, near, far) {
var f = 1.0 / Math.tan(fovy / 2),
nf = 1 / (near - far);
out[0] = f / aspect;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = f;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = (far + near) * nf;
out[11] = -1;
out[12] = 0;
out[13] = 0;
out[14] = (2 * far * near) * nf;
out[15] = 0;
return out;
};
/**
* Generates a orthogonal projection matrix with the given bounds
*
* @param {mat4} out mat4 frustum matrix will be written into
* @param {number} left Left bound of the frustum
* @param {number} right Right bound of the frustum
* @param {number} bottom Bottom bound of the frustum
* @param {number} top Top bound of the frustum
* @param {number} near Near bound of the frustum
* @param {number} far Far bound of the frustum
* @returns {mat4} out
*/
mat4.ortho = function (out, left, right, bottom, top, near, far) {
var lr = 1 / (left - right),
bt = 1 / (bottom - top),
nf = 1 / (near - far);
out[0] = -2 * lr;
out[1] = 0;
out[2] = 0;
out[3] = 0;
out[4] = 0;
out[5] = -2 * bt;
out[6] = 0;
out[7] = 0;
out[8] = 0;
out[9] = 0;
out[10] = 2 * nf;
out[11] = 0;
out[12] = (left + right) * lr;
out[13] = (top + bottom) * bt;
out[14] = (far + near) * nf;
out[15] = 1;
return out;
};
/**
* Generates a look-at matrix with the given eye position, focal point, and up axis
*
* @param {mat4} out mat4 frustum matrix will be written into
* @param {vec3} eye Position of the viewer
* @param {vec3} center Point the viewer is looking at
* @param {vec3} up vec3 pointing up
* @returns {mat4} out
*/
mat4.lookAt = function (out, eye, center, up) {
var x0, x1, x2, y0, y1, y2, z0, z1, z2, len,
eyex = eye[0],
eyey = eye[1],
eyez = eye[2],
upx = up[0],
upy = up[1],
upz = up[2],
centerx = center[0],
centery = center[1],
centerz = center[2];
if (Math.abs(eyex - centerx) < GLMAT_EPSILON &&
Math.abs(eyey - centery) < GLMAT_EPSILON &&
Math.abs(eyez - centerz) < GLMAT_EPSILON) {
return mat4.identity(out);
}
z0 = eyex - centerx;
z1 = eyey - centery;
z2 = eyez - centerz;
len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
z0 *= len;
z1 *= len;
z2 *= len;
x0 = upy * z2 - upz * z1;
x1 = upz * z0 - upx * z2;
x2 = upx * z1 - upy * z0;
len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
if (!len) {
x0 = 0;
x1 = 0;
x2 = 0;
} else {
len = 1 / len;
x0 *= len;
x1 *= len;
x2 *= len;
}
y0 = z1 * x2 - z2 * x1;
y1 = z2 * x0 - z0 * x2;
y2 = z0 * x1 - z1 * x0;
len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
if (!len) {
y0 = 0;
y1 = 0;
y2 = 0;
} else {
len = 1 / len;
y0 *= len;
y1 *= len;
y2 *= len;
}
out[0] = x0;
out[1] = y0;
out[2] = z0;
out[3] = 0;
out[4] = x1;
out[5] = y1;
out[6] = z1;
out[7] = 0;
out[8] = x2;
out[9] = y2;
out[10] = z2;
out[11] = 0;
out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
out[15] = 1;
return out;
};
/**
* Returns a string representation of a mat4
*
* @param {mat4} mat matrix to represent as a string
* @returns {String} string representation of the matrix
*/
mat4.str = function (a) {
return 'mat4(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ', ' +
a[4] + ', ' + a[5] + ', ' + a[6] + ', ' + a[7] + ', ' +
a[8] + ', ' + a[9] + ', ' + a[10] + ', ' + a[11] + ', ' +
a[12] + ', ' + a[13] + ', ' + a[14] + ', ' + a[15] + ')';
};
if(typeof(exports) !== 'undefined') {
exports.mat4 = mat4;
}
/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/**
* @class 2 Dimensional Vector
* @name vec2
*/
var vec2 = {};
/**
* Creates a new, empty vec2
*
* @returns {vec2} a new 2D vector
*/
vec2.create = function() {
var out = new GLMAT_ARRAY_TYPE(2);
out[0] = 0;
out[1] = 0;
return out;
};
/**
* Creates a new vec2 initialized with values from an existing vector
*
* @param {vec2} a vector to clone
* @returns {vec2} a new 2D vector
*/
vec2.clone = function(a) {
var out = new GLMAT_ARRAY_TYPE(2);
out[0] = a[0];
out[1] = a[1];
return out;
};
/**
* Creates a new vec2 initialized with the given values
*
* @param {Number} x X component
* @param {Number} y Y component
* @returns {vec2} a new 2D vector
*/
vec2.fromValues = function(x, y) {
var out = new GLMAT_ARRAY_TYPE(2);
out[0] = x;
out[1] = y;
return out;
};
/**
* Copy the values from one vec2 to another
*
* @param {vec2} out the receiving vector
* @param {vec2} a the source vector
* @returns {vec2} out
*/
vec2.copy = function(out, a) {
out[0] = a[0];
out[1] = a[1];
return out;
};
/**
* Set the components of a vec2 to the given values
*
* @param {vec2} out the receiving vector
* @param {Number} x X component
* @param {Number} y Y component
* @returns {vec2} out
*/
vec2.set = function(out, x, y) {
out[0] = x;
out[1] = y;
return out;
};
/**
* Adds two vec2's
*
* @param {vec2} out the receiving vector
* @param {vec2} a the first operand
* @param {vec2} b the second operand
* @returns {vec2} out
*/
vec2.add = function(out, a, b) {
out[0] = a[0] + b[0];
out[1] = a[1] + b[1];
return out;
};
/**
* Subtracts two vec2's
*
* @param {vec2} out the receiving vector
* @param {vec2} a the first operand
* @param {vec2} b the second operand
* @returns {vec2} out
*/
vec2.subtract = function(out, a, b) {
out[0] = a[0] - b[0];
out[1] = a[1] - b[1];
return out;
};
/**
* Alias for {@link vec2.subtract}
* @function
*/
vec2.sub = vec2.subtract;
/**
* Multiplies two vec2's
*
* @param {vec2} out the receiving vector
* @param {vec2} a the first operand
* @param {vec2} b the second operand
* @returns {vec2} out
*/
vec2.multiply = function(out, a, b) {
out[0] = a[0] * b[0];
out[1] = a[1] * b[1];
return out;
};
/**
* Alias for {@link vec2.multiply}
* @function
*/
vec2.mul = vec2.multiply;
/**
* Divides two vec2's
*
* @param {vec2} out the receiving vector
* @param {vec2} a the first operand
* @param {vec2} b the second operand
* @returns {vec2} out
*/
vec2.divide = function(out, a, b) {
out[0] = a[0] / b[0];
out[1] = a[1] / b[1];
return out;
};
/**
* Alias for {@link vec2.divide}
* @function
*/
vec2.div = vec2.divide;
/**
* Returns the minimum of two vec2's
*
* @param {vec2} out the receiving vector
* @param {vec2} a the first operand
* @param {vec2} b the second operand
* @returns {vec2} out
*/
vec2.min = function(out, a, b) {
out[0] = Math.min(a[0], b[0]);
out[1] = Math.min(a[1], b[1]);
return out;
};
/**
* Returns the maximum of two vec2's
*
* @param {vec2} out the receiving vector
* @param {vec2} a the first operand
* @param {vec2} b the second operand
* @returns {vec2} out
*/
vec2.max = function(out, a, b) {
out[0] = Math.max(a[0], b[0]);
out[1] = Math.max(a[1], b[1]);
return out;
};
/**
* Scales a vec2 by a scalar number
*
* @param {vec2} out the receiving vector
* @param {vec2} a the vector to scale
* @param {Number} b amount to scale the vector by
* @returns {vec2} out
*/
vec2.scale = function(out, a, b) {
out[0] = a[0] * b;
out[1] = a[1] * b;
return out;
};
/**
* Calculates the euclidian distance between two vec2's
*
* @param {vec2} a the first operand
* @param {vec2} b the second operand
* @returns {Number} distance between a and b
*/
vec2.distance = function(a, b) {
var x = b[0] - a[0],
y = b[1] - a[1];
return Math.sqrt(x*x + y*y);
};
/**
* Alias for {@link vec2.distance}
* @function
*/
vec2.dist = vec2.distance;
/**
* Calculates the squared euclidian distance between two vec2's
*
* @param {vec2} a the first operand
* @param {vec2} b the second operand
* @returns {Number} squared distance between a and b
*/
vec2.squaredDistance = function(a, b) {
var x = b[0] - a[0],
y = b[1] - a[1];
return x*x + y*y;
};
/**
* Alias for {@link vec2.squaredDistance}
* @function
*/
vec2.sqrDist = vec2.squaredDistance;
/**
* Calculates the length of a vec2
*
* @param {vec2} a vector to calculate length of
* @returns {Number} length of a
*/
vec2.length = function (a) {
var x = a[0],
y = a[1];
return Math.sqrt(x*x + y*y);
};
/**
* Alias for {@link vec2.length}
* @function
*/
vec2.len = vec2.length;
/**
* Calculates the squared length of a vec2
*
* @param {vec2} a vector to calculate squared length of
* @returns {Number} squared length of a
*/
vec2.squaredLength = function (a) {
var x = a[0],
y = a[1];
return x*x + y*y;
};
/**
* Alias for {@link vec2.squaredLength}
* @function
*/
vec2.sqrLen = vec2.squaredLength;
/**
* Negates the components of a vec2
*
* @param {vec2} out the receiving vector
* @param {vec2} a vector to negate
* @returns {vec2} out
*/
vec2.negate = function(out, a) {
out[0] = -a[0];
out[1] = -a[1];
return out;
};
/**
* Normalize a vec2
*
* @param {vec2} out the receiving vector
* @param {vec2} a vector to normalize
* @returns {vec2} out
*/
vec2.normalize = function(out, a) {
var x = a[0],
y = a[1];
var len = x*x + y*y;
if (len > 0) {
//TODO: evaluate use of glm_invsqrt here?
len = 1 / Math.sqrt(len);
out[0] = a[0] * len;
out[1] = a[1] * len;
}
return out;
};
/**
* Calculates the dot product of two vec2's
*
* @param {vec2} a the first operand
* @param {vec2} b the second operand
* @returns {Number} dot product of a and b
*/
vec2.dot = function (a, b) {
return a[0] * b[0] + a[1] * b[1];
};
/**
* Computes the cross product of two vec2's
* Note that the cross product must by definition produce a 3D vector
*
* @param {vec3} out the receiving vector
* @param {vec2} a the first operand
* @param {vec2} b the second operand
* @returns {vec3} out
*/
vec2.cross = function(out, a, b) {
var z = a[0] * b[1] - a[1] * b[0];
out[0] = out[1] = 0;
out[2] = z;
return out;
};
/**
* Performs a linear interpolation between two vec2's
*
* @param {vec2} out the receiving vector
* @param {vec2} a the first operand
* @param {vec2} b the second operand
* @param {Number} t interpolation amount between the two inputs
* @returns {vec2} out
*/
vec2.lerp = function (out, a, b, t) {
var ax = a[0],
ay = a[1];
out[0] = ax + t * (b[0] - ax);
out[1] = ay + t * (b[1] - ay);
return out;
};
/**
* Transforms the vec2 with a mat2
*
* @param {vec2} out the receiving vector
* @param {vec2} a the vector to transform
* @param {mat2} m matrix to transform with
* @returns {vec2} out
*/
vec2.transformMat2 = function(out, a, m) {
var x = a[0],
y = a[1];
out[0] = m[0] * x + m[2] * y;
out[1] = m[1] * x + m[3] * y;
return out;
};
/**
* Transforms the vec2 with a mat2d
*
* @param {vec2} out the receiving vector
* @param {vec2} a the vector to transform
* @param {mat2d} m matrix to transform with
* @returns {vec2} out
*/
vec2.transformMat2d = function(out, a, m) {
var x = a[0],
y = a[1];
out[0] = m[0] * x + m[2] * y + m[4];
out[1] = m[1] * x + m[3] * y + m[5];
return out;
};
/**
* Transforms the vec2 with a mat3
* 3rd vector component is implicitly '1'
*
* @param {vec2} out the receiving vector
* @param {vec2} a the vector to transform
* @param {mat3} m matrix to transform with
* @returns {vec2} out
*/
vec2.transformMat3 = function(out, a, m) {
var x = a[0],
y = a[1];
out[0] = m[0] * x + m[3] * y + m[6];
out[1] = m[1] * x + m[4] * y + m[7];
return out;
};
/**
* Transforms the vec2 with a mat4
* 3rd vector component is implicitly '0'
* 4th vector component is implicitly '1'
*
* @param {vec2} out the receiving vector
* @param {vec2} a the vector to transform
* @param {mat4} m matrix to transform with
* @returns {vec2} out
*/
vec2.transformMat4 = function(out, a, m) {
var x = a[0],
y = a[1];
out[0] = m[0] * x + m[4] * y + m[12];
out[1] = m[1] * x + m[5] * y + m[13];
return out;
};
/**
* Perform some operation over an array of vec2s.
*
* @param {Array} a the array of vectors to iterate over
* @param {Number} stride Number of elements between the start of each vec2. If 0 assumes tightly packed
* @param {Number} offset Number of elements to skip at the beginning of the array
* @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array
* @param {Function} fn Function to call for each vector in the array
* @param {Object} [arg] additional argument to pass to fn
* @returns {Array} a
* @function
*/
vec2.forEach = (function() {
var vec = vec2.create();
return function(a, stride, offset, count, fn, arg) {
var i, l;
if(!stride) {
stride = 2;
}
if(!offset) {
offset = 0;
}
if(count) {
l = Math.min((count * stride) + offset, a.length);
} else {
l = a.length;
}
for(i = offset; i < l; i += stride) {
vec[0] = a[i]; vec[1] = a[i+1];
fn(vec, vec, arg);
a[i] = vec[0]; a[i+1] = vec[1];
}
return a;
};
})();
/**
* Returns a string representation of a vector
*
* @param {vec2} vec vector to represent as a string
* @returns {String} string representation of the vector
*/
vec2.str = function (a) {
return 'vec2(' + a[0] + ', ' + a[1] + ')';
};
if(typeof(exports) !== 'undefined') {
exports.vec2 = vec2;
}
/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/**
* @class 3 Dimensional Vector
* @name vec3
*/
var vec3 = {};
/**
* Creates a new, empty vec3
*
* @returns {vec3} a new 3D vector
*/
vec3.create = function() {
var out = new GLMAT_ARRAY_TYPE(3);
out[0] = 0;
out[1] = 0;
out[2] = 0;
return out;
};
/**
* Creates a new vec3 initialized with values from an existing vector
*
* @param {vec3} a vector to clone
* @returns {vec3} a new 3D vector
*/
vec3.clone = function(a) {
var out = new GLMAT_ARRAY_TYPE(3);
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
return out;
};
/**
* Creates a new vec3 initialized with the given values
*
* @param {Number} x X component
* @param {Number} y Y component
* @param {Number} z Z component
* @returns {vec3} a new 3D vector
*/
vec3.fromValues = function(x, y, z) {
var out = new GLMAT_ARRAY_TYPE(3);
out[0] = x;
out[1] = y;
out[2] = z;
return out;
};
/**
* Copy the values from one vec3 to another
*
* @param {vec3} out the receiving vector
* @param {vec3} a the source vector
* @returns {vec3} out
*/
vec3.copy = function(out, a) {
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
return out;
};
/**
* Set the components of a vec3 to the given values
*
* @param {vec3} out the receiving vector
* @param {Number} x X component
* @param {Number} y Y component
* @param {Number} z Z component
* @returns {vec3} out
*/
vec3.set = function(out, x, y, z) {
out[0] = x;
out[1] = y;
out[2] = z;
return out;
};
/**
* Adds two vec3's
*
* @param {vec3} out the receiving vector
* @param {vec3} a the first operand
* @param {vec3} b the second operand
* @returns {vec3} out
*/
vec3.add = function(out, a, b) {
out[0] = a[0] + b[0];
out[1] = a[1] + b[1];
out[2] = a[2] + b[2];
return out;
};
/**
* Subtracts two vec3's
*
* @param {vec3} out the receiving vector
* @param {vec3} a the first operand
* @param {vec3} b the second operand
* @returns {vec3} out
*/
vec3.subtract = function(out, a, b) {
out[0] = a[0] - b[0];
out[1] = a[1] - b[1];
out[2] = a[2] - b[2];
return out;
};
/**
* Alias for {@link vec3.subtract}
* @function
*/
vec3.sub = vec3.subtract;
/**
* Multiplies two vec3's
*
* @param {vec3} out the receiving vector
* @param {vec3} a the first operand
* @param {vec3} b the second operand
* @returns {vec3} out
*/
vec3.multiply = function(out, a, b) {
out[0] = a[0] * b[0];
out[1] = a[1] * b[1];
out[2] = a[2] * b[2];
return out;
};
/**
* Alias for {@link vec3.multiply}
* @function
*/
vec3.mul = vec3.multiply;
/**
* Divides two vec3's
*
* @param {vec3} out the receiving vector
* @param {vec3} a the first operand
* @param {vec3} b the second operand
* @returns {vec3} out
*/
vec3.divide = function(out, a, b) {
out[0] = a[0] / b[0];
out[1] = a[1] / b[1];
out[2] = a[2] / b[2];
return out;
};
/**
* Alias for {@link vec3.divide}
* @function
*/
vec3.div = vec3.divide;
/**
* Returns the minimum of two vec3's
*
* @param {vec3} out the receiving vector
* @param {vec3} a the first operand
* @param {vec3} b the second operand
* @returns {vec3} out
*/
vec3.min = function(out, a, b) {
out[0] = Math.min(a[0], b[0]);
out[1] = Math.min(a[1], b[1]);
out[2] = Math.min(a[2], b[2]);
return out;
};
/**
* Returns the maximum of two vec3's
*
* @param {vec3} out the receiving vector
* @param {vec3} a the first operand
* @param {vec3} b the second operand
* @returns {vec3} out
*/
vec3.max = function(out, a, b) {
out[0] = Math.max(a[0], b[0]);
out[1] = Math.max(a[1], b[1]);
out[2] = Math.max(a[2], b[2]);
return out;
};
/**
* Scales a vec3 by a scalar number
*
* @param {vec3} out the receiving vector
* @param {vec3} a the vector to scale
* @param {Number} b amount to scale the vector by
* @returns {vec3} out
*/
vec3.scale = function(out, a, b) {
out[0] = a[0] * b;
out[1] = a[1] * b;
out[2] = a[2] * b;
return out;
};
/**
* Calculates the euclidian distance between two vec3's
*
* @param {vec3} a the first operand
* @param {vec3} b the second operand
* @returns {Number} distance between a and b
*/
vec3.distance = function(a, b) {
var x = b[0] - a[0],
y = b[1] - a[1],
z = b[2] - a[2];
return Math.sqrt(x*x + y*y + z*z);
};
/**
* Alias for {@link vec3.distance}
* @function
*/
vec3.dist = vec3.distance;
/**
* Calculates the squared euclidian distance between two vec3's
*
* @param {vec3} a the first operand
* @param {vec3} b the second operand
* @returns {Number} squared distance between a and b
*/
vec3.squaredDistance = function(a, b) {
var x = b[0] - a[0],
y = b[1] - a[1],
z = b[2] - a[2];
return x*x + y*y + z*z;
};
/**
* Alias for {@link vec3.squaredDistance}
* @function
*/
vec3.sqrDist = vec3.squaredDistance;
/**
* Calculates the length of a vec3
*
* @param {vec3} a vector to calculate length of
* @returns {Number} length of a
*/
vec3.length = function (a) {
var x = a[0],
y = a[1],
z = a[2];
return Math.sqrt(x*x + y*y + z*z);
};
/**
* Alias for {@link vec3.length}
* @function
*/
vec3.len = vec3.length;
/**
* Calculates the squared length of a vec3
*
* @param {vec3} a vector to calculate squared length of
* @returns {Number} squared length of a
*/
vec3.squaredLength = function (a) {
var x = a[0],
y = a[1],
z = a[2];
return x*x + y*y + z*z;
};
/**
* Alias for {@link vec3.squaredLength}
* @function
*/
vec3.sqrLen = vec3.squaredLength;
/**
* Negates the components of a vec3
*
* @param {vec3} out the receiving vector
* @param {vec3} a vector to negate
* @returns {vec3} out
*/
vec3.negate = function(out, a) {
out[0] = -a[0];
out[1] = -a[1];
out[2] = -a[2];
return out;
};
/**
* Normalize a vec3
*
* @param {vec3} out the receiving vector
* @param {vec3} a vector to normalize
* @returns {vec3} out
*/
vec3.normalize = function(out, a) {
var x = a[0],
y = a[1],
z = a[2];
var len = x*x + y*y + z*z;
if (len > 0) {
//TODO: evaluate use of glm_invsqrt here?
len = 1 / Math.sqrt(len);
out[0] = a[0] * len;
out[1] = a[1] * len;
out[2] = a[2] * len;
}
return out;
};
/**
* Calculates the dot product of two vec3's
*
* @param {vec3} a the first operand
* @param {vec3} b the second operand
* @returns {Number} dot product of a and b
*/
vec3.dot = function (a, b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
};
/**
* Computes the cross product of two vec3's
*
* @param {vec3} out the receiving vector
* @param {vec3} a the first operand
* @param {vec3} b the second operand
* @returns {vec3} out
*/
vec3.cross = function(out, a, b) {
var ax = a[0], ay = a[1], az = a[2],
bx = b[0], by = b[1], bz = b[2];
out[0] = ay * bz - az * by;
out[1] = az * bx - ax * bz;
out[2] = ax * by - ay * bx;
return out;
};
/**
* Performs a linear interpolation between two vec3's
*
* @param {vec3} out the receiving vector
* @param {vec3} a the first operand
* @param {vec3} b the second operand
* @param {Number} t interpolation amount between the two inputs
* @returns {vec3} out
*/
vec3.lerp = function (out, a, b, t) {
var ax = a[0],
ay = a[1],
az = a[2];
out[0] = ax + t * (b[0] - ax);
out[1] = ay + t * (b[1] - ay);
out[2] = az + t * (b[2] - az);
return out;
};
/**
* Transforms the vec3 with a mat4.
* 4th vector component is implicitly '1'
*
* @param {vec3} out the receiving vector
* @param {vec3} a the vector to transform
* @param {mat4} m matrix to transform with
* @returns {vec3} out
*/
vec3.transformMat4 = function(out, a, m) {
var x = a[0], y = a[1], z = a[2];
out[0] = m[0] * x + m[4] * y + m[8] * z + m[12];
out[1] = m[1] * x + m[5] * y + m[9] * z + m[13];
out[2] = m[2] * x + m[6] * y + m[10] * z + m[14];
return out;
};
/**
* Transforms the vec3 with a quat
*
* @param {vec3} out the receiving vector
* @param {vec3} a the vector to transform
* @param {quat} q quaternion to transform with
* @returns {vec3} out
*/
vec3.transformQuat = function(out, a, q) {
var x = a[0], y = a[1], z = a[2],
qx = q[0], qy = q[1], qz = q[2], qw = q[3],
// calculate quat * vec
ix = qw * x + qy * z - qz * y,
iy = qw * y + qz * x - qx * z,
iz = qw * z + qx * y - qy * x,
iw = -qx * x - qy * y - qz * z;
// calculate result * inverse quat
out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
return out;
};
/**
* Perform some operation over an array of vec3s.
*
* @param {Array} a the array of vectors to iterate over
* @param {Number} stride Number of elements between the start of each vec3. If 0 assumes tightly packed
* @param {Number} offset Number of elements to skip at the beginning of the array
* @param {Number} count Number of vec3s to iterate over. If 0 iterates over entire array
* @param {Function} fn Function to call for each vector in the array
* @param {Object} [arg] additional argument to pass to fn
* @returns {Array} a
* @function
*/
vec3.forEach = (function() {
var vec = vec3.create();
return function(a, stride, offset, count, fn, arg) {
var i, l;
if(!stride) {
stride = 3;
}
if(!offset) {
offset = 0;
}
if(count) {
l = Math.min((count * stride) + offset, a.length);
} else {
l = a.length;
}
for(i = offset; i < l; i += stride) {
vec[0] = a[i]; vec[1] = a[i+1]; vec[2] = a[i+2];
fn(vec, vec, arg);
a[i] = vec[0]; a[i+1] = vec[1]; a[i+2] = vec[2];
}
return a;
};
})();
/**
* Returns a string representation of a vector
*
* @param {vec3} vec vector to represent as a string
* @returns {String} string representation of the vector
*/
vec3.str = function (a) {
return 'vec3(' + a[0] + ', ' + a[1] + ', ' + a[2] + ')';
};
if(typeof(exports) !== 'undefined') {
exports.vec3 = vec3;
}
/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
/**
* @class 4 Dimensional Vector
* @name vec4
*/
var vec4 = {};
/**
* Creates a new, empty vec4
*
* @returns {vec4} a new 4D vector
*/
vec4.create = function() {
var out = new GLMAT_ARRAY_TYPE(4);
out[0] = 0;
out[1] = 0;
out[2] = 0;
out[3] = 0;
return out;
};
/**
* Creates a new vec4 initialized with values from an existing vector
*
* @param {vec4} a vector to clone
* @returns {vec4} a new 4D vector
*/
vec4.clone = function(a) {
var out = new GLMAT_ARRAY_TYPE(4);
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
out[3] = a[3];
return out;
};
/**
* Creates a new vec4 initialized with the given values
*
* @param {Number} x X component
* @param {Number} y Y component
* @param {Number} z Z component
* @param {Number} w W component
* @returns {vec4} a new 4D vector
*/
vec4.fromValues = function(x, y, z, w) {
var out = new GLMAT_ARRAY_TYPE(4);
out[0] = x;
out[1] = y;
out[2] = z;
out[3] = w;
return out;
};
/**
* Copy the values from one vec4 to another
*
* @param {vec4} out the receiving vector
* @param {vec4} a the source vector
* @returns {vec4} out
*/
vec4.copy = function(out, a) {
out[0] = a[0];
out[1] = a[1];
out[2] = a[2];
out[3] = a[3];
return out;
};
/**
* Set the components of a vec4 to the given values
*
* @param {vec4} out the receiving vector
* @param {Number} x X component
* @param {Number} y Y component
* @param {Number} z Z component
* @param {Number} w W component
* @returns {vec4} out
*/
vec4.set = function(out, x, y, z, w) {
out[0] = x;
out[1] = y;
out[2] = z;
out[3] = w;
return out;
};
/**
* Adds two vec4's
*
* @param {vec4} out the receiving vector
* @param {vec4} a the first operand
* @param {vec4} b the second operand
* @returns {vec4} out
*/
vec4.add = function(out, a, b) {
out[0] = a[0] + b[0];
out[1] = a[1] + b[1];
out[2] = a[2] + b[2];
out[3] = a[3] + b[3];
return out;
};
/**
* Subtracts two vec4's
*
* @param {vec4} out the receiving vector
* @param {vec4} a the first operand
* @param {vec4} b the second operand
* @returns {vec4} out
*/
vec4.subtract = function(out, a, b) {
out[0] = a[0] - b[0];
out[1] = a[1] - b[1];
out[2] = a[2] - b[2];
out[3] = a[3] - b[3];
return out;
};
/**
* Alias for {@link vec4.subtract}
* @function
*/
vec4.sub = vec4.subtract;
/**
* Multiplies two vec4's
*
* @param {vec4} out the receiving vector
* @param {vec4} a the first operand
* @param {vec4} b the second operand
* @returns {vec4} out
*/
vec4.multiply = function(out, a, b) {
out[0] = a[0] * b[0];
out[1] = a[1] * b[1];
out[2] = a[2] * b[2];
out[3] = a[3] * b[3];
return out;
};
/**
* Alias for {@link vec4.multiply}
* @function
*/
vec4.mul = vec4.multiply;
/**
* Divides two vec4's
*
* @param {vec4} out the receiving vector
* @param {vec4} a the first operand
* @param {vec4} b the second operand
* @returns {vec4} out
*/
vec4.divide = function(out, a, b) {
out[0] = a[0] / b[0];
out[1] = a[1] / b[1];
out[2] = a[2] / b[2];
out[3] = a[3] / b[3];
return out;
};
/**
* Alias for {@link vec4.divide}
* @function
*/
vec4.div = vec4.divide;
/**
* Returns the minimum of two vec4's
*
* @param {vec4} out the receiving vector
* @param {vec4} a the first operand
* @param {vec4} b the second operand
* @returns {vec4} out
*/
vec4.min = function(out, a, b) {
out[0] = Math.min(a[0], b[0]);
out[1] = Math.min(a[1], b[1]);
out[2] = Math.min(a[2], b[2]);
out[3] = Math.min(a[3], b[3]);
return out;
};
/**
* Returns the maximum of two vec4's
*
* @param {vec4} out the receiving vector
* @param {vec4} a the first operand
* @param {vec4} b the second operand
* @returns {vec4} out
*/
vec4.max = function(out, a, b) {
out[0] = Math.max(a[0], b[0]);
out[1] = Math.max(a[1], b[1]);
out[2] = Math.max(a[2], b[2]);
out[3] = Math.max(a[3], b[3]);
return out;
};
/**
* Scales a vec4 by a scalar number
*
* @param {vec4} out the receiving vector
* @param {vec4} a the vector to scale
* @param {Number} b amount to scale the vector by
* @returns {vec4} out
*/
vec4.scale = function(out, a, b) {
out[0] = a[0] * b;
out[1] = a[1] * b;
out[2] = a[2] * b;
out[3] = a[3] * b;
return out;
};
/**
* Calculates the euclidian distance between two vec4's
*
* @param {vec4} a the first operand
* @param {vec4} b the second operand
* @returns {Number} distance between a and b
*/
vec4.distance = function(a, b) {
var x = b[0] - a[0],
y = b[1] - a[1],
z = b[2] - a[2],
w = b[3] - a[3];
return Math.sqrt(x*x + y*y + z*z + w*w);
};
/**
* Alias for {@link vec4.distance}
* @function
*/
vec4.dist = vec4.distance;
/**
* Calculates the squared euclidian distance between two vec4's
*
* @param {vec4} a the first operand
* @param {vec4} b the second operand
* @returns {Number} squared distance between a and b
*/
vec4.squaredDistance = function(a, b) {
var x = b[0] - a[0],
y = b[1] - a[1],
z = b[2] - a[2],
w = b[3] - a[3];
return x*x + y*y + z*z + w*w;
};
/**
* Alias for {@link vec4.squaredDistance}
* @function
*/
vec4.sqrDist = vec4.squaredDistance;
/**
* Calculates the length of a vec4
*
* @param {vec4} a vector to calculate length of
* @returns {Number} length of a
*/
vec4.length = function (a) {
var x = a[0],
y = a[1],
z = a[2],
w = a[3];
return Math.sqrt(x*x + y*y + z*z + w*w);
};
/**
* Alias for {@link vec4.length}
* @function
*/
vec4.len = vec4.length;
/**
* Calculates the squared length of a vec4
*
* @param {vec4} a vector to calculate squared length of
* @returns {Number} squared length of a
*/
vec4.squaredLength = function (a) {
var x = a[0],
y = a[1],
z = a[2],
w = a[3];
return x*x + y*y + z*z + w*w;
};
/**
* Alias for {@link vec4.squaredLength}
* @function
*/
vec4.sqrLen = vec4.squaredLength;
/**
* Negates the components of a vec4
*
* @param {vec4} out the receiving vector
* @param {vec4} a vector to negate
* @returns {vec4} out
*/
vec4.negate = function(out, a) {
out[0] = -a[0];
out[1] = -a[1];
out[2] = -a[2];
out[3] = -a[3];
return out;
};
/**
* Normalize a vec4
*
* @param {vec4} out the receiving vector
* @param {vec4} a vector to normalize
* @returns {vec4} out
*/
vec4.normalize = function(out, a) {
var x = a[0],
y = a[1],
z = a[2],
w = a[3];
var len = x*x + y*y + z*z + w*w;
if (len > 0) {
len = 1 / Math.sqrt(len);
out[0] = a[0] * len;
out[1] = a[1] * len;
out[2] = a[2] * len;
out[3] = a[3] * len;
}
return out;
};
/**
* Calculates the dot product of two vec4's
*
* @param {vec4} a the first operand
* @param {vec4} b the second operand
* @returns {Number} dot product of a and b
*/
vec4.dot = function (a, b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
};
/**
* Performs a linear interpolation between two vec4's
*
* @param {vec4} out the receiving vector
* @param {vec4} a the first operand
* @param {vec4} b the second operand
* @param {Number} t interpolation amount between the two inputs
* @returns {vec4} out
*/
vec4.lerp = function (out, a, b, t) {
var ax = a[0],
ay = a[1],
az = a[2],
aw = a[3];
out[0] = ax + t * (b[0] - ax);
out[1] = ay + t * (b[1] - ay);
out[2] = az + t * (b[2] - az);
out[3] = aw + t * (b[3] - aw);
return out;
};
/**
* Transforms the vec4 with a mat4.
*
* @param {vec4} out the receiving vector
* @param {vec4} a the vector to transform
* @param {mat4} m matrix to transform with
* @returns {vec4} out
*/
vec4.transformMat4 = function(out, a, m) {
var x = a[0], y = a[1], z = a[2], w = a[3];
out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
return out;
};
/**
* Transforms the vec4 with a quat
*
* @param {vec4} out the receiving vector
* @param {vec4} a the vector to transform
* @param {quat} q quaternion to transform with
* @returns {vec4} out
*/
vec4.transformQuat = function(out, a, q) {
var x = a[0], y = a[1], z = a[2],
qx = q[0], qy = q[1], qz = q[2], qw = q[3],
// calculate quat * vec
ix = qw * x + qy * z - qz * y,
iy = qw * y + qz * x - qx * z,
iz = qw * z + qx * y - qy * x,
iw = -qx * x - qy * y - qz * z;
// calculate result * inverse quat
out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
return out;
};
/**
* Perform some operation over an array of vec4s.
*
* @param {Array} a the array of vectors to iterate over
* @param {Number} stride Number of elements between the start of each vec4. If 0 assumes tightly packed
* @param {Number} offset Number of elements to skip at the beginning of the array
* @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array
* @param {Function} fn Function to call for each vector in the array
* @param {Object} [arg] additional argument to pass to fn
* @returns {Array} a
* @function
*/
vec4.forEach = (function() {
var vec = vec4.create();
return function(a, stride, offset, count, fn, arg) {
var i, l;
if(!stride) {
stride = 4;
}
if(!offset) {
offset = 0;
}
if(count) {
l = Math.min((count * stride) + offset, a.length);
} else {
l = a.length;
}
for(i = offset; i < l; i += stride) {
vec[0] = a[i]; vec[1] = a[i+1]; vec[2] = a[i+2]; vec[3] = a[i+3];
fn(vec, vec, arg);
a[i] = vec[0]; a[i+1] = vec[1]; a[i+2] = vec[2]; a[i+3] = vec[3];
}
return a;
};
})();
/**
* Returns a string representation of a vector
*
* @param {vec4} vec vector to represent as a string
* @returns {String} string representation of the vector
*/
vec4.str = function (a) {
return 'vec4(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ')';
};
if(typeof(exports) !== 'undefined') {
exports.vec4 = vec4;
}
// 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.
'use strict';
base.requireRawScript('../third_party/gl-matrix/src/gl-matrix/common.js');
base.requireRawScript('../third_party/gl-matrix/src/gl-matrix/mat2d.js');
base.requireRawScript('../third_party/gl-matrix/src/gl-matrix/mat4.js');
base.requireRawScript('../third_party/gl-matrix/src/gl-matrix/vec2.js');
base.requireRawScript('../third_party/gl-matrix/src/gl-matrix/vec3.js');
base.requireRawScript('../third_party/gl-matrix/src/gl-matrix/vec4.js');
base.exportTo('base', function() {
var tmp_vec2 = vec2.create();
var tmp_vec2b = vec2.create();
var tmp_vec4 = vec4.create();
var tmp_mat2d = mat2d.create();
vec2.createFromArray = function(arr) {
if (arr.length != 2)
throw new Error('Should be length 2');
var v = vec2.create();
vec2.set(v, arr[0], arr[1]);
return v;
};
vec2.createXY = function(x, y) {
var v = vec2.create();
vec2.set(v, x, y);
return v;
};
vec2.toString = function(a) {
return '[' + a[0] + ', ' + a[1] + ']';
};
vec2.addTwoScaledUnitVectors = function(out, u1, scale1, u2, scale2) {
// out = u1 * scale1 + u2 * scale2
vec2.scale(tmp_vec2, u1, scale1);
vec2.scale(tmp_vec2b, u2, scale2);
vec2.add(out, tmp_vec2, tmp_vec2b);
}
vec3.createXYZ = function(x, y, z) {
var v = vec3.create();
vec3.set(v, x, y, z);
return v;
};
vec3.toString = function(a) {
return 'vec3(' + a[0] + ', ' + a[1] + ', ' + a[2] + ')';
}
mat2d.translateXY = function(out, x, y) {
vec2.set(tmp_vec2, x, y);
mat2d.translate(out, out, tmp_vec2);
}
mat2d.scaleXY = function(out, x, y) {
vec2.set(tmp_vec2, x, y);
mat2d.scale(out, out, tmp_vec2);
}
vec4.unitize = function(out, a) {
out[0] = a[0] / a[3];
out[1] = a[1] / a[3];
out[2] = a[2] / a[3];
out[3] = 1;
return out;
}
vec2.copyFromVec4 = function(out, a) {
vec4.unitize(tmp_vec4, a);
vec2.copy(out, tmp_vec4);
}
return {};
});
// 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.
'use strict';
/**
* @fileoverview 2D Rectangle math.
*/
base.require('base.gl_matrix');
base.exportTo('base', function() {
/**
* Tracks a 2D bounding box.
* @constructor
*/
function Rect() {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
};
Rect.fromXYWH = function(x, y, w, h) {
var rect = new Rect();
rect.x = x;
rect.y = y;
rect.width = w;
rect.height = h;
return rect;
}
Rect.fromArray = function(ary) {
if (ary.length != 4)
throw new Error('ary.length must be 4');
var rect = new Rect();
rect.x = ary[0];
rect.y = ary[1];
rect.width = ary[2];
rect.height = ary[3];
return rect;
}
Rect.prototype = {
__proto__: Object.prototype,
get left() {
return this.x;
},
get top() {
return this.y;
},
get right() {
return this.x + this.width;
},
get bottom() {
return this.y + this.height;
},
toString: function() {
return 'Rect(' + this.x + ', ' + this.y + ', ' +
this.width + ', ' + this.height + ')';
},
toArray: function() {
return [this.x, this.y, this.width, this.height];
},
clone: function() {
var rect = new Rect();
rect.x = this.x;
rect.y = this.y;
rect.width = this.width;
rect.height = this.height;
return rect;
},
enlarge: function(pad) {
var rect = new Rect();
this.enlargeFast(rect, pad);
return rect;
},
enlargeFast: function(out, pad) {
out.x = this.x - pad;
out.y = this.y - pad;
out.width = this.width + 2 * pad;
out.height = this.height + 2 * pad;
return out;
},
size: function() {
return {width: this.width, height: this.height};
},
scale: function(s) {
var rect = new Rect();
this.scaleFast(rect, s);
return rect;
},
scaleSize: function(s) {
return Rect.fromXYWH(this.x, this.y, this.width * s, this.height * s);
},
scaleFast: function(out, s) {
out.x = this.x * s;
out.y = this.y * s;
out.width = this.width * s;
out.height = this.height * s;
return out;
},
translate: function(v) {
var rect = new Rect();
this.translateFast(rect, v);
return rect;
},
translateFast: function(out, v) {
out.x = this.x + v[0];
out.y = this.x + v[1];
out.width = this.width;
out.height = this.height;
return out;
},
asUVRectInside: function(containingRect) {
var rect = new Rect();
rect.x = (this.x - containingRect.x) / containingRect.width;
rect.y = (this.y - containingRect.y) / containingRect.height;
rect.width = this.width / containingRect.width;
rect.height = this.height / containingRect.height;
return rect;
},
intersects: function(that) {
var ok = true;
ok &= this.x < that.right;
ok &= this.right > that.x;
ok &= this.y < that.bottom;
ok &= this.bottom > that.y;
return ok;
},
equalTo: function(rect) {
return rect &&
(this.x === rect.x) &&
(this.y === rect.y) &&
(this.width === rect.width) &&
(this.height === rect.height);
}
};
return {
Rect: Rect
};
});
// 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.
'use strict';
base.require('base.iteration_helpers');
base.require('base.rect');
base.exportTo('base', function() {
/**
* Adds a {@code getInstance} static method that always return the same
* instance object.
* @param {!Function} ctor The constructor for the class to add the static
* method to.
*/
function addSingletonGetter(ctor) {
ctor.getInstance = function() {
return ctor.instance_ || (ctor.instance_ = new ctor());
};
}
function instantiateTemplate(selector) {
return document.querySelector(selector).content.cloneNode(true);
}
function tracedFunction(fn, name, opt_this) {
function F() {
console.time(name);
try {
fn.apply(opt_this, arguments);
} finally {
console.timeEnd(name);
}
}
return F;
}
function normalizeException(e) {
if (typeof(e) == 'string') {
return {
message: e,
stack: ['<unknown>']
};
}
return {
message: e.message,
stack: e.stack ? e.stack : ['<unknown>']
};
}
function stackTrace() {
var stack = new Error().stack + '';
stack = stack.split('\n');
return stack.slice(2);
}
function windowRectForElement(element) {
var position = [element.offsetLeft, element.offsetTop];
var size = [element.offsetWidth, element.offsetHeight];
var node = element.offsetParent;
while (node) {
position[0] += node.offsetLeft;
position[1] += node.offsetTop;
node = node.offsetParent;
}
return base.Rect.fromXYWH(position[0], position[1], size[0], size[1]);
}
function clamp(x, lo, hi) {
return Math.min(Math.max(x, lo), hi);
}
function lerp(percentage, lo, hi) {
var range = hi - lo;
return lo + percentage * range;
}
function deg2rad(deg) {
return (Math.PI * deg) / 180.0;
}
function scrollIntoViewIfNeeded(el) {
var pr = el.parentElement.getBoundingClientRect();
var cr = el.getBoundingClientRect();
if (cr.top < pr.top) {
el.scrollIntoView(true);
} else if (cr.bottom > pr.bottom) {
el.scrollIntoView(false);
}
}
return {
addSingletonGetter: addSingletonGetter,
tracedFunction: tracedFunction,
normalizeException: normalizeException,
instantiateTemplate: instantiateTemplate,
stackTrace: stackTrace,
windowRectForElement: windowRectForElement,
scrollIntoViewIfNeeded: scrollIntoViewIfNeeded,
clamp: clamp,
lerp: lerp,
deg2rad: deg2rad
};
});
// 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.
'use strict';
base.require('base.utils');
base.exportTo('base', function() {
// Setting this to true will cause stack traces to get dumped into the
// tasks. When an exception happens the original stack will be printed.
//
// NOTE: This should never be set committed as true.
var recordRAFStacks = false;
var pendingPreAFs = [];
var pendingRAFs = [];
var pendingIdleCallbacks = [];
var currentRAFDispatchList = undefined;
var rafScheduled = false;
function scheduleRAF() {
if (rafScheduled)
return;
rafScheduled = true;
if (window.requestAnimationFrame) {
window.requestAnimationFrame(processRequests);
} else {
var delta = Date.now() - window.performance.now();
window.webkitRequestAnimationFrame(function(domTimeStamp) {
processRequests(domTimeStamp - delta);
});
}
}
function onAnimationFrameError(e, opt_stack) {
if (opt_stack)
console.log(opt_stack);
if (e.message)
console.error(e.message, e.stack);
else
console.error(e);
}
function runTask(task, frameBeginTime) {
try {
task.callback.call(task.context, frameBeginTime);
} catch (e) {
base.onAnimationFrameError(e, task.stack);
}
}
function processRequests(frameBeginTime) {
// We assume that we want to do a maximum of 10ms optional work per frame.
// Hopefully rAF will eventually pass this in for us.
var rafCompletionDeadline = frameBeginTime + 10;
rafScheduled = false;
var currentPreAFs = pendingPreAFs;
currentRAFDispatchList = pendingRAFs;
pendingPreAFs = [];
pendingRAFs = [];
for (var i = 0; i < currentPreAFs.length; i++)
runTask(currentPreAFs[i], frameBeginTime);
while (currentRAFDispatchList.length > 0)
runTask(currentRAFDispatchList.shift(), frameBeginTime);
currentRAFDispatchList = undefined;
while (pendingIdleCallbacks.length > 0 &&
window.performance.now() < rafCompletionDeadline)
runTask(pendingIdleCallbacks.shift());
if (pendingIdleCallbacks.length > 0)
scheduleRAF();
}
function getStack_() {
if (!recordRAFStacks)
return '';
var stackLines = base.stackTrace();
// Strip off getStack_.
stackLines.shift();
return stackLines.join('\n');
}
function requestPreAnimationFrame(callback, opt_this) {
pendingPreAFs.push({
callback: callback,
context: opt_this || window,
stack: getStack_()});
scheduleRAF();
}
function requestAnimationFrameInThisFrameIfPossible(callback, opt_this) {
if (!currentRAFDispatchList) {
requestAnimationFrame(callback, opt_this);
return;
}
currentRAFDispatchList.push({
callback: callback,
context: opt_this || window,
stack: getStack_()});
return;
}
function requestAnimationFrame(callback, opt_this) {
pendingRAFs.push({
callback: callback,
context: opt_this || window,
stack: getStack_()});
scheduleRAF();
}
function requestIdleCallback(callback, opt_this) {
pendingIdleCallbacks.push({
callback: callback,
context: opt_this || window,
stack: getStack_()});
scheduleRAF();
}
function forcePendingRAFTasksToRun(frameBeginTime) {
if (!rafScheduled)
return;
processRequests(frameBeginTime);
}
return {
onAnimationFrameError: onAnimationFrameError,
requestPreAnimationFrame: requestPreAnimationFrame,
requestAnimationFrame: requestAnimationFrame,
requestAnimationFrameInThisFrameIfPossible:
requestAnimationFrameInThisFrameIfPossible,
requestIdleCallback: requestIdleCallback,
forcePendingRAFTasksToRun: forcePendingRAFTasksToRun
};
});
// 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.
'use strict';
base.require('base.raf');
base.require('base.promise');
base.exportTo('tracing.importer', function() {
/**
* A task is a combination of a run callback, a set of subtasks, and an after
* task.
*
* When executed, a task does the following things:
* 1. Runs its callback
* 2. Runs its subtasks
* 3. Runs its after callback.
*
* The list of subtasks and after task can be mutated inside step #1 but as
* soon as the task's callback returns, the subtask list and after task is
* fixed and cannot be changed again.
*
* Use task.after().after().after() to describe the toplevel passes that make
* up your computation. Then, use subTasks to add detail to each subtask as it
* runs. For example:
* var pieces = [];
* taskA = new Task(function() { pieces = getPieces(); });
* taskA.after(function(taskA) {
* pieces.forEach(function(piece) {
* taskA.subTask(function(taskB) { piece.process(); }, this);
* });
* });
*
* @constructor
*/
function Task(runCb, thisArg) {
if (thisArg === undefined)
throw new Error('Almost certainly, you meant to pass a thisArg.');
this.runCb_ = runCb;
this.thisArg_ = thisArg;
this.afterTask_ = undefined;
this.subTasks_ = [];
}
Task.prototype = {
/*
* See constructor documentation on semantics of subtasks.
*/
subTask: function(cb, thisArg) {
if (cb instanceof Task)
this.subTasks_.push(cb);
else
this.subTasks_.push(new Task(cb, thisArg));
return this.subTasks_[this.subTasks_.length - 1];
},
/**
* Runs the current task and returns the task that should be executed next.
*/
run: function() {
this.runCb_.call(this.thisArg_, this);
var subTasks = this.subTasks_;
this.subTasks_ = undefined; // Prevent more subTasks from being posted.
if (!subTasks.length)
return this.afterTask_;
// If there are subtasks, then we want to execute all the subtasks and
// then this task's afterTask. To make this happen, we update the
// afterTask of all the subtasks so the point upward to each other, e.g.
// subTask[0].afterTask to subTask[1] and so on. Then, the last subTask's
// afterTask points at this task's afterTask.
for (var i = 1; i < subTasks.length; i++)
subTasks[i - 1].afterTask_ = subTasks[i];
subTasks[subTasks.length - 1].afterTask_ = this.afterTask_;
return subTasks[0];
},
/*
* See constructor documentation on semantics of after tasks.
*/
after: function(cb, thisArg) {
if (this.afterTask_)
throw new Error('Has an after task already');
if (cb instanceof Task)
this.afterTask_ = cb;
else
this.afterTask_ = new Task(cb, thisArg);
return this.afterTask_;
}
};
Task.RunSynchronously = function(task) {
var curTask = task;
while (curTask)
curTask = curTask.run();
}
/**
* Runs a task using raf.requestIdleCallback, returning
* a promise for its completion.
*/
Task.RunWhenIdle = function(task) {
return new base.Promise(function(resolver) {
var curTask = task;
function runAnother() {
curTask = curTask.run();
if (curTask) {
base.requestIdleCallback(runAnother);
return;
}
resolver.resolve();
}
base.requestIdleCallback(runAnother);
});
}
return {
Task: Task
};
});
// 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.
'use strict';
base.exportTo('base', function() {
var nextGUID = 1;
var GUID = {
allocate: function() {
return nextGUID++;
}
};
return {
GUID: GUID
};
});
// 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.
'use strict';
/**
* @fileoverview Helper functions for doing intersections and iteration
* over sorted arrays and intervals.
*
*/
base.exportTo('base', function() {
/**
* Finds the first index in the array whose value is >= loVal.
*
* The key for the search is defined by the mapFn. This array must
* be prearranged such that ary.map(mapFn) would also be sorted in
* ascending order.
*
* @param {Array} ary An array of arbitrary objects.
* @param {function():*} mapFn Callback that produces a key value
* from an element in ary.
* @param {number} loVal Value for which to search.
* @return {Number} Offset o into ary where all ary[i] for i <= o
* are < loVal, or ary.length if loVal is greater than all elements in
* the array.
*/
function findLowIndexInSortedArray(ary, mapFn, loVal) {
if (ary.length == 0)
return 1;
var low = 0;
var high = ary.length - 1;
var i, comparison;
var hitPos = -1;
while (low <= high) {
i = Math.floor((low + high) / 2);
comparison = mapFn(ary[i]) - loVal;
if (comparison < 0) {
low = i + 1; continue;
} else if (comparison > 0) {
high = i - 1; continue;
} else {
hitPos = i;
high = i - 1;
}
}
// return where we hit, or failing that the low pos
return hitPos != -1 ? hitPos : low;
}
/**
* Finds an index in an array of intervals that either
* intersects the provided loVal, or if no intersection is found,
* the index of the first interval whose start is > loVal.
*
* The array of intervals is defined implicitly via two mapping functions
* over the provided ary. mapLoFn determines the lower value of the interval,
* mapWidthFn the width. Intersection is lower-inclusive, e.g. [lo,lo+w).
*
* The array of intervals formed by this mapping must be non-overlapping and
* sorted in ascending order by loVal.
*
* @param {Array} ary An array of objects that can be converted into sorted
* nonoverlapping ranges [x,y) using the mapLoFn and mapWidth.
* @param {function():*} mapLoFn Callback that produces the low value for the
* interval represented by an element in the array.
* @param {function():*} mapWidthFn Callback that produces the width for the
* interval represented by an element in the array.
* @param {number} loVal The low value for the search.
* @return {Number} An index in the array that intersects or is first-above
* loVal, -1 if none found and loVal is below than all the intervals,
* ary.length if loVal is greater than all the intervals.
*/
function findLowIndexInSortedIntervals(ary, mapLoFn, mapWidthFn, loVal) {
var first = findLowIndexInSortedArray(ary, mapLoFn, loVal);
if (first == 0) {
if (loVal >= mapLoFn(ary[0]) &&
loVal < mapLoFn(ary[0]) + mapWidthFn(ary[0], 0)) {
return 0;
} else {
return -1;
}
} else if (first < ary.length) {
if (loVal >= mapLoFn(ary[first]) &&
loVal < mapLoFn(ary[first]) + mapWidthFn(ary[first], first)) {
return first;
} else if (loVal >= mapLoFn(ary[first - 1]) &&
loVal < mapLoFn(ary[first - 1]) +
mapWidthFn(ary[first - 1], first - 1)) {
return first - 1;
} else {
return ary.length;
}
} else if (first == ary.length) {
if (loVal >= mapLoFn(ary[first - 1]) &&
loVal < mapLoFn(ary[first - 1]) +
mapWidthFn(ary[first - 1], first - 1)) {
return first - 1;
} else {
return ary.length;
}
} else {
return ary.length;
}
}
/**
* Calls cb for all intervals in the implicit array of intervals
* defnied by ary, mapLoFn and mapHiFn that intersect the range
* [loVal,hiVal)
*
* This function uses the same scheme as findLowIndexInSortedArray
* to define the intervals. The same restrictions on sortedness and
* non-overlappingness apply.
*
* @param {Array} ary An array of objects that can be converted into sorted
* nonoverlapping ranges [x,y) using the mapLoFn and mapWidth.
* @param {function():*} mapLoFn Callback that produces the low value for the
* interval represented by an element in the array.
* @param {function():*} mapLoFn Callback that produces the width for the
* interval represented by an element in the array.
* @param {number} The low value for the search, inclusive.
* @param {number} loVal The high value for the search, non inclusive.
* @param {function():*} cb The function to run for intersecting intervals.
*/
function iterateOverIntersectingIntervals(ary, mapLoFn, mapWidthFn, loVal,
hiVal, cb) {
if (ary.length == 0)
return;
if (loVal > hiVal) return;
var i = findLowIndexInSortedArray(ary, mapLoFn, loVal);
if (i == -1) {
return;
}
if (i > 0) {
var hi = mapLoFn(ary[i - 1]) + mapWidthFn(ary[i - 1], i - 1);
if (hi >= loVal) {
cb(ary[i - 1]);
}
}
if (i == ary.length) {
return;
}
for (var n = ary.length; i < n; i++) {
var lo = mapLoFn(ary[i]);
if (lo >= hiVal)
break;
cb(ary[i]);
}
}
/**
* Non iterative version of iterateOverIntersectingIntervals.
*
* @return {Array} Array of elements in ary that intersect loVal, hiVal.
*/
function getIntersectingIntervals(ary, mapLoFn, mapWidthFn, loVal, hiVal) {
var tmp = [];
iterateOverIntersectingIntervals(ary, mapLoFn, mapWidthFn, loVal, hiVal,
function(d) {
tmp.push(d);
});
return tmp;
}
/**
* Finds the element in the array whose value is closest to |val|.
*
* The same restrictions on sortedness as for findLowIndexInSortedArray apply.
*
* @param {Array} ary An array of arbitrary objects.
* @param {function():*} mapFn Callback that produces a key value
* from an element in ary.
* @param {number} val Value for which to search.
* @param {number} maxDiff Maximum allowed difference in value between |val|
* and an element's value.
* @return {object} Object in the array whose value is closest to |val|, or
* null if no object is within range.
*/
function findClosestElementInSortedArray(ary, mapFn, val, maxDiff) {
if (ary.length === 0)
return null;
var aftIdx = findLowIndexInSortedArray(ary, mapFn, val);
var befIdx = aftIdx > 0 ? aftIdx - 1 : 0;
if (aftIdx === ary.length)
aftIdx -= 1;
var befDiff = Math.abs(val - mapFn(ary[befIdx]));
var aftDiff = Math.abs(val - mapFn(ary[aftIdx]));
if (befDiff > maxDiff && aftDiff > maxDiff)
return null;
var idx = befDiff < aftDiff ? befIdx : aftIdx;
return ary[idx];
}
/**
* Finds the closest interval in the implicit array of intervals
* defined by ary, mapLoFn and mapHiFn.
*
* This function uses the same scheme as findLowIndexInSortedArray
* to define the intervals. The same restrictions on sortedness and
* non-overlappingness apply.
*
* @param {Array} ary An array of objects that can be converted into sorted
* nonoverlapping ranges [x,y) using the mapLoFn and mapHiFn.
* @param {function():*} mapLoFn Callback that produces the low value for the
* interval represented by an element in the array.
* @param {function():*} mapHiFn Callback that produces the high for the
* interval represented by an element in the array.
* @param {number} val The value for the search.
* @param {number} maxDiff Maximum allowed difference in value between |val|
* and an interval's low or high value.
* @return {interval} Interval in the array whose high or low value is closest
* to |val|, or null if no interval is within range.
*/
function findClosestIntervalInSortedIntervals(ary, mapLoFn, mapHiFn, val,
maxDiff) {
if (ary.length === 0)
return null;
var idx = findLowIndexInSortedArray(ary, mapLoFn, val);
if (idx > 0)
idx -= 1;
var hiInt = ary[idx];
var loInt = hiInt;
if (val > mapHiFn(hiInt) && idx + 1 < ary.length)
loInt = ary[idx + 1];
var loDiff = Math.abs(val - mapLoFn(loInt));
var hiDiff = Math.abs(val - mapHiFn(hiInt));
if (loDiff > maxDiff && hiDiff > maxDiff)
return null;
if (loDiff < hiDiff)
return loInt;
else
return hiInt;
}
return {
findLowIndexInSortedArray: findLowIndexInSortedArray,
findLowIndexInSortedIntervals: findLowIndexInSortedIntervals,
iterateOverIntersectingIntervals: iterateOverIntersectingIntervals,
getIntersectingIntervals: getIntersectingIntervals,
findClosestElementInSortedArray: findClosestElementInSortedArray,
findClosestIntervalInSortedIntervals: findClosestIntervalInSortedIntervals
};
});
// 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.
'use strict';
base.require('base.guid');
/**
* @fileoverview Provides the Event class.
*/
base.exportTo('tracing.trace_model', function() {
/**
* The SelectionState enum defines how Events are displayed in the view.
*/
var SelectionState = {
NONE: 0,
SELECTED: 1,
HIGHLIGHTED: 2,
DIMMED: 3
};
/**
* A Event is the base type for any non-container, selectable piece
* of data in the trace model.
*
* @constructor
*/
function Event() {
this.guid_ = base.GUID.allocate();
this.selectionState = SelectionState.NONE;
}
Event.prototype = {
get guid() {
return this.guid_;
},
get selected() {
return this.selectionState === SelectionState.SELECTED;
}
};
return {
Event: Event,
SelectionState: SelectionState
};
});
// 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.
'use strict';
base.require('base.iteration_helpers');
base.require('base.sorted_array_utils');
base.require('tracing.trace_model.event');
base.exportTo('tracing.trace_model', function() {
function CounterSample(series, timestamp, value) {
tracing.trace_model.Event.call(this);
this.series_ = series;
this.timestamp_ = timestamp;
this.value_ = value;
}
CounterSample.groupByTimestamp = function(samples) {
var samplesByTimestamp = {};
for (var i = 0; i < samples.length; i++) {
var sample = samples[i];
var ts = sample.timestamp;
if (!samplesByTimestamp[ts])
samplesByTimestamp[ts] = [];
samplesByTimestamp[ts].push(sample);
}
var timestamps = base.dictionaryKeys(samplesByTimestamp);
timestamps.sort();
var groups = [];
for (var i = 0; i < timestamps.length; i++) {
var ts = timestamps[i];
var group = samplesByTimestamp[ts];
group.sort(function(x, y) {
return x.series.seriesIndex - y.series.seriesIndex;
});
groups.push(group);
}
return groups;
}
CounterSample.prototype = {
__proto__: tracing.trace_model.Event.prototype,
get series() {
return this.series_;
},
get timestamp() {
return this.timestamp_;
},
get value() {
return this.value_;
},
set timestamp(timestamp) {
this.timestamp_ = timestamp;
},
addBoundsToRange: function(range) {
range.addValue(this.timestamp);
},
toJSON: function() {
var obj = new Object();
var keys = Object.keys(this);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (typeof this[key] == 'function')
continue;
if (key == 'series_') {
obj[key] = this[key].guid;
continue;
}
obj[key] = this[key];
}
return obj;
},
getSampleIndex: function() {
return base.findLowIndexInSortedArray(
this.series.timestamps,
function(x) { return x; },
this.timestamp_);
}
};
return {
CounterSample: CounterSample
};
});
// 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.
'use strict';
base.require('tracing.trace_model.counter_sample');
/**
* @fileoverview Provides the CounterSeries class.
*/
base.exportTo('tracing.trace_model', function() {
var CounterSample = tracing.trace_model.CounterSample;
function CounterSeries(name, color) {
this.guid_ = base.GUID.allocate();
this.name_ = name;
this.color_ = color;
this.timestamps_ = [];
this.samples_ = [];
// Set by counter.addSeries
this.counter = undefined;
this.seriesIndex = undefined;
}
CounterSeries.prototype = {
__proto__: Object.prototype,
toJSON: function() {
var obj = new Object();
var keys = Object.keys(this);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (typeof this[key] == 'function')
continue;
if (key == 'counter') {
obj[key] = this[key].guid;
continue;
}
obj[key] = this[key];
}
return obj;
},
get length() {
return this.timestamps_.length;
},
get name() {
return this.name_;
},
get color() {
return this.color_;
},
get samples() {
return this.samples_;
},
get timestamps() {
return this.timestamps_;
},
getSample: function(idx) {
return this.samples_[idx];
},
getTimestamp: function(idx) {
return this.timestamps_[idx];
},
addSample: function(ts, val) {
this.timestamps_.push(ts);
var sample = new CounterSample(this, ts, val);
this.samples_.push(sample);
return sample;
},
getStatistics: function(sampleIndices) {
var sum = 0;
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
for (var i = 0; i < sampleIndices.length; ++i) {
var sample = this.getSample(sampleIndices[i]).value;
sum += sample;
min = Math.min(sample, min);
max = Math.max(sample, max);
}
return {
min: min,
max: max,
avg: (sum / sampleIndices.length),
start: this.getSample(sampleIndices[0]).value,
end: this.getSample(sampleIndices.length - 1).value
};
},
shiftTimestampsForward: function(amount) {
for (var i = 0; i < this.timestamps_.length; ++i) {
this.timestamps_[i] += amount;
this.samples_[i].timestamp = this.timestamps_[i];
}
},
iterateAllEvents: function(callback) {
this.samples_.forEach(callback);
}
};
return {
CounterSeries: CounterSeries
};
});
// 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.
'use strict';
base.require('base.guid');
base.require('base.range');
base.require('tracing.trace_model.counter_series');
/**
* @fileoverview Provides the Counter class.
*/
base.exportTo('tracing.trace_model', function() {
/**
* Stores all the samples for a given counter.
* @constructor
*/
function Counter(parent, id, category, name) {
this.guid_ = base.GUID.allocate();
this.parent = parent;
this.id = id;
this.category = category || '';
this.name = name;
this.series_ = [];
this.totals = [];
this.bounds = new base.Range();
}
Counter.prototype = {
__proto__: Object.prototype,
/*
* @return {Number} A globally unique identifier for this counter.
*/
get guid() {
return this.guid_;
},
toJSON: function() {
var obj = new Object();
var keys = Object.keys(this);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (typeof this[key] == 'function')
continue;
if (key == 'parent') {
obj[key] = this[key].guid;
continue;
}
obj[key] = this[key];
}
return obj;
},
set timestamps(arg) {
throw new Error('Bad counter API. No cookie.');
},
set seriesNames(arg) {
throw new Error('Bad counter API. No cookie.');
},
set seriesColors(arg) {
throw new Error('Bad counter API. No cookie.');
},
set samples(arg) {
throw new Error('Bad counter API. No cookie.');
},
addSeries: function(series) {
series.counter = this;
series.seriesIndex = this.series_.length;
this.series_.push(series);
return series;
},
getSeries: function(idx) {
return this.series_[idx];
},
get series() {
return this.series_;
},
get numSeries() {
return this.series_.length;
},
get numSamples() {
if (this.series_.length === 0)
return 0;
return this.series_[0].length;
},
get timestamps() {
if (this.series_.length === 0)
return [];
return this.series_[0].timestamps;
},
/**
* Obtains min, max, avg, values, start, and end for different series for
* a given counter
* getSampleStatistics([0,1])
* The statistics objects that this returns are an array of objects, one
* object for each series for the counter in the form:
* {min: minVal, max: maxVal, avg: avgVal, start: startVal, end: endVal}
*
* @param {Array.<Number>} Indices to summarize.
* @return {Object} An array of statistics. Each element in the array
* has data for one of the series in the selected counter.
*/
getSampleStatistics: function(sampleIndices) {
sampleIndices.sort();
var ret = [];
this.series_.forEach(function(series) {
ret.push(series.getStatistics(sampleIndices));
});
return ret;
},
/**
* Shifts all the timestamps inside this counter forward by the amount
* specified.
*/
shiftTimestampsForward: function(amount) {
for (var i = 0; i < this.series_.length; ++i)
this.series_[i].shiftTimestampsForward(amount);
},
/**
* Updates the bounds for this counter based on the samples it contains.
*/
updateBounds: function() {
this.totals = [];
this.maxTotal = 0;
this.bounds.reset();
if (this.series_.length === 0)
return;
var firstSeries = this.series_[0];
var lastSeries = this.series_[this.series_.length - 1];
this.bounds.addValue(firstSeries.getTimestamp(0));
this.bounds.addValue(lastSeries.getTimestamp(lastSeries.length - 1));
var numSeries = this.numSeries;
this.maxTotal = -Infinity;
// Sum the samples at each timestamp.
// Note, this assumes that all series have all timestamps.
for (var i = 0; i < firstSeries.length; ++i) {
var total = 0;
this.series_.forEach(function(series) {
total += series.getSample(i).value;
this.totals.push(total);
}.bind(this));
this.maxTotal = Math.max(total, this.maxTotal);
}
},
iterateAllEvents: function(callback) {
for (var i = 0; i < this.series_.length; i++)
this.series_[i].iterateAllEvents(callback);
}
};
/**
* Comparison between counters that orders by parent.compareTo, then name.
*/
Counter.compare = function(x, y) {
var tmp = x.parent.compareTo(y);
if (tmp != 0)
return tmp;
var tmp = x.name.localeCompare(y.name);
if (tmp == 0)
return x.tid - y.tid;
return tmp;
};
return {
Counter: Counter
};
});
// 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.
'use strict';
base.require('base.guid');
base.require('tracing.trace_model.event');
/**
* @fileoverview Provides the TimedEvent class.
*/
base.exportTo('tracing.trace_model', function() {
/**
* A TimedEvent is the base type for any piece of data in the trace model with
* a specific start and duration.
*
* @constructor
*/
function TimedEvent(start) {
tracing.trace_model.Event.call(this);
this.start = start;
this.duration = 0;
}
TimedEvent.prototype = {
__proto__: tracing.trace_model.Event.prototype,
get end() {
return this.start + this.duration;
},
addBoundsToRange: function(range) {
range.addValue(this.start);
range.addValue(this.end);
}
};
return {
TimedEvent: TimedEvent
};
});
// 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.
'use strict';
base.require('tracing.trace_model.timed_event');
/**
* @fileoverview Provides the Slice class.
*/
base.exportTo('tracing.trace_model', function() {
/**
* A Slice represents an interval of time plus parameters associated
* with that interval.
*
* @constructor
*/
function Slice(category, title, colorId, start, args, opt_duration,
opt_threadStart) {
tracing.trace_model.TimedEvent.call(this, start);
this.category = category || '';
this.title = title;
this.colorId = colorId;
this.args = args;
this.didNotFinish = false;
if (opt_duration !== undefined)
this.duration = opt_duration;
if (opt_threadStart !== undefined)
this.threadStart = opt_threadStart;
}
Slice.prototype = {
__proto__: tracing.trace_model.TimedEvent.prototype,
get analysisTypeName() {
return this.title;
}
};
return {
Slice: Slice
};
});
// 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.
'use strict';
/**
* @fileoverview Provides the Cpu class.
*/
base.require('base.range');
base.require('tracing.trace_model.slice');
base.require('tracing.trace_model.counter');
base.exportTo('tracing.trace_model', function() {
var Counter = tracing.trace_model.Counter;
var Slice = tracing.trace_model.Slice;
/**
* A CpuSlice represents an slice of time on a CPU.
*
* @constructor
*/
function CpuSlice(cat, title, colorId, start, args, opt_duration) {
Slice.apply(this, arguments);
this.threadThatWasRunning = undefined;
this.cpu = undefined;
}
CpuSlice.prototype = {
__proto__: Slice.prototype,
get analysisTypeName() {
return 'tracing.analysis.CpuSlice';
},
toJSON: function() {
var obj = new Object();
var keys = Object.keys(this);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (typeof this[key] == 'function')
continue;
if (key == 'cpu' || key == 'threadThatWasRunning') {
if (this[key])
obj[key] = this[key].guid;
continue;
}
obj[key] = this[key];
}
return obj;
},
getAssociatedTimeslice: function() {
if (!this.threadThatWasRunning)
return undefined;
var timeSlices = this.threadThatWasRunning.timeSlices;
for (var i = 0; i < timeSlices.length; i++) {
var timeSlice = timeSlices[i];
if (timeSlice.start !== this.start)
continue;
if (timeSlice.duration !== this.duration)
continue;
return timeSlice;
}
return undefined;
}
};
/**
* A ThreadTimeSlice is a slice of time on a specific thread where that thread
* was running on a specific CPU, or in a specific sleep state.
*
* As a thread switches moves through its life, it sometimes goes to sleep and
* can't run. Other times, its runnable but isn't actually assigned to a CPU.
* Finally, sometimes it gets put on a CPU to actually execute. Each of these
* states is represented by a ThreadTimeSlice:
*
* Sleeping or runnable: cpuOnWhichThreadWasRunning is undefined
* Running: cpuOnWhichThreadWasRunning is set.
*
* @constructor
*/
function ThreadTimeSlice(
thread, cat, title, colorId, start, args, opt_duration) {
Slice.call(this, cat, title, colorId, start, args, opt_duration);
this.thread = thread;
this.cpuOnWhichThreadWasRunning = undefined;
}
ThreadTimeSlice.prototype = {
__proto__: Slice.prototype,
get analysisTypeName() {
return 'tracing.analysis.ThreadTimeSlice';
},
toJSON: function() {
var obj = new Object();
var keys = Object.keys(this);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (typeof this[key] == 'function')
continue;
if (key == 'thread' || key == 'cpuOnWhichThreadWasRunning') {
if (this[key])
obj[key] = this[key].guid;
continue;
}
obj[key] = this[key];
}
return obj;
},
getAssociatedCpuSlice: function() {
if (!this.cpuOnWhichThreadWasRunning)
return undefined;
var cpuSlices = this.cpuOnWhichThreadWasRunning.slices;
for (var i = 0; i < cpuSlices.length; i++) {
var cpuSlice = cpuSlices[i];
if (cpuSlice.start !== this.start)
continue;
if (cpuSlice.duration !== this.duration)
continue;
return cpuSlice;
}
return undefined;
},
getCpuSliceThatTookCpu: function() {
if (this.cpuOnWhichThreadWasRunning)
return undefined;
var curIndex = this.thread.indexOfTimeSlice(this);
var cpuSliceWhenLastRunning;
while (curIndex >= 0) {
var curSlice = this.thread.timeSlices[curIndex];
if (!curSlice.cpuOnWhichThreadWasRunning) {
curIndex--;
continue;
}
cpuSliceWhenLastRunning = curSlice.getAssociatedCpuSlice();
break;
}
if (!cpuSliceWhenLastRunning)
return undefined;
var cpu = cpuSliceWhenLastRunning.cpu;
var indexOfSliceOnCpuWhenLastRunning =
cpu.indexOf(cpuSliceWhenLastRunning);
var nextRunningSlice = cpu.slices[indexOfSliceOnCpuWhenLastRunning + 1];
if (!nextRunningSlice)
return undefined;
if (Math.abs(nextRunningSlice.start - cpuSliceWhenLastRunning.end) <
0.00001)
return nextRunningSlice;
return undefined;
}
};
/**
* The Cpu represents a Cpu from the kernel's point of view.
* @constructor
*/
function Cpu(number) {
this.cpuNumber = number;
this.slices = [];
this.counters = {};
this.bounds = new base.Range();
};
Cpu.prototype = {
/**
* @return {TimlineCounter} The counter on this process named 'name',
* creating it if it doesn't exist.
*/
getOrCreateCounter: function(cat, name) {
var id;
if (cat.length)
id = cat + '.' + name;
else
id = name;
if (!this.counters[id])
this.counters[id] = new Counter(this, id, cat, name);
return this.counters[id];
},
/**
* Shifts all the timestamps inside this CPU forward by the amount
* specified.
*/
shiftTimestampsForward: function(amount) {
for (var sI = 0; sI < this.slices.length; sI++)
this.slices[sI].start = (this.slices[sI].start + amount);
for (var id in this.counters)
this.counters[id].shiftTimestampsForward(amount);
},
/**
* Updates the range based on the current slices attached to the cpu.
*/
updateBounds: function() {
this.bounds.reset();
if (this.slices.length) {
this.bounds.addValue(this.slices[0].start);
this.bounds.addValue(this.slices[this.slices.length - 1].end);
}
for (var id in this.counters) {
this.counters[id].updateBounds();
this.bounds.addRange(this.counters[id].bounds);
}
},
addCategoriesToDict: function(categoriesDict) {
for (var i = 0; i < this.slices.length; i++)
categoriesDict[this.slices[i].category] = true;
for (var id in this.counters)
categoriesDict[this.counters[id].category] = true;
},
get userFriendlyName() {
return 'CPU ' + this.cpuNumber;
},
/*
* Returns the index of the slice in the CPU's slices, or undefined.
*/
indexOf: function(cpuSlice) {
var i = base.findLowIndexInSortedArray(
this.slices,
function(slice) { return slice.start; },
cpuSlice.start);
if (this.slices[i] !== cpuSlice)
return undefined;
return i;
},
iterateAllEvents: function(callback) {
this.slices.forEach(callback);
for (var id in this.counters)
this.counters[id].iterateAllEvents(callback);
}
};
/**
* Comparison between processes that orders by cpuNumber.
*/
Cpu.compare = function(x, y) {
return x.cpuNumber - y.cpuNumber;
};
return {
Cpu: Cpu,
CpuSlice: CpuSlice,
ThreadTimeSlice: ThreadTimeSlice
};
});
// 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.
'use strict';
base.require('tracing.trace_model.event');
base.exportTo('tracing.trace_model', function() {
/**
* A snapshot of an object instance, at a given moment in time.
*
* Initialization of snapshots and instances is three phased:
*
* 1. Instances and snapshots are constructed. This happens during event
* importing. Little should be done here, because the object's data
* are still being used by the importer to reconstruct object references.
*
* 2. Instances and snapshtos are preinitialized. This happens after implicit
* objects have been found, but before any references have been found and
* switched to direct references. Thus, every snapshot stands on its own.
* This is a good time to do global field renaming and type conversion,
* e.g. recognizing domain-specific types and converting from C++ naming
* convention to JS.
*
* 3. Instances and snapshtos are initialized. At this point, {id_ref:
* '0x1000'} fields have been converted to snapshot references. This is a
* good time to generic initialization steps and argument verification.
*
* @constructor
*/
function ObjectSnapshot(objectInstance, ts, args) {
tracing.trace_model.Event.call(this);
this.objectInstance = objectInstance;
this.ts = ts;
this.args = args;
}
ObjectSnapshot.prototype = {
__proto__: tracing.trace_model.Event.prototype,
/**
* See ObjectSnapshot constructor notes on object initialization.
*/
preInitialize: function() {
},
/**
* See ObjectSnapshot constructor notes on object initialization.
*/
initialize: function() {
},
addBoundsToRange: function(range) {
range.addValue(this.ts);
}
};
ObjectSnapshot.nameToConstructorMap_ = {};
ObjectSnapshot.register = function(name, constructor) {
if (ObjectSnapshot.nameToConstructorMap_[name])
throw new Error('Constructor already registerd for ' + name);
ObjectSnapshot.nameToConstructorMap_[name] = constructor;
};
ObjectSnapshot.unregister = function(name) {
delete ObjectSnapshot.nameToConstructorMap_[name];
};
ObjectSnapshot.getConstructor = function(name) {
if (ObjectSnapshot.nameToConstructorMap_[name])
return ObjectSnapshot.nameToConstructorMap_[name];
return ObjectSnapshot;
};
return {
ObjectSnapshot: ObjectSnapshot
};
});
// 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.
'use strict';
/**
* @fileoverview Provides the ObjectSnapshot and ObjectHistory classes.
*/
base.require('base.range');
base.require('base.sorted_array_utils');
base.require('tracing.trace_model.event');
base.require('tracing.trace_model.object_snapshot');
base.exportTo('tracing.trace_model', function() {
var ObjectSnapshot = tracing.trace_model.ObjectSnapshot;
/**
* An object with a specific id, whose state has been snapshotted several
* times.
*
* @constructor
*/
function ObjectInstance(parent, id, category, name, creationTs) {
tracing.trace_model.Event.call(this);
this.parent = parent;
this.id = id;
this.category = category;
this.name = name;
this.creationTs = creationTs;
this.creationTsWasExplicit = false;
this.deletionTs = Number.MAX_VALUE;
this.deletionTsWasExplicit = false;
this.colorId = 0;
this.bounds = new base.Range();
this.snapshots = [];
this.hasImplicitSnapshots = false;
}
ObjectInstance.prototype = {
__proto__: tracing.trace_model.Event.prototype,
get typeName() {
return this.name;
},
addBoundsToRange: function(range) {
range.addRange(this.bounds);
},
addSnapshot: function(ts, args) {
if (ts < this.creationTs)
throw new Error('Snapshots must be >= instance.creationTs');
if (ts >= this.deletionTs)
throw new Error('Snapshots cannot be added after ' +
'an objects deletion timestamp.');
var lastSnapshot;
if (this.snapshots.length > 0) {
lastSnapshot = this.snapshots[this.snapshots.length - 1];
if (lastSnapshot.ts == ts)
throw new Error('Snapshots already exists at this time!');
if (ts < lastSnapshot.ts) {
throw new Error(
'Snapshots must be added in increasing timestamp order');
}
}
var snapshotConstructor =
tracing.trace_model.ObjectSnapshot.getConstructor(this.name);
var snapshot = new snapshotConstructor(this, ts, args);
this.snapshots.push(snapshot);
return snapshot;
},
wasDeleted: function(ts) {
var lastSnapshot;
if (this.snapshots.length > 0) {
lastSnapshot = this.snapshots[this.snapshots.length - 1];
if (lastSnapshot.ts > ts)
throw new Error(
'Instance cannot be deleted at ts=' +
ts + '. A snapshot exists that is older.');
}
this.deletionTs = ts;
this.deletionTsWasExplicit = true;
},
/**
* See ObjectSnapshot constructor notes on object initialization.
*/
preInitialize: function() {
for (var i = 0; i < this.snapshots.length; i++)
this.snapshots[i].preInitialize();
},
/**
* See ObjectSnapshot constructor notes on object initialization.
*/
initialize: function() {
for (var i = 0; i < this.snapshots.length; i++)
this.snapshots[i].initialize();
},
getSnapshotAt: function(ts) {
if (ts < this.creationTs) {
if (this.creationTsWasExplicit)
throw new Error('ts must be within lifetime of this instance');
return this.snapshots[0];
}
if (ts > this.deletionTs)
throw new Error('ts must be within lifetime of this instance');
var snapshots = this.snapshots;
var i = base.findLowIndexInSortedIntervals(
snapshots,
function(snapshot) { return snapshot.ts; },
function(snapshot, i) {
if (i == snapshots.length - 1)
return snapshots[i].objectInstance.deletionTs;
return snapshots[i + 1].ts - snapshots[i].ts;
},
ts);
if (i < 0) {
// Note, this is a little bit sketchy: this lets early ts point at the
// first snapshot, even before it is taken. We do this because raster
// tasks usually post before their tile snapshots are dumped. This may
// be a good line of code to re-visit if we start seeing strange and
// confusing object references showing up in the traces.
return this.snapshots[0];
}
if (i >= this.snapshots.length)
return this.snapshots[this.snapshots.length - 1];
return this.snapshots[i];
},
updateBounds: function() {
this.bounds.reset();
this.bounds.addValue(this.creationTs);
if (this.deletionTs != Number.MAX_VALUE)
this.bounds.addValue(this.deletionTs);
else if (this.snapshots.length > 0)
this.bounds.addValue(this.snapshots[this.snapshots.length - 1].ts);
},
shiftTimestampsForward: function(amount) {
this.creationTs += amount;
if (this.deletionTs != Number.MAX_VALUE)
this.deletionTs += amount;
this.snapshots.forEach(function(snapshot) {
snapshot.ts += amount;
});
}
};
ObjectInstance.nameToConstructorMap_ = {};
ObjectInstance.register = function(name, constructor) {
if (ObjectInstance.nameToConstructorMap_[name])
throw new Error('Constructor already registerd for ' + name);
ObjectInstance.nameToConstructorMap_[name] = constructor;
};
ObjectInstance.unregister = function(name) {
delete ObjectInstance.nameToConstructorMap_[name];
};
ObjectInstance.getConstructor = function(name) {
if (ObjectInstance.nameToConstructorMap_[name])
return ObjectInstance.nameToConstructorMap_[name];
return ObjectInstance;
};
return {
ObjectInstance: ObjectInstance
};
});
// 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.
'use strict';
/**
* @fileoverview Provides the TimeToObjectInstanceMap class.
*/
base.require('base.range');
base.require('base.sorted_array_utils');
base.exportTo('tracing.trace_model', function() {
/**
* Tracks all the instances associated with a given ID over its lifetime.
*
* An id can be used multiple times throughout a trace, referring to different
* objects at different times. This data structure does the bookkeeping to
* figure out what ObjectInstance is referred to at a given timestamp.
*
* @constructor
*/
function TimeToObjectInstanceMap(createObjectInstanceFunction, parent, id) {
this.createObjectInstanceFunction_ = createObjectInstanceFunction;
this.parent = parent;
this.id = id;
this.instances = [];
}
TimeToObjectInstanceMap.prototype = {
idWasCreated: function(category, name, ts) {
if (this.instances.length == 0) {
this.instances.push(this.createObjectInstanceFunction_(
this.parent, this.id, category, name, ts));
this.instances[0].creationTsWasExplicit = true;
return this.instances[0];
}
var lastInstance = this.instances[this.instances.length - 1];
if (ts < lastInstance.deletionTs) {
throw new Error('Mutation of the TimeToObjectInstanceMap must be ' +
'done in ascending timestamp order.');
}
lastInstance = this.createObjectInstanceFunction_(
this.parent, this.id, category, name, ts);
lastInstance.creationTsWasExplicit = true;
this.instances.push(lastInstance);
return lastInstance;
},
addSnapshot: function(category, name, ts, args) {
if (this.instances.length == 0) {
this.instances.push(this.createObjectInstanceFunction_(
this.parent, this.id, category, name, ts));
}
var i = base.findLowIndexInSortedIntervals(
this.instances,
function(inst) { return inst.creationTs; },
function(inst) { return inst.deletionTs - inst.creationTs; },
ts);
var instance;
if (i < 0) {
instance = this.instances[0];
if (ts > instance.deletionTs ||
instance.creationTsWasExplicit) {
throw new Error(
'At the provided timestamp, no instance was still alive');
}
if (instance.snapshots.length != 0) {
throw new Error(
'Cannot shift creationTs forward, ' +
'snapshots have been added. First snap was at ts=' +
instance.snapshots[0].ts + ' and creationTs was ' +
instance.creationTs);
}
instance.creationTs = ts;
} else if (i >= this.instances.length) {
instance = this.instances[this.instances.length - 1];
if (ts >= instance.deletionTs) {
// The snap is added after our oldest and deleted instance. This means
// that this is a new implicit instance.
instance = this.createObjectInstanceFunction_(
this.parent, this.id, category, name, ts);
this.instances.push(instance);
} else {
// If the ts is before the last objects deletion time, then the caller
// is trying to add a snapshot when there may have been an instance
// alive. In that case, try to move an instance's creationTs to
// include this ts, provided that it has an implicit creationTs.
// Search backward from the right for an instance that was definitely
// deleted before this ts. Any time an instance is found that has a
// moveable creationTs
var lastValidIndex;
for (var i = this.instances.length - 1; i >= 0; i--) {
var tmp = this.instances[i];
if (ts >= tmp.deletionTs)
break;
if (tmp.creationTsWasExplicit == false && tmp.snapshots.length == 0)
lastValidIndex = i;
}
if (lastValidIndex === undefined) {
throw new Error(
'Cannot add snapshot. No instance was alive that was mutable.');
}
instance = this.instances[lastValidIndex];
instance.creationTs = ts;
}
} else {
instance = this.instances[i];
}
return instance.addSnapshot(ts, args);
},
get lastInstance() {
if (this.instances.length == 0)
return undefined;
return this.instances[this.instances.length - 1];
},
idWasDeleted: function(category, name, ts) {
if (this.instances.length == 0) {
this.instances.push(this.createObjectInstanceFunction_(
this.parent, this.id, category, name, ts));
}
var lastInstance = this.instances[this.instances.length - 1];
if (ts < lastInstance.creationTs)
throw new Error('Cannot delete a id before it was crated');
if (lastInstance.deletionTs == Number.MAX_VALUE) {
lastInstance.wasDeleted(ts);
return lastInstance;
}
if (ts < lastInstance.deletionTs)
throw new Error('id was already deleted earlier.');
// A new instance was deleted with no snapshots in-between.
// Create an instance then kill it.
lastInstance = this.createObjectInstanceFunction_(
this.parent, this.id, category, name, ts);
this.instances.push(lastInstance);
return lastInstance;
},
getInstanceAt: function(ts) {
var i = base.findLowIndexInSortedIntervals(
this.instances,
function(inst) { return inst.creationTs; },
function(inst) { return inst.deletionTs - inst.creationTs; },
ts);
if (i < 0) {
if (this.instances[0].creationTsWasExplicit)
return undefined;
return this.instances[0];
} else if (i >= this.instances.length) {
return undefined;
}
return this.instances[i];
}
};
return {
TimeToObjectInstanceMap: TimeToObjectInstanceMap
};
});
// 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.
'use strict';
/**
* @fileoverview Provides the ObjectCollection class.
*/
base.require('base.utils');
base.require('base.range');
base.require('base.sorted_array_utils');
base.require('tracing.trace_model.object_instance');
base.require('tracing.trace_model.time_to_object_instance_map');
base.exportTo('tracing.trace_model', function() {
var ObjectInstance = tracing.trace_model.ObjectInstance;
/**
* A collection of object instances and their snapshots, accessible by id and
* time, or by object name.
*
* @constructor
*/
function ObjectCollection(parent) {
this.parent = parent;
this.bounds = new base.Range();
this.instanceMapsById_ = {}; // id -> TimeToObjectInstanceMap
this.instancesByTypeName_ = {};
this.createObjectInstance_ = this.createObjectInstance_.bind(this);
}
ObjectCollection.prototype = {
__proto__: Object.prototype,
createObjectInstance_: function(parent, id, category, name, creationTs) {
var constructor = tracing.trace_model.ObjectInstance.getConstructor(name);
var instance = new constructor(parent, id, category, name, creationTs);
var typeName = instance.typeName;
var instancesOfTypeName = this.instancesByTypeName_[typeName];
if (!instancesOfTypeName) {
instancesOfTypeName = [];
this.instancesByTypeName_[typeName] = instancesOfTypeName;
}
instancesOfTypeName.push(instance);
return instance;
},
getOrCreateInstanceMap_: function(id) {
var instanceMap = this.instanceMapsById_[id];
if (instanceMap)
return instanceMap;
instanceMap = new tracing.trace_model.TimeToObjectInstanceMap(
this.createObjectInstance_, this.parent, id);
this.instanceMapsById_[id] = instanceMap;
return instanceMap;
},
idWasCreated: function(id, category, name, ts) {
var instanceMap = this.getOrCreateInstanceMap_(id);
return instanceMap.idWasCreated(category, name, ts);
},
addSnapshot: function(id, category, name, ts, args) {
var instanceMap = this.getOrCreateInstanceMap_(id, category, name, ts);
var snapshot = instanceMap.addSnapshot(category, name, ts, args);
if (snapshot.objectInstance.category != category) {
throw new Error('Added snapshot with different category ' +
'than when it was created');
}
if (snapshot.objectInstance.name != name) {
throw new Error('Added snapshot with different name than ' +
'when it was created');
}
return snapshot;
},
idWasDeleted: function(id, category, name, ts) {
var instanceMap = this.getOrCreateInstanceMap_(id, category, name, ts);
var deletedInstance = instanceMap.idWasDeleted(category, name, ts);
if (!deletedInstance)
return;
if (deletedInstance.category != category) {
throw new Error('Deleting an object with a different category ' +
'than when it was created');
}
if (deletedInstance.name != name) {
throw new Error('Deleting an object with a different name than ' +
'when it was created');
}
},
autoDeleteObjects: function(maxTimestamp) {
base.iterItems(this.instanceMapsById_, function(id, i2imap) {
var lastInstance = i2imap.lastInstance;
if (lastInstance.deletionTs != Number.MAX_VALUE)
return;
i2imap.idWasDeleted(
lastInstance.category, lastInstance.name, maxTimestamp);
// idWasDeleted will cause lastInstance.deletionTsWasExplicit to be set
// to true. Unset it here.
lastInstance.deletionTsWasExplicit = false;
});
},
getObjectInstanceAt: function(id, ts) {
var instanceMap = this.instanceMapsById_[id];
if (!instanceMap)
return undefined;
return instanceMap.getInstanceAt(ts);
},
getSnapshotAt: function(id, ts) {
var instance = this.getObjectInstanceAt(id, ts);
if (!instance)
return undefined;
return instance.getSnapshotAt(ts);
},
iterObjectInstances: function(iter, opt_this) {
opt_this = opt_this || this;
base.iterItems(this.instanceMapsById_, function(id, i2imap) {
i2imap.instances.forEach(iter, opt_this);
});
},
getAllObjectInstances: function() {
var instances = [];
this.iterObjectInstances(function(i) { instances.push(i); });
return instances;
},
getAllInstancesNamed: function(name) {
return this.instancesByTypeName_[name];
},
getAllInstancesByTypeName: function() {
return this.instancesByTypeName_;
},
preInitializeAllObjects: function() {
this.iterObjectInstances(function(instance) {
instance.preInitialize();
});
},
initializeAllObjects: function() {
this.iterObjectInstances(function(instance) {
instance.initialize();
});
},
initializeInstances: function() {
this.iterObjectInstances(function(instance) {
instance.initialize();
});
},
updateBounds: function() {
this.bounds.reset();
this.iterObjectInstances(function(instance) {
instance.updateBounds();
this.bounds.addRange(instance.bounds);
}, this);
},
shiftTimestampsForward: function(amount) {
this.iterObjectInstances(function(instance) {
instance.shiftTimestampsForward(amount);
});
},
addCategoriesToDict: function(categoriesDict) {
this.iterObjectInstances(function(instance) {
categoriesDict[instance.category] = true;
});
},
toJSON: function() {
// TODO(nduca): Implement this if we need it.
return {};
},
iterateAllEvents: function(callback) {
this.iterObjectInstances(function(instance) {
callback(instance);
instance.snapshots.forEach(callback);
});
}
};
return {
ObjectCollection: ObjectCollection
};
});
// 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.
'use strict';
base.require('tracing.trace_model.slice');
/**
* @fileoverview Provides the AsyncSlice class.
*/
base.exportTo('tracing.trace_model', function() {
/**
* A AsyncSlice represents an interval of time during which an
* asynchronous operation is in progress. An AsyncSlice consumes no CPU time
* itself and so is only associated with Threads at its start and end point.
*
* @constructor
*/
function AsyncSlice(category, title, colorId, start, args) {
tracing.trace_model.Slice.apply(this, arguments);
};
AsyncSlice.prototype = {
__proto__: tracing.trace_model.Slice.prototype,
toJSON: function() {
var obj = new Object();
var keys = Object.keys(this);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (typeof this[key] == 'function')
continue;
if (key == 'startThread' || key == 'endThread') {
obj[key] = this[key].guid;
continue;
}
obj[key] = this[key];
}
return obj;
},
id: undefined,
startThread: undefined,
endThread: undefined,
subSlices: undefined
};
return {
AsyncSlice: AsyncSlice
};
});
// 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.
'use strict';
/**
* @fileoverview Provides the AsyncSliceGroup class.
*/
base.require('base.range');
base.require('tracing.trace_model.async_slice');
base.exportTo('tracing.trace_model', function() {
/**
* A group of AsyncSlices.
* @constructor
*/
function AsyncSliceGroup() {
this.slices = [];
this.bounds = new base.Range();
}
AsyncSliceGroup.prototype = {
__proto__: Object.prototype,
/**
* Helper function that pushes the provided slice onto the slices array.
*/
push: function(slice) {
this.slices.push(slice);
},
/**
* @return {Number} The number of slices in this group.
*/
get length() {
return this.slices.length;
},
/**
* Shifts all the timestamps inside this group forward by the amount
* specified.
*/
shiftTimestampsForward: function(amount) {
for (var sI = 0; sI < this.slices.length; sI++) {
var slice = this.slices[sI];
slice.start = (slice.start + amount);
for (var sJ = 0; sJ < slice.subSlices.length; sJ++)
slice.subSlices[sJ].start += amount;
}
},
/**
* Updates the bounds for this group based on the slices it contains.
*/
updateBounds: function() {
this.bounds.reset();
for (var i = 0; i < this.slices.length; i++) {
this.bounds.addValue(this.slices[i].start);
this.bounds.addValue(this.slices[i].end);
}
},
/**
* Breaks up this group into slices based on start thread.
*
* @return {Array} An array of AsyncSliceGroups where each group has
* slices that started on the same thread.
*/
computeSubGroups: function() {
var subGroupsByGUID = {};
for (var i = 0; i < this.slices.length; ++i) {
var slice = this.slices[i];
var sliceGUID = slice.startThread.guid;
if (!subGroupsByGUID[sliceGUID])
subGroupsByGUID[sliceGUID] = new AsyncSliceGroup();
subGroupsByGUID[sliceGUID].slices.push(slice);
}
var groups = [];
for (var guid in subGroupsByGUID) {
var group = subGroupsByGUID[guid];
group.updateBounds();
groups.push(group);
}
return groups;
},
iterateAllEvents: function(callback) {
for (var i = 0; i < this.slices.length; i++) {
var slice = this.slices[i];
callback(slice);
if (slice.subSlices)
slice.subSlices.forEach(callback);
}
}
};
return {
AsyncSliceGroup: AsyncSliceGroup
};
});
// 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.
'use strict';
base.require('tracing.trace_model.timed_event');
/**
* @fileoverview Provides the Sample class.
*/
base.exportTo('tracing.trace_model', function() {
/**
* A Sample represents a sample taken at an instant in time
* plus parameters associated with that sample.
*
* @constructor
*/
function Sample(category, title, colorId, start, args) {
tracing.trace_model.TimedEvent.call(this, start);
this.category = category || '';
this.title = title;
this.colorId = colorId;
this.args = args;
}
Sample.prototype = {
__proto__: tracing.trace_model.TimedEvent.prototype
};
return {
Sample: Sample
};
});
// 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.
'use strict';
base.require('tracing.trace_model.event');
/**
* @fileoverview Provides color scheme related functions.
*/
base.exportTo('tracing', function() {
var SelectionState = tracing.trace_model.SelectionState;
// The color palette is split in half, with the upper
// half of the palette being the "highlighted" verison
// of the base color. So, color 7's highlighted form is
// 7 + (palette.length / 2).
//
// These bright versions of colors are automatically generated
// from the base colors.
//
// Within the color palette, there are "regular" colors,
// which can be used for random color selection, and
// reserved colors, which are used when specific colors
// need to be used, e.g. where red is desired.
var paletteBase = [
{r: 138, g: 113, b: 152},
{r: 175, g: 112, b: 133},
{r: 127, g: 135, b: 225},
{r: 93, g: 81, b: 137},
{r: 116, g: 143, b: 119},
{r: 178, g: 214, b: 122},
{r: 87, g: 109, b: 147},
{r: 119, g: 155, b: 95},
{r: 114, g: 180, b: 160},
{r: 132, g: 85, b: 103},
{r: 157, g: 210, b: 150},
{r: 148, g: 94, b: 86},
{r: 164, g: 108, b: 138},
{r: 139, g: 191, b: 150},
{r: 110, g: 99, b: 145},
{r: 80, g: 129, b: 109},
{r: 125, g: 140, b: 149},
{r: 93, g: 124, b: 132},
{r: 140, g: 85, b: 140},
{r: 104, g: 163, b: 162},
{r: 132, g: 141, b: 178},
{r: 131, g: 105, b: 147},
{r: 135, g: 183, b: 98},
{r: 152, g: 134, b: 177},
{r: 141, g: 188, b: 141},
{r: 133, g: 160, b: 210},
{r: 126, g: 186, b: 148},
{r: 112, g: 198, b: 205},
{r: 180, g: 122, b: 195},
{r: 203, g: 144, b: 152},
// Reserved Entires
{r: 182, g: 125, b: 143},
{r: 126, g: 200, b: 148},
{r: 133, g: 160, b: 210},
{r: 240, g: 240, b: 240},
{r: 199, g: 155, b: 125}];
// Make sure this number tracks the number of reserved entries in the
// palette.
var numReservedColorIds = 5;
function brighten(c) {
var k;
if (c.r >= 240 && c.g >= 240 && c.b >= 240)
k = -0.20;
else
k = 0.45;
return {r: Math.min(255, c.r + Math.floor(c.r * k)),
g: Math.min(255, c.g + Math.floor(c.g * k)),
b: Math.min(255, c.b + Math.floor(c.b * k))};
}
function colorToRGBString(c) {
return 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')';
}
function colorToRGBAString(c, a) {
return 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',' + a + ')';
}
/**
* The number of color IDs that getStringColorId can choose from.
*/
var numRegularColorIds = paletteBase.length - numReservedColorIds;
var highlightIdBoost = paletteBase.length;
var paletteRaw = paletteBase.concat(paletteBase.map(brighten));
var palette = paletteRaw.map(colorToRGBString);
/**
* Computes a simplistic hashcode of the provide name. Used to chose colors
* for slices.
* @param {string} name The string to hash.
*/
function getStringHash(name) {
var hash = 0;
for (var i = 0; i < name.length; ++i)
hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF;
return hash;
}
/**
* Gets the color palette.
*/
function getColorPalette() {
return palette;
}
/**
* @return {Number} The value to add to a color ID to get its highlighted
* colro ID. E.g. 7 + getPaletteHighlightIdBoost() yields a brightened from
* of 7's base color.
*/
function getColorPaletteHighlightIdBoost() {
return highlightIdBoost;
}
/**
* @param {String} name The color name.
* @return {Number} The color ID for the given color name.
*/
function getColorIdByName(name) {
if (name == 'iowait')
return numRegularColorIds;
if (name == 'running')
return numRegularColorIds + 1;
if (name == 'runnable')
return numRegularColorIds + 2;
if (name == 'sleeping')
return numRegularColorIds + 3;
if (name == 'UNKNOWN')
return numRegularColorIds + 4;
throw new Error('Unrecognized color ') + name;
}
// Previously computed string color IDs. They are based on a stable hash, so
// it is safe to save them throughout the program time.
var stringColorIdCache = {};
/**
* @return {Number} A color ID that is stably associated to the provided via
* the getStringHash method. The color ID will be chosen from the regular
* ID space only, e.g. no reserved ID will be used.
*/
function getStringColorId(string) {
if (stringColorIdCache[string] === undefined) {
var hash = getStringHash(string);
stringColorIdCache[string] = hash % numRegularColorIds;
}
return stringColorIdCache[string];
}
/**
* Provides methods to get view values for events.
*/
var EventPresenter = {
getAlpha_: function(event) {
if (event.selectionState === SelectionState.DIMMED)
return 0.3;
return 1.0;
},
getColorIdOffset_: function(event) {
if (event.selectionState === SelectionState.SELECTED)
return highlightIdBoost;
return 0;
},
getTextColor: function(event) {
if (event.selectionState === SelectionState.DIMMED)
return 'rgb(60,60,60)';
return 'rgb(0,0,0)';
},
getSliceColorId: function(slice) {
return slice.colorId + this.getColorIdOffset_(slice);
},
getSliceAlpha: function(slice, async) {
var alpha = this.getAlpha_(slice);
if (async)
alpha *= 0.3;
return alpha;
},
getInstantSliceColor: function(instant) {
var colorId = instant.colorId + this.getColorIdOffset_(instant);
return colorToRGBAString(paletteRaw[colorId], this.getAlpha_(instant));
},
getObjectInstanceColor: function(instance) {
var colorId = instance.colorId + this.getColorIdOffset_(instance);
return colorToRGBAString(paletteRaw[colorId], 0.25);
},
getObjectSnapshotColor: function(snapshot) {
var colorId =
snapshot.objectInstance.colorId + this.getColorIdOffset_(snapshot);
return palette[colorId];
},
getCounterSeriesColor: function(colorId, selectionState) {
return colorToRGBAString(
paletteRaw[colorId],
this.getAlpha_({selectionState: selectionState}));
},
getBarSnapshotColor: function(snapshot, offset) {
var colorId =
(snapshot.objectInstance.colorId + offset) % numRegularColorIds;
colorId += this.getColorIdOffset_(snapshot);
return colorToRGBAString(paletteRaw[colorId], this.getAlpha_(snapshot));
}
};
return {
getColorPalette: getColorPalette,
getColorPaletteHighlightIdBoost: getColorPaletteHighlightIdBoost,
getColorIdByName: getColorIdByName,
getStringHash: getStringHash,
getStringColorId: getStringColorId,
EventPresenter: EventPresenter
};
});
// 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.
'use strict';
/**
* @fileoverview Provides the SliceGroup class.
*/
base.require('base.range');
base.require('tracing.trace_model.slice');
base.require('tracing.color_scheme');
base.require('tracing.filter');
base.exportTo('tracing.trace_model', function() {
var Slice = tracing.trace_model.Slice;
/**
* A group of Slices, plus code to create them from B/E events, as
* well as arrange them into subRows.
*
* Do not mutate the slices array directly. Modify it only by
* SliceGroup mutation methods.
*
* @constructor
* @param {function(new:Slice, category, title, colorId, start, args)=}
* opt_sliceConstructor The constructor to use when creating slices.
*/
function SliceGroup(opt_sliceConstructor) {
var sliceConstructor = opt_sliceConstructor || Slice;
this.sliceConstructor = sliceConstructor;
this.openPartialSlices_ = [];
this.slices = [];
this.bounds = new base.Range();
this.topLevelSlices = [];
}
SliceGroup.prototype = {
__proto__: Object.prototype,
/**
* @return {Number} The number of slices in this group.
*/
get length() {
return this.slices.length;
},
/**
* Helper function that pushes the provided slice onto the slices array.
* @param {Slice} slice The slice to be added to the slices array.
*/
pushSlice: function(slice) {
this.slices.push(slice);
return slice;
},
/**
* Helper function that pushes the provided slice onto the slices array.
* @param {Array.<Slice>} slices An array of slices to be added.
*/
pushSlices: function(slices) {
this.slices.push.apply(this.slices, slices);
},
/**
* Push an instant event into the slice list.
* @param {tracing.trace_model.InstantEvent} instantEvent The instantEvent.
*/
pushInstantEvent: function(instantEvent) {
this.slices.push(instantEvent);
},
/**
* Opens a new slice in the group's slices.
*
* Calls to beginSlice and
* endSlice must be made with non-monotonically-decreasing timestamps.
*
* @param {String} category Category name of the slice to add.
* @param {String} title Title of the slice to add.
* @param {Number} ts The timetsamp of the slice, in milliseconds.
* @param {Object.<string, Object>=} opt_args Arguments associated with
* the slice.
*/
beginSlice: function(category, title, ts, opt_args, opt_tts) {
if (this.openPartialSlices_.length) {
var prevSlice = this.openPartialSlices_[
this.openPartialSlices_.length - 1];
if (ts < prevSlice.start)
throw new Error('Slices must be added in increasing timestamp order');
}
var colorId = tracing.getStringColorId(title);
var slice = new this.sliceConstructor(category, title, colorId, ts,
opt_args ? opt_args : {}, null,
opt_tts);
this.openPartialSlices_.push(slice);
return slice;
},
isTimestampValidForBeginOrEnd: function(ts) {
if (!this.openPartialSlices_.length)
return true;
var top = this.openPartialSlices_[this.openPartialSlices_.length - 1];
return ts >= top.start;
},
/**
* @return {Number} The number of beginSlices for which an endSlice has not
* been issued.
*/
get openSliceCount() {
return this.openPartialSlices_.length;
},
get mostRecentlyOpenedPartialSlice() {
if (!this.openPartialSlices_.length)
return undefined;
return this.openPartialSlices_[this.openPartialSlices_.length - 1];
},
/**
* Ends the last begun slice in this group and pushes it onto the slice
* array.
*
* @param {Number} ts Timestamp when the slice ended.
* @return {Slice} slice.
*/
endSlice: function(ts, opt_tts) {
if (!this.openSliceCount)
throw new Error('endSlice called without an open slice');
var slice = this.openPartialSlices_[this.openSliceCount - 1];
this.openPartialSlices_.splice(this.openSliceCount - 1, 1);
if (ts < slice.start)
throw new Error('Slice ' + slice.title +
' end time is before its start.');
slice.duration = ts - slice.start;
if (opt_tts && slice.threadStart)
slice.threadTime = opt_tts - slice.threadStart;
this.pushSlice(slice);
return slice;
},
/**
* Push a complete event as a Slice into the slice list.
* The timestamp can be in any order.
*
* @param {String} category Category name of the slice to add.
* @param {String} title Title of the slice to add.
* @param {Number} ts The timetsamp of the slice, in milliseconds.
* @param {Number} duration The duration of the slice, in milliseconds.
* @param {Object.<string, Object>=} opt_args Arguments associated with
* the slice.
*/
pushCompleteSlice: function(category, title, ts, duration, opt_args) {
var colorId = tracing.getStringColorId(title);
var slice = new this.sliceConstructor(category, title, colorId, ts,
opt_args ? opt_args : {},
duration);
this.pushSlice(slice);
return slice;
},
/**
* Closes any open slices.
* @param {Number=} opt_maxTimestamp The end time to use for the closed
* slices. If not provided,
* the max timestamp for this slice is provided.
*/
autoCloseOpenSlices: function(opt_maxTimestamp) {
if (!opt_maxTimestamp) {
this.updateBounds();
opt_maxTimestamp = this.bounds.max;
}
while (this.openSliceCount > 0) {
var slice = this.endSlice(opt_maxTimestamp);
slice.didNotFinish = true;
}
},
/**
* Shifts all the timestamps inside this group forward by the amount
* specified.
*/
shiftTimestampsForward: function(amount) {
for (var sI = 0; sI < this.slices.length; sI++) {
var slice = this.slices[sI];
slice.start = (slice.start + amount);
}
for (var sI = 0; sI < this.openPartialSlices_.length; sI++) {
var slice = this.openPartialSlices_[i];
slice.start = (slice.start + amount);
}
},
/**
* Updates the bounds for this group based on the slices it contains.
*/
updateBounds: function() {
this.bounds.reset();
for (var i = 0; i < this.slices.length; i++) {
this.bounds.addValue(this.slices[i].start);
this.bounds.addValue(this.slices[i].end);
}
if (this.openPartialSlices_.length) {
this.bounds.addValue(this.openPartialSlices_[0].start);
this.bounds.addValue(
this.openPartialSlices_[this.openPartialSlices_.length - 1].start);
}
},
copySlice: function(slice) {
var newSlice = new this.sliceConstructor(slice.category, slice.title,
slice.colorId, slice.start,
slice.args, slice.duration);
newSlice.didNotFinish = slice.didNotFinish;
return newSlice;
},
iterateAllEvents: function(callback) {
this.slices.forEach(callback);
this.openPartialSlices_.forEach(callback);
},
/**
* Construct subSlices for this group.
* Populate the group topLevelSlices, parent slices get a subSlices[]
* and a selfTime, child slices get a parentSlice reference.
*/
createSubSlices: function() {
function addSliceIfBounds(root, child) {
// Because we know that the start time of child is >= the start time
// of all other slices seen so far, we can just check the last slice
// of each row for bounding.
if (child.start >= root.start && child.end <= root.end) {
if (root.subSlices && root.subSlices.length > 0) {
if (addSliceIfBounds(root.subSlices[root.subSlices.length - 1],
child))
return true;
}
if (!root.selfTime)
root.selfTime = root.duration;
child.parentSlice = root;
if (!root.subSlices)
root.subSlices = [];
root.subSlices.push(child);
root.selfTime -= child.duration;
return true;
}
return false;
}
if (!this.slices.length)
return;
var ops = [];
for (var i = 0; i < this.slices.length; i++) {
if (this.slices[i].subSlices)
this.slices[i].subSlices.splice(0,
this.slices[i].subSlices.length);
ops.push(i);
}
var groupSlices = this.slices;
ops.sort(function(ix, iy) {
var x = groupSlices[ix];
var y = groupSlices[iy];
if (x.start != y.start)
return x.start - y.start;
// Elements get inserted into the slices array in order of when the
// slices end. Because slices must be properly nested, we break
// start-time ties by assuming that the elements appearing earlier
// in the slices array (and thus ending earlier) start later.
return iy - ix;
});
var rootSlice = this.slices[ops[0]];
this.topLevelSlices = [];
this.topLevelSlices.push(rootSlice);
for (var i = 1; i < ops.length; i++) {
var slice = this.slices[ops[i]];
if (!addSliceIfBounds(rootSlice, slice)) {
rootSlice = slice;
this.topLevelSlices.push(rootSlice);
}
}
}
};
/**
* Merge two slice groups.
*
* If the two groups do not nest properly some of the slices of groupB will
* be split to accomodate the improper nesting. This is done to accomodate
* combined kernel and userland call stacks on Android. Because userland
* tracing is done by writing to the trace_marker file, the kernel calls
* that get invoked as part of that write may not be properly nested with
* the userland call trace. For example the following sequence may occur:
*
* kernel enter sys_write (the write to trace_marker)
* user enter some_function
* kernel exit sys_write
* ...
* kernel enter sys_write (the write to trace_marker)
* user exit some_function
* kernel exit sys_write
*
* This is handled by splitting the sys_write call into two slices as
* follows:
*
* | sys_write | some_function | sys_write (cont.) |
* | sys_write (cont.) | | sys_write |
*
* The colorId of both parts of the split slices are kept the same, and the
* " (cont.)" suffix is appended to the later parts of a split slice.
*
* The two input SliceGroups are not modified by this, and the merged
* SliceGroup will contain a copy of each of the input groups' slices (those
* copies may be split).
*/
SliceGroup.merge = function(groupA, groupB) {
// This is implemented by traversing the two slice groups in reverse
// order. The slices in each group are sorted by ascending end-time, so
// we must do the traversal from back to front in order to maintain the
// sorting.
//
// We traverse the two groups simultaneously, merging as we go. At each
// iteration we choose the group from which to take the next slice based
// on which group's next slice has the greater end-time. During this
// traversal we maintain a stack of currently "open" slices for each input
// group. A slice is considered "open" from the time it gets reached in
// our input group traversal to the time we reach an slice in this
// traversal with an end-time before the start time of the "open" slice.
//
// Each time a slice from groupA is opened or closed (events corresponding
// to the end-time and start-time of the input slice, respectively) we
// split all of the currently open slices from groupB.
if (groupA.openPartialSlices_.length > 0) {
throw new Error('groupA has open partial slices');
}
if (groupB.openPartialSlices_.length > 0) {
throw new Error('groupB has open partial slices');
}
var result = new SliceGroup();
var slicesA = groupA.slices;
var slicesB = groupB.slices;
var idxA = slicesA.length - 1;
var idxB = slicesB.length - 1;
var openA = [];
var openB = [];
var splitOpenSlices = function(when) {
for (var i = 0; i < openB.length; i++) {
var oldSlice = openB[i];
var oldEnd = oldSlice.end;
if (when < oldSlice.start || oldEnd < when) {
throw new Error('slice should not be split');
}
var newSlice = result.copySlice(oldSlice);
oldSlice.start = when;
oldSlice.duration = oldEnd - when;
oldSlice.title += ' (cont.)';
newSlice.duration = when - newSlice.start;
openB[i] = newSlice;
result.pushSlice(newSlice);
}
};
var closeOpenSlices = function(upTo) {
while (openA.length > 0 || openB.length > 0) {
var nextA = openA[openA.length - 1];
var nextB = openB[openB.length - 1];
var startA = nextA && nextA.start;
var startB = nextB && nextB.start;
if ((startA === undefined || startA < upTo) &&
(startB === undefined || startB < upTo)) {
return;
}
if (startB === undefined || startA > startB) {
splitOpenSlices(startA);
openA.pop();
} else {
openB.pop();
}
}
};
while (idxA >= 0 || idxB >= 0) {
var sA = slicesA[idxA];
var sB = slicesB[idxB];
var nextSlice, isFromB;
if (sA === undefined || (sB !== undefined && sA.end < sB.end)) {
nextSlice = result.copySlice(sB);
isFromB = true;
idxB--;
} else {
nextSlice = result.copySlice(sA);
isFromB = false;
idxA--;
}
closeOpenSlices(nextSlice.end);
result.pushSlice(nextSlice);
if (isFromB) {
openB.push(nextSlice);
} else {
splitOpenSlices(nextSlice.end);
openA.push(nextSlice);
}
}
closeOpenSlices();
result.slices.reverse();
return result;
};
return {
SliceGroup: SliceGroup
};
});
// 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.
'use strict';
/**
* @fileoverview Provides the Thread class.
*/
base.require('base.guid');
base.require('base.range');
base.require('tracing.trace_model.slice');
base.require('tracing.trace_model.slice_group');
base.require('tracing.trace_model.async_slice_group');
base.require('tracing.trace_model.sample');
base.exportTo('tracing.trace_model', function() {
var Slice = tracing.trace_model.Slice;
var SliceGroup = tracing.trace_model.SliceGroup;
var AsyncSlice = tracing.trace_model.AsyncSlice;
var AsyncSliceGroup = tracing.trace_model.AsyncSliceGroup;
/**
* A ThreadSlice represents an interval of time on a thread resource
* with associated nestinged slice information.
*
* ThreadSlices are typically associated with a specific trace event pair on a
* specific thread.
* For example,
* TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
* TRACE_EVENT_END0() at time=0.3ms
* This results in a single slice from 0.1 with duration 0.2 on a
* specific thread.
*
* @constructor
*/
function ThreadSlice(cat, title, colorId, start, args, opt_duration,
opt_threadStart) {
Slice.call(this, cat, title, colorId, start, args, opt_duration,
opt_threadStart);
// Do not modify this directly.
// subSlices is configured by SliceGroup.rebuildSubRows_.
this.subSlices = [];
}
ThreadSlice.prototype = {
__proto__: Slice.prototype
};
/**
* A Thread stores all the trace events collected for a particular
* thread. We organize the synchronous slices on a thread by "subrows," where
* subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on.
* The asynchronous slices are stored in an AsyncSliceGroup object.
*
* The slices stored on a Thread should be instances of
* ThreadSlice.
*
* @constructor
*/
function Thread(parent, tid) {
this.guid_ = base.GUID.allocate();
if (!parent)
throw new Error('Parent must be provided.');
this.parent = parent;
this.sortIndex = 0;
this.tid = tid;
this.sliceGroup = new SliceGroup(ThreadSlice);
this.timeSlices = undefined;
this.samples_ = [];
this.kernelSliceGroup = new SliceGroup();
this.asyncSliceGroup = new AsyncSliceGroup();
this.bounds = new base.Range();
this.ephemeralSettings = {};
}
Thread.prototype = {
/*
* @return {Number} A globally unique identifier for this counter.
*/
get guid() {
return this.guid_;
},
compareTo: function(that) {
return Thread.compare(this, that);
},
toJSON: function() {
var obj = new Object();
var keys = Object.keys(this);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (typeof this[key] == 'function')
continue;
if (key == 'parent') {
obj[key] = this[key].guid;
continue;
}
obj[key] = this[key];
}
return obj;
},
/**
* Adds a new sample in the thread's samples.
*
* Calls to addSample must be made with non-monotonically-decreasing
* timestamps.
*
* @param {String} category Category of the sample to add.
* @param {String} title Title of the sample to add.
* @param {Number} ts The timetsamp of the sample, in milliseconds.
* @param {Object.<string, Object>=} opt_args Arguments associated with
* the sample.
*/
addSample: function(category, title, ts, opt_args) {
if (this.samples_.length) {
var lastSample = this.samples_[this.samples_.length - 1];
if (ts < lastSample.start) {
throw new
Error('Samples must be added in increasing timestamp order.');
}
}
var colorId = tracing.getStringColorId(title);
var sample = new tracing.trace_model.Sample(category, title, colorId, ts,
opt_args ? opt_args : {});
this.samples_.push(sample);
return sample;
},
/**
* Returns the array of samples added to this thread. If no samples
* have been added, an empty array is returned.
*
* @return {Array<Sample>} array of samples.
*/
get samples() {
return this.samples_;
},
/**
* Name of the thread, if present.
*/
name: undefined,
/**
* Shifts all the timestamps inside this thread forward by the amount
* specified.
*/
shiftTimestampsForward: function(amount) {
this.sliceGroup.shiftTimestampsForward(amount);
if (this.timeSlices) {
for (var i = 0; i < this.timeSlices.length; i++) {
var slice = this.timeSlices[i];
slice.start += amount;
}
}
if (this.samples_.length) {
for (var i = 0; i < this.samples_.length; i++) {
var sample = this.samples_[i];
sample.start += amount;
}
}
this.kernelSliceGroup.shiftTimestampsForward(amount);
this.asyncSliceGroup.shiftTimestampsForward(amount);
},
/**
* Determins whether this thread is empty. If true, it usually implies
* that it should be pruned from the model.
*/
get isEmpty() {
if (this.sliceGroup.length)
return false;
if (this.sliceGroup.openSliceCount)
return false;
if (this.timeSlices && this.timeSlices.length)
return false;
if (this.kernelSliceGroup.length)
return false;
if (this.asyncSliceGroup.length)
return false;
if (this.samples_.length)
return false;
return true;
},
/**
* Updates the bounds based on the
* current objects associated with the thread.
*/
updateBounds: function() {
this.bounds.reset();
this.sliceGroup.updateBounds();
this.bounds.addRange(this.sliceGroup.bounds);
this.kernelSliceGroup.updateBounds();
this.bounds.addRange(this.kernelSliceGroup.bounds);
this.asyncSliceGroup.updateBounds();
this.bounds.addRange(this.asyncSliceGroup.bounds);
if (this.timeSlices && this.timeSlices.length) {
this.bounds.addValue(this.timeSlices[0].start);
this.bounds.addValue(
this.timeSlices[this.timeSlices.length - 1].end);
}
if (this.samples_.length) {
this.bounds.addValue(this.samples_[0].start);
this.bounds.addValue(
this.samples_[this.samples_.length - 1].end);
}
},
addCategoriesToDict: function(categoriesDict) {
for (var i = 0; i < this.sliceGroup.length; i++)
categoriesDict[this.sliceGroup.slices[i].category] = true;
for (var i = 0; i < this.kernelSliceGroup.length; i++)
categoriesDict[this.kernelSliceGroup.slices[i].category] = true;
for (var i = 0; i < this.asyncSliceGroup.length; i++)
categoriesDict[this.asyncSliceGroup.slices[i].category] = true;
for (var i = 0; i < this.samples_.length; i++)
categoriesDict[this.samples_[i].category] = true;
},
autoCloseOpenSlices: function(opt_maxTimestamp) {
this.sliceGroup.autoCloseOpenSlices(opt_maxTimestamp);
this.kernelSliceGroup.autoCloseOpenSlices(opt_maxTimestamp);
},
mergeKernelWithUserland: function() {
if (this.kernelSliceGroup.length > 0) {
var newSlices = SliceGroup.merge(
this.sliceGroup, this.kernelSliceGroup);
this.sliceGroup.slices = newSlices.slices;
this.kernelSliceGroup = new SliceGroup();
this.updateBounds();
}
},
createSubSlices: function() {
this.sliceGroup.createSubSlices();
},
/**
* @return {String} A user-friendly name for this thread.
*/
get userFriendlyName() {
return this.name || this.tid;
},
/**
* @return {String} User friendly details about this thread.
*/
get userFriendlyDetails() {
return 'tid: ' + this.tid +
(this.name ? ', name: ' + this.name : '');
},
getSettingsKey: function() {
if (!this.name)
return undefined;
var parentKey = this.parent.getSettingsKey();
if (!parentKey)
return undefined;
return parentKey + '.' + this.name;
},
/*
* Returns the index of the slice in the timeSlices array, or undefined.
*/
indexOfTimeSlice: function(timeSlice) {
var i = base.findLowIndexInSortedArray(
this.timeSlices,
function(slice) { return slice.start; },
timeSlice.start);
if (this.timeSlices[i] !== timeSlice)
return undefined;
return i;
},
iterateAllEvents: function(callback) {
this.sliceGroup.iterateAllEvents(callback);
this.kernelSliceGroup.iterateAllEvents(callback);
this.asyncSliceGroup.iterateAllEvents(callback);
if (this.timeSlices && this.timeSlices.length)
this.timeSlices.forEach(callback);
this.samples_.forEach(callback);
}
};
/**
* Comparison between threads that orders first by parent.compareTo,
* then by names, then by tid.
*/
Thread.compare = function(x, y) {
var tmp = x.parent.compareTo(y.parent);
if (tmp)
return tmp;
tmp = x.sortIndex - y.sortIndex;
if (tmp)
return tmp;
tmp = base.comparePossiblyUndefinedValues(
x.name, y.name,
function(x, y) { return x.localeCompare(y); });
if (tmp)
return tmp;
return x.tid - y.tid;
};
return {
ThreadSlice: ThreadSlice,
Thread: Thread
};
});
// 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.
'use strict';
/**
* @fileoverview Provides the Settings object.
*/
base.exportTo('base', function() {
var storage_ = localStorage;
/**
* Settings is a simple wrapper around local storage, to make it easier
* to test classes that have settings.
*
* May be called as new base.Settings() or simply base.Settings()
* @constructor
*/
function Settings() {
return Settings;
};
/**
* Get the setting with the given name.
*
* @param {string} key The name of the setting.
* @param {string=} opt_default The default value to return if not set.
* @param {string=} opt_namespace If set, the setting name will be prefixed
* with this namespace, e.g. "categories.settingName". This is useful for
* a set of related settings.
*/
Settings.get = function(key, opt_default, opt_namespace) {
key = Settings.namespace_(key, opt_namespace);
var rawVal = storage_.getItem(key);
if (rawVal === null || rawVal === undefined)
return opt_default;
// Old settings versions used to stringify objects instead of putting them
// into JSON. If those are encountered, parse will fail. In that case,
// "upgrade" the setting to the default value.
try {
return JSON.parse(rawVal).value;
} catch (e) {
storage_.removeItem(Settings.namespace_(key, opt_namespace));
return opt_default;
}
},
/**
* Set the setting with the given name to the given value.
*
* @param {string} key The name of the setting.
* @param {string} value The value of the setting.
* @param {string=} opt_namespace If set, the setting name will be prefixed
* with this namespace, e.g. "categories.settingName". This is useful for
* a set of related settings.
*/
Settings.set = function(key, value, opt_namespace) {
if (value === undefined)
throw new Error('Settings.set: value must not be undefined');
var v = JSON.stringify({value: value});
storage_.setItem(Settings.namespace_(key, opt_namespace), v);
},
/**
* Return a list of all the keys, or all the keys in the given namespace
* if one is provided.
*
* @param {string=} opt_namespace If set, only return settings which
* begin with this prefix.
*/
Settings.keys = function(opt_namespace) {
var result = [];
opt_namespace = opt_namespace || '';
for (var i = 0; i < storage_.length; i++) {
var key = storage_.key(i);
if (Settings.isnamespaced_(key, opt_namespace))
result.push(Settings.unnamespace_(key, opt_namespace));
}
return result;
},
Settings.isnamespaced_ = function(key, opt_namespace) {
return key.indexOf(Settings.normalize_(opt_namespace)) == 0;
},
Settings.namespace_ = function(key, opt_namespace) {
return Settings.normalize_(opt_namespace) + key;
},
Settings.unnamespace_ = function(key, opt_namespace) {
return key.replace(Settings.normalize_(opt_namespace), '');
},
/**
* All settings are prefixed with a global namespace to avoid collisions.
* Settings may also be namespaced with an additional prefix passed into
* the get, set, and keys methods in order to group related settings.
* This method makes sure the two namespaces are always set properly.
*/
Settings.normalize_ = function(opt_namespace) {
return Settings.NAMESPACE + (opt_namespace ? opt_namespace + '.' : '');
}
Settings.setAlternativeStorageInstance = function(instance) {
storage_ = instance;
}
Settings.getAlternativeStorageInstance = function() {
if (storage_ === localStorage)
return undefined;
return storage_;
}
Settings.NAMESPACE = 'trace-viewer';
return {
Settings: Settings
};
});
// 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.
'use strict';
base.require('base.settings');
base.exportTo('tracing', function() {
var Settings = base.Settings;
/**
* A way to persist settings specific to parts of a trace model.
*
* This object should not be persisted because it builds up internal data
* structures that map model objects to settings keys. It should thus be
* created for the druation of whatever interaction(s) you're going to do with
* model settings, and then discarded.
*
* This system works on a notion of an object key: for an object's key, it
* considers all the other keys in the model. If it is unique, then the key is
* persisted to base.Settings. However, if it is not unique, then the
* setting is stored on the object itself. Thus, objects with unique keys will
* be persisted across page reloads, whereas objects with nonunique keys will
* not.
*/
function TraceModelSettings(model) {
this.model = model;
this.objectsByKey_ = [];
this.nonuniqueKeys_ = [];
this.buildObjectsByKeyMap_();
this.removeNonuniqueKeysFromSettings_();
}
TraceModelSettings.prototype = {
buildObjectsByKeyMap_: function() {
var objects = [this.model.kernel];
objects.push.apply(objects,
base.dictionaryValues(this.model.processes));
objects.push.apply(objects,
this.model.getAllThreads());
var objectsByKey = {};
var NONUNIQUE_KEY = 'nonuniqueKey';
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
var objectKey = object.getSettingsKey();
if (!objectKey)
continue;
if (objectsByKey[objectKey] === undefined) {
objectsByKey[objectKey] = object;
continue;
}
objectsByKey[objectKey] = NONUNIQUE_KEY;
}
var nonuniqueKeys = {};
base.dictionaryKeys(objectsByKey).forEach(function(objectKey) {
if (objectsByKey[objectKey] !== NONUNIQUE_KEY)
return;
delete objectsByKey[objectKey];
nonuniqueKeys[objectKey] = true;
});
this.nonuniqueKeys = nonuniqueKeys;
this.objectsByKey_ = objectsByKey;
},
removeNonuniqueKeysFromSettings_: function() {
var settings = Settings.get('trace_model_settings', {});
var settingsChanged = false;
base.dictionaryKeys(settings).forEach(function(objectKey) {
if (!this.nonuniqueKeys[objectKey])
return;
settingsChanged = true;
delete settings[objectKey];
}, this);
if (settingsChanged)
Settings.set('trace_model_settings', settings);
},
hasUniqueSettingKey: function(object) {
var objectKey = object.getSettingsKey();
if (!objectKey)
return false;
return this.objectsByKey_[objectKey] !== undefined;
},
getSettingFor: function(object, objectLevelKey, defaultValue) {
var objectKey = object.getSettingsKey();
if (!objectKey || !this.objectsByKey_[objectKey]) {
var ephemeralValue = object.ephemeralSettings[objectLevelKey];
if (ephemeralValue !== undefined)
return ephemeralValue;
return defaultValue;
}
var settings = Settings.get('trace_model_settings', {});
if (!settings[objectKey])
settings[objectKey] = {};
var value = settings[objectKey][objectLevelKey];
if (value !== undefined)
return value;
return defaultValue;
},
setSettingFor: function(object, objectLevelKey, value) {
var objectKey = object.getSettingsKey();
if (!objectKey || !this.objectsByKey_[objectKey]) {
object.ephemeralSettings[objectLevelKey] = value;
return;
}
var settings = Settings.get('trace_model_settings', {});
if (!settings[objectKey])
settings[objectKey] = {};
settings[objectKey][objectLevelKey] = value;
Settings.set('trace_model_settings', settings);
}
};
return {
TraceModelSettings: TraceModelSettings
};
});
// 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.
'use strict';
/**
* @fileoverview Provides the ProcessBase class.
*/
base.require('base.guid');
base.require('base.range');
base.require('tracing.trace_model.counter');
base.require('tracing.trace_model.object_collection');
base.require('tracing.trace_model.thread');
base.require('tracing.trace_model_settings');
base.exportTo('tracing.trace_model', function() {
var Thread = tracing.trace_model.Thread;
var Counter = tracing.trace_model.Counter;
/**
* The ProcessBase is an partial base class, upon which Kernel
* and Process are built.
*
* @constructor
*/
function ProcessBase(model) {
if (!model)
throw new Error('Must provide a model');
this.guid_ = base.GUID.allocate();
this.model = model;
this.threads = {};
this.counters = {};
this.objects = new tracing.trace_model.ObjectCollection(this);
this.bounds = new base.Range();
this.sortIndex = 0;
this.ephemeralSettings = {};
};
ProcessBase.compare = function(x, y) {
return x.sortIndex - y.sortIndex;
};
ProcessBase.prototype = {
/*
* @return {Number} A globally unique identifier for this counter.
*/
get guid() {
return this.guid_;
},
/**
* Gets the number of threads in this process.
*/
get numThreads() {
var n = 0;
for (var p in this.threads) {
n++;
}
return n;
},
toJSON: function() {
var obj = new Object();
var keys = Object.keys(this);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (typeof this[key] == 'function')
continue;
if (key == 'model')
continue;
obj[key] = this[key];
}
return obj;
},
/**
* Shifts all the timestamps inside this process forward by the amount
* specified.
*/
shiftTimestampsForward: function(amount) {
for (var tid in this.threads)
this.threads[tid].shiftTimestampsForward(amount);
for (var id in this.counters)
this.counters[id].shiftTimestampsForward(amount);
this.objects.shiftTimestampsForward(amount);
},
/**
* Closes any open slices.
*/
autoCloseOpenSlices: function(opt_maxTimestamp) {
for (var tid in this.threads) {
var thread = this.threads[tid];
thread.autoCloseOpenSlices(opt_maxTimestamp);
}
},
autoDeleteObjects: function(maxTimestamp) {
this.objects.autoDeleteObjects(maxTimestamp);
},
/**
* Called by the model after finalizing imports,
* but before joining refs.
*/
preInitializeObjects: function() {
this.objects.preInitializeAllObjects();
},
/**
* Called by the model after joining refs.
*/
initializeObjects: function() {
this.objects.initializeAllObjects();
},
/**
* Merge slices from the kernel with those from userland for each thread.
*/
mergeKernelWithUserland: function() {
for (var tid in this.threads) {
var thread = this.threads[tid];
thread.mergeKernelWithUserland();
}
},
updateBounds: function() {
this.bounds.reset();
for (var tid in this.threads) {
this.threads[tid].updateBounds();
this.bounds.addRange(this.threads[tid].bounds);
}
for (var id in this.counters) {
this.counters[id].updateBounds();
this.bounds.addRange(this.counters[id].bounds);
}
this.objects.updateBounds();
this.bounds.addRange(this.objects.bounds);
},
addCategoriesToDict: function(categoriesDict) {
for (var tid in this.threads)
this.threads[tid].addCategoriesToDict(categoriesDict);
for (var id in this.counters)
categoriesDict[this.counters[id].category] = true;
this.objects.addCategoriesToDict(categoriesDict);
},
/**
* @param {String} The name of the thread to find.
* @return {Array} An array of all the matched threads.
*/
findAllThreadsNamed: function(name) {
var namedThreads = [];
for (var tid in this.threads) {
var thread = this.threads[tid];
if (thread.name == name)
namedThreads.push(thread);
}
return namedThreads;
},
/**
* Removes threads from the process that are fully empty.
*/
pruneEmptyContainers: function() {
var threadsToKeep = {};
for (var tid in this.threads) {
var thread = this.threads[tid];
if (!thread.isEmpty)
threadsToKeep[tid] = thread;
}
this.threads = threadsToKeep;
},
/**
* @return {TimlineThread} The thread identified by tid on this process,
* creating it if it doesn't exist.
*/
getOrCreateThread: function(tid) {
if (!this.threads[tid])
this.threads[tid] = new Thread(this, tid);
return this.threads[tid];
},
/**
* @return {TimlineCounter} The counter on this process named 'name',
* creating it if it doesn't exist.
*/
getOrCreateCounter: function(cat, name) {
var id = cat + '.' + name;
if (!this.counters[id])
this.counters[id] = new Counter(this, id, cat, name);
return this.counters[id];
},
getSettingsKey: function() {
throw new Error('Not implemented');
},
iterateAllEvents: function(callback) {
for (var tid in this.threads)
this.threads[tid].iterateAllEvents(callback);
for (var id in this.counters)
this.counters[id].iterateAllEvents(callback);
this.objects.iterateAllEvents(callback);
}
};
return {
ProcessBase: ProcessBase
};
});
// 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.
'use strict';
/**
* @fileoverview Provides the Process class.
*/
base.require('tracing.trace_model.cpu');
base.require('tracing.trace_model.process_base');
base.exportTo('tracing.trace_model', function() {
var Cpu = tracing.trace_model.Cpu;
var ProcessBase = tracing.trace_model.ProcessBase;
/**
* The Kernel represents kernel-level objects in the
* model.
* @constructor
*/
function Kernel(model) {
if (model === undefined)
throw new Error('model must be provided');
ProcessBase.call(this, model);
this.cpus = {};
};
/**
* Comparison between kernels is pretty meaningless.
*/
Kernel.compare = function(x, y) {
return 0;
};
Kernel.prototype = {
__proto__: ProcessBase.prototype,
compareTo: function(that) {
return Kernel.compare(this, that);
},
get userFriendlyName() {
return 'Kernel';
},
get userFriendlyDetails() {
return 'Kernel';
},
/**
* @return {Cpu} Gets a specific Cpu or creates one if
* it does not exist.
*/
getOrCreateCpu: function(cpuNumber) {
if (!this.cpus[cpuNumber])
this.cpus[cpuNumber] = new Cpu(cpuNumber);
return this.cpus[cpuNumber];
},
shiftTimestampsForward: function(amount) {
ProcessBase.prototype.shiftTimestampsForward.call(this);
for (var cpuNumber in this.cpus)
this.cpus[cpuNumber].shiftTimestampsForward(amount);
},
updateBounds: function() {
ProcessBase.prototype.updateBounds.call(this);
for (var cpuNumber in this.cpus) {
var cpu = this.cpus[cpuNumber];
cpu.updateBounds();
this.bounds.addRange(cpu.bounds);
}
},
addCategoriesToDict: function(categoriesDict) {
ProcessBase.prototype.addCategoriesToDict.call(this, categoriesDict);
for (var cpuNumber in this.cpus)
this.cpus[cpuNumber].addCategoriesToDict(categoriesDict);
},
getSettingsKey: function() {
return 'kernel';
},
iterateAllEvents: function(callback) {
for (var cpuNumber in this.cpus)
this.cpus[cpuNumber].iterateAllEvents(callback);
ProcessBase.prototype.iterateAllEvents.call(this, callback);
}
};
return {
Kernel: Kernel
};
});
// 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.
'use strict';
/**
* @fileoverview Provides the Process class.
*/
base.require('tracing.trace_model.process_base');
base.exportTo('tracing.trace_model', function() {
var ProcessBase = tracing.trace_model.ProcessBase;
/**
* The Process represents a single userland process in the
* trace.
* @constructor
*/
function Process(model, pid) {
if (model === undefined)
throw new Error('model must be provided');
if (pid === undefined)
throw new Error('pid must be provided');
tracing.trace_model.ProcessBase.call(this, model);
this.pid = pid;
this.name = undefined;
this.labels = [];
this.instantEvents = [];
};
/**
* Comparison between processes that orders by pid.
*/
Process.compare = function(x, y) {
var tmp = tracing.trace_model.ProcessBase.compare(x, y);
if (tmp)
return tmp;
tmp = base.comparePossiblyUndefinedValues(
x.name, y.name,
function(x, y) { return x.localeCompare(y); });
if (tmp)
return tmp;
tmp = base.compareArrays(x.labels, y.labels,
function(x, y) { return x.localeCompare(y); });
if (tmp)
return tmp;
return x.pid - y.pid;
};
Process.prototype = {
__proto__: tracing.trace_model.ProcessBase.prototype,
compareTo: function(that) {
return Process.compare(this, that);
},
pushInstantEvent: function(instantEvent) {
this.instantEvents.push(instantEvent);
},
get userFriendlyName() {
var res;
if (this.name)
res = this.name + ' (pid ' + this.pid + ')';
else
res = 'Process ' + this.pid;
if (this.labels.length)
res += ': ' + this.labels.join(', ');
return res;
},
get userFriendlyDetails() {
if (this.name)
return this.name + ' (pid ' + this.pid + ')';
return 'pid: ' + this.pid;
},
getSettingsKey: function() {
if (!this.name)
return undefined;
if (!this.labels.length)
return 'processes.' + this.name;
return 'processes.' + this.name + '.' + this.labels.join('.');
},
shiftTimestampsForward: function(amount) {
for (var id in this.instantEvents)
this.instantEvents[id].start += amount;
tracing.trace_model.ProcessBase.prototype
.shiftTimestampsForward.apply(this, arguments);
},
iterateAllEvents: function(callback) {
this.instantEvents.forEach(callback);
ProcessBase.prototype.iterateAllEvents.call(this, callback);
}
};
return {
Process: Process
};
});
// 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.
'use strict';
base.require('base.events');
base.exportTo('base', function() {
/**
* Fires a property change event on the target.
* @param {EventTarget} target The target to dispatch the event on.
* @param {string} propertyName The name of the property that changed.
* @param {*} newValue The new value for the property.
* @param {*} oldValue The old value for the property.
*/
function dispatchPropertyChange(target, propertyName, newValue, oldValue,
opt_bubbles, opt_cancelable) {
var e = new base.Event(propertyName + 'Change',
opt_bubbles, opt_cancelable);
e.propertyName = propertyName;
e.newValue = newValue;
e.oldValue = oldValue;
var error;
e.throwError = function(err) { // workaround CR 239648
error = err;
};
target.dispatchEvent(e);
if (error)
throw error;
}
function setPropertyAndDispatchChange(obj, propertyName, newValue) {
var privateName = propertyName + '_';
var oldValue = obj[propertyName];
obj[privateName] = newValue;
if (oldValue !== newValue)
base.dispatchPropertyChange(obj, propertyName,
newValue, oldValue, true, false);
}
/**
* Converts a camelCase javascript property name to a hyphenated-lower-case
* attribute name.
* @param {string} jsName The javascript camelCase property name.
* @return {string} The equivalent hyphenated-lower-case attribute name.
*/
function getAttributeName(jsName) {
return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
}
/* Creates a private name unlikely to collide with object properties names
* @param {string} name The defineProperty name
* @return {string} an obfuscated name
*/
function getPrivateName(name) {
return name + '_base_';
}
/**
* The kind of property to define in {@code defineProperty}.
* @enum {number}
* @const
*/
var PropertyKind = {
/**
* Plain old JS property where the backing data is stored as a 'private'
* field on the object.
*/
JS: 'js',
/**
* The property backing data is stored as an attribute on an element.
*/
ATTR: 'attr',
/**
* The property backing data is stored as an attribute on an element. If the
* element has the attribute then the value is true.
*/
BOOL_ATTR: 'boolAttr'
};
/**
* Helper function for defineProperty that returns the getter to use for the
* property.
* @param {string} name The name of the property.
* @param {base.PropertyKind} kind The kind of the property.
* @return {function():*} The getter for the property.
*/
function getGetter(name, kind) {
switch (kind) {
case PropertyKind.JS:
var privateName = getPrivateName(name);
return function() {
return this[privateName];
};
case PropertyKind.ATTR:
var attributeName = getAttributeName(name);
return function() {
return this.getAttribute(attributeName);
};
case PropertyKind.BOOL_ATTR:
var attributeName = getAttributeName(name);
return function() {
return this.hasAttribute(attributeName);
};
}
}
/**
* Helper function for defineProperty that returns the setter of the right
* kind.
* @param {string} name The name of the property we are defining the setter
* for.
* @param {base.PropertyKind} kind The kind of property we are getting the
* setter for.
* @param {function(*):void=} opt_setHook A function to run after the property
* is set, but before the propertyChange event is fired.
* @param {boolean=} opt_bubbles Whether the event bubbles or not.
* @param {boolean=} opt_cancelable Whether the default action of the event
* can be prevented.
* @return {function(*):void} The function to use as a setter.
*/
function getSetter(name, kind, opt_setHook, opt_bubbles, opt_cancelable) {
switch (kind) {
case PropertyKind.JS:
var privateName = getPrivateName(name);
return function(value) {
var oldValue = this[privateName];
if (value !== oldValue) {
this[privateName] = value;
if (opt_setHook)
opt_setHook.call(this, value, oldValue);
dispatchPropertyChange(this, name, value, oldValue,
opt_bubbles, opt_cancelable);
}
};
case PropertyKind.ATTR:
var attributeName = getAttributeName(name);
return function(value) {
var oldValue = this.getAttribute(attributeName);
if (value !== oldValue) {
if (value == undefined)
this.removeAttribute(attributeName);
else
this.setAttribute(attributeName, value);
if (opt_setHook)
opt_setHook.call(this, value, oldValue);
dispatchPropertyChange(this, name, value, oldValue,
opt_bubbles, opt_cancelable);
}
};
case PropertyKind.BOOL_ATTR:
var attributeName = getAttributeName(name);
return function(value) {
var oldValue = (this.getAttribute(attributeName) === name);
if (value !== oldValue) {
if (value)
this.setAttribute(attributeName, name);
else
this.removeAttribute(attributeName);
if (opt_setHook)
opt_setHook.call(this, value, oldValue);
dispatchPropertyChange(this, name, value, oldValue,
opt_bubbles, opt_cancelable);
}
};
}
}
/**
* Defines a property on an object. When the setter changes the value a
* property change event with the type {@code name + 'Change'} is fired.
* @param {!Object} obj The object to define the property for.
* @param {string} name The name of the property.
* @param {base.PropertyKind=} opt_kind What kind of underlying storage to
* use.
* @param {function(*):void=} opt_setHook A function to run after the
* property is set, but before the propertyChange event is fired.
* @param {boolean=} opt_bubbles Whether the event bubbles or not.
* @param {boolean=} opt_cancelable Whether the default action of the event
* can be prevented.
*/
function defineProperty(obj, name, opt_kind, opt_setHook,
opt_bubbles, opt_cancelable) {
console.error("Don't use base.defineProperty");
if (typeof obj == 'function')
obj = obj.prototype;
var kind = opt_kind || PropertyKind.JS;
if (!obj.__lookupGetter__(name))
obj.__defineGetter__(name, getGetter(name, kind));
if (!obj.__lookupSetter__(name))
obj.__defineSetter__(name, getSetter(name, kind, opt_setHook,
opt_bubbles, opt_cancelable));
}
return {
PropertyKind: PropertyKind,
defineProperty: defineProperty,
dispatchPropertyChange: dispatchPropertyChange,
setPropertyAndDispatchChange: setPropertyAndDispatchChange
};
});
// 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.
'use strict';
base.exportTo('ui', function() {
/**
* Decorates elements as an instance of a class.
* @param {string|!Element} source The way to find the element(s) to decorate.
* If this is a string then {@code querySeletorAll} is used to find the
* elements to decorate.
* @param {!Function} constr The constructor to decorate with. The constr
* needs to have a {@code decorate} function.
*/
function decorate(source, constr) {
var elements;
if (typeof source == 'string')
elements = base.doc.querySelectorAll(source);
else
elements = [source];
for (var i = 0, el; el = elements[i]; i++) {
if (!(el instanceof constr))
constr.decorate(el);
}
}
/**
* Defines a tracing UI component, a function that can be called to construct
* the component.
*
* Base class:
* <pre>
* var List = ui.define('list');
* List.prototype = {
* __proto__: HTMLUListElement.prototype,
* decorate: function() {
* ...
* },
* ...
* };
* </pre>
*
* Derived class:
* <pre>
* var CustomList = ui.define('custom-list', List);
* CustomList.prototype = {
* __proto__: List.prototype,
* decorate: function() {
* ...
* },
* ...
* };
* </pre>
*
* @param {string} tagName The tagName of the newly created subtype. If
* subclassing, this is used for debugging. If not subclassing, then it is
* the tag name that will be created by the component.
* @param {function=} opt_parentConstructor The parent class for this new
* element, if subclassing is desired. If provided, the parent class must
* be also a function created by ui.define.
* @return {function(Object=):Element} The newly created component
* constructor.
*/
function define(tagName, opt_parentConstructor) {
if (typeof tagName == 'function') {
throw new Error('Passing functions as tagName is deprecated. Please ' +
'use (tagName, opt_parentConstructor) to subclass');
}
var tagName = tagName.toLowerCase();
if (opt_parentConstructor && !opt_parentConstructor.tagName)
throw new Error('opt_parentConstructor was not created by ui.define');
/**
* Creates a new UI element constructor.
* Arguments passed to the constuctor are provided to the decorate method.
* You will need to call the parent elements decorate method from within
* your decorate method and pass any required parameters.
* @constructor
*/
function f() {
if (opt_parentConstructor &&
f.prototype.__proto__ != opt_parentConstructor.prototype) {
throw new Error(
tagName + ' prototye\'s __proto__ field is messed up. ' +
'It MUST be the prototype of ' + opt_parentConstructor.tagName);
}
// Walk up the parent constructors until we can find the type of tag
// to create.
var tag = tagName;
if (opt_parentConstructor) {
var parent = opt_parentConstructor;
while (parent && parent.tagName) {
tag = parent.tagName;
parent = parent.parentConstructor;
}
}
var el = base.doc.createElement(tag);
f.decorate.call(this, el, arguments);
return el;
}
try {
// f.name is not directly writable. So make it writable anyway.
Object.defineProperty(
f, 'name',
{value: tagName, writable: false, configurable: false});
} catch (e) {
// defineProperty throws a TypeError about name already being defined
// although, it also correctly sets the value to tagName.
}
/**
* Decorates an element as a UI element class.
* @param {!Element} el The element to decorate.
*/
f.decorate = function(el) {
el.__proto__ = f.prototype;
el.decorate.apply(el, arguments[1]);
el.constructor = f;
};
f.tagName = tagName;
f.parentConstructor = (opt_parentConstructor ? opt_parentConstructor :
undefined);
f.toString = function() {
if (!f.parentConstructor)
return f.tagName;
return f.parentConstructor.toString() + '::' + f.tagName;
};
return f;
}
function elementIsChildOf(el, potentialParent) {
if (el == potentialParent)
return false;
var cur = el;
while (cur.parentNode) {
if (cur == potentialParent)
return true;
cur = cur.parentNode;
}
return false;
};
return {
decorate: decorate,
define: define,
elementIsChildOf: elementIsChildOf
};
});
// 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.
'use strict';
/**
* @fileoverview Implements an element that is hidden by default, but
* when shown, dims and (attempts to) disable the main document.
*
* You can turn any div into an overlay. Note that while an
* overlay element is shown, its parent is changed. Hiding the overlay
* restores its original parentage.
*
*/
base.requireTemplate('ui.overlay');
base.require('base.utils');
base.require('base.properties');
base.require('base.events');
base.require('ui');
base.exportTo('ui', function() {
/**
* Creates a new overlay element. It will not be visible until shown.
* @constructor
* @extends {HTMLDivElement}
*/
var Overlay = ui.define('overlay');
Overlay.prototype = {
__proto__: HTMLDivElement.prototype,
/**
* Initializes the overlay element.
*/
decorate: function() {
this.classList.add('overlay');
this.parentEl_ = this.ownerDocument.body;
this.visible_ = false;
this.userCanClose_ = true;
this.onKeyDown_ = this.onKeyDown_.bind(this);
this.onClick_ = this.onClick_.bind(this);
this.onFocusIn_ = this.onFocusIn_.bind(this);
this.onDocumentClick_ = this.onDocumentClick_.bind(this);
this.onClose_ = this.onClose_.bind(this);
this.addEventListener('visibleChange',
ui.Overlay.prototype.onVisibleChange_.bind(this), true);
// Setup the shadow root
this.shadow_ = this.createShadowRoot();
this.shadow_.appendChild(base.instantiateTemplate('#overlay-template'));
this.closeBtn_ = this.shadow_.querySelector('close-button');
this.closeBtn_.addEventListener('click', this.onClose_);
this.shadow_
.querySelector('overlay-frame')
.addEventListener('click', this.onClick_);
this.observer_ = new WebKitMutationObserver(
this.didButtonBarMutate_.bind(this));
this.observer_.observe(this.shadow_.querySelector('button-bar'),
{ childList: true });
// title is a variable on regular HTMLElements. However, we want to
// use it for something more useful.
Object.defineProperty(
this, 'title', {
get: function() {
return this.shadow_.querySelector('title').textContent;
},
set: function(title) {
this.shadow_.querySelector('title').textContent = title;
}
});
},
set userCanClose(userCanClose) {
this.userCanClose_ = userCanClose;
this.closeBtn_.style.display =
userCanClose ? 'block' : 'none';
},
get leftButtons() {
return this.shadow_.querySelector('left-buttons');
},
get rightButtons() {
return this.shadow_.querySelector('right-buttons');
},
get visible() {
return this.visible_;
},
set visible(newValue) {
if (this.visible_ === newValue)
return;
base.setPropertyAndDispatchChange(this, 'visible', newValue);
},
onVisibleChange_: function() {
this.visible_ ? this.show_() : this.hide_();
},
show_: function() {
this.parentEl_.appendChild(this);
if (this.userCanClose_) {
document.addEventListener('keydown', this.onKeyDown_);
document.addEventListener('click', this.onDocumentClick_);
}
this.parentEl_.addEventListener('focusin', this.onFocusIn_);
this.tabIndex = 0;
// Focus the first thing we find that makes sense. (Skip the close button
// as it doesn't make sense as the first thing to focus.)
var focusEl = undefined;
var elList = this.querySelectorAll('button, input, list, select, a');
if (elList.length > 0) {
if (elList[0] === this.closeBtn_) {
if (elList.length > 1)
focusEl = elList[1];
} else {
focusEl = elList[0];
}
}
if (focusEl === undefined)
focusEl = this;
focusEl.focus();
},
hide_: function() {
this.parentEl_.removeChild(this);
this.parentEl_.removeEventListener('focusin', this.onFocusIn_);
if (this.closeBtn_)
this.closeBtn_.removeEventListener(this.onClose_);
document.removeEventListener('keydown', this.onKeyDown_);
document.removeEventListener('click', this.onDocumentClick_);
},
onClose_: function(e) {
this.visible = false;
e.stopPropagation();
e.preventDefault();
},
onFocusIn_: function(e) {
if (e.target === this)
return;
window.setTimeout(function() { this.focus(); }, 0);
e.preventDefault();
e.stopPropagation();
},
didButtonBarMutate_: function(e) {
var hasButtons = this.leftButtons.children.length +
this.rightButtons.children.length > 0;
if (hasButtons)
this.shadow_.querySelector('button-bar').style.display = undefined;
else
this.shadow_.querySelector('button-bar').style.display = 'none';
},
onKeyDown_: function(e) {
// Disallow shift-tab back to another element.
if (e.keyCode === 9 && // tab
e.shiftKey &&
e.target === this) {
e.preventDefault();
return;
}
if (e.keyCode !== 27) // escape
return;
this.visible = false;
e.preventDefault();
},
onClick_: function(e) {
e.stopPropagation();
},
onDocumentClick_: function(e) {
if (!this.userCanClose_)
return;
this.visible = false;
e.preventDefault();
e.stopPropagation();
}
};
return {
Overlay: Overlay
};
});
// 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.
'use strict';
/**
* @fileoverview TraceModel is a parsed representation of the
* TraceEvents obtained from base/trace_event in which the begin-end
* tokens are converted into a hierarchy of processes, threads,
* subrows, and slices.
*
* The building block of the model is a slice. A slice is roughly
* equivalent to function call executing on a specific thread. As a
* result, slices may have one or more subslices.
*
* A thread contains one or more subrows of slices. Row 0 corresponds to
* the "root" slices, e.g. the topmost slices. Row 1 contains slices that
* are nested 1 deep in the stack, and so on. We use these subrows to draw
* nesting tasks.
*
*/
base.require('base.range');
base.require('base.events');
base.require('base.interval_tree');
base.require('tracing.importer.importer');
base.require('tracing.importer.task');
base.require('tracing.trace_model.process');
base.require('tracing.trace_model.kernel');
base.require('tracing.filter');
base.require('ui.overlay');
base.exportTo('tracing', function() {
var Importer = tracing.importer.Importer;
var Process = tracing.trace_model.Process;
var Kernel = tracing.trace_model.Kernel;
/**
* Builds a model from an array of TraceEvent objects.
* @param {Object=} opt_eventData Data from a single trace to be imported into
* the new model. See TraceModel.importTraces for details on how to
* import multiple traces at once.
* @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
* Defaults to true.
* @constructor
*/
function TraceModel(opt_eventData, opt_shiftWorldToZero) {
this.kernel = new Kernel(this);
this.processes = {};
this.metadata = [];
this.categories = [];
this.bounds = new base.Range();
this.instantEvents = [];
this.flowEvents = [];
this.flowIntervalTree = new base.IntervalTree(
function(s) { return s.start; },
function(e) { return e.start; });
this.importWarnings_ = [];
this.reportedImportWarnings_ = {};
if (opt_eventData)
this.importTraces([opt_eventData], opt_shiftWorldToZero);
}
TraceModel.importerConstructors_ = [];
/**
* Registers an importer. All registered importers are considered
* when processing an import request.
*
* @param {Function} importerConstructor The importer's constructor function.
*/
TraceModel.registerImporter = function(importerConstructor) {
TraceModel.importerConstructors_.push(importerConstructor);
};
TraceModel.prototype = {
__proto__: base.EventTarget.prototype,
get numProcesses() {
var n = 0;
for (var p in this.processes)
n++;
return n;
},
/**
* @return {Process} Gets a TimlineProcess for a specified pid or
* creates one if it does not exist.
*/
getOrCreateProcess: function(pid) {
if (!this.processes[pid])
this.processes[pid] = new Process(this, pid);
return this.processes[pid];
},
pushInstantEvent: function(instantEvent) {
this.instantEvents.push(instantEvent);
},
/**
* Generates the set of categories from the slices and counters.
*/
updateCategories_: function() {
var categoriesDict = {};
this.kernel.addCategoriesToDict(categoriesDict);
for (var pid in this.processes)
this.processes[pid].addCategoriesToDict(categoriesDict);
this.categories = [];
for (var category in categoriesDict)
if (category != '')
this.categories.push(category);
},
updateBounds: function() {
this.bounds.reset();
this.kernel.updateBounds();
this.bounds.addRange(this.kernel.bounds);
for (var pid in this.processes) {
this.processes[pid].updateBounds();
this.bounds.addRange(this.processes[pid].bounds);
}
},
shiftWorldToZero: function() {
if (this.bounds.isEmpty)
return;
var timeBase = this.bounds.min;
this.kernel.shiftTimestampsForward(-timeBase);
for (var id in this.instantEvents)
this.instantEvents[id].start -= timeBase;
for (var pid in this.processes)
this.processes[pid].shiftTimestampsForward(-timeBase);
this.updateBounds();
},
getAllThreads: function() {
var threads = [];
for (var tid in this.kernel.threads) {
threads.push(process.threads[tid]);
}
for (var pid in this.processes) {
var process = this.processes[pid];
for (var tid in process.threads) {
threads.push(process.threads[tid]);
}
}
return threads;
},
/**
* @return {Array} An array of all processes in the model.
*/
getAllProcesses: function() {
var processes = [];
for (var pid in this.processes)
processes.push(this.processes[pid]);
return processes;
},
/**
* @return {Array} An array of all the counters in the model.
*/
getAllCounters: function() {
var counters = [];
counters.push.apply(
counters, base.dictionaryValues(this.kernel.counters));
for (var pid in this.processes) {
var process = this.processes[pid];
for (var tid in process.counters) {
counters.push(process.counters[tid]);
}
}
return counters;
},
/**
* @param {String} The name of the thread to find.
* @return {Array} An array of all the matched threads.
*/
findAllThreadsNamed: function(name) {
var namedThreads = [];
namedThreads.push.apply(
namedThreads,
this.kernel.findAllThreadsNamed(name));
for (var pid in this.processes) {
namedThreads.push.apply(
namedThreads,
this.processes[pid].findAllThreadsNamed(name));
}
return namedThreads;
},
createImporter_: function(eventData) {
var importerConstructor;
for (var i = 0; i < TraceModel.importerConstructors_.length; ++i) {
if (TraceModel.importerConstructors_[i].canImport(eventData)) {
importerConstructor = TraceModel.importerConstructors_[i];
break;
}
}
if (!importerConstructor)
throw new Error(
'Could not find an importer for the provided eventData.');
var importer = new importerConstructor(
this, eventData);
return importer;
},
/**
* Imports the provided traces into the model. The eventData type
* is undefined and will be passed to all the importers registered
* via TraceModel.registerImporter. The first importer that returns true
* for canImport(events) will be used to import the events.
*
* The primary trace is provided via the eventData variable. If multiple
* traces are to be imported, specify the first one as events, and the
* remainder in the opt_additionalEventData array.
*
* @param {Array} traces An array of eventData to be imported. Each
* eventData should correspond to a single trace file and will be handled by
* a separate importer.
* @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
* Defaults to true.
* @param {bool=} opt_pruneEmptyContainers Whether to prune empty
* containers. Defaults to true.
*/
importTraces: function(traces,
opt_shiftWorldToZero,
opt_pruneEmptyContainers) {
var progressMeter = {
update: function(msg) {}
};
var task = this.createImportTracesTask(
progressMeter,
traces,
opt_shiftWorldToZero,
opt_pruneEmptyContainers);
tracing.importer.Task.RunSynchronously(task);
},
/**
* Imports a trace with the usual options from importTraces, but
* does so using idle callbacks, putting up an import dialog
* during the import process.
*/
importTracesWithProgressDialog: function(traces,
opt_shiftWorldToZero,
opt_pruneEmptyContainers) {
var overlay = ui.Overlay();
overlay.title = 'Importing...';
overlay.userCanClose = false;
overlay.msgEl = document.createElement('div');
overlay.appendChild(overlay.msgEl);
overlay.msgEl.style.margin = '20px';
overlay.update = function(msg) {
this.msgEl.textContent = msg;
}
overlay.visible = true;
var task = this.createImportTracesTask(
overlay,
traces,
opt_shiftWorldToZero,
opt_pruneEmptyContainers);
var promise = tracing.importer.Task.RunWhenIdle(task);
promise.then(
function() {
overlay.visible = false;
}, function(err) {
overlay.visible = false;
});
return promise;
},
/**
* Creates a task that will import the provided traces into the model,
* updating the progressMeter as it goes. Parameters are as defined in
* importTraces.
*/
createImportTracesTask: function(progressMeter,
traces,
opt_shiftWorldToZero,
opt_pruneEmptyContainers) {
if (this.importing_)
throw new Error('Already importing.');
if (opt_shiftWorldToZero === undefined)
opt_shiftWorldToZero = true;
if (opt_pruneEmptyContainers === undefined)
opt_pruneEmptyContainers = true;
this.importing_ = true;
// Just some simple setup. It is useful to have a nop first
// task so that we can set up the lastTask = lastTask.after()
// pattern that follows.
var importTask = new tracing.importer.Task(function() {
progressMeter.update('I will now import your traces for you...');
}, this);
var lastTask = importTask;
var importers = [];
lastTask = lastTask.after(function() {
// Copy the traces array, we may mutate it.
traces = traces.slice(0);
progressMeter.update('Creating importers...');
// Figure out which importers to use.
for (var i = 0; i < traces.length; ++i)
importers.push(this.createImporter_(traces[i]));
// Some traces have other traces inside them. Before doing the full
// import, ask the importer if it has any subtraces, and if so, create
// importers for them, also.
for (var i = 0; i < importers.length; i++) {
var subtraces = importers[i].extractSubtraces();
for (var j = 0; j < subtraces.length; j++) {
traces.push(subtraces[j]);
importers.push(this.createImporter_(subtraces[j]));
}
}
// Sort them on priority. This ensures importing happens in a
// predictable order, e.g. linux_perf_importer before
// trace_event_importer.
importers.sort(function(x, y) {
return x.importPriority - y.importPriority;
});
}, this);
// Run the import.
lastTask = lastTask.after(function(task) {
importers.forEach(function(importer, index) {
task.subTask(function() {
progressMeter.update(
'Importing ' + (index + 1) + ' of ' + importers.length);
importer.importEvents(index > 0);
}, this);
}, this);
}, this);
// Autoclose open slices.
lastTask = lastTask.after(function() {
progressMeter.update('Autoclosing open slices...');
this.updateBounds();
this.kernel.autoCloseOpenSlices(this.bounds.max);
for (var pid in this.processes)
this.processes[pid].autoCloseOpenSlices(this.bounds.max);
}, this);
// Finalize import.
lastTask = lastTask.after(function(task) {
importers.forEach(function(importer, index) {
progressMeter.update(
'Finalizing import ' + (index + 1) + '/' + importers.length);
importer.finalizeImport();
}, this);
}, this);
// Run preinit.
lastTask = lastTask.after(function() {
progressMeter.update('Initializing objects (step 1/2)...');
for (var pid in this.processes)
this.processes[pid].preInitializeObjects();
}, this);
// Prune empty containers.
if (opt_pruneEmptyContainers) {
lastTask = lastTask.after(function() {
progressMeter.update('Pruning empty containers...');
this.kernel.pruneEmptyContainers();
for (var pid in this.processes) {
this.processes[pid].pruneEmptyContainers();
}
}, this);
}
// Merge kernel and userland slices on each thread.
lastTask = lastTask.after(function() {
progressMeter.update('Merging kernel with userland...');
for (var pid in this.processes)
this.processes[pid].mergeKernelWithUserland();
}, this);
lastTask = lastTask.after(function() {
progressMeter.update('Computing final world bounds...');
this.updateBounds();
this.updateCategories_();
if (opt_shiftWorldToZero)
this.shiftWorldToZero();
}, this);
// Build the flow event interval tree.
lastTask = lastTask.after(function() {
progressMeter.update('Building flow event map...');
for (var i = 0; i < this.flowEvents.length; ++i) {
var pair = this.flowEvents[i];
this.flowIntervalTree.insert(pair[0], pair[1]);
}
this.flowIntervalTree.updateHighValues();
}, this);
// Join refs.
lastTask = lastTask.after(function() {
progressMeter.update('Joining object refs...');
for (var i = 0; i < importers.length; i++)
importers[i].joinRefs();
}, this);
// Delete any undeleted objects.
lastTask = lastTask.after(function() {
progressMeter.update('Cleaning up undeleted objects...');
for (var pid in this.processes)
this.processes[pid].autoDeleteObjects(this.bounds.max);
}, this);
// Run initializers.
lastTask = lastTask.after(function() {
progressMeter.update('Initializing objects (step 2/2)...');
for (var pid in this.processes)
this.processes[pid].initializeObjects();
}, this);
// Cleanup.
lastTask.after(function() {
this.importing_ = false;
}, this);
return importTask;
},
/**
* @param {Object} data The import warning data. Data must provide two
* accessors: type, message. The types are used to determine if we
* should output the message, we'll only output one message of each type.
* The message is the actual warning content.
*/
importWarning: function(data) {
this.importWarnings_.push(data);
// Only log each warning type once. We may want to add some kind of
// flag to allow reporting all importer warnings.
if (this.reportedImportWarnings_[data.type] === true)
return;
console.warn(data.message);
this.reportedImportWarnings_[data.type] = true;
},
get hasImportWarnings() {
return (this.importWarnings_.length > 0);
},
get importWarnings() {
return this.importWarnings_;
},
/**
* Iterates all events in the model and calls callback on each event.
* @param {function(event)} callback The callback called for every event.
*/
iterateAllEvents: function(callback) {
this.instantEvents.forEach(callback);
this.kernel.iterateAllEvents(callback);
for (var pid in this.processes)
this.processes[pid].iterateAllEvents(callback);
}
};
/**
* Importer for empty strings and arrays.
* @constructor
*/
function TraceModelEmptyImporter(events) {
this.importPriority = 0;
};
TraceModelEmptyImporter.canImport = function(eventData) {
if (eventData instanceof Array && eventData.length == 0)
return true;
if (typeof(eventData) === 'string' || eventData instanceof String) {
return eventData.length == 0;
}
return false;
};
TraceModelEmptyImporter.prototype = {
__proto__: Importer.prototype
};
TraceModel.registerImporter(TraceModelEmptyImporter);
return {
TraceModel: TraceModel
};
});
/**
JSZip - A Javascript class for generating and reading zip files
<http://stuartk.com/jszip>
(c) 2009-2012 Stuart Knightley <stuart [at] stuartk.com>
Dual licenced under the MIT license or GPLv3. See LICENSE.markdown.
Usage:
zip = new JSZip();
zip.file("hello.txt", "Hello, World!").file("tempfile", "nothing");
zip.folder("images").file("smile.gif", base64Data, {base64: true});
zip.file("Xmas.txt", "Ho ho ho !", {date : new Date("December 25, 2007 00:00:01")});
zip.remove("tempfile");
base64zip = zip.generate();
**/
"use strict";
/**
* Representation a of zip file in js
* @constructor
* @param {String=|ArrayBuffer=|Uint8Array=|Buffer=} data the data to load, if any (optional).
* @param {Object=} options the options for creating this objects (optional).
*/
var JSZip = function(data, options) {
// object containing the files :
// {
// "folder/" : {...},
// "folder/data.txt" : {...}
// }
this.files = {};
// Where we are in the hierarchy
this.root = "";
if (data) {
this.load(data, options);
}
};
JSZip.signature = {
LOCAL_FILE_HEADER : "\x50\x4b\x03\x04",
CENTRAL_FILE_HEADER : "\x50\x4b\x01\x02",
CENTRAL_DIRECTORY_END : "\x50\x4b\x05\x06",
ZIP64_CENTRAL_DIRECTORY_LOCATOR : "\x50\x4b\x06\x07",
ZIP64_CENTRAL_DIRECTORY_END : "\x50\x4b\x06\x06",
DATA_DESCRIPTOR : "\x50\x4b\x07\x08"
};
// Default properties for a new file
JSZip.defaults = {
base64: false,
binary: false,
dir: false,
date: null,
compression: null
};
JSZip.prototype = (function () {
/**
* Returns the raw data of a ZipObject, decompress the content if necessary.
* @param {ZipObject} file the file to use.
* @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
*/
var getRawData = function (file) {
if (file._data instanceof JSZip.CompressedObject) {
file._data = file._data.getContent();
file.options.binary = true;
file.options.base64 = false;
if (JSZip.utils.getTypeOf(file._data) === "uint8array") {
var copy = file._data;
// when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array.
// if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file).
file._data = new Uint8Array(copy.length);
// with an empty Uint8Array, Opera fails with a "Offset larger than array size"
if (copy.length !== 0) {
file._data.set(copy, 0);
}
}
}
return file._data;
};
/**
* Returns the data of a ZipObject in a binary form. If the content is an unicode string, encode it.
* @param {ZipObject} file the file to use.
* @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
*/
var getBinaryData = function (file) {
var result = getRawData(file), type = JSZip.utils.getTypeOf(result);
if (type === "string") {
if (!file.options.binary) {
// unicode text !
// unicode string => binary string is a painful process, check if we can avoid it.
if (JSZip.support.uint8array && typeof TextEncoder === "function") {
return TextEncoder("utf-8").encode(result);
}
if (JSZip.support.nodebuffer) {
return new Buffer(result, "utf-8");
}
}
return file.asBinary();
}
return result;
}
/**
* Transform this._data into a string.
* @param {function} filter a function String -> String, applied if not null on the result.
* @return {String} the string representing this._data.
*/
var dataToString = function (asUTF8) {
var result = getRawData(this);
if (result === null || typeof result === "undefined") {
return "";
}
// if the data is a base64 string, we decode it before checking the encoding !
if (this.options.base64) {
result = JSZip.base64.decode(result);
}
if (asUTF8 && this.options.binary) {
// JSZip.prototype.utf8decode supports arrays as input
// skip to array => string step, utf8decode will do it.
result = JSZip.prototype.utf8decode(result);
} else {
// no utf8 transformation, do the array => string step.
result = JSZip.utils.transformTo("string", result);
}
if (!asUTF8 && !this.options.binary) {
result = JSZip.prototype.utf8encode(result);
}
return result;
};
/**
* A simple object representing a file in the zip file.
* @constructor
* @param {string} name the name of the file
* @param {String|ArrayBuffer|Uint8Array|Buffer} data the data
* @param {Object} options the options of the file
*/
var ZipObject = function (name, data, options) {
this.name = name;
this._data = data;
this.options = options;
};
ZipObject.prototype = {
/**
* Return the content as UTF8 string.
* @return {string} the UTF8 string.
*/
asText : function () {
return dataToString.call(this, true);
},
/**
* Returns the binary content.
* @return {string} the content as binary.
*/
asBinary : function () {
return dataToString.call(this, false);
},
/**
* Returns the content as a nodejs Buffer.
* @return {Buffer} the content as a Buffer.
*/
asNodeBuffer : function () {
var result = getBinaryData(this);
return JSZip.utils.transformTo("nodebuffer", result);
},
/**
* Returns the content as an Uint8Array.
* @return {Uint8Array} the content as an Uint8Array.
*/
asUint8Array : function () {
var result = getBinaryData(this);
return JSZip.utils.transformTo("uint8array", result);
},
/**
* Returns the content as an ArrayBuffer.
* @return {ArrayBuffer} the content as an ArrayBufer.
*/
asArrayBuffer : function () {
return this.asUint8Array().buffer;
}
};
/**
* Transform an integer into a string in hexadecimal.
* @private
* @param {number} dec the number to convert.
* @param {number} bytes the number of bytes to generate.
* @returns {string} the result.
*/
var decToHex = function(dec, bytes) {
var hex = "", i;
for(i = 0; i < bytes; i++) {
hex += String.fromCharCode(dec&0xff);
dec=dec>>>8;
}
return hex;
};
/**
* Merge the objects passed as parameters into a new one.
* @private
* @param {...Object} var_args All objects to merge.
* @return {Object} a new object with the data of the others.
*/
var extend = function () {
var result = {}, i, attr;
for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers
for (attr in arguments[i]) {
if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") {
result[attr] = arguments[i][attr];
}
}
}
return result;
};
/**
* Transforms the (incomplete) options from the user into the complete
* set of options to create a file.
* @private
* @param {Object} o the options from the user.
* @return {Object} the complete set of options.
*/
var prepareFileAttrs = function (o) {
o = o || {};
if (o.base64 === true && o.binary == null) {
o.binary = true;
}
o = extend(o, JSZip.defaults);
o.date = o.date || new Date();
if (o.compression !== null) o.compression = o.compression.toUpperCase();
return o;
};
/**
* Add a file in the current folder.
* @private
* @param {string} name the name of the file
* @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
* @param {Object} o the options of the file
* @return {Object} the new file.
*/
var fileAdd = function (name, data, o) {
// be sure sub folders exist
var parent = parentFolder(name), dataType = JSZip.utils.getTypeOf(data);
if (parent) {
folderAdd.call(this, parent);
}
o = prepareFileAttrs(o);
if (o.dir || data === null || typeof data === "undefined") {
o.base64 = false;
o.binary = false;
data = null;
} else if (dataType === "string") {
if (o.binary && !o.base64) {
// optimizedBinaryString == true means that the file has already been filtered with a 0xFF mask
if (o.optimizedBinaryString !== true) {
// this is a string, not in a base64 format.
// Be sure that this is a correct "binary string"
data = JSZip.utils.string2binary(data);
}
}
} else { // arraybuffer, uint8array, ...
o.base64 = false;
o.binary = true;
if (!dataType && !(data instanceof JSZip.CompressedObject)) {
throw new Error("The data of '" + name + "' is in an unsupported format !");
}
// special case : it's way easier to work with Uint8Array than with ArrayBuffer
if (dataType === "arraybuffer") {
data = JSZip.utils.transformTo("uint8array", data);
}
}
return this.files[name] = new ZipObject(name, data, o);
};
/**
* Find the parent folder of the path.
* @private
* @param {string} path the path to use
* @return {string} the parent folder, or ""
*/
var parentFolder = function (path) {
if (path.slice(-1) == '/') {
path = path.substring(0, path.length - 1);
}
var lastSlash = path.lastIndexOf('/');
return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
};
/**
* Add a (sub) folder in the current folder.
* @private
* @param {string} name the folder's name
* @return {Object} the new folder.
*/
var folderAdd = function (name) {
// Check the name ends with a /
if (name.slice(-1) != "/") {
name += "/"; // IE doesn't like substr(-1)
}
// Does this folder already exist?
if (!this.files[name]) {
fileAdd.call(this, name, null, {dir:true});
}
return this.files[name];
};
/**
* Generate a JSZip.CompressedObject for a given zipOject.
* @param {ZipObject} file the object to read.
* @param {JSZip.compression} compression the compression to use.
* @return {JSZip.CompressedObject} the compressed result.
*/
var generateCompressedObjectFrom = function (file, compression) {
var result = new JSZip.CompressedObject(), content;
// the data has not been decompressed, we might reuse things !
if (file._data instanceof JSZip.CompressedObject) {
result.uncompressedSize = file._data.uncompressedSize;
result.crc32 = file._data.crc32;
if (result.uncompressedSize === 0 || file.options.dir) {
compression = JSZip.compressions['STORE'];
result.compressedContent = "";
result.crc32 = 0;
} else if (file._data.compressionMethod === compression.magic) {
result.compressedContent = file._data.getCompressedContent();
} else {
content = file._data.getContent()
// need to decompress / recompress
result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
}
} else {
// have uncompressed data
content = getBinaryData(file);
if (!content || content.length === 0 || file.options.dir) {
compression = JSZip.compressions['STORE'];
content = "";
}
result.uncompressedSize = content.length;
result.crc32 = this.crc32(content);
result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
}
result.compressedSize = result.compressedContent.length;
result.compressionMethod = compression.magic;
return result;
};
/**
* Generate the various parts used in the construction of the final zip file.
* @param {string} name the file name.
* @param {ZipObject} file the file content.
* @param {JSZip.CompressedObject} compressedObject the compressed object.
* @param {number} offset the current offset from the start of the zip file.
* @return {object} the zip parts.
*/
var generateZipParts = function(name, file, compressedObject, offset) {
var data = compressedObject.compressedContent,
utfEncodedFileName = this.utf8encode(file.name),
useUTF8 = utfEncodedFileName !== file.name,
o = file.options,
dosTime,
dosDate;
// date
// @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
// @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
// @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html
dosTime = o.date.getHours();
dosTime = dosTime << 6;
dosTime = dosTime | o.date.getMinutes();
dosTime = dosTime << 5;
dosTime = dosTime | o.date.getSeconds() / 2;
dosDate = o.date.getFullYear() - 1980;
dosDate = dosDate << 4;
dosDate = dosDate | (o.date.getMonth() + 1);
dosDate = dosDate << 5;
dosDate = dosDate | o.date.getDate();
var header = "";
// version needed to extract
header += "\x0A\x00";
// general purpose bit flag
// set bit 11 if utf8
header += useUTF8 ? "\x00\x08" : "\x00\x00";
// compression method
header += compressedObject.compressionMethod;
// last mod file time
header += decToHex(dosTime, 2);
// last mod file date
header += decToHex(dosDate, 2);
// crc-32
header += decToHex(compressedObject.crc32, 4);
// compressed size
header += decToHex(compressedObject.compressedSize, 4);
// uncompressed size
header += decToHex(compressedObject.uncompressedSize, 4);
// file name length
header += decToHex(utfEncodedFileName.length, 2);
// extra field length
header += "\x00\x00";
var fileRecord = JSZip.signature.LOCAL_FILE_HEADER + header + utfEncodedFileName;
var dirRecord = JSZip.signature.CENTRAL_FILE_HEADER +
// version made by (00: DOS)
"\x14\x00" +
// file header (common to file and central directory)
header +
// file comment length
"\x00\x00" +
// disk number start
"\x00\x00" +
// internal file attributes TODO
"\x00\x00" +
// external file attributes
(file.options.dir===true?"\x10\x00\x00\x00":"\x00\x00\x00\x00")+
// relative offset of local header
decToHex(offset, 4) +
// file name
utfEncodedFileName;
return {
fileRecord : fileRecord,
dirRecord : dirRecord,
compressedObject : compressedObject
};
};
/**
* An object to write any content to a string.
* @constructor
*/
var StringWriter = function () {
this.data = [];
};
StringWriter.prototype = {
/**
* Append any content to the current string.
* @param {Object} input the content to add.
*/
append : function (input) {
input = JSZip.utils.transformTo("string", input);
this.data.push(input);
},
/**
* Finalize the construction an return the result.
* @return {string} the generated string.
*/
finalize : function () {
return this.data.join("");
}
};
/**
* An object to write any content to an Uint8Array.
* @constructor
* @param {number} length The length of the array.
*/
var Uint8ArrayWriter = function (length) {
this.data = new Uint8Array(length);
this.index = 0;
};
Uint8ArrayWriter.prototype = {
/**
* Append any content to the current array.
* @param {Object} input the content to add.
*/
append : function (input) {
if (input.length !== 0) {
// with an empty Uint8Array, Opera fails with a "Offset larger than array size"
input = JSZip.utils.transformTo("uint8array", input);
this.data.set(input, this.index);
this.index += input.length;
}
},
/**
* Finalize the construction an return the result.
* @return {Uint8Array} the generated array.
*/
finalize : function () {
return this.data;
}
};
// return the actual prototype of JSZip
return {
/**
* Read an existing zip and merge the data in the current JSZip object.
* The implementation is in jszip-load.js, don't forget to include it.
* @param {String|ArrayBuffer|Uint8Array|Buffer} stream The stream to load
* @param {Object} options Options for loading the stream.
* options.base64 : is the stream in base64 ? default : false
* @return {JSZip} the current JSZip object
*/
load : function (stream, options) {
throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
},
/**
* Filter nested files/folders with the specified function.
* @param {Function} search the predicate to use :
* function (relativePath, file) {...}
* It takes 2 arguments : the relative path and the file.
* @return {Array} An array of matching elements.
*/
filter : function (search) {
var result = [], filename, relativePath, file, fileClone;
for (filename in this.files) {
if ( !this.files.hasOwnProperty(filename) ) { continue; }
file = this.files[filename];
// return a new object, don't let the user mess with our internal objects :)
fileClone = new ZipObject(file.name, file._data, extend(file.options));
relativePath = filename.slice(this.root.length, filename.length);
if (filename.slice(0, this.root.length) === this.root && // the file is in the current root
search(relativePath, fileClone)) { // and the file matches the function
result.push(fileClone);
}
}
return result;
},
/**
* Add a file to the zip file, or search a file.
* @param {string|RegExp} name The name of the file to add (if data is defined),
* the name of the file to find (if no data) or a regex to match files.
* @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded
* @param {Object} o File options
* @return {JSZip|Object|Array} this JSZip object (when adding a file),
* a file (when searching by string) or an array of files (when searching by regex).
*/
file : function(name, data, o) {
if (arguments.length === 1) {
if (name instanceof RegExp) {
var regexp = name;
return this.filter(function(relativePath, file) {
return !file.options.dir && regexp.test(relativePath);
});
} else { // text
return this.filter(function (relativePath, file) {
return !file.options.dir && relativePath === name;
})[0]||null;
}
} else { // more than one argument : we have data !
name = this.root+name;
fileAdd.call(this, name, data, o);
}
return this;
},
/**
* Add a directory to the zip file, or search.
* @param {String|RegExp} arg The name of the directory to add, or a regex to search folders.
* @return {JSZip} an object with the new directory as the root, or an array containing matching folders.
*/
folder : function(arg) {
if (!arg) {
return this;
}
if (arg instanceof RegExp) {
return this.filter(function(relativePath, file) {
return file.options.dir && arg.test(relativePath);
});
}
// else, name is a new folder
var name = this.root + arg;
var newFolder = folderAdd.call(this, name);
// Allow chaining by returning a new object with this folder as the root
var ret = this.clone();
ret.root = newFolder.name;
return ret;
},
/**
* Delete a file, or a directory and all sub-files, from the zip
* @param {string} name the name of the file to delete
* @return {JSZip} this JSZip object
*/
remove : function(name) {
name = this.root + name;
var file = this.files[name];
if (!file) {
// Look for any folders
if (name.slice(-1) != "/") {
name += "/";
}
file = this.files[name];
}
if (file) {
if (!file.options.dir) {
// file
delete this.files[name];
} else {
// folder
var kids = this.filter(function (relativePath, file) {
return file.name.slice(0, name.length) === name;
});
for (var i = 0; i < kids.length; i++) {
delete this.files[kids[i].name];
}
}
}
return this;
},
/**
* Generate the complete zip file
* @param {Object} options the options to generate the zip file :
* - base64, (deprecated, use type instead) true to generate base64.
* - compression, "STORE" by default.
* - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
* @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file
*/
generate : function(options) {
options = extend(options || {}, {
base64 : true,
compression : "STORE",
type : "base64"
});
JSZip.utils.checkSupport(options.type);
var zipData = [], localDirLength = 0, centralDirLength = 0, writer, i;
// first, generate all the zip parts.
for (var name in this.files) {
if ( !this.files.hasOwnProperty(name) ) { continue; }
var file = this.files[name];
var compressionName = file.compression || options.compression.toUpperCase();
var compression = JSZip.compressions[compressionName];
if (!compression) {
throw new Error(compressionName + " is not a valid compression method !");
}
var compressedObject = generateCompressedObjectFrom.call(this, file, compression);
var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength);
localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize;
centralDirLength += zipPart.dirRecord.length;
zipData.push(zipPart);
}
var dirEnd = "";
// end of central dir signature
dirEnd = JSZip.signature.CENTRAL_DIRECTORY_END +
// number of this disk
"\x00\x00" +
// number of the disk with the start of the central directory
"\x00\x00" +
// total number of entries in the central directory on this disk
decToHex(zipData.length, 2) +
// total number of entries in the central directory
decToHex(zipData.length, 2) +
// size of the central directory 4 bytes
decToHex(centralDirLength, 4) +
// offset of start of central directory with respect to the starting disk number
decToHex(localDirLength, 4) +
// .ZIP file comment length
"\x00\x00";
// we have all the parts (and the total length)
// time to create a writer !
switch(options.type.toLowerCase()) {
case "uint8array" :
case "arraybuffer" :
case "blob" :
case "nodebuffer" :
writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
break;
case "base64" :
default : // case "string" :
writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
break;
}
for (i = 0; i < zipData.length; i++) {
writer.append(zipData[i].fileRecord);
writer.append(zipData[i].compressedObject.compressedContent);
}
for (i = 0; i < zipData.length; i++) {
writer.append(zipData[i].dirRecord);
}
writer.append(dirEnd);
var zip = writer.finalize();
switch(options.type.toLowerCase()) {
// case "zip is an Uint8Array"
case "uint8array" :
case "arraybuffer" :
case "nodebuffer" :
return JSZip.utils.transformTo(options.type.toLowerCase(), zip);
case "blob" :
return JSZip.utils.arrayBuffer2Blob(JSZip.utils.transformTo("arraybuffer", zip));
// case "zip is a string"
case "base64" :
return (options.base64) ? JSZip.base64.encode(zip) : zip;
default : // case "string" :
return zip;
}
},
/**
*
* Javascript crc32
* http://www.webtoolkit.info/
*
*/
crc32 : function crc32(input, crc) {
if (typeof input === "undefined" || !input.length) {
return 0;
}
var isArray = JSZip.utils.getTypeOf(input) !== "string";
var table = [
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
];
if (typeof(crc) == "undefined") { crc = 0; }
var x = 0;
var y = 0;
var byte = 0;
crc = crc ^ (-1);
for( var i = 0, iTop = input.length; i < iTop; i++ ) {
byte = isArray ? input[i] : input.charCodeAt(i);
y = ( crc ^ byte ) & 0xFF;
x = table[y];
crc = ( crc >>> 8 ) ^ x;
}
return crc ^ (-1);
},
// Inspired by http://my.opera.com/GreyWyvern/blog/show.dml/1725165
clone : function() {
var newObj = new JSZip();
for (var i in this) {
if (typeof this[i] !== "function") {
newObj[i] = this[i];
}
}
return newObj;
},
/**
* http://www.webtoolkit.info/javascript-utf8.html
*/
utf8encode : function (string) {
// TextEncoder + Uint8Array to binary string is faster than checking every bytes on long strings.
// http://jsperf.com/utf8encode-vs-textencoder
// On short strings (file names for example), the TextEncoder API is (currently) slower.
if (JSZip.support.uint8array && typeof TextEncoder === "function") {
var u8 = TextEncoder("utf-8").encode(string);
return JSZip.utils.transformTo("string", u8);
}
if (JSZip.support.nodebuffer) {
return JSZip.utils.transformTo("string", new Buffer(string, "utf-8"));
}
// array.join may be slower than string concatenation but generates less objects (less time spent garbage collecting).
// See also http://jsperf.com/array-direct-assignment-vs-push/31
var result = [], resIndex = 0;
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
result[resIndex++] = String.fromCharCode(c);
} else if ((c > 127) && (c < 2048)) {
result[resIndex++] = String.fromCharCode((c >> 6) | 192);
result[resIndex++] = String.fromCharCode((c & 63) | 128);
} else {
result[resIndex++] = String.fromCharCode((c >> 12) | 224);
result[resIndex++] = String.fromCharCode(((c >> 6) & 63) | 128);
result[resIndex++] = String.fromCharCode((c & 63) | 128);
}
}
return result.join("");
},
/**
* http://www.webtoolkit.info/javascript-utf8.html
*/
utf8decode : function (input) {
var result = [], resIndex = 0;
var type = JSZip.utils.getTypeOf(input);
var isArray = type !== "string";
var i = 0;
var c = 0, c1 = 0, c2 = 0, c3 = 0;
// check if we can use the TextDecoder API
// see http://encoding.spec.whatwg.org/#api
if (JSZip.support.uint8array && typeof TextDecoder === "function") {
return TextDecoder("utf-8").decode(
JSZip.utils.transformTo("uint8array", input)
);
}
if (JSZip.support.nodebuffer) {
return JSZip.utils.transformTo("nodebuffer", input).toString("utf-8");
}
while ( i < input.length ) {
c = isArray ? input[i] : input.charCodeAt(i);
if (c < 128) {
result[resIndex++] = String.fromCharCode(c);
i++;
} else if ((c > 191) && (c < 224)) {
c2 = isArray ? input[i+1] : input.charCodeAt(i+1);
result[resIndex++] = String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
} else {
c2 = isArray ? input[i+1] : input.charCodeAt(i+1);
c3 = isArray ? input[i+2] : input.charCodeAt(i+2);
result[resIndex++] = String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return result.join("");
}
};
}());
/*
* Compression methods
* This object is filled in as follow :
* name : {
* magic // the 2 bytes indentifying the compression method
* compress // function, take the uncompressed content and return it compressed.
* uncompress // function, take the compressed content and return it uncompressed.
* compressInputType // string, the type accepted by the compress method. null to accept everything.
* uncompressInputType // string, the type accepted by the uncompress method. null to accept everything.
* }
*
* STORE is the default compression method, so it's included in this file.
* Other methods should go to separated files : the user wants modularity.
*/
JSZip.compressions = {
"STORE" : {
magic : "\x00\x00",
compress : function (content) {
return content; // no compression
},
uncompress : function (content) {
return content; // no compression
},
compressInputType : null,
uncompressInputType : null
}
};
/*
* List features that require a modern browser, and if the current browser support them.
*/
JSZip.support = {
// contains true if JSZip can read/generate ArrayBuffer, false otherwise.
arraybuffer : (function(){
return typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
})(),
// contains true if JSZip can read/generate nodejs Buffer, false otherwise.
nodebuffer : (function(){
return typeof Buffer !== "undefined";
})(),
// contains true if JSZip can read/generate Uint8Array, false otherwise.
uint8array : (function(){
return typeof Uint8Array !== "undefined";
})(),
// contains true if JSZip can read/generate Blob, false otherwise.
blob : (function(){
// the spec started with BlobBuilder then replaced it with a construtor for Blob.
// Result : we have browsers that :
// * know the BlobBuilder (but with prefix)
// * know the Blob constructor
// * know about Blob but not about how to build them
// About the "=== 0" test : if given the wrong type, it may be converted to a string.
// Instead of an empty content, we will get "[object Uint8Array]" for example.
if (typeof ArrayBuffer === "undefined") {
return false;
}
var buffer = new ArrayBuffer(0);
try {
return new Blob([buffer], { type: "application/zip" }).size === 0;
}
catch(e) {}
try {
var builder = new (window.BlobBuilder || window.WebKitBlobBuilder ||
window.MozBlobBuilder || window.MSBlobBuilder)();
builder.append(buffer);
return builder.getBlob('application/zip').size === 0;
}
catch(e) {}
return false;
})()
};
(function () {
JSZip.utils = {
/**
* Convert a string to a "binary string" : a string containing only char codes between 0 and 255.
* @param {string} str the string to transform.
* @return {String} the binary string.
*/
string2binary : function (str) {
var result = "";
for (var i = 0; i < str.length; i++) {
result += String.fromCharCode(str.charCodeAt(i) & 0xff);
}
return result;
},
/**
* Create a Uint8Array from the string.
* @param {string} str the string to transform.
* @return {Uint8Array} the typed array.
* @throws {Error} an Error if the browser doesn't support the requested feature.
* @deprecated : use JSZip.utils.transformTo instead.
*/
string2Uint8Array : function (str) {
return JSZip.utils.transformTo("uint8array", str);
},
/**
* Create a string from the Uint8Array.
* @param {Uint8Array} array the array to transform.
* @return {string} the string.
* @throws {Error} an Error if the browser doesn't support the requested feature.
* @deprecated : use JSZip.utils.transformTo instead.
*/
uint8Array2String : function (array) {
return JSZip.utils.transformTo("string", array);
},
/**
* Create a blob from the given ArrayBuffer.
* @param {ArrayBuffer} buffer the buffer to transform.
* @return {Blob} the result.
* @throws {Error} an Error if the browser doesn't support the requested feature.
*/
arrayBuffer2Blob : function (buffer) {
JSZip.utils.checkSupport("blob");
try {
// Blob constructor
return new Blob([buffer], { type: "application/zip" });
}
catch(e) {}
try {
// deprecated, browser only, old way
var builder = new (window.BlobBuilder || window.WebKitBlobBuilder ||
window.MozBlobBuilder || window.MSBlobBuilder)();
builder.append(buffer);
return builder.getBlob('application/zip');
}
catch(e) {}
// well, fuck ?!
throw new Error("Bug : can't construct the Blob.");
},
/**
* Create a blob from the given string.
* @param {string} str the string to transform.
* @return {Blob} the result.
* @throws {Error} an Error if the browser doesn't support the requested feature.
*/
string2Blob : function (str) {
var buffer = JSZip.utils.transformTo("arraybuffer", str);
return JSZip.utils.arrayBuffer2Blob(buffer);
}
};
/**
* The identity function.
* @param {Object} input the input.
* @return {Object} the same input.
*/
function identity(input) {
return input;
};
/**
* Fill in an array with a string.
* @param {String} str the string to use.
* @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated).
* @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array.
*/
function stringToArrayLike(str, array) {
for (var i = 0; i < str.length; ++i) {
array[i] = str.charCodeAt(i) & 0xFF;
}
return array;
};
/**
* Transform an array-like object to a string.
* @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
* @return {String} the result.
*/
function arrayLikeToString(array) {
// Performances notes :
// --------------------
// String.fromCharCode.apply(null, array) is the fastest, see
// see http://jsperf.com/converting-a-uint8array-to-a-string/2
// but the stack is limited (and we can get huge arrays !).
//
// result += String.fromCharCode(array[i]); generate too many strings !
//
// This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2
var chunk = 65536;
var result = [], len = array.length, type = JSZip.utils.getTypeOf(array), k = 0;
while (k < len && chunk > 1) {
try {
if (type === "array" || type === "nodebuffer") {
result.push(String.fromCharCode.apply(null, array.slice(k, Math.max(k + chunk, len))));
} else {
result.push(String.fromCharCode.apply(null, array.subarray(k, k + chunk)));
}
k += chunk;
} catch (e) {
chunk = Math.floor(chunk / 2);
}
}
return result.join("");
};
/**
* Copy the data from an array-like to an other array-like.
* @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array.
* @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated.
* @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array.
*/
function arrayLikeToArrayLike(arrayFrom, arrayTo) {
for(var i = 0; i < arrayFrom.length; i++) {
arrayTo[i] = arrayFrom[i];
}
return arrayTo;
};
// a matrix containing functions to transform everything into everything.
var transform = {};
// string to ?
transform["string"] = {
"string" : identity,
"array" : function (input) {
return stringToArrayLike(input, new Array(input.length));
},
"arraybuffer" : function (input) {
return transform["string"]["uint8array"](input).buffer;
},
"uint8array" : function (input) {
return stringToArrayLike(input, new Uint8Array(input.length));
},
"nodebuffer" : function (input) {
return stringToArrayLike(input, new Buffer(input.length));
}
};
// array to ?
transform["array"] = {
"string" : arrayLikeToString,
"array" : identity,
"arraybuffer" : function (input) {
return (new Uint8Array(input)).buffer;
},
"uint8array" : function (input) {
return new Uint8Array(input);
},
"nodebuffer" : function (input) {
return new Buffer(input);
}
};
// arraybuffer to ?
transform["arraybuffer"] = {
"string" : function (input) {
return arrayLikeToString(new Uint8Array(input));
},
"array" : function (input) {
return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
},
"arraybuffer" : identity,
"uint8array" : function (input) {
return new Uint8Array(input);
},
"nodebuffer" : function (input) {
return new Buffer(new Uint8Array(input));
}
};
// uint8array to ?
transform["uint8array"] = {
"string" : arrayLikeToString,
"array" : function (input) {
return arrayLikeToArrayLike(input, new Array(input.length));
},
"arraybuffer" : function (input) {
return input.buffer;
},
"uint8array" : identity,
"nodebuffer" : function(input) {
return new Buffer(input);
}
};
// nodebuffer to ?
transform["nodebuffer"] = {
"string" : arrayLikeToString,
"array" : function (input) {
return arrayLikeToArrayLike(input, new Array(input.length));
},
"arraybuffer" : function (input) {
return transform["nodebuffer"]["uint8array"](input).buffer;
},
"uint8array" : function (input) {
return arrayLikeToArrayLike(input, new Uint8Array(input.length));
},
"nodebuffer" : identity
};
/**
* Transform an input into any type.
* The supported output type are : string, array, uint8array, arraybuffer, nodebuffer.
* If no output type is specified, the unmodified input will be returned.
* @param {String} outputType the output type.
* @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert.
* @throws {Error} an Error if the browser doesn't support the requested output type.
*/
JSZip.utils.transformTo = function (outputType, input) {
if (!input) {
// undefined, null, etc
// an empty string won't harm.
input = "";
}
if (!outputType) {
return input;
}
JSZip.utils.checkSupport(outputType);
var inputType = JSZip.utils.getTypeOf(input);
var result = transform[inputType][outputType](input);
return result;
};
/**
* Return the type of the input.
* The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer.
* @param {Object} input the input to identify.
* @return {String} the (lowercase) type of the input.
*/
JSZip.utils.getTypeOf = function (input) {
if (typeof input === "string") {
return "string";
}
if (input instanceof Array) {
return "array";
}
if (JSZip.support.nodebuffer && Buffer.isBuffer(input)) {
return "nodebuffer";
}
if (JSZip.support.uint8array && input instanceof Uint8Array) {
return "uint8array";
}
if (JSZip.support.arraybuffer && input instanceof ArrayBuffer) {
return "arraybuffer";
}
};
/**
* Throw an exception if the type is not supported.
* @param {String} type the type to check.
* @throws {Error} an Error if the browser doesn't support the requested type.
*/
JSZip.utils.checkSupport = function (type) {
var supported = true;
switch (type.toLowerCase()) {
case "uint8array":
supported = JSZip.support.uint8array;
break;
case "arraybuffer":
supported = JSZip.support.arraybuffer;
break;
case "nodebuffer":
supported = JSZip.support.nodebuffer;
break;
case "blob":
supported = JSZip.support.blob;
break;
}
if (!supported) {
throw new Error(type + " is not supported by this browser");
}
};
})();
(function (){
/**
* Represents an entry in the zip.
* The content may or may not be compressed.
* @constructor
*/
JSZip.CompressedObject = function () {
this.compressedSize = 0;
this.uncompressedSize = 0;
this.crc32 = 0;
this.compressionMethod = null;
this.compressedContent = null;
};
JSZip.CompressedObject.prototype = {
/**
* Return the decompressed content in an unspecified format.
* The format will depend on the decompressor.
* @return {Object} the decompressed content.
*/
getContent : function () {
return null; // see implementation
},
/**
* Return the compressed content in an unspecified format.
* The format will depend on the compressed conten source.
* @return {Object} the compressed content.
*/
getCompressedContent : function () {
return null; // see implementation
}
};
})();
/**
*
* Base64 encode / decode
* http://www.webtoolkit.info/
*
* Hacked so that it doesn't utf8 en/decode everything
**/
JSZip.base64 = (function() {
// private property
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
return {
// public method for encoding
encode : function(input, utf8) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
_keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
_keyStr.charAt(enc3) + _keyStr.charAt(enc4);
}
return output;
},
// public method for decoding
decode : function(input, utf8) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = _keyStr.indexOf(input.charAt(i++));
enc2 = _keyStr.indexOf(input.charAt(i++));
enc3 = _keyStr.indexOf(input.charAt(i++));
enc4 = _keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
return output;
}
};
}());
// enforcing Stuk's coding style
// vim: set shiftwidth=3 softtabstop=3:
"use strict";
(function () {
if(!JSZip) {
throw "JSZip not defined";
}
var context = {};
(function () {
// https://github.com/imaya/zlib.js
// tag 0.1.6
// file bin/deflate.min.js
/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';var l=void 0,p=this;function q(c,d){var a=c.split("."),b=p;!(a[0]in b)&&b.execScript&&b.execScript("var "+a[0]);for(var e;a.length&&(e=a.shift());)!a.length&&d!==l?b[e]=d:b=b[e]?b[e]:b[e]={}};var r="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array;function u(c){var d=c.length,a=0,b=Number.POSITIVE_INFINITY,e,f,g,h,k,m,s,n,t;for(n=0;n<d;++n)c[n]>a&&(a=c[n]),c[n]<b&&(b=c[n]);e=1<<a;f=new (r?Uint32Array:Array)(e);g=1;h=0;for(k=2;g<=a;){for(n=0;n<d;++n)if(c[n]===g){m=0;s=h;for(t=0;t<g;++t)m=m<<1|s&1,s>>=1;for(t=m;t<e;t+=k)f[t]=g<<16|n;++h}++g;h<<=1;k<<=1}return[f,a,b]};function v(c,d){this.g=[];this.h=32768;this.c=this.f=this.d=this.k=0;this.input=r?new Uint8Array(c):c;this.l=!1;this.i=w;this.p=!1;if(d||!(d={}))d.index&&(this.d=d.index),d.bufferSize&&(this.h=d.bufferSize),d.bufferType&&(this.i=d.bufferType),d.resize&&(this.p=d.resize);switch(this.i){case x:this.a=32768;this.b=new (r?Uint8Array:Array)(32768+this.h+258);break;case w:this.a=0;this.b=new (r?Uint8Array:Array)(this.h);this.e=this.u;this.m=this.r;this.j=this.s;break;default:throw Error("invalid inflate mode");
}}var x=0,w=1;
v.prototype.t=function(){for(;!this.l;){var c=y(this,3);c&1&&(this.l=!0);c>>>=1;switch(c){case 0:var d=this.input,a=this.d,b=this.b,e=this.a,f=l,g=l,h=l,k=b.length,m=l;this.c=this.f=0;f=d[a++];if(f===l)throw Error("invalid uncompressed block header: LEN (first byte)");g=f;f=d[a++];if(f===l)throw Error("invalid uncompressed block header: LEN (second byte)");g|=f<<8;f=d[a++];if(f===l)throw Error("invalid uncompressed block header: NLEN (first byte)");h=f;f=d[a++];if(f===l)throw Error("invalid uncompressed block header: NLEN (second byte)");h|=
f<<8;if(g===~h)throw Error("invalid uncompressed block header: length verify");if(a+g>d.length)throw Error("input buffer is broken");switch(this.i){case x:for(;e+g>b.length;){m=k-e;g-=m;if(r)b.set(d.subarray(a,a+m),e),e+=m,a+=m;else for(;m--;)b[e++]=d[a++];this.a=e;b=this.e();e=this.a}break;case w:for(;e+g>b.length;)b=this.e({o:2});break;default:throw Error("invalid inflate mode");}if(r)b.set(d.subarray(a,a+g),e),e+=g,a+=g;else for(;g--;)b[e++]=d[a++];this.d=a;this.a=e;this.b=b;break;case 1:this.j(z,
A);break;case 2:B(this);break;default:throw Error("unknown BTYPE: "+c);}}return this.m()};
var C=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],D=r?new Uint16Array(C):C,E=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],F=r?new Uint16Array(E):E,G=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],H=r?new Uint8Array(G):G,I=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],J=r?new Uint16Array(I):I,K=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,
13],L=r?new Uint8Array(K):K,M=new (r?Uint8Array:Array)(288),N,O;N=0;for(O=M.length;N<O;++N)M[N]=143>=N?8:255>=N?9:279>=N?7:8;var z=u(M),P=new (r?Uint8Array:Array)(30),Q,R;Q=0;for(R=P.length;Q<R;++Q)P[Q]=5;var A=u(P);function y(c,d){for(var a=c.f,b=c.c,e=c.input,f=c.d,g;b<d;){g=e[f++];if(g===l)throw Error("input buffer is broken");a|=g<<b;b+=8}g=a&(1<<d)-1;c.f=a>>>d;c.c=b-d;c.d=f;return g}
function S(c,d){for(var a=c.f,b=c.c,e=c.input,f=c.d,g=d[0],h=d[1],k,m,s;b<h;){k=e[f++];if(k===l)break;a|=k<<b;b+=8}m=g[a&(1<<h)-1];s=m>>>16;c.f=a>>s;c.c=b-s;c.d=f;return m&65535}
function B(c){function d(a,c,b){var d,f,e,g;for(g=0;g<a;)switch(d=S(this,c),d){case 16:for(e=3+y(this,2);e--;)b[g++]=f;break;case 17:for(e=3+y(this,3);e--;)b[g++]=0;f=0;break;case 18:for(e=11+y(this,7);e--;)b[g++]=0;f=0;break;default:f=b[g++]=d}return b}var a=y(c,5)+257,b=y(c,5)+1,e=y(c,4)+4,f=new (r?Uint8Array:Array)(D.length),g,h,k,m;for(m=0;m<e;++m)f[D[m]]=y(c,3);g=u(f);h=new (r?Uint8Array:Array)(a);k=new (r?Uint8Array:Array)(b);c.j(u(d.call(c,a,g,h)),u(d.call(c,b,g,k)))}
v.prototype.j=function(c,d){var a=this.b,b=this.a;this.n=c;for(var e=a.length-258,f,g,h,k;256!==(f=S(this,c));)if(256>f)b>=e&&(this.a=b,a=this.e(),b=this.a),a[b++]=f;else{g=f-257;k=F[g];0<H[g]&&(k+=y(this,H[g]));f=S(this,d);h=J[f];0<L[f]&&(h+=y(this,L[f]));b>=e&&(this.a=b,a=this.e(),b=this.a);for(;k--;)a[b]=a[b++-h]}for(;8<=this.c;)this.c-=8,this.d--;this.a=b};
v.prototype.s=function(c,d){var a=this.b,b=this.a;this.n=c;for(var e=a.length,f,g,h,k;256!==(f=S(this,c));)if(256>f)b>=e&&(a=this.e(),e=a.length),a[b++]=f;else{g=f-257;k=F[g];0<H[g]&&(k+=y(this,H[g]));f=S(this,d);h=J[f];0<L[f]&&(h+=y(this,L[f]));b+k>e&&(a=this.e(),e=a.length);for(;k--;)a[b]=a[b++-h]}for(;8<=this.c;)this.c-=8,this.d--;this.a=b};
v.prototype.e=function(){var c=new (r?Uint8Array:Array)(this.a-32768),d=this.a-32768,a,b,e=this.b;if(r)c.set(e.subarray(32768,c.length));else{a=0;for(b=c.length;a<b;++a)c[a]=e[a+32768]}this.g.push(c);this.k+=c.length;if(r)e.set(e.subarray(d,d+32768));else for(a=0;32768>a;++a)e[a]=e[d+a];this.a=32768;return e};
v.prototype.u=function(c){var d,a=this.input.length/this.d+1|0,b,e,f,g=this.input,h=this.b;c&&("number"===typeof c.o&&(a=c.o),"number"===typeof c.q&&(a+=c.q));2>a?(b=(g.length-this.d)/this.n[2],f=258*(b/2)|0,e=f<h.length?h.length+f:h.length<<1):e=h.length*a;r?(d=new Uint8Array(e),d.set(h)):d=h;return this.b=d};
v.prototype.m=function(){var c=0,d=this.b,a=this.g,b,e=new (r?Uint8Array:Array)(this.k+(this.a-32768)),f,g,h,k;if(0===a.length)return r?this.b.subarray(32768,this.a):this.b.slice(32768,this.a);f=0;for(g=a.length;f<g;++f){b=a[f];h=0;for(k=b.length;h<k;++h)e[c++]=b[h]}f=32768;for(g=this.a;f<g;++f)e[c++]=d[f];this.g=[];return this.buffer=e};
v.prototype.r=function(){var c,d=this.a;r?this.p?(c=new Uint8Array(d),c.set(this.b.subarray(0,d))):c=this.b.subarray(0,d):(this.b.length>d&&(this.b.length=d),c=this.b);return this.buffer=c};q("Zlib.RawInflate",v);q("Zlib.RawInflate.prototype.decompress",v.prototype.t);var T={ADAPTIVE:w,BLOCK:x},U,V,W,X;if(Object.keys)U=Object.keys(T);else for(V in U=[],W=0,T)U[W++]=V;W=0;for(X=U.length;W<X;++W)V=U[W],q("Zlib.RawInflate.BufferType."+V,T[V]);}).call(this);
}).call(context);
var uncompress = function (input) {
var inflate = new context.Zlib.RawInflate(input);
return inflate.decompress();
};
var USE_TYPEDARRAY =
(typeof Uint8Array !== 'undefined') &&
(typeof Uint16Array !== 'undefined') &&
(typeof Uint32Array !== 'undefined');
// we add the compression method for JSZip
if(!JSZip.compressions["DEFLATE"]) {
JSZip.compressions["DEFLATE"] = {
magic : "\x08\x00",
uncompress : uncompress,
uncompressInputType : USE_TYPEDARRAY ? "uint8array" : "array"
}
} else {
JSZip.compressions["DEFLATE"].uncompress = uncompress;
JSZip.compressions["DEFLATE"].uncompressInputType = USE_TYPEDARRAY ? "uint8array" : "array";
}
})();
// enforcing Stuk's coding style
// vim: set shiftwidth=3 softtabstop=3:
// 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.
'use strict';
/**
* @fileoverview GzipImporter inflates gzip compressed data and passes it along
* to an actual importer.
*/
base.require('tracing.importer.importer');
base.require('tracing.trace_model');
base.requireRawScript('../third_party/jszip/jszip.js');
base.requireRawScript('../third_party/jszip/jszip-inflate.js');
base.exportTo('tracing.importer', function() {
var Importer = tracing.importer.Importer;
var GZIP_HEADER_ID1 = 0x1f;
var GZIP_HEADER_ID2 = 0x8b;
var GZIP_DEFLATE_COMPRESSION = 8;
function GzipImporter(model, eventData) {
// Normalize the data into an Uint8Array.
if (typeof(eventData) === 'string' || eventData instanceof String) {
eventData = GzipImporter.unescapeData_(eventData);
eventData = JSZip.utils.transformTo('uint8array', eventData);
} else if (eventData instanceof ArrayBuffer) {
eventData = new Uint8Array(eventData);
} else
throw new Error('Unknown gzip data format');
this.model_ = model;
this.gzipData_ = eventData;
}
/**
* @param {eventData} Possibly gzip compressed data as a string or an
* ArrayBuffer. If this is a string, it is assumed to be
* escaped as described in {unescapeData_} below.
* @return {boolean} Whether obj looks like gzip compressed data.
*/
GzipImporter.canImport = function(eventData) {
var header;
if (eventData instanceof ArrayBuffer)
header = new Uint8Array(eventData.slice(0, 3));
else if (typeof(eventData) === 'string' || eventData instanceof String) {
header = this.unescapeData_(eventData.substring(0, 7));
header =
[header.charCodeAt(0), header.charCodeAt(1), header.charCodeAt(2)];
} else
return false;
return header[0] == GZIP_HEADER_ID1 &&
header[1] == GZIP_HEADER_ID2 &&
header[2] == GZIP_DEFLATE_COMPRESSION;
};
/**
* @param {data} A string that has been escaped so that negative bytes
* (> 0x7f) are represented as a charcode of 0xffff followed
* by the byte value encoded as four hexadecimal characters.
* @return {string} Unescaped string.
*/
GzipImporter.unescapeData_ = function(data) {
var result = [];
for (var i = 0; i < data.length; i++) {
var charCode = data.charCodeAt(i);
if (charCode == 0xffff) {
if (i + 4 >= data.length)
throw new Error('Unexpected end of gzip data');
charCode = parseInt(data.substr(i + 1, 4), 16) & 0xff;
i += 4;
}
result.push(String.fromCharCode(charCode));
}
return result.join('');
};
/**
* @return {string} The input string escaped as described in unescapeData_.
*/
GzipImporter.escapeData_ = function(data) {
var result = [];
for (var i = 0; i < data.length; i++) {
var charCode = data.charCodeAt(i);
if (charCode > 0x7f)
result.push(String.fromCharCode(-1) + 'ff' + charCode.toString(16));
else
result.push(String.fromCharCode(charCode));
}
return result.join('');
};
/**
* Inflates (decompresses) the data stored in the given gzip bitstream.
* @return {string} Inflated data.
*/
GzipImporter.inflateGzipData_ = function(data) {
var position = 0;
function getByte() {
if (position >= data.length)
throw new Error('Unexpected end of gzip data');
return data[position++];
}
function getWord() {
var low = getByte();
var high = getByte();
return (high << 8) + low;
}
function skipBytes(amount) {
position += amount;
}
function skipZeroTerminatedString() {
while (getByte() != 0) {}
}
var id1 = getByte();
var id2 = getByte();
if (id1 !== GZIP_HEADER_ID1 || id2 !== GZIP_HEADER_ID2)
throw new Error('Not gzip data');
var compression_method = getByte();
if (compression_method !== GZIP_DEFLATE_COMPRESSION)
throw new Error('Unsupported compression method: ' + compression_method);
var flags = getByte();
var have_header_crc = flags & (1 << 1);
var have_extra_fields = flags & (1 << 2);
var have_file_name = flags & (1 << 3);
var have_comment = flags & (1 << 4);
// Skip modification time, extra flags and OS.
skipBytes(4 + 1 + 1);
// Skip remaining fields before compressed data.
if (have_extra_fields) {
var bytes_to_skip = getWord();
skipBytes(bytes_to_skip);
}
if (have_file_name)
skipZeroTerminatedString();
if (have_comment)
skipZeroTerminatedString();
if (have_header_crc)
getWord();
// Inflate the data using jszip.
var inflated_data =
JSZip.compressions['DEFLATE'].uncompress(data.subarray(position));
return JSZip.utils.transformTo('string', inflated_data);
},
GzipImporter.prototype = {
__proto__: Importer.prototype,
/**
* Called by the Model to extract subtraces from the event data. The
* subtraces are passed on to other importers that can recognize them.
*/
extractSubtraces: function() {
var eventData = GzipImporter.inflateGzipData_(this.gzipData_);
return eventData ? [eventData] : [];
}
};
tracing.TraceModel.registerImporter(GzipImporter);
return {
GzipImporter: GzipImporter
};
});
// 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.
'use strict';
/**
* @fileoverview Base class for linux perf event parsers.
*
* The linux perf trace event importer depends on subclasses of
* Parser to parse event data. Each subclass corresponds
* to a group of trace events; e.g. SchedParser implements
* parsing of sched:* kernel trace events. Parser subclasses must
* call Parser.registerSubtype to arrange to be instantiated
* and their constructor must register their event handlers with the
* importer. For example,
*
* var Parser = tracing.importer.linux_perf.Parser;
*
* function WorkqueueParser(importer) {
* Parser.call(this, importer);
*
* importer.registerEventHandler('workqueue_execute_start',
* WorkqueueParser.prototype.executeStartEvent.bind(this));
* importer.registerEventHandler('workqueue_execute_end',
* WorkqueueParser.prototype.executeEndEvent.bind(this));
* }
*
* Parser.registerSubtype(WorkqueueParser);
*
* When a registered event name is found in the data stream the associated
* event handler is invoked:
*
* executeStartEvent: function(eventName, cpuNumber, ts, eventBase)
*
* If the routine returns false the caller will generate an import error
* saying there was a problem parsing it. Handlers can also emit import
* messages using this.importer.model.importWarning. If this is done in lieu of
* the generic import error it may be desirable for the handler to return
* true.
*
* Trace events generated by writing to the trace_marker file are expected
* to have a leading text marker followed by a ':'; e.g. the trace clock
* synchronization event is:
*
* tracing_mark_write: trace_event_clock_sync: parent_ts=0
*
* To register an event handler for these events, prepend the marker with
* 'tracing_mark_write:'; e.g.
*
* this.registerEventHandler('tracing_mark_write:trace_event_clock_sync',
*
* All subclasses should depend on importer.linux_perf.parser, e.g.
*
* base.defineModule('importer.linux_perf.workqueue_parser')
* .dependsOn('importer.linux_perf.parser')
* .exportsTo('tracing', function()
*
* and be listed in the dependsOn of LinuxPerfImporter. Beware that after
* adding a new subclass you must run build/generate_about_tracing_contents.py
* to regenerate about_tracing.*.
*/
base.exportTo('tracing.importer.linux_perf', function() {
var subtypeConstructors = [];
/**
* Registers a subclass that will help parse linux perf events.
* The importer will call createParsers (below) before importing
* data so each subclass can register its handlers.
*
* @param {Function} subtypeConstructor The subtype's constructor function.
*/
Parser.registerSubtype = function(subtypeConstructor) {
subtypeConstructors.push(subtypeConstructor);
};
Parser.getSubtypeConstructors = function() {
return subtypeConstructors;
};
/**
* Parses linux perf events.
* @constructor
*/
function Parser(importer) {
this.importer = importer;
this.model = importer.model;
}
Parser.prototype = {
__proto__: Object.prototype
};
return {
Parser: Parser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses trace_marker events that were inserted in the trace by
* userland.
*/
base.require('tracing.importer.linux_perf.parser');
base.require('tracing.trace_model.counter_series');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux trace mark events that were inserted in the trace by userland.
* @constructor
*/
function AndroidParser(importer) {
Parser.call(this, importer);
importer.registerEventHandler('tracing_mark_write:android',
AndroidParser.prototype.traceMarkWriteAndroidEvent.bind(this));
importer.registerEventHandler('0:android',
AndroidParser.prototype.traceMarkWriteAndroidEvent.bind(this));
this.model_ = importer.model_;
this.ppids_ = {};
}
function parseArgs(argsString) {
var args = {};
if (argsString) {
var argsArray = argsString.split(';');
for (var i = 0; i < argsArray.length; ++i) {
var parts = argsArray[i].split('=');
if (parts[0])
args[parts.shift()] = parts.join('=');
}
}
return args;
}
AndroidParser.prototype = {
__proto__: Parser.prototype,
openAsyncSlice: function(thread, category, name, cookie, ts, args) {
var slice = new tracing.trace_model.AsyncSlice(
category, name, tracing.getStringColorId(name), ts);
var key = category + ':' + name + ':' + cookie;
slice.id = cookie;
slice.startThread = thread;
slice.args = args;
if (!this.openAsyncSlices) {
this.openAsyncSlices = { };
}
this.openAsyncSlices[key] = slice;
},
closeAsyncSlice: function(thread, category, name, cookie, ts, args) {
if (!this.openAsyncSlices) {
// No async slices have been started.
return;
}
var key = category + ':' + name + ':' + cookie;
var slice = this.openAsyncSlices[key];
if (!slice) {
// No async slices w/ this key have been started.
return;
}
for (var arg in args) {
if (slice.args[arg] !== undefined) {
this.model_.importWarning({
type: 'parse_error',
message: 'Both the S and F events of ' + slice.title +
' provided values for argument ' + arg + '.' +
' The value of the F event will be used.'
});
}
slice.args[arg] = args[arg];
}
slice.endThread = thread;
slice.duration = ts - slice.start;
slice.startThread.asyncSliceGroup.push(slice);
slice.subSlices = [new tracing.trace_model.Slice(slice.category,
slice.title, slice.colorId, slice.start, slice.args, slice.duration)];
delete this.openAsyncSlices[key];
},
traceMarkWriteAndroidEvent: function(eventName, cpuNumber, pid, ts,
eventBase) {
var eventData = eventBase.details.split('|');
switch (eventData[0]) {
case 'B':
var ppid = parseInt(eventData[1]);
var title = eventData[2];
var args = parseArgs(eventData[3]);
var category = eventData[4];
var thread = this.model_.getOrCreateProcess(ppid)
.getOrCreateThread(pid);
thread.name = eventBase.threadName;
if (!thread.sliceGroup.isTimestampValidForBeginOrEnd(ts)) {
this.model_.importWarning({
type: 'parse_error',
message: 'Timestamps are moving backward.'
});
return false;
}
this.ppids_[pid] = ppid;
thread.sliceGroup.beginSlice(category, title, ts, args);
break;
case 'E':
var ppid = this.ppids_[pid];
if (ppid === undefined) {
// Silently ignore unmatched E events.
break;
}
var thread = this.model_.getOrCreateProcess(ppid)
.getOrCreateThread(pid);
if (!thread.sliceGroup.openSliceCount) {
// Silently ignore unmatched E events.
break;
}
var slice = thread.sliceGroup.endSlice(ts);
var args = parseArgs(eventData[3]);
for (var arg in args) {
if (slice.args[arg] !== undefined) {
this.model_.importWarning({
type: 'parse_error',
message: 'Both the B and E events of ' + slice.title +
' provided values for argument ' + arg + '.' +
' The value of the E event will be used.'
});
}
slice.args[arg] = args[arg];
}
break;
case 'X':
var ppid = parseInt(eventData[1]);
var title = eventData[2];
var args = parseArgs(eventData[3]);
var category = eventData[4];
var duration = parseInt(eventData[5]) / 1000;
var thread = this.model_.getOrCreateProcess(ppid)
.getOrCreateThread(pid);
thread.name = eventBase.threadName;
this.ppids_[pid] = ppid;
thread.sliceGroup.pushCompleteSlice(
category, title, ts, duration, args);
break;
case 'C':
var ppid = parseInt(eventData[1]);
var name = eventData[2];
var value = parseInt(eventData[3]);
var category = eventData[4];
var ctr = this.model_.getOrCreateProcess(ppid)
.getOrCreateCounter(category, name);
// Initialize the counter's series fields if needed.
if (ctr.numSeries === 0) {
ctr.addSeries(new tracing.trace_model.CounterSeries(value,
tracing.getStringColorId(ctr.name + '.' + 'value')));
}
ctr.series.forEach(function(series) {
series.addSample(ts, value);
});
break;
case 'S':
var ppid = parseInt(eventData[1]);
var name = eventData[2];
var cookie = parseInt(eventData[3]);
var args = parseArgs(eventData[4]);
var category = eventData[5];
var thread = this.model_.getOrCreateProcess(ppid)
.getOrCreateThread(pid);
thread.name = eventBase.threadName;
this.ppids_[pid] = ppid;
this.openAsyncSlice(thread, category, name, cookie, ts, args);
break;
case 'F':
var ppid = this.ppids_[pid];
if (ppid === undefined) {
// Silently ignore unmatched F events.
break;
}
var thread = this.model_.getOrCreateProcess(ppid)
.getOrCreateThread(pid);
var name = eventData[2];
var cookie = parseInt(eventData[3]);
var args = parseArgs(eventData[4]);
var category = eventData[5];
this.closeAsyncSlice(thread, category, name, cookie, ts, args);
break;
default:
return false;
}
return true;
}
};
Parser.registerSubtype(AndroidParser);
return {
AndroidParser: AndroidParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses trace_marker events that were inserted in the trace by
* userland.
*/
base.require('tracing.importer.linux_perf.parser');
base.require('tracing.trace_model.counter_series');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux trace mark events that were inserted in the trace by userland.
* @constructor
*/
function BusParser(importer) {
Parser.call(this, importer);
importer.registerEventHandler('memory_bus_usage',
BusParser.prototype.traceMarkWriteBusEvent.bind(this));
this.model_ = importer.model_;
this.ppids_ = {};
}
BusParser.prototype = {
__proto__: Parser.prototype,
traceMarkWriteBusEvent: function(eventName, cpuNumber, pid, ts,
eventBase, threadName) {
var re = new RegExp('bus=(\\S+) rw_bytes=(\\d+) r_bytes=(\\d+) ' +
'w_bytes=(\\d+) cycles=(\\d+) ns=(\\d+)');
var event = re.exec(eventBase.details);
var name = event[1];
var rw_bytes = parseInt(event[2]);
var r_bytes = parseInt(event[3]);
var w_bytes = parseInt(event[4]);
var cycles = parseInt(event[5]);
var ns = parseInt(event[6]);
// BW in MB/s
var r_bw = r_bytes * 1000000000 / ns;
r_bw /= 1024 * 1024;
var w_bw = w_bytes * 1000000000 / ns;
w_bw /= 1024 * 1024;
var ctr = this.model_.getOrCreateProcess(0)
.getOrCreateCounter(null, 'bus ' + name + ' read');
if (ctr.numSeries === 0) {
ctr.addSeries(new tracing.trace_model.CounterSeries('value',
tracing.getStringColorId(ctr.name + '.' + 'value')));
}
ctr.series.forEach(function(series) {
series.addSample(ts, r_bw);
});
ctr = this.model_.getOrCreateProcess(0)
.getOrCreateCounter(null, 'bus ' + name + ' write');
if (ctr.numSeries === 0) {
ctr.addSeries(new tracing.trace_model.CounterSeries('value',
tracing.getStringColorId(ctr.name + '.' + 'value')));
}
ctr.series.forEach(function(series) {
series.addSample(ts, r_bw);
});
return true;
}
};
Parser.registerSubtype(BusParser);
return {
BusParser: BusParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses trace_marker events that were inserted in the trace by
* userland.
*/
base.require('tracing.importer.linux_perf.parser');
base.require('tracing.trace_model.counter_series');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux trace mark events that were inserted in the trace by userland.
* @constructor
*/
function ClockParser(importer) {
Parser.call(this, importer);
importer.registerEventHandler('clock_set_rate',
ClockParser.prototype.traceMarkWriteClockEvent.bind(this));
this.model_ = importer.model_;
this.ppids_ = {};
}
ClockParser.prototype = {
__proto__: Parser.prototype,
traceMarkWriteClockEvent: function(eventName, cpuNumber, pid, ts,
eventBase, threadName) {
var event = /(\S+) state=(\d+) cpu_id=(\d+)/.exec(eventBase.details);
var name = event[1];
var rate = parseInt(event[2]);
var ctr = this.model_.getOrCreateProcess(0)
.getOrCreateCounter(null, name);
// Initialize the counter's series fields if needed.
if (ctr.numSeries === 0) {
ctr.addSeries(new tracing.trace_model.CounterSeries('value',
tracing.getStringColorId(ctr.name + '.' + 'value')));
}
ctr.series.forEach(function(series) {
series.addSample(ts, rate);
});
return true;
}
};
Parser.registerSubtype(ClockParser);
return {
ClockParser: ClockParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses cpufreq events in the Linux event trace format.
*/
base.require('tracing.importer.linux_perf.parser');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux cpufreq trace events.
* @constructor
*/
function CpufreqParser(importer) {
Parser.call(this, importer);
importer.registerEventHandler('cpufreq_interactive_up',
CpufreqParser.prototype.cpufreqUpDownEvent.bind(this));
importer.registerEventHandler('cpufreq_interactive_down',
CpufreqParser.prototype.cpufreqUpDownEvent.bind(this));
importer.registerEventHandler('cpufreq_interactive_already',
CpufreqParser.prototype.cpufreqTargetEvent.bind(this));
importer.registerEventHandler('cpufreq_interactive_notyet',
CpufreqParser.prototype.cpufreqTargetEvent.bind(this));
importer.registerEventHandler('cpufreq_interactive_setspeed',
CpufreqParser.prototype.cpufreqTargetEvent.bind(this));
importer.registerEventHandler('cpufreq_interactive_target',
CpufreqParser.prototype.cpufreqTargetEvent.bind(this));
importer.registerEventHandler('cpufreq_interactive_boost',
CpufreqParser.prototype.cpufreqBoostUnboostEvent.bind(this));
importer.registerEventHandler('cpufreq_interactive_unboost',
CpufreqParser.prototype.cpufreqBoostUnboostEvent.bind(this));
}
function splitData(input) {
// TODO(sleffler) split by cpu
var data = {};
var args = input.split(/\s+/);
var len = args.length;
for (var i = 0; i < len; i++) {
var item = args[i].split('=');
data[item[0]] = parseInt(item[1]);
}
return data;
}
CpufreqParser.prototype = {
__proto__: Parser.prototype,
cpufreqSlice: function(ts, eventName, cpu, args) {
// TODO(sleffler) should be per-cpu
var kthread = this.importer.getOrCreatePseudoThread('cpufreq');
kthread.openSlice = eventName;
var slice = new tracing.trace_model.Slice('', kthread.openSlice,
tracing.getStringColorId(kthread.openSlice), ts, args, 0);
kthread.thread.sliceGroup.pushSlice(slice);
},
cpufreqBoostSlice: function(ts, eventName, args) {
var kthread = this.importer.getOrCreatePseudoThread('cpufreq_boost');
kthread.openSlice = eventName;
var slice = new tracing.trace_model.Slice('', kthread.openSlice,
tracing.getStringColorId(kthread.openSlice), ts, args, 0);
kthread.thread.sliceGroup.pushSlice(slice);
},
/**
* Parses cpufreq events and sets up state in the importer.
*/
cpufreqUpDownEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var data = splitData(eventBase.details);
this.cpufreqSlice(ts, eventName, data.cpu, data);
return true;
},
cpufreqTargetEvent: function(eventName, cpuNumber, pid, ts,
eventBase) {
var data = splitData(eventBase.details);
this.cpufreqSlice(ts, eventName, data.cpu, data);
return true;
},
cpufreqBoostUnboostEvent: function(eventName, cpuNumber, pid, ts,
eventBase) {
this.cpufreqBoostSlice(ts, eventName,
{
type: eventBase.details
});
return true;
}
};
Parser.registerSubtype(CpufreqParser);
return {
CpufreqParser: CpufreqParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses filesystem and block device events in the Linux event
* trace format.
*/
base.require('tracing.importer.linux_perf.parser');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux filesystem and block device trace events.
* @constructor
*/
function DiskParser(importer) {
Parser.call(this, importer);
importer.registerEventHandler('ext4_sync_file_enter',
DiskParser.prototype.ext4SyncFileEnterEvent.bind(this));
importer.registerEventHandler('ext4_sync_file_exit',
DiskParser.prototype.ext4SyncFileExitEvent.bind(this));
importer.registerEventHandler('block_rq_issue',
DiskParser.prototype.blockRqIssueEvent.bind(this));
importer.registerEventHandler('block_rq_complete',
DiskParser.prototype.blockRqCompleteEvent.bind(this));
}
DiskParser.prototype = {
__proto__: Parser.prototype,
openAsyncSlice: function(ts, category, threadName, pid, key, name) {
var kthread = this.importer.getOrCreateKernelThread(
category + ':' + threadName, pid);
var slice = new tracing.trace_model.AsyncSlice(
category, name, tracing.getStringColorId(name), ts);
slice.startThread = kthread.thread;
if (!kthread.openAsyncSlices) {
kthread.openAsyncSlices = { };
}
kthread.openAsyncSlices[key] = slice;
},
closeAsyncSlice: function(ts, category, threadName, pid, key, args) {
var kthread = this.importer.getOrCreateKernelThread(
category + ':' + threadName, pid);
if (kthread.openAsyncSlices) {
var slice = kthread.openAsyncSlices[key];
if (slice) {
slice.duration = ts - slice.start;
slice.args = args;
slice.endThread = kthread.thread;
slice.subSlices = [
new tracing.trace_model.Slice(category, slice.title,
slice.colorId, slice.start, slice.args, slice.duration)
];
kthread.thread.asyncSliceGroup.push(slice);
delete kthread.openAsyncSlices[key];
}
}
},
/**
* Parses events and sets up state in the importer.
*/
ext4SyncFileEnterEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /dev (\d+,\d+) ino (\d+) parent (\d+) datasync (\d+)/.
exec(eventBase.details);
if (!event)
return false;
var device = event[1];
var inode = parseInt(event[2]);
var datasync = event[4] == 1;
var key = device + '-' + inode;
var action = datasync ? 'fdatasync' : 'fsync';
this.openAsyncSlice(ts, 'ext4', eventBase.threadName, eventBase.pid,
key, action);
return true;
},
ext4SyncFileExitEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /dev (\d+,\d+) ino (\d+) ret (\d+)/.exec(eventBase.details);
if (!event)
return false;
var device = event[1];
var inode = parseInt(event[2]);
var error = parseInt(event[3]);
var key = device + '-' + inode;
this.closeAsyncSlice(ts, 'ext4', eventBase.threadName, eventBase.pid,
key, {
device: device,
inode: inode,
error: error
});
return true;
},
blockRqIssueEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = new RegExp('(\\d+,\\d+) (F)?([DWRN])(F)?(A)?(S)?(M)? ' +
'\\d+ \\(.*\\) (\\d+) \\+ (\\d+) \\[.*\\]').exec(eventBase.details);
if (!event)
return false;
var action;
switch (event[3]) {
case 'D':
action = 'discard';
break;
case 'W':
action = 'write';
break;
case 'R':
action = 'read';
break;
case 'N':
action = 'none';
break;
default:
action = 'unknown';
break;
}
if (event[2]) {
action += ' flush';
}
if (event[4] == 'F') {
action += ' fua';
}
if (event[5] == 'A') {
action += ' ahead';
}
if (event[6] == 'S') {
action += ' sync';
}
if (event[7] == 'M') {
action += ' meta';
}
var device = event[1];
var sector = parseInt(event[8]);
var numSectors = parseInt(event[9]);
var key = device + '-' + sector + '-' + numSectors;
this.openAsyncSlice(ts, 'block', eventBase.threadName, eventBase.pid,
key, action);
return true;
},
blockRqCompleteEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = new RegExp('(\\d+,\\d+) (F)?([DWRN])(F)?(A)?(S)?(M)? ' +
'\\(.*\\) (\\d+) \\+ (\\d+) \\[(.*)\\]').exec(eventBase.details);
if (!event)
return false;
var device = event[1];
var sector = parseInt(event[8]);
var numSectors = parseInt(event[9]);
var error = parseInt(event[10]);
var key = device + '-' + sector + '-' + numSectors;
this.closeAsyncSlice(ts, 'block', eventBase.threadName, eventBase.pid,
key, {
device: device,
sector: sector,
numSectors: numSectors,
error: error
});
return true;
}
};
Parser.registerSubtype(DiskParser);
return {
DiskParser: DiskParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses drm driver events in the Linux event trace format.
*/
base.require('tracing.importer.linux_perf.parser');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux drm trace events.
* @constructor
*/
function DrmParser(importer) {
Parser.call(this, importer);
importer.registerEventHandler('drm_vblank_event',
DrmParser.prototype.vblankEvent.bind(this));
}
DrmParser.prototype = {
__proto__: Parser.prototype,
drmVblankSlice: function(ts, eventName, args) {
var kthread = this.importer.getOrCreatePseudoThread('drm_vblank');
kthread.openSlice = eventName;
var slice = new tracing.trace_model.Slice('', kthread.openSlice,
tracing.getStringColorId(kthread.openSlice), ts, args, 0);
kthread.thread.sliceGroup.pushSlice(slice);
},
/**
* Parses drm driver events and sets up state in the importer.
*/
vblankEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /crtc=(\d+), seq=(\d+)/.exec(eventBase.details);
if (!event)
return false;
var crtc = parseInt(event[1]);
var seq = parseInt(event[2]);
this.drmVblankSlice(ts, 'vblank:' + crtc,
{
crtc: crtc,
seq: seq
});
return true;
}
};
Parser.registerSubtype(DrmParser);
return {
DrmParser: DrmParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses exynos events in the Linux event trace format.
*/
base.require('tracing.importer.linux_perf.parser');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux exynos trace events.
* @constructor
*/
function ExynosParser(importer) {
Parser.call(this, importer);
importer.registerEventHandler('exynos_flip_request',
ExynosParser.prototype.flipEvent.bind(this));
importer.registerEventHandler('exynos_flip_complete',
ExynosParser.prototype.flipEvent.bind(this));
importer.registerEventHandler('exynos_busfreq_target_int',
ExynosParser.prototype.busfreqTargetIntEvent.bind(this));
importer.registerEventHandler('exynos_busfreq_target_mif',
ExynosParser.prototype.busfreqTargetMifEvent.bind(this));
importer.registerEventHandler('exynos_page_flip_state',
ExynosParser.prototype.pageFlipStateEvent.bind(this));
}
ExynosParser.prototype = {
__proto__: Parser.prototype,
exynosFlipOpenSlice: function(ts, pipe) {
// use pipe?
var kthread = this.importer.getOrCreatePseudoThread('exynos_flip');
kthread.openSliceTS = ts;
kthread.openSlice = 'flip:' + pipe;
},
exynosFlipCloseSlice: function(ts, args) {
var kthread = this.importer.getOrCreatePseudoThread('exynos_flip');
if (kthread.openSlice) {
var slice = new tracing.trace_model.Slice('', kthread.openSlice,
tracing.getStringColorId(kthread.openSlice),
kthread.openSliceTS,
args,
ts - kthread.openSliceTS);
kthread.thread.sliceGroup.pushSlice(slice);
}
kthread.openSlice = undefined;
},
/**
* Parses exynos events and sets up state in the importer.
*/
flipEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /pipe=(\d+)/.exec(eventBase.details);
if (!event)
return false;
var pipe = parseInt(event[1]);
if (eventName == 'exynos_flip_request')
this.exynosFlipOpenSlice(ts, pipe);
else
this.exynosFlipCloseSlice(ts,
{
pipe: pipe
});
return true;
},
exynosBusfreqSample: function(name, ts, frequency) {
var targetCpu = this.importer.getOrCreateCpuState(0);
var counter = targetCpu.cpu.getOrCreateCounter('', name);
if (counter.numSeries === 0) {
counter.addSeries(new tracing.trace_model.CounterSeries('frequency',
tracing.getStringColorId(counter.name + '.' + 'frequency')));
}
counter.series.forEach(function(series) {
series.addSample(ts, frequency);
});
},
/**
* Parses exynos_busfreq_target_int events and sets up state.
*/
busfreqTargetIntEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /frequency=(\d+)/.exec(eventBase.details);
if (!event)
return false;
this.exynosBusfreqSample('INT Frequency', ts, parseInt(event[1]));
return true;
},
/**
* Parses exynos_busfreq_target_mif events and sets up state.
*/
busfreqTargetMifEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /frequency=(\d+)/.exec(eventBase.details);
if (!event)
return false;
this.exynosBusfreqSample('MIF Frequency', ts, parseInt(event[1]));
return true;
},
exynosPageFlipStateOpenSlice: function(ts, pipe, fb, state) {
var kthread = this.importer.getOrCreatePseudoThread(
'exynos_flip_state (pipe:' + pipe + ', fb:' + fb + ')');
kthread.openSliceTS = ts;
kthread.openSlice = state;
},
exynosPageFlipStateCloseSlice: function(ts, pipe, fb, args) {
var kthread = this.importer.getOrCreatePseudoThread(
'exynos_flip_state (pipe:' + pipe + ', fb:' + fb + ')');
if (kthread.openSlice) {
var slice = new tracing.trace_model.Slice('', kthread.openSlice,
tracing.getStringColorId(kthread.openSlice),
kthread.openSliceTS,
args,
ts - kthread.openSliceTS);
kthread.thread.sliceGroup.pushSlice(slice);
}
kthread.openSlice = undefined;
},
/**
* Parses page_flip_state events and sets up state in the importer.
*/
pageFlipStateEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /pipe=(\d+), fb=(\d+), state=(.*)/.exec(eventBase.details);
if (!event)
return false;
var pipe = parseInt(event[1]);
var fb = parseInt(event[2]);
var state = event[3];
this.exynosPageFlipStateCloseSlice(ts, pipe, fb,
{
pipe: pipe,
fb: fb
});
if (state !== 'flipped')
this.exynosPageFlipStateOpenSlice(ts, pipe, fb, state);
return true;
}
};
Parser.registerSubtype(ExynosParser);
return {
ExynosParser: ExynosParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses gesture events in the Linux event trace format.
*/
base.require('tracing.importer.linux_perf.parser');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses trace events generated by gesture library for touchpad.
* @constructor
*/
function GestureParser(importer) {
Parser.call(this, importer);
importer.registerEventHandler('tracing_mark_write:log',
GestureParser.prototype.logEvent.bind(this));
importer.registerEventHandler('tracing_mark_write:SyncInterpret',
GestureParser.prototype.syncEvent.bind(this));
importer.registerEventHandler('tracing_mark_write:HandleTimer',
GestureParser.prototype.timerEvent.bind(this));
}
GestureParser.prototype = {
__proto__: Parser.prototype,
/**
* Parse events generate by gesture library.
* gestureOpenSlice and gestureCloseSlice are two common
* functions to store the begin time and end time for all
* events in gesture library
*/
gestureOpenSlice: function(title, ts, opt_args) {
var thread = this.importer.getOrCreatePseudoThread('gesture').thread;
thread.sliceGroup.beginSlice(
'touchpad_gesture', title, ts, opt_args);
},
gestureCloseSlice: function(title, ts) {
var thread = this.importer.getOrCreatePseudoThread('gesture').thread;
if (thread.sliceGroup.openSliceCount) {
var slice = thread.sliceGroup.mostRecentlyOpenedPartialSlice;
if (slice.title != title) {
this.importer.model.importWarning({
type: 'title_match_error',
message: 'Titles do not match. Title is ' +
slice.title + ' in openSlice, and is ' +
title + ' in endSlice'
});
} else {
thread.sliceGroup.endSlice(ts);
}
}
},
/**
* For log events, events will come in pairs with a tag log:
* like this:
* tracing_mark_write: log: start: TimerLogOutputs
* tracing_mark_write: log: end: TimerLogOutputs
* which represent the start and the end time of certain log behavior
* Take these logs above for example, they are the start and end time
* of logging Output for HandleTimer function
*/
logEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var innerEvent =
/^\s*(\w+):\s*(\w+)$/.exec(eventBase.details);
switch (innerEvent[1]) {
case 'start':
this.gestureOpenSlice('GestureLog', ts, {name: innerEvent[2]});
break;
case 'end':
this.gestureCloseSlice('GestureLog', ts);
}
return true;
},
/**
* For SyncInterpret events, events will come in pairs with
* a tag SyncInterpret:
* like this:
* tracing_mark_write: SyncInterpret: start: ClickWiggleFilterInterpreter
* tracing_mark_write: SyncInterpret: end: ClickWiggleFilterInterpreter
* which represent the start and the end time of SyncInterpret function
* inside the certain interpreter in the gesture library.
* Take the logs above for example, they are the start and end time
* of the SyncInterpret function inside ClickWiggleFilterInterpreter
*/
syncEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var innerEvent = /^\s*(\w+):\s*(\w+)$/.exec(eventBase.details);
switch (innerEvent[1]) {
case 'start':
this.gestureOpenSlice('SyncInterpret', ts,
{interpreter: innerEvent[2]});
break;
case 'end':
this.gestureCloseSlice('SyncInterpret', ts);
}
return true;
},
/**
* For HandleTimer events, events will come in pairs with
* a tag HandleTimer:
* like this:
* tracing_mark_write: HandleTimer: start: LookaheadFilterInterpreter
* tracing_mark_write: HandleTimer: end: LookaheadFilterInterpreter
* which represent the start and the end time of HandleTimer function
* inside the certain interpreter in the gesture library.
* Take the logs above for example, they are the start and end time
* of the HandleTimer function inside LookaheadFilterInterpreter
*/
timerEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var innerEvent = /^\s*(\w+):\s*(\w+)$/.exec(eventBase.details);
switch (innerEvent[1]) {
case 'start':
this.gestureOpenSlice('HandleTimer', ts,
{interpreter: innerEvent[2]});
break;
case 'end':
this.gestureCloseSlice('HandleTimer', ts);
}
return true;
}
};
Parser.registerSubtype(GestureParser);
return {
GestureParser: GestureParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses i915 driver events in the Linux event trace format.
*/
base.require('tracing.importer.linux_perf.parser');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux i915 trace events.
* @constructor
*/
function I915Parser(importer) {
Parser.call(this, importer);
importer.registerEventHandler('i915_gem_object_create',
I915Parser.prototype.gemObjectCreateEvent.bind(this));
importer.registerEventHandler('i915_gem_object_bind',
I915Parser.prototype.gemObjectBindEvent.bind(this));
importer.registerEventHandler('i915_gem_object_unbind',
I915Parser.prototype.gemObjectBindEvent.bind(this));
importer.registerEventHandler('i915_gem_object_change_domain',
I915Parser.prototype.gemObjectChangeDomainEvent.bind(this));
importer.registerEventHandler('i915_gem_object_pread',
I915Parser.prototype.gemObjectPreadWriteEvent.bind(this));
importer.registerEventHandler('i915_gem_object_pwrite',
I915Parser.prototype.gemObjectPreadWriteEvent.bind(this));
importer.registerEventHandler('i915_gem_object_fault',
I915Parser.prototype.gemObjectFaultEvent.bind(this));
importer.registerEventHandler('i915_gem_object_clflush',
// NB: reuse destroy handler
I915Parser.prototype.gemObjectDestroyEvent.bind(this));
importer.registerEventHandler('i915_gem_object_destroy',
I915Parser.prototype.gemObjectDestroyEvent.bind(this));
importer.registerEventHandler('i915_gem_ring_dispatch',
I915Parser.prototype.gemRingDispatchEvent.bind(this));
importer.registerEventHandler('i915_gem_ring_flush',
I915Parser.prototype.gemRingFlushEvent.bind(this));
importer.registerEventHandler('i915_gem_request',
I915Parser.prototype.gemRequestEvent.bind(this));
importer.registerEventHandler('i915_gem_request_add',
I915Parser.prototype.gemRequestEvent.bind(this));
importer.registerEventHandler('i915_gem_request_complete',
I915Parser.prototype.gemRequestEvent.bind(this));
importer.registerEventHandler('i915_gem_request_retire',
I915Parser.prototype.gemRequestEvent.bind(this));
importer.registerEventHandler('i915_gem_request_wait_begin',
I915Parser.prototype.gemRequestEvent.bind(this));
importer.registerEventHandler('i915_gem_request_wait_end',
I915Parser.prototype.gemRequestEvent.bind(this));
importer.registerEventHandler('i915_gem_ring_wait_begin',
I915Parser.prototype.gemRingWaitEvent.bind(this));
importer.registerEventHandler('i915_gem_ring_wait_end',
I915Parser.prototype.gemRingWaitEvent.bind(this));
importer.registerEventHandler('i915_reg_rw',
I915Parser.prototype.regRWEvent.bind(this));
importer.registerEventHandler('i915_flip_request',
I915Parser.prototype.flipEvent.bind(this));
importer.registerEventHandler('i915_flip_complete',
I915Parser.prototype.flipEvent.bind(this));
}
I915Parser.prototype = {
__proto__: Parser.prototype,
i915FlipOpenSlice: function(ts, obj, plane) {
// use i915_flip_obj_plane?
var kthread = this.importer.getOrCreatePseudoThread('i915_flip');
kthread.openSliceTS = ts;
kthread.openSlice = 'flip:' + obj + '/' + plane;
},
i915FlipCloseSlice: function(ts, args) {
var kthread = this.importer.getOrCreatePseudoThread('i915_flip');
if (kthread.openSlice) {
var slice = new tracing.trace_model.Slice('', kthread.openSlice,
tracing.getStringColorId(kthread.openSlice),
kthread.openSliceTS,
args,
ts - kthread.openSliceTS);
kthread.thread.sliceGroup.pushSlice(slice);
}
kthread.openSlice = undefined;
},
i915GemObjectSlice: function(ts, eventName, obj, args) {
var kthread = this.importer.getOrCreatePseudoThread('i915_gem');
kthread.openSlice = eventName + ':' + obj;
var slice = new tracing.trace_model.Slice('', kthread.openSlice,
tracing.getStringColorId(kthread.openSlice), ts, args, 0);
kthread.thread.sliceGroup.pushSlice(slice);
},
i915GemRingSlice: function(ts, eventName, dev, ring, args) {
var kthread = this.importer.getOrCreatePseudoThread('i915_gem_ring');
kthread.openSlice = eventName + ':' + dev + '.' + ring;
var slice = new tracing.trace_model.Slice('', kthread.openSlice,
tracing.getStringColorId(kthread.openSlice), ts, args, 0);
kthread.thread.sliceGroup.pushSlice(slice);
},
i915RegSlice: function(ts, eventName, reg, args) {
var kthread = this.importer.getOrCreatePseudoThread('i915_reg');
kthread.openSlice = eventName + ':' + reg;
var slice = new tracing.trace_model.Slice('', kthread.openSlice,
tracing.getStringColorId(kthread.openSlice), ts, args, 0);
kthread.thread.sliceGroup.pushSlice(slice);
},
/**
* Parses i915 driver events and sets up state in the importer.
*/
gemObjectCreateEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /obj=(\w+), size=(\d+)/.exec(eventBase.details);
if (!event)
return false;
var obj = event[1];
var size = parseInt(event[2]);
this.i915GemObjectSlice(ts, eventName, obj,
{
obj: obj,
size: size
});
return true;
},
gemObjectBindEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
// TODO(sleffler) mappable
var event = /obj=(\w+), offset=(\w+), size=(\d+)/.exec(eventBase.details);
if (!event)
return false;
var obj = event[1];
var offset = event[2];
var size = parseInt(event[3]);
this.i915ObjectGemSlice(ts, eventName + ':' + obj,
{
obj: obj,
offset: offset,
size: size
});
return true;
},
gemObjectChangeDomainEvent: function(eventName, cpuNumber, pid, ts,
eventBase) {
var event = /obj=(\w+), read=(\w+=>\w+), write=(\w+=>\w+)/
.exec(eventBase.details);
if (!event)
return false;
var obj = event[1];
var read = event[2];
var write = event[3];
this.i915GemObjectSlice(ts, eventName, obj,
{
obj: obj,
read: read,
write: write
});
return true;
},
gemObjectPreadWriteEvent: function(eventName, cpuNumber, pid, ts,
eventBase) {
var event = /obj=(\w+), offset=(\d+), len=(\d+)/.exec(eventBase.details);
if (!event)
return false;
var obj = event[1];
var offset = parseInt(event[2]);
var len = parseInt(event[3]);
this.i915GemObjectSlice(ts, eventName, obj,
{
obj: obj,
offset: offset,
len: len
});
return true;
},
gemObjectFaultEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
// TODO(sleffler) writable
var event = /obj=(\w+), (\w+) index=(\d+)/.exec(eventBase.details);
if (!event)
return false;
var obj = event[1];
var type = event[2];
var index = parseInt(event[3]);
this.i915GemObjectSlice(ts, eventName, obj,
{
obj: obj,
type: type,
index: index
});
return true;
},
gemObjectDestroyEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /obj=(\w+)/.exec(eventBase.details);
if (!event)
return false;
var obj = event[1];
this.i915GemObjectSlice(ts, eventName, obj,
{
obj: obj
});
return true;
},
gemRingDispatchEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /dev=(\d+), ring=(\d+), seqno=(\d+)/.exec(eventBase.details);
if (!event)
return false;
var dev = parseInt(event[1]);
var ring = parseInt(event[2]);
var seqno = parseInt(event[3]);
this.i915GemRingSlice(ts, eventName, dev, ring,
{
dev: dev,
ring: ring,
seqno: seqno
});
return true;
},
gemRingFlushEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /dev=(\d+), ring=(\w+), invalidate=(\w+), flush=(\w+)/
.exec(eventBase.details);
if (!event)
return false;
var dev = parseInt(event[1]);
var ring = parseInt(event[2]);
var invalidate = event[3];
var flush = event[4];
this.i915GemRingSlice(ts, eventName, dev, ring,
{
dev: dev,
ring: ring,
invalidate: invalidate,
flush: flush
});
return true;
},
gemRequestEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /dev=(\d+), ring=(\d+), seqno=(\d+)/.exec(eventBase.details);
if (!event)
return false;
var dev = parseInt(event[1]);
var ring = parseInt(event[2]);
var seqno = parseInt(event[3]);
this.i915GemRingSlice(ts, eventName, dev, ring,
{
dev: dev,
ring: ring,
seqno: seqno
});
return true;
},
gemRingWaitEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /dev=(\d+), ring=(\d+)/.exec(eventBase.details);
if (!event)
return false;
var dev = parseInt(event[1]);
var ring = parseInt(event[2]);
this.i915GemRingSlice(ts, eventName, dev, ring,
{
dev: dev,
ring: ring
});
return true;
},
regRWEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /(\w+) reg=(\w+), len=(\d+), val=(\(\w+, \w+\))/
.exec(eventBase.details);
if (!event)
return false;
var rw = event[1];
var reg = event[2];
var len = event[3];
var data = event[3];
this.i915RegSlice(ts, rw, reg,
{
rw: rw,
reg: reg,
len: len,
data: data
});
return true;
},
flipEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /plane=(\d+), obj=(\w+)/.exec(eventBase.details);
if (!event)
return false;
var plane = parseInt(event[1]);
var obj = event[2];
if (eventName == 'i915_flip_request')
this.i915FlipOpenSlice(ts, obj, plane);
else
this.i915FlipCloseSlice(ts,
{
obj: obj,
plane: plane
});
return true;
}
};
Parser.registerSubtype(I915Parser);
return {
I915Parser: I915Parser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses graph_ent and graph_ret events that were inserted by
* the Linux kernel's function graph trace.
*/
base.require('tracing.importer.linux_perf.parser');
base.exportTo('tracing.importer.linux_perf', function() {
var LinuxPerfParser = tracing.importer.linux_perf.Parser;
/**
* Parses graph_ent and graph_ret events that were inserted by the Linux
* kernel's function graph trace.
* @constructor
*/
function KernelFuncParser(importer) {
LinuxPerfParser.call(this, importer);
importer.registerEventHandler('graph_ent',
KernelFuncParser.prototype.traceKernelFuncEnterEvent.
bind(this));
importer.registerEventHandler('graph_ret',
KernelFuncParser.prototype.traceKernelFuncReturnEvent.
bind(this));
this.model_ = importer.model_;
this.ppids_ = {};
}
var TestExports = {};
var funcEnterRE = new RegExp('func=(.+)');
TestExports.funcEnterRE = funcEnterRE;
KernelFuncParser.prototype = {
__proto__: LinuxPerfParser.prototype,
traceKernelFuncEnterEvent: function(eventName, cpuNumber, pid, ts,
eventBase) {
var eventData = funcEnterRE.exec(eventBase.details);
if (!eventData)
return false;
if (eventBase.tgid === undefined) {
return false;
}
var tgid = parseInt(eventBase.tgid);
var name = eventData[1];
var thread = this.model_.getOrCreateProcess(tgid)
.getOrCreateThread(pid);
thread.name = eventBase.threadName;
var slices = thread.kernelSliceGroup;
if (!slices.isTimestampValidForBeginOrEnd(ts)) {
this.model_.importWarning({
type: 'parse_error',
message: 'Timestamps are moving backward.'
});
return false;
}
var slice = slices.beginSlice(null, name, ts, {});
return true;
},
traceKernelFuncReturnEvent: function(eventName, cpuNumber, pid, ts,
eventBase) {
if (eventBase.tgid === undefined) {
return false;
}
var tgid = parseInt(eventBase.tgid);
var thread = this.model_.getOrCreateProcess(tgid)
.getOrCreateThread(pid);
thread.name = eventBase.threadName;
var slices = thread.kernelSliceGroup;
if (!slices.isTimestampValidForBeginOrEnd(ts)) {
this.model_.importWarning({
type: 'parse_error',
message: 'Timestamps are moving backward.'
});
return false;
}
if (slices.openSliceCount > 0) {
slices.endSlice(ts);
}
return true;
}
};
LinuxPerfParser.registerSubtype(KernelFuncParser);
return {
KernelFuncParser: KernelFuncParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses Mali DDK/kernel events in the Linux event trace format.
*/
base.require('tracing.importer.linux_perf.parser');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses Mali DDK/kernel trace events.
* @constructor
*/
function MaliParser(importer) {
Parser.call(this, importer);
// kernel DVFS events
importer.registerEventHandler('mali_dvfs_event',
MaliParser.prototype.dvfsEventEvent.bind(this));
importer.registerEventHandler('mali_dvfs_set_clock',
MaliParser.prototype.dvfsSetClockEvent.bind(this));
importer.registerEventHandler('mali_dvfs_set_voltage',
MaliParser.prototype.dvfsSetVoltageEvent.bind(this));
// kernel Mali hw counter events
this.addJMCounter('mali_hwc_MESSAGES_SENT', 'Messages Sent');
this.addJMCounter('mali_hwc_MESSAGES_RECEIVED', 'Messages Received');
this.addJMCycles('mali_hwc_GPU_ACTIVE', 'GPU Active');
this.addJMCycles('mali_hwc_IRQ_ACTIVE', 'IRQ Active');
for (var i = 0; i < 7; i++) {
var jobStr = 'JS' + i;
var jobHWCStr = 'mali_hwc_' + jobStr;
this.addJMCounter(jobHWCStr + '_JOBS', jobStr + ' Jobs');
this.addJMCounter(jobHWCStr + '_TASKS', jobStr + ' Tasks');
this.addJMCycles(jobHWCStr + '_ACTIVE', jobStr + ' Active');
this.addJMCycles(jobHWCStr + '_WAIT_READ', jobStr + ' Wait Read');
this.addJMCycles(jobHWCStr + '_WAIT_ISSUE', jobStr + ' Wait Issue');
this.addJMCycles(jobHWCStr + '_WAIT_DEPEND', jobStr + ' Wait Depend');
this.addJMCycles(jobHWCStr + '_WAIT_FINISH', jobStr + ' Wait Finish');
}
this.addTilerCounter('mali_hwc_TRIANGLES', 'Triangles');
this.addTilerCounter('mali_hwc_QUADS', 'Quads');
this.addTilerCounter('mali_hwc_POLYGONS', 'Polygons');
this.addTilerCounter('mali_hwc_POINTS', 'Points');
this.addTilerCounter('mali_hwc_LINES', 'Lines');
this.addTilerCounter('mali_hwc_VCACHE_HIT', 'VCache Hit');
this.addTilerCounter('mali_hwc_VCACHE_MISS', 'VCache Miss');
this.addTilerCounter('mali_hwc_FRONT_FACING', 'Front Facing');
this.addTilerCounter('mali_hwc_BACK_FACING', 'Back Facing');
this.addTilerCounter('mali_hwc_PRIM_VISIBLE', 'Prim Visible');
this.addTilerCounter('mali_hwc_PRIM_CULLED', 'Prim Culled');
this.addTilerCounter('mali_hwc_PRIM_CLIPPED', 'Prim Clipped');
this.addTilerCounter('mali_hwc_WRBUF_HIT', 'Wrbuf Hit');
this.addTilerCounter('mali_hwc_WRBUF_MISS', 'Wrbuf Miss');
this.addTilerCounter('mali_hwc_WRBUF_LINE', 'Wrbuf Line');
this.addTilerCounter('mali_hwc_WRBUF_PARTIAL', 'Wrbuf Partial');
this.addTilerCounter('mali_hwc_WRBUF_STALL', 'Wrbuf Stall');
this.addTilerCycles('mali_hwc_ACTIVE', 'Tiler Active');
this.addTilerCycles('mali_hwc_INDEX_WAIT', 'Index Wait');
this.addTilerCycles('mali_hwc_INDEX_RANGE_WAIT', 'Index Range Wait');
this.addTilerCycles('mali_hwc_VERTEX_WAIT', 'Vertex Wait');
this.addTilerCycles('mali_hwc_PCACHE_WAIT', 'Pcache Wait');
this.addTilerCycles('mali_hwc_WRBUF_WAIT', 'Wrbuf Wait');
this.addTilerCycles('mali_hwc_BUS_READ', 'Bus Read');
this.addTilerCycles('mali_hwc_BUS_WRITE', 'Bus Write');
this.addTilerCycles('mali_hwc_TILER_UTLB_STALL', 'Tiler UTLB Stall');
this.addTilerCycles('mali_hwc_TILER_UTLB_HIT', 'Tiler UTLB Hit');
this.addFragCycles('mali_hwc_FRAG_ACTIVE', 'Active');
/* NB: don't propagate spelling mistakes to labels */
this.addFragCounter('mali_hwc_FRAG_PRIMATIVES', 'Primitives');
this.addFragCounter('mali_hwc_FRAG_PRIMATIVES_DROPPED',
'Primitives Dropped');
this.addFragCycles('mali_hwc_FRAG_CYCLE_DESC', 'Descriptor Processing');
this.addFragCycles('mali_hwc_FRAG_CYCLES_PLR', 'PLR Processing??');
this.addFragCycles('mali_hwc_FRAG_CYCLES_VERT', 'Vertex Processing');
this.addFragCycles('mali_hwc_FRAG_CYCLES_TRISETUP', 'Triangle Setup');
this.addFragCycles('mali_hwc_FRAG_CYCLES_RAST', 'Rasterization???');
this.addFragCounter('mali_hwc_FRAG_THREADS', 'Threads');
this.addFragCounter('mali_hwc_FRAG_DUMMY_THREADS', 'Dummy Threads');
this.addFragCounter('mali_hwc_FRAG_QUADS_RAST', 'Quads Rast');
this.addFragCounter('mali_hwc_FRAG_QUADS_EZS_TEST', 'Quads EZS Test');
this.addFragCounter('mali_hwc_FRAG_QUADS_EZS_KILLED', 'Quads EZS Killed');
this.addFragCounter('mali_hwc_FRAG_QUADS_LZS_TEST', 'Quads LZS Test');
this.addFragCounter('mali_hwc_FRAG_QUADS_LZS_KILLED', 'Quads LZS Killed');
this.addFragCycles('mali_hwc_FRAG_CYCLE_NO_TILE', 'No Tiles');
this.addFragCounter('mali_hwc_FRAG_NUM_TILES', 'Tiles');
this.addFragCounter('mali_hwc_FRAG_TRANS_ELIM', 'Transactions Eliminated');
this.addComputeCycles('mali_hwc_COMPUTE_ACTIVE', 'Active');
this.addComputeCounter('mali_hwc_COMPUTE_TASKS', 'Tasks');
this.addComputeCounter('mali_hwc_COMPUTE_THREADS', 'Threads Started');
this.addComputeCycles('mali_hwc_COMPUTE_CYCLES_DESC',
'Waiting for Descriptors');
this.addTripipeCycles('mali_hwc_TRIPIPE_ACTIVE', 'Active');
this.addArithCounter('mali_hwc_ARITH_WORDS', 'Instructions (/Pipes)');
this.addArithCycles('mali_hwc_ARITH_CYCLES_REG',
'Reg scheduling stalls (/Pipes)');
this.addArithCycles('mali_hwc_ARITH_CYCLES_L0',
'L0 cache miss stalls (/Pipes)');
this.addArithCounter('mali_hwc_ARITH_FRAG_DEPEND',
'Frag dep check failures (/Pipes)');
this.addLSCounter('mali_hwc_LS_WORDS', 'Instruction Words Completed');
this.addLSCounter('mali_hwc_LS_ISSUES', 'Full Pipeline Issues');
this.addLSCounter('mali_hwc_LS_RESTARTS', 'Restarts (unpairable insts)');
this.addLSCounter('mali_hwc_LS_REISSUES_MISS',
'Pipeline reissue (cache miss/uTLB)');
this.addLSCounter('mali_hwc_LS_REISSUES_VD',
'Pipeline reissue (varying data)');
/* TODO(sleffler) fix kernel event typo */
this.addLSCounter('mali_hwc_LS_REISSUE_ATTRIB_MISS',
'Pipeline reissue (attribute cache miss)');
this.addLSCounter('mali_hwc_LS_REISSUE_NO_WB', 'Writeback not used');
this.addTexCounter('mali_hwc_TEX_WORDS', 'Words');
this.addTexCounter('mali_hwc_TEX_BUBBLES', 'Bubbles');
this.addTexCounter('mali_hwc_TEX_WORDS_L0', 'Words L0');
this.addTexCounter('mali_hwc_TEX_WORDS_DESC', 'Words Desc');
this.addTexCounter('mali_hwc_TEX_THREADS', 'Threads');
this.addTexCounter('mali_hwc_TEX_RECIRC_FMISS', 'Recirc due to Full Miss');
this.addTexCounter('mali_hwc_TEX_RECIRC_DESC', 'Recirc due to Desc Miss');
this.addTexCounter('mali_hwc_TEX_RECIRC_MULTI', 'Recirc due to Multipass');
this.addTexCounter('mali_hwc_TEX_RECIRC_PMISS',
'Recirc due to Partial Cache Miss');
this.addTexCounter('mali_hwc_TEX_RECIRC_CONF',
'Recirc due to Cache Conflict');
this.addLSCCounter('mali_hwc_LSC_READ_HITS', 'Read Hits');
this.addLSCCounter('mali_hwc_LSC_READ_MISSES', 'Read Misses');
this.addLSCCounter('mali_hwc_LSC_WRITE_HITS', 'Write Hits');
this.addLSCCounter('mali_hwc_LSC_WRITE_MISSES', 'Write Misses');
this.addLSCCounter('mali_hwc_LSC_ATOMIC_HITS', 'Atomic Hits');
this.addLSCCounter('mali_hwc_LSC_ATOMIC_MISSES', 'Atomic Misses');
this.addLSCCounter('mali_hwc_LSC_LINE_FETCHES', 'Line Fetches');
this.addLSCCounter('mali_hwc_LSC_DIRTY_LINE', 'Dirty Lines');
this.addLSCCounter('mali_hwc_LSC_SNOOPS', 'Snoops');
this.addAXICounter('mali_hwc_AXI_TLB_STALL', 'Address channel stall');
this.addAXICounter('mali_hwc_AXI_TLB_MISS', 'Cache Miss');
this.addAXICounter('mali_hwc_AXI_TLB_TRANSACTION', 'Transactions');
this.addAXICounter('mali_hwc_LS_TLB_MISS', 'LS Cache Miss');
this.addAXICounter('mali_hwc_LS_TLB_HIT', 'LS Cache Hit');
this.addAXICounter('mali_hwc_AXI_BEATS_READ', 'Read Beats');
this.addAXICounter('mali_hwc_AXI_BEATS_WRITE', 'Write Beats');
this.addMMUCounter('mali_hwc_MMU_TABLE_WALK', 'Page Table Walks');
this.addMMUCounter('mali_hwc_MMU_REPLAY_MISS',
'Cache Miss from Replay Buffer');
this.addMMUCounter('mali_hwc_MMU_REPLAY_FULL', 'Replay Buffer Full');
this.addMMUCounter('mali_hwc_MMU_NEW_MISS', 'Cache Miss on New Request');
this.addMMUCounter('mali_hwc_MMU_HIT', 'Cache Hit');
this.addMMUCycles('mali_hwc_UTLB_STALL', 'UTLB Stalled');
this.addMMUCycles('mali_hwc_UTLB_REPLAY_MISS', 'UTLB Replay Miss');
this.addMMUCycles('mali_hwc_UTLB_REPLAY_FULL', 'UTLB Replay Full');
this.addMMUCycles('mali_hwc_UTLB_NEW_MISS', 'UTLB New Miss');
this.addMMUCycles('mali_hwc_UTLB_HIT', 'UTLB Hit');
this.addL2Counter('mali_hwc_L2_READ_BEATS', 'Read Beats');
this.addL2Counter('mali_hwc_L2_WRITE_BEATS', 'Write Beats');
this.addL2Counter('mali_hwc_L2_ANY_LOOKUP', 'Any Lookup');
this.addL2Counter('mali_hwc_L2_READ_LOOKUP', 'Read Lookup');
this.addL2Counter('mali_hwc_L2_SREAD_LOOKUP', 'Shareable Read Lookup');
this.addL2Counter('mali_hwc_L2_READ_REPLAY', 'Read Replayed');
this.addL2Counter('mali_hwc_L2_READ_SNOOP', 'Read Snoop');
this.addL2Counter('mali_hwc_L2_READ_HIT', 'Read Cache Hit');
this.addL2Counter('mali_hwc_L2_CLEAN_MISS', 'CleanUnique Miss');
this.addL2Counter('mali_hwc_L2_WRITE_LOOKUP', 'Write Lookup');
this.addL2Counter('mali_hwc_L2_SWRITE_LOOKUP', 'Shareable Write Lookup');
this.addL2Counter('mali_hwc_L2_WRITE_REPLAY', 'Write Replayed');
this.addL2Counter('mali_hwc_L2_WRITE_SNOOP', 'Write Snoop');
this.addL2Counter('mali_hwc_L2_WRITE_HIT', 'Write Cache Hit');
this.addL2Counter('mali_hwc_L2_EXT_READ_FULL', 'ExtRD with BIU Full');
this.addL2Counter('mali_hwc_L2_EXT_READ_HALF', 'ExtRD with BIU >1/2 Full');
this.addL2Counter('mali_hwc_L2_EXT_WRITE_FULL', 'ExtWR with BIU Full');
this.addL2Counter('mali_hwc_L2_EXT_WRITE_HALF', 'ExtWR with BIU >1/2 Full');
this.addL2Counter('mali_hwc_L2_EXT_READ', 'External Read (ExtRD)');
this.addL2Counter('mali_hwc_L2_EXT_READ_LINE', 'ExtRD (linefill)');
this.addL2Counter('mali_hwc_L2_EXT_WRITE', 'External Write (ExtWR)');
this.addL2Counter('mali_hwc_L2_EXT_WRITE_LINE', 'ExtWR (linefill)');
this.addL2Counter('mali_hwc_L2_EXT_WRITE_SMALL', 'ExtWR (burst size <64B)');
this.addL2Counter('mali_hwc_L2_EXT_BARRIER', 'External Barrier');
this.addL2Counter('mali_hwc_L2_EXT_AR_STALL', 'Address Read stalls');
this.addL2Counter('mali_hwc_L2_EXT_R_BUF_FULL',
'Response Buffer full stalls');
this.addL2Counter('mali_hwc_L2_EXT_RD_BUF_FULL',
'Read Data Buffer full stalls');
this.addL2Counter('mali_hwc_L2_EXT_R_RAW', 'RAW hazard stalls');
this.addL2Counter('mali_hwc_L2_EXT_W_STALL', 'Write Data stalls');
this.addL2Counter('mali_hwc_L2_EXT_W_BUF_FULL', 'Write Data Buffer full');
this.addL2Counter('mali_hwc_L2_EXT_R_W_HAZARD', 'WAW or WAR hazard stalls');
this.addL2Counter('mali_hwc_L2_TAG_HAZARD', 'Tag hazard replays');
this.addL2Cycles('mali_hwc_L2_SNOOP_FULL', 'Snoop buffer full');
this.addL2Cycles('mali_hwc_L2_REPLAY_FULL', 'Replay buffer full');
// DDK events (from X server)
importer.registerEventHandler('tracing_mark_write:mali_driver',
MaliParser.prototype.maliDDKEvent.bind(this));
this.model_ = importer.model_;
}
MaliParser.prototype = {
__proto__: Parser.prototype,
maliDDKOpenSlice: function(pid, tid, ts, func, blockinfo) {
var thread = this.importer.model_.getOrCreateProcess(pid)
.getOrCreateThread(tid);
var funcArgs = /^([\w\d_]*)(?:\(\))?:?\s*(.*)$/.exec(func);
thread.sliceGroup.beginSlice('gpu-driver', funcArgs[1], ts,
{ 'args': funcArgs[2],
'blockinfo': blockinfo });
},
maliDDKCloseSlice: function(pid, tid, ts, args, blockinfo) {
var thread = this.importer.model_.getOrCreateProcess(pid)
.getOrCreateThread(tid);
if (!thread.sliceGroup.openSliceCount) {
// Discard unmatched ends.
return;
}
thread.sliceGroup.endSlice(ts);
},
/**
* Deduce the format of Mali perf events.
*
* @return {RegExp} the regular expression for parsing data when the format
* is recognized; otherwise null.
*/
autoDetectLineRE: function(line) {
// Matches Mali perf events with thread info
var lineREWithThread =
/^\s*\(([\w\-]*)\)\s*(\w+):\s*([\w\\\/\.\-]*@\d*):?\s*(.*)$/;
if (lineREWithThread.test(line))
return lineREWithThread;
// Matches old-style Mali perf events
var lineRENoThread = /^s*()(\w+):\s*([\w\\\/.\-]*):?\s*(.*)$/;
if (lineRENoThread.test(line))
return lineRENoThread;
return null;
},
lineRE: null,
/**
* Parses maliDDK events and sets up state in the importer.
* events will come in pairs with a cros_trace_print_enter
* like this (line broken here for formatting):
*
* tracing_mark_write: mali_driver: (mali-012345) cros_trace_print_enter: \
* gles/src/texture/mali_gles_texture_slave.c@1505: gles2_texturep_upload
*
* and a cros_trace_print_exit like this:
*
* tracing_mark_write: mali_driver: (mali-012345) cros_trace_print_exit: \
* gles/src/texture/mali_gles_texture_slave.c@1505:
*/
maliDDKEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
if (this.lineRE == null) {
this.lineRE = this.autoDetectLineRE(eventBase.details);
if (this.lineRE == null)
return false;
}
var maliEvent = this.lineRE.exec(eventBase.details);
// Old-style Mali perf events have no thread id, so make one.
var tid = (maliEvent[1] === '' ? 'mali' : maliEvent[1]);
switch (maliEvent[2]) {
case 'cros_trace_print_enter':
this.maliDDKOpenSlice(pid, tid, ts, maliEvent[4],
maliEvent[3]);
break;
case 'cros_trace_print_exit':
this.maliDDKCloseSlice(pid, tid, ts, [], maliEvent[3]);
}
return true;
},
/*
* Kernel event support.
*/
dvfsSample: function(counterName, seriesName, ts, s) {
var value = parseInt(s);
var counter = this.model_.getOrCreateProcess(0).
getOrCreateCounter('DVFS', counterName);
if (counter.numSeries === 0) {
counter.addSeries(new tracing.trace_model.CounterSeries(seriesName,
tracing.getStringColorId(counter.name)));
}
counter.series.forEach(function(series) {
series.addSample(ts, value);
});
},
dvfsEventEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /utilization=(\d+)/.exec(eventBase.details);
if (!event)
return false;
this.dvfsSample('DVFS Utilization', 'utilization', ts, event[1]);
return true;
},
dvfsSetClockEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /frequency=(\d+)/.exec(eventBase.details);
if (!event)
return false;
this.dvfsSample('DVFS Frequency', 'frequency', ts, event[1]);
return true;
},
dvfsSetVoltageEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /voltage=(\d+)/.exec(eventBase.details);
if (!event)
return false;
this.dvfsSample('DVFS Voltage', 'voltage', ts, event[1]);
return true;
},
hwcSample: function(cat, counterName, seriesName, ts, eventBase) {
var event = /val=(\d+)/.exec(eventBase.details);
if (!event)
return false;
var value = parseInt(event[1]);
var counter = this.model_.getOrCreateProcess(0).
getOrCreateCounter(cat, counterName);
if (counter.numSeries === 0) {
counter.addSeries(new tracing.trace_model.CounterSeries(seriesName,
tracing.getStringColorId(counter.name)));
}
counter.series.forEach(function(series) {
series.addSample(ts, value);
});
return true;
},
/*
* Job Manager block counters.
*/
jmSample: function(ctrName, seriesName, ts, eventBase) {
return this.hwcSample('mali:jm', 'JM: ' + ctrName, seriesName, ts,
eventBase);
},
addJMCounter: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.jmSample(hwcTitle, 'count', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
addJMCycles: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.jmSample(hwcTitle, 'cycles', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
/*
* Tiler block counters.
*/
tilerSample: function(ctrName, seriesName, ts, eventBase) {
return this.hwcSample('mali:tiler', 'Tiler: ' + ctrName, seriesName,
ts, eventBase);
},
addTilerCounter: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.tilerSample(hwcTitle, 'count', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
addTilerCycles: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.tilerSample(hwcTitle, 'cycles', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
/*
* Fragment counters.
*/
fragSample: function(ctrName, seriesName, ts, eventBase) {
return this.hwcSample('mali:fragment', 'Fragment: ' + ctrName,
seriesName, ts, eventBase);
},
addFragCounter: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.fragSample(hwcTitle, 'count', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
addFragCycles: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.fragSample(hwcTitle, 'cycles', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
/*
* Compute counters.
*/
computeSample: function(ctrName, seriesName, ts, eventBase) {
return this.hwcSample('mali:compute', 'Compute: ' + ctrName,
seriesName, ts, eventBase);
},
addComputeCounter: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.computeSample(hwcTitle, 'count', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
addComputeCycles: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.computeSample(hwcTitle, 'cycles', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
/*
* Tripipe counters.
*/
addTripipeCycles: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.hwcSample('mali:shader', 'Tripipe: ' + hwcTitle, 'cycles',
ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
/*
* Arith counters.
*/
arithSample: function(ctrName, seriesName, ts, eventBase) {
return this.hwcSample('mali:arith', 'Arith: ' + ctrName, seriesName, ts,
eventBase);
},
addArithCounter: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.arithSample(hwcTitle, 'count', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
addArithCycles: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.arithSample(hwcTitle, 'cycles', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
/*
* Load/Store counters.
*/
addLSCounter: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.hwcSample('mali:ls', 'LS: ' + hwcTitle, 'count', ts,
eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
/*
* Texture counters.
*/
textureSample: function(ctrName, seriesName, ts, eventBase) {
return this.hwcSample('mali:texture', 'Texture: ' + ctrName,
seriesName, ts, eventBase);
},
addTexCounter: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.textureSample(hwcTitle, 'count', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
/*
* LSC counters.
*/
addLSCCounter: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.hwcSample('mali:lsc', 'LSC: ' + hwcTitle, 'count', ts,
eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
/*
* TLB counters.
*/
addAXICounter: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.hwcSample('mali:axi', 'AXI: ' + hwcTitle, 'count', ts,
eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
/*
* MMU counters.
*/
mmuSample: function(ctrName, seriesName, ts, eventBase) {
return this.hwcSample('mali:mmu', 'MMU: ' + ctrName, seriesName, ts,
eventBase);
},
addMMUCounter: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.mmuSample(hwcTitle, 'count', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
addMMUCycles: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.mmuSample(hwcTitle, 'cycles', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
/*
* L2 counters.
*/
l2Sample: function(ctrName, seriesName, ts, eventBase) {
return this.hwcSample('mali:l2', 'L2: ' + ctrName, seriesName, ts,
eventBase);
},
addL2Counter: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.l2Sample(hwcTitle, 'count', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
},
addL2Cycles: function(hwcEventName, hwcTitle) {
function handler(eventName, cpuNumber, pid, ts, eventBase) {
return this.l2Sample(hwcTitle, 'cycles', ts, eventBase);
}
this.importer.registerEventHandler(hwcEventName, handler.bind(this));
}
};
Parser.registerSubtype(MaliParser);
return {
MaliParser: MaliParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses power events in the Linux event trace format.
*/
base.require('tracing.importer.linux_perf.parser');
base.require('tracing.trace_model.counter_series');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux power trace events.
* @constructor
*/
function PowerParser(importer) {
Parser.call(this, importer);
// NB: old-style power events, deprecated
importer.registerEventHandler('power_start',
PowerParser.prototype.powerStartEvent.bind(this));
importer.registerEventHandler('power_frequency',
PowerParser.prototype.powerFrequencyEvent.bind(this));
importer.registerEventHandler('cpu_frequency',
PowerParser.prototype.cpuFrequencyEvent.bind(this));
importer.registerEventHandler('cpu_idle',
PowerParser.prototype.cpuIdleEvent.bind(this));
}
PowerParser.prototype = {
__proto__: Parser.prototype,
cpuStateSlice: function(ts, targetCpuNumber, eventType, cpuState) {
var targetCpu = this.importer.getOrCreateCpuState(targetCpuNumber);
var powerCounter;
if (eventType != '1') {
this.importer.model.importWarning({
type: 'parse_error',
message: 'Don\'t understand power_start events of ' +
'type ' + eventType
});
return;
}
powerCounter = targetCpu.cpu.getOrCreateCounter('', 'C-State');
if (powerCounter.numSeries === 0) {
powerCounter.addSeries(new tracing.trace_model.CounterSeries('state',
tracing.getStringColorId(powerCounter.name + '.' + 'state')));
}
powerCounter.series.forEach(function(series) {
series.addSample(ts, cpuState);
});
},
cpuIdleSlice: function(ts, targetCpuNumber, cpuState) {
var targetCpu = this.importer.getOrCreateCpuState(targetCpuNumber);
var powerCounter = targetCpu.cpu.getOrCreateCounter('', 'C-State');
if (powerCounter.numSeries === 0) {
powerCounter.addSeries(new tracing.trace_model.CounterSeries('state',
tracing.getStringColorId(powerCounter.name)));
}
// NB: 4294967295/-1 means an exit from the current state
var val = (cpuState != 4294967295 ? cpuState : 0);
powerCounter.series.forEach(function(series) {
series.addSample(ts, val);
});
},
cpuFrequencySlice: function(ts, targetCpuNumber, powerState) {
var targetCpu = this.importer.getOrCreateCpuState(targetCpuNumber);
var powerCounter =
targetCpu.cpu.getOrCreateCounter('', 'Clock Frequency');
if (powerCounter.numSeries === 0) {
powerCounter.addSeries(new tracing.trace_model.CounterSeries('state',
tracing.getStringColorId(powerCounter.name + '.' + 'state')));
}
powerCounter.series.forEach(function(series) {
series.addSample(ts, powerState);
});
},
/**
* Parses power events and sets up state in the importer.
*/
powerStartEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /type=(\d+) state=(\d) cpu_id=(\d)+/.exec(eventBase.details);
if (!event)
return false;
var targetCpuNumber = parseInt(event[3]);
var cpuState = parseInt(event[2]);
this.cpuStateSlice(ts, targetCpuNumber, event[1], cpuState);
return true;
},
powerFrequencyEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /type=(\d+) state=(\d+) cpu_id=(\d)+/
.exec(eventBase.details);
if (!event)
return false;
var targetCpuNumber = parseInt(event[3]);
var powerState = parseInt(event[2]);
this.cpuFrequencySlice(ts, targetCpuNumber, powerState);
return true;
},
cpuFrequencyEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /state=(\d+) cpu_id=(\d)+/.exec(eventBase.details);
if (!event)
return false;
var targetCpuNumber = parseInt(event[2]);
var powerState = parseInt(event[1]);
this.cpuFrequencySlice(ts, targetCpuNumber, powerState);
return true;
},
cpuIdleEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /state=(\d+) cpu_id=(\d)+/.exec(eventBase.details);
if (!event)
return false;
var targetCpuNumber = parseInt(event[2]);
var cpuState = parseInt(event[1]);
this.cpuIdleSlice(ts, targetCpuNumber, cpuState);
return true;
}
};
Parser.registerSubtype(PowerParser);
return {
PowerParser: PowerParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses scheduler events in the Linux event trace format.
*/
base.require('tracing.importer.linux_perf.parser');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux sched trace events.
* @constructor
*/
function SchedParser(importer) {
Parser.call(this, importer);
importer.registerEventHandler('sched_switch',
SchedParser.prototype.schedSwitchEvent.bind(this));
importer.registerEventHandler('sched_wakeup',
SchedParser.prototype.schedWakeupEvent.bind(this));
}
var TestExports = {};
// Matches the sched_switch record
var schedSwitchRE = new RegExp(
'prev_comm=(.+) prev_pid=(\\d+) prev_prio=(\\d+) ' +
'prev_state=(\\S\\+?|\\S\\|\\S) ==> ' +
'next_comm=(.+) next_pid=(\\d+) next_prio=(\\d+)');
TestExports.schedSwitchRE = schedSwitchRE;
// Matches the sched_wakeup record
var schedWakeupRE =
/comm=(.+) pid=(\d+) prio=(\d+) success=(\d+) target_cpu=(\d+)/;
TestExports.schedWakeupRE = schedWakeupRE;
SchedParser.prototype = {
__proto__: Parser.prototype,
/**
* Parses scheduler events and sets up state in the importer.
*/
schedSwitchEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = schedSwitchRE.exec(eventBase.details);
if (!event)
return false;
var prevState = event[4];
var nextComm = event[5];
var nextPid = parseInt(event[6]);
var nextPrio = parseInt(event[7]);
var cpuState = this.importer.getOrCreateCpuState(cpuNumber);
cpuState.switchRunningLinuxPid(this.importer,
prevState, ts, nextPid, nextComm, nextPrio);
return true;
},
schedWakeupEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = schedWakeupRE.exec(eventBase.details);
if (!event)
return false;
var fromPid = pid;
var comm = event[1];
var pid = parseInt(event[2]);
var prio = parseInt(event[3]);
this.importer.markPidRunnable(ts, pid, comm, prio, fromPid);
return true;
}
};
Parser.registerSubtype(SchedParser);
return {
SchedParser: SchedParser,
_SchedParserTestExports: TestExports
};
});
// 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.
'use strict';
/**
* @fileoverview Parses sync events in the Linux event trace format.
*/
base.require('tracing.importer.linux_perf.parser');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux sync trace events.
* @constructor
*/
function SyncParser(importer) {
Parser.call(this, importer);
importer.registerEventHandler(
'sync_timeline',
SyncParser.prototype.timelineEvent.bind(this));
importer.registerEventHandler(
'sync_wait',
SyncParser.prototype.syncWaitEvent.bind(this));
importer.registerEventHandler(
'sync_pt',
SyncParser.prototype.syncPtEvent.bind(this));
this.model_ = importer.model_;
}
var syncTimelineRE = /name=(\S+) value=(\S*)/;
var syncWaitRE = /(\S+) name=(\S+) state=(\d+)/;
var syncPtRE = /name=(\S+) value=(\S*)/;
SyncParser.prototype = {
__proto__: Parser.prototype,
/**
* Parses sync events and sets up state in the importer.
*/
timelineEvent: function(eventName, cpuNumber, pid,
ts, eventBase) {
var event = syncTimelineRE.exec(eventBase.details);
if (!event)
return false;
var thread = this.importer.getOrCreatePseudoThread(event[1]);
if (thread.lastActiveTs !== undefined) {
var duration = ts - thread.lastActiveTs;
var value = thread.lastActiveValue;
if (value == undefined)
value = ' ';
var slice = new tracing.trace_model.Slice(
'', value,
tracing.getStringColorId(value),
thread.lastActiveTs, {},
duration);
thread.thread.sliceGroup.pushSlice(slice);
}
thread.lastActiveTs = ts;
thread.lastActiveValue = event[2];
return true;
},
syncWaitEvent: function(eventName, cpuNumber, pid, ts,
eventBase) {
var event = syncWaitRE.exec(eventBase.details);
if (!event)
return false;
if (eventBase.tgid === undefined) {
return false;
}
var tgid = parseInt(eventBase.tgid);
var thread = this.model_.getOrCreateProcess(tgid)
.getOrCreateThread(pid);
thread.name = eventBase.threadName;
var slices = thread.kernelSliceGroup;
if (!slices.isTimestampValidForBeginOrEnd(ts)) {
this.model_.importWarning({
type: 'parse_error',
message: 'Timestamps are moving backward.'
});
return false;
}
var name = 'fence_wait("' + event[2] + '")';
if (event[1] == 'begin') {
var slice = slices.beginSlice(null, name, ts, {
'Start state': event[3]
});
} else if (event[1] == 'end') {
if (slices.openSliceCount > 0) {
slices.endSlice(ts);
}
} else {
return false;
}
return true;
},
syncPtEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = syncPtRE.exec(eventBase.details);
if (!event)
return false;
return true;
var thread = this.importer.getOrCreateKernelThread(
eventBase[1]).thread;
thread.syncWaitSyncPts[event[1]] = event[2];
return true;
}
};
Parser.registerSubtype(SyncParser);
return {
SyncParser: SyncParser
};
});
// 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.
'use strict';
/**
* @fileoverview Parses workqueue events in the Linux event trace format.
*/
base.require('tracing.importer.linux_perf.parser');
base.exportTo('tracing.importer.linux_perf', function() {
var Parser = tracing.importer.linux_perf.Parser;
/**
* Parses linux workqueue trace events.
* @constructor
*/
function WorkqueueParser(importer) {
Parser.call(this, importer);
importer.registerEventHandler('workqueue_execute_start',
WorkqueueParser.prototype.executeStartEvent.bind(this));
importer.registerEventHandler('workqueue_execute_end',
WorkqueueParser.prototype.executeEndEvent.bind(this));
importer.registerEventHandler('workqueue_queue_work',
WorkqueueParser.prototype.executeQueueWork.bind(this));
importer.registerEventHandler('workqueue_activate_work',
WorkqueueParser.prototype.executeActivateWork.bind(this));
}
// Matches the workqueue_execute_start record
// workqueue_execute_start: work struct c7a8a89c: function MISRWrapper
var workqueueExecuteStartRE = /work struct (.+): function (\S+)/;
// Matches the workqueue_execute_start record
// workqueue_execute_end: work struct c7a8a89c
var workqueueExecuteEndRE = /work struct (.+)/;
WorkqueueParser.prototype = {
__proto__: Parser.prototype,
/**
* Parses workqueue events and sets up state in the importer.
*/
executeStartEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = workqueueExecuteStartRE.exec(eventBase.details);
if (!event)
return false;
var kthread = this.importer.getOrCreateKernelThread(eventBase.threadName,
pid, pid);
kthread.openSliceTS = ts;
kthread.openSlice = event[2];
return true;
},
executeEndEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = workqueueExecuteEndRE.exec(eventBase.details);
if (!event)
return false;
var kthread = this.importer.getOrCreateKernelThread(eventBase.threadName,
pid, pid);
if (kthread.openSlice) {
var slice = new tracing.trace_model.Slice('', kthread.openSlice,
tracing.getStringColorId(kthread.openSlice),
kthread.openSliceTS,
{},
ts - kthread.openSliceTS);
kthread.thread.sliceGroup.pushSlice(slice);
}
kthread.openSlice = undefined;
return true;
},
executeQueueWork: function(eventName, cpuNumber, pid, ts, eventBase) {
// TODO: Do something with this event?
return true;
},
executeActivateWork: function(eventName, cpuNumber, pid, ts, eventBase) {
// TODO: Do something with this event?
return true;
}
};
Parser.registerSubtype(WorkqueueParser);
return {
WorkqueueParser: WorkqueueParser
};
});
// 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.
/**
* @fileoverview Imports text files in the Linux event trace format into the
* Tracemodel. This format is output both by sched_trace and by Linux's perf
* tool.
*
* This importer assumes the events arrive as a string. The unit tests provide
* examples of the trace format.
*
* Linux scheduler traces use a definition for 'pid' that is different than
* tracing uses. Whereas tracing uses pid to identify a specific process, a pid
* in a linux trace refers to a specific thread within a process. Within this
* file, we the definition used in Linux traces, as it improves the importing
* code's readability.
*/
'use strict';
base.require('tracing.trace_model');
base.require('tracing.color_scheme');
base.require('tracing.importer.importer');
base.require('tracing.importer.linux_perf.bus_parser');
base.require('tracing.importer.linux_perf.clock_parser');
base.require('tracing.importer.linux_perf.cpufreq_parser');
base.require('tracing.importer.linux_perf.disk_parser');
base.require('tracing.importer.linux_perf.drm_parser');
base.require('tracing.importer.linux_perf.exynos_parser');
base.require('tracing.importer.linux_perf.gesture_parser');
base.require('tracing.importer.linux_perf.i915_parser');
base.require('tracing.importer.linux_perf.mali_parser');
base.require('tracing.importer.linux_perf.power_parser');
base.require('tracing.importer.linux_perf.sched_parser');
base.require('tracing.importer.linux_perf.sync_parser');
base.require('tracing.importer.linux_perf.workqueue_parser');
base.require('tracing.importer.linux_perf.android_parser');
base.require('tracing.importer.linux_perf.kfunc_parser');
base.exportTo('tracing.importer', function() {
var Importer = tracing.importer.Importer;
/**
* Represents the scheduling state for a single thread.
* @constructor
*/
function CpuState(cpu) {
this.cpu = cpu;
}
CpuState.prototype = {
__proto__: Object.prototype,
/**
* Switches the active pid on this Cpu. If necessary, add a Slice
* to the cpu representing the time spent on that Cpu since the last call to
* switchRunningLinuxPid.
*/
switchRunningLinuxPid: function(importer, prevState, ts, pid, comm, prio) {
// Generate a slice if the last active pid was not the idle task
if (this.lastActivePid !== undefined && this.lastActivePid != 0) {
var duration = ts - this.lastActiveTs;
var thread = importer.threadsByLinuxPid[this.lastActivePid];
var name;
if (thread)
name = thread.userFriendlyName;
else
name = this.lastActiveComm;
var slice = new tracing.trace_model.CpuSlice(
'', name,
tracing.getStringColorId(name),
this.lastActiveTs,
{
comm: this.lastActiveComm,
tid: this.lastActivePid,
prio: this.lastActivePrio,
stateWhenDescheduled: prevState
},
duration);
slice.cpu = this.cpu;
this.cpu.slices.push(slice);
}
this.lastActiveTs = ts;
this.lastActivePid = pid;
this.lastActiveComm = comm;
this.lastActivePrio = prio;
}
};
/**
* Imports linux perf events into a specified model.
* @constructor
*/
function LinuxPerfImporter(model, events) {
this.importPriority = 2;
this.model_ = model;
this.events_ = events;
this.clockSyncRecords_ = [];
this.cpuStates_ = {};
this.wakeups_ = [];
this.kernelThreadStates_ = {};
this.buildMapFromLinuxPidsToThreads();
this.lineNumberBase = 0;
this.lineNumber = -1;
this.pseudoThreadCounter = 1;
this.parsers_ = [];
this.eventHandlers_ = {};
}
var TestExports = {};
// Matches the trace record in 3.2 and later with the print-tgid option:
// <idle>-0 0 [001] d... 1.23: sched_switch
//
// A TGID (Thread Group ID) is basically what the Linux kernel calls what
// userland refers to as a process ID (as opposed to a Linux pid, which is
// what userland calls a thread ID).
var lineREWithTGID = new RegExp(
'^\\s*(.+)-(\\d+)\\s+\\(\\s*(\\d+|-+)\\)\\s\\[(\\d+)\\]' +
'\\s+[dX.][N.][Hhs.][0-9a-f.]' +
'\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)$');
var lineParserWithTGID = function(line) {
var groups = lineREWithTGID.exec(line);
if (!groups) {
return groups;
}
var tgid = groups[3];
if (tgid[0] === '-')
tgid = undefined;
return {
threadName: groups[1],
pid: groups[2],
tgid: tgid,
cpuNumber: groups[4],
timestamp: groups[5],
eventName: groups[6],
details: groups[7]
};
};
TestExports.lineParserWithTGID = lineParserWithTGID;
// Matches the default trace record in 3.2 and later (includes irq-info):
// <idle>-0 [001] d... 1.23: sched_switch
var lineREWithIRQInfo = new RegExp(
'^\\s*(.+)-(\\d+)\\s+\\[(\\d+)\\]' +
'\\s+[dX.][N.][Hhs.][0-9a-f.]' +
'\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)$');
var lineParserWithIRQInfo = function(line) {
var groups = lineREWithIRQInfo.exec(line);
if (!groups) {
return groups;
}
return {
threadName: groups[1],
pid: groups[2],
cpuNumber: groups[3],
timestamp: groups[4],
eventName: groups[5],
details: groups[6]
};
};
TestExports.lineParserWithIRQInfo = lineParserWithIRQInfo;
// Matches the default trace record pre-3.2:
// <idle>-0 [001] 1.23: sched_switch
var lineREWithLegacyFmt =
/^\s*(.+)-(\d+)\s+\[(\d+)\]\s*(\d+\.\d+):\s+(\S+):\s(.*)$/;
var lineParserWithLegacyFmt = function(line) {
var groups = lineREWithLegacyFmt.exec(line);
if (!groups) {
return groups;
}
return {
threadName: groups[1],
pid: groups[2],
cpuNumber: groups[3],
timestamp: groups[4],
eventName: groups[5],
details: groups[6]
};
};
TestExports.lineParserWithLegacyFmt = lineParserWithLegacyFmt;
// Matches the trace_event_clock_sync record
// 0: trace_event_clock_sync: parent_ts=19581477508
var traceEventClockSyncRE = /trace_event_clock_sync: parent_ts=(\d+\.?\d*)/;
TestExports.traceEventClockSyncRE = traceEventClockSyncRE;
// Some kernel trace events are manually classified in slices and
// hand-assigned a pseudo PID.
var pseudoKernelPID = 0;
/**
* Deduce the format of trace data. Linix kernels prior to 3.3 used one
* format (by default); 3.4 and later used another. Additionally, newer
* kernels can optionally trace the TGID.
*
* @return {function} the function for parsing data when the format is
* recognized; otherwise null.
*/
function autoDetectLineParser(line) {
if (line[0] == '{')
return false;
if (lineREWithTGID.test(line))
return lineParserWithTGID;
if (lineREWithIRQInfo.test(line))
return lineParserWithIRQInfo;
if (lineREWithLegacyFmt.test(line))
return lineParserWithLegacyFmt;
return null;
};
TestExports.autoDetectLineParser = autoDetectLineParser;
/**
* Guesses whether the provided events is a Linux perf string.
* Looks for the magic string "# tracer" at the start of the file,
* or the typical task-pid-cpu-timestamp-function sequence of a typical
* trace's body.
*
* @return {boolean} True when events is a linux perf array.
*/
LinuxPerfImporter.canImport = function(events) {
if (!(typeof(events) === 'string' || events instanceof String))
return false;
if (LinuxPerfImporter._extractEventsFromSystraceHTML(events, false).ok)
return true;
if (/^# tracer:/.test(events))
return true;
var m = /^(.+)\n/.exec(events);
if (m)
events = m[1];
if (autoDetectLineParser(events))
return true;
return false;
};
LinuxPerfImporter._extractEventsFromSystraceHTML = function(
incoming_events, produce_result) {
var failure = {ok: false};
if (produce_result === undefined)
produce_result = true;
if (/^<!DOCTYPE HTML>/.test(incoming_events) == false)
return failure;
var lines = incoming_events.split('\n');
var cur_line = 1;
function advanceToLineMatching(regex) {
for (; cur_line < lines.length; cur_line++) {
if (regex.test(lines[cur_line]))
return true;
}
return false;
}
// Try to find the data...
if (!advanceToLineMatching(/^ <script>$/))
return failure;
if (!advanceToLineMatching(/^ var linuxPerfData = "\\$/))
return failure;
var events_begin_at_line = cur_line + 1;
if (!advanceToLineMatching(/^ <\/script>$/))
return failure;
var events_end_at_line = cur_line;
if (!advanceToLineMatching(/^<\/body>$/))
return failure;
if (!advanceToLineMatching(/^<\/html>$/))
return failure;
var raw_events = lines.slice(events_begin_at_line,
events_end_at_line);
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
function stripSuffix(str, suffix) {
if (!endsWith(str, suffix))
return str;
return str.substring(str, str.length - suffix.length);
}
// Strip off escaping in the file needed to preserve linebreaks.
var events = [];
if (produce_result) {
for (var i = 0; i < raw_events.length; i++) {
var event = raw_events[i];
event = stripSuffix(event, '\\n\\');
events.push(event);
}
} else {
events = [raw_events[raw_events.length - 1]];
}
// Last event ends differently. Strip that off too,
// treating absence of that trailing stirng as a failure.
var oldLastEvent = events[events.length - 1];
var newLastEvent = stripSuffix(oldLastEvent, '\\n";');
if (newLastEvent == oldLastEvent)
return failure;
events[events.length - 1] = newLastEvent;
return {ok: true,
lines: produce_result ? events : undefined,
events_begin_at_line: events_begin_at_line};
};
LinuxPerfImporter.prototype = {
__proto__: Importer.prototype,
get model() {
return this.model_;
},
/**
* Precomputes a lookup table from linux pids back to existing
* Threads. This is used during importing to add information to each
* thread about whether it was running, descheduled, sleeping, et
* cetera.
*/
buildMapFromLinuxPidsToThreads: function() {
this.threadsByLinuxPid = {};
this.model_.getAllThreads().forEach(
function(thread) {
this.threadsByLinuxPid[thread.tid] = thread;
}.bind(this));
},
/**
* @return {CpuState} A CpuState corresponding to the given cpuNumber.
*/
getOrCreateCpuState: function(cpuNumber) {
if (!this.cpuStates_[cpuNumber]) {
var cpu = this.model_.kernel.getOrCreateCpu(cpuNumber);
this.cpuStates_[cpuNumber] = new CpuState(cpu);
}
return this.cpuStates_[cpuNumber];
},
/**
* @return {TimelinThread} A thread corresponding to the kernelThreadName.
*/
getOrCreateKernelThread: function(kernelThreadName, pid, tid) {
if (!this.kernelThreadStates_[kernelThreadName]) {
var thread = this.model_.getOrCreateProcess(pid).getOrCreateThread(tid);
thread.name = kernelThreadName;
this.kernelThreadStates_[kernelThreadName] = {
pid: pid,
thread: thread,
openSlice: undefined,
openSliceTS: undefined
};
this.threadsByLinuxPid[pid] = thread;
}
return this.kernelThreadStates_[kernelThreadName];
},
/**
* @return {TimelinThread} A pseudo thread corresponding to the
* threadName. Pseudo threads are for events that we want to break
* out to a separate timeline but would not otherwise happen.
* These threads are assigned to pseudoKernelPID and given a
* unique (incrementing) TID.
*/
getOrCreatePseudoThread: function(threadName) {
var thread = this.kernelThreadStates_[threadName];
if (!thread) {
thread = this.getOrCreateKernelThread(threadName, pseudoKernelPID,
this.pseudoThreadCounter);
this.pseudoThreadCounter++;
}
return thread;
},
/**
* Imports the data in this.events_ into model_.
*/
importEvents: function(isSecondaryImport) {
this.createParsers();
this.importCpuData();
if (!this.alignClocks(isSecondaryImport))
return;
this.buildMapFromLinuxPidsToThreads();
this.buildPerThreadCpuSlicesFromCpuState();
},
/**
* Builds the timeSlices array on each thread based on our knowledge of what
* each Cpu is doing. This is done only for Threads that are
* already in the model, on the assumption that not having any traced data
* on a thread means that it is not of interest to the user.
*/
buildPerThreadCpuSlicesFromCpuState: function() {
// Push the cpu slices to the threads that they run on.
for (var cpuNumber in this.cpuStates_) {
var cpuState = this.cpuStates_[cpuNumber];
var cpu = cpuState.cpu;
for (var i = 0; i < cpu.slices.length; i++) {
var cpuSlice = cpu.slices[i];
var thread = this.threadsByLinuxPid[cpuSlice.args.tid];
if (!thread)
continue;
cpuSlice.threadThatWasRunning = thread;
if (!thread.tempCpuSlices)
thread.tempCpuSlices = [];
thread.tempCpuSlices.push(cpuSlice);
}
}
for (var i in this.wakeups_) {
var wakeup = this.wakeups_[i];
var thread = this.threadsByLinuxPid[wakeup.tid];
if (!thread)
continue;
thread.tempWakeups = thread.tempWakeups || [];
thread.tempWakeups.push(wakeup);
}
// Create slices for when the thread is not running.
var runningId = tracing.getColorIdByName('running');
var runnableId = tracing.getColorIdByName('runnable');
var sleepingId = tracing.getColorIdByName('sleeping');
var ioWaitId = tracing.getColorIdByName('iowait');
this.model_.getAllThreads().forEach(function(thread) {
if (thread.tempCpuSlices === undefined)
return;
var origSlices = thread.tempCpuSlices;
delete thread.tempCpuSlices;
origSlices.sort(function(x, y) {
return x.start - y.start;
});
var wakeups = thread.tempWakeups || [];
delete thread.tempWakeups;
wakeups.sort(function(x, y) {
return x.ts - y.ts;
});
// Walk the slice list and put slices between each original slice to
// show when the thread isn't running.
var slices = [];
if (origSlices.length) {
var slice = origSlices[0];
if (wakeups.length && wakeups[0].ts < slice.start) {
var wakeup = wakeups.shift();
var wakeupDuration = slice.start - wakeup.ts;
var args = {'wakeup from tid': wakeup.fromTid};
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread, '', 'Runnable', runnableId,
wakeup.ts, args, wakeupDuration));
}
var runningSlice = new tracing.trace_model.ThreadTimeSlice(
thread, '', 'Running', runningId,
slice.start, {}, slice.duration);
runningSlice.cpuOnWhichThreadWasRunning = slice.cpu;
slices.push(runningSlice);
}
var wakeup = undefined;
for (var i = 1; i < origSlices.length; i++) {
var prevSlice = origSlices[i - 1];
var nextSlice = origSlices[i];
var midDuration = nextSlice.start - prevSlice.end;
while (wakeups.length && wakeups[0].ts < nextSlice.start) {
var w = wakeups.shift();
if (wakeup === undefined && w.ts > prevSlice.end) {
wakeup = w;
}
}
// Push a sleep slice onto the slices list, interrupting it with a
// wakeup if appropriate.
var pushSleep = function(title, id) {
if (wakeup !== undefined) {
midDuration = wakeup.ts - prevSlice.end;
}
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread,
'', title, id, prevSlice.end, {}, midDuration));
if (wakeup !== undefined) {
var wakeupDuration = nextSlice.start - wakeup.ts;
var args = {'wakeup from tid': wakeup.fromTid};
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread,
'', 'Runnable', runnableId, wakeup.ts, args, wakeupDuration));
wakeup = undefined;
}
};
if (prevSlice.args.stateWhenDescheduled == 'S') {
pushSleep('Sleeping', sleepingId);
} else if (prevSlice.args.stateWhenDescheduled == 'R' ||
prevSlice.args.stateWhenDescheduled == 'R+') {
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread,
'', 'Runnable', runnableId, prevSlice.end, {}, midDuration));
} else if (prevSlice.args.stateWhenDescheduled == 'D') {
pushSleep('Uninterruptible Sleep', ioWaitId);
} else if (prevSlice.args.stateWhenDescheduled == 'T') {
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread, '', '__TASK_STOPPED', ioWaitId,
prevSlice.end, {}, midDuration));
} else if (prevSlice.args.stateWhenDescheduled == 't') {
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread, '', 'debug', ioWaitId,
prevSlice.end, {}, midDuration));
} else if (prevSlice.args.stateWhenDescheduled == 'Z') {
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread, '', 'Zombie', ioWaitId,
prevSlice.end, {}, midDuration));
} else if (prevSlice.args.stateWhenDescheduled == 'X') {
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread, '', 'Exit Dead', ioWaitId,
prevSlice.end, {}, midDuration));
} else if (prevSlice.args.stateWhenDescheduled == 'x') {
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread, '', 'Task Dead', ioWaitId,
prevSlice.end, {}, midDuration));
} else if (prevSlice.args.stateWhenDescheduled == 'K') {
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread, '', 'Wakekill', ioWaitId,
prevSlice.end, {}, midDuration));
} else if (prevSlice.args.stateWhenDescheduled == 'W') {
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread, '', 'Waking', ioWaitId,
prevSlice.end, {}, midDuration));
} else if (prevSlice.args.stateWhenDescheduled == 'D|K') {
pushSleep('Uninterruptible Sleep | WakeKill', ioWaitId);
} else if (prevSlice.args.stateWhenDescheduled == 'D|W') {
pushSleep('Uninterruptible Sleep | Waking', ioWaitId);
} else {
slices.push(new tracing.trace_model.ThreadTimeSlice(
thread, '', 'UNKNOWN', ioWaitId,
prevSlice.end, {}, midDuration));
this.model_.importWarning({
type: 'parse_error',
message: 'Unrecognized sleep state: ' +
prevSlice.args.stateWhenDescheduled
});
}
var runningSlice = new tracing.trace_model.ThreadTimeSlice(
thread, '', 'Running', runningId,
nextSlice.start, {}, nextSlice.duration);
runningSlice.cpuOnWhichThreadWasRunning = prevSlice.cpu;
slices.push(runningSlice);
}
thread.timeSlices = slices;
}, this);
},
/**
* Walks the slices stored on this.cpuStates_ and adjusts their timestamps
* based on any alignment metadata we discovered.
*/
alignClocks: function(isSecondaryImport) {
if (this.clockSyncRecords_.length == 0) {
// If this is a secondary import, and no clock syncing records were
// found, then abort the import. Otherwise, just skip clock alignment.
if (!isSecondaryImport)
return true;
// Remove the newly imported CPU slices from the model.
this.abortImport();
return false;
}
// Shift all the slice times based on the sync record.
var sync = this.clockSyncRecords_[0];
// NB: parentTS of zero denotes no times-shift; this is
// used when user and kernel event clocks are identical.
if (sync.parentTS == 0 || sync.parentTS == sync.perfTS)
return true;
var timeShift = sync.parentTS - sync.perfTS;
for (var cpuNumber in this.cpuStates_) {
var cpuState = this.cpuStates_[cpuNumber];
var cpu = cpuState.cpu;
for (var i = 0; i < cpu.slices.length; i++) {
var slice = cpu.slices[i];
slice.start = slice.start + timeShift;
slice.duration = slice.duration;
}
for (var counterName in cpu.counters) {
var counter = cpu.counters[counterName];
for (var sI = 0; sI < counter.timestamps.length; sI++)
counter.timestamps[sI] = (counter.timestamps[sI] + timeShift);
}
}
for (var kernelThreadName in this.kernelThreadStates_) {
var kthread = this.kernelThreadStates_[kernelThreadName];
var thread = kthread.thread;
thread.shiftTimestampsForward(timeShift);
}
return true;
},
/**
* Removes any data that has been added to the model because of an error
* detected during the import.
*/
abortImport: function() {
if (this.pushedEventsToThreads)
throw new Error('Cannot abort, have alrady pushedCpuDataToThreads.');
for (var cpuNumber in this.cpuStates_)
delete this.model_.kernel.cpus[cpuNumber];
for (var kernelThreadName in this.kernelThreadStates_) {
var kthread = this.kernelThreadStates_[kernelThreadName];
var thread = kthread.thread;
var process = thread.parent;
delete process.threads[thread.tid];
delete this.model_.processes[process.pid];
}
this.model_.importWarning({
type: 'clock_sync',
message: 'Cannot import kernel trace without a clock sync.'
});
},
/**
* Creates an instance of each registered linux perf event parser.
* This allows the parsers to register handlers for the events they
* understand. We also register our own special handlers (for the
* timestamp synchronization markers).
*/
createParsers: function() {
// Instantiate the parsers; this will register handlers for known events
var parserConstructors =
tracing.importer.linux_perf.Parser.getSubtypeConstructors();
for (var i = 0; i < parserConstructors.length; ++i) {
var parserConstructor = parserConstructors[i];
this.parsers_.push(new parserConstructor(this));
}
this.registerEventHandler('tracing_mark_write:trace_event_clock_sync',
LinuxPerfImporter.prototype.traceClockSyncEvent.bind(this));
this.registerEventHandler('tracing_mark_write',
LinuxPerfImporter.prototype.traceMarkingWriteEvent.bind(this));
// NB: old-style trace markers; deprecated
this.registerEventHandler('0:trace_event_clock_sync',
LinuxPerfImporter.prototype.traceClockSyncEvent.bind(this));
this.registerEventHandler('0',
LinuxPerfImporter.prototype.traceMarkingWriteEvent.bind(this));
},
/**
* Registers a linux perf event parser used by importCpuData.
*/
registerEventHandler: function(eventName, handler) {
// TODO(sleffler) how to handle conflicts?
this.eventHandlers_[eventName] = handler;
},
/**
* Records the fact that a pid has become runnable. This data will
* eventually get used to derive each thread's timeSlices array.
*/
markPidRunnable: function(ts, pid, comm, prio, fromPid) {
// The the pids that get passed in to this function are Linux kernel
// pids, which identify threads. The rest of trace-viewer refers to
// these as tids, so the change of nomenclature happens in the following
// construction of the wakeup object.
this.wakeups_.push({ts: ts, tid: pid, fromTid: fromPid});
},
/**
* Processes a trace_event_clock_sync event.
*/
traceClockSyncEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
var event = /parent_ts=(\d+\.?\d*)/.exec(eventBase.details);
if (!event)
return false;
this.clockSyncRecords_.push({
perfTS: ts,
parentTS: event[1] * 1000
});
return true;
},
/**
* Processes a trace_marking_write event.
*/
traceMarkingWriteEvent: function(eventName, cpuNumber, pid, ts, eventBase,
threadName) {
var event = /^\s*(\w+):\s*(.*)$/.exec(eventBase.details);
if (!event) {
// Check if the event matches events traced by the Android framework
var tag = eventBase.details.substring(0, 2);
if (tag == 'B|' || tag == 'E' || tag == 'E|' || tag == 'X|' ||
tag == 'C|' || tag == 'S|' || tag == 'F|') {
eventBase.subEventName = 'android';
} else {
return false;
}
} else {
eventBase.subEventName = event[1];
eventBase.details = event[2];
}
var writeEventName = eventName + ':' + eventBase.subEventName;
var handler = this.eventHandlers_[writeEventName];
if (!handler) {
this.model_.importWarning({
type: 'parse_error',
message: 'Unknown trace_marking_write event ' + writeEventName
});
return true;
}
return handler(writeEventName, cpuNumber, pid, ts, eventBase, threadName);
},
/**
* Walks the this.events_ structure and creates Cpu objects.
*/
importCpuData: function() {
var extractResult = LinuxPerfImporter._extractEventsFromSystraceHTML(
this.events_, true);
if (extractResult.ok) {
this.lineNumberBase = extractResult.events_begin_at_line;
this.lines_ = extractResult.lines;
} else {
this.lineNumberBase = 0;
this.lines_ = this.events_.split('\n');
}
var lineParser = null;
for (this.lineNumber = 0;
this.lineNumber < this.lines_.length;
++this.lineNumber) {
var line = this.lines_[this.lineNumber];
if (line.length == 0 || /^#/.test(line))
continue;
if (lineParser == null) {
lineParser = autoDetectLineParser(line);
if (lineParser == null) {
this.model_.importWarning({
type: 'parse_error',
message: 'Cannot parse line: ' + line
});
continue;
}
}
var eventBase = lineParser(line);
if (!eventBase) {
this.model_.importWarning({
type: 'parse_error',
message: 'Unrecognized line: ' + line
});
continue;
}
var pid = parseInt(eventBase.pid);
var cpuNumber = parseInt(eventBase.cpuNumber);
var ts = parseFloat(eventBase.timestamp) * 1000;
var eventName = eventBase.eventName;
var handler = this.eventHandlers_[eventName];
if (!handler) {
this.model_.importWarning({
type: 'parse_error',
message: 'Unknown event ' + eventName + ' (' + line + ')'
});
continue;
}
if (!handler(eventName, cpuNumber, pid, ts, eventBase)) {
this.model_.importWarning({
type: 'parse_error',
message: 'Malformed ' + eventName + ' event (' + line + ')'
});
}
}
}
};
tracing.TraceModel.registerImporter(LinuxPerfImporter);
return {
LinuxPerfImporter: LinuxPerfImporter,
_LinuxPerfImporterTestExports: TestExports
};
});
// 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.
'use strict';
base.require('base.gl_matrix');
base.exportTo('base', function() {
var tmpVec2s = [];
for (var i = 0; i < 8; i++)
tmpVec2s[i] = vec2.create();
var tmpVec2a = vec4.create();
var tmpVec4a = vec4.create();
var tmpVec4b = vec4.create();
var tmpMat4 = mat4.create();
var tmpMat4b = mat4.create();
var p00 = vec2.createXY(0, 0);
var p10 = vec2.createXY(1, 0);
var p01 = vec2.createXY(0, 1);
var p11 = vec2.createXY(1, 1);
var lerpingVecA = vec2.create();
var lerpingVecB = vec2.create();
function lerpVec2(out, a, b, amt) {
vec2.scale(lerpingVecA, a, amt);
vec2.scale(lerpingVecB, b, 1 - amt);
vec2.add(out, lerpingVecA, lerpingVecB);
vec2.normalize(out, out);
return out;
}
/**
* @constructor
*/
function Quad() {
this.p1 = vec2.create();
this.p2 = vec2.create();
this.p3 = vec2.create();
this.p4 = vec2.create();
}
Quad.fromXYWH = function(x, y, w, h) {
var q = new Quad();
vec2.set(q.p1, x, y);
vec2.set(q.p2, x + w, y);
vec2.set(q.p3, x + w, y + h);
vec2.set(q.p4, x, y + h);
return q;
}
Quad.fromRect = function(r) {
return new Quad.fromXYWH(
r.x, r.y,
r.width, r.height);
}
Quad.from4Vecs = function(p1, p2, p3, p4) {
var q = new Quad();
vec2.set(q.p1, p1[0], p1[1]);
vec2.set(q.p2, p2[0], p2[1]);
vec2.set(q.p3, p3[0], p3[1]);
vec2.set(q.p4, p4[0], p4[1]);
return q;
}
Quad.from8Array = function(arr) {
if (arr.length != 8)
throw new Error('Array must be 8 long');
var q = new Quad();
q.p1[0] = arr[0];
q.p1[1] = arr[1];
q.p2[0] = arr[2];
q.p2[1] = arr[3];
q.p3[0] = arr[4];
q.p3[1] = arr[5];
q.p4[0] = arr[6];
q.p4[1] = arr[7];
return q;
};
Quad.prototype = {
pointInside: function(point) {
return pointInImplicitQuad(point,
this.p1, this.p2, this.p3, this.p4);
},
boundingRect: function() {
var x0 = Math.min(this.p1[0], this.p2[0], this.p3[0], this.p4[0]);
var y0 = Math.min(this.p1[1], this.p2[1], this.p3[1], this.p4[1]);
var x1 = Math.max(this.p1[0], this.p2[0], this.p3[0], this.p4[0]);
var y1 = Math.max(this.p1[1], this.p2[1], this.p3[1], this.p4[1]);
return new base.Rect.fromXYWH(x0, y0, x1 - x0, y1 - y0);
},
clone: function() {
var q = new Quad();
vec2.copy(q.p1, this.p1);
vec2.copy(q.p2, this.p2);
vec2.copy(q.p3, this.p3);
vec2.copy(q.p4, this.p4);
return q;
},
scale: function(s) {
var q = new Quad();
this.scaleFast(q, s);
return q;
},
scaleFast: function(dstQuad, s) {
vec2.copy(dstQuad.p1, this.p1, s);
vec2.copy(dstQuad.p2, this.p2, s);
vec2.copy(dstQuad.p3, this.p3, s);
vec2.copy(dstQuad.p3, this.p3, s);
},
isRectangle: function() {
// Simple rectangle check. Note: will not handle out-of-order components.
var bounds = this.boundingRect();
return (
bounds.x == this.p1[0] &&
bounds.y == this.p1[1] &&
bounds.width == this.p2[0] - this.p1[0] &&
bounds.y == this.p2[1] &&
bounds.width == this.p3[0] - this.p1[0] &&
bounds.height == this.p3[1] - this.p2[1] &&
bounds.x == this.p4[0] &&
bounds.height == this.p4[1] - this.p2[1]
);
},
projectUnitRect: function(rect) {
var q = new Quad();
this.projectUnitRectFast(q, rect);
return q;
},
projectUnitRectFast: function(dstQuad, rect) {
var v12 = tmpVec2s[0];
var v14 = tmpVec2s[1];
var v23 = tmpVec2s[2];
var v43 = tmpVec2s[3];
var l12, l14, l23, l43;
vec2.sub(v12, this.p2, this.p1);
l12 = vec2.length(v12);
vec2.scale(v12, v12, 1 / l12);
vec2.sub(v14, this.p4, this.p1);
l14 = vec2.length(v14);
vec2.scale(v14, v14, 1 / l14);
vec2.sub(v23, this.p3, this.p2);
l23 = vec2.length(v23);
vec2.scale(v23, v23, 1 / l23);
vec2.sub(v43, this.p3, this.p4);
l43 = vec2.length(v43);
vec2.scale(v43, v43, 1 / l43);
var b12 = tmpVec2s[0];
var b14 = tmpVec2s[1];
var b23 = tmpVec2s[2];
var b43 = tmpVec2s[3];
lerpVec2(b12, v12, v43, rect.y);
lerpVec2(b43, v12, v43, 1 - rect.bottom);
lerpVec2(b14, v14, v23, rect.x);
lerpVec2(b23, v14, v23, 1 - rect.right);
vec2.addTwoScaledUnitVectors(tmpVec2a,
b12, l12 * rect.x,
b14, l14 * rect.y);
vec2.add(dstQuad.p1, this.p1, tmpVec2a);
vec2.addTwoScaledUnitVectors(tmpVec2a,
b12, l12 * -(1.0 - rect.right),
b23, l23 * rect.y);
vec2.add(dstQuad.p2, this.p2, tmpVec2a);
vec2.addTwoScaledUnitVectors(tmpVec2a,
b43, l43 * -(1.0 - rect.right),
b23, l23 * -(1.0 - rect.bottom));
vec2.add(dstQuad.p3, this.p3, tmpVec2a);
vec2.addTwoScaledUnitVectors(tmpVec2a,
b43, l43 * rect.left,
b14, l14 * -(1.0 - rect.bottom));
vec2.add(dstQuad.p4, this.p4, tmpVec2a);
},
toString: function() {
return 'Quad(' +
vec2.toString(this.p1) + ', ' +
vec2.toString(this.p2) + ', ' +
vec2.toString(this.p3) + ', ' +
vec2.toString(this.p4) + ')';
}
};
function sign(p1, p2, p3) {
return (p1[0] - p3[0]) * (p2[1] - p3[1]) -
(p2[0] - p3[0]) * (p1[1] - p3[1]);
}
function pointInTriangle2(pt, p1, p2, p3) {
var b1 = sign(pt, p1, p2) < 0.0;
var b2 = sign(pt, p2, p3) < 0.0;
var b3 = sign(pt, p3, p1) < 0.0;
return ((b1 == b2) && (b2 == b3));
}
function pointInImplicitQuad(point, p1, p2, p3, p4) {
return pointInTriangle2(point, p1, p2, p3) ||
pointInTriangle2(point, p1, p3, p4);
}
return {
pointInTriangle2: pointInTriangle2,
pointInImplicitQuad: pointInImplicitQuad,
Quad: Quad
};
});
// 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.
'use strict';
base.require('tracing.trace_model.timed_event');
/**
* @fileoverview Provides the Flow class.
*/
base.exportTo('tracing.trace_model', function() {
/**
* A Flow represents an interval of time plus parameters associated
* with that interval.
*
* @constructor
*/
function FlowEvent(category, id, title, colorId, start, args) {
tracing.trace_model.TimedEvent.call(this, start);
this.category = category || '';
this.title = title;
this.colorId = colorId;
this.start = start;
this.args = args;
this.id = id;
}
FlowEvent.prototype = {
__proto__: tracing.trace_model.TimedEvent.prototype
};
return {
FlowEvent: FlowEvent
};
});
// 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.
'use strict';
base.require('tracing.trace_model.timed_event');
/**
* @fileoverview Provides the InstantEvent class.
*/
base.exportTo('tracing.trace_model', function() {
var InstantEventType = {
GLOBAL: 1,
PROCESS: 2,
THREAD: 3
};
function InstantEvent(category, title, colorId, start, args) {
tracing.trace_model.TimedEvent.call(this);
this.category = category || '';
this.title = title;
this.colorId = colorId;
this.start = start;
this.args = args;
this.type = undefined;
};
InstantEvent.prototype = {
__proto__: tracing.trace_model.TimedEvent.prototype,
selected: false
};
function GlobalInstantEvent(category, title, colorId, start, args) {
InstantEvent.apply(this, arguments);
this.type = InstantEventType.GLOBAL;
};
GlobalInstantEvent.prototype = {
__proto__: InstantEvent.prototype
};
function ProcessInstantEvent(category, title, colorId, start, args) {
InstantEvent.apply(this, arguments);
this.type = InstantEventType.PROCESS;
};
ProcessInstantEvent.prototype = {
__proto__: InstantEvent.prototype
};
function ThreadInstantEvent(category, title, colorId, start, args) {
InstantEvent.apply(this, arguments);
this.type = InstantEventType.THREAD;
};
ThreadInstantEvent.prototype = {
__proto__: InstantEvent.prototype
};
return {
GlobalInstantEvent: GlobalInstantEvent,
ProcessInstantEvent: ProcessInstantEvent,
ThreadInstantEvent: ThreadInstantEvent,
InstantEventType: InstantEventType,
InstantEvent: InstantEvent
};
});
// 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.
'use strict';
/**
* @fileoverview TraceEventImporter imports TraceEvent-formatted data
* into the provided model.
*/
base.require('base.quad');
base.require('tracing.trace_model');
base.require('tracing.color_scheme');
base.require('tracing.importer.importer');
base.require('tracing.trace_model.instant_event');
base.require('tracing.trace_model.flow_event');
base.require('tracing.trace_model.counter_series');
base.exportTo('tracing.importer', function() {
var Importer = tracing.importer.Importer;
function deepCopy(value) {
if (!(value instanceof Object)) {
if (value === undefined || value === null)
return value;
if (typeof value == 'string')
return value.substring();
if (typeof value == 'boolean')
return value;
if (typeof value == 'number')
return value;
throw new Error('Unrecognized: ' + typeof value);
}
var object = value;
if (object instanceof Array) {
var res = new Array(object.length);
for (var i = 0; i < object.length; i++)
res[i] = deepCopy(object[i]);
return res;
}
if (object.__proto__ != Object.prototype)
throw new Error('Can only clone simple types');
var res = {};
for (var key in object) {
res[key] = deepCopy(object[key]);
}
return res;
}
function TraceEventImporter(model, eventData) {
this.importPriority = 1;
this.model_ = model;
this.events_ = undefined;
this.systemTraceEvents_ = undefined;
this.eventsWereFromString_ = false;
this.allAsyncEvents_ = [];
this.allFlowEvents_ = [];
this.allObjectEvents_ = [];
if (typeof(eventData) === 'string' || eventData instanceof String) {
// If the event data begins with a [, then we know it should end with a ].
// The reason we check for this is because some tracing implementations
// cannot guarantee that a ']' gets written to the trace file. So, we are
// forgiving and if this is obviously the case, we fix it up before
// throwing the string at JSON.parse.
if (eventData[0] === '[') {
eventData = eventData.replace(/[\r|\n]*$/, '')
.replace(/\s*,\s*$/, '');
if (eventData[eventData.length - 1] !== ']')
eventData = eventData + ']';
}
this.events_ = JSON.parse(eventData);
this.eventsWereFromString_ = true;
} else {
this.events_ = eventData;
}
// Some trace_event implementations put the actual trace events
// inside a container. E.g { ... , traceEvents: [ ] }
// If we see that, just pull out the trace events.
if (this.events_.traceEvents) {
var container = this.events_;
this.events_ = this.events_.traceEvents;
// Some trace_event implementations put linux_perf_importer traces as a
// huge string inside container.systemTraceEvents. If we see that, pull it
// out. It will be picked up by extractSubtraces later on.
this.systemTraceEvents_ = container.systemTraceEvents;
// Any other fields in the container should be treated as metadata.
for (var fieldName in container) {
if (fieldName === 'traceEvents' || fieldName === 'systemTraceEvents')
continue;
this.model_.metadata.push({name: fieldName,
value: container[fieldName]});
}
}
}
/**
* @return {boolean} Whether obj is a TraceEvent array.
*/
TraceEventImporter.canImport = function(eventData) {
// May be encoded JSON. But we dont want to parse it fully yet.
// Use a simple heuristic:
// - eventData that starts with [ are probably trace_event
// - eventData that starts with { are probably trace_event
// May be encoded JSON. Treat files that start with { as importable by us.
if (typeof(eventData) === 'string' || eventData instanceof String) {
return eventData[0] == '{' || eventData[0] == '[';
}
// Might just be an array of events
if (eventData instanceof Array && eventData.length && eventData[0].ph)
return true;
// Might be an object with a traceEvents field in it.
if (eventData.traceEvents)
return eventData.traceEvents instanceof Array &&
eventData.traceEvents[0].ph;
return false;
};
TraceEventImporter.prototype = {
__proto__: Importer.prototype,
extractSubtraces: function() {
var tmp = this.systemTraceEvents_;
this.systemTraceEvents_ = undefined;
return tmp ? [tmp] : [];
},
/**
* Deep copying is only needed if the trace was given to us as events.
*/
deepCopyIfNeeded_: function(obj) {
if (this.eventsWereFromString_)
return obj;
return deepCopy(obj);
},
/**
* Helper to process an async event.
*/
processAsyncEvent: function(event) {
var thread = this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
this.allAsyncEvents_.push({
event: event,
thread: thread});
},
/**
* Helper to process a flow event.
*/
processFlowEvent: function(event) {
var thread = this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
this.allFlowEvents_.push({
event: event,
thread: thread
});
},
/**
* Helper that creates and adds samples to a Counter object based on
* 'C' phase events.
*/
processCounterEvent: function(event) {
var ctr_name;
if (event.id !== undefined)
ctr_name = event.name + '[' + event.id + ']';
else
ctr_name = event.name;
var ctr = this.model_.getOrCreateProcess(event.pid)
.getOrCreateCounter(event.cat, ctr_name);
// Initialize the counter's series fields if needed.
if (ctr.numSeries === 0) {
for (var seriesName in event.args) {
ctr.addSeries(new tracing.trace_model.CounterSeries(seriesName,
tracing.getStringColorId(ctr.name + '.' + seriesName)));
}
if (ctr.numSeries === 0) {
this.model_.importWarning({
type: 'counter_parse_error',
message: 'Expected counter ' + event.name +
' to have at least one argument to use as a value.'
});
// Drop the counter.
delete ctr.parent.counters[ctr.name];
return;
}
}
var ts = event.ts / 1000;
ctr.series.forEach(function(series) {
var val = event.args[series.name] ? event.args[series.name] : 0;
series.addSample(ts, val);
});
},
processObjectEvent: function(event) {
var thread = this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
this.allObjectEvents_.push({
event: event,
thread: thread});
},
processDurationEvent: function(event) {
var thread = this.model_.getOrCreateProcess(event.pid)
.getOrCreateThread(event.tid);
if (!thread.sliceGroup.isTimestampValidForBeginOrEnd(event.ts / 1000)) {
this.model_.importWarning({
type: 'duration_parse_error',
message: 'Timestamps are moving backward.'
});
return;
}
if (event.ph == 'B') {
thread.sliceGroup.beginSlice(event.cat, event.name, event.ts / 1000,
this.deepCopyIfNeeded_(event.args),
event.tts / 1000);
} else {
if (!thread.sliceGroup.openSliceCount) {
this.model_.importWarning({
type: 'duration_parse_error',
message: 'E phase event without a matching B phase event.'
});
return;
}
var slice = thread.sliceGroup.endSlice(event.ts / 1000,
event.tts / 1000);
for (var arg in event.args) {
if (slice.args[arg] !== undefined) {
this.model_.importWarning({
type: 'duration_parse_error',
message: 'Both the B and E phases of ' + slice.name +
' provided values for argument ' + arg + '.' +
' The value of the E phase event will be used.'
});
}
slice.args[arg] = this.deepCopyIfNeeded_(event.args[arg]);
}
}
},
processCompleteEvent: function(event) {
var thread = this.model_.getOrCreateProcess(event.pid)
.getOrCreateThread(event.tid);
thread.sliceGroup.pushCompleteSlice(event.cat, event.name,
event.ts / 1000, event.dur / 1000,
this.deepCopyIfNeeded_(event.args));
},
processMetadataEvent: function(event) {
if (event.name == 'process_name') {
var process = this.model_.getOrCreateProcess(event.pid);
process.name = event.args.name;
} else if (event.name == 'process_labels') {
var process = this.model_.getOrCreateProcess(event.pid);
process.labels.push.apply(
process.labels, event.args.labels.split(','));
} else if (event.name == 'process_sort_index') {
var process = this.model_.getOrCreateProcess(event.pid);
process.sortIndex = event.args.sort_index;
} else if (event.name == 'thread_name') {
var thread = this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
thread.name = event.args.name;
} else if (event.name == 'thread_sort_index') {
var thread = this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
thread.sortIndex = event.args.sort_index;
} else {
this.model_.importWarning({
type: 'metadata_parse_error',
message: 'Unrecognized metadata name: ' + event.name
});
}
},
// Treat an Instant event as a duration 0 slice.
// SliceTrack's redraw() knows how to handle this.
processInstantEvent: function(event) {
var constructor;
switch (event.s) {
case 'g':
constructor = tracing.trace_model.GlobalInstantEvent;
break;
case 'p':
constructor = tracing.trace_model.ProcessInstantEvent;
break;
case 't':
// fall through
default:
// Default to thread to support old style input files.
constructor = tracing.trace_model.ThreadInstantEvent;
break;
}
var colorId = tracing.getStringColorId(event.name);
var instantEvent = new constructor(event.cat, event.name,
colorId, event.ts / 1000, this.deepCopyIfNeeded_(event.args));
switch (instantEvent.type) {
case tracing.trace_model.InstantEventType.GLOBAL:
this.model_.pushInstantEvent(instantEvent);
break;
case tracing.trace_model.InstantEventType.PROCESS:
var process = this.model_.getOrCreateProcess(event.pid);
process.pushInstantEvent(instantEvent);
break;
case tracing.trace_model.InstantEventType.THREAD:
var thread = this.model_.getOrCreateProcess(event.pid)
.getOrCreateThread(event.tid);
thread.sliceGroup.pushInstantEvent(instantEvent);
break;
default:
throw new Error('Unknown instant event type: ' + event.s);
}
},
processSampleEvent: function(event) {
var thread = this.model_.getOrCreateProcess(event.pid)
.getOrCreateThread(event.tid);
thread.addSample(event.cat, event.name, event.ts / 1000,
this.deepCopyIfNeeded_(event.args));
},
/**
* Walks through the events_ list and outputs the structures discovered to
* model_.
*/
importEvents: function() {
var events = this.events_;
for (var eI = 0; eI < events.length; eI++) {
var event = events[eI];
if (event.ph === 'B' || event.ph === 'E') {
this.processDurationEvent(event);
} else if (event.ph === 'X') {
this.processCompleteEvent(event);
} else if (event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
event.ph === 'p') {
this.processAsyncEvent(event);
// Note, I is historic. The instant event marker got changed, but we
// want to support loading old trace files so we have both I and i.
} else if (event.ph == 'I' || event.ph == 'i') {
this.processInstantEvent(event);
} else if (event.ph == 'P') {
this.processSampleEvent(event);
} else if (event.ph == 'C') {
this.processCounterEvent(event);
} else if (event.ph == 'M') {
this.processMetadataEvent(event);
} else if (event.ph === 'N' || event.ph === 'D' || event.ph === 'O') {
this.processObjectEvent(event);
} else if (event.ph === 's' || event.ph === 't' || event.ph === 'f') {
this.processFlowEvent(event);
} else {
this.model_.importWarning({
type: 'parse_error',
message: 'Unrecognized event phase: ' +
event.ph + ' (' + event.name + ')'
});
}
}
},
/**
* Called by the Model after all other importers have imported their
* events.
*/
finalizeImport: function() {
this.createSubSlices_();
this.createAsyncSlices_();
this.createFlowSlices_();
this.createExplicitObjects_();
this.createImplicitObjects_();
},
/**
* Called by the model to join references between objects, after final model
* bounds have been computed.
*/
joinRefs: function() {
this.joinObjectRefs_();
},
createSubSlices_: function() {
base.iterItems(this.model_.processes, function(pid, process) {
base.iterItems(process.threads, function(tid, thread) {
thread.createSubSlices();
}, this);
}, this);
},
createAsyncSlices_: function() {
if (this.allAsyncEvents_.length === 0)
return;
this.allAsyncEvents_.sort(function(x, y) {
return x.event.ts - y.event.ts;
});
var asyncEventStatesByNameThenID = {};
var allAsyncEvents = this.allAsyncEvents_;
for (var i = 0; i < allAsyncEvents.length; i++) {
var asyncEventState = allAsyncEvents[i];
var event = asyncEventState.event;
var name = event.name;
if (name === undefined) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'Async events (ph: S, T, p, or F) require a name ' +
' parameter.'
});
continue;
}
var id = event.id;
if (id === undefined) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'Async events (ph: S, T, p, or F) require an id parameter.'
});
continue;
}
// TODO(simonjam): Add a synchronous tick on the appropriate thread.
if (event.ph === 'S') {
if (asyncEventStatesByNameThenID[name] === undefined)
asyncEventStatesByNameThenID[name] = {};
if (asyncEventStatesByNameThenID[name][id]) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'At ' + event.ts + ', a slice of the same id ' + id +
' was alrady open.'
});
continue;
}
asyncEventStatesByNameThenID[name][id] = [];
asyncEventStatesByNameThenID[name][id].push(asyncEventState);
} else {
if (asyncEventStatesByNameThenID[name] === undefined) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'At ' + event.ts + ', no slice named ' + name +
' was open.'
});
continue;
}
if (asyncEventStatesByNameThenID[name][id] === undefined) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'At ' + event.ts + ', no slice named ' + name +
' with id=' + id + ' was open.'
});
continue;
}
var events = asyncEventStatesByNameThenID[name][id];
events.push(asyncEventState);
if (event.ph === 'F') {
// Create a slice from start to end.
var slice = new tracing.trace_model.AsyncSlice(
events[0].event.cat,
name,
tracing.getStringColorId(name),
events[0].event.ts / 1000);
slice.duration = (event.ts / 1000) - (events[0].event.ts / 1000);
slice.startThread = events[0].thread;
slice.endThread = asyncEventState.thread;
slice.id = id;
slice.args = this.deepCopyIfNeeded_(events[0].event.args);
slice.subSlices = [];
var stepType = events[1].event.ph;
var isValid = true;
// Create subSlices for each step.
for (var j = 1; j < events.length; ++j) {
var subName = name;
if (events[j].event.ph == 'T' || events[j].event.ph == 'p') {
isValid = this.assertStepTypeMatches_(stepType, events[j]);
if (!isValid)
break;
}
var targetEvent;
if (stepType == 'T') {
targetEvent = events[j - 1];
} else {
targetEvent = events[j];
}
var subName = events[0].event.name;
if (targetEvent.event.ph == 'T' || targetEvent.event.ph == 'p')
subName = subName + ':' + targetEvent.event.args.step;
var subSlice = new tracing.trace_model.AsyncSlice(
events[0].event.cat,
subName,
tracing.getStringColorId(subName + j),
events[j - 1].event.ts / 1000);
subSlice.duration =
(events[j].event.ts / 1000) - (events[j - 1].event.ts / 1000);
subSlice.startThread = events[j - 1].thread;
subSlice.endThread = events[j].thread;
subSlice.id = id;
subSlice.args = base.concatenateObjects(events[0].event.args,
targetEvent.event.args);
slice.subSlices.push(subSlice);
if (events[j].event.ph == 'F' && stepType == 'T') {
// The args for the finish event go in the last subSlice.
var lastSlice = slice.subSlices[slice.subSlices.length - 1];
lastSlice.args = base.concatenateObjects(lastSlice.args,
event.args);
}
}
if (isValid) {
// Add |slice| to the start-thread's asyncSlices.
slice.startThread.asyncSliceGroup.push(slice);
}
delete asyncEventStatesByNameThenID[name][id];
}
}
}
},
assertStepTypeMatches_: function(stepType, event) {
if (stepType != event.event.ph) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'At ' + event.event.ts + ', a slice named ' +
event.event.name + ' with id=' + event.event.id +
' had both begin and end steps, which is not allowed.'
});
return false;
}
return true;
},
createFlowSlices_: function() {
if (this.allFlowEvents_.length === 0)
return;
this.allFlowEvents_.sort(function(x, y) {
return x.event.ts - y.event.ts;
});
var flowIdToEvent = {};
for (var i = 0; i < this.allFlowEvents_.length; ++i) {
var data = this.allFlowEvents_[i];
var event = data.event;
var thread = data.thread;
if (event.name === undefined) {
this.model_.importWarning({
type: 'flow_slice_parse_error',
message: 'Flow events (ph: s, t or f) require a name parameter.'
});
continue;
}
if (event.id === undefined) {
this.model_.importWarning({
type: 'flow_slice_parse_error',
message: 'Flow events (ph: s, t or f) require an id parameter.'
});
continue;
}
var slice = new tracing.trace_model.FlowEvent(
event.cat,
event.id,
event.name,
tracing.getStringColorId(event.name),
event.ts / 1000,
this.deepCopyIfNeeded_(event.args));
thread.sliceGroup.pushSlice(slice);
if (event.ph === 's') {
if (flowIdToEvent[event.id] !== undefined) {
this.model_.importWarning({
type: 'flow_slice_start_error',
message: 'event id ' + event.id + ' already seen when ' +
'encountering start of flow event.'});
}
flowIdToEvent[event.id] = slice;
} else if (event.ph === 't' || event.ph === 'f') {
var flowPosition = flowIdToEvent[event.id];
if (flowPosition === undefined) {
this.model_.importWarning({
type: 'flow_slice_ordering_error',
message: 'Found flow phase ' + event.ph + ' for id: ' + event.id +
' but no flow start found.'
});
continue;
}
this.model_.flowEvents.push([flowPosition, slice]);
if (event.ph === 'f') {
flowIdToEvent[event.id] = undefined;
} else {
// Make this slice the next start event in this flow.
flowIdToEvent[event.id] = slice;
}
}
}
},
/**
* This function creates objects described via the N, D, and O phase
* events.
*/
createExplicitObjects_: function() {
if (this.allObjectEvents_.length == 0)
return;
function processEvent(objectEventState) {
var event = objectEventState.event;
var thread = objectEventState.thread;
if (event.name === undefined) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing ' + JSON.stringify(event) + ': ' +
'Object events require an name parameter.'
});
}
if (event.id === undefined) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing ' + JSON.stringify(event) + ': ' +
'Object events require an id parameter.'
});
}
var process = thread.parent;
var ts = event.ts / 1000;
var instance;
if (event.ph == 'N') {
try {
instance = process.objects.idWasCreated(
event.id, event.cat, event.name, ts);
} catch (e) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing create of ' +
event.id + ' at ts=' + ts + ': ' + e
});
return;
}
} else if (event.ph == 'O') {
if (event.args.snapshot === undefined) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing ' + event.id + ' at ts=' + ts + ': ' +
'Snapshots must have args: {snapshot: ...}'
});
return;
}
var snapshot;
try {
snapshot = process.objects.addSnapshot(
event.id, event.cat, event.name, ts,
this.deepCopyIfNeeded_(event.args.snapshot));
} catch (e) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing snapshot of ' +
event.id + ' at ts=' + ts + ': ' + e
});
return;
}
instance = snapshot.objectInstance;
} else if (event.ph == 'D') {
try {
instance = process.objects.idWasDeleted(
event.id, event.cat, event.name, ts);
} catch (e) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing delete of ' +
event.id + ' at ts=' + ts + ': ' + e
});
return;
}
}
if (instance)
instance.colorId = tracing.getStringColorId(instance.typeName);
}
this.allObjectEvents_.sort(function(x, y) {
return x.event.ts - y.event.ts;
});
var allObjectEvents = this.allObjectEvents_;
for (var i = 0; i < allObjectEvents.length; i++) {
var objectEventState = allObjectEvents[i];
try {
processEvent.call(this, objectEventState);
} catch (e) {
this.model_.importWarning({
type: 'object_parse_error',
message: e.message
});
}
}
},
createImplicitObjects_: function() {
base.iterItems(this.model_.processes, function(pid, process) {
this.createImplicitObjectsForProcess_(process);
}, this);
},
// Here, we collect all the snapshots that internally contain a
// Javascript-level object inside their args list that has an "id" field,
// and turn that into a snapshot of the instance referred to by id.
createImplicitObjectsForProcess_: function(process) {
function processField(referencingObject,
referencingObjectFieldName,
referencingObjectFieldValue,
containingSnapshot) {
if (!referencingObjectFieldValue)
return;
if (referencingObjectFieldValue instanceof
tracing.trace_model.ObjectSnapshot)
return null;
if (referencingObjectFieldValue.id === undefined)
return;
var implicitSnapshot = referencingObjectFieldValue;
var rawId = implicitSnapshot.id;
var m = /(.+)\/(.+)/.exec(rawId);
if (!m)
throw new Error('Implicit snapshots must have names.');
delete implicitSnapshot.id;
var name = m[1];
var id = m[2];
var res;
var cat;
if (implicitSnapshot.cat !== undefined)
cat = implicitSnapshot.cat;
else
cat = containingSnapshot.objectInstance.category;
try {
res = process.objects.addSnapshot(
id, cat,
name, containingSnapshot.ts,
implicitSnapshot);
} catch (e) {
this.model_.importWarning({
type: 'object_snapshot_parse_error',
message: 'While processing implicit snapshot of ' +
rawId + ' at ts=' + containingSnapshot.ts + ': ' + e
});
return;
}
res.objectInstance.hasImplicitSnapshots = true;
res.containingSnapshot = containingSnapshot;
referencingObject[referencingObjectFieldName] = res;
if (!(res instanceof tracing.trace_model.ObjectSnapshot))
throw new Error('Created object must be instanceof snapshot');
return res.args;
}
/**
* Iterates over the fields in the object, calling func for every
* field/value found.
*
* @return {object} If the function does not want the field's value to be
* iterated, return null. If iteration of the field value is desired, then
* return either undefined (if the field value did not change) or the new
* field value if it was changed.
*/
function iterObject(object, func, containingSnapshot, thisArg) {
if (!(object instanceof Object))
return;
if (object instanceof Array) {
for (var i = 0; i < object.length; i++) {
var res = func.call(thisArg, object, i, object[i],
containingSnapshot);
if (res === null)
continue;
if (res)
iterObject(res, func, containingSnapshot, thisArg);
else
iterObject(object[i], func, containingSnapshot, thisArg);
}
return;
}
for (var key in object) {
var res = func.call(thisArg, object, key, object[key],
containingSnapshot);
if (res === null)
continue;
if (res)
iterObject(res, func, containingSnapshot, thisArg);
else
iterObject(object[key], func, containingSnapshot, thisArg);
}
}
// TODO(nduca): We may need to iterate the instances in sorted order by
// creationTs.
process.objects.iterObjectInstances(function(instance) {
instance.snapshots.forEach(function(snapshot) {
if (snapshot.args.id !== undefined)
throw new Error('args cannot have an id field inside it');
iterObject(snapshot.args, processField, snapshot, this);
}, this);
}, this);
},
joinObjectRefs_: function() {
base.iterItems(this.model_.processes, function(pid, process) {
this.joinObjectRefsForProcess_(process);
}, this);
},
joinObjectRefsForProcess_: function(process) {
// Iterate the world, looking for id_refs
var patchupsToApply = [];
base.iterItems(process.threads, function(tid, thread) {
thread.asyncSliceGroup.slices.forEach(function(item) {
this.searchItemForIDRefs_(
patchupsToApply, process.objects, 'start', item);
}, this);
thread.sliceGroup.slices.forEach(function(item) {
this.searchItemForIDRefs_(
patchupsToApply, process.objects, 'start', item);
}, this);
}, this);
process.objects.iterObjectInstances(function(instance) {
instance.snapshots.forEach(function(item) {
this.searchItemForIDRefs_(
patchupsToApply, process.objects, 'ts', item);
}, this);
}, this);
// Change all the fields pointing at id_refs to their real values.
patchupsToApply.forEach(function(patchup) {
patchup.object[patchup.field] = patchup.value;
});
},
searchItemForIDRefs_: function(patchupsToApply, objectCollection,
itemTimestampField, item) {
if (!item.args)
throw new Error('');
function handleField(object, fieldName, fieldValue) {
if (fieldValue === undefined ||
(!fieldValue.id_ref && !fieldValue.idRef))
return;
var id = fieldValue.id_ref || fieldValue.idRef;
var ts = item[itemTimestampField];
var snapshot = objectCollection.getSnapshotAt(id, ts);
if (!snapshot)
return;
// We have to delay the actual change to the new value until after all
// refs have been located. Otherwise, we could end up recursing in
// ways we definitely didn't intend.
patchupsToApply.push({object: object,
field: fieldName,
value: snapshot});
}
function iterObjectFieldsRecursively(object) {
if (!(object instanceof Object))
return;
if ((object instanceof tracing.trace_model.ObjectSnapshot) ||
(object instanceof Float32Array) ||
(object instanceof base.Quad))
return;
if (object instanceof Array) {
for (var i = 0; i < object.length; i++) {
handleField(object, i, object[i]);
iterObjectFieldsRecursively(object[i]);
}
return;
}
for (var key in object) {
var value = object[key];
handleField(object, key, value);
iterObjectFieldsRecursively(value);
}
}
iterObjectFieldsRecursively(item.args);
}
};
tracing.TraceModel.registerImporter(TraceEventImporter);
return {
TraceEventImporter: TraceEventImporter
};
});
// 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.
'use strict';
/**
* @fileoverview Splay tree used by CodeMap.
*/
base.exportTo('tracing.importer.v8', function() {
/**
* Constructs a Splay tree. A splay tree is a self-balancing binary
* search tree with the additional property that recently accessed
* elements are quick to access again. It performs basic operations
* such as insertion, look-up and removal in O(log(n)) amortized time.
*
* @constructor
*/
function SplayTree() {
};
/**
* Pointer to the root node of the tree.
*
* @type {SplayTree.Node}
* @private
*/
SplayTree.prototype.root_ = null;
/**
* @return {boolean} Whether the tree is empty.
*/
SplayTree.prototype.isEmpty = function() {
return !this.root_;
};
/**
* Inserts a node into the tree with the specified key and value if
* the tree does not already contain a node with the specified key. If
* the value is inserted, it becomes the root of the tree.
*
* @param {number} key Key to insert into the tree.
* @param {*} value Value to insert into the tree.
*/
SplayTree.prototype.insert = function(key, value) {
if (this.isEmpty()) {
this.root_ = new SplayTree.Node(key, value);
return;
}
// Splay on the key to move the last node on the search path for
// the key to the root of the tree.
this.splay_(key);
if (this.root_.key == key) {
return;
}
var node = new SplayTree.Node(key, value);
if (key > this.root_.key) {
node.left = this.root_;
node.right = this.root_.right;
this.root_.right = null;
} else {
node.right = this.root_;
node.left = this.root_.left;
this.root_.left = null;
}
this.root_ = node;
};
/**
* Removes a node with the specified key from the tree if the tree
* contains a node with this key. The removed node is returned. If the
* key is not found, an exception is thrown.
*
* @param {number} key Key to find and remove from the tree.
* @return {SplayTree.Node} The removed node.
*/
SplayTree.prototype.remove = function(key) {
if (this.isEmpty()) {
throw Error('Key not found: ' + key);
}
this.splay_(key);
if (this.root_.key != key) {
throw Error('Key not found: ' + key);
}
var removed = this.root_;
if (!this.root_.left) {
this.root_ = this.root_.right;
} else {
var right = this.root_.right;
this.root_ = this.root_.left;
// Splay to make sure that the new root has an empty right child.
this.splay_(key);
// Insert the original right child as the right child of the new
// root.
this.root_.right = right;
}
return removed;
};
/**
* Returns the node having the specified key or null if the tree doesn't
* contain a node with the specified key.
*
*
* @param {number} key Key to find in the tree.
* @return {SplayTree.Node} Node having the specified key.
*/
SplayTree.prototype.find = function(key) {
if (this.isEmpty()) {
return null;
}
this.splay_(key);
return this.root_.key == key ? this.root_ : null;
};
/**
* @return {SplayTree.Node} Node having the minimum key value.
*/
SplayTree.prototype.findMin = function() {
if (this.isEmpty()) {
return null;
}
var current = this.root_;
while (current.left) {
current = current.left;
}
return current;
};
/**
* @return {SplayTree.Node} Node having the maximum key value.
*/
SplayTree.prototype.findMax = function(opt_startNode) {
if (this.isEmpty()) {
return null;
}
var current = opt_startNode || this.root_;
while (current.right) {
current = current.right;
}
return current;
};
/**
* @return {SplayTree.Node} Node having the maximum key value that
* is less or equal to the specified key value.
*/
SplayTree.prototype.findGreatestLessThan = function(key) {
if (this.isEmpty()) {
return null;
}
// Splay on the key to move the node with the given key or the last
// node on the search path to the top of the tree.
this.splay_(key);
// Now the result is either the root node or the greatest node in
// the left subtree.
if (this.root_.key <= key) {
return this.root_;
} else if (this.root_.left) {
return this.findMax(this.root_.left);
} else {
return null;
}
};
/**
* @return {Array<*>} An array containing all the values of tree's nodes
* paired with keys.
*
*/
SplayTree.prototype.exportKeysAndValues = function() {
var result = [];
this.traverse_(function(node) { result.push([node.key, node.value]); });
return result;
};
/**
* @return {Array<*>} An array containing all the values of tree's nodes.
*/
SplayTree.prototype.exportValues = function() {
var result = [];
this.traverse_(function(node) { result.push(node.value); });
return result;
};
/**
* Perform the splay operation for the given key. Moves the node with
* the given key to the top of the tree. If no node has the given
* key, the last node on the search path is moved to the top of the
* tree. This is the simplified top-down splaying algorithm from:
* "Self-adjusting Binary Search Trees" by Sleator and Tarjan
*
* @param {number} key Key to splay the tree on.
* @private
*/
SplayTree.prototype.splay_ = function(key) {
if (this.isEmpty()) {
return;
}
// Create a dummy node. The use of the dummy node is a bit
// counter-intuitive: The right child of the dummy node will hold
// the L tree of the algorithm. The left child of the dummy node
// will hold the R tree of the algorithm. Using a dummy node, left
// and right will always be nodes and we avoid special cases.
var dummy, left, right;
dummy = left = right = new SplayTree.Node(null, null);
var current = this.root_;
while (true) {
if (key < current.key) {
if (!current.left) {
break;
}
if (key < current.left.key) {
// Rotate right.
var tmp = current.left;
current.left = tmp.right;
tmp.right = current;
current = tmp;
if (!current.left) {
break;
}
}
// Link right.
right.left = current;
right = current;
current = current.left;
} else if (key > current.key) {
if (!current.right) {
break;
}
if (key > current.right.key) {
// Rotate left.
var tmp = current.right;
current.right = tmp.left;
tmp.left = current;
current = tmp;
if (!current.right) {
break;
}
}
// Link left.
left.right = current;
left = current;
current = current.right;
} else {
break;
}
}
// Assemble.
left.right = current.left;
right.left = current.right;
current.left = dummy.right;
current.right = dummy.left;
this.root_ = current;
};
/**
* Performs a preorder traversal of the tree.
*
* @param {function(SplayTree.Node)} f Visitor function.
* @private
*/
SplayTree.prototype.traverse_ = function(f) {
var nodesToVisit = [this.root_];
while (nodesToVisit.length > 0) {
var node = nodesToVisit.shift();
if (node == null) {
continue;
}
f(node);
nodesToVisit.push(node.left);
nodesToVisit.push(node.right);
}
};
/**
* Constructs a Splay tree node.
*
* @param {number} key Key.
* @param {*} value Value.
*/
SplayTree.Node = function(key, value) {
this.key = key;
this.value = value;
};
/**
* @type {SplayTree.Node}
*/
SplayTree.Node.prototype.left = null;
/**
* @type {SplayTree.Node}
*/
SplayTree.Node.prototype.right = null;
return {
SplayTree: SplayTree
};
});
// 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.
'use strict';
base.require('tracing.importer.v8.splaytree');
/**
* @fileoverview Map addresses to dynamically created functions.
*/
base.exportTo('tracing.importer.v8', function() {
/**
* Constructs a mapper that maps addresses into code entries.
*
* @constructor
*/
function CodeMap() {
/**
* Dynamic code entries. Used for JIT compiled code.
*/
this.dynamics_ = new tracing.importer.v8.SplayTree();
/**
* Name generator for entries having duplicate names.
*/
this.dynamicsNameGen_ = new tracing.importer.v8.CodeMap.NameGenerator();
/**
* Static code entries. Used for statically compiled code.
*/
this.statics_ = new tracing.importer.v8.SplayTree();
/**
* Libraries entries. Used for the whole static code libraries.
*/
this.libraries_ = new tracing.importer.v8.SplayTree();
/**
* Map of memory pages occupied with static code.
*/
this.pages_ = [];
};
/**
* The number of alignment bits in a page address.
*/
CodeMap.PAGE_ALIGNMENT = 12;
/**
* Page size in bytes.
*/
CodeMap.PAGE_SIZE =
1 << CodeMap.PAGE_ALIGNMENT;
/**
* Adds a dynamic (i.e. moveable and discardable) code entry.
*
* @param {number} start The starting address.
* @param {CodeMap.CodeEntry} codeEntry Code entry object.
*/
CodeMap.prototype.addCode = function(start, codeEntry) {
this.deleteAllCoveredNodes_(this.dynamics_, start, start + codeEntry.size);
this.dynamics_.insert(start, codeEntry);
};
/**
* Moves a dynamic code entry. Throws an exception if there is no dynamic
* code entry with the specified starting address.
*
* @param {number} from The starting address of the entry being moved.
* @param {number} to The destination address.
*/
CodeMap.prototype.moveCode = function(from, to) {
var removedNode = this.dynamics_.remove(from);
this.deleteAllCoveredNodes_(this.dynamics_, to,
to + removedNode.value.size);
this.dynamics_.insert(to, removedNode.value);
};
/**
* Discards a dynamic code entry. Throws an exception if there is no dynamic
* code entry with the specified starting address.
*
* @param {number} start The starting address of the entry being deleted.
*/
CodeMap.prototype.deleteCode = function(start) {
var removedNode = this.dynamics_.remove(start);
};
/**
* Adds a library entry.
*
* @param {number} start The starting address.
* @param {CodeMap.CodeEntry} codeEntry Code entry object.
*/
CodeMap.prototype.addLibrary = function(
start, codeEntry) {
this.markPages_(start, start + codeEntry.size);
this.libraries_.insert(start, codeEntry);
};
/**
* Adds a static code entry.
*
* @param {number} start The starting address.
* @param {CodeMap.CodeEntry} codeEntry Code entry object.
*/
CodeMap.prototype.addStaticCode = function(
start, codeEntry) {
this.statics_.insert(start, codeEntry);
};
/**
* @private
*/
CodeMap.prototype.markPages_ = function(start, end) {
for (var addr = start; addr <= end;
addr += CodeMap.PAGE_SIZE) {
this.pages_[addr >>> CodeMap.PAGE_ALIGNMENT] = 1;
}
};
/**
* @private
*/
CodeMap.prototype.deleteAllCoveredNodes_ = function(tree, start, end) {
var to_delete = [];
var addr = end - 1;
while (addr >= start) {
var node = tree.findGreatestLessThan(addr);
if (!node) break;
var start2 = node.key, end2 = start2 + node.value.size;
if (start2 < end && start < end2) to_delete.push(start2);
addr = start2 - 1;
}
for (var i = 0, l = to_delete.length; i < l; ++i) tree.remove(to_delete[i]);
};
/**
* @private
*/
CodeMap.prototype.isAddressBelongsTo_ = function(addr, node) {
return addr >= node.key && addr < (node.key + node.value.size);
};
/**
* @private
*/
CodeMap.prototype.findInTree_ = function(tree, addr) {
var node = tree.findGreatestLessThan(addr);
return node && this.isAddressBelongsTo_(addr, node) ? node.value : null;
};
/**
* Finds a code entry that contains the specified address. Both static and
* dynamic code entries are considered.
*
* @param {number} addr Address.
*/
CodeMap.prototype.findEntry = function(addr) {
var pageAddr = addr >>> CodeMap.PAGE_ALIGNMENT;
if (pageAddr in this.pages_) {
// Static code entries can contain "holes" of unnamed code.
// In this case, the whole library is assigned to this address.
return this.findInTree_(this.statics_, addr) ||
this.findInTree_(this.libraries_, addr);
}
var min = this.dynamics_.findMin();
var max = this.dynamics_.findMax();
if (max != null && addr < (max.key + max.value.size) && addr >= min.key) {
var dynaEntry = this.findInTree_(this.dynamics_, addr);
if (dynaEntry == null) return null;
// Dedupe entry name.
if (!dynaEntry.nameUpdated_) {
dynaEntry.name = this.dynamicsNameGen_.getName(dynaEntry.name);
dynaEntry.nameUpdated_ = true;
}
return dynaEntry;
}
return null;
};
/**
* Returns a dynamic code entry using its starting address.
*
* @param {number} addr Address.
*/
CodeMap.prototype.findDynamicEntryByStartAddress =
function(addr) {
var node = this.dynamics_.find(addr);
return node ? node.value : null;
};
/**
* Returns an array of all dynamic code entries.
*/
CodeMap.prototype.getAllDynamicEntries = function() {
return this.dynamics_.exportValues();
};
/**
* Returns an array of pairs of all dynamic code entries and their addresses.
*/
CodeMap.prototype.getAllDynamicEntriesWithAddresses = function() {
return this.dynamics_.exportKeysAndValues();
};
/**
* Returns an array of all static code entries.
*/
CodeMap.prototype.getAllStaticEntries = function() {
return this.statics_.exportValues();
};
/**
* Returns an array of all libraries entries.
*/
CodeMap.prototype.getAllLibrariesEntries = function() {
return this.libraries_.exportValues();
};
/**
* Creates a code entry object.
*
* @param {number} size Code entry size in bytes.
* @param {string=} opt_name Code entry name.
* @constructor
*/
CodeMap.CodeEntry = function(size, opt_name) {
this.size = size;
this.name = opt_name || '';
this.nameUpdated_ = false;
};
CodeMap.CodeEntry.prototype.getName = function() {
return this.name;
};
CodeMap.CodeEntry.prototype.toString = function() {
return this.name + ': ' + this.size.toString(16);
};
CodeMap.NameGenerator = function() {
this.knownNames_ = {};
};
CodeMap.NameGenerator.prototype.getName = function(name) {
if (!(name in this.knownNames_)) {
this.knownNames_[name] = 0;
return name;
}
var count = ++this.knownNames_[name];
return name + ' {' + count + '}';
};
return {
CodeMap: CodeMap
};
});
// 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.
'use strict';
/**
* @fileoverview Log Reader is used to process log file produced by V8.
*/
base.exportTo('tracing.importer.v8', function() {
/**
* Creates a CSV lines parser.
*/
function CsvParser() {
};
/**
* A regex for matching a CSV field.
* @private
*/
CsvParser.CSV_FIELD_RE_ = /^"((?:[^"]|"")*)"|([^,]*)/;
/**
* A regex for matching a double quote.
* @private
*/
CsvParser.DOUBLE_QUOTE_RE_ = /""/g;
/**
* Parses a line of CSV-encoded values. Returns an array of fields.
*
* @param {string} line Input line.
*/
CsvParser.prototype.parseLine = function(line) {
var fieldRe = CsvParser.CSV_FIELD_RE_;
var doubleQuoteRe = CsvParser.DOUBLE_QUOTE_RE_;
var pos = 0;
var endPos = line.length;
var fields = [];
if (endPos > 0) {
do {
var fieldMatch = fieldRe.exec(line.substr(pos));
if (typeof fieldMatch[1] === 'string') {
var field = fieldMatch[1];
pos += field.length + 3; // Skip comma and quotes.
fields.push(field.replace(doubleQuoteRe, '"'));
} else {
// The second field pattern will match anything, thus
// in the worst case the match will be an empty string.
var field = fieldMatch[2];
pos += field.length + 1; // Skip comma.
fields.push(field);
}
} while (pos <= endPos);
}
return fields;
};
/**
* Base class for processing log files.
*
* @param {Array.<Object>} dispatchTable A table used for parsing and
* processing log records.
*
* @constructor
*/
function LogReader(dispatchTable) {
/**
* @type {Array.<Object>}
*/
this.dispatchTable_ = dispatchTable;
/**
* Current line.
* @type {number}
*/
this.lineNum_ = 0;
/**
* CSV lines parser.
* @type {CsvParser}
*/
this.csvParser_ = new CsvParser();
};
/**
* Used for printing error messages.
*
* @param {string} str Error message.
*/
LogReader.prototype.printError = function(str) {
// Do nothing.
};
/**
* Processes a portion of V8 profiler event log.
*
* @param {string} chunk A portion of log.
*/
LogReader.prototype.processLogChunk = function(chunk) {
this.processLog_(chunk.split('\n'));
};
/**
* Processes a line of V8 profiler event log.
*
* @param {string} line A line of log.
*/
LogReader.prototype.processLogLine = function(line) {
this.processLog_([line]);
};
/**
* Processes stack record.
*
* @param {number} pc Program counter.
* @param {number} func JS Function.
* @param {Array.<string>} stack String representation of a stack.
* @return {Array.<number>} Processed stack.
*/
LogReader.prototype.processStack = function(pc, func, stack) {
var fullStack = func ? [pc, func] : [pc];
var prevFrame = pc;
for (var i = 0, n = stack.length; i < n; ++i) {
var frame = stack[i];
var firstChar = frame.charAt(0);
if (firstChar == '+' || firstChar == '-') {
// An offset from the previous frame.
prevFrame += parseInt(frame, 16);
fullStack.push(prevFrame);
// Filter out possible 'overflow' string.
} else if (firstChar != 'o') {
fullStack.push(parseInt(frame, 16));
}
}
return fullStack;
};
/**
* Returns whether a particular dispatch must be skipped.
*
* @param {!Object} dispatch Dispatch record.
* @return {boolean} True if dispatch must be skipped.
*/
LogReader.prototype.skipDispatch = function(dispatch) {
return false;
};
/**
* Does a dispatch of a log record.
*
* @param {Array.<string>} fields Log record.
* @private
*/
LogReader.prototype.dispatchLogRow_ = function(fields) {
// Obtain the dispatch.
var command = fields[0];
if (!(command in this.dispatchTable_)) return;
var dispatch = this.dispatchTable_[command];
if (dispatch === null || this.skipDispatch(dispatch)) {
return;
}
// Parse fields.
var parsedFields = [];
for (var i = 0; i < dispatch.parsers.length; ++i) {
var parser = dispatch.parsers[i];
if (parser === null) {
parsedFields.push(fields[1 + i]);
} else if (typeof parser == 'function') {
parsedFields.push(parser(fields[1 + i]));
} else {
// var-args
parsedFields.push(fields.slice(1 + i));
break;
}
}
// Run the processor.
dispatch.processor.apply(this, parsedFields);
};
/**
* Processes log lines.
*
* @param {Array.<string>} lines Log lines.
* @private
*/
LogReader.prototype.processLog_ = function(lines) {
for (var i = 0, n = lines.length; i < n; ++i, ++this.lineNum_) {
var line = lines[i];
if (!line) {
continue;
}
try {
var fields = this.csvParser_.parseLine(line);
this.dispatchLogRow_(fields);
} catch (e) {
this.printError('line ' + (this.lineNum_ + 1) + ': ' +
(e.message || e));
}
}
};
return {
LogReader: LogReader
};
});
// 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.
'use strict';
/**
* @fileoverview V8LogImporter imports v8.log files into the provided model.
*/
base.require('tracing.importer.importer');
base.require('tracing.trace_model');
base.require('tracing.trace_model.slice');
base.require('tracing.color_scheme');
base.require('tracing.importer.v8.log_reader');
base.require('tracing.importer.v8.codemap');
base.exportTo('tracing.importer', function() {
var Importer = tracing.importer.Importer;
function V8LogImporter(model, eventData) {
this.importPriority = 3;
this.model_ = model;
this.logData_ = eventData;
this.code_map_ = new tracing.importer.v8.CodeMap();
this.v8_timer_thread_ = undefined;
this.v8_stack_thread_ = undefined;
this.v8_samples_thread_ = undefined;
}
var kV8BinarySuffixes = ['/d8', '/libv8.so'];
var kStackFrames = 8;
var TimerEventDefaultArgs = {
'V8.Execute': { pause: false, no_execution: false},
'V8.External': { pause: false, no_execution: true},
'V8.CompileFullCode': { pause: true, no_execution: true},
'V8.RecompileSynchronous': { pause: true, no_execution: true},
'V8.RecompileParallel': { pause: false, no_execution: false},
'V8.CompileEval': { pause: true, no_execution: true},
'V8.Parse': { pause: true, no_execution: true},
'V8.PreParse': { pause: true, no_execution: true},
'V8.ParseLazy': { pause: true, no_execution: true},
'V8.GCScavenger': { pause: true, no_execution: true},
'V8.GCCompactor': { pause: true, no_execution: true},
'V8.GCContext': { pause: true, no_execution: true}
};
/**
* @return {boolean} Whether obj is a V8 log string.
*/
V8LogImporter.canImport = function(eventData) {
if (typeof(eventData) !== 'string' && !(eventData instanceof String))
return false;
return eventData.substring(0, 12) == 'timer-event,' ||
eventData.substring(0, 5) == 'tick,' ||
eventData.substring(0, 15) == 'shared-library,' ||
eventData.substring(0, 9) == 'profiler,';
};
V8LogImporter.prototype = {
__proto__: Importer.prototype,
processTimerEvent_: function(name, start, length) {
var args = TimerEventDefaultArgs[name];
if (args === undefined) return;
start /= 1000; // Convert to milliseconds.
length /= 1000;
var colorId = tracing.getStringColorId(name);
var slice = new tracing.trace_model.Slice('v8', name, colorId, start,
args, length);
this.v8_timer_thread_.sliceGroup.pushSlice(slice);
},
processTimerEventStart_: function(name, start) {
var args = TimerEventDefaultArgs[name];
if (args === undefined) return;
start /= 1000; // Convert to milliseconds.
this.v8_timer_thread_.sliceGroup.beginSlice('v8', name, start, args);
},
processTimerEventEnd_: function(name, end) {
end /= 1000; // Convert to milliseconds.
this.v8_timer_thread_.sliceGroup.endSlice(end);
},
processCodeCreateEvent_: function(type, kind, address, size, name) {
var code_entry = new tracing.importer.v8.CodeMap.CodeEntry(size, name);
code_entry.kind = kind;
this.code_map_.addCode(address, code_entry);
},
processCodeMoveEvent_: function(from, to) {
this.code_map_.moveCode(from, to);
},
processCodeDeleteEvent_: function(address) {
this.code_map_.deleteCode(address);
},
processSharedLibrary_: function(name, start, end) {
var code_entry = new tracing.importer.v8.CodeMap.CodeEntry(
end - start, name);
code_entry.kind = -3; // External code kind.
for (var i = 0; i < kV8BinarySuffixes.length; i++) {
var suffix = kV8BinarySuffixes[i];
if (name.indexOf(suffix, name.length - suffix.length) >= 0) {
code_entry.kind = -1; // V8 runtime code kind.
break;
}
}
this.code_map_.addLibrary(start, code_entry);
},
findCodeKind_: function(kind) {
for (name in CodeKinds) {
if (CodeKinds[name].kinds.indexOf(kind) >= 0) {
return CodeKinds[name];
}
}
},
nameForCodeEntry_: function(entry) {
if (entry)
return entry.name;
return 'UnknownCode';
},
processTickEvent_: function(pc, sp, start, unused_x, unused_y, vmstate,
stack) {
var entry = this.code_map_.findEntry(pc);
var name = this.nameForCodeEntry_(entry);
start /= 1000;
this.v8_samples_thread_.addSample('v8', name, start);
if (stack && stack.length) {
for (var i = 0; i < 8; i++) {
if (!stack[i]) break;
entry = this.code_map_.findEntry(stack[i]);
name = this.nameForCodeEntry_(entry);
var colorId = tracing.getStringColorId(name);
var slice = new tracing.trace_model.Slice(
'v8', name, colorId, start, {}, 0);
this.v8_stack_thread_.sliceGroup.pushSlice(slice);
}
}
},
processDistortion_: function(distortion_in_picoseconds) {
distortion_per_entry = distortion_in_picoseconds / 1000000;
},
processPlotRange_: function(start, end) {
xrange_start_override = start;
xrange_end_override = end;
},
/**
* Walks through the events_ list and outputs the structures discovered to
* model_.
*/
importEvents: function() {
var logreader = new tracing.importer.v8.LogReader(
{ 'timer-event' : {
parsers: [null, parseInt, parseInt],
processor: this.processTimerEvent_.bind(this)
},
'shared-library': {
parsers: [null, parseInt, parseInt],
processor: this.processSharedLibrary_.bind(this)
},
'timer-event-start' : {
parsers: [null, parseInt],
processor: this.processTimerEventStart_.bind(this)
},
'timer-event-end' : {
parsers: [null, parseInt],
processor: this.processTimerEventEnd_.bind(this)
},
'code-creation': {
parsers: [null, parseInt, parseInt, parseInt, null],
processor: this.processCodeCreateEvent_.bind(this)
},
'code-move': {
parsers: [parseInt, parseInt],
processor: this.processCodeMoveEvent_.bind(this)
},
'code-delete': {
parsers: [parseInt],
processor: this.processCodeDeleteEvent_.bind(this)
},
'tick': {
parsers: [parseInt, parseInt, parseInt, null, null, parseInt,
'var-args'],
processor: this.processTickEvent_.bind(this)
},
'distortion': {
parsers: [parseInt],
processor: this.processDistortion_.bind(this)
},
'plot-range': {
parsers: [parseInt, parseInt],
processor: this.processPlotRange_.bind(this)
}
});
this.v8_timer_thread_ =
this.model_.getOrCreateProcess(-32).getOrCreateThread(1);
this.v8_timer_thread_.name = 'V8 Timers';
this.v8_stack_thread_ =
this.model_.getOrCreateProcess(-32).getOrCreateThread(2);
this.v8_stack_thread_.name = 'V8 JavaScript';
this.v8_samples_thread_ =
this.model_.getOrCreateProcess(-32).getOrCreateThread(3);
this.v8_samples_thread_.name = 'V8 PC';
var lines = this.logData_.split('\n');
for (var i = 0; i < lines.length; i++) {
logreader.processLogLine(lines[i]);
}
}
};
tracing.TraceModel.registerImporter(V8LogImporter);
return {
V8LogImporter: V8LogImporter
};
});
/**
JSZip - A Javascript class for generating and reading zip files
<http://stuartk.com/jszip>
(c) 2009-2012 Stuart Knightley <stuart [at] stuartk.com>
Dual licenced under the MIT license or GPLv3. See LICENSE.markdown.
Usage:
zip = new JSZip();
zip.file("hello.txt", "Hello, World!").file("tempfile", "nothing");
zip.folder("images").file("smile.gif", base64Data, {base64: true});
zip.file("Xmas.txt", "Ho ho ho !", {date : new Date("December 25, 2007 00:00:01")});
zip.remove("tempfile");
base64zip = zip.generate();
**/
"use strict";
/**
* Representation a of zip file in js
* @constructor
* @param {String=|ArrayBuffer=|Uint8Array=|Buffer=} data the data to load, if any (optional).
* @param {Object=} options the options for creating this objects (optional).
*/
var JSZip = function(data, options) {
// object containing the files :
// {
// "folder/" : {...},
// "folder/data.txt" : {...}
// }
this.files = {};
// Where we are in the hierarchy
this.root = "";
if (data) {
this.load(data, options);
}
};
JSZip.signature = {
LOCAL_FILE_HEADER : "\x50\x4b\x03\x04",
CENTRAL_FILE_HEADER : "\x50\x4b\x01\x02",
CENTRAL_DIRECTORY_END : "\x50\x4b\x05\x06",
ZIP64_CENTRAL_DIRECTORY_LOCATOR : "\x50\x4b\x06\x07",
ZIP64_CENTRAL_DIRECTORY_END : "\x50\x4b\x06\x06",
DATA_DESCRIPTOR : "\x50\x4b\x07\x08"
};
// Default properties for a new file
JSZip.defaults = {
base64: false,
binary: false,
dir: false,
date: null,
compression: null
};
JSZip.prototype = (function () {
/**
* Returns the raw data of a ZipObject, decompress the content if necessary.
* @param {ZipObject} file the file to use.
* @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
*/
var getRawData = function (file) {
if (file._data instanceof JSZip.CompressedObject) {
file._data = file._data.getContent();
file.options.binary = true;
file.options.base64 = false;
if (JSZip.utils.getTypeOf(file._data) === "uint8array") {
var copy = file._data;
// when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array.
// if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file).
file._data = new Uint8Array(copy.length);
// with an empty Uint8Array, Opera fails with a "Offset larger than array size"
if (copy.length !== 0) {
file._data.set(copy, 0);
}
}
}
return file._data;
};
/**
* Returns the data of a ZipObject in a binary form. If the content is an unicode string, encode it.
* @param {ZipObject} file the file to use.
* @return {String|ArrayBuffer|Uint8Array|Buffer} the data.
*/
var getBinaryData = function (file) {
var result = getRawData(file), type = JSZip.utils.getTypeOf(result);
if (type === "string") {
if (!file.options.binary) {
// unicode text !
// unicode string => binary string is a painful process, check if we can avoid it.
if (JSZip.support.uint8array && typeof TextEncoder === "function") {
return TextEncoder("utf-8").encode(result);
}
if (JSZip.support.nodebuffer) {
return new Buffer(result, "utf-8");
}
}
return file.asBinary();
}
return result;
}
/**
* Transform this._data into a string.
* @param {function} filter a function String -> String, applied if not null on the result.
* @return {String} the string representing this._data.
*/
var dataToString = function (asUTF8) {
var result = getRawData(this);
if (result === null || typeof result === "undefined") {
return "";
}
// if the data is a base64 string, we decode it before checking the encoding !
if (this.options.base64) {
result = JSZip.base64.decode(result);
}
if (asUTF8 && this.options.binary) {
// JSZip.prototype.utf8decode supports arrays as input
// skip to array => string step, utf8decode will do it.
result = JSZip.prototype.utf8decode(result);
} else {
// no utf8 transformation, do the array => string step.
result = JSZip.utils.transformTo("string", result);
}
if (!asUTF8 && !this.options.binary) {
result = JSZip.prototype.utf8encode(result);
}
return result;
};
/**
* A simple object representing a file in the zip file.
* @constructor
* @param {string} name the name of the file
* @param {String|ArrayBuffer|Uint8Array|Buffer} data the data
* @param {Object} options the options of the file
*/
var ZipObject = function (name, data, options) {
this.name = name;
this._data = data;
this.options = options;
};
ZipObject.prototype = {
/**
* Return the content as UTF8 string.
* @return {string} the UTF8 string.
*/
asText : function () {
return dataToString.call(this, true);
},
/**
* Returns the binary content.
* @return {string} the content as binary.
*/
asBinary : function () {
return dataToString.call(this, false);
},
/**
* Returns the content as a nodejs Buffer.
* @return {Buffer} the content as a Buffer.
*/
asNodeBuffer : function () {
var result = getBinaryData(this);
return JSZip.utils.transformTo("nodebuffer", result);
},
/**
* Returns the content as an Uint8Array.
* @return {Uint8Array} the content as an Uint8Array.
*/
asUint8Array : function () {
var result = getBinaryData(this);
return JSZip.utils.transformTo("uint8array", result);
},
/**
* Returns the content as an ArrayBuffer.
* @return {ArrayBuffer} the content as an ArrayBufer.
*/
asArrayBuffer : function () {
return this.asUint8Array().buffer;
}
};
/**
* Transform an integer into a string in hexadecimal.
* @private
* @param {number} dec the number to convert.
* @param {number} bytes the number of bytes to generate.
* @returns {string} the result.
*/
var decToHex = function(dec, bytes) {
var hex = "", i;
for(i = 0; i < bytes; i++) {
hex += String.fromCharCode(dec&0xff);
dec=dec>>>8;
}
return hex;
};
/**
* Merge the objects passed as parameters into a new one.
* @private
* @param {...Object} var_args All objects to merge.
* @return {Object} a new object with the data of the others.
*/
var extend = function () {
var result = {}, i, attr;
for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers
for (attr in arguments[i]) {
if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") {
result[attr] = arguments[i][attr];
}
}
}
return result;
};
/**
* Transforms the (incomplete) options from the user into the complete
* set of options to create a file.
* @private
* @param {Object} o the options from the user.
* @return {Object} the complete set of options.
*/
var prepareFileAttrs = function (o) {
o = o || {};
if (o.base64 === true && o.binary == null) {
o.binary = true;
}
o = extend(o, JSZip.defaults);
o.date = o.date || new Date();
if (o.compression !== null) o.compression = o.compression.toUpperCase();
return o;
};
/**
* Add a file in the current folder.
* @private
* @param {string} name the name of the file
* @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file
* @param {Object} o the options of the file
* @return {Object} the new file.
*/
var fileAdd = function (name, data, o) {
// be sure sub folders exist
var parent = parentFolder(name), dataType = JSZip.utils.getTypeOf(data);
if (parent) {
folderAdd.call(this, parent);
}
o = prepareFileAttrs(o);
if (o.dir || data === null || typeof data === "undefined") {
o.base64 = false;
o.binary = false;
data = null;
} else if (dataType === "string") {
if (o.binary && !o.base64) {
// optimizedBinaryString == true means that the file has already been filtered with a 0xFF mask
if (o.optimizedBinaryString !== true) {
// this is a string, not in a base64 format.
// Be sure that this is a correct "binary string"
data = JSZip.utils.string2binary(data);
}
}
} else { // arraybuffer, uint8array, ...
o.base64 = false;
o.binary = true;
if (!dataType && !(data instanceof JSZip.CompressedObject)) {
throw new Error("The data of '" + name + "' is in an unsupported format !");
}
// special case : it's way easier to work with Uint8Array than with ArrayBuffer
if (dataType === "arraybuffer") {
data = JSZip.utils.transformTo("uint8array", data);
}
}
return this.files[name] = new ZipObject(name, data, o);
};
/**
* Find the parent folder of the path.
* @private
* @param {string} path the path to use
* @return {string} the parent folder, or ""
*/
var parentFolder = function (path) {
if (path.slice(-1) == '/') {
path = path.substring(0, path.length - 1);
}
var lastSlash = path.lastIndexOf('/');
return (lastSlash > 0) ? path.substring(0, lastSlash) : "";
};
/**
* Add a (sub) folder in the current folder.
* @private
* @param {string} name the folder's name
* @return {Object} the new folder.
*/
var folderAdd = function (name) {
// Check the name ends with a /
if (name.slice(-1) != "/") {
name += "/"; // IE doesn't like substr(-1)
}
// Does this folder already exist?
if (!this.files[name]) {
fileAdd.call(this, name, null, {dir:true});
}
return this.files[name];
};
/**
* Generate a JSZip.CompressedObject for a given zipOject.
* @param {ZipObject} file the object to read.
* @param {JSZip.compression} compression the compression to use.
* @return {JSZip.CompressedObject} the compressed result.
*/
var generateCompressedObjectFrom = function (file, compression) {
var result = new JSZip.CompressedObject(), content;
// the data has not been decompressed, we might reuse things !
if (file._data instanceof JSZip.CompressedObject) {
result.uncompressedSize = file._data.uncompressedSize;
result.crc32 = file._data.crc32;
if (result.uncompressedSize === 0 || file.options.dir) {
compression = JSZip.compressions['STORE'];
result.compressedContent = "";
result.crc32 = 0;
} else if (file._data.compressionMethod === compression.magic) {
result.compressedContent = file._data.getCompressedContent();
} else {
content = file._data.getContent()
// need to decompress / recompress
result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
}
} else {
// have uncompressed data
content = getBinaryData(file);
if (!content || content.length === 0 || file.options.dir) {
compression = JSZip.compressions['STORE'];
content = "";
}
result.uncompressedSize = content.length;
result.crc32 = this.crc32(content);
result.compressedContent = compression.compress(JSZip.utils.transformTo(compression.compressInputType, content));
}
result.compressedSize = result.compressedContent.length;
result.compressionMethod = compression.magic;
return result;
};
/**
* Generate the various parts used in the construction of the final zip file.
* @param {string} name the file name.
* @param {ZipObject} file the file content.
* @param {JSZip.CompressedObject} compressedObject the compressed object.
* @param {number} offset the current offset from the start of the zip file.
* @return {object} the zip parts.
*/
var generateZipParts = function(name, file, compressedObject, offset) {
var data = compressedObject.compressedContent,
utfEncodedFileName = this.utf8encode(file.name),
useUTF8 = utfEncodedFileName !== file.name,
o = file.options,
dosTime,
dosDate;
// date
// @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
// @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
// @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html
dosTime = o.date.getHours();
dosTime = dosTime << 6;
dosTime = dosTime | o.date.getMinutes();
dosTime = dosTime << 5;
dosTime = dosTime | o.date.getSeconds() / 2;
dosDate = o.date.getFullYear() - 1980;
dosDate = dosDate << 4;
dosDate = dosDate | (o.date.getMonth() + 1);
dosDate = dosDate << 5;
dosDate = dosDate | o.date.getDate();
var header = "";
// version needed to extract
header += "\x0A\x00";
// general purpose bit flag
// set bit 11 if utf8
header += useUTF8 ? "\x00\x08" : "\x00\x00";
// compression method
header += compressedObject.compressionMethod;
// last mod file time
header += decToHex(dosTime, 2);
// last mod file date
header += decToHex(dosDate, 2);
// crc-32
header += decToHex(compressedObject.crc32, 4);
// compressed size
header += decToHex(compressedObject.compressedSize, 4);
// uncompressed size
header += decToHex(compressedObject.uncompressedSize, 4);
// file name length
header += decToHex(utfEncodedFileName.length, 2);
// extra field length
header += "\x00\x00";
var fileRecord = JSZip.signature.LOCAL_FILE_HEADER + header + utfEncodedFileName;
var dirRecord = JSZip.signature.CENTRAL_FILE_HEADER +
// version made by (00: DOS)
"\x14\x00" +
// file header (common to file and central directory)
header +
// file comment length
"\x00\x00" +
// disk number start
"\x00\x00" +
// internal file attributes TODO
"\x00\x00" +
// external file attributes
(file.options.dir===true?"\x10\x00\x00\x00":"\x00\x00\x00\x00")+
// relative offset of local header
decToHex(offset, 4) +
// file name
utfEncodedFileName;
return {
fileRecord : fileRecord,
dirRecord : dirRecord,
compressedObject : compressedObject
};
};
/**
* An object to write any content to a string.
* @constructor
*/
var StringWriter = function () {
this.data = [];
};
StringWriter.prototype = {
/**
* Append any content to the current string.
* @param {Object} input the content to add.
*/
append : function (input) {
input = JSZip.utils.transformTo("string", input);
this.data.push(input);
},
/**
* Finalize the construction an return the result.
* @return {string} the generated string.
*/
finalize : function () {
return this.data.join("");
}
};
/**
* An object to write any content to an Uint8Array.
* @constructor
* @param {number} length The length of the array.
*/
var Uint8ArrayWriter = function (length) {
this.data = new Uint8Array(length);
this.index = 0;
};
Uint8ArrayWriter.prototype = {
/**
* Append any content to the current array.
* @param {Object} input the content to add.
*/
append : function (input) {
if (input.length !== 0) {
// with an empty Uint8Array, Opera fails with a "Offset larger than array size"
input = JSZip.utils.transformTo("uint8array", input);
this.data.set(input, this.index);
this.index += input.length;
}
},
/**
* Finalize the construction an return the result.
* @return {Uint8Array} the generated array.
*/
finalize : function () {
return this.data;
}
};
// return the actual prototype of JSZip
return {
/**
* Read an existing zip and merge the data in the current JSZip object.
* The implementation is in jszip-load.js, don't forget to include it.
* @param {String|ArrayBuffer|Uint8Array|Buffer} stream The stream to load
* @param {Object} options Options for loading the stream.
* options.base64 : is the stream in base64 ? default : false
* @return {JSZip} the current JSZip object
*/
load : function (stream, options) {
throw new Error("Load method is not defined. Is the file jszip-load.js included ?");
},
/**
* Filter nested files/folders with the specified function.
* @param {Function} search the predicate to use :
* function (relativePath, file) {...}
* It takes 2 arguments : the relative path and the file.
* @return {Array} An array of matching elements.
*/
filter : function (search) {
var result = [], filename, relativePath, file, fileClone;
for (filename in this.files) {
if ( !this.files.hasOwnProperty(filename) ) { continue; }
file = this.files[filename];
// return a new object, don't let the user mess with our internal objects :)
fileClone = new ZipObject(file.name, file._data, extend(file.options));
relativePath = filename.slice(this.root.length, filename.length);
if (filename.slice(0, this.root.length) === this.root && // the file is in the current root
search(relativePath, fileClone)) { // and the file matches the function
result.push(fileClone);
}
}
return result;
},
/**
* Add a file to the zip file, or search a file.
* @param {string|RegExp} name The name of the file to add (if data is defined),
* the name of the file to find (if no data) or a regex to match files.
* @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded
* @param {Object} o File options
* @return {JSZip|Object|Array} this JSZip object (when adding a file),
* a file (when searching by string) or an array of files (when searching by regex).
*/
file : function(name, data, o) {
if (arguments.length === 1) {
if (name instanceof RegExp) {
var regexp = name;
return this.filter(function(relativePath, file) {
return !file.options.dir && regexp.test(relativePath);
});
} else { // text
return this.filter(function (relativePath, file) {
return !file.options.dir && relativePath === name;
})[0]||null;
}
} else { // more than one argument : we have data !
name = this.root+name;
fileAdd.call(this, name, data, o);
}
return this;
},
/**
* Add a directory to the zip file, or search.
* @param {String|RegExp} arg The name of the directory to add, or a regex to search folders.
* @return {JSZip} an object with the new directory as the root, or an array containing matching folders.
*/
folder : function(arg) {
if (!arg) {
return this;
}
if (arg instanceof RegExp) {
return this.filter(function(relativePath, file) {
return file.options.dir && arg.test(relativePath);
});
}
// else, name is a new folder
var name = this.root + arg;
var newFolder = folderAdd.call(this, name);
// Allow chaining by returning a new object with this folder as the root
var ret = this.clone();
ret.root = newFolder.name;
return ret;
},
/**
* Delete a file, or a directory and all sub-files, from the zip
* @param {string} name the name of the file to delete
* @return {JSZip} this JSZip object
*/
remove : function(name) {
name = this.root + name;
var file = this.files[name];
if (!file) {
// Look for any folders
if (name.slice(-1) != "/") {
name += "/";
}
file = this.files[name];
}
if (file) {
if (!file.options.dir) {
// file
delete this.files[name];
} else {
// folder
var kids = this.filter(function (relativePath, file) {
return file.name.slice(0, name.length) === name;
});
for (var i = 0; i < kids.length; i++) {
delete this.files[kids[i].name];
}
}
}
return this;
},
/**
* Generate the complete zip file
* @param {Object} options the options to generate the zip file :
* - base64, (deprecated, use type instead) true to generate base64.
* - compression, "STORE" by default.
* - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob.
* @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file
*/
generate : function(options) {
options = extend(options || {}, {
base64 : true,
compression : "STORE",
type : "base64"
});
JSZip.utils.checkSupport(options.type);
var zipData = [], localDirLength = 0, centralDirLength = 0, writer, i;
// first, generate all the zip parts.
for (var name in this.files) {
if ( !this.files.hasOwnProperty(name) ) { continue; }
var file = this.files[name];
var compressionName = file.compression || options.compression.toUpperCase();
var compression = JSZip.compressions[compressionName];
if (!compression) {
throw new Error(compressionName + " is not a valid compression method !");
}
var compressedObject = generateCompressedObjectFrom.call(this, file, compression);
var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength);
localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize;
centralDirLength += zipPart.dirRecord.length;
zipData.push(zipPart);
}
var dirEnd = "";
// end of central dir signature
dirEnd = JSZip.signature.CENTRAL_DIRECTORY_END +
// number of this disk
"\x00\x00" +
// number of the disk with the start of the central directory
"\x00\x00" +
// total number of entries in the central directory on this disk
decToHex(zipData.length, 2) +
// total number of entries in the central directory
decToHex(zipData.length, 2) +
// size of the central directory 4 bytes
decToHex(centralDirLength, 4) +
// offset of start of central directory with respect to the starting disk number
decToHex(localDirLength, 4) +
// .ZIP file comment length
"\x00\x00";
// we have all the parts (and the total length)
// time to create a writer !
switch(options.type.toLowerCase()) {
case "uint8array" :
case "arraybuffer" :
case "blob" :
case "nodebuffer" :
writer = new Uint8ArrayWriter(localDirLength + centralDirLength + dirEnd.length);
break;
case "base64" :
default : // case "string" :
writer = new StringWriter(localDirLength + centralDirLength + dirEnd.length);
break;
}
for (i = 0; i < zipData.length; i++) {
writer.append(zipData[i].fileRecord);
writer.append(zipData[i].compressedObject.compressedContent);
}
for (i = 0; i < zipData.length; i++) {
writer.append(zipData[i].dirRecord);
}
writer.append(dirEnd);
var zip = writer.finalize();
switch(options.type.toLowerCase()) {
// case "zip is an Uint8Array"
case "uint8array" :
case "arraybuffer" :
case "nodebuffer" :
return JSZip.utils.transformTo(options.type.toLowerCase(), zip);
case "blob" :
return JSZip.utils.arrayBuffer2Blob(JSZip.utils.transformTo("arraybuffer", zip));
// case "zip is a string"
case "base64" :
return (options.base64) ? JSZip.base64.encode(zip) : zip;
default : // case "string" :
return zip;
}
},
/**
*
* Javascript crc32
* http://www.webtoolkit.info/
*
*/
crc32 : function crc32(input, crc) {
if (typeof input === "undefined" || !input.length) {
return 0;
}
var isArray = JSZip.utils.getTypeOf(input) !== "string";
var table = [
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
];
if (typeof(crc) == "undefined") { crc = 0; }
var x = 0;
var y = 0;
var byte = 0;
crc = crc ^ (-1);
for( var i = 0, iTop = input.length; i < iTop; i++ ) {
byte = isArray ? input[i] : input.charCodeAt(i);
y = ( crc ^ byte ) & 0xFF;
x = table[y];
crc = ( crc >>> 8 ) ^ x;
}
return crc ^ (-1);
},
// Inspired by http://my.opera.com/GreyWyvern/blog/show.dml/1725165
clone : function() {
var newObj = new JSZip();
for (var i in this) {
if (typeof this[i] !== "function") {
newObj[i] = this[i];
}
}
return newObj;
},
/**
* http://www.webtoolkit.info/javascript-utf8.html
*/
utf8encode : function (string) {
// TextEncoder + Uint8Array to binary string is faster than checking every bytes on long strings.
// http://jsperf.com/utf8encode-vs-textencoder
// On short strings (file names for example), the TextEncoder API is (currently) slower.
if (JSZip.support.uint8array && typeof TextEncoder === "function") {
var u8 = TextEncoder("utf-8").encode(string);
return JSZip.utils.transformTo("string", u8);
}
if (JSZip.support.nodebuffer) {
return JSZip.utils.transformTo("string", new Buffer(string, "utf-8"));
}
// array.join may be slower than string concatenation but generates less objects (less time spent garbage collecting).
// See also http://jsperf.com/array-direct-assignment-vs-push/31
var result = [], resIndex = 0;
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
result[resIndex++] = String.fromCharCode(c);
} else if ((c > 127) && (c < 2048)) {
result[resIndex++] = String.fromCharCode((c >> 6) | 192);
result[resIndex++] = String.fromCharCode((c & 63) | 128);
} else {
result[resIndex++] = String.fromCharCode((c >> 12) | 224);
result[resIndex++] = String.fromCharCode(((c >> 6) & 63) | 128);
result[resIndex++] = String.fromCharCode((c & 63) | 128);
}
}
return result.join("");
},
/**
* http://www.webtoolkit.info/javascript-utf8.html
*/
utf8decode : function (input) {
var result = [], resIndex = 0;
var type = JSZip.utils.getTypeOf(input);
var isArray = type !== "string";
var i = 0;
var c = 0, c1 = 0, c2 = 0, c3 = 0;
// check if we can use the TextDecoder API
// see http://encoding.spec.whatwg.org/#api
if (JSZip.support.uint8array && typeof TextDecoder === "function") {
return TextDecoder("utf-8").decode(
JSZip.utils.transformTo("uint8array", input)
);
}
if (JSZip.support.nodebuffer) {
return JSZip.utils.transformTo("nodebuffer", input).toString("utf-8");
}
while ( i < input.length ) {
c = isArray ? input[i] : input.charCodeAt(i);
if (c < 128) {
result[resIndex++] = String.fromCharCode(c);
i++;
} else if ((c > 191) && (c < 224)) {
c2 = isArray ? input[i+1] : input.charCodeAt(i+1);
result[resIndex++] = String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
} else {
c2 = isArray ? input[i+1] : input.charCodeAt(i+1);
c3 = isArray ? input[i+2] : input.charCodeAt(i+2);
result[resIndex++] = String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return result.join("");
}
};
}());
/*
* Compression methods
* This object is filled in as follow :
* name : {
* magic // the 2 bytes indentifying the compression method
* compress // function, take the uncompressed content and return it compressed.
* uncompress // function, take the compressed content and return it uncompressed.
* compressInputType // string, the type accepted by the compress method. null to accept everything.
* uncompressInputType // string, the type accepted by the uncompress method. null to accept everything.
* }
*
* STORE is the default compression method, so it's included in this file.
* Other methods should go to separated files : the user wants modularity.
*/
JSZip.compressions = {
"STORE" : {
magic : "\x00\x00",
compress : function (content) {
return content; // no compression
},
uncompress : function (content) {
return content; // no compression
},
compressInputType : null,
uncompressInputType : null
}
};
/*
* List features that require a modern browser, and if the current browser support them.
*/
JSZip.support = {
// contains true if JSZip can read/generate ArrayBuffer, false otherwise.
arraybuffer : (function(){
return typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined";
})(),
// contains true if JSZip can read/generate nodejs Buffer, false otherwise.
nodebuffer : (function(){
return typeof Buffer !== "undefined";
})(),
// contains true if JSZip can read/generate Uint8Array, false otherwise.
uint8array : (function(){
return typeof Uint8Array !== "undefined";
})(),
// contains true if JSZip can read/generate Blob, false otherwise.
blob : (function(){
// the spec started with BlobBuilder then replaced it with a construtor for Blob.
// Result : we have browsers that :
// * know the BlobBuilder (but with prefix)
// * know the Blob constructor
// * know about Blob but not about how to build them
// About the "=== 0" test : if given the wrong type, it may be converted to a string.
// Instead of an empty content, we will get "[object Uint8Array]" for example.
if (typeof ArrayBuffer === "undefined") {
return false;
}
var buffer = new ArrayBuffer(0);
try {
return new Blob([buffer], { type: "application/zip" }).size === 0;
}
catch(e) {}
try {
var builder = new (window.BlobBuilder || window.WebKitBlobBuilder ||
window.MozBlobBuilder || window.MSBlobBuilder)();
builder.append(buffer);
return builder.getBlob('application/zip').size === 0;
}
catch(e) {}
return false;
})()
};
(function () {
JSZip.utils = {
/**
* Convert a string to a "binary string" : a string containing only char codes between 0 and 255.
* @param {string} str the string to transform.
* @return {String} the binary string.
*/
string2binary : function (str) {
var result = "";
for (var i = 0; i < str.length; i++) {
result += String.fromCharCode(str.charCodeAt(i) & 0xff);
}
return result;
},
/**
* Create a Uint8Array from the string.
* @param {string} str the string to transform.
* @return {Uint8Array} the typed array.
* @throws {Error} an Error if the browser doesn't support the requested feature.
* @deprecated : use JSZip.utils.transformTo instead.
*/
string2Uint8Array : function (str) {
return JSZip.utils.transformTo("uint8array", str);
},
/**
* Create a string from the Uint8Array.
* @param {Uint8Array} array the array to transform.
* @return {string} the string.
* @throws {Error} an Error if the browser doesn't support the requested feature.
* @deprecated : use JSZip.utils.transformTo instead.
*/
uint8Array2String : function (array) {
return JSZip.utils.transformTo("string", array);
},
/**
* Create a blob from the given ArrayBuffer.
* @param {ArrayBuffer} buffer the buffer to transform.
* @return {Blob} the result.
* @throws {Error} an Error if the browser doesn't support the requested feature.
*/
arrayBuffer2Blob : function (buffer) {
JSZip.utils.checkSupport("blob");
try {
// Blob constructor
return new Blob([buffer], { type: "application/zip" });
}
catch(e) {}
try {
// deprecated, browser only, old way
var builder = new (window.BlobBuilder || window.WebKitBlobBuilder ||
window.MozBlobBuilder || window.MSBlobBuilder)();
builder.append(buffer);
return builder.getBlob('application/zip');
}
catch(e) {}
// well, fuck ?!
throw new Error("Bug : can't construct the Blob.");
},
/**
* Create a blob from the given string.
* @param {string} str the string to transform.
* @return {Blob} the result.
* @throws {Error} an Error if the browser doesn't support the requested feature.
*/
string2Blob : function (str) {
var buffer = JSZip.utils.transformTo("arraybuffer", str);
return JSZip.utils.arrayBuffer2Blob(buffer);
}
};
/**
* The identity function.
* @param {Object} input the input.
* @return {Object} the same input.
*/
function identity(input) {
return input;
};
/**
* Fill in an array with a string.
* @param {String} str the string to use.
* @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated).
* @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array.
*/
function stringToArrayLike(str, array) {
for (var i = 0; i < str.length; ++i) {
array[i] = str.charCodeAt(i) & 0xFF;
}
return array;
};
/**
* Transform an array-like object to a string.
* @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform.
* @return {String} the result.
*/
function arrayLikeToString(array) {
// Performances notes :
// --------------------
// String.fromCharCode.apply(null, array) is the fastest, see
// see http://jsperf.com/converting-a-uint8array-to-a-string/2
// but the stack is limited (and we can get huge arrays !).
//
// result += String.fromCharCode(array[i]); generate too many strings !
//
// This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2
var chunk = 65536;
var result = [], len = array.length, type = JSZip.utils.getTypeOf(array), k = 0;
while (k < len && chunk > 1) {
try {
if (type === "array" || type === "nodebuffer") {
result.push(String.fromCharCode.apply(null, array.slice(k, Math.max(k + chunk, len))));
} else {
result.push(String.fromCharCode.apply(null, array.subarray(k, k + chunk)));
}
k += chunk;
} catch (e) {
chunk = Math.floor(chunk / 2);
}
}
return result.join("");
};
/**
* Copy the data from an array-like to an other array-like.
* @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array.
* @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated.
* @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array.
*/
function arrayLikeToArrayLike(arrayFrom, arrayTo) {
for(var i = 0; i < arrayFrom.length; i++) {
arrayTo[i] = arrayFrom[i];
}
return arrayTo;
};
// a matrix containing functions to transform everything into everything.
var transform = {};
// string to ?
transform["string"] = {
"string" : identity,
"array" : function (input) {
return stringToArrayLike(input, new Array(input.length));
},
"arraybuffer" : function (input) {
return transform["string"]["uint8array"](input).buffer;
},
"uint8array" : function (input) {
return stringToArrayLike(input, new Uint8Array(input.length));
},
"nodebuffer" : function (input) {
return stringToArrayLike(input, new Buffer(input.length));
}
};
// array to ?
transform["array"] = {
"string" : arrayLikeToString,
"array" : identity,
"arraybuffer" : function (input) {
return (new Uint8Array(input)).buffer;
},
"uint8array" : function (input) {
return new Uint8Array(input);
},
"nodebuffer" : function (input) {
return new Buffer(input);
}
};
// arraybuffer to ?
transform["arraybuffer"] = {
"string" : function (input) {
return arrayLikeToString(new Uint8Array(input));
},
"array" : function (input) {
return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength));
},
"arraybuffer" : identity,
"uint8array" : function (input) {
return new Uint8Array(input);
},
"nodebuffer" : function (input) {
return new Buffer(new Uint8Array(input));
}
};
// uint8array to ?
transform["uint8array"] = {
"string" : arrayLikeToString,
"array" : function (input) {
return arrayLikeToArrayLike(input, new Array(input.length));
},
"arraybuffer" : function (input) {
return input.buffer;
},
"uint8array" : identity,
"nodebuffer" : function(input) {
return new Buffer(input);
}
};
// nodebuffer to ?
transform["nodebuffer"] = {
"string" : arrayLikeToString,
"array" : function (input) {
return arrayLikeToArrayLike(input, new Array(input.length));
},
"arraybuffer" : function (input) {
return transform["nodebuffer"]["uint8array"](input).buffer;
},
"uint8array" : function (input) {
return arrayLikeToArrayLike(input, new Uint8Array(input.length));
},
"nodebuffer" : identity
};
/**
* Transform an input into any type.
* The supported output type are : string, array, uint8array, arraybuffer, nodebuffer.
* If no output type is specified, the unmodified input will be returned.
* @param {String} outputType the output type.
* @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert.
* @throws {Error} an Error if the browser doesn't support the requested output type.
*/
JSZip.utils.transformTo = function (outputType, input) {
if (!input) {
// undefined, null, etc
// an empty string won't harm.
input = "";
}
if (!outputType) {
return input;
}
JSZip.utils.checkSupport(outputType);
var inputType = JSZip.utils.getTypeOf(input);
var result = transform[inputType][outputType](input);
return result;
};
/**
* Return the type of the input.
* The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer.
* @param {Object} input the input to identify.
* @return {String} the (lowercase) type of the input.
*/
JSZip.utils.getTypeOf = function (input) {
if (typeof input === "string") {
return "string";
}
if (input instanceof Array) {
return "array";
}
if (JSZip.support.nodebuffer && Buffer.isBuffer(input)) {
return "nodebuffer";
}
if (JSZip.support.uint8array && input instanceof Uint8Array) {
return "uint8array";
}
if (JSZip.support.arraybuffer && input instanceof ArrayBuffer) {
return "arraybuffer";
}
};
/**
* Throw an exception if the type is not supported.
* @param {String} type the type to check.
* @throws {Error} an Error if the browser doesn't support the requested type.
*/
JSZip.utils.checkSupport = function (type) {
var supported = true;
switch (type.toLowerCase()) {
case "uint8array":
supported = JSZip.support.uint8array;
break;
case "arraybuffer":
supported = JSZip.support.arraybuffer;
break;
case "nodebuffer":
supported = JSZip.support.nodebuffer;
break;
case "blob":
supported = JSZip.support.blob;
break;
}
if (!supported) {
throw new Error(type + " is not supported by this browser");
}
};
})();
(function (){
/**
* Represents an entry in the zip.
* The content may or may not be compressed.
* @constructor
*/
JSZip.CompressedObject = function () {
this.compressedSize = 0;
this.uncompressedSize = 0;
this.crc32 = 0;
this.compressionMethod = null;
this.compressedContent = null;
};
JSZip.CompressedObject.prototype = {
/**
* Return the decompressed content in an unspecified format.
* The format will depend on the decompressor.
* @return {Object} the decompressed content.
*/
getContent : function () {
return null; // see implementation
},
/**
* Return the compressed content in an unspecified format.
* The format will depend on the compressed conten source.
* @return {Object} the compressed content.
*/
getCompressedContent : function () {
return null; // see implementation
}
};
})();
/**
*
* Base64 encode / decode
* http://www.webtoolkit.info/
*
* Hacked so that it doesn't utf8 en/decode everything
**/
JSZip.base64 = (function() {
// private property
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
return {
// public method for encoding
encode : function(input, utf8) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
_keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
_keyStr.charAt(enc3) + _keyStr.charAt(enc4);
}
return output;
},
// public method for decoding
decode : function(input, utf8) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = _keyStr.indexOf(input.charAt(i++));
enc2 = _keyStr.indexOf(input.charAt(i++));
enc3 = _keyStr.indexOf(input.charAt(i++));
enc4 = _keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
return output;
}
};
}());
// enforcing Stuk's coding style
// vim: set shiftwidth=3 softtabstop=3:
/**
JSZip - A Javascript class for generating and reading zip files
<http://stuartk.com/jszip>
(c) 2011 David Duponchel <d.duponchel@gmail.com>
Dual licenced under the MIT license or GPLv3. See LICENSE.markdown.
**/
/*global JSZip */
"use strict";
(function (root) {
var JSZip = root.JSZip;
var MAX_VALUE_16BITS = 65535;
var MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1
/**
* Prettify a string read as binary.
* @param {string} str the string to prettify.
* @return {string} a pretty string.
*/
var pretty = function (str) {
var res = '', code, i;
for (i = 0; i < (str||"").length; i++) {
code = str.charCodeAt(i);
res += '\\x' + (code < 16 ? "0" : "") + code.toString(16).toUpperCase();
}
return res;
};
/**
* Find a compression registered in JSZip.
* @param {string} compressionMethod the method magic to find.
* @return {Object|null} the JSZip compression object, null if none found.
*/
var findCompression = function (compressionMethod) {
for (var method in JSZip.compressions) {
if( !JSZip.compressions.hasOwnProperty(method) ) { continue; }
if (JSZip.compressions[method].magic === compressionMethod) {
return JSZip.compressions[method];
}
}
return null;
};
// class DataReader {{{
/**
* Read bytes from a source.
* Developer tip : when debugging, a watch on pretty(this.reader.data.slice(this.reader.index))
* is very useful :)
* @constructor
* @param {String|ArrayBuffer|Uint8Array|Buffer} data the data to read.
*/
function DataReader(data) {
this.data = null; // type : see implementation
this.length = 0;
this.index = 0;
}
DataReader.prototype = {
/**
* Check that the offset will not go too far.
* @param {string} offset the additional offset to check.
* @throws {Error} an Error if the offset is out of bounds.
*/
checkOffset : function (offset) {
this.checkIndex(this.index + offset);
},
/**
* Check that the specifed index will not be too far.
* @param {string} newIndex the index to check.
* @throws {Error} an Error if the index is out of bounds.
*/
checkIndex : function (newIndex) {
if (this.length < newIndex || newIndex < 0) {
throw new Error("End of data reached (data length = " +
this.length + ", asked index = " +
(newIndex) + "). Corrupted zip ?");
}
},
/**
* Change the index.
* @param {number} newIndex The new index.
* @throws {Error} if the new index is out of the data.
*/
setIndex : function (newIndex) {
this.checkIndex(newIndex);
this.index = newIndex;
},
/**
* Skip the next n bytes.
* @param {number} n the number of bytes to skip.
* @throws {Error} if the new index is out of the data.
*/
skip : function (n) {
this.setIndex(this.index + n);
},
/**
* Get the byte at the specified index.
* @param {number} i the index to use.
* @return {number} a byte.
*/
byteAt : function(i) {
// see implementations
},
/**
* Get the next number with a given byte size.
* @param {number} size the number of bytes to read.
* @return {number} the corresponding number.
*/
readInt : function (size) {
var result = 0, i;
this.checkOffset(size);
for(i = this.index + size - 1; i >= this.index; i--) {
result = (result << 8) + this.byteAt(i);
}
this.index += size;
return result;
},
/**
* Get the next string with a given byte size.
* @param {number} size the number of bytes to read.
* @return {string} the corresponding string.
*/
readString : function (size) {
return JSZip.utils.transformTo("string", this.readData(size));
},
/**
* Get raw data without conversion, <size> bytes.
* @param {number} size the number of bytes to read.
* @return {Object} the raw data, implementation specific.
*/
readData : function (size) {
// see implementations
},
/**
* Find the last occurence of a zip signature (4 bytes).
* @param {string} sig the signature to find.
* @return {number} the index of the last occurence, -1 if not found.
*/
lastIndexOfSignature : function (sig) {
// see implementations
},
/**
* Get the next date.
* @return {Date} the date.
*/
readDate : function () {
var dostime = this.readInt(4);
return new Date(
((dostime >> 25) & 0x7f) + 1980, // year
((dostime >> 21) & 0x0f) - 1, // month
(dostime >> 16) & 0x1f, // day
(dostime >> 11) & 0x1f, // hour
(dostime >> 5) & 0x3f, // minute
(dostime & 0x1f) << 1); // second
}
};
/**
* Read bytes from a string.
* @constructor
* @param {String} data the data to read.
*/
function StringReader(data, optimizedBinaryString) {
this.data = data;
if (!optimizedBinaryString) {
this.data = JSZip.utils.string2binary(this.data);
}
this.length = this.data.length;
this.index = 0;
}
StringReader.prototype = new DataReader();
/**
* @see DataReader.byteAt
*/
StringReader.prototype.byteAt = function(i) {
return this.data.charCodeAt(i);
};
/**
* @see DataReader.lastIndexOfSignature
*/
StringReader.prototype.lastIndexOfSignature = function (sig) {
return this.data.lastIndexOf(sig);
};
/**
* @see DataReader.readData
*/
StringReader.prototype.readData = function (size) {
this.checkOffset(size);
// this will work because the constructor applied the "& 0xff" mask.
var result = this.data.slice(this.index, this.index + size);
this.index += size;
return result;
};
/**
* Read bytes from an Uin8Array.
* @constructor
* @param {Uint8Array} data the data to read.
*/
function Uint8ArrayReader(data) {
if (data) {
this.data = data;
this.length = this.data.length;
this.index = 0;
}
}
Uint8ArrayReader.prototype = new DataReader();
/**
* @see DataReader.byteAt
*/
Uint8ArrayReader.prototype.byteAt = function(i) {
return this.data[i];
};
/**
* @see DataReader.lastIndexOfSignature
*/
Uint8ArrayReader.prototype.lastIndexOfSignature = function (sig) {
var sig0 = sig.charCodeAt(0),
sig1 = sig.charCodeAt(1),
sig2 = sig.charCodeAt(2),
sig3 = sig.charCodeAt(3);
for(var i = this.length - 4;i >= 0;--i) {
if (this.data[i] === sig0 && this.data[i+1] === sig1 && this.data[i+2] === sig2 && this.data[i+3] === sig3) {
return i;
}
}
return -1;
};
/**
* @see DataReader.readData
*/
Uint8ArrayReader.prototype.readData = function (size) {
this.checkOffset(size);
var result = this.data.subarray(this.index, this.index + size);
this.index += size;
return result;
};
/**
* Read bytes from a Buffer.
* @constructor
* @param {Buffer} data the data to read.
*/
function NodeBufferReader(data) {
this.data = data;
this.length = this.data.length;
this.index = 0;
}
NodeBufferReader.prototype = new Uint8ArrayReader();
/**
* @see DataReader.readData
*/
NodeBufferReader.prototype.readData = function (size) {
this.checkOffset(size);
var result = this.data.slice(this.index, this.index + size);
this.index += size;
return result;
};
// }}} end of DataReader
// class ZipEntry {{{
/**
* An entry in the zip file.
* @constructor
* @param {Object} options Options of the current file.
* @param {Object} loadOptions Options for loading the data.
*/
function ZipEntry(options, loadOptions) {
this.options = options;
this.loadOptions = loadOptions;
}
ZipEntry.prototype = {
/**
* say if the file is encrypted.
* @return {boolean} true if the file is encrypted, false otherwise.
*/
isEncrypted : function () {
// bit 1 is set
return (this.bitFlag & 0x0001) === 0x0001;
},
/**
* say if the file has utf-8 filename/comment.
* @return {boolean} true if the filename/comment is in utf-8, false otherwise.
*/
useUTF8 : function () {
// bit 11 is set
return (this.bitFlag & 0x0800) === 0x0800;
},
/**
* Prepare the function used to generate the compressed content from this ZipFile.
* @param {DataReader} reader the reader to use.
* @param {number} from the offset from where we should read the data.
* @param {number} length the length of the data to read.
* @return {Function} the callback to get the compressed content (the type depends of the DataReader class).
*/
prepareCompressedContent : function (reader, from, length) {
return function () {
var previousIndex = reader.index;
reader.setIndex(from);
var compressedFileData = reader.readData(length);
reader.setIndex(previousIndex);
return compressedFileData;
}
},
/**
* Prepare the function used to generate the uncompressed content from this ZipFile.
* @param {DataReader} reader the reader to use.
* @param {number} from the offset from where we should read the data.
* @param {number} length the length of the data to read.
* @param {JSZip.compression} compression the compression used on this file.
* @param {number} uncompressedSize the uncompressed size to expect.
* @return {Function} the callback to get the uncompressed content (the type depends of the DataReader class).
*/
prepareContent : function (reader, from, length, compression, uncompressedSize) {
return function () {
var compressedFileData = JSZip.utils.transformTo(compression.uncompressInputType, this.getCompressedContent());
var uncompressedFileData = compression.uncompress(compressedFileData);
if (uncompressedFileData.length !== uncompressedSize) {
throw new Error("Bug : uncompressed data size mismatch");
}
return uncompressedFileData;
}
},
/**
* Read the local part of a zip file and add the info in this object.
* @param {DataReader} reader the reader to use.
*/
readLocalPart : function(reader) {
var compression, localExtraFieldsLength;
// we already know everything from the central dir !
// If the central dir data are false, we are doomed.
// On the bright side, the local part is scary : zip64, data descriptors, both, etc.
// The less data we get here, the more reliable this should be.
// Let's skip the whole header and dash to the data !
reader.skip(22);
// in some zip created on windows, the filename stored in the central dir contains \ instead of /.
// Strangely, the filename here is OK.
// I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes
// or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators...
// Search "unzip mismatching "local" filename continuing with "central" filename version" on
// the internet.
//
// I think I see the logic here : the central directory is used to display
// content and the local directory is used to extract the files. Mixing / and \
// may be used to display \ to windows users and use / when extracting the files.
// Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394
this.fileNameLength = reader.readInt(2);
localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir
this.fileName = reader.readString(this.fileNameLength);
reader.skip(localExtraFieldsLength);
if (this.compressedSize == -1 || this.uncompressedSize == -1) {
throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory " +
"(compressedSize == -1 || uncompressedSize == -1)");
}
compression = findCompression(this.compressionMethod);
if (compression === null) { // no compression found
throw new Error("Corrupted zip : compression " + pretty(this.compressionMethod) +
" unknown (inner file : " + this.fileName + ")");
}
this.decompressed = new JSZip.CompressedObject();
this.decompressed.compressedSize = this.compressedSize;
this.decompressed.uncompressedSize = this.uncompressedSize;
this.decompressed.crc32 = this.crc32;
this.decompressed.compressionMethod = this.compressionMethod;
this.decompressed.getCompressedContent = this.prepareCompressedContent(reader, reader.index, this.compressedSize, compression);
this.decompressed.getContent = this.prepareContent(reader, reader.index, this.compressedSize, compression, this.uncompressedSize);
// we need to compute the crc32...
if (this.loadOptions.checkCRC32) {
this.decompressed = JSZip.utils.transformTo("string", this.decompressed.getContent());
if (JSZip.prototype.crc32(this.decompressed) !== this.crc32) {
throw new Error("Corrupted zip : CRC32 mismatch");
}
}
},
/**
* Read the central part of a zip file and add the info in this object.
* @param {DataReader} reader the reader to use.
*/
readCentralPart : function(reader) {
this.versionMadeBy = reader.readString(2);
this.versionNeeded = reader.readInt(2);
this.bitFlag = reader.readInt(2);
this.compressionMethod = reader.readString(2);
this.date = reader.readDate();
this.crc32 = reader.readInt(4);
this.compressedSize = reader.readInt(4);
this.uncompressedSize = reader.readInt(4);
this.fileNameLength = reader.readInt(2);
this.extraFieldsLength = reader.readInt(2);
this.fileCommentLength = reader.readInt(2);
this.diskNumberStart = reader.readInt(2);
this.internalFileAttributes = reader.readInt(2);
this.externalFileAttributes = reader.readInt(4);
this.localHeaderOffset = reader.readInt(4);
if (this.isEncrypted()) {
throw new Error("Encrypted zip are not supported");
}
this.fileName = reader.readString(this.fileNameLength);
this.readExtraFields(reader);
this.parseZIP64ExtraField(reader);
this.fileComment = reader.readString(this.fileCommentLength);
// warning, this is true only for zip with madeBy == DOS (plateform dependent feature)
this.dir = this.externalFileAttributes & 0x00000010 ? true : false;
},
/**
* Parse the ZIP64 extra field and merge the info in the current ZipEntry.
* @param {DataReader} reader the reader to use.
*/
parseZIP64ExtraField : function(reader) {
if(!this.extraFields[0x0001]) {
return;
}
// should be something, preparing the extra reader
var extraReader = new StringReader(this.extraFields[0x0001].value);
// I really hope that these 64bits integer can fit in 32 bits integer, because js
// won't let us have more.
if(this.uncompressedSize === MAX_VALUE_32BITS) {
this.uncompressedSize = extraReader.readInt(8);
}
if(this.compressedSize === MAX_VALUE_32BITS) {
this.compressedSize = extraReader.readInt(8);
}
if(this.localHeaderOffset === MAX_VALUE_32BITS) {
this.localHeaderOffset = extraReader.readInt(8);
}
if(this.diskNumberStart === MAX_VALUE_32BITS) {
this.diskNumberStart = extraReader.readInt(4);
}
},
/**
* Read the central part of a zip file and add the info in this object.
* @param {DataReader} reader the reader to use.
*/
readExtraFields : function(reader) {
var start = reader.index,
extraFieldId,
extraFieldLength,
extraFieldValue;
this.extraFields = this.extraFields || {};
while (reader.index < start + this.extraFieldsLength) {
extraFieldId = reader.readInt(2);
extraFieldLength = reader.readInt(2);
extraFieldValue = reader.readString(extraFieldLength);
this.extraFields[extraFieldId] = {
id: extraFieldId,
length: extraFieldLength,
value: extraFieldValue
};
}
},
/**
* Apply an UTF8 transformation if needed.
*/
handleUTF8 : function() {
if (this.useUTF8()) {
this.fileName = JSZip.prototype.utf8decode(this.fileName);
this.fileComment = JSZip.prototype.utf8decode(this.fileComment);
}
}
};
// }}} end of ZipEntry
// class ZipEntries {{{
/**
* All the entries in the zip file.
* @constructor
* @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary data to load.
* @param {Object} loadOptions Options for loading the data.
*/
function ZipEntries(data, loadOptions) {
this.files = [];
this.loadOptions = loadOptions;
if (data) {
this.load(data);
}
}
ZipEntries.prototype = {
/**
* Check that the reader is on the speficied signature.
* @param {string} expectedSignature the expected signature.
* @throws {Error} if it is an other signature.
*/
checkSignature : function(expectedSignature) {
var signature = this.reader.readString(4);
if (signature !== expectedSignature) {
throw new Error("Corrupted zip or bug : unexpected signature " +
"(" + pretty(signature) + ", expected " + pretty(expectedSignature) + ")");
}
},
/**
* Read the end of the central directory.
*/
readBlockEndOfCentral : function () {
this.diskNumber = this.reader.readInt(2);
this.diskWithCentralDirStart = this.reader.readInt(2);
this.centralDirRecordsOnThisDisk = this.reader.readInt(2);
this.centralDirRecords = this.reader.readInt(2);
this.centralDirSize = this.reader.readInt(4);
this.centralDirOffset = this.reader.readInt(4);
this.zipCommentLength = this.reader.readInt(2);
this.zipComment = this.reader.readString(this.zipCommentLength);
},
/**
* Read the end of the Zip 64 central directory.
* Not merged with the method readEndOfCentral :
* The end of central can coexist with its Zip64 brother,
* I don't want to read the wrong number of bytes !
*/
readBlockZip64EndOfCentral : function () {
this.zip64EndOfCentralSize = this.reader.readInt(8);
this.versionMadeBy = this.reader.readString(2);
this.versionNeeded = this.reader.readInt(2);
this.diskNumber = this.reader.readInt(4);
this.diskWithCentralDirStart = this.reader.readInt(4);
this.centralDirRecordsOnThisDisk = this.reader.readInt(8);
this.centralDirRecords = this.reader.readInt(8);
this.centralDirSize = this.reader.readInt(8);
this.centralDirOffset = this.reader.readInt(8);
this.zip64ExtensibleData = {};
var extraDataSize = this.zip64EndOfCentralSize - 44,
index = 0,
extraFieldId,
extraFieldLength,
extraFieldValue;
while(index < extraDataSize) {
extraFieldId = this.reader.readInt(2);
extraFieldLength = this.reader.readInt(4);
extraFieldValue = this.reader.readString(extraFieldLength);
this.zip64ExtensibleData[extraFieldId] = {
id: extraFieldId,
length: extraFieldLength,
value: extraFieldValue
};
}
},
/**
* Read the end of the Zip 64 central directory locator.
*/
readBlockZip64EndOfCentralLocator : function () {
this.diskWithZip64CentralDirStart = this.reader.readInt(4);
this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8);
this.disksCount = this.reader.readInt(4);
if (this.disksCount > 1) {
throw new Error("Multi-volumes zip are not supported");
}
},
/**
* Read the local files, based on the offset read in the central part.
*/
readLocalFiles : function() {
var i, file;
for(i = 0; i < this.files.length; i++) {
file = this.files[i];
this.reader.setIndex(file.localHeaderOffset);
this.checkSignature(JSZip.signature.LOCAL_FILE_HEADER);
file.readLocalPart(this.reader);
file.handleUTF8();
}
},
/**
* Read the central directory.
*/
readCentralDir : function() {
var file;
this.reader.setIndex(this.centralDirOffset);
while(this.reader.readString(4) === JSZip.signature.CENTRAL_FILE_HEADER) {
file = new ZipEntry({
zip64: this.zip64
}, this.loadOptions);
file.readCentralPart(this.reader);
this.files.push(file);
}
},
/**
* Read the end of central directory.
*/
readEndOfCentral : function() {
var offset = this.reader.lastIndexOfSignature(JSZip.signature.CENTRAL_DIRECTORY_END);
if (offset === -1) {
throw new Error("Corrupted zip : can't find end of central directory");
}
this.reader.setIndex(offset);
this.checkSignature(JSZip.signature.CENTRAL_DIRECTORY_END);
this.readBlockEndOfCentral();
/* extract from the zip spec :
4) If one of the fields in the end of central directory
record is too small to hold required data, the field
should be set to -1 (0xFFFF or 0xFFFFFFFF) and the
ZIP64 format record should be created.
5) The end of central directory record and the
Zip64 end of central directory locator record must
reside on the same disk when splitting or spanning
an archive.
*/
if ( this.diskNumber === MAX_VALUE_16BITS
|| this.diskWithCentralDirStart === MAX_VALUE_16BITS
|| this.centralDirRecordsOnThisDisk === MAX_VALUE_16BITS
|| this.centralDirRecords === MAX_VALUE_16BITS
|| this.centralDirSize === MAX_VALUE_32BITS
|| this.centralDirOffset === MAX_VALUE_32BITS
) {
this.zip64 = true;
/*
Warning : the zip64 extension is supported, but ONLY if the 64bits integer read from
the zip file can fit into a 32bits integer. This cannot be solved : Javascript represents
all numbers as 64-bit double precision IEEE 754 floating point numbers.
So, we have 53bits for integers and bitwise operations treat everything as 32bits.
see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators
and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf section 8.5
*/
// should look for a zip64 EOCD locator
offset = this.reader.lastIndexOfSignature(JSZip.signature.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
if (offset === -1) {
throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator");
}
this.reader.setIndex(offset);
this.checkSignature(JSZip.signature.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
this.readBlockZip64EndOfCentralLocator();
// now the zip64 EOCD record
this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir);
this.checkSignature(JSZip.signature.ZIP64_CENTRAL_DIRECTORY_END);
this.readBlockZip64EndOfCentral();
}
},
prepareReader : function (data) {
var type = JSZip.utils.getTypeOf(data);
if (type === "string" && !JSZip.support.uint8array) {
this.reader = new StringReader(data, this.loadOptions.optimizedBinaryString);
} else if (type === "nodebuffer") {
this.reader = new NodeBufferReader(data);
} else {
this.reader = new Uint8ArrayReader(JSZip.utils.transformTo("uint8array", data));
}
},
/**
* Read a zip file and create ZipEntries.
* @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file.
*/
load : function(data) {
this.prepareReader(data);
this.readEndOfCentral();
this.readCentralDir();
this.readLocalFiles();
}
};
// }}} end of ZipEntries
/**
* Implementation of the load method of JSZip.
* It uses the above classes to decode a zip file, and load every files.
* @param {String|ArrayBuffer|Uint8Array|Buffer} data the data to load.
* @param {Object} options Options for loading the data.
* options.base64 : is the data in base64 ? default : false
*/
JSZip.prototype.load = function(data, options) {
var files, zipEntries, i, input;
options = options || {};
if(options.base64) {
data = JSZip.base64.decode(data);
}
zipEntries = new ZipEntries(data, options);
files = zipEntries.files;
for (i = 0; i < files.length; i++) {
input = files[i];
this.file(input.fileName, input.decompressed, {
binary:true,
optimizedBinaryString:true,
date:input.date,
dir:input.dir
});
}
return this;
};
}(this));
// enforcing Stuk's coding style
// vim: set shiftwidth=3 softtabstop=3 foldmethod=marker:
"use strict";
(function () {
if(!JSZip) {
throw "JSZip not defined";
}
var context = {};
(function () {
// https://github.com/imaya/zlib.js
// tag 0.1.6
// file bin/deflate.min.js
/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';var l=void 0,p=this;function q(c,d){var a=c.split("."),b=p;!(a[0]in b)&&b.execScript&&b.execScript("var "+a[0]);for(var e;a.length&&(e=a.shift());)!a.length&&d!==l?b[e]=d:b=b[e]?b[e]:b[e]={}};var r="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array;function u(c){var d=c.length,a=0,b=Number.POSITIVE_INFINITY,e,f,g,h,k,m,s,n,t;for(n=0;n<d;++n)c[n]>a&&(a=c[n]),c[n]<b&&(b=c[n]);e=1<<a;f=new (r?Uint32Array:Array)(e);g=1;h=0;for(k=2;g<=a;){for(n=0;n<d;++n)if(c[n]===g){m=0;s=h;for(t=0;t<g;++t)m=m<<1|s&1,s>>=1;for(t=m;t<e;t+=k)f[t]=g<<16|n;++h}++g;h<<=1;k<<=1}return[f,a,b]};function v(c,d){this.g=[];this.h=32768;this.c=this.f=this.d=this.k=0;this.input=r?new Uint8Array(c):c;this.l=!1;this.i=w;this.p=!1;if(d||!(d={}))d.index&&(this.d=d.index),d.bufferSize&&(this.h=d.bufferSize),d.bufferType&&(this.i=d.bufferType),d.resize&&(this.p=d.resize);switch(this.i){case x:this.a=32768;this.b=new (r?Uint8Array:Array)(32768+this.h+258);break;case w:this.a=0;this.b=new (r?Uint8Array:Array)(this.h);this.e=this.u;this.m=this.r;this.j=this.s;break;default:throw Error("invalid inflate mode");
}}var x=0,w=1;
v.prototype.t=function(){for(;!this.l;){var c=y(this,3);c&1&&(this.l=!0);c>>>=1;switch(c){case 0:var d=this.input,a=this.d,b=this.b,e=this.a,f=l,g=l,h=l,k=b.length,m=l;this.c=this.f=0;f=d[a++];if(f===l)throw Error("invalid uncompressed block header: LEN (first byte)");g=f;f=d[a++];if(f===l)throw Error("invalid uncompressed block header: LEN (second byte)");g|=f<<8;f=d[a++];if(f===l)throw Error("invalid uncompressed block header: NLEN (first byte)");h=f;f=d[a++];if(f===l)throw Error("invalid uncompressed block header: NLEN (second byte)");h|=
f<<8;if(g===~h)throw Error("invalid uncompressed block header: length verify");if(a+g>d.length)throw Error("input buffer is broken");switch(this.i){case x:for(;e+g>b.length;){m=k-e;g-=m;if(r)b.set(d.subarray(a,a+m),e),e+=m,a+=m;else for(;m--;)b[e++]=d[a++];this.a=e;b=this.e();e=this.a}break;case w:for(;e+g>b.length;)b=this.e({o:2});break;default:throw Error("invalid inflate mode");}if(r)b.set(d.subarray(a,a+g),e),e+=g,a+=g;else for(;g--;)b[e++]=d[a++];this.d=a;this.a=e;this.b=b;break;case 1:this.j(z,
A);break;case 2:B(this);break;default:throw Error("unknown BTYPE: "+c);}}return this.m()};
var C=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],D=r?new Uint16Array(C):C,E=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],F=r?new Uint16Array(E):E,G=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],H=r?new Uint8Array(G):G,I=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],J=r?new Uint16Array(I):I,K=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,
13],L=r?new Uint8Array(K):K,M=new (r?Uint8Array:Array)(288),N,O;N=0;for(O=M.length;N<O;++N)M[N]=143>=N?8:255>=N?9:279>=N?7:8;var z=u(M),P=new (r?Uint8Array:Array)(30),Q,R;Q=0;for(R=P.length;Q<R;++Q)P[Q]=5;var A=u(P);function y(c,d){for(var a=c.f,b=c.c,e=c.input,f=c.d,g;b<d;){g=e[f++];if(g===l)throw Error("input buffer is broken");a|=g<<b;b+=8}g=a&(1<<d)-1;c.f=a>>>d;c.c=b-d;c.d=f;return g}
function S(c,d){for(var a=c.f,b=c.c,e=c.input,f=c.d,g=d[0],h=d[1],k,m,s;b<h;){k=e[f++];if(k===l)break;a|=k<<b;b+=8}m=g[a&(1<<h)-1];s=m>>>16;c.f=a>>s;c.c=b-s;c.d=f;return m&65535}
function B(c){function d(a,c,b){var d,f,e,g;for(g=0;g<a;)switch(d=S(this,c),d){case 16:for(e=3+y(this,2);e--;)b[g++]=f;break;case 17:for(e=3+y(this,3);e--;)b[g++]=0;f=0;break;case 18:for(e=11+y(this,7);e--;)b[g++]=0;f=0;break;default:f=b[g++]=d}return b}var a=y(c,5)+257,b=y(c,5)+1,e=y(c,4)+4,f=new (r?Uint8Array:Array)(D.length),g,h,k,m;for(m=0;m<e;++m)f[D[m]]=y(c,3);g=u(f);h=new (r?Uint8Array:Array)(a);k=new (r?Uint8Array:Array)(b);c.j(u(d.call(c,a,g,h)),u(d.call(c,b,g,k)))}
v.prototype.j=function(c,d){var a=this.b,b=this.a;this.n=c;for(var e=a.length-258,f,g,h,k;256!==(f=S(this,c));)if(256>f)b>=e&&(this.a=b,a=this.e(),b=this.a),a[b++]=f;else{g=f-257;k=F[g];0<H[g]&&(k+=y(this,H[g]));f=S(this,d);h=J[f];0<L[f]&&(h+=y(this,L[f]));b>=e&&(this.a=b,a=this.e(),b=this.a);for(;k--;)a[b]=a[b++-h]}for(;8<=this.c;)this.c-=8,this.d--;this.a=b};
v.prototype.s=function(c,d){var a=this.b,b=this.a;this.n=c;for(var e=a.length,f,g,h,k;256!==(f=S(this,c));)if(256>f)b>=e&&(a=this.e(),e=a.length),a[b++]=f;else{g=f-257;k=F[g];0<H[g]&&(k+=y(this,H[g]));f=S(this,d);h=J[f];0<L[f]&&(h+=y(this,L[f]));b+k>e&&(a=this.e(),e=a.length);for(;k--;)a[b]=a[b++-h]}for(;8<=this.c;)this.c-=8,this.d--;this.a=b};
v.prototype.e=function(){var c=new (r?Uint8Array:Array)(this.a-32768),d=this.a-32768,a,b,e=this.b;if(r)c.set(e.subarray(32768,c.length));else{a=0;for(b=c.length;a<b;++a)c[a]=e[a+32768]}this.g.push(c);this.k+=c.length;if(r)e.set(e.subarray(d,d+32768));else for(a=0;32768>a;++a)e[a]=e[d+a];this.a=32768;return e};
v.prototype.u=function(c){var d,a=this.input.length/this.d+1|0,b,e,f,g=this.input,h=this.b;c&&("number"===typeof c.o&&(a=c.o),"number"===typeof c.q&&(a+=c.q));2>a?(b=(g.length-this.d)/this.n[2],f=258*(b/2)|0,e=f<h.length?h.length+f:h.length<<1):e=h.length*a;r?(d=new Uint8Array(e),d.set(h)):d=h;return this.b=d};
v.prototype.m=function(){var c=0,d=this.b,a=this.g,b,e=new (r?Uint8Array:Array)(this.k+(this.a-32768)),f,g,h,k;if(0===a.length)return r?this.b.subarray(32768,this.a):this.b.slice(32768,this.a);f=0;for(g=a.length;f<g;++f){b=a[f];h=0;for(k=b.length;h<k;++h)e[c++]=b[h]}f=32768;for(g=this.a;f<g;++f)e[c++]=d[f];this.g=[];return this.buffer=e};
v.prototype.r=function(){var c,d=this.a;r?this.p?(c=new Uint8Array(d),c.set(this.b.subarray(0,d))):c=this.b.subarray(0,d):(this.b.length>d&&(this.b.length=d),c=this.b);return this.buffer=c};q("Zlib.RawInflate",v);q("Zlib.RawInflate.prototype.decompress",v.prototype.t);var T={ADAPTIVE:w,BLOCK:x},U,V,W,X;if(Object.keys)U=Object.keys(T);else for(V in U=[],W=0,T)U[W++]=V;W=0;for(X=U.length;W<X;++W)V=U[W],q("Zlib.RawInflate.BufferType."+V,T[V]);}).call(this);
}).call(context);
var uncompress = function (input) {
var inflate = new context.Zlib.RawInflate(input);
return inflate.decompress();
};
var USE_TYPEDARRAY =
(typeof Uint8Array !== 'undefined') &&
(typeof Uint16Array !== 'undefined') &&
(typeof Uint32Array !== 'undefined');
// we add the compression method for JSZip
if(!JSZip.compressions["DEFLATE"]) {
JSZip.compressions["DEFLATE"] = {
magic : "\x08\x00",
uncompress : uncompress,
uncompressInputType : USE_TYPEDARRAY ? "uint8array" : "array"
}
} else {
JSZip.compressions["DEFLATE"].uncompress = uncompress;
JSZip.compressions["DEFLATE"].uncompressInputType = USE_TYPEDARRAY ? "uint8array" : "array";
}
})();
// enforcing Stuk's coding style
// vim: set shiftwidth=3 softtabstop=3:
// 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.
'use strict';
/**
* @fileoverview ZipImporter inflates zip compressed data and passes it along
* to an actual importer.
*/
base.requireRawScript('../third_party/jszip/jszip.js');
base.requireRawScript('../third_party/jszip/jszip-load.js');
base.requireRawScript('../third_party/jszip/jszip-inflate.js');
base.require('tracing.importer.importer');
base.require('tracing.importer.gzip_importer');
base.require('tracing.trace_model');
base.exportTo('tracing.importer', function() {
var Importer = tracing.importer.Importer;
function ZipImporter(model, eventData) {
if (eventData instanceof ArrayBuffer)
eventData = new Uint8Array(eventData);
else if (typeof(eventData) === 'string' || eventData instanceof String)
eventData = tracing.importer.GzipImporter.unescapeData_(eventData);
this.model_ = model;
this.eventData_ = eventData;
}
/**
* @param {eventData} string Possibly zip compressed data.
* @return {boolean} Whether eventData looks like zip compressed data.
*/
ZipImporter.canImport = function(eventData) {
var header;
if (eventData instanceof ArrayBuffer)
header = new Uint8Array(eventData.slice(0, 2));
else if (typeof(eventData) === 'string' || eventData instanceof String)
header = [eventData.charCodeAt(0), eventData.charCodeAt(1)];
else
return false;
return header[0] === 'P'.charCodeAt(0) && header[1] === 'K'.charCodeAt(0);
};
ZipImporter.prototype = {
__proto__: Importer.prototype,
extractSubtraces: function() {
var zip = new JSZip(this.eventData_);
var subtraces = [];
for (var idx in zip.files)
subtraces.push(zip.files[idx].asText());
return subtraces;
}
};
tracing.TraceModel.registerImporter(ZipImporter);
return {
ZipImporter: ZipImporter
};
});
// 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.
'use strict';
base.require('tracing.importer.gzip_importer');
base.require('tracing.importer.zip_importer');
base.require('tracing.importer.linux_perf_importer');
base.require('tracing.importer.trace_event_importer');
base.require('tracing.importer.v8_log_importer');
// 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.
'use strict';
/**
* @fileoverview Helper functions for use in selection_analysis files.
*/
base.exportTo('tracing.analysis', function() {
function tsRound(ts) {
return Math.round(ts * 1000.0) / 1000.0;
}
return {
tsRound: tsRound
};
});
// 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.
'use strict';
/**
* @fileoverview Code for the viewport.
*/
base.require('base.events');
base.require('base.guid');
base.require('base.range');
base.require('tracing.trace_model.instant_event');
base.require('tracing.trace_model');
base.exportTo('tracing', function() {
var EVENT_TYPES = [
{
constructor: tracing.trace_model.Slice,
name: 'slice',
pluralName: 'slices'
},
{
constructor: tracing.trace_model.InstantEvent,
name: 'instantEvent',
pluralName: 'instantEvents'
},
{
constructor: tracing.trace_model.CounterSample,
name: 'counterSample',
pluralName: 'counterSamples'
},
{
constructor: tracing.trace_model.ObjectSnapshot,
name: 'objectSnapshot',
pluralName: 'objectSnapshots'
},
{
constructor: tracing.trace_model.ObjectInstance,
name: 'objectInstance',
pluralName: 'objectInstances'
},
{
constructor: tracing.trace_model.Sample,
name: 'sample',
pluralName: 'samples'
}
];
/**
* Represents a selection within a and its associated set of tracks.
* @constructor
*/
function Selection(opt_events) {
this.bounds_dirty_ = true;
this.bounds_ = new base.Range();
this.length_ = 0;
this.guid_ = base.GUID.allocate();
if (opt_events) {
for (var i = 0; i < opt_events.length; i++)
this.push(opt_events[i]);
}
}
Selection.prototype = {
__proto__: Object.prototype,
get bounds() {
if (this.bounds_dirty_) {
this.bounds_.reset();
for (var i = 0; i < this.length_; i++)
this[i].addBoundsToRange(this.bounds_);
this.bounds_dirty_ = false;
}
return this.bounds_;
},
get duration() {
if (this.bounds_.isEmpty)
return 0;
return this.bounds_.max - this.bounds_.min;
},
get length() {
return this.length_;
},
get guid() {
return this.guid_;
},
clear: function() {
for (var i = 0; i < this.length_; ++i)
delete this[i];
this.length_ = 0;
this.bounds_dirty_ = true;
},
push: function(event) {
this[this.length_++] = event;
this.bounds_dirty_ = true;
return event;
},
addSelection: function(selection) {
for (var i = 0; i < selection.length; i++)
this.push(selection[i]);
},
subSelection: function(index, count) {
count = count || 1;
var selection = new Selection();
selection.bounds_dirty_ = true;
if (index < 0 || index + count > this.length_)
throw new Error('Index out of bounds');
for (var i = index; i < index + count; i++)
selection.push(this[i]);
return selection;
},
getEventsOrganizedByType: function() {
var events = {};
EVENT_TYPES.forEach(function(eventType) {
events[eventType.pluralName] = new Selection();
});
for (var i = 0; i < this.length_; i++) {
var event = this[i];
EVENT_TYPES.forEach(function(eventType) {
if (event instanceof eventType.constructor)
events[eventType.pluralName].push(event);
});
}
return events;
},
enumEventsOfType: function(type, func) {
for (var i = 0; i < this.length_; i++)
if (this[i] instanceof type)
func(this[i]);
},
map: function(fn) {
for (var i = 0; i < this.length_; i++)
fn(this[i]);
},
/**
* Helper for selection previous or next.
* @param {boolean} forwardp If true, select one forward (next).
* Else, select previous.
*
* @param {TimelineViewport} viewport The viewport to use to determine what
* is near to the current selection.
*
* @return {boolean} true if current selection changed.
*/
getShiftedSelection: function(viewport, offset) {
var newSelection = new Selection();
for (var i = 0; i < this.length_; i++) {
var event = this[i];
var track = viewport.trackForEvent(event);
track.addItemNearToProvidedEventToSelection(
event, offset, newSelection);
}
if (newSelection.length == 0)
return undefined;
return newSelection;
}
};
return {
Selection: Selection
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.analysis.analysis_link');
base.require('base.events');
base.require('tracing.selection');
base.require('tracing.analysis.util');
base.require('ui');
base.exportTo('tracing.analysis', function() {
var tsRound = tracing.analysis.tsRound;
var RequestSelectionChangeEvent = base.Event.bind(
undefined, 'requestSelectionChange', true, false);
/**
* A clickable link that requests a change of selection to the return value of
* this.selectionGenerator when clicked.
*
* @constructor
*/
var AnalysisLink = ui.define('a');
AnalysisLink.prototype = {
__proto__: HTMLAnchorElement.prototype,
decorate: function() {
this.classList.add('analysis-link');
this.selectionGenerator;
this.addEventListener('click', this.onClicked_.bind(this));
},
onClicked_: function() {
var event = new RequestSelectionChangeEvent();
event.selection = this.selectionGenerator();
this.dispatchEvent(event);
}
};
/**
* Changes the selection to the given ObjectSnapshot when clicked.
* @constructor
*/
var ObjectSnapshotLink = ui.define(
'object-snapshot-link', AnalysisLink);
ObjectSnapshotLink.prototype = {
__proto__: AnalysisLink.prototype,
decorate: function() {
AnalysisLink.prototype.decorate.apply(this);
},
set objectSnapshot(snapshot) {
this.textContent =
snapshot.objectInstance.typeName + ' ' +
snapshot.objectInstance.id + ' @ ' +
tsRound(snapshot.ts) + ' ms';
this.selectionGenerator = function() {
var selection = new tracing.Selection();
selection.push(snapshot);
return selection;
}.bind(this);
}
};
/**
* Changes the selection to the given ObjectInstance when clicked.
* @constructor
*/
var ObjectInstanceLink = ui.define(
'object-instance-link', AnalysisLink);
ObjectInstanceLink.prototype = {
__proto__: AnalysisLink.prototype,
decorate: function() {
AnalysisLink.prototype.decorate.apply(this);
},
set objectInstance(instance) {
this.textContent = instance.typeName + ' ' + instance.id;
this.selectionGenerator = function() {
var selection = new tracing.Selection();
selection.push(instance);
return selection;
}.bind(this);
}
};
return {
RequestSelectionChangeEvent: RequestSelectionChangeEvent,
AnalysisLink: AnalysisLink,
ObjectSnapshotLink: ObjectSnapshotLink,
ObjectInstanceLink: ObjectInstanceLink
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.analysis.generic_object_view');
base.require('base.utils');
base.require('tracing.analysis.analysis_link');
base.require('ui');
base.exportTo('tracing.analysis', function() {
/**
* @constructor
*/
var GenericObjectView = ui.define('x-generic-object-view');
GenericObjectView.prototype = {
__proto__: HTMLUnknownElement.prototype,
decorate: function() {
this.object_ = undefined;
},
get object() {
return this.object_;
},
set object(object) {
this.object_ = object;
this.updateContents_();
},
updateContents_: function() {
this.textContent = '';
this.appendElementsForType_('', this.object_, 0, 0, 5, '');
},
appendElementsForType_: function(
label, object, indent, depth, maxDepth, suffix) {
if (depth > maxDepth) {
this.appendSimpleText_(
label, indent, '<recursion limit reached>', suffix);
return;
}
if (object === undefined) {
this.appendSimpleText_(label, indent, 'undefined', suffix);
return;
}
if (object === null) {
this.appendSimpleText_(label, indent, 'null', suffix);
return;
}
if (!(object instanceof Object)) {
var type = typeof object;
if (type == 'string') {
this.appendSimpleText_(label, indent, '"' + object + '"', suffix);
} else {
this.appendSimpleText_(label, indent, object, suffix);
}
return;
}
if (object instanceof tracing.trace_model.ObjectSnapshot) {
var link = new tracing.analysis.ObjectSnapshotLink(object);
link.objectSnapshot = object;
this.appendElementWithLabel_(label, indent, link, suffix);
return;
}
if (object instanceof tracing.trace_model.ObjectInstance) {
var link = new tracing.analysis.ObjectInstanceLink(object);
link.objectInstance = object;
this.appendElementWithLabel_(label, indent, link, suffix);
return;
}
if (object instanceof base.Rect) {
this.appendSimpleText_(label, indent, object.toString(), suffix);
return;
}
if (object instanceof Array) {
this.appendElementsForArray_(
label, object, indent, depth, maxDepth, suffix);
return;
}
this.appendElementsForObject_(
label, object, indent, depth, maxDepth, suffix);
},
appendElementsForArray_: function(
label, object, indent, depth, maxDepth, suffix) {
if (object.length == 0) {
this.appendSimpleText_(label, indent, '[]', suffix);
return;
}
this.appendElementsForType_(
label + '[',
object[0],
indent, depth + 1, maxDepth,
object.length > 1 ? ',' : ']' + suffix);
for (var i = 1; i < object.length; i++) {
this.appendElementsForType_(
'',
object[i],
indent + label.length + 1, depth + 1, maxDepth,
i < object.length - 1 ? ',' : ']' + suffix);
}
return;
},
appendElementsForObject_: function(
label, object, indent, depth, maxDepth, suffix) {
var keys = base.dictionaryKeys(object);
if (keys.length == 0) {
this.appendSimpleText_(label, indent, '{}', suffix);
return;
}
this.appendElementsForType_(
label + '{' + keys[0] + ': ',
object[keys[0]],
indent, depth, maxDepth,
keys.length > 1 ? ',' : '}' + suffix);
for (var i = 1; i < keys.length; i++) {
this.appendElementsForType_(
keys[i] + ': ',
object[keys[i]],
indent + label.length + 1, depth + 1, maxDepth,
i < keys.length - 1 ? ',' : '}' + suffix);
}
},
appendElementWithLabel_: function(label, indent, dataElement, suffix) {
var row = document.createElement('div');
var indentSpan = document.createElement('span');
indentSpan.style.whiteSpace = 'pre';
for (var i = 0; i < indent; i++)
indentSpan.textContent += ' ';
row.appendChild(indentSpan);
var labelSpan = document.createElement('span');
labelSpan.textContent = label;
row.appendChild(labelSpan);
row.appendChild(dataElement);
var suffixSpan = document.createElement('span');
suffixSpan.textContent = suffix;
row.appendChild(suffixSpan);
row.dataElement = dataElement;
this.appendChild(row);
},
appendSimpleText_: function(label, indent, text, suffix) {
var el = this.ownerDocument.createElement('span');
el.textContent = text;
this.appendElementWithLabel_(label, indent, el, suffix);
return el;
}
};
/**
* @constructor
*/
var GenericObjectViewWithLabel = ui.define(
'x-generic-object-view-with-label');
GenericObjectViewWithLabel.prototype = {
__proto__: HTMLUnknownElement.prototype,
decorate: function() {
this.labelEl_ = document.createElement('div');
this.genericObjectView_ = new tracing.analysis.GenericObjectView();
this.appendChild(this.labelEl_);
this.appendChild(this.genericObjectView_);
},
get label() {
return this.labelEl_.textContent;
},
set label(label) {
this.labelEl_.textContent = label;
},
get object() {
return this.genericObjectView_.object;
},
set object(object) {
this.genericObjectView_.object = object;
}
};
return {
GenericObjectView: GenericObjectView,
GenericObjectViewWithLabel: GenericObjectViewWithLabel
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.analysis.analysis_results');
base.require('tracing.analysis.util');
base.require('tracing.analysis.analysis_link');
base.require('tracing.analysis.generic_object_view');
base.require('ui');
base.exportTo('tracing.analysis', function() {
var AnalysisResults = ui.define('div');
AnalysisResults.prototype = {
__proto__: HTMLDivElement.prototype,
decorate: function() {
this.className = 'analysis-results';
},
get requiresTallView() {
return false;
},
clear: function() {
this.textContent = '';
},
createSelectionChangingLink: function(text, selectionGenerator,
opt_tooltip) {
var el = this.ownerDocument.createElement('a');
tracing.analysis.AnalysisLink.decorate(el);
el.textContent = text;
el.selectionGenerator = selectionGenerator;
if (opt_tooltip)
el.title = opt_tooltip;
return el;
},
appendElement_: function(parent, tagName, opt_text) {
var n = parent.ownerDocument.createElement(tagName);
parent.appendChild(n);
if (opt_text != undefined)
n.textContent = opt_text;
return n;
},
appendText_: function(parent, text) {
var textElement = parent.ownerDocument.createTextNode(text);
parent.appendChild(textNode);
return textNode;
},
appendTableCell_: function(table, row, cellnum, text) {
var td = this.appendElement_(row, 'td', text);
td.className = table.className + '-col-' + cellnum;
return td;
},
appendTableCell: function(table, row, text) {
return this.appendTableCell_(table, row, row.children.length, text);
},
appendTableCellWithTooltip_: function(table, row, cellnum, text, tooltip) {
if (tooltip) {
var td = this.appendElement_(row, 'td');
td.className = table.className + '-col-' + cellnum;
var span = this.appendElement_(td, 'span', text);
span.className = 'tooltip';
span.title = tooltip;
return td;
} else {
this.appendTableCell_(table, row, cellnum, text);
}
},
/**
* Adds a table with the given className.
* @return {HTMLTableElement} The newly created table.
*/
appendTable: function(className, numColumns) {
var table = this.appendElement_(this, 'table');
table.headerRow = this.appendElement_(table, 'tr');
table.className = className + ' analysis-table';
table.numColumns = numColumns;
return table;
},
/**
* Creates and appends a row to |table| with a left-aligned |label]
* header that spans all columns.
*/
appendTableHeader: function(table, label) {
var th = this.appendElement_(table.headerRow, 'th', label);
th.className = 'analysis-table-header';
},
appendTableRow: function(table) {
return this.appendElement_(table, 'tr');
},
/**
* Creates and appends a row to |table| with a left-aligned |label]
* in the first column and an optional |opt_value| in the second
* column.
*/
appendSummaryRow: function(table, label, opt_value) {
var row = this.appendElement_(table, 'tr');
row.className = 'analysis-table-row';
this.appendTableCell_(table, row, 0, label);
if (opt_value !== undefined) {
var objectView = new tracing.analysis.GenericObjectView();
objectView.object = opt_value;
objectView.classList.add('analysis-table-col-1');
objectView.style.display = 'table-cell';
row.appendChild(objectView);
} else {
this.appendTableCell_(table, row, 1, '');
}
for (var i = 2; i < table.numColumns; i++)
this.appendTableCell_(table, row, i, '');
},
/**
* Adds a spacing row to spread out results.
*/
appendSpacingRow: function(table) {
var row = this.appendElement_(table, 'tr');
row.className = 'analysis-table-row';
for (var i = 0; i < table.numColumns; i++)
this.appendTableCell_(table, row, i, ' ');
},
/**
* Creates and appends a row to |table| with a left-aligned |label]
* in the first column and a millisecvond |time| value in the second
* column.
*/
appendSummaryRowTime: function(table, label, time) {
this.appendSummaryRow(table, label,
tracing.analysis.tsRound(time) + ' ms');
},
/**
* Creates and appends a row to |table| that summarizes one or more slices,
* or one or more counters.
* The row has a left-aligned |label| in the first column, the |duration|
* of the data in the second, the number of |occurrences| in the third.
* @param {object=} opt_statistics May be undefined, or an object which
* contains calculated staistics containing min/max/avg for slices, or
* min/max/avg/start/end for counters.
*/
appendDataRow: function(
table, label, opt_duration, opt_occurences,
opt_statistics, opt_selectionGenerator) {
var tooltip = undefined;
if (opt_statistics) {
tooltip = 'Min Duration:\u0009' +
tracing.analysis.tsRound(opt_statistics.min) +
' ms \u000DMax Duration:\u0009' +
tracing.analysis.tsRound(opt_statistics.max) +
' ms \u000DAvg Duration:\u0009' +
tracing.analysis.tsRound(opt_statistics.avg) +
' ms (\u03C3 = ' +
tracing.analysis.tsRound(opt_statistics.avg_stddev) + ')';
if (opt_statistics.start) {
tooltip += '\u000DStart Time:\u0009' +
tracing.analysis.tsRound(opt_statistics.start) + ' ms';
}
if (opt_statistics.end) {
tooltip += '\u000DEnd Time:\u0009' +
tracing.analysis.tsRound(opt_statistics.end) + ' ms';
}
if (opt_statistics.frequency && opt_statistics.frequency_stddev) {
tooltip += '\u000DFrequency:\u0009' +
tracing.analysis.tsRound(opt_statistics.frequency) +
' occurrences/s (\u03C3 = ' +
tracing.analysis.tsRound(opt_statistics.frequency_stddev) + ')';
}
}
var row = this.appendElement_(table, 'tr');
row.className = 'analysis-table-row';
if (!opt_selectionGenerator) {
this.appendTableCellWithTooltip_(table, row, 0, label, tooltip);
} else {
var labelEl = this.appendTableCellWithTooltip_(
table, row, 0, label, tooltip);
labelEl.textContent = '';
labelEl.appendChild(
this.createSelectionChangingLink(label, opt_selectionGenerator,
tooltip));
}
if (opt_duration !== undefined) {
if (opt_duration instanceof Array) {
this.appendTableCellWithTooltip_(table, row, 1,
'[' + opt_duration.join(', ') + ']', tooltip);
} else {
this.appendTableCellWithTooltip_(table, row, 1,
tracing.analysis.tsRound(opt_duration) + ' ms', tooltip);
}
} else {
this.appendTableCell_(table, row, 1, '');
}
if (opt_occurences !== undefined) {
this.appendTableCellWithTooltip_(table, row, 2,
String(opt_occurences) + ' occurrences', tooltip);
} else {
this.appendTableCell_(table, row, 2, '');
}
}
};
return {
AnalysisResults: AnalysisResults
};
});
// 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.
'use strict';
base.require('tracing.analysis.util');
base.require('ui');
base.require('tracing.trace_model.counter_sample');
base.exportTo('tracing.analysis', function() {
var CounterSample = tracing.trace_model.CounterSample;
function analyzeCounterSamples(results, allSamples) {
var samplesByCounter = {};
for (var i = 0; i < allSamples.length; i++) {
var ctr = allSamples[i].series.counter;
if (!samplesByCounter[ctr.guid])
samplesByCounter[ctr.guid] = [];
samplesByCounter[ctr.guid].push(allSamples[i]);
}
for (var guid in samplesByCounter) {
var samples = samplesByCounter[guid];
var ctr = samples[0].series.counter;
var timestampGroups = CounterSample.groupByTimestamp(samples);
if (timestampGroups.length == 1)
analyzeSingleCounterTimestamp(results, ctr, timestampGroups[0]);
else
analyzeMultipleCounterTimestamps(results, ctr, timestampGroups);
}
}
function analyzeSingleCounterTimestamp(
results, ctr, samplesWithSameTimestamp) {
var table = results.appendTable('analysis-counter-table', 2);
results.appendTableHeader(table, 'Selected counter:');
results.appendSummaryRow(table, 'Title', ctr.name);
results.appendSummaryRowTime(
table, 'Timestamp', samplesWithSameTimestamp[0].timestamp);
for (var i = 0; i < samplesWithSameTimestamp.length; i++) {
var sample = samplesWithSameTimestamp[i];
results.appendSummaryRow(table, sample.series.name,
sample.value);
}
}
function analyzeMultipleCounterTimestamps(results, ctr, samplesByTimestamp) {
var table = results.appendTable('analysis-counter-table', 2);
results.appendTableHeader(table, 'Counter ' + ctr.name);
var sampleIndices = [];
for (var i = 0; i < samplesByTimestamp.length; i++)
sampleIndices.push(samplesByTimestamp[i][0].getSampleIndex());
var stats = ctr.getSampleStatistics(sampleIndices);
for (var i = 0; i < stats.length; i++) {
var samples = [];
for (var k = 0; k < sampleIndices.length; ++k)
samples.push(ctr.getSeries(i).getSample(sampleIndices[k]).value);
results.appendDataRow(
table,
ctr.name + ': series(' + ctr.getSeries(i).name + ')',
samples,
samples.length,
stats[i]);
}
}
return {
analyzeCounterSamples: analyzeCounterSamples,
analyzeSingleCounterTimestamp: analyzeSingleCounterTimestamp,
analyzeMultipleCounterTimestamps: analyzeMultipleCounterTimestamps
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.analysis.analyze_slices');
base.require('tracing.analysis.util');
base.require('ui');
base.exportTo('tracing.analysis', function() {
function analyzeSingleSlice(results, slice) {
var table = results.appendTable('analysis-slice-table', 2);
results.appendTableHeader(table, 'Selected slice:');
results.appendSummaryRow(table, 'Title', slice.title);
if (slice.category)
results.appendSummaryRow(table, 'Category', slice.category);
results.appendSummaryRowTime(table, 'Start', slice.start);
results.appendSummaryRowTime(table, 'Duration', slice.duration);
if (slice.durationInUserTime) {
results.appendSummaryRowTime(
table, 'Duration (U)', slice.durationInUserTime);
}
var n = 0;
for (var argName in slice.args) {
n += 1;
}
if (n > 0) {
results.appendSummaryRow(table, 'Args');
for (var argName in slice.args) {
var argVal = slice.args[argName];
// TODO(sleffler) use span instead?
results.appendSummaryRow(table, ' ' + argName, argVal);
}
}
}
function analyzeMultipleSlices(results, slices) {
var tsLo = slices.bounds.min;
var tsHi = slices.bounds.max;
var numTitles = 0;
var slicesByTitle = {};
for (var i = 0; i < slices.length; i++) {
var slice = slices[i];
if (slicesByTitle[slice.title] === undefined) {
slicesByTitle[slice.title] = [];
numTitles++;
}
var sliceGroup = slicesByTitle[slice.title];
sliceGroup.push(slices[i]);
}
var table;
table = results.appendTable('analysis-slices-table', 3);
results.appendTableHeader(table, 'Slices:');
var totalDuration = 0;
base.iterItems(slicesByTitle,
function(sliceGroupTitle, sliceGroup) {
var duration = 0;
var avg = 0;
var startOfFirstOccurrence = Number.MAX_VALUE;
var startOfLastOccurrence = -Number.MAX_VALUE;
var frequencyDetails = undefined;
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
for (var i = 0; i < sliceGroup.length; i++) {
var slice = sliceGroup[i];
duration += slice.duration;
startOfFirstOccurrence = Math.min(slice.start,
startOfFirstOccurrence);
startOfLastOccurrence = Math.max(slice.start,
startOfLastOccurrence);
min = Math.min(slice.duration, min);
max = Math.max(slice.duration, max);
}
totalDuration += duration;
if (sliceGroup.length == 0)
avg = 0;
avg = duration / sliceGroup.length;
var statistics = {min: min,
max: max,
avg: avg,
avg_stddev: undefined,
frequency: undefined,
frequency_stddev: undefined};
// Compute the stddev of the slice durations.
var sumOfSquaredDistancesToMean = 0;
for (var i = 0; i < sliceGroup.length; i++) {
var signedDistance =
statistics.avg - sliceGroup[i].duration;
sumOfSquaredDistancesToMean += signedDistance * signedDistance;
}
statistics.avg_stddev = Math.sqrt(
sumOfSquaredDistancesToMean / (sliceGroup.length - 1));
// We require at least 3 samples to compute the stddev.
var elapsed = startOfLastOccurrence - startOfFirstOccurrence;
if (sliceGroup.length > 2 && elapsed > 0) {
var numDistances = sliceGroup.length - 1;
statistics.frequency = (1000 * numDistances) / elapsed;
// Compute the stddev.
sumOfSquaredDistancesToMean = 0;
for (var i = 1; i < sliceGroup.length; i++) {
var currentFrequency = 1000 /
(sliceGroup[i].start -
sliceGroup[i - 1].start);
var signedDistance = statistics.frequency - currentFrequency;
sumOfSquaredDistancesToMean += signedDistance * signedDistance;
}
statistics.frequency_stddev = Math.sqrt(
sumOfSquaredDistancesToMean / (numDistances - 1));
}
results.appendDataRow(
table, sliceGroupTitle, duration, sliceGroup.length,
statistics,
function() {
return new tracing.Selection(sliceGroup);
});
// The whole selection is a single type so list out the information
// for each sub slice.
if (numTitles === 1) {
for (var i = 0; i < sliceGroup.length; i++) {
analyzeSingleSlice(results, sliceGroup[i]);
}
}
});
// Only one row so we already know the totals.
if (numTitles !== 1) {
results.appendDataRow(table, '*Totals', totalDuration, slices.length);
results.appendSpacingRow(table);
}
results.appendSummaryRowTime(table, 'Selection start', tsLo);
results.appendSummaryRowTime(table, 'Selection extent', tsHi - tsLo);
}
return {
analyzeSingleSlice: analyzeSingleSlice,
analyzeMultipleSlices: analyzeMultipleSlices
};
});
// 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.
'use strict';
base.require('tracing.analysis.analyze_counters');
base.require('tracing.analysis.analyze_slices');
base.require('tracing.analysis.util');
base.require('ui');
base.exportTo('tracing.analysis', function() {
/**
* Analyzes the selection, outputting the analysis results into the provided
* results object.
*
* @param {AnalysisResults} results Where the analysis is placed.
* @param {Selection} selection What to analyze.
*/
function analyzeSelection(results, selection) {
analyzeEventsByType(results, selection.getEventsOrganizedByType());
}
function analyzeEventsByType(results, eventsByType) {
var sliceEvents = eventsByType.slices;
var counterSampleEvents = eventsByType.counterSamples;
var instantEvents = eventsByType.instantEvents;
var sampleEvents = eventsByType.samples;
var objectEvents = new tracing.Selection();
objectEvents.addSelection(eventsByType.objectSnapshots);
objectEvents.addSelection(eventsByType.objectInstances);
if (sliceEvents.length == 1) {
tracing.analysis.analyzeSingleSlice(results, sliceEvents[0]);
} else if (sliceEvents.length > 1) {
tracing.analysis.analyzeMultipleSlices(results, sliceEvents);
}
if (instantEvents.length == 1) {
tracing.analysis.analyzeSingleSlice(results, instantEvents[0]);
} else if (instantEvents.length > 1) {
tracing.analysis.analyzeMultipleSlices(results, instantEvents);
}
if (sampleEvents.length == 1) {
tracing.analysis.analyzeSingleSlice(results, sampleEvents[0]);
} else if (sampleEvents.length > 1) {
tracing.analysis.analyzeMultipleSlices(results, sampleEvents);
}
if (counterSampleEvents.length != 0)
tracing.analysis.analyzeCounterSamples(results, counterSampleEvents);
if (objectEvents.length)
analyzeObjectEvents(results, objectEvents);
}
/**
* Extremely simplistic analysis of objects. Mainly exists to provide
* click-through to the main object's analysis view.
*/
function analyzeObjectEvents(results, objectEvents) {
objectEvents = base.asArray(objectEvents).sort(
base.Range.compareByMinTimes);
var table = results.appendTable('analysis-object-sample-table', 2);
results.appendTableHeader(table, 'Selected Objects:');
objectEvents.forEach(function(event) {
var row = results.appendTableRow(table);
var ts;
var objectText;
var selectionGenerator;
if (event instanceof tracing.trace_model.ObjectSnapshot) {
var objectSnapshot = event;
ts = tracing.analysis.tsRound(objectSnapshot.ts);
objectText = objectSnapshot.objectInstance.typeName + ' ' +
objectSnapshot.objectInstance.id;
selectionGenerator = function() {
var selection = new tracing.Selection();
selection.push(objectSnapshot);
return selection;
};
} else {
var objectInstance = event;
var deletionTs = objectInstance.deletionTs == Number.MAX_VALUE ?
'' : tracing.analysis.tsRound(objectInstance.deletionTs);
ts = tracing.analysis.tsRound(objectInstance.creationTs) +
'-' + deletionTs;
objectText = objectInstance.typeName + ' ' +
objectInstance.id;
selectionGenerator = function() {
var selection = new tracing.Selection();
selection.push(objectInstance);
return selection;
};
}
results.appendTableCell(table, row, ts);
var linkContainer = results.appendTableCell(table, row, '');
linkContainer.appendChild(
results.createSelectionChangingLink(objectText, selectionGenerator));
});
}
return {
analyzeSelection: analyzeSelection,
analyzeEventsByType: analyzeEventsByType
};
});
// 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.
'use strict';
base.require('ui');
base.exportTo('tracing.analysis', function() {
var ObjectInstanceView = ui.define('object-instance-view');
ObjectInstanceView.prototype = {
__proto__: HTMLDivElement.prototype,
decorate: function() {
this.objectInstance_ = undefined;
},
get requiresTallView() {
return true;
},
set modelEvent(obj) {
this.objectInstance = obj;
},
get modelEvent() {
return this.objectInstance;
},
get objectInstance() {
return this.objectInstance_;
},
set objectInstance(i) {
this.objectInstance_ = i;
this.updateContents();
},
updateContents: function() {
throw new Error('Not implemented');
}
};
ObjectInstanceView.typeNameToViewInfoMap = {};
ObjectInstanceView.register = function(typeName,
viewConstructor,
opt_options) {
if (ObjectInstanceView.typeNameToViewInfoMap[typeName])
throw new Error('Handler already registerd for ' + typeName);
var options = opt_options || {
showInTrackView: true
};
ObjectInstanceView.typeNameToViewInfoMap[typeName] = {
constructor: viewConstructor,
options: options
};
};
ObjectInstanceView.unregister = function(typeName) {
if (ObjectInstanceView.typeNameToViewInfoMap[typeName] === undefined)
throw new Error(typeName + ' not registered');
delete ObjectInstanceView.typeNameToViewInfoMap[typeName];
};
ObjectInstanceView.getViewInfo = function(typeName) {
return ObjectInstanceView.typeNameToViewInfoMap[typeName];
};
return {
ObjectInstanceView: ObjectInstanceView
};
});
// 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.
'use strict';
base.require('ui');
base.exportTo('tracing.analysis', function() {
var ObjectSnapshotView = ui.define('object-snapshot-view');
ObjectSnapshotView.prototype = {
__proto__: HTMLDivElement.prototype,
decorate: function() {
this.objectSnapshot_ = undefined;
},
get requiresTallView() {
return true;
},
set modelEvent(obj) {
this.objectSnapshot = obj;
},
get modelEvent() {
return this.objectSnapshot;
},
get objectSnapshot() {
return this.objectSnapshot_;
},
set objectSnapshot(i) {
this.objectSnapshot_ = i;
this.updateContents();
},
updateContents: function() {
throw new Error('Not implemented');
}
};
ObjectSnapshotView.typeNameToViewInfoMap = {};
ObjectSnapshotView.register = function(typeName,
viewConstructor,
opt_options) {
if (ObjectSnapshotView.typeNameToViewInfoMap[typeName])
throw new Error('Handler already registered for ' + typeName);
var options = opt_options || {
showInTrackView: true
};
ObjectSnapshotView.typeNameToViewInfoMap[typeName] = {
constructor: viewConstructor,
options: options
};
};
ObjectSnapshotView.unregister = function(typeName) {
if (ObjectSnapshotView.typeNameToViewInfoMap[typeName] === undefined)
throw new Error(typeName + ' not registered');
delete ObjectSnapshotView.typeNameToViewInfoMap[typeName];
};
ObjectSnapshotView.getViewInfo = function(typeName) {
return ObjectSnapshotView.typeNameToViewInfoMap[typeName];
};
return {
ObjectSnapshotView: ObjectSnapshotView
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.analysis.default_object_view');
base.require('tracing.analysis.analysis_link');
base.require('tracing.analysis.object_instance_view');
base.require('tracing.analysis.object_snapshot_view');
base.require('tracing.analysis.util');
base.require('tracing.analysis.generic_object_view');
base.exportTo('tracing.analysis', function() {
var tsRound = tracing.analysis.tsRound;
/*
* Displays an object instance in a human readable form.
* @constructor
*/
var DefaultObjectSnapshotView = ui.define(
'default-object-snapshot-view',
tracing.analysis.ObjectSnapshotView);
DefaultObjectSnapshotView.prototype = {
__proto__: tracing.analysis.ObjectSnapshotView.prototype,
decorate: function() {
tracing.analysis.ObjectSnapshotView.prototype.decorate.apply(this);
this.classList.add('default-object-view');
this.classList.add('default-object-snapshot-view');
},
updateContents: function() {
var snapshot = this.objectSnapshot;
if (!snapshot) {
this.textContent = '';
return;
}
var instance = snapshot.objectInstance;
var html = '';
html += '<div class="title">Snapshot of <a id="instance-link"></a> @ ' +
tsRound(snapshot.ts) + 'ms</div>\n';
html += '<table>';
html += '<tr>';
html += '<tr><td>args:</td><td id="args"></td></tr>\n';
html += '</table>';
this.innerHTML = html;
// TODO(nduca): ui.decoreate doesn't work when subclassed. So,
// replace the template element.
var instanceLinkEl = new tracing.analysis.ObjectInstanceLink();
instanceLinkEl.objectInstance = instance;
var tmp = this.querySelector('#instance-link');
tmp.parentElement.replaceChild(instanceLinkEl, tmp);
var argsEl = this.querySelector('#args');
argsEl.textContent = '';
var objectView = tracing.analysis.GenericObjectView();
objectView.object = snapshot.args;
argsEl.appendChild(objectView);
}
};
/**
* Displays an object instance in a human readable form.
* @constructor
*/
var DefaultObjectInstanceView = ui.define(
'default-object-instance-view',
tracing.analysis.ObjectInstanceView);
DefaultObjectInstanceView.prototype = {
__proto__: tracing.analysis.ObjectInstanceView.prototype,
decorate: function() {
tracing.analysis.ObjectInstanceView.prototype.decorate.apply(this);
this.classList.add('default-object-view');
this.classList.add('default-object-instance-view');
},
updateContents: function() {
var instance = this.objectInstance;
if (!instance) {
this.textContent = '';
return;
}
var html = '';
html += '<div class="title">' +
instance.typeName + ' ' +
instance.id + '</div>\n';
html += '<table>';
html += '<tr>';
html += '<tr><td>creationTs:</td><td>' +
instance.creationTs + '</td></tr>\n';
if (instance.deletionTs != Number.MAX_VALUE) {
html += '<tr><td>deletionTs:</td><td>' +
instance.deletionTs + '</td></tr>\n';
} else {
html += '<tr><td>deletionTs:</td><td>not deleted</td></tr>\n';
}
html += '<tr><td>snapshots:</td><td id="snapshots"></td></tr>\n';
html += '</table>';
this.innerHTML = html;
var snapshotsEl = this.querySelector('#snapshots');
instance.snapshots.forEach(function(snapshot) {
var snapshotLink = new tracing.analysis.ObjectSnapshotLink();
snapshotLink.objectSnapshot = snapshot;
snapshotsEl.appendChild(snapshotLink);
});
}
};
return {
DefaultObjectSnapshotView: DefaultObjectSnapshotView,
DefaultObjectInstanceView: DefaultObjectInstanceView
};
});
// 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.
'use strict';
base.require('ui');
base.exportTo('tracing.analysis', function() {
/**
* Slice views allow customized visualization of specific slices, indexed by
* title. If not registered, the default slice viewing logic is used.
*
* @constructor
*/
var SliceView = ui.define('slice-view');
SliceView.prototype = {
__proto__: HTMLDivElement.prototype,
decorate: function() {
this.objectInstance_ = undefined;
},
get requiresTallView() {
return false;
},
set modelEvent(obj) {
this.slice = obj;
},
get modelEvent() {
return this.slice;
},
get slice() {
return this.slice_;
},
set slice(s) {
this.slice_ = s;
this.updateContents();
},
updateContents: function() {
throw new Error('Not implemented');
}
};
SliceView.titleToViewInfoMap = {};
SliceView.register = function(title, viewConstructor) {
if (SliceView.titleToViewInfoMap[title])
throw new Error('Handler already registerd for ' + title);
SliceView.titleToViewInfoMap[title] = {
constructor: viewConstructor
};
};
SliceView.unregister = function(title) {
if (SliceView.titleToViewInfoMap[title] === undefined)
throw new Error(title + ' not registered');
delete SliceView.titleToViewInfoMap[title];
};
SliceView.getViewInfo = function(title) {
return SliceView.titleToViewInfoMap[title];
};
return {
SliceView: SliceView
};
});
// 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.
'use strict';
/**
* @fileoverview Displays an analysis of the selection.
*/
base.requireStylesheet('tracing.analysis.analysis_view');
base.require('base.guid');
base.require('tracing.analysis.analysis_results');
base.require('tracing.analysis.analyze_selection');
base.require('tracing.analysis.default_object_view');
base.require('tracing.analysis.object_instance_view');
base.require('tracing.analysis.object_snapshot_view');
base.require('tracing.analysis.slice_view');
base.require('tracing.analysis.util');
base.require('ui');
base.exportTo('tracing.analysis', function() {
var AnalysisView = ui.define('div');
AnalysisView.prototype = {
__proto__: HTMLDivElement.prototype,
decorate: function() {
this.className = 'analysis-view';
this.currentView_ = undefined;
this.currentSelection_ = undefined;
this.selections_ = [];
this.guid_ = base.GUID.allocate();
window.addEventListener('popstate', this.onPopState.bind(this));
},
changeViewType: function(viewType) {
if (this.currentView_ instanceof viewType)
return;
this.textContent = '';
try {
this.currentView_ = new viewType();
this.appendChild(this.currentView_);
} catch (e) {
this.currentView_ = undefined;
throw e;
}
this.updateClassList_();
},
updateClassList_: function() {
if (this.currentView_ instanceof tracing.analysis.AnalysisResults)
this.classList.remove('viewing-old-style-analysis');
else
this.classList.add('viewing-old-style-analysis');
if (this.currentView_ &&
this.currentView_.requiresTallView) {
this.classList.add('tall-mode');
} else {
this.classList.remove('tall-mode');
}
},
get currentView() {
return this.currentView_;
},
get selection() {
return this.currentSelection_;
},
set selection(selection) {
this.selections_.push(selection);
var state = {
view_guid: this.guid_,
selection_guid: selection.guid
};
window.history.pushState(state);
this.processSelection(selection);
},
clearSelectionHistory: function() {
this.selections_ = [];
},
onPopState: function(event) {
if ((event.state === null) ||
(event.state.view_guid !== this.guid_))
return;
var idx;
for (idx = 0; idx < this.selections_.length; ++idx) {
if (this.selections_[idx].guid === event.state.selection_guid)
break;
}
if (idx >= this.selections_.length)
return;
this.processSelection(this.selections_[idx]);
event.stopPropagation();
},
processSelection: function(selection) {
var eventsByType = selection.getEventsOrganizedByType();
if (selection.length == 1 &&
eventsByType.counterSamples.length == 0) {
if (this.tryToProcessSelectionUsingCustomView(selection[0]))
return;
}
this.changeViewType(tracing.analysis.AnalysisResults);
this.currentView.clear();
this.currentSelection_ = selection;
tracing.analysis.analyzeEventsByType(this.currentView, eventsByType);
},
tryToProcessSelectionUsingCustomView: function(event) {
var obj;
var typeName;
var viewBaseType;
var defaultViewType;
var viewProperty;
if (event instanceof tracing.trace_model.ObjectSnapshot) {
typeName = event.objectInstance.typeName;
viewBaseType = tracing.analysis.ObjectSnapshotView;
defaultViewType = tracing.analysis.DefaultObjectSnapshotView;
} else if (event instanceof tracing.trace_model.ObjectInstance) {
typeName = event.typeName;
viewBaseType = tracing.analysis.ObjectInstanceView;
defaultViewType = tracing.analysis.DefaultObjectInstanceView;
} else if (event instanceof tracing.trace_model.Slice) {
typeName = event.analysisTypeName;
viewBaseType = tracing.analysis.SliceView;
defaultViewType = undefined;
} else {
return false;
}
var customViewInfo = viewBaseType.getViewInfo(typeName);
var viewType = customViewInfo ?
customViewInfo.constructor : defaultViewType;
// Some view types don't have default views. In those cases, we fall
// back to the standard analysis sytem.
if (!viewType)
return false;
this.changeViewType(viewType);
this.currentView.modelEvent = event;
return true;
}
};
return {
AnalysisView: AnalysisView
};
});
// 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.
'use strict';
base.require('base.utils');
base.require('tracing.analysis.slice_view');
base.require('tracing.analysis.util');
base.require('tracing.analysis.analysis_link');
base.requireTemplate('tracing.analysis.slice_view');
base.exportTo('tracing.analysis', function() {
var tsRound = tracing.analysis.tsRound;
/**
* @constructor
*/
var CpuSliceView = ui.define('cpu-slice-view', tracing.analysis.SliceView);
CpuSliceView.prototype = {
__proto__: tracing.analysis.SliceView.prototype,
decorate: function() {
tracing.analysis.SliceView.prototype.decorate.call(this);
this.classList.add('cpu-slice-view');
},
updateContents: function() {
this.textContent = '';
this.appendChild(base.instantiateTemplate('#cpu-slice-view-template'));
var cpuSlice = this.slice;
var thread = cpuSlice.threadThatWasRunning;
if (thread) {
this.querySelector('#process-name').textContent =
thread.parent.userFriendlyName;
this.querySelector('#thread-name').textContent =
thread.userFriendlyName;
} else {
this.querySelector('#process-name').parentElement.style.display =
'none';
this.querySelector('#thread-name').textContent = cpuSlice.title;
}
this.querySelector('#start').textContent = tsRound(cpuSlice.start) + 'ms';
this.querySelector('#duration').textContent =
tsRound(cpuSlice.duration) + 'ms';
var runningThreadEl = this.querySelector('#running-thread');
var timeSlice = cpuSlice.getAssociatedTimeslice();
if (!timeSlice) {
runningThreadEl.parentElement.style.display = 'none';
} else {
var threadLink = new tracing.analysis.AnalysisLink();
threadLink.textContent = 'Click to select';
threadLink.selectionGenerator = function() {
var selection = new tracing.Selection();
selection.push(timeSlice);
return selection;
}.bind(this);
runningThreadEl.appendChild(threadLink);
}
}
};
tracing.analysis.SliceView.register(
'tracing.analysis.CpuSlice', CpuSliceView);
return {
CpuSliceView: CpuSliceView
};
});
// 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.
'use strict';
base.require('base.sorted_array_utils');
base.require('base.utils');
base.require('tracing.analysis.generic_object_view');
base.require('tracing.analysis.slice_view');
base.require('tracing.analysis.util');
base.require('tracing.analysis.analysis_link');
base.require('tracing.color_scheme');
base.requireTemplate('tracing.analysis.slice_view');
base.exportTo('tracing.analysis', function() {
var tsRound = tracing.analysis.tsRound;
/**
* @constructor
*/
var ThreadTimeSliceView = ui.define(
'thread-time-slice-view', tracing.analysis.SliceView);
ThreadTimeSliceView.prototype = {
__proto__: tracing.analysis.SliceView.prototype,
decorate: function() {
tracing.analysis.SliceView.prototype.decorate.call(this);
this.classList.add('thread-time-slice-view');
},
updateContents: function() {
this.textContent = '';
this.appendChild(
base.instantiateTemplate('#thread-time-slice-view-template'));
var timeSlice = this.slice;
var thread = timeSlice.thread;
this.querySelector('#state').textContent = timeSlice.title;
var stateColor = tracing.getColorPalette()[timeSlice.colorId];
this.querySelector('#state').style.backgroundColor = stateColor;
this.querySelector('#process-name').textContent =
thread.parent.userFriendlyName;
this.querySelector('#thread-name').textContent = thread.userFriendlyName;
this.querySelector('#start').textContent =
tsRound(timeSlice.start) + 'ms';
this.querySelector('#duration').textContent =
tsRound(timeSlice.duration) + 'ms';
var onCpuEl = this.querySelector('#on-cpu');
var runningInsteadEl = this.querySelector('#running-instead');
if (timeSlice.cpuOnWhichThreadWasRunning) {
runningInsteadEl.parentElement.removeChild(runningInsteadEl);
var cpuLink = new tracing.analysis.AnalysisLink();
cpuLink.textContent =
timeSlice.cpuOnWhichThreadWasRunning.userFriendlyName;
cpuLink.selectionGenerator = function() {
var selection = new tracing.Selection();
selection.push(timeSlice.getAssociatedCpuSlice());
return selection;
}.bind(this);
onCpuEl.appendChild(cpuLink);
} else {
onCpuEl.parentElement.removeChild(onCpuEl);
var cpuSliceThatTookCpu = timeSlice.getCpuSliceThatTookCpu();
if (cpuSliceThatTookCpu) {
var cpuLink = new tracing.analysis.AnalysisLink();
if (cpuSliceThatTookCpu.thread)
cpuLink.textContent = cpuSliceThatTookCpu.thread.userFriendlyName;
else
cpuLink.textContent = cpuSliceThatTookCpu.title;
cpuLink.selectionGenerator = function() {
var selection = new tracing.Selection();
selection.push(cpuSliceThatTookCpu);
return selection;
}.bind(this);
runningInsteadEl.appendChild(cpuLink);
} else {
runningInsteadEl.parentElement.removeChild(runningInsteadEl);
}
}
var argsEl = this.querySelector('#args');
if (base.dictionaryKeys(timeSlice.args).length > 0) {
var argsView = new tracing.analysis.GenericObjectView();
argsView.object = timeSlice.args;
argsEl.appendChild(argsView);
} else {
argsEl.parentElement.removeChild(argsEl);
}
}
};
tracing.analysis.SliceView.register(
'tracing.analysis.ThreadTimeSlice', ThreadTimeSliceView);
return {
ThreadTimeSliceView: ThreadTimeSliceView
};
});
// 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.
'use strict';
base.exportTo('ui', function() {
/**
* Represents a procedural animation that can be run by an
* ui.AnimationController.
*
* @constructor
*/
function Animation() {
}
Animation.prototype = {
/**
* Called when an animation has been queued after a running animation.
*
* @return {boolean} True if the animation can take on the responsibilities
* of the running animation. If true, takeOverFor will be called on the
* animation.
*
* This can be used to build animations that accelerate as pairs of them are
* queued.
*/
canTakeOverFor: function(existingAnimation) {
throw new Error('Not implemented');
},
/**
* Called to take over responsiblities of an existingAnimation.
*
* At this point, the existingAnimation has been ticked one last time, then
* stopped. This animation will be started after this returns and has the
* job of finishing(or transitioning away from) the effect the existing
* animation was trying to accomplish.
*/
takeOverFor: function(existingAnimation, newStartTimestamp, target) {
throw new Error('Not implemented');
},
start: function(timestamp, target) {
throw new Error('Not implemented');
},
/**
* Called when an animation is stopped before it finishes. The animation can
* do what it wants here, usually nothing.
*
* @param {Number} timestamp When the animation was stopped.
* @param {Object} target The object being animated. May be undefined, take
* care.
* @param {boolean} willBeTakenOverByAnotherAnimation Whether this animation
* is going to be handed to another animation's takeOverFor function.
*/
didStopEarly: function(timestamp, target,
willBeTakenOverByAnotherAnimation) {
},
/**
* @return {boolean} true if the animation is finished.
*/
tick: function(timestamp, target) {
throw new Error('Not implemented');
}
};
return {
Animation: Animation
};
});
// 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.
'use strict';
base.require('base.utils');
base.require('ui.animation');
base.exportTo('tracing', function() {
var kDefaultPanAnimatoinDurationMs = 100.0;
/**
* Pans a TimelineDisplayTransform by a given amount.
* @constructor
* @extends {ui.Animation}
* @param {Number} deltaX The total amount of change to the transform's panX.
* @param {Number} deltaY The total amount of change to the transform's panY.
* @param {Number=} opt_durationMs How long the pan animation should run.
* Defaults to kDefaultPanAnimatoinDurationMs.
*/
function TimelineDisplayTransformPanAnimation(
deltaX, deltaY, opt_durationMs) {
this.deltaX = deltaX;
this.deltaY = deltaY;
if (opt_durationMs === undefined)
this.durationMs = kDefaultPanAnimatoinDurationMs;
else
this.durationMs = opt_durationMs;
this.startPanX = undefined;
this.startPanY = undefined;
this.startTimeMs = undefined;
}
TimelineDisplayTransformPanAnimation.prototype = {
__proto__: ui.Animation.prototype,
get affectsPanY() {
return this.deltaY !== 0;
},
canTakeOverFor: function(existingAnimation) {
return existingAnimation instanceof TimelineDisplayTransformPanAnimation;
},
takeOverFor: function(existing, timestamp, target) {
var remainingDeltaXOnExisting = existing.goalPanX - target.panX;
var remainingDeltaYOnExisting = existing.goalPanY - target.panY;
var remainingTimeOnExisting = timestamp - (
existing.startTimeMs + existing.durationMs);
remainingTimeOnExisting = Math.max(remainingTimeOnExisting, 0);
this.deltaX += remainingDeltaXOnExisting;
this.deltaY += remainingDeltaYOnExisting;
this.durationMs += remainingTimeOnExisting;
},
start: function(timestamp, target) {
this.startTimeMs = timestamp;
this.startPanX = target.panX;
this.startPanY = target.panY;
},
tick: function(timestamp, target) {
var percentDone = (timestamp - this.startTimeMs) / this.durationMs;
percentDone = base.clamp(percentDone, 0, 1);
target.panX = base.lerp(percentDone, this.startPanX, this.goalPanX);
if (this.affectsPanY)
target.panY = base.lerp(percentDone, this.startPanY, this.goalPanY);
return timestamp >= this.startTimeMs + this.durationMs;
},
get goalPanX() {
return this.startPanX + this.deltaX;
},
get goalPanY() {
return this.startPanY + this.deltaY;
}
};
/**
* Zooms in/out on a specified location in the world.
*
* Zooming in and out is all about keeping the area under the mouse cursor,
* here called the "focal point" in the same place under the zoom. If one
* simply changes the scale, the area under the mouse cursor will change. To
* keep the focal point from moving during the zoom, the pan needs to change
* in order to compensate. Thus, a ZoomTo animation is given both a focal
* point in addition to the amount by which to zoom.
*
* @constructor
* @extends {ui.Animation}
* @param {Number} goalFocalPointXWorld The X coordinate in the world which is
* of interest.
* @param {Number} goalFocalPointXView Where on the screen the
* goalFocalPointXWorld should stay centered during the zoom.
* @param {Number} goalFocalPointY Where the panY should be when the zoom
* completes.
* @param {Number} zoomInRatioX The ratio of the current scaleX to the goal
* scaleX.
*/
function TimelineDisplayTransformZoomToAnimation(
goalFocalPointXWorld,
goalFocalPointXView,
goalFocalPointY,
zoomInRatioX,
opt_durationMs) {
this.goalFocalPointXWorld = goalFocalPointXWorld;
this.goalFocalPointXView = goalFocalPointXView;
this.goalFocalPointY = goalFocalPointY;
this.zoomInRatioX = zoomInRatioX;
if (opt_durationMs === undefined)
this.durationMs = kDefaultPanAnimatoinDurationMs;
else
this.durationMs = opt_durationMs;
this.startTimeMs = undefined;
this.startScaleX = undefined;
this.goalScaleX = undefined;
this.startPanY = undefined;
this.goalPanY = undefined;
}
TimelineDisplayTransformZoomToAnimation.prototype = {
__proto__: ui.Animation.prototype,
get affectsPanY() {
return this.startPanY != this.goalPanY;
},
canTakeOverFor: function(existingAnimation) {
return false;
},
takeOverFor: function(existingAnimation, timestamp, target) {
this.goalScaleX = target.scaleX * this.zoomInRatioX;
},
start: function(timestamp, target) {
this.startTimeMs = timestamp;
this.startScaleX = target.scaleX;
this.goalScaleX = this.zoomInRatioX * target.scaleX;
this.startPanY = target.panY;
},
tick: function(timestamp, target) {
var percentDone = (timestamp - this.startTimeMs) / this.durationMs;
percentDone = base.clamp(percentDone, 0, 1);
target.scaleX = base.lerp(percentDone, this.startScaleX, this.goalScaleX);
if (this.affectsPanY) {
target.panY = base.lerp(
percentDone, this.startPanY, this.goalFocalPointY);
}
target.xPanWorldPosToViewPos(
this.goalFocalPointXWorld, this.goalFocalPointXView);
return timestamp >= this.startTimeMs + this.durationMs;
}
};
return {
TimelineDisplayTransformPanAnimation:
TimelineDisplayTransformPanAnimation,
TimelineDisplayTransformZoomToAnimation:
TimelineDisplayTransformZoomToAnimation
};
});
// 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.
'use strict';
/**
* @fileoverview Provides a caching layer for elided text values.
*/
base.exportTo('tracing', function() {
/**
* Cache for elided strings.
* Moved from the ElidedTitleCache protoype to a "global" for speed
* (variable reference is 100x faster).
* key: String we wish to elide.
* value: Another dict whose key is width
* and value is an ElidedStringWidthPair.
*/
var elidedTitleCacheDict = {};
var elidedTitleCache = new ElidedTitleCache();
/**
* A cache for elided strings.
* @constructor
*/
function ElidedTitleCache() {
// TODO(jrg): possibly obsoleted with the elided string cache.
// Consider removing.
this.textWidthMap = {};
}
ElidedTitleCache.prototype = {
/**
* Return elided text.
*
* @param {ctx} Context The graphics context.
* @param {pixWidth} Pixel width.
* @param {title} Original title text.
* @param {width} Drawn width in world coords.
* @param {sliceDuration} Where the title must fit (in world coords).
* @return {ElidedStringWidthPair} Elided string and width.
*/
get: function(ctx, pixWidth, title, width, sliceDuration) {
var elidedDict = elidedTitleCacheDict[title];
if (!elidedDict) {
elidedDict = {};
elidedTitleCacheDict[title] = elidedDict;
}
var elidedDictForPixWidth = elidedDict[pixWidth];
if (!elidedDictForPixWidth) {
elidedDict[pixWidth] = {};
elidedDictForPixWidth = elidedDict[pixWidth];
}
var stringWidthPair = elidedDictForPixWidth[sliceDuration];
if (stringWidthPair === undefined) {
var newtitle = title;
var elided = false;
while (this.labelWidthWorld(ctx, newtitle, pixWidth) > sliceDuration) {
if (newtitle.length * 0.75 < 1)
break;
newtitle = newtitle.substring(0, newtitle.length * 0.75);
elided = true;
}
if (elided && newtitle.length > 3)
newtitle = newtitle.substring(0, newtitle.length - 3) + '...';
stringWidthPair = new ElidedStringWidthPair(
newtitle, this.labelWidth(ctx, newtitle));
elidedDictForPixWidth[sliceDuration] = stringWidthPair;
}
return stringWidthPair;
},
quickMeasureText_: function(ctx, text) {
var w = this.textWidthMap[text];
if (!w) {
w = ctx.measureText(text).width;
this.textWidthMap[text] = w;
}
return w;
},
labelWidth: function(ctx, title) {
return this.quickMeasureText_(ctx, title) + 2;
},
labelWidthWorld: function(ctx, title, pixWidth) {
return this.labelWidth(ctx, title) * pixWidth;
}
};
/**
* A pair representing an elided string and world-coordinate width
* to draw it.
* @constructor
*/
function ElidedStringWidthPair(string, width) {
this.string = string;
this.width = width;
}
return {
ElidedTitleCache: ElidedTitleCache
};
});
// 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.
'use strict';
base.require('base.sorted_array_utils');
base.require('tracing.color_scheme');
base.require('tracing.elided_cache');
/**
* @fileoverview Provides various helper methods for drawing to a provided
* canvas.
*/
base.exportTo('tracing', function() {
var elidedTitleCache = new tracing.ElidedTitleCache();
var palette = tracing.getColorPalette();
var EventPresenter = tracing.EventPresenter;
/**
* Should we elide text on trace labels?
* Without eliding, text that is too wide isn't drawn at all.
* Disable if you feel this causes a performance problem.
* This is a default value that can be overridden in tracks for testing.
* @const
*/
var SHOULD_ELIDE_TEXT = true;
/**
* Draw the define line into |ctx|.
*
* @param {Context} ctx The context to draw into.
* @param {float} x1 The start x position of the line.
* @param {float} y1 The start y position of the line.
* @param {float} x2 The end x position of the line.
* @param {float} y2 The end y position of the line.
*/
function drawLine(ctx, x1, y1, x2, y2) {
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
}
/**
* Draw the defined triangle into |ctx|.
*
* @param {Context} ctx The context to draw into.
* @param {float} x1 The first corner x.
* @param {float} y1 The first corner y.
* @param {float} x2 The second corner x.
* @param {float} y2 The second corner y.
* @param {float} x3 The third corner x.
* @param {float} y3 The third corner y.
*/
function drawTriangle(ctx, x1, y1, x2, y2, x3, y3) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(x3, y3);
ctx.closePath();
}
/**
* Draw an arrow into |ctx|.
*
* @param {Context} ctx The context to draw into.
* @param {float} x1 The shaft x.
* @param {float} y1 The shaft y.
* @param {float} x2 The head x.
* @param {float} y2 The head y.
* @param {float} arrowLength The length of the head.
* @param {float} arrowWidth The width of the head.
*/
function drawArrow(ctx, x1, y1, x2, y2, arrowLength, arrowWidth) {
var dx = x2 - x1;
var dy = y2 - y1;
var len = Math.sqrt(dx * dx + dy * dy);
var perc = (len - arrowLength) / len;
var bx = x1 + perc * dx;
var by = y1 + perc * dy;
var ux = dx / len;
var uy = dy / len;
var ax = uy * arrowWidth;
var ay = -ux * arrowWidth;
ctx.beginPath();
drawLine(ctx, x1, y1, x2, y2);
ctx.stroke();
drawTriangle(ctx,
bx + ax, by + ay,
x2, y2,
bx - ax, by - ay);
ctx.fill();
}
/**
* Draw the provided slices to the screen.
*
* Each of the elements in |slices| must provide the follow methods:
* * start
* * duration
* * colorId
* * selected
*
* @param {Context} ctx The canvas context.
* @param {TimelineDrawTransform} dt The draw transform.
* @param {float} viewLWorld The left most point of the world viewport.
* @param {float} viewLWorld The right most point of the world viewport.
* @param {float} viewHeight The height of the viewport.
* @param {Array} slices The slices to draw.
* @param {bool} async Whether the slices are drawn with async style.
*/
function drawSlices(ctx, dt, viewLWorld, viewRWorld, viewHeight, slices,
async) {
var pixelRatio = window.devicePixelRatio || 1;
var pixWidth = dt.xViewVectorToWorld(1);
var height = viewHeight * pixelRatio;
// Begin rendering in world space.
ctx.save();
dt.applyTransformToCanvas(ctx);
var tr = new tracing.FastRectRenderer(
ctx, 2 * pixWidth, 2 * pixWidth, palette);
tr.setYandH(0, height);
var lowSlice = base.findLowIndexInSortedArray(
slices,
function(slice) { return slice.start + slice.duration; },
viewLWorld);
for (var i = lowSlice; i < slices.length; ++i) {
var slice = slices[i];
var x = slice.start;
if (x > viewRWorld)
break;
var w = pixWidth;
if (slice.duration > 0) {
w = Math.max(slice.duration, 0.001);
if (w < pixWidth)
w = pixWidth;
}
var colorId = EventPresenter.getSliceColorId(slice);
var alpha = EventPresenter.getSliceAlpha(slice, async);
tr.fillRect(x, w, colorId, alpha);
}
tr.flush();
ctx.restore();
}
/**
* Draw the provided instant slices as lines to the screen.
*
* Each of the elements in |slices| must provide the follow methods:
* * start
* * duration with value of 0.
* * colorId
* * selected
*
* @param {Context} ctx The canvas context.
* @param {TimelineDrawTransform} dt The draw transform.
* @param {float} viewLWorld The left most point of the world viewport.
* @param {float} viewLWorld The right most point of the world viewport.
* @param {float} viewHeight The height of the viewport.
* @param {Array} slices The slices to draw.
* @param {Numer} lineWidthInPixels The width of the lines.
*/
function drawInstantSlicesAsLines(
ctx, dt, viewLWorld, viewRWorld, viewHeight, slices, lineWidthInPixels) {
var pixelRatio = window.devicePixelRatio || 1;
var height = viewHeight * pixelRatio;
var pixWidth = dt.xViewVectorToWorld(1);
// Begin rendering in world space.
ctx.save();
ctx.lineWidth = pixWidth * lineWidthInPixels;
dt.applyTransformToCanvas(ctx);
ctx.beginPath();
var lowSlice = base.findLowIndexInSortedArray(
slices,
function(slice) { return slice.start; },
viewLWorld);
for (var i = lowSlice; i < slices.length; ++i) {
var slice = slices[i];
var x = slice.start;
if (x > viewRWorld)
break;
ctx.strokeStyle = EventPresenter.getInstantSliceColor(slice);
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
}
ctx.stroke();
ctx.restore();
}
/**
* Draws the labels for the given slices.
*
* The |slices| array must contain objects with the following API:
* * start
* * duration
* * title
* * didNotFinish (optional)
*
* @param {Context} ctx The graphics context.
* @param {TimelineDrawTransform} dt The draw transform.
* @param {float} viewLWorld The left most point of the world viewport.
* @param {float} viewLWorld The right most point of the world viewport.
* @param {Array} slices The slices to label.
* @param {bool} async Whether the slice labels are drawn with async style.
*/
function drawLabels(ctx, dt, viewLWorld, viewRWorld, slices, async) {
var pixelRatio = window.devicePixelRatio || 1;
var pixWidth = dt.xViewVectorToWorld(1);
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.font = (10 * pixelRatio) + 'px sans-serif';
if (async)
ctx.font = 'italic ' + ctx.font;
var lowSlice = base.findLowIndexInSortedArray(
slices,
function(slice) { return slice.start + slice.duration; },
viewLWorld);
// Don't render text until until it is 20px wide
var quickDiscardThresshold = pixWidth * 20;
for (var i = lowSlice; i < slices.length; ++i) {
var slice = slices[i];
if (slice.start > viewRWorld)
break;
if (slice.duration <= quickDiscardThresshold)
continue;
var title = slice.title +
(slice.didNotFinish ? ' (Did Not Finish)' : '');
var drawnTitle = title;
var drawnWidth = elidedTitleCache.labelWidth(ctx, drawnTitle);
var fullLabelWidth = elidedTitleCache.labelWidthWorld(
ctx, drawnTitle, pixWidth);
if (SHOULD_ELIDE_TEXT && fullLabelWidth > slice.duration) {
var elidedValues = elidedTitleCache.get(
ctx, pixWidth,
drawnTitle, drawnWidth,
slice.duration);
drawnTitle = elidedValues.string;
drawnWidth = elidedValues.width;
}
if (drawnWidth * pixWidth < slice.duration) {
ctx.fillStyle = EventPresenter.getTextColor(slice);
var cX = dt.xWorldToView(slice.start + 0.5 * slice.duration);
ctx.fillText(drawnTitle, cX, 2.5 * pixelRatio, drawnWidth);
}
}
ctx.restore();
}
return {
drawSlices: drawSlices,
drawInstantSlicesAsLines: drawInstantSlicesAsLines,
drawLabels: drawLabels,
drawLine: drawLine,
drawTriangle: drawTriangle,
drawArrow: drawArrow,
elidedTitleCache_: elidedTitleCache
};
});
// 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.
'use strict';
base.require('base.utils');
base.exportTo('tracing', function() {
function TimelineDisplayTransform(opt_that) {
if (opt_that) {
this.set(opt_that);
return;
}
this.scaleX = 1;
this.panX = 0;
this.panY = 0;
}
TimelineDisplayTransform.prototype = {
set: function(that) {
this.scaleX = that.scaleX;
this.panX = that.panX;
this.panY = that.panY;
},
clone: function() {
return new TimelineDisplayTransform(this);
},
equals: function(that) {
var eq = true;
if (that === undefined || that === null)
return false;
eq &= this.panX === that.panX;
eq &= this.panY === that.panY;
eq &= this.scaleX === that.scaleX;
return !!eq;
},
almostEquals: function(that) {
var eq = true;
if (that === undefined || that === null)
return false;
eq &= Math.abs(this.panX - that.panX) < 0.001;
eq &= Math.abs(this.panY - that.panY) < 0.001;
eq &= Math.abs(this.scaleX - that.scaleX) < 0.001;
return !!eq;
},
incrementPanXInViewUnits: function(xDeltaView) {
this.panX += this.xViewVectorToWorld(xDeltaView);
},
xPanWorldPosToViewPos: function(worldX, viewX, viewWidth) {
if (typeof viewX == 'string') {
if (viewX === 'left') {
viewX = 0;
} else if (viewX === 'center') {
viewX = viewWidth / 2;
} else if (viewX === 'right') {
viewX = viewWidth - 1;
} else {
throw new Error('viewX must be left|center|right or number.');
}
}
this.panX = (viewX / this.scaleX) - worldX;
},
xPanWorldBoundsIntoView: function(worldMin, worldMax, viewWidth) {
if (this.xWorldToView(worldMin) < 0)
this.xPanWorldPosToViewPos(worldMin, 'left', viewWidth);
else if (this.xWorldToView(worldMax) > viewWidth)
this.xPanWorldPosToViewPos(worldMax, 'right', viewWidth);
},
xSetWorldBounds: function(worldMin, worldMax, viewWidth) {
var worldWidth = worldMax - worldMin;
var scaleX = viewWidth / worldWidth;
var panX = -worldMin;
this.setPanAndScale(panX, scaleX);
},
setPanAndScale: function(p, s) {
this.scaleX = s;
this.panX = p;
},
xWorldToView: function(x) {
return (x + this.panX) * this.scaleX;
},
xWorldVectorToView: function(x) {
return x * this.scaleX;
},
xViewToWorld: function(x) {
return (x / this.scaleX) - this.panX;
},
xViewVectorToWorld: function(x) {
return x / this.scaleX;
},
applyTransformToCanvas: function(ctx) {
ctx.transform(this.scaleX, 0, 0, 1, this.panX * this.scaleX, 0);
}
};
return {
TimelineDisplayTransform: TimelineDisplayTransform
};
});
// 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.
'use strict';
base.require('base.event_target');
base.require('base.raf');
base.require('ui.animation');
base.exportTo('ui', function() {
/**
* Manages execution, queueing and blending of ui.Animations against
* a single target.
*
* Targets must have a cloneAnimationState() method that returns all the
* animatable states of that target.
*
* @constructor
* @extends {base.EventTarget}
*/
function AnimationController() {
base.EventTarget.call(this);
this.target_ = undefined;
this.activeAnimation_ = undefined;
this.tickScheduled_ = false;
}
AnimationController.prototype = {
__proto__: base.EventTarget.prototype,
get target() {
return this.target_;
},
set target(target) {
if (this.activeAnimation_)
throw new Error('Cannot change target while animation is running.');
if (target.cloneAnimationState === undefined ||
typeof target.cloneAnimationState !== 'function')
throw new Error('target must have a cloneAnimationState function');
this.target_ = target;
},
get activeAnimation() {
return this.activeAnimation_;
},
get hasActiveAnimation() {
return !!this.activeAnimation_;
},
queueAnimation: function(animation, opt_now) {
if (this.target_ === undefined)
throw new Error('Cannot queue animations without a target');
var now;
if (opt_now !== undefined)
now = opt_now;
else
now = window.performance.now();
if (this.activeAnimation_) {
// Must tick the animation before stopping it case its about to stop,
// and to update the target with its final sets of edits up to this
// point.
var done = this.activeAnimation_.tick(now, this.target_);
if (done)
this.activeAnimation_ = undefined;
}
if (this.activeAnimation_) {
if (animation.canTakeOverFor(this.activeAnimation_)) {
this.activeAnimation_.didStopEarly(now, this.target_, true);
animation.takeOverFor(this.activeAnimation_, now, this.target_);
} else {
this.activeAnimation_.didStopEarly(now, this.target_, false);
}
}
this.activeAnimation_ = animation;
this.activeAnimation_.start(now, this.target_);
if (this.tickScheduled_)
return;
this.tickScheduled_ = true;
base.requestAnimationFrame(this.tickActiveAnimation_, this);
},
cancelActiveAnimation: function(opt_now) {
if (!this.activeAnimation_)
return;
var now;
if (opt_now !== undefined)
now = opt_now;
else
now = window.performance.now();
this.activeAnimation_.didStopEarly(now, this.target_, false);
this.activeAnimation_ = undefined;
},
tickActiveAnimation_: function(frameBeginTime) {
this.tickScheduled_ = false;
if (!this.activeAnimation_)
return;
if (this.target_ === undefined) {
this.activeAnimation_.didStopEarly(frameBeginTime, this.target_, false);
return;
}
var oldTargetState = this.target_.cloneAnimationState();
var done = this.activeAnimation_.tick(frameBeginTime, this.target_);
if (done)
this.activeAnimation_ = undefined;
if (this.activeAnimation_) {
this.tickScheduled_ = true;
base.requestAnimationFrame(this.tickActiveAnimation_, this);
}
if (oldTargetState) {
var e = new Event('didtick');
e.oldTargetState = oldTargetState;
this.dispatchEvent(e, false, false);
}
}
};
return {
AnimationController: AnimationController
};
});
// 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.
'use strict';
/**
* @fileoverview Code for the viewport.
*/
base.require('base.events');
base.require('tracing.draw_helpers');
base.require('tracing.timeline_display_transform');
base.require('ui.animation');
base.require('ui.animation_controller');
base.exportTo('tracing', function() {
var TimelineDisplayTransform = tracing.TimelineDisplayTransform;
/**
* The TimelineViewport manages the transform used for navigating
* within the timeline. It is a simple transform:
* x' = (x+pan) * scale
*
* The timeline code tries to avoid directly accessing this transform,
* instead using this class to do conversion between world and viewspace,
* as well as the math for centering the viewport in various interesting
* ways.
*
* @constructor
* @extends {base.EventTarget}
*/
function TimelineViewport(parentEl) {
this.parentEl_ = parentEl;
this.modelTrackContainer_ = undefined;
this.currentDisplayTransform_ = new TimelineDisplayTransform();
this.initAnimationController_();
// Flow events
this.showFlowEvents_ = false;
// Grid system.
this.gridTimebase_ = 0;
this.gridStep_ = 1000 / 60;
this.gridEnabled_ = false;
// Init logic.
this.hasCalledSetupFunction_ = false;
this.onResize_ = this.onResize_.bind(this);
this.onModelTrackControllerScroll_ =
this.onModelTrackControllerScroll_.bind(this);
// The following code uses an interval to detect when the parent element
// is attached to the document. That is a trigger to run the setup function
// and install a resize listener.
this.checkForAttachInterval_ = setInterval(
this.checkForAttach_.bind(this), 250);
this.markers = [];
this.majorMarkPositions = [];
this.eventToTrackMap_ = {};
}
TimelineViewport.prototype = {
__proto__: base.EventTarget.prototype,
/**
* Allows initialization of the viewport when the viewport's parent element
* has been attached to the document and given a size.
* @param {Function} fn Function to call when the viewport can be safely
* initialized.
*/
setWhenPossible: function(fn) {
this.pendingSetFunction_ = fn;
},
/**
* @return {boolean} Whether the current timeline is attached to the
* document.
*/
get isAttachedToDocument_() {
var cur = this.parentEl_;
// Allow not providing a parent element, used by tests.
if (cur === undefined)
return;
while (cur.parentNode)
cur = cur.parentNode;
return cur == this.parentEl_.ownerDocument;
},
onResize_: function() {
this.dispatchChangeEvent();
},
/**
* Checks whether the parentNode is attached to the document.
* When it is, it installs the iframe-based resize detection hook
* and then runs the pendingSetFunction_, if present.
*/
checkForAttach_: function() {
if (!this.isAttachedToDocument_ || this.clientWidth == 0)
return;
if (!this.iframe_) {
this.iframe_ = document.createElement('iframe');
this.iframe_.style.cssText =
'position:absolute;width:100%;height:0;border:0;visibility:hidden;';
this.parentEl_.appendChild(this.iframe_);
this.iframe_.contentWindow.addEventListener('resize', this.onResize_);
}
var curSize = this.parentEl_.clientWidth + 'x' +
this.parentEl_.clientHeight;
if (this.pendingSetFunction_) {
this.lastSize_ = curSize;
try {
this.pendingSetFunction_();
} catch (ex) {
console.log('While running setWhenPossible:',
ex.message ? ex.message + '\n' + ex.stack : ex.stack);
}
this.pendingSetFunction_ = undefined;
}
window.clearInterval(this.checkForAttachInterval_);
this.checkForAttachInterval_ = undefined;
},
/**
* Fires the change event on this viewport. Used to notify listeners
* to redraw when the underlying model has been mutated.
*/
dispatchChangeEvent: function() {
base.dispatchSimpleEvent(this, 'change');
},
dispatchMarkersChangeEvent_: function() {
base.dispatchSimpleEvent(this, 'markersChange');
},
detach: function() {
if (this.checkForAttachInterval_) {
window.clearInterval(this.checkForAttachInterval_);
this.checkForAttachInterval_ = undefined;
}
if (this.iframe_) {
this.iframe_.removeEventListener('resize', this.onResize_);
this.parentEl_.removeChild(this.iframe_);
}
},
initAnimationController_: function() {
this.dtAnimationController_ = new ui.AnimationController();
this.dtAnimationController_.addEventListener(
'didtick', function(e) {
this.onCurentDisplayTransformChange_(e.oldTargetState);
}.bind(this));
var that = this;
this.dtAnimationController_.target = {
get panX() {
return that.currentDisplayTransform_.panX;
},
set panX(panX) {
that.currentDisplayTransform_.panX = panX;
},
get panY() {
return that.currentDisplayTransform_.panY;
},
set panY(panY) {
that.currentDisplayTransform_.panY = panY;
},
get scaleX() {
return that.currentDisplayTransform_.scaleX;
},
set scaleX(scaleX) {
that.currentDisplayTransform_.scaleX = scaleX;
},
cloneAnimationState: function() {
return that.currentDisplayTransform_.clone();
},
xPanWorldPosToViewPos: function(xWorld, xView) {
that.currentDisplayTransform_.xPanWorldPosToViewPos(
xWorld, xView, that.modelTrackContainer_.canvas.clientWidth);
}
};
},
get currentDisplayTransform() {
return this.currentDisplayTransform_;
},
setDisplayTransformImmediately: function(displayTransform) {
this.dtAnimationController_.cancelActiveAnimation();
var oldDisplayTransform =
this.dtAnimationController_.target.cloneAnimationState();
this.currentDisplayTransform_.set(displayTransform);
this.onCurentDisplayTransformChange_(oldDisplayTransform);
},
queueDisplayTransformAnimation: function(animation) {
if (!(animation instanceof ui.Animation))
throw new Error('animation must be instanceof ui.Animation');
this.dtAnimationController_.queueAnimation(animation);
},
onCurentDisplayTransformChange_: function(oldDisplayTransform) {
// Ensure panY stays clamped in the track container's scroll range.
if (this.modelTrackContainer_) {
this.currentDisplayTransform.panY = base.clamp(
this.currentDisplayTransform.panY,
0,
this.modelTrackContainer_.scrollHeight -
this.modelTrackContainer_.clientHeight);
}
var changed = !this.currentDisplayTransform.equals(oldDisplayTransform);
var yChanged = this.currentDisplayTransform.panY !==
oldDisplayTransform.panY;
if (yChanged)
this.modelTrackContainer_.scrollTop = this.currentDisplayTransform.panY;
if (changed)
this.dispatchChangeEvent();
},
onModelTrackControllerScroll_: function(e) {
if (this.dtAnimationController_.activeAnimation &&
this.dtAnimationController_.activeAnimation.affectsPanY)
this.dtAnimationController_.cancelActiveAnimation();
var panY = this.modelTrackContainer_.scrollTop;
this.currentDisplayTransform_.panY = panY;
},
get modelTrackContainer() {
return this.modelTrackContainer_;
},
set modelTrackContainer(m) {
if (this.modelTrackContainer_)
this.modelTrackContainer_.removeEventListener('scroll',
this.onModelTrackControllerScroll_);
this.modelTrackContainer_ = m;
this.modelTrackContainer_.addEventListener('scroll',
this.onModelTrackControllerScroll_);
},
get showFlowEvents() {
return this.showFlowEvents_;
},
set showFlowEvents(showFlowEvents) {
this.showFlowEvents_ = showFlowEvents;
this.dispatchChangeEvent();
},
get gridEnabled() {
return this.gridEnabled_;
},
set gridEnabled(enabled) {
if (this.gridEnabled_ == enabled)
return;
this.gridEnabled_ = enabled && true;
this.dispatchChangeEvent();
},
get gridTimebase() {
return this.gridTimebase_;
},
set gridTimebase(timebase) {
if (this.gridTimebase_ == timebase)
return;
this.gridTimebase_ = timebase;
this.dispatchChangeEvent();
},
get gridStep() {
return this.gridStep_;
},
createMarker: function(positionWorld) {
return new ViewportMarker(this, positionWorld);
},
addMarker: function(marker) {
if (this.markers.indexOf(marker) >= 0)
return false;
this.markers.push(marker);
this.dispatchChangeEvent();
this.dispatchMarkersChangeEvent_();
return marker;
},
removeMarker: function(marker) {
for (var i = 0; i < this.markers.length; ++i) {
if (this.markers[i] === marker) {
this.markers.splice(i, 1);
this.dispatchChangeEvent();
this.dispatchMarkersChangeEvent_();
return true;
}
}
},
findMarkerNear: function(positionWorld, nearnessInViewPixels) {
// Converts pixels into distance in world.
var dt = this.currentDisplayTransform;
var nearnessThresholdWorld = dt.xViewVectorToWorld(
nearnessInViewPixels);
for (var i = 0; i < this.markers.length; ++i) {
if (Math.abs(this.markers[i].positionWorld - positionWorld) <=
nearnessThresholdWorld) {
var marker = this.markers[i];
return marker;
}
}
return undefined;
},
drawMarkLines: function(ctx) {
// Apply subpixel translate to get crisp lines.
// http://www.mobtowers.com/html5-canvas-crisp-lines-every-time/
ctx.save();
ctx.translate((Math.round(ctx.lineWidth) % 2) / 2, 0);
ctx.beginPath();
for (var idx in this.majorMarkPositions) {
var x = Math.floor(this.majorMarkPositions[idx]);
tracing.drawLine(ctx, x, 0, x, ctx.canvas.height);
}
ctx.strokeStyle = '#ddd';
ctx.stroke();
ctx.restore();
},
drawGridLines: function(ctx, viewLWorld, viewRWorld) {
if (!this.gridEnabled)
return;
var dt = this.currentDisplayTransform;
var x = this.gridTimebase;
// Apply subpixel translate to get crisp lines.
// http://www.mobtowers.com/html5-canvas-crisp-lines-every-time/
ctx.save();
ctx.translate((Math.round(ctx.lineWidth) % 2) / 2, 0);
ctx.beginPath();
while (x < viewRWorld) {
if (x >= viewLWorld) {
// Do conversion to viewspace here rather than on
// x to avoid precision issues.
var vx = Math.floor(dt.xWorldToView(x));
tracing.drawLine(ctx, vx, 0, vx, ctx.canvas.height);
}
x += this.gridStep;
}
ctx.strokeStyle = 'rgba(255, 0, 0, 0.25)';
ctx.stroke();
ctx.restore();
},
drawMarkerLines: function(ctx, viewLWorld, viewRWorld) {
// Dim the area left and right of the markers if there are 2 markers.
var dt = this.currentDisplayTransform;
if (this.markers.length === 2) {
var posWorld0 = this.markers[0].positionWorld;
var posWorld1 = this.markers[1].positionWorld;
var markerLWorld = Math.min(posWorld0, posWorld1);
var markerRWorld = Math.max(posWorld0, posWorld1);
var markerLView = Math.round(dt.xWorldToView(markerLWorld));
var markerRView = Math.round(dt.xWorldToView(markerRWorld));
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
if (markerLWorld > viewLWorld) {
ctx.fillRect(dt.xWorldToView(viewLWorld), 0,
markerLView, ctx.canvas.height);
}
if (markerRWorld < viewRWorld) {
ctx.fillRect(markerRView, 0,
dt.xWorldToView(viewRWorld), ctx.canvas.height);
}
}
var pixelRatio = window.devicePixelRatio || 1;
ctx.lineWidth = Math.round(pixelRatio);
for (var i = 0; i < this.markers.length; ++i) {
var marker = this.markers[i];
var ts = marker.positionWorld;
if (ts < viewLWorld || ts >= viewRWorld)
continue;
marker.drawLine(ctx, ctx.canvas.height);
}
ctx.lineWidth = 1;
},
drawMarkerIndicators: function(ctx, viewLWorld, viewRWorld) {
for (var i = 0; i < this.markers.length; ++i) {
var marker = this.markers[i];
var ts = marker.positionWorld;
if (ts < viewLWorld || ts >= viewRWorld)
continue;
marker.drawIndicator(ctx);
}
},
rebuildEventToTrackMap: function() {
this.eventToTrackMap_ = undefined;
var eventToTrackMap = {};
eventToTrackMap.addEvent = function(event, track) {
if (!track)
throw new Error('Must provide a track.');
this[event.guid] = track;
};
this.modelTrackContainer_.addEventsToTrackMap(eventToTrackMap);
this.eventToTrackMap_ = eventToTrackMap;
},
trackForEvent: function(event) {
return this.eventToTrackMap_[event.guid];
}
};
/**
* Represents a marked position in the world, at a viewport level.
* @constructor
*/
function ViewportMarker(vp, positionWorld) {
this.viewport_ = vp;
this.positionWorld_ = positionWorld;
this.selected_ = false;
this.snapIndicator_ = {
show: false,
y: 0,
height: 0
};
}
ViewportMarker.prototype = {
get positionWorld() {
return this.positionWorld_;
},
set positionWorld(positionWorld) {
this.positionWorld_ = positionWorld;
this.viewport_.dispatchChangeEvent();
},
get positionView() {
return this.viewport_.currentDisplayTransform.xWorldToView(
this.positionWorld);
},
set selected(selected) {
if (this.selected === selected)
return;
this.selected_ = selected;
this.viewport_.dispatchChangeEvent();
},
get selected() {
return this.selected_;
},
get color() {
return this.selected ? 'rgb(255, 0, 0)' : 'rgb(0, 0, 0)';
},
drawLine: function(ctx, height) {
var dt = this.viewport_.currentDisplayTransform;
var viewX = Math.round(dt.xWorldToView(this.positionWorld_));
// Apply subpixel translate to get crisp lines.
// http://www.mobtowers.com/html5-canvas-crisp-lines-every-time/
ctx.save();
ctx.translate((Math.round(ctx.lineWidth) % 2) / 2, 0);
ctx.beginPath();
tracing.drawLine(ctx, viewX, 0, viewX, height);
ctx.strokeStyle = this.color;
ctx.stroke();
ctx.restore();
},
drawIndicator: function(ctx) {
if (!this.snapIndicator_.show)
return;
var dt = this.viewport_.currentDisplayTransform;
var viewX = Math.round(dt.xWorldToView(this.positionWorld_));
// Apply subpixel translate to get crisp lines.
// http://www.mobtowers.com/html5-canvas-crisp-lines-every-time/
ctx.save();
ctx.translate((Math.round(ctx.lineWidth) % 2) / 2, 0);
var pixelRatio = window.devicePixelRatio || 1;
var viewY = this.snapIndicator_.y * devicePixelRatio;
var viewHeight = this.snapIndicator_.height * devicePixelRatio;
var arrowSize = 4 * pixelRatio;
ctx.fillStyle = this.color;
tracing.drawTriangle(ctx,
viewX - arrowSize * 0.75, viewY,
viewX + arrowSize * 0.75, viewY,
viewX, viewY + arrowSize);
ctx.fill();
tracing.drawTriangle(ctx,
viewX - arrowSize * 0.75, viewY + viewHeight,
viewX + arrowSize * 0.75, viewY + viewHeight,
viewX, viewY + viewHeight - arrowSize);
ctx.fill();
ctx.restore();
},
setSnapIndicator: function(show, y, height) {
this.snapIndicator_.show = show;
this.snapIndicator_.y = y;
this.snapIndicator_.height = height;
}
};
return {
TimelineViewport: TimelineViewport,
ViewportMarker: ViewportMarker
};
});
// 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.
'use strict';
base.exportTo('tracing', function() {
var constants = {
HEADING_WIDTH: 250,
MIN_MOUSE_SELECTION_DISTANCE: 4,
LEFT_MOUSE_BUTTON: 0
};
return {
constants: constants
};
});
// 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.
'use strict';
base.require('base.range');
base.require('tracing.constants');
base.require('tracing.selection');
base.require('tracing.trace_model.slice');
/**
* @fileoverview Provides the TimingTool class.
*/
base.exportTo('tracing', function() {
var constants = tracing.constants;
/**
* Tool for taking time measurements in the TimelineTrackView using
* Viewportmarkers.
* @constructor
*/
var TimingTool = function(viewport, targetElement) {
this.viewport = viewport;
this.rangeStartMarker_ = viewport.createMarker(0);
this.rangeEndMarker_ = viewport.createMarker(0);
this.cursorMarker_ = viewport.createMarker(0);
this.activeMarker_ = this.cursorMarker_;
// Prepare the event handlers to be added and removed repeatedly.
this.onMouseMove_ = this.onMouseMove_.bind(this);
this.onDblClick_ = this.onDblClick_.bind(this);
this.targetElement_ = targetElement;
};
TimingTool.prototype = {
getWorldXFromEvent_: function(e) {
var pixelRatio = window.devicePixelRatio || 1;
var modelTrackContainer = this.viewport.modelTrackContainer;
var viewX = (e.clientX -
modelTrackContainer.offsetLeft -
constants.HEADING_WIDTH) * pixelRatio;
return this.viewport.currentDisplayTransform.xViewToWorld(viewX);
},
onEnterTiming: function(e) {
// Show the cursor marker if it was the active marker, otherwise the two
// range markers should be left.
if (this.activeMarker_ === this.cursorMarker_)
this.viewport.addMarker(this.cursorMarker_);
this.targetElement_.addEventListener('mousemove', this.onMouseMove_);
this.targetElement_.addEventListener('dblclick', this.onDblClick_);
},
onBeginTiming: function(e) {
var mouseEvent = e.data;
var worldX = this.getWorldXFromEvent_(mouseEvent);
// Check if click was on a range marker that can be moved.
if (!this.activeMarker_) {
var marker = this.viewport.findMarkerNear(worldX, 6);
if (marker === this.rangeStartMarker_ ||
marker === this.rangeEndMarker_) {
// Set the clicked marker as active marker so it will be moved.
this.activeMarker_ = marker;
marker.selected = true;
return;
}
} else {
// Otherwise start selecting a new range by hiding the cursor marker and
// adding the end marker. This is skipped if there was already a range
// on screen.
this.viewport.removeMarker(this.cursorMarker_);
this.viewport.addMarker(this.rangeEndMarker_);
}
// Set the range markers to the mouse or snapped position and select them.
var snapPos = this.getSnappedToEventPosition_(mouseEvent);
this.updateMarkerToSnapPosition_(this.rangeStartMarker_, snapPos);
this.updateMarkerToSnapPosition_(this.rangeEndMarker_, snapPos);
this.rangeStartMarker_.selected = true;
this.rangeEndMarker_.selected = true;
// The end marker is the one that is moved.
this.activeMarker_ = this.rangeEndMarker_;
},
onUpdateTiming: function(e) {
if (!this.activeMarker_ || this.activeMarker_ === this.cursorMarker_)
return;
var mouseEvent = e.data;
// Update the position of the active marker to the cursor position.
// This is either the end marker when creating a range, or one of the
// range markers when they are moved.
var snapPos = this.getSnappedToEventPosition_(mouseEvent);
this.updateMarkerToSnapPosition_(this.activeMarker_, snapPos);
// When creating a range, only show the start marker after the range
// exceeds a certain amount. This prevents a short flicker showing the
// dimmed areas left and right of the range when clicking.
if (this.rangeStartMarker_.selected && this.rangeEndMarker_.selected) {
var rangeX = Math.abs(this.rangeStartMarker_.positionView -
this.rangeEndMarker_.positionView);
if (rangeX >= constants.MIN_MOUSE_SELECTION_DISTANCE)
this.viewport.addMarker(this.rangeStartMarker_);
}
},
onEndTiming: function(e) {
var mouseEvent = e.data;
if (!this.activeMarker_ || !this.activeMarker_.selected)
return;
e.consumed = true;
// Check if a range selection is finished now.
if (this.rangeStartMarker_.selected && this.rangeEndMarker_.selected) {
var rangeX = Math.abs(this.rangeStartMarker_.positionView -
this.rangeEndMarker_.positionView);
// The range is only valid when it exceeds the minimum mouse selection
// distance, otherwise it could have been just a click.
if (rangeX >= constants.MIN_MOUSE_SELECTION_DISTANCE) {
this.rangeStartMarker_.selected = false;
this.rangeEndMarker_.selected = false;
this.activeMarker_ = null;
} else {
// If the range is not valid, hide it and activate the cursor marker.
this.viewport.removeMarker(this.rangeStartMarker_);
this.viewport.removeMarker(this.rangeEndMarker_);
this.viewport.addMarker(this.cursorMarker_);
this.cursorMarker_.positionWorld =
this.getWorldXFromEvent_(mouseEvent);
this.activeMarker_ = this.cursorMarker_;
e.consumed = false;
}
return;
}
// Deselect and deactivate a range marker that was moved.
this.activeMarker_.selected = false;
this.activeMarker_ = null;
},
onExitTiming: function(e) {
// If there is a selected range the markers are left on screen, but the
// cursor marker gets removed.
if (this.activeMarker_ === this.cursorMarker_)
this.viewport.removeMarker(this.cursorMarker_);
this.targetElement_.removeEventListener('mousemove', this.onMouseMove_);
this.targetElement_.removeEventListener('dblclick', this.onDblClick_);
},
onMouseMove_: function(e) {
var worldX = this.getWorldXFromEvent_(e);
if (this.activeMarker_) {
// Update the position of the cursor marker.
if (this.activeMarker_ === this.cursorMarker_) {
var snapPos = this.getSnappedToEventPosition_(e);
this.updateMarkerToSnapPosition_(this.cursorMarker_, snapPos);
}
return;
}
// If there is no active marker then look for a marker close to the cursor
// and indicate that it can be moved by displaying it selected.
var marker = this.viewport.findMarkerNear(worldX, 6);
if (marker === this.rangeStartMarker_ ||
marker === this.rangeEndMarker_) {
marker.selected = true;
} else {
// Otherwise deselect markers that may have been selected before.
this.rangeEndMarker_.selected = false;
this.rangeStartMarker_.selected = false;
}
},
onDblClick_: function(e) {
var modelTrackContainer = this.viewport.modelTrackContainer;
var modelTrackContainerRect = modelTrackContainer.getBoundingClientRect();
var eventWorldX = this.getWorldXFromEvent_(e);
var y = e.clientY;
var selection = new tracing.Selection();
modelTrackContainer.addClosestEventToSelection(
eventWorldX, Infinity, y, y, selection);
if (!selection.length)
return;
var slice = selection[0];
if (!(slice instanceof tracing.trace_model.Slice))
return;
if (slice.start > eventWorldX || slice.end < eventWorldX)
return;
var track = this.viewport.trackForEvent(slice);
var trackRect = track.getBoundingClientRect();
var snapPos = {
x: slice.start,
y: trackRect.top +
modelTrackContainer.scrollTop - modelTrackContainerRect.top,
height: trackRect.height,
snapped: true
};
this.updateMarkerToSnapPosition_(this.rangeStartMarker_, snapPos);
snapPos.x = slice.end;
this.updateMarkerToSnapPosition_(this.rangeEndMarker_, snapPos);
this.viewport.addMarker(this.rangeStartMarker_);
this.viewport.addMarker(this.rangeEndMarker_);
this.viewport.removeMarker(this.cursorMarker_);
this.activeMarker_ = null;
},
/**
* Get the closest position of an event within a vertical range of the mouse
* position if possible, otherwise use the position of the mouse pointer.
* @param {MouseEvent} e Mouse event with the current mouse coordinates.
* @return {
* {Number} x, The x coordinate in world space.
* {Number} y, The y coordinate in world space.
* {Number} height, The height of the event.
* {boolean} snapped Whether the coordinates are from a snapped event or
* the mouse position.
* }
*/
getSnappedToEventPosition_: function(e) {
var pixelRatio = window.devicePixelRatio || 1;
var EVENT_SNAP_RANGE = 16 * pixelRatio;
var modelTrackContainer = this.viewport.modelTrackContainer;
var modelTrackContainerRect = modelTrackContainer.getBoundingClientRect();
var viewport = this.viewport;
var dt = viewport.currentDisplayTransform;
var worldMaxDist = dt.xViewVectorToWorld(EVENT_SNAP_RANGE);
var worldX = this.getWorldXFromEvent_(e);
var mouseY = e.clientY;
var selection = new tracing.Selection();
// Look at the track under mouse position first for better performance.
modelTrackContainer.addClosestEventToSelection(
worldX, worldMaxDist, mouseY, mouseY, selection);
// Look at all tracks visible on screen.
if (!selection.length) {
modelTrackContainer.addClosestEventToSelection(
worldX, worldMaxDist,
modelTrackContainerRect.top, modelTrackContainerRect.bottom,
selection);
}
var minDistX = worldMaxDist;
var minDistY = Infinity;
var pixWidth = dt.xViewVectorToWorld(1);
// Create result object with the mouse coordinates.
var result = {
x: worldX,
y: mouseY - modelTrackContainerRect.top,
height: 0,
snapped: false
};
var eventBounds = new base.Range();
for (var i = 0; i < selection.length; i++) {
var event = selection[i];
var track = viewport.trackForEvent(event);
var trackRect = track.getBoundingClientRect();
eventBounds.reset();
event.addBoundsToRange(eventBounds);
var eventX;
if (Math.abs(eventBounds.min - worldX) <
Math.abs(eventBounds.max - worldX)) {
eventX = eventBounds.min;
} else {
eventX = eventBounds.max;
}
var distX = eventX - worldX;
var eventY = trackRect.top;
var eventHeight = trackRect.height;
var distY = Math.abs(eventY + eventHeight / 2 - mouseY);
// Prefer events with a closer y position if their x difference is below
// the width of a pixel.
if ((distX <= minDistX || Math.abs(distX - minDistX) < pixWidth) &&
distY < minDistY) {
minDistX = distX;
minDistY = distY;
// Retrieve the event position from the hit.
result.x = eventX;
result.y = eventY +
modelTrackContainer.scrollTop - modelTrackContainerRect.top;
result.height = eventHeight;
result.snapped = true;
}
}
return result;
},
/**
* Update the marker to the snapped position.
* @param {ViewportMarker} marker The marker to be updated.
* @param {
* {Number} x, The new positionWorld of the marker.
* {Number} y, The new indicatorY of the marker.
* {Number} height, The new indicatorHeight of the marker.
* {boolean} snapped Whether the coordinates are from a snapped event or
* the mouse position.
* } snapPos
*/
updateMarkerToSnapPosition_: function(marker, snapPos) {
marker.setSnapIndicator(snapPos.snapped, snapPos.y, snapPos.height);
marker.positionWorld = snapPos.x;
}
};
return {
TimingTool: TimingTool
};
});
// 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.
'use strict';
/**
* @fileoverview Container that decorates its children.
*/
base.require('base.events');
base.require('ui');
base.exportTo('ui', function() {
/**
* @constructor
*/
var ContainerThatDecoratesItsChildren = ui.define('div');
ContainerThatDecoratesItsChildren.prototype = {
__proto__: HTMLUnknownElement.prototype,
decorate: function() {
this.observer_ = new WebKitMutationObserver(this.didMutate_.bind(this));
this.observer_.observe(this, { childList: true });
// textContent is a variable on regular HTMLElements. However, we want to
// hook and prevent writes to it.
Object.defineProperty(
this, 'textContent',
{ get: undefined, set: this.onSetTextContent_});
},
appendChild: function(x) {
HTMLUnknownElement.prototype.appendChild.call(this, x);
this.didMutate_(this.observer_.takeRecords());
},
insertBefore: function(x, y) {
HTMLUnknownElement.prototype.insertBefore.call(this, x, y);
this.didMutate_(this.observer_.takeRecords());
},
removeChild: function(x) {
HTMLUnknownElement.prototype.removeChild.call(this, x);
this.didMutate_(this.observer_.takeRecords());
},
replaceChild: function(x, y) {
HTMLUnknownElement.prototype.replaceChild.call(this, x, y);
this.didMutate_(this.observer_.takeRecords());
},
onSetTextContent_: function(textContent) {
if (textContent != '')
throw new Error('textContent can only be set to \'\'.');
this.clear();
},
clear: function() {
while (this.lastChild)
HTMLUnknownElement.prototype.removeChild.call(this, this.lastChild);
this.didMutate_(this.observer_.takeRecords());
},
didMutate_: function(records) {
this.beginDecorating_();
for (var i = 0; i < records.length; i++) {
var addedNodes = records[i].addedNodes;
if (addedNodes) {
for (var j = 0; j < addedNodes.length; j++)
this.decorateChild_(addedNodes[j]);
}
var removedNodes = records[i].removedNodes;
if (removedNodes) {
for (var j = 0; j < removedNodes.length; j++) {
this.undecorateChild_(removedNodes[j]);
}
}
}
this.doneDecoratingForNow_();
},
decorateChild_: function(child) {
throw new Error('Not implemented');
},
undecorateChild_: function(child) {
throw new Error('Not implemented');
},
beginDecorating_: function() {
},
doneDecoratingForNow_: function() {
}
};
return {
ContainerThatDecoratesItsChildren: ContainerThatDecoratesItsChildren
};
});
// 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.
'use strict';
/**
* @fileoverview Renders an array of slices into the provided div,
* using a child canvas element. Uses a FastRectRenderer to draw only
* the visible slices.
*/
base.requireStylesheet('tracing.tracks.track');
base.require('ui');
base.require('ui.container_that_decorates_its_children');
base.exportTo('tracing.tracks', function() {
/**
* The base class for all tracks.
* @constructor
*/
var Track = ui.define('track', ui.ContainerThatDecoratesItsChildren);
Track.prototype = {
__proto__: ui.ContainerThatDecoratesItsChildren.prototype,
decorate: function(viewport) {
ui.ContainerThatDecoratesItsChildren.prototype.decorate.call(this);
if (viewport === undefined)
throw new Error('viewport is required when creating a Track.');
this.viewport_ = viewport;
this.classList.add('track');
},
get viewport() {
return this.viewport_;
},
context: function() {
// This is a little weird here, but we have to be able to walk up the
// parent tree to get the context.
if (!this.parentNode)
return undefined;
if (!this.parentNode.context)
throw new Error('Parent container does not support context() method.');
return this.parentNode.context();
},
decorateChild_: function(childTrack) {
},
undecorateChild_: function(childTrack) {
if (childTrack.detach)
childTrack.detach();
},
updateContents_: function() {
},
drawTrack: function(type) {
var ctx = this.context();
var pixelRatio = window.devicePixelRatio || 1;
var bounds = this.getBoundingClientRect();
var canvasBounds = ctx.canvas.getBoundingClientRect();
ctx.save();
ctx.translate(0, pixelRatio * (bounds.top - canvasBounds.top));
var dt = this.viewport.currentDisplayTransform;
var viewLWorld = dt.xViewToWorld(0);
var viewRWorld = dt.xViewToWorld(bounds.width * pixelRatio);
this.draw(type, viewLWorld, viewRWorld);
ctx.restore();
},
draw: function(type, viewLWorld, viewRWorld) {
},
addEventsToTrackMap: function(eventToTrackMap) {
},
addIntersectingItemsInRangeToSelection: function(
loVX, hiVX, loVY, hiVY, selection) {
var pixelRatio = window.devicePixelRatio || 1;
var dt = this.viewport.currentDisplayTransform;
var viewPixWidthWorld = dt.xViewVectorToWorld(1);
var loWX = dt.xViewToWorld(loVX * pixelRatio);
var hiWX = dt.xViewToWorld(hiVX * pixelRatio);
var clientRect = this.getBoundingClientRect();
var a = Math.max(loVY, clientRect.top);
var b = Math.min(hiVY, clientRect.bottom);
if (a > b)
return;
this.addIntersectingItemsInRangeToSelectionInWorldSpace(
loWX, hiWX, viewPixWidthWorld, selection);
},
addIntersectingItemsInRangeToSelectionInWorldSpace: function(
loWX, hiWX, viewPixWidthWorld, selection) {
},
/**
* Gets implemented by supporting track types. The method adds the event
* closest to worldX to the selection.
*
* @param {number} worldX The position that is looked for.
* @param {number} worldMaxDist The maximum distance allowed from worldX to
* the event.
* @param {number} loY Lower Y bound of the search interval in view space.
* @param {number} hiY Upper Y bound of the search interval in view space.
* @param {Selection} selection Selection to which to add hits.
*/
addClosestEventToSelection: function(
worldX, worldMaxDist, loY, hiY, selection) {
},
addClosestInstantEventToSelection: function(instantEvents, worldX,
worldMaxDist, selection) {
var instantEvent = base.findClosestElementInSortedArray(
instantEvents,
function(x) { return x.start; },
worldX,
worldMaxDist);
if (!instantEvent)
return;
selection.push(instantEvent);
}
};
return {
Track: Track
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.tracks.drawing_container');
base.require('base.raf');
base.require('tracing.tracks.track');
base.require('ui');
base.exportTo('tracing.tracks', function() {
var DrawType = {
SLICE: 1,
INSTANT_EVENT: 2,
BACKGROUND: 3,
GRID: 4,
FLOW_ARROWS: 5,
MARKERS: 6
};
var DrawingContainer = ui.define('drawing-container', tracing.tracks.Track);
DrawingContainer.prototype = {
__proto__: tracing.tracks.Track.prototype,
decorate: function(viewport) {
tracing.tracks.Track.prototype.decorate.call(this, viewport);
this.classList.add('drawing-container');
this.canvas_ = document.createElement('canvas');
this.canvas_.className = 'drawing-container-canvas';
this.canvas_.style.left = tracing.constants.HEADING_WIDTH + 'px';
this.appendChild(this.canvas_);
this.ctx_ = this.canvas_.getContext('2d');
this.viewportChange_ = this.viewportChange_.bind(this);
this.viewport.addEventListener('change', this.viewportChange_);
},
// Needed to support the calls in TimelineTrackView.
get canvas() {
return this.canvas_;
},
context: function() {
return this.ctx_;
},
viewportChange_: function() {
this.invalidate();
},
invalidate: function() {
if (this.rafPending_)
return;
this.rafPending_ = true;
base.requestPreAnimationFrame(function() {
this.rafPending_ = false;
this.updateCanvasSizeIfNeeded_();
base.requestAnimationFrameInThisFrameIfPossible(function() {
this.drawTrackContents_();
}, this);
}, this);
},
drawTrackContents_: function() {
this.ctx_.clearRect(0, 0, this.canvas_.width, this.canvas_.height);
var typesToDraw = [
DrawType.BACKGROUND,
DrawType.GRID,
DrawType.INSTANT_EVENT,
DrawType.SLICE,
DrawType.MARKERS
];
if (this.viewport.showFlowEvents)
typesToDraw.push(DrawType.FLOW_ARROWS);
for (var idx in typesToDraw) {
for (var i = 0; i < this.children.length; ++i) {
if (!(this.children[i] instanceof tracing.tracks.Track))
continue;
this.children[i].drawTrack(typesToDraw[idx]);
}
}
var pixelRatio = window.devicePixelRatio || 1;
var bounds = this.canvas_.getBoundingClientRect();
var dt = this.viewport.currentDisplayTransform;
var viewLWorld = dt.xViewToWorld(0);
var viewRWorld = dt.xViewToWorld(
bounds.width * pixelRatio);
this.viewport.drawGridLines(this.ctx_, viewLWorld, viewRWorld);
},
updateCanvasSizeIfNeeded_: function() {
var visibleChildTracks =
base.asArray(this.children).filter(this.visibleFilter_);
var thisBounds = this.getBoundingClientRect();
var firstChildTrackBounds = visibleChildTracks[0].getBoundingClientRect();
var lastChildTrackBounds =
visibleChildTracks[visibleChildTracks.length - 1].
getBoundingClientRect();
var innerWidth = firstChildTrackBounds.width -
tracing.constants.HEADING_WIDTH;
var innerHeight = lastChildTrackBounds.bottom - firstChildTrackBounds.top;
var pixelRatio = window.devicePixelRatio || 1;
if (this.canvas_.width != innerWidth * pixelRatio) {
this.canvas_.width = innerWidth * pixelRatio;
this.canvas_.style.width = innerWidth + 'px';
}
if (this.canvas_.height != innerHeight * pixelRatio) {
this.canvas_.height = innerHeight * pixelRatio;
this.canvas_.style.height = innerHeight + 'px';
}
var canvasTop =
firstChildTrackBounds.top - thisBounds.top + this.scrollTop;
if (this.canvas_.style.top + 'px' !== canvasTop)
this.canvas_.style.top = canvasTop + 'px';
},
visibleFilter_: function(element) {
if (!(element instanceof tracing.tracks.Track))
return false;
return window.getComputedStyle(element).display !== 'none';
},
addClosestEventToSelection: function(
worldX, worldMaxDist, loY, hiY, selection) {
for (var i = 0; i < this.children.length; ++i) {
if (!(this.children[i] instanceof tracing.tracks.Track))
continue;
var trackClientRect = this.children[i].getBoundingClientRect();
var a = Math.max(loY, trackClientRect.top);
var b = Math.min(hiY, trackClientRect.bottom);
if (a <= b) {
this.children[i].addClosestEventToSelection(
worldX, worldMaxDist, loY, hiY, selection);
}
}
tracing.tracks.Track.prototype.addClosestEventToSelection.
apply(this, arguments);
},
addEventsToTrackMap: function(eventToTrackMap) {
for (var i = 0; i < this.children.length; ++i) {
if (!(this.children[i] instanceof tracing.tracks.Track))
continue;
this.children[i].addEventsToTrackMap(eventToTrackMap);
}
}
};
return {
DrawingContainer: DrawingContainer,
DrawType: DrawType
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.tracks.heading_track');
base.require('tracing.constants');
base.require('tracing.tracks.track');
base.require('ui');
base.exportTo('tracing.tracks', function() {
/**
* A track with a header. Provides the basic heading and tooltip
* infrastructure. Subclasses must implement drawing code.
* @constructor
* @extends {HTMLDivElement}
*/
var HeadingTrack = ui.define('heading-track', tracing.tracks.Track);
HeadingTrack.prototype = {
__proto__: tracing.tracks.Track.prototype,
decorate: function(viewport) {
tracing.tracks.Track.prototype.decorate.call(this, viewport);
this.classList.add('heading-track');
this.headingDiv_ = document.createElement('heading');
this.headingDiv_.style.width = tracing.constants.HEADING_WIDTH + 'px';
this.appendChild(this.headingDiv_);
},
get heading() {
return this.headingDiv_.textContent;
},
set heading(text) {
this.headingDiv_.textContent = text;
},
set tooltip(text) {
this.headingDiv_.title = text;
},
draw: function(type, viewLWorld, viewRWorld) {
throw new Error('draw implementation missing');
}
};
return {
HeadingTrack: HeadingTrack
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.tracks.ruler_track');
base.require('tracing.constants');
base.require('tracing.tracks.track');
base.require('tracing.tracks.heading_track');
base.require('tracing.draw_helpers');
base.require('ui');
base.exportTo('tracing.tracks', function() {
/**
* A track that displays the ruler.
* @constructor
* @extends {HeadingTrack}
*/
var RulerTrack = ui.define('ruler-track', tracing.tracks.HeadingTrack);
var logOf10 = Math.log(10);
function log10(x) {
return Math.log(x) / logOf10;
}
RulerTrack.prototype = {
__proto__: tracing.tracks.HeadingTrack.prototype,
decorate: function(viewport) {
tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
this.classList.add('ruler-track');
this.strings_secs_ = [];
this.strings_msecs_ = [];
this.viewportMarkersChange_ = this.viewportMarkersChange_.bind(this);
viewport.addEventListener('markersChange', this.viewportMarkersChange_);
},
detach: function() {
tracing.tracks.HeadingTrack.prototype.detach.call(this);
this.viewport.removeEventListener('markersChange',
this.viewportMarkersChange_);
},
viewportMarkersChange_: function() {
if (this.viewport.markers.length < 1)
this.classList.remove('ruler-track-with-distance-measurements');
else
this.classList.add('ruler-track-with-distance-measurements');
},
draw: function(type, viewLWorld, viewRWorld) {
switch (type) {
case tracing.tracks.DrawType.SLICE:
this.drawSlices_(viewLWorld, viewRWorld);
break;
case tracing.tracks.DrawType.MARKERS:
this.viewport.drawMarkerLines(this.context(), viewLWorld, viewRWorld);
break;
}
},
drawSlices_: function(viewLWorld, viewRWorld) {
var ctx = this.context();
var pixelRatio = window.devicePixelRatio || 1;
var bounds = ctx.canvas.getBoundingClientRect();
var width = bounds.width * pixelRatio;
var height = bounds.height * pixelRatio;
var measurements = this.classList.contains(
'ruler-track-with-distance-measurements');
var rulerHeight = measurements ? (height * 2) / 5 : height;
var vp = this.viewport;
var dt = vp.currentDisplayTransform;
var idealMajorMarkDistancePix = 150 * pixelRatio;
var idealMajorMarkDistanceWorld =
dt.xViewVectorToWorld(idealMajorMarkDistancePix);
var majorMarkDistanceWorld;
// The conservative guess is the nearest enclosing 0.1, 1, 10, 100, etc.
var conservativeGuess =
Math.pow(10, Math.ceil(log10(idealMajorMarkDistanceWorld)));
// Once we have a conservative guess, consider things that evenly add up
// to the conservative guess, e.g. 0.5, 0.2, 0.1 Pick the one that still
// exceeds the ideal mark distance.
var divisors = [10, 5, 2, 1];
for (var i = 0; i < divisors.length; ++i) {
var tightenedGuess = conservativeGuess / divisors[i];
if (dt.xWorldVectorToView(tightenedGuess) < idealMajorMarkDistancePix)
continue;
majorMarkDistanceWorld = conservativeGuess / divisors[i - 1];
break;
}
var unit;
var unitDivisor;
var tickLabels = undefined;
if (majorMarkDistanceWorld < 100) {
unit = 'ms';
unitDivisor = 1;
tickLabels = this.strings_msecs_;
} else {
unit = 's';
unitDivisor = 1000;
tickLabels = this.strings_secs_;
}
var numTicksPerMajor = 5;
var minorMarkDistanceWorld = majorMarkDistanceWorld / numTicksPerMajor;
var minorMarkDistancePx = dt.xWorldVectorToView(minorMarkDistanceWorld);
var firstMajorMark =
Math.floor(viewLWorld / majorMarkDistanceWorld) *
majorMarkDistanceWorld;
var minorTickH = Math.floor(rulerHeight * 0.25);
ctx.save();
var pixelRatio = window.devicePixelRatio || 1;
ctx.lineWidth = Math.round(pixelRatio);
// Apply subpixel translate to get crisp lines.
// http://www.mobtowers.com/html5-canvas-crisp-lines-every-time/
var crispLineCorrection = (ctx.lineWidth % 2) / 2;
ctx.translate(crispLineCorrection, -crispLineCorrection);
ctx.fillStyle = 'rgb(0, 0, 0)';
ctx.strokeStyle = 'rgb(0, 0, 0)';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.font = (9 * pixelRatio) + 'px sans-serif';
vp.majorMarkPositions = [];
// Each iteration of this loop draws one major mark
// and numTicksPerMajor minor ticks.
//
// Rendering can't be done in world space because canvas transforms
// affect line width. So, do the conversions manually.
ctx.beginPath();
for (var curX = firstMajorMark;
curX < viewRWorld;
curX += majorMarkDistanceWorld) {
var curXView = Math.floor(dt.xWorldToView(curX));
var unitValue = curX / unitDivisor;
var roundedUnitValue = Math.floor(unitValue * 100000) / 100000;
if (!tickLabels[roundedUnitValue])
tickLabels[roundedUnitValue] = roundedUnitValue + ' ' + unit;
ctx.fillText(tickLabels[roundedUnitValue],
curXView + (2 * pixelRatio), 0);
vp.majorMarkPositions.push(curXView);
// Major mark
tracing.drawLine(ctx, curXView, 0, curXView, rulerHeight);
// Minor marks
for (var i = 1; i < numTicksPerMajor; ++i) {
var xView = Math.floor(curXView + minorMarkDistancePx * i);
tracing.drawLine(ctx,
xView, rulerHeight - minorTickH,
xView, rulerHeight);
}
}
// Draw bottom bar.
ctx.strokeStyle = 'rgb(0, 0, 0)';
tracing.drawLine(ctx, 0, height, width, height);
ctx.stroke();
// Give distance between directly adjacent markers.
if (!measurements)
return;
// Draw middle bar.
tracing.drawLine(ctx, 0, rulerHeight, width, rulerHeight);
ctx.stroke();
// Obtain a sorted array of markers
var sortedMarkers = vp.markers.slice();
sortedMarkers.sort(function(a, b) {
return a.positionWorld_ - b.positionWorld_;
});
// Distance Variables.
var displayDistance;
var displayTextColor = 'rgb(0,0,0)';
// Arrow Variables.
var arrowSpacing = 10 * pixelRatio;
var arrowColor = 'rgb(128,121,121)';
var arrowPosY = rulerHeight * 1.75;
var arrowWidthView = 3 * pixelRatio;
var arrowLengthView = 10 * pixelRatio;
var spaceForArrowsView = 2 * (arrowWidthView + arrowSpacing);
ctx.textBaseline = 'middle';
ctx.font = (14 * pixelRatio) + 'px sans-serif';
var textPosY = arrowPosY;
// If there is only on marker, draw it's timestamp next to the line.
if (sortedMarkers.length === 1) {
var markerWorld = sortedMarkers[0].positionWorld;
var markerView = dt.xWorldToView(markerWorld);
var displayValue = markerWorld / unitDivisor;
displayValue = Math.abs((Math.floor(displayValue * 1000) / 1000));
var textToDraw = displayValue + ' ' + unit;
var textLeftView = markerView + 4 * pixelRatio;
var textWidthView = ctx.measureText(textToDraw).width;
// Put text to the left in case it gets cut off.
if (textLeftView + textWidthView > width)
textLeftView = markerView - 4 * pixelRatio - textWidthView;
ctx.fillStyle = displayTextColor;
ctx.fillText(textToDraw, textLeftView, textPosY);
}
for (i = 0; i < sortedMarkers.length - 1; i++) {
var leftMarker = sortedMarkers[i];
var rightMarker = sortedMarkers[i + 1];
var leftMarkerView = dt.xWorldToView(leftMarker.positionWorld);
var rightMarkerView = dt.xWorldToView(rightMarker.positionWorld);
var distanceBetweenMarkers =
rightMarker.positionWorld - leftMarker.positionWorld;
var distanceBetweenMarkersView =
dt.xWorldVectorToView(distanceBetweenMarkers);
var positionInMiddleOfMarkersView =
leftMarkerView + (distanceBetweenMarkersView / 2);
// Determine units.
if (distanceBetweenMarkers < 100) {
unit = 'ms';
unitDivisor = 1;
} else {
unit = 's';
unitDivisor = 1000;
}
// Calculate display value to print.
displayDistance = distanceBetweenMarkers / unitDivisor;
var roundedDisplayDistance =
Math.abs((Math.floor(displayDistance * 1000) / 1000));
var textToDraw = roundedDisplayDistance + ' ' + unit;
var textWidthView = ctx.measureText(textToDraw).width;
var spaceForArrowsAndTextView =
textWidthView + spaceForArrowsView + arrowSpacing;
// Set text positions.
var textLeftView = positionInMiddleOfMarkersView - textWidthView / 2;
var textRightView = textLeftView + textWidthView;
if (sortedMarkers.length === 2 &&
spaceForArrowsAndTextView > distanceBetweenMarkersView) {
// Print the display distance text right of the 2 markers.
textLeftView = rightMarkerView + 2 * arrowSpacing;
// Put text to the left in case it gets cut off.
if (textLeftView + textWidthView > width)
textLeftView = leftMarkerView - 2 * arrowSpacing - textWidthView;
ctx.fillStyle = displayTextColor;
ctx.fillText(textToDraw, textLeftView, textPosY);
// Draw the arrows pointing from outside in and a line in between.
ctx.strokeStyle = arrowColor;
ctx.beginPath();
tracing.drawLine(ctx, leftMarkerView, arrowPosY, rightMarkerView,
arrowPosY);
ctx.stroke();
ctx.fillStyle = arrowColor;
tracing.drawArrow(ctx,
leftMarkerView - 1.5 * arrowSpacing, arrowPosY,
leftMarkerView, arrowPosY,
arrowLengthView, arrowWidthView);
tracing.drawArrow(ctx,
rightMarkerView + 1.5 * arrowSpacing, arrowPosY,
rightMarkerView, arrowPosY,
arrowLengthView, arrowWidthView);
} else if (spaceForArrowsView <= distanceBetweenMarkersView) {
var leftArrowStart;
var rightArrowStart;
if (spaceForArrowsAndTextView <= distanceBetweenMarkersView) {
// Print the display distance text.
ctx.fillStyle = displayTextColor;
ctx.fillText(textToDraw, textLeftView, textPosY);
leftArrowStart = textLeftView - arrowSpacing;
rightArrowStart = textRightView + arrowSpacing;
} else {
leftArrowStart = positionInMiddleOfMarkersView;
rightArrowStart = positionInMiddleOfMarkersView;
}
// Draw the arrows pointing inside out.
ctx.strokeStyle = arrowColor;
ctx.fillStyle = arrowColor;
tracing.drawArrow(ctx,
leftArrowStart, arrowPosY,
leftMarkerView, arrowPosY,
arrowLengthView, arrowWidthView);
tracing.drawArrow(ctx,
rightArrowStart, arrowPosY,
rightMarkerView, arrowPosY,
arrowLengthView, arrowWidthView);
}
}
ctx.restore();
},
/**
* Adds items intersecting the given range to a selection.
* @param {number} loVX Lower X bound of the interval to search, in
* viewspace.
* @param {number} hiVX Upper X bound of the interval to search, in
* viewspace.
* @param {number} loVY Lower Y bound of the interval to search, in
* viewspace.
* @param {number} hiVY Upper Y bound of the interval to search, in
* viewspace.
* @param {Selection} selection Selection to which to add results.
*/
addIntersectingItemsInRangeToSelection: function(
loVX, hiVX, loY, hiY, selection) {
// Does nothing. There's nothing interesting to pick on the ruler
// track.
},
addAllObjectsMatchingFilterToSelection: function(filter, selection) {
}
};
return {
RulerTrack: RulerTrack
};
});
// 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.
'use strict';
base.exportTo('base', function() {
/**
* Uses an embedded iframe to measure provided elements without forcing layout
* on the main document. You must call attach() on the stick before using it,
* and call detach() on it when you are done using it.
* @constructor
* @extends {Object}
*/
function MeasuringStick() {
this.iframe_ = undefined;
}
MeasuringStick.prototype = {
__proto__: Object.prototype,
/**
* Measures the provided element without forcing layout on the main
* document.
*/
measure: function(element) {
this.iframe_.contentDocument.body.appendChild(element);
var style = this.iframe_.contentWindow.getComputedStyle(element);
var width = parseInt(style.width, 10);
var height = parseInt(style.height, 10);
this.iframe_.contentDocument.body.removeChild(element);
return { width: width, height: height };
},
attach: function() {
var iframe = document.createElement('iframe');
iframe.style.cssText =
'position:absolute;width:100%;height:0;border:0;visibility:hidden';
document.body.appendChild(iframe);
this.iframe_ = iframe;
this.iframe_.contentDocument.body.style.cssText =
'padding:0;margin:0;overflow:hidden';
var stylesheets = document.querySelectorAll('link[rel=stylesheet]');
for (var i = 0; i < stylesheets.length; i++) {
var stylesheet = stylesheets[i];
var link = this.iframe_.contentDocument.createElement('link');
link.rel = 'stylesheet';
link.href = stylesheet.href;
this.iframe_.contentDocument.head.appendChild(link);
}
},
detach: function() {
document.body.removeChild(this.iframe_);
this.iframe_ = undefined;
}
};
return {
MeasuringStick: MeasuringStick
};
});
// 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.
'use strict';
base.require('tracing.tracks.track');
base.require('tracing.filter');
base.require('ui');
base.exportTo('tracing.tracks', function() {
/**
* A generic track that contains other tracks as its children.
* @constructor
*/
var ContainerTrack = ui.define('container-track', tracing.tracks.Track);
ContainerTrack.prototype = {
__proto__: tracing.tracks.Track.prototype,
decorate: function(viewport) {
tracing.tracks.Track.prototype.decorate.call(this, viewport);
},
detach: function() {
this.textContent = '';
},
get tracks_() {
var tracks = [];
for (var i = 0; i < this.children.length; i++) {
if (this.children[i].classList.contains('track'))
tracks.push(this.children[i]);
}
return tracks;
},
drawTrack: function(type) {
for (var i = 0; i < this.children.length; ++i) {
if (!(this.children[i] instanceof tracing.tracks.Track))
continue;
this.children[i].drawTrack(type);
}
},
/**
* Adds items intersecting the given range to a selection.
* @param {number} loVX Lower X bound of the interval to search, in
* viewspace.
* @param {number} hiVX Upper X bound of the interval to search, in
* viewspace.
* @param {number} loY Lower Y bound of the interval to search, in
* viewspace space.
* @param {number} hiY Upper Y bound of the interval to search, in
* viewspace space.
* @param {Selection} selection Selection to which to add results.
*/
addIntersectingItemsInRangeToSelection: function(
loVX, hiVX, loY, hiY, selection) {
for (var i = 0; i < this.tracks_.length; i++) {
var trackClientRect = this.tracks_[i].getBoundingClientRect();
var a = Math.max(loY, trackClientRect.top);
var b = Math.min(hiY, trackClientRect.bottom);
if (a <= b)
this.tracks_[i].addIntersectingItemsInRangeToSelection(
loVX, hiVX, loY, hiY, selection);
}
tracing.tracks.Track.prototype.addIntersectingItemsInRangeToSelection.
apply(this, arguments);
},
addEventsToTrackMap: function(eventToTrackMap) {
for (var i = 0; i < this.children.length; ++i)
this.children[i].addEventsToTrackMap(eventToTrackMap);
},
addAllObjectsMatchingFilterToSelection: function(filter, selection) {
for (var i = 0; i < this.tracks_.length; i++)
this.tracks_[i].addAllObjectsMatchingFilterToSelection(
filter, selection);
},
addClosestEventToSelection: function(
worldX, worldMaxDist, loY, hiY, selection) {
for (var i = 0; i < this.tracks_.length; i++) {
var trackClientRect = this.tracks_[i].getBoundingClientRect();
var a = Math.max(loY, trackClientRect.top);
var b = Math.min(hiY, trackClientRect.bottom);
if (a <= b) {
this.tracks_[i].addClosestEventToSelection(
worldX, worldMaxDist, loY, hiY, selection);
}
}
tracing.tracks.Track.prototype.addClosestEventToSelection.
apply(this, arguments);
}
};
return {
ContainerTrack: ContainerTrack
};
});
// 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.
'use strict';
/**
* @fileoverview Provides a mechanism for drawing massive numbers of
* colored rectangles into a canvas in an efficient manner, provided
* they are drawn left to right with fixed y and height throughout.
*
* The basic idea used here is to fuse subpixel rectangles together so that
* we never issue a canvas fillRect for them. It turns out Javascript can
* do this quite efficiently, compared to asking Canvas2D to do the same.
*
* A few extra things are done by this class in the name of speed:
* - Viewport culling: off-viewport rectangles are discarded.
*
* - The actual discarding operation is done in world space,
* e.g. pre-transform.
*
* - Rather than expending compute cycles trying to figure out an average
* color for fused rectangles from css strings, you instead draw using
* palletized colors. The fused rect color is choosen from the rectangle with
* the higher alpha value, if equal the max pallete index encountered.
*
* Make sure to flush the trackRenderer before finishing drawing in order
* to commit any queued drawing operations.
*/
base.exportTo('tracing', function() {
/**
* Creates a fast rect renderer with a specific set of culling rules
* and color pallette.
* @param {GraphicsContext2D} ctx Canvas2D drawing context.
* @param {number} minRectSize Only rectangles with width < minRectSize are
* considered for merging.
* @param {number} maxMergeDist Controls how many successive small rectangles
* can be merged together before issuing a rectangle.
* @param {Array} pallette The color pallete for drawing. Pallette slots
* should map to valid Canvas fillStyle strings.
*
* @constructor
*/
function FastRectRenderer(ctx, minRectSize, maxMergeDist, pallette) {
this.ctx_ = ctx;
this.minRectSize_ = minRectSize;
this.maxMergeDist_ = maxMergeDist;
this.pallette_ = pallette;
}
FastRectRenderer.prototype = {
y_: 0,
h_: 0,
merging_: false,
mergeStartX_: 0,
mergeCurRight_: 0,
mergedColorId_: 0,
mergedAlpha_: 0,
/**
* Changes the y position and height for subsequent fillRect
* calls. x and width are specifieid on the fillRect calls.
*/
setYandH: function(y, h) {
this.flush();
this.y_ = y;
this.h_ = h;
},
/**
* Fills rectangle at the specified location, if visible. If the
* rectangle is subpixel, it will be merged with adjacent rectangles.
* The drawing operation may not take effect until flush is called.
* @param {number} colorId The color of this rectangle, as an index
* in the renderer's color pallete.
* @param {number} alpha The opacity of the rectangle as 0.0-1.0 number.
*/
fillRect: function(x, w, colorId, alpha) {
var r = x + w;
if (w < this.minRectSize_) {
if (r - this.mergeStartX_ > this.maxMergeDist_)
this.flush();
if (!this.merging_) {
this.merging_ = true;
this.mergeStartX_ = x;
this.mergeCurRight_ = r;
this.mergedColorId_ = colorId;
this.mergedAlpha_ = alpha;
} else {
this.mergeCurRight_ = r;
if (this.mergedAlpha_ < alpha ||
(this.mergedAlpha_ === alpha && this.mergedColorId_ < colorId)) {
this.mergedAlpha_ = alpha;
this.mergedColorId_ = colorId;
}
}
} else {
if (this.merging_)
this.flush();
this.ctx_.fillStyle = this.pallette_[colorId];
this.ctx_.globalAlpha = alpha;
this.ctx_.fillRect(x, this.y_, w, this.h_);
}
},
/**
* Commits any pending fillRect operations to the underlying graphics
* context.
*/
flush: function() {
if (this.merging_) {
this.ctx_.fillStyle = this.pallette_[this.mergedColorId_];
this.ctx_.globalAlpha = this.mergedAlpha_;
this.ctx_.fillRect(this.mergeStartX_, this.y_,
this.mergeCurRight_ - this.mergeStartX_, this.h_);
this.merging_ = false;
}
}
};
return {
FastRectRenderer: FastRectRenderer
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.tracks.slice_track');
base.require('base.sorted_array_utils');
base.require('tracing.tracks.heading_track');
base.require('tracing.fast_rect_renderer');
base.require('tracing.draw_helpers');
base.require('ui');
base.exportTo('tracing.tracks', function() {
/**
* A track that displays an array of Slice objects.
* @constructor
* @extends {HeadingTrack}
*/
var SliceTrack = ui.define(
'slice-track', tracing.tracks.HeadingTrack);
SliceTrack.prototype = {
__proto__: tracing.tracks.HeadingTrack.prototype,
decorate: function(viewport) {
tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
this.classList.add('slice-track');
this.asyncStyle_ = false;
this.slices_ = null;
},
get asyncStyle() {
return this.asyncStyle_;
},
set asyncStyle(v) {
this.asyncStyle_ = !!v;
},
get slices() {
return this.slices_;
},
set slices(slices) {
this.slices_ = slices || [];
},
get height() {
return window.getComputedStyle(this).height;
},
set height(height) {
this.style.height = height;
},
get hasVisibleContent() {
return this.slices.length > 0;
},
draw: function(type, viewLWorld, viewRWorld) {
switch (type) {
case tracing.tracks.DrawType.SLICE:
this.drawSlices_(viewLWorld, viewRWorld);
break;
}
},
drawSlices_: function(viewLWorld, viewRWorld) {
var ctx = this.context();
ctx.save();
var bounds = this.getBoundingClientRect();
tracing.drawSlices(
ctx,
this.viewport.currentDisplayTransform,
viewLWorld,
viewRWorld,
bounds.height,
this.slices_,
this.asyncStyle_);
ctx.restore();
if (bounds.height <= 8)
return;
tracing.drawLabels(
ctx,
this.viewport.currentDisplayTransform,
viewLWorld,
viewRWorld,
this.slices_,
this.asyncStyle_);
},
addEventsToTrackMap: function(eventToTrackMap) {
if (this.slices_ === undefined || this.slices_ === null)
return;
this.slices_.forEach(function(slice) {
eventToTrackMap.addEvent(slice, this);
}, this);
},
addIntersectingItemsInRangeToSelectionInWorldSpace: function(
loWX, hiWX, viewPixWidthWorld, selection) {
function onSlice(slice) {
selection.push(slice);
}
base.iterateOverIntersectingIntervals(this.slices_,
function(x) { return x.start; },
function(x) { return x.duration; },
loWX, hiWX,
onSlice);
},
/**
* Find the index for the given slice.
* @return {index} Index of the given slice, or undefined.
* @private
*/
indexOfSlice_: function(slice) {
var index = base.findLowIndexInSortedArray(this.slices_,
function(x) { return x.start; },
slice.start);
while (index < this.slices_.length &&
slice.start == this.slices_[index].start &&
slice.colorId != this.slices_[index].colorId) {
index++;
}
return index < this.slices_.length ? index : undefined;
},
/**
* Add the item to the left or right of the provided event, if any, to the
* selection.
* @param {slice} The current slice.
* @param {Number} offset Number of slices away from the event to look.
* @param {Selection} selection The selection to add an event to,
* if found.
* @return {boolean} Whether an event was found.
* @private
*/
addItemNearToProvidedEventToSelection: function(event, offset, selection) {
var index = this.indexOfSlice_(event);
if (index === undefined)
return false;
var newIndex = index + offset;
if (newIndex < 0 || newIndex >= this.slices_.length)
return false;
selection.push(this.slices_[newIndex]);
return true;
},
addAllObjectsMatchingFilterToSelection: function(filter, selection) {
for (var i = 0; i < this.slices_.length; ++i) {
if (filter.matchSlice(this.slices_[i]))
selection.push(this.slices_[i]);
}
},
addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY,
selection) {
var slice = base.findClosestIntervalInSortedIntervals(
this.slices_,
function(x) { return x.start; },
function(x) { return x.end; },
worldX,
worldMaxDist);
if (slice)
selection.push(slice);
}
};
return {
SliceTrack: SliceTrack
};
});
// 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.
'use strict';
base.require('tracing.tracks.container_track');
base.require('tracing.tracks.slice_track');
base.require('tracing.filter');
base.require('tracing.trace_model');
base.require('ui');
base.exportTo('tracing.tracks', function() {
/**
* Visualizes a Cpu using a series of of SliceTracks.
* @constructor
*/
var CpuTrack =
ui.define('cpu-track', tracing.tracks.ContainerTrack);
CpuTrack.prototype = {
__proto__: tracing.tracks.ContainerTrack.prototype,
decorate: function(viewport) {
tracing.tracks.ContainerTrack.prototype.decorate.call(this, viewport);
this.classList.add('cpu-track');
},
get cpu() {
return this.cpu_;
},
set cpu(cpu) {
this.cpu_ = cpu;
this.updateContents_();
},
get tooltip() {
return this.tooltip_;
},
set tooltip(value) {
this.tooltip_ = value;
this.updateContents_();
},
get hasVisibleContent() {
return this.children.length > 0;
},
updateContents_: function() {
this.detach();
if (!this.cpu_)
return;
var slices = this.cpu_.slices;
if (slices.length) {
var track = new tracing.tracks.SliceTrack(this.viewport);
track.slices = slices;
track.heading = this.cpu_.userFriendlyName + ':';
this.appendChild(track);
}
for (var counterName in this.cpu_.counters) {
var counter = this.cpu_.counters[counterName];
track = new tracing.tracks.CounterTrack(this.viewport);
track.heading = this.cpu_.userFriendlyName + ' ' +
counter.name + ':';
track.counter = counter;
this.appendChild(track);
}
}
};
return {
CpuTrack: CpuTrack
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.tracks.object_instance_track');
base.require('base.sorted_array_utils');
base.require('tracing.trace_model.event');
base.require('tracing.tracks.heading_track');
base.require('tracing.color_scheme');
base.require('ui');
base.exportTo('tracing.tracks', function() {
var SelectionState = tracing.trace_model.SelectionState;
var EventPresenter = tracing.EventPresenter;
/**
* A track that displays an array of Slice objects.
* @constructor
* @extends {HeadingTrack}
*/
var ObjectInstanceTrack = ui.define(
'object-instance-track', tracing.tracks.HeadingTrack);
ObjectInstanceTrack.prototype = {
__proto__: tracing.tracks.HeadingTrack.prototype,
decorate: function(viewport) {
tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
this.classList.add('object-instance-track');
this.objectInstances_ = [];
this.objectSnapshots_ = [];
},
get objectInstances() {
return this.objectInstances_;
},
set objectInstances(objectInstances) {
if (!objectInstances || objectInstances.length == 0) {
this.heading = '';
this.objectInstances_ = [];
this.objectSnapshots_ = [];
return;
}
this.heading = objectInstances[0].typeName;
this.objectInstances_ = objectInstances;
this.objectSnapshots_ = [];
this.objectInstances_.forEach(function(instance) {
this.objectSnapshots_.push.apply(
this.objectSnapshots_, instance.snapshots);
}, this);
},
get height() {
return window.getComputedStyle(this).height;
},
set height(height) {
this.style.height = height;
},
get snapshotRadiusView() {
return 7 * (window.devicePixelRatio || 1);
},
draw: function(type, viewLWorld, viewRWorld) {
switch (type) {
case tracing.tracks.DrawType.SLICE:
this.drawSlices_(viewLWorld, viewRWorld);
break;
}
},
drawSlices_: function(viewLWorld, viewRWorld) {
var ctx = this.context();
var pixelRatio = window.devicePixelRatio || 1;
var bounds = this.getBoundingClientRect();
var height = bounds.height * pixelRatio;
var halfHeight = height * 0.5;
var twoPi = Math.PI * 2;
// Culling parameters.
var dt = this.viewport.currentDisplayTransform;
var snapshotRadiusView = this.snapshotRadiusView;
var snapshotRadiusWorld = dt.xViewVectorToWorld(height);
var loI;
// Begin rendering in world space.
ctx.save();
dt.applyTransformToCanvas(ctx);
// Instances
var objectInstances = this.objectInstances_;
var loI = base.findLowIndexInSortedArray(
objectInstances,
function(instance) {
return instance.deletionTs;
},
viewLWorld);
ctx.strokeStyle = 'rgb(0,0,0)';
for (var i = loI; i < objectInstances.length; ++i) {
var instance = objectInstances[i];
var x = instance.creationTs;
if (x > viewRWorld)
break;
var right = instance.deletionTs == Number.MAX_VALUE ?
viewRWorld : instance.deletionTs;
ctx.fillStyle = EventPresenter.getObjectInstanceColor(instance);
ctx.fillRect(x, pixelRatio, right - x, height - 2 * pixelRatio);
}
ctx.restore();
// Snapshots. Has to run in worldspace because ctx.arc gets transformed.
var objectSnapshots = this.objectSnapshots_;
loI = base.findLowIndexInSortedArray(
objectSnapshots,
function(snapshot) {
return snapshot.ts + snapshotRadiusWorld;
},
viewLWorld);
for (var i = loI; i < objectSnapshots.length; ++i) {
var snapshot = objectSnapshots[i];
var x = snapshot.ts;
if (x - snapshotRadiusWorld > viewRWorld)
break;
var xView = dt.xWorldToView(x);
ctx.fillStyle = EventPresenter.getObjectSnapshotColor(snapshot);
ctx.beginPath();
ctx.arc(xView, halfHeight, snapshotRadiusView, 0, twoPi);
ctx.fill();
if (snapshot.selected) {
ctx.lineWidth = 5;
ctx.strokeStyle = 'rgb(100,100,0)';
ctx.stroke();
ctx.beginPath();
ctx.arc(xView, halfHeight, snapshotRadiusView - 1, 0, twoPi);
ctx.lineWidth = 2;
ctx.strokeStyle = 'rgb(255,255,0)';
ctx.stroke();
} else {
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgb(0,0,0)';
ctx.stroke();
}
}
ctx.lineWidth = 1;
// For performance reasons we only check the SelectionState of the first
// instance. If it's DIMMED we assume that all are DIMMED.
// TODO(egraether): Allow partial highlight.
var selectionState = SelectionState.NONE;
if (objectInstances.length &&
objectInstances[0].selectionState === SelectionState.DIMMED) {
selectionState = SelectionState.DIMMED;
}
// Dim the track when there is an active highlight.
if (selectionState === SelectionState.DIMMED) {
var width = bounds.width * pixelRatio;
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.fillRect(0, 0, width, height);
ctx.restore();
}
},
addEventsToTrackMap: function(eventToTrackMap) {
if (this.objectInstance_ !== undefined) {
this.objectInstance_.forEach(function(obj) {
eventToTrackMap.addEvent(obj, this);
}, this);
}
if (this.objectSnapshots_ !== undefined) {
this.objectSnapshots_.forEach(function(obj) {
eventToTrackMap.addEvent(obj, this);
}, this);
}
},
addIntersectingItemsInRangeToSelectionInWorldSpace: function(
loWX, hiWX, viewPixWidthWorld, selection) {
// Pick snapshots first.
var foundSnapshot = false;
function onSnapshot(snapshot) {
selection.push(snapshot);
foundSnapshot = true;
}
var snapshotRadiusView = this.snapshotRadiusView;
var snapshotRadiusWorld = viewPixWidthWorld * snapshotRadiusView;
base.iterateOverIntersectingIntervals(
this.objectSnapshots_,
function(x) { return x.ts - snapshotRadiusWorld; },
function(x) { return 2 * snapshotRadiusWorld; },
loWX, hiWX,
onSnapshot);
if (foundSnapshot)
return;
// Try picking instances.
base.iterateOverIntersectingIntervals(
this.objectInstances_,
function(x) { return x.creationTs; },
function(x) { return x.deletionTs - x.creationTs; },
loWX, hiWX,
selection.push.bind(selection));
},
/**
* Add the item to the left or right of the provided event, if any, to the
* selection.
* @param {event} The current event item.
* @param {Number} offset Number of slices away from the event to look.
* @param {Selection} selection The selection to add an event to,
* if found.
* @return {boolean} Whether an event was found.
* @private
*/
addItemNearToProvidedEventToSelection: function(event, offset, selection) {
var events;
if (event instanceof tracing.trace_model.ObjectSnapshot)
events = this.objectSnapshots_;
else if (event instanceof tracing.trace_model.ObjectInstance)
events = this.objectInstances_;
else
throw new Error('Unrecognized event');
var index = events.indexOf(event);
var newIndex = index + offset;
if (newIndex >= 0 && newIndex < events.length) {
selection.push(events[newIndex]);
return true;
}
return false;
},
addAllObjectsMatchingFilterToSelection: function(filter, selection) {
},
addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY,
selection) {
var snapshot = base.findClosestElementInSortedArray(
this.objectSnapshots_,
function(x) { return x.ts; },
worldX,
worldMaxDist);
if (!snapshot)
return;
selection.push(snapshot);
// TODO(egraether): Search for object instances as well, which was not
// implemented because it makes little sense with the current visual and
// needs to take care of overlapping intervals.
}
};
ObjectInstanceTrack.typeNameToTrackConstructorMap = {};
ObjectInstanceTrack.register = function(typeName, constructor) {
if (ObjectInstanceTrack.typeNameToTrackConstructorMap[typeName])
throw new Error('Handler already registered for ' + typeName);
ObjectInstanceTrack.typeNameToTrackConstructorMap[typeName] =
constructor;
};
ObjectInstanceTrack.getTrackConstructor = function(typeName) {
return ObjectInstanceTrack.typeNameToTrackConstructorMap[typeName];
};
return {
ObjectInstanceTrack: ObjectInstanceTrack
};
});
// 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.
'use strict';
base.require('tracing.tracks.heading_track');
base.require('tracing.color_scheme');
base.require('ui');
base.exportTo('tracing.tracks', function() {
/**
* A track that displays traces as stacked bars.
* @constructor
* @extends {HeadingTrack}
*/
var StackedBarsTrack = ui.define(
'stacked-bars-track', tracing.tracks.HeadingTrack);
StackedBarsTrack.prototype = {
__proto__: tracing.tracks.HeadingTrack.prototype,
decorate: function(viewport) {
tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
this.classList.add('stacked-bars-track');
this.objectInstance_ = null;
},
addEventsToTrackMap: function(eventToTrackMap) {
var objectSnapshots = this.objectInstance_.snapshots;
objectSnapshots.forEach(function(obj) {
eventToTrackMap.addEvent(obj, this);
}, this);
},
/**
* Used to hit-test clicks in the graph.
*/
addIntersectingItemsInRangeToSelectionInWorldSpace: function(
loWX, hiWX, viewPixWidthWorld, selection) {
function onSnapshot(snapshot) {
selection.push(snapshot);
}
var snapshots = this.objectInstance_.snapshots;
var maxBounds = this.objectInstance_.parent.model.bounds.max;
base.iterateOverIntersectingIntervals(
snapshots,
function(x) { return x.ts; },
function(x, i) {
if (i == snapshots.length - 1) {
if (snapshots.length == 1)
return maxBounds;
return snapshots[i].ts - snapshots[i - 1].ts;
}
return snapshots[i + 1].ts - snapshots[i].ts;
},
loWX, hiWX,
onSnapshot);
},
/**
* Add the item to the left or right of the provided item, if any, to the
* selection.
* @param {slice} The current slice.
* @param {Number} offset Number of slices away from the object to look.
* @param {Selection} selection The selection to add an event to,
* if found.
* @return {boolean} Whether an event was found.
* @private
*/
addItemNearToProvidedEventToSelection: function(event, offset, selection) {
if (!(event instanceof tracing.trace_model.ObjectSnapshot))
throw new Error('Unrecognized event');
var objectSnapshots = this.objectInstance_.snapshots;
var index = objectSnapshots.indexOf(event);
var newIndex = index + offset;
if (newIndex >= 0 && newIndex < objectSnapshots.length) {
selection.push(objectSnapshots[newIndex]);
return true;
}
return false;
},
addAllObjectsMatchingFilterToSelection: function(filter, selection) {
},
addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY,
selection) {
var snapshot = base.findClosestElementInSortedArray(
this.objectInstance_.snapshots,
function(x) { return x.ts; },
worldX,
worldMaxDist);
if (!snapshot)
return;
selection.push(snapshot);
}
};
return {
StackedBarsTrack: StackedBarsTrack
};
});
// 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.
'use strict';
base.requireStylesheet('tcmalloc.heap_instance_track');
base.require('base.sorted_array_utils');
base.require('tracing.tracks.stacked_bars_track');
base.require('tracing.tracks.object_instance_track');
base.require('tracing.color_scheme');
base.require('ui');
base.exportTo('tcmalloc', function() {
var EventPresenter = tracing.EventPresenter;
/**
* A track that displays heap memory data.
* @constructor
* @extends {StackedBarsTrack}
*/
var HeapInstanceTrack = ui.define(
'heap-instance-track', tracing.tracks.StackedBarsTrack);
HeapInstanceTrack.prototype = {
__proto__: tracing.tracks.StackedBarsTrack.prototype,
decorate: function(viewport) {
tracing.tracks.StackedBarsTrack.prototype.decorate.call(this, viewport);
this.classList.add('heap-instance-track');
this.objectInstance_ = null;
},
set objectInstances(objectInstances) {
if (!objectInstances) {
this.objectInstance_ = [];
return;
}
if (objectInstances.length != 1)
throw new Error('Bad object instance count.');
this.objectInstance_ = objectInstances[0];
this.maxBytes_ = this.computeMaxBytes_(
this.objectInstance_.snapshots);
},
computeMaxBytes_: function(snapshots) {
var maxBytes = 0;
for (var i = 0; i < snapshots.length; i++) {
var snapshot = snapshots[i];
// Sum all the current allocations in this snapshot.
var traceNames = Object.keys(snapshot.heap_.children);
var sumBytes = 0;
for (var j = 0; j < traceNames.length; j++) {
sumBytes += snapshot.heap_.children[traceNames[j]].currentBytes;
}
// Keep track of the maximum across all snapshots.
if (sumBytes > maxBytes)
maxBytes = sumBytes;
}
return maxBytes;
},
get height() {
return window.getComputedStyle(this).height;
},
set height(height) {
this.style.height = height;
},
draw: function(type, viewLWorld, viewRWorld) {
switch (type) {
case tracing.tracks.DrawType.SLICE:
this.drawSlices_(viewLWorld, viewRWorld);
break;
}
},
drawSlices_: function(viewLWorld, viewRWorld) {
var ctx = this.context();
var pixelRatio = window.devicePixelRatio || 1;
var bounds = this.getBoundingClientRect();
var width = bounds.width * pixelRatio;
var height = bounds.height * pixelRatio;
// Culling parameters.
var dt = this.viewport.currentDisplayTransform;
// Scale by the size of the largest snapshot.
var maxBytes = this.maxBytes_;
var objectSnapshots = this.objectInstance_.snapshots;
var lowIndex = base.findLowIndexInSortedArray(
objectSnapshots,
function(snapshot) {
return snapshot.ts;
},
viewLWorld);
// Assure that the stack with the left edge off screen still gets drawn
if (lowIndex > 0)
lowIndex -= 1;
for (var i = lowIndex; i < objectSnapshots.length; ++i) {
var snapshot = objectSnapshots[i];
var left = snapshot.ts;
if (left > viewRWorld)
break;
var leftView = dt.xWorldToView(left);
if (leftView < 0)
leftView = 0;
// Compute the edges for the column graph bar.
var right;
if (i != objectSnapshots.length - 1) {
right = objectSnapshots[i + 1].ts;
} else {
// If this is the last snaphot of multiple snapshots, use the width of
// the previous snapshot for the width.
if (objectSnapshots.length > 1)
right = objectSnapshots[i].ts + (objectSnapshots[i].ts -
objectSnapshots[i - 1].ts);
else
// If there's only one snapshot, use max bounds as the width.
right = this.objectInstance_.parent.model.bounds.max;
}
var rightView = dt.xWorldToView(right);
if (rightView > width)
rightView = width;
// Floor the bounds to avoid a small gap between stacks.
leftView = Math.floor(leftView);
rightView = Math.floor(rightView);
// Draw a stacked bar graph. Largest item is stored first in the
// heap data structure, so iterate backwards. Likewise draw from
// the bottom of the bar upwards.
var currentY = height;
var keys = Object.keys(snapshot.heap_.children);
for (var k = keys.length - 1; k >= 0; k--) {
var trace = snapshot.heap_.children[keys[k]];
if (this.objectInstance_.selectedTraces &&
this.objectInstance_.selectedTraces.length > 0 &&
this.objectInstance_.selectedTraces[0] == keys[k]) {
// A trace selected in the analysis view is bright yellow.
ctx.fillStyle = 'rgb(239, 248, 206)';
} else
ctx.fillStyle = EventPresenter.getBarSnapshotColor(snapshot, k);
var barHeight = height * trace.currentBytes / maxBytes;
ctx.fillRect(leftView, currentY - barHeight,
Math.max(rightView - leftView, 1), barHeight);
currentY -= barHeight;
}
}
ctx.lineWidth = 1;
}
};
tracing.tracks.ObjectInstanceTrack.register(
'memory::Heap', HeapInstanceTrack);
return {
HeapInstanceTrack: HeapInstanceTrack
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.tracks.counter_track');
base.require('tracing.trace_model.event');
base.require('tracing.tracks.heading_track');
base.require('tracing.color_scheme');
base.require('ui');
base.exportTo('tracing.tracks', function() {
var SelectionState = tracing.trace_model.SelectionState;
var EventPresenter = tracing.EventPresenter;
var LAST_SAMPLE_PIXELS = 8;
/**
* A track that displays a Counter object.
* @constructor
* @extends {HeadingTrack}
*/
var CounterTrack =
ui.define('counter-track', tracing.tracks.HeadingTrack);
CounterTrack.prototype = {
__proto__: tracing.tracks.HeadingTrack.prototype,
decorate: function(viewport) {
tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
this.classList.add('counter-track');
},
get counter() {
return this.counter_;
},
set counter(counter) {
this.counter_ = counter;
this.heading = counter.name + ': ';
},
draw: function(type, viewLWorld, viewRWorld) {
switch (type) {
case tracing.tracks.DrawType.SLICE:
this.drawSlices_(viewLWorld, viewRWorld);
break;
}
},
drawSlices_: function(viewLWorld, viewRWorld) {
var ctx = this.context();
var pixelRatio = window.devicePixelRatio || 1;
var bounds = this.getBoundingClientRect();
var height = bounds.height * pixelRatio;
var counter = this.counter_;
// Culling parametrs.
var vp = this.viewport;
var dt = vp.currentDisplayTransform;
var pixWidth = dt.xViewVectorToWorld(1);
// Drop sampels that are less than skipDistancePix apart.
var skipDistancePix = 1;
var skipDistanceWorld = dt.xViewVectorToWorld(skipDistancePix);
// Begin rendering in world space.
ctx.save();
dt.applyTransformToCanvas(ctx);
// Figure out where drawing should begin.
var numSeries = counter.numSeries;
var numSamples = counter.numSamples;
var startIndex = base.findLowIndexInSortedArray(
counter.timestamps,
function(x) { return x; },
viewLWorld);
var timestamps = counter.timestamps;
startIndex = startIndex - 1 > 0 ? startIndex - 1 : 0;
// Draw indices one by one until we fall off the viewRWorld.
var yScale = height / counter.maxTotal;
for (var seriesIndex = counter.numSeries - 1;
seriesIndex >= 0; seriesIndex--) {
var series = counter.series[seriesIndex];
// For performance reasons we only check the SelectionState of the first
// sample. If it's DIMMED we assume that the whole series is DIMMED.
// TODO(egraether): Allow partial highlight.
var selectionState = SelectionState.NONE;
if (series.samples.length &&
series.samples[0].selectionState === SelectionState.DIMMED) {
selectionState = SelectionState.DIMMED;
}
ctx.fillStyle = EventPresenter.getCounterSeriesColor(
series.color, selectionState);
ctx.beginPath();
// Set iLast and xLast such that the first sample we draw is the
// startIndex sample.
var iLast = startIndex - 1;
var xLast = iLast >= 0 ?
timestamps[iLast] - skipDistanceWorld : -1;
var yLastView = height;
// Iterate over samples from iLast onward until we either fall off the
// viewRWorld or we run out of samples. To avoid drawing too much, after
// drawing a sample at xLast, skip subsequent samples that are less than
// skipDistanceWorld from xLast.
var hasMoved = false;
while (true) {
var i = iLast + 1;
if (i >= numSamples) {
ctx.lineTo(xLast, yLastView);
ctx.lineTo(xLast + LAST_SAMPLE_PIXELS * pixWidth, yLastView);
ctx.lineTo(xLast + LAST_SAMPLE_PIXELS * pixWidth, height);
break;
}
var x = timestamps[i];
var y = counter.totals[i * numSeries + seriesIndex];
var yView = height - (yScale * y);
if (x > viewRWorld) {
ctx.lineTo(x, yLastView);
ctx.lineTo(x, height);
break;
}
if (i + 1 < numSamples) {
var xNext = timestamps[i + 1];
if (xNext - xLast <= skipDistanceWorld && xNext < viewRWorld) {
iLast = i;
continue;
}
}
if (!hasMoved) {
ctx.moveTo(viewLWorld, height);
hasMoved = true;
}
if (x - xLast < skipDistanceWorld) {
// We know that xNext > xLast + skipDistanceWorld, so we can
// safely move this sample's x over that much without passing
// xNext. This ensure that the previous sample is visible when
// zoomed out very far.
x = xLast + skipDistanceWorld;
}
ctx.lineTo(x, yLastView);
ctx.lineTo(x, yView);
iLast = i;
xLast = x;
yLastView = yView;
}
ctx.closePath();
ctx.fill();
}
ctx.fillStyle = 'rgba(255, 0, 0, 1)';
for (var seriesIndex = counter.numSeries - 1;
seriesIndex >= 0; seriesIndex--) {
var series = counter.series[seriesIndex];
var seriesSamples = series.samples;
for (var i = startIndex; timestamps[i] < viewRWorld; i++) {
if (!seriesSamples[i].selected)
continue;
var x = timestamps[i];
for (var seriesIndex = counter.numSeries - 1;
seriesIndex >= 0; seriesIndex--) {
var y = counter.totals[i * numSeries + seriesIndex];
var yView = height - (yScale * y);
ctx.fillRect(x - pixWidth, yView - 1, 3 * pixWidth, 3);
}
}
}
ctx.restore();
},
addEventsToTrackMap: function(eventToTrackMap) {
var allSeries = this.counter_.series;
for (var seriesIndex = 0; seriesIndex < allSeries.length; seriesIndex++) {
var samples = allSeries[seriesIndex].samples;
for (var i = 0; i < samples.length; i++)
eventToTrackMap.addEvent(samples[i], this);
}
},
addIntersectingItemsInRangeToSelectionInWorldSpace: function(
loWX, hiWX, viewPixWidthWorld, selection) {
function getSampleWidth(x, i) {
if (i === counter.timestamps.length - 1) {
var dt = this.viewport.currentDisplayTransform;
var pixWidth = dt.xViewVectorToWorld(1);
return LAST_SAMPLE_PIXELS * pixWidth;
}
return counter.timestamps[i + 1] - counter.timestamps[i];
}
var counter = this.counter_;
var iLo = base.findLowIndexInSortedIntervals(counter.timestamps,
function(x) { return x; },
getSampleWidth.bind(this),
loWX);
var iHi = base.findLowIndexInSortedIntervals(counter.timestamps,
function(x) { return x; },
getSampleWidth.bind(this),
hiWX);
// Iterate over every sample intersecting..
for (var sampleIndex = iLo; sampleIndex <= iHi; sampleIndex++) {
if (sampleIndex < 0)
continue;
if (sampleIndex >= counter.timestamps.length)
continue;
// TODO(nduca): Pick the seriesIndexHit based on the loY - hiY values.
for (var seriesIndex = 0;
seriesIndex < this.counter.numSeries;
seriesIndex++) {
var series = this.counter.series[seriesIndex];
selection.push(series.samples[sampleIndex]);
}
}
},
addItemNearToProvidedEventToSelection: function(sample, offset, selection) {
var index = sample.getSampleIndex();
var newIndex = index + offset;
if (newIndex < 0 || newIndex >= sample.series.samples.length)
return false;
selection.push(sample.series.samples[newIndex]);
return true;
},
addAllObjectsMatchingFilterToSelection: function(filter, selection) {
},
addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY,
selection) {
var counter = this.counter;
if (!counter.numSeries)
return;
var stackHeight = 0;
for (var i = 0; i < counter.numSeries; i++) {
var counterSample = base.findClosestElementInSortedArray(
counter.series_[i].samples_,
function(x) { return x.timestamp; },
worldX,
worldMaxDist);
if (!counterSample)
continue;
selection.push(counterSample);
}
}
};
return {
CounterTrack: CounterTrack
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.tracks.spacing_track');
base.require('tracing.tracks.heading_track');
base.exportTo('tracing.tracks', function() {
/**
* @constructor
*/
var SpacingTrack = ui.define('spacing-track', tracing.tracks.HeadingTrack);
SpacingTrack.prototype = {
__proto__: tracing.tracks.HeadingTrack.prototype,
decorate: function(viewport) {
tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
this.classList.add('spacing-track');
},
draw: function(type, viewLWorld, viewRWorld) {
},
addAllObjectsMatchingFilterToSelection: function(filter, selection) {
}
};
return {
SpacingTrack: SpacingTrack
};
});
// 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.
'use strict';
base.require('base.sorted_array_utils');
base.require('tracing.tracks.container_track');
base.require('ui');
base.exportTo('tracing.tracks', function() {
/**
* A track that displays a SliceGroup.
* @constructor
* @extends {ContainerTrack}
*/
var SliceGroupTrack = ui.define(
'slice-group-track', tracing.tracks.ContainerTrack);
SliceGroupTrack.prototype = {
__proto__: tracing.tracks.ContainerTrack.prototype,
decorate: function(viewport) {
tracing.tracks.ContainerTrack.prototype.decorate.call(this, viewport);
this.classList.add('slice-group-track');
this.tooltip_ = '';
this.heading_ = '';
},
get group() {
return this.group_;
},
set group(g) {
this.group_ = g;
this.updateContents_();
},
get heading() {
return this.heading_;
},
set heading(h) {
this.heading_ = h;
this.updateContents_();
},
get tooltip() {
return this.tooltip_;
},
set tooltip(t) {
this.tooltip_ = t;
this.updateContents_();
},
addSliceTrack_: function(slices) {
var track = new tracing.tracks.SliceTrack(this.viewport);
track.slices = slices;
this.appendChild(track);
return track;
},
get subRows() {
return base.asArray(this.children).map(function(sliceTrack) {
return sliceTrack.slices;
});
},
get hasVisibleContent() {
return this.children.length > 0;
},
updateContents_: function() {
if (!this.group_) {
this.updateHeadingAndTooltip_();
return;
}
var slices = this.group_.slices;
if (this.areArrayContentsSame_(this.filteredSlices_, slices)) {
this.updateHeadingAndTooltip_();
return;
}
this.filteredSlices_ = slices;
this.detach();
if (!slices.length)
return;
var subRows = this.buildSubRows_(slices);
for (var srI = 0; srI < subRows.length; srI++) {
var subRow = subRows[srI];
if (!subRow.length)
continue;
this.addSliceTrack_(subRow);
}
this.updateHeadingAndTooltip_();
},
updateHeadingAndTooltip_: function() {
if (!this.firstChild)
return;
this.firstChild.heading = this.heading_;
this.firstChild.tooltip = this.tooltip_;
},
/**
* Breaks up the list of slices into N rows, each of which is a list of
* slices that are non overlapping.
*/
buildSubRows_: function(slices) {
// This function works by walking through slices by start time.
//
// The basic idea here is to insert each slice as deep into the subrow
// list as it can go such that every subSlice is fully contained by its
// parent slice.
//
// Visually, if we start with this:
// 0: [ a ]
// 1: [ b ]
// 2: [c][d]
//
// To place this slice:
// [e]
// We first check row 2's last item, [d]. [e] wont fit into [d] (they dont
// even intersect). So we go to row 1. That gives us [b], and [d] wont fit
// into that either. So, we go to row 0 and its last slice, [a]. That can
// completely contain [e], so that means we should add [e] as a subchild
// of [a]. That puts it on row 1, yielding:
// 0: [ a ]
// 1: [ b ][e]
// 2: [c][d]
//
// If we then get this slice:
// [f]
// We do the same deepest-to-shallowest walk of the subrows trying to fit
// it. This time, it doesn't fit in any open slice. So, we simply append
// it to row 0:
// 0: [ a ] [f]
// 1: [ b ][e]
// 2: [c][d]
if (!slices.length)
return [];
var ops = [];
for (var i = 0; i < slices.length; i++) {
if (slices[i].subSlices)
slices[i].subSlices.splice(0,
slices[i].subSlices.length);
ops.push(i);
}
ops.sort(function(ix, iy) {
var x = slices[ix];
var y = slices[iy];
if (x.start != y.start)
return x.start - y.start;
// Elements get inserted into the slices array in order of when the
// slices end. Because slices must be properly nested, we break
// start-time ties by assuming that the elements appearing earlier in
// the slices array (and thus ending earlier) start later.
return iy - ix;
});
var subRows = [[]];
this.badSlices_ = []; // TODO(simonjam): Connect this again.
for (var i = 0; i < ops.length; i++) {
var op = ops[i];
var slice = slices[op];
// Try to fit the slice into the existing subrows.
var inserted = false;
for (var j = subRows.length - 1; j >= 0; j--) {
if (subRows[j].length == 0)
continue;
var insertedSlice = subRows[j][subRows[j].length - 1];
if (slice.start < insertedSlice.start) {
this.badSlices_.push(slice);
inserted = true;
}
if (slice.start >= insertedSlice.start &&
slice.end <= insertedSlice.end) {
// Insert it into subRow j + 1.
while (subRows.length <= j + 1)
subRows.push([]);
subRows[j + 1].push(slice);
if (insertedSlice.subSlices)
insertedSlice.subSlices.push(slice);
inserted = true;
break;
}
}
if (inserted)
continue;
// Append it to subRow[0] as a root.
subRows[0].push(slice);
}
return subRows;
},
areArrayContentsSame_: function(a, b) {
if (!a || !b)
return false;
if (!a.length || !b.length)
return false;
if (a.length != b.length)
return false;
for (var i = 0; i < a.length; ++i) {
if (a[i] != b[i])
return false;
}
return true;
}
};
return {
SliceGroupTrack: SliceGroupTrack
};
});
// 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.
'use strict';
base.require('tracing.tracks.slice_group_track');
base.require('ui');
base.exportTo('tracing.tracks', function() {
/**
* A track that displays a AsyncSliceGroup.
* @constructor
* @extends {SliceGroup}
*/
var AsyncSliceGroupTrack = ui.define(
'async-slice-group-track',
tracing.tracks.SliceGroupTrack);
AsyncSliceGroupTrack.prototype = {
__proto__: tracing.tracks.SliceGroupTrack.prototype,
decorate: function(viewport) {
tracing.tracks.SliceGroupTrack.prototype.decorate.call(this, viewport);
this.classList.add('async-slice-group-track');
},
addSliceTrack_: function(slices) {
var track =
tracing.tracks.SliceGroupTrack.prototype.addSliceTrack_.call(
this, slices);
track.asyncStyle = true;
return track;
},
/**
* Breaks up the list of slices into N rows, each of which is a list of
* slices that are non overlapping.
*
* It uses a very simple approach: walk through the slices in sorted order
* by start time. For each slice, try to fit it in an existing subRow. If it
* doesn't fit in any subrow, make another subRow.
*/
buildSubRows_: function() {
var slices = this.group_.slices;
slices.sort(function(x, y) {
return x.start - y.start;
});
var subRows = [];
for (var i = 0; i < slices.length; i++) {
var slice = slices[i];
var found = false;
for (var j = 0; j < subRows.length; j++) {
var subRow = subRows[j];
var lastSliceInSubRow = subRow[subRow.length - 1];
if (slice.start >= lastSliceInSubRow.end) {
found = true;
// Instead of plotting one big slice for the entire
// AsyncEvent, we plot each of the subSlices.
if (slice.subSlices === undefined || slice.subSlices.length < 1)
throw new Error('AsyncEvent missing subSlices: ') +
slice.name;
for (var k = 0; k < slice.subSlices.length; k++)
subRow.push(slice.subSlices[k]);
break;
}
}
if (!found) {
var subRow = [];
if (slice.subSlices !== undefined) {
for (var k = 0; k < slice.subSlices.length; k++)
subRow.push(slice.subSlices[k]);
subRows.push(subRow);
}
}
}
return subRows;
}
};
return {
AsyncSliceGroupTrack: AsyncSliceGroupTrack
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.tracks.thread_track');
base.require('tracing.tracks.container_track');
base.require('tracing.tracks.slice_track');
base.require('tracing.tracks.slice_group_track');
base.require('tracing.tracks.async_slice_group_track');
base.require('tracing.filter');
base.require('ui');
base.exportTo('tracing.tracks', function() {
/**
* Visualizes a Thread using a series of of SliceTracks.
* @constructor
*/
var ThreadTrack = ui.define('thread-track', tracing.tracks.ContainerTrack);
ThreadTrack.prototype = {
__proto__: tracing.tracks.ContainerTrack.prototype,
decorate: function(viewport) {
tracing.tracks.ContainerTrack.prototype.decorate.call(this, viewport);
this.classList.add('thread-track');
},
get thread() {
return this.thread_;
},
set thread(thread) {
this.thread_ = thread;
this.updateContents_();
},
get hasVisibleContent() {
return this.tracks_.length > 0;
},
updateContents_: function() {
this.detach();
if (!this.thread_)
return;
this.heading = this.thread_.userFriendlyName + ': ';
this.tooltip = this.thread_.userFriendlyDetails;
if (this.thread_.asyncSliceGroup.length) {
var asyncTrack = new tracing.tracks.AsyncSliceGroupTrack(this.viewport);
asyncTrack.group = this.thread_.asyncSliceGroup;
if (asyncTrack.hasVisibleContent)
this.appendChild(asyncTrack);
}
if (this.thread_.samples.length) {
var samplesTrack = new tracing.tracks.SliceTrack(this.viewport);
samplesTrack.group = this.thread_;
samplesTrack.slices = this.thread_.samples;
this.appendChild(samplesTrack);
}
if (this.thread_.timeSlices) {
var timeSlicesTrack = new tracing.tracks.SliceTrack(this.viewport);
timeSlicesTrack.heading = '';
timeSlicesTrack.height = '4px';
timeSlicesTrack.slices = this.thread_.timeSlices;
if (timeSlicesTrack.hasVisibleContent)
this.appendChild(timeSlicesTrack);
}
if (this.thread_.sliceGroup.length) {
var track = new tracing.tracks.SliceGroupTrack(this.viewport);
track.heading = this.thread_.userFriendlyName;
track.tooltip = this.thread_.userFriendlyDetails;
track.group = this.thread_.sliceGroup;
if (track.hasVisibleContent)
this.appendChild(track);
}
},
collapsedDidChange: function(collapsed) {
if (collapsed) {
var h = parseInt(this.tracks[0].height);
for (var i = 0; i < this.tracks.length; ++i) {
if (h > 2) {
this.tracks[i].height = Math.floor(h) + 'px';
} else {
this.tracks[i].style.display = 'none';
}
h = h * 0.5;
}
} else {
for (var i = 0; i < this.tracks.length; ++i) {
this.tracks[i].height = this.tracks[0].height;
this.tracks[i].style.display = '';
}
}
}
};
return {
ThreadTrack: ThreadTrack
};
});
// 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.
'use strict';
base.require('ui');
base.require('base.settings');
base.exportTo('ui', function() {
function createSpan(opt_dictionary) {
var spanEl = document.createElement('span');
if (opt_dictionary) {
if (opt_dictionary.className)
spanEl.className = opt_dictionary.className;
if (opt_dictionary.textContent)
spanEl.textContent = opt_dictionary.textContent;
if (opt_dictionary.parent)
opt_dictionary.parent.appendChild(spanEl);
}
return spanEl;
};
function createDiv(opt_dictionary) {
var divEl = document.createElement('div');
if (opt_dictionary) {
if (opt_dictionary.className)
divEl.className = opt_dictionary.className;
if (opt_dictionary.parent)
opt_dictionary.parent.appendChild(divEl);
}
return divEl;
};
function createScopedStyle(styleContent) {
var styleEl = document.createElement('style');
styleEl.scoped = true;
styleEl.innerHTML = styleContent;
return styleEl;
}
function createSelector(
targetEl, targetElProperty,
settingsKey, defaultValue,
items) {
var defaultValueIndex;
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (item.value == defaultValue) {
defaultValueIndex = i;
break;
}
}
if (defaultValueIndex === undefined)
throw new Error('defaultValue must be in the items list');
var selectorEl = document.createElement('select');
selectorEl.addEventListener('change', onChange);
for (var i = 0; i < items.length; i++) {
var item = items[i];
var optionEl = document.createElement('option');
optionEl.textContent = item.label;
optionEl.targetPropertyValue = item.value;
selectorEl.appendChild(optionEl);
}
function onChange(e) {
var value = selectorEl.selectedOptions[0].targetPropertyValue;
base.Settings.set(settingsKey, value);
targetEl[targetElProperty] = value;
}
var oldSetter = targetEl.__lookupSetter__('selectedIndex');
selectorEl.__defineGetter__('selectedValue', function(v) {
return selectorEl.children[selectorEl.selectedIndex].targetPropertyValue;
});
selectorEl.__defineSetter__('selectedValue', function(v) {
for (var i = 0; i < selectorEl.children.length; i++) {
var value = selectorEl.children[i].targetPropertyValue;
if (value == v) {
selectorEl.selectedIndex = i;
onChange();
return;
}
}
throw new Error('Not a valid value');
});
var initialValue = base.Settings.get(settingsKey, defaultValue);
var didSet = false;
for (var i = 0; i < selectorEl.children.length; i++) {
if (selectorEl.children[i].targetPropertyValue == initialValue) {
didSet = true;
targetEl[targetElProperty] = initialValue;
selectorEl.selectedIndex = i;
break;
}
}
if (!didSet) {
selectorEl.selectedIndex = defaultValueIndex;
targetEl[targetElProperty] = defaultValue;
}
return selectorEl;
}
var nextCheckboxId = 1;
function createCheckBox(targetEl, targetElProperty,
settingsKey, defaultValue,
label) {
var buttonEl = document.createElement('input');
buttonEl.type = 'checkbox';
var initialValue = base.Settings.get(settingsKey, defaultValue);
buttonEl.checked = !!initialValue;
if (targetEl)
targetEl[targetElProperty] = initialValue;
function onChange() {
base.Settings.set(settingsKey, buttonEl.checked);
if (targetEl)
targetEl[targetElProperty] = buttonEl.checked;
}
buttonEl.addEventListener('change', onChange);
var id = '#checkbox-' + nextCheckboxId++;
var spanEl = createSpan({className: 'labeled-checkbox'});
buttonEl.setAttribute('id', id);
var labelEl = document.createElement('label');
labelEl.textContent = label;
labelEl.setAttribute('for', id);
spanEl.appendChild(buttonEl);
spanEl.appendChild(labelEl);
spanEl.__defineSetter__('checked', function(opt_bool) {
buttonEl.checked = !!opt_bool;
onChange();
});
spanEl.__defineGetter__('checked', function() {
return buttonEl.checked;
});
return spanEl;
}
return {
createSpan: createSpan,
createDiv: createDiv,
createScopedStyle: createScopedStyle,
createSelector: createSelector,
createCheckBox: createCheckBox
};
});
// 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.
'use strict';
base.require('tcmalloc.heap_instance_track');
base.require('tracing.analysis.object_snapshot_view');
base.require('tracing.analysis.object_instance_view');
base.require('tracing.tracks.container_track');
base.require('tracing.tracks.counter_track');
base.require('tracing.tracks.object_instance_track');
base.require('tracing.tracks.spacing_track');
base.require('tracing.tracks.thread_track');
base.require('tracing.trace_model_settings');
base.require('tracing.filter');
base.require('ui');
base.require('ui.dom_helpers');
base.requireStylesheet('tracing.tracks.process_track_base');
base.exportTo('tracing.tracks', function() {
var ObjectSnapshotView = tracing.analysis.ObjectSnapshotView;
var ObjectInstanceView = tracing.analysis.ObjectInstanceView;
var TraceModelSettings = tracing.TraceModelSettings;
var SpacingTrack = tracing.tracks.SpacingTrack;
/**
* Visualizes a Process by building ThreadTracks and CounterTracks.
* @constructor
*/
var ProcessTrackBase =
ui.define('process-track-base', tracing.tracks.ContainerTrack);
ProcessTrackBase.prototype = {
__proto__: tracing.tracks.ContainerTrack.prototype,
decorate: function(viewport) {
tracing.tracks.ContainerTrack.prototype.decorate.call(this, viewport);
this.processBase_ = undefined;
this.classList.add('process-track-base');
this.classList.add('expanded');
this.processNameEl_ = ui.createSpan();
this.processNameEl_.classList.add('process-track-name');
this.headerEl_ = ui.createDiv({className: 'process-track-header'});
this.headerEl_.appendChild(this.processNameEl_);
this.headerEl_.addEventListener('click', this.onHeaderClick_.bind(this));
this.appendChild(this.headerEl_);
},
get processBase() {
return this.processBase_;
},
set processBase(processBase) {
this.processBase_ = processBase;
if (this.processBase_) {
var modelSettings = new TraceModelSettings(this.processBase_.model);
var defaultValue;
if (this.processBase_.labels !== undefined &&
this.processBase_.labels.length == 1 &&
this.processBase_.labels[0] == 'chrome://tracing') {
defaultValue = false;
} else {
defaultValue = true;
}
this.expanded = modelSettings.getSettingFor(
this.processBase_, 'expanded', defaultValue);
}
this.updateContents_();
},
get expanded() {
return this.classList.contains('expanded');
},
set expanded(expanded) {
expanded = !!expanded;
if (this.expanded === expanded)
return;
this.classList.toggle('expanded');
// Expanding and collapsing tracks is, essentially, growing and shrinking
// the viewport. We dispatch a change event to trigger any processing
// to happen.
this.viewport_.dispatchChangeEvent();
if (!this.processBase_)
return;
var modelSettings = new TraceModelSettings(this.processBase_.model);
modelSettings.setSettingFor(this.processBase_, 'expanded', expanded);
},
get hasVisibleContent() {
if (this.expanded)
return this.children.length > 1;
return true;
},
onHeaderClick_: function(e) {
e.stopPropagation();
e.preventDefault();
this.expanded = !this.expanded;
},
updateContents_: function() {
this.tracks_.forEach(function(track) {
this.removeChild(track);
}, this);
if (!this.processBase_)
return;
this.processNameEl_.textContent = this.processBase_.userFriendlyName;
this.headerEl_.title = this.processBase_.userFriendlyDetails;
// Create the object instance tracks for this process.
this.willAppendTracks_();
this.appendObjectInstanceTracks_();
this.appendCounterTracks_();
this.appendThreadTracks_();
this.didAppendTracks_();
},
addEventsToTrackMap: function(eventToTrackMap) {
this.tracks_.forEach(function(track) {
track.addEventsToTrackMap(eventToTrackMap);
});
},
willAppendTracks_: function() {
},
didAppendTracks_: function() {
},
appendObjectInstanceTracks_: function() {
var instancesByTypeName =
this.processBase_.objects.getAllInstancesByTypeName();
var instanceTypeNames = base.dictionaryKeys(instancesByTypeName);
instanceTypeNames.sort();
var didAppendAtLeastOneTrack = false;
instanceTypeNames.forEach(function(typeName) {
var allInstances = instancesByTypeName[typeName];
// If a object snapshot has a view it will be shown,
// unless the view asked for it to not be shown.
var instanceViewInfo = ObjectInstanceView.getViewInfo(typeName);
var snapshotViewInfo = ObjectSnapshotView.getViewInfo(typeName);
if (instanceViewInfo && !instanceViewInfo.options.showInTrackView)
instanceViewInfo = undefined;
if (snapshotViewInfo && !snapshotViewInfo.options.showInTrackView)
snapshotViewInfo = undefined;
var hasViewInfo = instanceViewInfo || snapshotViewInfo;
// There are some instances that don't merit their own track in
// the UI. Filter them out.
var visibleInstances = [];
for (var i = 0; i < allInstances.length; i++) {
var instance = allInstances[i];
// Do not create tracks for instances that have no snapshots.
if (instance.snapshots.length === 0)
continue;
// Do not create tracks for instances that have implicit snapshots
// and don't have a view.
if (instance.hasImplicitSnapshots && !hasViewInfo)
continue;
visibleInstances.push(instance);
}
if (visibleInstances.length === 0)
return;
// Look up the constructor for this track, or use the default
// constructor if none exists.
var trackConstructor =
tracing.tracks.ObjectInstanceTrack.getTrackConstructor(typeName);
if (!trackConstructor)
trackConstructor = tracing.tracks.ObjectInstanceTrack;
var track = new trackConstructor(this.viewport);
track.objectInstances = visibleInstances;
this.appendChild(track);
didAppendAtLeastOneTrack = true;
}, this);
if (didAppendAtLeastOneTrack)
this.appendChild(new SpacingTrack(this.viewport));
},
appendCounterTracks_: function() {
// Add counter tracks for this process.
var counters = base.dictionaryValues(this.processBase.counters);
counters.sort(tracing.trace_model.Counter.compare);
// Create the counters for this process.
counters.forEach(function(counter) {
var track = new tracing.tracks.CounterTrack(this.viewport);
track.counter = counter;
this.appendChild(track);
this.appendChild(new SpacingTrack(this.viewport));
}.bind(this));
},
appendThreadTracks_: function() {
// Get a sorted list of threads.
var threads = base.dictionaryValues(this.processBase.threads);
threads.sort(tracing.trace_model.Thread.compare);
// Create the threads.
threads.forEach(function(thread) {
var track = new tracing.tracks.ThreadTrack(this.viewport);
track.thread = thread;
if (!track.hasVisibleContent)
return;
this.appendChild(track);
this.appendChild(new SpacingTrack(this.viewport));
}.bind(this));
}
};
return {
ProcessTrackBase: ProcessTrackBase
};
});
// 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.
'use strict';
base.require('tracing.tracks.process_track_base');
base.require('tracing.tracks.cpu_track');
base.require('tracing.tracks.spacing_track');
base.exportTo('tracing.tracks', function() {
var Cpu = tracing.trace_model.Cpu;
var CpuTrack = tracing.tracks.cpu_track;
var ProcessTrackBase = tracing.tracks.ProcessTrackBase;
var SpacingTrack = tracing.tracks.SpacingTrack;
/**
* @constructor
*/
var KernelTrack = ui.define('kernel-track', ProcessTrackBase);
KernelTrack.prototype = {
__proto__: ProcessTrackBase.prototype,
decorate: function(viewport) {
tracing.tracks.ProcessTrackBase.prototype.decorate.call(this, viewport);
},
// Kernel maps to processBase because we derive from ProcessTrackBase.
set kernel(kernel) {
this.processBase = kernel;
},
get kernel() {
return this.processBase;
},
willAppendTracks_: function() {
var cpus = base.dictionaryValues(this.kernel.cpus);
cpus.sort(tracing.trace_model.Cpu.compare);
var didAppendAtLeastOneTrack = false;
for (var i = 0; i < cpus.length; ++i) {
var cpu = cpus[i];
var track = new tracing.tracks.CpuTrack(this.viewport);
track.cpu = cpu;
if (!track.hasVisibleContent)
continue;
this.appendChild(track);
didAppendAtLeastOneTrack = true;
}
if (didAppendAtLeastOneTrack)
this.appendChild(new SpacingTrack(this.viewport));
}
};
return {
KernelTrack: KernelTrack
};
});
// 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.
'use strict';
base.require('tracing.tracks.process_track_base');
base.require('tracing.draw_helpers');
base.exportTo('tracing.tracks', function() {
var ProcessTrackBase = tracing.tracks.ProcessTrackBase;
/**
* @constructor
*/
var ProcessTrack = ui.define('process-track', ProcessTrackBase);
ProcessTrack.prototype = {
__proto__: ProcessTrackBase.prototype,
decorate: function(viewport) {
tracing.tracks.ProcessTrackBase.prototype.decorate.call(this, viewport);
},
drawTrack: function(type) {
switch (type) {
case tracing.tracks.DrawType.INSTANT_EVENT:
if (!this.processBase.instantEvents ||
this.processBase.instantEvents.length === 0)
break;
var ctx = this.context();
var pixelRatio = window.devicePixelRatio || 1;
var bounds = this.getBoundingClientRect();
var canvasBounds = ctx.canvas.getBoundingClientRect();
ctx.save();
ctx.translate(0, pixelRatio * (bounds.top - canvasBounds.top));
var dt = this.viewport.currentDisplayTransform;
var viewLWorld = dt.xViewToWorld(0);
var viewRWorld = dt.xViewToWorld(
bounds.width * pixelRatio);
tracing.drawInstantSlicesAsLines(
ctx,
this.viewport.currentDisplayTransform,
viewLWorld,
viewRWorld,
bounds.height,
this.processBase.instantEvents,
1);
ctx.restore();
break;
case tracing.tracks.DrawType.BACKGROUND:
this.drawBackground_();
// Don't bother recursing further, Process is the only level that
// draws backgrounds.
return;
}
tracing.tracks.ContainerTrack.prototype.drawTrack.call(this, type);
},
drawBackground_: function() {
var ctx = this.context();
var canvasBounds = ctx.canvas.getBoundingClientRect();
var pixelRatio = window.devicePixelRatio || 1;
var draw = false;
ctx.fillStyle = '#eee';
for (var i = 0; i < this.children.length; ++i) {
if (!(this.children[i] instanceof tracing.tracks.Track) ||
(this.children[i] instanceof tracing.tracks.SpacingTrack))
continue;
draw = !draw;
if (!draw)
continue;
var bounds = this.children[i].getBoundingClientRect();
ctx.fillRect(0, pixelRatio * (bounds.top - canvasBounds.top),
ctx.canvas.width, pixelRatio * bounds.height);
}
},
// Process maps to processBase because we derive from ProcessTrackBase.
set process(process) {
this.processBase = process;
},
get process() {
return this.processBase;
},
addIntersectingItemsInRangeToSelectionInWorldSpace: function(
loWX, hiWX, viewPixWidthWorld, selection) {
function onPickHit(instantEvent) {
selection.push(instantEvent);
}
base.iterateOverIntersectingIntervals(this.processBase.instantEvents,
function(x) { return x.start; },
function(x) { return x.duration; },
loWX, hiWX,
onPickHit.bind(this));
tracing.tracks.ContainerTrack.prototype.
addIntersectingItemsInRangeToSelectionInWorldSpace.
apply(this, arguments);
},
addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY,
selection) {
this.addClosestInstantEventToSelection(this.processBase.instantEvents,
worldX, worldMaxDist, selection);
tracing.tracks.ContainerTrack.prototype.addClosestEventToSelection.
apply(this, arguments);
}
};
return {
ProcessTrack: ProcessTrack
};
});
// 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.
'use strict';
base.requireStylesheet('tracing.tracks.trace_model_track');
base.require('base.measuring_stick');
base.require('tracing.tracks.container_track');
base.require('tracing.tracks.kernel_track');
base.require('tracing.tracks.process_track');
base.require('tracing.draw_helpers');
base.require('ui');
base.exportTo('tracing.tracks', function() {
/**
* Visualizes a Model by building ProcessTracks and
* CpuTracks.
* @constructor
*/
var TraceModelTrack = ui.define(
'trace-model-track', tracing.tracks.ContainerTrack);
TraceModelTrack.prototype = {
__proto__: tracing.tracks.ContainerTrack.prototype,
decorate: function(viewport) {
tracing.tracks.ContainerTrack.prototype.decorate.call(this, viewport);
this.classList.add('model-track');
},
detach: function() {
tracing.tracks.ContainerTrack.prototype.detach.call(this);
},
get model() {
return this.model_;
},
set model(model) {
this.model_ = model;
this.updateContents_();
},
get hasVisibleContent() {
return this.children.length > 0;
},
updateContents_: function() {
this.textContent = '';
if (!this.model_)
return;
this.appendKernelTrack_();
// Get a sorted list of processes.
var processes = this.model_.getAllProcesses();
processes.sort(tracing.trace_model.Process.compare);
for (var i = 0; i < processes.length; ++i) {
var process = processes[i];
var track = new tracing.tracks.ProcessTrack(this.viewport);
track.process = process;
if (!track.hasVisibleContent)
continue;
this.appendChild(track);
}
this.viewport_.rebuildEventToTrackMap();
},
addEventsToTrackMap: function(eventToTrackMap) {
if (!this.model_)
return;
var tracks = this.children;
for (var i = 0; i < tracks.length; ++i)
tracks[i].addEventsToTrackMap(eventToTrackMap);
if (this.instantEvents === undefined)
return;
var vp = this.viewport_;
this.instantEvents.forEach(function(ev) {
eventToTrackMap.addEvent(ev, this);
}.bind(this));
},
appendKernelTrack_: function() {
var kernel = this.model.kernel;
var track = new tracing.tracks.KernelTrack(this.viewport);
track.kernel = this.model.kernel;
if (!track.hasVisibleContent)
return;
this.appendChild(track);
},
drawTrack: function(type) {
var ctx = this.context();
var pixelRatio = window.devicePixelRatio || 1;
var bounds = this.getBoundingClientRect();
var canvasBounds = ctx.canvas.getBoundingClientRect();
ctx.save();
ctx.translate(0, pixelRatio * (bounds.top - canvasBounds.top));
var dt = this.viewport.currentDisplayTransform;
var viewLWorld = dt.xViewToWorld(0);
var viewRWorld = dt.xViewToWorld(bounds.width * pixelRatio);
switch (type) {
case tracing.tracks.DrawType.GRID:
this.viewport.drawMarkLines(ctx);
// The model is the only thing that draws grid lines.
ctx.restore();
return;
case tracing.tracks.DrawType.FLOW_ARROWS:
if (this.model_.flowIntervalTree.size === 0) {
ctx.restore();
return;
}
this.drawFlowArrows_(viewLWorld, viewRWorld);
ctx.restore();
return;
case tracing.tracks.DrawType.INSTANT_EVENT:
if (!this.model_.instantEvents ||
this.model_.instantEvents.length === 0)
break;
tracing.drawInstantSlicesAsLines(
ctx,
this.viewport.currentDisplayTransform,
viewLWorld,
viewRWorld,
bounds.height,
this.model_.instantEvents,
1);
break;
case tracing.tracks.DrawType.MARKERS:
this.viewport.drawMarkerLines(ctx, viewLWorld, viewRWorld);
this.viewport.drawMarkerIndicators(ctx, viewLWorld, viewRWorld);
ctx.restore();
return;
}
ctx.restore();
tracing.tracks.ContainerTrack.prototype.drawTrack.call(this, type);
},
drawFlowArrows_: function(viewLWorld, viewRWorld) {
var ctx = this.context();
var dt = this.viewport.currentDisplayTransform;
dt.applyTransformToCanvas(ctx);
var pixWidth = dt.xViewVectorToWorld(1);
ctx.strokeStyle = 'rgba(0, 0, 0, 0.4)';
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
ctx.lineWidth = pixWidth > 1.0 ? 1 : pixWidth;
var events =
this.model_.flowIntervalTree.findIntersection(viewLWorld, viewRWorld);
var minWidth = 2 * pixWidth;
var canvasBounds = ctx.canvas.getBoundingClientRect();
for (var i = 0; i < events.length; ++i) {
var startEvent = events[i][0];
var endEvent = events[i][1];
// Skip lines that will be, essentially, vertical.
var distance = endEvent.start - startEvent.start;
if (distance <= minWidth)
continue;
this.drawFlowArrowBetween_(
ctx, startEvent, endEvent, canvasBounds, pixWidth);
}
},
drawFlowArrowBetween_: function(ctx, startEvent, endEvent,
canvasBounds, pixWidth) {
var pixelRatio = window.devicePixelRatio || 1;
var startTrack = this.viewport.trackForEvent(startEvent);
var endTrack = this.viewport.trackForEvent(endEvent);
var startBounds = startTrack.getBoundingClientRect();
var endBounds = endTrack.getBoundingClientRect();
var startSize = startBounds.left + startBounds.top +
startBounds.bottom + startBounds.right;
var endSize = endBounds.left + endBounds.top +
endBounds.bottom + endBounds.right;
// Nothing to do if both ends of the track are collapsed.
if (startSize === 0 && endSize === 0)
return;
var startY = this.calculateTrackY_(startTrack, canvasBounds);
var endY = this.calculateTrackY_(endTrack, canvasBounds);
var pixelStartY = pixelRatio * startY;
var pixelEndY = pixelRatio * endY;
var half = (endEvent.start - startEvent.start) / 2;
ctx.beginPath();
ctx.moveTo(startEvent.start, pixelStartY);
ctx.bezierCurveTo(
startEvent.start + half, pixelStartY,
startEvent.start + half, pixelEndY,
endEvent.start, pixelEndY);
ctx.stroke();
var arrowWidth = 5 * pixWidth * pixelRatio;
var distance = endEvent.start - startEvent.start;
if (distance <= (2 * arrowWidth))
return;
var tipX = endEvent.start;
var tipY = pixelEndY;
var arrowHeight = (endBounds.height / 4) * pixelRatio;
tracing.drawTriangle(ctx,
tipX, tipY,
tipX - arrowWidth, tipY - arrowHeight,
tipX - arrowWidth, tipY + arrowHeight);
ctx.fill();
},
calculateTrackY_: function(track, canvasBounds) {
var bounds = track.getBoundingClientRect();
var size = bounds.left + bounds.top + bounds.bottom + bounds.right;
if (size === 0)
return this.calculateTrackY_(track.parentNode, canvasBounds);
return bounds.top - canvasBounds.top + (bounds.height / 2);
},
addIntersectingItemsInRangeToSelectionInWorldSpace: function(
loWX, hiWX, viewPixWidthWorld, selection) {
function onPickHit(instantEvent) {
selection.push(instantEvent);
}
base.iterateOverIntersectingIntervals(this.model_.instantEvents,
function(x) { return x.start; },
function(x) { return x.duration; },
loWX, hiWX,
onPickHit.bind(this));
tracing.tracks.ContainerTrack.prototype.
addIntersectingItemsInRangeToSelectionInWorldSpace.
apply(this, arguments);
},
addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY,
selection) {
this.addClosestInstantEventToSelection(this.model_.instantEvents,
worldX, worldMaxDist, selection);
tracing.tracks.ContainerTrack.prototype.addClosestEventToSelection.
apply(this, arguments);
}
};
return {
TraceModelTrack: TraceModelTrack
};
});
// 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.
'use strict';
base.require('base.guid');
base.exportTo('base', function() {
/**
* KeyEventManager avoids leaks when listening for keys.
*
* A common but leaky pattern is:
* document.addEventListener('key*', function().bind(this))
* This leaks.
*
* Instead do this:
* KeyEventManager.instance.addListener('keyDown', func, this);
*
* This will not leak. BUT, note, if "this" is not attached to the document,
* it will NOT receive input events.
*
* Conceptually, KeyEventManager works by making the this refrence "weak",
* which is actually accomplished by putting a guid on the thisArg. When keys
* are received, we look for elements with that guid and dispatch the keys to
* them.
*/
function KeyEventManager(opt_document) {
this.document_ = opt_document || document;
if (KeyEventManager.instance)
throw new Error('KeyEventManager is a singleton.');
this.onEvent_ = this.onEvent_.bind(this);
this.document_.addEventListener('keydown', this.onEvent_);
this.document_.addEventListener('keypress', this.onEvent_);
this.document_.addEventListener('keyup', this.onEvent_);
this.listeners_ = [];
}
KeyEventManager.instance = undefined;
KeyEventManager.resetInstanceForUnitTesting = function() {
if (KeyEventManager.instance) {
KeyEventManager.instance.destroy();
KeyEventManager.instance = undefined;
}
KeyEventManager.instance = new KeyEventManager();
}
KeyEventManager.prototype = {
addListener: function(type, handler, thisArg) {
if (!thisArg.keyEventManagerGuid_) {
thisArg.keyEventManagerGuid_ = base.GUID.allocate();
thisArg.keyEventManagerRefCount_ = 0;
}
thisArg.classList.add('key-event-manager-target');
thisArg.keyEventManagerRefCount_++;
var guid = thisArg.keyEventManagerGuid_;
this.listeners_.push({
guid: guid,
type: type,
handler: handler
});
},
onEvent_: function(event) {
// This does standard DOM event propagation of the given event, but using
// guids to locate the thisArg for each listener. See event_target.js for
// notes on how this works.
var preventDefaultState = undefined;
var stopPropagationCalled = false;
var oldPreventDefault = event.preventDefault;
event.preventDefault = function() {
preventDefaultState = false;
oldPreventDefault.call(this);
};
var oldStopPropagation = event.stopPropagation;
event.stopPropagation = function() {
stopPropagationCalled = true;
oldStopPropagation.call(this);
};
event.stopImmediatePropagation = function() {
throw new Error('Not implemented');
};
var possibleThisArgs = this.document_.querySelectorAll(
'.key-event-manager-target');
var possibleThisArgsByGUID = {};
for (var i = 0; i < possibleThisArgs.length; i++) {
possibleThisArgsByGUID[possibleThisArgs[i].keyEventManagerGuid_] =
possibleThisArgs[i];
}
// We need to copy listeners_ and verify the thisArgs exists on each loop
// iteration because the event callbacks can change the DOM and listener
// list.
var listeners = this.listeners_.concat();
var type = event.type;
var prevented = 0;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
if (listener.type !== type)
continue;
// thisArg went away.
var thisArg = possibleThisArgsByGUID[listener.guid];
if (!thisArg)
continue;
var handler = listener.handler;
if (handler.handleEvent)
prevented |= handler.handleEvent.call(handler, event) === false;
else
prevented |= handler.call(thisArg, event) === false;
if (stopPropagationCalled)
break;
}
// We want to return false if preventDefaulted, or one of the handlers
// return false. But otherwise, we want to return undefiend.
return !prevented && preventDefaultState;
},
removeListener: function(type, handler, thisArg) {
if (thisArg.keyEventManagerGuid_ === undefined)
throw new Error('Was not registered with KeyEventManager');
if (thisArg.keyEventManagerRefCount_ === 0)
throw new Error('No events were registered on the provided thisArg');
for (var i = 0; i < this.listeners_.length; i++) {
var listener = this.listeners_[i];
if (listener.type == type &&
listener.handler == handler &&
listener.guid == thisArg.keyEventManagerGuid_) {
thisArg.keyEventManagerRefCount_--;
if (thisArg.keyEventManagerRefCount_ === 0)
thisArg.classList.remove('key-event-manager-target');
this.listeners_.splice(i, 1);
return;
}
}
throw new Error('Listener not found');
},
destroy: function() {
this.listeners_.splice(0);
this.document_.removeEventListener('keydown', this.onEvent_);
this.document_.removeEventListener('keypress', this.onEvent_);
this.document_.removeEventListener('keyup', this.onEvent_);
},
dispatchFakeEvent: function(type, args) {
var e = new KeyboardEvent(type, args);
return KeyEventManager.instance.onEvent_.call(undefined, e);
}
};
KeyEventManager.instance = new KeyEventManager();
return {
KeyEventManager: KeyEventManager
};
});
// 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.
'use strict';
/**
* @fileoverview A Mouse-event abtraction that waits for
* mousedown, then watches for subsequent mousemove events
* until the next mouseup event, then waits again.
* State changes are signaled with
* 'mouse-tracker-start' : mousedown and tracking
* 'mouse-tracker-move' : mouse move
* 'mouse-tracker-end' : mouseup and not tracking.
*/
base.exportTo('ui', function() {
/**
* @constructor
* @param {HTMLElement} targetElement will recv events 'mouse-tracker-start',
* 'mouse-tracker-move', 'mouse-tracker-end'.
*/
function MouseTracker(opt_targetElement) {
this.onMouseDown_ = this.onMouseDown_.bind(this);
this.onMouseMove_ = this.onMouseMove_.bind(this);
this.onMouseUp_ = this.onMouseUp_.bind(this);
this.targetElement = opt_targetElement;
}
MouseTracker.prototype = {
get targetElement() {
return this.targetElement_;
},
set targetElement(targetElement) {
if (this.targetElement_)
this.targetElement_.removeEventListener('mousedown', this.onMouseDown_);
this.targetElement_ = targetElement;
if (this.targetElement_)
this.targetElement_.addEventListener('mousedown', this.onMouseDown_);
},
onMouseDown_: function(e) {
if (e.button !== 0)
return true;
e = this.remakeEvent_(e, 'mouse-tracker-start');
this.targetElement_.dispatchEvent(e);
document.addEventListener('mousemove', this.onMouseMove_);
document.addEventListener('mouseup', this.onMouseUp_);
this.targetElement_.addEventListener('blur', this.onMouseUp_);
this.savePreviousUserSelect_ = document.body.style['-webkit-user-select'];
document.body.style['-webkit-user-select'] = 'none';
e.preventDefault();
return true;
},
onMouseMove_: function(e) {
e = this.remakeEvent_(e, 'mouse-tracker-move');
this.targetElement_.dispatchEvent(e);
},
onMouseUp_: function(e) {
document.removeEventListener('mousemove', this.onMouseMove_);
document.removeEventListener('mouseup', this.onMouseUp_);
this.targetElement_.removeEventListener('blur', this.onMouseUp_);
document.body.style['-webkit-user-select'] =
this.savePreviousUserSelect_;
e = this.remakeEvent_(e, 'mouse-tracker-end');
this.targetElement_.dispatchEvent(e);
},
remakeEvent_: function(e, newType) {
var remade = new base.Event(newType, true, true);
remade.x = e.x;
remade.y = e.y;
remade.offsetX = e.offsetX;
remade.offsetY = e.offsetY;
remade.clientX = e.clientX;
remade.clientY = e.clientY;
return remade;
}
};
function trackMouseMovesUntilMouseUp(mouseMoveHandler, opt_mouseUpHandler) {
function cleanupAndDispatchToMouseUp(e) {
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', cleanupAndDispatchToMouseUp);
if (opt_mouseUpHandler)
opt_mouseUpHandler(e);
}
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', cleanupAndDispatchToMouseUp);
}
return {
MouseTracker: MouseTracker,
trackMouseMovesUntilMouseUp: trackMouseMovesUntilMouseUp
};
});
// 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.
'use strict';
base.requireStylesheet('ui.tool_button');
base.requireStylesheet('ui.mouse_mode_selector');
base.requireTemplate('ui.mouse_mode_selector');
base.require('base.events');
base.require('tracing.constants');
base.require('base.events');
base.require('base.iteration_helpers');
base.require('base.utils');
base.require('base.key_event_manager');
base.require('ui');
base.require('ui.mouse_tracker');
base.exportTo('ui', function() {
var MOUSE_SELECTOR_MODE = {};
MOUSE_SELECTOR_MODE.SELECTION = 0x1;
MOUSE_SELECTOR_MODE.PANSCAN = 0x2;
MOUSE_SELECTOR_MODE.ZOOM = 0x4;
MOUSE_SELECTOR_MODE.TIMING = 0x8;
MOUSE_SELECTOR_MODE.ROTATE = 0x10;
MOUSE_SELECTOR_MODE.ALL_MODES = 0x1F;
var allModeInfo = {};
allModeInfo[MOUSE_SELECTOR_MODE.PANSCAN] = {
title: 'pan',
className: 'pan-scan-mode-button',
eventNames: {
enter: 'enterpan',
begin: 'beginpan',
update: 'updatepan',
end: 'endpan',
exit: 'exitpan'
}
};
allModeInfo[MOUSE_SELECTOR_MODE.SELECTION] = {
title: 'selection',
className: 'selection-mode-button',
eventNames: {
enter: 'enterselection',
begin: 'beginselection',
update: 'updateselection',
end: 'endselection',
exit: 'exitselection'
}
};
allModeInfo[MOUSE_SELECTOR_MODE.ZOOM] = {
title: 'zoom',
className: 'zoom-mode-button',
eventNames: {
enter: 'enterzoom',
begin: 'beginzoom',
update: 'updatezoom',
end: 'endzoom',
exit: 'exitzoom'
}
};
allModeInfo[MOUSE_SELECTOR_MODE.TIMING] = {
title: 'timing',
className: 'timing-mode-button',
eventNames: {
enter: 'entertiming',
begin: 'begintiming',
update: 'updatetiming',
end: 'endtiming',
exit: 'exittiming'
}
};
allModeInfo[MOUSE_SELECTOR_MODE.ROTATE] = {
title: 'rotate',
className: 'rotate-mode-button',
eventNames: {
enter: 'enterrotate',
begin: 'beginrotate',
update: 'updaterotate',
end: 'endrotate',
exit: 'exitrotate'
}
};
var MODIFIER = {
SHIFT: 0x1,
SPACE: 0x2,
CMD_OR_CTRL: 0x4
};
/**
* Provides a panel for switching the interaction mode of the mouse.
* It handles the user interaction and dispatches events for the various
* modes.
*
* @constructor
* @extends {HTMLDivElement}
*/
var MouseModeSelector = ui.define('div');
MouseModeSelector.prototype = {
__proto__: HTMLDivElement.prototype,
decorate: function(opt_targetElement) {
this.classList.add('mouse-mode-selector');
var node = base.instantiateTemplate('#mouse-mode-selector-template');
this.appendChild(node);
this.buttonsEl_ = this.querySelector('.buttons');
this.dragHandleEl_ = this.querySelector('.drag-handle');
this.supportedModeMask = MOUSE_SELECTOR_MODE.ALL_MODES;
this.initialRelativeMouseDownPos_ = {x: 0, y: 0};
this.defaultMode_ = MOUSE_SELECTOR_MODE.PANSCAN;
this.settingsKey_ = undefined;
this.mousePos_ = {x: 0, y: 0};
this.mouseDownPos_ = {x: 0, y: 0};
this.dragHandleEl_.addEventListener('mousedown',
this.onDragHandleMouseDown_.bind(this));
this.onMouseDown_ = this.onMouseDown_.bind(this);
this.onMouseMove_ = this.onMouseMove_.bind(this);
this.onMouseUp_ = this.onMouseUp_.bind(this);
this.buttonsEl_.addEventListener('mouseup', this.onButtonMouseUp_);
this.buttonsEl_.addEventListener('mousedown', this.onButtonMouseDown_);
this.buttonsEl_.addEventListener('click', this.onButtonPress_.bind(this));
base.KeyEventManager.instance.addListener(
'keydown', this.onKeyDown_, this);
base.KeyEventManager.instance.addListener(
'keyup', this.onKeyUp_, this);
this.mode_ = undefined;
this.modeToKeyCodeMap_ = {};
this.modifierToModeMap_ = {};
this.targetElement = opt_targetElement;
this.modeBeforeAlternativeModeActivated_ = null;
this.exitAlternativeModeModifier_ = null;
this.isInteracting_ = false;
this.isClick_ = false;
},
set targetElement(target) {
if (this.targetElement_)
this.targetElement_.removeEventListener('mousedown', this.onMouseDown_);
this.targetElement_ = target;
if (this.targetElement_)
this.targetElement_.addEventListener('mousedown', this.onMouseDown_);
},
get defaultMode() {
return this.defaultMode_;
},
set defaultMode(defaultMode) {
this.defaultMode_ = defaultMode;
},
get settingsKey() {
return this.settingsKey_;
},
set settingsKey(settingsKey) {
this.settingsKey_ = settingsKey;
if (!this.settingsKey_)
return;
var mode = base.Settings.get(this.settingsKey_ + '.mode', undefined);
// Modes changed from 1,2,3,4 to 0x1, 0x2, 0x4, 0x8. Fix any stray
// settings to the best of our abilities.
if (allModeInfo[mode] === undefined)
mode = undefined;
// Restoring settings against unsupported modes should just go back to the
// default mode.
if ((mode & this.supportedModeMask_) === 0)
mode = undefined;
if (!mode)
mode = this.defaultMode_;
this.mode = mode;
var pos = base.Settings.get(this.settingsKey_ + '.pos', undefined);
if (pos)
this.pos = pos;
},
get supportedModeMask() {
return this.supportedModeMask_;
},
/**
* Sets the supported modes. Should be an OR-ing of MOUSE_SELECTOR_MODE
* values.
*/
set supportedModeMask(supportedModeMask) {
if (this.mode && (supportedModeMask & this.mode) === 0)
throw new Error('supportedModeMask must include current mode.');
function createButtonForMode(mode) {
var button = document.createElement('div');
button.mode = mode;
button.title = allModeInfo[mode].title;
button.classList.add('tool-button');
button.classList.add(allModeInfo[mode].className);
return button;
}
this.supportedModeMask_ = supportedModeMask;
this.buttonsEl_.textContent = '';
for (var modeName in MOUSE_SELECTOR_MODE) {
if (modeName == 'ALL_MODES')
continue;
var mode = MOUSE_SELECTOR_MODE[modeName];
if ((this.supportedModeMask_ & mode) === 0)
continue;
this.buttonsEl_.appendChild(createButtonForMode(mode));
}
},
get mode() {
return this.currentMode_;
},
set mode(newMode) {
if (newMode !== undefined) {
if (typeof newMode !== 'number')
throw new Error('Mode must be a number');
if ((newMode & this.supportedModeMask_) === 0)
throw new Error('Cannot switch to this mode, it is not supported');
if (allModeInfo[newMode] === undefined)
throw new Error('Unrecognized mode');
}
var modeInfo;
if (this.currentMode_ === newMode)
return;
if (this.currentMode_) {
modeInfo = allModeInfo[this.currentMode_];
var buttonEl = this.buttonsEl_.querySelector('.' + modeInfo.className);
if (buttonEl)
buttonEl.classList.remove('active');
if (!this.isInAlternativeMode_)
base.dispatchSimpleEvent(this, modeInfo.eventNames.exit, true);
}
this.currentMode_ = newMode;
if (this.currentMode_) {
modeInfo = allModeInfo[this.currentMode_];
var buttonEl = this.buttonsEl_.querySelector('.' + modeInfo.className);
if (buttonEl)
buttonEl.classList.add('active');
if (!this.isInAlternativeMode_)
base.dispatchSimpleEvent(this, modeInfo.eventNames.enter, true);
}
if (this.settingsKey_ && !this.isInAlternativeMode_)
base.Settings.set(this.settingsKey_ + '.mode', this.mode);
},
setKeyCodeForMode: function(mode, keyCode) {
if ((mode & this.supportedModeMask_) === 0)
throw new Error('Mode not supported');
this.modeToKeyCodeMap_[mode] = keyCode;
if (!this.buttonsEl_)
return;
var modeInfo = allModeInfo[mode];
var buttonEl = this.buttonsEl_.querySelector('.' + modeInfo.className);
if (buttonEl) {
buttonEl.title =
modeInfo.title + ' (' + String.fromCharCode(keyCode) + ')';
}
},
setPositionFromEvent_: function(pos, e) {
pos.x = e.clientX;
pos.y = e.clientY;
},
onMouseDown_: function(e) {
if (e.button !== tracing.constants.LEFT_MOUSE_BUTTON)
return;
this.setPositionFromEvent_(this.mouseDownPos_, e);
var mouseEvent = new base.Event(
allModeInfo[this.mode].eventNames.begin, true);
mouseEvent.data = e;
this.dispatchEvent(mouseEvent);
this.isInteracting_ = true;
this.isClick_ = true;
ui.trackMouseMovesUntilMouseUp(this.onMouseMove_, this.onMouseUp_);
},
onMouseMove_: function(e) {
this.setPositionFromEvent_(this.mousePos_, e);
var mouseEvent = new base.Event(
allModeInfo[this.mode].eventNames.update, true);
mouseEvent.data = e;
mouseEvent.deltaX = e.x - this.mouseDownPos_.x;
mouseEvent.deltaY = e.y - this.mouseDownPos_.y;
mouseEvent.mouseDownPosition = this.mouseDownPos_;
this.dispatchEvent(mouseEvent);
if (this.isInteracting_)
this.checkIsClick_(e);
},
onMouseUp_: function(e) {
if (e.button !== tracing.constants.LEFT_MOUSE_BUTTON)
return;
var mouseEvent = new base.Event(
allModeInfo[this.mode].eventNames.end, true);
mouseEvent.data = e;
mouseEvent.consumed = false;
mouseEvent.isClick = this.isClick_;
this.dispatchEvent(mouseEvent);
if (this.isClick_ && !mouseEvent.consumed)
this.dispatchClickEvents_(e);
this.isInteracting_ = false;
},
onButtonMouseDown_: function(e) {
e.preventDefault();
e.stopImmediatePropagation();
},
onButtonMouseUp_: function(e) {
e.preventDefault();
e.stopImmediatePropagation();
},
onButtonPress_: function(e) {
this.setAlternateMode_(null);
this.mode = e.target.mode;
e.preventDefault();
},
onKeyDown_: function(e) {
// Prevent the user from changing modes during an interaction.
if (this.isInteracting_)
return;
if (this.isInAlternativeMode_)
return;
var modifierToModeMap = this.modifierToModeMap_;
var mode = this.mode;
var m = MODIFIER;
var modifier;
var shiftPressed = e.shiftKey;
var spacePressed = e.keyCode === ' '.charCodeAt(0);
var cmdOrCtrlPressed =
(base.isMac && e.metaKey) || (!base.isMac && e.ctrlKey);
if (shiftPressed && modifierToModeMap[m.SHIFT] !== mode)
modifier = m.SHIFT;
else if (spacePressed && modifierToModeMap[m.SPACE] !== mode)
modifier = m.SPACE;
else if (cmdOrCtrlPressed && modifierToModeMap[m.CMD_OR_CTRL] !== mode)
modifier = m.CMD_OR_CTRL;
else
return;
this.setAlternateMode_(modifier);
},
onKeyUp_: function(e) {
// Prevent the user from changing modes during an interaction.
if (this.isInteracting_)
return;
var didHandleKey = false;
base.iterItems(this.modeToKeyCodeMap_, function(modeStr, keyCode) {
if (e.keyCode === keyCode) {
this.setAlternateMode_(null);
var mode = parseInt(modeStr);
this.mode = mode;
didHandleKey = true;
}
}, this);
if (didHandleKey) {
e.preventDefault();
e.stopPropagation();
return;
}
if (!this.isInAlternativeMode_)
return;
var shiftReleased = !e.shiftKey;
var spaceReleased = e.keyCode === ' '.charCodeAt(0);
var cmdOrCtrlReleased =
(base.isMac && !e.metaKey) || (!base.isMac && !e.ctrlKey);
var exitModifier = this.exitAlternativeModeModifier_;
if ((shiftReleased && exitModifier === MODIFIER.SHIFT) ||
(spaceReleased && exitModifier === MODIFIER.SPACE) ||
(cmdOrCtrlReleased && exitModifier === MODIFIER.CMD_OR_CTRL)) {
this.setAlternateMode_(null);
}
},
get isInAlternativeMode_() {
return !!this.modeBeforeAlternativeModeActivated_;
},
setAlternateMode_: function(modifier) {
if (!modifier) {
if (this.isInAlternativeMode_) {
this.mode = this.modeBeforeAlternativeModeActivated_;
this.modeBeforeAlternativeModeActivated_ = null;
}
return;
}
var alternateMode = this.modifierToModeMap_[modifier];
if ((alternateMode & this.supportedModeMask_) === 0)
return;
this.modeBeforeAlternativeModeActivated_ = this.mode;
this.exitAlternativeModeModifier_ = modifier;
this.mode = alternateMode;
},
setModifierForAlternateMode: function(mode, modifier) {
this.modifierToModeMap_[modifier] = mode;
},
get pos() {
return {
x: parseInt(this.style.left),
y: parseInt(this.style.top)
};
},
set pos(pos) {
pos = this.constrainPositionToBounds_(pos);
this.style.left = pos.x + 'px';
this.style.top = pos.y + 'px';
if (this.settingsKey_)
base.Settings.set(this.settingsKey_ + '.pos', this.pos);
},
constrainPositionToBounds_: function(pos) {
var parent = this.offsetParent || document.body;
var parentRect = base.windowRectForElement(parent);
var top = 0;
var bottom = parentRect.height - this.offsetHeight;
var left = 0;
var right = parentRect.width - this.offsetWidth;
var res = {};
res.x = Math.max(pos.x, left);
res.x = Math.min(res.x, right);
res.y = Math.max(pos.y, top);
res.y = Math.min(res.y, bottom);
return res;
},
onDragHandleMouseDown_: function(e) {
e.preventDefault();
e.stopImmediatePropagation();
this.initialRelativeMouseDownPos_.x = e.clientX - this.offsetLeft;
this.initialRelativeMouseDownPos_.y = e.clientY - this.offsetTop;
ui.trackMouseMovesUntilMouseUp(this.onDragHandleMouseMove_.bind(this));
},
onDragHandleMouseMove_: function(e) {
var pos = {};
pos.x = (e.clientX - this.initialRelativeMouseDownPos_.x);
pos.y = (e.clientY - this.initialRelativeMouseDownPos_.y);
this.pos = pos;
},
checkIsClick_: function(e) {
if (!this.isInteracting_ || !this.isClick_)
return;
var deltaX = this.mousePos_.x - this.mouseDownPos_.x;
var deltaY = this.mousePos_.y - this.mouseDownPos_.y;
var minDist = tracing.constants.MIN_MOUSE_SELECTION_DISTANCE;
if (deltaX * deltaX + deltaY * deltaY > minDist * minDist)
this.isClick_ = false;
},
dispatchClickEvents_: function(e) {
if (!this.isClick_)
return;
var eventNames = allModeInfo[MOUSE_SELECTOR_MODE.SELECTION].eventNames;
var mouseEvent = new base.Event(eventNames.begin, true);
mouseEvent.data = e;
this.dispatchEvent(mouseEvent);
mouseEvent = new base.Event(eventNames.end, true);
mouseEvent.data = e;
this.dispatchEvent(mouseEvent);
}
};
return {
MouseModeSelector: MouseModeSelector,
MOUSE_SELECTOR_MODE: MOUSE_SELECTOR_MODE,
MODIFIER: MODIFIER
};
});
// 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.
'use strict';
/**
* @fileoverview Interactive visualizaiton of TraceModel objects
* based loosely on gantt charts. Each thread in the TraceModel is given a
* set of Tracks, one per subrow in the thread. The TimelineTrackView class
* acts as a controller, creating the individual tracks, while Tracks
* do actual drawing.
*
* Visually, the TimelineTrackView produces (prettier) visualizations like the
* following:
* Thread1: AAAAAAAAAA AAAAA
* BBBB BB
* Thread2: CCCCCC CCCCC
*
*/
base.requireStylesheet('ui.trace_viewer');
base.requireStylesheet('tracing.timeline_track_view');
base.require('base.events');
base.require('base.properties');
base.require('base.settings');
base.require('tracing.filter');
base.require('tracing.selection');
base.require('tracing.timeline_viewport');
base.require('tracing.timeline_display_transform_animations');
base.require('tracing.timing_tool');
base.require('tracing.trace_model.event');
base.require('tracing.tracks.drawing_container');
base.require('tracing.tracks.trace_model_track');
base.require('tracing.tracks.ruler_track');
base.require('ui');
base.require('ui.mouse_mode_selector');
base.exportTo('tracing', function() {
var Selection = tracing.Selection;
var SelectionState = tracing.trace_model.SelectionState;
var Viewport = tracing.TimelineViewport;
var tempDisplayTransform = new tracing.TimelineDisplayTransform();
function intersectRect_(r1, r2) {
var results = new Object;
if (r2.left > r1.right || r2.right < r1.left ||
r2.top > r1.bottom || r2.bottom < r1.top) {
return false;
}
results.left = Math.max(r1.left, r2.left);
results.top = Math.max(r1.top, r2.top);
results.right = Math.min(r1.right, r2.right);
results.bottom = Math.min(r1.bottom, r2.bottom);
results.width = (results.right - results.left);
results.height = (results.bottom - results.top);
return results;
}
/**
* Renders a TraceModel into a div element, making one
* Track for each subrow in each thread of the model, managing
* overall track layout, and handling user interaction with the
* viewport.
*
* @constructor
* @extends {HTMLDivElement}
*/
var TimelineTrackView = ui.define('div');
TimelineTrackView.prototype = {
__proto__: HTMLDivElement.prototype,
model_: null,
decorate: function() {
this.classList.add('timeline-track-view');
this.viewport_ = new Viewport(this);
this.viewportDisplayTransformAtMouseDown_ = null;
this.rulerTrackContainer_ =
new tracing.tracks.DrawingContainer(this.viewport_);
this.appendChild(this.rulerTrackContainer_);
this.rulerTrackContainer_.invalidate();
this.rulerTrack_ = new tracing.tracks.RulerTrack(this.viewport_);
this.rulerTrackContainer_.appendChild(this.rulerTrack_);
this.modelTrackContainer_ =
new tracing.tracks.DrawingContainer(this.viewport_);
this.appendChild(this.modelTrackContainer_);
this.modelTrackContainer_.style.display = 'block';
this.modelTrackContainer_.invalidate();
this.viewport_.modelTrackContainer = this.modelTrackContainer_;
this.modelTrack_ = new tracing.tracks.TraceModelTrack(this.viewport_);
this.modelTrackContainer_.appendChild(this.modelTrack_);
this.timingTool_ = new tracing.TimingTool(this.viewport_,
this);
this.initMouseModeSelector();
this.dragBox_ = this.ownerDocument.createElement('div');
this.dragBox_.className = 'drag-box';
this.appendChild(this.dragBox_);
this.hideDragBox_();
this.bindEventListener_(document, 'keypress', this.onKeypress_, this);
this.bindEventListener_(document, 'keydown', this.onKeydown_, this);
this.bindEventListener_(document, 'keyup', this.onKeyup_, this);
this.bindEventListener_(this, 'dblclick', this.onDblClick_, this);
this.bindEventListener_(this, 'mousewheel', this.onMouseWheel_, this);
this.addEventListener('mousemove', this.onMouseMove_);
this.mouseViewPosAtMouseDown_ = {x: 0, y: 0};
this.lastMouseViewPos_ = {x: 0, y: 0};
this.selection_ = new Selection();
this.highlight_ = new Selection();
this.isPanningAndScanning_ = false;
this.isZooming_ = false;
},
/**
* Wraps the standard addEventListener but automatically binds the provided
* func to the provided target, tracking the resulting closure. When detach
* is called, these listeners will be automatically removed.
*/
bindEventListener_: function(object, event, func, target) {
if (!this.boundListeners_)
this.boundListeners_ = [];
var boundFunc = func.bind(target);
this.boundListeners_.push({object: object,
event: event,
boundFunc: boundFunc});
object.addEventListener(event, boundFunc);
},
initMouseModeSelector: function() {
this.mouseModeSelector_ = new ui.MouseModeSelector(this);
this.appendChild(this.mouseModeSelector_);
this.mouseModeSelector_.addEventListener('beginpan',
this.onBeginPanScan_.bind(this));
this.mouseModeSelector_.addEventListener('updatepan',
this.onUpdatePanScan_.bind(this));
this.mouseModeSelector_.addEventListener('endpan',
this.onEndPanScan_.bind(this));
this.mouseModeSelector_.addEventListener('beginselection',
this.onBeginSelection_.bind(this));
this.mouseModeSelector_.addEventListener('updateselection',
this.onUpdateSelection_.bind(this));
this.mouseModeSelector_.addEventListener('endselection',
this.onEndSelection_.bind(this));
this.mouseModeSelector_.addEventListener('beginzoom',
this.onBeginZoom_.bind(this));
this.mouseModeSelector_.addEventListener('updatezoom',
this.onUpdateZoom_.bind(this));
this.mouseModeSelector_.addEventListener('endzoom',
this.onEndZoom_.bind(this));
this.mouseModeSelector_.addEventListener('entertiming',
this.timingTool_.onEnterTiming.bind(this.timingTool_));
this.mouseModeSelector_.addEventListener('begintiming',
this.timingTool_.onBeginTiming.bind(this.timingTool_));
this.mouseModeSelector_.addEventListener('updatetiming',
this.timingTool_.onUpdateTiming.bind(this.timingTool_));
this.mouseModeSelector_.addEventListener('endtiming',
this.timingTool_.onEndTiming.bind(this.timingTool_));
this.mouseModeSelector_.addEventListener('exittiming',
this.timingTool_.onExitTiming.bind(this.timingTool_));
var m = ui.MOUSE_SELECTOR_MODE;
this.mouseModeSelector_.supportedModeMask =
m.SELECTION | m.PANSCAN | m.ZOOM | m.TIMING;
this.mouseModeSelector_.settingsKey =
'timelineTrackView.mouseModeSelector';
this.mouseModeSelector_.setKeyCodeForMode(m.PANSCAN, '2'.charCodeAt(0));
this.mouseModeSelector_.setKeyCodeForMode(m.SELECTION, '1'.charCodeAt(0));
this.mouseModeSelector_.setKeyCodeForMode(m.ZOOM, '3'.charCodeAt(0));
this.mouseModeSelector_.setKeyCodeForMode(m.TIMING, '4'.charCodeAt(0));
this.mouseModeSelector_.setModifierForAlternateMode(
m.SELECTION, ui.MODIFIER.SHIFT);
this.mouseModeSelector_.setModifierForAlternateMode(
m.PANSCAN, ui.MODIFIER.SPACE);
this.mouseModeSelector_.setModifierForAlternateMode(
m.ZOOM, ui.MODIFIER.CMD_OR_CTRL);
},
detach: function() {
this.modelTrack_.detach();
for (var i = 0; i < this.boundListeners_.length; i++) {
var binding = this.boundListeners_[i];
binding.object.removeEventListener(binding.event, binding.boundFunc);
}
this.boundListeners_ = undefined;
this.viewport_.detach();
},
get viewport() {
return this.viewport_;
},
get model() {
return this.model_;
},
set model(model) {
if (!model)
throw new Error('Model cannot be null');
var modelInstanceChanged = this.model_ != model;
this.model_ = model;
this.modelTrack_.model = model;
// Set up a reasonable viewport.
if (modelInstanceChanged)
this.viewport_.setWhenPossible(this.setInitialViewport_.bind(this));
base.setPropertyAndDispatchChange(this, 'model', model);
},
get hasVisibleContent() {
return this.modelTrack_.hasVisibleContent;
},
setInitialViewport_: function() {
var w = this.modelTrackContainer_.canvas.width;
var min;
var range;
if (this.model_.bounds.isEmpty) {
min = 0;
range = 1000;
} else if (this.model_.bounds.range == 0) {
min = this.model_.bounds.min;
range = 1000;
} else {
min = this.model_.bounds.min;
range = this.model_.bounds.range;
}
var boost = range * 0.15;
tempDisplayTransform.set(this.viewport.currentDisplayTransform);
tempDisplayTransform.xSetWorldBounds(min - boost,
min + range + boost,
w);
this.viewport.setDisplayTransformImmediately(tempDisplayTransform);
},
/**
* @param {Filter} filter The filter to use for finding matches.
* @param {Selection} selection The selection to add matches to.
* @return {Array} An array of objects that match the provided
* TitleFilter.
*/
addAllObjectsMatchingFilterToSelection: function(filter, selection) {
this.modelTrack_.addAllObjectsMatchingFilterToSelection(
filter, selection);
},
/**
* @return {Element} The element whose focused state determines
* whether to respond to keyboard inputs.
* Defaults to the parent element.
*/
get focusElement() {
if (this.focusElement_)
return this.focusElement_;
return this.parentElement;
},
/**
* Sets the element whose focus state will determine whether
* to respond to keybaord input.
*/
set focusElement(value) {
this.focusElement_ = value;
},
get listenToKeys_() {
if (!this.viewport_.isAttachedToDocument_)
return false;
if (this.activeElement instanceof tracing.FindControl)
return false;
if (!this.focusElement_)
return true;
if (this.focusElement.tabIndex >= 0) {
if (document.activeElement == this.focusElement)
return true;
return ui.elementIsChildOf(document.activeElement, this.focusElement);
}
return true;
},
onMouseMove_: function(e) {
// Zooming requires the delta since the last mousemove so we need to avoid
// tracking it when the zoom interaction is active.
if (this.isZooming_)
return;
this.storeLastMousePos_(e);
},
onKeypress_: function(e) {
var vp = this.viewport_;
if (!this.listenToKeys_)
return;
if (document.activeElement.nodeName == 'INPUT')
return;
var viewWidth = this.modelTrackContainer_.canvas.clientWidth;
var curMouseV, curCenterW;
switch (e.keyCode) {
case 119: // w
case 44: // ,
this.zoomBy_(1.5, true);
break;
case 115: // s
case 111: // o
this.zoomBy_(1 / 1.5, true);
break;
case 103: // g
this.onGridToggle_(true);
break;
case 71: // G
this.onGridToggle_(false);
break;
case 87: // W
case 60: // <
this.zoomBy_(10, true);
break;
case 83: // S
case 79: // O
this.zoomBy_(1 / 10, true);
break;
case 97: // a
this.queueSmoothPan_(viewWidth * 0.3, 0);
break;
case 100: // d
case 101: // e
this.queueSmoothPan_(viewWidth * -0.3, 0);
break;
case 65: // A
this.queueSmoothPan_(viewWidth * 0.5, 0);
break;
case 68: // D
this.queueSmoothPan_(viewWidth * -0.5, 0);
break;
case 48: // 0
this.setInitialViewport_();
break;
case 102: // f
this.zoomToSelection();
break;
}
},
// Not all keys send a keypress.
onKeydown_: function(e) {
if (!this.listenToKeys_)
return;
var sel;
var vp = this.viewport;
var viewWidth = this.modelTrackContainer_.canvas.clientWidth;
switch (e.keyCode) {
case 37: // left arrow
sel = this.selection.getShiftedSelection(
this.viewport, -1);
if (sel) {
this.setSelectionAndClearHighlight(sel);
this.panToSelection();
e.preventDefault();
} else {
this.queueSmoothPan_(viewWidth * 0.3, 0);
}
break;
case 39: // right arrow
sel = this.selection.getShiftedSelection(
this.viewport, 1);
if (sel) {
this.setSelectionAndClearHighlight(sel);
this.panToSelection();
e.preventDefault();
} else {
this.queueSmoothPan_(-viewWidth * 0.3, 0);
}
break;
case 9: // TAB
if (this.focusElement.tabIndex == -1) {
if (e.shiftKey)
this.selectPrevious_(e);
else
this.selectNext_(e);
e.preventDefault();
}
break;
}
},
onKeyup_: function(e) {
if (!this.listenToKeys_)
return;
if (!e.shiftKey) {
if (this.dragBeginEvent_) {
this.setDragBoxPosition_(this.dragBoxXStart_, this.dragBoxYStart_,
this.dragBoxXEnd_, this.dragBoxYEnd_);
}
}
},
onDblClick_: function(e) {
if (this.mouseModeSelector_.mode !== ui.MOUSE_SELECTOR_MODE.SELECTION)
return;
if (!this.selection.length || !this.selection[0].title)
return;
var selection = new Selection();
var filter = new tracing.ExactTitleFilter(this.selection[0].title);
this.addAllObjectsMatchingFilterToSelection(filter, selection);
this.setSelectionAndClearHighlight(selection);
},
onMouseWheel_: function(e) {
if (!e.altKey)
return;
var delta = e.wheelDelta / 120;
var zoomScale = Math.pow(1.5, delta);
this.zoomBy_(zoomScale);
e.preventDefault();
},
queueSmoothPan_: function(viewDeltaX, deltaY) {
var deltaX = this.viewport_.currentDisplayTransform.xViewVectorToWorld(
viewDeltaX);
var animation = new tracing.TimelineDisplayTransformPanAnimation(
deltaX, deltaY);
this.viewport_.queueDisplayTransformAnimation(animation);
},
/**
* Zoom in or out on the timeline by the given scale factor.
* @param {Number} scale The scale factor to apply. If <1, zooms out.
* @param {boolean} Whether to change the zoom level smoothly.
*/
zoomBy_: function(scale, smooth) {
smooth = !!smooth;
var vp = this.viewport;
var viewWidth = this.modelTrackContainer_.canvas.clientWidth;
var pixelRatio = window.devicePixelRatio || 1;
var goalFocalPointXView = this.lastMouseViewPos_.x * pixelRatio;
var goalFocalPointXWorld = vp.currentDisplayTransform.xViewToWorld(
goalFocalPointXView);
if (smooth) {
var animation = new tracing.TimelineDisplayTransformZoomToAnimation(
goalFocalPointXWorld, goalFocalPointXView,
vp.currentDisplayTransform.panY,
scale);
vp.queueDisplayTransformAnimation(animation);
} else {
tempDisplayTransform.set(vp.currentDisplayTransform);
tempDisplayTransform.scaleX = tempDisplayTransform.scaleX * scale;
tempDisplayTransform.xPanWorldPosToViewPos(
goalFocalPointXWorld, goalFocalPointXView, viewWidth);
vp.setDisplayTransformImmediately(tempDisplayTransform);
}
},
/**
* Zoom into the current selection.
*/
zoomToSelection: function() {
if (!this.selectionOfInterest.length)
return;
var bounds = this.selectionOfInterest.bounds;
if (!bounds.range)
return;
var worldCenter = bounds.center;
var adjustedWorldRange = bounds.range * 1.25;
var newScale = this.modelTrackContainer_.canvas.width /
adjustedWorldRange;
var zoomInRatio = newScale / this.viewport.currentDisplayTransform.scaleX;
var animation = new tracing.TimelineDisplayTransformZoomToAnimation(
worldCenter, 'center',
this.viewport.currentDisplayTransform.panY,
zoomInRatio);
this.viewport.queueDisplayTransformAnimation(animation);
},
/**
* Pan the view so the current selection becomes visible.
*/
panToSelection: function() {
if (!this.selectionOfInterest.length)
return;
var bounds = this.selectionOfInterest.bounds;
var worldCenter = bounds.center;
var viewWidth = this.modelTrackContainer_.canvas.width;
var dt = this.viewport.currentDisplayTransform;
if (false && !bounds.range) {
if (dt.xWorldToView(bounds.center) < 0 ||
dt.xWorldToView(bounds.center) > viewWidth) {
tempDisplayTransform.set(dt);
tempDisplayTransform.xPanWorldPosToViewPos(
worldCenter, 'center', viewWidth);
var deltaX = tempDisplayTransform.panX - dt.panX;
var animation = new tracing.TimelineDisplayTransformPanAnimation(
deltaX, 0);
this.viewport.queueDisplayTransformAnimation(animation);
}
return;
}
tempDisplayTransform.set(dt);
tempDisplayTransform.xPanWorldBoundsIntoView(
bounds.min,
bounds.max,
viewWidth);
var deltaX = tempDisplayTransform.panX - dt.panX;
var animation = new tracing.TimelineDisplayTransformPanAnimation(
deltaX, 0);
this.viewport.queueDisplayTransformAnimation(animation);
},
/**
* Sets the selected events and changes the SelectionState of the events to
* SELECTED.
* @param {Selection} selection A Selection of the new selected events.
*/
set selection(selection) {
this.setSelectionAndHighlight(selection, this.highlight_);
},
get selection() {
return this.selection_;
},
/**
* Sets the highlighted events and changes the SelectionState of the events
* to HIGHLIGHTED. All other events are set to DIMMED, except SELECTED
* ones.
* @param {Selection} selection A Selection of the new selected events.
*/
set highlight(highlight) {
this.setSelectionAndHighlight(this.selection_, highlight);
},
get highlight() {
return this.highlight_;
},
/**
* Getter for events of interest, primarily SELECTED and secondarily
* HIGHLIGHTED events.
*/
get selectionOfInterest() {
if (!this.selection_.length && this.highlight_.length)
return this.highlight_;
return this.selection_;
},
/**
* Sets the selected events, changes the SelectionState of the events to
* SELECTED and clears the highlighted events.
* @param {Selection} selection A Selection of the new selected events.
*/
setSelectionAndClearHighlight: function(selection) {
this.setSelectionAndHighlight(selection, null);
},
/**
* Sets the highlighted events, changes the SelectionState of the events to
* HIGHLIGHTED and clears the selected events. All other events are set to
* DIMMED.
* @param {Selection} highlight A Selection of the new highlighted events.
*/
setHighlightAndClearSelection: function(highlight) {
this.setSelectionAndHighlight(null, highlight);
},
/**
* Sets both selected and highlighted events. If an event is both it will be
* set to SELECTED. All other events are set to DIMMED.
* @param {Selection} selection A Selection of the new selected events.
* @param {Selection} highlight A Selection of the new highlighted events.
*/
setSelectionAndHighlight: function(selection, highlight) {
if (selection === this.selection_ && highlight === this.highlight_)
return;
if ((selection !== null && !(selection instanceof Selection)) ||
(highlight !== null && !(highlight instanceof Selection))) {
throw new Error('Expected Selection');
}
if (highlight && highlight.length) {
// Set all events to DIMMED. This needs to be done before clearing the
// old highlight, so that the old events are still available. This is
// also necessary when the highlight doesn't change, because it might
// have overlapping events with selection.
this.resetEventsTo_(SelectionState.DIMMED);
// Switch the highlight.
if (highlight !== this.highlight_)
this.highlight_ = highlight;
// Set HIGHLIGHTED on the events of the new highlight.
this.setSelectionState_(highlight, SelectionState.HIGHLIGHTED);
} else {
// If no highlight is active the SelectionState needs to be cleared.
// Note that this also clears old SELECTED events, so it doesn't need
// to be called again when setting the selection.
this.resetEventsTo_(SelectionState.NONE);
this.highlight_ = new Selection();
}
if (selection && selection.length) {
// Switch the selection
if (selection !== this.selection_)
this.selection_ = selection;
// Set SELECTED on the events of the new highlight.
this.setSelectionState_(selection, SelectionState.SELECTED);
} else
this.selection_ = new Selection();
base.dispatchSimpleEvent(this, 'selectionChange');
if (this.selectionOfInterest.length) {
var track = this.viewport.trackForEvent(this.selectionOfInterest[0]);
if (track)
track.scrollIntoViewIfNeeded();
}
this.viewport.dispatchChangeEvent(); // Triggers a redraw.
},
/**
* Sets a new SelectionState on all events in the selection.
* @param {Selection} selection The affected selection.
* @param {SelectionState} selectionState The new selection state.
*/
setSelectionState_: function(selection, selectionState) {
for (var i = 0; i < selection.length; i++)
selection[i].selectionState = selectionState;
},
/**
* Resets all events to the provided SelectionState. When the SelectionState
* changes from or to DIMMED all events in the model need to get updated.
* @param {SelectionState} selectionState The SelectionState to reset to.
*/
resetEventsTo_: function(selectionState) {
var dimmed = this.highlight_.length;
var resetAll = (dimmed && selectionState !== SelectionState.DIMMED) ||
(!dimmed && selectionState === SelectionState.DIMMED);
if (resetAll) {
this.model.iterateAllEvents(
function(event) { event.selectionState = selectionState; });
} else {
this.setSelectionState_(this.selection_, selectionState);
this.setSelectionState_(this.highlight_, selectionState);
}
},
hideDragBox_: function() {
this.dragBox_.style.left = '-1000px';
this.dragBox_.style.top = '-1000px';
this.dragBox_.style.width = 0;
this.dragBox_.style.height = 0;
},
setDragBoxPosition_: function(xStart, yStart, xEnd, yEnd) {
var loY = Math.min(yStart, yEnd);
var hiY = Math.max(yStart, yEnd);
var loX = Math.min(xStart, xEnd);
var hiX = Math.max(xStart, xEnd);
var modelTrackRect = this.modelTrack_.getBoundingClientRect();
var dragRect = {left: loX, top: loY, width: hiX - loX, height: hiY - loY};
dragRect.right = dragRect.left + dragRect.width;
dragRect.bottom = dragRect.top + dragRect.height;
var modelTrackContainerRect =
this.modelTrackContainer_.getBoundingClientRect();
var clipRect = {
left: modelTrackContainerRect.left,
top: modelTrackContainerRect.top,
right: modelTrackContainerRect.right,
bottom: modelTrackContainerRect.bottom
};
var headingWidth = window.getComputedStyle(
this.querySelector('heading')).width;
var trackTitleWidth = parseInt(headingWidth);
clipRect.left = clipRect.left + trackTitleWidth;
var finalDragBox = intersectRect_(clipRect, dragRect);
this.dragBox_.style.left = finalDragBox.left + 'px';
this.dragBox_.style.width = finalDragBox.width + 'px';
this.dragBox_.style.top = finalDragBox.top + 'px';
this.dragBox_.style.height = finalDragBox.height + 'px';
var pixelRatio = window.devicePixelRatio || 1;
var canv = this.modelTrackContainer_.canvas;
var dt = this.viewport.currentDisplayTransform;
var loWX = dt.xViewToWorld(
(loX - canv.offsetLeft) * pixelRatio);
var hiWX = dt.xViewToWorld(
(hiX - canv.offsetLeft) * pixelRatio);
var roundedDuration = Math.round((hiWX - loWX) * 100) / 100;
this.dragBox_.textContent = roundedDuration + 'ms';
var e = new base.Event('selectionChanging');
e.loWX = loWX;
e.hiWX = hiWX;
this.dispatchEvent(e);
},
onGridToggle_: function(left) {
var tb = left ? this.selection.bounds.min : this.selection.bounds.max;
// Toggle the grid off if the grid is on, the marker position is the same
// and the same element is selected (same timebase).
if (this.viewport.gridEnabled &&
this.viewport.gridSide === left &&
this.viewport.gridInitialTimebase === tb) {
this.viewport.gridside = undefined;
this.viewport.gridEnabled = false;
this.viewport.gridInitialTimebase = undefined;
return;
}
// Shift the timebase left until its just left of model_.bounds.min.
var numIntervalsSinceStart = Math.ceil((tb - this.model_.bounds.min) /
this.viewport.gridStep_);
this.viewport.gridEnabled = true;
this.viewport.gridSide = left;
this.viewport.gridInitialTimebase = tb;
this.viewport.gridTimebase = tb -
(numIntervalsSinceStart + 1) * this.viewport.gridStep_;
},
storeLastMousePos_: function(e) {
this.lastMouseViewPos_ = this.extractRelativeMousePosition_(e);
},
extractRelativeMousePosition_: function(e) {
var canv = this.modelTrackContainer_.canvas;
return {
x: e.clientX - canv.offsetLeft,
y: e.clientY - canv.offsetTop
};
},
storeInitialMouseDownPos_: function(e) {
var position = this.extractRelativeMousePosition_(e);
this.mouseViewPosAtMouseDown_.x = position.x;
this.mouseViewPosAtMouseDown_.y = position.y;
},
focusElements_: function() {
if (document.activeElement)
document.activeElement.blur();
if (this.focusElement.tabIndex >= 0)
this.focusElement.focus();
},
storeInitialInteractionPositionsAndFocus_: function(mouseEvent) {
this.storeInitialMouseDownPos_(mouseEvent);
this.storeLastMousePos_(mouseEvent);
this.focusElements_();
},
onBeginPanScan_: function(e) {
var vp = this.viewport;
var mouseEvent = e.data;
this.viewportDisplayTransformAtMouseDown_ =
vp.currentDisplayTransform.clone();
this.isPanningAndScanning_ = true;
this.storeInitialInteractionPositionsAndFocus_(mouseEvent);
mouseEvent.preventDefault();
},
onUpdatePanScan_: function(e) {
if (!this.isPanningAndScanning_)
return;
var viewWidth = this.modelTrackContainer_.canvas.clientWidth;
var mouseEvent = e.data;
var pixelRatio = window.devicePixelRatio || 1;
var xDeltaView = pixelRatio * (this.lastMouseViewPos_.x -
this.mouseViewPosAtMouseDown_.x);
var yDelta = this.lastMouseViewPos_.y -
this.mouseViewPosAtMouseDown_.y;
tempDisplayTransform.set(this.viewportDisplayTransformAtMouseDown_);
tempDisplayTransform.incrementPanXInViewUnits(xDeltaView);
tempDisplayTransform.panY -= yDelta;
this.viewport.setDisplayTransformImmediately(tempDisplayTransform);
mouseEvent.preventDefault();
mouseEvent.stopPropagation();
this.storeLastMousePos_(mouseEvent);
},
onEndPanScan_: function(e) {
var mouseEvent = e.data;
this.isPanningAndScanning_ = false;
this.storeLastMousePos_(mouseEvent);
if (!e.isClick)
e.consumed = true;
},
onBeginSelection_: function(e) {
var mouseEvent = e.data;
var canv = this.modelTrackContainer_.canvas;
var rect = this.modelTrack_.getBoundingClientRect();
var canvRect = canv.getBoundingClientRect();
var inside = rect &&
mouseEvent.clientX >= rect.left &&
mouseEvent.clientX < rect.right &&
mouseEvent.clientY >= rect.top &&
mouseEvent.clientY < rect.bottom &&
mouseEvent.clientX >= canvRect.left &&
mouseEvent.clientX < canvRect.right;
if (!inside)
return;
this.dragBeginEvent_ = mouseEvent;
this.storeInitialInteractionPositionsAndFocus_(mouseEvent);
mouseEvent.preventDefault();
},
onUpdateSelection_: function(e) {
var mouseEvent = e.data;
if (!this.dragBeginEvent_)
return;
// Update the drag box
this.dragBoxXStart_ = this.dragBeginEvent_.clientX;
this.dragBoxXEnd_ = mouseEvent.clientX;
this.dragBoxYStart_ = this.dragBeginEvent_.clientY;
this.dragBoxYEnd_ = mouseEvent.clientY;
this.setDragBoxPosition_(this.dragBoxXStart_, this.dragBoxYStart_,
this.dragBoxXEnd_, this.dragBoxYEnd_);
},
onEndSelection_: function(e) {
e.consumed = true;
if (!this.dragBeginEvent_)
return;
var mouseEvent = e.data;
// Stop the dragging.
this.hideDragBox_();
var eDown = this.dragBeginEvent_ || mouseEvent;
this.dragBeginEvent_ = null;
// Figure out extents of the drag.
var loY = Math.min(eDown.clientY, mouseEvent.clientY);
var hiY = Math.max(eDown.clientY, mouseEvent.clientY);
var loX = Math.min(eDown.clientX, mouseEvent.clientX);
var hiX = Math.max(eDown.clientX, mouseEvent.clientX);
var tracksContainerBoundingRect =
this.modelTrackContainer_.getBoundingClientRect();
var topBoundary = tracksContainerBoundingRect.height;
// Convert to worldspace.
var canv = this.modelTrackContainer_.canvas;
var loVX = loX - canv.offsetLeft;
var hiVX = hiX - canv.offsetLeft;
// Figure out what has been selected.
var selection = new Selection();
this.modelTrack_.addIntersectingItemsInRangeToSelection(
loVX, hiVX, loY, hiY, selection);
// Activate the new selection.
this.setSelectionAndClearHighlight(selection);
},
onBeginZoom_: function(e) {
var mouseEvent = e.data;
this.isZooming_ = true;
this.storeInitialInteractionPositionsAndFocus_(mouseEvent);
mouseEvent.preventDefault();
},
onUpdateZoom_: function(e) {
if (!this.isZooming_)
return;
var mouseEvent = e.data;
var newPosition = this.extractRelativeMousePosition_(mouseEvent);
var zoomScaleValue = 1 + (this.lastMouseViewPos_.y -
newPosition.y) * 0.01;
this.zoomBy_(zoomScaleValue, false);
this.storeLastMousePos_(mouseEvent);
},
onEndZoom_: function(e) {
this.isZooming_ = false;
if (!e.isClick)
e.consumed = true;
}
};
return {
TimelineTrackView: TimelineTrackView
};
});
// 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.
'use strict';
/**
* @fileoverview FindControl and FindController.
*/
base.requireTemplate('tracing.find_control');
base.require('tracing.timeline_track_view');
base.require('tracing.filter');
base.exportTo('tracing', function() {
/**
* FindControl
* @constructor
*/
var FindControl = ui.define('find-control');
FindControl.prototype = {
__proto__: HTMLUnknownElement.prototype,
decorate: function() {
var shadow = this.createShadowRoot();
shadow.applyAuthorStyles = true;
shadow.resetStyleInheritance = true;
shadow.appendChild(base.instantiateTemplate('#find-control-template'));
this.hitCountEl_ = shadow.querySelector('.hit-count-label');
shadow.querySelector('.find-previous')
.addEventListener('click', this.findPrevious_.bind(this));
shadow.querySelector('.find-next')
.addEventListener('click', this.findNext_.bind(this));
this.filterEl_ = shadow.querySelector('#find-control-filter');
this.filterEl_.addEventListener('input',
this.filterTextChanged_.bind(this));
this.filterEl_.addEventListener('keydown', function(e) {
e.stopPropagation();
if (e.keyCode == 13) {
if (e.shiftKey)
this.findPrevious_();
else
this.findNext_();
}
}.bind(this));
this.filterEl_.addEventListener('keypress', function(e) {
e.stopPropagation();
});
this.filterEl_.addEventListener('blur', function(e) {
this.updateHitCountEl_();
}.bind(this));
this.filterEl_.addEventListener('focus', function(e) {
this.controller.reset();
this.filterTextChanged_();
this.filterEl_.select();
}.bind(this));
// Prevent that the input text is deselected after focusing the find
// control with the mouse.
this.filterEl_.addEventListener('mouseup', function(e) {
e.preventDefault();
});
this.updateHitCountEl_();
},
get controller() {
return this.controller_;
},
set controller(c) {
this.controller_ = c;
this.updateHitCountEl_();
},
focus: function() {
this.filterEl_.focus();
},
hasFocus: function() {
return this === document.activeElement;
},
filterTextChanged_: function() {
this.controller.filterText = this.filterEl_.value;
this.updateHitCountEl_();
},
findNext_: function() {
if (this.controller)
this.controller.findNext();
this.updateHitCountEl_();
},
findPrevious_: function() {
if (this.controller)
this.controller.findPrevious();
this.updateHitCountEl_();
},
updateHitCountEl_: function() {
if (!this.controller || !this.hasFocus()) {
this.hitCountEl_.textContent = '';
return;
}
var i = this.controller.currentHitIndex;
var n = this.controller.filterHits.length;
if (n == 0)
this.hitCountEl_.textContent = '0 of 0';
else
this.hitCountEl_.textContent = (i + 1) + ' of ' + n;
}
};
function FindController() {
this.timeline_ = undefined;
this.model_ = undefined;
this.filterText_ = '';
this.filterHits_ = new tracing.Selection();
this.filterHitsDirty_ = true;
this.currentHitIndex_ = -1;
};
FindController.prototype = {
__proto__: Object.prototype,
get timeline() {
return this.timeline_;
},
set timeline(t) {
this.timeline_ = t;
this.filterHitsDirty_ = true;
},
get filterText() {
return this.filterText_;
},
set filterText(f) {
if (f == this.filterText_)
return;
this.filterText_ = f;
this.filterHitsDirty_ = true;
if (!this.timeline)
return;
this.timeline.setHighlightAndClearSelection(this.filterHits);
},
get filterHits() {
if (this.filterHitsDirty_) {
this.filterHitsDirty_ = false;
this.filterHits_ = new tracing.Selection();
this.currentHitIndex_ = -1;
if (this.timeline_ && this.filterText.length) {
var filter = new tracing.TitleFilter(this.filterText);
this.timeline.addAllObjectsMatchingFilterToSelection(
filter, this.filterHits_);
}
}
return this.filterHits_;
},
get currentHitIndex() {
return this.currentHitIndex_;
},
find_: function(dir) {
var firstHit = this.currentHitIndex_ === -1;
if (firstHit && dir < 0)
this.currentHitIndex_ = 0;
var N = this.filterHits.length;
this.currentHitIndex_ = (this.currentHitIndex_ + dir + N) % N;
if (!this.timeline)
return;
this.timeline.selection =
this.filterHits.subSelection(this.currentHitIndex_, 1);
},
findNext: function() {
this.find_(1);
},
findPrevious: function() {
this.find_(-1);
},
reset: function() {
this.filterText_ = '';
this.filterHitsDirty_ = true;
}
};
return {
FindControl: FindControl,
FindController: FindController
};
});
// 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.
'use strict';
base.require('ui');
base.requireStylesheet('ui.drag_handle');
base.exportTo('ui', function() {
/**
* Detects when user clicks handle determines new height of container based
* on user's vertical mouse move and resizes the target.
* @constructor
* @extends {HTMLDivElement}
* You will need to set target to be the draggable element
*/
var DragHandle = ui.define('x-drag-handle');
DragHandle.prototype = {
__proto__: HTMLDivElement.prototype,
decorate: function() {
this.lastMousePos_ = 0;
this.onMouseMove_ = this.onMouseMove_.bind(this);
this.onMouseUp_ = this.onMouseUp_.bind(this);
this.addEventListener('mousedown', this.onMouseDown_);
this.target_ = undefined;
this.horizontal = true;
this.observer_ = new WebKitMutationObserver(
this.didTargetMutate_.bind(this));
this.targetSizesByModeKey_ = {};
},
get modeKey_() {
return this.target_.className == '' ? '.' : this.target_.className;
},
get target() {
return this.target_;
},
set target(target) {
this.observer_.disconnect();
this.target_ = target;
if (!this.target_)
return;
this.observer_.observe(this.target_, {
attributes: true,
attributeFilter: ['class']
});
},
get horizontal() {
return this.horizontal_;
},
set horizontal(h) {
this.horizontal_ = h;
if (this.horizontal_)
this.className = 'horizontal-drag-handle';
else
this.className = 'vertical-drag-handle';
},
get vertical() {
return !this.horizontal_;
},
set vertical(v) {
this.horizontal = !v;
},
forceMutationObserverFlush_: function() {
var records = this.observer_.takeRecords();
if (records.length)
this.didTargetMutate_(records);
},
didTargetMutate_: function(e) {
var modeSize = this.targetSizesByModeKey_[this.modeKey_];
if (modeSize !== undefined) {
this.setTargetSize_(modeSize);
return;
}
// If we hadn't previously sized the target, then just remove any manual
// sizing that we applied.
this.target_.style[this.targetStyleKey_] = '';
},
get targetStyleKey_() {
return this.horizontal_ ? 'height' : 'width';
},
getTargetSize_: function() {
// If style is not set, start off with computed height.
var targetStyleKey = this.targetStyleKey_;
if (!this.target_.style[targetStyleKey]) {
this.target_.style[targetStyleKey] =
window.getComputedStyle(this.target_)[targetStyleKey];
}
var size = parseInt(this.target_.style[targetStyleKey]);
this.targetSizesByModeKey_[this.modeKey_] = size;
return size;
},
setTargetSize_: function(s) {
this.target_.style[this.targetStyleKey_] = s + 'px';
this.targetSizesByModeKey_[this.modeKey_] = s;
},
applyDelta_: function(delta) {
// Apply new size to the container.
var curSize = this.getTargetSize_();
var newSize;
if (this.target_ === this.nextSibling) {
newSize = curSize + delta;
} else {
newSize = curSize - delta;
}
this.setTargetSize_(newSize);
},
onMouseMove_: function(e) {
// Compute the difference in height position.
var curMousePos = this.horizontal_ ? e.clientY : e.clientX;
var delta = this.lastMousePos_ - curMousePos;
this.applyDelta_(delta);
this.lastMousePos_ = curMousePos;
e.preventDefault();
return true;
},
onMouseDown_: function(e) {
if (!this.target_)
return;
this.forceMutationObserverFlush_();
this.lastMousePos_ = this.horizontal_ ? e.clientY : e.clientX;
document.addEventListener('mousemove', this.onMouseMove_);
document.addEventListener('mouseup', this.onMouseUp_);
e.preventDefault();
return true;
},
onMouseUp_: function(e) {
document.removeEventListener('mousemove', this.onMouseMove_);
document.removeEventListener('mouseup', this.onMouseUp_);
e.preventDefault();
}
};
return {
DragHandle: DragHandle
};
});
// 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.
'use strict';
/**
* @fileoverview View visualizes TRACE_EVENT events using the
* tracing.Timeline component and adds in selection summary and control buttons.
*/
base.requireStylesheet('ui.trace_viewer');
base.requireStylesheet('tracing.timeline_view');
base.requireTemplate('tracing.timeline_view');
base.require('base.utils');
base.require('base.settings');
base.require('tracing.analysis.analysis_view');
base.require('tracing.find_control');
base.require('tracing.timeline_track_view');
base.require('ui.dom_helpers');
base.require('ui.overlay');
base.require('ui.drag_handle');
base.require('tracing.analysis.cpu_slice_view');
base.require('tracing.analysis.thread_time_slice_view');
base.exportTo('tracing', function() {
/**
* View
* @constructor
* @extends {HTMLDivElement}
*/
var TimelineView = ui.define('div');
TimelineView.prototype = {
__proto__: HTMLDivElement.prototype,
decorate: function() {
this.classList.add('timeline-view');
var node = base.instantiateTemplate('#timeline-view-template');
this.appendChild(node);
this.titleEl_ = this.querySelector('.title');
this.leftControlsEl_ = this.querySelector('#left-controls');
this.rightControlsEl_ = this.querySelector('#right-controls');
this.timelineContainer_ = this.querySelector('.container');
this.findCtl_ = new tracing.FindControl();
this.findCtl_.controller = new tracing.FindController();
this.showFlowEvents_ = false;
this.rightControls.appendChild(ui.createCheckBox(
this, 'showFlowEvents',
'tracing.TimelineView.showFlowEvents', false,
'Flow events'));
this.rightControls.appendChild(this.createMetadataButton_());
this.rightControls.appendChild(this.findCtl_);
this.rightControls.appendChild(this.createHelpButton_());
this.dragEl_ = new ui.DragHandle();
this.appendChild(this.dragEl_);
this.analysisEl_ = new tracing.analysis.AnalysisView();
this.analysisEl_.addEventListener(
'requestSelectionChange',
this.onRequestSelectionChange_.bind(this));
this.appendChild(this.analysisEl_);
// Bookkeeping.
this.onSelectionChanged_ = this.onSelectionChanged_.bind(this);
document.addEventListener('keydown', this.onKeyDown_.bind(this), true);
document.addEventListener('keypress', this.onKeypress_.bind(this), true);
this.dragEl_.target = this.analysisEl_;
},
get showFlowEvents() {
return this.showFlowEvents_;
},
set showFlowEvents(showFlowEvents) {
this.showFlowEvents_ = showFlowEvents;
if (!this.timeline_)
return;
this.timeline_.viewport.showFlowEvents = showFlowEvents;
},
createHelpButton_: function() {
var node = base.instantiateTemplate('#help-btn-template');
var showEl = node.querySelector('.view-help-button');
var helpTextEl = node.querySelector('.view-help-text');
var dlg = new ui.Overlay();
dlg.title = 'chrome://tracing Help';
dlg.classList.add('view-help-overlay');
dlg.appendChild(node);
function onClick(e) {
dlg.visible = !dlg.visible;
var mod = base.isMac ? 'cmd ' : 'ctrl';
var spans = helpTextEl.querySelectorAll('span.mod');
for (var i = 0; i < spans.length; i++) {
spans[i].textContent = mod;
}
// Stop event so it doesn't trigger new click listener on document.
e.stopPropagation();
return false;
}
showEl.addEventListener('click', onClick.bind(this));
return showEl;
},
createMetadataButton_: function() {
var node = base.instantiateTemplate('#metadata-btn-template');
var showEl = node.querySelector('.view-metadata-button');
var textEl = node.querySelector('.info-button-text');
var dlg = new ui.Overlay();
dlg.title = 'Metadata for trace';
dlg.classList.add('view-metadata-overlay');
dlg.appendChild(node);
function onClick(e) {
dlg.visible = true;
var metadataStrings = [];
var model = this.model;
for (var data in model.metadata) {
var meta = model.metadata[data];
var name = JSON.stringify(meta.name);
var value = JSON.stringify(meta.value, undefined, ' ');
metadataStrings.push(name + ': ' + value);
}
textEl.textContent = metadataStrings.join('\n');
e.stopPropagation();
return false;
}
showEl.addEventListener('click', onClick.bind(this));
function updateVisibility() {
showEl.style.display =
(this.model && this.model.metadata.length) ? '' : 'none';
}
var updateVisibility_ = updateVisibility.bind(this);
updateVisibility_();
this.addEventListener('modelChange', updateVisibility_);
return showEl;
},
get leftControls() {
return this.leftControlsEl_;
},
get rightControls() {
return this.rightControlsEl_;
},
get viewTitle() {
return this.titleEl_.textContent.substring(
this.titleEl_.textContent.length - 2);
},
set viewTitle(text) {
if (text === undefined) {
this.titleEl_.textContent = '';
this.titleEl_.hidden = true;
return;
}
this.titleEl_.hidden = false;
this.titleEl_.textContent = text;
},
get model() {
if (this.timeline_)
return this.timeline_.model;
return undefined;
},
set model(model) {
var modelInstanceChanged = model != this.model;
var modelValid = model && !model.bounds.isEmpty;
// Remove old timeline if the model has completely changed.
if (modelInstanceChanged) {
this.timelineContainer_.textContent = '';
if (this.timeline_) {
this.timeline_.removeEventListener(
'selectionChange', this.onSelectionChanged_);
this.timeline_.detach();
this.timeline_ = undefined;
this.findCtl_.controller.timeline = undefined;
}
}
// Create new timeline if needed.
if (modelValid && !this.timeline_) {
this.timeline_ = new tracing.TimelineTrackView();
this.timeline_.focusElement =
this.focusElement_ ? this.focusElement_ : this.parentElement;
this.timelineContainer_.appendChild(this.timeline_);
this.findCtl_.controller.timeline = this.timeline_;
this.timeline_.addEventListener(
'selectionChange', this.onSelectionChanged_);
this.timeline_.viewport.showFlowEvents = this.showFlowEvents;
this.analysisEl_.clearSelectionHistory();
}
// Set the model.
if (modelValid)
this.timeline_.model = model;
base.dispatchSimpleEvent(this, 'modelChange');
// Do things that are selection specific
if (modelInstanceChanged)
this.onSelectionChanged_();
},
get timeline() {
return this.timeline_;
},
get settings() {
if (!this.settings_)
this.settings_ = new base.Settings();
return this.settings_;
},
/**
* Sets the element whose focus state will determine whether
* to respond to keybaord input.
*/
set focusElement(value) {
this.focusElement_ = value;
if (this.timeline_)
this.timeline_.focusElement = value;
},
/**
* @return {Element} The element whose focused state determines
* whether to respond to keyboard inputs.
* Defaults to the parent element.
*/
get focusElement() {
if (this.focusElement_)
return this.focusElement_;
return this.parentElement;
},
/**
* @return {boolean} Whether the current timeline is attached to the
* document.
*/
get isAttachedToDocument_() {
var cur = this;
while (cur.parentNode)
cur = cur.parentNode;
return cur == this.ownerDocument;
},
get listenToKeys_() {
if (!this.isAttachedToDocument_)
return;
if (!this.focusElement_)
return true;
if (this.focusElement.tabIndex >= 0)
return document.activeElement == this.focusElement;
return true;
},
onKeyDown_: function(e) {
if (!this.listenToKeys_)
return;
if (e.keyCode === 27) { // ESC
this.focus();
e.preventDefault();
}
},
onKeypress_: function(e) {
if (!this.listenToKeys_)
return;
if (e.keyCode === '/'.charCodeAt(0)) {
if (this.findCtl_.hasFocus())
this.focus();
else
this.findCtl_.focus();
e.preventDefault();
} else if (e.keyCode === '?'.charCodeAt(0)) {
this.querySelector('.view-help-button').click();
e.preventDefault();
}
},
beginFind: function() {
if (this.findInProgress_)
return;
this.findInProgress_ = true;
var dlg = tracing.FindControl();
dlg.controller = new tracing.FindController();
dlg.controller.timeline = this.timeline;
dlg.visible = true;
dlg.addEventListener('close', function() {
this.findInProgress_ = false;
}.bind(this));
dlg.addEventListener('findNext', function() {
});
dlg.addEventListener('findPrevious', function() {
});
},
onSelectionChanged_: function(e) {
var oldScrollTop = this.timelineContainer_.scrollTop;
var selection = this.timeline_ ?
this.timeline_.selectionOfInterest :
new tracing.Selection();
this.analysisEl_.selection = selection;
this.timelineContainer_.scrollTop = oldScrollTop;
},
onRequestSelectionChange_: function(e) {
this.timeline_.selection = e.selection;
e.stopPropagation();
}
};
return {
TimelineView: TimelineView
};
});
// 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.
'use strict';
base.requireStylesheet('ui.trace_viewer');
base.require('tracing.timeline_view');
base.require('tracing.importer');