| /** |
| // 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'); |
| |