From fa0cb3469b767fb77ebd0c4cad095045c4a3ab91 Mon Sep 17 00:00:00 2001 From: Dana Dahlstrom Date: Fri, 28 Feb 2025 14:00:00 -0800 Subject: [PATCH] Delete unused third_party/perfetto/ui Fixed: 398543084 Reviewed-on: https://github.com/youtube/cobalt/pull/5006 --- third_party/perfetto/ui/.clang-format | 5 - third_party/perfetto/ui/.eslintrc.js | 71 - third_party/perfetto/ui/.gitignore | 3 - third_party/perfetto/ui/BUILD.gn | 54 - third_party/perfetto/ui/OWNERS | 8 - third_party/perfetto/ui/PRESUBMIT.py | 78 - third_party/perfetto/ui/README.md | 47 - third_party/perfetto/ui/build | 18 - third_party/perfetto/ui/build.js | 804 - third_party/perfetto/ui/config/.gitignore | 1 - .../ui/config/gn_deprecation_banner.txt | 6 - .../perfetto/ui/config/integrationtest_env.js | 65 - .../ui/config/integrationtest_setup.js | 56 - .../ui/config/integrationtest_teardown.js | 24 - .../ui/config/jest.integrationtest.config.js | 21 - .../ui/config/jest.unittest.config.js | 19 - .../perfetto/ui/config/rollup.config.js | 102 - third_party/perfetto/ui/eslint | 26 - third_party/perfetto/ui/eslint-all | 19 - third_party/perfetto/ui/node | 18 - third_party/perfetto/ui/npm | 18 - third_party/perfetto/ui/package-lock.json | 18515 ---------------- third_party/perfetto/ui/package.json | 62 - third_party/perfetto/ui/release/OWNERS | 7 - .../perfetto/ui/release/build_all_channels.py | 147 - .../perfetto/ui/release/builder_entrypoint.sh | 40 - third_party/perfetto/ui/release/channels.json | 17 - .../perfetto/ui/release/roll_branch.py | 47 - .../perfetto/ui/release/roll_canary.sh | 20 - .../perfetto/ui/release/roll_stable.sh | 20 - third_party/perfetto/ui/run-all-tests | 20 - third_party/perfetto/ui/run-dev-server | 17 - third_party/perfetto/ui/run-integrationtests | 18 - third_party/perfetto/ui/run-unittests | 18 - third_party/perfetto/ui/src/assets/.gitignore | 1 - .../perfetto/ui/src/assets/analyze_page.scss | 34 - third_party/perfetto/ui/src/assets/brand.png | Bin 4000 -> 0 bytes .../perfetto/ui/src/assets/common.scss | 916 - .../perfetto/ui/src/assets/details.scss | 724 - .../perfetto/ui/src/assets/favicon.png | Bin 2238 -> 0 bytes .../perfetto/ui/src/assets/flags_page.scss | 79 - third_party/perfetto/ui/src/assets/fonts.scss | 19 - .../perfetto/ui/src/assets/hiring_banner.scss | 20 - .../perfetto/ui/src/assets/home_page.scss | 135 - third_party/perfetto/ui/src/assets/index.html | 116 - .../perfetto/ui/src/assets/logo-128.png | Bin 14930 -> 0 bytes .../perfetto/ui/src/assets/logo-3d.png | Bin 48137 -> 0 bytes .../perfetto/ui/src/assets/metrics_page.scss | 50 - third_party/perfetto/ui/src/assets/modal.scss | 207 - .../perfetto/ui/src/assets/perfetto.scss | 40 - .../perfetto/ui/src/assets/rec_atrace.png | Bin 43695 -> 0 bytes .../ui/src/assets/rec_battery_counters.png | Bin 27414 -> 0 bytes .../ui/src/assets/rec_board_voltage.png | Bin 28263 -> 0 bytes .../perfetto/ui/src/assets/rec_cpu_coarse.png | Bin 53670 -> 0 bytes .../perfetto/ui/src/assets/rec_cpu_fine.png | Bin 84341 -> 0 bytes .../perfetto/ui/src/assets/rec_cpu_freq.png | Bin 29285 -> 0 bytes .../ui/src/assets/rec_cpu_voltage.png | Bin 24619 -> 0 bytes .../ui/src/assets/rec_frame_timeline.png | Bin 25975 -> 0 bytes .../perfetto/ui/src/assets/rec_ftrace.png | Bin 22382 -> 0 bytes .../ui/src/assets/rec_gpu_mem_total.png | Bin 52638 -> 0 bytes .../ui/src/assets/rec_java_heap_dump.png | Bin 62897 -> 0 bytes .../perfetto/ui/src/assets/rec_lmk.png | Bin 43733 -> 0 bytes .../perfetto/ui/src/assets/rec_logcat.png | Bin 42205 -> 0 bytes .../perfetto/ui/src/assets/rec_long_trace.png | Bin 21705 -> 0 bytes .../perfetto/ui/src/assets/rec_mem_hifreq.png | Bin 16988 -> 0 bytes .../perfetto/ui/src/assets/rec_meminfo.png | Bin 58545 -> 0 bytes .../src/assets/rec_native_heap_profiler.png | Bin 65155 -> 0 bytes .../perfetto/ui/src/assets/rec_one_shot.png | Bin 23210 -> 0 bytes .../perfetto/ui/src/assets/rec_profiling.png | Bin 65111 -> 0 bytes .../perfetto/ui/src/assets/rec_ps_stats.png | Bin 72069 -> 0 bytes .../perfetto/ui/src/assets/rec_ring_buf.png | Bin 24968 -> 0 bytes .../perfetto/ui/src/assets/rec_syscalls.png | Bin 29115 -> 0 bytes .../perfetto/ui/src/assets/rec_vmstat.png | Bin 49966 -> 0 bytes .../perfetto/ui/src/assets/record.scss | 1344 -- .../ui/src/assets/scheduling_latency.png | Bin 10720 -> 0 bytes .../perfetto/ui/src/assets/sidebar.scss | 333 - .../perfetto/ui/src/assets/topbar.scss | 236 - .../ui/src/assets/trace_info_page.scss | 96 - .../perfetto/ui/src/assets/typefaces.scss | 114 - .../ui/src/assets/widgets/anchor.scss | 48 - .../ui/src/assets/widgets/button.scss | 104 - .../ui/src/assets/widgets/checkbox.scss | 137 - .../ui/src/assets/widgets/empty_state.scss | 47 - .../perfetto/ui/src/assets/widgets/menu.scss | 77 - .../ui/src/assets/widgets/multiselect.scss | 59 - .../perfetto/ui/src/assets/widgets/popup.scss | 74 - .../ui/src/assets/widgets/select.scss | 62 - .../ui/src/assets/widgets/spinner.scss | 39 - .../ui/src/assets/widgets/switch.scss | 114 - .../ui/src/assets/widgets/text_input.scss | 51 - .../perfetto/ui/src/assets/widgets/theme.scss | 47 - .../perfetto/ui/src/assets/widgets/tree.scss | 126 - .../perfetto/ui/src/assets/widgets_page.scss | 77 - .../perfetto/ui/src/base/array_utils.ts | 33 - .../ui/src/base/array_utils_unittest.ts | 51 - .../perfetto/ui/src/base/binary_search.ts | 102 - .../ui/src/base/binary_search_unittest.ts | 65 - .../perfetto/ui/src/base/comparison_utils.ts | 90 - third_party/perfetto/ui/src/base/deferred.ts | 27 - .../perfetto/ui/src/base/deferred_unittest.ts | 39 - .../perfetto/ui/src/base/generic_set.ts | 46 - .../perfetto/ui/src/base/http_utils.ts | 27 - third_party/perfetto/ui/src/base/logging.ts | 77 - .../perfetto/ui/src/base/math_utils.ts | 23 - .../ui/src/base/math_utils_unittest.ts | 29 - third_party/perfetto/ui/src/base/set_utils.ts | 61 - .../ui/src/base/set_utils_unittest.ts | 47 - .../perfetto/ui/src/base/string_utils.ts | 117 - .../ui/src/base/string_utils_unittest.ts | 82 - .../ui/src/base/trace_config_utils.ts | 67 - .../ui/src/base/utils/index-browser.js | 16 - .../perfetto/ui/src/base/utils/index.js | 18 - .../perfetto/ui/src/base/utils/package.json | 8 - .../chrome_tracing_controller.ts | 295 - .../src/chrome_extension/devtools_socket.ts | 102 - .../perfetto/ui/src/chrome_extension/index.ts | 63 - .../ui/src/chrome_extension/manifest.json | 29 - third_party/perfetto/ui/src/common/actions.ts | 1229 - .../ui/src/common/actions_unittest.ts | 468 - .../ui/src/common/aggregation_data.ts | 67 - .../perfetto/ui/src/common/arg_types.ts | 31 - .../ui/src/common/array_buffer_builder.ts | 101 - .../perfetto/ui/src/common/cache_manager.ts | 185 - .../perfetto/ui/src/common/canvas_utils.ts | 90 - .../ui/src/common/canvas_utils_unittest.ts | 35 - .../perfetto/ui/src/common/channels.ts | 49 - .../perfetto/ui/src/common/colorizer.ts | 192 - .../ui/src/common/colorizer_unittest.ts | 62 - .../ui/src/common/comparator_builder.ts | 45 - .../perfetto/ui/src/common/constants.ts | 15 - .../perfetto/ui/src/common/conversion_jobs.ts | 26 - .../perfetto/ui/src/common/dragndrop_logic.ts | 72 - .../ui/src/common/dragndrop_logic_unittest.ts | 45 - .../perfetto/ui/src/common/empty_state.ts | 171 - third_party/perfetto/ui/src/common/engine.ts | 470 - third_party/perfetto/ui/src/common/errors.ts | 70 - .../perfetto/ui/src/common/event_set.ts | 152 - .../ui/src/common/event_set_nocompile_test.ts | 71 - .../perfetto/ui/src/common/feature_flags.ts | 246 - .../ui/src/common/feature_flags_unittest.ts | 118 - .../ui/src/common/flamegraph_unittest.ts | 1073 - .../perfetto/ui/src/common/flamegraph_util.ts | 125 - third_party/perfetto/ui/src/common/hash.ts | 22 - .../perfetto/ui/src/common/http_rpc_engine.ts | 100 - .../perfetto/ui/src/common/immer_init.ts | 29 - third_party/perfetto/ui/src/common/logs.ts | 37 - .../perfetto/ui/src/common/metatracing.ts | 124 - .../perfetto/ui/src/common/metric_data.ts | 20 - .../perfetto/ui/src/common/plugin_api.ts | 82 - third_party/perfetto/ui/src/common/plugins.ts | 130 - .../ui/src/common/plugins_unittest.ts | 25 - .../ui/src/common/proto_ring_buffer.ts | 156 - .../src/common/proto_ring_buffer_unittest.ts | 140 - third_party/perfetto/ui/src/common/protos.ts | 134 - .../perfetto/ui/src/common/protos_unittest.ts | 23 - third_party/perfetto/ui/src/common/queries.ts | 75 - .../perfetto/ui/src/common/query_result.ts | 950 - .../ui/src/common/query_result_unittest.ts | 463 - .../perfetto/ui/src/common/query_utils.ts | 55 - .../ui/src/common/query_utils_unittest.ts | 41 - .../common/recordingV2/adb_connection_impl.ts | 88 - .../adb_connection_over_websocket.ts | 228 - .../recordingV2/adb_connection_over_webusb.ts | 610 - .../common/recordingV2/adb_file_handler.ts | 123 - .../src/common/recordingV2/auth/adb_auth.ts | 199 - .../recordingV2/auth/adb_key_manager.ts | 99 - .../auth/credentials_interfaces.d.ts | 37 - .../chrome_traced_tracing_session.ts | 227 - .../common/recordingV2/host_os_byte_stream.ts | 84 - .../recordingV2/recording_config_utils.ts | 692 - .../recordingV2/recording_error_handling.ts | 131 - .../recordingV2/recording_interfaces_v2.ts | 223 - .../recordingV2/recording_page_controller.ts | 522 - .../src/common/recordingV2/recording_utils.ts | 156 - .../android_websocket_target_factory.ts | 257 - ...droid_websocket_target_factory_unittest.ts | 55 - .../android_webusb_target_factory.ts | 145 - .../target_factories/chrome_target_factory.ts | 91 - .../chrome_target_factory_unittest.ts | 40 - .../host_os_target_factory.ts | 81 - .../recordingV2/target_factories/index.ts | 19 - .../virtual_target_factory.ts | 59 - .../recordingV2/target_factory_registry.ts | 47 - .../recordingV2/targets/android_target.ts | 154 - .../targets/android_virtual_target.ts | 55 - .../targets/android_websocket_target.ts | 40 - .../targets/android_webusb_target.ts | 42 - .../recordingV2/targets/chrome_target.ts | 80 - .../recordingV2/targets/host_os_target.ts | 137 - .../recordingV2/traced_tracing_session.ts | 419 - .../recordingV2/websocket_menu_controller.ts | 74 - .../perfetto/ui/src/common/registry.ts | 58 - .../ui/src/common/registry_unittest.ts | 60 - third_party/perfetto/ui/src/common/schema.ts | 117 - .../perfetto/ui/src/common/schema_unittest.ts | 40 - .../perfetto/ui/src/common/search_data.ts | 28 - .../ui/src/common/selection_observer.ts | 32 - third_party/perfetto/ui/src/common/state.ts | 948 - .../perfetto/ui/src/common/state_unittest.ts | 67 - .../perfetto/ui/src/common/thread_state.ts | 55 - third_party/perfetto/ui/src/common/time.ts | 120 - .../perfetto/ui/src/common/time_unittest.ts | 37 - .../perfetto/ui/src/common/track_data.ts | 23 - .../perfetto/ui/src/common/upload_utils.ts | 121 - .../ui/src/common/upload_utils_unittest.ts | 105 - .../ui/src/common/wasm_engine_proxy.ts | 75 - .../perfetto/ui/src/common/worker_messages.ts | 30 - third_party/perfetto/ui/src/controller/adb.ts | 660 - .../ui/src/controller/adb_base_controller.ts | 138 - .../ui/src/controller/adb_interfaces.ts | 83 - .../ui/src/controller/adb_jsdomtest.ts | 81 - .../adb_record_controller_jsdomtest.ts | 126 - .../ui/src/controller/adb_shell_controller.ts | 188 - .../src/controller/adb_socket_controller.ts | 370 - .../aggregation/aggregation_controller.ts | 184 - .../counter_aggregation_controller.ts | 144 - .../aggregation/cpu_aggregation_controller.ts | 114 - .../cpu_by_process_aggregation_controller.ts | 101 - .../frame_aggregation_controller.ts | 103 - .../slice_aggregation_controller.ts | 114 - .../thread_aggregation_controller.ts | 172 - .../ui/src/controller/app_controller.ts | 55 - .../src/controller/area_selection_handler.ts | 57 - .../area_selection_handler_unittest.ts | 136 - .../perfetto/ui/src/controller/args_parser.ts | 136 - .../chrome_proxy_record_controller.ts | 110 - .../ui/src/controller/consumer_port_types.ts | 69 - .../perfetto/ui/src/controller/controller.ts | 114 - .../ui/src/controller/controller_unittest.ts | 159 - .../src/controller/cpu_profile_controller.ts | 173 - .../src/controller/flamegraph_controller.ts | 507 - .../src/controller/flow_events_controller.ts | 340 - .../ui/src/controller/ftrace_controller.ts | 156 - .../perfetto/ui/src/controller/index.ts | 45 - .../ui/src/controller/loading_manager.ts | 44 - .../ui/src/controller/logs_controller.ts | 322 - .../ui/src/controller/metrics_controller.ts | 60 - .../ui/src/controller/permalink_controller.ts | 223 - .../src/controller/pivot_table_controller.ts | 308 - .../pivot_table_tree_builder_unittest.ts | 43 - .../ui/src/controller/record_config_types.ts | 117 - .../ui/src/controller/record_controller.ts | 427 - .../record_controller_interfaces.ts | 58 - .../controller/record_controller_jsdomtest.ts | 420 - .../ui/src/controller/search_controller.ts | 314 - .../ui/src/controller/selection_controller.ts | 522 - .../ui/src/controller/trace_controller.ts | 936 - .../src/controller/trace_error_controller.ts | 45 - .../ui/src/controller/trace_stream.ts | 160 - .../ui/src/controller/track_controller.ts | 294 - .../ui/src/controller/track_decider.ts | 1888 -- .../perfetto/ui/src/controller/validators.ts | 267 - .../ui/src/controller/validators_unittest.ts | 109 - .../controller/visualised_args_controller.ts | 133 - third_party/perfetto/ui/src/engine/index.ts | 41 - .../perfetto/ui/src/engine/wasm_bridge.ts | 122 - .../ui/src/frontend/aggregation_panel.ts | 141 - .../perfetto/ui/src/frontend/analytics.ts | 130 - .../perfetto/ui/src/frontend/analyze_page.ts | 226 - .../perfetto/ui/src/frontend/anchor.ts | 35 - .../ui/src/frontend/android_bug_tool.ts | 83 - .../perfetto/ui/src/frontend/animation.ts | 53 - .../ui/src/frontend/base_slice_track.ts | 814 - .../src/frontend/base_slice_track_unittest.ts | 115 - .../perfetto/ui/src/frontend/bottom_tab.ts | 326 - .../perfetto/ui/src/frontend/checkerboard.ts | 66 - .../ui/src/frontend/chrome_slice_panel.ts | 525 - .../perfetto/ui/src/frontend/classnames.ts | 25 - .../ui/src/frontend/classnames_unittest.ts | 46 - .../perfetto/ui/src/frontend/clipboard.ts | 64 - .../ui/src/frontend/cookie_consent.ts | 58 - .../perfetto/ui/src/frontend/counter_panel.ts | 60 - .../ui/src/frontend/cpu_profile_panel.ts | 50 - .../perfetto/ui/src/frontend/css_constants.ts | 60 - third_party/perfetto/ui/src/frontend/debug.ts | 36 - .../perfetto/ui/src/frontend/details_panel.ts | 407 - .../ui/src/frontend/download_utils.ts | 32 - .../src/frontend/drag/border_drag_strategy.ts | 43 - .../ui/src/frontend/drag/drag_strategy.ts | 30 - .../src/frontend/drag/inner_drag_strategy.ts | 34 - .../src/frontend/drag/outer_drag_strategy.ts | 30 - .../ui/src/frontend/drag_gesture_handler.ts | 79 - .../perfetto/ui/src/frontend/error_dialog.ts | 400 - .../perfetto/ui/src/frontend/events.ts | 24 - .../ui/src/frontend/file_drop_handler.ts | 64 - .../perfetto/ui/src/frontend/flags_page.ts | 142 - .../perfetto/ui/src/frontend/flamegraph.ts | 447 - .../ui/src/frontend/flamegraph_panel.ts | 343 - .../ui/src/frontend/flamegraph_unittest.ts | 53 - .../ui/src/frontend/flow_events_panel.ts | 207 - .../ui/src/frontend/flow_events_renderer.ts | 325 - .../ui/src/frontend/frontend_local_state.ts | 206 - .../perfetto/ui/src/frontend/ftrace_panel.ts | 224 - .../perfetto/ui/src/frontend/globals.ts | 642 - .../ui/src/frontend/gridline_helper.ts | 183 - .../src/frontend/gridline_helper_unittest.ts | 314 - .../perfetto/ui/src/frontend/help_modal.ts | 191 - .../perfetto/ui/src/frontend/home_page.ts | 61 - .../perfetto/ui/src/frontend/hsluv_cache.ts | 39 - third_party/perfetto/ui/src/frontend/icons.ts | 29 - third_party/perfetto/ui/src/frontend/index.ts | 366 - .../ui/src/frontend/keyboard_event_handler.ts | 285 - .../ui/src/frontend/keyboard_layout_map.ts | 38 - .../ui/src/frontend/legacy_trace_viewer.ts | 160 - .../perfetto/ui/src/frontend/live_reload.ts | 78 - .../perfetto/ui/src/frontend/logs_filters.ts | 179 - .../perfetto/ui/src/frontend/logs_panel.ts | 179 - .../perfetto/ui/src/frontend/metrics_page.ts | 84 - third_party/perfetto/ui/src/frontend/modal.ts | 300 - .../ui/src/frontend/named_slice_track.ts | 85 - .../perfetto/ui/src/frontend/notes_panel.ts | 354 - .../src/frontend/overview_timeline_panel.ts | 223 - third_party/perfetto/ui/src/frontend/pages.ts | 73 - .../ui/src/frontend/pan_and_zoom_handler.ts | 311 - third_party/perfetto/ui/src/frontend/panel.ts | 37 - .../ui/src/frontend/panel_container.ts | 513 - third_party/perfetto/ui/src/frontend/perf.ts | 119 - .../perfetto/ui/src/frontend/perf_unittest.ts | 57 - .../perfetto/ui/src/frontend/pivot_table.ts | 558 - .../frontend/pivot_table_argument_popup.ts | 96 - .../frontend/pivot_table_query_generator.ts | 191 - .../ui/src/frontend/pivot_table_types.ts | 114 - .../perfetto/ui/src/frontend/popup_menu.ts | 188 - .../ui/src/frontend/post_message_handler.ts | 243 - .../perfetto/ui/src/frontend/publish.ts | 205 - .../perfetto/ui/src/frontend/query_history.ts | 161 - .../ui/src/frontend/query_result_tab.ts | 156 - .../perfetto/ui/src/frontend/query_table.ts | 229 - .../perfetto/ui/src/frontend/raf_scheduler.ts | 207 - .../perfetto/ui/src/frontend/rate_limiters.ts | 43 - .../perfetto/ui/src/frontend/record_config.ts | 221 - .../perfetto/ui/src/frontend/record_page.ts | 777 - .../ui/src/frontend/record_page_v2.ts | 565 - .../ui/src/frontend/record_widgets.ts | 434 - .../frontend/recording/advanced_settings.ts | 110 - .../frontend/recording/android_settings.ts | 196 - .../src/frontend/recording/chrome_settings.ts | 132 - .../ui/src/frontend/recording/cpu_settings.ts | 74 - .../ui/src/frontend/recording/gpu_settings.ts | 41 - .../src/frontend/recording/memory_settings.ts | 328 - .../src/frontend/recording/power_settings.ts | 69 - .../recording/recording_multiple_choice.ts | 110 - .../frontend/recording/recording_sections.ts | 22 - .../frontend/recording/recording_settings.ts | 98 - .../frontend/recording/recording_ui_utils.ts | 21 - .../recording/reset_interface_modal.ts | 64 - .../frontend/recording/reset_target_modal.ts | 160 - .../ui/src/frontend/reorderable_cells.ts | 158 - .../perfetto/ui/src/frontend/router.ts | 197 - .../ui/src/frontend/router_unittest.ts | 106 - .../ui/src/frontend/rpc_http_dialog.ts | 153 - .../perfetto/ui/src/frontend/scroll_helper.ts | 163 - .../ui/src/frontend/search_handler.ts | 99 - .../src/frontend/service_worker_controller.ts | 154 - .../perfetto/ui/src/frontend/sidebar.ts | 1030 - third_party/perfetto/ui/src/frontend/slice.ts | 31 - .../ui/src/frontend/slice_details_panel.ts | 181 - .../perfetto/ui/src/frontend/slice_layout.ts | 76 - .../perfetto/ui/src/frontend/slice_panel.ts | 55 - .../perfetto/ui/src/frontend/sql_types.ts | 92 - .../perfetto/ui/src/frontend/sql_utils.ts | 58 - .../ui/src/frontend/sql_utils_unittest.ts | 44 - .../frontend/tables/attribute_modal_holder.ts | 71 - .../perfetto/ui/src/frontend/tables/table.ts | 233 - .../ui/src/frontend/tables/table_showcase.ts | 70 - .../perfetto/ui/src/frontend/task_tracker.ts | 64 - .../ui/src/frontend/task_tracker_unittest.ts | 34 - .../src/frontend/thread_and_process_info.ts | 121 - .../perfetto/ui/src/frontend/thread_state.ts | 208 - .../ui/src/frontend/thread_state_tab.ts | 79 - .../ui/src/frontend/tickmark_panel.ts | 76 - .../ui/src/frontend/time_axis_panel.ts | 55 - .../perfetto/ui/src/frontend/time_scale.ts | 106 - .../ui/src/frontend/time_scale_unittest.ts | 77 - .../ui/src/frontend/time_selection_panel.ts | 206 - .../perfetto/ui/src/frontend/topbar.ts | 268 - .../perfetto/ui/src/frontend/trace_attrs.ts | 33 - .../ui/src/frontend/trace_converter.ts | 116 - .../ui/src/frontend/trace_info_page.ts | 361 - .../ui/src/frontend/trace_url_handler.ts | 239 - third_party/perfetto/ui/src/frontend/track.ts | 212 - .../perfetto/ui/src/frontend/track_cache.ts | 177 - .../ui/src/frontend/track_cache_unittest.ts | 60 - .../ui/src/frontend/track_group_panel.ts | 290 - .../perfetto/ui/src/frontend/track_panel.ts | 448 - .../ui/src/frontend/track_registry.ts | 21 - third_party/perfetto/ui/src/frontend/value.ts | 189 - .../ui/src/frontend/vertical_line_helper.ts | 43 - .../perfetto/ui/src/frontend/viewer_page.ts | 295 - .../ui/src/frontend/widgets/button.ts | 88 - .../ui/src/frontend/widgets/checkbox.ts | 59 - .../ui/src/frontend/widgets/duration.ts | 27 - .../ui/src/frontend/widgets/empty_state.ts | 42 - .../perfetto/ui/src/frontend/widgets/icon.ts | 36 - .../perfetto/ui/src/frontend/widgets/menu.ts | 154 - .../ui/src/frontend/widgets/multiselect.ts | 238 - .../perfetto/ui/src/frontend/widgets/popup.ts | 264 - .../ui/src/frontend/widgets/portal.ts | 101 - .../ui/src/frontend/widgets/select.ts | 31 - .../ui/src/frontend/widgets/spinner.ts | 32 - .../ui/src/frontend/widgets/switch.ts | 51 - .../ui/src/frontend/widgets/text_input.ts | 39 - .../ui/src/frontend/widgets/timestamp.ts | 28 - .../perfetto/ui/src/frontend/widgets/tree.ts | 128 - .../perfetto/ui/src/frontend/widgets/utils.ts | 44 - .../ui/src/frontend/widgets/utils_unittest.ts | 73 - .../perfetto/ui/src/frontend/widgets_page.ts | 590 - .../ui/src/service_worker/service_worker.ts | 242 - .../ui/src/service_worker/tsconfig.json | 15 - .../ui/src/test/diff_viewer/README.md | 18 - .../ui/src/test/diff_viewer/index.html | 32 - .../ui/src/test/diff_viewer/script.js | 136 - .../ui/src/test/perfetto_ui_test_helper.ts | 130 - .../ui/src/test/ui_integrationtest.ts | 322 - .../perfetto/ui/src/traceconv/index.ts | 235 - .../ui/src/tracks/actual_frames/index.ts | 181 - .../ui/src/tracks/android_log/index.ts | 161 - .../ui/src/tracks/async_slices/index.ts | 159 - .../ui/src/tracks/chrome_scroll_jank/index.ts | 141 - .../ui/src/tracks/chrome_slices/index.ts | 461 - .../perfetto/ui/src/tracks/counter/index.ts | 579 - .../perfetto/ui/src/tracks/cpu_freq/index.ts | 485 - .../ui/src/tracks/cpu_profile/index.ts | 248 - .../ui/src/tracks/cpu_slices/index.ts | 467 - .../src/tracks/debug/add_debug_track_menu.ts | 117 - .../ui/src/tracks/debug/details_tab.ts | 121 - .../perfetto/ui/src/tracks/debug/index.ts | 25 - .../ui/src/tracks/debug/slice_track.ts | 141 - .../ui/src/tracks/expected_frames/index.ts | 167 - .../perfetto/ui/src/tracks/ftrace/index.ts | 164 - .../src/tracks/generic_slice_track/index.ts | 55 - .../ui/src/tracks/heap_profile/index.ts | 223 - .../ui/src/tracks/null_track/index.ts | 45 - .../src/tracks/perf_samples_profile/index.ts | 216 - .../ui/src/tracks/process_scheduling/index.ts | 329 - .../ui/src/tracks/process_summary/index.ts | 227 - .../ui/src/tracks/thread_state/index.ts | 301 - .../ui/src/tracks/visualised_args/index.ts | 76 - third_party/perfetto/ui/tsconfig.base.json | 28 - third_party/perfetto/ui/tsconfig.json | 22 - 440 files changed, 85642 deletions(-) delete mode 100644 third_party/perfetto/ui/.clang-format delete mode 100644 third_party/perfetto/ui/.eslintrc.js delete mode 100644 third_party/perfetto/ui/.gitignore delete mode 100644 third_party/perfetto/ui/BUILD.gn delete mode 100644 third_party/perfetto/ui/OWNERS delete mode 100644 third_party/perfetto/ui/PRESUBMIT.py delete mode 100644 third_party/perfetto/ui/README.md delete mode 100755 third_party/perfetto/ui/build delete mode 100644 third_party/perfetto/ui/build.js delete mode 100644 third_party/perfetto/ui/config/.gitignore delete mode 100644 third_party/perfetto/ui/config/gn_deprecation_banner.txt delete mode 100644 third_party/perfetto/ui/config/integrationtest_env.js delete mode 100644 third_party/perfetto/ui/config/integrationtest_setup.js delete mode 100644 third_party/perfetto/ui/config/integrationtest_teardown.js delete mode 100644 third_party/perfetto/ui/config/jest.integrationtest.config.js delete mode 100644 third_party/perfetto/ui/config/jest.unittest.config.js delete mode 100644 third_party/perfetto/ui/config/rollup.config.js delete mode 100755 third_party/perfetto/ui/eslint delete mode 100755 third_party/perfetto/ui/eslint-all delete mode 100755 third_party/perfetto/ui/node delete mode 100755 third_party/perfetto/ui/npm delete mode 100644 third_party/perfetto/ui/package-lock.json delete mode 100644 third_party/perfetto/ui/package.json delete mode 100644 third_party/perfetto/ui/release/OWNERS delete mode 100755 third_party/perfetto/ui/release/build_all_channels.py delete mode 100755 third_party/perfetto/ui/release/builder_entrypoint.sh delete mode 100644 third_party/perfetto/ui/release/channels.json delete mode 100644 third_party/perfetto/ui/release/roll_branch.py delete mode 100755 third_party/perfetto/ui/release/roll_canary.sh delete mode 100755 third_party/perfetto/ui/release/roll_stable.sh delete mode 100755 third_party/perfetto/ui/run-all-tests delete mode 100755 third_party/perfetto/ui/run-dev-server delete mode 100755 third_party/perfetto/ui/run-integrationtests delete mode 100755 third_party/perfetto/ui/run-unittests delete mode 100644 third_party/perfetto/ui/src/assets/.gitignore delete mode 100644 third_party/perfetto/ui/src/assets/analyze_page.scss delete mode 100644 third_party/perfetto/ui/src/assets/brand.png delete mode 100644 third_party/perfetto/ui/src/assets/common.scss delete mode 100644 third_party/perfetto/ui/src/assets/details.scss delete mode 100644 third_party/perfetto/ui/src/assets/favicon.png delete mode 100644 third_party/perfetto/ui/src/assets/flags_page.scss delete mode 100644 third_party/perfetto/ui/src/assets/fonts.scss delete mode 100644 third_party/perfetto/ui/src/assets/hiring_banner.scss delete mode 100644 third_party/perfetto/ui/src/assets/home_page.scss delete mode 100644 third_party/perfetto/ui/src/assets/index.html delete mode 100644 third_party/perfetto/ui/src/assets/logo-128.png delete mode 100644 third_party/perfetto/ui/src/assets/logo-3d.png delete mode 100644 third_party/perfetto/ui/src/assets/metrics_page.scss delete mode 100644 third_party/perfetto/ui/src/assets/modal.scss delete mode 100644 third_party/perfetto/ui/src/assets/perfetto.scss delete mode 100644 third_party/perfetto/ui/src/assets/rec_atrace.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_battery_counters.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_board_voltage.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_cpu_coarse.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_cpu_fine.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_cpu_freq.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_cpu_voltage.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_frame_timeline.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_ftrace.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_gpu_mem_total.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_java_heap_dump.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_lmk.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_logcat.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_long_trace.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_mem_hifreq.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_meminfo.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_native_heap_profiler.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_one_shot.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_profiling.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_ps_stats.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_ring_buf.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_syscalls.png delete mode 100644 third_party/perfetto/ui/src/assets/rec_vmstat.png delete mode 100644 third_party/perfetto/ui/src/assets/record.scss delete mode 100644 third_party/perfetto/ui/src/assets/scheduling_latency.png delete mode 100644 third_party/perfetto/ui/src/assets/sidebar.scss delete mode 100644 third_party/perfetto/ui/src/assets/topbar.scss delete mode 100644 third_party/perfetto/ui/src/assets/trace_info_page.scss delete mode 100644 third_party/perfetto/ui/src/assets/typefaces.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/anchor.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/button.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/checkbox.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/empty_state.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/menu.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/multiselect.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/popup.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/select.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/spinner.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/switch.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/text_input.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/theme.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets/tree.scss delete mode 100644 third_party/perfetto/ui/src/assets/widgets_page.scss delete mode 100644 third_party/perfetto/ui/src/base/array_utils.ts delete mode 100644 third_party/perfetto/ui/src/base/array_utils_unittest.ts delete mode 100644 third_party/perfetto/ui/src/base/binary_search.ts delete mode 100644 third_party/perfetto/ui/src/base/binary_search_unittest.ts delete mode 100644 third_party/perfetto/ui/src/base/comparison_utils.ts delete mode 100644 third_party/perfetto/ui/src/base/deferred.ts delete mode 100644 third_party/perfetto/ui/src/base/deferred_unittest.ts delete mode 100644 third_party/perfetto/ui/src/base/generic_set.ts delete mode 100644 third_party/perfetto/ui/src/base/http_utils.ts delete mode 100644 third_party/perfetto/ui/src/base/logging.ts delete mode 100644 third_party/perfetto/ui/src/base/math_utils.ts delete mode 100644 third_party/perfetto/ui/src/base/math_utils_unittest.ts delete mode 100644 third_party/perfetto/ui/src/base/set_utils.ts delete mode 100644 third_party/perfetto/ui/src/base/set_utils_unittest.ts delete mode 100644 third_party/perfetto/ui/src/base/string_utils.ts delete mode 100644 third_party/perfetto/ui/src/base/string_utils_unittest.ts delete mode 100644 third_party/perfetto/ui/src/base/trace_config_utils.ts delete mode 100644 third_party/perfetto/ui/src/base/utils/index-browser.js delete mode 100644 third_party/perfetto/ui/src/base/utils/index.js delete mode 100644 third_party/perfetto/ui/src/base/utils/package.json delete mode 100644 third_party/perfetto/ui/src/chrome_extension/chrome_tracing_controller.ts delete mode 100644 third_party/perfetto/ui/src/chrome_extension/devtools_socket.ts delete mode 100644 third_party/perfetto/ui/src/chrome_extension/index.ts delete mode 100644 third_party/perfetto/ui/src/chrome_extension/manifest.json delete mode 100644 third_party/perfetto/ui/src/common/actions.ts delete mode 100644 third_party/perfetto/ui/src/common/actions_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/aggregation_data.ts delete mode 100644 third_party/perfetto/ui/src/common/arg_types.ts delete mode 100644 third_party/perfetto/ui/src/common/array_buffer_builder.ts delete mode 100644 third_party/perfetto/ui/src/common/cache_manager.ts delete mode 100644 third_party/perfetto/ui/src/common/canvas_utils.ts delete mode 100644 third_party/perfetto/ui/src/common/canvas_utils_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/channels.ts delete mode 100644 third_party/perfetto/ui/src/common/colorizer.ts delete mode 100644 third_party/perfetto/ui/src/common/colorizer_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/comparator_builder.ts delete mode 100644 third_party/perfetto/ui/src/common/constants.ts delete mode 100644 third_party/perfetto/ui/src/common/conversion_jobs.ts delete mode 100644 third_party/perfetto/ui/src/common/dragndrop_logic.ts delete mode 100644 third_party/perfetto/ui/src/common/dragndrop_logic_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/empty_state.ts delete mode 100644 third_party/perfetto/ui/src/common/engine.ts delete mode 100644 third_party/perfetto/ui/src/common/errors.ts delete mode 100644 third_party/perfetto/ui/src/common/event_set.ts delete mode 100644 third_party/perfetto/ui/src/common/event_set_nocompile_test.ts delete mode 100644 third_party/perfetto/ui/src/common/feature_flags.ts delete mode 100644 third_party/perfetto/ui/src/common/feature_flags_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/flamegraph_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/flamegraph_util.ts delete mode 100644 third_party/perfetto/ui/src/common/hash.ts delete mode 100644 third_party/perfetto/ui/src/common/http_rpc_engine.ts delete mode 100644 third_party/perfetto/ui/src/common/immer_init.ts delete mode 100644 third_party/perfetto/ui/src/common/logs.ts delete mode 100644 third_party/perfetto/ui/src/common/metatracing.ts delete mode 100644 third_party/perfetto/ui/src/common/metric_data.ts delete mode 100644 third_party/perfetto/ui/src/common/plugin_api.ts delete mode 100644 third_party/perfetto/ui/src/common/plugins.ts delete mode 100644 third_party/perfetto/ui/src/common/plugins_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/proto_ring_buffer.ts delete mode 100644 third_party/perfetto/ui/src/common/proto_ring_buffer_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/protos.ts delete mode 100644 third_party/perfetto/ui/src/common/protos_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/queries.ts delete mode 100644 third_party/perfetto/ui/src/common/query_result.ts delete mode 100644 third_party/perfetto/ui/src/common/query_result_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/query_utils.ts delete mode 100644 third_party/perfetto/ui/src/common/query_utils_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/adb_connection_impl.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/adb_connection_over_websocket.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/adb_connection_over_webusb.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/adb_file_handler.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/auth/adb_auth.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/auth/adb_key_manager.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/auth/credentials_interfaces.d.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/chrome_traced_tracing_session.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/host_os_byte_stream.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/recording_config_utils.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/recording_error_handling.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/recording_interfaces_v2.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/recording_page_controller.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/recording_utils.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/target_factories/android_websocket_target_factory.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/target_factories/android_websocket_target_factory_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/target_factories/android_webusb_target_factory.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/target_factories/index.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/target_factories/virtual_target_factory.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/target_factory_registry.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/targets/android_target.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/targets/android_virtual_target.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/targets/android_websocket_target.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/targets/android_webusb_target.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/targets/chrome_target.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/targets/host_os_target.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/traced_tracing_session.ts delete mode 100644 third_party/perfetto/ui/src/common/recordingV2/websocket_menu_controller.ts delete mode 100644 third_party/perfetto/ui/src/common/registry.ts delete mode 100644 third_party/perfetto/ui/src/common/registry_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/schema.ts delete mode 100644 third_party/perfetto/ui/src/common/schema_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/search_data.ts delete mode 100644 third_party/perfetto/ui/src/common/selection_observer.ts delete mode 100644 third_party/perfetto/ui/src/common/state.ts delete mode 100644 third_party/perfetto/ui/src/common/state_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/thread_state.ts delete mode 100644 third_party/perfetto/ui/src/common/time.ts delete mode 100644 third_party/perfetto/ui/src/common/time_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/track_data.ts delete mode 100644 third_party/perfetto/ui/src/common/upload_utils.ts delete mode 100644 third_party/perfetto/ui/src/common/upload_utils_unittest.ts delete mode 100644 third_party/perfetto/ui/src/common/wasm_engine_proxy.ts delete mode 100644 third_party/perfetto/ui/src/common/worker_messages.ts delete mode 100644 third_party/perfetto/ui/src/controller/adb.ts delete mode 100644 third_party/perfetto/ui/src/controller/adb_base_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/adb_interfaces.ts delete mode 100644 third_party/perfetto/ui/src/controller/adb_jsdomtest.ts delete mode 100644 third_party/perfetto/ui/src/controller/adb_record_controller_jsdomtest.ts delete mode 100644 third_party/perfetto/ui/src/controller/adb_shell_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/adb_socket_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/aggregation/aggregation_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/aggregation/counter_aggregation_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/aggregation/cpu_aggregation_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/aggregation/frame_aggregation_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/aggregation/slice_aggregation_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/aggregation/thread_aggregation_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/app_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/area_selection_handler.ts delete mode 100644 third_party/perfetto/ui/src/controller/area_selection_handler_unittest.ts delete mode 100644 third_party/perfetto/ui/src/controller/args_parser.ts delete mode 100644 third_party/perfetto/ui/src/controller/chrome_proxy_record_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/consumer_port_types.ts delete mode 100644 third_party/perfetto/ui/src/controller/controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/controller_unittest.ts delete mode 100644 third_party/perfetto/ui/src/controller/cpu_profile_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/flamegraph_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/flow_events_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/ftrace_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/index.ts delete mode 100644 third_party/perfetto/ui/src/controller/loading_manager.ts delete mode 100644 third_party/perfetto/ui/src/controller/logs_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/metrics_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/permalink_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/pivot_table_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/pivot_table_tree_builder_unittest.ts delete mode 100644 third_party/perfetto/ui/src/controller/record_config_types.ts delete mode 100644 third_party/perfetto/ui/src/controller/record_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/record_controller_interfaces.ts delete mode 100644 third_party/perfetto/ui/src/controller/record_controller_jsdomtest.ts delete mode 100644 third_party/perfetto/ui/src/controller/search_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/selection_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/trace_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/trace_error_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/trace_stream.ts delete mode 100644 third_party/perfetto/ui/src/controller/track_controller.ts delete mode 100644 third_party/perfetto/ui/src/controller/track_decider.ts delete mode 100644 third_party/perfetto/ui/src/controller/validators.ts delete mode 100644 third_party/perfetto/ui/src/controller/validators_unittest.ts delete mode 100644 third_party/perfetto/ui/src/controller/visualised_args_controller.ts delete mode 100644 third_party/perfetto/ui/src/engine/index.ts delete mode 100644 third_party/perfetto/ui/src/engine/wasm_bridge.ts delete mode 100644 third_party/perfetto/ui/src/frontend/aggregation_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/analytics.ts delete mode 100644 third_party/perfetto/ui/src/frontend/analyze_page.ts delete mode 100644 third_party/perfetto/ui/src/frontend/anchor.ts delete mode 100644 third_party/perfetto/ui/src/frontend/android_bug_tool.ts delete mode 100644 third_party/perfetto/ui/src/frontend/animation.ts delete mode 100644 third_party/perfetto/ui/src/frontend/base_slice_track.ts delete mode 100644 third_party/perfetto/ui/src/frontend/base_slice_track_unittest.ts delete mode 100644 third_party/perfetto/ui/src/frontend/bottom_tab.ts delete mode 100644 third_party/perfetto/ui/src/frontend/checkerboard.ts delete mode 100644 third_party/perfetto/ui/src/frontend/chrome_slice_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/classnames.ts delete mode 100644 third_party/perfetto/ui/src/frontend/classnames_unittest.ts delete mode 100644 third_party/perfetto/ui/src/frontend/clipboard.ts delete mode 100644 third_party/perfetto/ui/src/frontend/cookie_consent.ts delete mode 100644 third_party/perfetto/ui/src/frontend/counter_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/cpu_profile_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/css_constants.ts delete mode 100644 third_party/perfetto/ui/src/frontend/debug.ts delete mode 100644 third_party/perfetto/ui/src/frontend/details_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/download_utils.ts delete mode 100644 third_party/perfetto/ui/src/frontend/drag/border_drag_strategy.ts delete mode 100644 third_party/perfetto/ui/src/frontend/drag/drag_strategy.ts delete mode 100644 third_party/perfetto/ui/src/frontend/drag/inner_drag_strategy.ts delete mode 100644 third_party/perfetto/ui/src/frontend/drag/outer_drag_strategy.ts delete mode 100644 third_party/perfetto/ui/src/frontend/drag_gesture_handler.ts delete mode 100644 third_party/perfetto/ui/src/frontend/error_dialog.ts delete mode 100644 third_party/perfetto/ui/src/frontend/events.ts delete mode 100644 third_party/perfetto/ui/src/frontend/file_drop_handler.ts delete mode 100644 third_party/perfetto/ui/src/frontend/flags_page.ts delete mode 100644 third_party/perfetto/ui/src/frontend/flamegraph.ts delete mode 100644 third_party/perfetto/ui/src/frontend/flamegraph_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/flamegraph_unittest.ts delete mode 100644 third_party/perfetto/ui/src/frontend/flow_events_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/flow_events_renderer.ts delete mode 100644 third_party/perfetto/ui/src/frontend/frontend_local_state.ts delete mode 100644 third_party/perfetto/ui/src/frontend/ftrace_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/globals.ts delete mode 100644 third_party/perfetto/ui/src/frontend/gridline_helper.ts delete mode 100644 third_party/perfetto/ui/src/frontend/gridline_helper_unittest.ts delete mode 100644 third_party/perfetto/ui/src/frontend/help_modal.ts delete mode 100644 third_party/perfetto/ui/src/frontend/home_page.ts delete mode 100644 third_party/perfetto/ui/src/frontend/hsluv_cache.ts delete mode 100644 third_party/perfetto/ui/src/frontend/icons.ts delete mode 100644 third_party/perfetto/ui/src/frontend/index.ts delete mode 100644 third_party/perfetto/ui/src/frontend/keyboard_event_handler.ts delete mode 100644 third_party/perfetto/ui/src/frontend/keyboard_layout_map.ts delete mode 100644 third_party/perfetto/ui/src/frontend/legacy_trace_viewer.ts delete mode 100644 third_party/perfetto/ui/src/frontend/live_reload.ts delete mode 100644 third_party/perfetto/ui/src/frontend/logs_filters.ts delete mode 100644 third_party/perfetto/ui/src/frontend/logs_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/metrics_page.ts delete mode 100644 third_party/perfetto/ui/src/frontend/modal.ts delete mode 100644 third_party/perfetto/ui/src/frontend/named_slice_track.ts delete mode 100644 third_party/perfetto/ui/src/frontend/notes_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/overview_timeline_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/pages.ts delete mode 100644 third_party/perfetto/ui/src/frontend/pan_and_zoom_handler.ts delete mode 100644 third_party/perfetto/ui/src/frontend/panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/panel_container.ts delete mode 100644 third_party/perfetto/ui/src/frontend/perf.ts delete mode 100644 third_party/perfetto/ui/src/frontend/perf_unittest.ts delete mode 100644 third_party/perfetto/ui/src/frontend/pivot_table.ts delete mode 100644 third_party/perfetto/ui/src/frontend/pivot_table_argument_popup.ts delete mode 100644 third_party/perfetto/ui/src/frontend/pivot_table_query_generator.ts delete mode 100644 third_party/perfetto/ui/src/frontend/pivot_table_types.ts delete mode 100644 third_party/perfetto/ui/src/frontend/popup_menu.ts delete mode 100644 third_party/perfetto/ui/src/frontend/post_message_handler.ts delete mode 100644 third_party/perfetto/ui/src/frontend/publish.ts delete mode 100644 third_party/perfetto/ui/src/frontend/query_history.ts delete mode 100644 third_party/perfetto/ui/src/frontend/query_result_tab.ts delete mode 100644 third_party/perfetto/ui/src/frontend/query_table.ts delete mode 100644 third_party/perfetto/ui/src/frontend/raf_scheduler.ts delete mode 100644 third_party/perfetto/ui/src/frontend/rate_limiters.ts delete mode 100644 third_party/perfetto/ui/src/frontend/record_config.ts delete mode 100644 third_party/perfetto/ui/src/frontend/record_page.ts delete mode 100644 third_party/perfetto/ui/src/frontend/record_page_v2.ts delete mode 100644 third_party/perfetto/ui/src/frontend/record_widgets.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/advanced_settings.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/android_settings.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/chrome_settings.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/cpu_settings.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/gpu_settings.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/memory_settings.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/power_settings.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/recording_multiple_choice.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/recording_sections.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/recording_settings.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/recording_ui_utils.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/reset_interface_modal.ts delete mode 100644 third_party/perfetto/ui/src/frontend/recording/reset_target_modal.ts delete mode 100644 third_party/perfetto/ui/src/frontend/reorderable_cells.ts delete mode 100644 third_party/perfetto/ui/src/frontend/router.ts delete mode 100644 third_party/perfetto/ui/src/frontend/router_unittest.ts delete mode 100644 third_party/perfetto/ui/src/frontend/rpc_http_dialog.ts delete mode 100644 third_party/perfetto/ui/src/frontend/scroll_helper.ts delete mode 100644 third_party/perfetto/ui/src/frontend/search_handler.ts delete mode 100644 third_party/perfetto/ui/src/frontend/service_worker_controller.ts delete mode 100644 third_party/perfetto/ui/src/frontend/sidebar.ts delete mode 100644 third_party/perfetto/ui/src/frontend/slice.ts delete mode 100644 third_party/perfetto/ui/src/frontend/slice_details_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/slice_layout.ts delete mode 100644 third_party/perfetto/ui/src/frontend/slice_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/sql_types.ts delete mode 100644 third_party/perfetto/ui/src/frontend/sql_utils.ts delete mode 100644 third_party/perfetto/ui/src/frontend/sql_utils_unittest.ts delete mode 100644 third_party/perfetto/ui/src/frontend/tables/attribute_modal_holder.ts delete mode 100644 third_party/perfetto/ui/src/frontend/tables/table.ts delete mode 100644 third_party/perfetto/ui/src/frontend/tables/table_showcase.ts delete mode 100644 third_party/perfetto/ui/src/frontend/task_tracker.ts delete mode 100644 third_party/perfetto/ui/src/frontend/task_tracker_unittest.ts delete mode 100644 third_party/perfetto/ui/src/frontend/thread_and_process_info.ts delete mode 100644 third_party/perfetto/ui/src/frontend/thread_state.ts delete mode 100644 third_party/perfetto/ui/src/frontend/thread_state_tab.ts delete mode 100644 third_party/perfetto/ui/src/frontend/tickmark_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/time_axis_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/time_scale.ts delete mode 100644 third_party/perfetto/ui/src/frontend/time_scale_unittest.ts delete mode 100644 third_party/perfetto/ui/src/frontend/time_selection_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/topbar.ts delete mode 100644 third_party/perfetto/ui/src/frontend/trace_attrs.ts delete mode 100644 third_party/perfetto/ui/src/frontend/trace_converter.ts delete mode 100644 third_party/perfetto/ui/src/frontend/trace_info_page.ts delete mode 100644 third_party/perfetto/ui/src/frontend/trace_url_handler.ts delete mode 100644 third_party/perfetto/ui/src/frontend/track.ts delete mode 100644 third_party/perfetto/ui/src/frontend/track_cache.ts delete mode 100644 third_party/perfetto/ui/src/frontend/track_cache_unittest.ts delete mode 100644 third_party/perfetto/ui/src/frontend/track_group_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/track_panel.ts delete mode 100644 third_party/perfetto/ui/src/frontend/track_registry.ts delete mode 100644 third_party/perfetto/ui/src/frontend/value.ts delete mode 100644 third_party/perfetto/ui/src/frontend/vertical_line_helper.ts delete mode 100644 third_party/perfetto/ui/src/frontend/viewer_page.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/button.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/checkbox.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/duration.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/empty_state.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/icon.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/menu.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/multiselect.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/popup.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/portal.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/select.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/spinner.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/switch.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/text_input.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/timestamp.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/tree.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/utils.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets/utils_unittest.ts delete mode 100644 third_party/perfetto/ui/src/frontend/widgets_page.ts delete mode 100644 third_party/perfetto/ui/src/service_worker/service_worker.ts delete mode 100644 third_party/perfetto/ui/src/service_worker/tsconfig.json delete mode 100644 third_party/perfetto/ui/src/test/diff_viewer/README.md delete mode 100644 third_party/perfetto/ui/src/test/diff_viewer/index.html delete mode 100644 third_party/perfetto/ui/src/test/diff_viewer/script.js delete mode 100644 third_party/perfetto/ui/src/test/perfetto_ui_test_helper.ts delete mode 100644 third_party/perfetto/ui/src/test/ui_integrationtest.ts delete mode 100644 third_party/perfetto/ui/src/traceconv/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/actual_frames/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/android_log/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/async_slices/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/chrome_scroll_jank/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/chrome_slices/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/counter/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/cpu_freq/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/cpu_profile/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/cpu_slices/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/debug/add_debug_track_menu.ts delete mode 100644 third_party/perfetto/ui/src/tracks/debug/details_tab.ts delete mode 100644 third_party/perfetto/ui/src/tracks/debug/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/debug/slice_track.ts delete mode 100644 third_party/perfetto/ui/src/tracks/expected_frames/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/ftrace/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/generic_slice_track/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/heap_profile/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/null_track/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/perf_samples_profile/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/process_scheduling/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/process_summary/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/thread_state/index.ts delete mode 100644 third_party/perfetto/ui/src/tracks/visualised_args/index.ts delete mode 100644 third_party/perfetto/ui/tsconfig.base.json delete mode 100644 third_party/perfetto/ui/tsconfig.json diff --git a/third_party/perfetto/ui/.clang-format b/third_party/perfetto/ui/.clang-format deleted file mode 100644 index 265b56290800..000000000000 --- a/third_party/perfetto/ui/.clang-format +++ /dev/null @@ -1,5 +0,0 @@ -Language: JavaScript -BasedOnStyle: Google -ColumnLimit: 80 -BinPackArguments: false -JavaScriptWrapImports: true \ No newline at end of file diff --git a/third_party/perfetto/ui/.eslintrc.js b/third_party/perfetto/ui/.eslintrc.js deleted file mode 100644 index b890b89d55c4..000000000000 --- a/third_party/perfetto/ui/.eslintrc.js +++ /dev/null @@ -1,71 +0,0 @@ -module.exports = { - 'env': { - 'browser': true, - 'es2021': true, - 'node': true, - }, - 'extends': [ - 'google', - ], - 'parser': '@typescript-eslint/parser', - 'parserOptions': { - 'ecmaVersion': 'latest', - 'sourceType': 'module', - }, - 'plugins': [ - '@typescript-eslint', - ], - 'rules': { - // We don't want to enforce jsdoc everywhere: - 'require-jsdoc': 'off', - - // Max line length is 80 with 2 space tabs. This must match the - // ui/.clang-format definition: - 'max-len': [ - 'error', - { - 'code': 80, - 'tabWidth': 2, - 'ignoreUrls': true, - 'ignoreTemplateLiterals': true, - 'ignoreStrings': true, - }, - ], - - // Indentation handled by clang-format --js: - 'indent': 'off', - - // clang-format --js formats EOL comments after (e.g.) an if like: - // if (foo) { // insightful comment - // with two spaces between the slash and the brace. Turn - // ignoreEOLComments on to allow that. We still want - // no-multi-spaces turned on in general as it fixes issues like: - // if (a === b) - 'no-multi-spaces': ['error', {ignoreEOLComments: true}], - - // Default no-unused-vars doesn't understand TypeScript enums. See: - // https://github.com/typescript-eslint/typescript-eslint/issues/2621 - 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': - ['error', {'argsIgnorePattern': '^_.*', 'varsIgnorePattern': '^_.*'}], - - // new Array() is banned (use [] instead) but new Array() is - // allowed since it can be clearer to put the type by the - // construtor. - 'no-array-constructor': 'off', - '@typescript-eslint/no-array-constructor': ['error'], - - // Rest parameters are not equivalent to 'arguments'. - // Rest parameters are arrays: https://developer.mozilla.org/en-US/docs/Web/ - // JavaScript/Reference/Functions/rest_parameters - // 'arguments' are objects: https://developer.mozilla.org/en-US/docs/Web/ - // JavaScript/Reference/Functions/arguments - 'prefer-rest-params': 'off', - - // We have a lot normal functions which are capitalised. - // TODO(hjd): Switch these to be lowercase and remove capIsNew. - // There are also some properties like: foo.factory these should - // stay. - 'new-cap': ['error', {'capIsNew': false, 'properties': false}], - }, -}; diff --git a/third_party/perfetto/ui/.gitignore b/third_party/perfetto/ui/.gitignore deleted file mode 100644 index b4e1aa56dcac..000000000000 --- a/third_party/perfetto/ui/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/node_modules/ -/out -/src/gen diff --git a/third_party/perfetto/ui/BUILD.gn b/third_party/perfetto/ui/BUILD.gn deleted file mode 100644 index 6f7e69f304dd..000000000000 --- a/third_party/perfetto/ui/BUILD.gn +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import("../gn/perfetto.gni") - -# Prevent that this file is accidentally included in embedder builds. -assert(enable_perfetto_ui) - -nodejs_bin = rebase_path("//tools/node", root_build_dir) - -group("ui") { - deps = [ - ":ui_build($host_toolchain)", - "//src/trace_processor:trace_processor.wasm($wasm_toolchain)", - "//src/traceconv:traceconv.wasm($wasm_toolchain)", - ] -} - -action("deprecation_warning") { - script = "../gn/standalone/build_tool_wrapper.py" - outputs = [ "$target_out_dir/never_written_always_execute_rule-2.stamp" ] - inputs = [] - args = [ - "cat", - rebase_path("config/gn_deprecation_banner.txt", root_build_dir), - ] -} - -action("ui_build") { - deps = [ ":deprecation_warning" ] - script = "../gn/standalone/build_tool_wrapper.py" - outputs = [ "$target_out_dir/never_written_always_execute_rule.stamp" ] - inputs = [ - "//tools/node", - "build.js", - ] - args = [ - nodejs_bin, - rebase_path("build.js", root_build_dir), - "--out", - ".", - ] -} diff --git a/third_party/perfetto/ui/OWNERS b/third_party/perfetto/ui/OWNERS deleted file mode 100644 index 7b9c12ba0ef9..000000000000 --- a/third_party/perfetto/ui/OWNERS +++ /dev/null @@ -1,8 +0,0 @@ -hjd@google.com -primiano@google.com - -# Chrome-related bits (but also good escalations for many other UI changes). -ddrone@google.com -dproy@google.com -eseckler@google.com -nuskos@google.com diff --git a/third_party/perfetto/ui/PRESUBMIT.py b/third_party/perfetto/ui/PRESUBMIT.py deleted file mode 100644 index 26cf3bc5b6ba..000000000000 --- a/third_party/perfetto/ui/PRESUBMIT.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import print_function -import time -import subprocess -from os.path import relpath - - -USE_PYTHON3 = True - - -def RunAndReportIfLong(func, *args, **kargs): - start = time.time() - results = func(*args, **kargs) - end = time.time() - limit = 0.5 # seconds - name = func.__name__ - runtime = end - start - if runtime > limit: - print("{} took >{:.2}s ({:.2}s)".format(name, limit, runtime)) - return results - - -def CheckChange(input, output): - results = [] - results += RunAndReportIfLong(CheckEslint, input, output) - return results - - -def CheckChangeOnUpload(input_api, output_api): - return CheckChange(input_api, output_api) - - -def CheckChangeOnCommit(input_api, output_api): - return CheckChange(input_api, output_api) - - -def CheckEslint(input_api, output_api): - path = input_api.os_path - ui_path = input_api.PresubmitLocalPath() - node = path.join(ui_path, 'node') - lint_path = path.join(ui_path, 'node_modules', '.bin', 'eslint') - - if not path.exists(lint_path): - repo_root = input_api.change.RepositoryRoot() - install_path = path.join(repo_root, 'tools', 'install-build-deps') - return [ - output_api.PresubmitError( - f"eslint not found. Please first run\n $ {install_path} --ui") - ] - - def file_filter(x): - return input_api.FilterSourceFile( - x, files_to_check=[r'.*\.ts$', r'.*\.js$']) - - files = input_api.AffectedSourceFiles(file_filter) - - if not files: - return [] - paths = [f.AbsoluteLocalPath() for f in files] - - cmd = [node, lint_path] + paths - if subprocess.call(cmd): - s = ' '.join(cmd) - return [output_api.PresubmitError(f"eslint errors. Run: $ {s}")] - return [] diff --git a/third_party/perfetto/ui/README.md b/third_party/perfetto/ui/README.md deleted file mode 100644 index e64d31c93347..000000000000 --- a/third_party/perfetto/ui/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Perfetto UI - -## Quick Start - -```bash -$ git clone https://android.googlesource.com/platform/external/perfetto/ -$ cd perfetto - -# Will build into ./out/ui by default. Can be changed with --out path/ -# The final bundle will be available at ./ui/out/dist/. -# The build script creates a symlink from ./ui/out to $OUT_PATH/ui/. -ui/build - -# This will automatically build the UI. There is no need to manually run -# ui/build before running ui/run-dev-server. -ui/run-dev-server -``` - -Then navigate to `http://localhost:10000`. - -See also https://perfetto.dev/docs/contributing/build-instructions#ui-development - -## Unit tests - -```bash -ui/run-unittests # Add --watch to run them in watch mode. -``` - -## Integration tests (browser screenshot difftests) - -```bash -run-integrationtests -``` - -To rebaseline screenshots after a UI change - -```bash -ui/run-integrationtests --rebaseline - -tools/test_data upload - -git add -A - -git commit -``` - -See also https://perfetto.dev/docs/contributing/testing#ui-pixel-diff-tests diff --git a/third_party/perfetto/ui/build b/third_party/perfetto/ui/build deleted file mode 100755 index 8045f2bdb83e..000000000000 --- a/third_party/perfetto/ui/build +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# Copyright (C) 2021 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -UI_DIR="$(cd -P ${BASH_SOURCE[0]%/*}; pwd)" - -$UI_DIR/node $UI_DIR/build.js "$@" diff --git a/third_party/perfetto/ui/build.js b/third_party/perfetto/ui/build.js deleted file mode 100644 index eeca3bc5c6ca..000000000000 --- a/third_party/perfetto/ui/build.js +++ /dev/null @@ -1,804 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - - -// This script takes care of: -// - The build process for the whole UI and the chrome extension. -// - The HTTP dev-server with live-reload capabilities. -// The reason why this is a hand-rolled script rather than a conventional build -// system is keeping incremental build fast and maintaining the set of -// dependencies contained. -// The only way to keep incremental build fast (i.e. O(seconds) for the -// edit-one-line -> reload html cycles) is to run both the TypeScript compiler -// and the rollup bundler in --watch mode. Any other attempt, leads to O(10s) -// incremental-build times. -// This script allows mixing build tools that support --watch mode (tsc and -// rollup) and auto-triggering-on-file-change rules via node-watch. -// When invoked without any argument (e.g., for production builds), this script -// just runs all the build tasks serially. It doesn't to do any mtime-based -// check, it always re-runs all the tasks. -// When invoked with --watch, it mounts a pipeline of tasks based on node-watch -// and runs them together with tsc --watch and rollup --watch. -// The output directory structure is carefully crafted so that any change to UI -// sources causes cascading triggers of the next steps. -// The overall build graph looks as follows: -// +----------------+ +-----------------------------+ -// | protos/*.proto |----->| pbjs out/tsc/gen/protos.js |--+ -// +----------------+ +-----------------------------+ | -// +-----------------------------+ | -// | pbts out/tsc/gen/protos.d.ts|<-+ -// +-----------------------------+ -// | -// V +-------------------------+ -// +---------+ +-----+ | out/tsc/frontend/*.js | -// | ui/*.ts |------------->| tsc |-> +-------------------------+ +--------+ -// +---------+ +-----+ | out/tsc/controller/*.js |-->| rollup | -// ^ +-------------------------+ +--------+ -// +------------+ | out/tsc/engine/*.js | | -// +-----------+ |*.wasm.js | +-------------------------+ | -// |ninja *.cc |->|*.wasm.d.ts | | -// +-----------+ |*.wasm |-----------------+ | -// +------------+ | | -// V V -// +-----------+ +------+ +------------------------------------------------+ -// | ui/*.scss |->| scss |--->| Final out/dist/ dir | -// +-----------+ +------+ +------------------------------------------------+ -// +----------------------+ | +----------+ +---------+ +--------------------+| -// | src/assets/*.png | | | assets/ | |*.wasm.js| | frontend_bundle.js || -// +----------------------+ | | *.css | |*.wasm | +--------------------+| -// | buildtools/typefaces |-->| | *.png | +---------+ | engine_bundle.js || -// +----------------------+ | | *.woff2 | +--------------------+| -// | buildtools/legacy_tv | | | tv.html | |traceconv_bundle.js || -// +----------------------+ | +----------+ +--------------------+| -// +------------------------------------------------+ - -const argparse = require('argparse'); -const childProcess = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const http = require('http'); -const path = require('path'); -const fswatch = require('node-watch'); // Like fs.watch(), but works on Linux. -const pjoin = path.join; - -const ROOT_DIR = path.dirname(__dirname); // The repo root. -const VERSION_SCRIPT = pjoin(ROOT_DIR, 'tools/write_version_header.py'); -const GEN_IMPORTS_SCRIPT = pjoin(ROOT_DIR, 'tools/gen_ui_imports'); - -const cfg = { - watch: false, - verbose: false, - debug: false, - startHttpServer: false, - httpServerListenHost: '127.0.0.1', - httpServerListenPort: 10000, - wasmModules: ['trace_processor', 'traceconv'], - crossOriginIsolation: false, - testFilter: '', - noOverrideGnArgs: false, - - // The fields below will be changed by main() after cmdline parsing. - // Directory structure: - // out/xxx/ -> outDir : Root build dir, for both ninja/wasm and UI. - // ui/ -> outUiDir : UI dir. All outputs from this script. - // tsc/ -> outTscDir : Transpiled .ts -> .js. - // gen/ -> outGenDir : Auto-generated .ts/.js (e.g. protos). - // dist/ -> outDistRootDir : Only index.html and service_worker.js - // v1.2/ -> outDistDir : JS bundles and assets - // chrome_extension/ : Chrome extension. - outDir: pjoin(ROOT_DIR, 'out/ui'), - version: '', // v1.2.3, derived from the CHANGELOG + git. - outUiDir: '', - outUiTestArtifactsDir: '', - outDistRootDir: '', - outTscDir: '', - outGenDir: '', - outDistDir: '', - outExtDir: '', -}; - -const RULES = [ - {r: /ui\/src\/assets\/index.html/, f: copyIndexHtml}, - {r: /ui\/src\/assets\/((.*)[.]png)/, f: copyAssets}, - {r: /buildtools\/typefaces\/(.+[.]woff2)/, f: copyAssets}, - {r: /buildtools\/catapult_trace_viewer\/(.+(js|html))/, f: copyAssets}, - {r: /ui\/src\/assets\/.+[.]scss/, f: compileScss}, - {r: /ui\/src\/assets\/.+[.]scss/, f: compileScss}, - {r: /ui\/src\/chrome_extension\/.*/, f: copyExtensionAssets}, - { - r: /ui\/src\/test\/diff_viewer\/(.+[.](?:html|js))/, - f: copyUiTestArtifactsAssets, - }, - {r: /.*\/dist\/.+\/(?!manifest\.json).*/, f: genServiceWorkerManifestJson}, - {r: /.*\/dist\/.*/, f: notifyLiveServer}, -]; - -const tasks = []; -let tasksTot = 0; -let tasksRan = 0; -const httpWatches = []; -const tStart = Date.now(); -const subprocesses = []; - -async function main() { - const parser = new argparse.ArgumentParser(); - parser.add_argument('--out', {help: 'Output directory'}); - parser.add_argument('--watch', '-w', {action: 'store_true'}); - parser.add_argument('--serve', '-s', {action: 'store_true'}); - parser.add_argument('--serve-host', {help: '--serve bind host'}); - parser.add_argument('--serve-port', {help: '--serve bind port', type: 'int'}); - parser.add_argument('--verbose', '-v', {action: 'store_true'}); - parser.add_argument('--no-build', '-n', {action: 'store_true'}); - parser.add_argument('--no-wasm', '-W', {action: 'store_true'}); - parser.add_argument('--run-unittests', '-t', {action: 'store_true'}); - parser.add_argument('--run-integrationtests', '-T', {action: 'store_true'}); - parser.add_argument('--debug', '-d', {action: 'store_true'}); - parser.add_argument('--interactive', '-i', {action: 'store_true'}); - parser.add_argument('--rebaseline', '-r', {action: 'store_true'}); - parser.add_argument('--no-depscheck', {action: 'store_true'}); - parser.add_argument('--cross-origin-isolation', {action: 'store_true'}); - parser.add_argument('--test-filter', '-f', { - help: 'filter Jest tests by regex, e.g. \'chrome_render\'', - }); - parser.add_argument('--no-override-gn-args', {action: 'store_true'}); - - const args = parser.parse_args(); - const clean = !args.no_build; - cfg.outDir = path.resolve(ensureDir(args.out || cfg.outDir)); - cfg.outUiDir = ensureDir(pjoin(cfg.outDir, 'ui'), clean); - cfg.outUiTestArtifactsDir = ensureDir(pjoin(cfg.outDir, 'ui-test-artifacts')); - cfg.outExtDir = ensureDir(pjoin(cfg.outUiDir, 'chrome_extension')); - cfg.outDistRootDir = ensureDir(pjoin(cfg.outUiDir, 'dist')); - const proc = exec('python3', [VERSION_SCRIPT, '--stdout'], {stdout: 'pipe'}); - cfg.version = proc.stdout.toString().trim(); - cfg.outDistDir = ensureDir(pjoin(cfg.outDistRootDir, cfg.version)); - cfg.outTscDir = ensureDir(pjoin(cfg.outUiDir, 'tsc')); - cfg.outGenDir = ensureDir(pjoin(cfg.outUiDir, 'tsc/gen')); - cfg.testFilter = args.test_filter || ''; - cfg.watch = !!args.watch; - cfg.verbose = !!args.verbose; - cfg.debug = !!args.debug; - cfg.startHttpServer = args.serve; - cfg.noOverrideGnArgs = !!args.no_override_gn_args; - if (args.serve_host) { - cfg.httpServerListenHost = args.serve_host; - } - if (args.serve_port) { - cfg.httpServerListenPort = args.serve_port; - } - if (args.interactive) { - process.env.PERFETTO_UI_TESTS_INTERACTIVE = '1'; - } - if (args.rebaseline) { - process.env.PERFETTO_UI_TESTS_REBASELINE = '1'; - } - if (args.cross_origin_isolation) { - cfg.crossOriginIsolation = true; - } - - process.on('SIGINT', () => { - console.log('\nSIGINT received. Killing all child processes and exiting'); - for (const proc of subprocesses) { - if (proc) proc.kill('SIGINT'); - } - process.exit(130); // 130 -> Same behavior of bash when killed by SIGINT. - }); - - if (!args.no_depscheck) { - // Check that deps are current before starting. - const installBuildDeps = pjoin(ROOT_DIR, 'tools/install-build-deps'); - const checkDepsPath = pjoin(cfg.outDir, '.check_deps'); - let args = [installBuildDeps, `--check-only=${checkDepsPath}`, '--ui']; - - if (process.platform === 'darwin') { - const result = childProcess.spawnSync('arch', ['-arm64', 'true']); - const isArm64Capable = result.status === 0; - if (isArm64Capable) { - const archArgs = [ - 'arch', - '-arch', - 'arm64', - ]; - args = archArgs.concat(args); - } - } - const cmd = args.shift(); - exec(cmd, args); - } - - console.log('Entering', cfg.outDir); - process.chdir(cfg.outDir); - - // Enqueue empty task. This is needed only for --no-build --serve. The HTTP - // server is started when the task queue reaches quiescence, but it takes at - // least one task for that. - addTask(() => {}); - - if (!args.no_build) { - updateSymlinks(); // Links //ui/out -> //out/xxx/ui/ - - buildWasm(args.no_wasm); - scanDir('ui/src/assets'); - scanDir('ui/src/chrome_extension'); - scanDir('ui/src/test/diff_viewer'); - scanDir('buildtools/typefaces'); - scanDir('buildtools/catapult_trace_viewer'); - generateImports('ui/src/tracks', 'all_tracks.ts'); - compileProtos(); - genVersion(); - transpileTsProject('ui'); - transpileTsProject('ui/src/service_worker'); - - if (cfg.watch) { - transpileTsProject('ui', {watch: cfg.watch}); - transpileTsProject('ui/src/service_worker', {watch: cfg.watch}); - } - - bundleJs('rollup.config.js'); - genServiceWorkerManifestJson(); - - // Watches the /dist. When changed: - // - Notifies the HTTP live reload clients. - // - Regenerates the ServiceWorker file map. - scanDir(cfg.outDistRootDir); - } - - // We should enter the loop only in watch mode, where tsc and rollup are - // asynchronous because they run in watch mode. - if (args.no_build && !isDistComplete()) { - console.log('No build was requested, but artifacts are not available.'); - console.log('In case of execution error, re-run without --no-build.'); - } - if (!args.no_build) { - const tStart = Date.now(); - while (!isDistComplete()) { - const secs = Math.ceil((Date.now() - tStart) / 1000); - process.stdout.write( - `\t\tWaiting for first build to complete... ${secs} s\r`); - await new Promise((r) => setTimeout(r, 500)); - } - } - if (cfg.watch) console.log('\nFirst build completed!'); - - if (cfg.startHttpServer) { - startServer(); - } - if (args.run_unittests) { - runTests('jest.unittest.config.js'); - } - if (args.run_integrationtests) { - runTests('jest.integrationtest.config.js'); - } -} - -// ----------- -// Build rules -// ----------- - -function runTests(cfgFile) { - const args = [ - '--rootDir', - cfg.outTscDir, - '--verbose', - '--runInBand', - '--detectOpenHandles', - '--forceExit', - '--projects', - pjoin(ROOT_DIR, 'ui/config', cfgFile), - ]; - if (cfg.testFilter.length > 0) { - args.push('-t', cfg.testFilter); - } - if (cfg.watch) { - args.push('--watchAll'); - addTask(execNode, ['jest', args, {async: true}]); - } else { - addTask(execNode, ['jest', args]); - } -} - -function copyIndexHtml(src) { - const indexHtml = () => { - let html = fs.readFileSync(src).toString(); - // First copy the index.html as-is into the dist/v1.2.3/ directory. This is - // only used for archival purporses, so one can open - // ui.perfetto.dev/v1.2.3/ to skip the auto-update and channel logic. - fs.writeFileSync(pjoin(cfg.outDistDir, 'index.html'), html); - - // Then copy it into the dist/ root by patching the version code. - // TODO(primiano): in next CLs, this script should take a - // --release_map=xxx.json argument, to populate this with multiple channels. - const versionMap = JSON.stringify({'stable': cfg.version}); - const bodyRegex = /data-perfetto_version='[^']*'/; - html = html.replace(bodyRegex, `data-perfetto_version='${versionMap}'`); - fs.writeFileSync(pjoin(cfg.outDistRootDir, 'index.html'), html); - }; - addTask(indexHtml); -} - -function copyAssets(src, dst) { - addTask(cp, [src, pjoin(cfg.outDistDir, 'assets', dst)]); -} - -function copyUiTestArtifactsAssets(src, dst) { - addTask(cp, [src, pjoin(cfg.outUiTestArtifactsDir, dst)]); -} - -function compileScss() { - const src = pjoin(ROOT_DIR, 'ui/src/assets/perfetto.scss'); - const dst = pjoin(cfg.outDistDir, 'perfetto.css'); - // In watch mode, don't exit(1) if scss fails. It can easily happen by - // having a typo in the css. It will still print an error. - const noErrCheck = !!cfg.watch; - addTask(execNode, ['node-sass', ['--quiet', src, dst], {noErrCheck}]); -} - -function compileProtos() { - const dstJs = pjoin(cfg.outGenDir, 'protos.js'); - const dstTs = pjoin(cfg.outGenDir, 'protos.d.ts'); - // We've ended up pulling in all the protos (via trace.proto, - // trace_packet.proto) below which means |dstJs| ends up being - // 23k lines/12mb. We should probably not do that. - // TODO(hjd): Figure out how to use lazy with pbjs/pbts. - const inputs = [ - 'protos/perfetto/common/trace_stats.proto', - 'protos/perfetto/common/tracing_service_capabilities.proto', - 'protos/perfetto/config/perfetto_config.proto', - 'protos/perfetto/ipc/consumer_port.proto', - 'protos/perfetto/ipc/wire_protocol.proto', - 'protos/perfetto/metrics/metrics.proto', - 'protos/perfetto/trace/perfetto/perfetto_metatrace.proto', - 'protos/perfetto/trace/trace.proto', - 'protos/perfetto/trace/trace_packet.proto', - 'protos/perfetto/trace_processor/trace_processor.proto', - ]; - // Can't put --no-comments here - The comments are load bearing for - // the pbts invocation which follows. - const pbjsArgs = [ - '--no-beautify', - '--force-number', - '--no-delimited', - '--no-verify', - '-t', - 'static-module', - '-w', - 'commonjs', - '-p', - ROOT_DIR, - '-o', - dstJs, - ].concat(inputs); - addTask(execNode, ['pbjs', pbjsArgs]); - - // Note: If you are looking into slowness of pbts it is not pbts - // itself that is slow. It invokes jsdoc to parse the comments out of - // the |dstJs| with https://github.com/hegemonic/catharsis which is - // pinning a CPU core the whole time. - const pbtsArgs = ['--no-comments', '-p', ROOT_DIR, '-o', dstTs, dstJs]; - addTask(execNode, ['pbts', pbtsArgs]); -} - -function generateImports(dir, name) { - // We have to use the symlink (ui/src/gen) rather than cfg.outGenDir - // below since we want to generate the correct relative imports. For example: - // ui/src/frontend/foo.ts - // import '../gen/all_plugins.ts'; - // ui/src/gen/all_plugins.ts (aka ui/out/tsc/gen/all_plugins.ts) - // import '../frontend/some_plugin.ts'; - const dstTs = pjoin(ROOT_DIR, 'ui/src/gen', name); - const inputDir = pjoin(ROOT_DIR, dir); - const args = [GEN_IMPORTS_SCRIPT, inputDir, '--out', dstTs]; - addTask(exec, ['python3', args]); -} - -// Generates a .ts source that defines the VERSION and SCM_REVISION constants. -function genVersion() { - const cmd = 'python3'; - const args = - [VERSION_SCRIPT, '--ts_out', pjoin(cfg.outGenDir, 'perfetto_version.ts')]; - addTask(exec, [cmd, args]); -} - -function updateSymlinks() { - // /ui/out -> /out/ui. - mklink(cfg.outUiDir, pjoin(ROOT_DIR, 'ui/out')); - - // /ui/src/gen -> /out/ui/ui/tsc/gen) - mklink(cfg.outGenDir, pjoin(ROOT_DIR, 'ui/src/gen')); - - // /out/ui/test/data -> /test/data (For UI tests). - mklink( - pjoin(ROOT_DIR, 'test/data'), - pjoin(ensureDir(pjoin(cfg.outDir, 'test')), 'data')); - - // Creates a out/dist_version -> out/dist/v1.2.3 symlink, so rollup config - // can point to that without having to know the current version number. - mklink( - path.relative(cfg.outUiDir, cfg.outDistDir), - pjoin(cfg.outUiDir, 'dist_version')); - - mklink( - pjoin(ROOT_DIR, 'ui/node_modules'), pjoin(cfg.outTscDir, 'node_modules')); -} - -// Invokes ninja for building the {trace_processor, traceconv} Wasm modules. -// It copies the .wasm directly into the out/dist/ dir, and the .js/.ts into -// out/tsc/, so the typescript compiler and the bundler can pick them up. -function buildWasm(skipWasmBuild) { - if (!skipWasmBuild) { - if (!cfg.noOverrideGnArgs) { - const gnArgs = ['gen', `--args=is_debug=${cfg.debug}`, cfg.outDir]; - addTask(exec, [pjoin(ROOT_DIR, 'tools/gn'), gnArgs]); - } - - const ninjaArgs = ['-C', cfg.outDir]; - ninjaArgs.push(...cfg.wasmModules.map((x) => `${x}_wasm`)); - addTask(exec, [pjoin(ROOT_DIR, 'tools/ninja'), ninjaArgs]); - } - - const wasmOutDir = pjoin(cfg.outDir, 'wasm'); - for (const wasmMod of cfg.wasmModules) { - // The .wasm file goes directly into the dist dir (also .map in debug) - for (const ext of ['.wasm'].concat(cfg.debug ? ['.wasm.map'] : [])) { - const src = `${wasmOutDir}/${wasmMod}${ext}`; - addTask(cp, [src, pjoin(cfg.outDistDir, wasmMod + ext)]); - } - // The .js / .ts go into intermediates, they will be bundled by rollup. - for (const ext of ['.js', '.d.ts']) { - const fname = `${wasmMod}${ext}`; - addTask(cp, [pjoin(wasmOutDir, fname), pjoin(cfg.outGenDir, fname)]); - } - } -} - -// This transpiles all the sources (frontend, controller, engine, extension) in -// one go. The only project that has a dedicated invocation is service_worker. -function transpileTsProject(project, options) { - const args = ['--project', pjoin(ROOT_DIR, project)]; - - if (options !== undefined && options.watch) { - args.push('--watch', '--preserveWatchOutput'); - addTask(execNode, ['tsc', args, {async: true}]); - } else { - addTask(execNode, ['tsc', args]); - } -} - -// Creates the three {frontend, controller, engine}_bundle.js in one invocation. -function bundleJs(cfgName) { - const rcfg = pjoin(ROOT_DIR, 'ui/config', cfgName); - const args = ['-c', rcfg, '--no-indent']; - args.push(...(cfg.verbose ? [] : ['--silent'])); - if (cfg.watch) { - // --waitForBundleInput is sadly quite busted so it is required ts - // has build at least once before invoking this. - args.push('--watch', '--no-watch.clearScreen'); - addTask(execNode, ['rollup', args, {async: true}]); - } else { - addTask(execNode, ['rollup', args]); - } -} - -function genServiceWorkerManifestJson() { - function makeManifest() { - const manifest = {resources: {}}; - // When building the subresource manifest skip source maps, the manifest - // itself and the copy of the index.html which is copied under /v1.2.3/. - // The root /index.html will be fetched by service_worker.js separately. - const skipRegex = /(\.map|manifest\.json|index.html)$/; - walk(cfg.outDistDir, (absPath) => { - const contents = fs.readFileSync(absPath); - const relPath = path.relative(cfg.outDistDir, absPath); - const b64 = crypto.createHash('sha256').update(contents).digest('base64'); - manifest.resources[relPath] = 'sha256-' + b64; - }, skipRegex); - const manifestJson = JSON.stringify(manifest, null, 2); - fs.writeFileSync(pjoin(cfg.outDistDir, 'manifest.json'), manifestJson); - } - addTask(makeManifest, []); -} - -function startServer() { - console.log( - 'Starting HTTP server on', - `http://${cfg.httpServerListenHost}:${cfg.httpServerListenPort}`); - http.createServer(function(req, res) { - console.debug(req.method, req.url); - let uri = req.url.split('?', 1)[0]; - if (uri.endsWith('/')) { - uri += 'index.html'; - } - - if (uri === '/live_reload') { - // Implements the Server-Side-Events protocol. - const head = { - 'Content-Type': 'text/event-stream', - 'Connection': 'keep-alive', - 'Cache-Control': 'no-cache', - }; - res.writeHead(200, head); - const arrayIdx = httpWatches.length; - // We never remove from the array, the delete leaves an undefined item - // around. It makes keeping track of the index easier at the cost of a - // small leak. - httpWatches.push(res); - req.on('close', () => delete httpWatches[arrayIdx]); - return; - } - - let absPath = path.normalize(path.join(cfg.outDistRootDir, uri)); - // We want to be able to use the data in '/test/' for e2e tests. - // However, we don't want do create a symlink into the 'dist/' dir, - // because 'dist/' gets shipped on the production server. - if (uri.startsWith('/test/')) { - absPath = pjoin(ROOT_DIR, uri); - } - - // Don't serve contents outside of the project root (b/221101533). - if (path.relative(ROOT_DIR, absPath).startsWith('..')) { - res.writeHead(403); - res.end('403 Forbidden - Request path outside of the repo root'); - return; - } - - fs.readFile(absPath, function(err, data) { - if (err) { - res.writeHead(404); - res.end(JSON.stringify(err)); - return; - } - - const mimeMap = { - 'html': 'text/html', - 'css': 'text/css', - 'js': 'application/javascript', - 'wasm': 'application/wasm', - }; - const ext = uri.split('.').pop(); - const cType = mimeMap[ext] || 'octect/stream'; - const head = { - 'Content-Type': cType, - 'Content-Length': data.length, - 'Last-Modified': fs.statSync(absPath).mtime.toUTCString(), - 'Cache-Control': 'no-cache', - }; - if (cfg.crossOriginIsolation) { - head['Cross-Origin-Opener-Policy'] = 'same-origin'; - head['Cross-Origin-Embedder-Policy'] = 'require-corp'; - } - res.writeHead(200, head); - res.write(data); - res.end(); - }); - }) - .listen(cfg.httpServerListenPort, cfg.httpServerListenHost); -} - -function isDistComplete() { - const requiredArtifacts = [ - 'frontend_bundle.js', - 'engine_bundle.js', - 'traceconv_bundle.js', - 'trace_processor.wasm', - 'perfetto.css', - ]; - const relPaths = new Set(); - walk(cfg.outDistDir, (absPath) => { - relPaths.add(path.relative(cfg.outDistDir, absPath)); - }); - for (const fName of requiredArtifacts) { - if (!relPaths.has(fName)) return false; - } - return true; -} - -// Called whenever a change in the out/dist directory is detected. It sends a -// Server-Side-Event to the live_reload.ts script. -function notifyLiveServer(changedFile) { - for (const cli of httpWatches) { - if (cli === undefined) continue; - cli.write( - 'data: ' + path.relative(cfg.outDistRootDir, changedFile) + '\n\n'); - } -} - -function copyExtensionAssets() { - addTask(cp, [ - pjoin(ROOT_DIR, 'ui/src/assets/logo-128.png'), - pjoin(cfg.outExtDir, 'logo-128.png'), - ]); - addTask(cp, [ - pjoin(ROOT_DIR, 'ui/src/chrome_extension/manifest.json'), - pjoin(cfg.outExtDir, 'manifest.json'), - ]); -} - -// ----------------------- -// Task chaining functions -// ----------------------- - -function addTask(func, args) { - const task = new Task(func, args); - for (const t of tasks) { - if (t.identity === task.identity) { - return; - } - } - tasks.push(task); - setTimeout(runTasks, 0); -} - -function runTasks() { - const snapTasks = tasks.splice(0); // snap = std::move(tasks). - tasksTot += snapTasks.length; - for (const task of snapTasks) { - const DIM = '\u001b[2m'; - const BRT = '\u001b[37m'; - const RST = '\u001b[0m'; - const ms = (new Date(Date.now() - tStart)).toISOString().slice(17, -1); - const ts = `[${DIM}${ms}${RST}]`; - const descr = task.description.substr(0, 80); - console.log(`${ts} ${BRT}${++tasksRan}/${tasksTot}${RST}\t${descr}`); - task.func.apply(/* this=*/ undefined, task.args); - } -} - -// Executes all the RULES that match the given |absPath|. -function scanFile(absPath) { - console.assert(fs.existsSync(absPath)); - console.assert(path.isAbsolute(absPath)); - const normPath = path.relative(ROOT_DIR, absPath); - for (const rule of RULES) { - const match = rule.r.exec(normPath); - if (!match || match[0] !== normPath) continue; - const captureGroup = match.length > 1 ? match[1] : undefined; - rule.f(absPath, captureGroup); - } -} - -// Walks the passed |dir| recursively and, for each file, invokes the matching -// RULES. If --watch is used, it also installs a fswatch() and re-triggers the -// matching RULES on each file change. -function scanDir(dir, regex) { - const filterFn = regex ? (absPath) => regex.test(absPath) : () => true; - const absDir = path.isAbsolute(dir) ? dir : pjoin(ROOT_DIR, dir); - // Add a fs watch if in watch mode. - if (cfg.watch) { - fswatch(absDir, {recursive: true}, (_eventType, filePath) => { - if (!filterFn(filePath)) return; - if (cfg.verbose) { - console.log('File change detected', _eventType, filePath); - } - if (fs.existsSync(filePath)) { - scanFile(filePath, filterFn); - } - }); - } - walk(absDir, (f) => { - if (filterFn(f)) scanFile(f); - }); -} - -function exec(cmd, args, opts) { - opts = opts || {}; - opts.stdout = opts.stdout || 'inherit'; - if (cfg.verbose) console.log(`${cmd} ${args.join(' ')}\n`); - const spwOpts = {cwd: cfg.outDir, stdio: ['ignore', opts.stdout, 'inherit']}; - const checkExitCode = (code, signal) => { - if (signal === 'SIGINT' || signal === 'SIGTERM') return; - if (code !== 0 && !opts.noErrCheck) { - console.error(`${cmd} ${args.join(' ')} failed with code ${code}`); - process.exit(1); - } - }; - if (opts.async) { - const proc = childProcess.spawn(cmd, args, spwOpts); - const procIndex = subprocesses.length; - subprocesses.push(proc); - return new Promise((resolve, _reject) => { - proc.on('exit', (code, signal) => { - delete subprocesses[procIndex]; - checkExitCode(code, signal); - resolve(); - }); - }); - } else { - const spawnRes = childProcess.spawnSync(cmd, args, spwOpts); - checkExitCode(spawnRes.status, spawnRes.signal); - return spawnRes; - } -} - -function execNode(module, args, opts) { - const modPath = pjoin(ROOT_DIR, 'ui/node_modules/.bin', module); - const nodeBin = pjoin(ROOT_DIR, 'tools/node'); - args = [modPath].concat(args || []); - return exec(nodeBin, args, opts); -} - -// ------------------------------------------ -// File system & subprocess utility functions -// ------------------------------------------ - -class Task { - constructor(func, args) { - this.func = func; - this.args = args || []; - // |identity| is used to dedupe identical tasks in the queue. - this.identity = JSON.stringify([this.func.name, this.args]); - } - - get description() { - const ret = this.func.name.startsWith('exec') ? [] : [this.func.name]; - const flattenedArgs = [].concat(...this.args); - for (const arg of flattenedArgs) { - const argStr = `${arg}`; - if (argStr.startsWith('/')) { - ret.push(path.relative(cfg.outDir, arg)); - } else { - ret.push(argStr); - } - } - return ret.join(' '); - } -} - -function walk(dir, callback, skipRegex) { - for (const child of fs.readdirSync(dir)) { - const childPath = pjoin(dir, child); - const stat = fs.lstatSync(childPath); - if (skipRegex !== undefined && skipRegex.test(child)) continue; - if (stat.isDirectory()) { - walk(childPath, callback, skipRegex); - } else if (!stat.isSymbolicLink()) { - callback(childPath); - } - } -} - -function ensureDir(dirPath, clean) { - const exists = fs.existsSync(dirPath); - if (exists && clean) { - console.log('rm', dirPath); - fs.rmSync(dirPath, {recursive: true}); - } - if (!exists || clean) fs.mkdirSync(dirPath, {recursive: true}); - return dirPath; -} - -function cp(src, dst) { - ensureDir(path.dirname(dst)); - if (cfg.verbose) { - console.log( - 'cp', path.relative(ROOT_DIR, src), '->', path.relative(ROOT_DIR, dst)); - } - fs.copyFileSync(src, dst); -} - -function mklink(src, dst) { - // If the symlink already points to the right place don't touch it. This is - // to avoid changing the mtime of the ui/ dir when unnecessary. - if (fs.existsSync(dst)) { - if (fs.lstatSync(dst).isSymbolicLink() && fs.readlinkSync(dst) === src) { - return; - } else { - fs.unlinkSync(dst); - } - } - fs.symlinkSync(src, dst); -} - -main(); diff --git a/third_party/perfetto/ui/config/.gitignore b/third_party/perfetto/ui/config/.gitignore deleted file mode 100644 index acd163cfb80b..000000000000 --- a/third_party/perfetto/ui/config/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/jest.individual.unit.config.js diff --git a/third_party/perfetto/ui/config/gn_deprecation_banner.txt b/third_party/perfetto/ui/config/gn_deprecation_banner.txt deleted file mode 100644 index 06aed2379631..000000000000 --- a/third_party/perfetto/ui/config/gn_deprecation_banner.txt +++ /dev/null @@ -1,6 +0,0 @@ - -------------------------------------------------------- -WARNING: building the UI through GN+ninja is deprecated. -Going forward use the //ui/build script -------------------------------------------------------- - \ No newline at end of file diff --git a/third_party/perfetto/ui/config/integrationtest_env.js b/third_party/perfetto/ui/config/integrationtest_env.js deleted file mode 100644 index 830025ec85d5..000000000000 --- a/third_party/perfetto/ui/config/integrationtest_env.js +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const NodeEnvironment = require('jest-environment-node'); -const puppeteer = require('puppeteer'); - -module.exports = class IntegrationtestEnvironment extends NodeEnvironment { - constructor(config) { - super(config); - } - - async setup() { - await super.setup(); - const headless = process.env.PERFETTO_UI_TESTS_INTERACTIVE !== '1'; - if (headless) { - console.log('Starting Perfetto UI tests in headless mode.'); - console.log( - 'Pass --interactive to run-integrationtests or set ' + - 'PERFETTO_UI_TESTS_INTERACTIVE=1 to inspect the behavior ' + - 'in a visible Chrome window'); - } - this.global.__BROWSER__ = await puppeteer.launch({ - args: [ - '--window-size=1920,1080', - '--disable-accelerated-2d-canvas', - '--disable-gpu', - '--no-sandbox', // Disable sandbox to run in Docker. - '--disable-setuid-sandbox', - '--font-render-hinting=none', - '--enable-benchmarking', // Disable finch and other sources of non - // determinism. - ], - - // This is so screenshot in --interactive and headless mode match. The - // scrollbars are never part of the screenshot, but without this cmdline - // switch, in headless mode we don't get any blank space (as if it was - // overflow:hidden) and that changes the layout of the page. - ignoreDefaultArgs: ['--hide-scrollbars'], - - headless: headless, - }); - } - - async teardown() { - if (this.global.__BROWSER__) { - await this.global.__BROWSER__.close(); - } - await super.teardown(); - } - - runScript(script) { - return super.runScript(script); - } -}; diff --git a/third_party/perfetto/ui/config/integrationtest_setup.js b/third_party/perfetto/ui/config/integrationtest_setup.js deleted file mode 100644 index 7dc18736c08e..000000000000 --- a/third_party/perfetto/ui/config/integrationtest_setup.js +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const path = require('path'); -const http = require('http'); -const childProcess = require('child_process'); - -module.exports = async function() { - // Start the local HTTP server. - const ROOT_DIR = path.dirname(path.dirname(__dirname)); - const node = path.join(ROOT_DIR, 'ui', 'node'); - const args = [ - path.join(ROOT_DIR, 'ui', 'build.js'), - '--serve', - '--no-build', - '--out=.', - ]; - const spwOpts = {stdio: ['ignore', 'inherit', 'inherit']}; - const srvProc = childProcess.spawn(node, args, spwOpts); - global.__DEV_SERVER__ = srvProc; - - // Wait for the HTTP server to be ready. - let attempts = 10; - for (; attempts > 0; attempts--) { - await new Promise((r) => setTimeout(r, 1000)); - try { - await new Promise((resolve, reject) => { - const req = http.request('http://127.0.0.1:10000/frontend_bundle.js'); - req.end(); - req.on('error', (err) => reject(err)); - req.on('finish', () => resolve()); - }); - break; - } catch (err) { - console.error('Waiting for HTTP server to come up', err.message); - } - } - if (attempts === 0) { - throw new Error('HTTP server didn\'t come up'); - } - if (srvProc.exitCode !== null) { - throw new Error( - `The dev server unexpectedly exited, code=${srvProc.exitCode}`); - } -}; diff --git a/third_party/perfetto/ui/config/integrationtest_teardown.js b/third_party/perfetto/ui/config/integrationtest_teardown.js deleted file mode 100644 index 4cfbc849bd1a..000000000000 --- a/third_party/perfetto/ui/config/integrationtest_teardown.js +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -module.exports = async function() { - const proc = global.__DEV_SERVER__; - // Kill the HTTP server. - proc.kill(); - for (;;) { - if (proc.exitCode !== null || proc.killed) break; - console.log('Waiting for dev server termination'); - await new Promise((r) => setTimeout(r, 1000)); - } -}; diff --git a/third_party/perfetto/ui/config/jest.integrationtest.config.js b/third_party/perfetto/ui/config/jest.integrationtest.config.js deleted file mode 100644 index 9f58c3dd99a3..000000000000 --- a/third_party/perfetto/ui/config/jest.integrationtest.config.js +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -module.exports = { - transform: {}, - testRegex: '.*_integrationtest.js$', - globalSetup: __dirname + '/integrationtest_setup.js', - globalTeardown: __dirname + '/integrationtest_teardown.js', - testEnvironment: __dirname + '/integrationtest_env.js', -}; diff --git a/third_party/perfetto/ui/config/jest.unittest.config.js b/third_party/perfetto/ui/config/jest.unittest.config.js deleted file mode 100644 index e8efad3eed3b..000000000000 --- a/third_party/perfetto/ui/config/jest.unittest.config.js +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -module.exports = { - transform: {}, - testRegex: '_(unittest|jsdomtest)[.]js$', - testEnvironment: 'jsdom', -}; diff --git a/third_party/perfetto/ui/config/rollup.config.js b/third_party/perfetto/ui/config/rollup.config.js deleted file mode 100644 index d83d2b9c3dd1..000000000000 --- a/third_party/perfetto/ui/config/rollup.config.js +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import commonjs from '@rollup/plugin-commonjs'; -import nodeResolve from '@rollup/plugin-node-resolve'; -import replace from 'rollup-plugin-re'; -import sourcemaps from 'rollup-plugin-sourcemaps'; - -const path = require('path'); -const ROOT_DIR = path.dirname(path.dirname(__dirname)); // The repo root. -const OUT_SYMLINK = path.join(ROOT_DIR, 'ui/out'); - -function defBundle(bundle, distDir) { - return { - input: `${OUT_SYMLINK}/tsc/${bundle}/index.js`, - output: { - name: bundle, - format: 'iife', - esModule: false, - file: `${OUT_SYMLINK}/${distDir}/${bundle}_bundle.js`, - sourcemap: true, - }, - plugins: [ - nodeResolve({ - mainFields: ['browser'], - browser: true, - preferBuiltins: false, - }), - - commonjs({ - strictRequires: true, - }), - - replace({ - patterns: [ - // Protobufjs's inquire() uses eval but that's not really needed in - // the browser. https://github.com/protobufjs/protobuf.js/issues/593 - {test: /eval\(.*\(moduleName\);/g, replace: 'undefined;'}, - - // Immer entry point has a if (process.env.NODE_ENV === 'production') - // but |process| is not defined in the browser. Bypass. - // https://github.com/immerjs/immer/issues/557 - {test: /process\.env\.NODE_ENV/g, replace: '\'production\''}, - ], - }), - - // Translate source maps to point back to the .ts sources. - sourcemaps(), - ], - onwarn: function(warning, warn) { - // Ignore circular dependency warnings coming from third party code. - if (warning.code === 'CIRCULAR_DEPENDENCY' && - warning.importer.includes('node_modules')) { - return; - } - - // Call the default warning handler for all remaining warnings. - warn(warning); - }, - }; -} - -function defServiceWorkerBundle() { - return { - input: `${OUT_SYMLINK}/tsc/service_worker/service_worker.js`, - output: { - name: 'service_worker', - format: 'iife', - esModule: false, - file: `${OUT_SYMLINK}/dist/service_worker.js`, - sourcemap: true, - }, - plugins: [ - nodeResolve({ - mainFields: ['browser'], - browser: true, - preferBuiltins: false, - }), - commonjs(), - sourcemaps(), - ], - }; -} - -export default [ - defBundle('frontend', 'dist_version'), - defBundle('engine', 'dist_version'), - defBundle('traceconv', 'dist_version'), - defBundle('chrome_extension', 'chrome_extension'), - defServiceWorkerBundle(), -]; diff --git a/third_party/perfetto/ui/eslint b/third_party/perfetto/ui/eslint deleted file mode 100755 index 2afa3242a131..000000000000 --- a/third_party/perfetto/ui/eslint +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# Copyright (C) 2022 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -e -u -ROOT_DIR="$(dirname $(cd -P ${BASH_SOURCE[0]%/*}; pwd))" -NODE_PATH="$ROOT_DIR/ui/node" -ESLINT_PATH="$ROOT_DIR/ui/node_modules/.bin/eslint" - -if [[ ! -x "$ESLINT_PATH" ]]; then - echo "eslint not found ($ESLINT_PATH). Run '$ROOT_DIR/tools/install-build-deps --ui'" - exit 1 -fi - -exec "$NODE_PATH" "$ESLINT_PATH" "$@" diff --git a/third_party/perfetto/ui/eslint-all b/third_party/perfetto/ui/eslint-all deleted file mode 100755 index b3adad546cb3..000000000000 --- a/third_party/perfetto/ui/eslint-all +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -# Copyright (C) 2022 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -e -u -ROOT_DIR="$(dirname $(cd -P ${BASH_SOURCE[0]%/*}; pwd))" - -exec "$ROOT_DIR/ui/eslint" "$@" $(find $ROOT_DIR/ui/src -type f \( -name "*.ts" -o -name "*.js" \)) diff --git a/third_party/perfetto/ui/node b/third_party/perfetto/ui/node deleted file mode 100755 index 8ec17be0e6ca..000000000000 --- a/third_party/perfetto/ui/node +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -e -u -ROOT_DIR="$(dirname $(cd -P ${BASH_SOURCE[0]%/*}; pwd))" -exec "$ROOT_DIR/tools/node" "$@" diff --git a/third_party/perfetto/ui/npm b/third_party/perfetto/ui/npm deleted file mode 100755 index e703031850af..000000000000 --- a/third_party/perfetto/ui/npm +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -e -u -ROOT_DIR="$(dirname $(cd -P ${BASH_SOURCE[0]%/*}; pwd))" -exec "$ROOT_DIR/tools/npm" "$@" diff --git a/third_party/perfetto/ui/package-lock.json b/third_party/perfetto/ui/package-lock.json deleted file mode 100644 index 7dc51bd84470..000000000000 --- a/third_party/perfetto/ui/package-lock.json +++ /dev/null @@ -1,18515 +0,0 @@ -{ - "name": "perfetto-ui", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "perfetto-ui", - "version": "1.0.0", - "license": "Apache-2.0", - "dependencies": { - "@popperjs/core": "^2.11.6", - "@types/chrome": "0.0.186", - "@types/color-convert": "^1.9.0", - "@types/filesystem": "^0.0.32", - "@types/mithril": "^2.0.11", - "@types/node": "^14.0.10", - "@types/pako": "^1.0.1", - "@types/pngjs": "^6.0.1", - "@types/uuid": "^8.3.4", - "@types/w3c-web-usb": "^1.0.4", - "color-convert": "^2.0.1", - "custom_utils": "file:src/base/utils", - "devtools-protocol": "0.0.847576", - "esbuild": "^0.15.12", - "events": "^3.1.0", - "hsluv": "^0.1.0", - "immer": "^9.0.2", - "jsbn-rsa": "^1.0.4", - "mithril": "^2.2.0", - "noice-json-rpc": "^1.2.0", - "pako": "^1.0.11", - "protobufjs": "^6.9.0", - "util": "^0.12.3", - "uuid": "^9.0.0" - }, - "devDependencies": { - "@rollup/plugin-commonjs": "^24.0.1", - "@rollup/plugin-node-resolve": "^15.0.1", - "@types/jest": "^26.0.23", - "@types/pixelmatch": "^5.2.3", - "@typescript-eslint/eslint-plugin": "^5.56.0", - "@typescript-eslint/parser": "^5.25.0", - "dingusjs": "^0.0.3", - "eslint": "^8.15.0", - "eslint-config-google": "^0.14.0", - "jest": "^26.6.3", - "node-sass": "^8.0.0", - "node-watch": "^0.7.1", - "pixelmatch": "^5.2.1", - "pngjs": "^6.0.0", - "prettier": "^2.8.0", - "puppeteer": "^19.8.0", - "rollup": "^2.38.5", - "rollup-plugin-re": "^1.0.7", - "rollup-plugin-sourcemaps": "^0.6.3", - "tslib": "^2.5.0", - "typescript": "^4.9.5" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", - "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", - "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.3", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.3", - "@babel/types": "^7.21.3", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.21.3", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", - "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", - "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.3", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", - "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, - "dependencies": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - }, - "bin": { - "watch": "cli.js" - }, - "engines": { - "node": ">=0.1.95" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", - "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", - "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", - "cpu": [ - "loong64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.1.tgz", - "integrity": "sha512-BISJ6ZE4xQsuL/FmsyRaiffpq977bMlsKfGHTQrOGFErfByxIe6iZTxPf/00Zon9b9a7iUykfQwejN3s2ZW/Bw==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", - "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.5.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/core/node_modules/jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "node-notifier": "^8.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "dev": true, - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@popperjs/core": { - "version": "2.11.7", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", - "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "24.0.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz", - "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^8.0.3", - "is-reference": "1.2.1", - "magic-string": "^0.27.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/@rollup/pluginutils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", - "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", - "dev": true - }, - "node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/@rollup/plugin-commonjs/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz", - "integrity": "sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.0", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", - "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve/node_modules/@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", - "dev": true - }, - "node_modules/@rollup/plugin-node-resolve/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/chrome": { - "version": "0.0.186", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.186.tgz", - "integrity": "sha512-Ykpf95dbv0resO/PcRF/9vKETOKma5D2sSUKo8mSL1vz03IgVhyHuCrlzbDYMLrXIl9CcyGnYTMG2Zg0WAk62w==", - "dependencies": { - "@types/filesystem": "*", - "@types/har-format": "*" - } - }, - "node_modules/@types/color-convert": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-1.9.0.tgz", - "integrity": "sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg==", - "dependencies": { - "@types/color-name": "*" - } - }, - "node_modules/@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" - }, - "node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "node_modules/@types/filesystem": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz", - "integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==", - "dependencies": { - "@types/filewriter": "*" - } - }, - "node_modules/@types/filewriter": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz", - "integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==" - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/har-format": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.10.tgz", - "integrity": "sha512-o0J30wqycjF5miWDKYKKzzOU1ZTLuA42HZ4HE7/zqTOc/jTLdQ5NhYWvsRQo45Nfi1KHoRdNhteSI4BAxTF1Pg==" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "26.0.24", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz", - "integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==", - "dev": true, - "dependencies": { - "jest-diff": "^26.0.0", - "pretty-format": "^26.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true - }, - "node_modules/@types/mithril": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@types/mithril/-/mithril-2.0.12.tgz", - "integrity": "sha512-vedzt04n3EB7rcnfSLCv3+w3qJLkGWdsNRBKvelTqhSJSfg73Roq9b+rcnn9zeqGYtQAMqNcO6vNBR/w0OzipQ==" - }, - "node_modules/@types/node": { - "version": "14.18.41", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.41.tgz", - "integrity": "sha512-2cfHr8AsUjKx6u4Q+d2eqK51z8+HueoumCQGCKVt95y/yGG4uajOuCANSnE20mbLw94h3tMcddIJ8nYkTu2mFw==" - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, - "node_modules/@types/pako": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.4.tgz", - "integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==" - }, - "node_modules/@types/pixelmatch": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.4.tgz", - "integrity": "sha512-HDaSHIAv9kwpMN7zlmwfTv6gax0PiporJOipcrGsVNF3Ba+kryOZc0Pio5pn6NhisgWr7TaajlPEKTbTAypIBQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/pngjs": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.1.tgz", - "integrity": "sha512-J39njbdW1U/6YyVXvC9+1iflZghP8jgRf2ndYghdJb5xL49LYDB+1EuAxfbuJ2IBbWIL3AjHPQhgaTxT3YaYeg==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true - }, - "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" - }, - "node_modules/@types/w3c-web-usb": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz", - "integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==" - }, - "node_modules/@types/yargs": { - "version": "15.0.15", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz", - "integrity": "sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "node_modules/@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", - "dev": true, - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.56.0.tgz", - "integrity": "sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/type-utils": "5.56.0", - "@typescript-eslint/utils": "5.56.0", - "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.56.0.tgz", - "integrity": "sha512-sn1OZmBxUsgxMmR8a8U5QM/Wl+tyqlH//jTqCg8daTAmhAk26L2PFhcqPLlYBhYUJMZJK276qLXlHN3a83o2cg==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/typescript-estree": "5.56.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", - "integrity": "sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/visitor-keys": "5.56.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", - "integrity": "sha512-8WxgOgJjWRy6m4xg9KoSHPzBNZeQbGlQOH7l2QEhQID/+YseaFxg5J/DLwWSsi9Axj4e/cCiKx7PVzOq38tY4A==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "5.56.0", - "@typescript-eslint/utils": "5.56.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@typescript-eslint/type-utils/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.56.0.tgz", - "integrity": "sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz", - "integrity": "sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/visitor-keys": "5.56.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", - "integrity": "sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/typescript-estree": "5.56.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", - "integrity": "sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.56.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", - "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "depd": "^2.0.0", - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", - "dev": true, - "dependencies": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cacache/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001470", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001470.tgz", - "integrity": "sha512-065uNwY6QtHCBOExzbV6m236DDhYCCtPmQUCoQtwkVqzud8v5QPidoMr6CoMkC2nfp6nksjttqWQRRh75LqUmA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dev": true, - "dependencies": { - "rsvp": "^4.8.4" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", - "dev": true - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "node_modules/cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", - "dev": true, - "dependencies": { - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - } - }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dev": true, - "dependencies": { - "node-fetch": "2.6.7" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "node_modules/custom_utils": { - "resolved": "src/base/utils", - "link": true - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/data-urls/node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/data-urls/node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/data-urls/node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.847576", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.847576.tgz", - "integrity": "sha512-0M8kobnSQE0Jmly7Mhbeq0W/PpZfnuK+WjN2ZRVPbGqYwCHCioAVp84H0TcLimgECcN5H976y5QiXMGBC9JKmg==" - }, - "node_modules/diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/dingusjs": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/dingusjs/-/dingusjs-0.0.3.tgz", - "integrity": "sha512-DaVAaUC2npjHpRq7EcZv3SL4ZMgvzCxXOYZmHCXIstFP6f/y/4XAXWtVdsFjxDVTcPfsIM92QV4K25vNVrrX8Q==", - "dev": true - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.340", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.340.tgz", - "integrity": "sha512-zx8hqumOqltKsv/MF50yvdAlPF9S/4PXbyfzJS6ZGhbddGkRegdwImmfSVqCkEziYzrIGZ/TlrzBND4FysfkDg==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/esbuild": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", - "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.15.18", - "@esbuild/linux-loong64": "0.15.18", - "esbuild-android-64": "0.15.18", - "esbuild-android-arm64": "0.15.18", - "esbuild-darwin-64": "0.15.18", - "esbuild-darwin-arm64": "0.15.18", - "esbuild-freebsd-64": "0.15.18", - "esbuild-freebsd-arm64": "0.15.18", - "esbuild-linux-32": "0.15.18", - "esbuild-linux-64": "0.15.18", - "esbuild-linux-arm": "0.15.18", - "esbuild-linux-arm64": "0.15.18", - "esbuild-linux-mips64le": "0.15.18", - "esbuild-linux-ppc64le": "0.15.18", - "esbuild-linux-riscv64": "0.15.18", - "esbuild-linux-s390x": "0.15.18", - "esbuild-netbsd-64": "0.15.18", - "esbuild-openbsd-64": "0.15.18", - "esbuild-sunos-64": "0.15.18", - "esbuild-windows-32": "0.15.18", - "esbuild-windows-64": "0.15.18", - "esbuild-windows-arm64": "0.15.18" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", - "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-android-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", - "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", - "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", - "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", - "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", - "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", - "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", - "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", - "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", - "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", - "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", - "cpu": [ - "mips64el" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", - "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", - "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", - "cpu": [ - "riscv64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-linux-s390x": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", - "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-netbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", - "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-openbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", - "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-sunos-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", - "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", - "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", - "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/esbuild-windows-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", - "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", - "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.36.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.5.0", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-google": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", - "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/exec-sh": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", - "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", - "dev": true - }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "dependencies": { - "globule": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "dev": true, - "dependencies": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/globule/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globule/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", - "dev": true, - "optional": true - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/hsluv": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/hsluv/-/hsluv-0.1.0.tgz", - "integrity": "sha512-ERcanKLAszD2XN3Vh5r5Szkrv9q0oSTudmP0rkiKAGM/3NMc9FLmMZBB7TSqTaXJfSDBOreYTfjezCOYbRKqlw==" - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "dev": true - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "optional": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", - "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "import-local": "^3.0.2", - "jest-cli": "^26.6.3" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-cli/node_modules/jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "fsevents": "^2.1.2" - } - }, - "node_modules/jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "dev": true, - "dependencies": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runner/node_modules/jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - }, - "bin": { - "jest-runtime": "bin/jest-runtime.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runtime/node_modules/jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", - "dev": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", - "dev": true, - "dependencies": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", - "dev": true - }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn-rsa": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/jsbn-rsa/-/jsbn-rsa-1.0.4.tgz", - "integrity": "sha512-unHyEPFGjr6WCzrcMiwdNhYMlq4gXt6Hg5JuKOyE7OXJ7GbVMpottnqsUkPeZCAYqByAkn4N8gJwCpnacduOew==" - }, - "node_modules/jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsdom/node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jsdom/node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/jsdom/node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jsdom/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-fetch-happen/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dev": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/mithril": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/mithril/-/mithril-2.2.2.tgz", - "integrity": "sha512-YRm6eLv2UUaWaWHdH8L+desW9+DN7+oM34CxJv6tT2e1lNVue8bxQlknQeDRn9aKlO8sIujm2wqUHwM+Hb1wGQ==", - "dependencies": { - "ospec": "4.0.0" - }, - "bin": { - "ospec": "ospec/bin/ospec" - } - }, - "node_modules/mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", - "dev": true - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "dev": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "dev": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/node-gyp/node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node_modules/node-notifier": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", - "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", - "dev": true, - "optional": true, - "dependencies": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - } - }, - "node_modules/node-notifier/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true - }, - "node_modules/node-sass": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-8.0.0.tgz", - "integrity": "sha512-jPzqCF2/e6JXw6r3VxfIqYc8tKQdkj5Z/BDATYyG6FL6b/LuYBNFGFVhus0mthcWifHm/JzBpKAd+3eXsWeK/A==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "async-foreach": "^0.1.3", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "lodash": "^4.17.15", - "make-fetch-happen": "^10.0.4", - "meow": "^9.0.0", - "nan": "^2.17.0", - "node-gyp": "^8.4.1", - "sass-graph": "^4.0.1", - "stdout-stream": "^1.4.0", - "true-case-path": "^2.2.1" - }, - "bin": { - "node-sass": "bin/node-sass" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/node-sass/node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "dev": true, - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-sass/node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-sass/node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-sass/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/node-sass/node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-sass/node_modules/cacache/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-sass/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/node-sass/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/node-sass/node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-sass/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-sass/node_modules/minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", - "dev": true, - "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/node-sass/node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-sass/node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-sass/node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "dev": true, - "dependencies": { - "unique-slug": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-sass/node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/node-watch": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz", - "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/noice-json-rpc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/noice-json-rpc/-/noice-json-rpc-1.2.0.tgz", - "integrity": "sha512-Wm+otW+drKzdqlSPoSwj34tUEq/Xj1gX6Cr2avrykvTW4IY7d3ngLmP+PErALzS0s9nYRokXvYDM54sbFvLlDA==" - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", - "dev": true - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ospec": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ospec/-/ospec-4.0.0.tgz", - "integrity": "sha512-MpDtkpscOxHYb4w71v7GB4LBsRuzxZnM+HdwjhzJQzu+5EJvA80yxTaKw+wp5Dmf5RV2/Bg3Uvz2vlI/PhW9Ow==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "ospec": "bin/ospec" - } - }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pixelmatch": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", - "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", - "dev": true, - "dependencies": { - "pngjs": "^6.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "dev": true, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/puppeteer": { - "version": "19.8.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.8.0.tgz", - "integrity": "sha512-MpQClmttCUxv4bVokX/YSXLCU12CUApuRf0rIJyGknYcIrDQNkLUx1N7hNt88Ya4lq9VDsdiDEJ3bcPijqJYPQ==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "cosmiconfig": "8.1.3", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "puppeteer-core": "19.8.0" - } - }, - "node_modules/puppeteer/node_modules/chromium-bidi": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.5.tgz", - "integrity": "sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg==", - "dev": true, - "dependencies": { - "mitt": "3.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/puppeteer/node_modules/devtools-protocol": { - "version": "0.0.1107588", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1107588.tgz", - "integrity": "sha512-yIR+pG9x65Xko7bErCUSQaDLrO/P1p3JUzEk7JCU4DowPcGHkTGUGQapcfcLc4qj0UaALwZ+cr0riFgiqpixcg==", - "dev": true - }, - "node_modules/puppeteer/node_modules/puppeteer-core": { - "version": "19.8.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.8.0.tgz", - "integrity": "sha512-5gBkLR9nae7chWDhI3mpj5QA+hPmjEOW29qw5ap5g51Uo5Lxe5Yip1uyQwZSjg5Wn/eyE9grh2Lyx3m8rPK90A==", - "dev": true, - "dependencies": { - "chromium-bidi": "0.4.5", - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.1107588", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.13.0" - }, - "engines": { - "node": ">=14.14.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-re": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/rollup-plugin-re/-/rollup-plugin-re-1.0.7.tgz", - "integrity": "sha512-TyFf3QaV/eJ/50k4wp5BM0SodGy0Idq0uOgvA1q3gHRwgXLPVX5y3CRKkBuHzKTZPC9CTZX7igKw5UvgjDls8w==", - "dev": true, - "dependencies": { - "magic-string": "^0.16.0", - "rollup-pluginutils": "^2.0.1" - } - }, - "node_modules/rollup-plugin-re/node_modules/magic-string": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.16.0.tgz", - "integrity": "sha512-c4BEos3y6G2qO0B9X7K0FVLOPT9uGrjYwYRLFmDqyl5YMboUviyecnXWp94fJTSMwPw2/sf+CEYt5AGpmklkkQ==", - "dev": true, - "dependencies": { - "vlq": "^0.2.1" - } - }, - "node_modules/rollup-plugin-sourcemaps": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz", - "integrity": "sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.0.9", - "source-map-resolve": "^0.6.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "@types/node": ">=10.0.0", - "rollup": ">=0.31.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "dependencies": { - "estree-walker": "^0.6.1" - } - }, - "node_modules/rollup-pluginutils/node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - }, - "node_modules/rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true, - "engines": { - "node": "6.* || >= 7.*" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "deprecated": "some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added", - "dev": true, - "dependencies": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "bin": { - "sane": "src/cli.js" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/sane/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/sane/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/sane/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sane/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sane/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sane/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/sane/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/sane/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/sass-graph": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", - "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", - "dev": true, - "dependencies": { - "glob": "^7.0.0", - "lodash": "^4.17.11", - "scss-tokenizer": "^0.4.3", - "yargs": "^17.2.1" - }, - "bin": { - "sassgraph": "bin/sassgraph" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/sass-graph/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/sass-graph/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/sass-graph/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/sass-graph/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/sass-graph/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/scss-tokenizer": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", - "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", - "dev": true, - "dependencies": { - "js-base64": "^2.4.9", - "source-map": "^0.7.3" - } - }, - "node_modules/scss-tokenizer/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "dev": true, - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", - "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", - "dev": true - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stdout-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/stdout-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/stdout-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/stdout-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/tar": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", - "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^4.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", - "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/true-case-path": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", - "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==", - "dev": true - }, - "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", - "integrity": "sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/vlq": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", - "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", - "dev": true - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dev": true - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "src/base/utils": { - "name": "custom_utils", - "version": "0.0.1" - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", - "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", - "dev": true - }, - "@babel/core": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", - "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.3", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.3", - "@babel/types": "^7.21.3", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", - "dev": true, - "requires": { - "@babel/types": "^7.21.3", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", - "dev": true, - "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", - "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", - "dev": true - }, - "@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", - "dev": true, - "requires": { - "@babel/types": "^7.20.2" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", - "dev": true - }, - "@babel/helpers": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", - "dev": true, - "requires": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", - "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", - "dev": true - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" - } - }, - "@babel/traverse": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", - "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.3", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", - "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - } - }, - "@esbuild/android-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", - "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", - "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", - "optional": true - }, - "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.3.0" - } - }, - "@eslint-community/regexpp": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.1.tgz", - "integrity": "sha512-BISJ6ZE4xQsuL/FmsyRaiffpq977bMlsKfGHTQrOGFErfByxIe6iZTxPf/00Zon9b9a7iUykfQwejN3s2ZW/Bw==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", - "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.5.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - } - }, - "@eslint/js": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", - "dev": true - }, - "@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true - }, - "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - } - }, - "@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - } - } - } - }, - "@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - } - }, - "@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - } - }, - "@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - } - }, - "@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "node-notifier": "^8.0.0", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - } - }, - "@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", - "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - } - }, - "@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", - "dev": true, - "requires": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - } - }, - "@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - } - }, - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "dev": true, - "requires": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "dev": true, - "requires": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - } - }, - "@popperjs/core": { - "version": "2.11.7", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", - "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==" - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "@rollup/plugin-commonjs": { - "version": "24.0.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz", - "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^8.0.3", - "is-reference": "1.2.1", - "magic-string": "^0.27.0" - }, - "dependencies": { - "@rollup/pluginutils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", - "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", - "dev": true, - "requires": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - } - }, - "@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "@rollup/plugin-node-resolve": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz", - "integrity": "sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.0", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "dependencies": { - "@rollup/pluginutils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", - "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", - "dev": true, - "requires": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - } - }, - "@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", - "dev": true - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - } - } - }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - } - }, - "@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@types/babel__core": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", - "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz", - "integrity": "sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/chrome": { - "version": "0.0.186", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.186.tgz", - "integrity": "sha512-Ykpf95dbv0resO/PcRF/9vKETOKma5D2sSUKo8mSL1vz03IgVhyHuCrlzbDYMLrXIl9CcyGnYTMG2Zg0WAk62w==", - "requires": { - "@types/filesystem": "*", - "@types/har-format": "*" - } - }, - "@types/color-convert": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-1.9.0.tgz", - "integrity": "sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg==", - "requires": { - "@types/color-name": "*" - } - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" - }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "@types/filesystem": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz", - "integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==", - "requires": { - "@types/filewriter": "*" - } - }, - "@types/filewriter": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz", - "integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==" - }, - "@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/har-format": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.10.tgz", - "integrity": "sha512-o0J30wqycjF5miWDKYKKzzOU1ZTLuA42HZ4HE7/zqTOc/jTLdQ5NhYWvsRQo45Nfi1KHoRdNhteSI4BAxTF1Pg==" - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "26.0.24", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz", - "integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==", - "dev": true, - "requires": { - "jest-diff": "^26.0.0", - "pretty-format": "^26.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true - }, - "@types/mithril": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@types/mithril/-/mithril-2.0.12.tgz", - "integrity": "sha512-vedzt04n3EB7rcnfSLCv3+w3qJLkGWdsNRBKvelTqhSJSfg73Roq9b+rcnn9zeqGYtQAMqNcO6vNBR/w0OzipQ==" - }, - "@types/node": { - "version": "14.18.41", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.41.tgz", - "integrity": "sha512-2cfHr8AsUjKx6u4Q+d2eqK51z8+HueoumCQGCKVt95y/yGG4uajOuCANSnE20mbLw94h3tMcddIJ8nYkTu2mFw==" - }, - "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, - "@types/pako": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.4.tgz", - "integrity": "sha512-Z+5bJSm28EXBSUJEgx29ioWeEEHUh6TiMkZHDhLwjc9wVFH+ressbkmX6waUZc5R3Gobn4Qu5llGxaoflZ+yhA==" - }, - "@types/pixelmatch": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.4.tgz", - "integrity": "sha512-HDaSHIAv9kwpMN7zlmwfTv6gax0PiporJOipcrGsVNF3Ba+kryOZc0Pio5pn6NhisgWr7TaajlPEKTbTAypIBQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/pngjs": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.1.tgz", - "integrity": "sha512-J39njbdW1U/6YyVXvC9+1iflZghP8jgRf2ndYghdJb5xL49LYDB+1EuAxfbuJ2IBbWIL3AjHPQhgaTxT3YaYeg==", - "requires": { - "@types/node": "*" - } - }, - "@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true - }, - "@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true - }, - "@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" - }, - "@types/w3c-web-usb": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz", - "integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==" - }, - "@types/yargs": { - "version": "15.0.15", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz", - "integrity": "sha512-IziEYMU9XoVj8hWg7k+UJrXALkGFjWJhn5QFEv9q4p+v40oZhSuC135M38st8XPjICL7Ey4TV64ferBGUoJhBg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.56.0.tgz", - "integrity": "sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg==", - "dev": true, - "requires": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/type-utils": "5.56.0", - "@typescript-eslint/utils": "5.56.0", - "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } - } - }, - "@typescript-eslint/parser": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.56.0.tgz", - "integrity": "sha512-sn1OZmBxUsgxMmR8a8U5QM/Wl+tyqlH//jTqCg8daTAmhAk26L2PFhcqPLlYBhYUJMZJK276qLXlHN3a83o2cg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/typescript-estree": "5.56.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", - "integrity": "sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/visitor-keys": "5.56.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", - "integrity": "sha512-8WxgOgJjWRy6m4xg9KoSHPzBNZeQbGlQOH7l2QEhQID/+YseaFxg5J/DLwWSsi9Axj4e/cCiKx7PVzOq38tY4A==", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.56.0", - "@typescript-eslint/utils": "5.56.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } - } - }, - "@typescript-eslint/types": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.56.0.tgz", - "integrity": "sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz", - "integrity": "sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/visitor-keys": "5.56.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } - } - }, - "@typescript-eslint/utils": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", - "integrity": "sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/typescript-estree": "5.56.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", - "integrity": "sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.56.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } - } - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "agentkeepalive": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", - "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "depd": "^2.0.0", - "humanize-ms": "^1.2.1" - } - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true - }, - "async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" - }, - "babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", - "dev": true, - "requires": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "dependencies": { - "istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true - }, - "cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, - "requires": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - } - }, - "caniuse-lite": { - "version": "1.0.30001470", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001470.tgz", - "integrity": "sha512-065uNwY6QtHCBOExzbV6m236DDhYCCtPmQUCoQtwkVqzud8v5QPidoMr6CoMkC2nfp6nksjttqWQRRh75LqUmA==", - "dev": true - }, - "capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dev": true, - "requires": { - "rsvp": "^4.8.4" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", - "dev": true, - "requires": { - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" - } - }, - "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dev": true, - "requires": { - "node-fetch": "2.6.7" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "custom_utils": { - "version": "file:src/base/utils" - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "dependencies": { - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - } - } - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, - "decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true - } - } - }, - "decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "devtools-protocol": { - "version": "0.0.847576", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.847576.tgz", - "integrity": "sha512-0M8kobnSQE0Jmly7Mhbeq0W/PpZfnuK+WjN2ZRVPbGqYwCHCioAVp84H0TcLimgECcN5H976y5QiXMGBC9JKmg==" - }, - "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true - }, - "dingusjs": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/dingusjs/-/dingusjs-0.0.3.tgz", - "integrity": "sha512-DaVAaUC2npjHpRq7EcZv3SL4ZMgvzCxXOYZmHCXIstFP6f/y/4XAXWtVdsFjxDVTcPfsIM92QV4K25vNVrrX8Q==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "electron-to-chromium": { - "version": "1.4.340", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.340.tgz", - "integrity": "sha512-zx8hqumOqltKsv/MF50yvdAlPF9S/4PXbyfzJS6ZGhbddGkRegdwImmfSVqCkEziYzrIGZ/TlrzBND4FysfkDg==", - "dev": true - }, - "emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "optional": true, - "requires": { - "iconv-lite": "^0.6.2" - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true - }, - "err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "esbuild": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", - "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", - "requires": { - "@esbuild/android-arm": "0.15.18", - "@esbuild/linux-loong64": "0.15.18", - "esbuild-android-64": "0.15.18", - "esbuild-android-arm64": "0.15.18", - "esbuild-darwin-64": "0.15.18", - "esbuild-darwin-arm64": "0.15.18", - "esbuild-freebsd-64": "0.15.18", - "esbuild-freebsd-arm64": "0.15.18", - "esbuild-linux-32": "0.15.18", - "esbuild-linux-64": "0.15.18", - "esbuild-linux-arm": "0.15.18", - "esbuild-linux-arm64": "0.15.18", - "esbuild-linux-mips64le": "0.15.18", - "esbuild-linux-ppc64le": "0.15.18", - "esbuild-linux-riscv64": "0.15.18", - "esbuild-linux-s390x": "0.15.18", - "esbuild-netbsd-64": "0.15.18", - "esbuild-openbsd-64": "0.15.18", - "esbuild-sunos-64": "0.15.18", - "esbuild-windows-32": "0.15.18", - "esbuild-windows-64": "0.15.18", - "esbuild-windows-arm64": "0.15.18" - } - }, - "esbuild-android-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", - "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", - "optional": true - }, - "esbuild-android-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", - "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", - "optional": true - }, - "esbuild-darwin-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", - "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", - "optional": true - }, - "esbuild-darwin-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", - "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", - "optional": true - }, - "esbuild-freebsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", - "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", - "optional": true - }, - "esbuild-freebsd-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", - "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", - "optional": true - }, - "esbuild-linux-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", - "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", - "optional": true - }, - "esbuild-linux-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", - "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", - "optional": true - }, - "esbuild-linux-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", - "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", - "optional": true - }, - "esbuild-linux-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", - "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", - "optional": true - }, - "esbuild-linux-mips64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", - "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", - "optional": true - }, - "esbuild-linux-ppc64le": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", - "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", - "optional": true - }, - "esbuild-linux-riscv64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", - "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", - "optional": true - }, - "esbuild-linux-s390x": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", - "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", - "optional": true - }, - "esbuild-netbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", - "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", - "optional": true - }, - "esbuild-openbsd-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", - "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", - "optional": true - }, - "esbuild-sunos-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", - "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", - "optional": true - }, - "esbuild-windows-32": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", - "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", - "optional": true - }, - "esbuild-windows-64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", - "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", - "optional": true - }, - "esbuild-windows-arm64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", - "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", - "optional": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "eslint": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", - "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.36.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.5.0", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "eslint-config-google": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", - "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" - }, - "exec-sh": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", - "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", - "dev": true - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - } - } - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "requires": { - "is-callable": "^1.1.3" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "requires": { - "globule": "^1.0.0" - } - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", - "dev": true - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "dev": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "dev": true, - "requires": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "dependencies": { - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", - "dev": true, - "optional": true - }, - "hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "hsluv": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/hsluv/-/hsluv-0.1.0.tgz", - "integrity": "sha512-ERcanKLAszD2XN3Vh5r5Szkrv9q0oSTudmP0rkiKAGM/3NMc9FLmMZBB7TSqTaXJfSDBOreYTfjezCOYbRKqlw==" - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==" - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "dev": true - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "requires": { - "builtin-modules": "^3.3.0" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "optional": true - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "import-local": "^3.0.2", - "jest-cli": "^26.6.3" - } - }, - "jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - } - }, - "jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - }, - "dependencies": { - "jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - } - } - } - }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - } - }, - "jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - } - }, - "jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - } - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - } - }, - "jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - } - }, - "jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "dev": true, - "requires": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" - } - }, - "jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*" - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true - }, - "jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" - } - }, - "jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - }, - "dependencies": { - "jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - } - } - } - }, - "jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - }, - "dependencies": { - "jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - } - } - } - }, - "jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, - "jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" - } - }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - } - }, - "jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - } - } - }, - "jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", - "dev": true, - "requires": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" - } - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - } - }, - "js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", - "dev": true - }, - "js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsbn-rsa": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/jsbn-rsa/-/jsbn-rsa-1.0.4.tgz", - "integrity": "sha512-unHyEPFGjr6WCzrcMiwdNhYMlq4gXt6Hg5JuKOyE7OXJ7GbVMpottnqsUkPeZCAYqByAkn4N8gJwCpnacduOew==" - }, - "jsdom": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", - "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", - "dev": true, - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - }, - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "requires": {} - } - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.13" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, - "requires": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true - }, - "map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "dependencies": { - "type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true - } - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - } - }, - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dev": true, - "requires": { - "encoding": "^0.1.12", - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - } - }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "mithril": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/mithril/-/mithril-2.2.2.tgz", - "integrity": "sha512-YRm6eLv2UUaWaWHdH8L+desW9+DN7+oM34CxJv6tT2e1lNVue8bxQlknQeDRn9aKlO8sIujm2wqUHwM+Hb1wGQ==", - "requires": { - "ospec": "4.0.0" - } - }, - "mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", - "dev": true - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "dev": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "dev": true, - "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "dependencies": { - "are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - } - }, - "gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, - "requires": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - } - }, - "npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "requires": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - } - } - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true - }, - "node-notifier": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", - "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==", - "dev": true, - "optional": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "optional": true - } - } - }, - "node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true - }, - "node-sass": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-8.0.0.tgz", - "integrity": "sha512-jPzqCF2/e6JXw6r3VxfIqYc8tKQdkj5Z/BDATYyG6FL6b/LuYBNFGFVhus0mthcWifHm/JzBpKAd+3eXsWeK/A==", - "dev": true, - "requires": { - "async-foreach": "^0.1.3", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "lodash": "^4.17.15", - "make-fetch-happen": "^10.0.4", - "meow": "^9.0.0", - "nan": "^2.17.0", - "node-gyp": "^8.4.1", - "sass-graph": "^4.0.1", - "stdout-stream": "^1.4.0", - "true-case-path": "^2.2.1" - }, - "dependencies": { - "@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "dev": true, - "requires": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - } - }, - "@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "dev": true, - "requires": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - } - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dev": true, - "requires": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" - }, - "dependencies": { - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - } - } - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true - }, - "make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "dev": true, - "requires": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", - "dev": true, - "requires": { - "encoding": "^0.1.13", - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - } - }, - "socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - } - }, - "ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", - "dev": true, - "requires": { - "minipass": "^3.1.1" - } - }, - "unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "dev": true, - "requires": { - "unique-slug": "^3.0.0" - } - }, - "unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - } - } - }, - "node-watch": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz", - "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==", - "dev": true - }, - "noice-json-rpc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/noice-json-rpc/-/noice-json-rpc-1.2.0.tgz", - "integrity": "sha512-Wm+otW+drKzdqlSPoSwj34tUEq/Xj1gX6Cr2avrykvTW4IY7d3ngLmP+PErALzS0s9nYRokXvYDM54sbFvLlDA==" - }, - "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "requires": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "ospec": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ospec/-/ospec-4.0.0.tgz", - "integrity": "sha512-MpDtkpscOxHYb4w71v7GB4LBsRuzxZnM+HdwjhzJQzu+5EJvA80yxTaKw+wp5Dmf5RV2/Bg3Uvz2vlI/PhW9Ow==", - "requires": { - "glob": "^7.1.3" - } - }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "dev": true - }, - "pixelmatch": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", - "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", - "dev": true, - "requires": { - "pngjs": "^6.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - } - } - }, - "pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, - "promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "requires": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - } - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - } - }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true - }, - "puppeteer": { - "version": "19.8.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.8.0.tgz", - "integrity": "sha512-MpQClmttCUxv4bVokX/YSXLCU12CUApuRf0rIJyGknYcIrDQNkLUx1N7hNt88Ya4lq9VDsdiDEJ3bcPijqJYPQ==", - "dev": true, - "requires": { - "cosmiconfig": "8.1.3", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "puppeteer-core": "19.8.0" - }, - "dependencies": { - "chromium-bidi": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.5.tgz", - "integrity": "sha512-rkav9YzRfAshSTG3wNXF7P7yNiI29QAo1xBXElPoCoSQR5n20q3cOyVhDv6S7+GlF/CJ/emUxlQiR0xOPurkGg==", - "dev": true, - "requires": { - "mitt": "3.0.0" - } - }, - "devtools-protocol": { - "version": "0.0.1107588", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1107588.tgz", - "integrity": "sha512-yIR+pG9x65Xko7bErCUSQaDLrO/P1p3JUzEk7JCU4DowPcGHkTGUGQapcfcLc4qj0UaALwZ+cr0riFgiqpixcg==", - "dev": true - }, - "puppeteer-core": { - "version": "19.8.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.8.0.tgz", - "integrity": "sha512-5gBkLR9nae7chWDhI3mpj5QA+hPmjEOW29qw5ap5g51Uo5Lxe5Yip1uyQwZSjg5Wn/eyE9grh2Lyx3m8rPK90A==", - "dev": true, - "requires": { - "chromium-bidi": "0.4.5", - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.1107588", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.13.0" - } - } - } - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "rollup-plugin-re": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/rollup-plugin-re/-/rollup-plugin-re-1.0.7.tgz", - "integrity": "sha512-TyFf3QaV/eJ/50k4wp5BM0SodGy0Idq0uOgvA1q3gHRwgXLPVX5y3CRKkBuHzKTZPC9CTZX7igKw5UvgjDls8w==", - "dev": true, - "requires": { - "magic-string": "^0.16.0", - "rollup-pluginutils": "^2.0.1" - }, - "dependencies": { - "magic-string": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.16.0.tgz", - "integrity": "sha512-c4BEos3y6G2qO0B9X7K0FVLOPT9uGrjYwYRLFmDqyl5YMboUviyecnXWp94fJTSMwPw2/sf+CEYt5AGpmklkkQ==", - "dev": true, - "requires": { - "vlq": "^0.2.1" - } - } - } - }, - "rollup-plugin-sourcemaps": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz", - "integrity": "sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.9", - "source-map-resolve": "^0.6.0" - } - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1" - }, - "dependencies": { - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - } - } - }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, - "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "sass-graph": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", - "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", - "dev": true, - "requires": { - "glob": "^7.0.0", - "lodash": "^4.17.11", - "scss-tokenizer": "^0.4.3", - "yargs": "^17.2.1" - }, - "dependencies": { - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "scss-tokenizer": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", - "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", - "dev": true, - "requires": { - "js-base64": "^2.4.9", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true - } - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - } - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "dev": true, - "requires": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - } - }, - "socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-resolve": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", - "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0" - } - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "requires": { - "minipass": "^3.1.1" - } - }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "stdout-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "tar": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", - "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", - "dev": true, - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^4.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "dependencies": { - "minipass": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", - "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - }, - "dependencies": { - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - } - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true - }, - "true-case-path": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", - "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==", - "dev": true - }, - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true - }, - "unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "dev": true, - "requires": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - } - } - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "dev": true - } - } - }, - "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "dev": true - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" - }, - "v8-to-istanbul": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", - "integrity": "sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true - } - } - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vlq": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", - "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", - "dev": true - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dev": true - }, - "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, - "wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "requires": {} - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/third_party/perfetto/ui/package.json b/third_party/perfetto/ui/package.json deleted file mode 100644 index 0f4d69bb48df..000000000000 --- a/third_party/perfetto/ui/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "perfetto-ui", - "version": "1.0.0", - "description": "Perfetto UI", - "repository": "https://android.googlesource.com/platform/external/perfetto", - "main": "main.js", - "author": "Perfetto Team", - "license": "Apache-2.0", - "dependencies": { - "@popperjs/core": "^2.11.6", - "@types/chrome": "0.0.186", - "@types/color-convert": "^1.9.0", - "@types/filesystem": "^0.0.32", - "@types/mithril": "^2.0.11", - "@types/node": "^14.0.10", - "@types/pako": "^1.0.1", - "@types/pngjs": "^6.0.1", - "@types/uuid": "^8.3.4", - "@types/w3c-web-usb": "^1.0.4", - "color-convert": "^2.0.1", - "custom_utils": "file:src/base/utils", - "devtools-protocol": "0.0.847576", - "esbuild": "^0.15.12", - "events": "^3.1.0", - "hsluv": "^0.1.0", - "immer": "^9.0.2", - "jsbn-rsa": "^1.0.4", - "mithril": "^2.2.0", - "noice-json-rpc": "^1.2.0", - "pako": "^1.0.11", - "protobufjs": "^6.9.0", - "util": "^0.12.3", - "uuid": "^9.0.0" - }, - "devDependencies": { - "@rollup/plugin-commonjs": "^24.0.1", - "@rollup/plugin-node-resolve": "^15.0.1", - "@types/jest": "^26.0.23", - "@types/pixelmatch": "^5.2.3", - "@typescript-eslint/eslint-plugin": "^5.56.0", - "@typescript-eslint/parser": "^5.25.0", - "dingusjs": "^0.0.3", - "eslint": "^8.15.0", - "eslint-config-google": "^0.14.0", - "jest": "^26.6.3", - "node-sass": "^8.0.0", - "node-watch": "^0.7.1", - "pixelmatch": "^5.2.1", - "pngjs": "^6.0.0", - "prettier": "^2.8.0", - "puppeteer": "^19.8.0", - "rollup": "^2.38.5", - "rollup-plugin-re": "^1.0.7", - "rollup-plugin-sourcemaps": "^0.6.3", - "tslib": "^2.5.0", - "typescript": "^4.9.5" - }, - "scripts": { - "build": "node build.js", - "test": "node build.js --run-unittests" - } -} diff --git a/third_party/perfetto/ui/release/OWNERS b/third_party/perfetto/ui/release/OWNERS deleted file mode 100644 index 9d27999f5a49..000000000000 --- a/third_party/perfetto/ui/release/OWNERS +++ /dev/null @@ -1,7 +0,0 @@ -set noparent -eseckler@google.com -hjd@google.com -lalitm@google.com -octaviant@google.com -primiano@google.com -skyostil@google.com diff --git a/third_party/perfetto/ui/release/build_all_channels.py b/third_party/perfetto/ui/release/build_all_channels.py deleted file mode 100755 index 4a61e5e17bce..000000000000 --- a/third_party/perfetto/ui/release/build_all_channels.py +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2021 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" Builds all the revisions in channels.json and deploys them if --upload. - -See go/perfetto-ui-autopush for docs on how this works end-to-end. -""" - -import argparse -import json -import os -import re -import shutil -import subprocess -import sys - -from os.path import dirname -pjoin = os.path.join - -BUCKET_NAME = 'ui.perfetto.dev' -CUR_DIR = dirname(os.path.abspath(__file__)) -ROOT_DIR = dirname(dirname(CUR_DIR)) - - -def check_call_and_log(args): - print(' '.join(args)) - subprocess.check_call(args) - - -def check_output(args): - return subprocess.check_output(args).decode().strip() - - -def version_exists(version): - url = 'https://commondatastorage.googleapis.com/%s/%s/manifest.json' % ( - BUCKET_NAME, version) - return 0 == subprocess.call(['curl', '-fLs', '-o', '/dev/null', url]) - - -def build_git_revision(channel, git_ref, tmp_dir): - workdir = pjoin(tmp_dir, channel) - check_call_and_log(['rm', '-rf', workdir]) - check_call_and_log(['git', 'clone', '--quiet', '--shared', ROOT_DIR, workdir]) - old_cwd = os.getcwd() - os.chdir(workdir) - try: - check_call_and_log(['git', 'reset', '--hard', git_ref]) - check_call_and_log(['git', 'clean', '-dfx']) - git_sha = check_output(['git', 'rev-parse', 'HEAD']) - print('===================================================================') - print('Building UI for channel %s @ %s (%s)' % (channel, git_ref, git_sha)) - print('===================================================================') - version = check_output(['tools/write_version_header.py', '--stdout']) - check_call_and_log(['tools/install-build-deps', '--ui']) - check_call_and_log(['ui/build']) - return version, pjoin(workdir, 'ui/out/dist') - finally: - os.chdir(old_cwd) - - -def build_all_channels(channels, tmp_dir, merged_dist_dir): - channel_map = {} - for chan in channels: - channel = chan['name'] - git_ref = chan['rev'] - # version here is something like "v1.2.3". - version, dist_dir = build_git_revision(channel, git_ref, tmp_dir) - channel_map[channel] = version - check_call_and_log(['cp', '-an', pjoin(dist_dir, version), merged_dist_dir]) - if channel != 'stable': - continue - # Copy also the /index.html and /service_worker.*, but only for the stable - # channel. The /index.html and SW must be shared between all channels, - # because they are all reachable through ui.perfetto.dev/. Both the index - # and the SQ are supposed to be version-independent (go/perfetto-channels). - # If an accidental incompatibility bug sneaks in, we should much rather - # crash canary (or any other channel) rather than stable. Hence why we copy - # the index+sw from the stable channel. - for fname in os.listdir(dist_dir): - fpath = pjoin(dist_dir, fname) - if os.path.isfile(fpath): - check_call_and_log(['cp', '-an', fpath, merged_dist_dir]) - return channel_map - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--upload', action='store_true') - parser.add_argument('--tmp', default='/tmp/perfetto_ui') - args = parser.parse_args() - - # Read the releases.json, which maps channel names to git refs, e.g.: - # {name:'stable', rev:'a0b1c2...0}, {name:'canary', rev:'HEAD'} - channels = [] - with open(pjoin(CUR_DIR, 'channels.json')) as f: - channels = json.load(f)['channels'] - - merged_dist_dir = pjoin(args.tmp, 'dist') - check_call_and_log(['rm', '-rf', merged_dist_dir]) - shutil.os.makedirs(merged_dist_dir) - channel_map = build_all_channels(channels, args.tmp, merged_dist_dir) - - print('Updating index in ' + merged_dist_dir) - with open(pjoin(merged_dist_dir, 'index.html'), 'r+') as f: - index_html = f.read() - f.seek(0, 0) - f.truncate() - index_html = re.sub(r"data-perfetto_version='[^']*'", - "data-perfetto_version='%s'" % json.dumps(channel_map), - index_html) - f.write(index_html) - - if not args.upload: - return - - print('===================================================================') - print('Uploading to gs://%s' % BUCKET_NAME) - print('===================================================================') - cp_cmd = [ - 'gsutil', '-m', '-h', 'Cache-Control:public, max-age=3600', 'cp', '-j', - 'html,js,css,wasm' - ] - for name in os.listdir(merged_dist_dir): - path = pjoin(merged_dist_dir, name) - if os.path.isdir(path): - if version_exists(name): - print('Skipping upload of %s because it already exists on GCS' % name) - continue - check_call_and_log(cp_cmd + ['-r', path, 'gs://%s/' % BUCKET_NAME]) - else: - # /index.html or /service_worker.js{,.map} - check_call_and_log(cp_cmd + [path, 'gs://%s/%s' % (BUCKET_NAME, name)]) - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/third_party/perfetto/ui/release/builder_entrypoint.sh b/third_party/perfetto/ui/release/builder_entrypoint.sh deleted file mode 100755 index 7a940b86cc0f..000000000000 --- a/third_party/perfetto/ui/release/builder_entrypoint.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -# Copyright (C) 2021 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# See go/perfetto-ui-autopush for docs on how this works end-to-end. - -set -exu - -CUR_DUR=$(dirname ${BASH_SOURCE[0]}) - -env -pwd -mount - -# This script will be run in /workspace after the Cloud Build environment has -# pulled the GitHub repo in shallow mode. We want to build off the official -# AOSP repo, not the GitHub mirror though, hence why the clone below. -# GitHub is used only as a trigger. This is because Google Cloud Build doesn't -# support yet triggering from Gerrit. - -cd /workspace/ -ls -A1 | xargs rm -rf -UPSTREAM="https://android.googlesource.com/platform/external/perfetto.git" -git clone $UPSTREAM upstream - -cd upstream/ -git rev-parse HEAD -mkdir /workspace/tmp -python3 -u "$CUR_DUR/build_all_channels.py" --upload --tmp=/workspace/tmp diff --git a/third_party/perfetto/ui/release/channels.json b/third_party/perfetto/ui/release/channels.json deleted file mode 100644 index fb05de45dcf8..000000000000 --- a/third_party/perfetto/ui/release/channels.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "channels": [ - { - "name": "stable", - "rev": "1f0f1aa6babb0b3ff273c5102aa677b2604e9a4e" - }, - { - "name": "canary", - "rev": "056cc57893b57b82ed411256c2bd4091641f55fb" - }, - { - "name": "autopush", - "rev": "HEAD" - } - ] -} - diff --git a/third_party/perfetto/ui/release/roll_branch.py b/third_party/perfetto/ui/release/roll_branch.py deleted file mode 100644 index 25a1e59b3133..000000000000 --- a/third_party/perfetto/ui/release/roll_branch.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (C) 2023 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import json - - -def main(): - [_, channel_name, rev] = sys.argv - - with open('channels.json', 'r') as f: - channels_json = json.load(f) - - found = False - for channel in channels_json['channels']: - if channel['name'] == channel_name: - if channel['rev'] == rev: - print(f'Channel {channel_name} is already at {rev}!') - return - channel['rev'] = rev - found = True - break - - if not found: - print(f'Failed to find channel {channel_name}!') - return - - with open('channels.json', 'w') as f: - json.dump(channels_json, f, indent=2) - # Right now channels.json ends with two line breaks, - # keep it that way to minimise diffs. - f.write('\n\n') - - -if __name__ == "__main__": - main() diff --git a/third_party/perfetto/ui/release/roll_canary.sh b/third_party/perfetto/ui/release/roll_canary.sh deleted file mode 100755 index bb233b1c6635..000000000000 --- a/third_party/perfetto/ui/release/roll_canary.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# Copyright (C) 2023 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -RELEASE_DIR="$(cd -P ${BASH_SOURCE[0]%/*}; pwd)" - -cd $RELEASE_DIR -git remote update -python3 roll_branch.py canary $(git rev-parse origin/ui-canary) diff --git a/third_party/perfetto/ui/release/roll_stable.sh b/third_party/perfetto/ui/release/roll_stable.sh deleted file mode 100755 index 9aaca681f617..000000000000 --- a/third_party/perfetto/ui/release/roll_stable.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# Copyright (C) 2023 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -RELEASE_DIR="$(cd -P ${BASH_SOURCE[0]%/*}; pwd)" - -cd $RELEASE_DIR -git remote update -python3 roll_branch.py stable $(git rev-parse origin/ui-stable) diff --git a/third_party/perfetto/ui/run-all-tests b/third_party/perfetto/ui/run-all-tests deleted file mode 100755 index 7974fa1d44f6..000000000000 --- a/third_party/perfetto/ui/run-all-tests +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# Copyright (C) 2021 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -UI_DIR="$(cd -P ${BASH_SOURCE[0]%/*}; pwd)" - -$UI_DIR/node $UI_DIR/build.js "$@" # Build just once. -$UI_DIR/node $UI_DIR/build.js --no-build --run-unittests "$@" -$UI_DIR/node $UI_DIR/build.js --no-build --run-integrationtests "$@" diff --git a/third_party/perfetto/ui/run-dev-server b/third_party/perfetto/ui/run-dev-server deleted file mode 100755 index 201ed634060a..000000000000 --- a/third_party/perfetto/ui/run-dev-server +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -UI_DIR="$(cd -P ${BASH_SOURCE[0]%/*}; pwd)" -$UI_DIR/node $UI_DIR/build.js --serve --watch "$@" diff --git a/third_party/perfetto/ui/run-integrationtests b/third_party/perfetto/ui/run-integrationtests deleted file mode 100755 index abcbf4ace991..000000000000 --- a/third_party/perfetto/ui/run-integrationtests +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# Copyright (C) 2021 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -UI_DIR="$(cd -P ${BASH_SOURCE[0]%/*}; pwd)" - -$UI_DIR/node $UI_DIR/build.js --run-integrationtests "$@" diff --git a/third_party/perfetto/ui/run-unittests b/third_party/perfetto/ui/run-unittests deleted file mode 100755 index 7ebdf42a34c9..000000000000 --- a/third_party/perfetto/ui/run-unittests +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# Copyright (C) 2021 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -UI_DIR="$(cd -P ${BASH_SOURCE[0]%/*}; pwd)" - -$UI_DIR/node $UI_DIR/build.js --run-unittests "$@" diff --git a/third_party/perfetto/ui/src/assets/.gitignore b/third_party/perfetto/ui/src/assets/.gitignore deleted file mode 100644 index f898216f795f..000000000000 --- a/third_party/perfetto/ui/src/assets/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/vector/ diff --git a/third_party/perfetto/ui/src/assets/analyze_page.scss b/third_party/perfetto/ui/src/assets/analyze_page.scss deleted file mode 100644 index 3d600052b0bd..000000000000 --- a/third_party/perfetto/ui/src/assets/analyze_page.scss +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -.analyze-page { - overflow-y: auto; - overflow-x: hidden; - .query-input { - width: 100%; - background-color: #111; - // When the user resizes the text box, the height is explicitly - // set as an inline style with overrides this style. - min-height: 2em; - height: var(--height-before-resize); - color: rgb(157, 220, 103); - font-size: inherit; - font-family: var(--monospace-font); - line-height: 1.2em; - padding: 0.5em; - overflow: auto; - resize: vertical; - outline: none; - } -} diff --git a/third_party/perfetto/ui/src/assets/brand.png b/third_party/perfetto/ui/src/assets/brand.png deleted file mode 100644 index dc6f8b6f57f1de6d85a4799693ae759b93541d1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4000 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 eVppT_OCY6Vh$m>MasLXr0Vr)lWW` main { - height: 100%; - width: 100%; - padding: 0; - margin: 0; - overscroll-behavior: none; - overflow: hidden; -} - -pre, -code { - font-family: var(--monospace-font); -} - -// This is to minimize Mac vs Linux Chrome Headless rendering differences -// when running UI intergrationtests via puppeteer. -body.testing { - -webkit-font-smoothing: antialiased !important; - font-kerning: none !important; -} - -h1, -h2, -h3 { - font-family: inherit; - font-size: inherit; - font-weight: inherit; - padding: 0; - margin: 0; -} -table { - user-select: text; -} - -body > main { - display: grid; - grid-template-areas: - "sidebar topbar" - "sidebar alerts" - "sidebar page"; - grid-template-rows: auto auto 1fr; - grid-template-columns: auto 1fr; - color: #121212; - overflow: hidden; -} - -body.filedrag::after { - content: "Drop the trace file to open it"; - position: fixed; - z-index: 99; - top: 0; - left: 0; - right: 0; - bottom: 0; - border: 10px dashed #404854; - text-align: center; - font-size: 3rem; - line-height: 100vh; - color: #333; - background: rgba(255, 255, 255, 0.5); -} - -button { - background: none; - color: inherit; - border: none; - padding: 0; - font: inherit; - cursor: pointer; - outline: inherit; -} - -button.close { - font-family: var(--monospace-font); -} - -.full-page-loading-screen { - position: absolute; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - background: #3e4a5a url("assets/logo-3d.png") no-repeat fixed center; -} - -.page { - grid-area: page; - position: relative; - display: flex; - flex-direction: column; - overflow: hidden; -} - -.split-panel { - flex: 1; - display: flex; - flex-flow: row; - position: relative; - overflow: hidden; -} - -.alerts { - grid-area: alerts; - background-color: #f2f2f2; - > div { - font-family: "Roboto", sans-serif; - font-weight: 400; - letter-spacing: 0.25px; - padding: 1rem; - display: flex; - justify-content: space-between; - align-items: center; - button { - width: 24px; - height: 24px; - } - } -} - -.query-table-container { - width: 100%; -} - -@mixin table-font-size { - font-size: 14px; - line-height: 18px; -} - -$table-hover-color: hsl(214, 22%, 90%); - -$table-border-color: rgba(60, 76, 92, 0.4); - -$bottom-tab-padding: 10px; - -@mixin table-component { - @include bottom-panel-font; - @include table-font-size; - - width: 100%; - border-collapse: collapse; - - thead { - font-weight: normal; - } - - tr:hover td { - background-color: $table-hover-color; - } - - tr.header { - border-bottom: 1px solid $table-border-color; - text-align: center; - } - - td { - padding: 2px 1px; - - i.material-icons { - // Shrink the icons inside the table cells to increase the information - // density a little bit. - font-size: 16px; - } - } -} - -.generic-table { - @include table-component; -} - -.pivot-table { - @include table-component; - - thead, - i { - cursor: pointer; - } - td.first { - border-left: 1px solid $table-border-color; - padding-left: 6px; - } - thead td.reorderable-cell { - cursor: grab; - } - .disabled { - cursor: default; - } - .indent { - display: inline-block; - // 16px is the width of expand_more/expand_less icon to pad out cells - // without the button - width: 16px; - } - strong { - font-weight: 400; - } -} - -.query-table { - width: 100%; - font-size: 14px; - border: 0; - thead td { - position: sticky; - top: 0; - background-color: hsl(214, 22%, 90%); - color: #262f3c; - text-align: center; - padding: 1px 3px; - border-style: solid; - border-color: #fff; - border-right-width: 1px; - border-left-width: 1px; - } - tbody tr { - @include transition(); - background-color: hsl(214, 22%, 100%); - font-family: var(--monospace-font); - &:nth-child(even) { - background-color: hsl(214, 22%, 95%); - } - td:first-child { - padding-left: 5px; - } - td:last-child { - padding-right: 5px; - } - &:hover { - background-color: hsl(214, 22%, 90%); - } - &[clickable] { - cursor: pointer; - } - } -} - -.query-error { - padding: 20px 10px; - color: hsl(-10, 50%, 50%); - font-family: "Roboto Condensed", sans-serif; - font-weight: 300; -} - -.dropdown { - display: inline-block; - position: relative; -} - -.dropdown-button { - cursor: pointer; -} - -.popup-menu { - position: absolute; - background-color: white; - right: 0; - box-shadow: 0 0 4px 0 #999; - /* TODO(hjd): Reduce and make z-index use sensible. */ - z-index: 1000; - - &.closed { - display: none; - } - - &.opened { - display: block; - } - - button.open-menu { - border-radius: 0; - margin: 0; - height: auto; - background: transparent; - color: #111; - font-size: 12px; - padding: 0.4em 1em; - white-space: nowrap; - width: 100%; - text-align: right; - line-height: 1; - display: block; // Required in order for inherited white-space property not - // to screw up vertical rendering of the popup menu items. - - &:hover { - background: #c7d0db; - } - } - - .nested-menu { - padding-right: 1em; - } -} - -.track { - display: grid; - grid-template-columns: auto 1fr; - grid-template-rows: 1fr 0; - - &::after { - display: block; - content: ""; - grid-column: 1 / span 2; - border-top: 1px solid var(--track-border-color); - margin-top: -1px; - z-index: 2; - } - - .track-shell { - @include transition(); - padding-left: 10px; - display: grid; - cursor: grab; - grid-template-areas: "title buttons"; - grid-template-columns: 1fr auto; - align-items: center; - width: var(--track-shell-width); - background: #fff; - border-right: 1px solid #c7d0db; - - &.drag { - background-color: #eee; - box-shadow: 0 4px 12px -4px #999 inset; - } - &.drop-before { - box-shadow: 0 4px 2px -1px hsl(213, 40%, 50%) inset; - } - &.drop-after { - box-shadow: 0 -4px 2px -1px hsl(213, 40%, 50%) inset; - } - - &.selected { - background-color: #ebeef9; - } - - &.alternating-thread-track { - background: hsl(214, 22%, 95%); - } - - .chip { - background-color: #bed6ff; - border-radius: $pf-border-radius; - font-size: smaller; - padding: 0 0.1rem; - margin-left: 1ch; - } - - h1 { - grid-area: title; - color: hsl(213, 22%, 30%); - @include track_shell_title(); - } - .track-buttons { - grid-area: buttons; - display: flex; - height: 100%; - align-items: center; - } - .track-button { - @include transition(); - color: rgb(60, 86, 136); - cursor: pointer; - width: 22px; - font-size: 18px; - opacity: 0; - } - - .track-button.show { - opacity: 1; - } - .track-button.full-height { - display: flex; - height: 100%; - align-items: center; - justify-content: center; - - &:hover { - background-color: #ebeef9; - } - } - - &:hover .track-button { - opacity: 1; - } - &.flash { - background-color: #ffe263; - } - } -} - -.scrolling-panel-container { - position: relative; - overflow-x: hidden; - overflow-y: auto; - flex: 1 1 auto; - will-change: transform; // Force layer creation. - display: grid; - grid-template-columns: 1fr; - grid-template-rows: 1fr; - grid-template-areas: "space"; -} - -.details-panel-container { - box-shadow: #0000003b 0px 0px 3px 1px; - position: relative; - overflow-x: hidden; - overflow-y: auto; - flex: 1 1 auto; - // TODO(hjd): This causes the sticky header to flicker when scrolling. - // Is will-change necessary in the details panel? - // will-change: transform; - display: grid; - grid-template-columns: 1fr; - grid-template-rows: 1fr; - grid-template-areas: "space"; -} - -.pinned-panel-container { - position: relative; - // Override top level overflow: hidden so height of this flex item can be - // its content height. - overflow: visible; - box-shadow: 1px 3px 15px rgba(23, 32, 44, 0.3); - z-index: 2; - display: grid; - grid-template-columns: 1fr; - grid-template-rows: 1fr; - grid-template-areas: "space"; -} - -// In the scrolling case, since the canvas is overdrawn and continuously -// repositioned, we need the canvas to be in a div with overflow hidden and -// height equaling the total height of the content to prevent scrolling -// height from growing. -.scroll-limiter { - position: relative; - grid-area: space; - overflow: hidden; -} - -canvas.main-canvas { - z-index: -1; -} - -.panels { - grid-area: space; - user-select: none; -} - -.panel { - position: relative; // Otherwise canvas covers panel dom. - - &.sticky { - position: sticky; - z-index: 3; - top: 0; - background-color: hsl(215, 22%, 19%); - } -} - -.pan-and-zoom-content { - flex: 1; - position: relative; - display: flex; - flex-flow: column nowrap; -} - -.overview-timeline { - height: 70px; -} - -.time-axis-panel { - height: 12px; -} - -.tickbar { - height: 5px; -} - -.notes-panel { - height: 20px; -} - -.x-scrollable { - overflow-x: auto; -} - -header.overview { - display: flex; - align-content: baseline; - background-color: hsl(213, 22%, 82%); - color: hsl(213, 22%, 20%); - font-family: "Roboto Condensed", sans-serif; - font-size: 15px; - font-weight: 400; - padding: 4px 10px; - vertical-align: middle; - border-color: hsl(213, 22%, 75%); - border-style: solid; - border-width: 1px 0; - .code { - font-family: var(--monospace-font); - font-size: 12px; - margin-left: 10px; - color: hsl(213, 22%, 40%); - } - span { - white-space: nowrap; - } - span.code { - text-overflow: ellipsis; - max-width: 50vw; - overflow: hidden; - } - span.spacer { - flex-grow: 1; - } -} - -.text-select { - user-select: text; -} - -button.query-ctrl { - background: #262f3c; - color: white; - border-radius: 10px; - font-size: 10px; - height: 18px; - line-height: 18px; - min-width: 7em; - margin: auto 0 auto 1rem; -} - -.debug-panel-border { - position: absolute; - top: 0; - height: 100%; - width: 100%; - border: 1px solid rgba(69, 187, 73, 0.5); - pointer-events: none; -} - -.perf-stats { - --stroke-color: hsl(217, 39%, 94%); - position: fixed; - bottom: 0; - left: 0; - width: 600px; - color: var(--stroke-color); - font-family: var(--monospace-font); - padding: 10px 24px; - z-index: 100; - background-color: rgba(27, 28, 29, 0.9); - button { - text-decoration: underline; - color: hsl(45, 100%, 48%); - &:hover { - color: hsl(6, 70%, 53%); - } - } - .close-button { - position: absolute; - right: 20px; - top: 10px; - width: 20px; - height: 20px; - color: var(--stroke-color); - } - & > section { - padding: 5px; - border-bottom: 1px solid var(--stroke-color); - } - div { - margin: 2px 0; - } - table, - td, - th { - border: 1px solid var(--stroke-color); - text-align: center; - padding: 4px; - margin: 4px 0; - } - table { - border-collapse: collapse; - } -} - -.track-group-panel { - --collapsed-background: hsla(190, 49%, 97%, 1); - --collapsed-transparent: hsla(190, 49%, 97%, 0); - --expanded-background: hsl(215, 22%, 19%); - --expanded-transparent: hsl(215, 22%, 19%, 0); - display: grid; - grid-template-columns: auto 1fr; - grid-template-rows: 1fr; - transition: background-color 0.4s, color 0.4s; - height: 40px; - &::after { - display: block; - content: ""; - grid-column: 1 / span 2; - border-top: 1px solid var(--track-border-color); - margin-top: -1px; - } - &[collapsed="true"] { - background-color: var(--collapsed-transparent); - .shell { - border-right: 1px solid #c7d0db; - background-color: var(--collapsed-background); - } - .track-button { - color: rgb(60, 86, 136); - } - } - &[collapsed="false"] { - background-color: var(--expanded-transparent); - color: white; - font-weight: bold; - .shell.flash { - color: #121212; - } - .track-button { - color: white; - } - span.chip { - color: #121212; - } - } - .shell { - padding: 4px 4px; - display: grid; - grid-template-areas: "fold-button title check"; - grid-template-columns: 28px 1fr 20px; - align-items: center; - line-height: 1; - width: var(--track-shell-width); - min-height: 40px; - transition: background-color 0.4s; - - .track-title { - user-select: text; - } - - .track-subtitle { - font-size: 0.6rem; - font-weight: normal; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - // Maximum width according to grid-template-columns value for .shell - width: calc(var(--track-shell-width) - 56px); - } - - .chip { - background-color: #bed6ff; - border-radius: 3px; - font-size: smaller; - padding: 0 0.1rem; - margin-left: 1ch; - } - - .title-wrapper { - grid-area: title; - overflow: hidden; - } - h1 { - @include track_shell_title(); - } - .fold-button { - grid-area: fold-button; - } - .track-button { - font-size: 20px; - } - &:hover { - cursor: pointer; - .fold-button { - color: hsl(45, 100%, 48%); - } - } - &.flash { - background-color: #ffe263; - } - &.selected { - background-color: #ebeef9; - } - } - .track-content { - display: grid; - span { - @include track_shell_title(); - align-self: center; - } - } -} - -.time-selection-panel { - height: 10px; -} - -.cookie-consent { - position: absolute; - z-index: 10; - left: 10px; - bottom: 10px; - width: 550px; - background-color: #19212b; - font-size: 14px; - color: rgb(180, 183, 186); - border-radius: 5px; - padding: 20px; - - .buttons { - display: flex; - justify-content: flex-end; - margin-top: 10px; - font-size: 15px; - } - - button { - padding: 10px; - border-radius: 3px; - color: #fff; - margin-left: 5px; - a { - text-decoration: none; - color: #fff; - } - } - button:hover { - background-color: #373f4b; - cursor: pointer; - } -} - -.disallow-selection { - user-select: none; -} - -.pivot-table { - user-select: text; - padding: $bottom-tab-padding; - - button.mode-button { - border-radius: 10px; - padding: 7px; - margin: 5px; - background-color: #c7d0db; - } - - &.query-error { - color: red; - } - - .total-values { - text-align: right; - padding-right: 10px; - } - - .empty-result { - padding: 10px; - } - - td.menu { - text-align: left; - } - - td { - // In context menu icon to go on a separate line. - // In regular pivot table cells, avoids wrapping the icon spacer to go on - // a separate line. - white-space: pre; - } -} - -.name-completion { - input { - width: 90%; - } - min-height: 20vh; - min-width: 30vw; - - .arguments-popup-sizer { - color: transparent; - user-select: none; - height: 0; - } -} - -.reorderable-cell { - &.dragged { - color: gray; - } - - &.highlight-left { - background: linear-gradient(90deg, $table-border-color, transparent 20%); - } - - &.highlight-right { - background: linear-gradient(270deg, $table-border-color, transparent 20%); - } -} - -.history-item { - border-bottom: 1px solid hsl(213, 22%, 75%); - padding: 0.25em 0.5em; - max-height: 150px; - overflow-y: auto; - - pre { - font-size: 10pt; - margin: 0; - overflow-x: auto; - } - - &:hover .history-item-buttons { - visibility: visible; - } -} - -.history-item-buttons { - position: sticky; - float: right; - top: 0; - visibility: hidden; - - button { - margin: 0 0.5rem; - - i.material-icons, - i.material-icons-filled { - font-size: 18px; - } - } -} - -.context-wrapper { - white-space: nowrap; - - i.material-icons { - margin-left: 0; - } -} diff --git a/third_party/perfetto/ui/src/assets/details.scss b/third_party/perfetto/ui/src/assets/details.scss deleted file mode 100644 index 9ba93b3b9f85..000000000000 --- a/third_party/perfetto/ui/src/assets/details.scss +++ /dev/null @@ -1,724 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -.details-content { - display: grid; - grid-template-rows: auto 1fr; - - .handle { - background-color: hsl(215, 1%, 95%); - border: 1px solid rgba(0, 0, 0, 0.1); - border-bottom: none; - cursor: row-resize; - // Disable user selection since this handle is draggable to resize the - // bottom panels. - user-select: none; - height: 28px; - min-height: 28px; - display: grid; - grid-auto-columns: 1fr 60px; - grid-template-areas: "tabs buttons"; - - .tabs { - display: flex; - grid-area: tabs; - overflow: hidden; - - .tab { - font-family: "Roboto Condensed", sans-serif; - color: #3c4b5d; - padding: 3px 10px 0 10px; - margin-top: 3px; - font-size: 13px; - border-radius: 3px 3px 0 0; - background-color: #0000000f; - border-right: solid 1px hsla(0, 0%, 75%, 1); - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - z-index: 5; - box-shadow: #0000003b 0px 0px 3px 1px; - - &[active] { - background-color: white; - &:hover { - cursor: default; - background-color: white; - } - } - - &:hover { - cursor: pointer; - background-color: hsla(0, 0%, 85%, 1); - } - - &:nth-child(1) { - margin-left: 3px; - } - } - } - - i.material-icons { - font-size: 24px; - margin-right: 5px; - margin-top: 1px; - &:hover { - cursor: pointer; - } - &[disabled] { - color: rgb(219, 219, 219); - &:hover { - cursor: default; - } - } - } - - .buttons { - grid-area: buttons; - text-align: right; - } - - .handle-title { - font-family: "Roboto Condensed", sans-serif; - font-weight: 300; - color: #3c4b5d; - margin-left: 5px; - padding: 5px; - font-size: 13px; - } - } -} - -.details-panel { - @include bottom-panel-font; - - .material-icons { - @include transition(0.3s); - font-size: 16px; - margin-left: 5px; - &:hover { - cursor: pointer; - } - &.grey { - border-radius: 3px; - border: 1px solid transparent; - background-color: #e8e8e8; - &:hover { - border: #475566 solid 1px; - } - } - } - - .details-panel-heading { - padding: 10px 0 5px 0; - position: sticky; - top: 0; - - // Relative/absolute/etc.. (non static) elements stack on top of this sticky - // header, so setting the z-index here to 1 forces this header to render - // over the top of other elements in the underlying panels. - z-index: 1; - - display: flex; - background: white; - &.aggregation { - padding-top: 5px; - display: grid; - grid-template-areas: - "description range" - "heading heading"; - grid-template-columns: 1fr auto; - .states { - font-size: 11px; - margin: 0 10px 2px 10px; - display: flex; - overflow: hidden; - .state { - height: 20px; - line-height: 20px; - padding-left: 3px; - padding-right: 3px; - border-left: white 1px solid; - &:hover { - min-width: fit-content; - } - } - } - .time-range { - text-align: right; - font-size: 11px; - font-weight: 400; - margin-right: 5px; - } - table { - grid-area: heading; - } - th { - cursor: pointer; - .material-icons { - margin-left: 2px; - font-size: 18px; - } - } - } - h2 { - font-size: 16px; - font-weight: 400; - padding: 0 10px; - &.split { - width: 50%; - } - } - &.flamegraph-profile { - display: flex; - justify-content: space-between; - align-content: center; - height: 30px; - padding: 0; - font-size: 12px; - * { - align-self: center; - } - .options { - display: inline-flex; - justify-content: space-around; - } - .details { - display: inline-flex; - justify-content: flex-end; - } - .title { - justify-self: start; - margin-left: 5px; - font-size: 14px; - margin-right: 10px; - } - .time { - justify-self: end; - margin-right: 10px; - } - .selected { - justify-self: end; - margin-right: 10px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: 200px; - } - } - } - - table { - @include transition(0.1s); - @include table-font-size; - width: 100%; - // Aggregation panel uses multiple table elements that need to be aligned, - // which is done by using fixed table layout. - table-layout: fixed; - word-wrap: break-word; - padding: 0 10px; - tr:hover { - td, - th { - background-color: $table-hover-color; - - &.no-highlight { - background-color: white; - } - } - } - th { - text-align: left; - width: 30%; - font-weight: normal; - vertical-align: top; - } - td.value { - white-space: pre-wrap; - } - td.padding { - min-width: 10px; - } - .array-index { - text-align: right; - } - } - - .auto-layout { - table-layout: auto; - } - - .slice-details-latency-panel { - // This panel is set to relative to make this panel a positioned element - // This is to allow the absolute text panels below to be positioned relative - // to this panel and not our parent. - position: relative; - font-size: 13px; - user-select: text; - - .text-detail { - font-size: 10px; - } - - .slice-details-wakeup-text { - position: absolute; - left: 40px; - top: 20px; - } - - .slice-details-latency-text { - position: absolute; - left: 106px; - top: 90px; - } - - .slice-details-image { - user-select: none; - width: 360px; - height: 300px; - } - } -} - -.details-table-multicolumn { - display: flex; -} - -.flow-link:hover { - cursor: pointer; - text-decoration: underline; -} - -.flow-info i.material-icons { - color: rgb(60, 86, 136); -} - -.warning { - position: relative; - font-size: 13px; - color: hsl(45, 100%, 48%); -} - -.warning i.material-icons { - font-size: 13px; -} - -.warning .tooltip { - visibility: hidden; - - background-color: white; - color: #3f4040; - box-shadow: 1px 3px 15px rgba(23, 32, 44, 0.3); - - padding: 4px; - border-radius: 4px; - - text-align: center; - white-space: nowrap; - - position: absolute; - z-index: 1; - top: -5px; - left: 105%; -} - -.warning:hover .tooltip { - visibility: visible; -} - -.flow-button { - color: rgb(60, 86, 136); -} - -.half-width-panel { - max-width: 50%; - flex-grow: 1; - height: fit-content; -} - -.notes-editor-panel { - padding: 10px; - display: flex; - flex-direction: column; - height: 100%; - font-family: "Roboto Condensed", sans-serif; - font-weight: 300; - color: #3c4b5d; - - .notes-editor-panel-heading-bar { - display: flex; - padding-bottom: 8px; - font-size: 14px; - .notes-editor-panel-heading { - padding-top: 3px; - } - input { - vertical-align: middle; - } - } - - button { - background: #262f3c; - color: white; - border-radius: 10px; - font-size: 10px; - height: 22px; - line-height: 18px; - min-width: 7em; - margin: auto 0 auto 1rem; - } - - input[type="text"] { - flex-grow: 1; - border-radius: 4px; - border: 1px solid #dcdcdc; - padding: 3px; - margin: 0 10px; - &:focus { - outline: none; - box-shadow: 1px 1px 1px rgba(23, 32, 44, 0.3); - } - } -} - -.sum { - font-weight: bolder; - font-size: 12px; - .sum-data { - border-bottom: 1px solid $table-border-color; - } -} - -.log-panel { - width: 100%; - height: 100%; - display: grid; - grid-template-rows: auto 1fr; - font-family: "Roboto Condensed", sans-serif; - user-select: text; - - header { - position: sticky; - top: 0; - z-index: 1; - background-color: white; - color: #3c4b5d; - padding: 5px; - display: grid; - grid-template-columns: auto auto; - justify-content: space-between; - } - - .log-rows-label { - display: flex; - align-items: center; - } - - .log-filters { - display: flex; - margin-right: 5px; - align-items: center; - - .log-label { - padding-right: 0.35rem; - } - - .tag-container { - height: auto; - min-height: 34px; - padding: 2px; - margin: 2px; - cursor: text; - border-radius: 3px; - display: flex; - align-items: center; - - .chips .chip { - display: inline-block; - width: auto; - float: left; - - background-color: #0878b2; - color: #fff; - border-radius: 3px; - margin: 2px; - overflow: hidden; - - .chip-button { - padding: 4px; - cursor: pointer; - background-color: #054570; - display: inline-block; - } - - .chip-text { - padding: 4px; - display: inline-block; - pointer-events: none; - } - } - - .chip-input { - margin-left: 5px; - } - } - - .filter-widget { - user-select: none; - cursor: pointer; - position: relative; - display: inline-block; - } - - .filter-widget .tooltip { - visibility: hidden; - width: 120px; - background-color: black; - color: #fff; - text-align: center; - border-radius: 6px; - padding: 5px 0; - - /* Position the tooltip */ - position: absolute; - z-index: 1; - top: 130%; - right: 50%; - } - - .filter-widget:hover .tooltip { - visibility: visible; - } - } - - header.stale { - color: grey; - } - - .rows { - position: relative; - direction: ltr; - width: 100%; - - .row { - @include transition(); - position: absolute; - width: 100%; - height: 20px; - line-height: 20px; - background-color: hsl(214, 22%, 100%); - - &.D { - color: hsl(122, 20%, 40%); - } - &.V { - color: hsl(122, 20%, 30%); - } - &.I { - color: hsl(0, 0%, 20%); - } - &.W { - color: hsl(45, 60%, 45%); - } - &.E { - color: hsl(4, 90%, 58%); - } - &.F { - color: hsl(291, 64%, 42%); - } - &.stale { - color: #aaa; - } - &:nth-child(even) { - background-color: hsl(214, 22%, 95%); - } - &:hover { - background-color: $table-hover-color; - } - .cell { - font-size: 11px; - font-family: var(--monospace-font); - white-space: nowrap; - overflow: scroll; - padding-left: 10px; - padding-right: 10px; - display: inline-block; - &:first-child { - padding-left: 5px; - } - &:last-child { - padding-right: 5px; - } - &:only-child { - width: 100%; - } - - // The following children will be used as columns in the table showing - // Android logs. - - // 1.Timestamp - &:nth-child(1) { - width: 7rem; - text-overflow: clip; - text-align: right; - } - // 2.Level - &:nth-child(2) { - width: 4rem; - } - // 3.Tag - &:nth-child(3) { - width: 13rem; - } - - &.with-process { - // 4.Process name - &:nth-child(4) { - width: 18rem; - } - // 5.Message - a long string, will take most of the display space. - &:nth-child(5) { - width: calc(100% - 42rem); - } - } - - &.no-process { - // 4.Message - a long string, will take most of the display space. - &:nth-child(4) { - width: calc(100% - 24rem); - } - } - - &.row-header { - text-align: left; - font-weight: bold; - font-size: 13px; - } - - &.row-header:first-child { - padding-left: 15px; - } - } - } - } -} - -.pf-details-table { - margin: 10px; -} - -.ftrace-panel { - display: grid; - grid-template-rows: auto 1fr; - font-family: "Roboto Condensed", sans-serif; - user-select: text; - - .sticky { - position: sticky; - top: 0; - z-index: 1; - background-color: white; - color: #3c4b5d; - padding: 5px 10px; - display: grid; - grid-template-columns: auto auto; - justify-content: space-between; - } - - .ftrace-rows-label { - display: flex; - align-items: center; - } - - header.stale { - color: grey; - } - - .rows { - position: relative; - direction: ltr; - min-width: 100%; - font-size: 12px; - - .row { - @include transition(); - position: absolute; - min-width: 100%; - line-height: 20px; - background-color: hsl(214, 22%, 100%); - white-space: nowrap; - - &:nth-child(even) { - background-color: hsl(214, 22%, 95%); - } - - &:hover { - background-color: $table-hover-color; - } - - .cell { - font-family: var(--monospace-font); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-right: 8px; - display: inline-block; - - .colour { - display: inline-block; - height: 10px; - width: 10px; - margin-right: 4px; - } - - &:first-child { - margin-left: 8px; - } - - &:last-child { - margin-right: 8px; - } - - &:only-child { - width: 100%; - } - - // Timestamp - &:nth-child(1) { - width: 13em; - // text-align: right; - } - - // Name - &:nth-child(2) { - width: 24em; - } - - // CPU - &:nth-child(3) { - width: 3em; - } - - // Process - &:nth-child(4) { - width: 24em; - } - - &.row-header { - font-weight: bold; - } - } - } - } -} diff --git a/third_party/perfetto/ui/src/assets/favicon.png b/third_party/perfetto/ui/src/assets/favicon.png deleted file mode 100644 index 837b75bc20f23f2948d5d6ed885acc5a6fdaa366..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2238 zcmV;v2toIWP)0aHlCOAC;DkIhFaS9Cd8e1k`3^L^0#<{1fBtihM$^K-fyRuWaJ?xvJl0%9P)2YvEY^HEOl!Uwt~J#o z@EXhTzeo2J;WPNGrak^5V^fpEbf)6)SkvjSSW^Q5C`1EO2dN9|e5^?iwpzPK=*;=y zTJu>C2nz!1#&_Vm9vk8V679Xhbtx-J<8HSUaSkDF<2&))ZAKgbyvWy>MjBKtrP2M2 zW(XRV3IP)>(EXKk$XTO@x3)yU2U|j5Ej%)z}7XORJKP z(%T}*%Z~{)E>vfN-lHwxzt{@C+p<987{b8YVTW=@UubY>1aW?wA8cI|3=>8Tf=FGm zm=MRXO|4_#^cE|z*j0Ru2~IPE{~{ZBm)l*Wn`5CPT9iDE>T+Kw$qxpbElxlLa}~C& zMaf6#OfNBRi>Hfs%eR7NA2)F)PCNW%3*f|m2Q;wzjX<#S2!U+XJo`r@({06jQbpQ;AOJa+l)Fz-sr43%F)&yNwWhf@@z0(ez z-^(O**dVen1#*{ZVd=8|P_iT#_7egp-tPvtcKWi%{He)MUtbRot~mrnI`wXMcuJqd zGK{jlL!+N(ySi=*t+Gk`e4GXT1y<-YJe7$J#Dbd$;`7Jm#6W3b5Zoy1M80|y)Ysi& z1bBG1P_(C9+u#pT`XrW%lpH5uRK*qK^qQ0=k2o=o0pl$Y>OsgGsW-#+5bq z!(TNQm+lQo>d80ZSz8_{mu+^-}glELKsc`S!J%)T;T^*Q*AFK^^ijNttlr;w9JD-?h)- z`VWJjgX?+<3h-}Fx>_dBiiL zUSJu@L(2ns1*n}A$_RkS)f-t;Y0i8CCK~95bSV~hd7Vl!nQ&Mw@-NRTa3lZqk?(l# z{b^-u-+1i8o~DvdfWTaeH0{!}KX_ehJlUoW#WHbO1(CVLLl+`Q!`K;G2Z zy!XeFF@8RM5Y&zjg*)TI;Gc{rH}X#8=vckrEIWrsRo-cHrlXcPyj@_ zjtY(l=cex8n7wU^`0_;}K4KWrK&nN4*q8}$_imjV`Ae6tkU%I^((`t$QB;6}ID>nS zBb;4%dpdRPQ<^1e_0=LDooI#gfBeb%`b$@>lU2`N(#?egs9+C+24>v>7a-gMpH%Vg z-}|{#)dVOl+r=VZRb2xEjW${Jj3beskO0>SqG-EAxNC3~rg>q&MB$hpa{!@wb;wVd zwSYw)QM_rZ{GLScx5zM{!(-%*0+h*_@%g=JQ-yq@EgNoBRkO(7sjUS=`cOr6Iz)!S z8bdrSK$)g-t*)uiZ=G=P(`#y)Dgu0UJQ03jyNL*L3nM>z!;kvGModJ{-!EF{21GDW7-W%2~r zYxgL_=@y`M9Kehb-yn-S)bMANe>*_VwQT`bW zPR>%L-or*MuK-69wd^548^OR_{#hXY*IG;9O7WsTfmeV@&Jk?j(-GVqv_2W5^IUNC!f$kT}^)T%LsiUbh$WPUvHknDBT!fliyu zV>229p`!LiPPH5a6ADw4q1Hl#F&mq#u=jWk1H8sE z7Ad_2Y%{h)ZgC9*yv8zCed&|Iu;dOTtY~0>*I1_Xz2Q0w#N=&<_<|Y+c#UOZJJ4L) z@aTjH4Sez8bKnLnqwhn)KG>%%gUnBg>UN{p3kAnMZ% z!7}!7~xtP>xcsIE{*Xmj><7cZ6$ylBgnfr%C|f& z#~rnm05aTQwdnq0t_>1W1Cq89Kx!Z)xicPO!B5`zD-i?R{$BCq=dG~ zwMPLIZIct*D9Hz@p|Df|qEA*ljL^*+4hJ>4oRwpm)~bXckY zQoT=$Zk86?F0UwmuVC^btJ|x&U0&*e2|18D9hSe%riBhn$T>12DPbuwAtKk+wfK=4 zIfteMQ*07bVse6 - - - - Perfetto UI - - - - - - -
-
-
-Perfetto UI - An unrecoverable problem occurred
-
-If you are seeing this message, something went wrong while loading the UI.
-Please file a bug (details below) and try these remediation steps:
-
-* Force-reload the page with Ctrl+Shift+R (Mac: Meta+Shift+R) or
-  Shift + click on the refresh button.
-
-* Clear all the site storage and caches and reload the page.
-
-* Clear the site data and caches from devtools, following these instructions.
-
-In any case, **FILE A BUG** attaching logs and screenshots from devtools.
-  Googlers:      go/perfetto-ui-bug
-  Non-googlers:  github.com/google/perfetto/issues/new
-
-
-Technical Information: -
-
-
- - - diff --git a/third_party/perfetto/ui/src/assets/logo-128.png b/third_party/perfetto/ui/src/assets/logo-128.png deleted file mode 100644 index 43a2cd1702a35253f645fad20d7669990edd74b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14930 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 QsQ>@~07*qoM6N<$g0s)GNdN!< diff --git a/third_party/perfetto/ui/src/assets/logo-3d.png b/third_party/perfetto/ui/src/assets/logo-3d.png deleted file mode 100644 index b832ae6f6034a086ffbc570c5e2d76255ed0581e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48137 zcmeFY_dA^56E`kF)ToOPqOG!8)ChtoYj<_4_vj@`B6>@7R)XCnN`hFcCegbfYLtX1 ziRdLOqW2y=xA*7!{TH5}9>2IQ*SXJ}nR(63>&zSp`ns^2lx&nlL_{|caCIaR5iyL2 z=(;@l4d5^9Po)HbUnDLnIx0j&mC029>_~}-nm;4dRg3~B*R#p2QcYVg0;>C7H-~M_ zz6rSukp1zB-hOMOkm$OkII)yb2~RP(kd#@4-4-eRde1CVh5iOVzdzQ@!G4R+uUfSE z>@0T=ss8X*MrOclz@L+Fr6DpMsSfe2lT(FdIpyPPfbjqK^MBIw6Z82!wpe^PI9Y|a(BwaG(C+VerX&DswMiErlHJJtt*$O$hrZFra*FyZk)&^4a$K~IABF>Mc&z_&WIIkv~Xfy4?m6J0Max8qDFWm;u+N7YnM zBo}SZ@m{v<&OLjC_98lSVH8qk;SMu<5QS1zh~1<-XS{sNN^>h*MP0e;J^BAku7C}d zJSljG>rIcMx+xaAaL4Kv&Os8}#eB?b#$VlidHr=jCdk7D;-Y+!vh*N5UWl8Xgrt%y zX7YLU(6X%6ajE(?C|&&UWbRv+dp?&fX_MUgZ&!XR;`jK`xWr=Tq6I&-n1yjVk^f0s zeJ2`!d6R|Xn{Kvdwc+g^mQldO4imT#CCew}PE_j*hdcMrPL{2_xCD~u?c{rkT&>EE zqZ1RDa@{Fx1C%}rj}^F6s;XrYHM=F&dhfb%wp#wT@e7fJNsa@{Z-e^d^BwwI^QzV! zKO6;$d>*H7DvBw1T>EDle|>~HZ*hvQe0!Ystl^dSO=^*mBzpch1-}RtXr8qyO6}s& zeeYw5R^tBx@`d27B)0Iv38qtjee_^*u7O?Rj}?!FCiD9thQ%$ED=O?_T{HKnts>RbuJ)!!R9aZ9)-cp9B{1i?S`nv7& zY+2f>t+;GeLp!@(E*l|kkY>sPERgI!aVgxf*0Gnroyb1@eQ1c!{=yvAZM<~D)l)5n zIT3rvaMqCcT#sUjzPY9~bTOJ)=|4dfbX5X`AD=t)Z!q8&4=$5w4SnS8+P8J)AT1;EB&yKf6+euVc>k;%usmEHg z`rRxdpF|==Z7yPC*dw`kJieR-Jc`(iEQB||)k}{=QGYZLrMX&fFud{Oyr_`N$TG+P ze@McJdv2c>%N4I)_gnGLkpwA|8;q1GCXfxlS8Rai)dyy;`n8N}(O2n(Uw!%eoj9)e zPhu56I4A*#j<1h23gl{sQ*5nXEfu$o6{2pV5u+ZiB<;4Zt2m~n5+PAx|G-)fu7IbP zOfZd5$Shth3^a-D-RAu^m~zLXT|8-grK-F0$V{D^d{O;K%#&6Gh$H`))>i^o2Aq#} z^=Gvi2E~FaN!v~M)odR{M01hzNH5OVZ6&^!QK+?>j69y3ce_H_eXg{F$-Ha-FwobD zW+dF?kxN_Vky%*ZP%aum|wI8KK73>9lF63BPbIyNh%^+A*ez}f*w0;X3SEZiT^&6 z=iPf^zSkXYS}(997PqQt5Qgw+*~TwstIKblIy}e&&K)1?pI-N-5U!wS_^vsT6c8RS zB9^DfWQ6BnGIDg~OLzH}PG+U>pybKKXevl1rPW?0+9aM zNA)oJQ-8E8cmQUm&qW@2|Ig3ncRU-9zLoXPO`T=#L1Wg0_4=*TtN)GiKuViu%BBzg zu!LA+S6(baA4~u{NK;v7Znm97Nml5eDGjt$20o-Cd5ZE3xO{{&dIur@!4E(V@pXBy zeyjd$OCZF0SqgpfMy8-RLJE=l-26h9+Tf5XaFQ6nnb-tbl`XXpC51h0Pj>N)2Cz@C zLGaft14<#v4f_F?p>#6&e}58N#0>AGH(yM=Mvmz}?POuv+HW7_1T>M_j%r-LcAu%6 z60l+Kv(omv+dT@8XY;xe8Cr{_s<{87N;;Ku#3J;P%Rt!>tzNGnQHv3yPDt!Y=XCe9I;!Yg?sY z3LKDU2Vc~E{?1*jU$1Xie9k}{Ork=EU-ArSngT%3kTQ~TDWiqT&{mvXX1m09EHxS7 ziz5;pIhDl%bQJZ?CyB0b4(odIc{}kl&kRv`8|j_DS13pmZf|qJ)fiVB7P*_D{>JGY zHmzp*TBVkPet6eCJ0TbZS(aNwl3ab=m_Okbs95|^HV!dt8u;Q$iMn!BQsq9q#f07`bRLs((+~?9UlgT@e02isOquZeuHEnn*xp;2(Yc3e!LBYYf<81yE5Brk-I!{+hB6fOi25mzaQ=`WLZywJA( ze}_nO<%hmGY#2WGF)hO2mrdsj{*sD?VSJF@KOs-*s=Lnm6OrgBJJVBiruW2ky34 z5rp~(r!eDt`}E)hZwxQa6Aj1@Vy88LqVFm86UZhSZxUReqe)jci-hOP8WY^ zuo?5z)?1Y?CG%sQeI?J@j4ZOVE_MhOSWWqfo}^t>-yrjU=T9uJKSen7r@B0tqTN*I zi1W=GT)(W`i}xCnc~9oYIh7df*lbV^*(BVXF+5h>oqIoiT?Voj3UBF3|75z)KWBU~ z7UIdN@~a_*VkBn#ru~vDRTRXfKC(;|^|hI$WZG}AmKc@$pFJtJ|!T#asQ=vDq^wC=Dc1yolqvKBHlpK#jd6M!j)k($tE;CT7Jk zA}=wq${(s5qU+0hv|m6!(XiQ)PFaXy8R2gg$1?wJJ_On!37wdiD7@igQT z!F+phIL6%~J{L*dQ*c%sWaw=7_!RtZWwKm%*`Lswj3^{oX=ip|p zW^U=p`(JFlDCEL%3hlK_bacH5EavLT=dB+i!KOIlpRSH^k=?u&y9aQP#?a6LgiNXxhu~`;$9L(pB+`CMK zFaR_{CCMG^mNv)4k1*Zlf@Lwkk8g{a-~UI%@?_3UR15o zWD0}l+Z*1c-AWxnxjBq8C2R4UbX@962)DZ)KYlNd1=>XqQ7Y$M=a`$jCuZ>oDeq=X zE02tpg8B=(A1~)ivKTV{&+tOw8QIzAs)j!MMwowP2NU$6*Jwio1eMK(_vAL5}8Xx0Jz8u>S7whLvm5{*D6x}O%rZ}O{GD+Ab?>|dgnCY z2ZQaxrtl{h>EGJJ7PRtB7oNf75X^T_-_rLP>+I*WD%Xi#EXNyYEfveJeo`W|O}~hF zp}h!XkGC!JE|GzZR$sWYE6FudoTKOBx*z{OQPeFB1uPAjwan33y}`#}H6D8>RaFCQ zaddvtVsFagQB6$r;zn05Qm!-;EV`~1KEQ<-$s#AEsQqFUeHxI5He6{c~vc&v*ed`y_n(S%ia6!rJzb0I|*NBlvq4=@ei1@Un zza`)k?3_KZS9N^{vs}1IeD1Tq4{*0Fe$%NBR;A7+1Uv5aVKf^;5MEobJWFD?jnyPy zhvlghsxK4H_3bbD704NgK0nouxn*n2s-2%nZvBBCH(7~hH-@Dx!&+D*5R*v=Oe2=t zMqvHkH$&It-vQN+3=+7KXigV2+T=b;n;s4-N*YKkl+@nytu}D)6h3 zFLteXtn-6P_H7us5{`=IIaF#M>6o7SlHq$73>Sj`**yJn8437_{YI4CK(f(-#&hEg zxBQVu|FrT|9ckrCTb8^^%Eh^#{;}|cmRa0S{2neoNX<!U0Q5wTIxlaIy5QG~d8))GCqSYee|OfUe#op|C$z zY3+e?9^6&c>_6Zm4EzYoYzItNk=h+8rm-0Bw9XD%m6SgoZMUZrf^~i0hYr>3?1%l6 z4>)zLX5xoBiTQ@_SsFFX-<*`X^s~U8!-O&pO1HE=*;G@r^>X!PRBeWHJj%qw@+)@j^HRh`(<-gKdxDR zIBJy(jS|TI<(R$kjKv*c;Mc(Zu}pJ#ef;l;2^P#NvLc7*TJX+gfK!gFp92-kTMkvauAgq{BPzxFM_J`OZLhMJTfA>h zyu%;K*5$#RUf(UfE4cvq7G{EC!{o27=Nz$x)e%o^;DzAH{geK&fJXRw&q49UaqajU zQrxFq?y6uFxnMRtP6wi~u`Ui!Xd|ubg71+3WpM5OJWXM~mKEPZIn(GyosF?+UeoF(4lX)aRGo5gj2KJ>L zIS=VB69X#ci*NHs43JK3JMDNSI_r3X5bVd)#xGdg&4_zxJX=gHC8HH$W>&zyQ^y63 zBbf)U>pQGxcbS>$^-&hUTFZ^3@>8sFKM&n|!e%zk|Kn9zc)!S*eJ)(vo|-rErfkX> z1#DVdKeZ%*u<%wu>;{e73R-;Tp;Dof;|I?aR55vNKK6a8wP(2?CJ6yzKqe92%OF=; za*=buW-a;|gy6AUFSD8$*=f6~k%>Erw@%pFJ{XtChVfEBFwN{g%C5O!#zL$)r>y6j zq7TKMfQn1?N^x&X3O%?_@sJqo4a(7c#qMLeHzU6d!ka21{>CfMBFG z*=RTek7{$P3cgVR26-EY^UlaRIxr%Ws0|-@ho?mGx6)gWGQ1CvXdo4pK0H9S0?wHBaiIWN;(Gh{IquzmRp=q`fc14;py-H;n@FV zZ39!M2>AU)|DHP9{O{xdb?`Fj zoy{;>#9&Og^ll@xCndy@UF(4sPmuaPZ}Rp9fi>r=h{-qY%i+Skv08PrT%=`z`3mH9pW#awgOS=C%( z)_D%7W2;=PEvI9v-Wut$aFjfLjQ}t8F**-%Mcn4JZ&6555)NHX1_1N+R+`=ZCFG!x zy`cS=WkM6p*l(c2T-xVSAj${H`1I#}v0AW;$aipx|G7d{Ot|c7Z&$3=D=ukm@}}+^ z2DJPVil?zsPao|vunPB*vd|B0M-Z4^Yfzr+W6qBnm~i6MOiG+N3nh`CC@?+UiBv3D zWid;ung&6CO?^s+2{DSJXqBWcbp9%PNIjE!w{DQpX0W(|?@b`P6D2Q!= z?8P?&X2s2XVtBh_yE$K9NMr7X7kXB`+78l zJa=q+g*x}$Ht8o*v(k#z2YfbuMS;KIDz$EeBYord0f73 z(Y~6nfFV61jS<4%Y=x5-C@v+ToHFKy{fU_yj>g?k07<7dU+j4$yjK7$L8jq$Yu19p z*}`)`0d=}Vx(9JZ}!e-1c8w)NuSWin2l+L7C&>AEwU=! zd8WX@I1}UP8F{Q8M=?qgUf45`LZk99>#gcj760UTmO9nlas6eI+KQNRPsuI{pM4KX zmo|-n!-pyB-ya2>|5~lT1gQ9ns5~0ZgV#b|YYd#k&j(CsPd{JN67>4Xq-lOy@4_H; zew}oGsJa?lfIW+U$dAx{fUsEA3iBtK(#CwsjP~Q0n5nF5<_n;s+1*oK*nq|A+eXWuzst$s)Dg( zAdB87YB_U0FPHpUV+N#AZz%{{Ln>m}T1B>U;C?UrNT;K?0y#g8RRAW8qRcc2P0auAz%vQz!C= z&jL`)N-pWVIo}Kef5`H~ak`_aCHyFQ)N= zp6z-WbT^%XJQN@nDm%~0WGqpuvEJ>!EqB(N-jQ=^Z1z0b&05A|!QuRFm9(U0DwyMJ zxOIf)rf%103!iWk z8XvGXkTK=YjhkA?BEdc_M-f7D08i!etE#TH)n|Pvp^Yk=(ncZYoRGeq_K;EmM76RD zreEj~2uQ+r{waY)IKa5mCKCW{YfOD%T$_7I(b5&_*d$>Ab-t zMBXUJMK{WR6odM878zuz3~YRw@wyd6O~E(RC{(0~#rGdcSYkrQkFklB8&XI|ExARP zsAFqM#naW8s8veo4(|8^N-pZCn8c`)u3BQ{P7*oHCwAldUy6b;HSrBCn<~a39F%NC zDQ`inu>ppwI%FO@DMn8rnwDx5dtOskS1$J(!I#xM>n=k-7|8^G^Pi=B!k3Q!c2kSt zuJ2#TyxT1{wK0RtK3zka+nDH0iFN{WpMYvqdovwmu$xa+D54WJZ3LAQLR>d6?r55omCQ{;7H5|e|TjW9ZI%OlbaX< znoakPRJL!kb*n{#aLGnI&AtUA(y@$v=PatJ=Z1dyU)Q&cs&wXFomz~8q7+qND=R8F zcdJ-={gYF$e9#km>&t(n(@rk2Wg~-`7={EUsK!RJ5Yv^7RMkk&vY>s1-19I zUIHAS#$%_8#UNCofLyC{kwJxhb7WG}d{JhR!vn14gUQ#amN)2*b(HsWrI=|rrBDpr zH|wn(Qo{X)4HPeKm)s}-D4&JG0#lPo{0)UlpSCW)Tb8&MasaDepFtNgy-W9dtbeBC z7VsQ(nTX-AJ}rZpZPDlLD{C=s>DGJ@zN`m@YDqaPJ|6B;6ZY{pu(fqswWbDO;ZX9?hN89N;}F;6gi+cQpIhC|(5M$X8_}zNV$D zo~Pf;>)74r`N`mO5S)7T(_!nd_9&Q zH61TB`!4_KzTp(VJgcb0C$Akht2_(bSzoj7gjQW!PF6?649Lc%*PPe_SEf2N56j{F zugBlwQshIyF}Lask(<16SllTVe}=zZ2hB*DFvc+a?sJeNkoy2s)BM#tdc>IMJu^W! zG=_b{dQ;;|K*4B?!~4I3AD0L8{mOPB!#iq5;m5oHxwr7CvD+1H;4=B(zJ*Mm@L)Yc zQjt;+>8BqbujiIh_qb`ctc0u~h6O1lnDj?FzR=u-9`GX?Y2oRWIvKC81T*wGV3O7P z4AvsDxnbmyzc$09LWvIIDGHJja&QB~txR_!sjVNh#%|7j8pBf?>m>|^`KHovDmpQ7 zR~54F7>RV9!+5Nz&t~EVEY9;Zlb&3V6KQQz4EP$dYNvmw3lp;k#blu}K#EW0A*FW_ zvt+RLa^I{1EmZTr6tYAX;EIK zHl{}kVL=WkP2#4z$jJLOHD5M^8nV)LhY_21)A#6b`P=Kfte(cl%PaethWD}=CXh*i zNo9eIyF7yv`Our%$rXUOWlgkQS9#ivQJ?qPD4s7V;p!56OJSrtZgt==mAp)-Hl}J_ z7%@!T8}^nH!d!agU@qpqVc{CZFIXCnfp>aJj*`AbOOi%mum5(OF!~ZY_0*YoEc=E} z5zp1-q69~I$J;>Po}W8W0laTn#qHJ|yky3vnG%FeEf!rcrhNH0K8bW8SK0c{9k=!O z$!zMI4w>Mk|H*N_lH(8&N#i9u{DspCr%+#R0alZ7Nhq_$kGc$@l=!M@*>~72BAb)D zdRs8}GCd}a96ZX2=jB{^s-KsS+?F*Hff_hug3=5wp(#9xg&HTe=KoR|xN}XPDm?T3$fID_Owg;M3Iir>RBObvnIxqD z471OBgciD|mx5b`@6JCZJmN=$!7Cr$1f199<|G*YmpUu64ef8rUU0j9(?TcKy-I{Z13?WIRt%qIfWoL6D;<%k zN2D=H#l@MRAFuFJnLHzXVUAou2sno8DlmCgE3`G)?_r&nym5*l$s^yUex`HhoE>d_ z?@r_#UZ5S)LdFMb9&!}Ctr5)^P|@=$SonCO z_4Y|9Im2FHSk75}9PCz9d*gkOJp>s+s833M5k@+CI_`0=FpTs{Meaq8LC4A7SJ>QW2BMR2lN*a6q9ouDX;u?8p4CHjd`*p?Py?P3s~*|GEzhb1(cn z4yL?+V0Zz?4wBKI>$5tu_jLW;62;;YKuWQQ;l(X3S!TpABPFYiM*pvAjRLr?j}b3o zL9K5Fqz;U0Qh0$$vw+&Ur1R)>w`H;KW!$~=yne{Ab(8PPTYQmvh|p}&kAb4$mkXPb zbU%_l5?uFEO;-$9NqT-ZTCPhrN377-jb9Uxvjk-xRE<0ma_b}Rlyzo^Nme9*RCRD@ zaA)#qqnNY!pgyVjS<$q-0r@}0FeF!Me^B10;?zYH%1L|onFuL7dFeIN=<MtSGe3#c-~ zg<4irsyW#uun_xcdVc7009z^HjmhwIa{y?K-!MGp0|p1+KJnCPDPMw|>3`sAl>|w= z#YioG|5q?oyg2`~DQvmbRnlnUen7gI)hF-@WcW~VYcykJptxQsO&-D<6MduEz+~|< zUJOYaDCqMq1c7q8IxHY_atw zY}zh>#VZXVl4pViA`u{SOc$v(>SYL=zU4Cy#77Vq-L%8+sO=NvzW7gVSlkT14Me!^ zt+`%$=|^1;EGq03HWWiKo6{kWSXdVuJJr{*RHiH6C02A3?lIrNeWeJ(Q|o%^$FCM9 z3ca-7&xT@obE?-WYIUPSvzgDEZO!j10=(TG7bq;Ole`S)!V63iY zpk__T93m^bErI9M#lXcl)f{Og8-A@-nrGos9i7kM@sW`)cPmhV1i(a!ql-)1yOB4A zZ_XbjR{0 zOzJy-Zs==zU9#)h(QKG}z)ltr3y(Fjg&tanaQLP$yv<%}G|Gp6up)elKPDA_C=r64}ca;0c| z1U*4Q?J5i4k+C?~$ovP=)V9(TnmdX*N1Mv2(bUGvQVDtqsQ%<(LeOKu`;)Fr+@){I zO2yff$G-iE^mPh(O#brQ?^ENNwu)LBK4^vS6=1Hp1eb3K!>rqDjp&^pcy-) z5gb)a3iL_;>*lf1LGO(I008*3>KK-cYtMh?HoVFs-{tf-qUNd$@4pzdZ4v|UU**@G zYM-U682a0T2U^?N=2b#`-R;M*sYd|!dUFGv$xEx*pBe+NQ3an%F(x{-Y#7 zqaO$b1nut*+`VKU<>i{%gK9=q?l&UGyp;~JPbIo~I=Y4$RkFCJ)_K%%{q$I#AMa)e zikn)yIos*|j?abR7)?z~Y8TiI<>W)ajHNy3By-3^aq;whuhEqWgh zoi(vn+R0q7hgrw5Z)OcxwR2^TxitIpGTd?>dWdLGuRjK5wEoycIzE$IM5bE6P^%g! zOlk^z6B`Q8bZKR8k(>8irT@V~WkOPEciQD>&~qGWQDC7T_+=a26e~65xd7J}+QR9Mm=PFCtvuA&??((R5ZF)$(6qUIo28@)u7$c6YM|z(Y zwt|)jEXWH+;O%d)Rz(DLnA*2U(=0jafWHpQ-0+Ed8w=GZ|BAzfEOI zQmJy?0cO|}4Xv^Nedlx@m^skrxd3dTK#|X)x*&1{HpF|Rl)$5tfGdf6&JeUZBKnx^ z0a?0gV|7O-1LWg%mc&CQmMcpc4sLUrs)e|4@X}4?mey( z$BQ^qo$7G|;+=b(DB&}LL08v8qXmbLz^zZJ)dIu+NFsqtQt!z>Gd3kRijT8anqTw= zY-0mAXP!S~5wk7o z5Ze%+`Z%VeF@JRh%~v~1WX5t?L#k}BqGL${VXKH|^>XP_0o? z1E>U<=y~-h2qSdP1*TAO1bKGcXOjq`_&9;o5E!(2Le5$`cyK%53alUI#qh^93I4_N zcihv*v&y`mTPSTF0E$oVDABPD4|3E_7`Qy<093!;O%+Su1{7H`8RB$<>GdH%{lcyN zM1t&->~(x`BfzhqzgrpfP+SOdo;QCPzKj-^47h&#DalX6!l4-}RHRPJr^q*H)Fs@o zhbYlOTanh^T43oeW4qK$B)Im&IZquQv)~h~CTCa50}dOeKUq%y37(l2c6+uF)Mz{T z$KB6s%j+3R6#CqN5B|r7l1lFSy$80Ax6YqW?$?h0Ubc$*WhMk29UQPgeZo0oc|zZ^ zx}d;Z(3_t1w>!WPcOK>fa{Hp_eFsTtPq~jXCK^S`t%N6%MpArDL8euPIK?x);kVtE zf>oEdR(ae>k6t*NK1oEYK-xty`Rn^hsSc;ldG%O{p?&j zrr$h|`(-8wH5@`;zL1ej$5pPY*t;W9Vhn%3o= z*Ps4Q3DeykU{G9Q+|@D{wS*wmzm;ZzG9l34d?L`B%(Cn}#ssj4J$;uZg_ESTa}VXD zzP^gfkX#+LVm1TfNW{R2^hkcBJ5&ARy#FXQ=%ZCT+s-}ef*{^0l_b0DNo2tNd7e;vV$)X3#!wG8iJU2e4wa~xT6X`%Wu-n_k~GUG-@p`l*e{kW3(x*#cgBg&7r z&g2R3NuPZ!VJ0bH^wu|Uc}M2a#N1t=c598{s|i!j3qI5jLB6H(7?*x6xEh#=V)AyX zO@<3Q!uEG!gmrnvS)m#~kzIh4&Yt^4AVnA^)gT2-A_3N;b9B`j z4wrv^@~j98+?eGObQ1ze17@3JQd5-BO>zj*`j1jx#R>#?lh6M98z|N*N5!UmIEshm z`Vn$Nwaof4Z4ZOLqH&0#UIDk5mj$l;kgpir4xY{G&#(VdX9SpAH*Gr=A#F#4&9f%^ z+p+Xs7ot{sd-*uE_}{j9A7c1VcARS$J`}ljBVL*hh1(2IAke@4vpq-L(mCqdmtm5^ zyV5u8?e6)pT0?@E=uhA9D0|^whzlf00$k#SNxw+r5y){w)>!>(~NWrkH58u6&hkd1ZWz4x&(c*u8QHo8nk<^f%#(qU_fBM zEXcZ4INaP#ZIgr;`@sp`tXej^99^z`wu1H@6uT?+sy#?)Y^t*+dUE2*Q}-Y7km(j8 z_I`dpR3uv&Nn6vstLBrz@NuRtJvJ#5mJ@+kG^9=2X|o;*ywq@^pZL<-oEs_tMZx#*a6Y(!9R5lU9EU9WPB?k& z2HW92-GA1ruV#o-V|dH3QF>ox)aNG>9?CD&umRE`JhYJ5l<7s+5XddpUc3^}SjeAU zvVsQmkuX&(muY-`Ob?Jy<*%S^cFhZ{Q78OO8*os0R zn~rrw4y9Jo_q6x2T*|MewR~-GhPX%!t?R$9OaxAJ5~UZ#Ybg>QM?cXP zI_4LepjeeQ{Z$(D7)tMmU zhbWPk!TX9nx6M>I@@4I~BYr&)ib}8_DIDIPzFAYfuXwt?suQ35vPk{pJxWj_jKS|H zL4~wF8hek6&Ic?CyxOTP%cHWh1#(9udjx+M9DR-(c{FK#^rmge$0&?{C7uh>m)MFf z0L8otN*+t_d5v4aOFOEdy81JkWsqY^rIKdqTy-gV4cwz{Jsq!Vs{e(Z{yP;VPySk@ z{wCl~9vaJx4v7E7G#$00_@g5*?q(}ENE4`0%c8TI2E^(g<`4Y`ft*|fOW|$qMb{2-TytUJJ)Gw_|KuI|xNlf$ zYNIOI5JSR;NWl%qA<`<~Juaw12?(2;2A?dxSAJ?a=Popbw~K*Q^Va|0WKn+iSe8po zG>gId^`BJNnYu7(kFTM-)a!ASUS#(}jOrU{d>m!vR10J)Xf(q#JRIhZf2QXbG0oF6 zCK+6QDhFzsEtY$YtK>I92)ZX+Aox`qqB`TLdm~6|&Qt?kPYQwxr7^FX} z3ttR$NuN1AfMlsso1-zT{FGYX!_!L z4YpU@{PaGKE^?*)QHPsN^;yMx;S@du!d3gFSTcjAmhi^f{pvOEY<@+h6=sua6m0D* z0deqg3N4J+Lw&_w-NxZ)8N6V@*TO3!e{j^Zgg@Ie83k%Fmy}5kLw3r1s_RlAl1)04 z`CvDZr8aVkk!f18wOl0Xb#fT7kK=Q>MbevFExY}~ZM7AHx9{)!%SX96Yqd=j2*LCc zZ_^Cax#sQW;1GE638$OmA$LyT9G+K}-v6Dpgntw}LM16sCCck>XfJ)>pd4@h zPmH&3-vE6gZtFTQ_50M1K$}l3^FKs63PJ7r(cT2O3JR6{91(Xn^e*B1$#ebLKiCwo zFwoJ&XPy}O&lATdMQL9VtF}CBW^q^iJw8>^dI#s;htGS_>cHbDSMocc*_ZO(y=bX< z4*B()Y3=(=1US#sGOknrn(3@Ld<~B7o?7-oPrX1k!_l{Vj3CgMA-KWJL(GS}z)L*~ zGny6jN~-JHCU2^Ylf+3hjVh_EeQlROtXdKn3=@jlSu5*@k2BBuk+5&S9j01`R?dxh z`_9A@r|=4U<{GYkOTiT+dY<4aUB2r~a+N7pi}X47sTC91Hk--{=YNy#(ND)r*cM6tDgp&P%rqKLI%D zHt}KPEypzCSUcWcZ0fVO@r)7S_5zepAEqmhbfl7Fz`J_w0d&XF5_sBJ z5y)Y!VL&|7Y%9knFIWi~KIJ%rIIxH4k{rMQ-KMDWZmMy^kXYV@i~N0VAYZ|l>_e~K z;yL_aX68qP_a!0}>^TX&><~&>Ta?iX88_4<1!n^(z+iYD_XChDwnNB67gE59OYtr} zpPYPmzXlbh;@)48s@af8XMz`37>`f`{ZlFVT46lY-luEVQ`T?YvSF<^=p;|mTd3OF zc#<%lGHHj@1N3p)=DEWK$Cn61*dhXKu3X1eKtU@~8lw0MHL1_#LSCxWPFdn?H z2aGO+(N;B1drikLpPEbFB4J-~HWw5?u&razQNa%_@B%PAwT#JF;~Mb9Gm=X#_7*Ev zJ&2fm0DO6o2v@K#A00sBG*O@%06$bD0VF_tuniz{9@r10QShSh8-#&c-`sUXm3mTX zwSx<(;^$O&4e0fq{DkVcg3p4L!VP40n$crge?Hr!Fhly$`disb?-Jy_$uao}XdcI7 zP7%+d$NtA)2qd?rQ~0fZ(jOBY)Yl26FL=RoI(#o1GF5~Y;+_C>A=n(qAns-uXS=*n zRk_8WfODEAzvZM(?Sw&F%xtRbPL_45v@a!KLPYod&m1E>20*$Zc(o$RWq(i8lOQ&I z@X{InQ(Mk|wfPZLh&Se&+dlYspdID;^H2_s-qSRAMt}bN;oLF$Jev(ik+s~1r zT^GW^VX8CE2Ena&s22;n$(>AU$}Z`Vu_j&dRIq6kZp4-Y|_1 z;MrRZ-~&cej5;>o9U_^*3&kQ73F3e{E+~waBLw<@gLhmS0(Ds4!jE8WwKRdp!ua?x z^OSsM$5Q|sf7XLobrY1(x}--Dqp8)t3fDM$ z@RX_uMh7k#KnEOe-}B%;Y$4c21NRj$09kQ~xT3oZBjlnXvg4@VaurBMij=W<%Z z7OSjEqYDMaIbctPPOw{@;RE;ykNg9PQF|4OMt_Bw)@QVaIC=BhMr|PPQpK}u;C|*@ z08XS^ezg)3BX-WAoy`8sy~fb_t_DA153n6vFDU~ATw&22Z$nUAz*tkNz;*qfwNY?? zCBB!HuO!%j;}%-uccDlHJjI9kW1euXEu(S#rsfk(BcnrZos2y(H(YGql}aIw0aQzX zkBlXDf4%`p-t*tp$)RPY0pih{N7Gb(OU*l8%y){nHgD=u0#ui_E%hR;^D&g+;AEm z*u=!d-HMnA3Rs}sY0pp|*Y7(!OYjujQ@r#oV0iW8`_Yv)tE@lcK~SgR2KHqvk4p-^ ziOMVa1&L%*rqfe{PL(s{-Z7}o(&MTX`f#uv+(SDk<8gd^3cwl|d|Qr)xccjWLJ z3Ii+coiJvt;o7y4ECvdu>48AfqdxcP{rnWX+u=%aOfF(Iu4jhzScCjv^TFGI-txQE(X1_t$5R~8&1sXu8A4> zbN?;7vDXBuu&e#I2s57PaozAog#b%wKKX!^tSg*T(PExHD4*BWAeH5@U?z26LFut{ zCHRaK#rc)C92DLzqwKu93beQ3RYG>(-)P+Lyq)bmtxY-j%dL_92QUfy1H2VF;)Vjc z$OP%3c-{|!R8SwX3Z|4XAHg!U59>9sGynd{_%2mHu6oO`L%GRHC97}NW`Hw(5&$KY zMG8+{4_3SNR^&@6$HyZ!%D!7afDz!-8dy;VC4FJzBp$Y;?H#C8DJ57;j|eVfhkgTC zN6*&*%OX_(lYbBfn|I*on9Qn%=>@`HH>`D8H}qzavi$#{>pjESe#5u%*61*cP$Nnu zNGNK@tQCn_iB(lvrS=YLRE<^nMdaJI_%E2_RM4d!zV-9|tzkGOxE3(;4U#y2Jr>xrG__1<3;nD^@t^@9&0OyMa0NwaYokrR(r2#1zXkIaWyp zW^qESF7dzXV<^31xPlHa#oVdcQxY=a-fvGj17iI;YnvnhF#!P7AI5-z#SDEu12PT?W$4H7pKeyVxYvmyLx(FMfeG_!Qp z_0Q#kc}s!(fGA{j6A;_%xoY97E^}$)uceZY2c8Qx^swEmRPXM~ex!qWTjNue46%LJ za_?^do?kAv5f6C0C_U#iI!;C!#$Gl5G^j2vPQv!)W$QPtdmQeS$?rzcF4y5s#;T&MEa&dt~~4$T!A*l%I$dMm6o{P_2Ua z;HrAd4BHyEr0x<~dodDvg}GFnkqxAB{2Ip?0w?$9yzZeA*cJQOxcN1r8 zxV#=G389HfTR0;F%av>2>v%5K5&tad^4m1-!)T zSv}`8FMyN%5LBa(WIj2&)(6;Sv@cYS?=>>aAoJT$rgK$w5m>_Jqj-HW zHgWbf;9-I&h5l!fE2I}eu_G}uUYIf@I zK#|ymnGY8RDSl-rymmY#8=LVsg63c?cG6)SxYLzk6!1Dl;r^toubIs|ez3g`kl3Mb z5q>W<6JhYpZJ{DCG31Aamti6m(~-mq*UFSYLMjl=Afr2G3;-dA|7}~H1p*XqH0LEB zV0W$502ML78kXGjZFT1NDt_Usc;_{{b zZ=w9HlznBzzBWfMCywgKDK!|UsSus7gXY-cV~9@=_buP4I(AC}Qzxq2y6yxB0Kc<& zHxf>gggU;F>CdN;!*8{QvHDIy$ya=A91z51TIBH)|;D|5LGK;X4os7+M*iPf8=VI9)}i=@0%18Um`n zE~3bujR;clS{fjml{>_V=oxs-ITUZZi7J(q&9nTEPdIYbh9padXy57R+Rm*AAVQGq zuim&PoX8p`euJU_VQ8ARKJh2rsSIM#34gE^;0(`fE@8qt*ePtah*`m1UxRrJL2`kd z!k=1P{NZJM>IuNQJjX>;+Mf^T;QC(u#9fbHt`Fu zk4f6uG#+>~i%f%TF7oXVIEzsE4W^K&cO$IWr>YR743IosZPGEnzRx#dKv1o5q3uh* zvM>vA>m@3Gs<}Ppg_Q^V=r0HVV@Y~o{?=Rm#?37U}O1LS~N;!493>t zhIKE;9TflYT$Mqcu0Zg2D&+AT3l-VCTo*Rn>g#ZQ*1>Hr?}k(Gu22z5AqjH#$%g-5 zkGv(UAH#indidsqS8=m=Lwk`H-dzI8nKvAmVSw>IN>|3pW!JPT$if??f`D^vU6DeB zwp`z;GETM$HcFn^SuSA&@*W)--`%T4y5toq*}i4q?YeKrkPZJ<XbC&1lX((%^ zuFrH9!UC=tSz*t@eFNY<*~70u58MkMH?TUKE(5++@rpC)=G$vtRebt3p^KYcY^MXe z`aUfs)=!wect4fvyUe2sD|8Lsm6bhh-5b=7yuNuL28hx>fw5VFrzx;^YpC%m7Nky( zb#Q}={mbs`o4Mu&X8`(25E#^+FPR)&^_<+e@f??^HzX1#9P5?5B=eNPla5#f?(^C| zmt*1gv?eB9nE&?Zp|at|veT^iG0!rDS(LCsk=*%mL_kK)(q{By5No;e0#*R%| zAoz{fkFVEjtt~^EXDh1Z52XZALS4k0pXdLH8o@Ie;Z>8=k}jV@m?xOp#V}!V!jq5% z@q#lDUWg+punsc~Jw^eQX3%IBbVXgzrc=T0_A~lxk%(tt3D-HiWYEKBz$(gTCguLT zxxV-7RqxkwoYYp9e-TQeeD7RBuHul>fxa=`5I7gM=VUk#ypi$bSOFG-WejU~PPmvvW9E2M4XR??dOPmY&`(s^z|~87KHFnZjKE!+iG}&g*cx5_QA>m5r^J?c^@T+;=4VwwHUkTM@V= zUxs@w3Z(R&+6C}?n^>S-h&5Xc#4htIW$v~=H2v2sbydqq574lKT*W}O;QMyKxJi`e z-{tEZ=Aswkx~xV^_i%4T23y*#iI7q-ei-Wy9xqQ*+;^<^Hvk*pD~t06^tK6TyY+?EW{e`2kp3>n^Z$ zxGt5%$5_?6i*MH>DgtC)7j`r6pS;e;$R7iFk~mgi@cTq?P(e=r+3L8^S-!(YIy%;x zakp*j+JJxNIpzL0+Q*c|jT1n;OW%>ny^h@2|dKy zUT;;@?P+&4=2DoKa~k8CQIF3Jy8NcDleN77bdUJ<%-I?f-azvk^s! ze*kX(|6y88rix!G2u>z50@X^tkw>0ER}qi+|K$6ICs;G5!)&ilPzHgJ3s{FJm3S=~ z1K_iA2LFAie0O-Sn6Y(6P%J<{%G$b`m+pz&@z#MdW_@Vfyqm3d%nC!WEt#+=R^*D6 zKqXPnQDK_1*k#Xj8aO-fNJdL&|E;_DNB2ccJL(~Z_!F9j*Mi5s0z}E`Q)(tl7~tIK zh{n5DOZ^%j-bCYP`g#3}J~$dCtk$?8%RVz58WXghZP~f~nUll0%`hfhU@y{rqpo!0 z*JtJi2PuG^o-y8fI<;k%@hgpN?5KCj7(`i#?vv>yk+1zl|DSs0)7?v;dsZ}j^@bE0 z&tE;zl4E5``Dvpvb-cG&VAO;KXsynuS3qQ}%a^OF1FMCIY~W4N7)6w4-V>0O5x%pl z(b>)-6pqi)Cr+T?4=z%>NKnapSchqC{5n0C9zeEgeiQijxxj3p_-X!^zqXVQ{3i}+ zVCFrOiDUWp@$EOuCEH`lUBnN`kgZGIU6=%Dp>h$lRAxXW9Ap%{J+2H>&vL9lpbu{V zr7{#LOb|u3ECIChK{)C85vQ+xHx;&a#YHxNC?qm2_Mf>O7Cl_QF813}+vHoejoO_) zc1SLrR}P&BO1mC>T$=_yW?v(`^qY+dBdcmZ(>EsM=IZ$}>0xEyq{Gv$Ga4&r(25;- z8?l9JS#mNN=~zmkfmgvU?tI7oc$Gt4&$2f^WrAal7kfKOy5`RN-e1eM2%u&Mcx6hU zu0~u82;2JcCL|R+s{YWSe@q$RfVBuu*|IFVu6Oqkr-k*lu_H>6dPF;43RccdnA&w8 zE_Pz}8w=vjGDrPCWW=YR3MG#@eO3s&(G^QqRfKb^{t)wAx0(KzHVUP3QXg zt*{%+N@XWdxK^PQ>ZWjM%m_q1^}}za1_NSM;6VA z{Blbb-q=npNdjRQ-NjDL?{y-#2h)Y{bhP04zH4?R#ukaWq(J7MrwckB!>OX6Q1 zeb@^7?|^)k+Q;r>(O#D(X-Sr;m_9yK%o0W&kGx|Kl6W8oMIjz8fAZaphbA0>unzX{ z#f)@IpvJj{mjv`~q^K>Nf1$G^@}?5we?!kw+jh6e2E;GE%N$CPRjZ)Sk<1@q?(tP+0TS4Yd z-JSbzuVHC1^%HuX^H`me<5knSQUPs1F?*EFN9C#NlhlG1f&c0fK-7{yf1b%GFt&3%hn~v5tPWn2O9})ynM;yY@TF+L| zq`C`JTlFA+16WcWh(|>J>(_dRo%32JVIlixZBYK(%z270AHCL+{ZjwpUW4^kNpD&q zg)*BZY~cIcgJ(Z7!Kd!^yDy#RmS_}&{Y<^Iz9*e|m-JMFU9bj3Q@VReaNpM}*DIIE zlJ-E)sV7&d+UaEA_P^Ao+0@J5WMxBsOND%Qs%TxJSN7f&4qg`)?B0T5>jsQ&{4~T& zOoz&ae)KxSmz)vwSD@tn#*7_`IErvZ4JI0EV8+kr#Y~x6y-mp#`$;Rz(rry&>R0YX z-8QGJO#CG@6f5(}>}s2yi}Ow~ZJL%xY);n~-0QPm#MI~mJxrT9Hv>-z1Pw`R&5<7Y z_2JB_3~0;JWS94zJr}Ym?BhldCjB+!T_J6EcM&g#Wj%TfZ= zk6#7~0e6$P{eC{XSEG0}jp1vW^6Kp7p9}6`jF~cLP)GJgFK;j22K(z`ThBuE63MGy zVNE~pUmbrf3O=hh_t}WGr8Iv%z_OsEdFxW&mpzet_A;^oE!rz_*>OS`1bYN|o46}u zbQaKCRo6Hwv{!Sf+#$p11a53!%Wca{*zsyC<~LiVYbin#<>(;&(;5ksZE0XrpFq@% zcvRykNoxszqV=V6zVm?jy{3O?)9ZN!;ELAANtE^A1=vs=L`rbOkrP?hz=&KED9UJ> zJL~?b0<`rgS^x5^2s1u@5f(#6g`H%S56l)cg+n_g1Rkp0v1N|0r+V!Fl<x_+`dACTEFFV4%JOxp8J8AS!*33#OMS1B>%7$vhnWniuRBZ>&aLs>~^MeMWfnrfKe+gLV_h4KoGKgUk&0lM7#f z`y7Rpn2o=R1_W|KkJ@W7C|v&-7R4pc42ai6#{byjH$*85p*i=?0uu5oo+J-@2zK;!^Z# zLL{hh)`>+o`-2ja(EKlOdo}`6vr|ihqR8-FYYC6SRh}mpipa;qsu)nB?sY0XQ(Nl3 z=kNiuV$%44oOtpFML6t$#&uEQ z28|f)9Sb{#nQWO!xp>5A>JB)F_1AO@A9#6OOoi4_x@n?l5}> z;Y&mEd}Q(%qoaKbvm;@DNK-(7Gj=Q^xxO#o@lh{k3D(S)njTj?P?H7rDOSF<2gEjswwv~5YGJioJk~4}jviRd$sUIB)98O7Db>ojCd;f=^V9qDNIuClGv+8yCEzuzV4}(8p z=*DGp*QRsm&CFWJUDKTpZ%s`6a+aO&V{XpfEd+(Fohx|2jMgW}(fSN$STFwa3uwO1 z?XyZ>1`fIVD;qe^99r+?Y{SCJFL0e?i{4Q^nxkIs}r`boEY1Z&bhu8K0Nq+X)LASZ?nQSmsE*dD(Mhz0U}z9xM3p z^D%6*XfVYg5P1>T@$L6-^)$a z&-Ckp3l)Cq4|2ipavQ%RazT&hPsUwl9@GW01FstHdW@YT?n>=b)_*)7srk#zSz{~y zT_3Y!oZs~x+^}z{K|&2%$UeU!)lG|l_E0!F8KVccse@)_bt8lOVyA1BMjRM>Ar=kG z0%rS?=6ql8p{vfKWGG#w2<5YSbpn(_&QtVJI#)Z8(TVXEjwSm2?3MezDRrkFyDMxo zu^SmlhF~dObM?l2_}@3^KpCHe$5mOb2SRsT*M?{d5tdNvD?RM2kbau549a)Q+!yE9 zwp!-b4Ra6&EZ!rug!iKV|5Zo> z?v4QByK?J{vF6ZN-f**ap2`(u^W^BO1>lxTP{K0oHX~>vhFpHQz4cp(USv9X^FX+! zsVd^JMK<$2bfzMs={kT#Y42j*x|!W-8u_{J>mDrO?I22+9N?Eoc5UMTUi(A%%Hks@ zaP7%$L^>FI0)?Y?%HuCjpT8i<3{)PLMpIpx_Q!CLg&uj?MQbpPmK9hZX4h{3Mnjmc z)~~5=O!br!r_7Os>qdgqZe(~GUh&|GN66xN!C^Odz%4u3{D`WHC>P-!DwvLTGlS~p z@e!jx%r;J0pPH+;lkc+%ZF`OOl_7l7A^IbfXK9*Uz8c7VpLb%>7+f29wf!;WhN%U} zW`aQO{0tMzsl5VYe@d3HoPDv;K!1JNpn!%fJX*bRekFC+m2J|e>1yS&@Gy}|OndLF zt(ytjR~#6Opj@AaNj}V+<_LtKWx^p+1tr$d^2zTAs1h4$K?6ufm2=ldyy*MX{*)fW zly{YkZ1jp5Gxp^u7Z4;_Q@6?WZVeHQ1#)wG z7WzE9y+qXD?9*Fi6)E<1;cM${7;bM+mhBp0W!I4(Jrn`)thrw5BWtU}qC-md9+?gG z^te@-xQE!RA=RuPGehP%_)K0%w#z#(HFb;|Y!VemL&K?Nsde&vyY3 z#5LeQ_wLhd(g-6VTqr0BWDHiih_q|RT>qL!foCPc(Fa{}_|+>%PdCR^HG^bX&~ZXx z!@>;su`|fp9C&bIt!+?c(i-nl<22%cOOFG1OT|{Xh1n0G?zLR}d88l_$Ah8JEyJAU5!U?W*%m)W=aR*L1Mp_y>Lfi5#A$>_eE2UY`%7Vy`%v4 zpY&9Gu)`<^ymxZWWLJ9h?jP7=S4%kv*et*=B8fF0MW7S0 z5K&4O9Klc&FoTnOFf2{4_wPb1S?Rnwd6H3}u$kvjSk1IU962Rl2|A0UsU1+;Lrwaw zsHLf{aethN0yetGF#kL!w8cD~CItb#Xr$ywmQ42q%_b6nDk;yGFI** zA4ed)9({TA6y~WeBiB_I&c4=rZ&%P&w5aCL3irv|@W&5}=hy$}vqLuMU0XK4i&C8b zMKj>9i2SQLX0HUjf=JVaa*-sV-A(j2XI$9VJt68UzT`wNf7lWW#II6}z<=4fi^D96JEO zA)ASVIKHC(V;blE_^IvAK_BRcVOC&iR^h2ol^x&QM8Y54FD#U(6YR#eM{Y7Lilq$` zGTvCo?agM8}&sENb>W z=6aoD$1tE@l?ckL8x&@$ z#(yPm{2m#srNmUr`Y@%AUQ60;U2Q;kFAIVbdfeN>#;SrIq;IL2blb>YxvwF?JvN8g7{D$iE_DwRfm$T!n1rke|-XI_|fsqjPVE~Vbj9q z${oHxn~J<9(lc4OrQxox#eN>@sobL zcY`U+DQ|zAWw;iU`75(-vE`B@m>y?+fM~SQ9CG`6hVXh+=p_ivCawucj-$B4=A`k>H56@`j|p;`z7Dfb+~uWor2cxu%&_gN~r zil;f01?J0vcmGI?B)7dkUwUu2*zDEbi{I{lwUF2|Xa*HRq*A5=!80et_$>{orjpA@ zKcDvEYrCkygHMJGBb7kZ*t!j6RX-cX&Z2U;S)HMU!WYH7$7&qz5 zEDncAz%t3K$eXPUpbX^Ts(5yk&=inv-5#Mw+R#Fk&Y|Xn;Gx%MNXeR&=U&ZJ?2nSA z?T~CvZe@uKUzgn_J8@pQPXOcUU=uA5ymHPK`VyV#ujvep>KuG*DR=w&iHdEBQ0edY zq7f8NOh@v7d8SFt@#e>Hq2z|8?l~1MWZF`R9xc966MKRc?7j#DWRl#o?e2JrC5?J` z3B*f0=>GjXw+6WAae4qMmRmu$jz8hPo9y7`VetswRB0Yssl`C(UOdE$vQCw&C>jc~ zH0{e2RZE0~oyQ+BYYm)czWDgx$UjeeMXe_9pek1 z?~qiPDVK2^8;#2tI~3-Xtjz=StdV`$ftCo!`K6d-^VTy@q!z(Y|LU1-2k=($M6V<* zKA52r+NY)?e_4G&6TG!`3(f%a^dBAQHZEs=j1XciKqG(}H_niJ0qrYCh-?rIy>|Tm zy9D%Dq1AynU6n? zGLTqI`Zhn>0E=RzIL8%j;WUWpU~`iim&!XZ_9#kQO2Q&$L_X^Y`h9?k=sYaF2>^m+ zUjXNl^jVz}ieIqb=clE#{CqufXXx5-%O{-YJemgVBVtiduDECdEw3jjd~0c1QbB4E zU#CLp^>E*iBoE;GC7OKrT3Nj9U-tT+Fg#+%(#Dek8G&TAjw5>}fo>hx--LAQAsALA zg+KaEM^o2Cg+JTPUHF&}OP`>BGc@;6b$m=&>O29cPwKN>(lvx)z7lKj__c4ANxSL0 zh+j9ED?m19Koe19(Z?vDw}{z$$-HV(vmtwELI+jiMdc88hqHAqXN&8Tv#`2KEXeGj z2z|?GVAyj6=zENzFXzgLq!~tA(0~0O9s;7;N&TN1<~ka1cK@G>GGv zn&3n`(2Bg$8RU#dopq65>B{~2F3kqh?rNorLj~(jC(uS={~8FUBP03l7Ct0>?aTlD z#39Pg>HbiRpslqYHp#4I6Z3JYOF)O1mMWBwX2M_j$L}#2Qal1HlZhZ7Arldd$Sb-e zCd4NAL}PP`1vS{qfcG@uU%CbIapW9pSiQ2URGX`$a5JmJW zjHIHnqapOjpoFZh8&*^LgM}y%sD%L(#EX0(shCLaW6{ObCGb7gidgk%BewrC3}}vo zI)$42ejcQ*yy*?vGMAxzR?Ev=XxIya%eN8Z5sT1OyS)pV&GA(7L`dmDWOREXC9Su6 zuHK3d@90T`r(6F0ajwuPWLX!pXGnof>imrpNM%HdzB%*n@`_Z*SSp&tiF&8njTXBg zg@3%TUuyj?J6?!W_u^~0@<9BH?Yp_wUvm-x&wtPMJv!U0g(+Ew?q?w0ajT$=@k*5d zP);adY}@GpcVGIEtN}!;Q_CSyx@gfPeqdbbtAyFAAmNR_**c9%4{DY$BP5(*H-#R9Z>r5|vi3r(^gG$!`gb9MtuggVW$U3Y@?5vs1c0We8m+ zmz;;Lym6-`fYUyT5Q-Hg?&t+q3r6K;bE8n;FQleA)@Fs&Mz51@oGS$9Gv8$g)LSKY zp2l8{6a<1qGI2NG8~oQgz%-WyWD`sWlSv?dY>p@Qfo>h8H(^F3NV&*#nuhH@u5Uz04aaNLz58*sg@Q+=y4AAejgazPDgs2 zmZkHsII%GFr!Npq~UR-Yc721t+neRd(79CP@6T1b0(Tlu0jmtLa_0$$i9@GK- znM_R0(q!>^>D^HJ>GUp-#MUJj&`JnDQc{D|1rvi=-Zb5?<)QcfUMEH1;cE zl`qj(OG1+In`w=|_`7k=vN&J`1qj{@%*~TvLmJ>A;s`3|epDx>S27>QB#z|U?*fp{ zcQsgh|A7W_a7lw`kYr}9^t{4A;QM1i|Ci92JZa+@p>a-~>7N9?Yu@79;vCNm;)MEt z>74~)lix}Rajjo9`W)d-48&M}5NvpcjczcU%FjT09LP>MV!;3M%U_qtM|bHDcJ?Or zgce4-n>>TvoYk$=%{TFzvn~zjGraM2@go?QRq-Nsq_;9-E7o}_dw-P5ZE<%V6)}#JU2LwdidzrmXgC|S^ z{6>7tU?ss2cHN74&Au_{MT~a*ac5pd09S)a(eM8IP1A61gL5{TpaM=6bN}8}tFD9gI z8|L~0`M7n`w^%Yl?XIoogdXdWA>88ZB_ZFXCr#`v>wwg*>pcR+A<9?gJcKqrE z6P+fnGk8PF`a)eH3iBb}zf*m-jJysP`|^w4+nyacv35f@w(lXYF7dfH$k1noeKIpg zyN(9+tV&5DqfE8_w%;Qan;dh*0i(CWMr~uy+-<)F=Q{ziA~6>hk)XcQ)oKrJu~VEC zO_qf~L2RgZpi!y9KBWeN4vE93;|0>=tXBmWeLL>ni`8-IR(xFq#K4ju_Skjw!xHpz z&Hbtv!CbAuo2QB12Ts^+FEG*|LTCqe5H6DYuigX{!~+aCB0IC*RStLWDgPtQh!kUV#2|)e@zcks{AL+2Eq} z`}ti9K+A+6`t~us&9W3V%k(1+ihm+QPWL$9t!-)l+(1y!wi}$l-YamH@bbf8LYl3H zaQm>U3wFzwspP`qB3S*vR5|dY!S0U%^|OBALN+iU!RLhm+|S=cE4(S7hbIT6Oc|8$a$mtY5F4nW+G$&ai;3n#w&BS1x0^(=R6UBvi9M zfk)PJq8?r`#qIG+eU6|=4QflB)$>FV8$&%0lsUwa40r`vWRXctKR{`rTAYyi*^iTy z`%Mv(Nkm=wRGiQ8=u5M;#(+x@!rtDJ?d(NlT8c%OCx!VsaVGPVSR-hnP9LV&ax=rq76J?55 z8p?BU8xa&4#&C!@pG^EdYt>GqudbtE=Xu~+5qpF(n_lzGwmo138E%e;U=yhGVKxpJ zGsD2`C#X?GO%s@`QA{D{M39+nLPfh;=z8i}O~3%9n7>CaTvjXUoDT@&CGOjg zT{?Ss3MKqtkikL3c6=Wjwzz*{d4v@)7lfnD#!)Ko5B{(W~^ zpEqwf@;sBKD*k`bW#=#;=h-m=S>hj!r(wbRO#+f=>TTw8Vwc%)$e$UuJSXaK@W9Jt zqWQ^Ce1>Un(Qt$z0NDOq1dE&LI;rL!ft68Rf3izKga(`+_OEywkzX9uiqCrQ8v!Kt zjtf_{FR?Dc(%rV*QA1CpdZGcY>8QLIlk%47)ZAlPP?&ljv)A)6k>rOrwJ_tK075ji zj-TZC&Alugc_?GL?#%5|qiFZE5o>0n#QI&9d7;6rezk8fwh$EoQL!M9#4Mt>gv9mW zT8WS7ekv1`A+grsz2VRajNCXK{?}~~NaFaS36uuu;hPV;gDATbDACt^w0{-jCqtSo z!v7sd%RU>ktR3RaC~dSPeVTW+Ty(yjZXK!1sQex`=G~tW{o%3~4Z-~##wQ3NBnNEL_-}76Jzggp?i!+_5Au1`r)Zr>M&2+n+0AtURJPRK zuwucD7&&`a^S!?{I4xZ?bmQINvv@gINtdECvsR7mIJ4Xr&q7)s{eD$Xclza@ti?z3 zZ&~;01#$qiFRMal{^TFQ#Lna2$CLM+-#===q>R>Q9VJ^@%RZBPNiO@KIP|S0 zjGaKPSQstoi5l0Ft+gG8+Im66W zzdZ?uWO;-7iLi~YH3YQSE^$k=S4cFhukx8u?HShOHxjg z%uX^gDl~;(4-%e5Nk*|1-*v!do&)Ec1-rBl{$q@SoSd~MiO=CcNit`lbU^Sy4JEKP zZ`*-vXh0Xy_htlKw5HmBkxNz_-7N9EoFIuC`sIfwi-=Rp5m(RO6s{|IVa+|I#zZl6 zdIy`d{MjmFDd2vVa3bIh^nuCAVxG9MgwHJ0J7+FxhC_1D!Si3Dw^77cUB-BF+po0< zq5POC-g2G6D6{HCV|rv5P8HwytnSEMs>R4=>peFschtXT%!G}y&u-{Envc*r%tsL7 z=~pD^q{DfB!Jsz+~j!RlLQl5$^Xt8|Bk!hK6D4Xnt*8 z?hJk?I$m&nk%|BAD>5UYUp|)d{S1oN8ze93)y&})GtUG4Z<5;R#$)c8NoI1;)I8g?n5E>e3k(5Ay!!3%MI==v9IDt3YtI0_ z5wF+(EgayELyP5~;ywUc(IH7* zdA+VR-iLRFZnX;C(XlM?B1odiv-0^+dZr8!1s_ax|49*#&N99;1 zgac}m1KUd|r#!WzYb#oh)_3_H1iDH9nwQ9EK20!5O;PnCIEoD!QF>Z}tKolTjbV;A z4_&t3-Be8k07D|m|MJoE#t)ai)2&6XC8<95J9uQ-jbdYSLhVITa9Z>0A#FqjaLyzL z;m`R5 z#x@Pi=vbKskmkr~)AW5`U4BKe4mCf?qtPlz@|%a0-Q9!u_g7G{L?%ThQ2>sI3?_fPg2W^z)wsT0N?O(dXJ`+y z^qdEBe)MLl5IvIPnhxgi&biFyRlCfPAF^#IMUr~K!RS65eLMQ$lPB+Sy16PnG6Vdo zVokLGBz--HDt`DLBQTG?L^#<09`-D`Y0Urm@_P%_-3v5Oy$H(hZJY+?R^5QjZb&I6 zW!hZ=&j1jhcXnZYD!WSg_CiO8qZN-Ye5Z?kx`vnO^GH2X5)!%(^9I?1k$y+{#VFxa z8~6mX&cz_vA3q>5kh-a@;@--z_d5tzo7Q14-U3XE4;RY(?0gk}TR{vK9KUMA49d7I zue{4+WP2#L(x77ui`>z9LjhbMQt4 zpR73Y{Efoy%WB97p7Z7)`X>)H{=CO&Tu^YZtNaH1`EZ#Mxl z-}R}E5Q*TG5py#%^d0@z6>RU5)fr#4BaIe}p#V)w$GflQe4zkcKdbKaXQ>=$zn+@K z1(1!QsR9hvxN0!p3Bs!vH8KiN02V3)$>t5p{V{zr&0=Ooq~TZA3DKvN*PB|Gpy=&T zWN-7aYB~ecidUhwG$=3j<|VwN(NrZs#eE#kAtPK=;9(yOG>8{KeX0D&F^@d)%+`b= z_ah){n&;E?e<6S|@zdbIheHnAaq1Im<;2(r`@l8CXk&ll&0ED4SM(;+3*0&%)a7Gn zBhisi@qVaP1SG%fA}siM)v6jy8t+NHXc};<4l5U`BJUAwtT|}emJs`C;@?%kj?<5Z ze}B~ikkxC6KKE;bUBB`@9bL@x9o==awG@Is|BYQMOlwbG{emN3hBVJw(xnaxnmrbx zi`g)vNHhTPrYji4Y4rAKrs54&P=@Z{T&ZJfBzfaJQ^5s9)y|_q2g^2?Sn1JU7VZs~ z&TwOY5pQa-{frARZie;}Bj2=lJN>}b2Wb**_aX(c9(A&bP&P!S2QTslt-GP(Z4(nY zOJ9&xL#StG;YjleeP$RpOeT5k1qfvG+^+XoB)%ec3olrypx*XZ!$%k&0s?TCv< zXMV9Mzc=*_oc7;*Xe$1vdGksd-%Yh>vv3^xf{-w&BIJDlSQ?Na*gAY0Q1`gJQb~u` z$%mQV2mO4A7g5;GZ$9Uio4A;v0s`?u>ZkCcq;e6q;5<&<(mIE^DVt4kp(b&Bs!LwdguqyyFvI( zi!|04%zPM5o;8tk6Mb{%NH!DLxt?$0|FlNMw?+TeglYlwp;ch3GnTQz!&doP#XWyT z>&;*lucwYo3$8tFH;!Cr`}s=l)U(b6Kzvz`E}F+le-VtKq9;RVmLgAs3q8^bCy>OA z+;YOd)E<;{o%oH*@A?La8j$9_->aE`J^S+`+i3Osb%^wo+7QlO(7Y2v&Gm6$0eqqfFs%~GV5C1x%wtB|wC*jh#_HWb3kftDbbr-HRiWY@2)-&;`i z_GSFL3z|?}tm4#`F+fyhwtSPp!5Zh+!V|1OKQqWmtTae9va^&?%d+{Yr11AitXSHt zn+o7{I}1uY1AWt~`|g;LU`sS z@7^;C&hYWvx=J`P9f!)P8_Nmp1DbK?#2L=ds>0>+eq^G8NO=R#(y{c>Jn}0Y`u?4Yi!Dlp_0GzSh%1D+oCW?*gAn6bl#=2U-1mz zFaTdYTyqNupbMd#DG9?}%XJHK@lSs@&Azg&ZDgZJwM>CN%g@gq@F9^ z!-@RXo8b_Z+OePXyomev9sPV)6m1(bM$_h+@n+Rbw7fv~G3KkuE-g@&NdMz~p!H7y zIM?Bat;h#)1m!EpHcZT@&;v>NG5Z%t*OUg_zjU*(%iMMbIgG%NA2u%M+zNQI=IJWD zR%o_#jXkpdEOL0?Cx)>D)?26CE}z07{O%oRYA zgWCr0xtB8DAbfH!|2pMUaDXI8-CkeFy0cYA!hXSPvT9N_25s}vU-Snp)dn?DbK% z@pbmsg)A?0S*C99d)S|Ab*Ak%FpfEenXif`LEYxd;^{^w!A#FCEXJoz$CE7qD+8Kc z_Bl)Xq-xWfy1E|02X7TZQ4%@UELWi03hK8 z5V+_kP5Qnhy&h~s1Tz$LG{z6Dr%H-l8J>3H0R=M81UqOnGkF(&`Ki&U)M1WT4}=?7vD)|}etmv5 zvOfFsqQ8+h1;^{0CY#Zue~&KQ_v}uo>LcR&;ZdmnbgZN7)`6g>iH25bK~dnGQxiEB z7a$*$IA5lX{btXDt89I}3V-w9=+TSdGhSA8SCU{Y{uzrDdeye40@Y8j`sOGaM=S5G zOUbNv|ETsT>(~(IV^bb2(=AaWn%}jX!j~YgZy0t1Ldtu;bmLa zHd#yo9S{!&L*4*2fbKT!`Q1L<&NXNX$3W8S*6%*#$qs1my@t%Ez@k#_>`5nvAuZ+fV}|E zc}+jQ_(RkC|H8}K?Cid|!8GD1*tE?Ca@4@4sk|_|U0XngI_5<0aIBCzeU{2;_Qq$e> zEqHqoaTtH-24d`Trqpr_>z>Jt`F?7>nPkn6hR;lB$5RXH<;aqybS`M%U zmCfmrgd+xm_7kIx()njw4=ioHrx1@W9xn`(mV;RHPo&xfPvWB0m_M^4p;m=i{4-ib z(It3hPedE)goM>|Tw{y@1TGvyGwYhm@OP+QM9bO37e1#n0%qL5QE#066&1(GN_AIU z&7uw)ULItJuXAP=I?CCfSih2cYV+1<Y<))OJ~E=yB67tMoCeDm z2>b#nbp<_f(4-+ATgxEuWJk7yMm>i?+|2`1oHfSiJ#f9bS4ZUd4$%%1@gt!s?_cfE zha^27WJ4c4vqg>!E(@=cYd(rq(?6vtS0k@tK2&9q5o;2YYTgPyJlJ?Not!M5DmQTXlgK*Q@o-FECdiK6K8 zMz08R-xpJLb3=}cI5E++Nk6&%bX|0RbUjg>tlImwvv1;&{R4K%*tdFd@oyG~oJM+2| zud-+CP^LJD*8${6cGbu7M;wn@?BRmT$@TCfar4;p0_Hls9BGKE($R;mMMoU^rU}RU zx3}U9*d>jU{4w>`rwT<{E0}SfiL!pHb=%nZ5t+GCcGVrmv7lVL>6KccLH9|>xE zw?2LRTFPia5^GHxN-uaXNW^xs=nb=F(QB_?A&(m;PIs{j7s_;1qW9^orVyc1g%b@G z%!kCn&Su@@GM3Pkdv~}a6v#0d>7-IubYM7!?VNsh_O{IHEgq4duT~`Id z27gaz@=~1l0vpATcWezAs2l5WtR=!WTGE99e3XDc1IP(v0}SRDd3Aj;7X9Rt6h}$` z3`QpeLXYeildmzsl$2JlO9Ch>b=EOuxVnBI1ml-(_-uuweBmTAH%jQJwwl79zm0M} zh1+^Wf)_VRdQmSA-`;nf4kPOVjEHm;`oM z>>A@)w9~D?v?IHn=O>zE)s!y%8oGB`Tn*+9FRNHcDDUGJ9&ufqY*JKGsDUfvZLY_z&SGAldPv>Uj%C(+(Ok z*NgCfW>Wq8;v;Z80%CveK2CkG!z&|b9=bWWVlCMGqR%++2P*`KgT4My&I z7MS<|bcDI=)^zsbPOe+8WWcvf-$1O7iTB3dA^Fn`YDjNU13iuVKW%y#AX>D9$=*$1 zphE`h8>KoK$!q#e{JH}!ku{11&0IbEZb8qFrJ0WDw>uwB`fLoq!UVSn)a@A@dO{4? zV~mno9u1Sa2QbYeu&njDc*K{~eMxseZdBlc&b1_04j?`Z*eZhp>ICl8kyy)c>*uBH z#=Yx?wu<@$>gi7X`h-gb-KKf?l8;ztt58%8KMUwTxYge!$=*J*>Hp~ZD~3rYqV}Wp z?TM~$p4Dt(sBW?>KdV0st9A5BGy4|nfy8$!`lUIJ>4MPH6*UF@j?VA@aNS5uJ1aKl zpo^_?u;A|b+5C8f;!yg<{8VP$w-)P@C%0JqZcj9}Htl+vycD#Y8hBQ&p5aoBmFy+} zUbE;PJM1Htzn6SsCvG1b1e*P-@Ia*a2;h6LjbaU#=X(60->p`{cRjmOOq;%zD$wU< z++>ZoFdK)q<&iERxKMp(V45kaR0oL7EPe0JyJ_gg7m zGuw$+rAHJpC5xX1hCZg5h(32e)V&X+m4R-Md(JibE>dGA5svVuml&T#y#%ib{Gv6C z)&3z>+-7rb{%PNANS5&?yU2Qc#Sd*fMoG8Rfq|bb!wU2eR>^=*=54|BS|uj&G<2AZ z%|zqjD>^|#vrBG99VSqxyeCg%m4__qJS157>w|29~LYOUWZKatE z6U^TF7kLGk&y$v`E3y*PN$}J`6xpu>{B6hc~hX1ch62NFFDfT#YM*)`-vNGaSd2mO|bm;CVs z{NM{@L3l^4$@4Lp3=(3&%(O2V-x8>q4AWUn4JglS*XPP_ZCsIW^|^)y*|-RghC08}pZB^PUiM*GgODT% zN17jUt0&z5mWU)zzC4Z(60#|v_>{QF_I$Y&X~U(B4yca` zIV6_6VUiGa?$2K_RTEXld3bW`EELCW#+L`=Z@<%9Ib0(glunp;xlDZT69bD8_K|6~ zI58#`B=j0~$XoDG^KY8KP-ljqGIqVVAf+Ssu}0!F_yQm|Tg|i>m)whW?aq}n(@1ZO zz+7(3d$$TM;loiJ<)!buSf(ocHRTu^c64>>zaR6eUXm>P(7oUtpvo2D@#tT~w1oW* zPzxa&X326Ib;C@Cf*;_%R=rogWnn$x1U&H@>#3wZY}`Z=)|6TRY$S>1jX|~l|nOgASbP(OIOIQC$ov{xX0g(dgfskM_lsxwv;ETGwCm{ z%s`tx(x~^_$2H>r+{aGIVocP=2)JhK3*$pa%93yzC`V+OI)mH zZ86BTW29D^i42X75}!n>=ReJ23Mm08_e2A38CV>+ojsX!mpo?>4eaRo1k}>S526R0Txl0-Kf4)2$!VpXXlkLDmu$sD%u8m7XmFH|w0b3!!2!3@u~uo9cd)ShCnr!f)JpVXi2ks+9Jc1Wz>y7`GjRj8f54@iAm z5H5j%#B{@^WRqq8Ur0{(E7!h#+|%WX`;Zhosd1*sv6SH<^#`Xt_{6jjm^m@M%Jp>F z`OBBb=}~KUh#2=q(6*W~6G_2CV*`!Ibif!!w0!Yqd@ag^5lgDYon4b!=C7!iB;oYE zGS4AwJD4R1@1WHZ)-vSBVZr0ke(aTppo||LCtw$~gd}?2cGw_5*vCT1_hl16x-bf( zSDG%@BX1D)v#F|qe6>Va>Lm{8c^nWL3F=6^Ms%Cx)Afe)PBUer_=~d zOr-8tSGt%TO*~o*M2l%41E$`EWYLl`?Y*`fw9gOXq}oB6Tey|UXP2txe1@P7<{v?jhdzjhEN)jNsHt}v}?dQ#rf>JDNY9#2Og)5Q%*=_W5UK4uJ@6=XDdK#D_ z95oSB;MN5MsB;92t7e(JRO!-fBGCZ?EhgbD?m79K@jL7K+DvkCaVOrKj@us*Te#|_ zTUpRNd_^T|1f7T9&ZU0AX1u%**OhQ0G*neDfb@74}i*$UOx-GGfq$Bh@({9f*jujOc zRXy`#buxN>R)qNd(!?B@$RlHaW_oVc{A0F}G0iIKtn;pNI$GdWbJQz_Q7O<*;vl$K zOZP`pkN|);V#%r`+H~V?sqU|yV#?wC7=t^4hl-@wo8-(4e(@_csD8~hY0}-9jloxo z%~^Rap2|}B0~+8IWEwPASbFUJ9ze|!4h{a?#N|}EYD%Z%({kdpP~?4Rz^B9|gRLk- z)lir%{PU1@z=ka;##fxRMZPnCsM{Cxln8g>RA{d)S^3BapMf=XozoiXTTSn-!ya0z7^4{7^tn3DWxFUV5;p08Wt>J+^`?8y^Y zj~nX2=Wo@`#T*|M`F)d#Doe+o5{nAj$S`Lc`|ECD{gPjrdDzWyC_Z#7$|zkZu3v!p zy)WPU%*5zF-nyH*xdHb*JMaaSn%{)wz*$elF=0U)k%`A6M$FSmf^KXX&L8fSzkZTx z0&jLB(JJXR1Z&H#Q%&rLF~9VUU#cOr5wq5nTWZb-nSxddZtX*8G1N=mVj=IA@256? z&yltCRJy0P`INScbh~Nk$;H7(%2(1kdU0#xy=5N;VK@}Gu@x)AM{%}{-`kKR$;#k< z$V+9kR=&h0)h zSoqtj7kFSyIfrk1z&pY<-qSLXMP)O~Z^~_M3=^_h4b1y$KO|MPv+~p~KDU}@*0D7> z#_7VRGGwLOwf(v+);e*an)Km547OvbQL4vvo8T|q79FIXT%EM2*l=1#?Z7x+368_7 zgg6_aJ~SJAIxjQ-iChXE&cXf%DxFRE{)=hKlM@eoX6`Z~1R3+Pb85jR?wry3{PIfZ!yC|Vp3=wBiOPnrb5g^9S=$>Oaf^LQnnc>ny9LapM2+E`SeP4^Yt-zE4}G4 zDY&1aF5MpWN-Ie1{Qjza#w+u9t7y%6{v}BiPPwYdFb&-%0*^Mo2xq)h7J^jal2)GC z>G1gy@@?R332W?Q`n;u}imluyQJ(T=eRp4!X%5)w`y*dY4~sW3&w$xcyKaMZA4X{9 zA6*U|*7c9D4&$=vsb}3d%#L1Hv-+WCU&Lhi@ljNklCDnSeK2l05Wu zo;WUCl~_uzQy6R?{v|ViaY=Mg*ENDYjKwyG{D8+Px8~%{kdu3?>fdRF4OoGJnoIf+ zduCa&=Hi_#GQub(%+s+^Mr*MeY%{9T_F{2x#m&<&%-;^m=8F58va#&y#_#a54**fX zNbyJo_1NvxR6xeQc}LXwgSCJc`@4$}(xuhiZ-b0c=qz##zS?XhuwHM=$C6qxfs$b$ zwe(8xN07Vxt=@#vi6Lh!(KCc>P((p^ z{NwO|n6y?oV#}~qNKz!dd*vpr4w;G(gYju_7E6|7Gm{QDfrVT-IcOU7Oppu@c_>Uy zcPKhG!#$68Svthem=^;g_USr&&X|`1V)nT!ezQjBUp`wG<D*nrpN zZv^OyVh?5-Rz^1U6q<>2c=PzCJ^J`Ht(xD$rsGTC=kw`f*{)xhxdhl`a}nTTp7#f4 zYJ%}fmDF^O6P3k>p;?BrV5tTQwPmwD0|rGw@B09XC1Wu{&61y%=jpw`*+!R?+eV%P-7)KU+Jdpu}v7{ygMOc2xNLyGiIylN?$KWqQuAqRaH z6|oydM>K*5(CDIf8m0N)PE1a*a@w@wOhrAO*FO3Fc@WmAYCeWIxNFqpao8{OG@>$1 zPgEVFTgGdZy>KUxiXd=~que^>_h*LN260nVN^%d|<$!F7)!0 zcglM#XT<*SYWD9{qu!tWgJ7cw={9P=@$$~fGEGPfLGAth@H zS~#;&v2$%JFi~IBh-Gt>YrUvqM0}n1P8=*gFwjPmVS^*~iNd2jao@+*7_k+`{NwH& z2-{^~i&v-Bh}AlxU`|wp>fLr>+joLf0v+R8r=qp!~BN6%rP}w#eYY z+GloDs-bkI$k@YGHI9rqiX5}kPTG}g<`J~(-8=1+@mY`z{|qN@Hi21Re+|VqXGfD^ z02@Xdh!ld?>NT?@fYpl0xs4--X3y4%Fm)-brl8J+%K1HQp9Ln>Z9Z2(36#erl_`V^ z$?rT)JT&+#{s8-$vV-i9bzB*BH~iT63TnqcCA^Je`8rnL?CjsHHsuAs`Lq-UCLZBk$s{StpEE$*xIcO1fP2cJ6t8Q^QP?Z|NPML&< zX{apRjp<;Wy>Xam3T0AmozISVK;m?5JsV110Y9R=aSRBF71<>w&2FFKJmb_a8PK&6 z4o;gtgCXZQ59rZUE)52D@B0U>-%v}TGTn&J=I;@cq8#j=*%_*UMm#K^wH$=V7b!UCBM$U0XRMReA9OJt2}yli5d7*^*}+TAS&^*c^*;8 zt>xF(+Mrjk@erN!o9%`ZPPwuDC|3<3Bcys`0OKcS7<$|Qx}Ar+$SBGR$v-joHle^r zNL3IS(qO(mO-c1%E9&GkmBd9QCO1 zse#1va5NJWHKu4T6W4qiwBW+-RfrL4DY9}^hn5f}Nw=3d(FU+e^IiXGCoW3lzyF^r zan}}G)qj{OVp@qNaSGanAK~7`gUXF0HffyBl1(ZiK+fmh(I`9Pc%Y5unYB%tPj$q4 z%t;<%zqvribfaF>tjmdvaP`OlHfFk8pQ}zD3LDP*HZ(pEK0cF%abe->$OZ;RbDYz3 zK042NL>8@*SJLXCy7)~AI0QN*$|zmdm>*8|(x~ALaoeF$P?rQ=(1Z7Ph|mlVuOM&l z+cymA*NBJKnQn}FVnp=t=p~gS+Fcq%Ch8SwQQEq#KOPza75_n(V|GsU;4zxYg5qtn zRijIiRiDxbR`M7q|9Y(dj8fDTlyXp6Vf@IvXpUZtR&iVs+-+37s?E;=&ZyXs| zU(?ElLP8i zJ_ohvk=Z%1K4&=}_61%l*q+D$7_8%_LNS%9F~3qi1ZFzS?&vs8N&=&c9M4Ok7-Xlo zIf-P6_`;W|w~ zLw}uYl{R%12moIJW+)Av;5o}#$uj_&x_(l>LY)A0&R5RY>1mqIa-7b?L#lP8+TtQP z^Cr3c2Qqio&_;Rm5N-8p>L;>~suq0`wGyaKD#&A0bS+Zh!hlz%8#-_Za)1Zr0CW74 zS@E6UN69EPREJ(vs+El=|BQ2$pP+B$M zM<{)7nHuIt2;;ntj*c$rX6O2%+DBu-vRi(}```dA+dh7e;8h>IPUSQ6B?#%q_*}8=bHP;sw8dcJZ_W4En+HqkfYmJ_8(2Z=2d$y=bYfuxh%g*k z)Ag2ujUD~^;9?Nd=j)6^J|Yo>Q;Wl7<;ZTL*w1_kn(z)8snSb>Ky|>j-nw}<|MYpB zOKp6t$O>bQ@d-a|(AuO1ryLYbR$=3w?6+<3d?!-7I($@z%$WccjH3MIV{nETAHRAY38^AWN8PH6R^q2nOIO|T+I8~%C zWfFdq?sh3RPyWjEMp5j%4fGi+KGZL*CcEF0dB){&v&$4uO4hN;tXD2ptPY$q*_Iq> z@>1MW_PK4p6G{;NWLS3OxrlS%;+K-5qxHg6-p+0nEU=bE-99JgI=`JYA(?nuVM1tO zxr)gMh1XYG3^XUey;;e&Q%)#;9jQW#ApjJ)JO)O1Pwzoj@Ov0oMX%5~L(*p%wPRy` zf@r`B%zr*jf$Av6K(^=H8E;t~E_3lw%J1D|N!?0fw0NuN$0kn=i~(aR1sEUDVw6pgiSn75L zL~*y;;+s6+xAuZZh?K7kn+CaU_>vl|&S=XKq;$Iy_O|JWeLrZc4tm?eH&bBl zpK`tk_^36_Mo?*F@8{fR&A54mhVdHBL~t4Xxy2~~RLf+9nA?W4>APW{4)Yy9O)p4f z1w_|%W-Z<`N2Jw@Ts-!k5v@;`iY|Ma%BAJNQ@@mxgw~#F=++?J=6Y; zzb!4Jc4Yj~=8dwTElu=E75V9xeqER@(U#eB>d z$(F)8?w>fYX8;a>n2_sv2vkrd{62=RtsbIj5@cj#DOgd(R76bAJH>=+cIJTc{SxX{ zB2_84S=}*WLz84BlW~~2ra*Kb2bwaowlgh-2TU`Zb(1n+gd;0L?=HCSM#HL~y!dKC zh+DaTO&~h{R)^f6(ceeTAC!Rtv6vh5{hQ?&%qJa>W8+n{En3c|CXLvZA=3$0$`2{h z?{#gS@DM}ppTmq4W3HnZyo78WIZ=UNmwVQmOd!uxjirenY8-4>a_PnF;k&aa6x|QS zvl$>@6>ily`J4AmT?vAg@fBZR%>~2hhrf=DyKQijn8+0nlUvDi0v?88 zh<%>xHR_Nk>PZ--_TX@E8>LIF#|J6*5iv{V%ee>s?JMaWHw8Gt;401Jj_dSB%B{2i z$!;X$D;ZXhL}01ZR*++WGl~%5{>=%M<>Ip#g7P#bd^7(xJU-vX-A=8K>H&S0@o$U3 zMpl4dna|XOMyKu8UA=r|6_jzFwPyeF)sv5;Fmh$@(1D>w$>M)!d&q`Tac!-`(lC*e zc;sn-B;rJ*#gbBzaf==_)A6nPXf@)4HI(I5Q2-~&Dtbn4OBYB(4ibIk95oY-?R1R@1y|SHAR0Vd*cNY(14lpIW zf|G8}n~PAxu5sRv5MUU61|(kjzu^{m%Ojk>lGiewcJI(GzWQiBa{-E|HeLK%>78{i zmoCWrjBd$3<`p*$1kOk+++segL28u_PN4h{*d4=E6{XT9JCXv!^ol-IVRvr~IF43C zZYry3Inhg@^-t+A1z1$BG~{BdPzOM1tk53fhD-+hG4J?wPPbtH>=i+f*csguy3j~R z#@3x)osy~ixfeX&aG>Qz*ZVEP{i72qU1xm!beJej$))?4F{7oY6paVMQnxic%8D5{ zD{x7Pc`dHOks(VB)boXK)+Hr6zY#e2i*LR)@aA#*&@a*XnZTr0N(FE0lBP;`!;lT4 zsAu@)DnK7r$EIMiGkZ!ocgYDjWM}q=DtIgsZ-^OKj443K zkp6r34RC4FKWGQP~u0ETBwXdhfDNbwr&#SPF>UuLJ^sCoQVq3eS#b zYhRBV3Pp@aLMzCD{3(+u8(n<-^NN0Dqp}P*bd51H>lL;@%6nVfIk08=$=yGV`j%5@ zzl+KAd@o6xPx=jW{4ayc+fvO(ybFpgRJqlYkG{_RSfI7g?`Yh*ZCG{N!lUD7;LU^f z1>hzUv(?d(H%m>2|mF|Kv;Lf*_YOM0X4e?F&= z#=QRSKXX6d!FMgB7oO{Ma4`F8;?=`s;i+Y-^l=w>oi|lE(&brJd9S?Es*mmpbqtOi zYQb&h_ZH_9gOyL4R_5=WyPS)!&A$lt_`7>ff6$;aat!|(S)H;n|LX})XrvRZx{AU8 zA(7*(0L_-Pifj%WW;9>~A(a>`CKQ!n@H$#Q-nEmW0q5@BFuauVJCM``JBmQ+*ZZTo zG3!KH11mQ@kHjj`?iDdz>V&XuMeuO);j<6#mk14cQ~zqU<;2Oa(E+uP0DcE>{c2m& zWJuGE|Jq8E4_d_jOuvExuZSu#Z2E1%)AVhoRr%oJ%k=<_Iiuvm=d>0dt31va8vl>^SrYs znEJ0sX|UK|k+NXJ)sdlK?^Se$j8AkbuexI>$v(5H^fU@SNe69JKnDwO$~+LL87d%0eoC{O9#r(890WI2DHg(6 zhjB)A5=PqS@bAa4Rhq6=d=s$8Rr$xFq&>pP?hXp06{*i0KW->=Wr9|zCBI^D=MFm@-*j4vQ?QzBi42)EMPQiWIa z>D)D4MO#Bzd2CWNQ5y8Hm`D^=xrW_am^)f|RPxXRwlQq66;0S+P6i0<$ejmFwd5)w zL;^O+41+_u#`fzL>VaU4QYxJUhE6oYLLrTU8X1gG$<8QZr|&=1AO+J-DtIC&?$~wP z;=S03q<}<7V8|6<*iwZKDZsO7$6KZTA%RdwisQ0e16pHCm04dUeE%6|yBm27mjdBJ z4THS0s2l~9wCLgO0Ydzk-el);Wym!hG6WpbZ*6V8J`!aPRS2JkbCS6UhjQ_f;pj9o zNZE}R;>oK)itrRo48!7;1_sKK&1wHpOdR-kBCvuYFE8Y_cbkr7ErnM8LJ{6wJIF)4 zf)Ks{T^lO@$c;8G<=J_x_u+Xuq~k5J+S_G15`zB{%ORBEFI{Kr>4lsmOl2002~VY# z*~Ce^uOdlsFWPT|R&~TO;g?;>#SBqMJrpq+n>=Q~CTkxV!iL_i`LIY7GvCv%u#A%m zff&qGCWkFz?ScbaY@Gr&9EbZ@^kWNSSS%uEbfce+EvZ5)LX&e1*!XG3{U2}KBVHEy zN6u*xI^@k|w%!l9C$~^^g7;(I=ugk`zTrPCHXuWbT(ji;-|%0X|S-ES;$m1tL$kQ=}{ z`G;~Vs2)?HfXo&zeCToN`z=!RDs)gvA4&Ke@0-_RX{wDD;c2TKz%X^dXhH*R9HV!v z?1Ym3T{hHm{)dHB3TmWaZ2aRC56no&wav;m3*nINwxxP;()AHPR|!MoM!|{oAYWMz zhCr+J2iJmMEe`E-`Fk&eLJz=jL$-m(_!!E-rbH-V+=+Y(Tl#_evg>QoWlO1LvS`bJ zo`ARL#BvZ`*7Hn28v5I3G=Qnz%#{DvfTY-xI&VlkbO0W%Vukobi%*2sc*0dhc-9A? zzY$nkhFzsxGN-sDI?>~V+?AQaalEaER03_$T?u46ClhY+*_9M>PXn4QR59UZ__a<3 z8`7cu_pSx0BhZ7=v>XrPtaIBUxT^-CBGffueNA%@8uCzd5fVE_`$x9nLAB1;_G;F% z?C+nRI^!yskgZVt&q`WqV1%0fqcBWu|3e^uFc?SQUzdaMer^B8{_9i!@Rq9ly<Z zV___0knu3qF;dJ(H1)vdW8N_gLsGs_=|?Syb0Ehl*6|f41j{DG;chPxj^ksmVSuYyuQW~g zn=6^m>->r$I}7Z5eql@*QlT_jD%D4!fObegI7DeYj>TD5uGyZC6wUU3pGv3T7?EbE zL)V=Scas#d3&N7^Z1}QGvn3};_$r$8R}}*C)abnjH=&|26?!~~69fqqfZaHaqv7cGcdnDX6i=aXw*H=k>17>xTz+6feq&W3i5vD~9|-J$%IaBadiINa}r= z2+TA?#AbIInQ=@{29eP5OWI~!ld8&$0NCVHJ6w4v#tJs}zbcb2bMuW`7xG4KJw)SX z(MiZ4UsVNe=790(uZauk?dC|6{TuwhGBW~hn?W$c|KTr&CaM6*n|%V&;021QajRqq z^Ugh$FqL~lml^(5S-Am<+kalK=T05-Nf7@xad{uDB{n~#8iWXAajmehZ=<4U1{5Ie*SUc zE&J+P(tu%jc}VQsA;=(6kxKrcwp#?R;U77>>X+Xy`gWc^`sES%4^tzKB(hbY8nBX{ zXO(XKgY%jz4OD(37z+Q+qL5l2QcI}6sE|Qavn<}Y^%s9#HtFAynAwamC2rk=cWREO zC0Bz~uZ0A*+TGmVv>}Mc@BNj@iyPOn2h_V$<=IB0JE!daF8qgAAVg2Y!&rMg&-kBz z!7L&T!TZl2N*D$TB-a)LO$lZ_g#KR^{Qv!HQ033n2Zp_t0tf3j2>3y&YO7Q!S%&@> D2FgTO diff --git a/third_party/perfetto/ui/src/assets/metrics_page.scss b/third_party/perfetto/ui/src/assets/metrics_page.scss deleted file mode 100644 index 1487e343e0ad..000000000000 --- a/third_party/perfetto/ui/src/assets/metrics_page.scss +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "widgets/theme"; - -.metrics-page { - padding: 30px; - font-family: "Roboto", sans-serif; - overflow-y: auto; - - .metric-run-button { - background-color: #262f3c; - color: #fff; - border-radius: 4px; - padding: 5px 10px; - font-weight: bold; - font-family: "Roboto"; - } - - select { - margin: 10px; - font-family: "Roboto"; - font-size: 1em; - border: 1px solid black; - background-color: #eee; - } - - pre { - background-color: #eee; - padding: 20px; - font-family: "Roboto Mono"; - line-height: 1.5em; - border-radius: $pf-border-radius; - overflow-x: auto; - &.metric-error { - color: #ef6c00; - } - } -} diff --git a/third_party/perfetto/ui/src/assets/modal.scss b/third_party/perfetto/ui/src/assets/modal.scss deleted file mode 100644 index ee575f05e979..000000000000 --- a/third_party/perfetto/ui/src/assets/modal.scss +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "widgets/theme"; - -// The opacity changes are only transitional. Once the `modalFadeOut` animation -// reaches the end, the Mithril component that renders .modal-backdrop -// (and .modal-dialog) is fully destroyed and removed from the DOM. -// We use keyframes+animation, rather than transition, because the former allow -// hooking the onanimationend events to synchronize the Mithril removal with -// the end of the CSS animation. -@keyframes modalFadeOut { - from { - opacity: 1; - } - to { - opacity: 0; - } -} - -@keyframes modalFadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -.modal-backdrop { - position: absolute; - z-index: 99; - background-color: rgba(0, 0, 0, 0.6); - top: 0; - left: 0; - right: 0; - bottom: 0; - backdrop-filter: blur(2px); - animation: modalFadeIn 0.25s var(--anim-easing); - animation-fill-mode: both; - - &.modal-fadeout { - animation: modalFadeOut 0.25s var(--anim-easing); - animation-fill-mode: both; - } -} - -.modal-dialog { - position: absolute; - z-index: 100; - background-color: #fff; - margin: auto; - min-width: 25vw; - min-height: 10vh; - padding: 30px; - max-width: 90vw; - max-height: 90vh; - border-radius: $pf-border-radius; - overflow-y: auto; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - font-family: Roboto, sans-serif; - font-weight: 300; - - &.modal-dialog-valign-top { - top: 1rem; - transform: translate(-50%, 0); - } - - > header { - display: flex; - justify-content: space-between; - align-items: center; - - h2 { - margin-top: 0; - margin-bottom: 0; - font-family: "Roboto", sans-serif; - font-weight: 600; - font-size: 1.25rem; - line-height: 1.25; - color: #262f3c; - box-sizing: border-box; - } - - button { - background: transparent; - border: 0; - } - } // header - - main { - font-size: 1rem; - margin-top: 2rem; - margin-bottom: 2rem; - line-height: 1.5; - color: rgba(0, 0, 0, 0.8); - - .small-font { - font-size: 0.9rem; - } - } - - footer { - display: flex; - justify-content: space-around; - } // footer - - .modal-btn { - font-size: 0.875rem; - padding-left: 1rem; - padding-right: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - background-color: #e6e6e6; - color: rgba(0, 0, 0, 0.8); - border: 2px solid transparent; - border-radius: 4px; - cursor: pointer; - text-transform: none; - overflow: visible; - margin: 5px; - transform: translateZ(0); - transition: border-color 0.25s var(--anim-easing), - background-color 0.25s var(--anim-easing); - - &:focus { - border-color: #03a9f4; - } - &:hover { - background-color: #ececec; - } - } - - .modal-btn-primary { - background-color: hsl(215deg, 22%, 19%); - color: #fff; - &:hover { - background-color: hsl(215deg, 22%, 35%); - } - } -} - -.help { - table { - margin-bottom: 15px; - td { - min-width: 250px; - } - td:first-child { - font-family: var(--monospace-font); - } - } - h2 { - font: inherit; - font-weight: bold; - } -} - -.modal-pre { - white-space: pre-line; - font-size: 13px; -} - -.modal-logs, -.modal-bash { - white-space: pre-wrap; - border: 1px solid #999; - background: #eee; - font-size: 10px; - font-family: var(--monospace-font); - margin-top: 10px; - margin-bottom: 10px; - min-height: 50px; - max-height: 40vh; - overflow: auto; -} - -.modal-bash { - margin: 0; - padding: 5px 0; - overflow: auto; - min-height: 0; -} - -.modal-textarea { - display: block; - margin-top: 10px; - margin-bottom: 10px; - width: 100%; -} - -.modal-small { - font-size: 0.75rem; -} diff --git a/third_party/perfetto/ui/src/assets/perfetto.scss b/third_party/perfetto/ui/src/assets/perfetto.scss deleted file mode 100644 index 7a0057618354..000000000000 --- a/third_party/perfetto/ui/src/assets/perfetto.scss +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "typefaces"; -@import "common"; -@import "home_page"; -@import "analyze_page"; -@import "metrics_page"; -@import "sidebar"; -@import "topbar"; -@import "record"; -@import "modal"; -@import "details"; -@import "trace_info_page"; -@import "flags_page"; -@import "hiring_banner"; -@import "widgets_page"; -@import "widgets/button"; -@import "widgets/checkbox"; -@import "widgets/text_input"; -@import "widgets/empty_state"; -@import "widgets/anchor"; -@import "widgets/popup"; -@import "widgets/multiselect"; -@import "widgets/select"; -@import "widgets/menu"; -@import "widgets/spinner"; -@import "widgets/tree"; -@import "widgets/switch"; diff --git a/third_party/perfetto/ui/src/assets/rec_atrace.png b/third_party/perfetto/ui/src/assets/rec_atrace.png deleted file mode 100644 index d63f2a2a5fd36884a42c2e3525faa3b5acdcf8ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43695 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�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 diff --git a/third_party/perfetto/ui/src/assets/rec_cpu_coarse.png b/third_party/perfetto/ui/src/assets/rec_cpu_coarse.png deleted file mode 100644 index b7241bffa7e11a7a42020a6eefecbf2cd04b23a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53670 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

R`B|m_&X+djx|oN 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±upE5P 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�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 Cn{mnj diff --git a/third_party/perfetto/ui/src/assets/rec_frame_timeline.png b/third_party/perfetto/ui/src/assets/rec_frame_timeline.png deleted file mode 100644 index 2c83762657065dae2264d0e44183c7cf4ab4c003..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25975 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 F{|9aANnii~ diff --git a/third_party/perfetto/ui/src/assets/rec_ftrace.png b/third_party/perfetto/ui/src/assets/rec_ftrace.png deleted file mode 100644 index a907f9e6d8ac00d22297d201fcdf7e6e5608b570..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22382 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 S4U!6UuNml=Xn)XjivNF`De;g1 diff --git a/third_party/perfetto/ui/src/assets/rec_gpu_mem_total.png b/third_party/perfetto/ui/src/assets/rec_gpu_mem_total.png deleted file mode 100644 index 4b5a44a62a6453d2d21a8b48471a085231d59d25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52638 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 diff --git a/third_party/perfetto/ui/src/assets/rec_lmk.png b/third_party/perfetto/ui/src/assets/rec_lmk.png deleted file mode 100644 index 7324cf984e9f2e4165893014cdd20923773a790c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43733 zcmeEuV~;37xAoYzZO<9owr$(CZQHhOow04(Ji|NB^XB~#H@VqKcP0Iyt9DhduB^3o zMaavF!9rm{0RRBNN{9<90ssIO|F*Xvz<#fMlFPKeH$W#vF+qUZ8Ju$f0Db@oVF6`# zz$+baeN>edUZ%RzS3#hH5G+Y3++qlEF=2rU*2u2fT9c^q%hA^M5uNR2JxW7h6a)p& zTNMIG(kDD|L2Tz6`fnp3__>x&$0!kpGMS{TY}* z*qf+5iV_X$f4e{Ip~)uAL?6ZoXva7>dkSsA=CyXlO~9rhCio2ikmNs5wQ>Fwkz4KK zhl9dJYa^20>R!rk2wVf>!q2HS9=QNq<|=B%=AVnBkfyr<|bjTrO8ox#Cr2iT?Y#Jw=Fi<}hZ9 zg<^u9Uh~4gkDT_9oU(gQ#RO5P0(jX$&l@`$DE}m9G1~?fBE84|*0s#_2RQ zU+M0#>qd@6AcN*^6DOV)E91xkX^IpB5GKs%Yuclw6hgoypQccVlR9Qc<|N1h$Bv>8 zlL3f;BanmH4})UMfVr*Ep~c62jr1ipIXmvbhtCr`I5>zRkbo;rO8mb$*M<&|1a7Tj zw%clVP$;CJ8LHpfR@peZxR$@0aJ5>k?fGpcZ<7CaDEjb#X~*&Ix_69P#)iepRDX4< zNaftv9vD}BOhCA}h!(++w`aWbmY$HXBIvm&pdlP^27msMJ4be^`@J9&56uKl! zRVZiEFk-}zuxLDbNZFG$X*}ABm@5q@P`+`>E~~G$jw6=1MN)*SB$1iDYME-mlKJR* z93leTLkb)svJBi}*|Jzk+(N@MrIQ){p~GTHk(cer#OK6g$96GkMwdn`9;LXw*>ahx zXnWYPINJX=_=)-ZO(2oz{lz-Fw1o$JeNlcsX~GChK*lArr%a=qZ5kku7@PJmqng6e zkZ6b+yaYf1hX!G_x)ATctvCrl)FB5S5Qv%8X{b1iBqS^31MljAg~v{+K@2Kp``b&P z3ao8v1iQVd|C^;b3WV}qg(XPwe1iV8+6$=;8dB;nxRm~ODA;xcSLS5`QRHcK?|rENmb;sd$1$BdP@x z0R+k9{7IslM`(Bo4hDxp7K#)WJ@zanGg*%68j}e#%z)D1{=}puQ)H;%a$;a9e&VS=_Uy|v{P+2sjR`YF2?j z=AxpEj2`&j88TqKTLz8A;BSIfTIHg>4J8PkbIPw<%Cktrf7n~!Ct{LlF-z9kkhn@e ze}QC(#AWKk9wXv6tv|^nb2d2AIz~tHAByqs%l#Pg=Nc&(sm!y0HQYuVIQIZhy3G~fWbwaQLmNtwaUoFp$! zLbUS&&AJu?*-QAfw4(IosMV}Kth*qTs|13yu|(AGaA=`#H+i~NMKxkuWdK=MSyJm? z*J|0Mkfsq6??pnqjm{B;m(3=(+6)eoSl&incjs*?SAj$TLT=8RxB!|@yZpTJ!gli@5qLOelUf|uFnA;;Db-@jaU@4J_1Xz zV=HzU5b{A!59$o9Lf_*g}V(I8&Js9AYS>0FB)d$Sd1H z|FQ+isdfn*exGvs^fq4?*}Guy?$*(3GL=bfQt#I_rLs<#81{Ps(=(g2wmi47Y1*^9 zB`#6?(v_*VwQT?Um4H|d7r-%}4AWIiUtq%qeV+S96!86RO~a_g&B4q7O{@DeJlE(VnpE5p?2^03@Tr$_SO`*qwftR=Ug*m#?@~;nq9yv$_tG!WIW227J zE!gH|SHpt}CQ!yKMB4f4+m(=(r!as?u-Hh4lXpAqCfmNiAuTtYweyN~<~F*5RfLPnQy zu(#GmI6um!(IG&mzDHW=w9?Gs7x4G%HNG8shxe&Yd%!PpunXv`E}ICTt)~bmjD-B+ z4^?~vdJjPYnSj$Hm$wnrl-pyUbTOXX*qn%$g$^TJtV}B{yX@4=ZJyJic>s0myvpX& z<{QDRx6h3KHg&m1*-k1DJU%1CF;d^IA(}+ZQhKy$Po;``+{ullUC4NKQfnDwR!n90 zATt$3FYdJL@^_bG zmv$rjvBH!2x8V>6kv93Ti&?=PcD9?d=`=i3%-oBYE=N?GPYG=aW zVooyePVv(-X^8X701F(So+2nX5;*2y9Td>%OVz`!T}DbWfSXDi<0`DsY7gVrmw^D) za}KsB9bdeEdMqH~weI@_uby+D?9`2H*lnBInY7<2O}G-Oo)i}7jG1UIp;x+O$uv{u z6D>WST6j&?bIFd4r-HmTw&I-$->@Tgu;%3;`!MQxW6JsZDe%IdS+MM$&ZgDk0(|+O zv$AQgq|{wGUvz63l-R8Y;5o z8z!FMxaGk><2l*Wm!<`ag%%4rEVRV-%q1oO{J9aexC$U7q9dC)|8dGX#NaZs45J|4)%&F>!_ zA53qS@pk6(z0OCor3m%;%yush984ze^E^T$n`7=UErQ{I*{;3%?gi_l5{S_DC&Gyq+e7SCPQY1L&?V!cWlxI|XTX9r`a2n!x!M7-nGO$x zlXFETQFC?b4^Q+Mc|AnmNb2?+{^hAmdfG3v+$2E${psMek5)fk1tu}WJVxq>;{kBv z>tqYx9%RxwuChz}=%7whvPXr5jm3o29Um>X6L4WR+_1pb@x<~NzA!f#owszjE^OZ_ zAc;9Y4_kGFUZMBH;#wfDP#=408qS8#VbnTR5guJ*YLX*#Soxd}wC_5$HPO$d4*d{@ z1G;rH^c@SdOg!C%`H4?EH!p}pJW(#lQb^4z=xMWg%vT>O1^qF;I5oUlYnU}~xM#Am zu|x=Kqt_-`%z3y?M#NyFr(Zaj+mr9=O2e_WMIr=ZX+YcWG>Z;KGFUkOtFA3QT1fN< z1ud~R-`|-X1F#9UGN1nepD)qZ4>FrDTvKAN)lR+7pxuV^-v%Q{eLF5}t~t1F&~T(+ zmMLBMvf!9wTCpcpZ_0{s_d33tkg$Mvk)y6HOYevb6=>hbz&M0NW>Lj#)e@I-ob_%*2oWA*2{+S>fG10SE5 z&_b~RB0fM$J-zPWMXDHg>UvDKA#!1ghda_Jy12|T?fhO*p3@5Y&aM)ux;?r(?w?kX z{SIFZZ&zQJXZ1XR;345iCi^__(cagl?ep&E3~xGx*08ZtInuN=F4rWg4JKBj4#w=pcXhawBbD08dnI6QPL zsYS?hSDAzC|G=!gdDN~NK`ge~8T&%^$&Iv1@`vdiGDc&#`NMBsa{hje#K7Lo%6AmcEH6cpw1f05*2D9H7UWtZsEbc1Q$tIv}NE6vk#BzP*(MLvp>7L0;PxnxQIx{FpI zu=^8iC0klYB1RYWVD-lBwDwv#?&5>o zanZfYnJv9>XlHn2nEC!`RM6AhdJDHm>wr$d;N8%+c{r!b`Qx(Dp|<& zBDhz8MLudHs2@%W(1Y^t16Ur3=}pg3&*);PnH7`xRgD%qGB*RNUFnf7>1XWgqyzg} z*=w!Db>$0xB}9zDqdP0abmUu9bz_QHdaBaXB-aQ-dG=ObJXlr8@3Tuz3S}Y|J90&L z5;4kC`|L5t zH;(pDj6i;d80IQfO;{<1b!+QV6J+B+V_;DkI9AAfo4Y`NU+kN14P$S$#@ z_$<3}_5`(=!iipTQ-oR|lmWzbYz+Syq(Ib*+*>@N=nXXPDVd+VQ>hp_-!cjYArNnh zT5gF%UYHfo0fVd+8kolDSuNP|K85SPOp5D%nuE%}Z(d@6jKqaSl1n%dR*ysaX6S+s+CU zA`-TF(hty=QyydC(&au}@Qg*EKsXRWTcC6Deh?VZyTv(irD>PujZIs6QyLu;tGM6( zsZv`)*+^<rjhwK(7BU8`E9q*kM8QUL{m5&Y$upraDPHsXGD2{b+Iw+wPw3K2%^^h?%V2dU<6$3CF?nAlDRP#OOYG|au}jkUD(2<_Ucrpa|kaOeu1b@!|qQE zF4qY+SfG;n(U4s9Zma9{#-B+inCey-ex8B(_Nsu)&niXu?BPy_*G|)#ynmru?E{A0 zh;z}rlafWd&HZ%jwUW`+UT45Yo9+8RIlTSI%JY;K;JX!9_N{Mu8kK7Uwfz%*1 zW6)t^bA+WlM_YaC!B!FT)wP!H%)%&Ncd#_6_6Q9q@0r2j__0748w-m9V3ntWfONQL=5w-eaCc|eE}2x<{+Tqcot{%j zwv}2rn$^08_NhUSj7MR=&42ySSPZDHj#Y1I)j!YA$8qhJq?P%~`Mt}rg^z4e)INel z!r-gxtY~_TZn-fuWD^HO;CYGh2#l+>F{%k&d-v!X$olR&lGrIpD3L3HBp_!@cP;`3 zQvwx+Yozb}1YdXPxpn$~&<@q*FSM%T^SCmD9`wGB#bB>?RtWtyT{jJ~$Z{iiRnbp> zOP7#~+$p-{@Nwi&cMregd_pOgSxt=FyYuWdam8v2k0>y zH}sE+_6q(Slz#VJ#9pd+CU{s)mEMIRkXN(ABg^USPCD^IIet1^;xaS0TQJ;kJQDhY zxk%AJ#7MdZM+=@%#_InNxp2NvBlPHs``(zcn8J1d<02FOeu|%ECT3P3x;cBz5{(})L z0yUh>pzaT*`_T<_11^1mW14%}H^Nfou=2q>)M7d7U)^1`)N(Bz1~o8K*L?bhbg^Oy zfQnk0*w%$_?eG4F@cY!~c5xh1^|txlR=hLRwI6b`w7bLjBsl zaG@RsM{`ux6jf=u{hpGFgUG{z$a_^!&`kUE?md)x>yQIMoO(lFP1lllH7#~8^q2Pg zxA811=VbEAJh7ybwv9p|YG&|HgNoiE?M0T|BNCo@{(&$)+gHyBo@Lg@;J@z%ed^WB zW=^b&A-_2K&f}_Rhl!Eat;arHK9$HU{bA131P5KrO(H-Ds5$RwuoKW;1sx73E0*dq zKc3-RTp9W)j?R{fMX_L;&e^I0(W21odd0PN$QAh3Ex7BC7ov2NRT@K^UqY%p$@^rE zYkW++KB>bvFL*BN)iLY;yBbEfwe~UGDip=3BE6`5dl^pPUV)n!`l9_)1?@NhlpEm&k1V{8uY-rsn$0r>A9*!{Q^e|PXdIGR5$twDoQU}% znTFinmY$@3*;HyzM=|3qU-Xi%YQTeYA=^L~Mpt}QhR8!ECMW4P#?m zl5@i<(h^yKj^~@_%l55D=d~7a?Yv>`6oQ8M6f01AL5u# zaHEk`G|N~|x4|};s%R3llAaQcdse4zBF-b3hjqlT1aEg_?UOfJxY2b4Hd= zhk|8oxQ~6-hr8`gHarcty&(K`RivEy%_?GppNP8t3Xu6DaMQWLm|KN1G{z_XRYn!l zp5#nva9Nw;BkZw>1@4zUZ$qwrRhVev4c8;$KU&endnre!sDcd%Ulm@Py(mY>s*D}Z zKeHrjt(Ff8tHrb zGuLWEjHLBs2V^9txrG+PKI3BbwxXWX1%LZpxuGi>DuT!rZ3_J5eP+e!X)DHIINW?Z zQ+8EgWOnl{{O;zH{_cwLD|_rOr%vO+MfpVg^413@&U_0Sh5GOZ_)ec6{mV)AJ(W!H z5c=FydfcmZet$q?VForM+W)DP-V(-J!WQsHqa@!=WMoK-B8MFej6ITL!U8c6l1Z_v_ZE?I&{tHi_{7G-y8Az_fK`&rY-CxsJMc z5B7rqx{F-8MB5=Q4|x@Tzt@KU1Nltv^Rnz*e1H9#D0)ubQdTm{zTxy`(HZ~s%(E*w zwgZHEOnJ5;aWevHc82f7rL@7r)L|Z|b3k{z>W9e`&JuUsej(28YYPi|#fiv8hPK!_ zv^%F=jXi*|n@~MxJ6U*SC3ezGc8OnGA z@5NgtWX8I9-Ua&t>GQ+!>vg_WXbUw`M_2lyrc>Z>3{=I03Yw@waWWg=!3g z+!}bZLY?s?^sK+JVJ}!?GyjROC#R8WwzvVh1YWqP-nclzcSu=(b`M&(-|h-=qs!V~ zNSY1^b0x0K6MvbqE3+=%V9!4~X!^Zrb$VPPxZFjMu(YE0szKaE@aIgQU&UEKKF|5NTTrOC*>0CUTe0;<`uKG2p}*C6mbW^nDF{>)Pz5SCB%wBh zCXKP)CEtsTILqw!{>}Fqolc6~=^S=}jS6N{OgGwuM&vDu;OMHx(0q@s!%%^L#bJys zH?&s^QH&2W>RO`SkE(M?7FUyAjs4c`Y$|Jpd zZfVhm;1->)t}L-Oy}-U4ZMmWipP4a0#^>$drdTH?X7exGk|?>$rAn-^QoxzdcSwO> z7$PlsqLJvXh(+uP)aTBrQ1p=C_cK@l64PCa$**CMuWiqP;jlR-F8B0Q-2 zRC;)|6|fof3Of5Dz&v4vz7X${vu&{vY+03bx5ij^g1YAE33zCnS@<^@tDzojNE;C% zMH!Y?-5N-CH`ma141}8^;+mod)GSRMI?-1cNqJNBk@46Klpnb{V|>*47B=9Q*!f3a zmHp|2=F+6YN#*_+Y55^$07%a&g&bRv7EEg0{aU`}+E9e*FB;d3x6m<$)v6#CPxWMzDu;3lkS!-{#ASx`l~tnLPX+>>9T>eWACMIwkN3sbh5p1;c&VL7_=f))of?Zshde=}s%s~YLSTGirI7H{81a?}~zqaJ2gQ=N3!*@&51Up9v9)(;7Ym*J_0?NUI;*eG?tNY% zq~!)y##!)PJ1>#`9kFD`4-n>ks=9)l&h~`P<_jToyPRMzLEXk5hi1OA{9*eSQ6Lvv zmSoLf=Q>twrRuXq6DnNZmTd2y%!D>E2wd*Awhiu9c$GCIBk`x9=JT{;6o^|`ZgoL1 zL8~>r?eUJQ24|!0HsW(^D=ZHHt7^L$*)D@}dM9z#cK$9~dwwUveh9`syYOjXt~APj zSrl4somhrPwJ4RdEOvCT4F)it(7*3qs3fE-__N z-`N`V-o59W$N1{){`D(&9YVZr^fVNaKt5U$wt?xR+W|^eRY&I6sO&T+ zjD|#mOH-3axA}2h1bM8A3TE_nIkt6ErAVvp5Q?Zo(WMVWmJ4x_R^oHRiI9n~@l>R% zl(($t2x~aNjky0xWfZj|n~xvd?a5Ay&71O*ma3Ve;?m?)q1_7m6BK3*uv06|#?Tx4 z3X7lM#Ss{%`P(!4a$dI6wTd=^=KP%H2*aP!uu$jxGrnIBNQWL?Y5C}X`ady<<@G5- z`-S(1x(A=8l+u3jFc>r>3(l7xUNjE5Z}SUM28SWan;RI%mJxZmfuVT}I+3iX2-8bM zW;AmK&~foy9FG*4j^jJ0JlWDQXf;C4*S9HHs#3q#pbpl$3MNHRr~{Pp@QTNd${{9{ zK;>)EhN^DxpRY2G5l2)Ib^DCkGP{#KQEM}>8;W*M`^-iM(4mT^M=7#T-d1ccFQAb! zxV3M!M-B&*SuL2o&4=%i#Vi%*dDOMvc3<&Kk)15aLZcF5^Zmjd)~1lhTlmqw2o8Ji zH`56vILD%@npBr_ug7j9{FI+M?=Dj1Q8KPH+K?qY8~);TwwCs$slrl=id|Ti_x%bb zr^hh@Y($pS&DzCQM!#~O5f?Ai{Pv$n%MXNWo|YL(y7RaszQjtY*-lB{T&%Fl7kl{p z>`&z}?nzGN!b?(1W{~mce5*IKaU8|x+kWfP{6TG^zWj?t74J;D_M`Y3xe@P~E%Z|- z5F;H*oOSi***S_Mg#^lxjKb^E=h0E$UV-Kz!Gk1MWadF}-Nma=%>q0|ZqvdC&ruQ2 z_T5Fe*Fj^(d7^d{OZ<8COHr0Z)Ntz^a4*fRnyn8^ zP*2N&oNcZumCc)sK5)-%E|)`Tg=hMKk@v&?561?yRUap+KJF%(M(PkAUf+5oP5>zP zpSp#Y@|)fGFRWV9N9P;#*ha+yp5-KqA4F8+*_LV-|$N0N*Mqmw@Zz{kmeS0<^B0V&06!)(0rdL$-8`CFBdLdh?lvMRzB}tG zx)Mcq{uSkl8LwimO3dxeIopkRB{!8|f0O@7Kyj(B%7KJU$o!K-@Ql*;Ch z@w(#?ACXqv#6;J=J?2XAl#@`_oo=#TIv6^i*_r1F?PZZyF0hXvB~dX?d+}^T>XGX# z3r)-fGFB+3-BXt-%{I?3$pDE3D`3eCTQlZXS1g^7KPl;Y<#xI6K)m=G)=#c zm3)t(t%$`>iL2`h0L&%R@wUa*x3uMzvj+^W`0sL6#mDpI5u1VFHLA9ZL<$GVI@~xE zdL5Exy_MR2d~Np8`HDy^w@>&%DD=@e`5-p`IO~yt{hKF8MA3@;EN@F(*+JmO&qjk3 z-377i;Rxd^fKW9%%jjdYN8~)(05b#2CDspnHzPZ8@AkJ{>AvdcEuUUg(1|WQe2kZt z0s=Y;nwj?UB{YmYeV^l{?8Ez_8f%_z7u(L=;!abDHrobbC>2o$3Wan^c- z)hMY)R}^0QU{+~g#rdAFYMR8mvNttt-IBK%Gj70%?lK1EeZFsl%o26 zC(|;i1T6itEeDtNezOJ}h98pDj=(QY%XZdLeLp#YxjwCmC6au62Ru9y6vlAVmvs6I zT6Ff=Q-m6TfPSeTgOe<^e99{t*5`7c%8A5lM0G)|JE`_!55J&i$`yuPhW0QI$ zt#V3#9_n@Uklh{<)#WY~T=254Ub~H3<>YCILsX3gr+VHz8ovI{>@pQakYc3JE7e$LC|vahc6mfB?zAQ8QK8-xCdSvo4#!&a}J#S9%dgBi^i zw|4O@tY=qZcQT=V!9m*|3hJUTpqLB+buI0cw^xVHDu-!Dn+VU~AJ~bd9BQDQ83NuE zx00xn)8OYA0W0-aq(r%V{3|#LBQ1PL#bZr`_$v%h9b7sp`Q5FkM4UuhSh&bWWALIz zG*dM0-+sf=|8#X|iK=dh#|NMk4IGTcGl;YDy83RrKqSo*T(3!bsik$twuccE2GP#- z1a8O_pA7)%NbgZFr4YC3h{A*?uYB!zR8eOk77=H;kH1iUWwzrU+&wc)##GbAQS*8| z$jEF49(1P_YQ7Zv=7_N`57VGo=21^G0lcyN$(23y9cZ9Ns1c<>-Bt%YndDBo%|bU$U6%5m76cK+6~lM z0)&E%P6<~WK{GWv6yUp6#U|Wy@t&_XB&74bbv*1wr3W0UdCttKa%8p0D&?)?@q8gM z@%M?c*Ku_0VJ<1eO|-{%5_jUgd+hKbzb-W|z^4toqasVXbBf9F??+v7YqyUs%ia`T zdl7tS)frg`+BRh@T7TgIv4p(OMI73{uOT2&!I;^g?UyB4+NBiygGlEQzPq4~bA3hP za!!%7?j6BmKM}vnu{?7PSp|71vqVW`dT=yeWjV#fbidQ)$K(q?zte=A-X*c#RX%NUj-l^Ay?*LWGPdVIn{ zQ#m8__yUx}H6eAemGc4Ve8NEA0p(bFaOVo-f=wZQ&^3gO;ihzN!=;NQ1Q1(YA;Xp+ zxcT5C<>+{dOJA_BG~>y|zo7dzRP4vq+>p~UlHqaQ{QM@_YTJAo*YsjjKe{^V@ell+ zdh#R-ZNO?6rJV&zu@VJ7b1L+vw#Ofs-48@v(#1gkJu zHDc~)^j=sxloXk0l5%(HAa}RGMSDbSUWogW|ctWXWbi&sl*mUnfc%nNL$P)9c zttdWz3yfds?C`3sh({!qq?MO8(sZTXcc4Or1Vk*{OA&UM&K~Q)KLdixd=qPP$OuS2 zvz_HEuSH4SzrzWH&r+ ztTS3HFsm^8B9v`;!l%|N-4WtOv)vW<=xOpm#co~s;Xlsir|@&5(v5$chWE8+sm_0i zA!haEdD%zt?_I&u=^d#vVLDVe;Xj<__IM8!>cIULL-($`IRx*Xc}5JS4hLOvD;BTC z0!fIh-qzR)cu+tfCRJz(-U58vRT}8rWbHm+<5g!Xmh$9rxmIg+B1S`{bJ?U5Yi0I4-Nmpkvub}M|VFig}8 zsY{k3U5Iyv4rC7E_W%ie%sD*ZD6wgNXap{1vMy3v|4cc4oXj zLuSS*rUD9>o&B9ia#2eqQ8U8Dv0mG%`{^z!(7l1Etr*dDX{pq8GIV@XaUk7Jn;zy*Q5|y&%+OsMy0Xz#L20#T`cNWKeZCvH7>>jASzW2~ zjL7#W^W4_}sQcDjK+mP3BfF2Q>Gp(=Ro;HQ+z7BU)D#aaV`YV`&8$Kaz?uu7AZtaA(JY3e`OUA%L#|<|QRJ_}?x3GpdrkXSAe7Jt} zwa#L^j7daqHt|;8aR0e+;Pf`dz7|&pZOsl_4Hq_w*UNfj5Yz<9?QRQH@|j3KD8;xi z&K~=;bAA1iiZ=FE_NCk z|6a_>S8+@6bo0Ec*_GB#nwRe}#^D;a(#z{~e3Fso^L1{dimC_b^LztC)} zW3qWEH5AK|&kBjOqr#XfumWK@5YpYJ0_jxb)!$y_nhAH9pPEaY-r-dC@<^6#wG^kS z1JdiF)>_bU$4BAVty(0g&Aw=N7{nc8`lzuoTp^_AY#B|paJ;x7q=(q;U-tPuZgb1( z%+`Vxr^X*m#H;mzriw6d!ifRqf=ynseG2kLCeyY~#kFJN%a&Ovs}xS4FRnBLg4%+#)A1m$E_b zgc5kTTApmxNU6P)DhN=~p6{S4v|WL*8ez^=3jQ@+Ic$V3W7jHQC^ff zG0eZ0JSL{4n=W(v+wFl)kj($%aygr2uMz8~i`m#7boG0FwvRRVDR2f~8x!mO;hwk% zFG7XGXoxIe2eNlRyz4I*P@qmvNj(m7f`C)iBgbiAf2aSp(zVV)<5An1>U^nGJkQuW zqt@55kj7!iTH}=yL+U$^@_xh*&$t)wJ0sV9d%-1MRJBbkFzpM~1N>c8OY)v~sueVE z6wFux2+0`2uxL@F@X#00<`M=|?TqyMt1tMMKJl_*^dvdWJFIIR|GaJ$?wn>-G=9co zamTYYR${0Ymq&wWX^GGH)tkCR?zeu8Tefc-So;(!D^Inqxi6iboNxZ}(O87(nuC#| z(E~WhSkPM>3COdKIPlQM_c&q~Oe-!46^|%CL&9fnfCe=!>lUl62J-Iy&rvph*XBQK zVCL`Hg9H?_pubK4r2zo}F=cmt=@Y;js}m={Aou~9%Cms+MPTz)iI~tWX1t>iSios@ zs(Q;y%6YUU>d}2{zR33sRXidaCSr~w_T1`uI+ds8YMMwH42&|IeeGm}Ww>+d@HgU2S^kZjJ|^d-5aA=o=50*rBZ4=l;i-WDO$`L)5T6vR z$BQRNJK;~yP!q_)QBbv}9_}jsxGji>@t~bVn#&`XVMH6jZhhv69>iV_YcVTwb+ao& z^ablgP-Jp$Kk-azFCZ;YQ57?*UVI3CqXlkG`zd{1Szv4(%$_xR{@iAeKa=JI;$<6lc1gmk$=!wcSWFLV@! z=3Rd>on4BT#)ePT3F z{naB#bWn`5Fu6@G!QWEb*X`WFEp%)2dO!Wo+I_&k) zhI{|vvZsf{gPQm=IAk&IOb0J6i^JpZ;4CPX&)YH5C=%!nEdDcE!n=+zyB6wL@N+d8 zAZ_w_fISS(hMCEqJgkon8BpI_quCtFXR8mW8GuY289I$EpS2#}fH_pOedynnyiM5V zLP{4nPn>?FLA0Y)@6u0N@AQx_Oiq~N%0hA_u3+5JjMO1WhXd}y8 zZnwt_l6>*%hcRj~x}Z!KY&9@R9idqr{%xxFHHApag+{H8%C`QX2vcZ};FOSu>57ZF zc`LlYMie_#)x_f3q16*{d7NxlrIU(`v2n_F-Rv{*3PhQ2gG%cL3;y2N51cCp?oM^g zS;T}~$U z=A4fUe8gyw9B0NR8#OF@(g;^fs4X~5&_ht`fc;)8bjmOo3-y*7GyKoJfsiWGf!*vb z6XCcqRKT5qR{QNo&*NOV2A<%I%rN0zeH7X-E-*|~9pFY$Ps$X%k=S`XzJ?4#f#%SB z0#Zt+z8IfyMLF9tR{Kk4g?$md@R23Fr_wMxxA(&5cqh+$jEclRXL0>bMo;GNAi<@^ zqKX?x4r-x)8q!VoTS@Cm zj<0|9Tw;Xcw43#U1rQyr*HY7O#OC8SsAXjM-is)6`;*^7E|fk^=`l2b`#$FSu28t{ zF0P8K_3*z`%;mkmWqRU9)*95AiB2jpsh6gI!h2QiR;_OdPOxKfNe8~c3na(}jQ2>; zz_aE{jLg>ppBDQwJ|R|4B)jkg-J2lx@2Sns0{&K)>#cRhwhWit%_t_z)%gQ+v3GUd z*R%8Zl93G;!Sg>DGfu@ zt8sRv;`Wj7`%#F>Q12bs;R=JIP=R?vVkuDXc-ZKnj;0s4MW(~yPwsiz2V@A?-CS>% z-`z-zgO9<}PaqI7QsLr##={Ud)XO8Q7Mu%cdw-)b1-z12l~69Ofj3X+b-fw6@U49O zIy_D%x)4ZhS1Pw~ZjOorX6OC49pz=kN+f~t?FADoGq$gt7TPhS( z7hhD%bjBvCxg;1G%T7J*4TRV0V4bF_cj-D(Z%I z*PaIAGG>{rD7i98CO{F=P8r*!hxMIqDrQIv`9;+k zC}6?OTsLJTsBI$V4?zC@V|2d*%F%N{#v_y*T3u5|*_4tN!8YKNiq!aeXeu~kKr+lP z5@UUY60e%@d0p|2%?{Fns8XG3n+j?W-#gdTQ3oc78#(U`SnY4iyVG z3R~7R-oWVFJsu-8RwKL5e!jF(elx~jGeq~}4mIm{*XG!+BTG4cC zs<~wAY$8%!Ov{Y-SQMN8!zUV708O9|bqJdoXh)8&p4k;w+(KYpgTRONOecbw=v5k@ zFF($Hk;L-YSnP@WImCJwWyKdBtG8Ap%l!=NM5w!`zapA8b)KeY$74L%#|j99mDLs6 zvD9p%Wi#?zVK9bu&`DpXnU3QbF0hF{2~@nm`MM(zpZO z3uFraTkphXgAF({ikv;4g~5L!u=ewH1a*)B8o>-Xb>@`r;Kxgz+B?QiSIhl$4nk{@TkSfJe~Cq#%b#Y~5sNL2o7I{b|b-j&x{z z)~}gcRl&(ViH||2nDmg#{{PPcNc$$ueoRSfzWWpyF|oIk+nawe>nz}59pI8*s!A~| z*2`|DGk7firYoCPnjQ@0eyzkD5!pn zB5T($5HbfaA`2a^A~03{I7n=XBdd3aW20P&?dEMovd9)nAce7J-l$K$%~G@N6Qy}E z5!2gf%I;tDthkHK9D2^iC;D{kKcBb9LnadEFPmbOF6wLLQp5CaSpL?%%}e+t)Ud7S z$txqQFY8Ua=plzEWUX)Y(mSpFGI zv?5rWG9ariQM@s2y#fc-p9n9uthh5)YToovTKAcj3mzPnkti-jr@TOd>AgP0)3 zP7pR;;7xKxs?2VI_n*ajQgV9tMH>pJ=!%dbPIu_!f%8J7=5qCC7}B z_DQ0|4aD|F3PzcM8jhCgb4djvHx2FB7(0}?u__-tw88&jG0V36C8+$9>hGAb?1X;L zP*z^L;_Edte_MU(#{1M2#(BHwVIjC2L8@?PH&0zhR>vXfFJFf5a%a8D{JIvqf*AAH z^}v4ar{aB?oSf8Hl6N*h;|&^Al13Y75T6AtxDFWQ__~+wIPUX&a{m!8R%HPFOw-0oY6#ltGDs- zMu;&qADmMWdzNdYNsJgFpUx+S)}YRE-!}-~c%|CXu5Glsldh1OAKl7`Qq6-OcBjQI zwV03-pL&U#&+NFYrJ>)!u)ujF;|7g&uly8{P{EesXEQ*d1T6N8_J zhMH6K2U~CnV_85Lao6`J`3^Y5-WkS#Ug0~wa0!1^YQ+!f;0M&J;NGNFA%i;z&Jpy zpzpBzcrhb_(62cJ0tl0Y%3;A@Qw?nb=!YBGd7W=&T|Q7bMb$BV;p5KN!=LsftbQ>* zK1yA6%EtS|WeE}nTWN}Ch1M0;OR6PE^~@bHTp-78l0h`<0^s+}sL?v#Jt!ThF|0{q z5~W(1iNS(d0!eDOwxw;nNE+y4{~l=v3Tj&TbX19wf;G*e1bPeu+E`x=&70f;8UUpM z;<(=UGCN?<^Y&HKcSA~v+=9#GgAMhVw%siZdZJG(eK8ldKrGwBBpQo)7?E{Hc`GW4 z1WOeU26-sxgZM{vH$64(;n3DSsG!$08gS_i;yr&z{ zqi8>?^!zRg#CP)|E`Nx5sJu2LdwyyQc8BF?Cd%6Y1%;)kLpZ*TIzc=<7~F0l^f!O* z0!4}JVw!?9mzZdf<+B$UhxSl=`k6gjw)JmL$tRi8%G9C1Cxh9bu=sL|?3Lfx<6M>? z2^EwYY&Gs03iRqkcJ(c;V;CsP)9i+VrPk{eEJ7eec51ezx6?sjTSjmaL5iT(nHYrp zQ3}?x?}l9v;oU5xnHaP%v8A^d_jLCu2uK2rLY}^E3VH~N3S~AQOHNW7h<{*|NNN=L zo%II+VZ1q=OjI)VQe+MfVjK7DYw=#!DcWnDY2aY}qt z)3#Xi{gklgeOzPv4$;fkGcamk*KtnG_9amr3}D0jTi%KFTCoQB4dmTuX^VcOV_Xn^ z7R;tBh$$*`96$w(pukFClsnQuoHhc_j8+jII3tguVtN$=;hxyhw8z>;>zNTNBRD7A z7rXh;5+)8z-bN8j5dss!BD~k5-Nw)m<1ODs_|i97 zg@tV|Uf=E1`-Mv+VyfVm)6dRy)N@J2cDVAr%Mv8W8t|^~hK3eTDSUiL>FjF3X^ln86Z>EW9*ZFi~F`o)ADbBGcHEBd?p6P#4bqBUal~VB> zCJVpAHWA*;e#n`y_tbh~s@~n2`#uXX%6KhL%VUO6E(ZMWz&j4)*8}f%4O3J&xsUw7 z{XEVT7mD=M9p8dqjJ8CCC&K;;6QAE>pt%q;ntrO-1~Hi2ii>GBiwAa#8F7;c6qH$* zW+jwX(ijngTKP!S>jqKYnu=vu??EVgf>3r0@}=*vR@HxelptZgWc%lmc-orIk@Le? zOJYGmsvVA`Z%k=immpOT*)?=Ss}t9;7v3-&Qe*nm9B(TpD14!!EMgF;QNXeZ^FmM( zILg4lSGF4UMFKs90m~n=*>$tJ(JpH;cE+blPDZSY7*)epc4Mo+_vRyM;4u;5R*VJfw(QT zt;bG~yK@8cGAaDp8UB>@SxwHtEwLp!)|AjUJ~M+>Boy-KRyu_NhcX7~zDsSPyyBynNIa@hTe zO_GRL;^Hz2s53a#!CHlQJlvQ27u-=PkDlLI_C49lUopj036FCoPwa>@F{R8w_c6B3JQkU zPeb0&mRy1It}(+`sF2S-LzHJX5u(} zp?xsWX)r@BGdN}INn9SifGVR=_jeW7rp zvnM-|WI(xWzGXd6im7m-+8Jv;POUlEmd&oc1E3NObLpZ<`5SY6jjO9$7 z{ubU^E{sy8HJrrFG#2lgiEGc`S20fGM$4F3kCg@OxFLw^1w!&4dxQjeCt(Vs;V#i} zwm!s&o8h`7mSx(L-84||&dh_u-wKAW)^=LYOk5~{DB{~%qgRod5lz(Vi*M-!Tf<^o zl3?=m8~DIe?MPaE#hTBN^VPI7Wc6rqLBIc1NAfg=G%ib!;AOeoM_Hac)jzb#z`!ai zZHVail@O;+lnEkWhRe4#w%!UBBvS;m zrI-hX6sB!wRnunpLLngsR+h>CwH>NdfL(9${=0mg7 zq^(?RA3JsJ2dkOReL$3W;xexD?Q`#lt(+>L4idld?6%e82mg^NJJ9;W-xxmp;ngCx z$(}Xb&bdijkFCI#-a7MumRX;(wHsz@R9vD=!o;iYvB4$pC~*roJ}5T0sc0MC(-TkbT#RkbjfJIju5-;tig|NRmc40UDbIyvy&z@i3WHjzvmLRd2lkHn+uqj+=BNX1= zw#_Mb-4N?XtO0xzVTj6s$!?$m01m;TaB>X^_B(cbe2Eg5p*=_5?V@QRL}VCaO2U!^ z3WA`8x$zNTm$Su|n_2W+yQTfwfta+^G1*zl?r?4-AtNJU9cUM^B*;x|d!K|obZ_9#! zaQX?G`vvCQ=5E-garT-S&vxhuKTBrfEhhefXW|u1T-lJp@c4-N(z+eCg17?$XBVn zNVd^jdY3-Lvw!fT=~c^mdP8}ZcG)w0W%V#+3FZv~9QP?fLs#H+WG33koM7d^lwk>L zCrify8|R>aAsM=^JN&hzC{yyJ^56X8T>8_Z$LYIW|Dz5qx@SJp@9DYWZ61sDX)!Xp za_Ytz0rg_oCYbIM&Cu3s;AUtc#^_ph(2>bD+v{f8KahmosOL07C7RKbu^?Kf5^Tj#;3FcU>b7NGchr_vFGGk?b}OdOEyPw%?cI| zX+7&Tps_?$>vLc0+nnqI$ z2+(y*ESQ$s3>>LqGg<^shTp)Y>W2y~F&s|vf%Fsi0G$>G&_*tn{)uDpZn|c2|?pWeX_7DQ4_gvsl~8KlSYT? z^@M z8n{Gh4QQ94z&ZFX40B5RsWkBkLM zrCMh)8{L-AH+>5O+T}kd{X9jh5ax7l7J#jY*1pY+Ad|EPBtX0gt^?o5YIs(-BW?YV zPgCK&d!5Nb4`S#@jfKZ;))=N8XVlRjXbUA+J}xs_J*$8cOLwAmY#R#ENKYYYUT83V z7Pgp*6W!q+olW8~bH^eZFx{WjwPFruOM$>LgX5Pe$`U}1xzZ`f3Pa^t1AELL> zwg@@*Qj`oN-`Iiue6!@3e(yM2y! zpIl4lZX}Xcr4`zRciMkF<7%&5gQC*fqrEa4V_dxuB&FltP)kg9S{LiY0A{^sFEq4LLvlvnq22`71xk3%MxpfHQvu4G-3R`2;ik#cMsKP6^}kPAntzARr&LL2PGdu$8Gc5ZHK-Fder<^0M#j6+hZl-eSb z$tZCglP9mToZ3i-fJ3NOef!H!!>A}}BgJ5Knb{LTo04`gH$QCeqQu+qlA)wSV6Qqr zlTCnu>U!25NjvU3z07}t$;ZYL^={nWKxHQpY<{jS_R;%%>- zrVv-_1ZmEkIjZ#ZTLZ8yCH5Z?;T2cxQmiX#eitk&!6_MRO8&AT!+Lc$rR`Ni%NF@+ zM0TbIB1jml$_3FP{0CTneK*o^tSy3hD~OpFxTH!Vg}dd|&lLvX@CsdXsA8jz!$2lp zvrUZsrE@XaBvEf+QeS>CxDnh2VvzU<-nI|eru`bWLk0^56_GxhZR5S6RBYA(yo*Mq zq95QjTbk)IH#{Il&tXQqUkIU(huDY&S-DIsDCEI$E3~R0#;Zi^7bj6Kh?pLNdhv(T zqDmApSUwQ3)~IVN2G$!`kk>-BLb7jSnu4639zl6#Y?A)X()TSNVdFtf;x2w-j}bzF zhc{L{oDA6Emad%UM12u=4g?84(&Bj>fT-RB<6gDJy1WqJiHWJi*@R-RqKQhLz6UYN z0&!{!g@R#dTt0}(C1}(oFwPZ3iunn=i6KM-Vig=6LSM0o0ejy`o9MUvhtyfvA$dH% z>_^n4HLNsP`AiGWP(V*3Eyt_rt8Nca&e`o0pf^FIt^n^NfSNai`nm#$FSD&1PeJ8*vI^*Athw zkq;c8PK8$Fx~?NA1p{{|nmZBL{C|6A0$@dT?EUJy&&muifPyRnDxkoCON<)C1rz}_ ziaRPAHLKa)_k79s@?!FqU1Sf_9@^4)gu@#~ffV~+lUEOvqm7x;E7aZA zew`JbUb>+LUje@VO@Ju#$dNZrek!lYcYTxODiC%bIrD5d+4VuSw-|*Qd54w|c)R+? z=f?(+%j$rhQdza{euPo6Lc|HcU6~V-cI|W0r-nlXH8r*SJ%Qw+;~_t?*sxXQL!R_G z4)PMVX=_O!r8WL2-RygM)aTobK>9-0+_+;SX4STDSROU|_7OKsZ5>HKx!K*3Rezag z*&KX{{19f=K9DQFAglL>?sL?qRFjhD*;h@iq<5WT_347HN5k3=lX@RPwbVJbVMSf} ziI5?Ga&pi)$8^=Mx$<~ri}C~Vh&$#<&(XquyfHSsn=w~8{U)v<-YEv^59I(2pifb$ zOdF2Yr9ueM!@Y>;3j-tvWscjNKDr%sce-D0f${PCFZlpn031JzmLj>^%Edktq;YjZ zMS)l!P5pJ!He($|_oH!&&Z2iXM14I&BIQX0gJ0g#f_lKa3spFAe2tn-ofJLG<^uSz z$W{(0g-qNRYkZKJ+;nneP#6m|qMh{zf|o^JLKxkz`?aZwZ|7dX3}j*8*c>gg=LiQD z9jkcWUjv)D3$3@9@xO}NX(wo{>)rHUJC#t1xUs9{zl=-e0qX_%Id9rD-lmJ?>ZSl2 zcTassVp}Mhh=A23uL#zQ(mv^$pXa|IWcd4?+aq;4WjXe9udgp(Bt>2Gt$_z>jP*H@ z!dTi&;I29`TdQkGCO?>?L8?rD$V%qFXvjNpt&BuV=w`k84Xk1SSH^i7?4LZg_Vu&OUU;a^Ph-C zddlglb(wIi-;Ty~0KwS}f7HJ_P;*RpqgefGaPBWND#5GPE%1J0?+2+NKj^>^^Wz02mt-J9a44lfrvCn?iI0D9QKI`-dzzaE zU6G!(X^})s>NXvS%a*F7Z-2laO2^{y&#_!{Up;$2x4J-@J9F-8w4~Loeanp5+Cv`p zc!%;IwH2U2C+~+#wPK6UnUy60b_jsHBU}S2leJqgxW!Eq!}3>Fmy5i=ATnuqhL1-X z5yb{>QNII0v36Mj?W_AWT@WK`lGaZ#@2U1=H}xmMJRWBC{$ghRBs~G)@fG@?&6Anu znD^>>vZF35_zw_9>lpJnXTJ(R>N1U|UnLqqLQ7XTbJgmzz{$^Nrf*SXuZaf3`9?*I zM4y^6Cy*L6XjJ4POqL6lF7yR}gg5I4GQp6qSmaAVlR!JfOJx>;~6{$T>S=)zn~^Jowt^mz!W-; z#7v5xu4EZ`GkqIO82FUNqD{4D-+PWZ&4!St?Q=?0@a2Uu3w9vQ&j<3R=CEsy9yVBl zno5lSfj%>`h+oEex#m@b1O(*wRwXe^T!V-3t<3%u64V+>ClBr<&wTi*^g=L=&`jhC z3AeRAk<*GIb&C}(X`#Y-e~q1?!wdu>WJynZ0x6TIhx!h0I7mxk1;n#aq8e*i*(~S# z-`Kv3pn0EtOI3)m?-REAVjbXzrKp>R*4o-Y!@!5}p(S&jb}gSf_b^5{I;XT0hjgU0 z=KNazqDN$+3SKGShRIe@ zpwD)?>+wbSqLO6*De$3i-wA?u8|o!7k8bEXH*;(iIrQpQ?XE$YzZMD%@z>L$)WZEk zs`s82{%4Tk#!EoLefxJZlO~P2>F@bcA^8bF29Rq={h|%m#s!td5nn zC&f-jowinhryQ!J&R)o+i8gqmx^)*eTYM=j-^u*6}PhcbbXRG{Q z_9X_2%+O=nw1^)}=ro>RIi@iC`K_D>Le2!fpolnA%0u;)WC^j}XRWF$YhKs8@n4>y zPt9#Smu+alTQKYCGe^`*I1-Y_L)G#NgwdJwP(JvNpOSgR>223#5;;e;q1ENmUIl9? zD9@ZeT=tO#yf1=kn3mG$HC$pq$TfpVReg0@@Hsv!5C|qYCmWuXb4#tdZFQ8L&JM5Z zbV>-E%L9Aa@)FC-bu62}#*K2W)eN2wFTDcEed~(ka znQ@OZQ2kalF??pd86$$}@VWc?%T7I{poB-4`X|0KTh3WcMu$E`^>yvTFNwDO6C=5I zy96Wovg+JX=BrCy@{Nkt7&Tl!3X+TP*fwv;lS|H6zhlq({wh4UcQWtUCa(oUDF@EI z`T;834De(nCfIYB_bNr&>8$G$tbU(;FQ&gM!`Y=w`vDacdfYDmK#eQ3jWSXuXl8D+ zUK<$z_18lG9wb_wQW=vw@kuQ)tK|y%HrrM#urs`;i7om74efTuitsK4$hh#-fXnM@ zYhkKW0L>%@cpIbs)odqgrigUHE>-blC@Q`x3&Cp0TGUn95b)_^eW6S~s zr;fq&#D_W4=v3VEQ~-pYKvJR{Zks)O_9o7~=5Wt%?mUa4Hh`@f4xt*iS!+ok)qL=Q zv=!uAiIE9vKp1^cPy05bRcl>1hs>8p()Mn(eGmtALG!b2`6s`m0_YI+O*4DL2ukTu zi$HpPLVe~0O>9cc&=oM4Xs%x-;s3Fdi>aTc9PNhgd(mK~a=gjp6#flD?;iFhDM4|N zX%L|^8sX6|FJJ!cHdmGg_Fv5$I`CIboKZvd*+png>zB#5xK1^}vZ_rr{t%dioeb8L zi*z3JolkB)7U6Zx7POK+_bb=R$i>tB|MbanV_dApCml|*QhW{;camv?E>15ci^05D zZD0gG64NrR7;HNmnA|Bv`6p|9kCilHi;#v{{&1RTFs;LciA;M%L z0>ajM@Lj!SOF8YF(J&VdW3klP7m;U@wZxYp=UJrm#>M=yBITUZ2a&~rd8oN>1?OA{ z_+efx=2gDWD9(|p@>;nY+P_VokL>v?Xd@)E)%2)>5@B>kry_ZbXKV{p#_;6?%-m@0 z&MmwR+3>8KTW!@XN=$Y(WhCyIwPmITBiz8t+*6XWnKPSwKh|F|1=6v;{?WYNk(Qo7 zu#{o?g$Fd>U`Qc7f)D6qjCUjWp}&JFAG~{D9n(@o9gOKVR^e&V!#v@D5%{itnmgI% zkO};e;Te7?6&{oX{|?8`W99yMCTL>fGWi-TVZKpl zw=;7Pe7C7K<4}JZ~Q9XfSd+CgvZ<&8qi|2 zD(QEz_dV=nXK-KRlVx%p--C?#i%d8++R9}-k*9R^?anZ+WzB5?rlrJ7<*#m7I5n0= zz!=ZMT=-C?aGOG6SA~T$=V~;BHEutZ`mbQBkHR3Ag5F!GbJRuMlwYN84~C4t>G!-s zV{?bh_W?a9XaHee+S%47P%P}{@iyIRRJ34HyG_SHdzz{Mg*Ee~J?GqnnR7OR`DzZ$ zAQ1K}wk4${@|?a8dVXfZB-jfx;iW3P$n|=YzH7WgU*Y^Mj&RKEgFoac2D^x7($oKr zY#w^0tWv)a^RCuX=WGod-N#q}EdI9aWjPr~)HTG(O?OIV=$INa)g{k5Giv2-DQVxQ z7qrNRXO(%ot@1r<1yY^KuJIn*QbV$+U7gwXKK{6*_NYeQc1<51E8vE9B)RMwI%Vkx zeym5-YOBfbY_%+qQl@n}keVw=`}3|23{x^R09Kho*&ca-zQa{;Oc+pCra;d@aGb-5 z1JJFmy=%z9dB{}YD6>(YhC4Z{_EobQw=f2?%m{adSv0gkTx%r9_5+xB)2$iVrbJV& zBG%{+nK|zio=zR5TVO_~FsGevkb`EJz<+f&4EE3Xw;IrUI*%uo8~yqYHkU+ljkhT! z)SfaLvvt3=f(~YWhY~rDfMR{Yvw;_+D{J;?R%}s}8O+{P4Tp)1WOnZ$00xq^p&=#+ zt?722V+p~t4?DrIbnU}$;x)FKt^E$3k1t@!3z+gYDToi+L6U z-2t_FeRunCp>$@ka(y@`Gth=d@uMr}8JxqM_F`GqbWu#>;C2@EnLz7pMT4p~{!P~5 z{~EzK`OL*Ottj*x#9tb%=_QRdLg&4i+*dCQ8A0mB`erSv%N$Oe-2Ox=Tv=7Uc?Z{T zOAm%KwWG2 z?}oWPnF$wHp-E!b8@N^!Cc6~K_zMomL4#>|J-^90yNkmz+07j709=x~cIswMFX?V> zU9~9m+6_is6t^DR^0C#KzH=)M7y!7MtZiI(b4)+>9C+G@)ay&06h)b-Hlwg^<~9%2 z=SsAbC*qgMaQr)upy5OScZV>YRilO8>GJugfKkL~z`T|+A88=jDfyNjPmcvTnI`R$ zpS*T#5hFjLJVXSovv!vy54gtAn{JQE@9bp;O4bnp4+;2eM+op)v)99(>TSz7B75f4D zJa)q=Hq*3+)Sy`W3R*>W_BUMhnw;x_{JRo{T+H!QQJ8u<^(%7iSCK#%VVMFav+@KlO>lDZv1&eGV)6(P#-z{{Baay?RX3Bvm6YD`ZLXBDv0ZTV{rzZq$k$5-`qGGKoJS|0P|0 zJLE$nUkXxr6WD)+M=NgmmI{ln&`X_RBvLj%M2M~Cc{G)NbyqL31{*#1N zRf%s&-}D(|mY7CHhfk&2jx`clS30WkebI3+?_G)L38F2&&^e!y7iRuW!xs$m{IlU% zIk(!X+pHBxe#?(ZyfNUsE#r_feMreRHr2V`fD;wia3liB35pE_eDEtrJiDnxjE@0C zz;z65WdJMJ>j5f^cALjEcy)U<NbO>z5`)W=g> zE&qVoVC;Da zzq3$+6^8zV<8M;rAHqndmy3XV==L+}%4FDm@Rh}P6SQf`>!hQ(iWU9y_^@nYr2aBW zmfsJP+_P=HZ$B0-;##Orm>oA+n!)+=VuEY9L>pkC)0_z`Lgm zx?k40hG_VRwiLG}$Ypt_%RSZoWhl(3C(kGW-O~6A|8&N8>#77dbZ~vZGO=l<`>ZjgiHFdJ4Oj9zgKAzJkopH=tbTzOJK z5adm)swS%00_LveY^V|@1L{Q)|1vcU9Q%a6E!n@NX_|_TvI)!AKe0p*yAQN zJEjw9?Ta13od8I%o6jHG+3Ykl?T)1RA=fpunEnW(`_LQ|sLokW)cuy-2m#23!9VWLuzO?GKsu%5My9^wVhkQjCn?HZv=-g%N)E z#`s~-(j9(nM;!zOX_Pfv=oC+KEy$O zu9|@ylcNUbmhUBtfF)-FC;wv_&PWg{%A=hbI~!Kr6bWEbH0T&Z81?1D;z3u|2@Opb zgct0n|WXr~OW1Mmy{V>d97%ddmN45H3wR z8#tE5_p$B+IM5HI-Mwf)PhnDPu-DtiRuXYfWIX1t+lMy??=}g_#E*kH?m0KyT7^KO zOn!in+sT;IoolN$VpPbl`QDFQYk$12H=(tz_UJOX9@f&Om{Jmq+vI&$csFLc8)3fb zVAzg{Ds#GGGE~1p355Cqg6uPAKMd3GE$0~VFZ4Z$0Y5`id*3-;2ArIJJ2CrBu;Jtg zD6Vfsa;zX4ge?f1bK7X!`{a&ibk}Rc!1I~WN1AI+zuVcq6O@twt`7)ck?wOhJAa)e z{kaLh0u=O^F+v8jL^}(?^cru-<)oyX%Hlk^{RU8R)+kD*Ypjta z{W`Xjr+Fp~pa=zVuz!?-H)^U$F@(V4exIk(aJ#}_qHo#?kq*fdNSU-h>fe1CHo7(y zNEtpP@U%z${tx#8ZRuM(+&Lb}d0XXwi|q58u;)XMP*Y(L%2Ai4pxHAuw#c+1ig3dW zbSDgHksC6tePHrmfoTon*u$A{YdyYUrFIq`h6P=4!};p&bMIE3j>OHJ^W86+!yx(V`c z1&A|(COLP3TPW6#c?I`Z1v8H`3m?MqC1*^^U0lJwvy71ZFW0lRH0EX$cS{!J6;;c62Uq0U$1gRrMupwGfzAd{So-{~5;p74RE zG5l*l?TqRu*&jg2QO@BY)2MUbDkz{+f1_OX1Ibs=%5ntb8EDg=9IBrp_?%dyKsVWr ze@=z}5=vj>>DcN25h8wNtqQc3VvSUwDTqS_8WO4x&#{-H_}0HJ#qD#YPi?w*qhSQ% z!1=R>0Xc+grLDahZKbhVbr@4?2Dn;V3ZzVwUAnAcvFe&(+kA9qLC*{(Zkri>v8$$2 z@eVoy%;m6KWtk0IZt~C1EU4qzg3+SzTu^?S zsWqu@sHReG=d>XGXF29J?WaN$&F2EHvl7JMV%_I7#yp>S^n`)#rC8jlK+2zS=A&^> znD4HP_~ie&1*73J{XI^XE20&KW<)pwiaS?r^=;Y*l0e(@9~Ik(NCh{LN8-D1;IdE; zndID)bLFM;r4O#=Q9KJjTIdulvbW+@f@4o19nezHZE=e2`Rqik{TL&RP-!uE&=C|t z^5z*G(TFyYAwn}^fM8TD#kpqYR?Au7GrASu(lW+h9si8LS0C|7l$C&}-zDkPTBC>T zQi0Y|tdV98w*?xR#>ropZ=382WlADPg4Hn@56W(v z(!nOXR=MBLHAtqlii7cvLla8vC`*A1X67k#uPC&w?x-)j@2i> z$hw3v4+M&vrv6bh8Vuk&003UXC$utijQUulvtWdIksO&i!Ik=lEDY+4C<+xLSX`MF z9syR4FexP==pWqLjgCzKYfo0}cQN6|K=y5QUrz;Gc^tq5Fm(36i5A!3yq>s{WSvxz z<=CeP)P`6h?XPA?gzfn-!8g$IF2sj2)yeQ+_p|Ik(1$WhWpwFowM)V1qP0c}q#e_0 z^Ogz+a%`c#KbiP(fn`_h2FX#h-~)wDYb6{qaO&Ay;a zHe+%r$0_(naYI9T6^LyfYZmpQVYBBj_DH>fhi-(bjoGIyI zpyu@%#jD~{?)L|LRn_|rfY8r|XQgCs^_IN?DHGJd(y*Fw?b8rCV~+d1drZ}081HgY()`h#L0mg2LN7_7IQ$@U zniYPUz1yxL<4bkLCJVPc~I8BcSxuKIrWs_=7b zS~?enyaR#ufBdez5y>+P7=x*`dFzuCDH^P^h!X|645T2;y@Wh*zt@yM?%CObhg45M zAQY7GvH}#;Y!JM|FZ0H&Oe=}YSx9UM@|pb9oX%eq;s)1v8&LH$^c zR-^q=fkJu0ox;aG2mP!-3f_YRdG%(i?}=*{mqzNe=-S*mO}t$n`RC0V7o|QWjWwbm zpfe70_a#1)aayP^5S9&2_HTS#``wxrrcxylv-h=^Dp!KN&hYVna_-THvsnr5q^<8OG-k-Cp+veV%kC*{3h~wB^@8l1{2WzDYDcqvR7|5L!(&<9onT;pQITJWM$|RR1XB?BI+}hKUc9%^hU}8dth7J7;Flk&0#g-et4BwC6kJ$r4D3jOH)dw&N)$k5*mUA zqTZaFFP9C^K-6JYDJ-A?=l$3B?zNmO(>OeXa6M4v_M7xgwZ52 zH>`2?>sI|j6yWcN5(E_qMG|XMES06AthGWyn+xPAEC&{qS z)=C`hAR9C!(~lmY^9%*7QZ%?n23{r`^}Ew-^VSwybBZdITg_zf6NAEgTE=4+nqE0( z%ZouKK9%~O{K0w{c>GA<<8_(0onh>)MOvQa6+CCa1_<`v&~OV2hQU1F^9)Lu8(Uir zd{t;Ri>CAP3T0w^i}d1M8Abr^W2#LpzlFSl(@1?3l;wkW%3l#WhR^j!b<>uh2EBfCm7M`!KW0_S z03+IS`+T%Ee@y2#7f7rGJL2q59j=A8wgQ}QNVcVtMrF$n#wsOvHpe-RHgXZ#Lw5>N zT<^}nt;cOfzsJtD{R!`HhoVw>)9DAe;X>v$hSZk7zN>#@ZaWnml%Mz!Q1xd4MG#Zj z*pyk4_UB*Gw)&f01k%F#Iue+L;asdClbpY%7!1 z4@4pJiN?b`GlVav_0V&jrq2aU>+2jVxIef)$#b07wBe zds4ho+M%S61u!zD|8Ob)@N1-vnpU?kAmsQu=G&yMk#l|a55f~v8qSr+cJIL#>6ecb zfwy#l3`NkLSyCjA8egr;46uRn8h>6Pr>3Q72kG;A7zTOiPtbN$Om?wYY!FP*PLkMK zbaQJ0tu^L}Fb+S?R%t8)u0%^xIB}acYV&l6)<|)h(w~RZ05rfq3@Qvu%L2(TmY{XN ztK->8PffWNC-~G%ACEG6gn(9r-3D2E7L~|!M}PpE9>dCe25QzLj#*5{Q&Su&{sVkm zh%jmpUV2O$z=s3`m`?}C> z0IdPW$_6H9r3}OhZT_czJO0Rh4_a!R?1jRU0jTry6Y zARyPJH`oGAEiK)b0~vRZg?2I$n&c3qTCV+}OEomrgsjUn^S1k3_vf_>r31p~MxNQT zAwS^+^?|PauaKRe^p;ME5AZ+HV65k*=MqQ@L?7Xz30NDn8V&GeQDDbK#bS0A2}SWO zQj{OM&v2g4WXErXFbY$~{kEEf?DhL%o#TmEjo;8}Atz=#Dgp^3-36^pE`GDEJ>&^7 zo*VZ41#gJoi21`WIK~(54s^XsVMp0rmjd+f4@V4=;QdJX46}*T+uai;qc2ZD{V`aN zwUFNH_*D*KcpHc{g4xCSQS`;8Kh?=MhZb`-tKiEYYcBf$04jG$L_t(p#SSCu_eajy z3W^|o&yQhdYXBiM#QZqNwdN5k?sLf&{)oGzg&vb8TgIjOr zlrg~2U(59W=$Itz%wt>?!0k>OT$edo`vEoo77VZo0re!unl$>2rcbmmEbo!f^LDQL zYE8K>?lTg;{2~OO3nkd3IVL;k-NbsMfB~5QDZHfl4nRS*2_EdgoI-gDtkJFX zScv&j(+X`>$hA(rln^qW=S7@%8mj9o-G@o+D?GDP5h@?U*$42b{zW^0R!tAJGEhpo}c+L?ZS`q@)VLVITN6bH^5%~*Seu9ln0Ta-uE9bw6FKaya_<;>6$3M~kLZHzB zEM=Oft?e6$>%&Zir==O=>YJ7yM~f+iUO5gxK`Xu@m@nq+DU{{7!KO({dUDP|MfQO0 zy~UvIePvf&(XuS=?(QzZo#5{79^7GL!QCB#Y}_HZI|&lp-8b&;&f}bSf5jbleO#Y< zb@!a3YmMrvB7Dd38?np@v%^ZbBA#WV$jdW_2h`oCpx&Hwe)+_ctwl7u&@fhoSWN4# zH5I$`6as5lhM8Ts6#Uf~yi(D{v6~v+|PBdXvqY6Vv)UxCu?wO z*1+Gl<9XVOW9|GR@DoHB`#IEnAtvg~t8r^N`a&+E$m^D;Jd`GH5mT%^{kD&+E2E&i z5UU2eTq&m5EMezRpPyP8DOH)4k?Y_N%Uem*qKktOMdb8-D7Pdx;jF3Ra8&GeUB>ex zddOwRgmf*CQdz9T0_B`>lt51Ax|^l!8&m>NKlNEM8e9=felP--pyZ>dx3P=P2hyB1 zJIO(}(A^jJBnCJ)fX4shXeRE1rF~3&tB7E7*~ zyF^6}=qRtZ=XRN_rmMf9!Gyq)L{i8Q!>q7e4mEuLIdLF^HmC&~T`!EooMR*(rc)1+ z-M;2^j9$06w_yYEDh%u&GZ{k-<&nuJhqX08xd*K5fHr^eDs;Mbp62r0PwL!Fnf8#F zi@j}S*42?*XP)CMJ+Rfr%nqse<2d8fPPl&G!Ly~Lj5bYpcmWl6&v-$Bshosl25SeV zNu?ESW^)@E9KDKFG13)+?Dib%H5ywNyN&hiOu)2>#s1NZ_C}DOu|p36`~zMNj=jOu!1n)1nG%0e!s*+1lomQz5kO3B70HY!zG zG&Qb3ln+PsP{U9;E-J1GQL5&0a_HbRmQ+Sl=2z!VMig0=K+9t5id$V%?X83r?L#FpN;}2^@*J*u32jQlvfiEMwxR!;&Vg~ z?q4uiws{247m9^)G$PxRnj^hw&Oe!A(<8jj$RCyXKs}g&g?WA0#onZICX7-n3g>U0 z;?wRm1dpLsd;HV3bAhr65RzIRpskK3X@s-ouF>ewU^O-5eK5a)CS}6b_X;-y=r6~y zgzIexFbEnU9A7Zj-+W}#+!d&>jVp@0z+CYXfsnlN{H-DxDajeHCR$2 zG(FN+UL@OZ(JWh+Ehp?mtk7DzO@nX^kq&01uAVS1?hmBWA3vQLUWN@YZ5P~o#gY>evZ1+ zzZThkkr-5uv`=RO57zYqf+3i7DiX}Nh+w4zM@Aa*Bit1*nl@uQSaGignyqCTFYxHr z?lXWpkv<&^STMmtA_Qbd-c=;3o{S0rA*3t&4OreC$?j6%N82X!_}pq6(DkC!h4R{X zg)A|!;mKY_e95;m@}wr6RXUn+6c$o?Bm!sz8!l|54qksUI`qMgW?KBn19N`mLK~5@ zS=qVLL;CjJIq}Qdg5V;LX{p$1_>q}Ch!B(>u(ci_LZDzaBaoGDO~0nsQbli08;>)dpJsg+ya%IG(=H17}F+z<$iH{zROR74yOr7vDtu{~>ThwQp- zUP5%ZTlvaF2AgdAU$-rv1-^i^g6BLd>)~8DVtgS@4 zqP)eKV~PaisYnN)L^j4jiN4PF%X6TycCP;G0`7(Sc6vDWXRH43aYltJ(V_oOSa4T* zz5X>29-MlSjT~9%40GKOh&%}h&)SlJCp z?TQHE{5`S>_Y*RnKL&-bxiMh3Za=?wW@xhw-H!(#iiEUse$e%-gzvI!_goQ;@+Myh zS4Z+hA1D$!R}fh{Zl1jAwrD#-Yd({Igxs>FcSk*ZM|v97%KdQPW;EwH8BDnXs>Oo} zezd(IrJs84M3vfsW3unLU)84nxvF87LF*4)bmLh)tooodr|moP&UMhWG^C89H|sJ} z2nIws5GwOmSiu-F_^^)H9HSv(ZV`2db1NDTtk+m%TcJtq-mRM@@24$|ZWJSoD9RUS zM_#{)kU8W1iSJ-bXbnhc7kTM|;$MWQcy$V3BzEeo9&wwh@y4db{!P!SlJrNPi4Dd28NWDVc?y*p-0t%PG5(cf zoO+>PJ4wQBfsdKSWd|0wN22Y#5%ndk=G9$jqj@VpkvPQ_ohMoCK>ffAXSkj#1La%| zBcV1~eTJ+#bZ|E_5dJ&?rnvpi><&DY+6p>m&jmY?!9L2bU2m$__fey3(UX|XIJ{*1 z8}|4YQYf91ebproWNeRU=%uUxiwXR<=gtAx#6THc)ls_;tIWYyhPb5xXGYpw#JlfV zkMkdl{@EhcKp57EdRP-q0&MRDD`?3uFFVnj_6Ymv7cj31Uz~p3a})6onXwm^lz+er z_+?ytThXCUmSA@ft{6iVT?=22`0XcB=NAX~6TEV! z7_8_}kpG&a)nafF_8hl8bQdWA$|2GA@|;a3?eCgwqS#BVtc@zZmfxG{6wf5vorJYn zk{H%xN~vU{9OT_h%Dfz0e*nf?y{07Z^<(~Alo~x&(w;+T3?%6YQe6v$(TuJ&yMcPM zTsP+FV%(jMSHOE^igO5dtZ)`>U~N^o;m|E;UJH)n4l znsO)M{tsqdvQW5YjmMx<7J@WfNY1 zIWc=xScfoWCdCD`^xx#TAzf-bnYgF~zN5)k9OxB$IvsyLthM~TY1RJT%uKJ-BRyVyilqBmgYM^4ql`0kHRc0&hfQO?`(^jgJYg4` z@>6wdgZVX)*Ri$(H)ybO*{&>F)zbHyKp7+NUmPi$&aS@J*7$^?GMTce+@#@(5zx|H^$;k1p9B6Q;vp%{VL#-nC+ys+w*i<3>qvM89%;L4EKd0nP zHzSxDA_5M?Mq53@V#-w#ic~eP1z!q?HSPgW-IrwvH7QO&0GWD7KrWcW_c& zUdG1*|8b0)f7fgY<$lgSK(ZQ|_PXk}d5i4Qlj)bzlX``D+k3Ji)J0ze}7m8bm-D@2v9jP{O-$x9`Ks1#xMKtQMU4k|_Cbak1RVL%Ld+kxFW z_LME_D?m&rv&saSCR>IEv!^K&>EIB{;)?{~)Vh$>?P`Con$Qss;baK^Q*u%E$k= z)6-o9H@H3|HnJ8=c5-m~^GQv4b)4RC;n>?NOl{QaN zL(V4+4JjV@)x{sRIkIE_0dDqh)Jwfyo064!XF{cUrxU)APi=cWqGn~xv+veFPWneI z*5_?PfH;2YAT)o53ApQTdShfKOo~MDTWXDhX%CFkDxillmSPBumXtOv%zc_wXmhp8 zu`CwRg1(?^ucbz^RkX8j0rIJd=YD15q=J1)!c_Mk`5zevG&PZ!(xKDfbk{ri-V?Hi z`8_3&sTt(~DDTe>Kw#MRoOZDxbY>U&TO(}8n_(` z$5Rn+M7k0&PL{H~ReG}qd0q8#MOAzzeJrM+rtt)Qw@NexeGm`&NDMHe;*1qpbI5af zVaM&P!KIcgn*5v{(Jg!Dgu{km#i6lKWp(&X_*^gY7d84MSr) z6O#9OYYbh$L0?o{=h`;J%!vk{?g?}p$NmqRaUS-jLDht)tCZ5dU|9_v*GScKdphf zu%yCUpo51YlE!hCpQCzMJ*7N^`;gc_w`ti$>H@F+lmZYX5O$ zTQtlsb55JDL)P=-q*l#|bn?6}-&L25_OjHsAByPhchr|%D)B4hn&jTdtHCNNP!Tm) zrq*JeO3cL3Qgf-IvKa5_fyb1hGkRJA4{~#E8EbH&*>Ui#A!1hANDD zziSSy#@kKus)JBS-;O=ufWEp)O?FXQjutwCpMih5*A*X#NisR!m;XNU;)k52SHp*P zAfS#p$ROsVkBwjK*N{}{31#t^O6h$1u8>Z~+REO#H;9UyVg3MDY~95!l9qlK4ml{6 z0r*Ct-yHVOS7>R(wCpiwg;OfZXI7`kJ=+J}&nObe?G-jkdDA37t!!!hCWdRx9V+Ab z6Q-_1yy~+ zX9tY|Z348G8S*YfuwQn&>`q=@tdp@3yMmnhPJ23(2p;a%y)xUQKrov$O$3wFj|%e7 z)8G)pY>)Y<1?KDvuPYdFk#2Snnjiqbzx69%Wx-@=mC4|T))ne(N_ZOQBp@)#$tncW zFD9zVGt%oZl{;!HBq!VJ{RM zHugLuItEKSV-vq$cJ(6H&Pukt=61z3b1Jp!M0j*9(WmB>*)!)?9eUZMo3W*j=<<7y zYxM~q;F!Lu7KX z9k=%lV9O2rSTu8F$+k$B59O4_UEu&#Ehb!92vq(yLmiquZw*!CtPgW*PU(;wRv0wI z=^DD3_MiJK7Po$nQJqcC^||`H_XNj%W^eLEXf;)EjYkBOI(igHuzdj{a$T;@pBDhY33*m1%I zaby_Ff**E8qrJ*JGA0K5m*w}myK^#c)b?Rv=BDN86ff<3l(>GwF?z@}Wt{UTw!wKu;hDiwGX{J`29Gi~2#`>9G$XE+)80Jmm4FaVE$sRZ zs-7z;+r?*nXkH?XL42%$2ns2n1M*TUqXmHv?-%=4Wvmz0?MbT-~QR+Iz_j|!}YrWBI^im%71DJ(A zG1JW7j|qZRVs>`00d^DyIj2p%i%hkvhKk*!0g`dPFyfyC5RVq2Go@(DbNcDP~WV zs-#7h9ekwQ2#eCR)ah{q23~CHp)O@@H?paP2T#7^7_Vg@ktfux;rKFP+R&c28u|4A zm26r>$XgP>)gWQmXI&dRupAkt&o=oQD*4rvD;rf|WIFc; zfi>4RUbu#DUz_G={M@6GNls5tj=Pzuy?3n;3r1(zwfb>f6kfkEk(!7D#)xjodP;+y zaBD2EzxU&hZA{_hVugjw{L8$QJNQBQgtY(*d5#=1J_I`>f!W_w_Vb1!b`G_A`zP%J z8Ub8ApO$J*+Lu4HB~5bkwgdx-y|K=E480%got*B%>^$dgkfJBD*S%>tC;jf1zJR>F zN*nq>(u#2yh)6sdX7FT@&dl0+DLqbQaOAIfSn)Q*r2a?T zcm{LV0SY;yxSz*OZN*B>(iK0iA7u7ASh6vxc!_8;9FQ@?N&3Tey>INhv9BJk%i9^2 zDx$fgBFr24LL%P5fc?r11&M@VU-8e{6TL;H*-R5cN8NpHgPeu*GIA~z@d>^(ms92F zXkTYfPD04M8K;c{mWebGjag#W#zzx5+}>%+j)YSc4;uvej}*@(C)%TK{4w5DF3SZJbK`e$hl;IvOCE-^cJ?~lC@z;8tcZEr| zv=vQ5@62~mjMbiRRsn>LbImiOUD+~@ie#>~NO1*{(+D(8qU%#8B`m9vU;Nc+lc__1 z9;7VtxZ}izbcplSSi@W78JXlvoSsFRJ*e}}7u(hTj2*`gR|YqZYj&A@Vs zeOk1B%s(NJ)zbCRuU2?=h`axaE$;gFqY{J^;BgotHKoyqiy8rOT^EW8IdpM8vF|^8 z+Fs`s*>z;_l49``i*yXmu}W-@`c$xKUo^CkASnx7eW@O9*+jTqukyG?A5=(XWl@rl zZxa}O%I08;Q}wsjyjGZ%xO)}s`WO4wBC!0^>w$)*zD@8m0^Rg+;CyD&<;zDWg zEa~qWqp5vf*QeYR7pl-QhYBf(dPA9`^O;UKNe*wVu@`}J+X{M$N=?;bzQOvuRR7{l zx7Q+kN;_$#MKpm9bZL30&NizA4w_h=xD%=ec`gs{bhF~)zXr#4j*gfWr4sgra=6MY57;7rF zxO=4j(cYfq;ewJ^N097yD;H>Q_hTsjg+RFQAAc=bU3p&jAF8#|4D8#Vz9Paa20Gg6 zs5DQr*(kPbAO3Bf{DX4T;Hc8OfU_S>qzOFSaA2oEpUESaD@ z@n9e*p7TWC-OwT~^G#2u~gINfetE(lnr2&FaYr@d4irY{Q|8nZ%f|OzM z$vU}%DwQ}t2E@?i`5(d5k>q8mHDzp3Ll}xQP6d#upf`hvWB&=>iKJ-FP9&?K%p|GK zhB?iNr*3ZXQyZSX(8I{we)7c;sJPBnWKU(3+7!17TSWdAWenhqmaEbb^|?WsZV7;x zd;{U<$LYJfyB~h$crV;_zj8C-`y5Z9Xe^vLXk#)n#tv2@7G6icc~JoKl$NmkJMKez zH{@QRwbA$N`7dVDL!FV6ywX!A27HFqzPdGF&3aj8F>g_@UT;%1G-C^(uOv&>1#0u&@*9DetUiVueV#H2 z)|3bn6^e>+`nk>TH9blQ&=LRiQz(@ZoV9`~G&^ zZ{NWoRqDi;(6}-GE?^j!31=mpszw749=Nlesz{lS{)Duv;o8aP?`}M4w43D!gC8sbxBvlEJ zJqO~Lt&5{*bsAi7(UytupMh)=ACc~i+b70dGvrZ#x=O4=HZw3g9Q#>pg!Bs1081MAovR z!KrC7_ibQlwJ`GXE%aO!8@#D-ZIihZl54Og7GWbHQi@V4_t9R$>;9s3w-2x!Y5@Lr z_cvzUdd%!iRNYv)^Q^jhrZ^rQz3*Pd^{5?YKKiMqIPt+;sxu&!^p{6deY|te%1BU^ zvaYsteRPbzFvDC|ls)Q|k-0g_O+l?9)TYF)-%RUJV<<&$szUr{lPimhgeaR`bn=Kb z8BmEDGoK-n5dr;-K#UMUmGGOr?lIi`+HF!f`52ildkkDn<_-{{h3;&H1oIZ2A=azs zA?Ej5(JCEQI%&dCr`XbD+CED!))KlYD2eo^m47Xes1a7U*r-{REKf;}-GiT*5e#|K zyutqTfn2m<>#&gPao`qz@Zc9yXE%!=adjxdqu0;TTD8xYXV_OCoa8i|O6GG<%jBPe z9N?k{o@F3VRsz82cFigm|1j^5jjAI%!5BQYRAGW}9Sft12kYu`wJ?0wP!&iN@u*LW zQQJ*972dt(-6V)=$A-0fz#4|eSde?nuSN2xGV*4HA)1SD7P8no1j7zDms$e- zo^sEDB9T*d5k=P%x)x}(KFL2m=kikEn&|q?rrud|zaiOmDl1*8zxQ$z)pDhW5_;O7 zt%kSm7{16WfJ03?e$eSUp52Acq(4epy9Y^Z(xNF=uL&YoSa^5%$uQ@+dr8L@yBu%l z9Ovw-{w)fW4q7Y|sYEPp=Qym9lGob+s^2s9SIbV|3`I|6avjd#BzVILB6wk4(K)Bm zPoD@;<*@k29rG@IZhZHgcTSAuu4+L7QKHCB9E@cKnz0E4#SY*rTSLD`w$u$zB1z0q zC{j;I!?i5w<%@7Uj8m3y9aci}=@;JXx2Za{N`ogr(DFln@i8gd%sOl`4h#Le?Vs!5 zvj(GOs2ZT5wB>mhLyiQ$)IzQ!3r4)b;!g8-H{%cJOj4hbK+}{Z?f}Z4um2NW6}__t z)a=HV@QTmaX(0A+(QOsN!5ppH{D`gDGgWIJZd| z_n#oX$|&$vyP$aj4X|M}HzaqMU>i-@sH%(U&5yb3wSSni&82$$55(|yaoPGO!5evu8mb$ zJ*B-BKR|o?iNA;iI5SP5SI`;z&a=({0pyvHIgkmyM1=uBROQU}yx4I6!TWm6K_glU z4V^6s(cyAu*+fj5pskrdSRhR}zTg4BIHtEVYVC=>h#kd||5U%fNCXFw3WU<|jm6Ol zzdv?|Vg|ty8!ib?Y|j~pfc`#LcfleLnf>*P(Ks#Qe4|V{2s06)MUpP-((dj^RahG8 zd&UG{ytI8H^Lu*i8;OcgR&Epz?te0hk^rGwMZT~B7OlE(${;yOoPwW@)SGG{m6#Mh zP~Nv$%BA^KRRWBYBijdxw$0Wl%%P&BDhAKQPl*lgT@2GrOh4R2)r)^`=0C4rf15mD4IZ2r-qXnId z&EyrulKw1ml!@o97Xb0X5gI_rXI3WQs>kQ6rMFbKyb44X5vj&5^-=x=Ai)?0(Uk}4 zQp&_4wE31Cb1?drPJIxel$B3sXoEKidUv6H#*C*_Z32g%E}zuDCz`~fqgx)l1dUa IVkW`=4{IiTRR910 diff --git a/third_party/perfetto/ui/src/assets/rec_logcat.png b/third_party/perfetto/ui/src/assets/rec_logcat.png deleted file mode 100644 index b3b49053557fe052d01f413ce036d85198b7060c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42205 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%0D8w?YB%wE{5_JgbDFIB2NwHxfd7-L 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

?B(a z^EXuYKsYEUhQXy){|c6(g56aC9^zvo)|rQ1l^;rCfb64tGEz)siBIw_LD9DR`5K>h zf^>MB?Vxl`<`)mX>d5NRUk&5e5Mw8V0(1s*2z3n$5(*q|^+xa7~m+}$JqMF-e=ayW+&@r8oa5RB^} zGB{8Q6IKb~FoLH#nw){7cmg6-L326NQ9_!jQL8Os$f9!^Y?DyVxf8jGh^MqAf{ zGVEX_$>`mCU(FPo9H7$}mSN}R^#}u0Mej^UxF|+l*~5_%!O@+xPYo1u_B8ofuh3Nd z89)3*MdTHP-=qTJ&F51x)lIsZG;yDA?mMvQHyIb>ZAUY;vZAx>6dm{bR|9|7aPVe@ z-@;T39bi@ZJgY1?4mQZPh0p6NjzcgK5cp%slg9b>K@nFF289T5hEbv8%|wU&1-vM$ ztOGWAq8kOZR+3;qam1G~$rC1bV~{xxx1)*+h3#b{+EkB<1iHHr_j4myI>c|cx=1i1 zOd<$!BF2Tu<_b`Z*f2KG?BU5t32=Uj0@!PifVpi5KbRw~P`dChEld`iiw~}r(Alun z>`Z>cnt7R0fCFS`Q5Th*J*oxSx%ZGmgtY&1?5Oy5UTl69Wv711zP>~Td)ZCMhbD-# zg{oV81JEXTm%sqG-Fwe8lTI&gyypGG?%|V>j)>XQEI7T_JIzrk>UXQ923zTcdK#f- z8-qBcOSnwlJzJsAAXb|oL=GgXj5xc>Hb$HBzZDUfwK|ghwK_JNu%p%yEpRS)J)DiyA>2 zp|Y*8NchdnBLl-)3^)cYaYujZ^?4zWQ$j|eOx~v13Nb}=HYW08tKjSqTK)yuL5TU4 zll+q)neYch+kUP$DVx}92I>Yw6ptTwTU2kAD&j(73?35Yz$4o##e94et81px^bx^rAAeA|i5xba*`J#F7A1V7Gc>*zn&C zzM8ph5lGUSgqpgGhZO?0B;FLylo+vkl=a9TP_WZDoom zhn{0EL?DpJ*PTfhlX(Ubhi&CvpJG3MoGsd9Hs0A^5c!?J#AL9DC?l$R6fZ=<*Vft5 z*(AL4A5nwPxU!1Eu50p9lP^SZP{tW7;eV@T5@%Jx*q4sw6`MJ_1sqzCb z4peGwoZ-{XFxkO3Q9R_ptJJ#q<=Zs)1)@qBox%Pt2|8@w!-f{b(8?4E9DTl)=5r10 zFhMYl55UyC~x@sZPFr_ru*T1l}MIbHV=)0cVvyO-f)8E#aH-(9Bv zhsh&Cp}#wfq~gICUJs?5 zZNx?tR6n*tz_F1{*4iw z=eDK>dthsGGN=Jf-bdORTDRTJtbeRhP~DBq_-M$C+RImboajmdUJ}Vm#x^KYNlP}h zpXxvO?56t>zxd7*-qd#xuAjj9Gpi316hN!5lSy;eKV1!W7%cXwPky0xVj{t)G(`D` zJtUxufI5gAMxU*3jX?6YLa#WtX20pSc$?Oy6E(ZV1!A-96`iEOu zA%SvW9jd$NUG7tM!nrLlKl-oPdw>kMzCx?Y;6E%cFzOuDIiW1Ce(8!-=xP3QN~)oO ze;uLcB7GN;aK`R1uPM>K_FVL1!Ya~A&}NKgbLoQ5CV%PzPa}MRg;;fw_?i`1*Lxuh zkp2h=K4}BynwAP?EU1@zHbrNa5M%iStjd{qk6eb*-C*D)@*Yz7AQ;?JxIXCZYy z7uC))VjRugo|CtDT+GUIPXb5+NCe2XtF7`*-#}$!$&kv>p*F^#&?<`Y*vg#}Kt`LK ztX3QABTV5MTy5CIiR29g@IKm`6$I?gz7c6|tDj+4lTvJwj8rwTF76hyXx_a1(le~2 z=)O>Lbg$?DGMYpt{r|H1Wi%|YiJ}Z9s2)^l;{4NK?l_hh&aX18rRc!cv|p0RJ7C9S z(#*Qyvetszbf%5j#m9zefD3@@qk@u&G(0{NlMfD;@Z*5D<2z?MS6~au2uUGV+vzCK z1;ZJGN0C+}v&|v4)0i%rGL9H+24NeV!@DdqOI}Km?+pztKlmZ{io#q`-oj+Q{npxr z_`8cC{ujGD!y*c^>N+c-XQpU^ZQfpN{>3#zNCzJ*Edr&1fmHBe0$8d=rK{PfRSsEE z)Lj*YCb<8UI+{53qSV5QU{E8j6zXOYj3gLsI;Vju#wW5H@hVcg41Dis4-+7@|GZ?$ z&|v&A91iWW*J2SI1wir9hJGl-^`hkrCgz5zpPkP1-^$mtjYp(TtusPVlp+lqI#RaC zpYP8W{E&V`K-l&;rP-rVKElvmf2G~}VUd#{-X{XXEP4-{xxxJ!CT^;;es>xYU5G~e zw`OK7)*a{Av=h{%wHzLrGmVrNUzKu_Xyl&IUoJ+xSoM_p7jtwX9}J?{$(-o?v`v$x zPtqi_u1nul#(x6PtvF~oRwHe4FJypXbz)NhO=}5CP9u15sKc*WPXuwo{p|)^Rt(K5 z7`i0sM!Bk#M0*;)^PIi}*psmg_NDTMmn26qL;^B2N9M?BFODChHaqcs?O~>4K;sNQlcs(^6IfnrZnC z`yHgi*@}u)$j&0}qdH$GbH={?oXj=$2SS+wvi#2pTvw!l9@+z`H7PuJJd?xgyopB$ zG{Gd3G~(~;Z&y3xKu%geL?&IPC1LBL*?|i1Xf@4JB~JFzi#M1;=~%RBze>pB5ti=n zZE4hrll@c3W|)oOkclyCf+n;HhEGdQ#90iW4>4k6Mlmy}ZG_3zv3YQlcbqjC{>2mO1S-{Lol^E_S2?-*w7?bEwGtf3lIXf zWTTQPYl^vhZ3WA#Ne1Kq|L(cW~5`t*QJf6v-E(LuZc}^}L^lSHf+WK)7zq#ni5j`Ec~w zP<+?YkvjjNqfFsClJv>c#_B%uCAAaTpSWduaOcUerEj=AYdMJnZ~R%d71rd#okTwl z@85&*S|ebZg3Uws%5a`YI9|bHVb24|1=0d(Ehs1wjSao(|-3W-)8vR zTlC=*yPuJ~8J*O9ZWa@&A$P@|44x9YGORl=E@{3AjE5-l4&e$ni4B1qYPk=G<;sS9 z)0j%U@&EJO)FdCb+}|xu^?TtxqFKx$!Tez4UN})QA2qKtmL)Sjpcr-yyHs zt)jeF3@TWul&yFL%3K=3rL9K&{P`IB33j+rav270gk#C^ExF|JXQ}vZ^NX^Q8zy!7 z#upK(Y>pLPkpzuI;AXYO?#LSmzU}#YQSN0j6Ua6lszRBy#V@uH$h%UR4oosK6jTc)U4@jmL!aO2H44IwfvWZWX<4 z(yD&QiRe7PuO#|PQ4>+=K<{S0#{C}csLPQMOYvTVt_Bf4YV`Y`wGyVIh9G#Ee-n_N z+H}0K0`g3P6UD&^l$h(*GvXwz?pEO7$a%xO%II*D3E%R%znd|&yutJzG6z`*H!0A> zkm$EZR#e>A4TWS`LX(!6P8F3Z)3Q*PoZLJVj6ktuF-&4J7iqpCQ*4C|tre~1IdCFy zPbotC&}?L_E?cH_`M|1-`IG;!%F$91TmG z4@+m8n^M!1?JL(otx9U!_j;{4IXAK6KVb_@ta%ErJE6m3KHX-M)*y*?y&Q}slt8q9 zhA{Lgr{Hd9{kYA9oULi6@uXBxgUcZ3d81sUqU5Puj9aEn<=9;}rmd~&a3%=%M^J1# zXiNdi1mXIQxkDMKp;TY*N;WR5BtH37f|$gJ=&1k$`$2{rW|j=}4_`#mMXqV-inu_W z#Ea&J!8{tCx(!xx$_>A6v|eS z$2AN9tR7^;=?WmZbR)@z%|RypI%Iw|7a8ZV21S zZw^$w(|zA>ZjIPIS%iR9N6lfESQaWL=`uq;^P6*IGGzdYTY{tlkmCi(;c4;qhA7*Y z(ZJXtrHb&bk`sA6uZimC>>J4^2-z^0#5E_}rsP6gTkeQ08LsdHCFo=!94kA|3fXGZ zah8(d`&Tv|Dus=jMpf}m&)a=KlCU#Rvj9gpsw>xc0U!tu80QV5xo&NbyOgo9h4j%X ztM9k-xFxjv3$109>Q#tUwv-f==WwP;D(gMbTV!E!P4k{)Rb?^xs;9)$a;Ap1<&AAg z&J>8ORHhOC_96>~Bj{_gemHYLkLHWM-j4m5e~)lOiqXKV;rB3tcIw8Y2iEw_$8pk^ zYi2!i0~xlz1s#H>S}H}!h|P+9{#&Nj%KZ?Vb0x3&`5-f684(TX2m(vh!!tkW3!%85 z;;P&38QG7S5$ih%GTxwAWaD;_dBV6&3a?H+gY5o%;F_vVb0@#!%_`1@o0K&q&A7@i z(q9UVa5&3KDR5q^T8_wU8F9dX3Z7-!Y}!mWd*~P0$94~-YP4nSP%j8Muek;n!)sMn zluzj#Sv6^nur0vH4p+0fHPW z#Kh-h+1SFYPq?e54Uvdsq8{FjM2%55D*;^gM5H?8I-gQw`+lB(qh-%x{1>TiwQV-t z40`6>60zAlLw8!Yze1T#HP z#El^Ll~f^y}i1G0$l>G{WPGyfze$^1PgYoY+sLC-KL zbP;2?vYgq@=IOa)y$#=&8H)brs+H|{>5Wu?mL40rEqQxl(Jx<_rIEyVCpFF_tV*5w z!Q#>QB2_vna?C2C=_tA@iy2~i-s4lnSd-fxM6_}cMv^=zcoF(|Qb<^~r8jM4c{YmD z0+xmy?-t(F%Y**4Lr4$U9}pZ=8}hRJP**9+;;s4yp}5v6U(@ry71YX*W<1Z~h+3Qlu0VBwt(%UPahIN0gRDh-Mh2b}s%DnA{YL?@& z94l3q00|3}G${1X^tB?6q%uqmCwO}3(puMw@X#>_<*LD*NMaCQgYLrzym81XR>i+A zi36wIj^K9jH+(xlv|%wwfLnfn_GPxcR{xmEz>D)d;O%EiNh$60IM0=Bn;ArCF`I+| z(W@kqm~xGxk#IvmfANoolNvexHq^>VFtqui2Lt3MV6(Yc@5rr zosEn!AUq%oRO~1@GBDOc*gq^mN1y&v!)tnZ{VhJUA{IJ20IWSk3i>Uc(xNGerec5(;e1QmA|?5l{b?7vZW>i%$6X$5 zH5FG;c+{+!&f-5%py55zI(K01=lLI~-23uek-Tq?0{UKX%qn)o=Nya}*2He8UYvU2Sqw0D`R6>m9dI<6Di2 zi1y17ZzN$qfHvLUy(ap9-vZE!8ZE9*qE+WqPH@z-_9_Y+gd9#TkEvN^D0gDa`u=*r z2D-PXWeZ3w5KCM{@5k4(T>~#8n&mJ=96bJxRp;-GCgc={OfGgR-RjZN>z-r#Z@{db*}WNuCpzikha1YY~bC5 zxGt!Fw{3GwMx2mu5xa|q)qlXcEh`#$a%=60@Y&f2MtFiXOqkDa9~^({aKF5d=ERy% zHXmM}k|FzG5r}i!b=J&WPl~sTRL%Bngg66LWm~jlaJxtbRCRG>9S+_Nn;)b@BI0N_ zT-0!(?WLt&5Wo=@gg}>wZukjt5@w-_n*f?HWfI=5C4)F@PukvF83aT1-Y`-@P|ZfS z2HKw}8PgHgrf3y6Qc0lw0ML2VFf41n#9Fczn=Ncj{O-7rAfk?HS^^=$6e8+)Z5B#~ z>_f^FP(#l7A!)iCtG`$Q6eOf;UrqTAtT!sxx(>?kCg zjC>5SX>ux+8;3!qF{BX-0SsYOCNyMnQlUgcM3$}doL<@9Da?l|I!YDq-<#Fr8c zRUK5Z*dQr`w?&(mkqouJ&o!W@J;a%IN8?t&MGe)QHe=@)IA(+9)9PiYxhD3Q}Pwr1P*UZcgY1-p=tDd#??Wb{FhbhsHDmVcR{! zk4hx&1@-6iXT`F2B+*E%FjTG%<%6&Ysbl)r zlFiNHe92?YHK3g(D#`RgR2X5dbXX1(7KQB(O%R^^ut+srRFJ7sLLfq{QsML30`Ldh z&Ts^)0xq`kBu zmQMwNG1#IZX=m09>|OHx(>9*(MbCNZ6`K%FFl#xEOrY% zYes-4JlAGrZLTlKDnP_@@TS*@xJ$v1EBm^b_6GxwuT`-KQ7fl$1}mj>#BXgr z;?fZj$M?^6F+}jjPJFYJ=4orBs+x;Z0x@hQTkYqiowoT5`3|qN0ALhu7CW+_1hS9s zA8k)m(^rzrffuPtlwB3uki1ET6bKy@e2`{eN8&|jpS-^ajfm?J^s>?l*#HkB@KtGo zy@AGX8sWqEIGHi%&@kFhihzi4(gi)b??mMCeOO(7CWM-eDzZDFuSR?_s2C26J}D9g z`v_Ag*)Ti`3#Bp}+6DVJAc#pnC3^scfhUuJkOA^HClo7qH`oth@nk5mXgws%A(;%q z==;RaK4P{iUbbZk z0ys1XVZAL-3Q0ldGhWtiVHyuwrEWDVO$i< z+{JI#CZv}GHIRD8wgebbX$eU}`@on=BO(i53Vs-sjKLWgVK?=EPgd6Rkb0P_Tq4sJ zBYJSy!YB;sp08sqOm z1|HUsP6?AtQLpUmLmKf0jUk*La02lGDV;LLoUL^7ChmVQxkVTNQEdW`tu2PfmLsHu zqQXSR7;P*J*1DkKymC#7&K;)agH&n5NKGeP-BZ2FLN({=5G!-G;etw3ym;ba86n#T z0UTjL2!|$KDEKK6fynDu`sR4eU@;O7-yK5C1q5Q@-auwTr)O;^yj+;NUdUV_?@{m? z)9q_trJKQk7=G*vkgV5#_5_Ic8s)Vz*I&HCp?{LP1ZrpMj%X>vK3$qqwdsp zr|G|c!>~=?ewBh6s@4yQpoRh)!pk~cp)r*ZTS^uzIxo`In3&7kel#?f!77KKR=Zg3 z9{QE6N{FxtTXYnLpGz|xc+%d1j*WUC)i7QbL(+snMbY6`)N=?LNr=0)n^cZK5xhUD zdUhea35zh4kQ1u*w*kBxA7fYR<)Gn;VE1MBxiZ`WRVh;$P%jgE&~~lnqeL7giU*&&@$)L>qsB>^_EVik zfvf4s-&AXMBZ|aC2EImc7|tXc00bG5I=nYsSGU0r{acRfV^mks-!7N)1e)zdPf>fv z_M!#~>*wq8r}U?H{3xOb^)a(phQiY{z84|vS0>P&PHBdaK-+%!SXk^l56(>b@`^?twsU5p| z)V#)%)n5NMaJFqy9rG?!J-xjukw~aSO+wY;-_%vf)aOsNfp!ASVYP+8lVZE*m|Csm zZ_)i@mCOm*)(Fea7(IdTbhaZimPBF5(-OwtK|8MMur|hS(gYwwurOqkp4dQbEDy}& z$O$W(5to8gTKPF+XsKIF7*cDbu}QT z(CGLowXtWDwDEDhVEy3lvDAKqBzjs!{Cc3n0JDwESL3w{)Gq%CeLY+oSI>_jKN<6w zncUI$aB2tCUHg@vi$mSljL9sbp2^)O?a{Pw5A27I*^_i&KUw&w?7=pJe>JMcYf*oU z{2q0lEZnylJI77B30>Hi&@PQhHi`WNFNG;Oqkf$GM>%Kc7km!o_xXw6TfR!kvu1kO zh8o{4S!?SbZbDU7KnF%?X!=xy{5C3Oq|5Mb_ljkHA zD(%r`@L2v|rMgqkb3)sWf86fDzq|0ywy+J?EZh#_->>m6Y++j#+K(oU>T<~Hk1^2> zp*H^=X{pM&$NXQM*CgCn4^Pgke*(iu(-#EfEdJlq6L1c7#xq zQ@u`!jCNv5*GB?EA*z=y0xY#ivZ>+S=3TV86U+@RbdVjPj#e#9paF(d*p=?-FGNw7 zQV}Q6rEq|=0V2G*?MbzBCnt{L)kdi?r!yJFf1~)XbTO0CuF_RXp5ZuB__wWOjnXG{ zj4ORFoRE*C7&;V$(Ie@^wQ&@faSCK0krR!{c!F`HetEtBU-O8#&*9qUJuW>AqvEDo zn5z9fgE~hNqafH8+FL1+F`N|{8l|Ez@Niy7yC~?eNTT9z4X5HaV~sdJBtwd0wk>cW zsy|GjA($&0!;FZ^S!59!hR$yc3*w5SfTKY5Q6QW3#f^f|H@aba6vtVp$ZZgDbU3Ao zoBlzJVnXS}HXoXy)If5R*jov$XXfJs0;h%zm`ZI1(SQW=(B@0Yh+rO@LVInnK!sP8 zVzpVm2k)gS_F?#(`n#LykB+*24eoKT%eNYuda)WdfI8G=)b@e2bMSVP7 zJB+`XgcYh;S?SVpUI0Ky!xAi*934Xn666@{7Z?&HB+XPNji+>mqur=dA*`10VCJ%k zz}Dl6qkyA8by8qk|9w!E?UHQ4Hd%9x!!xrbuWbG#n9=Pr2QUhXZLnN%O`t@Exr4w0 z0xFIqV5+e_k@DJZb*lA42<9X+jhxEPP~sSEgEl0>D#=t#q3+hkXM@2;xjG^ov<*QP zCjjqs#ZkafpgJkgJMxkM7#MiBQlZ21=Kh;ejrpMHtMmf_64CM1)?Z4efnfBMaUe07 zSF0b3;7frBT~_JxMd!JgrtOJ^?ge4&Rua7)s9LHjg9YaEsx)>!c4i^DRHf^=1gd(1aV!}oe=g&f`V=llrdkpl-*oaI{87x_6Lkg1P znCur+b_^>ML2R^!_!0>^>Qzv^O=J&>(_#%GW-mnud9IF_wK43<=9t{2yNIKJqkt6! z7B_s@svcwW>0Cya!dK~K35yjr>OKSg_Km+HzDD%f3-5TXb4$@~M0+>E5R6e-X(4TH zkQs9sXOEHBbqbDzIVD?28qS7PM3EW(5medHPi8G1CbK|RG9-#8_o43teS;Lnt)CWV z#E>}j>b89uX86`z_l^RN0!KiBAlkN6b4o)@MZv(vDMWQJj#khT!SfH|xS;0gK}YEQz+yLKwONm?9}J#Wer zXhQVuU^w>a20^{?CNLu!9#z)Oa3u346m1H?FQ<-x|NQzc11g_-Yx-}xsW9jS_j9~uo2X0dtyi-`bw@r zlEzX@W$LJm?Tdk;>CV!cm;`T3odc^CWj7~62Tgr&Q-33BnE)8Zkk5yPON!|X%ftP~ z0M_4vuTAaR{dN>^6qsQOL?Zw!VTO%GV=5k_S}Ub8kPsrO7EB=~A+cH-Tjcz7L($oi znMz`T#8vD@UNlNJI`X|;NS?{OT3JaWlF1IkUj1cj$5k^fstjp+VqpsOR)jXPEjyTX zHn{`9jdPJs=G?N)qE!lNs9J_Z$5=ZCf~d{NHpvyzs+P|nz~@Fpzut&=RiBhNS7o27 zJLxDeM<_ranMf3x1kCNRXbt`$Xlx`3l@b0$BlsP~M5}*CBM}wFZyt{#+IHISLmsOA zsOw=OkE`~ElL3vc)pbypjDl@U{iw9l{h5Ax$Daa{ZM&+gnFlS!LDkW4s#?-`zVspW zc|BZT#)N%6o`~vCy$q+*dnKtPqwNzSy>svsA5;`wF=reKm#$ zj7c4na$UR~=i|B#)Gjg^OI=Q?fY=oFbzkF|-I#>V7VFK)*{Yf8W4ETL;{P#$-_ka+Q z-@$_V^Q{R=$MJ20jQZ}Q-&%D-=k=k}C@?I~_~=%(pk}#zKg;1Kv_?|76uy{g$1|MT zIvK_XiRrkgtd+=POEhJqVtmV7%?_ks5T_3&$b+zcnKzkM+8OOWt$T#TAhAKRm+e7YCzzAQzrM! z5pKDvFen=3d%)~J22uAMIG(*fSgo+XX8G#E{?r{BKr^E|RXa{F$sa$=+UUHg)k^** zOmy$aK7olcG=9XhOX1C|lbd?K3&z(8CYMpk^e`OxuEZGHC6$5Xg8}m+!iY?bX&ofB zgGtYT-{aXsNayg7wC8mf;r?T)Q6?FkONaDhv~VIturL2lZnN2OOo&h58YxVs@0|9& zgQ++?c7Vy7g~xlxohdxoEtbId8VuH$N}yga5eqC#Kq{TH>cH3JpV zmqM?~$x!x;JSg2s zd-8dSrk_6!yNUb($&H13VHABPTBNC<^7wUQ9r^o*f}~s9el^}(yLlt#RCc8v$IKiD zf0=@_E542nCG-d+p|b?zzkTqBK-8QPYGq=2)F0L3@{=FtT4Y@f!;F~5dGE-h66=aF zwrrVE3ZCB#wiu5!f+0oVSkR|Vns*H*)Y6R8jM;2Cp}p37ADn-an2_TVV=GLeJjUkQ z*!!=lCNqj8XP?Fg`A*Zd)%Hy!_=}9}mfTD_EdLrzF!-=w0jN}{D(MC)vb|wIs!}RF z5i4EWfRk)QPxfT=byeTsL1ptf)o$U%Z^lLX)0>~nh_urH7<*=PXyQcy&zSz0e%dBU zoT*PF)g~m_quOaTCh?%X)6^-q4Kfs_VF(j>ngK$Y&Vf0dSXQ=t3h!N4xyv1Pj)@_e z;E~SfqV57bnaqKKL(7$dnoTmzUX6uT8rhp~#xqDrj5?d|XXnsQBwO+-N>&qF#cs2_ z89;y#HXC8)SV%I<^8^-kq(5WKg(>`gx(9BSC=f)_G2^n$_<0QtqPPv;3S#5on~B43 zvQ#+?)DVwUrOK}ItWb&qZ}ah~uEwTVY~*hztzD>p%9kw$J9N*!F8 zhG9m8r!wp5I6^ZqEPq&LBq32oo&fFFAUD)DF)DMFZNrR69@+}1Dq+zJ0sH93Y{Rte z^kipQhSb;XA<~!2L<0J!vLiw}rZosdJ0@qO5!#e;>8DMd;8xo$ZUybBBKjZ}Q}(gV z*bjYG7-*CXr4g#KHMXiAw9WXV!ibrZi$zpaRtfH;y8PkFej76GQZ+_e((i9=Tjf*= zI$JYf%}}#(@}?^XQ-L#st8KtkN<+*R%(UygfBx2aiR^7QBk4EQj-ek)7@>4-L?o23 zd5QQ8Ddo}TM0F>}A1lLDCaH%^UZB}#fp;S|Ui)RC)udjsI&DZ*%aAauK`(T8k@$1~ zF>p0Aui;(tQPd-X!&-z0v7HD_uY4)_QPGo{g_%#oObk@_Y#F#y+EPrfi!GA?b3_Yr8QCj^|?=O2*F+ zk`+}uTl#cP8(u$j`f0QwEO{uk4HEu4kv`=Dc4J+{=@QJ8G1j=c*Q%cKQiRwHk+8dfHGo9#e ze#6;Td6i6i#>v`@D8MRoseXfNZmL_9cVutUDd|^MQ)(f)yB6#AM9X z6LNW&Xs|8!X#i#_^rpm0sOm1Adb;dylm48}gBh&!xJq3QPjuVWA(`2(yb+!k(njRd z%U&~hlGopgRMcEZbkv<@g~kfD-!^cMz-xJIQR4+_KGGW47OtkbvL!QMX-ceynkO9n z2m)X<0p{ijle(VQqkjz5jLA$NR`-kmP+2NrJ6(KPfYBv_Mdt+iv_56|m-pY@he#;+ zj}fzzj2GAXMOj!?>Vn__zO#&aK=mN$&_Dhfq+9yjOoEL{^}_`uZC+fA&aXcgi^03a zW@`-uK*vThp}mI@y!jltm?dKmQ>dcv1*(pP&oGR-(|{;KX(ccr28&l4bt=0cL1ua% z+OxtFpel{4QmU62pKcj4#4<)yJlZ7any`**W`)Tui$=wK!upo9y65{)_Y5P)%K4!t zv;$*WGH6Pyf^@P?Yz|@d)-x8L3oD7xpXk6ujqoeJ%tzp@wCq1g{uBk7)h?FJ$bHZRy~5>dMZZ@djMfIehe zW?@(yf(gQ0pP&~q<1|!#T#QiNsAI%cW+nn_HYvkFcS^_mM;{lTn*|koZaCf~Y&^8L z(oVA+u;{H&mBnFmq?)i&Y)iIhF%lZ1cGqC;Na8t$mCV_XM#VkMT`VLo!h-TF*|+(# zZ}hhUqM*GytX3k}e}k%Z5>k*LGrk{?Q8^KG!}v=*HKh^yEks}drQ=#TL*R5{lu0GO z!sL0XoWh`Dj5jsaJyek`L(DAOG6*CXCxv=UMvNXz{d-qC8G^ka%Q#zfzGLC)oHnm8 zh2d3*2$b~67CQf6-^ON@1qgtvb1o7n9W=#P2dkCbm(?|-$}E@(3z>2lW=s$O?QSX9 z%<6$=S{ssfw0Ut7WTAAz$;iHF6(`k8ETkVN!!U~>u~!cL9Ft))-rcYX-n5GYee5Ix z090II&}6}TkIVI8d{=pw1`8r^GUpj?_bFeLka7OF_X7?I`rK!tHto`CZrX=iJ%;`IiSuL}@{`-J9(iM-4A@kuoN0s&pAo&=ql<;n<-Pb_XR|y`?1>hDucgB@t+8HkBsBRC7 zgr!!2DmF^;9K(^={X|L=cy~rG216k7Xl`IgAp)3)=JYBc5vS7i;ONsuhdTO}I)+q4 zkz@FSD{|^--^A)tLHE1910g*^s4C1*gOdt@fnC_1(H>;{MY~b8^@4>1UZ)bEl^GE z9eG3ykW4{gYp)27k-A$mspt>$3MAf|AFNV#U#mQRhnJ}?zrwXs70?JxZ*nrBD8jMO z22w7H?{tGmgQ0DLs)|rq4KEG1!qor{+Fer6TVDcI<0;c!rsOfgYN_+5s*x%yhBpc0 ziOy$7M$U31@NR8?cn)gX8j)zjIIa>=wHdaD9Kb80rVPo z4SN$EoC$dGpzD86P)rMAY9 zETQb_>j6VFz1eLGnHf=0T-i~oAtEnKq28MlLoJNhWNfq_5LH!4)ncxp4Zy!PKgDJg zYITcAi1r8CO*EQoS)s2=J0^`anF2uoU6=^NFf)VdGERx}>fZ(W*-M4y+0l4Tx`#pY z@!l8#fa)qDLB(De0?lXaeYCf+gf+gijriCBjY$9moro#fP~AthU%8?uVQnikzB%$8 zNbut?SV@Rn0MW;c?=0cidxjs)L*y?kcj=;YTE%voEaJE@)QcgG-;SA>&I<7E0+7T` zRcqATR5R1ZuqlMYJFhJZ3P@I3tMLUh9DaILDIM_!<^)5c-R0EzUxohKeljvokNR%2 z!sIbJA$HWBZcbO+GPWI6T||Y2Wn`Sl32P;IVLW{h(G<=@oUwOTyyj#D9Ug`D8mfu} zKme$?ZP63MO@by08()jppsI`hGF%m=vtvLwHbYgx@FsDqAy7?d48Uv+6H2e#MCygJ4B*vuRA%EWW#%)^J;fL zqc~7Sgz!4zXJ@A(DoljweL@b}OTrl6*+{GU;8p=t8yRaB@fWFXs$_f8*S;g*YqU_* zjbY&3gQ;$)=LvdPFVgs=4eFC}KQ?~^-c7d*s7VQiqC>8>o7yoGa}5LUmUjrw(_w%l zwA+!Xb1~2n((%|VD7cuSMx|&WPQzN2mN~(Y=<^WP&xp@UpyCo9ML)kzLHXFPuq3n( zUuvkj!urbe0$Icr9zSSKZf$i4?Sg0mVG4bP1|~L16RL_jYEQLp&WT|&oHxfXDTPH6 z@*)ye2dXXwNH`~Vt}Z2lvZemp=*`)dMf+O4U-oVOi1TSaJ{X;bDV>BVWTe9g<uD?aM-;AvuY1GGY`3BExJYN{%<^yh?bo2EW*^>iORA+AIu171my& zpZ!gE=79FY?m<9-tz@q6d(ziP!@Er+h04Bi;uat=?G{K(#;tvKK|0qqC|fkDy!&`} zD4E9Ku+30KD_cH`1#LLHX%ODcZk}Yg5ONY#+po z)WZ`9Td1lEIxq^%7h5JCA-=_HF)fRcM}_ zUPRbKYuSI$*N6_(^Xt#HxvbPF!1^Isv1o{NIEXe4=!`l~H79+A!Xc_46+_KS=T)l6 zXs_U+#}kT&9* zxBRmX>CbTf_-Asp==?4o>?M2Q)jcXk(AN5xdO7-2krGTA07(mAQUOSNrUZy!=`s#R z@;H>G>@$NES_1$8=c?|(t#S>CJdJv+E77W98LPIHOkMUnP3%8vdmigZVMjpd7|mORvs!ZWykR8FP=D+YI-G62F1P?+NQ3-aDE#+(k# zWN-IlKN=OOqjzgx!;hNoD{gfZ6UO*!6BS{lK(kXj+z;>-rAog0LEwKmbP!sHnEuHnq|FrA1r4dZe0gU}6jE$1owcAd*83p4?CK?I%~5Q=+hl(;=BW z-x;bn*`B|f01w^~eY$Y{$sP)aXm2hQA}DG1f?S1buTonhSeTS6QwA~tXHEUy#)ai{h@sx=$>Ra-;&&`c6| zwvVW|F?j}dsW3P&4MHHI7GI`FwqaxAX+-BkvVdNLfU5;UN$-Id4H1y!P!;~ET80#) zbr^<3U!#GEH>9~i1x+=?GRIK07v@LGWpgE7P5rfwtKlQOO|t;tmdu6!!W1%c7K~55 zB75QfusxaT3*nkcZ8P?fBW!QR&d#<=kKz89>u(L$tWevu--9Fk7pCyJ>t_$IvoH+< zb$NX*!bpd{{6B0e7Ph0!?fIgb+ttfOzsvvN*tN}6hIE<5!eAXzsDwdt^4bIdQ97qKLr3v23sRmfUc42@`PApD z<*#|Wt7W{C?_71EFT8sFMV?e_)aBP^ISfr*wbV4ZvCU_N?(%}(kXwxf{K%CFQu`1aK z_e5stP1#6D)SvGT_!{Y;kL#wrBR`OfcG?Q=pre4JKvh%V2fqO1dU(+K9_XUMpu4pB z=&(ec)@_-jl3}HE3Yjyk+p(g+)2Sjvygq?Y@=QDAOUfB+?%Vw`Yk8`TLqSK2g_It0-i7gl?eeylb7H^BvX%psyF5UkUFH8+D%-F%ck&;oZ|;KDdYu2A-b2_Ut`cdM2N} zW7Z=JNJvC~O;?C4Vmbnoin$5MqE!}Q-5o=kj+*Y6qrlvvK&*m)Ca@F!t$GcH+$r=SybJWT}l2 zRRae`i?Hs&)yC|)Wj7i}0Y`zOMS(~ehD3*TLbRDMB)4}Ks`bOea|;3wCN2o#I}QVj1dMYkYB zDpjW%ju)hiY8Y&AjIi331U0;gNCXVl$f6_ohI=svCN>8z<@Ox~90lee1*Qiz7{~d& zM-WzvZSRKjGU45b-ffvF*^t)QyA`AMmwL7|m{KSX_FW*wG1~z*nq)`;KcoX= zPXX2PL}46^vlS+t0pa0;dB@p8CvL}4z)@gMQ9z=XOim$%dozynN0y-T=K}9$e8jNP zyLAKz|7I$76g=4ZvNNp`0EswGRs zdvp~X1snwsXJQ)r5!1laah_Tgs@uXO=sYm`d$k|`m?SZ5e8kBJ5PcQl-P1igMsFWZ z?U?RV)g7zanHS}ePi$Us7-(ktl z_K)0}-1M5)CU3m?oyv1mF@R6v%ql!(1wuD_y;pOo(0kfpN7WqRx$eC>^XFDjJ+ z$@(ZDFYnp*M%{^fwH&R4tEYj~X($g>XbDZqN}qjx@h76SyqN@+*^?b_)YFig>E>x} zbkQ@QKhiLeLIC+>Q2A_fNnfqKdp?k@{v7LLaOW69zIe&TVARpd*kD=oYvbej>pwT{ zT7NCio?~UBQ-jV%hV-8;PJvukW9oA_arfGD+&fHy&Ty@YL+}1SmI4I#?hr5DX3}^B z2t4^hQgAf41#Fc}*ZtjBBs;g=qEnw~KHZVqji=Ii!>_N9%7WD>3A~Wva=d&*6Jq`J zhIi)k+WUZTrNK+Lvx;yC^I&Z!(BwM>)o-r}`ZB8qVL|@-AO`}P2^~isU zN@ESI$gd|qmFyW?Gz097<3RCm+wgu{PrC6}^OJTMW^}{&Us?v+)_Uvjou2=eT!T&~ zSEvdp4Q?3!&xWMEev8H#Jd%Dr_4mn0aZynE?^3MCe`uTx*gCWD9et3Gir!h%yORna zrkt3)PkD(>T9d4D3*9jO$*6P=WGy|v_ybFUO&SDJ$9o1^F~%jPOMFO;u^MBnGq_4} zYW8c%q~K_}^z;uX>t}aD_EqE7jecOF>Z8t7*;bwT!s+Df{C9L*_}xm1xM%RKdgps< z^;{iu*Ze9n!szNEj-aK&B?Tmthmw%DQG*ZEOqzD|UT0&;(+h)BlgUxteFLX-07GmSE{p zQ82NG6-yyrL+>UhQ*0^`j{srr-P>2AcW*p~h2nDtwu3;WbyEUv)trpmL(|PC3 z*Vm=IK9S6b7m`VpvS;YcQOpRv^D%u4z=~|f0LwKkw@z*ROvu;j#U`D4Xqj66smA{= zRy~{T(T8zcFf(G7o-mK3(^`?eA(|PvY@OewA!A1LWkK4~Co6r`hJ{3V`P}2l@V1=Ln?D-mc(YtOD@Oi>WcP^jX=VGB6X=ss z?!{;Xo|^i*WS95@X*dYtJT&^jWGo;_-OS3<62k7Xbn)b|s#c38ROHK5F?5*no(=Dt z73I;%yW}`@+|*lV=D(LL7A_=*hCi6}W_KFnDv7}n=vjgZikZO}VS0|Kx$T9*ndIF3 z_r++HV@&6JGGobT&rQZiX=C-pJYkCL(EgUJl5}2+0Rkt}N>V z+aCNZSe;BQ{oK+x9~}LY;C57;s-+-joBS}ml%a7>v?0IC$+`R4wDjGiAP+A>4_2Jj zeBoqmLi}iy0ha4nqB^PSC^o5*?5#-;Z6`MP$KoYszic)93MHmJkSa3*oWuycZ}=S> zTOtu zjHrJ~tIGM((d6)s_v-ooGAZZIBrm=4!=nEUoDM2n@2RP;BnNg;y)0Gr6}B?ZDPd%2 zfeuHfKcki2q*T3ECWo~`3Dv3~OdVGmb#h}J9DSc`M~6c%YxNug#OEam38+;<>{{sE z>vh-+w#uC6)2_1$ele7eWk?c2vwmI5glLkzGo1uI7t|H3PF%KFpgr1%v`xJN*7~ec zkW*}BpqDCUWq|_*A2Kh|aPJXI0o~Mn+f62O;;j6H77J(ZF}D1Lm{do4ul2iafAgiu z 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 diff --git a/third_party/perfetto/ui/src/assets/rec_long_trace.png b/third_party/perfetto/ui/src/assets/rec_long_trace.png deleted file mode 100644 index 23aad0b25e09c6ea9f8b04baabfc8f08ff0c9976..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21705 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 diff --git a/third_party/perfetto/ui/src/assets/rec_one_shot.png b/third_party/perfetto/ui/src/assets/rec_one_shot.png deleted file mode 100644 index 7539c7270af5795bc69717b44b9af592e43ad4d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23210 zcmeEOWm6nY*Tz|#MS=x)cPGK!-Q6KT0t9EV#oZSV4#7Ra-3boC-66Qc@^b%<_otVleVj;#yv?XF15f$fW0o`1vsa@_-J-LsI+T2yAinBKaB#J2)zeJPKnfDgYc+ z2Z8ue1RgAGxx%@&w2uo)g2dDPoASE;{=c=14)AX0v&p1)u5Zi4pFdFB4Y&Oi!DMTh zus}*P03qD}hW~}&|G_GN0o-s9qQgpdV>3va%_{R7J6!FKNZu?OE- zLa*HQy@eNf0qzH&qj_HJlDvZW_96Cw$1yV1Pq%Z6K6j^&|IoR|FV<<(jkuWn7jL?R zzWl9i4tRUsfFFt@6}Foxl6Nc5xjrtxesVH2TtN(gUgw?XLRlFDf|nN+&raT!Z1XT6 z0^hb_%=yL6c3plf^NF2(q@d$UsQSZN_A37KsA|2#m%RL>u!;23%9n=N&$n}$LusPs zPkD$tLu9^{dAW0@XLkowUb3=2kwHgO8S(Zhr(D-e=AKDi9W)ZjyJ=C}VVzCZ7vtu*!CVWpFB>7mV_H zD}Eo67*F=ehtD`mc_>87`Nt^w94NraJViZbuG!Z8Rq3^?T8ot4@y+tniDlD~W|Mtm zoUG#;_lAmbR*_qr{IQYCnrPvl%NU|t0fen|B@gHU%l>#hnv%d{qTF~G&ZYVB3&FVn0X}*MBD*32#jJCX!$)c*Ojhp<+nO%uleOZXgHjr;>X{D1UHX#K_LU0V z8rkaK`k_}dexd=-HZ2ORljr`q7k%7KJ`ERrF$aD8N1?_u zjHcSOvUs2Lx`h=mY?JeS;j|!{&B3#3`?q0vhwkepvZ$Wv#;)@6TLn)C2JI=o8ZCcw zh25yQ;+^7%Z{r*zL|juoCW;(aqEMN-m@}qYCYE@G-qfvWp;gvfIJB z-}OXHm%X^(ZU_>_XulUX=cbbA==*V682`E8sd&C~mNw>Y!NdMd#Qy@sfBg&^L)@@g zOHOBAL8%FUwzjrBxapIW`~33+P;Y$`Uh62^`X^!X%VCvbNP{U5Ih>g zzxuT=`yj_Nw`;=Xz~E~SqIn7V1FrRo|NR<&+x@z4Ll^Y6=F!?ez~8+#0t!mYV_91I zzG%#y5nhia`ob~$eDnE!)$L$Tc$E@XP-}SrW5v0x>iN?}9CP(Ai{@3=F>BMer^V~@ zwju>j1$b$CEt$BF8EdX%tQklQk9lu{7WXqBP%bdjA9JY2aprI*ok3%S1wYk7s@X-x!yMXPf2Fugd-zGbS<~mHN9lrr;nBem@>PkKE6UPWV_;p%8 zmMwO!^18iIgWsaOm~iv$eqMIFcE{59<#NdFZ98u^ysL8YXST#^fj*raEIt6e%%z??@0x0UlOu>a zL(Kxa1n>TyGMhdscogIg#7pmGqo|~gr|TBw0Q}PRT(R8vo)=_Euy%-BXH}N&`TtV1o%|7QaB5lkgOdIHuZ^ALv)=pr|4m8NFs>(XJ0U#}qL2g`J_vUG3iPh!)P^(P-zrUDcQd46 zBljzu=mX|vCo`)Ve+D^&?tl^D8Rajtzmp=@O3iO?ucmEJ`*{JNp>FdO$OWd*;g1)g z$!P|b!f`~4WsWht*SnUk?*@Nmag=c6ilsN+GtcR-ow%_D1K%?RnI}UO)<&iBvn7;X zI1$Qmssh2AgCb#cRHTN5_sBnOmiKl@&tDlas6>GVRapnvJY0^sfMM`VHNxvouI<{{Cu|xyCA-9OUKE4U23g8HR4J zn}1&zfzmnmPXdh$0q)2c#lW^^!O{J<*b~lFlYB*+Y1d@a?TUYd&)njF_ip)a7v#El znv_lVx0DIwh^uztP)sK{GWvcPI(P7n&{%hl{td5V$gFgP$5_RXsBu^*df77{WaWab;F@=iK|{O3G$p6;u<% zWTT&U@}PM5GLX5uRZFb!o+Y1ft>^MP&l!hlLLx0Kdkwpr6-6}ZrgTq|+J(>4;=h*+l3IZ4fw2s&=5y757IK=2SNyf!tc9 zsn>j3(G7Mdnu&Zc6@lE)NIU$ic;V%CgwPjM^s`~}J}yCUgFomh`BVg@L|fOGmnG@E zFUE}Rez?w1nm?*s^mR^iX^g!q`*mXy^kLpWMt(RHjI{@dP0HUHps`IfQwHAoDSdsY zxLb9su~UB}T;=5<1S`Yj301cVL0!)y(qGRZi<9}OW%9;Xk zeYjF&w?2@Y)v$Bc2njlR-HxDMwS!98dp>YZsYzUwNTR6h(^{*3dbGM*I|uQveC@;N zvnYNB2pzK1CMrXb5UyZxHXn{ups(i*Jt4@?iloh0pC8Oh$~bk|uv17!zZD#c&M|yK z234@OxD)?vq5W#%o>?nN-F|#rVN4-e`SHLMXWfr|>iib792RKpq4k#wK3de&GiSC; zfI#C1=~lE4cl*NM$&KP&gEN)0t~Y3-y|BM17DB8eZBPnUr}d0YT&_v+&;ecCr0GxL zo3R6zSqoopdfvJ<|Ca_`6Yss#=Z&{cQI@rB4tg*?Y_N;;=g=8fNbN=O%h7vboGfot z+o35TC~%)*$qo4T8xuqwaGq?jt84U4y7{zTvn00r)Wi$=sD$ZQdUo2j0qV4KSL~Jy zK$#7tb)RM!1%yK}g-;Xwt@A_cQ^E34e}wN>l@dKwD5}{{b@a?bF$drW&uQ@0ZA7vB zOi7*XvVXl-vj)+lwW*>Ar5K=%N^MWxhgq%P=@fBqxR!nWsZ6r^aER-^=bt>>W+E2s zy=?QorwNB|FvyH+t0Sb&HhXX2QHp@7gRliIF{7pZ3_9bOYuab*A&^K*b?1DsnZO@8 zVNws5v#O$N0S3y-7iY)8PU_AFzDe?m|btA@$7N8aH6XW%&GXL5xxIP+v z)&G8G+eC6knxBeBjhZ7obzL%r@Q*)4Ss^)C@J-sF*M{rF$q4xBUJIxCBZPQm2Kj_0 zCKF0qz)oYI?gZ)OsLJ(OwpZt-g9m~?1UD+Ilt3+a!x6BMBsa7<6r&EiHY#d7b-EFO zX`ECRQ=00MNt;>5=PkYY4yMuS!sNNj{}AxtxuFg!XQCxZgC2z-M(?%-fqD1j98hJ) zCoQQz0})tcG8mFVvIgS%Jf!y)?==f8tgcDPLR$*JUHBiv<9p8cqfun3lL|NV;*)TM z&-k@IU$%xu!$Y*sZO&JDHGo|wTSNekr{$~fRKoOm5ql7!sT6J;$lLt17cqhP@;KRG zN$aPY{+n|O?8n)1Tf&Y)M~BeL*TSqOAU5UPJ}vX_bQ15=hr)0&pJ7g0KW|aZjiN(W zd*&I`>~7VJJ`Q`bBG2F{t%$*EnrkZ(|IkV7JQ<=jx@(paex+(g@#AypA5>O6BXiQ` zPuJFl;Lo2G8>iOrQWB|nyi{>KT%P@#eZn1=29aotDj1t)6KJ#!4m{zT0>yrQ^z%#y zl^?atmfL81Xp}lU;)F_Ilq+#xYI7Te3+9w3x+oE@-cZK5Lb(Wwk~3(>m-h-(D(MnDzgm%~MAWDZzz$|(E&rW%mR+Wgl-Ww^0T zPv9XQ!rjR+I_T#HP1D>Zx01N}H**)!ijIO2`SldS@f6OpQ#j{=gc7chTup_9@<0uI z8BbS0jH3WVqW4eAZz@CqqPlrb5WR{>LS8Z|E-bZ#t zqya^+ua@4@^}~cGMZj2QolfMGa9mfzO@=@72!=ckS?^hH`06j&l z(RtA1_+CuNc27-XHfO30tzy>7-HhOR!^*^1?lr%%{m5e5Qcv7g_Djw2V!u|e?ZR5z zR=y$AHrLx?z_ll6N&%>k5J5I;H(#vQQ;JW0m1k}uWeF%px2H%_i+;Eq(+lbvi| zWI&xOKw_(+{h(2_=n zCo2B)RYdfK0tzbvBjpZ>Q+r)jH_A@jqvbF}tc~*kU-m{?M)etGX$jf=8ncm z1AmL(N6O2xfRSRD72BCpp{6)CIf@MUxw`*~SopXC0wq z%#;P>haAsep_58n=CnL|xD$QOSR#Be*qzwoFhSF0MD)Z{csp289X^0+&U6BZc2Gf% zg|73j2;*_zP@gZ(l?i^aN0gGugDQvW*$;0HPE%rB@sabvkMw?=(&i>}2@G!`GGHU7 z2v|4bhs2F4g$gq$K?uUK;%EupmZ^`t%`B_c=T{r*9N=E`SxQFL+WlxtkW6sx!@=vl z?vqCuuDL?W$CB9&W|>;$CfM(LKZkBTgr^n1x8j>GbJmBG^idK=qaR1aNwt^M z=^n&5)SCM*FEYzY<1FBKD|+d{{4I|B=BNlj?+YCFKs)Z3h*&NBzUa6Pd;3N380wa6 zy9o6Q><48JKN>4NF+%gFCmjj70^_sg3z9@{Drqb1hj)sUF~M4eu%W^N6CyNqYDe9f z$i?J9X4N3}wWwYnJ2K2h-s>KFf?5HXnDAgkL4}Qi5CH@glJKghdFzYXL*V@NH&o2e z3);Zn*%AmN%!F1opxkt4WXx{`?C0m~&OIdJ80He_&vkkq@^Z-;lT{eB`Jwpx7Qo8w zS&SgqqGnm%qHXRIpK+JX0RmGgVINs&s;Lo8ti*A<5B_y8YH1dOGG*CBL!&tquL8!~F?;d>E; zoUK}F95e{)mcw)9<_5W#-|S}-5|m6PRr#WNYN=uA9uvuK{j^Q?!u;}sQ?vMLWXrTi zt7M*)yQ=5}C+W%dLyMJXw^EqS(TZ_D-JA%B&5eL^)T#ln!CXlYgW_o;;i&D!e-AVt zj96CY(zY^=@nWx)k(4=M;(FbL3@3`?F#Pki#tvMNZKNh@kL;FG(*?R^QM|-RbK7{a zpNaq7dX|{#$JubjGQgvg3}%R!yVfiGE+Is=O1bV!gYHJ&7%`6U*oNiVpc?bTca|101M{Q(#|HZ=AuRS=}N`eya-{LC< zX^m!pSCH2Iolf(6AX4uan#5*>Wyecmf#uNQ?yN+DxOR*2ek|E5%0S4q99l>pXittu zPmhdTI*X7z=E{n*DB&A;53(j2QqQ_tS?l13#3_pMv!0$1D|#Uj2YGNsy-~^34oe$X0;od3?0{}AW#>9d z6D1GCh+^R07cP#l#m&Xy#HwV8S>M2#9U4OVz!-FjOykh-jusJ?yV19wr%@`eIVM3Q?vJc{v?aIcp(-Dfh|BbQ7PWhPu`hk99*> zuq;E&>dAanRpgz(e}>WGT0U3tbkI8s#^IQI)|f@qfKkPoz{nV95J@Y-d&4BcGTVn# zYoLeteCx-FLbF^^SBgyhLPV+MxBJ(JxXO3PU0?s>8XAH=9+XmC1}Z$xzteA4LDyt~ zN~6eQ@1*g2>Y}DLkerUXv29KR!w%Hq@XGL^tzyQ4=3#AFX2q$?pDU5o{7|XeI>&5ZY!_$V29)JLPJbaabSgcf6UKrN&f%OkDA;9$yoC7ik57o}4FV=$w#0iE zEu{3lu*QkIruU=lEc3lkE|*~pVh<>r&(dA_Xw2xP3+K$;wBy_eG!X{?fXo%?aIu)_^t}X$ zv7-IFaO!sfC$t%yJalVW?sE}}^k7bv#HL#Nlh(1KBUaJN%>51lUu`RJ8S(~12yx`s zZ<6V=-#J1~ExzyYTXar4R8*y@awqT$*`YvO;9DJzO zj0xF~dBcX5V%lUB8ta9=>@l$*@jpnT=q5KL>I4$8sbM6&5u0H~5LeQfsA9!oEI~^L zjoXx@+ou|r(vT5Mt+|Zud^j!Rz|IY%)Cr&CkBfOO3UfRHJiz z;Z~csk{PX)!4}19WTGn>9M@wZ?a|mqP*-EyUk#T}<_wN3K|lvP6*==1&ar?>Lm35N z%x}<8YP3=aK!v_@)d6F`_;`A=(RKsjtIjgvJ?aw{PFK~85rBDE##88{#%&T-oS@Q! z5u{Ly@KZ}0eo(w9mW@i-6`r|`wVHUXT!^c3uB{Fb#2-uuP^Zs#jCw%AHo-0rvgqfB z;oC@&i4p6!)ssP|qo}DmHhdJJ+Po%-OTO!@z327^wJ!KtFDD4gnqKO~%MVOvv4?S;A^hhfHD+7EA)7W3XzUGv&3QZ_gBDg*0l=al9~dcz}_Pf;2KI zmT}F;+a2exFI~Qe4X4CX*kWp?>WKUA-oq1&r`M^ukDBWsI3Du%a?Nq9jcjh1@|MO7 zKo4E~zQi*yhcaNHm*L1+b(%c_gLxtSSyTw??BN($+x$sgY`1^>;Qg5I*L-VcTYJSL z1CW-fBJDZz)+<~wm@IdlF_pBt9#I|Y&n{N9oQGnki4DO*)CsN9U5#=9DSQno?^tof zL)7Hs31r0V>48Th6VXK$8=ek-)G`XK!8P0^K0ZvoKE`5XFu}qI48aA4b#)83O4{sF z_w+vX`PYB0J!UfB^hG=h@8vq3rK6j#zkw^a)#zkNbEEKu$&^tDDOeBR3rEp4!3bqs z`~!cWtb=Z7W$bOdrSd?Qv9=r6@7NdxqJu}#Pr;0s59o91YqBpWvbR&S6jfVtPBlH!9Fo;Ad^!pLFEM{(wxm zj*PQTijc&Q0)E{_U3|#n=`?Y;Cwt1-FGN5{a&bii6e(box7|daC+n;(^jPu3lQ)2V zagfp{m=#rGDk}IwV8=zI0cm;m`pyvi3=$3V(TA7_4@rT|i?~<~d)yN8GDtp!)p7ar z2xic}Bv|8%mh!9Ab3$o#3sno-6l!yw5F;8}W6n4XqXjr_GUU9alTTCGJ z`A2Aj)&;UPC7mCRk1EYBTdatmX0`jvq){ny$_<{Ii8i^#4D}Os7iw|=@X2IM&6EZa zXFM;#Nh!JVn|9NePfILvHT`~db4GN%Ez?E7k~{{7WN@?f&8pkvZ`xV?a}-Ks@Ak3L zXE#RNe0I(BDsVh*;PNAm zBW(#JCbDgyNWB87#cyjcL^oathReX z%kUYu^312I8a@nEQ+_N3xY#7^+0Gzcco%<&qE142Lk*te?j-EDBB2iqeCnIptvv&{ zg3)X^YE@7qawWZ!lBwFOr;W1lfDEn$!MH|{P7bA}^xAJ=Iz@gn*W1@^#H8iy=Y%La zVT8|1Bn)F*1SbkR@<2MR@mMJ~9BrZ#QBJecd~W3M?*)1#MKKT)v2HYPXm4tWJwZ{k z=Tw6Ga!z!W--&bOk|?_$kJ|7Ca?Fn@3$+HD@QH84Nein~+)C9;p@@wetWU)kDy-lC zx(5}JSCYKe<7;L_lw6ZepipTzXl4{?xJig*rU+R@L(9Q`ThJKYdUMV2VsXj1QMN|P zeaxRAW*i6LvDD|MUUD-c$sG`na9ZpP?z~F8SbE-3u>N(f6{2eD4rL>0 z06MOsd24>Tn!v#sMFeb+bgj%%rkZ!pAZDam#!Kc53u6>Eoa4AnyXmM4*#5&-JO#8< zqP%ZU433rhP34`N!6hAR(o3R6E0^7!_aN9Ikc~Gvu#wmom61CU`&o#SJjDKxERql1 zRrWAx0MwLYWd~wB)3%$m`0rPHZ&53`V#wR9H0Zm+n|?t{z?I|TCucPMOB3ovZt}&= z^C*4Ww`u0vtg?p%W;MpsrtjEC-bt!xw&UIiB9!x0yp|J1gd z(x&qCD_3G-G=@amM4ks*6+yzbQjTY(o%CI+5c|M2tk^Mf zH~mr%L4iEVKnODy@f|K3G0c%r&fCxaNnK3dOMm^D)(;i)kd+S+> zI?~R6$UKV2lAznEEm1m5@wd-E4|K^gJNwh~R-GODl~P95eW`qvXPF6;N9EI<5`$5V^P2}?j1{>WmZcz3N+R(dExPmJBFj?Rj|K7O@L z^SY{KLkHCW>sN7qCgXu26p=^`eS`#}Qoe}PsVYzu%YS18VTWmhKZGQhSL@8Tlcj}- zNz(YMF{zSHN=X{Zqv6(%3o(XAy`DA2)cQZPdm)cE0Nj*9z)7Wk?yHl##_y%7;ERJk zHUi@T-O&Ma zeT(11!XPIg3|Bo|+gIeY`O{9+Dfz<87~1x#LVc6LBvy9Or2I zOJN-;M|}d@&xJ+F_s{Ages3*OLW*J5`J5j$YB$kgOOS`g&)#L2Tn-!Frj@p7(;`am zI0U8EPt#78v-dniOcubo*DX7aKPIeM(W%~8RX@_W9-!4;qaEKNEw-WLTxYnzdV?%vV0F0ckrWPDJ)4VQ($D&CVw7^ zr;*E7i7EbMU?+@*Wf$SblOtvr@Ba<=n*epA;%kRaIEW?=v2dzZ2S;w5c=rM0oD$bX zo#J~sh|yj1o4O7)Prz;0;#~=Gw005-+tw`{KgbVJ733T_TyWuk9}D7JbT7N8y+1dvg02;MTo&T z+>2Ijk3|}|T#S>h07wZ3X{tT{g(R4S7=Y=bgL2!ifdj~xBUb2SiTNvAo|;kSC)j-A zc!O1(*1P$%;?i|ScdlVNFC>*RWF@;0$c_L6Z0wtoHTAyUtqNu0eG$ zYeoVMuDs7B#v0Q?-FM)=tdEni#tIdx)HNSKuso{($9RtHPl8}+Hw(J`>paIE>g(*J{y!n<2Z9JCL*L&f6$ymPOf|hc0MYDWFX?UMMZ zz?Bo`F28>&|Ga(u!_UlkEgn&y@ixh>2T%LoPP2$OeJMx|+zy6MLDU@BGLY+tpNc@t z1AiVO{4mVx_}K39kmCMJcCpGFEt&Y?-)BvT8Z(=OSoLacQ^!B~J~5Oicf)hPj-{~x z@)YS%_C1Ws?V6Y-5Uy$IniTOUuyH?f$P)V5qKFcmNG3`!lrTQDdhty=?`Bi4NIG-^ zgQLeWAjgm|27Fy(i*4T0bCj75QF`8lrsPXY75j9Y<(a3rlMIm1ggZh`f=CyKGCCw& zrDQ6i}Q2{8m;A`Ydft2p=I%9RMeH-O5& z@fT&KRn#3>%%1<0F?KF0a4Hv*SfMn=Y`Ca!ZJAb3l6at#qfFt723~Al|4pne9Y>IJ z=6aE_;-I*VRh8cx{3bc4Jbo&|ss6xlRZ8$N|T5tjZ8Xih^;w zVP%ew+#b&-{W@t;n6Gn&1p>)wr2#@OHV8zOKO*||ym-C`QfIR6&wAd*%x#0?Kixrm zS$ah&MD4zn9<~tzo0a#G7J7bWW=Yw6>tbRBe&^&S&X~{BX$}+I^(A@dq^HyWL?&&W zjl}XoYLZdCAi96o*y;Y^R3hyicN+5PWp8%Tzk*KIp)@roHm@4@oe|A7Yw@%O4nihV z$Y;ON;6~2d=(+0YwI^IHysV zdpxdwHG#(yP&7Qq*V;kMPCMh~;(ILz*%Io{nGK4f$SLXwD*TJUqcifWLgg<-Uirp# zcULbt9;=+Hmc49>5nwp3%Dh9KMnj2)KJAcso6ELI zB9n*!FHwIP3P3-x0VgiqB4=BYWkL|(m1OK|+|h*=%J)WXKNEA|GI|<6r8dSc@*d3b zH3zlNTnBph*E!tZf6$**%>*+V;vQ2Zc&vUKGS{Q|fyc&cAJ7J3Xf7ANRlHm?I1&(a zIE*5g4&MYPAmED5t+I57V{(U=C$!fchtQ`$+}SIiemCti6h}nndq(L~Ggw&br$;=B zA~56WT4E4Uq4Ueo@LMCu*H7W8hbhg+99LxDf@7(RP?Sw&_315kWsbS@QGb20VfEiC zSEBe9cZw%nE z6hTJf(AVv}?PAM|72e&~4M41?vN9+(G>*DR9|4#z&L-TuXNj;5pq7gfVlU}D%Obav z-H0A@W;ah^U&A)3If2U4=QP$L;j@S)PVVGab>{u_^WpWXDH1z&e${|~CqgLi{bo-+ zLP0b^eSR4HsFp=aG$b*3-U5taLxwx>6prv()(*XIl3JvH?)8=&@dmF@_?hYsZ<(ex;suT29JMqIGo$noyMjjC<3&Z|24bt%M-65k|K_4R0pr4AxEQ4flSWEgJo1Nqdu;^DBXcWCdz({1r;V`SUH;vhkql9lC4PyQ!{saItV@TNq(wz zf^Wrr%BIitl$rvVk~{}Og0X&8{7te~=&Oh6`{HIDf-@@Ch&X}e6jg~~XX7WCd*d&R znKxzGEoaN3d-Ap^iE&!1z&XS++$GNnXhF`C5^suyNv0oJ)pcM*?FeV!(}BHiYktT0N3 z^YGzY_1?Pq?uObN$+RWo_9qlVN{P70{JJu!<4vM2Y&RPs54y8$-^fRWvPIeX(B$uZ z9v@clQEAd!RQJbH_jXoF6J=}l*2%dy06nU2( z__iE zuC1hOjFT8cA&OJCt4Bk`N!E(gppG}1AFUX7)L8$_Pag?rB&7Uk4h8G=eI+pUyVO%p zr$ZJc&M4InA3<9y7{T7ZszjhyESY3gj+#1SqLL;3y#qj!qs=WQOItsjZ}apheG31P z$T6>RDTrVzw#c02C6^29s`7mHD~Eqc9jX1YgVIg|jBVKo{JY5?lX1%+dqV-@S@f_M z;iO7rQ8W#7fynW_(DheF6XVPaHW=5LT1?vCc_aKfhtF_#6D~~45|z^l%Q6%rgw3B# zz)N~dNXymJFS*p_@rF*rHLn$zqMpxbgclcvvVlT?BC6ys<54ySS*Ih~Thl4FjcAg_ zHW}9S9mG)TKk2|Fyy2w4jX>+^u?QgNwcW6C+STM&7ls6ELTp_6mYEn2gfP>cVPl5J z@R7;qj}SYOYZA^Yt8G?XzDry4q<4@EcP;jHuJ96kkbB_6^PX**Vc1cV@4&e_RDTQ5>`b2{)@CbEIgc!#b`*jB-b(xW4UW%j0a7V}-q#$hpN>MKt6jO# zcQ9ujpo{2um~+GQNoD$+A%0uE8@NPrJk|fFu_x=)HJ9G0++A$__;rSeaROYy*Hs7;KMep1%xy|3Wu2A!i-(ON$IbYFmfB3<0|Sm z6`Yay`Eoz=nYihpL=YZ>9psr~jT044dKA;C_uknP4H0v$7$7|A3@4@77+7a`F8cdd zjC|QVpcF^+coJi8sq7R2<#BnVJcJ-4MTut(Hnwul`j)FDrUx7NW985|`h+&rNq3`> zdnt8Z_uegN@+RwDP9siQ{$$Ogb58)7`Ai=%NA6tl*+(j*)bjh$)PfyoNQdsJ*mCQG z;H-NPO?Gw|f(cif5xei}o%Y`?RmyMs-dq@|)`pnp^da1e>B;^-Sgf()0&r}Sf?&CI z$7?oEXnfdiC@Et&ZZFa4+|p1)0@w{@<`IA#$23V?8Y+(KRmBJfk2}iVLXwAvv_O=HvI<$ar?n zMIubp;4fllVOtMgddX3T{aWuHeF*u90H+gudXNz*&T8!kKIi_~aDt>ghomGv|}`gE}M9)+O^LrTZ7hRS?h zzpcVY;*|bnGeQ<6Ur{5!>-YN8g(n%VF>)~{P<%jsVZ)K*?2W$*lo%XQQ6}RX9g2wH z?464wv~nurf+fvNE(;$&*fPqfZ@nx|XgfpBaz5q}9M`Z`kA#|lpGvt%X2^INQ#Jss zQLu=eC3+sys#fPjP@G5}% zL?uBGD_=6-8+Cs8JRXsOk)NHIU9{aW2RssRXJv8_W5};j9HjWM^Tbh&i4G3CrR*GR zbl$jhye(NUF|Mm7IOE*TUI^k%ST__Q-?pgGolv)B$@wscg9dpm!)@4j?}^1WP80sZ zKB>&Aaj+8lBUcrc%&L>(i534c;zy^J1YXb36-jv@9#59i&*|-9}RC ze%UmfLn!I4`=rcBi!8RHLxzXM4KRV_3q(Oxy3IxFV!&{wHR3qNzAyhGg(GLFWyl3z zk24*KlsY=Ivdg5Db>mYI1<-*OW90IK^0A4D|8^C}8G^>UpC;!y|K_d%<1T%T-*jD) zyW?(WZ5z=%B%NWO#H4OsJ`&TI0c6iWrU*ugQt<~_0Sa9lzX_zN5SB1B5W0O!J>1a0 zV!YA)nLT}F8d}1jWIDbj5A03kk$NbRdx%nFq8<;o9YvN_pkT$%F^7xhWF^372x^PE z`je=a+F!+~V9#fLU+YEI1Lsy@Qm_Ytl)WccI+hn1sjXr-A-O8I`WLTgW^GNvZPI2~ zw?iL;+=3hgR5UwL+K>L~jh0j#oC8ntvrvOy*4BD{5itT-A96XVB$0Z_g|y(aQ}|*Y z`F>!+mDi1nALECtWVMYo1`U4v9p67}%FNaHkR6o@VHSa9-7=Q1m4&X6w!=`xVpY+-)h>v@9z*UKinOdAy7g7wN4MDv;a>9!&#im=%%Aia*ZgD z7A!{gQ($(I; zP+Pw5-%bKQ7=a>r#6El`qkhfs=SR+?$;;ADS@>G~`A&~>yp);~=L|lcAPVyF zY^&xn!MzaUjP*!!7y6i1{jRxo=Ix1!P%(|`Y_X)@bZ>Tk$(N2fs7NqG&am0&t-Gty+mfFfViYYr-h5R}guG zHG{=_vo~GTMudES9YXOc)~rP=#khA}WCuhgLs6PMX^)E5UBiHJnV<*XYDbQhUUhR= z(Euy@3-JnLq)QAo^dYYM9ijz)8F!Jy#u4iYjO+=fNBPT#LcyOZ9#g9veE7lGS8Jv9 z<<3vDqWSTq@W&C=1J{pjS8>PU$?tAdGWklLvz`GvCSN3>t z;-J?c4-R{jD(^ zs`Xsz?ry0ITfwBRfyWa0s+@<%Z}YQi+E>ck*z+Wv*c=*~g>zQKZZw7V}pT@>o$I%1+0Hgwdcdk6(EsFB*^W8NOw)1F+5CXA`KFR&07z<$^pvV zD*!eb!pkj2nX=vxxM#4IIaQNVxb#10YBIf%%4>c*44_hu^3?>ZB3B`p&v}8I3jjp< zIvTD*XN7qXGpSGxf%SW1DcJwzY)n36O?%p#~gSpD1FLpTDnUiXp za=a^I9xpqv%OnD26^0YkLk<Tf*nDr=5=rFW26u?2jV>+(yzbc+|vAfZHDSdGzbL+EjL`nI6V z@USPOQ1pd#ND=MatUC1#Hl>fNIMENRx)5o~r>%x10>eR~DOMFk7IR}oR8{y{TXiaL zIay!U4i+x%zEDvK)>HF>dHB(w{JKLqo@B%JQ{K+WY7Nr2zb9M(T%W!v)Gvm(OYbxl z+x8QtHt2tJWq-pFWAedLAF&+vDNRvS7^DUyvWrpcRP>k=H7imIgoiHwu-R8AM~Z##*r zo_`dQV6fgYX_YSQ)cuAbCV>={`;ErFoOnsswC#N_P54^}C&8Si>MK{RA4qtn2-uI$ zu+D#vH@uFgjRPys|6-o|&X@Hjk3R(}{k$`_^4p{$$M)xCEUp0VQq!h*=v=dwQ2 zoag(jUn3|YRXXN;JQpXM72_d6hcR2LQzDWLG7w^G1Xn`H3Ke(I-+Oq~M~1`rQ9FH9 zO7UuVWj>|u0V(n2%{b+UGz|?qKi)0vvvT7A8kjili!3$RvZ@dYF5F^FV_D~u9LW;l z?Mz0Acgwd__XC7t@y5HjkV_WLjd-RzXcop8xolK;JKBha?`n}L^V1oxA@q^1CNxZ z0{|y0d7E`BBkS?SrtB(>*JsZ%u-xONX?RiIOxM5Lc5qAb8vC;T&+6m*b`=N)6lK%g zNm)I}EYdrsU*rvpB@l$83h9olJVhTY>J=QDKIwElM?`Xs$8JO7BMKR|#xXjCA~n+GI5t2G52h9@dsdh&LcTRuV6X4Cgt6^MlIlrsJa< zH7m{c`81ceU^`4d$hgJ5d#RD6Jt3d!5FLA?U3VOGAfc*KurkEsw?fkBxhd~U%gB^> z9Dg)mghJqp)gSA=uU`L>+n#MOTD3N=AtV_sXL&x;fN)}jb$@;`x2YN6OpG3yNZJq~ z4Bw183M!mneCVPS38y%%5QQi3K;=J9f|HZw4Hw zLy)snuYiX4KHj+!u&C(J{uH%nnKO2ttx^Xl)T??`hAg)ZyThg8EY%X(`~eQM1f^@ufdhrGJLR>WgeMNx5O_h_WT%GnSuH(YzR_(w&H z@Div7d668;K|@PA$cWLTuI-`}lun!pQMMoZ)4;Lm8yTyS*dji=mbe6}ppJwA?RADta@YubEFz3{ibWv!?s zt9l)u7Zik_vKjO`f+Kao`Hg0g3@^wLX zX|}23Bir{S(5;|HkT$DO(55a{wWKls@w-*)eZJs=m_2&Q_xFA7)V8Tv!usxN(WD`= zx-Ri&YTNCYljHS@Qb9ZDt87?(h{W5JsSiwiGvXtz7w)4x*Sw^cxtjIP<)}Ow=VEz) z&Atcz=3kQpd34@TQR^t#+~<>>N6S6jA>{emU3G{|nK6BJw zincH48f4GaAZe2Ua>VI@4P9)gjzDX28>8*Wxx1ROhF|#k5+3_qJT!93Si4>ZbSmE( zAOjJ?{PgD*!A{ocMvhLuk|g{~cLpWX7(-Qj8}v~Hdf4rPOB^-wW^Vd2*COsuTugqsRhf%>Y!G$;e(%2I8X5sK zTk`EVsS?jrW@8f@-rY1+nMLj2*Yz`cb_v0b=L-5Bke>4Qs*?N#kTHPo^#lB?-J;tq zc|Gq58lkJE-|qwkV(xbC=ec{721Yv5na-sJ!T{lV=G3xQy|jtp7&Xa=Yi)vF?}`CG z$HiZAMM1-qnsdt6WB&GB!}ZAS%&p*H>S~O$(B($(GWh3yR2a>n{8RZDmhU`;4!il_ zXNZOml2SEM226rVJBFgq|F^)gUyo_5_zS)i(vP(=oxq!1jA$-*G?vfvyU8X;@jT}J zZRdEirntZC8C79_y#q*^hJtvq6JhAV zO-2cW#H@NE79SAWFg8wng-@U43~KlXZEKRzQd1Ny8;R@%^gt1C5$C)!)lQ=?zV{KY zm%!u2w`v6K;uB5$_xso*|b_#hz@KdFOs>bLdk#%M@0G)u9q(u8kDY0blH>0CU z0YxQM%drggqSHe(#^%`|wAATnyXduVmXy*XpKQy=e!Z#VS#_}4<7}0`YTyW;4x~UF zUNu_k%Y+Tz{<~Up+J_9oS+Rtl;WPuF!Gx>p`W9YYH@-)(+w5Ht^Q`IO!M3Cc}Z50nq1Y!X3!@Gq*5&QZf>bM7VdDe za@3+&e(}S=T^iM2(|lsUqbK|TS1M2_Lm^()`~xk2U4kC3@2v@<#A*DO-<~OA7A;}< z?2p+utKS)YqL;N=@*dekVI1^+ISMv^A+}N{YFOobN<(Pm4v=Nj9-=la8lo)8qkNVoWFA5p zL;)eW4Q*ajxneLW7reJFSC#W1$_+nR=bw&X%uWlUpv9&wQkjkxwch)_q?@>og5wN32(5Yts!t^AGH?MMrnd>#K2OVW zJb9GzBH+#hG?ldf`x`o_QcRhEF4POD_bjW+4}foG0)02TgaLUH>rXi84CzJr7U99tZS zZ^;MtP^pPB=1yQw;#S0%dX%%C;>}~ive?sy#B}WFS}hzGbC^V|gGdTfIBqgsz>nTh zK|y-@*~%{wL)OgwHTWnml5BKgE2dWyn?wDGg8c)Ia3 zAf|Dx+I}(w=-Zb}svIAdK6o!m{Pd^Z6gA9t;_@=jbZKQ^!(vT&aD`3iYR^kZeU&VY zTlg8q5vI|xWv)!Y=GGZ**^g(D)pnfVc3+;B^`x$iW+m-o5a19UKJpmVz!db02-(iq5_2(7(;6UYQ zj3kNP+Szl4q8!PN3&BB;wK{zuWNzwG`ByQGWEzcJAGW5_^RU*5j$tzFuWFYXlX zTLf0->sO{`;Fqts9G9WzvI{a|`$FqFiw?d=48d$6wg7KtLT2nb3K)A~?{|oTf?cMU zTI(nP5B<}>1WiaM{_K}@JoF7M0}?T}s7YHU(O-`mL$a>-W(4~C_uI00clPXgH!xya zyhAd#S_UUkAsZ=PM^DFm`x&THGDU@jq%1l9k~0=87~4Vi3N61^zwe;DBp%Ik2<3)k z6sy7xmhv+Vk|3dRrTT;Zk~N{nNAV-O@$)c$8?5qDr$9{awCnQcAIit;bgYMw1;A3vn{ypPVSry%#leNn~Gh27wv*ux;QGD;Cdn zLKEd}G|I%-1g*k93uq}de%3>cofj?kAfG+Q?V(|w?Lbv1I#A_&?ZndXKFu0MY{&_d zL9t-$psx7gJOzPQTL@hHR>JT_&;@tXHl3-Frh;p?crb!wmjhLg54K|5)ep}cFFGb$6AvvZf7K48 zs0I(GbG9hN?=-cvt2eE#pSp}VL-qXrt34c)V93(i(opk}f`tgDy=q|x5fk1B17eCn z@S*}srt$^Of2dX;CMu~u>u(4LE**)Qb8glGLXB&J_CD?y1n=SHq1eH_-=?P%TT4qN zOkvaAIuU&0`DY#k>8)OAXQ`3Hmw|V5jx@uh!?rb9zi$0_&SNV#?(7%bkNqO;tj`xe);KOyvrs& zpVXDMlG0Twbltx1UovPVz>Ql3?uTlgu&ptZF+!jV@<m85G?dN>I%V~rrKZ!f@f|UhPo7>x4=rCCs^uAq{VJPnmU%iovT8Gx zJ$J}zpM4g-$5ohJ=}%}T&nR8U;Z$?dKkd7TB?V4@vp6W`{zb*V2}>l#qVa#3@_!TN r|8M>s8z=XX{|4;;TvN*uc6^StNLxo#A$*PkpFX3bX`oT6ZujOt3?ceX diff --git a/third_party/perfetto/ui/src/assets/rec_profiling.png b/third_party/perfetto/ui/src/assets/rec_profiling.png deleted file mode 100644 index 385670c8c9fb7d30dbf492150203f991df8f95b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65111 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 C?yblG diff --git a/third_party/perfetto/ui/src/assets/rec_ring_buf.png b/third_party/perfetto/ui/src/assets/rec_ring_buf.png deleted file mode 100644 index a2490fa5d4ff5ad49be674d6784cd68cefcb221f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24968 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

}5vO$YJmP=M!}`93s_)PlW&9-~*F-1U!0U7; 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

l<)Ju1u# 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 diff --git a/third_party/perfetto/ui/src/assets/rec_vmstat.png b/third_party/perfetto/ui/src/assets/rec_vmstat.png deleted file mode 100644 index 58a4e714e8c5c8d5e67a2907708d5507d303f1e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49966 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 diff --git a/third_party/perfetto/ui/src/assets/record.scss b/third_party/perfetto/ui/src/assets/record.scss deleted file mode 100644 index e4820d8c41f8..000000000000 --- a/third_party/perfetto/ui/src/assets/record.scss +++ /dev/null @@ -1,1344 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "widgets/theme"; - -:root { - --record-text-color: #333; -} - -// The whole record page. -.record-page { - position: relative; - overflow-y: scroll; - background-color: #fefefe; - padding: 40px 20px; -} - -// The always-visible centered box that has menu and sections on the right. -.record-container { - position: relative; - max-width: 900px; - min-height: 500px; - margin: auto; - border-radius: $pf-border-radius; - box-shadow: 0 1px 2px 0 #aaa, 0 1px 3px 1px #eee; - background-color: #fff; - display: grid; - grid-template-rows: auto 1fr; - grid-template-areas: - "header" - "content"; - overflow: hidden; - z-index: 6; - - // The body of the record container. - .record-container-content { - display: grid; - grid-template-columns: 2fr 5fr; - grid-template-areas: "sidebar section"; - } - - .full-centered { - width: 100%; - height: 100%; - text-align: center; - padding: 180px 30px; - font-family: "Roboto", sans-serif; - font-size: 25px; - } -} - -.record-modal { - display: flex; - flex-direction: column; - - .line { - padding: 10px 10px 10px 10px; - border-bottom: 1px solid #808080; - } - - .record-modal-section { - display: flex; - flex-direction: row; - - .logo-wrapping { - width: 150px; - height: 150px; - display: inline-block; - margin: 50px 30px 0px 0px; - align-self: center; - - i.material-icons { - color: #16161d; - font-size: 150px; - } - } - - select { - min-width: 300px; - min-height: 80px; - } - - .record-modal-description { - display: flex; - flex-direction: column; - align-items: left; - - .record-modal-command { - display: flex; - flex-direction: row; - align-items: center; - padding: 10px 0px 10px 0px; - color: #fff; - - .code-snippet { - width: 100%; - } - } - - h3 { - padding-top: 15px; - align-self: start; - font-size: 1.2rem; - color: #0000ff; - } - - h4 { - align-self: start; - font-size: 1.1rem; - } - - text { - padding: 10px 0px 10px 0px; - color: #000000; - } - - input[type="text"] { - flex-grow: 1; - border-radius: $pf-border-radius; - border: 1px solid #dcdcdc; - padding: 3px; - margin: 0 10px; - min-width: 170px; - - &:focus { - outline: none; - box-shadow: 1px 1px 1px rgba(23, 32, 44, 0.3); - } - } - } - } - - .record-modal-button, - .record-modal-button-high, - .record-modal-logo-button { - font-size: 0.875rem; - padding-left: 1rem; - padding-right: 1rem; - padding-top: 0.5rem; - padding-bottom: 0.5rem; - background-color: #0000ff; - color: #fff; - cursor: pointer; - -webkit-appearance: button; - text-transform: none; - overflow: visible; - line-height: 1.15; - margin: 5px; - will-change: transform; - -moz-osx-font-smoothing: grayscale; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - transform: translateZ(0); - transition: transform 0.25s ease-out; - align-self: end; - text-align: center; - } - - .record-modal-button, - .record-modal-button-high { - border-radius: $pf-border-radius; - border-style: none; - border-width: 0; - } - - .record-modal-button-high:disabled { - background-color: #808080; - } - - .record-modal-button-high { - height: 100%; - align-self: center; - display: flex; - align-items: center; - } - - .record-modal-logo-button { - border-radius: 50%; - width: 35px; - height: 35px; - display: flex; - align-items: center; - justify-content: center; - } -} - -.hider { - @include transition(0.2s); - position: fixed; - left: 0; - top: 0; - bottom: 0; - right: 0; - background: #000; - opacity: 0.2; - z-index: 5; -} - -.record-header { - grid-area: header; - padding: 10px; - display: flex; - flex-direction: column; - border-bottom: 1px solid #eee; - - .top-part { - display: flex; - justify-content: space-between; - align-items: center; - - // Connect/start/stop tracing buttons. - .button { - display: flex; - justify-content: flex-end; - align-items: center; - width: auto; - height: 50px; - margin: 0; - > * { - @include transition(0.2s); - cursor: pointer; - border-radius: 10px; - margin: 10px; - text-align: center; - background-color: #eee; - font-family: "Roboto", sans-serif; - font-size: 17px; - @media (max-width: 1280px) { - font-size: 1.6vw; - } - padding: 7px; - - &:hover { - background-color: hsl(88, 50%, 84%); - box-shadow: 0 0 4px 0 #999; - } - - &.selected { - background-color: hsl(88, 50%, 67%); - box-shadow: 0 0 4px 0 #999; - } - - &.disabled { - background-color: hsl(0, 0%, 97%); - } - } - } - - .target-and-status { - display: flex; - flex-direction: column; - justify-content: space-evenly; - - .target { - display: flex; - flex-direction: row; - align-items: center; - } - - label, - select, - button { - font-weight: 300; - margin: 3px; - color: #333; - font-size: 17px; - font-family: "Roboto", sans-serif; - align-items: center; - - &.error-label { - max-width: 500px; - color: rgb(255, 0, 0); - font-size: 15px; - } - } - .chip { - @include transition(); - display: flex; - align-items: center; - border: 1px solid #eee; - outline: none; - margin: 4px; - border-radius: 20px; - padding: 4px; - height: 30px; - &:hover, - &:active { - box-shadow: 0 0 4px 0 #ccc; - background-color: #fafafa; - } - i { - margin: 3px; - align-items: center; - } - } - } - } - - .note { - border-radius: 3px; - margin-bottom: 5px; - background: #f9eeba; - padding: 10px; - font-family: "Roboto", sans-serif; - font-size: 14px; - line-height: 20px; - } - - select { - @include transition(); - margin-left: 10px; - border-radius: 0; - border: 1px solid #eee; - outline: none; - &:hover, - &:active { - box-shadow: 0 0 6px #ccc; - } - } -} - -// The left-hand-side menu with 'Cpu', 'Memory' etc. -.record-menu { - grid-area: sidebar; - .rec { - color: #ee3326; - } - - background-color: #fcfcfc; - border-right: 1px solid #eee; - padding-bottom: 1em; - - header { - font-family: "Roboto", sans-serif; - font-size: 14px; - font-weight: 700; - margin: 1em; - } - - ul { - list-style-type: none; - margin: 0; - padding: 0; - } - - a, - a:link, - a:visited { - text-decoration: none; - } - - li { - @include transition(); - height: 55px; - padding: 0 1em; - font-size: 15px; - letter-spacing: 0.5px; - font-family: "Roboto", sans-serif; - font-weight: 600; - color: #666; - display: grid; - grid-template-columns: 50px 1fr; - grid-template-rows: 40px 1fr; - grid-template-areas: "icon title" "icon subtext"; - cursor: pointer; - overflow: hidden; - - i { - margin: auto; - font-size: 32px; - width: 38px; - height: 38px; - padding: 3px; - grid-area: icon; - } - - .title { - transition: line-height 0.25s ease; - grid-area: title; - line-height: 55px; - display: block; - } - - .sub { - @include transition(0.5s); - grid-area: subtext; - font-size: 10px; - line-height: 12.5px; - margin-top: -5px; - opacity: 0; - } - - &:hover { - background-color: hsl(214, 0%, 90%); - .title { - line-height: 50px; - } - .sub { - opacity: 1; - transition-duration: 0.25s; - transition-delay: 0s; - } - } - - &.active { - background-color: hsl(214, 80%, 70%); - .title, - .sub { - color: white; - } - } - } // li - - &.disabled { - opacity: 0.5; - pointer-events: none; - } -} // record-menu - -.record-section { - grid-area: section; - background: #fff; - transition: opacity 0.25s ease; - opacity: 0; - display: none; - - &:not(.active) { - max-height: 0; - } - - &.active { - display: block; - opacity: 1; - } - - .config { - &:nth-of-type(2n) { - background-color: #e7e7e7; - } - - height: auto; - width: 100%; - padding: 0; - display: flex; - align-items: center; - } - - .parsing-errors { - padding: 1em; - border: 1px solid #dc143c; - color: #dc143c; - } - - .title-config { - display: inline-block; - margin: var(--record-section-padding); - flex-grow: 1; - word-break: break-all; - } - - .config-button { - border-radius: 100%; - margin-right: 10px; - text-align: center; - justify-items: center; - font-family: "Roboto", sans-serif; - padding: 7px; - - &:hover:enabled { - box-shadow: 0 0 3px 0 #aaa; - } - - &:not(:enabled) { - background-color: hsl(0, 0%, 83%); - color: hsl(0, 0%, 50%); - } - - &.load:enabled { - background-color: hsl(88, 50%, 67%); - } - - &.delete { - background-color: hsl(0, 50%, 67%); - } - - &.save { - &.long { - width: 160px; - } - - &:enabled { - background-color: hsl(197, 50%, 67%); - } - } - - &.reset { - width: 300px; - background-color: hsl(0, 50%, 67%); - } - } - - .reset-wrapper { - padding: 1em; - } - - .input-config { - margin-top: 20px; - margin-bottom: 20px; - display: flex; - align-items: center; - padding: 0; - - input { - border-radius: 20px; - border: 1px solid #eee; - line-height: 36px; - padding: 0 10px; - font-size: 18px; - font-family: "Roboto Condensed", sans-serif; - font-weight: 300; - color: #666; - flex-grow: 1; - margin-right: 10px; - margin-left: 10px; - - background-color: transparent; - &:focus { - outline: none; - } - &::placeholder { - color: #b4b7ba; - font-family: "Roboto", sans-serif; - font-weight: 400; - } - } - } - - // By default space all section elements by the same amount. - --record-section-padding: 20px; - - > * { - padding-left: var(--record-section-padding); - padding-right: var(--record-section-padding); - &:first-child { - padding-top: 20px; - } - &:last-child { - padding-bottom: 20px; - } - } - - > header { - text-align: center; - font-family: "Roboto", sans-serif; - font-size: 20px; - padding: 15px 10px; - color: #333; - letter-spacing: 0.5px; - } - - .hide { - opacity: 0; - visibility: hidden; - } - - .probe { - display: grid; - grid-template-rows: 40px 1fr; - grid-template-columns: 220px 1fr; - grid-template-areas: "label label" "img descr"; - transition: color 0.2s ease; - padding-top: var(--record-section-padding); - padding-bottom: var(--record-section-padding); - - &.compact { - padding-top: 10px; - padding-bottom: 10px; - } - - &:nth-of-type(2n) { - background-color: #f9f9f9; - } - - > img { - transition: filter 0.2s ease, opacity 0.2s ease; - grid-area: img; - width: 210px; - box-sizing: content-box; - cursor: pointer; - opacity: 0.5; - filter: saturate(0.15); - } - - &:hover { - > img { - opacity: 1; - } - > label { - color: #333; - input[type="checkbox"]::after { - background: hsl(207, 60%, 60%); - } - } - } // :hover - - > label { - grid-area: label; - cursor: pointer; - font-family: "Roboto", sans-serif; - font-size: 20px; - font-weight: 400; - color: #999; - - // The per-probe on-off switch. - input[type="checkbox"] { - -moz-appearance: none; - -webkit-appearance: none; - cursor: pointer; - margin: 0 10px 0 3px; - position: relative; - display: inline-block; - height: 20px; - width: 44px; - background: #89898966; - border-radius: 100px; - transition: all 0.3s ease; - overflow: visible; - vertical-align: middle; - - &:focus { - outline: none; - } - - &::after { - position: absolute; - left: -2px; - top: -3px; - display: block; - width: 26px; - height: 26px; - border-radius: 100px; - background: #f5f5f5; - box-shadow: 0 3px 3px rgba(0, 0, 0, 0.15); - content: ""; - transition: all 0.3s ease; - } - &:checked { - background: #8398b7; - } - &:focus::after { - background: hsl(207, 60%, 60%); - } - &:checked::after { - left: 20px; - background: #27303d; - } - } // checkbox - } // label - - // The content of the probe section. - > div { - grid-area: descr; - font-size: 14px; - font-weight: 200; - min-height: 50px; - color: var(--record-text-color); - line-height: 20px; - } - - // .probe-config is showed only when the probe is enabled. - .probe-config { - @include transition(0.3s); - opacity: 0; - visibility: hidden; - margin: 10px 10px 0 0; - max-height: 0; - } - - &.enabled { - .probe-config { - opacity: 1; - visibility: visible; - max-height: 100vh; - } - > label span { - color: #4e80b7; - } - > img { - filter: saturate(1); - opacity: 1; - } - } - } // probe - - .toggle { - transition: color 0.2s ease; - padding-top: var(--record-section-padding); - - &:hover { - > img { - opacity: 1; - } - > label { - color: #333; - input[type="checkbox"]::after { - background: hsl(207, 60%, 60%); - } - } - } // :hover - - > label { - cursor: pointer; - font-size: 14px; - color: var(--record-text-color); - - // The per-probe on-off switch. - input[type="checkbox"] { - -moz-appearance: none; - -webkit-appearance: none; - cursor: pointer; - margin: 0 12px 0 2px; - position: relative; - display: inline-block; - height: 10px; - width: 22px; - background: #89898966; - border-radius: 100px; - transition: all 0.3s ease; - overflow: visible; - vertical-align: middle; - - &:focus { - outline: none; - } - - &::after { - position: absolute; - left: -5px; - top: -5px; - display: block; - width: 20px; - height: 20px; - border-radius: 100px; - background: #f5f5f5; - box-shadow: 0 3px 3px rgba(0, 0, 0, 0.15); - content: ""; - transition: all 0.3s ease; - } - &:checked { - background: #8398b7; - } - &:focus::after { - background: hsl(207, 60%, 60%); - } - &:checked::after { - left: 12px; - background: #27303d; - } - } // checkbox - } // label - - // The content of the toggle section. - > div.descr { - padding-left: 36px; - font-size: 12px; - color: #666; - } - } // toggle - - // The three "Stop when full", "Ring buffer", "Long trace" buttons. - .record-mode { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-template-areas: ". . ."; - grid-template-rows: 1fr; - padding-top: 0; - - input[type="radio"] { - appearance: none; - -webkit-appearance: none; - display: none; - } - - > * { - @include transition(0.2s); - cursor: pointer; - border-radius: 15px; - margin: 5px; - text-align: center; - background-color: #eee; - font-family: "Roboto", sans-serif; - font-size: 20px; - @media (max-width: 1280px) { - font-size: 1.6vw; - } - padding-bottom: 10px; - - &:hover { - background-color: hsl(88, 50%, 84%); - box-shadow: 0 0 4px 0 #999; - } - - &.selected { - background-color: hsl(88, 50%, 67%); - box-shadow: 0 0 4px 0 #999; - } - - img { - width: 100%; - } - } - } // record-mode - - // There are two types of sliders: - // 1) The full-width one (default), e.g. the one used in the main recording - // page for the duration of the trace. This one has both an icon and a - // label on the top. - // 2) The smaller ones (.thin) used in the probes. This one has no icon. - .slider { - @include transition(0.3s); - display: grid; - grid-template-columns: 40px 1fr 130px 0; - grid-template-rows: 30px min-content 1fr; - grid-template-areas: - "hdr hdr hdr hdr" "descr descr descr descr" - "icon slider label unit"; - margin-top: var(--record-section-padding); - - &.thin { - grid-template-columns: 1fr 1fr 100px 0; - grid-template-areas: - "hdr hdr hdr hdr" "descr descr descr descr" - "slider slider label unit"; - } - - &.greyed-out { - opacity: 0.5; - } - - > * { - height: 40px; - line-height: 40px; - } - - > header { - @include transition(0.3s); - opacity: 0.6; - color: #333; - grid-area: hdr; - } - - &.thin > header { - opacity: 1; - color: var(--record-text-color); - font-size: 14px; - } - - &.thin > header.descr { - grid-area: descr; - font-size: 12px; - color: #666; - height: 20px; - line-height: 20px; - } - - &:hover > header { - opacity: 1; - transition-duration: 0.15s; - } - - > i { - grid-area: icon; - font-size: 32px; - color: #333; - } - - input[type="range"] { - grid-area: slider; - width: 100%; - appearance: none; - -webkit-appearance: none; - scroll-snap-type: x mandatory; - background-color: transparent; - outline: none; - margin-left: -10px; - margin-top: -5px; - - &::-webkit-slider-runnable-track { - margin: 10px; - width: 100%; - height: 10px; - background-color: #ddd; - border-radius: 4px; - } - - &::-webkit-slider-thumb { - @include transition(); - appearance: none; - -webkit-appearance: none; - border: none; - border-radius: 3px; - height: 20px; - width: 40px; - background-color: rgb(33, 150, 243); - margin-top: -5px; - cursor: pointer; - content: ""; - } - - &:hover::-webkit-slider-thumb, - &:focus::-webkit-slider-thumb { - box-shadow: 0 0 4px rgb(16, 81, 134); - transform: scale(1, 1.1); - } - } - - &.thin input[type="range"]::-webkit-slider-runnable-track { - height: 8px; - } - - &.thin input[type="range"]::-webkit-slider-thumb { - width: 20px; - border-radius: 100%; - } - - .spinner { - @include transition(); - grid-area: label; - border: 1px solid #fafafa; - border-bottom: 2px solid #ddd; - padding: 0 5px; - border-radius: 2px; - background-color: rgba(255, 255, 255, 60%); - font-family: "Roboto", sans-serif; - font-size: 16px; - font-weight: 100; - height: 35px; - outline: none; - - &::-webkit-inner-spin-button, - &::-webkit-outer-spin-button, - &::-webkit-clear-button { - -webkit-appearance: none; - margin: 0; - } - - &:hover, - &:focus { - border-bottom-color: hsl(207, 90%, 54%); - background-color: hsl(207, 50%, 97%); - } - - &:invalid { - border-bottom-color: hsl(9, 90%, 54%); - background-color: hsl(9, 50%, 97%); - } - } - - &.thin .spinner { - font-size: 14px; - margin-top: -5px; - } - - .unit { - grid-area: unit; - font-size: 12px; - color: var(--record-text-color); - position: relative; - line-height: 37px; - overflow: hidden; - width: 35px; - left: -45px; - text-align: right; - margin-top: -5px; - } - } - - .chrome-categories { - margin: var(--record-section-padding) 0; - - display: flex; - flex-direction: row; - - .categories-list { - width: 50%; - - h3 { - margin: 6px 0; - } - - .config-button { - border-radius: 10px; - border: 1px solid #eee; - margin: 0 5px; - font-size: 0.8rem; - } - - .checkboxes { - list-style-type: none; - padding: 0; - font-size: 0.9rem; - - li { - margin: 6px 0; - } - input { - margin-right: 8px; - } - } - } - } - - .dropdown { - border: 1px solid #eee; - outline: none; - -webkit-appearance: none; - - option, - optgroup { - @include transition(); - min-height: 25px; - font-size: 12px; - color: var(--record-text-color); - cursor: pointer; - padding: 5px 0; - } - - option { - padding: 2.5px 5px; - border-bottom: 1px solid #eee; - &:hover { - background-color: hsl(214, 80%, 90%); - } - &::before { - display: none; - content: ""; - } - } - - &.singlecolumn { - margin: var(--record-section-padding) 0; - padding: 0; - max-width: 100%; - width: 100%; - overflow-y: auto; - height: 400px; - optgroup { - display: grid; - padding: 0; - grid-template-columns: 1fr; - } - option { - margin: 0; - } - } - - &.multicolumn { - padding: 0; - max-width: 100%; - width: 100%; - overflow-y: auto; - optgroup { - display: grid; - padding: 0; - grid-template-columns: 1fr 1fr 1fr; - } - option { - &:nth-of-type(3n + 1) { - border-left: 1px solid #eee; - border-right: 1px solid #eee; - } - margin: 0; - } - - &.two-columns { - height: 400px; - margin: var(--record-section-padding); - optgroup { - display: grid; - padding: 0; - grid-template-columns: 1fr 1fr; - } - option { - &:nth-of-type(2n + 1) { - border-left: 1px solid #eee; - border-right: 1px solid #eee; - } - margin: 0; - } - } - } - } - - .atrace-categories { - height: 227px; - } - - .ftrace-events { - height: 152px; - } - - textarea.extra-input { - width: 100%; - height: 60px; - border: 1px solid #eee; - resize: none; - outline: none; - font-family: var(--monospace-font); - - &::placeholder { - color: #aaa; - } - } - - textarea.atrace-apps-list { - margin-top: 16px; - height: 100px; - } - - &.instructions { - label, - select { - font-weight: 100; - color: #333; - font-size: 16px; - font-family: "Roboto", sans-serif; - } - - .note { - border: 1px dashed #ddd; - background: #f9eeba; - margin: var(--record-section-padding); - padding: 10px; - font-family: "Roboto", sans-serif; - font-size: 14px; - line-height: 20px; - } - - select { - @include transition(); - margin-left: 10px; - border-radius: 0; - border: 1px solid #eee; - outline: none; - - &:hover, - &:active { - box-shadow: 0 0 6px #ccc; - } - } - // Stop/cancel buttons - .buttons { - display: flex; - justify-content: center; - align-items: center; - width: auto; - height: 70px; - > * { - @include transition(0.2s); - cursor: pointer; - border-radius: 10px; - text-align: center; - margin: 3px; - background-color: #eee; - font-family: "Roboto", sans-serif; - flex-grow: 1; - font-size: 17px; - @media (max-width: 1280px) { - font-size: 1.6vw; - } - padding: 7px; - - &:hover { - background-color: hsl(88, 50%, 84%); - box-shadow: 0 0 4px 0 #999; - } - - &.selected { - background-color: hsl(88, 50%, 67%); - box-shadow: 0 0 4px 0 #999; - } - } - } - - .permalinkconfig { - margin: var(--record-section-padding); - height: 40px; - max-width: 200px; - border-radius: 10px; - text-align: center; - justify-items: center; - font-family: "Roboto", sans-serif; - padding: 7px; - background-color: hsl(88, 50%, 67%); - - &:hover { - box-shadow: 0 0 4px 0 #999; - } - } - - progress { - -webkit-appearance: none; - appearance: none; - width: 600px; - height: 30px; - margin: var(--record-section-padding); - border-radius: 5px; - } - ::-webkit-progress-value { - background-color: hsl(88, 50%, 67%); - } - ::-webkit-progress-bar { - background-color: #eee; - } - } -} // record-section - -.inline-chip { - @include transition(); - &:hover, - &:active { - box-shadow: 0 0 2px 0 #ccc; - background-color: #fafafa; - } - - > i.material-icons { - color: rgb(60, 60, 60); - font-size: 14px; - } - - line-height: 25px; - font-size: smaller; - padding: 2px 4px; - border: 1px solid #eee; - margin: 2px; - border-radius: 9px; -} - -a.inline-chip, -a.inline-chip:link, -a.inline-chip:visited { - text-decoration: none; - color: var(--record-text-color); -} - -.code-snippet { - display: grid; - position: relative; - padding: 0; - margin: var(--record-section-padding); - background-color: #111; - border-radius: 4px; - box-shadow: 0 0 12px #999; - - @keyframes ripple { - 0% { - transform: scale(1); - } - 30% { - transform: scale(1.2); - } - 60% { - transform: scale(1); - } - 80% { - transform: scale(1.3); - } - 100% { - transform: scale(1.2); - } - } - - &::before { - height: 20px; - content: ""; - display: block; - background-color: #598eca; - } - - &.no-top-bar { - white-space: pre; - &::before { - height: 0; - } - } - - > code { - display: block; - margin: 10px 5px 20px 20px; - color: #ccc; - font-family: var(--monospace-font); - font-size: 12px; - line-height: 20px; - overflow-y: auto; - white-space: pre-wrap; - word-wrap: break-word; - - // 510px and not 500px, so the overflowing line gets truncated, giving - // a clear indication that the code box scrolls. - max-height: 510px; - } - - > button { - @include transition(); - display: inline-block; - position: absolute; - top: 30px; - right: 20px; - color: white; - border-radius: 100%; - background-color: #333; - box-shadow: 0 0 2px rgba(255, 255, 255, 200); - padding: 5px; - font-size: 16px; - line-height: 13px; // Deliberately smaller to center the icon. - user-select: none; - - &:hover { - background-color: #444; - transform: scale(1.1); - } - } - - &:active:hover > button:not(:hover) { - animation: ripple linear 0.5s; - background-color: #701d17; - transform: scale(1.1); - } - - > button:active:hover { - transform: scale(0.9); - } -} // code-snippet diff --git a/third_party/perfetto/ui/src/assets/scheduling_latency.png b/third_party/perfetto/ui/src/assets/scheduling_latency.png deleted file mode 100644 index 2a2807448adc8f63cc455ae0abc002ca6840fbb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10720 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 * { - border-bottom: 1px solid #404854; - } - input[type="file"] { - display: none; - } - > header { - font-family: "Roboto Condensed", sans-serif; - font-weight: 700; - font-size: 24px; - height: var(--topbar-height); - line-height: var(--topbar-height); - vertical-align: middle; - padding: 0 12px; - color: #fff; - overflow: visible; - .brand { - height: 36px; - margin-top: 4px; - } - &::before { - z-index: 10; - } - &.canary::before, - &.autopush::before { - display: block; - position: absolute; - font-size: 10px; - line-height: 5px; - font-family: "Roboto", sans-serif; - top: 7px; - right: 48px; - } - &.canary::before { - content: "CANARY"; - color: #ffd700; - } - &.autopush::before { - content: "AUTOPUSH"; - color: #aed581; - } - } - .sidebar-button { - position: fixed; - z-index: 5; - background-color: #262f3c; - height: var(--topbar-height); - left: calc(var(--sidebar-width) - 50px); - border-radius: 0 5px 5px 0; - border-bottom: inherit; - visibility: visible; // So stays visible when the sidebar is hidden. - transition: left var(--anim-easing) var(--sidebar-timing); - width: 48px; - overflow: hidden; - > button { - vertical-align: middle; - } - } - &.hide-sidebar { - visibility: hidden; - margin-left: calc(var(--sidebar-width) * -1); - .sidebar-button { - left: 0; - background-color: transparent; - border-radius: unset; - border-bottom: none; - color: #aaaaaa; - } - } - .sidebar-scroll { - overflow-y: auto; - flex: 1; - &::-webkit-scrollbar { - width: 0.5em; - } - &::-webkit-scrollbar-track { - background-color: #19212b; - border-radius: 2px; - } - &::-webkit-scrollbar-thumb { - background: #b4b7ba6e; - border-radius: 2px; - } - > .sidebar-scroll-container { - position: relative; - min-height: 100%; - padding-bottom: var(--sidebar-padding-bottom); - - > section { - @include transition(); - padding: 20px 0; - max-height: 80px; - .section-header { - cursor: pointer; - > h1, - > h2 { - letter-spacing: 0.25px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin: 0 12px; - } - > h1 { - color: #fff; - font-size: 15px; - } - > h2 { - @include transition(); - color: rgba(255, 255, 255, 0.5); - font-size: 12px; - margin-top: 8px; - font-weight: 400; - } - &:before { - @include material-icon("expand_more"); - float: right; - color: rgba(255, 255, 255, 0.3); - margin-right: 12px; - margin-top: -4px; - } - } - &:hover { - background-color: #373f4b; - } - &.expanded { - background-color: #19212b; - max-height: unset; - .section-header { - h2 { - opacity: 0; - } - - &:before { - content: "expand_less"; - } - } - - .section-content { - pointer-events: inherit; - opacity: 1; - } - } - } - - .section-content { - pointer-events: none; - @include transition(); - opacity: 0; - color: #b4b7ba; - a { - color: #b4b7ba; - } - ul { - list-style-type: none; - margin: 0; - padding: 0; - } - li { - @include transition(); - a { - line-height: 24px; - font-size: 14px; - padding: 4px 12px; - text-decoration: none; - display: block; - &.pending { - color: rgba(255, 255, 255, 0.3); - &::after { - content: " "; - display: inline-block; - vertical-align: middle; - box-sizing: border-box; - width: 18px; - height: 18px; - margin-left: 10px; - border-radius: 50%; - border: 2px solid #b4b7ba; - border-color: #b4b7ba transparent; - animation: pending-spinner 1.25s linear infinite; - } - @keyframes pending-spinner { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } - } - } - &[disabled] { - text-decoration: line-through; - } - } - .material-icons { - margin-right: 10px; - font-size: 20px; - } - &:hover { - background-color: #373f4b; - } - .trace-file-name { - white-space: break-spaces; - font-family: "Roboto Condensed", sans-serif; - word-break: break-all; - font-weight: 300; - letter-spacing: 0; - margin-top: -10px; - color: #fff; - } - } - } - } - } - - .sidebar-footer { - position: absolute; - bottom: 0; - width: 100%; - padding: 2px 10px; - display: grid; - height: -var(--sidebar-padding-bottom); - grid-template-columns: repeat(4, min-content); - grid-gap: 10px; - - > button { - color: hsl(217, 39%, 94%); - i { - font-size: 24px; - } - - &:hover { - color: hsl(45, 100%, 48%); - } - } - - > .dbg-info-square { - font-family: "Roboto Condensed", sans-serif; - width: 24px; - height: 24px; - line-height: 24px; - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - - margin: 1px 0; - background: #12161b; - color: #4e71b3; - border-radius: $pf-border-radius; - font-size: 12px; - text-align: center; - &.green { - background: #7aca75; - color: #12161b; - } - &.amber { - background: #ffc107; - color: #333; - } - &.red { - background: #d32f2f; - color: #fff; - } - > div { - font-size: 10px; - line-height: 11px; - } - } - - .version { - position: absolute; - right: 8px; - bottom: 3px; - font-size: 12px; - font-family: "Roboto Condensed", sans-serif; - a { - color: rgba(255, 255, 255, 0.5); - text-decoration: none; - } - margin-top: 11px; - } - } -} - -// Hide the footer when running integration tests, as the version code and the -// tiny text with pending queries can fail the screenshot diff test. -body.testing .sidebar-footer { - visibility: hidden; -} - -.keycap { - background-color: #fafbfc; - border: 1px solid #d1d5da; - border-bottom-color: #c6cbd1; - border-radius: 3px; - box-shadow: inset 0 -1px 0 #c6cbd1; - color: #444d56; - display: inline-block; - font-family: var(--monospace-font); - vertical-align: middle; - - line-height: 20px; - padding: 3px 7px; -} diff --git a/third_party/perfetto/ui/src/assets/topbar.scss b/third_party/perfetto/ui/src/assets/topbar.scss deleted file mode 100644 index 14a42b456888..000000000000 --- a/third_party/perfetto/ui/src/assets/topbar.scss +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "widgets/theme"; - -@mixin omnibox-width() { - width: 90%; - max-width: 600px; -} - -.topbar { - grid-area: topbar; - position: relative; - z-index: 3; - overflow: visible; - background-color: hsl(215, 1%, 95%); - box-shadow: 0px 1px 2px 1px #00000026; - min-height: var(--topbar-height); - display: flex; - justify-content: center; - align-items: center; - .omnibox { - @include omnibox-width(); - @include transition(0.25s); - display: grid; - grid-template-areas: "icon input stepthrough"; - grid-template-columns: 34px auto max-content; - border-radius: $pf-border-radius; - background-color: #fcfcfc; - border: 0; - line-height: 34px; - &:before { - @include material-icon("search"); - margin: 5px; - color: #aaa; - grid-area: icon; - } - input { - grid-area: input; - border: 0; - padding: 0 10px; - font-size: 18px; - font-family: "Roboto Condensed", sans-serif; - font-weight: 300; - color: #666; - background-color: transparent; - &:focus { - outline: none; - } - &::placeholder { - color: #b4b7ba; - font-family: "Roboto Condensed", sans-serif; - font-weight: 400; - } - } - &.command-mode { - background-color: #111; - border-radius: 0; - width: 100%; - max-width: 100%; - margin-top: 0; - border-left: 1px solid #404854; - height: var(--topbar-height); - input { - color: #9ddc67; - font-family: var(--monospace-font); - padding-left: 0; - } - &:before { - content: "attach_money"; - color: #9ddc67; - font-size: 26px; - padding-top: 5px; - } - } - &.message-mode { - background-color: hsl(0, 0%, 89%); - border-radius: $pf-border-radius; - input::placeholder { - font-weight: 400; - font-family: var(--monospace-font); - color: hsl(213, 40%, 50%); - } - &:before { - content: "info"; - } - } - .stepthrough { - grid-area: stepthrough; - display: flex; - font: inherit; - font-size: 14px; - font-family: "Roboto Condensed", sans-serif; - font-weight: 300; - color: #aaa; - .current { - padding-right: 10px; - } - .material-icons.left { - border-right: rgb(218, 217, 217) solid 1px; - } - } - } - .progress { - position: absolute; - bottom: 0; - height: 1px; - width: 100%; - } - .progress-anim { - &:before { - content: ""; - position: absolute; - background-color: hsl(219, 50%, 50%); - top: 0; - left: 0; - bottom: 0; - will-change: left, right; - animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) - infinite; - } - &:after { - content: ""; - position: absolute; - background-color: hsl(219, 50%, 50%); - top: 0; - left: 0; - bottom: 0; - will-change: left, right; - animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) - infinite; - animation-delay: 1.15s; - } - } - @keyframes indeterminate { - 0% { - left: -35%; - right: 100%; - } - 60% { - left: 100%; - right: -90%; - } - 100% { - left: 100%; - right: -90%; - } - } - @keyframes indeterminate-short { - 0% { - left: -35%; - right: 100%; - } - 60% { - left: 100%; - right: -90%; - } - 100% { - left: 100%; - right: -90%; - } - } - - .notification-btn { - @include transition(0.25s); - font-size: 16px; - padding: 8px 10px; - margin: 0 10px; - border-radius: 2px; - background: hsl(210, 10%, 73%); - &:hover { - background: hsl(210, 10%, 83%); - } - - &.preferred { - background: hsl(210, 98%, 53%); - color: #fff; - &:hover { - background: hsl(210, 98%, 63%); - } - } - } -} - -.error { - position: absolute; - right: 10px; - color: #ef6c00; - &:hover { - cursor: pointer; - } -} - -.helpful-hint { - position: absolute; - z-index: 10; - right: 5px; - top: 5px; - width: 300px; - background-color: white; - font-size: 12px; - color: #3f4040; - display: grid; - border-radius: 5px; - padding: 8px; - box-shadow: 1px 3px 15px rgba(23, 32, 44, 0.3); -} - -.hint-text { - padding-bottom: 5px; -} - -.hint-dismiss-button { - color: #f4fafb; - background-color: #19212b; - width: fit-content; - padding: 3px; - border-radius: 3px; -} - -.hide-sidebar { - .command-mode { - padding-left: 48px; - } -} diff --git a/third_party/perfetto/ui/src/assets/trace_info_page.scss b/third_party/perfetto/ui/src/assets/trace_info_page.scss deleted file mode 100644 index 3846b7a017ce..000000000000 --- a/third_party/perfetto/ui/src/assets/trace_info_page.scss +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -.trace-info-page { - overflow-y: auto; - overflow-x: hidden; - padding: 0 20px; - - section { - margin: 20px auto; - max-width: 800px; - font-size: 1rem; - padding: 20px; - border-radius: 8px; - - &.errors { - background-color: #f3e5f5; - } - - .metric-error { - font-family: var(--monospace-font); - font-size: 12px; - padding: 5px; - word-break: break-all; - } - - h2 { - font-family: "Roboto", sans-serif; - font-weight: 400; - letter-spacing: 0.25px; - font-size: 2rem; - margin-bottom: 1rem; - } - - h3 { - font-size: 0.9rem; - font-weight: 400; - line-height: 1.25rem; - margin: 10px 0; - color: #333; - } - - .contextual-help { - font-size: 18px; - margin-left: 10px; - color: #43a047; - cursor: default; - } - - table { - border-spacing: 4px 1px; - - thead td { - margin-bottom: 5px; - padding-bottom: 5px; - border-bottom: 1px solid #333; - font-weight: 500; - } - tr td { - min-height: 20px; - } - - tbody { - tr:nth-child(2n + 1) td { - background-color: rgba(0, 0, 0, 0.04); - } - - td.name { - min-width: 150px; - } - - td { - font-family: var(--monospace-font); - font-size: 12px; - padding: 5px; - word-break: break-all; - white-space: pre-wrap; - &:first-of-type { - font-weight: 800; - } - } - } - } - } -} diff --git a/third_party/perfetto/ui/src/assets/typefaces.scss b/third_party/perfetto/ui/src/assets/typefaces.scss deleted file mode 100644 index 02ef5e875f26..000000000000 --- a/third_party/perfetto/ui/src/assets/typefaces.scss +++ /dev/null @@ -1,114 +0,0 @@ -/* latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 100; - src: url(assets/Roboto-100.woff2) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, - U+FEFF, U+FFFD; -} - -/* latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 300; - src: url(assets/Roboto-300.woff2) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, - U+FEFF, U+FFFD; -} - -/* latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 400; - src: url(assets/Roboto-400.woff2) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, - U+FEFF, U+FFFD; -} - -/* latin */ -@font-face { - font-family: "Roboto"; - font-style: normal; - font-weight: 500; - src: url(assets/Roboto-500.woff2) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, - U+FEFF, U+FFFD; -} - -/* latin */ -@font-face { - font-family: "Roboto Condensed"; - font-style: normal; - font-weight: 300; - src: url(assets/RobotoCondensed-Light.woff2) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, - U+FEFF, U+FFFD; -} - -/* latin */ -@font-face { - font-family: "Roboto Condensed"; - font-style: normal; - font-weight: 400; - src: url(assets/RobotoCondensed-Regular.woff2) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, - U+FEFF, U+FFFD; -} - -/* latin */ -@font-face { - font-family: "Roboto Mono"; - font-style: normal; - font-weight: 400; - src: url(assets/RobotoMono-Regular.woff2) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, - U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, - U+FEFF, U+FFFD; -} - -@font-face { - font-family: "Material Symbols Sharp"; - font-style: normal; - font-weight: 100 700; - src: url(assets/MaterialSymbolsOutlined.woff2) format("woff2"); -} - -@mixin icon { - font-family: "Material Symbols Sharp"; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - vertical-align: middle; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: "liga"; - -webkit-font-smoothing: antialiased; - font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 24; -} - -@mixin icon-filled { - font-variation-settings: "FILL" 1, "wght" 400, "GRAD" 0, "opsz" 24; -} - -.material-icons { - @include icon; -} - -.material-icons-filled { - @include icon; - @include icon-filled; -} diff --git a/third_party/perfetto/ui/src/assets/widgets/anchor.scss b/third_party/perfetto/ui/src/assets/widgets/anchor.scss deleted file mode 100644 index 0d54a00e0bff..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/anchor.scss +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "theme"; - -.pf-anchor { - // Converting this element to a block type here ensures this element is the - // containing box for the icon when when floating the icon. - display: inline-block; - line-height: 1; - text-decoration: none; // Remove the default underling if href exists. - color: $pf-primary-background; - cursor: pointer; - border-bottom: dotted 1px $pf-primary-background; - transition: box-shadow $pf-anim-timing, background $pf-anim-timing; - - & > .material-icons { - // For some reason, floating this icon results in the most pleasing vertical - // alignment. - float: right; - margin: 0 0 0 2px; - font-size: inherit; - line-height: inherit; - color: inherit; - } - - &:hover { - // Gently darken the background and thicken the underline. - border-bottom-style: solid; - background: $pf-minimal-background-hover; - box-shadow: 0 1px 0 $pf-primary-background; - } - - &:focus-visible { - @include focus; - } -} diff --git a/third_party/perfetto/ui/src/assets/widgets/button.scss b/third_party/perfetto/ui/src/assets/widgets/button.scss deleted file mode 100644 index b42115b96ae1..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/button.scss +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "theme"; - -.pf-button { - font-family: $pf-font; - line-height: 1; - user-select: none; - color: $pf-primary-foreground; - background: $pf-primary-background; - transition: background $pf-anim-timing, box-shadow $pf-anim-timing; - border-radius: $pf-border-radius; - padding: 4px 8px; - white-space: nowrap; - min-width: max-content; - - & > .pf-left-icon { - float: left; - margin-right: 6px; // Make some room between the icon and label - } - - & > .pf-right-icon { - float: right; - margin-left: 6px; // Make some room between the icon and label - } - - & > .material-icons { - font-size: inherit; - line-height: inherit; - } - - &:hover { - background: $pf-primary-background-hover; - } - - &:active, - &.pf-active { - transition: none; - background: $pf-primary-background-active; - box-shadow: inset 1px 1px 4px #00000040; - } - - &:focus-visible { - @include focus; - } - - &[disabled] { - background: $pf-primary-background-disabled; - color: $pf-primary-foreground-disabled; - box-shadow: none; - cursor: not-allowed; - } - - // Remove default background in minimal mode, showing only the text - &.pf-minimal { - background: $pf-minimal-background; - color: $pf-minimal-foreground; - - &:hover { - background: $pf-minimal-background-hover; - } - - &:active, - &.pf-active { - background: $pf-minimal-background-active; - } - - &[disabled] { - color: $pf-minimal-foreground-disabled; - background: $pf-minimal-background-disabled; - cursor: not-allowed; - } - } - - // Reduce padding when compact - &.pf-compact { - padding: 2px 4px; - } - - // Reduce padding when we are icon-only - &.pf-icon-only { - & > i { - margin: 0; - } - - padding: 4px 4px; - - &.pf-compact { - padding: 0; - } - } -} diff --git a/third_party/perfetto/ui/src/assets/widgets/checkbox.scss b/third_party/perfetto/ui/src/assets/widgets/checkbox.scss deleted file mode 100644 index b33a0738f855..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/checkbox.scss +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "theme"; - -// This checkbox element is expected to contain a checkbox type input followed -// by an empty span element. -// The input is completely hidden and an entirely new checkbox is drawn inside -// the span element. This allows us to style it how we like, and also add some -// fancy transitions. -// The box of the checkbox is a fixed sized span element. The tick is also a -// fixed sized rectange rotated 45 degrees with only the bottom and right -// borders visible. -// When unchecked, the tick size and border width is 0, so the tick is -// completely invsible. When we transition to checked, the border size on the -// bottom and right sides is immmdiately set to full width, and the tick morphs -// into view first by expanding along the x axis first, then expanding up the -// y-axis. This has the effect of making the tick look like it's being drawn -// onto the page with a pen. -// When transitioning from checked to unchecked, the animation plays in reverse, -// and the border width is set to 0 right at the end in order to make the tick -// completely invisible again. -.pf-checkbox { - $tick-anim-time-width: 100ms; - $tick-anim-time-height: 150ms; - $tick-anim-time: $tick-anim-time-width + $tick-anim-time-height; - $tick-easing: linear; - - $box-size: 18px; - $tick-height: 9px; - $tick-width: 5px; - $box-label-padding: 6px; - - display: inline-block; - position: relative; // Turns this container into a positioned element - font-family: $pf-font; - font-size: inherit; - color: $pf-minimal-foreground; - user-select: none; - cursor: pointer; - padding-left: $box-size + $box-label-padding; - - // Hide the default checkbox - input { - position: absolute; - opacity: 0; - pointer-events: none; - } - - // The span forms the "box" of the checkbox - span { - position: absolute; - left: 0; - top: 0; - bottom: 0; - margin-top: auto; - margin-bottom: auto; - height: $box-size; - width: $box-size; - border-radius: $pf-border-radius; - border: solid 2px $pf-minimal-foreground; - transition: background $pf-anim-timing; - background: none; - - // The :after element forms the "tick" of the checkbox - &:after { - content: ""; - display: block; - position: absolute; - bottom: 7.5px; - left: 1px; - width: 0px; - height: 0px; - border-color: $pf-primary-foreground; - border-style: solid; - border-width: 0; - transform-origin: 0% 100%; // Put the origin at the short edge of the tick - transform: rotate(45deg); - transition: height $tick-anim-time-height $tick-easing, - width $tick-anim-time-width $tick-anim-time-height $tick-easing, - border-width 0ms $tick-anim-time; - } - } - - &:hover { - span { - background: $pf-minimal-background-hover; - } - } - - input:checked + span { - border-color: $pf-primary-background; - background: $pf-primary-background; - } - - input:focus-visible + span { - @include focus; - } - - input:checked + span:after { - width: $tick-width; - height: $tick-height; - border-width: 0 2px 2px 0; - transition: width $tick-anim-time-height $tick-easing, - height $tick-anim-time-height $tick-anim-time-width $tick-easing, - border-width 0ms; - } - - &.pf-disabled { - cursor: not-allowed; - color: $pf-minimal-foreground-disabled; - - span { - border-color: $pf-minimal-foreground-disabled; - background: none; - &:after { - border-color: $pf-primary-foreground; - } - } - - input:checked ~ span { - border-color: $pf-primary-background-disabled; - background: $pf-primary-background-disabled; - } - } -} diff --git a/third_party/perfetto/ui/src/assets/widgets/empty_state.scss b/third_party/perfetto/ui/src/assets/widgets/empty_state.scss deleted file mode 100644 index 081bca97e559..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/empty_state.scss +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "theme"; - -.pf-empty-state { - display: inline-flex; - flex-direction: column; - align-items: center; - margin: 10px; - user-select: none; - - & > i { - margin: auto; - font-size: 5em; // Size of the icon is relative to the font size. - color: $pf-minimal-foreground; - margin-bottom: 10px; - } - - .pf-empty-state-header { - margin-bottom: 10px; - color: $pf-minimal-foreground; - text-align: center; - - // Limit width to the size of the container and use no wrap & elipsis to - // stop the size getting out of control. - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .pf-empty-state-detail { - margin: auto; - } -} diff --git a/third_party/perfetto/ui/src/assets/widgets/menu.scss b/third_party/perfetto/ui/src/assets/widgets/menu.scss deleted file mode 100644 index e161d4cab4e1..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/menu.scss +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "theme"; - -.pf-menu { - display: flex; - flex-direction: column; - align-items: stretch; - padding: 5px 0; - - .pf-menu-item { - font-family: $pf-font; - font-size: inherit; - user-select: none; - text-align: left; - padding: 6px 12px; - white-space: nowrap; - min-width: max-content; - cursor: pointer; - - background: $pf-minimal-background; - color: $pf-minimal-foreground; - transition: background $pf-anim-timing; - - & > .material-icons { - font-size: inherit; - line-height: inherit; - } - - & > .pf-left-icon { - float: left; - margin-right: 6px; // Make some room between the icon and label - } - - & > .pf-right-icon { - float: right; - margin-left: 6px; // Make some room between the icon and label - } - - &:hover { - background: $pf-minimal-background-hover; - } - - &:active, - &.pf-active { - background: $pf-minimal-background-active; - } - - &[disabled] { - color: $pf-minimal-foreground-disabled; - background: $pf-minimal-background-disabled; - box-shadow: none; - cursor: not-allowed; - } - - &:focus-visible { - @include focus; - } - } - - .pf-menu-divider { - border-bottom: solid 1px $pf-colour-thin-border; - margin: 5px 0 5px 0; - } -} diff --git a/third_party/perfetto/ui/src/assets/widgets/multiselect.scss b/third_party/perfetto/ui/src/assets/widgets/multiselect.scss deleted file mode 100644 index 3988ede8991e..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/multiselect.scss +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Generic list with y-overflow, move me to a common file if we want to reuse. -.pf-list { - overflow-y: auto; -} - -.pf-multiselect-popup { - font-family: $pf-font; - display: flex; - flex-direction: column; - align-items: stretch; - width: 280px; - max-height: 300px; - margin: 5px; - & > .pf-search-bar { - margin-bottom: 8px; - display: flex; - & > .pf-search-box { - flex-grow: 1; - } - } - .pf-multiselect-item { - display: block; // Put each item on a new line - margin-top: 5px; - } - .pf-multiselect-header { - align-items: baseline; - display: flex; - position: sticky; - top: 0; - font-size: 1em; - background-color: white; - z-index: 1; - font-size: 0.75em; - border-bottom: solid 1px $pf-minimal-foreground; - padding-bottom: 2px; - min-width: max-content; - & > span { - margin-right: auto; - } - } - .pf-multiselect-container { - position: relative; - margin-bottom: 16px; - } -} diff --git a/third_party/perfetto/ui/src/assets/widgets/popup.scss b/third_party/perfetto/ui/src/assets/widgets/popup.scss deleted file mode 100644 index f5eb65e9983d..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/popup.scss +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "theme"; - -.pf-popup-portal { - position: absolute; - z-index: 10; // Hack to show popups over certain other elements -} - -.pf-popup { - background: white; - border: solid 1px $pf-colour-thin-border; - border-radius: $pf-border-radius; - box-shadow: 2px 2px 16px rgba(0, 0, 0, 0.2); - .pf-popup-content { - // Ensures all content is rendered above the arrow - position: relative; - } -} - -.pf-popup-arrow, -.pf-popup-arrow::before { - position: absolute; - width: 8px; - height: 8px; - background: inherit; - border: inherit; -} - -.pf-popup-arrow { - visibility: hidden; -} - -.pf-popup-arrow::before { - visibility: visible; - content: ""; - transform: rotate(45deg); -} - -.pf-popup[data-popper-placement^="top"] > .pf-popup-arrow { - bottom: -4px; - border-top: none; - border-left: none; -} - -.pf-popup[data-popper-placement^="bottom"] > .pf-popup-arrow { - top: -6px; - border-bottom: none; - border-right: none; -} - -.pf-popup[data-popper-placement^="left"] > .pf-popup-arrow { - right: -4px; - border-bottom: none; - border-left: none; -} - -.pf-popup[data-popper-placement^="right"] > .pf-popup-arrow { - left: -6px; - border-top: none; - border-right: none; -} diff --git a/third_party/perfetto/ui/src/assets/widgets/select.scss b/third_party/perfetto/ui/src/assets/widgets/select.scss deleted file mode 100644 index c054c34b709a..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/select.scss +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "theme"; - -// Select field styled to look similar to a text input with a thin underline. -// Inspired by matherial design. -.pf-select { - font-family: $pf-font; - font-size: inherit; - outline: none; // Disable the default outline - border: none; // Disable the default border - border-bottom: solid 1px $pf-minimal-foreground; // Thin underline - background: none; - transition: border $pf-anim-timing, box-shadow $pf-anim-timing, - background $pf-anim-timing; - // Round only the top corners to avoid rounding the edges of the underline - border-radius: $pf-border-radius $pf-border-radius 0 0; - cursor: pointer; - - // Very opinionated min width for a select input - // ... any smaller and it stops looking like a select input! - min-width: 80px; - - & > .material-icons { - font-size: inherit; - line-height: inherit; - float: left; - } - - &:hover { - background: $pf-minimal-background-hover; - } - - &:focus { - background: $pf-minimal-background-hover; - border-bottom: solid 1px $pf-primary-background; - - // The box-shadow thickens the bottom border, without adding to the height. - // This is the same technique used by materializecss: - // See https://materializecss.com/text-inputs.html - box-shadow: 0 1px 0 $pf-primary-background; - } - - &[disabled] { - border-bottom-color: $pf-minimal-foreground-disabled; - color: $pf-minimal-foreground-disabled; - background: $pf-minimal-background-disabled; - cursor: not-allowed; - } -} diff --git a/third_party/perfetto/ui/src/assets/widgets/spinner.scss b/third_party/perfetto/ui/src/assets/widgets/spinner.scss deleted file mode 100644 index cfaf3d7ceac6..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/spinner.scss +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "theme"; - -@keyframes pf-spinner-rotation { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } -} - -.pf-spinner { - display: inline-block; - width: 1em; - height: 1em; - border: solid 0.1em lightgray; - border-top: solid 0.1em $pf-primary-background; - border-radius: 50%; - animation: pf-spinner-rotation 1s infinite linear; - - &.easing { - animation-timing-function: ease-in-out; - } -} diff --git a/third_party/perfetto/ui/src/assets/widgets/switch.scss b/third_party/perfetto/ui/src/assets/widgets/switch.scss deleted file mode 100644 index 8c81c57a9966..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/switch.scss +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "theme"; - -// This checkbox element is expected to contain a checkbox type input followed -// by an empty span element. -// The input is completely hidden and an entirely new checkbox is drawn inside -// the span element. This allows us to style it how we like, and also add some -// fancy transitions. -// The box of the checkbox is a fixed sized span element. The tick is also a -// fixed sized rectange rotated 45 degrees with only the bottom and right -// borders visible. -// When unchecked, the tick size and border width is 0, so the tick is -// completely invsible. When we transition to checked, the border size on the -// bottom and right sides is immmdiately set to full width, and the tick morphs -// into view first by expanding along the x axis first, then expanding up the -// y-axis. This has the effect of making the tick look like it's being drawn -// onto the page with a pen. -// When transitioning from checked to unchecked, the animation plays in reverse, -// and the border width is set to 0 right at the end in order to make the tick -// completely invisible again. -.pf-switch { - display: inline-block; - position: relative; // Turns this container into a positioned element - font-family: $pf-font; - font-size: inherit; - color: $pf-minimal-foreground; - user-select: none; - cursor: pointer; - padding-left: 32px + 6px; - - // Hide the default checkbox - input { - position: absolute; - opacity: 0; - pointer-events: none; - } - - // The span forms the "box" of the checkbox - span { - position: absolute; - left: 0; - top: 0; - bottom: 0; - margin-top: auto; - margin-bottom: auto; - height: 16px; - width: 32px; - border-radius: 8px; - transition: background $pf-anim-timing; - background: grey; - vertical-align: middle; - - // The :after element forms the "tick" of the checkbox - &:after { - content: ""; - display: block; - position: absolute; - left: 2px; - top: 0; - bottom: 0; - margin-top: auto; - margin-bottom: auto; - width: 12px; - height: 12px; - background: $pf-primary-foreground; - box-sizing: border-box; - border-radius: 50%; - transition: left $pf-anim-timing; - } - } - - input:checked + span { - background: $pf-primary-background; - } - - input:checked + span:after { - left: 18px; - } - - input:focus-visible + span { - @include focus; - } - - &.pf-disabled { - cursor: not-allowed; - color: $pf-minimal-foreground-disabled; - - span { - border-color: $pf-minimal-foreground-disabled; - background: $pf-minimal-foreground-disabled; - &:after { - border-color: $pf-minimal-foreground-disabled; - } - } - - input:checked ~ span { - border-color: $pf-primary-background-disabled; - background: $pf-primary-background-disabled; - } - } -} diff --git a/third_party/perfetto/ui/src/assets/widgets/text_input.scss b/third_party/perfetto/ui/src/assets/widgets/text_input.scss deleted file mode 100644 index 40c4b6827d48..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/text_input.scss +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -@import "theme"; - -.pf-text-input { - font-family: $pf-font; - font-size: inherit; - outline: none; // Disable the default outline - border: none; // Disable the default border - border-bottom: solid 1px $pf-minimal-foreground; // Thin underline - background: none; - transition: border $pf-anim-timing, box-shadow $pf-anim-timing, - background $pf-anim-timing; - - // Round only the top corners to avoid rounding the edges of the underline - border-radius: $pf-border-radius $pf-border-radius 0 0; - - // The gentle hover effect indicates this component is interactive - &:hover { - background: $pf-minimal-background-hover; - } - - &:focus { - background: $pf-minimal-background-hover; - border-bottom: solid 1px $pf-primary-background; - - // The box-shadow thickens the bottom border, without adding to the height. - // This is the same technique used by materializecss: - // See https://materializecss.com/text-inputs.html - box-shadow: 0 1px 0 $pf-primary-background; - } - - &[disabled] { - border-bottom-color: $pf-minimal-foreground-disabled; - color: $pf-minimal-foreground-disabled; - background: $pf-minimal-background-disabled; - cursor: not-allowed; - } -} diff --git a/third_party/perfetto/ui/src/assets/widgets/theme.scss b/third_party/perfetto/ui/src/assets/widgets/theme.scss deleted file mode 100644 index cdb451b1ca14..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/theme.scss +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Standard theme settings for widgets - -$pf-font: "Roboto Condensed", sans-serif; -$pf-border-radius: 2px; -$pf-anim-timing: 150ms cubic-bezier(0.4, 0, 0.2, 1); - -// Here we describe two colour schemes: primary and minimal -// It is assumed widgets exist on a light background -// Primary is to be used for things like buttons and checkboxes -// Minimal is to be used for things like inputs and labels -// Some controls (i.e. checkboxes) may mix and match both in the same widget. -// Other controls might use the primary scheme by default, but have a minimal -// configuration which makes them use the minimal colour scheme. - -$pf-primary-foreground: #fff; -$pf-primary-foreground-disabled: #aaa; -$pf-primary-background: #3d5688; -$pf-primary-background-hover: #4966a2; -$pf-primary-background-active: #243e71; -$pf-primary-background-disabled: #666; - -$pf-minimal-foreground: #19212b; -$pf-minimal-foreground-disabled: #aaa; -$pf-minimal-background: none; -$pf-minimal-background-hover: #0001; -$pf-minimal-background-active: #0002; -$pf-minimal-background-disabled: none; - -$pf-colour-thin-border: #aaa; - -@mixin focus { - outline: 2px auto #64b5f6; -} diff --git a/third_party/perfetto/ui/src/assets/widgets/tree.scss b/third_party/perfetto/ui/src/assets/widgets/tree.scss deleted file mode 100644 index 569ad9a6f8ec..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets/tree.scss +++ /dev/null @@ -1,126 +0,0 @@ -@import "theme"; - -$indent: 20px; - -.pf-tree-left { - min-width: max-content; - padding: 2px 8px 2px 4px; - font-weight: 600; -} - -.pf-tree-right { - padding: 2px 4px; -} - -// In tree mode, the values and keys are represented simply using a nested set -// of divs, where each child is pushed in with a left margin which creates the -// effect of indenting nested subtrees. -.pf-ptree { - .pf-tree-children { - padding-left: $indent; - border-left: dotted 1px gray; - } - - .pf-tree-node { - display: grid; - width: max-content; - grid-template-columns: [left]auto [right]1fr; - border-radius: $pf-border-radius; - - &:hover { - background: lightgray; - } - - .pf-tree-left { - grid-column: left; - &:after { - content: ":"; - font-weight: 600; - padding-left: 4px; - padding-right: 8px; - } - } - - .pf-tree-right { - grid-column: right; - } - } -} - -// In grid mode, right elements should be horizontally aligned, regardless -// of indentation level. -// "Subgrid" is a convenient tool for aligning nested grids to an outer grid's -// columns, but it is not supported in Chrome as of March 2023. -// See https://caniuse.com/css-subgrid -// See https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Subgrid -// -// For future reference - this is what a subgrid implementation might look like: -// -// .pf-ptree-grid { -// display: grid; -// grid-template-columns: auto 1fr; -// -// .pf-tree-children { -// display: grid; -// grid-column: span 2; -// grid-template-columns: subgrid; -// padding-left: $indent; -// border-left: dotted 1px gray; -// } - -// .pf-tree-node { -// display: grid; -// grid-column: span 2; -// grid-template-columns: subgrid; -// width: max-content; -// border-radius: $pf-border-radius; - -// &:hover { -// background: lightgray; -// } -// } -// } - -@mixin indentation($max, $level) { - @if $level <= $max { - .pf-tree-children { - .pf-tree-left { - margin-left: $level * $indent; - } - @include indentation($max, $level + 1); - } - } -} - -.pf-ptree-grid { - display: grid; - grid-template-columns: auto 1fr; - - .pf-tree-children { - display: contents; - } - - .pf-tree-node { - display: contents; - - &:hover { - background: lightgray; - } - - .pf-tree-left { - background: inherit; - border-radius: $pf-border-radius 0 0 $pf-border-radius; - } - - .pf-tree-right { - background: inherit; - border-radius: 0 $pf-border-radius $pf-border-radius 0; - } - } - - @include indentation(16, 1); -} - -.pf-tree-children.pf-pgrid-hidden { - display: none; -} diff --git a/third_party/perfetto/ui/src/assets/widgets_page.scss b/third_party/perfetto/ui/src/assets/widgets_page.scss deleted file mode 100644 index 96286bcfd494..000000000000 --- a/third_party/perfetto/ui/src/assets/widgets_page.scss +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -.widgets-page { - padding: 20px; - font-size: 16px; - overflow: auto; - - h1 { - margin: 32px 0 0 0; - font-size: 28px; - } - - h2 { - margin: 16px 0 0 0; - font-size: 24px; - } - - ul { - margin-block-start: 5px; - margin-block-end: 0px; - padding-inline-start: 0px; - } - - li { - list-style-type: none; - margin: 2px 0 0 0; - } - - .widget-row { - margin: 10px; - - & > * { - margin-right: 5px; - } - } - - .widget-block { - display: flex; - flex-direction: row; - } - - .widget-controls { - margin: 10px; - } - - .widget-container { - display: flex; - min-width: 300px; - min-height: 250px; - border-radius: 3px; - box-shadow: inset 2px 2px 10px #00000020; - border: dashed 1px gray; - margin: 10px 0 10px 0; - padding: 16px; - - & > * { - margin: auto; - vertical-align: middle; - } - } - - .widget-container-wide { - min-width: 450px; - } -} diff --git a/third_party/perfetto/ui/src/base/array_utils.ts b/third_party/perfetto/ui/src/base/array_utils.ts deleted file mode 100644 index a3a9980e7b5e..000000000000 --- a/third_party/perfetto/ui/src/base/array_utils.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// A function similar to Python's `range`. -export function range(n: number): number[] { - if (n < 0) { - throw new Error('range size should be non-negative!'); - } - - const result = new Array(n); - - for (let i = 0; i < n; i++) { - result[i] = i; - } - - return result; -} - -// Checks whether all the strings in the array are unique. -export function allUnique(x: string[]): boolean { - return x.length == new Set(x).size; -} diff --git a/third_party/perfetto/ui/src/base/array_utils_unittest.ts b/third_party/perfetto/ui/src/base/array_utils_unittest.ts deleted file mode 100644 index fcc8f36cdc26..000000000000 --- a/third_party/perfetto/ui/src/base/array_utils_unittest.ts +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {allUnique, range} from './array_utils'; - -describe('range', () => { - it('returns array of elements in range [0; n)', () => { - expect(range(3)).toEqual([0, 1, 2]); - expect(range(5)).toEqual([0, 1, 2, 3, 4]); - }); - - it('returns empty array on n = 0', () => { - expect(range(0)).toEqual([]); - }); - - it('throws an error on negative input', () => { - expect(() => { - range(-10); - }).toThrowError(); - }); -}); - -describe('allUnique', () => { - it('returns true on array with unique elements', () => { - expect(allUnique(['a', 'b', 'c'])).toBeTruthy(); - }); - - it('returns false on array with repeated elements', () => { - expect(allUnique(['a', 'a', 'b'])).toBeFalsy(); - }); - - // Couple of corner cases - it('returns true on an empty array', () => { - expect(allUnique([])).toBeTruthy(); - }); - - it('returns true on an array with one element', () => { - expect(allUnique(['test'])).toBeTruthy(); - }); -}); diff --git a/third_party/perfetto/ui/src/base/binary_search.ts b/third_party/perfetto/ui/src/base/binary_search.ts deleted file mode 100644 index ef352c279d7a..000000000000 --- a/third_party/perfetto/ui/src/base/binary_search.ts +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -type Numbers = Float64Array|Uint32Array|number[]; -type Range = [number, number]; - -function searchImpl( - haystack: Numbers, needle: number, i: number, j: number): number { - if (i === j) return -1; - if (i + 1 === j) { - return (needle >= haystack[i]) ? i : -1; - } - - const mid = Math.floor((j - i) / 2) + i; - const midValue = haystack[mid]; - if (needle < midValue) { - return searchImpl(haystack, needle, i, mid); - } else { - return searchImpl(haystack, needle, mid, j); - } -} - -function searchRangeImpl( - haystack: Numbers, needle: number, i: number, j: number): Range { - if (i === j) return [i, j]; - if (i + 1 === j) { - if (haystack[i] <= needle) { - return [i, j]; - } else { - return [i, i]; - } - } - - const mid = Math.floor((j - i) / 2) + i; - const midValue = haystack[mid]; - - if (needle < midValue) { - return searchRangeImpl(haystack, needle, i, mid); - } else if (needle > midValue) { - return searchRangeImpl(haystack, needle, mid, j); - } else { - while (haystack[i] !== needle) i++; - while (haystack[j - 1] !== needle) j--; - return [i, j]; - } -} - -export function search(haystack: Numbers, needle: number): number { - return searchImpl(haystack, needle, 0, haystack.length); -} - -// Given a sorted array of numbers (|haystack|) and a |needle| return the -// half open range [i, j) of indexes where |haystack| is equal to needle. -export function searchEq( - haystack: Numbers, needle: number, optRange?: Range): Range { - const range = searchRange(haystack, needle, optRange); - const [i, j] = range; - if (haystack[i] === needle) return range; - return [j, j]; -} - -// Given a sorted array of numbers (|haystack|) and a |needle| return the -// smallest half open range [i, j) of indexes which contains |needle|. -export function searchRange( - haystack: Numbers, needle: number, optRange?: Range): Range { - const [left, right] = optRange ? optRange : [0, haystack.length]; - return searchRangeImpl(haystack, needle, left, right); -} - -// Given a sorted array of numbers (|haystack|) and a |needle| return a -// pair of indexes [i, j] such that: -// If there is at least one element in |haystack| smaller than |needle| -// i is the index of the largest such number otherwise -1; -// If there is at least one element in |haystack| larger than |needle| -// j is the index of the smallest such element otherwise -1. -// -// So we try to get the indexes of the two data points around needle -// or -1 if there is no such datapoint. -export function searchSegment(haystack: Numbers, needle: number): Range { - if (!haystack.length) return [-1, -1]; - - const left = search(haystack, needle); - if (left === -1) { - return [left, 0]; - } else if (left + 1 === haystack.length) { - return [left, -1]; - } else { - return [left, left + 1]; - } -} diff --git a/third_party/perfetto/ui/src/base/binary_search_unittest.ts b/third_party/perfetto/ui/src/base/binary_search_unittest.ts deleted file mode 100644 index c941e02602ed..000000000000 --- a/third_party/perfetto/ui/src/base/binary_search_unittest.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {search, searchEq, searchRange, searchSegment} from './binary_search'; - -test('binarySearch', () => { - expect(search(Float64Array.of(), 100)).toEqual(-1); - expect(search(Float64Array.of(42), 42)).toEqual(0); - expect(search(Float64Array.of(42), 43)).toEqual(0); - expect(search(Float64Array.of(42), 41)).toEqual(-1); - expect(search(Float64Array.of(42, 43), 42)).toEqual(0); - expect(search(Float64Array.of(42, 43), 43)).toEqual(1); - expect(search(Float64Array.of(42, 43), 44)).toEqual(1); - expect(search(Float64Array.of(42, 43, 44), 41)).toEqual(-1); - expect(search(Float64Array.of(42, 43, 44), 42)).toEqual(0); - expect(search(Float64Array.of(42, 43, 44), 43)).toEqual(1); - expect(search(Float64Array.of(42, 43, 44), 44)).toEqual(2); - expect(search(Float64Array.of(42, 43, 44), 45)).toEqual(2); -}); - -test('searchEq', () => { - expect(searchEq([], 100)).toEqual([0, 0]); - expect(searchEq([42], 41)).toEqual([0, 0]); - expect(searchEq([42], 42)).toEqual([0, 1]); - expect(searchEq([42], 43)).toEqual([1, 1]); - expect(searchEq([42, 44], 42)).toEqual([0, 1]); - expect(searchEq([42, 42], 42)).toEqual([0, 2]); - expect(searchEq([41, 43], 42)).toEqual([1, 1]); -}); - -test('searchRange', () => { - expect(searchRange([], 100)).toEqual([0, 0]); - expect(searchRange([42], 41)).toEqual([0, 0]); - expect(searchRange([42], 42)).toEqual([0, 1]); - expect(searchRange([42], 43)).toEqual([0, 1]); - - expect(searchRange([39, 41], 38)).toEqual([0, 0]); - expect(searchRange([39, 41], 39)).toEqual([0, 1]); - expect(searchRange([39, 41], 40)).toEqual([0, 1]); - expect(searchRange([39, 41], 41)).toEqual([1, 2]); - expect(searchRange([39, 41], 42)).toEqual([1, 2]); - - expect(searchRange([38, 40, 40, 40, 42], 39)).toEqual([0, 1]); - expect(searchRange([38, 40, 40, 40, 42], 40)).toEqual([1, 4]); - expect(searchRange([38, 40, 40, 40, 42], 41)).toEqual([3, 4]); -}); - -test('searchSegment', () => { - expect(searchSegment(Float64Array.of(), 100)).toEqual([-1, -1]); - expect(searchSegment(Float64Array.of(42), 41)).toEqual([-1, 0]); - expect(searchSegment(Float64Array.of(42), 42)).toEqual([0, -1]); - expect(searchSegment(Float64Array.of(42), 43)).toEqual([0, -1]); - expect(searchSegment(Float64Array.of(42, 44), 42)).toEqual([0, 1]); -}); diff --git a/third_party/perfetto/ui/src/base/comparison_utils.ts b/third_party/perfetto/ui/src/base/comparison_utils.ts deleted file mode 100644 index ea6110afe954..000000000000 --- a/third_party/perfetto/ui/src/base/comparison_utils.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {ColumnType} from '../common/query_result'; - -export type ComparisonFn = (a: X, b: X) => number; - -export type SortDirection = 'DESC'|'ASC'; - -// Having a comparison function of type S and a getter that returns value of -// type S from value of type T, values of type T can be compared. -export function comparingBy( - getter: (t: T) => S, comparison: ComparisonFn): ComparisonFn { - return (x, y) => { - return comparison(getter(x), getter(y)); - }; -} - -export function withDirection( - comparison: ComparisonFn, - sortDirection?: SortDirection): ComparisonFn { - if (sortDirection !== 'DESC') { - return comparison; - } - - return (x, y) => { - return comparison(y, x); - }; -} - -export type SortableValue = ColumnType|undefined; - -function columnTypeKind(a: SortableValue): number { - if (a === undefined) { - return 0; - } - if (a === null) { - return 1; - } - if (typeof a === 'number') { - return 2; - } - if (typeof a === 'string') { - return 3; - } - // a instanceof Uint8Array - return 4; -} - -export function compareUniversal(a: SortableValue, b: SortableValue): number { - if (a === undefined && b === undefined) { - return 0; - } - if (a === null && b === null) { - return 0; - } - if (typeof a === 'number' && typeof b === 'number') { - return a - b; - } - if (typeof a === 'string' && typeof b === 'string') { - return a.localeCompare(b); - } - if (a instanceof Uint8Array && b instanceof Uint8Array) { - // Do the lexicographical comparison - for (let i = 0; i < a.length && i < b.length; i++) { - if (a[i] < b[i]) { - return -1; - } - if (a[i] > b[i]) { - return 1; - } - } - // No discrepancies found in the common prefix, compare lengths of arrays. - return a.length - b.length; - } - - // Values are of different kinds, compare the kinds - return columnTypeKind(a) - columnTypeKind(b); -} diff --git a/third_party/perfetto/ui/src/base/deferred.ts b/third_party/perfetto/ui/src/base/deferred.ts deleted file mode 100644 index 114fb0c96efc..000000000000 --- a/third_party/perfetto/ui/src/base/deferred.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Promise wrapper with exposed resolve and reject callbacks. -export interface Deferred extends Promise { - readonly resolve: (value?: T|PromiseLike) => void; - readonly reject: (reason?: any) => void; -} - -// Create a promise with exposed resolve and reject callbacks. -export function defer(): Deferred { - let resolve = null as any; - let reject = null as any; - const p = new Promise((res, rej) => [resolve, reject] = [res, rej]); - return Object.assign(p, {resolve, reject}) as any; -} diff --git a/third_party/perfetto/ui/src/base/deferred_unittest.ts b/third_party/perfetto/ui/src/base/deferred_unittest.ts deleted file mode 100644 index a1717623db92..000000000000 --- a/third_party/perfetto/ui/src/base/deferred_unittest.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {defer, Deferred} from './deferred'; - -test('deferred can resolve', async () => { - const deferred: Deferred = defer(); - let i = 0; - deferred.then(() => i++); - - expect(i).toBe(0); - deferred.resolve(); - expect(i).toBe(0); - await deferred; - expect(i).toBe(1); -}); - -test('deferred can resolve with value', () => { - const deferred: Deferred = defer(); - deferred.resolve('foo'); - return expect(deferred).resolves.toBe('foo'); -}); - -test('deferred can reject', () => { - const deferred: Deferred = defer(); - deferred.reject('foo'); - return expect(deferred).rejects.toBe('foo'); -}); diff --git a/third_party/perfetto/ui/src/base/generic_set.ts b/third_party/perfetto/ui/src/base/generic_set.ts deleted file mode 100644 index 714835f05cd5..000000000000 --- a/third_party/perfetto/ui/src/base/generic_set.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// ES6 Set does not allow to reasonably store compound objects; this class -// rectifies the problem by implementing generic set on top of Map and an -// injective function from objects of generic type to strings. -export class GenericSet { - interner: (t: T) => string; - - // Passed function should be injective (as in never having the same output for - // two different inputs). - constructor(interner: (t: T) => string) { - this.interner = interner; - } - - backingMap = new Map(); - - has(column: T): boolean { - return this.backingMap.has(this.interner(column)); - } - - add(column: T) { - this.backingMap.set(this.interner(column), column); - } - - delete(column: T) { - this.backingMap.delete(this.interner(column)); - } - - values(): Iterable { - return this.backingMap.values(); - } -} diff --git a/third_party/perfetto/ui/src/base/http_utils.ts b/third_party/perfetto/ui/src/base/http_utils.ts deleted file mode 100644 index d036751465b0..000000000000 --- a/third_party/perfetto/ui/src/base/http_utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export function fetchWithTimeout( - input: RequestInfo, init: RequestInit, timeoutMs: number) { - return new Promise((resolve, reject) => { - const timer = setTimeout( - () => reject( - new Error(`fetch(${input}) timed out after ${timeoutMs} ms`)), - timeoutMs); - fetch(input, init) - .then((response) => resolve(response)) - .catch((err) => reject(err)) - .finally(() => clearTimeout(timer)); - }); -} diff --git a/third_party/perfetto/ui/src/base/logging.ts b/third_party/perfetto/ui/src/base/logging.ts deleted file mode 100644 index 945d96a983b1..000000000000 --- a/third_party/perfetto/ui/src/base/logging.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {SCM_REVISION, VERSION} from '../gen/perfetto_version'; - -export type ErrorHandler = (err: string) => void; - -let errorHandler: ErrorHandler = (_: string) => {}; - -export function assertExists(value: A | null | undefined): A { - if (value === null || value === undefined) { - throw new Error('Value doesn\'t exist'); - } - return value; -} - -export function assertTrue(value: boolean, optMsg?: string) { - if (!value) { - throw new Error(optMsg ?? 'Failed assertion'); - } -} - -export function assertFalse(value: boolean, optMsg?: string) { - assertTrue(!value, optMsg); -} - -export function setErrorHandler(handler: ErrorHandler) { - errorHandler = handler; -} - -export function reportError(err: ErrorEvent|PromiseRejectionEvent|{}) { - let errLog = ''; - let errorObj = undefined; - - if (err instanceof ErrorEvent) { - errLog = err.message; - errorObj = err.error; - } else if (err instanceof PromiseRejectionEvent) { - errLog = `${err.reason}`; - errorObj = err.reason; - } else { - errLog = `${err}`; - } - if (errorObj !== undefined && errorObj !== null) { - const errStack = (errorObj as {stack?: string}).stack; - errLog += '\n'; - errLog += errStack !== undefined ? errStack : JSON.stringify(errorObj); - } - errLog += '\n\n'; - errLog += `${VERSION} ${SCM_REVISION}\n`; - errLog += `UA: ${navigator.userAgent}\n`; - - console.error(errLog, err); - errorHandler(errLog); -} - -// This function serves two purposes. -// 1) A runtime check - if we are ever called, we throw an exception. -// This is useful for checking that code we suspect should never be reached is -// actually never reached. -// 2) A compile time check where typescript asserts that the value passed can be -// cast to the "never" type. -// This is useful for ensuring we exhastively check union types. -export function assertUnreachable(_x: never) { - throw new Error('This code should not be reachable'); -} diff --git a/third_party/perfetto/ui/src/base/math_utils.ts b/third_party/perfetto/ui/src/base/math_utils.ts deleted file mode 100644 index f1c18165ba89..000000000000 --- a/third_party/perfetto/ui/src/base/math_utils.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Round a number up to the nearest stepsize. -export function roundUpNearest(val: number, stepsize: number): number { - return stepsize * Math.ceil(val / stepsize); -} - -// Round a number down to the nearest stepsize. -export function roundDownNearest(val: number, stepsize: number): number { - return stepsize * Math.floor(val / stepsize); -} diff --git a/third_party/perfetto/ui/src/base/math_utils_unittest.ts b/third_party/perfetto/ui/src/base/math_utils_unittest.ts deleted file mode 100644 index 169b793e8ac8..000000000000 --- a/third_party/perfetto/ui/src/base/math_utils_unittest.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {roundDownNearest, roundUpNearest} from './math_utils'; - -describe('roundUpNearest()', () => { - it('rounds decimal values up to the right step size', () => { - expect(roundUpNearest(0.1, 0.5)).toBeCloseTo(0.5); - expect(roundUpNearest(17.2, 0.5)).toBeCloseTo(17.5); - }); -}); - -describe('roundDownNearest()', () => { - it('rounds decimal values down to the right step size', () => { - expect(roundDownNearest(0.4, 0.5)).toBeCloseTo(0.0); - expect(roundDownNearest(17.4, 0.5)).toBeCloseTo(17.0); - }); -}); diff --git a/third_party/perfetto/ui/src/base/set_utils.ts b/third_party/perfetto/ui/src/base/set_utils.ts deleted file mode 100644 index fc05f7138e6d..000000000000 --- a/third_party/perfetto/ui/src/base/set_utils.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export function union(xs: Set, ys: Set): Set { - if (xs.size === 0) { - return ys; - } - if (ys.size === 0) { - return xs; - } - const result = new Set(); - for (const x of xs) { - result.add(x); - } - for (const y of ys) { - result.add(y); - } - return result; -} - -export function intersect(xs: Set, ys: Set): Set { - if (xs.size === 0) { - return xs; - } - if (ys.size === 0) { - return ys; - } - const result = new Set(); - for (const x of xs) { - if (ys.has(x)) { - result.add(x); - } - } - return result; -} - -export function isSetEqual(xs: Set, ys: Set): boolean { - if (xs === ys) { - return true; - } - if (xs.size !== ys.size) { - return false; - } - for (const x of xs) { - if (!ys.has(x)) { - return false; - } - } - return true; -} diff --git a/third_party/perfetto/ui/src/base/set_utils_unittest.ts b/third_party/perfetto/ui/src/base/set_utils_unittest.ts deleted file mode 100644 index d70e22d2cc05..000000000000 --- a/third_party/perfetto/ui/src/base/set_utils_unittest.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {intersect, isSetEqual, union} from './set_utils'; - -// Just to make the tests easier to read: -function s(xs: T[]): Set { - return new Set(xs); -} - -test('union', () => { - expect(union(s([]), s([]))).toEqual(s([])); - expect(union(s([1]), s([2]))).toEqual(s([1, 2])); - expect(union(s([1]), s([]))).toEqual(s([1])); - expect(union(s([]), s([2]))).toEqual(s([2])); - expect(union(s([1]), s([1]))).toEqual(s([1])); - expect(union(s([1, 2]), s([2, 3]))).toEqual(s([1, 2, 3])); -}); - -test('intersect', () => { - expect(intersect(s([]), s([]))).toEqual(s([])); - expect(intersect(s([1]), s([2]))).toEqual(s([])); - expect(intersect(s([1]), s([]))).toEqual(s([])); - expect(intersect(s([]), s([2]))).toEqual(s([])); - expect(intersect(s([1]), s([1]))).toEqual(s([1])); - expect(intersect(s([1, 2]), s([2, 3]))).toEqual(s([2])); -}); - -test('isSetEqual', () => { - expect(isSetEqual(s([]), s([]))).toEqual(true); - expect(isSetEqual(s([1]), s([2]))).toEqual(false); - expect(isSetEqual(s([1]), s([]))).toEqual(false); - expect(isSetEqual(s([]), s([2]))).toEqual(false); - expect(isSetEqual(s([1]), s([1]))).toEqual(true); - expect(isSetEqual(s([1, 2]), s([2, 3]))).toEqual(false); -}); diff --git a/third_party/perfetto/ui/src/base/string_utils.ts b/third_party/perfetto/ui/src/base/string_utils.ts deleted file mode 100644 index bc33b70550c7..000000000000 --- a/third_party/perfetto/ui/src/base/string_utils.ts +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - decode as b64Decode, - encode as b64Encode, - length as b64Len, -} from '@protobufjs/base64'; -import { - length as utf8Len, - read as utf8Read, - write as utf8Write, -} from '@protobufjs/utf8'; - -import {assertTrue} from './logging'; - -// TextDecoder/Decoder requires the full DOM and isn't available in all types -// of tests. Use fallback implementation from protbufjs. -let Utf8Decoder: {decode: (buf: Uint8Array) => string;}; -let Utf8Encoder: {encode: (str: string) => Uint8Array;}; -try { - Utf8Decoder = new TextDecoder('utf-8'); - Utf8Encoder = new TextEncoder(); -} catch (_) { - if (typeof process === 'undefined') { - // Silence the warning when we know we are running under NodeJS. - console.warn( - 'Using fallback UTF8 Encoder/Decoder, This should happen only in ' + - 'tests and NodeJS-based environments, not in browsers.'); - } - Utf8Decoder = {decode: (buf: Uint8Array) => utf8Read(buf, 0, buf.length)}; - Utf8Encoder = { - encode: (str: string) => { - const arr = new Uint8Array(utf8Len(str)); - const written = utf8Write(str, arr, 0); - assertTrue(written === arr.length); - return arr; - }, - }; -} - -export function base64Encode(buffer: Uint8Array): string { - return b64Encode(buffer, 0, buffer.length); -} - -export function base64Decode(str: string): Uint8Array { - // if the string is in base64url format, convert to base64 - const b64 = str.replace(/-/g, '+').replace(/_/g, '/'); - const arr = new Uint8Array(b64Len(b64)); - const written = b64Decode(b64, arr, 0); - assertTrue(written === arr.length); - return arr; -} - -// encode binary array to hex string -export function hexEncode(bytes: Uint8Array): string { - return bytes.reduce( - (prev, cur) => prev + ('0' + cur.toString(16)).slice(-2), ''); -} - -export function utf8Encode(str: string): Uint8Array { - return Utf8Encoder.encode(str); -} - -// Note: not all byte sequences can be converted to<>from UTF8. This can be -// used only with valid unicode strings, not arbitrary byte buffers. -export function utf8Decode(buffer: Uint8Array): string { - return Utf8Decoder.decode(buffer); -} - -// The binaryEncode/Decode functions below allow to encode an arbitrary binary -// buffer into a string that can be JSON-encoded. binaryEncode() applies -// UTF-16 encoding to each byte individually. -// Unlike utf8Encode/Decode, any arbitrary byte sequence can be converted into a -// valid string, and viceversa. -// This should be only used when a byte array needs to be transmitted over an -// interface that supports only JSON serialization (e.g., postmessage to a -// chrome extension). - -export function binaryEncode(buf: Uint8Array): string { - let str = ''; - for (let i = 0; i < buf.length; i++) { - str += String.fromCharCode(buf[i]); - } - return str; -} - -export function binaryDecode(str: string): Uint8Array { - const buf = new Uint8Array(str.length); - const strLen = str.length; - for (let i = 0; i < strLen; i++) { - buf[i] = str.charCodeAt(i); - } - return buf; -} - -// A function used to interpolate strings into SQL query. The only replacement -// is done is that single quote replaced with two single quotes, according to -// SQLite documentation: -// https://www.sqlite.org/lang_expr.html#literal_values_constants_ -// -// The purpose of this function is to use in simple comparisons, to escape -// strings used in GLOB clauses see escapeQuery function. -export function sqliteString(str: string): string { - return `'${str.replace(/'/g, '\'\'')}'`; -} diff --git a/third_party/perfetto/ui/src/base/string_utils_unittest.ts b/third_party/perfetto/ui/src/base/string_utils_unittest.ts deleted file mode 100644 index e12c1a474850..000000000000 --- a/third_party/perfetto/ui/src/base/string_utils_unittest.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - base64Decode, - base64Encode, - binaryDecode, - binaryEncode, - sqliteString, - utf8Decode, - utf8Encode, -} from './string_utils'; - -test('string_utils.stringToBase64', () => { - const bytes = [...'Hello, world'].map((c) => c.charCodeAt(0)); - const buffer = new Uint8Array(bytes); - const b64Encoded = base64Encode(buffer); - expect(b64Encoded).toEqual('SGVsbG8sIHdvcmxk'); - expect(base64Decode(b64Encoded)).toEqual(buffer); -}); - -test('string_utils.bufferToBase64', () => { - const buffer = new Uint8Array([0xff, 0, 0, 0x81, 0x2a, 0xfe]); - const b64Encoded = base64Encode(buffer); - expect(b64Encoded).toEqual('/wAAgSr+'); - expect(base64Decode(b64Encoded)).toEqual(buffer); -}); - -test('string_utils.utf8EncodeAndDecode', () => { - const testString = '¡HéllØ wörld!'; - const buffer = utf8Encode(testString); - expect(buffer).toEqual(new Uint8Array([ - 194, - 161, - 72, - 195, - 169, - 108, - 108, - 195, - 152, - 32, - 119, - 195, - 182, - 114, - 108, - 100, - 33, - ])); - expect(utf8Decode(buffer)).toEqual(testString); -}); - -test('string_utils.binaryEncodeAndDecode', () => { - const buf = new Uint8Array(256 + 4); - for (let i = 0; i < 256; i++) { - buf[i] = i; - } - buf.set([0xf0, 0x28, 0x8c, 0xbc], 256); - const encodedStr = binaryEncode(buf); - expect(encodedStr.length).toEqual(buf.length); - const encodedThroughJson = JSON.parse(JSON.stringify(encodedStr)); - expect(binaryDecode(encodedStr)).toEqual(buf); - expect(binaryDecode(encodedThroughJson)).toEqual(buf); -}); - -test('string_utils.sqliteString', () => { - expect(sqliteString('that\'s it')).toEqual('\'that\'\'s it\''); - expect(sqliteString('no quotes')).toEqual('\'no quotes\''); - expect(sqliteString(`foo ' bar '`)).toEqual(`'foo '' bar '''`); -}); diff --git a/third_party/perfetto/ui/src/base/trace_config_utils.ts b/third_party/perfetto/ui/src/base/trace_config_utils.ts deleted file mode 100644 index 3a0896366ef6..000000000000 --- a/third_party/perfetto/ui/src/base/trace_config_utils.ts +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {TraceConfig} from '../common/protos'; -import {perfetto} from '../gen/protos'; - -// In this file are contained a few functions to simplify the proto parsing. - -export function extractTraceConfig(enableTracingRequest: Uint8Array): - Uint8Array|undefined { - try { - const enableTracingObject = - perfetto.protos.EnableTracingRequest.decode(enableTracingRequest); - if (!enableTracingObject.traceConfig) return undefined; - return perfetto.protos.TraceConfig.encode(enableTracingObject.traceConfig) - .finish(); - } catch (e) { // This catch is for possible proto encoding/decoding issues. - console.error('Error extracting the config: ', e.message); - return undefined; - } -} - -export function extractDurationFromTraceConfig(traceConfigProto: Uint8Array) { - try { - return perfetto.protos.TraceConfig.decode(traceConfigProto).durationMs; - } catch (e) { // This catch is for possible proto encoding/decoding issues. - return undefined; - } -} - -export function browserSupportsPerfettoConfig(): boolean { - const minimumChromeVersion = '91.0.4448.0'; - const runningVersion = String( - (/Chrome\/(([0-9]+\.?){4})/.exec(navigator.userAgent) || [, 0])[1]); - - if (!runningVersion) return false; - - const minVerArray = minimumChromeVersion.split('.').map(Number); - const runVerArray = runningVersion.split('.').map(Number); - - for (let index = 0; index < minVerArray.length; index++) { - if (runVerArray[index] === minVerArray[index]) continue; - return runVerArray[index] > minVerArray[index]; - } - return true; // Exact version match. -} - -export function hasSystemDataSourceConfig(config: TraceConfig): boolean { - for (const ds of config.dataSources) { - if (ds.config && ds.config.name && - !ds.config.name.startsWith('org.chromium.')) { - return true; - } - } - return false; -} diff --git a/third_party/perfetto/ui/src/base/utils/index-browser.js b/third_party/perfetto/ui/src/base/utils/index-browser.js deleted file mode 100644 index ff470557d0db..000000000000 --- a/third_party/perfetto/ui/src/base/utils/index-browser.js +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export const _TextDecoder = TextDecoder; -export const _TextEncoder = TextEncoder; diff --git a/third_party/perfetto/ui/src/base/utils/index.js b/third_party/perfetto/ui/src/base/utils/index.js deleted file mode 100644 index 534c91a118f5..000000000000 --- a/third_party/perfetto/ui/src/base/utils/index.js +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const util = require('util'); - -module.exports._TextDecoder = util.TextDecoder; -module.exports._TextEncoder = util.TextEncoder; diff --git a/third_party/perfetto/ui/src/base/utils/package.json b/third_party/perfetto/ui/src/base/utils/package.json deleted file mode 100644 index 4e72520c5462..000000000000 --- a/third_party/perfetto/ui/src/base/utils/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "custom_utils", - "browser": { - "./index.js": "./index-browser.js" - }, - "description": "Utils functions. The reason why this module has been created is that 'TextDecoder' and 'TextEncoder' are accessible under the global namespace in the browser, and under the 'util' module in nodejs. Since the tests are running under nodejs, thanks to the 'browser' entry in this package.json, the correct file will be used.", - "version": "0.0.1" -} diff --git a/third_party/perfetto/ui/src/chrome_extension/chrome_tracing_controller.ts b/third_party/perfetto/ui/src/chrome_extension/chrome_tracing_controller.ts deleted file mode 100644 index eff160c1defc..000000000000 --- a/third_party/perfetto/ui/src/chrome_extension/chrome_tracing_controller.ts +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Protocol} from 'devtools-protocol'; -import {ProtocolProxyApi} from 'devtools-protocol/types/protocol-proxy-api'; -import rpc from 'noice-json-rpc'; - -import {base64Encode} from '../base/string_utils'; -import { - browserSupportsPerfettoConfig, - extractTraceConfig, - hasSystemDataSourceConfig, -} from '../base/trace_config_utils'; -import {TraceConfig} from '../common/protos'; -import { - ConsumerPortResponse, - GetTraceStatsResponse, - ReadBuffersResponse, -} from '../controller/consumer_port_types'; -import {RpcConsumerPort} from '../controller/record_controller_interfaces'; -import {perfetto} from '../gen/protos'; - -import {DevToolsSocket} from './devtools_socket'; - -const CHUNK_SIZE: number = 1024 * 1024 * 16; // 16Mb - -export class ChromeTracingController extends RpcConsumerPort { - private streamHandle: string|undefined = undefined; - private uiPort: chrome.runtime.Port; - private api: ProtocolProxyApi.ProtocolApi; - private devtoolsSocket: DevToolsSocket; - private lastBufferUsageEvent: Protocol.Tracing.BufferUsageEvent|undefined; - private tracingSessionOngoing = false; - private tracingSessionId = 0; - - constructor(port: chrome.runtime.Port) { - super({ - onConsumerPortResponse: (message: ConsumerPortResponse) => - this.uiPort.postMessage(message), - - onError: (error: string) => - this.uiPort.postMessage({type: 'ChromeExtensionError', error}), - - onStatus: (status) => - this.uiPort.postMessage({type: 'ChromeExtensionStatus', status}), - }); - this.uiPort = port; - this.devtoolsSocket = new DevToolsSocket(); - this.devtoolsSocket.on('close', () => this.resetState()); - const rpcClient = new rpc.Client(this.devtoolsSocket); - this.api = rpcClient.api(); - this.api.Tracing.on('tracingComplete', this.onTracingComplete.bind(this)); - this.api.Tracing.on('bufferUsage', this.onBufferUsage.bind(this)); - this.uiPort.onDisconnect.addListener(() => { - this.devtoolsSocket.detach(); - }); - } - - handleCommand(methodName: string, requestData: Uint8Array) { - switch (methodName) { - case 'EnableTracing': - this.enableTracing(requestData); - break; - case 'FreeBuffers': - this.freeBuffers(); - break; - case 'ReadBuffers': - this.readBuffers(); - break; - case 'DisableTracing': - this.disableTracing(); - break; - case 'GetTraceStats': - this.getTraceStats(); - break; - case 'GetCategories': - this.getCategories(); - break; - default: - this.sendErrorMessage('Action not recognized'); - console.log('Received not recognized message: ', methodName); - break; - } - } - - enableTracing(enableTracingRequest: Uint8Array) { - this.resetState(); - const traceConfigProto = extractTraceConfig(enableTracingRequest); - if (!traceConfigProto) { - this.sendErrorMessage('Invalid trace config'); - return; - } - - this.handleStartTracing(traceConfigProto); - } - - toCamelCase(key: string, separator: string): string { - return key.split(separator) - .map((part, index) => { - return (index === 0) ? part : part[0].toUpperCase() + part.slice(1); - }) - .join(''); - } - - convertDictKeys(obj: any): any { - if (Array.isArray(obj)) { - return obj.map((v) => this.convertDictKeys(v)); - } - if (typeof obj === 'object' && obj !== null) { - const converted: any = {}; - for (const key of Object.keys(obj)) { - converted[this.toCamelCase(key, '_')] = this.convertDictKeys(obj[key]); - } - return converted; - } - return obj; - } - - convertToDevToolsConfig(config: any): Protocol.Tracing.TraceConfig { - // DevTools uses a different naming style for config properties: Dictionary - // keys are named "camelCase" style, rather than "underscore_case" style as - // in the TraceConfig. - config = this.convertDictKeys(config); - // recordMode is specified as an enum with camelCase values. - if (config.recordMode) { - config.recordMode = this.toCamelCase(config.recordMode as string, '-'); - } - return config as Protocol.Tracing.TraceConfig; - } - - // TODO(nicomazz): write unit test for this - extractChromeConfig(perfettoConfig: TraceConfig): - Protocol.Tracing.TraceConfig { - for (const ds of perfettoConfig.dataSources) { - if (ds.config && ds.config.name === 'org.chromium.trace_event' && - ds.config.chromeConfig && ds.config.chromeConfig.traceConfig) { - const chromeConfigJsonString = ds.config.chromeConfig.traceConfig; - const config = JSON.parse(chromeConfigJsonString); - return this.convertToDevToolsConfig(config); - } - } - return {}; - } - - freeBuffers() { - this.devtoolsSocket.detach(); - this.sendMessage({type: 'FreeBuffersResponse'}); - } - - async readBuffers(offset = 0) { - if (!this.devtoolsSocket.isAttached() || this.streamHandle === undefined) { - this.sendErrorMessage('No tracing session to read from'); - return; - } - - const res = await this.api.IO.read( - {handle: this.streamHandle, offset, size: CHUNK_SIZE}); - if (res === undefined) return; - - const chunk = res.base64Encoded ? atob(res.data) : res.data; - // The 'as {} as UInt8Array' is done because we can't send ArrayBuffers - // trough a chrome.runtime.Port. The conversion from string to ArrayBuffer - // takes place on the other side of the port. - const response: ReadBuffersResponse = { - type: 'ReadBuffersResponse', - slices: [{data: chunk as {} as Uint8Array, lastSliceForPacket: res.eof}], - }; - this.sendMessage(response); - if (res.eof) return; - this.readBuffers(offset + res.data.length); - } - - async disableTracing() { - await this.endTracing(this.tracingSessionId); - this.sendMessage({type: 'DisableTracingResponse'}); - } - - async endTracing(tracingSessionId: number) { - if (tracingSessionId !== this.tracingSessionId) { - return; - } - if (this.tracingSessionOngoing) { - await this.api.Tracing.end(); - } - this.tracingSessionOngoing = false; - } - - getTraceStats() { - let percentFull = 0; // If the statistics are not available yet, it is 0. - if (this.lastBufferUsageEvent && this.lastBufferUsageEvent.percentFull) { - percentFull = this.lastBufferUsageEvent.percentFull; - } - const stats: perfetto.protos.ITraceStats = { - bufferStats: - [{bufferSize: 1000, bytesWritten: Math.round(percentFull * 1000)}], - }; - const response: GetTraceStatsResponse = { - type: 'GetTraceStatsResponse', - traceStats: stats, - }; - this.sendMessage(response); - } - - getCategories() { - const fetchCategories = async () => { - const categories = (await this.api.Tracing.getCategories()).categories; - this.uiPort.postMessage({type: 'GetCategoriesResponse', categories}); - }; - // If a target is already attached, we simply fetch the categories. - if (this.devtoolsSocket.isAttached()) { - fetchCategories(); - return; - } - // Otherwise, we attach temporarily. - this.devtoolsSocket.attachToBrowser(async (error?: string) => { - if (error) { - this.sendErrorMessage( - `Could not attach to DevTools browser target ` + - `(req. Chrome >= M81): ${error}`); - return; - } - fetchCategories(); - this.devtoolsSocket.detach(); - }); - } - - resetState() { - this.devtoolsSocket.detach(); - this.streamHandle = undefined; - } - - onTracingComplete(params: Protocol.Tracing.TracingCompleteEvent) { - this.streamHandle = params.stream; - this.sendMessage({type: 'EnableTracingResponse'}); - } - - onBufferUsage(params: Protocol.Tracing.BufferUsageEvent) { - this.lastBufferUsageEvent = params; - } - - handleStartTracing(traceConfigProto: Uint8Array) { - this.devtoolsSocket.attachToBrowser(async (error?: string) => { - if (error) { - this.sendErrorMessage( - `Could not attach to DevTools browser target ` + - `(req. Chrome >= M81): ${error}`); - return; - } - - const requestParams: Protocol.Tracing.StartRequest = { - streamFormat: 'proto', - transferMode: 'ReturnAsStream', - streamCompression: 'gzip', - bufferUsageReportingInterval: 200, - }; - - const traceConfig = TraceConfig.decode(traceConfigProto); - if (browserSupportsPerfettoConfig()) { - const configEncoded = base64Encode(traceConfigProto); - await this.api.Tracing.start( - {perfettoConfig: configEncoded, ...requestParams}); - this.tracingSessionOngoing = true; - const tracingSessionId = ++this.tracingSessionId; - setTimeout( - () => this.endTracing(tracingSessionId), traceConfig.durationMs); - } else { - console.log( - 'Used Chrome version is too old to support ' + - 'perfettoConfig parameter. Using chrome config only instead.'); - - if (hasSystemDataSourceConfig(traceConfig)) { - this.sendErrorMessage( - 'System tracing is not supported by this Chrome version. Choose' + - ' the \'Chrome\' target instead to record a Chrome-only trace.'); - return; - } - - const chromeConfig = this.extractChromeConfig(traceConfig); - await this.api.Tracing.start( - {traceConfig: chromeConfig, ...requestParams}); - } - }); - } -} diff --git a/third_party/perfetto/ui/src/chrome_extension/devtools_socket.ts b/third_party/perfetto/ui/src/chrome_extension/devtools_socket.ts deleted file mode 100644 index 0e595504cb48..000000000000 --- a/third_party/perfetto/ui/src/chrome_extension/devtools_socket.ts +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import rpc from 'noice-json-rpc'; - -// To really understand how this works it is useful to see the implementation -// of noice-json-rpc. -export class DevToolsSocket implements rpc.LikeSocket { - private messageCallback: Function = (_: string) => {}; - private openCallback: Function = () => {}; - private closeCallback: Function = () => {}; - private target: chrome.debugger.Debuggee|undefined; - - constructor() { - chrome.debugger.onDetach.addListener(this.onDetach.bind(this)); - chrome.debugger.onEvent.addListener((_source, method, params) => { - if (this.messageCallback) { - const msg: rpc.JsonRpc2.Notification = {method, params}; - this.messageCallback(JSON.stringify(msg)); - } - }); - } - - send(message: string): void { - if (this.target === undefined) return; - - const msg: rpc.JsonRpc2.Request = JSON.parse(message); - chrome.debugger.sendCommand( - this.target, msg.method, msg.params, (result) => { - if (result === undefined) result = {}; - const response: rpc.JsonRpc2.Response = {id: msg.id, result}; - this.messageCallback(JSON.stringify(response)); - }); - } - - // This method will be called once for each event soon after the creation of - // this object. To understand better what happens, checking the implementation - // of noice-json-rpc is very useful. - // While the events "message" and "open" are for implementing the LikeSocket, - // "close" is a callback set from ChromeTracingController, to reset the state - // after a detach. - on(event: string, cb: Function) { - if (event === 'message') { - this.messageCallback = cb; - } else if (event === 'open') { - this.openCallback = cb; - } else if (event === 'close') { - this.closeCallback = cb; - } - } - - removeListener(_event: string, _cb: Function) { - throw new Error('Call unexpected'); - } - - attachToBrowser(then: (error?: string) => void) { - this.attachToTarget({targetId: 'browser'}, then); - } - - private attachToTarget( - target: chrome.debugger.Debuggee, then: (error?: string) => void) { - chrome.debugger.attach(target, /* requiredVersion=*/ '1.3', () => { - if (chrome.runtime.lastError) { - then(chrome.runtime.lastError.message); - return; - } - this.target = target; - this.openCallback(); - then(); - }); - } - - detach() { - if (this.target === undefined) return; - - chrome.debugger.detach(this.target, () => { - this.target = undefined; - }); - } - - onDetach(_source: chrome.debugger.Debuggee, _reason: string) { - if (_source === this.target) { - this.target = undefined; - this.closeCallback(); - } - } - - isAttached(): boolean { - return this.target !== undefined; - } -} diff --git a/third_party/perfetto/ui/src/chrome_extension/index.ts b/third_party/perfetto/ui/src/chrome_extension/index.ts deleted file mode 100644 index e0e97d214611..000000000000 --- a/third_party/perfetto/ui/src/chrome_extension/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {binaryDecode} from '../base/string_utils'; -import {ChromeTracingController} from './chrome_tracing_controller'; - -let chromeTraceController: ChromeTracingController|undefined = undefined; - -enableOnlyOnPerfettoHost(); - -// Listen for messages from the perfetto ui. -if (window.chrome) { - chrome.runtime.onConnectExternal.addListener((port) => { - chromeTraceController = new ChromeTracingController(port); - port.onMessage.addListener(onUIMessage); - }); -} - -function onUIMessage( - message: {method: string, requestData: string}, port: chrome.runtime.Port) { - if (message.method === 'ExtensionVersion') { - port.postMessage({version: chrome.runtime.getManifest().version}); - return; - } - console.assert(chromeTraceController !== undefined); - if (!chromeTraceController) return; - // ChromeExtensionConsumerPort sends the request data as string because - // chrome.runtime.port doesn't support ArrayBuffers. - const requestDataArray: Uint8Array = message.requestData ? - binaryDecode(message.requestData) : - new Uint8Array(); - chromeTraceController.handleCommand(message.method, requestDataArray); -} - -function enableOnlyOnPerfettoHost() { - function enableOnHostWithSuffix(suffix: string) { - return { - conditions: [new chrome.declarativeContent.PageStateMatcher({ - pageUrl: {hostSuffix: suffix}, - })], - actions: [new chrome.declarativeContent.ShowPageAction()], - }; - } - chrome.declarativeContent.onPageChanged.removeRules(undefined, () => { - chrome.declarativeContent.onPageChanged.addRules([ - enableOnHostWithSuffix('localhost'), - enableOnHostWithSuffix('127.0.0.1'), - enableOnHostWithSuffix('.perfetto.dev'), - enableOnHostWithSuffix('.storage.googleapis.com'), - ]); - }); -} diff --git a/third_party/perfetto/ui/src/chrome_extension/manifest.json b/third_party/perfetto/ui/src/chrome_extension/manifest.json deleted file mode 100644 index 19edc67936fa..000000000000 --- a/third_party/perfetto/ui/src/chrome_extension/manifest.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "Perfetto UI", - "key":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhm3X7qutsrskke84ltokTObnFJakd/d0XFQ6Ox2wQueHTGJM5GUNPTY/x8bdreNtGnfzvt/Sd0vABbR0wsS6lz5yY+g6ksMXJnigFe9N7uz8E3KojDrl3xYjIe+mkiJo8yxxzPydgb7GjQ6jmsX3g+yjj67kXzm9rZFkmoZ5WmqwBZlguPYVRN/W8CIIqBZkC3Qmq6uSG7b/g93YbwqmTmGiL2sAzgvXtqvDOD6503abtQkRC795E4VjJd+ffyeRH38fAEz5ZIrA6GJsfmov1TZTIu1NTwqylSpBYl5as7C6gpmuxDV4SvHvGT2hMQuIufDhZhErjI3B7bcX+XLe1wIDAQAB", - "description": "Enables the Perfetto trace viewer (https://ui.perfetto.dev) to record Chrome browser traces.", - "version": "0.0.0.15", - "manifest_version": 2, - "minimum_chrome_version": "81.0.4022.0", - "permissions": [ - "declarativeContent", - "debugger" - ], - "icons": { - "128": "logo-128.png" - }, - "background": { - "scripts": [ - "chrome_extension_bundle.js" - ], - "persistent": false - }, - "externally_connectable": { - "matches": [ - "*://localhost/*", - "*://127.0.0.1/*", - "https://*.perfetto.dev/*", - "https://storage.googleapis.com/*" - ] - } -} \ No newline at end of file diff --git a/third_party/perfetto/ui/src/common/actions.ts b/third_party/perfetto/ui/src/common/actions.ts deleted file mode 100644 index 1e602fcd7bf6..000000000000 --- a/third_party/perfetto/ui/src/common/actions.ts +++ /dev/null @@ -1,1229 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Draft} from 'immer'; - -import {assertExists, assertTrue, assertUnreachable} from '../base/logging'; -import {RecordConfig} from '../controller/record_config_types'; -import {globals} from '../frontend/globals'; -import { - Aggregation, - AggregationFunction, - TableColumn, - tableColumnEquals, - toggleEnabled, -} from '../frontend/pivot_table_types'; -import {DebugTrackV2Config} from '../tracks/debug/slice_track'; - -import {randomColor} from './colorizer'; -import { - computeIntervals, - DropDirection, - performReordering, -} from './dragndrop_logic'; -import {createEmptyState} from './empty_state'; -import {DEFAULT_VIEWING_OPTION, PERF_SAMPLES_KEY} from './flamegraph_util'; -import {traceEventBegin, traceEventEnd, TraceEventScope} from './metatracing'; -import { - AdbRecordingTarget, - Area, - CallsiteInfo, - EngineMode, - FlamegraphStateViewingOption, - FtraceFilterPatch, - LoadedConfig, - NewEngineMode, - OmniboxState, - Pagination, - PivotTableResult, - PrimaryTrackSortKey, - ProfileType, - RecordingTarget, - SCROLLING_TRACK_GROUP, - SortDirection, - State, - Status, - ThreadTrackSortKey, - TraceTime, - TrackSortKey, - TrackState, - UtidToTrackSortKey, - VisibleState, -} from './state'; -import {toNs} from './time'; - -export const DEBUG_SLICE_TRACK_KIND = 'DebugSliceTrack'; - -type StateDraft = Draft; - -export interface AddTrackArgs { - id?: string; - engineId: string; - kind: string; - name: string; - labels?: string[]; - trackSortKey: TrackSortKey; - trackGroup?: string; - config: {}; -} - -export interface PostedTrace { - buffer: ArrayBuffer; - title: string; - fileName?: string; - url?: string; - uuid?: string; - localOnly?: boolean; - keepApiOpen?: boolean; -} - -export interface PostedScrollToRange { - timeStart: number; - timeEnd: number; - viewPercentage?: number; -} - -function clearTraceState(state: StateDraft) { - const nextId = state.nextId; - const recordConfig = state.recordConfig; - const recordingTarget = state.recordingTarget; - const fetchChromeCategories = state.fetchChromeCategories; - const extensionInstalled = state.extensionInstalled; - const availableAdbDevices = state.availableAdbDevices; - const chromeCategories = state.chromeCategories; - const newEngineMode = state.newEngineMode; - - Object.assign(state, createEmptyState()); - state.nextId = nextId; - state.recordConfig = recordConfig; - state.recordingTarget = recordingTarget; - state.fetchChromeCategories = fetchChromeCategories; - state.extensionInstalled = extensionInstalled; - state.availableAdbDevices = availableAdbDevices; - state.chromeCategories = chromeCategories; - state.newEngineMode = newEngineMode; -} - -function generateNextId(draft: StateDraft): string { - const nextId = String(Number(draft.nextId) + 1); - draft.nextId = nextId; - return nextId; -} - -// A helper to clean the state for a given removeable track. -// This is not exported as action to make it clear that not all -// tracks are removeable. -function removeTrack(state: StateDraft, trackId: string) { - const track = state.tracks[trackId]; - delete state.tracks[trackId]; - - const removeTrackId = (arr: string[]) => { - const index = arr.indexOf(trackId); - if (index !== -1) arr.splice(index, 1); - }; - - if (track.trackGroup === SCROLLING_TRACK_GROUP) { - removeTrackId(state.scrollingTracks); - } else if (track.trackGroup !== undefined) { - removeTrackId(state.trackGroups[track.trackGroup].tracks); - } - state.pinnedTracks = state.pinnedTracks.filter((id) => id !== trackId); -} - -let statusTraceEvent: TraceEventScope|undefined; - -export const StateActions = { - - openTraceFromFile(state: StateDraft, args: {file: File}): void { - clearTraceState(state); - const id = generateNextId(state); - state.engine = { - id, - ready: false, - source: {type: 'FILE', file: args.file}, - }; - }, - - openTraceFromBuffer(state: StateDraft, args: PostedTrace): void { - clearTraceState(state); - const id = generateNextId(state); - state.engine = { - id, - ready: false, - source: {type: 'ARRAY_BUFFER', ...args}, - }; - }, - - openTraceFromUrl(state: StateDraft, args: {url: string}): void { - clearTraceState(state); - const id = generateNextId(state); - state.engine = { - id, - ready: false, - source: {type: 'URL', url: args.url}, - }; - }, - - openTraceFromHttpRpc(state: StateDraft, _args: {}): void { - clearTraceState(state); - const id = generateNextId(state); - state.engine = { - id, - ready: false, - source: {type: 'HTTP_RPC'}, - }; - }, - - setTraceUuid(state: StateDraft, args: {traceUuid: string}) { - state.traceUuid = args.traceUuid; - }, - - fillUiTrackIdByTraceTrackId( - state: StateDraft, trackState: TrackState, uiTrackId: string) { - const namespace = (trackState.config as {namespace?: string}).namespace; - if (namespace !== undefined) return; - - const setUiTrackId = (trackId: number, uiTrackId: string) => { - if (state.uiTrackIdByTraceTrackId[trackId] !== undefined && - state.uiTrackIdByTraceTrackId[trackId] !== uiTrackId) { - throw new Error(`Trying to map track id ${trackId} to UI track ${ - uiTrackId}, already mapped to ${ - state.uiTrackIdByTraceTrackId[trackId]}`); - } - state.uiTrackIdByTraceTrackId[trackId] = uiTrackId; - }; - - const config = trackState.config as {trackId: number}; - if (config.trackId !== undefined) { - setUiTrackId(config.trackId, uiTrackId); - return; - } - - const multiple = trackState.config as {trackIds: number[]}; - if (multiple.trackIds !== undefined) { - for (const trackId of multiple.trackIds) { - setUiTrackId(trackId, uiTrackId); - } - } - }, - - addTracks(state: StateDraft, args: {tracks: AddTrackArgs[]}) { - args.tracks.forEach((track) => { - const id = track.id === undefined ? generateNextId(state) : track.id; - track.id = id; - state.tracks[id] = track as TrackState; - this.fillUiTrackIdByTraceTrackId(state, track as TrackState, id); - if (track.trackGroup === SCROLLING_TRACK_GROUP) { - state.scrollingTracks.push(id); - } else if (track.trackGroup !== undefined) { - assertExists(state.trackGroups[track.trackGroup]).tracks.push(id); - } - }); - }, - - setUtidToTrackSortKey( - state: StateDraft, args: {threadOrderingMetadata: UtidToTrackSortKey}) { - state.utidToThreadSortKey = args.threadOrderingMetadata; - }, - - addTrack(state: StateDraft, args: { - id?: string; engineId: string; kind: string; name: string; - trackGroup?: string; config: {}; trackSortKey: TrackSortKey; - }): void { - const id = args.id !== undefined ? args.id : generateNextId(state); - state.tracks[id] = { - id, - engineId: args.engineId, - kind: args.kind, - name: args.name, - trackSortKey: args.trackSortKey, - trackGroup: args.trackGroup, - config: args.config, - }; - this.fillUiTrackIdByTraceTrackId(state, state.tracks[id], id); - if (args.trackGroup === SCROLLING_TRACK_GROUP) { - state.scrollingTracks.push(id); - } else if (args.trackGroup !== undefined) { - assertExists(state.trackGroups[args.trackGroup]).tracks.push(id); - } - }, - - addTrackGroup( - state: StateDraft, - // Define ID in action so a track group can be referred to without running - // the reducer. - args: { - engineId: string; name: string; id: string; summaryTrackId: string; - collapsed: boolean; - }): void { - state.trackGroups[args.id] = { - engineId: args.engineId, - name: args.name, - id: args.id, - collapsed: args.collapsed, - tracks: [args.summaryTrackId], - }; - }, - - addDebugTrack( - state: StateDraft, - args: {engineId: string, name: string, config: DebugTrackV2Config}): - void { - if (state.debugTrackId !== undefined) return; - const trackId = generateNextId(state); - this.addTrack(state, { - id: trackId, - engineId: args.engineId, - kind: DEBUG_SLICE_TRACK_KIND, - name: args.name, - trackSortKey: PrimaryTrackSortKey.DEBUG_SLICE_TRACK, - trackGroup: SCROLLING_TRACK_GROUP, - config: args.config, - }); - this.toggleTrackPinned(state, {trackId}); - }, - - removeDebugTrack(state: StateDraft, args: {trackId: string}): void { - const track = state.tracks[args.trackId]; - assertTrue(track.kind === DEBUG_SLICE_TRACK_KIND); - removeTrack(state, args.trackId); - }, - - removeVisualisedArgTracks(state: StateDraft, args: {trackIds: string[]}) { - for (const trackId of args.trackIds) { - const track = state.tracks[trackId]; - - const namespace = (track.config as {namespace?: string}).namespace; - if (namespace === undefined) { - throw new Error( - 'All visualised arg tracks should have non-empty namespace'); - } - - removeTrack(state, trackId); - } - }, - - maybeExpandOnlyTrackGroup(state: StateDraft, _: {}): void { - const trackGroups = Object.values(state.trackGroups); - if (trackGroups.length === 1) { - trackGroups[0].collapsed = false; - } - }, - - sortThreadTracks(state: StateDraft, _: {}) { - const getFullKey = (a: string) => { - const track = state.tracks[a]; - const threadTrackSortKey = track.trackSortKey as ThreadTrackSortKey; - if (threadTrackSortKey.utid === undefined) { - const sortKey = track.trackSortKey as PrimaryTrackSortKey; - return [ - sortKey, - 0, - 0, - 0, - ]; - } - const threadSortKey = state.utidToThreadSortKey[threadTrackSortKey.utid]; - return [ - threadSortKey ? threadSortKey.sortKey : - PrimaryTrackSortKey.ORDINARY_THREAD, - threadSortKey && threadSortKey.tid !== undefined ? threadSortKey.tid : - Number.MAX_VALUE, - threadTrackSortKey.utid, - threadTrackSortKey.priority, - ]; - }; - - // Use a numeric collator so threads are sorted as T1, T2, ..., T10, T11, - // rather than T1, T10, T11, ..., T2, T20, T21 . - const coll = new Intl.Collator([], {sensitivity: 'base', numeric: true}); - for (const group of Object.values(state.trackGroups)) { - group.tracks.sort((a: string, b: string) => { - const aRank = getFullKey(a); - const bRank = getFullKey(b); - for (let i = 0; i < aRank.length; i++) { - if (aRank[i] !== bRank[i]) return aRank[i] - bRank[i]; - } - - const aName = state.tracks[a].name.toLocaleLowerCase(); - const bName = state.tracks[b].name.toLocaleLowerCase(); - return coll.compare(aName, bName); - }); - } - }, - - updateAggregateSorting( - state: StateDraft, args: {id: string, column: string}) { - let prefs = state.aggregatePreferences[args.id]; - if (!prefs) { - prefs = {id: args.id}; - state.aggregatePreferences[args.id] = prefs; - } - - if (!prefs.sorting || prefs.sorting.column !== args.column) { - // No sorting set for current column. - state.aggregatePreferences[args.id].sorting = { - column: args.column, - direction: 'DESC', - }; - } else if (prefs.sorting.direction === 'DESC') { - // Toggle the direction if the column is currently sorted. - state.aggregatePreferences[args.id].sorting = { - column: args.column, - direction: 'ASC', - }; - } else { - // If direction is currently 'ASC' toggle to no sorting. - state.aggregatePreferences[args.id].sorting = undefined; - } - }, - - setVisibleTracks(state: StateDraft, args: {tracks: string[]}) { - state.visibleTracks = args.tracks; - }, - - updateTrackConfig(state: StateDraft, args: {id: string, config: {}}) { - if (state.tracks[args.id] === undefined) return; - state.tracks[args.id].config = args.config; - }, - - moveTrack( - state: StateDraft, - args: {srcId: string; op: 'before' | 'after', dstId: string}): void { - const moveWithinTrackList = (trackList: string[]) => { - const newList: string[] = []; - for (let i = 0; i < trackList.length; i++) { - const curTrackId = trackList[i]; - if (curTrackId === args.dstId && args.op === 'before') { - newList.push(args.srcId); - } - if (curTrackId !== args.srcId) { - newList.push(curTrackId); - } - if (curTrackId === args.dstId && args.op === 'after') { - newList.push(args.srcId); - } - } - trackList.splice(0); - newList.forEach((x) => { - trackList.push(x); - }); - }; - - moveWithinTrackList(state.pinnedTracks); - moveWithinTrackList(state.scrollingTracks); - }, - - toggleTrackPinned(state: StateDraft, args: {trackId: string}): void { - const id = args.trackId; - const isPinned = state.pinnedTracks.includes(id); - const trackGroup = assertExists(state.tracks[id]).trackGroup; - - if (isPinned) { - state.pinnedTracks.splice(state.pinnedTracks.indexOf(id), 1); - if (trackGroup === SCROLLING_TRACK_GROUP) { - state.scrollingTracks.unshift(id); - } - } else { - if (trackGroup === SCROLLING_TRACK_GROUP) { - state.scrollingTracks.splice(state.scrollingTracks.indexOf(id), 1); - } - state.pinnedTracks.push(id); - } - }, - - toggleTrackGroupCollapsed(state: StateDraft, args: {trackGroupId: string}): - void { - const id = args.trackGroupId; - const trackGroup = assertExists(state.trackGroups[id]); - trackGroup.collapsed = !trackGroup.collapsed; - }, - - requestTrackReload(state: StateDraft, _: {}) { - if (state.lastTrackReloadRequest) { - state.lastTrackReloadRequest++; - } else { - state.lastTrackReloadRequest = 1; - } - }, - - // TODO(hjd): engine.ready should be a published thing. If it's part - // of the state it interacts badly with permalinks. - setEngineReady( - state: StateDraft, - args: {engineId: string; ready: boolean, mode: EngineMode}): void { - const engine = state.engine; - if (engine === undefined || engine.id !== args.engineId) { - return; - } - engine.ready = args.ready; - engine.mode = args.mode; - }, - - setNewEngineMode(state: StateDraft, args: {mode: NewEngineMode}): void { - state.newEngineMode = args.mode; - }, - - // Marks all engines matching the given |mode| as failed. - setEngineFailed(state: StateDraft, args: {mode: EngineMode; failure: string}): - void { - if (state.engine !== undefined && state.engine.mode === args.mode) { - state.engine.failed = args.failure; - } - }, - - createPermalink(state: StateDraft, args: {isRecordingConfig: boolean}): void { - state.permalink = { - requestId: generateNextId(state), - hash: undefined, - isRecordingConfig: args.isRecordingConfig, - }; - }, - - setPermalink(state: StateDraft, args: {requestId: string; hash: string}): - void { - // Drop any links for old requests. - if (state.permalink.requestId !== args.requestId) return; - state.permalink = args; - }, - - loadPermalink(state: StateDraft, args: {hash: string}): void { - state.permalink = {requestId: generateNextId(state), hash: args.hash}; - }, - - clearPermalink(state: StateDraft, _: {}): void { - state.permalink = {}; - }, - - setTraceTime(state: StateDraft, args: TraceTime): void { - state.traceTime = args; - }, - - updateStatus(state: StateDraft, args: Status): void { - if (statusTraceEvent) { - traceEventEnd(statusTraceEvent); - } - statusTraceEvent = traceEventBegin(args.msg); - state.status = args; - }, - - // TODO(hjd): Remove setState - it causes problems due to reuse of ids. - setState(state: StateDraft, args: {newState: State}): void { - for (const key of Object.keys(state)) { - delete (state as any)[key]; - } - for (const key of Object.keys(args.newState)) { - (state as any)[key] = (args.newState as any)[key]; - } - - // If we're loading from a permalink then none of the engines can - // possibly be ready: - if (state.engine !== undefined) { - state.engine.ready = false; - } - }, - - setRecordConfig( - state: StateDraft, - args: {config: RecordConfig, configType?: LoadedConfig}): void { - state.recordConfig = args.config; - state.lastLoadedConfig = args.configType || {type: 'NONE'}; - }, - - selectNote(state: StateDraft, args: {id: string}): void { - if (args.id) { - state.currentSelection = { - kind: 'NOTE', - id: args.id, - }; - } - }, - - addAutomaticNote( - state: StateDraft, - args: {timestamp: number, color: string, text: string}): void { - const id = generateNextId(state); - state.notes[id] = { - noteType: 'DEFAULT', - id, - timestamp: args.timestamp, - color: args.color, - text: args.text, - }; - }, - - addNote(state: StateDraft, args: {timestamp: number, color: string}): void { - const id = generateNextId(state); - state.notes[id] = { - noteType: 'DEFAULT', - id, - timestamp: args.timestamp, - color: args.color, - text: '', - }; - this.selectNote(state, {id}); - }, - - markCurrentArea( - state: StateDraft, args: {color: string, persistent: boolean}): - void { - if (state.currentSelection === null || - state.currentSelection.kind !== 'AREA') { - return; - } - const id = args.persistent ? generateNextId(state) : '0'; - const color = args.persistent ? args.color : '#344596'; - state.notes[id] = { - noteType: 'AREA', - id, - areaId: state.currentSelection.areaId, - color, - text: '', - }; - state.currentSelection.noteId = id; - }, - - toggleMarkCurrentArea(state: StateDraft, args: {persistent: boolean}) { - const selection = state.currentSelection; - if (selection != null && selection.kind === 'AREA' && - selection.noteId !== undefined) { - this.removeNote(state, {id: selection.noteId}); - } else { - const color = randomColor(); - this.markCurrentArea(state, {color, persistent: args.persistent}); - } - }, - - markArea(state: StateDraft, args: {area: Area, persistent: boolean}): void { - const areaId = generateNextId(state); - assertTrue(args.area.endSec >= args.area.startSec); - state.areas[areaId] = { - id: areaId, - startSec: args.area.startSec, - endSec: args.area.endSec, - tracks: args.area.tracks, - }; - const noteId = args.persistent ? generateNextId(state) : '0'; - const color = args.persistent ? randomColor() : '#344596'; - state.notes[noteId] = { - noteType: 'AREA', - id: noteId, - areaId, - color, - text: '', - }; - }, - - changeNoteColor(state: StateDraft, args: {id: string, newColor: string}): - void { - const note = state.notes[args.id]; - if (note === undefined) return; - note.color = args.newColor; - }, - - changeNoteText(state: StateDraft, args: {id: string, newText: string}): void { - const note = state.notes[args.id]; - if (note === undefined) return; - note.text = args.newText; - }, - - removeNote(state: StateDraft, args: {id: string}): void { - if (state.notes[args.id] === undefined) return; - delete state.notes[args.id]; - // For regular notes, we clear the current selection but for an area note - // we only want to clear the note/marking and leave the area selected. - if (state.currentSelection === null) return; - if (state.currentSelection.kind === 'NOTE' && - state.currentSelection.id === args.id) { - state.currentSelection = null; - } else if ( - state.currentSelection.kind === 'AREA' && - state.currentSelection.noteId === args.id) { - state.currentSelection.noteId = undefined; - } - }, - - selectSlice( - state: StateDraft, - args: {id: number, trackId: string, scroll?: boolean}): void { - state.currentSelection = { - kind: 'SLICE', - id: args.id, - trackId: args.trackId, - }; - state.pendingScrollId = args.scroll ? args.id : undefined; - }, - - selectCounter( - state: StateDraft, - args: {leftTs: number, rightTs: number, id: number, trackId: string}): - void { - state.currentSelection = { - kind: 'COUNTER', - leftTs: args.leftTs, - rightTs: args.rightTs, - id: args.id, - trackId: args.trackId, - }; - }, - - selectHeapProfile( - state: StateDraft, - args: {id: number, upid: number, ts: number, type: ProfileType}): void { - state.currentSelection = { - kind: 'HEAP_PROFILE', - id: args.id, - upid: args.upid, - ts: args.ts, - type: args.type, - }; - this.openFlamegraph(state, { - type: args.type, - startNs: toNs(state.traceTime.startSec), - endNs: args.ts, - upids: [args.upid], - viewingOption: DEFAULT_VIEWING_OPTION, - }); - }, - - selectPerfSamples(state: StateDraft, args: { - id: number, - upid: number, - leftTs: number, - rightTs: number, - type: ProfileType - }): void { - state.currentSelection = { - kind: 'PERF_SAMPLES', - id: args.id, - upid: args.upid, - leftTs: args.leftTs, - rightTs: args.rightTs, - type: args.type, - }; - this.openFlamegraph(state, { - type: args.type, - startNs: args.leftTs, - endNs: args.rightTs, - upids: [args.upid], - viewingOption: PERF_SAMPLES_KEY, - }); - }, - - openFlamegraph(state: StateDraft, args: { - upids: number[], - startNs: number, - endNs: number, - type: ProfileType, - viewingOption: FlamegraphStateViewingOption - }): void { - state.currentFlamegraphState = { - kind: 'FLAMEGRAPH_STATE', - upids: args.upids, - startNs: args.startNs, - endNs: args.endNs, - type: args.type, - viewingOption: args.viewingOption, - focusRegex: '', - }; - }, - - selectCpuProfileSample( - state: StateDraft, args: {id: number, utid: number, ts: number}): void { - state.currentSelection = { - kind: 'CPU_PROFILE_SAMPLE', - id: args.id, - utid: args.utid, - ts: args.ts, - }; - }, - - expandFlamegraphState( - state: StateDraft, args: {expandedCallsite?: CallsiteInfo}): void { - if (state.currentFlamegraphState === null) return; - state.currentFlamegraphState.expandedCallsite = args.expandedCallsite; - }, - - changeViewFlamegraphState( - state: StateDraft, args: {viewingOption: FlamegraphStateViewingOption}): - void { - if (state.currentFlamegraphState === null) return; - state.currentFlamegraphState.viewingOption = args.viewingOption; - }, - - changeFocusFlamegraphState(state: StateDraft, args: {focusRegex: string}): - void { - if (state.currentFlamegraphState === null) return; - state.currentFlamegraphState.focusRegex = args.focusRegex; - }, - - selectChromeSlice( - state: StateDraft, - args: {id: number, trackId: string, table: string, scroll?: boolean}): - void { - state.currentSelection = { - kind: 'CHROME_SLICE', - id: args.id, - trackId: args.trackId, - table: args.table, - }; - state.pendingScrollId = args.scroll ? args.id : undefined; - }, - - selectDebugSlice(state: StateDraft, args: { - id: number, - sqlTableName: string, - startS: number, - durationS: number, - trackId: string, - }): void { - state.currentSelection = { - kind: 'DEBUG_SLICE', - id: args.id, - sqlTableName: args.sqlTableName, - startS: args.startS, - durationS: args.durationS, - trackId: args.trackId, - }; - }, - - clearPendingScrollId(state: StateDraft, _: {}): void { - state.pendingScrollId = undefined; - }, - - selectThreadState(state: StateDraft, args: {id: number, trackId: string}): - void { - state.currentSelection = { - kind: 'THREAD_STATE', - id: args.id, - trackId: args.trackId, - }; - }, - - selectLog( - state: StateDraft, args: {id: number, trackId: string, scroll?: boolean}): - void { - state.currentSelection = { - kind: 'LOG', - id: args.id, - trackId: args.trackId, - }; - state.pendingScrollId = args.scroll ? args.id : undefined; - }, - - deselect(state: StateDraft, _: {}): void { - state.currentSelection = null; - }, - - updateLogsPagination(state: StateDraft, args: Pagination): void { - state.logsPagination = args; - }, - - updateFtracePagination(state: StateDraft, args: Pagination): void { - state.ftracePagination = args; - }, - - updateFtraceFilter(state: StateDraft, patch: FtraceFilterPatch) { - const {excludedNames: diffs} = patch; - const excludedNames = state.ftraceFilter.excludedNames; - for (const [addRemove, name] of diffs) { - switch (addRemove) { - case 'add': - if (!excludedNames.some((excluded: string) => excluded === name)) { - excludedNames.push(name); - } - break; - case 'remove': - state.ftraceFilter.excludedNames = - state.ftraceFilter.excludedNames.filter( - (excluded: string) => excluded !== name); - break; - default: - assertUnreachable(addRemove); - break; - } - } - }, - - startRecording(state: StateDraft, _: {}): void { - state.recordingInProgress = true; - state.lastRecordingError = undefined; - state.recordingCancelled = false; - }, - - stopRecording(state: StateDraft, _: {}): void { - state.recordingInProgress = false; - }, - - cancelRecording(state: StateDraft, _: {}): void { - state.recordingInProgress = false; - state.recordingCancelled = true; - }, - - setExtensionAvailable(state: StateDraft, args: {available: boolean}): void { - state.extensionInstalled = args.available; - }, - - setRecordingTarget(state: StateDraft, args: {target: RecordingTarget}): void { - state.recordingTarget = args.target; - }, - - setFetchChromeCategories(state: StateDraft, args: {fetch: boolean}): void { - state.fetchChromeCategories = args.fetch; - }, - - setAvailableAdbDevices( - state: StateDraft, args: {devices: AdbRecordingTarget[]}): void { - state.availableAdbDevices = args.devices; - }, - - setOmnibox(state: StateDraft, args: OmniboxState): void { - state.omniboxState = args; - }, - - selectArea(state: StateDraft, args: {area: Area}): void { - const areaId = generateNextId(state); - assertTrue(args.area.endSec >= args.area.startSec); - state.areas[areaId] = { - id: areaId, - startSec: args.area.startSec, - endSec: args.area.endSec, - tracks: args.area.tracks, - }; - state.currentSelection = {kind: 'AREA', areaId}; - }, - - editArea(state: StateDraft, args: {area: Area, areaId: string}): void { - assertTrue(args.area.endSec >= args.area.startSec); - state.areas[args.areaId] = { - id: args.areaId, - startSec: args.area.startSec, - endSec: args.area.endSec, - tracks: args.area.tracks, - }; - }, - - reSelectArea(state: StateDraft, args: {areaId: string, noteId: string}): - void { - state.currentSelection = { - kind: 'AREA', - areaId: args.areaId, - noteId: args.noteId, - }; - }, - - toggleTrackSelection( - state: StateDraft, args: {id: string, isTrackGroup: boolean}) { - const selection = state.currentSelection; - if (selection === null || selection.kind !== 'AREA') return; - const areaId = selection.areaId; - const index = state.areas[areaId].tracks.indexOf(args.id); - if (index > -1) { - state.areas[areaId].tracks.splice(index, 1); - if (args.isTrackGroup) { // Also remove all child tracks. - for (const childTrack of state.trackGroups[args.id].tracks) { - const childIndex = state.areas[areaId].tracks.indexOf(childTrack); - if (childIndex > -1) { - state.areas[areaId].tracks.splice(childIndex, 1); - } - } - } - } else { - state.areas[areaId].tracks.push(args.id); - if (args.isTrackGroup) { // Also add all child tracks. - for (const childTrack of state.trackGroups[args.id].tracks) { - if (!state.areas[areaId].tracks.includes(childTrack)) { - state.areas[areaId].tracks.push(childTrack); - } - } - } - } - // It's super unexpected that |toggleTrackSelection| does not cause - // selection to be updated and this leads to bugs for people who do: - // if (oldSelection !== state.selection) etc. - // To solve this re-create the selection object here: - state.currentSelection = Object.assign({}, state.currentSelection); - }, - - setVisibleTraceTime(state: StateDraft, args: VisibleState): void { - state.frontendLocalState.visibleState = {...args}; - }, - - setChromeCategories(state: StateDraft, args: {categories: string[]}): void { - state.chromeCategories = args.categories; - }, - - setLastRecordingError(state: StateDraft, args: {error?: string}): void { - state.lastRecordingError = args.error; - state.recordingStatus = undefined; - }, - - setRecordingStatus(state: StateDraft, args: {status?: string}): void { - state.recordingStatus = args.status; - state.lastRecordingError = undefined; - }, - - requestSelectedMetric(state: StateDraft, _: {}): void { - if (!state.metrics.availableMetrics) throw Error('No metrics available'); - if (state.metrics.selectedIndex === undefined) { - throw Error('No metric selected'); - } - state.metrics.requestedMetric = - state.metrics.availableMetrics[state.metrics.selectedIndex]; - }, - - resetMetricRequest(state: StateDraft, args: {name: string}): void { - if (state.metrics.requestedMetric !== args.name) return; - state.metrics.requestedMetric = undefined; - }, - - setAvailableMetrics(state: StateDraft, args: {availableMetrics: string[]}): - void { - state.metrics.availableMetrics = args.availableMetrics; - if (args.availableMetrics.length > 0) state.metrics.selectedIndex = 0; - }, - - setMetricSelectedIndex(state: StateDraft, args: {index: number}): void { - if (!state.metrics.availableMetrics || - args.index >= state.metrics.availableMetrics.length) { - throw Error('metric selection out of bounds'); - } - state.metrics.selectedIndex = args.index; - }, - - togglePerfDebug(state: StateDraft, _: {}): void { - state.perfDebug = !state.perfDebug; - }, - - toggleSidebar(state: StateDraft, _: {}): void { - state.sidebarVisible = !state.sidebarVisible; - }, - - setHoveredUtidAndPid(state: StateDraft, args: {utid: number, pid: number}) { - state.hoveredPid = args.pid; - state.hoveredUtid = args.utid; - }, - - setHighlightedSliceId(state: StateDraft, args: {sliceId: number}) { - state.highlightedSliceId = args.sliceId; - }, - - setHighlightedFlowLeftId(state: StateDraft, args: {flowId: number}) { - state.focusedFlowIdLeft = args.flowId; - }, - - setHighlightedFlowRightId(state: StateDraft, args: {flowId: number}) { - state.focusedFlowIdRight = args.flowId; - }, - - setSearchIndex(state: StateDraft, args: {index: number}) { - state.searchIndex = args.index; - }, - - setHoverCursorTimestamp(state: StateDraft, args: {ts: number}) { - state.hoverCursorTimestamp = args.ts; - }, - - setHoveredNoteTimestamp(state: StateDraft, args: {ts: number}) { - state.hoveredNoteTimestamp = args.ts; - }, - - setCurrentTab(state: StateDraft, args: {tab: string|undefined}) { - state.currentTab = args.tab; - }, - - toggleAllTrackGroups(state: StateDraft, args: {collapsed: boolean}) { - for (const group of Object.values(state.trackGroups)) { - group.collapsed = args.collapsed; - } - }, - - clearAllPinnedTracks(state: StateDraft, _: {}) { - if (state.pinnedTracks.length > 0) { - // Clear pinnedTracks array - state.pinnedTracks.length = 0; - } - }, - - togglePivotTable(state: StateDraft, args: {areaId: string|null}) { - state.nonSerializableState.pivotTable.selectionArea = args.areaId === null ? - undefined : - {areaId: args.areaId, tracks: globals.state.areas[args.areaId].tracks}; - if (args.areaId !== - state.nonSerializableState.pivotTable.selectionArea?.areaId) { - state.nonSerializableState.pivotTable.queryResult = null; - } - }, - - setPivotStateQueryResult( - state: StateDraft, args: {queryResult: PivotTableResult|null}) { - state.nonSerializableState.pivotTable.queryResult = args.queryResult; - }, - - setPivotTableConstrainToArea(state: StateDraft, args: {constrain: boolean}) { - state.nonSerializableState.pivotTable.constrainToArea = args.constrain; - }, - - dismissFlamegraphModal(state: StateDraft, _: {}) { - state.flamegraphModalDismissed = true; - }, - - addPivotTableAggregation( - state: StateDraft, args: {aggregation: Aggregation, after: number}) { - state.nonSerializableState.pivotTable.selectedAggregations.splice( - args.after, 0, args.aggregation); - }, - - removePivotTableAggregation(state: StateDraft, args: {index: number}) { - state.nonSerializableState.pivotTable.selectedAggregations.splice( - args.index, 1); - }, - - setPivotTableQueryRequested( - state: StateDraft, args: {queryRequested: boolean}) { - state.nonSerializableState.pivotTable.queryRequested = args.queryRequested; - }, - - setPivotTablePivotSelected( - state: StateDraft, args: {column: TableColumn, selected: boolean}) { - toggleEnabled( - tableColumnEquals, - state.nonSerializableState.pivotTable.selectedPivots, - args.column, - args.selected); - }, - - setPivotTableAggregationFunction( - state: StateDraft, args: {index: number, function: AggregationFunction}) { - state.nonSerializableState.pivotTable.selectedAggregations[args.index] - .aggregationFunction = args.function; - }, - - setPivotTableSortColumn( - state: StateDraft, - args: {aggregationIndex: number, order: SortDirection}) { - state.nonSerializableState.pivotTable.selectedAggregations = - state.nonSerializableState.pivotTable.selectedAggregations.map( - (agg, index) => ({ - column: agg.column, - aggregationFunction: agg.aggregationFunction, - sortDirection: (index === args.aggregationIndex) ? args.order : - undefined, - })); - }, - - addVisualisedArg(state: StateDraft, args: {argName: string}) { - if (!state.visualisedArgs.includes(args.argName)) { - state.visualisedArgs.push(args.argName); - } - }, - - removeVisualisedArg(state: StateDraft, args: {argName: string}) { - state.visualisedArgs = - state.visualisedArgs.filter((val) => val !== args.argName); - }, - - setPivotTableArgumentNames( - state: StateDraft, args: {argumentNames: string[]}) { - state.nonSerializableState.pivotTable.argumentNames = args.argumentNames; - }, - - changePivotTablePivotOrder( - state: StateDraft, - args: {from: number, to: number, direction: DropDirection}) { - const pivots = state.nonSerializableState.pivotTable.selectedPivots; - state.nonSerializableState.pivotTable.selectedPivots = performReordering( - computeIntervals(pivots.length, args.from, args.to, args.direction), - pivots); - }, - - changePivotTableAggregationOrder( - state: StateDraft, - args: {from: number, to: number, direction: DropDirection}) { - const aggregations = - state.nonSerializableState.pivotTable.selectedAggregations; - state.nonSerializableState.pivotTable.selectedAggregations = - performReordering( - computeIntervals( - aggregations.length, args.from, args.to, args.direction), - aggregations); - }, - - setMinimumLogLevel(state: StateDraft, args: {minimumLevel: number}) { - state.logFilteringCriteria.minimumLevel = args.minimumLevel; - }, - - addLogTag(state: StateDraft, args: {tag: string}) { - if (!state.logFilteringCriteria.tags.includes(args.tag)) { - state.logFilteringCriteria.tags.push(args.tag); - } - }, - - removeLogTag(state: StateDraft, args: {tag: string}) { - state.logFilteringCriteria.tags = - state.logFilteringCriteria.tags.filter((t) => t !== args.tag); - }, - - updateLogFilterText(state: StateDraft, args: {textEntry: string}) { - state.logFilteringCriteria.textEntry = args.textEntry; - }, - - toggleCollapseByTextEntry(state: StateDraft, _: {}) { - state.logFilteringCriteria.hideNonMatching = - !state.logFilteringCriteria.hideNonMatching; - }, -}; - -// When we are on the frontend side, we don't really want to execute the -// actions above, we just want to serialize them and marshal their -// arguments, send them over to the controller side and have them being -// executed there. The magic below takes care of turning each action into a -// function that returns the marshaled args. - -// A DeferredAction is a bundle of Args and a method name. This is the marshaled -// version of a StateActions method call. -export interface DeferredAction { - type: string; - args: Args; -} - -// This type magic creates a type function DeferredActions which takes a type -// T and 'maps' its attributes. For each attribute on T matching the signature: -// (state: StateDraft, args: Args) => void -// DeferredActions has an attribute: -// (args: Args) => DeferredAction -type ActionFunction = (state: StateDraft, args: Args) => void; -type DeferredActionFunc = T extends ActionFunction? - (args: Args) => DeferredAction: - never; -type DeferredActions = { - [P in keyof C]: DeferredActionFunc; -}; - -// Actions is an implementation of DeferredActions. -// (since StateActions is a variable not a type we have to do -// 'typeof StateActions' to access the (unnamed) type of StateActions). -// It's a Proxy such that any attribute access returns a function: -// (args) => {return {type: ATTRIBUTE_NAME, args};} -export const Actions = - new Proxy>({} as any, { - get(_: any, prop: string, _2: any) { - return (args: {}): DeferredAction<{}> => { - return { - type: prop, - args, - }; - }; - }, - }); diff --git a/third_party/perfetto/ui/src/common/actions_unittest.ts b/third_party/perfetto/ui/src/common/actions_unittest.ts deleted file mode 100644 index 6702e723c14f..000000000000 --- a/third_party/perfetto/ui/src/common/actions_unittest.ts +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {produce} from 'immer'; - -import {assertExists} from '../base/logging'; -import {SLICE_TRACK_KIND} from '../tracks/chrome_slices'; -import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile'; -import { - PROCESS_SCHEDULING_TRACK_KIND, -} from '../tracks/process_scheduling'; -import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state'; - -import {StateActions} from './actions'; -import {createEmptyState} from './empty_state'; -import { - InThreadTrackSortKey, - PrimaryTrackSortKey, - ProfileType, - SCROLLING_TRACK_GROUP, - State, - TraceUrlSource, - TrackSortKey, -} from './state'; - -function fakeTrack(state: State, args: { - id: string, - kind?: string, - trackGroup?: string, - trackSortKey?: TrackSortKey, - name?: string, - tid?: string -}): State { - return produce(state, (draft) => { - StateActions.addTrack(draft, { - id: args.id, - engineId: '0', - kind: args.kind || 'SOME_TRACK_KIND', - name: args.name || 'A track', - trackSortKey: args.trackSortKey === undefined ? - PrimaryTrackSortKey.ORDINARY_TRACK : - args.trackSortKey, - trackGroup: args.trackGroup || SCROLLING_TRACK_GROUP, - config: {tid: args.tid || '0'}, - }); - }); -} - -function fakeTrackGroup( - state: State, args: {id: string, summaryTrackId: string}): State { - return produce(state, (draft) => { - StateActions.addTrackGroup(draft, { - name: 'A group', - id: args.id, - engineId: '0', - collapsed: false, - summaryTrackId: args.summaryTrackId, - }); - }); -} - -function pinnedAndScrollingTracks( - state: State, - ids: string[], - pinnedTracks: string[], - scrollingTracks: string[]): State { - for (const id of ids) { - state = fakeTrack(state, {id}); - } - state = produce(state, (draft) => { - draft.pinnedTracks = pinnedTracks; - draft.scrollingTracks = scrollingTracks; - }); - return state; -} - -test('add scrolling tracks', () => { - const once = produce(createEmptyState(), (draft) => { - StateActions.addTrack(draft, { - engineId: '1', - kind: 'cpu', - name: 'Cpu 1', - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - trackGroup: SCROLLING_TRACK_GROUP, - config: {}, - }); - }); - const twice = produce(once, (draft) => { - StateActions.addTrack(draft, { - engineId: '2', - kind: 'cpu', - name: 'Cpu 2', - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - trackGroup: SCROLLING_TRACK_GROUP, - config: {}, - }); - }); - - expect(Object.values(twice.tracks).length).toBe(2); - expect(twice.scrollingTracks.length).toBe(2); -}); - -test('add track to track group', () => { - let state = createEmptyState(); - state = fakeTrack(state, {id: 's'}); - - const afterGroup = produce(state, (draft) => { - StateActions.addTrackGroup(draft, { - engineId: '1', - name: 'A track group', - id: '123-123-123', - summaryTrackId: 's', - collapsed: false, - }); - }); - - const afterTrackAdd = produce(afterGroup, (draft) => { - StateActions.addTrack(draft, { - id: '1', - engineId: '1', - kind: 'slices', - name: 'renderer 1', - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - trackGroup: '123-123-123', - config: {}, - }); - }); - - expect(afterTrackAdd.trackGroups['123-123-123'].tracks[0]).toBe('s'); - expect(afterTrackAdd.trackGroups['123-123-123'].tracks[1]).toBe('1'); -}); - -test('reorder tracks', () => { - const once = produce(createEmptyState(), (draft) => { - StateActions.addTrack(draft, { - engineId: '1', - kind: 'cpu', - name: 'Cpu 1', - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - config: {}, - }); - StateActions.addTrack(draft, { - engineId: '2', - kind: 'cpu', - name: 'Cpu 2', - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - config: {}, - }); - }); - - const firstTrackId = once.scrollingTracks[0]; - const secondTrackId = once.scrollingTracks[1]; - - const twice = produce(once, (draft) => { - StateActions.moveTrack(draft, { - srcId: `${firstTrackId}`, - op: 'after', - dstId: `${secondTrackId}`, - }); - }); - - expect(twice.scrollingTracks[0]).toBe(secondTrackId); - expect(twice.scrollingTracks[1]).toBe(firstTrackId); -}); - -test('reorder pinned to scrolling', () => { - let state = createEmptyState(); - state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']); - - const after = produce(state, (draft) => { - StateActions.moveTrack(draft, { - srcId: 'b', - op: 'before', - dstId: 'c', - }); - }); - - expect(after.pinnedTracks).toEqual(['a']); - expect(after.scrollingTracks).toEqual(['b', 'c']); -}); - -test('reorder scrolling to pinned', () => { - let state = createEmptyState(); - state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']); - - const after = produce(state, (draft) => { - StateActions.moveTrack(draft, { - srcId: 'b', - op: 'after', - dstId: 'a', - }); - }); - - expect(after.pinnedTracks).toEqual(['a', 'b']); - expect(after.scrollingTracks).toEqual(['c']); -}); - -test('reorder clamp bottom', () => { - let state = createEmptyState(); - state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']); - - const after = produce(state, (draft) => { - StateActions.moveTrack(draft, { - srcId: 'a', - op: 'before', - dstId: 'a', - }); - }); - expect(after).toEqual(state); -}); - -test('reorder clamp top', () => { - let state = createEmptyState(); - state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']); - - const after = produce(state, (draft) => { - StateActions.moveTrack(draft, { - srcId: 'c', - op: 'after', - dstId: 'c', - }); - }); - expect(after).toEqual(state); -}); - -test('pin', () => { - let state = createEmptyState(); - state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a'], ['b', 'c']); - - const after = produce(state, (draft) => { - StateActions.toggleTrackPinned(draft, { - trackId: 'c', - }); - }); - expect(after.pinnedTracks).toEqual(['a', 'c']); - expect(after.scrollingTracks).toEqual(['b']); -}); - -test('unpin', () => { - let state = createEmptyState(); - state = pinnedAndScrollingTracks(state, ['a', 'b', 'c'], ['a', 'b'], ['c']); - - const after = produce(state, (draft) => { - StateActions.toggleTrackPinned(draft, { - trackId: 'a', - }); - }); - expect(after.pinnedTracks).toEqual(['b']); - expect(after.scrollingTracks).toEqual(['a', 'c']); -}); - -test('open trace', () => { - const state = createEmptyState(); - const recordConfig = state.recordConfig; - const after = produce(state, (draft) => { - StateActions.openTraceFromUrl(draft, { - url: 'https://example.com/bar', - }); - }); - - expect(after.engine).not.toBeUndefined(); - expect((after.engine!!.source as TraceUrlSource).url) - .toBe('https://example.com/bar'); - expect(after.recordConfig).toBe(recordConfig); -}); - -test('open second trace from file', () => { - const once = produce(createEmptyState(), (draft) => { - StateActions.openTraceFromUrl(draft, { - url: 'https://example.com/bar', - }); - }); - - const twice = produce(once, (draft) => { - StateActions.addTrack(draft, { - engineId: '1', - kind: 'cpu', - name: 'Cpu 1', - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - config: {}, - }); - }); - - const thrice = produce(twice, (draft) => { - StateActions.openTraceFromUrl(draft, { - url: 'https://example.com/foo', - }); - }); - - expect(thrice.engine).not.toBeUndefined(); - expect((thrice.engine!!.source as TraceUrlSource).url) - .toBe('https://example.com/foo'); - expect(thrice.pinnedTracks.length).toBe(0); - expect(thrice.scrollingTracks.length).toBe(0); -}); - -test('setEngineReady with missing engine is ignored', () => { - const state = createEmptyState(); - produce(state, (draft) => { - StateActions.setEngineReady( - draft, {engineId: '1', ready: true, mode: 'WASM'}); - }); -}); - -test('setEngineReady', () => { - const state = createEmptyState(); - const after = produce(state, (draft) => { - StateActions.openTraceFromUrl(draft, { - url: 'https://example.com/bar', - }); - const latestEngineId = assertExists(draft.engine).id; - StateActions.setEngineReady( - draft, {engineId: latestEngineId, ready: true, mode: 'WASM'}); - }); - expect(after.engine!!.ready).toBe(true); -}); - -test('sortTracksByPriority', () => { - let state = createEmptyState(); - state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'b'}); - state = fakeTrack(state, { - id: 'b', - kind: HEAP_PROFILE_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.HEAP_PROFILE_TRACK, - trackGroup: 'g', - }); - state = fakeTrack(state, { - id: 'a', - kind: PROCESS_SCHEDULING_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK, - trackGroup: 'g', - }); - - const after = produce(state, (draft) => { - StateActions.sortThreadTracks(draft, {}); - }); - - // High Priority tracks should be sorted before Low Priority tracks: - // 'b' appears twice because it's the summary track - expect(after.trackGroups['g'].tracks).toEqual(['a', 'b', 'b']); -}); - -test('sortTracksByPriorityAndKindAndName', () => { - let state = createEmptyState(); - state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'b'}); - state = fakeTrack(state, { - id: 'a', - kind: PROCESS_SCHEDULING_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK, - trackGroup: 'g', - }); - state = fakeTrack(state, { - id: 'b', - kind: SLICE_TRACK_KIND, - trackGroup: 'g', - trackSortKey: PrimaryTrackSortKey.MAIN_THREAD, - }); - state = fakeTrack(state, { - id: 'c', - kind: SLICE_TRACK_KIND, - trackGroup: 'g', - trackSortKey: PrimaryTrackSortKey.RENDER_THREAD, - }); - state = fakeTrack(state, { - id: 'd', - kind: SLICE_TRACK_KIND, - trackGroup: 'g', - trackSortKey: PrimaryTrackSortKey.GPU_COMPLETION_THREAD, - }); - state = fakeTrack( - state, {id: 'e', kind: HEAP_PROFILE_TRACK_KIND, trackGroup: 'g'}); - state = fakeTrack( - state, {id: 'f', kind: SLICE_TRACK_KIND, trackGroup: 'g', name: 'T2'}); - state = fakeTrack( - state, {id: 'g', kind: SLICE_TRACK_KIND, trackGroup: 'g', name: 'T10'}); - - const after = produce(state, (draft) => { - StateActions.sortThreadTracks(draft, {}); - }); - - // The order should be determined by: - // 1.High priority - // 2.Non ordinary track kinds - // 3.Low priority - // 4.Collated name string (ie. 'T2' will be before 'T10') - expect(after.trackGroups['g'].tracks) - .toEqual(['a', 'b', 'b', 'c', 'd', 'e', 'f', 'g']); -}); - -test('sortTracksByTidThenName', () => { - let state = createEmptyState(); - state = fakeTrackGroup(state, {id: 'g', summaryTrackId: 'a'}); - state = fakeTrack(state, { - id: 'a', - kind: SLICE_TRACK_KIND, - trackSortKey: { - utid: 1, - priority: InThreadTrackSortKey.ORDINARY, - }, - trackGroup: 'g', - name: 'aaa', - tid: '1', - }); - state = fakeTrack(state, { - id: 'b', - kind: SLICE_TRACK_KIND, - trackSortKey: { - utid: 2, - priority: InThreadTrackSortKey.ORDINARY, - }, - trackGroup: 'g', - name: 'bbb', - tid: '2', - }); - state = fakeTrack(state, { - id: 'c', - kind: THREAD_STATE_TRACK_KIND, - trackSortKey: { - utid: 1, - priority: InThreadTrackSortKey.ORDINARY, - }, - trackGroup: 'g', - name: 'ccc', - tid: '1', - }); - - const after = produce(state, (draft) => { - StateActions.sortThreadTracks(draft, {}); - }); - - expect(after.trackGroups['g'].tracks).toEqual(['a', 'a', 'c', 'b']); -}); - -test('perf samples open flamegraph', () => { - const state = createEmptyState(); - - const afterSelectingPerf = produce(state, (draft) => { - StateActions.selectPerfSamples( - draft, - {id: 0, upid: 0, leftTs: 0, rightTs: 0, type: ProfileType.PERF_SAMPLE}); - }); - - expect(assertExists(afterSelectingPerf.currentFlamegraphState).type) - .toBe(ProfileType.PERF_SAMPLE); -}); - -test('heap profile opens flamegraph', () => { - const state = createEmptyState(); - - const afterSelectingPerf = produce(state, (draft) => { - StateActions.selectHeapProfile( - draft, {id: 0, upid: 0, ts: 0, type: ProfileType.JAVA_HEAP_GRAPH}); - }); - - expect(assertExists(afterSelectingPerf.currentFlamegraphState).type) - .toBe(ProfileType.JAVA_HEAP_GRAPH); -}); diff --git a/third_party/perfetto/ui/src/common/aggregation_data.ts b/third_party/perfetto/ui/src/common/aggregation_data.ts deleted file mode 100644 index 96a9a477254c..000000000000 --- a/third_party/perfetto/ui/src/common/aggregation_data.ts +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export type Column = (StringColumn|TimestampColumn|NumberColumn|StateColumn)& - {title: string, columnId: string}; - -export interface StringColumn { - kind: 'STRING'; - data: Uint16Array; -} - -export interface TimestampColumn { - kind: 'TIMESTAMP_NS'; - data: Float64Array; -} - -export interface NumberColumn { - kind: 'NUMBER'; - data: Uint16Array; -} - -export interface StateColumn { - kind: 'STATE'; - data: Uint16Array; -} - -type TypedArrayConstructor = - Uint16ArrayConstructor|Float64ArrayConstructor|Uint32ArrayConstructor; -export interface ColumnDef { - title: string; - kind: string; - sum?: boolean; - columnConstructor: TypedArrayConstructor; - columnId: string; -} - -export interface AggregateData { - tabName: string; - columns: Column[]; - columnSums: string[]; - // For string interning. - strings: string[]; - // Some aggregations will have extra info to display; - extra?: ThreadStateExtra; -} - -export function isEmptyData(data: AggregateData) { - return data.columns.length === 0 || data.columns[0].data.length === 0; -} - -export interface ThreadStateExtra { - kind: 'THREAD_STATE'; - states: string[]; - values: Float64Array; - totalMs: number; -} diff --git a/third_party/perfetto/ui/src/common/arg_types.ts b/third_party/perfetto/ui/src/common/arg_types.ts deleted file mode 100644 index bbea394a0fbc..000000000000 --- a/third_party/perfetto/ui/src/common/arg_types.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export type Arg = string| - {kind: 'SLICE', trackId: string, sliceId: number, description?: string}; -export type Args = Map; - -export type ArgsTree = ArgsTreeMap|ArgsTreeArray|string; -export type ArgsTreeArray = ArgsTree[]; -export interface ArgsTreeMap { - [key: string]: ArgsTree; -} - -export function isArgTreeArray(item: ArgsTree): item is ArgsTreeArray { - return typeof item === 'object' && Array.isArray(item); -} - -export function isArgTreeMap(item: ArgsTree): item is ArgsTreeMap { - return typeof item === 'object' && !Array.isArray(item); -} diff --git a/third_party/perfetto/ui/src/common/array_buffer_builder.ts b/third_party/perfetto/ui/src/common/array_buffer_builder.ts deleted file mode 100644 index 58e69808f613..000000000000 --- a/third_party/perfetto/ui/src/common/array_buffer_builder.ts +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - length as utf8Len, - write as utf8Write, -} from '@protobufjs/utf8'; - -import {assertTrue} from '../base/logging'; - -// A token that can be appended to an `ArrayBufferBuilder`. -export type ArrayBufferToken = string|number|Uint8Array; - -// Return the length, in bytes, of a token to be inserted. -function tokenLength(token: ArrayBufferToken): number { - if (typeof token === 'string') { - return utf8Len(token); - } else if (token instanceof Uint8Array) { - return token.byteLength; - } else { - assertTrue(token >= 0 && token <= 0xffffffff); - // 32-bit integers take 4 bytes - return 4; - } -} - -// Insert a token into the buffer, at position `byteOffset`. -// -// @param dataView A DataView into the buffer to write into. -// @param typedArray A Uint8Array view into the buffer to write into. -// @param byteOffset Position to write at, in the buffer. -// @param token Token to insert into the buffer. -function insertToken( - dataView: DataView, - typedArray: Uint8Array, - byteOffset: number, - token: ArrayBufferToken): void { - if (typeof token === 'string') { - // Encode the string in UTF-8 - const written = utf8Write(token, typedArray, byteOffset); - assertTrue(written === utf8Len(token)); - } else if (token instanceof Uint8Array) { - // Copy the bytes from the other array - typedArray.set(token, byteOffset); - } else { - assertTrue(token >= 0 && token <= 0xffffffff); - // 32-bit little-endian value - dataView.setUint32(byteOffset, token, true); - } -} - -// Like a string builder, but for an ArrayBuffer instead of a string. This -// allows us to assemble messages to send/receive over the wire. Data can be -// appended to the buffer using `append()`. The data we append can be of the -// following types: -// -// - string: the ASCII string is appended. Throws an error if there are -// non-ASCII characters. -// - number: the number is appended as a 32-bit little-endian integer. -// - Uint8Array: the bytes are appended as-is to the buffer. -export class ArrayBufferBuilder { - private readonly tokens: ArrayBufferToken[] = []; - - // Return an `ArrayBuffer` that is the concatenation of all the tokens. - toArrayBuffer(): ArrayBuffer { - // Calculate the size of the buffer we need. - let byteLength = 0; - for (const token of this.tokens) { - byteLength += tokenLength(token); - } - // Allocate the buffer. - const buffer = new ArrayBuffer(byteLength); - const dataView = new DataView(buffer); - const typedArray = new Uint8Array(buffer); - // Fill the buffer with the tokens. - let byteOffset = 0; - for (const token of this.tokens) { - insertToken(dataView, typedArray, byteOffset, token); - byteOffset += tokenLength(token); - } - assertTrue(byteOffset === byteLength); - // Return the values. - return buffer; - } - - // Add one or more tokens to the value of this object. - append(token: ArrayBufferToken): void { - this.tokens.push(token); - } -} diff --git a/third_party/perfetto/ui/src/common/cache_manager.ts b/third_party/perfetto/ui/src/common/cache_manager.ts deleted file mode 100644 index 11b3aef49b0c..000000000000 --- a/third_party/perfetto/ui/src/common/cache_manager.ts +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * This file deals with caching traces in the browser's Cache storage. The - * traces are cached so that the UI can gracefully reload a trace when the tab - * containing it is discarded by Chrome (e.g. because the tab was not used for - * a long time) or when the user accidentally hits reload. - */ -import {ignoreCacheUnactionableErrors} from './errors'; -import {TraceArrayBufferSource, TraceSource} from './state'; - -const TRACE_CACHE_NAME = 'cached_traces'; -const TRACE_CACHE_SIZE = 10; - -let LAZY_CACHE: Cache|undefined = undefined; - -async function getCache(): Promise { - if (self.caches === undefined) { - // The browser doesn't support cache storage or the page is opened from - // a non-secure origin. - return undefined; - } - if (LAZY_CACHE !== undefined) { - return LAZY_CACHE; - } - LAZY_CACHE = await caches.open(TRACE_CACHE_NAME); - return LAZY_CACHE; -} - -async function cacheDelete(key: Request): Promise { - try { - const cache = await getCache(); - if (cache === undefined) return false; // Cache storage not supported. - return cache.delete(key); - } catch (e) { - return ignoreCacheUnactionableErrors(e, false); - } -} - -async function cachePut(key: string, value: Response): Promise { - try { - const cache = await getCache(); - if (cache === undefined) return; // Cache storage not supported. - cache.put(key, value); - } catch (e) { - ignoreCacheUnactionableErrors(e, undefined); - } -} - -async function cacheMatch(key: Request|string): Promise { - try { - const cache = await getCache(); - if (cache === undefined) return undefined; // Cache storage not supported. - return cache.match(key); - } catch (e) { - return ignoreCacheUnactionableErrors(e, undefined); - } -} - -async function cacheKeys(): Promise { - try { - const cache = await getCache(); - if (cache === undefined) return []; // Cache storage not supported. - return cache.keys(); - } catch (e) { - return ignoreCacheUnactionableErrors(e, []); - } -} - -export async function cacheTrace( - traceSource: TraceSource, traceUuid: string): Promise { - let trace; - let title = ''; - let fileName = ''; - let url = ''; - let contentLength = 0; - let localOnly = false; - switch (traceSource.type) { - case 'ARRAY_BUFFER': - trace = traceSource.buffer; - title = traceSource.title; - fileName = traceSource.fileName || ''; - url = traceSource.url || ''; - contentLength = traceSource.buffer.byteLength; - localOnly = traceSource.localOnly || false; - break; - case 'FILE': - trace = await traceSource.file.arrayBuffer(); - title = traceSource.file.name; - contentLength = traceSource.file.size; - break; - default: - return false; - } - - const headers = new Headers([ - ['x-trace-title', title], - ['x-trace-url', url], - ['x-trace-filename', fileName], - ['x-trace-local-only', `${localOnly}`], - ['content-type', 'application/octet-stream'], - ['content-length', `${contentLength}`], - [ - 'expires', - // Expires in a week from now (now = upload time) - (new Date((new Date()).getTime() + (1000 * 60 * 60 * 24 * 7))) - .toUTCString(), - ], - ]); - await deleteStaleEntries(); - await cachePut( - `/_${TRACE_CACHE_NAME}/${traceUuid}`, new Response(trace, {headers})); - return true; -} - -export async function tryGetTrace(traceUuid: string): - Promise { - await deleteStaleEntries(); - const response = await cacheMatch(`/_${TRACE_CACHE_NAME}/${traceUuid}`); - - if (!response) return undefined; - return { - type: 'ARRAY_BUFFER', - buffer: await response.arrayBuffer(), - title: response.headers.get('x-trace-title') || '', - fileName: response.headers.get('x-trace-filename') || undefined, - url: response.headers.get('x-trace-url') || undefined, - uuid: traceUuid, - localOnly: response.headers.get('x-trace-local-only') === 'true', - }; -} - -async function deleteStaleEntries() { - // Loop through stored traces and invalidate all but the most recent - // TRACE_CACHE_SIZE. - const keys = await cacheKeys(); - const storedTraces: Array<{key: Request, date: Date}> = []; - const now = new Date(); - const deletions = []; - for (const key of keys) { - const existingTrace = await cacheMatch(key); - if (existingTrace === undefined) { - continue; - } - const expires = existingTrace.headers.get('expires'); - if (expires === undefined || expires === null) { - // Missing `expires`, so give up and delete which is better than - // keeping it around forever. - deletions.push(cacheDelete(key)); - continue; - } - const expiryDate = new Date(expires); - if (expiryDate < now) { - deletions.push(cacheDelete(key)); - } else { - storedTraces.push({key, date: expiryDate}); - } - } - - // Sort the traces descending by time, such that most recent ones are placed - // at the beginning. Then, take traces from TRACE_CACHE_SIZE onwards and - // delete them from cache. - const oldTraces = - storedTraces.sort((a, b) => b.date.getTime() - a.date.getTime()) - .slice(TRACE_CACHE_SIZE); - for (const oldTrace of oldTraces) { - deletions.push(cacheDelete(oldTrace.key)); - } - - // TODO(hjd): Wrong Promise.all here, should use the one that - // ignores failures but need to upgrade TypeScript for that. - await Promise.all(deletions); -} diff --git a/third_party/perfetto/ui/src/common/canvas_utils.ts b/third_party/perfetto/ui/src/common/canvas_utils.ts deleted file mode 100644 index aef36a0ddf66..000000000000 --- a/third_party/perfetto/ui/src/common/canvas_utils.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export function cropText(str: string, charWidth: number, rectWidth: number) { - let displayText = ''; - const maxLength = Math.floor(rectWidth / charWidth) - 1; - if (str.length <= maxLength) { - displayText = str; - } else { - let limit = maxLength; - let maybeTripleDot = ''; - if (maxLength > 1) { - limit = maxLength - 1; - maybeTripleDot = '\u2026'; - } - // Javascript strings are UTF-16. |limit| could point in the middle of a - // 32-bit double-wchar codepoint (e.g., an emoji). Here we detect if the - // |limit|-th wchar is a leading surrogate and attach the trailing one. - const lastCharCode = str.charCodeAt(limit - 1); - limit += (lastCharCode >= 0xD800 && lastCharCode < 0xDC00) ? 1 : 0; - displayText = str.substring(0, limit) + maybeTripleDot; - } - return displayText; -} - -export function drawDoubleHeadedArrow( - ctx: CanvasRenderingContext2D, - x: number, - y: number, - length: number, - showArrowHeads: boolean, - width = 2, - color = 'black') { - ctx.beginPath(); - ctx.lineWidth = width; - ctx.lineCap = 'round'; - ctx.strokeStyle = color; - ctx.moveTo(x, y); - ctx.lineTo(x + length, y); - ctx.stroke(); - ctx.closePath(); - // Arrowheads on the each end of the line. - if (showArrowHeads) { - ctx.beginPath(); - ctx.moveTo(x + length - 8, y - 4); - ctx.lineTo(x + length, y); - ctx.lineTo(x + length - 8, y + 4); - ctx.stroke(); - ctx.closePath(); - ctx.beginPath(); - ctx.moveTo(x + 8, y - 4); - ctx.lineTo(x, y); - ctx.lineTo(x + 8, y + 4); - ctx.stroke(); - ctx.closePath(); - } -} - -export function drawIncompleteSlice( - ctx: CanvasRenderingContext2D, - x: number, - y: number, - width: number, - height: number) { - ctx.beginPath(); - const triangleSize = height / 4; - ctx.moveTo(x, y); - ctx.lineTo(x + width, y); - ctx.lineTo(x + width - 3, y + triangleSize * 0.5); - ctx.lineTo(x + width, y + triangleSize); - ctx.lineTo(x + width - 3, y + (triangleSize * 1.5)); - ctx.lineTo(x + width, y + 2 * triangleSize); - ctx.lineTo(x + width - 3, y + (triangleSize * 2.5)); - ctx.lineTo(x + width, y + 3 * triangleSize); - ctx.lineTo(x + width - 3, y + (triangleSize * 3.5)); - ctx.lineTo(x + width, y + 4 * triangleSize); - ctx.lineTo(x, y + height); - ctx.fill(); -} diff --git a/third_party/perfetto/ui/src/common/canvas_utils_unittest.ts b/third_party/perfetto/ui/src/common/canvas_utils_unittest.ts deleted file mode 100644 index 3aa005f0d58a..000000000000 --- a/third_party/perfetto/ui/src/common/canvas_utils_unittest.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {cropText} from './canvas_utils'; - -test('cropHelper regular text', () => { - const tripleDot = '\u2026'; - const emoji = '\uD83D\uDE00'; - expect(cropText( - 'com.android.camera [4096]', - /* charWidth=*/ 5, - /* rectWidth=*/ 2 * 5)) - .toBe('c'); - expect(cropText('com.android.camera [4096]', 5, 4 * 5 + 2)) - .toBe('co' + tripleDot); - expect(cropText('com.android.camera [4096]', 5, 5 * 5 + 2)) - .toBe('com' + tripleDot); - expect(cropText('com.android.camera [4096]', 5, 13 * 5 + 2)) - .toBe('com.android' + tripleDot); - expect(cropText('com.android.camera [4096]', 5, 26 * 5 + 2)) - .toBe('com.android.camera [4096]'); - expect(cropText(emoji + 'abc', 5, 2 * 5)).toBe(emoji); - expect(cropText(emoji + 'abc', 5, 5 * 5)).toBe(emoji + 'a' + tripleDot); -}); diff --git a/third_party/perfetto/ui/src/common/channels.ts b/third_party/perfetto/ui/src/common/channels.ts deleted file mode 100644 index da7283666d72..000000000000 --- a/third_party/perfetto/ui/src/common/channels.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {globals} from '../frontend/globals'; - -export const DEFAULT_CHANNEL = 'stable'; -const CHANNEL_KEY = 'perfettoUiChannel'; - -let currentChannel: string|undefined = undefined; -let nextChannel: string|undefined = undefined; - -// This is the channel the UI is currently running. It doesn't change once the -// UI has been loaded. -export function getCurrentChannel(): string { - if (currentChannel === undefined) { - currentChannel = localStorage.getItem(CHANNEL_KEY) || DEFAULT_CHANNEL; - } - return currentChannel; -} - -// This is the channel that will be applied on reload. -export function getNextChannel(): string { - if (nextChannel !== undefined) { - return nextChannel; - } - return getCurrentChannel(); -} - -export function channelChanged(): boolean { - return getCurrentChannel() !== getNextChannel(); -} - -export function setChannel(channel: string): void { - getCurrentChannel(); // Cache the current channel before mangling next one. - nextChannel = channel; - localStorage.setItem(CHANNEL_KEY, channel); - globals.rafScheduler.scheduleFullRedraw(); -} diff --git a/third_party/perfetto/ui/src/common/colorizer.ts b/third_party/perfetto/ui/src/common/colorizer.ts deleted file mode 100644 index 2b9006fd07d4..000000000000 --- a/third_party/perfetto/ui/src/common/colorizer.ts +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {hsl} from 'color-convert'; - -import {hash} from '../common/hash'; -import {cachedHsluvToHex} from '../frontend/hsluv_cache'; - -export interface Color { - c: string; - h: number; - s: number; - l: number; - a?: number; -} - -const MD_PALETTE: Color[] = [ - {c: 'red', h: 4, s: 90, l: 58}, - {c: 'pink', h: 340, s: 82, l: 52}, - {c: 'purple', h: 291, s: 64, l: 42}, - {c: 'deep purple', h: 262, s: 52, l: 47}, - {c: 'indigo', h: 231, s: 48, l: 48}, - {c: 'blue', h: 207, s: 90, l: 54}, - {c: 'light blue', h: 199, s: 98, l: 48}, - {c: 'cyan', h: 187, s: 100, l: 42}, - {c: 'teal', h: 174, s: 100, l: 29}, - {c: 'green', h: 122, s: 39, l: 49}, - {c: 'light green', h: 88, s: 50, l: 53}, - {c: 'lime', h: 66, s: 70, l: 54}, - {c: 'amber', h: 45, s: 100, l: 51}, - {c: 'orange', h: 36, s: 100, l: 50}, - {c: 'deep orange', h: 14, s: 100, l: 57}, - {c: 'brown', h: 16, s: 25, l: 38}, - {c: 'blue gray', h: 200, s: 18, l: 46}, - {c: 'yellow', h: 54, s: 100, l: 62}, -]; - -export const GRAY_COLOR: Color = { - c: 'grey', - h: 0, - s: 0, - l: 62, -}; - -// A piece of wisdom from a long forgotten blog post: "Don't make -// colors you want to change something normal like grey." -export const UNEXPECTED_PINK_COLOR: Color = { - c: '#ff69b4', - h: 330, - s: 1.0, - l: 0.706, -}; - -export function hueForCpu(cpu: number): number { - return (128 + (32 * cpu)) % 256; -} - -const DESAT_RED: Color = { - c: 'desat red', - h: 3, - s: 30, - l: 49, -}; -const DARK_GREEN: Color = { - c: 'dark green', - h: 120, - s: 44, - l: 34, -}; -const LIME_GREEN: Color = { - c: 'lime green', - h: 75, - s: 55, - l: 47, -}; -const TRANSPARENT_WHITE: Color = { - c: 'white', - h: 0, - s: 1, - l: 97, - a: 0.55, -}; -const ORANGE: Color = { - c: 'orange', - h: 36, - s: 100, - l: 50, -}; -const INDIGO: Color = { - c: 'indigo', - h: 231, - s: 48, - l: 48, -}; - -export function colorForState(state: string): Readonly { - if (state === 'Running') { - return DARK_GREEN; - } else if (state.startsWith('Runnable')) { - return LIME_GREEN; - } else if (state.includes('Uninterruptible Sleep')) { - if (state.includes('non-IO')) { - return DESAT_RED; - } - return ORANGE; - } else if (state.includes('Sleeping') || state.includes('Idle')) { - return TRANSPARENT_WHITE; - } - return INDIGO; -} - -export function textColorForState(stateCode: string): string { - const background = colorForState(stateCode); - return background.l > 80 ? '#404040' : '#fff'; -} - -export function colorForString(identifier: string): Color { - const colorIdx = hash(identifier, MD_PALETTE.length); - return Object.assign({}, MD_PALETTE[colorIdx]); -} - -export function colorForTid(tid: number): Color { - return colorForString(tid.toString()); -} - -export function colorForThread(thread?: {pid?: number, tid: number}): Color { - if (thread === undefined) { - return Object.assign({}, GRAY_COLOR); - } - const tid = thread.pid ? thread.pid : thread.tid; - return colorForTid(tid); -} - -// 40 different random hues 9 degrees apart. -export function randomColor(): string { - const hue = Math.floor(Math.random() * 40) * 9; - return '#' + hsl.hex([hue, 90, 30]); -} - -// Chooses a color uniform at random based on hash(sliceName). Returns [hue, -// saturation, lightness]. -// -// Prefer converting this to an RGB color using hsluv, not the browser's -// built-in vanilla HSL handling. This is because this function chooses -// hue/lightness uniform at random, but HSL is not perceptually uniform. See -// https://www.boronine.com/2012/03/26/Color-Spaces-for-Human-Beings/. -// -// If isSelected, the color will be particularly dark, making it stand out. -export function hslForSlice( - sliceName: string, isSelected: boolean|null): [number, number, number] { - const hue = hash(sliceName, 360); - // Saturation 100 would give the most differentiation between colors, but it's - // garish. - const saturation = 80; - const lightness = isSelected ? 30 : hash(sliceName + 'x', 40) + 40; - return [hue, saturation, lightness]; -} - -// Lightens the color for thread slices to represent wall time. -export function colorForThreadIdleSlice( - hue: number, - saturation: number, - lightness: number, - isSelected: boolean|null): string { - // Increase lightness by 80% when selected and 40% otherwise, - // without exceeding 88. - let newLightness = isSelected ? lightness * 1.8 : lightness * 1.4; - newLightness = Math.min(newLightness, 88); - return cachedHsluvToHex(hue, saturation, newLightness); -} - -export function colorToStr(color: Color) { - if (color.a !== undefined) { - return `hsla(${color.h}, ${color.s}%, ${color.l}%, ${color.a})`; - } - return `hsl(${color.h}, ${color.s}%, ${color.l}%)`; -} - -export function colorCompare(x: Color, y: Color) { - return (x.h - y.h) || (x.s - y.s) || (x.l - y.l); -} diff --git a/third_party/perfetto/ui/src/common/colorizer_unittest.ts b/third_party/perfetto/ui/src/common/colorizer_unittest.ts deleted file mode 100644 index d3ed01eae08c..000000000000 --- a/third_party/perfetto/ui/src/common/colorizer_unittest.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {colorForThread, hueForCpu} from './colorizer'; - -const PROCESS_A_THREAD_A = { - tid: 100, - pid: 100, -}; - -const PROCESS_A_THREAD_B = { - tid: 101, - pid: 100, -}; - -const PROCESS_B_THREAD_A = { - tid: 200, - pid: 200, -}; - -const PROCESS_UNK_THREAD_A = { - tid: 42, -}; - -const PROCESS_UNK_THREAD_B = { - tid: 42, -}; - -test('it gives threads colors by pid if present', () => { - const colorAA = colorForThread(PROCESS_A_THREAD_A); - const colorAB = colorForThread(PROCESS_A_THREAD_B); - const colorBA = colorForThread(PROCESS_B_THREAD_A); - expect(colorAA).toEqual(colorAB); - expect(colorAA).not.toEqual(colorBA); -}); - -test('it gives threads colors by tid if pid missing', () => { - const colorUnkA = colorForThread(PROCESS_UNK_THREAD_A); - const colorUnkB = colorForThread(PROCESS_UNK_THREAD_B); - expect(colorUnkA).toEqual(colorUnkB); -}); - -test('it copies colors', () => { - const a = colorForThread(PROCESS_A_THREAD_A); - const b = colorForThread(PROCESS_A_THREAD_A); - expect(a === b).toEqual(false); -}); - -test('it gives different cpus different hues', () => { - expect(hueForCpu(0)).not.toEqual(hueForCpu(1)); -}); diff --git a/third_party/perfetto/ui/src/common/comparator_builder.ts b/third_party/perfetto/ui/src/common/comparator_builder.ts deleted file mode 100644 index 17d0b64e4297..000000000000 --- a/third_party/perfetto/ui/src/common/comparator_builder.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Simple builder-style class to implement object equality more succinctly. -export class EqualsBuilder { - result = true; - first: T; - second: T; - - constructor(first: T, second: T) { - this.first = first; - this.second = second; - } - - comparePrimitive(getter: (arg: T) => string | number): EqualsBuilder { - if (this.result) { - this.result = getter(this.first) === getter(this.second); - } - return this; - } - - compare( - comparator: (first: S, second: S) => boolean, - getter: (arg: T) => S): EqualsBuilder { - if (this.result) { - this.result = comparator(getter(this.first), getter(this.second)); - } - return this; - } - - equals(): boolean { - return this.result; - } -} diff --git a/third_party/perfetto/ui/src/common/constants.ts b/third_party/perfetto/ui/src/common/constants.ts deleted file mode 100644 index cc1036636963..000000000000 --- a/third_party/perfetto/ui/src/common/constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export const TRACE_SUFFIX = '.perfetto-trace'; diff --git a/third_party/perfetto/ui/src/common/conversion_jobs.ts b/third_party/perfetto/ui/src/common/conversion_jobs.ts deleted file mode 100644 index efb56e271ead..000000000000 --- a/third_party/perfetto/ui/src/common/conversion_jobs.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export enum ConversionJobStatus { - InProgress = 'InProgress', - NotRunning = 'NotRunning', -} - -export type ConversionJobName = 'convert_systrace'|'convert_json'| - 'open_in_legacy'|'convert_pprof'|'create_permalink'; - -export interface ConversionJobStatusUpdate { - jobName: ConversionJobName; - jobStatus: ConversionJobStatus; -} diff --git a/third_party/perfetto/ui/src/common/dragndrop_logic.ts b/third_party/perfetto/ui/src/common/dragndrop_logic.ts deleted file mode 100644 index 27280c376a35..000000000000 --- a/third_party/perfetto/ui/src/common/dragndrop_logic.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertTrue} from '../base/logging'; - -export type DropDirection = 'left'|'right'; - -export interface Interval { - from: number; - to: number; -} - -/* - * When a drag'n'drop is performed in a linear sequence, the resulting reordered - * array will consist of several contiguous subarrays of the original glued - * together. - * - * This function implements the computation of these intervals. - * - * The drag'n'drop operation performed is as follows: in the sequence with given - * length, the element with index `dragFrom` is dropped on the `direction` to - * the element `dragTo`. - */ -export function computeIntervals( - length: number, dragFrom: number, dragTo: number, direction: DropDirection): - Interval[] { - assertTrue(dragFrom !== dragTo); - - if (dragTo < dragFrom) { - const prefixLen = direction == 'left' ? dragTo : dragTo + 1; - return [ - // First goes unchanged prefix. - {from: 0, to: prefixLen}, - // Then goes dragged element. - {from: dragFrom, to: dragFrom + 1}, - // Then goes suffix up to dragged element (which has already been moved). - {from: prefixLen, to: dragFrom}, - // Then the rest of an array. - {from: dragFrom + 1, to: length}, - ]; - } - - // Other case: dragTo > dragFrom - const prefixLen = direction == 'left' ? dragTo : dragTo + 1; - return [ - {from: 0, to: dragFrom}, - {from: dragFrom + 1, to: prefixLen}, - {from: dragFrom, to: dragFrom + 1}, - {from: prefixLen, to: length}, - ]; -} - -export function performReordering(intervals: Interval[], arr: T[]): T[] { - const result: T[] = []; - - for (const interval of intervals) { - result.push(...arr.slice(interval.from, interval.to)); - } - - return result; -} diff --git a/third_party/perfetto/ui/src/common/dragndrop_logic_unittest.ts b/third_party/perfetto/ui/src/common/dragndrop_logic_unittest.ts deleted file mode 100644 index 9c1c933751cd..000000000000 --- a/third_party/perfetto/ui/src/common/dragndrop_logic_unittest.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - computeIntervals, - performReordering, -} from './dragndrop_logic'; - -describe('performReordering', () => { - test('has the same elements in the result', () => { - const arr = [1, 2, 3, 4, 5, 6]; - const arrSet = new Set(arr); - - for (let i = 0; i < arr.length; i++) { - for (let j = 0; j < arr.length; j++) { - if (i === j) { - // The function has a precondition that two indices have to be - // different. - continue; - } - - const permutedLeft = - performReordering(computeIntervals(arr.length, i, j, 'left'), arr); - expect(new Set(permutedLeft)).toEqual(arrSet); - expect(permutedLeft.length).toEqual(arr.length); - - const permutedRight = - performReordering(computeIntervals(arr.length, i, j, 'right'), arr); - expect(new Set(permutedRight)).toEqual(arrSet); - expect(permutedRight.length).toEqual(arr.length); - } - } - }); -}); diff --git a/third_party/perfetto/ui/src/common/empty_state.ts b/third_party/perfetto/ui/src/common/empty_state.ts deleted file mode 100644 index bd1e6b6d6c5a..000000000000 --- a/third_party/perfetto/ui/src/common/empty_state.ts +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {createEmptyRecordConfig} from '../controller/record_config_types'; -import { - Aggregation, -} from '../frontend/pivot_table_types'; -import { - autosaveConfigStore, - recordTargetStore, -} from '../frontend/record_config'; - -import {featureFlags} from './feature_flags'; -import { - defaultTraceTime, - NonSerializableState, - State, - STATE_VERSION, -} from './state'; - -const AUTOLOAD_STARTED_CONFIG_FLAG = featureFlags.register({ - id: 'autoloadStartedConfig', - name: 'Auto-load last used recording config', - description: 'Starting a recording automatically saves its configuration. ' + - 'This flag controls whether this config is automatically loaded.', - defaultValue: true, -}); - -export function keyedMap( - keyFn: (key: T) => string, ...values: T[]): Map { - const result = new Map(); - - for (const value of values) { - result.set(keyFn(value), value); - } - - return result; -} - -export const COUNT_AGGREGATION: Aggregation = { - aggregationFunction: 'COUNT', - // Exact column is ignored for count aggregation because it does not matter - // what to count, use empty strings. - column: {kind: 'regular', table: '', column: ''}, -}; - -export function createEmptyNonSerializableState(): NonSerializableState { - return { - pivotTable: { - queryResult: null, - selectedPivots: [{kind: 'regular', table: 'slice', column: 'name'}], - selectedAggregations: [ - { - aggregationFunction: 'SUM', - column: {kind: 'regular', table: 'slice', column: 'dur'}, - sortDirection: 'DESC', - }, - { - aggregationFunction: 'SUM', - column: {kind: 'regular', table: 'slice', column: 'thread_dur'}, - }, - COUNT_AGGREGATION, - ], - constrainToArea: true, - queryRequested: false, - argumentNames: [], - }, - }; -} - -export function createEmptyState(): State { - return { - version: STATE_VERSION, - nextId: '-1', - newEngineMode: 'USE_HTTP_RPC_IF_AVAILABLE', - traceTime: {...defaultTraceTime}, - tracks: {}, - uiTrackIdByTraceTrackId: {}, - utidToThreadSortKey: {}, - aggregatePreferences: {}, - trackGroups: {}, - visibleTracks: [], - pinnedTracks: [], - scrollingTracks: [], - areas: {}, - queries: {}, - metrics: {}, - permalink: {}, - notes: {}, - visualisedArgs: [], - - recordConfig: AUTOLOAD_STARTED_CONFIG_FLAG.get() ? - autosaveConfigStore.get() : - createEmptyRecordConfig(), - displayConfigAsPbtxt: false, - lastLoadedConfig: {type: 'NONE'}, - - frontendLocalState: { - visibleState: { - ...defaultTraceTime, - lastUpdate: 0, - resolution: 0, - }, - }, - - omniboxState: { - omnibox: '', - mode: 'SEARCH', - }, - - logsPagination: { - offset: 0, - count: 0, - }, - - ftracePagination: { - offset: 0, - count: 0, - }, - - ftraceFilter: { - excludedNames: [], - }, - - status: {msg: '', timestamp: 0}, - currentSelection: null, - currentFlamegraphState: null, - traceConversionInProgress: false, - - perfDebug: false, - sidebarVisible: true, - hoveredUtid: -1, - hoveredPid: -1, - hoverCursorTimestamp: -1, - hoveredNoteTimestamp: -1, - highlightedSliceId: -1, - focusedFlowIdLeft: -1, - focusedFlowIdRight: -1, - searchIndex: -1, - - recordingInProgress: false, - recordingCancelled: false, - extensionInstalled: false, - flamegraphModalDismissed: false, - recordingTarget: recordTargetStore.getValidTarget(), - availableAdbDevices: [], - - fetchChromeCategories: false, - chromeCategories: undefined, - nonSerializableState: createEmptyNonSerializableState(), - - logFilteringCriteria: { - // The first two log priorities are ignored. - minimumLevel: 2, - tags: [], - textEntry: '', - hideNonMatching: true, - }, - }; -} diff --git a/third_party/perfetto/ui/src/common/engine.ts b/third_party/perfetto/ui/src/common/engine.ts deleted file mode 100644 index 8b6cb465ccb9..000000000000 --- a/third_party/perfetto/ui/src/common/engine.ts +++ /dev/null @@ -1,470 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {defer, Deferred} from '../base/deferred'; -import {assertExists, assertTrue} from '../base/logging'; -import {perfetto} from '../gen/protos'; - -import {ProtoRingBuffer} from './proto_ring_buffer'; -import { - ComputeMetricArgs, - ComputeMetricResult, - DisableAndReadMetatraceResult, - QueryArgs, - ResetTraceProcessorArgs, -} from './protos'; -import {NUM, NUM_NULL, STR} from './query_result'; -import { - createQueryResult, - QueryError, - QueryResult, - WritableQueryResult, -} from './query_result'; -import {TimeSpan} from './time'; - -import TraceProcessorRpc = perfetto.protos.TraceProcessorRpc; -import TraceProcessorRpcStream = perfetto.protos.TraceProcessorRpcStream; -import TPM = perfetto.protos.TraceProcessorRpc.TraceProcessorMethod; - -export interface LoadingTracker { - beginLoading(): void; - endLoading(): void; -} - -export class NullLoadingTracker implements LoadingTracker { - beginLoading(): void {} - endLoading(): void {} -} - - -// This is used to skip the decoding of queryResult from protobufjs and deal -// with it ourselves. See the comment below around `QueryResult.decode = ...`. -interface QueryResultBypass { - rawQueryResult: Uint8Array; -} - -export interface TraceProcessorConfig { - cropTrackEvents: boolean; - ingestFtraceInRawTable: boolean; - analyzeTraceProtoContent: boolean; -} - -// Abstract interface of a trace proccessor. -// This is the TypeScript equivalent of src/trace_processor/rpc.h. -// There are two concrete implementations: -// 1. WasmEngineProxy: creates a Wasm module and interacts over postMessage(). -// 2. HttpRpcEngine: connects to an external `trace_processor_shell --httpd`. -// and interacts via fetch(). -// In both cases, we have a byte-oriented pipe to interact with TraceProcessor. -// The derived class is only expected to deal with these two functions: -// 1. Implement the abstract rpcSendRequestBytes() function, sending the -// proto-encoded TraceProcessorRpc requests to the TraceProcessor instance. -// 2. Call onRpcResponseBytes() when response data is received. -export abstract class Engine { - abstract readonly id: string; - private _cpus?: number[]; - private _numGpus?: number; - private loadingTracker: LoadingTracker; - private txSeqId = 0; - private rxSeqId = 0; - private rxBuf = new ProtoRingBuffer(); - private pendingParses = new Array>(); - private pendingEOFs = new Array>(); - private pendingResetTraceProcessors = new Array>(); - private pendingQueries = new Array(); - private pendingRestoreTables = new Array>(); - private pendingComputeMetrics = new Array>(); - private pendingReadMetatrace?: Deferred; - private _isMetatracingEnabled = false; - - constructor(tracker?: LoadingTracker) { - this.loadingTracker = tracker ? tracker : new NullLoadingTracker(); - } - - // Called to send data to the TraceProcessor instance. This turns into a - // postMessage() or a HTTP request, depending on the Engine implementation. - abstract rpcSendRequestBytes(data: Uint8Array): void; - - // Called when an inbound message is received by the Engine implementation - // (e.g. onmessage for the Wasm case, on when HTTP replies are received for - // the HTTP+RPC case). - onRpcResponseBytes(dataWillBeRetained: Uint8Array) { - // Note: when hitting the fastpath inside ProtoRingBuffer, the |data| buffer - // is returned back by readMessage() (% subarray()-ing it) and held onto by - // other classes (e.g., QueryResult). For both fetch() and Wasm we are fine - // because every response creates a new buffer. - this.rxBuf.append(dataWillBeRetained); - for (;;) { - const msg = this.rxBuf.readMessage(); - if (msg === undefined) break; - this.onRpcResponseMessage(msg); - } - } - - // Parses a response message. - // |rpcMsgEncoded| is a sub-array to to the start of a TraceProcessorRpc - // proto-encoded message (without the proto preamble and varint size). - private onRpcResponseMessage(rpcMsgEncoded: Uint8Array) { - // Here we override the protobufjs-generated code to skip the parsing of the - // new streaming QueryResult and instead passing it through like a buffer. - // This is the overall problem: All trace processor responses are wrapped - // into a perfetto.protos.TraceProcessorRpc proto message. In all cases % - // TPM_QUERY_STREAMING, we want protobufjs to decode the proto bytes and - // give us a structured object. In the case of TPM_QUERY_STREAMING, instead, - // we want to deal with the proto parsing ourselves using the new - // QueryResult.appendResultBatch() method, because that handled streaming - // results more efficiently and skips several copies. - // By overriding the decode method below, we achieve two things: - // 1. We avoid protobufjs decoding the TraceProcessorRpc.query_result field. - // 2. We stash (a view of) the original buffer into the |rawQueryResult| so - // the `case TPM_QUERY_STREAMING` below can take it. - perfetto.protos.QueryResult.decode = - (reader: protobuf.Reader, length: number) => { - const res = - perfetto.protos.QueryResult.create() as {} as QueryResultBypass; - res.rawQueryResult = - reader.buf.subarray(reader.pos, reader.pos + length); - // All this works only if protobufjs returns the original ArrayBuffer - // from |rpcMsgEncoded|. It should be always the case given the - // current implementation. This check mainly guards against future - // behavioral changes of protobufjs. We don't want to accidentally - // hold onto some internal protobufjs buffer. We are fine holding - // onto |rpcMsgEncoded| because those come from ProtoRingBuffer which - // is buffer-retention-friendly. - assertTrue(res.rawQueryResult.buffer === rpcMsgEncoded.buffer); - reader.pos += length; - return res as {} as perfetto.protos.QueryResult; - }; - - const rpc = TraceProcessorRpc.decode(rpcMsgEncoded); - - if (rpc.fatalError !== undefined && rpc.fatalError.length > 0) { - throw new Error(`${rpc.fatalError}`); - } - - // Allow restarting sequences from zero (when reloading the browser). - if (rpc.seq !== this.rxSeqId + 1 && this.rxSeqId !== 0 && rpc.seq !== 0) { - // "(ERR:rpc_seq)" is intercepted by error_dialog.ts to show a more - // graceful and actionable error. - throw new Error(`RPC sequence id mismatch cur=${rpc.seq} last=${ - this.rxSeqId} (ERR:rpc_seq)`); - } - - this.rxSeqId = rpc.seq; - - let isFinalResponse = true; - - switch (rpc.response) { - case TPM.TPM_APPEND_TRACE_DATA: - const appendResult = assertExists(rpc.appendResult); - const pendingPromise = assertExists(this.pendingParses.shift()); - if (appendResult.error && appendResult.error.length > 0) { - pendingPromise.reject(appendResult.error); - } else { - pendingPromise.resolve(); - } - break; - case TPM.TPM_FINALIZE_TRACE_DATA: - assertExists(this.pendingEOFs.shift()).resolve(); - break; - case TPM.TPM_RESET_TRACE_PROCESSOR: - assertExists(this.pendingResetTraceProcessors.shift()).resolve(); - break; - case TPM.TPM_RESTORE_INITIAL_TABLES: - assertExists(this.pendingRestoreTables.shift()).resolve(); - break; - case TPM.TPM_QUERY_STREAMING: - const qRes = assertExists(rpc.queryResult) as {} as QueryResultBypass; - const pendingQuery = assertExists(this.pendingQueries[0]); - pendingQuery.appendResultBatch(qRes.rawQueryResult); - if (pendingQuery.isComplete()) { - this.pendingQueries.shift(); - } else { - isFinalResponse = false; - } - break; - case TPM.TPM_COMPUTE_METRIC: - const metricRes = assertExists(rpc.metricResult) as ComputeMetricResult; - const pendingComputeMetric = - assertExists(this.pendingComputeMetrics.shift()); - if (metricRes.error && metricRes.error.length > 0) { - const error = - new QueryError(`ComputeMetric() error: ${metricRes.error}`, { - query: 'COMPUTE_METRIC', - }); - pendingComputeMetric.reject(error); - } else { - pendingComputeMetric.resolve(metricRes); - } - break; - case TPM.TPM_DISABLE_AND_READ_METATRACE: - const metatraceRes = - assertExists(rpc.metatrace) as DisableAndReadMetatraceResult; - assertExists(this.pendingReadMetatrace).resolve(metatraceRes); - this.pendingReadMetatrace = undefined; - break; - default: - console.log( - 'Unexpected TraceProcessor response received: ', rpc.response); - break; - } // switch(rpc.response); - - if (isFinalResponse) { - this.loadingTracker.endLoading(); - } - } - - // TraceProcessor methods below this point. - // The methods below are called by the various controllers in the UI and - // deal with marshalling / unmarshaling requests to/from TraceProcessor. - - - // Push trace data into the engine. The engine is supposed to automatically - // figure out the type of the trace (JSON vs Protobuf). - parse(data: Uint8Array): Promise { - const asyncRes = defer(); - this.pendingParses.push(asyncRes); - const rpc = TraceProcessorRpc.create(); - rpc.request = TPM.TPM_APPEND_TRACE_DATA; - rpc.appendTraceData = data; - this.rpcSendRequest(rpc); - return asyncRes; // Linearize with the worker. - } - - // Notify the engine that we reached the end of the trace. - // Called after the last parse() call. - notifyEof(): Promise { - const asyncRes = defer(); - this.pendingEOFs.push(asyncRes); - const rpc = TraceProcessorRpc.create(); - rpc.request = TPM.TPM_FINALIZE_TRACE_DATA; - this.rpcSendRequest(rpc); - return asyncRes; // Linearize with the worker. - } - - // Updates the TraceProcessor Config. This method creates a new - // TraceProcessor instance, so it should be called before passing any trace - // data. - resetTraceProcessor( - {cropTrackEvents, ingestFtraceInRawTable, analyzeTraceProtoContent}: - TraceProcessorConfig): Promise { - const asyncRes = defer(); - this.pendingResetTraceProcessors.push(asyncRes); - const rpc = TraceProcessorRpc.create(); - rpc.request = TPM.TPM_RESET_TRACE_PROCESSOR; - const args = rpc.resetTraceProcessorArgs = new ResetTraceProcessorArgs(); - args.dropTrackEventDataBefore = cropTrackEvents ? - ResetTraceProcessorArgs.DropTrackEventDataBefore - .TRACK_EVENT_RANGE_OF_INTEREST : - ResetTraceProcessorArgs.DropTrackEventDataBefore.NO_DROP; - args.ingestFtraceInRawTable = ingestFtraceInRawTable; - args.analyzeTraceProtoContent = analyzeTraceProtoContent; - this.rpcSendRequest(rpc); - return asyncRes; - } - - // Resets the trace processor state by destroying any table/views created by - // the UI after loading. - restoreInitialTables(): Promise { - const asyncRes = defer(); - this.pendingRestoreTables.push(asyncRes); - const rpc = TraceProcessorRpc.create(); - rpc.request = TPM.TPM_RESTORE_INITIAL_TABLES; - this.rpcSendRequest(rpc); - return asyncRes; // Linearize with the worker. - } - - // Shorthand for sending a compute metrics request to the engine. - async computeMetric(metrics: string[]): Promise { - const asyncRes = defer(); - this.pendingComputeMetrics.push(asyncRes); - const rpc = TraceProcessorRpc.create(); - rpc.request = TPM.TPM_COMPUTE_METRIC; - const args = rpc.computeMetricArgs = new ComputeMetricArgs(); - args.metricNames = metrics; - args.format = ComputeMetricArgs.ResultFormat.TEXTPROTO; - this.rpcSendRequest(rpc); - return asyncRes; - } - - // Issues a streaming query and retrieve results in batches. - // The returned QueryResult object will be populated over time with batches - // of rows (each batch conveys ~128KB of data and a variable number of rows). - // The caller can decide whether to wait that all batches have been received - // (by awaiting the returned object or calling result.waitAllRows()) or handle - // the rows incrementally. - // - // Example usage: - // const res = engine.query('SELECT foo, bar FROM table'); - // console.log(res.numRows()); // Will print 0 because we didn't await. - // await(res.waitAllRows()); - // console.log(res.numRows()); // Will print the total number of rows. - // - // for (const it = res.iter({foo: NUM, bar:STR}); it.valid(); it.next()) { - // console.log(it.foo, it.bar); - // } - // - // Optional |tag| (usually a component name) can be provided to allow - // attributing trace processor workload to different UI components. - query(sqlQuery: string, tag?: string): Promise&QueryResult { - const rpc = TraceProcessorRpc.create(); - rpc.request = TPM.TPM_QUERY_STREAMING; - rpc.queryArgs = new QueryArgs(); - rpc.queryArgs.sqlQuery = sqlQuery; - if (tag) { - rpc.queryArgs.tag = tag; - } - const result = createQueryResult({ - query: sqlQuery, - }); - this.pendingQueries.push(result); - this.rpcSendRequest(rpc); - return result; - } - - isMetatracingEnabled(): boolean { - return this._isMetatracingEnabled; - } - - enableMetatrace(categories?: perfetto.protos.MetatraceCategories) { - const rpc = TraceProcessorRpc.create(); - rpc.request = TPM.TPM_ENABLE_METATRACE; - if (categories) { - rpc.enableMetatraceArgs = new perfetto.protos.EnableMetatraceArgs(); - rpc.enableMetatraceArgs.categories = categories; - } - this._isMetatracingEnabled = true; - this.rpcSendRequest(rpc); - } - - stopAndGetMetatrace(): Promise { - // If we are already finalising a metatrace, ignore the request. - if (this.pendingReadMetatrace) { - return Promise.reject(new Error('Already finalising a metatrace')); - } - - const result = defer(); - - const rpc = TraceProcessorRpc.create(); - rpc.request = TPM.TPM_DISABLE_AND_READ_METATRACE; - this._isMetatracingEnabled = false; - this.pendingReadMetatrace = result; - this.rpcSendRequest(rpc); - return result; - } - - // Marshals the TraceProcessorRpc request arguments and sends the request - // to the concrete Engine (Wasm or HTTP). - private rpcSendRequest(rpc: TraceProcessorRpc) { - rpc.seq = this.txSeqId++; - // Each message is wrapped in a TraceProcessorRpcStream to add the varint - // preamble with the size, which allows tokenization on the other end. - const outerProto = TraceProcessorRpcStream.create(); - outerProto.msg.push(rpc); - const buf = TraceProcessorRpcStream.encode(outerProto).finish(); - this.loadingTracker.beginLoading(); - this.rpcSendRequestBytes(buf); - } - - // TODO(hjd): When streaming must invalidate this somehow. - async getCpus(): Promise { - if (!this._cpus) { - const cpus = []; - const queryRes = await this.query( - 'select distinct(cpu) as cpu from sched order by cpu;'); - for (const it = queryRes.iter({cpu: NUM}); it.valid(); it.next()) { - cpus.push(it.cpu); - } - this._cpus = cpus; - } - return this._cpus; - } - - async getNumberOfGpus(): Promise { - if (!this._numGpus) { - const result = await this.query(` - select count(distinct(gpu_id)) as gpuCount - from gpu_counter_track - where name = 'gpufreq'; - `); - this._numGpus = result.firstRow({gpuCount: NUM}).gpuCount; - } - return this._numGpus; - } - - // TODO: This should live in code that's more specific to chrome, instead of - // in engine. - async getNumberOfProcesses(): Promise { - const result = await this.query('select count(*) as cnt from process;'); - return result.firstRow({cnt: NUM}).cnt; - } - - async getTraceTimeBounds(): Promise { - const result = await this.query( - `select start_ts as startTs, end_ts as endTs from trace_bounds`); - const bounds = result.firstRow({ - startTs: NUM, - endTs: NUM, - }); - return new TimeSpan(bounds.startTs / 1e9, bounds.endTs / 1e9); - } - - async getTracingMetadataTimeBounds(): Promise { - const queryRes = await this.query(`select - name, - int_value as intValue - from metadata - where name = 'tracing_started_ns' or name = 'tracing_disabled_ns' - or name = 'all_data_source_started_ns'`); - let startBound = -Infinity; - let endBound = Infinity; - const it = queryRes.iter({'name': STR, 'intValue': NUM_NULL}); - for (; it.valid(); it.next()) { - const columnName = it.name; - const timestamp = it.intValue; - if (timestamp === null) continue; - if (columnName === 'tracing_disabled_ns') { - endBound = Math.min(endBound, timestamp / 1e9); - } else { - startBound = Math.max(startBound, timestamp / 1e9); - } - } - - return new TimeSpan(startBound, endBound); - } - - getProxy(tag: string): EngineProxy { - return new EngineProxy(this, tag); - } -} - -// Lightweight wrapper over Engine exposing only `query` method and annotating -// all queries going through it with a tag. -export class EngineProxy { - private engine: Engine; - private tag: string; - - constructor(engine: Engine, tag: string) { - this.engine = engine; - this.tag = tag; - } - - query(sqlQuery: string, tag?: string): Promise&QueryResult { - return this.engine.query(sqlQuery, tag || this.tag); - } - - get engineId(): string { - return this.engine.id; - } -} diff --git a/third_party/perfetto/ui/src/common/errors.ts b/third_party/perfetto/ui/src/common/errors.ts deleted file mode 100644 index 9813b0e0ad3f..000000000000 --- a/third_party/perfetto/ui/src/common/errors.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Errors in JavaScript are (sadly) extremely free form. Three common -// cases are: -// - the error is a string -// - the error object itself has a 'message' field (normally because -// its an instance of Error or a subclass of Error). -// - the outer object wraps an error under the 'error' key. As in: -// https://google.github.io/styleguide/jsoncstyleguide.xml#Reserved_Property_Names_in_the_error_object -// TODO(hjd): Can this last case actually occur for us? Maybe this -// too closely followed the code from flash station? -interface ErrorLikeObject { - message?: unknown; - error?: {message?: unknown}; - stack?: unknown; - code?: unknown; -} - -// Attempt to coerce an error object into a string message. -// Sometimes an error message is wrapped in an Error object, sometimes not. -export function getErrorMessage(e: unknown|undefined|null) { - if (e && typeof e === 'object') { - const errorObject = e as ErrorLikeObject; - if (errorObject.message) { // regular Error Object - return String(errorObject.message); - } else if (errorObject.error?.message) { // API result - return String(errorObject.error.message); - } - } - const asString = String(e); - if (asString === '[object Object]') { - try { - return JSON.stringify(e); - } catch (stringifyError) { - // ignore failures and just fall through - } - } - return asString; -} - -// Occasionally operations using the cache API throw: -// 'UnknownError: Unexpected internal error. {}' -// It's not clear under which circumstances this can occur. A dive of -// the Chromium code didn't shed much light: -// https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/cache_storage/cache_storage_error.cc;l=26;drc=4cfe86482b000e848009077783ba35f83f3c3cfe -// https://source.chromium.org/chromium/chromium/src/+/main:content/browser/cache_storage/cache_storage_cache.cc;l=1686;drc=ab68c05beb790d04d1cb7fd8faa0a197fb40d399 -// Given the error is not actionable at present and caching is 'best -// effort' in any case ignore this error. We will want to throw for -// errors in general though so as not to hide errors we actually could -// fix. -// See b/227785665 for an example. -export function ignoreCacheUnactionableErrors(e: unknown, result: T): T { - if (getErrorMessage(e).includes('UnknownError')) { - return result; - } else { - throw e; - } -} diff --git a/third_party/perfetto/ui/src/common/event_set.ts b/third_party/perfetto/ui/src/common/event_set.ts deleted file mode 100644 index 3971fd8e147e..000000000000 --- a/third_party/perfetto/ui/src/common/event_set.ts +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// A single value. These are often retived from trace_processor so -// need to map to the related sqlite type: -// null = NULL, string = TEXT, number = INTEGER/REAL, boolean = INTEGER -export type Primitive = null|string|boolean|number; - -export const NullType = null; -export const NumType = 0 as const; -export const StrType = 'str' as const; -export const IdType = 'id' as const; -export const BoolType = true as const; - -// Values may be of any of the above types: -type KeyType = - typeof NumType|typeof StrType|typeof NullType|typeof IdType|typeof BoolType; - -// KeySet is a specification for the key/value pairs on an Event. -// - Every event must have a string ID. -// - In addition Events may have 1 or more key/value pairs. -// The *specification* for the key/value pair has to be *precisely* one -// of the KeySet constants above. So: -// const thisTypeChecks: KeySet = { id: IdType, foo: StrType }; -// const thisDoesNot: KeySet = { id: IdType, foo: "bar" }; -// Since although are is a string it's not a KeySet. -export type KeySet = { - readonly id: typeof IdType, - readonly [key: string]: KeyType, -}; - -export interface EmptyKeySet extends KeySet { - readonly id: typeof IdType; -} - -// A particular key/value pair on an Event matches the relevant entry -// on the KeySet if the KeyType and the value type 'match': -// IdType => string -// StrType => string -// BoolType => boolean -// NullType => null -// NumType => number -type IsExactly = P extends Q ? (Q extends P ? any : never) : never; -type IsId = T extends IsExactly? string : never; -type IsStr = T extends IsExactly? string : never; -type IsNum = T extends IsExactly? number : never; -type IsBool = T extends IsExactly? boolean : never; -type IsNull = T extends IsExactly? null : never; -type MapType = IsId|IsStr|IsNum|IsBool|IsNull; -type ConformingValue = T extends MapType? MapType: void; - -// A single trace Event. -// Events have: -// - A globally unique identifier `id`. -// - Zero or more key/value pairs. -// Note: Events do *not* have to have all possible keys/value pairs for -// the given id. It is expected that users will only materialise the -// key/value pairs relevant to the specific use case at hand. -export type UntypedEvent = { - readonly id: string, - readonly [key: string]: Primitive, -}; - -export type Event = { - [Property in keyof K]: ConformingValue; -}; - -type KeyUnion = P&Q; - -// An EventSet is a: -// - ordered -// - immutable -// - subset -// of events in the trace. -export interface EventSet

{ - // All possible keys for Events in this EventSet. - readonly keys: KeySet; - - // Methods for refining the set. - // Note: these are all synchronous - we expect the cost (and hence - // any asynchronous queries) to be deferred to analysis time. - filter(...filters: Filter[]): EventSet

; - sort(...sorts: Sort[]): EventSet

; - union(other: EventSet): EventSet>; - intersect(other: EventSet): EventSet>; - - // Methods for analysing the set. - // Note: these are all asynchronous - it's expected that these will - // often have to do queries. - count(): Promise; - isEmpty(): Promise; - materialise(keys: T, offset?: number, limit?: number): - Promise>; -} - -export type UntypedEventSet = EventSet; - -// An expression that operates on an Event and produces a Primitive as -// output. Expressions have to work both in JavaScript and in SQL. -// In SQL users can use buildQueryFragment to convert the expression -// into a snippet of SQL. For JavaScript they call execute(). In both -// cases you need to know which keys the expression uses, for this call -// `freeVariables`. -export interface Expr { - // Return a fragment of SQL that can be used to evaluate the - // expression. `binding` maps key names to column names in the - // resulting SQL. The caller must ensure that binding includes at - // least all the keys from `freeVariables`. - buildQueryFragment(binding: Map): string; - - // Run the expression on an Event. The caller must ensure that event - // has all the keys from `freeVariables` materialised. - execute(event: UntypedEvent): Primitive; - - // Returns the set of keys used in this expression. - // For example in an expression representing `(foo + 4) * bar` - // freeVariables would return the set {'foo', 'bar'}. - freeVariables(): Set; -} - -// A filter is a (normally boolean) expression. -export type Filter = Expr; - -// Sorting direction. -export enum Direction { - ASC, - DESC, -} - -// A sort is an expression combined with a direction: -export interface Sort { - direction: Direction; - expression: Expr; -} - -// An EventSet where the Event are accesible synchronously. -interface ConcreteEventSet extends EventSet { - readonly events: Event[]; -} - -export type UntypedConcreteEventSet = ConcreteEventSet; diff --git a/third_party/perfetto/ui/src/common/event_set_nocompile_test.ts b/third_party/perfetto/ui/src/common/event_set_nocompile_test.ts deleted file mode 100644 index e5f74d7de910..000000000000 --- a/third_party/perfetto/ui/src/common/event_set_nocompile_test.ts +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - BoolType, - Event, - IdType, - KeySet, - NullType, - NumType, - StrType, -} from './event_set'; - -export function keySetMustHaveId(): KeySet { - // @ts-expect-error - const ks: KeySet = {}; - return ks; -} - -export function keySetMustHaveCorrectIdType(): KeySet { - const ks: KeySet = { - // @ts-expect-error - id: StrType, - }; - return ks; -} - -export function eventMustHaveAllKeys(): Event { - const ks = { - id: IdType, - foo: StrType, - }; - - // @ts-expect-error - const event: Event = { - id: 'myid', - }; - - return event; -} - -export function eventMayHaveNonKeyTypeValues(): Event { - const ks = { - id: IdType, - foo: StrType, - bar: NumType, - baz: BoolType, - xyzzy: NullType, - }; - - const event: Event = { - id: 'myid', - foo: 'foo', - bar: 32, - baz: false, - xyzzy: null, - }; - - return event; -} diff --git a/third_party/perfetto/ui/src/common/feature_flags.ts b/third_party/perfetto/ui/src/common/feature_flags.ts deleted file mode 100644 index de2cc7c1afdc..000000000000 --- a/third_party/perfetto/ui/src/common/feature_flags.ts +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This file should not import anything else. Since the flags will be used from -// ~everywhere and the are "statically" initialized (i.e. files construct Flags -// at import time) if this file starts importing anything we will quickly run -// into issues with initialization order which will be a pain. - -interface FlagSettings { - id: string; - defaultValue: boolean; - description: string; - name?: string; - devOnly?: boolean; -} - -export enum OverrideState { - DEFAULT = 'DEFAULT', - TRUE = 'OVERRIDE_TRUE', - FALSE = 'OVERRIDE_FALSE', -} - -export interface FlagStore { - load(): object; - save(o: object): void; -} - -// Stored state for a number of flags. -interface FlagOverrides { - [id: string]: OverrideState; -} - -// Check if the given object is a valid FlagOverrides. -// This is necessary since someone could modify the persisted flags -// behind our backs. -function isFlagOverrides(o: object): o is FlagOverrides { - const states = - [OverrideState.TRUE.toString(), OverrideState.FALSE.toString()]; - for (const v of Object.values(o)) { - if (typeof v !== 'string' || !states.includes(v)) { - return false; - } - } - return true; -} - -class Flags { - private store: FlagStore; - private flags: Map; - private overrides: FlagOverrides; - - constructor(store: FlagStore) { - this.store = store; - this.flags = new Map(); - this.overrides = {}; - this.load(); - } - - register(settings: FlagSettings): Flag { - const id = settings.id; - if (this.flags.has(id)) { - throw new Error(`Flag with id "${id}" is already registered.`); - } - - const saved = this.overrides[id]; - const state = saved === undefined ? OverrideState.DEFAULT : saved; - const flag = new FlagImpl(this, state, settings); - this.flags.set(id, flag); - return flag; - } - - allFlags(): Flag[] { - const includeDevFlags = - ['127.0.0.1', '::1', 'localhost'].includes(window.location.hostname); - - let flags = [...this.flags.values()]; - flags = flags.filter((flag) => includeDevFlags || !flag.devOnly); - flags.sort((a, b) => a.name.localeCompare(b.name)); - return flags; - } - - resetAll() { - for (const flag of this.flags.values()) { - flag.state = OverrideState.DEFAULT; - } - this.save(); - } - - load(): void { - const o = this.store.load(); - if (isFlagOverrides(o)) { - this.overrides = o; - } - } - - save(): void { - for (const flag of this.flags.values()) { - if (flag.isOverridden()) { - this.overrides[flag.id] = flag.state; - } else { - delete this.overrides[flag.id]; - } - } - - this.store.save(this.overrides); - } -} - -export interface Flag { - // A unique identifier for this flag ("magicSorting") - readonly id: string; - - // The name of the flag the user sees ("New track sorting algorithm") - readonly name: string; - - // A longer description which is displayed to the user. - // "Sort tracks using an embedded tfLite model based on your expression - // while waiting for the trace to load." - readonly description: string; - - // Whether the flag defaults to true or false. - // If !flag.isOverridden() then flag.get() === flag.defaultValue - readonly defaultValue: boolean; - - // Get the current value of the flag. - get(): boolean; - - // Override the flag and persist the new value. - set(value: boolean): void; - - // If the flag has been overridden. - // Note: A flag can be overridden to its default value. - isOverridden(): boolean; - - // Reset the flag to its default setting. - reset(): void; - - // Get the current state of the flag. - overriddenState(): OverrideState; -} - -class FlagImpl implements Flag { - registry: Flags; - state: OverrideState; - - readonly id: string; - readonly name: string; - readonly description: string; - readonly defaultValue: boolean; - readonly devOnly: boolean; - - constructor(registry: Flags, state: OverrideState, settings: FlagSettings) { - this.registry = registry; - this.id = settings.id; - this.state = state; - this.description = settings.description; - this.defaultValue = settings.defaultValue; - this.name = settings.name || settings.id; - this.devOnly = settings.devOnly || false; - } - - get(): boolean { - switch (this.state) { - case OverrideState.TRUE: - return true; - case OverrideState.FALSE: - return false; - case OverrideState.DEFAULT: - default: - return this.defaultValue; - } - } - - set(value: boolean): void { - const next = value ? OverrideState.TRUE : OverrideState.FALSE; - if (this.state === next) { - return; - } - this.state = next; - this.registry.save(); - } - - overriddenState(): OverrideState { - return this.state; - } - - reset() { - this.state = OverrideState.DEFAULT; - this.registry.save(); - } - - isOverridden(): boolean { - return this.state !== OverrideState.DEFAULT; - } -} - -class LocalStorageStore implements FlagStore { - static KEY = 'perfettoFeatureFlags'; - - load(): object { - const s = localStorage.getItem(LocalStorageStore.KEY); - let parsed: object; - try { - parsed = JSON.parse(s || '{}'); - } catch (e) { - return {}; - } - if (typeof parsed !== 'object' || parsed === null) { - return {}; - } - return parsed; - } - - save(o: object): void { - const s = JSON.stringify(o); - localStorage.setItem(LocalStorageStore.KEY, s); - } -} - -export const FlagsForTesting = Flags; -export const featureFlags = new Flags(new LocalStorageStore()); - -export const PERF_SAMPLE_FLAG = featureFlags.register({ - id: 'perfSampleFlamegraph', - name: 'Perf Sample Flamegraph', - description: 'Show flamegraph generated by a perf sample.', - defaultValue: true, -}); - -export const RECORDING_V2_FLAG = featureFlags.register({ - id: 'recordingv2', - name: 'Recording V2', - description: 'Record using V2 interface', - defaultValue: false, -}); diff --git a/third_party/perfetto/ui/src/common/feature_flags_unittest.ts b/third_party/perfetto/ui/src/common/feature_flags_unittest.ts deleted file mode 100644 index d2cb97beb81b..000000000000 --- a/third_party/perfetto/ui/src/common/feature_flags_unittest.ts +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {FlagsForTesting as Flags, FlagStore} from './feature_flags'; - -class TestFlagStore implements FlagStore { - o: object = {}; - - load(): object { - return this.o; - } - - save(o: object): void { - this.o = o; - } -} - -test('create flag', () => { - const flags = new Flags(new TestFlagStore()); - const myFlag = flags.register({ - id: 'myFlag', - defaultValue: false, - description: '', - }); - expect(myFlag.get()).toEqual(false); - expect(myFlag.isOverridden()).toEqual(false); -}); - -test('registering the same flag twice is an error', () => { - const flags = new Flags(new TestFlagStore()); - flags.register({ - id: 'foo', - defaultValue: false, - description: '', - }); - expect(() => flags.register({ - id: 'foo', - defaultValue: false, - description: '', - })).toThrow('Flag with id "foo" is already registered.'); -}); - -test('can override', () => { - const flags = new Flags(new TestFlagStore()); - const foo = flags.register({ - id: 'foo', - defaultValue: false, - description: '', - }); - foo.set(true); - expect(foo.isOverridden()).toEqual(true); - expect(foo.get()).toEqual(true); -}); - -test('overrides are persisted', () => { - const store = new TestFlagStore(); - const flagsA = new Flags(store); - const fooA = flagsA.register({ - id: 'foo', - defaultValue: true, - description: 'some description', - }); - - fooA.set(true); - - const flagsB = new Flags(store); - const fooB = flagsB.register({ - id: 'foo', - defaultValue: false, - description: 'a new description', - }); - - expect(fooB.get()).toEqual(true); - expect(fooB.isOverridden()).toEqual(true); -}); - -test('flags can be reset', () => { - const flags = new Flags(new TestFlagStore()); - const foo = flags.register({ - id: 'foo', - defaultValue: false, - description: 'some description', - }); - - foo.set(false); - foo.reset(); - expect(foo.get()).toEqual(false); - expect(foo.isOverridden()).toEqual(false); -}); - -test('corrupt store is ignored', () => { - class Store { - load(): object { - return {'foo': 'bad state'}; - } - - save(_: object): void {} - } - const flags = new Flags(new Store()); - const foo = flags.register({ - id: 'foo', - defaultValue: false, - description: 'some description', - }); - - expect(foo.isOverridden()).toEqual(false); -}); diff --git a/third_party/perfetto/ui/src/common/flamegraph_unittest.ts b/third_party/perfetto/ui/src/common/flamegraph_unittest.ts deleted file mode 100644 index a9034bff7e0d..000000000000 --- a/third_party/perfetto/ui/src/common/flamegraph_unittest.ts +++ /dev/null @@ -1,1073 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {mergeCallsites} from './flamegraph_util'; -import {CallsiteInfo} from './state'; - -test('zeroCallsitesMerged', () => { - const callsites: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: -1, - name: 'B', - depth: 0, - totalSize: 8, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: 'A3', - depth: 1, - totalSize: 4, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 4, - parentId: 2, - name: 'B4', - depth: 1, - totalSize: 4, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - const mergedCallsites = mergeCallsites(callsites, 5); - - // Small callsites are not next ot each other, nothing should be changed. - expect(mergedCallsites).toEqual(callsites); -}); - -test('zeroCallsitesMerged2', () => { - const callsites: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: -1, - name: 'B', - depth: 0, - totalSize: 8, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: 'A3', - depth: 1, - totalSize: 6, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 4, - parentId: 1, - name: 'A4', - depth: 1, - totalSize: 4, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 5, - parentId: 2, - name: 'B5', - depth: 1, - totalSize: 8, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - const mergedCallsites = mergeCallsites(callsites, 5); - - // Small callsites are not next ot each other, nothing should be changed. - expect(mergedCallsites).toEqual(callsites); -}); - -test('twoCallsitesMerged', () => { - const callsites: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: 1, - name: 'A2', - depth: 1, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: 'A3', - depth: 1, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - const mergedCallsites = mergeCallsites(callsites, 6); - - expect(mergedCallsites).toEqual([ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: 1, - name: '[merged]', - depth: 1, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: true, - highlighted: false, - }, - ]); -}); - -test('manyCallsitesMerged', () => { - const callsites: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: 1, - name: 'A2', - depth: 1, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: 'A3', - depth: 1, - totalSize: 3, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 4, - parentId: 1, - name: 'A4', - depth: 1, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 5, - parentId: 1, - name: 'A5', - depth: 1, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 6, - parentId: 3, - name: 'A36', - depth: 2, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 7, - parentId: 4, - name: 'A47', - depth: 2, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 8, - parentId: 5, - name: 'A58', - depth: 2, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - const expectedMergedCallsites: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: 1, - name: 'A2', - depth: 1, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: '[merged]', - depth: 1, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: true, - highlighted: false, - }, - { - id: 6, - parentId: 3, - name: '[merged]', - depth: 2, - totalSize: 3, - selfSize: 0, - mapping: 'x', - merged: true, - highlighted: false, - }, - ]; - - const mergedCallsites = mergeCallsites(callsites, 4); - - // In this case, callsites A3, A4 and A5 should be merged since they are - // smaller then 4 and are on same depth with same parent. Callsites A36, A47 - // and A58 should also be merged since their parents are merged. - expect(mergedCallsites).toEqual(expectedMergedCallsites); -}); - -test('manyCallsitesMergedWithoutChildren', () => { - const callsites: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: -1, - name: 'B', - depth: 0, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: 'A3', - depth: 1, - totalSize: 3, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 4, - parentId: 1, - name: 'A4', - depth: 1, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 5, - parentId: 1, - name: 'A5', - depth: 1, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 6, - parentId: 2, - name: 'B6', - depth: 1, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 7, - parentId: 4, - name: 'A47', - depth: 2, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 8, - parentId: 6, - name: 'B68', - depth: 2, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - const expectedMergedCallsites: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: -1, - name: 'B', - depth: 0, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: '[merged]', - depth: 1, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: true, - highlighted: false, - }, - { - id: 6, - parentId: 2, - name: 'B6', - depth: 1, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 7, - parentId: 3, - name: 'A47', - depth: 2, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 8, - parentId: 6, - name: 'B68', - depth: 2, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - const mergedCallsites = mergeCallsites(callsites, 4); - - // In this case, callsites A3, A4 and A5 should be merged since they are - // smaller then 4 and are on same depth with same parent. Callsite A47 - // should not be merged with B68 althought they are small because they don't - // have sam parent. A47 should now have parent A3 because A4 is merged. - expect(mergedCallsites).toEqual(expectedMergedCallsites); -}); - -test('smallCallsitesNotNextToEachOtherInArray', () => { - const callsites: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 20, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: 1, - name: 'A2', - depth: 1, - totalSize: 8, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: 'A3', - depth: 1, - totalSize: 1, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 4, - parentId: 1, - name: 'A4', - depth: 1, - totalSize: 8, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 5, - parentId: 1, - name: 'A5', - depth: 1, - totalSize: 3, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - const expectedMergedCallsites: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 20, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: 1, - name: 'A2', - depth: 1, - totalSize: 8, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: '[merged]', - depth: 1, - totalSize: 4, - selfSize: 0, - mapping: 'x', - merged: true, - highlighted: false, - }, - { - id: 4, - parentId: 1, - name: 'A4', - depth: 1, - totalSize: 8, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - const mergedCallsites = mergeCallsites(callsites, 4); - - // In this case, callsites A3, A4 and A5 should be merged since they are - // smaller then 4 and are on same depth with same parent. Callsite A47 - // should not be merged with B68 althought they are small because they don't - // have sam parent. A47 should now have parent A3 because A4 is merged. - expect(mergedCallsites).toEqual(expectedMergedCallsites); -}); - -test('smallCallsitesNotMerged', () => { - const callsites: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: 1, - name: 'A2', - depth: 1, - totalSize: 2, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: 'A3', - depth: 1, - totalSize: 2, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - const mergedCallsites = mergeCallsites(callsites, 1); - - expect(mergedCallsites).toEqual(callsites); -}); - -test('mergingRootCallsites', () => { - const callsites: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: -1, - name: 'B', - depth: 0, - totalSize: 2, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - const mergedCallsites = mergeCallsites(callsites, 20); - - expect(mergedCallsites).toEqual([ - { - id: 1, - parentId: -1, - name: '[merged]', - depth: 0, - totalSize: 12, - selfSize: 0, - mapping: 'x', - merged: true, - highlighted: false, - }, - ]); -}); - -test('largerFlamegraph', () => { - const data: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 60, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: -1, - name: 'B', - depth: 0, - totalSize: 40, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: 'A3', - depth: 1, - totalSize: 25, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 4, - parentId: 1, - name: 'A4', - depth: 1, - totalSize: 15, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 5, - parentId: 1, - name: 'A5', - depth: 1, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 6, - parentId: 1, - name: 'A6', - depth: 1, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 7, - parentId: 2, - name: 'B7', - depth: 1, - totalSize: 30, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 8, - parentId: 2, - name: 'B8', - depth: 1, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 9, - parentId: 3, - name: 'A39', - depth: 2, - totalSize: 20, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 10, - parentId: 4, - name: 'A410', - depth: 2, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 11, - parentId: 4, - name: 'A411', - depth: 2, - totalSize: 3, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 12, - parentId: 4, - name: 'A412', - depth: 2, - totalSize: 2, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 13, - parentId: 5, - name: 'A513', - depth: 2, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 14, - parentId: 5, - name: 'A514', - depth: 2, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 15, - parentId: 7, - name: 'A715', - depth: 2, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 16, - parentId: 7, - name: 'A716', - depth: 2, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 17, - parentId: 7, - name: 'A717', - depth: 2, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 18, - parentId: 7, - name: 'A718', - depth: 2, - totalSize: 5, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 19, - parentId: 9, - name: 'A919', - depth: 3, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 20, - parentId: 17, - name: 'A1720', - depth: 3, - totalSize: 2, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - const expectedData: CallsiteInfo[] = [ - { - id: 1, - parentId: -1, - name: 'A', - depth: 0, - totalSize: 60, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 2, - parentId: -1, - name: 'B', - depth: 0, - totalSize: 40, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 3, - parentId: 1, - name: 'A3', - depth: 1, - totalSize: 25, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 4, - parentId: 1, - name: '[merged]', - depth: 1, - totalSize: 35, - selfSize: 0, - mapping: 'x', - merged: true, - highlighted: false, - }, - { - id: 7, - parentId: 2, - name: 'B7', - depth: 1, - totalSize: 30, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 8, - parentId: 2, - name: 'B8', - depth: 1, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 9, - parentId: 3, - name: 'A39', - depth: 2, - totalSize: 20, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 10, - parentId: 4, - name: '[merged]', - depth: 2, - totalSize: 25, - selfSize: 0, - mapping: 'x', - merged: true, - highlighted: false, - }, - { - id: 15, - parentId: 7, - name: '[merged]', - depth: 2, - totalSize: 25, - selfSize: 0, - mapping: 'x', - merged: true, - highlighted: false, - }, - { - id: 19, - parentId: 9, - name: 'A919', - depth: 3, - totalSize: 10, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - { - id: 20, - parentId: 15, - name: 'A1720', - depth: 3, - totalSize: 2, - selfSize: 0, - mapping: 'x', - merged: false, - highlighted: false, - }, - ]; - - // In this case, on depth 1, callsites A4, A5 and A6 should be merged and - // initiate merging of their children A410, A411, A412, A513, A514. On depth2, - // callsites A715, A716, A717 and A718 should be merged. - const actualData = mergeCallsites(data, 16); - - expect(actualData).toEqual(expectedData); -}); diff --git a/third_party/perfetto/ui/src/common/flamegraph_util.ts b/third_party/perfetto/ui/src/common/flamegraph_util.ts deleted file mode 100644 index 265834110bf7..000000000000 --- a/third_party/perfetto/ui/src/common/flamegraph_util.ts +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {CallsiteInfo} from './state'; - -export const SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY = 'SPACE'; -export const ALLOC_SPACE_MEMORY_ALLOCATED_KEY = 'ALLOC_SPACE'; -export const OBJECTS_ALLOCATED_NOT_FREED_KEY = 'OBJECTS'; -export const OBJECTS_ALLOCATED_KEY = 'ALLOC_OBJECTS'; -export const PERF_SAMPLES_KEY = 'PERF_SAMPLES'; - -export const DEFAULT_VIEWING_OPTION = SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY; - -export function expandCallsites( - data: CallsiteInfo[], clickedCallsiteIndex: number): CallsiteInfo[] { - if (clickedCallsiteIndex === -1) return data; - const expandedCallsites: CallsiteInfo[] = []; - if (clickedCallsiteIndex >= data.length || clickedCallsiteIndex < -1) { - return expandedCallsites; - } - const clickedCallsite = data[clickedCallsiteIndex]; - expandedCallsites.unshift(clickedCallsite); - // Adding parents - let parentId = clickedCallsite.parentId; - while (parentId > -1) { - expandedCallsites.unshift(data[parentId]); - parentId = data[parentId].parentId; - } - // Adding children - const parents: number[] = []; - parents.push(clickedCallsiteIndex); - for (let i = clickedCallsiteIndex + 1; i < data.length; i++) { - const element = data[i]; - if (parents.includes(element.parentId)) { - expandedCallsites.push(element); - parents.push(element.id); - } - } - return expandedCallsites; -} - -// Merge callsites that have approximately width less than -// MIN_PIXEL_DISPLAYED. All small callsites in the same depth and with same -// parent will be merged to one with total size of all merged callsites. -export function mergeCallsites(data: CallsiteInfo[], minSizeDisplayed: number) { - const mergedData: CallsiteInfo[] = []; - const mergedCallsites: Map = new Map(); - for (let i = 0; i < data.length; i++) { - // When a small callsite is found, it will be merged with other small - // callsites of the same depth. So if the current callsite has already been - // merged we can skip it. - if (mergedCallsites.has(data[i].id)) { - continue; - } - const copiedCallsite = copyCallsite(data[i]); - copiedCallsite.parentId = - getCallsitesParentHash(copiedCallsite, mergedCallsites); - - let mergedAny = false; - // If current callsite is small, find other small callsites with same depth - // and parent and merge them into the current one, marking them as merged. - if (copiedCallsite.totalSize <= minSizeDisplayed && i + 1 < data.length) { - let j = i + 1; - let nextCallsite = data[j]; - while (j < data.length && copiedCallsite.depth === nextCallsite.depth) { - if (copiedCallsite.parentId === - getCallsitesParentHash(nextCallsite, mergedCallsites) && - nextCallsite.totalSize <= minSizeDisplayed) { - copiedCallsite.totalSize += nextCallsite.totalSize; - mergedCallsites.set(nextCallsite.id, copiedCallsite.id); - mergedAny = true; - } - j++; - nextCallsite = data[j]; - } - if (mergedAny) { - copiedCallsite.name = '[merged]'; - copiedCallsite.merged = true; - } - } - mergedData.push(copiedCallsite); - } - return mergedData; -} - -function copyCallsite(callsite: CallsiteInfo): CallsiteInfo { - return { - id: callsite.id, - parentId: callsite.parentId, - depth: callsite.depth, - name: callsite.name, - totalSize: callsite.totalSize, - mapping: callsite.mapping, - selfSize: callsite.selfSize, - merged: callsite.merged, - highlighted: callsite.highlighted, - location: callsite.location, - }; -} - -function getCallsitesParentHash( - callsite: CallsiteInfo, map: Map): number { - return map.has(callsite.parentId) ? +map.get(callsite.parentId)! : - callsite.parentId; -} -export function findRootSize(data: CallsiteInfo[]) { - let totalSize = 0; - let i = 0; - while (i < data.length && data[i].depth === 0) { - totalSize += data[i].totalSize; - i++; - } - return totalSize; -} diff --git a/third_party/perfetto/ui/src/common/hash.ts b/third_party/perfetto/ui/src/common/hash.ts deleted file mode 100644 index 821dd3128afb..000000000000 --- a/third_party/perfetto/ui/src/common/hash.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export function hash(s: string, max: number): number { - let hash = 0x811c9dc5 & 0xfffffff; - for (let i = 0; i < s.length; i++) { - hash ^= s.charCodeAt(i); - hash = (hash * 16777619) & 0xffffffff; - } - return Math.abs(hash) % max; -} diff --git a/third_party/perfetto/ui/src/common/http_rpc_engine.ts b/third_party/perfetto/ui/src/common/http_rpc_engine.ts deleted file mode 100644 index ddeec7c12f6b..000000000000 --- a/third_party/perfetto/ui/src/common/http_rpc_engine.ts +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {fetchWithTimeout} from '../base/http_utils'; -import {assertExists} from '../base/logging'; -import {StatusResult} from '../common/protos'; - -import {Engine, LoadingTracker} from './engine'; - -export const RPC_URL = 'http://127.0.0.1:9001/'; -export const WS_URL = 'ws://127.0.0.1:9001/websocket'; - -const RPC_CONNECT_TIMEOUT_MS = 2000; - -export interface HttpRpcState { - connected: boolean; - status?: StatusResult; - failure?: string; -} - -export class HttpRpcEngine extends Engine { - readonly id: string; - errorHandler: (err: string) => void = () => {}; - private requestQueue = new Array(); - private websocket?: WebSocket; - private connected = false; - - constructor(id: string, loadingTracker?: LoadingTracker) { - super(loadingTracker); - this.id = id; - } - - rpcSendRequestBytes(data: Uint8Array): void { - if (this.websocket === undefined) { - this.websocket = new WebSocket(WS_URL); - this.websocket.onopen = () => this.onWebsocketConnected(); - this.websocket.onmessage = (e) => this.onWebsocketMessage(e); - this.websocket.onclose = (e) => - this.errorHandler(`Websocket closed (${e.code}: ${e.reason})`); - this.websocket.onerror = (e) => - this.errorHandler(`WebSocket error: ${e}`); - } - - if (this.connected) { - this.websocket.send(data); - } else { - this.requestQueue.push(data); // onWebsocketConnected() will flush this. - } - } - - private onWebsocketConnected() { - for (;;) { - const queuedMsg = this.requestQueue.shift(); - if (queuedMsg === undefined) break; - assertExists(this.websocket).send(queuedMsg); - } - this.connected = true; - } - - private onWebsocketMessage(e: MessageEvent) { - assertExists(e.data as Blob).arrayBuffer().then((buf) => { - super.onRpcResponseBytes(new Uint8Array(buf)); - }); - } - - static async checkConnection(): Promise { - const httpRpcState: HttpRpcState = {connected: false}; - console.info( - `It's safe to ignore the ERR_CONNECTION_REFUSED on ${RPC_URL} below. ` + - `That might happen while probing the external native accelerator. The ` + - `error is non-fatal and unlikely to be the culprit for any UI bug.`); - try { - const resp = await fetchWithTimeout( - RPC_URL + 'status', - {method: 'post', cache: 'no-cache'}, - RPC_CONNECT_TIMEOUT_MS); - if (resp.status !== 200) { - httpRpcState.failure = `${resp.status} - ${resp.statusText}`; - } else { - const buf = new Uint8Array(await resp.arrayBuffer()); - httpRpcState.connected = true; - httpRpcState.status = StatusResult.decode(buf); - } - } catch (err) { - httpRpcState.failure = `${err}`; - } - return httpRpcState; - } -} diff --git a/third_party/perfetto/ui/src/common/immer_init.ts b/third_party/perfetto/ui/src/common/immer_init.ts deleted file mode 100644 index 9b8817c61eb2..000000000000 --- a/third_party/perfetto/ui/src/common/immer_init.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {enableMapSet, enablePatches, setAutoFreeze} from 'immer'; - -export function initializeImmerJs() { - enablePatches(); - - // TODO(primiano): re-enable this, requires fixing some bugs that this bubbles - // up. This is a new feature of immer which freezes object after a produce(). - // Unfortunately we piled up a bunch of bugs where we shallow-copy objects - // from the global state (which is frozen) and later try to update the copies. - // By doing so, we accidentally the local copy of global state, which is - // supposed to be immutable. - setAutoFreeze(false); - - enableMapSet(); -} diff --git a/third_party/perfetto/ui/src/common/logs.ts b/third_party/perfetto/ui/src/common/logs.ts deleted file mode 100644 index 0fc2fae9ef3c..000000000000 --- a/third_party/perfetto/ui/src/common/logs.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export const LogExistsKey = 'log-exists'; -export const LogBoundsKey = 'log-bounds'; -export const LogEntriesKey = 'log-entries'; - -export interface LogExists { exists: boolean; } - -export interface LogBounds { - startTs: number; - endTs: number; - firstRowTs: number; - lastRowTs: number; - total: number; -} - -export interface LogEntries { - offset: number; - timestamps: number[]; - priorities: number[]; - tags: string[]; - messages: string[]; - isHighlighted: boolean[]; - processName: string[]; -} diff --git a/third_party/perfetto/ui/src/common/metatracing.ts b/third_party/perfetto/ui/src/common/metatracing.ts deleted file mode 100644 index c26371d221ce..000000000000 --- a/third_party/perfetto/ui/src/common/metatracing.ts +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {PerfettoMetatrace, Trace, TracePacket} from '../common/protos'; -import {perfetto} from '../gen/protos'; - -import {featureFlags} from './feature_flags'; -import {toNs} from './time'; - -const METATRACING_BUFFER_SIZE = 100000; -const JS_THREAD_ID = 2; - -import MetatraceCategories = perfetto.protos.MetatraceCategories; - -const AOMT_FLAG = featureFlags.register({ - id: 'alwaysOnMetatracing', - name: 'Enable always-on-metatracing', - description: 'Enables trace events in the UI and trace processor', - defaultValue: false, -}); - -const AOMT_DETAILED_FLAG = featureFlags.register({ - id: 'alwaysOnMetatracing_detailed', - name: 'Detailed always-on-metatracing', - description: 'Enables recording additional events for trace event', - defaultValue: false, -}); - -function getInitialCategories(): MetatraceCategories|undefined { - if (!AOMT_FLAG.get()) return undefined; - if (AOMT_DETAILED_FLAG.get()) return MetatraceCategories.ALL; - return MetatraceCategories.TOPLEVEL; -} - -let enabledCategories: MetatraceCategories|undefined = getInitialCategories(); - -export function enableMetatracing(categories?: MetatraceCategories) { - enabledCategories = categories || MetatraceCategories.ALL; -} - -export function disableMetatracingAndGetTrace(): Uint8Array { - enabledCategories = undefined; - return readMetatrace(); -} - -export function isMetatracingEnabled(): boolean { - return enabledCategories !== undefined; -} - -export function getEnabledMetatracingCategories(): MetatraceCategories| - undefined { - return enabledCategories; -} - -interface TraceEvent { - eventName: string; - startNs: number; - durNs: number; -} - -const traceEvents: TraceEvent[] = []; - -function readMetatrace(): Uint8Array { - const eventToPacket = (e: TraceEvent): TracePacket => { - return TracePacket.create({ - timestamp: e.startNs, - timestampClockId: 1, - perfettoMetatrace: PerfettoMetatrace.create({ - eventName: e.eventName, - threadId: JS_THREAD_ID, - eventDurationNs: e.durNs, - }), - }); - }; - const packets: TracePacket[] = []; - for (const event of traceEvents) { - packets.push(eventToPacket(event)); - } - const trace = Trace.create({ - packet: packets, - }); - return Trace.encode(trace).finish(); -} - -export type TraceEventScope = { - startNs: number, eventName: string; -}; - -const correctedTimeOrigin = new Date().getTime() - performance.now(); - -function now(): number { - return toNs((correctedTimeOrigin + performance.now()) / 1000); -} - -export function traceEventBegin(eventName: string): TraceEventScope { - return { - eventName, - startNs: now(), - }; -} - -export function traceEventEnd(traceEvent: TraceEventScope) { - if (!isMetatracingEnabled()) return; - - traceEvents.push({ - eventName: traceEvent.eventName, - startNs: traceEvent.startNs, - durNs: now() - traceEvent.startNs, - }); - while (traceEvents.length > METATRACING_BUFFER_SIZE) { - traceEvents.shift(); - } -} diff --git a/third_party/perfetto/ui/src/common/metric_data.ts b/third_party/perfetto/ui/src/common/metric_data.ts deleted file mode 100644 index a9db6c837012..000000000000 --- a/third_party/perfetto/ui/src/common/metric_data.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export interface MetricResult { - name: string; - // Either result or error should be set. - resultString?: string; - error?: string; -} diff --git a/third_party/perfetto/ui/src/common/plugin_api.ts b/third_party/perfetto/ui/src/common/plugin_api.ts deleted file mode 100644 index fa8a9fcd8ced..000000000000 --- a/third_party/perfetto/ui/src/common/plugin_api.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {EngineProxy} from '../common/engine'; -import {TrackControllerFactory} from '../controller/track_controller'; -import {TrackCreator} from '../frontend/track'; - -export {EngineProxy} from '../common/engine'; -export { - NUM, - NUM_NULL, - STR, - STR_NULL, -} from '../common/query_result'; - -export interface TrackInfo { - // The id of this 'type' of track. This id is used to select the - // correct |TrackCreator| to construct the track. - trackKind: string; - - // A human readable name for this specific track. It will normally be - // displayed on the left-hand-side of the track. - name: string; - - // An opaque config for the track. - config: {}; -} - -// Called any time a trace is loaded. Plugins should return all -// potential tracks. Zero or more of the provided tracks may be -// instantiated depending on the users choices. -export type TrackProvider = (engine: EngineProxy) => Promise; - -// The public API plugins use to extend the UI. This is passed to each -// plugin via the exposed 'activate' function. -export interface PluginContext { - // DEPRECATED. In prior versions of the UI tracks were split into a - // 'TrackController' and a 'Track'. In more recent versions of the UI - // the functionality of |TrackController| has been merged into Track so - // |TrackController|s are not necessary in new code. - registerTrackController(track: TrackControllerFactory): void; - - // Register a |TrackProvider|. |TrackProvider|s return |TrackInfo| for - // all potential tracks in a trace. The core UI selects some of these - // |TrackInfo|s and constructs concrete Track instances using the - // registered |TrackCreator|s. - registerTrackProvider(provider: TrackProvider): void; - - // Register a track factory. The core UI invokes |TrackCreator| to - // construct tracks discovered by invoking |TrackProvider|s. - // The split between 'construction' and 'discovery' allows - // plugins to reuse common tracks for new data. For example: the - // dev.perfetto.AndroidGpu plugin could register a TrackProvider - // which returns GPU counter tracks. The counter track factory itself - // could be registered in dev.perfetto.CounterTrack - a whole - // different plugin. - registerTrack(track: TrackCreator): void; -} - -export interface PluginInfo { - // A unique string for your plugin. To ensure the name is unique you - // may wish to use a URL with reversed components in the manner of - // Java package names. - pluginId: string; - - // This function is called when the plugin is loaded. Generally this - // is called at most once shortly after the UI is loaded. However in - // some situations it can be called multiple times - for example - // when the user is toggling plugins on/off. - activate: (ctx: PluginContext) => void; -} diff --git a/third_party/perfetto/ui/src/common/plugins.ts b/third_party/perfetto/ui/src/common/plugins.ts deleted file mode 100644 index aa414bc74248..000000000000 --- a/third_party/perfetto/ui/src/common/plugins.ts +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Engine} from '../common/engine'; -import { - TrackControllerFactory, - trackControllerRegistry, -} from '../controller/track_controller'; -import {TrackCreator} from '../frontend/track'; -import {trackRegistry} from '../frontend/track_registry'; - -import { - PluginContext, - PluginInfo, - TrackInfo, - TrackProvider, -} from './plugin_api'; -import {Registry} from './registry'; - -// Every plugin gets its own PluginContext. This is how we keep track -// what each plugin is doing and how we can blame issues on particular -// plugins. -export class PluginContextImpl implements PluginContext { - readonly pluginId: string; - private trackProviders: TrackProvider[]; - - constructor(pluginId: string) { - this.pluginId = pluginId; - this.trackProviders = []; - } - - // ================================================================== - // The plugin facing API of PluginContext: - registerTrackController(track: TrackControllerFactory): void { - trackControllerRegistry.register(track); - } - - registerTrack(track: TrackCreator): void { - trackRegistry.register(track); - } - - registerTrackProvider(provider: TrackProvider) { - this.trackProviders.push(provider); - } - // ================================================================== - - // ================================================================== - // Internal facing API: - findPotentialTracks(engine: Engine): Promise[] { - const proxy = engine.getProxy(this.pluginId); - return this.trackProviders.map((f) => f(proxy)); - } - - // Unload the plugin. Ideally no plugin code runs after this point. - // PluginContext should unregister everything. - revoke() { - // TODO(hjd): Remove from trackControllerRegistry, trackRegistry, - // etc. - } - // ================================================================== -} - -// 'Static' registry of all known plugins. -export class PluginRegistry extends Registry { - constructor() { - super((info) => info.pluginId); - } -} - -export class PluginManager { - private registry: PluginRegistry; - private contexts: Map; - - constructor(registry: PluginRegistry) { - this.registry = registry; - this.contexts = new Map(); - } - - activatePlugin(pluginId: string): void { - if (this.isActive(pluginId)) { - return; - } - const pluginInfo = this.registry.get(pluginId); - const context = new PluginContextImpl(pluginId); - this.contexts.set(pluginId, context); - pluginInfo.activate(context); - } - - deactivatePlugin(pluginId: string): void { - const context = this.getPluginContext(pluginId); - if (context === undefined) { - return; - } - context.revoke(); - this.contexts.delete(pluginId); - } - - isActive(pluginId: string): boolean { - return this.getPluginContext(pluginId) !== undefined; - } - - getPluginContext(pluginId: string): PluginContextImpl|undefined { - return this.contexts.get(pluginId); - } - - findPotentialTracks(engine: Engine): Promise[] { - const promises = []; - for (const context of this.contexts.values()) { - for (const promise of context.findPotentialTracks(engine)) { - promises.push(promise); - } - } - return promises; - } -} - -// TODO(hjd): Sort out the story for global singletons like these: -export const pluginRegistry = new PluginRegistry(); -export const pluginManager = new PluginManager(pluginRegistry); diff --git a/third_party/perfetto/ui/src/common/plugins_unittest.ts b/third_party/perfetto/ui/src/common/plugins_unittest.ts deleted file mode 100644 index 47594bcfe53e..000000000000 --- a/third_party/perfetto/ui/src/common/plugins_unittest.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {PluginContext} from './plugin_api'; -import {PluginManager, PluginRegistry} from './plugins'; - -test('can activate plugin', () => { - const registry = new PluginRegistry(); - registry.register({ - pluginId: 'foo', - activate: (_: PluginContext) => {}, - }); - const manager = new PluginManager(registry); - manager.activatePlugin('foo'); - expect(manager.isActive('foo')).toBe(true); -}); - -test('can deactivate plugin', () => { - const registry = new PluginRegistry(); - registry.register({ - pluginId: 'foo', - activate: (_: PluginContext) => {}, - }); - const manager = new PluginManager(registry); - manager.activatePlugin('foo'); - manager.deactivatePlugin('foo'); - expect(manager.isActive('foo')).toBe(false); -}); diff --git a/third_party/perfetto/ui/src/common/proto_ring_buffer.ts b/third_party/perfetto/ui/src/common/proto_ring_buffer.ts deleted file mode 100644 index 0aa58126b920..000000000000 --- a/third_party/perfetto/ui/src/common/proto_ring_buffer.ts +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertTrue} from '../base/logging'; - -// This class is the TypeScript equivalent of the identically-named C++ class in -// //protozero/proto_ring_buffer.h. See comments in that header for a detailed -// description. The architecture is identical. - -const kGrowBytes = 128 * 1024; -const kMaxMsgSize = 64 * 1024 * 1024; - -export class ProtoRingBuffer { - private buf = new Uint8Array(kGrowBytes); - private fastpath?: Uint8Array; - private rd = 0; - private wr = 0; - - // The caller must call ReadMessage() after each append() call. - // The |data| might be either copied in the internal ring buffer or returned - // (% subarray()) to the next ReadMessage() call. - append(data: Uint8Array) { - assertTrue(this.wr <= this.buf.length); - assertTrue(this.rd <= this.wr); - - // If the last call to ReadMessage() consumed all the data in the buffer and - // there are no incomplete messages pending, restart from the beginning - // rather than keep ringing. This is the most common case. - if (this.rd === this.wr) { - this.rd = this.wr = 0; - } - - // The caller is expected to issue a ReadMessage() after each append(). - const dataLen = data.length; - if (dataLen === 0) return; - assertTrue(this.fastpath === undefined); - if (this.rd === this.wr) { - const msg = ProtoRingBuffer.tryReadMessage(data, 0, dataLen); - if (msg !== undefined && - ((msg.byteOffset + msg.length) === (data.byteOffset + dataLen))) { - // Fastpath: in many cases, the underlying stream will effectively - // preserve the atomicity of messages for most small messages. - // In this case we can avoid the extra buffer roundtrip and return the - // original array (actually a subarray that skips the proto header). - // The next call to ReadMessage() will return this. - this.fastpath = msg; - return; - } - } - - let avail = this.buf.length - this.wr; - if (dataLen > avail) { - // This whole section should be hit extremely rarely. - - // Try first just recompacting the buffer by moving everything to the - // left. This can happen if we received "a message and a bit" on each - // append() call. - this.buf.copyWithin(0, this.rd, this.wr); - avail += this.rd; - this.wr -= this.rd; - this.rd = 0; - if (dataLen > avail) { - // Still not enough, expand the buffer. - let newSize = this.buf.length; - while (dataLen > newSize - this.wr) { - newSize += kGrowBytes; - } - assertTrue(newSize <= kMaxMsgSize * 2); - const newBuf = new Uint8Array(newSize); - newBuf.set(this.buf); - this.buf = newBuf; - // No need to touch rd / wr. - } - } - - // Append the received data at the end of the ring buffer. - this.buf.set(data, this.wr); - this.wr += dataLen; - } - - // Tries to extract a message from the ring buffer. If there is no message, - // or if the current message is still incomplete, returns undefined. - // The caller is expected to call this in a loop until it returns undefined. - // Note that a single write to Append() can yield more than one message - // (see ProtoRingBufferTest.CoalescingStream in the unittest). - readMessage(): Uint8Array|undefined { - if (this.fastpath !== undefined) { - assertTrue(this.rd === this.wr); - const msg = this.fastpath; - this.fastpath = undefined; - return msg; - } - assertTrue(this.rd <= this.wr); - if (this.rd >= this.wr) { - return undefined; // Completely empty. - } - const msg = ProtoRingBuffer.tryReadMessage(this.buf, this.rd, this.wr); - if (msg === undefined) return undefined; - assertTrue(msg.buffer === this.buf.buffer); - assertTrue(this.buf.byteOffset === 0); - this.rd = msg.byteOffset + msg.length; - - // Deliberately returning a copy of the data with slice(). In various cases - // (streaming query response) the caller will hold onto the returned buffer. - // If we get to this point, |msg| is a view of the circular buffer that we - // will overwrite on the next calls to append(). - return msg.slice(); - } - - private static tryReadMessage( - data: Uint8Array, dataStart: number, dataEnd: number): Uint8Array - |undefined { - assertTrue(dataEnd <= data.length); - let pos = dataStart; - if (pos >= dataEnd) return undefined; - const tag = data[pos++]; // Assume one-byte tag. - if (tag >= 0x80 || (tag & 0x07) !== 2 /* len delimited */) { - throw new Error( - `RPC framing error, unexpected tag ${tag} @ offset ${pos - 1}`); - } - - let len = 0; - for (let shift = 0; /* no check */; shift += 7) { - if (pos >= dataEnd) { - return undefined; // Not enough data to read varint. - } - const val = data[pos++]; - len |= ((val & 0x7f) << shift) >>> 0; - if (val < 0x80) break; - } - - if (len >= kMaxMsgSize) { - throw new Error( - `RPC framing error, message too large (${len} > ${kMaxMsgSize}`); - } - const end = pos + len; - if (end > dataEnd) return undefined; - - // This is a subarray() and not a slice() because in the |fastpath| case - // we want to just return the original buffer pushed by append(). - // In the slow-path (ring-buffer) case, the readMessage() above will create - // a copy via slice() before returning it. - return data.subarray(pos, end); - } -} diff --git a/third_party/perfetto/ui/src/common/proto_ring_buffer_unittest.ts b/third_party/perfetto/ui/src/common/proto_ring_buffer_unittest.ts deleted file mode 100644 index 96090dfb60eb..000000000000 --- a/third_party/perfetto/ui/src/common/proto_ring_buffer_unittest.ts +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import protobuf from 'protobufjs/minimal'; -import {assertTrue} from '../base/logging'; - -import {ProtoRingBuffer} from './proto_ring_buffer'; - -let seed = 1; - -// For reproducibility. -function Rnd(max: number) { - seed = seed * 16807 % 2147483647; - return seed % max; -} - -function MakeProtoMessage(fieldId: number, len: number) { - const writer = protobuf.Writer.create(); - const tag = (fieldId << 3) | 2; - assertTrue(tag < 0x80 && (tag & 7) === 2); - writer.uint32(tag); - const data = new Uint8Array(len); - for (let i = 0; i < len; i++) { - data[i] = 48 + ((fieldId + i) % 73); - } - writer.bytes(data); - const res = writer.finish(); - // For whatever reason the object returned by protobufjs' Writer cannot be - // directly .toEqual()-ed with Uint8Arrays. - const buf = new Uint8Array(res.length); - buf.set(res); - return buf; -} - -test('ProtoRingBufferTest.Fastpath', () => { - const buf = new ProtoRingBuffer(); - - for (let rep = 0; rep < 3; rep++) { - let inputBuf = MakeProtoMessage(1, 32); - buf.append(inputBuf); - let msg = buf.readMessage(); - expect(msg).toBeDefined(); - expect(msg).toBeInstanceOf(Uint8Array); - expect(msg!.length).toBe(32); - - // subarray(2) is to strip the proto preamble. The returned buffer starts at - // the start of the payload. - expect(msg).toEqual(inputBuf.subarray(2)); - - // When we hit the fastpath, the returned message should be a subarray of - // the same ArrayBuffer passed to append. - expect(msg!.buffer).toBe(inputBuf.buffer); - - inputBuf = MakeProtoMessage(2, 32); - buf.append(inputBuf.subarray(0, 13)); - expect(buf.readMessage()).toBeUndefined(); - buf.append(inputBuf.subarray(13)); - msg = buf.readMessage(); - expect(msg).toBeDefined(); - expect(msg).toBeInstanceOf(Uint8Array); - expect(msg).toEqual(inputBuf.subarray(2)); - expect(msg!.buffer !== inputBuf.buffer).toBeTruthy(); - } -}); - -test('ProtoRingBufferTest.CoalescingStream', () => { - const buf = new ProtoRingBuffer(); - - const mergedBuf = new Uint8Array(612); - const expected = new Array(); - for (let i = 1, pos = 0; i <= 6; i++) { - const msg = MakeProtoMessage(i, 100); - expected.push(msg); - mergedBuf.set(msg, pos); - pos += msg.length; - } - - const fragLens = [120, 20, 471, 1]; - let fragSum = 0; - fragLens.map((fragLen) => { - buf.append(mergedBuf.subarray(fragSum, fragSum + fragLen)); - fragSum += fragLen; - for (;;) { - const msg = buf.readMessage(); - if (msg === undefined) break; - const exp = expected.shift(); - expect(exp).toBeDefined(); - expect(msg).toEqual(exp!.subarray(-1 * msg.length)); - } - }); - expect(expected.length).toEqual(0); -}); - - -test('ProtoRingBufferTest.RandomSizes', () => { - const buf = new ProtoRingBuffer(); - const kNumMsg = 100; - const mergedBuf = new Uint8Array(1024 * 1024 * 32); - const expectedLengths = []; - let mergedLen = 0; - for (let i = 0; i < kNumMsg; i++) { - const fieldId = 1 + Rnd(15); // We support only one byte tag. - const rndVal = Rnd(1024); - let len = 1 + rndVal; - if ((rndVal % 100) < 5) { - len *= 1000; - } - const msg = MakeProtoMessage(fieldId, len); - assertTrue(mergedBuf.length >= mergedLen + msg.length); - expectedLengths.push(len); - mergedBuf.set(msg, mergedLen); - mergedLen += msg.length; - } - - for (let fragSum = 0; fragSum < mergedLen; /**/) { - let fragLen = 1 + Rnd(1024 * 32); - fragLen = Math.min(fragLen, mergedLen - fragSum); - buf.append(mergedBuf.subarray(fragSum, fragSum + fragLen)); - fragSum += fragLen; - for (;;) { - const msg = buf.readMessage(); - if (msg === undefined) break; - const expLen = expectedLengths.shift(); - expect(expLen).toBeDefined(); - expect(msg.length).toEqual(expLen); - } - } - expect(expectedLengths.length).toEqual(0); -}); diff --git a/third_party/perfetto/ui/src/common/protos.ts b/third_party/perfetto/ui/src/common/protos.ts deleted file mode 100644 index d0e89d0d1fd4..000000000000 --- a/third_party/perfetto/ui/src/common/protos.ts +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import protos from '../gen/protos'; - -// Aliases protos to avoid the super nested namespaces. -// See https://www.typescriptlang.org/docs/handbook/namespaces.html#aliases -import AndroidLogConfig = protos.perfetto.protos.AndroidLogConfig; -import AndroidPowerConfig = protos.perfetto.protos.AndroidPowerConfig; -import AndroidLogId = protos.perfetto.protos.AndroidLogId; -import BatteryCounters = - protos.perfetto.protos.AndroidPowerConfig.BatteryCounters; -import BufferConfig = protos.perfetto.protos.TraceConfig.BufferConfig; -import ChromeConfig = protos.perfetto.protos.ChromeConfig; -import TrackEventConfig = protos.perfetto.protos.TrackEventConfig; -import ConsumerPort = protos.perfetto.protos.ConsumerPort; -import NetworkPacketTraceConfig = - protos.perfetto.protos.NetworkPacketTraceConfig; -import NativeContinuousDumpConfig = - protos.perfetto.protos.HeapprofdConfig.ContinuousDumpConfig; -import JavaContinuousDumpConfig = - protos.perfetto.protos.JavaHprofConfig.ContinuousDumpConfig; -import DataSourceConfig = protos.perfetto.protos.DataSourceConfig; -import DataSourceDescriptor = protos.perfetto.protos.DataSourceDescriptor; -import FtraceConfig = protos.perfetto.protos.FtraceConfig; -import HeapprofdConfig = protos.perfetto.protos.HeapprofdConfig; -import JavaHprofConfig = protos.perfetto.protos.JavaHprofConfig; -import IAndroidPowerConfig = protos.perfetto.protos.IAndroidPowerConfig; -import IBufferConfig = protos.perfetto.protos.TraceConfig.IBufferConfig; -import IProcessStatsConfig = protos.perfetto.protos.IProcessStatsConfig; -import ISysStatsConfig = protos.perfetto.protos.ISysStatsConfig; -import ITraceConfig = protos.perfetto.protos.ITraceConfig; -import MeminfoCounters = protos.perfetto.protos.MeminfoCounters; -import ProcessStatsConfig = protos.perfetto.protos.ProcessStatsConfig; -import StatCounters = protos.perfetto.protos.SysStatsConfig.StatCounters; -import SysStatsConfig = protos.perfetto.protos.SysStatsConfig; -import TraceConfig = protos.perfetto.protos.TraceConfig; -import VmstatCounters = protos.perfetto.protos.VmstatCounters; -import IPCFrame = protos.perfetto.protos.IPCFrame; -import IMethodInfo = - protos.perfetto.protos.IPCFrame.BindServiceReply.IMethodInfo; -import IBufferStats = protos.perfetto.protos.TraceStats.IBufferStats; -import ISlice = protos.perfetto.protos.ReadBuffersResponse.ISlice; -import EnableTracingRequest = protos.perfetto.protos.EnableTracingRequest; -import DisableTracingRequest = protos.perfetto.protos.DisableTracingRequest; -import GetTraceStatsRequest = protos.perfetto.protos.GetTraceStatsRequest; -import FreeBuffersRequest = protos.perfetto.protos.FreeBuffersRequest; -import ReadBuffersRequest = protos.perfetto.protos.ReadBuffersRequest; -import QueryServiceStateRequest = - protos.perfetto.protos.QueryServiceStateRequest; -import EnableTracingResponse = protos.perfetto.protos.EnableTracingResponse; -import DisableTracingResponse = protos.perfetto.protos.DisableTracingResponse; -import GetTraceStatsResponse = protos.perfetto.protos.GetTraceStatsResponse; -import FreeBuffersResponse = protos.perfetto.protos.FreeBuffersResponse; -import ReadBuffersResponse = protos.perfetto.protos.ReadBuffersResponse; -import QueryServiceStateResponse = - protos.perfetto.protos.QueryServiceStateResponse; -// Trace Processor protos. -import QueryArgs = protos.perfetto.protos.QueryArgs; -import ResetTraceProcessorArgs = protos.perfetto.protos.ResetTraceProcessorArgs; -import StatusResult = protos.perfetto.protos.StatusResult; -import ComputeMetricArgs = protos.perfetto.protos.ComputeMetricArgs; -import ComputeMetricResult = protos.perfetto.protos.ComputeMetricResult; -import DisableAndReadMetatraceResult = - protos.perfetto.protos.DisableAndReadMetatraceResult; -import Trace = protos.perfetto.protos.Trace; -import TracePacket = protos.perfetto.protos.TracePacket; -import PerfettoMetatrace = protos.perfetto.protos.PerfettoMetatrace; - -export { - AndroidLogConfig, - AndroidLogId, - AndroidPowerConfig, - BatteryCounters, - BufferConfig, - ChromeConfig, - ConsumerPort, - ComputeMetricArgs, - ComputeMetricResult, - DataSourceConfig, - DisableAndReadMetatraceResult, - DataSourceDescriptor, - DisableTracingRequest, - DisableTracingResponse, - EnableTracingRequest, - EnableTracingResponse, - FreeBuffersRequest, - FreeBuffersResponse, - FtraceConfig, - GetTraceStatsRequest, - GetTraceStatsResponse, - HeapprofdConfig, - IAndroidPowerConfig, - IBufferConfig, - IBufferStats, - IMethodInfo, - IPCFrame, - IProcessStatsConfig, - ISlice, - ISysStatsConfig, - ITraceConfig, - JavaContinuousDumpConfig, - JavaHprofConfig, - MeminfoCounters, - NativeContinuousDumpConfig, - NetworkPacketTraceConfig, - ProcessStatsConfig, - PerfettoMetatrace, - ReadBuffersRequest, - ReadBuffersResponse, - QueryServiceStateRequest, - QueryServiceStateResponse, - QueryArgs, - ResetTraceProcessorArgs, - StatCounters, - StatusResult, - SysStatsConfig, - Trace, - TraceConfig, - TrackEventConfig, - TracePacket, - VmstatCounters, -}; diff --git a/third_party/perfetto/ui/src/common/protos_unittest.ts b/third_party/perfetto/ui/src/common/protos_unittest.ts deleted file mode 100644 index 181917a39494..000000000000 --- a/third_party/perfetto/ui/src/common/protos_unittest.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {TraceConfig} from './protos'; - -test('round trip config proto', () => { - const input = TraceConfig.create({ - durationMs: 42, - }); - const output = TraceConfig.decode(TraceConfig.encode(input).finish()); - expect(output.durationMs).toBe(42); -}); diff --git a/third_party/perfetto/ui/src/common/queries.ts b/third_party/perfetto/ui/src/common/queries.ts deleted file mode 100644 index 1d638197b826..000000000000 --- a/third_party/perfetto/ui/src/common/queries.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {EngineProxy} from './engine'; -import {Row} from './query_result'; - -const MAX_DISPLAY_ROWS = 10000; - -export interface QueryResponse { - query: string; - error?: string; - totalRowCount: number; - durationMs: number; - columns: string[]; - rows: Row[]; - statementCount: number; - statementWithOutputCount: number; -} - -export async function runQuery( - sqlQuery: string, engine: EngineProxy): Promise { - const startMs = performance.now(); - const queryRes = engine.query(sqlQuery); - - // TODO(primiano): once the controller thread is gone we should pass down - // the result objects directly to the frontend, iterate over the result - // and deal with pagination there. For now we keep the old behavior and - // truncate to 10k rows. - - try { - await queryRes.waitAllRows(); - } catch { - // In the case of a query error we don't want the exception to bubble up - // as a crash. The |queryRes| object will be populated anyways. - // queryRes.error() is used to tell if the query errored or not. If it - // errored, the frontend will show a graceful message instead. - } - - const durationMs = performance.now() - startMs; - const rows: Row[] = []; - const columns = queryRes.columns(); - let numRows = 0; - for (const iter = queryRes.iter({}); iter.valid(); iter.next()) { - const row: Row = {}; - for (const colName of columns) { - const value = iter.get(colName); - row[colName] = value === null ? 'NULL' : value; - } - rows.push(row); - if (++numRows >= MAX_DISPLAY_ROWS) break; - } - - const result: QueryResponse = { - query: sqlQuery, - durationMs, - error: queryRes.error(), - totalRowCount: queryRes.numRows(), - columns, - rows, - statementCount: queryRes.statementCount(), - statementWithOutputCount: queryRes.statementWithOutputCount(), - }; - return result; -} diff --git a/third_party/perfetto/ui/src/common/query_result.ts b/third_party/perfetto/ui/src/common/query_result.ts deleted file mode 100644 index e8d482d857ac..000000000000 --- a/third_party/perfetto/ui/src/common/query_result.ts +++ /dev/null @@ -1,950 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This file deals with deserialization and iteration of the proto-encoded -// byte buffer that is returned by TraceProcessor when invoking the -// TPM_QUERY_STREAMING method. The returned |query_result| buffer is optimized -// for being moved cheaply across workers and decoded on-the-flight as we step -// through the iterator. -// See comments around QueryResult in trace_processor.proto for more details. - -// The classes in this file are organized as follows: -// -// QueryResultImpl: -// The object returned by the Engine.query(sql) method. -// This object is a holder of row data. Batches of raw get appended -// incrementally as they are received by the remote TraceProcessor instance. -// QueryResultImpl also deals with asynchronicity of queries and allows callers -// to obtain a promise that waits for more (or all) rows. -// At any point in time the following objects hold a reference to QueryResult: -// - The Engine: for appending row batches. -// - UI code, typically controllers, who make queries. -// -// ResultBatch: -// Hold the data, returned by the remote TraceProcessor instance, for a number -// of rows (TP typically chunks the results in batches of 128KB). -// A QueryResultImpl holds exclusively ResultBatches for a given query. -// ResultBatch is not exposed externally, it's just an internal representation -// that helps with proto decoding. ResultBatch is immutable after it gets -// appended and decoded. The iteration state is held by the RowIteratorImpl. -// -// RowIteratorImpl: -// Decouples the data owned by QueryResultImpl (and its ResultBatch(es)) from -// the iteration state. The iterator effectively is the union of a ResultBatch -// and the row number in it. Rows within the batch are decoded as the user calls -// next(). When getting at the end of the batch, it takes care of switching to -// the next batch (if any) within the QueryResultImpl. -// This object is part of the API exposed to tracks / controllers. - -import protobuf from 'protobufjs/minimal'; - -// Disable Long.js support in protobuf. This seems to be enabled only in tests -// but not in production code. In any case, for now we want casting to number -// accepting the 2**53 limitation. This is consistent with passing -// --force-number in the protobuf.js codegen invocation in //ui/BUILD.gn . -// See also https://github.com/protobufjs/protobuf.js/issues/1253 . -protobuf.util.Long = undefined as any; -protobuf.configure(); - -import {defer, Deferred} from '../base/deferred'; -import {assertExists, assertFalse, assertTrue} from '../base/logging'; -import {utf8Decode} from '../base/string_utils'; - -export const NUM = 0; -export const STR = 'str'; -export const NUM_NULL: number|null = 1; -export const STR_NULL: string|null = 'str_null'; -export const BLOB: Uint8Array = new Uint8Array(); -export const BLOB_NULL: Uint8Array|null = new Uint8Array(); -export const LONG: bigint = 0n; -export const LONG_NULL: bigint|null = 1n; - -export type ColumnType = string|number|bigint|null|Uint8Array; - -const SHIFT_32BITS = 32n; - -// Fast decode varint int64 into a bigint -// Inspired by -// https://github.com/protobufjs/protobuf.js/blob/56b1e64979dae757b67a21d326e16acee39f2267/src/reader.js#L123 -export function decodeInt64Varint(buf: Uint8Array, pos: number): bigint { - let hi: number = 0; - let lo: number = 0; - let i = 0; - - if (buf.length - pos > 4) { // fast route (lo) - for (; i < 4; ++i) { - // 1st..4th - lo = (lo | (buf[pos] & 127) << i * 7) >>> 0; - if (buf[pos++] < 128) { - return BigInt(lo); - } - } - // 5th - lo = (lo | (buf[pos] & 127) << 28) >>> 0; - hi = (hi | (buf[pos] & 127) >> 4) >>> 0; - if (buf[pos++] < 128) { - return BigInt(hi) << SHIFT_32BITS | BigInt(lo); - } - i = 0; - } else { - for (; i < 3; ++i) { - if (pos >= buf.length) { - throw Error('Index out of range'); - } - // 1st..3rd - lo = (lo | (buf[pos] & 127) << i * 7) >>> 0; - if (buf[pos++] < 128) { - return BigInt(lo); - } - } - // 4th - lo = (lo | (buf[pos++] & 127) << i * 7) >>> 0; - return BigInt(hi) << SHIFT_32BITS | BigInt(lo); - } - if (buf.length - pos > 4) { // fast route (hi) - for (; i < 5; ++i) { - // 6th..10th - hi = (hi | (buf[pos] & 127) << i * 7 + 3) >>> 0; - if (buf[pos++] < 128) { - const big = BigInt(hi) << SHIFT_32BITS | BigInt(lo); - return BigInt.asIntN(64, big); - } - } - } else { - for (; i < 5; ++i) { - if (pos >= buf.length) { - throw Error('Index out of range'); - } - // 6th..10th - hi = (hi | (buf[pos] & 127) << i * 7 + 3) >>> 0; - if (buf[pos++] < 128) { - const big = BigInt(hi) << SHIFT_32BITS | BigInt(lo); - return BigInt.asIntN(64, big); - } - } - } - throw Error('invalid varint encoding'); -} - -// Info that could help debug a query error. For example the query -// in question, the stack where the query was issued, the active -// plugin etc. -export interface QueryErrorInfo { - query: string; -} - -export class QueryError extends Error { - readonly query: string; - - constructor(message: string, info: QueryErrorInfo) { - super(message); - this.query = info.query; - } - - toString() { - return `Query: ${this.query}\n` + super.toString(); - } -} - -// One row extracted from an SQL result: -export interface Row { - [key: string]: ColumnType; -} - -// The methods that any iterator has to implement. -export interface RowIteratorBase { - valid(): boolean; - next(): void; - - // Reflection support for cases where the column names are not known upfront - // (e.g. the query result table for user-provided SQL queries). - // It throws if the passed column name doesn't exist. - // Example usage: - // for (const it = queryResult.iter({}); it.valid(); it.next()) { - // for (const columnName : queryResult.columns()) { - // console.log(it.get(columnName)); - get(columnName: string): ColumnType; -} - -// A RowIterator is a type that has all the fields defined in the query spec -// plus the valid() and next() operators. This is to ultimately allow the -// clients to do: -// const result = await engine.query("select name, surname, id from people;"); -// const iter = queryResult.iter({name: STR, surname: STR, id: NUM}); -// for (; iter.valid(); iter.next()) -// console.log(iter.name, iter.surname); -export type RowIterator = RowIteratorBase&T; - -function columnTypeToString(t: ColumnType): string { - switch (t) { - case NUM: - return 'NUM'; - case NUM_NULL: - return 'NUM_NULL'; - case STR: - return 'STR'; - case STR_NULL: - return 'STR_NULL'; - case BLOB: - return 'BLOB'; - case BLOB_NULL: - return 'BLOB_NULL'; - case LONG: - return 'LONG'; - case LONG_NULL: - return 'LONG_NULL'; - default: - return `INVALID(${t})`; - } -} - -function isCompatible(actual: CellType, expected: ColumnType): boolean { - switch (actual) { - case CellType.CELL_NULL: - return expected === NUM_NULL || expected === STR_NULL || - expected === BLOB_NULL || expected === LONG_NULL; - case CellType.CELL_VARINT: - return expected === NUM || expected === NUM_NULL || expected === LONG || - expected === LONG_NULL; - case CellType.CELL_FLOAT64: - return expected === NUM || expected === NUM_NULL; - case CellType.CELL_STRING: - return expected === STR || expected === STR_NULL; - case CellType.CELL_BLOB: - return expected === BLOB || expected === BLOB_NULL; - default: - throw new Error(`Unknown CellType ${actual}`); - } -} - -// This has to match CellType in trace_processor.proto. -enum CellType { - CELL_NULL = 1, - CELL_VARINT = 2, - CELL_FLOAT64 = 3, - CELL_STRING = 4, - CELL_BLOB = 5, -} - -const CELL_TYPE_NAMES = - ['UNKNOWN', 'NULL', 'VARINT', 'FLOAT64', 'STRING', 'BLOB']; - -const TAG_LEN_DELIM = 2; - -// This is the interface exposed to readers (e.g. tracks). The underlying object -// (QueryResultImpl) owns the result data. This allows to obtain iterators on -// that. In future it will allow to wait for incremental updates (new rows being -// fetched) for streaming queries. -export interface QueryResult { - // Obtains an iterator. - // TODO(primiano): this should have an option to destruct data as we read. In - // the case of a long query (e.g. `SELECT * FROM sched` in the query prompt) - // we don't want to accumulate everything in memory. OTOH UI tracks want to - // keep the data around so they can redraw them on each animation frame. For - // now we keep everything in memory in the QueryResultImpl object. - // iter(spec: T): RowIterator; - iter(spec: T): RowIterator; - - // Like iter() for queries that expect only one row. It embeds the valid() - // check (i.e. throws if no rows are available) and returns directly the - // first result. - firstRow(spec: T): T; - - // If != undefined the query errored out and error() contains the message. - error(): string|undefined; - - // Returns the number of rows accumulated so far. Note that this number can - // change over time as more batches are received. It becomes stable only - // when isComplete() returns true or after waitAllRows() is resolved. - numRows(): number; - - // If true all rows have been fetched. Calling iter() will iterate through the - // last row. If false, iter() will return an iterator which might iterate - // through some rows (or none) but will surely not reach the end. - - isComplete(): boolean; - - // Returns a promise that is resolved only when all rows (i.e. all batches) - // have been fetched. The promise return value is always the object iself. - waitAllRows(): Promise; - - // Returns a promise that is resolved when either: - // - more rows are available - // - all rows are available - // The promise return value is always the object iself. - waitMoreRows(): Promise; - - // Can return an empty array if called before the first batch is resolved. - // This should be called only after having awaited for at least one batch. - columns(): string[]; - - // Returns the number of SQL statements in the query - // (e.g. 2 'if SELECT 1; SELECT 2;') - statementCount(): number; - - // Returns the number of SQL statement that produced output rows. This number - // is <= statementCount(). - statementWithOutputCount(): number; -} - -// Interface exposed to engine.ts to pump in the data as new row batches arrive. -export interface WritableQueryResult extends QueryResult { - // |resBytes| is a proto-encoded trace_processor.QueryResult message. - // The overall flow looks as follows: - // - The user calls engine.query('select ...') and gets a QueryResult back. - // - The query call posts a message to the worker that runs the SQL engine ( - // or sends a HTTP request in case of the RPC+HTTP interface). - // - The returned QueryResult object is initially empty. - // - Over time, the sql engine will postMessage() back results in batches. - // - Each bach will end up calling this appendResultBatch() method. - // - If there is any pending promise (e.g. the caller called - // queryResult.waitAllRows()), this call will awake them (if this is the - // last batch). - appendResultBatch(resBytes: Uint8Array): void; -} - -// The actual implementation, which bridges together the reader side and the -// writer side (the one exposed to the Engine). This is the same object so that -// when the engine pumps new row batches we can resolve pending promises that -// readers (e.g. track code) are waiting for. -class QueryResultImpl implements QueryResult, WritableQueryResult { - columnNames: string[] = []; - private _error?: string; - private _numRows = 0; - private _isComplete = false; - private _errorInfo: QueryErrorInfo; - private _statementCount = 0; - private _statementWithOutputCount = 0; - - constructor(errorInfo: QueryErrorInfo) { - this._errorInfo = errorInfo; - } - - // --- QueryResult implementation. - - // TODO(primiano): for the moment new batches are appended but old batches - // are never removed. This won't work with abnormally large result sets, as - // it will stash all rows in memory. We could switch to a model where the - // iterator is destructive and deletes batch objects once iterating past the - // end of each batch. If we do that, than we need to assign monotonic IDs to - // batches. Also if we do that, we should prevent creating more than one - // iterator for a QueryResult. - batches: ResultBatch[] = []; - - // Promise awaiting on waitAllRows(). This should be resolved only when the - // last result batch has been been retrieved. - private allRowsPromise?: Deferred; - - // Promise awaiting on waitMoreRows(). This resolved when the next - // batch is appended via appendResultBatch. - private moreRowsPromise?: Deferred; - - isComplete(): boolean { - return this._isComplete; - } - numRows(): number { - return this._numRows; - } - error(): string|undefined { - return this._error; - } - columns(): string[] { - return this.columnNames; - } - statementCount(): number { - return this._statementCount; - } - statementWithOutputCount(): number { - return this._statementWithOutputCount; - } - - iter(spec: T): RowIterator { - const impl = new RowIteratorImplWithRowData(spec, this); - return impl as {} as RowIterator; - } - - firstRow(spec: T): T { - const impl = new RowIteratorImplWithRowData(spec, this); - assertTrue(impl.valid()); - return impl as {} as RowIteratoras T; - } - - // Can be called only once. - waitAllRows(): Promise { - assertTrue(this.allRowsPromise === undefined); - this.allRowsPromise = defer(); - if (this._isComplete) { - this.resolveOrReject(this.allRowsPromise, this); - } - return this.allRowsPromise; - } - - waitMoreRows(): Promise { - if (this.moreRowsPromise !== undefined) { - return this.moreRowsPromise; - } - - const moreRowsPromise = defer(); - if (this._isComplete) { - this.resolveOrReject(moreRowsPromise, this); - } else { - this.moreRowsPromise = moreRowsPromise; - } - return moreRowsPromise; - } - - // --- WritableQueryResult implementation. - - // Called by the engine when a new QueryResult is available. Note that a - // single Query() call can yield >1 QueryResult due to result batching - // if more than ~64K of data are returned, e.g. when returning O(M) rows. - // |resBytes| is a proto-encoded trace_processor.QueryResult message. - // It is fine to retain the resBytes without slicing a copy, because - // ProtoRingBuffer does the slice() for us (or passes through the buffer - // coming from postMessage() (Wasm case) of fetch() (HTTP+RPC case). - appendResultBatch(resBytes: Uint8Array) { - const reader = protobuf.Reader.create(resBytes); - assertTrue(reader.pos === 0); - const columnNamesEmptyAtStartOfBatch = this.columnNames.length === 0; - const columnNamesSet = new Set(); - while (reader.pos < reader.len) { - const tag = reader.uint32(); - switch (tag >>> 3) { - case 1: // column_names - // Only the first batch should contain the column names. If this fires - // something is going wrong in the handling of the batch stream. - assertTrue(columnNamesEmptyAtStartOfBatch); - const origColName = reader.string(); - let colName = origColName; - // In some rare cases two columns can have the same name (b/194891824) - // e.g. `select 1 as x, 2 as x`. These queries don't happen in the - // UI code, but they can happen when the user types a query (e.g. - // with a join). The most practical thing we can do here is renaming - // the columns with a suffix. Keeping the same name will break when - // iterating, because column names become iterator object keys. - for (let i = 1; columnNamesSet.has(colName); ++i) { - colName = `${origColName}_${i}`; - assertTrue(i < 100); // Give up at some point; - } - columnNamesSet.add(colName); - this.columnNames.push(colName); - break; - case 2: // error - // The query has errored only if the |error| field is non-empty. - // In protos, we don't distinguish between non-present and empty. - // Make sure we don't propagate ambiguous empty strings to JS. - const err = reader.string(); - this._error = (err !== undefined && err.length) ? err : undefined; - break; - case 3: // batch - const batchLen = reader.uint32(); - const batchRaw = resBytes.subarray(reader.pos, reader.pos + batchLen); - reader.pos += batchLen; - - // The ResultBatch ctor parses the CellsBatch submessage. - const parsedBatch = new ResultBatch(batchRaw); - this.batches.push(parsedBatch); - this._isComplete = parsedBatch.isLastBatch; - - // In theory one could construct a valid proto serializing the column - // names after the cell batches. In practice the QueryResultSerializer - // doesn't do that so it's not worth complicating the code. - const numColumns = this.columnNames.length; - if (numColumns !== 0) { - assertTrue(parsedBatch.numCells % numColumns === 0); - this._numRows += parsedBatch.numCells / numColumns; - } else { - // numColumns == 0 is plausible for queries like CREATE TABLE ... . - assertTrue(parsedBatch.numCells === 0); - } - break; - - case 4: - this._statementCount = reader.uint32(); - break; - - case 5: - this._statementWithOutputCount = reader.uint32(); - break; - - default: - console.warn(`Unexpected QueryResult field ${tag >>> 3}`); - reader.skipType(tag & 7); - break; - } // switch (tag) - } // while (pos < end) - - if (this.moreRowsPromise !== undefined) { - this.resolveOrReject(this.moreRowsPromise, this); - this.moreRowsPromise = undefined; - } - - if (this._isComplete && this.allRowsPromise !== undefined) { - this.resolveOrReject(this.allRowsPromise, this); - } - } - - ensureAllRowsPromise(): Promise { - if (this.allRowsPromise === undefined) { - this.waitAllRows(); // Will populate |this.allRowsPromise|. - } - return assertExists(this.allRowsPromise); - } - - private resolveOrReject(promise: Deferred, arg: QueryResult) { - if (this._error === undefined) { - promise.resolve(arg); - } else { - promise.reject(new QueryError(this._error, this._errorInfo)); - } - } -} - -// This class holds onto a received result batch (a Uint8Array) and does some -// partial parsing to tokenize the various cell groups. This parsing mainly -// consists of identifying and caching the offsets of each cell group and -// initializing the varint decoders. This half parsing is done to keep the -// iterator's next() fast, without decoding everything into memory. -// This is an internal implementation detail and is not exposed outside. The -// RowIteratorImpl uses this class to iterate through batches (this class takes -// care of iterating within a batch, RowIteratorImpl takes care of switching -// batches when needed). -// Note: at any point in time there can be more than one ResultIterator -// referencing the same batch. The batch must be immutable. -class ResultBatch { - readonly isLastBatch: boolean = false; - readonly batchBytes: Uint8Array; - readonly cellTypesOff: number = 0; - readonly cellTypesLen: number = 0; - readonly varintOff: number = 0; - readonly varintLen: number = 0; - readonly float64Cells = new Float64Array(); - readonly blobCells: Uint8Array[] = []; - readonly stringCells: string[] = []; - - // batchBytes is a trace_processor.QueryResult.CellsBatch proto. - constructor(batchBytes: Uint8Array) { - this.batchBytes = batchBytes; - const reader = protobuf.Reader.create(batchBytes); - assertTrue(reader.pos === 0); - const end = reader.len; - - // Here we deconstruct the proto by hand. The CellsBatch is carefully - // designed to allow a very fast parsing from the TS side. We pack all cells - // of the same types together, so we can do only one call (per batch) to - // TextDecoder.decode(), we can overlay a memory-aligned typedarray for - // float values and can quickly tell and type-check the cell types. - // One row = N cells (we know the number upfront from the outer message). - // Each bach contains always an integer multiple of N cells (i.e. rows are - // never fragmented across different batches). - while (reader.pos < end) { - const tag = reader.uint32(); - switch (tag >>> 3) { - case 1: // cell types, a packed array containing one CellType per cell. - assertTrue((tag & 7) === TAG_LEN_DELIM); // Must be packed varint. - this.cellTypesLen = reader.uint32(); - this.cellTypesOff = reader.pos; - reader.pos += this.cellTypesLen; - break; - - case 2: // varint_cells, a packed varint buffer. - assertTrue((tag & 7) === TAG_LEN_DELIM); // Must be packed varint. - const packLen = reader.uint32(); - this.varintOff = reader.pos; - this.varintLen = packLen; - assertTrue(reader.buf === batchBytes); - assertTrue( - this.varintOff + this.varintLen <= - batchBytes.byteOffset + batchBytes.byteLength); - reader.pos += packLen; - break; - - case 3: // float64_cells, a 64-bit aligned packed fixed64 buffer. - assertTrue((tag & 7) === TAG_LEN_DELIM); // Must be packed varint. - const f64Len = reader.uint32(); - assertTrue(f64Len % 8 === 0); - // Float64Array's constructor is evil: the offset is in bytes but the - // length is in 8-byte words. - const f64Words = f64Len / 8; - const f64Off = batchBytes.byteOffset + reader.pos; - if (f64Off % 8 === 0) { - this.float64Cells = - new Float64Array(batchBytes.buffer, f64Off, f64Words); - } else { - // When using the production code in trace_processor's rpc.cc, the - // float64 should be 8-bytes aligned. The slow-path case is only for - // tests. - const slice = batchBytes.buffer.slice(f64Off, f64Off + f64Len); - this.float64Cells = new Float64Array(slice); - } - reader.pos += f64Len; - break; - - case 4: // blob_cells: one entry per blob. - assertTrue((tag & 7) === TAG_LEN_DELIM); - // protobufjs's bytes() under the hoods calls slice() and creates - // a copy. Fine here as blobs are rare and not a fastpath. - this.blobCells.push(new Uint8Array(reader.bytes())); - break; - - case 5: // string_cells: all the string cells concatenated with \0s. - assertTrue((tag & 7) === TAG_LEN_DELIM); - const strLen = reader.uint32(); - assertTrue(reader.pos + strLen <= end); - const subArr = batchBytes.subarray(reader.pos, reader.pos + strLen); - assertTrue(subArr.length === strLen); - // The reason why we do this split rather than creating one string - // per entry is that utf8 decoding has some non-negligible cost. See - // go/postmessage-benchmark . - this.stringCells = utf8Decode(subArr).split('\0'); - reader.pos += strLen; - break; - - case 6: // is_last_batch (boolean). - this.isLastBatch = !!reader.bool(); - break; - - case 7: // padding for realignment, skip silently. - reader.skipType(tag & 7); - break; - - default: - console.warn(`Unexpected QueryResult.CellsBatch field ${tag >>> 3}`); - reader.skipType(tag & 7); - break; - } // switch(tag) - } // while (pos < end) - } - - get numCells() { - return this.cellTypesLen; - } -} - -class RowIteratorImpl implements RowIteratorBase { - // The spec passed to the iter call containing the expected types, e.g.: - // {'colA': NUM, 'colB': NUM_NULL, 'colC': STRING}. - // This doesn't ever change. - readonly rowSpec: Row; - - // The object that holds the current row. This points to the parent - // RowIteratorImplWithRowData instance that created this class. - rowData: Row; - - // The QueryResult object we are reading data from. The engine will pump - // batches over time into this object. - private resultObj: QueryResultImpl; - - // All the member variables in the group below point to the identically-named - // members in result.batch[batchIdx]. This is to avoid indirection layers in - // the next() hotpath, so we can do this.float64Cells vs - // this.resultObj.batch[this.batchIdx].float64Cells. - // These are re-set every time tryMoveToNextBatch() is called (and succeeds). - private batchIdx = -1; // The batch index within |result.batches[]|. - private batchBytes = new Uint8Array(); - private columnNames: string[] = []; - private numColumns = 0; - private cellTypesEnd = -1; // -1 so the 1st next() hits tryMoveToNextBatch(). - private float64Cells = new Float64Array(); - private varIntReader = protobuf.Reader.create(this.batchBytes); - private blobCells: Uint8Array[] = []; - private stringCells: string[] = []; - - // These members instead are incremented as we read cells from next(). They - // are the mutable state of the iterator. - private nextCellTypeOff = 0; - private nextFloat64Cell = 0; - private nextStringCell = 0; - private nextBlobCell = 0; - private isValid = false; - - constructor(querySpec: Row, rowData: Row, res: QueryResultImpl) { - Object.assign(this, querySpec); - this.rowData = rowData; - this.rowSpec = {...querySpec}; // ... -> Copy all the key/value pairs. - this.resultObj = res; - this.next(); - } - - valid(): boolean { - return this.isValid; - } - - - get(columnName: string): ColumnType { - const res = this.rowData[columnName]; - if (res === undefined) { - throw new Error( - `Column '${columnName}' doesn't exist. ` + - `Actual columns: [${this.columnNames.join(',')}]`); - } - return res; - } - - // Moves the cursor next by one row and updates |isValid|. - // When this fails to move, two cases are possible: - // 1. We reached the end of the result set (this is the case if - // QueryResult.isComplete() == true when this fails). - // 2. We reached the end of the current batch, but more rows might come later - // (if QueryResult.isComplete() == false). - next() { - // At some point we might reach the end of the current batch, but the next - // batch might be available already. In this case we want next() to - // transparently move on to the next batch. - while (this.nextCellTypeOff + this.numColumns > this.cellTypesEnd) { - // If TraceProcessor is behaving well, we should never end up in a - // situation where we have leftover cells. TP is expected to serialize - // whole rows in each QueryResult batch and NOT truncate them midway. - // If this assert fires the TP RPC logic has a bug. - assertTrue( - this.nextCellTypeOff === this.cellTypesEnd || - this.cellTypesEnd === -1); - if (!this.tryMoveToNextBatch()) { - this.isValid = false; - return; - } - } - - const rowData = this.rowData; - const numColumns = this.numColumns; - - // Read the current row. - for (let i = 0; i < numColumns; i++) { - const cellType = this.batchBytes[this.nextCellTypeOff++]; - const colName = this.columnNames[i]; - const expType = this.rowSpec[colName]; - - switch (cellType) { - case CellType.CELL_NULL: - rowData[colName] = null; - break; - - case CellType.CELL_VARINT: - if (expType === NUM || expType === NUM_NULL) { - // This is very subtle. The return type of int64 can be either a - // number or a Long.js {high:number, low:number} if Long.js is - // installed. The default state seems different in node and browser. - // We force-disable Long.js support in the top of this source file. - const val = this.varIntReader.int64(); - rowData[colName] = val as {} as number; - } else { - // LONG, LONG_NULL, or unspecified - return as bigint - const value = - decodeInt64Varint(this.batchBytes, this.varIntReader.pos); - rowData[colName] = value; - this.varIntReader.skip(); // Skips a varint - } - break; - - case CellType.CELL_FLOAT64: - rowData[colName] = this.float64Cells[this.nextFloat64Cell++]; - break; - - case CellType.CELL_STRING: - rowData[colName] = this.stringCells[this.nextStringCell++]; - break; - - case CellType.CELL_BLOB: - const blob = this.blobCells[this.nextBlobCell++]; - rowData[colName] = blob; - break; - - default: - throw new Error(`Invalid cell type ${cellType}`); - } - } // For (cells) - this.isValid = true; - } - - private tryMoveToNextBatch(): boolean { - const nextBatchIdx = this.batchIdx + 1; - if (nextBatchIdx >= this.resultObj.batches.length) { - return false; - } - - this.columnNames = this.resultObj.columnNames; - this.numColumns = this.columnNames.length; - - this.batchIdx = nextBatchIdx; - const batch = assertExists(this.resultObj.batches[nextBatchIdx]); - this.batchBytes = batch.batchBytes; - this.nextCellTypeOff = batch.cellTypesOff; - this.cellTypesEnd = batch.cellTypesOff + batch.cellTypesLen; - this.float64Cells = batch.float64Cells; - this.blobCells = batch.blobCells; - this.stringCells = batch.stringCells; - this.varIntReader = protobuf.Reader.create(batch.batchBytes); - this.varIntReader.pos = batch.varintOff; - this.varIntReader.len = batch.varintOff + batch.varintLen; - this.nextFloat64Cell = 0; - this.nextStringCell = 0; - this.nextBlobCell = 0; - - // Check that all the expected columns are present. - for (const expectedCol of Object.keys(this.rowSpec)) { - if (this.columnNames.indexOf(expectedCol) < 0) { - throw new Error( - `Column ${expectedCol} not found in the SQL result ` + - `set {${this.columnNames.join(' ')}}`); - } - } - - // Check that the cells types are consistent. - const numColumns = this.numColumns; - if (batch.numCells === 0) { - // This can happen if the query result contains just an error. In this - // an empty batch with isLastBatch=true is appended as an EOF marker. - // In theory TraceProcessor could return an empty batch in the middle and - // that would be fine from a protocol viewpoint. In practice, no code path - // does that today so it doesn't make sense trying supporting it with a - // recursive call to tryMoveToNextBatch(). - assertTrue(batch.isLastBatch); - return false; - } - - assertTrue(numColumns > 0); - for (let i = this.nextCellTypeOff; i < this.cellTypesEnd; i++) { - const col = (i - this.nextCellTypeOff) % numColumns; - const colName = this.columnNames[col]; - const actualType = this.batchBytes[i] as CellType; - const expType = this.rowSpec[colName]; - - // If undefined, the caller doesn't want to read this column at all, so - // it can be whatever. - if (expType === undefined) continue; - - let err = ''; - if (!isCompatible(actualType, expType)) { - if (actualType === CellType.CELL_NULL) { - err = 'SQL value is NULL but that was not expected' + - ` (expected type: ${columnTypeToString(expType)}). ` + - 'Did you mean NUM_NULL, LONG_NULL, STR_NULL or BLOB_NULL?'; - } else { - err = `Incompatible cell type. Expected: ${ - columnTypeToString( - expType)} actual: ${CELL_TYPE_NAMES[actualType]}`; - } - } - if (err.length > 0) { - throw new Error( - `Error @ row: ${Math.floor(i / numColumns)} col: '` + - `${colName}': ${err}`); - } - } - return true; - } -} - -// This is the object ultimately returned to the client when calling -// QueryResult.iter(...). -// The only reason why this is disjoint from RowIteratorImpl is to avoid -// naming collisions between the members variables required by RowIteratorImpl -// and the column names returned by the iterator. -class RowIteratorImplWithRowData implements RowIteratorBase { - private _impl: RowIteratorImpl; - - next: () => void; - valid: () => boolean; - get: (columnName: string) => ColumnType; - - constructor(querySpec: Row, res: QueryResultImpl) { - const thisAsRow = this as {} as Row; - Object.assign(thisAsRow, querySpec); - this._impl = new RowIteratorImpl(querySpec, thisAsRow, res); - this.next = this._impl.next.bind(this._impl); - this.valid = this._impl.valid.bind(this._impl); - this.get = this._impl.get.bind(this._impl); - } -} - -// This is a proxy object that wraps QueryResultImpl, adding await-ability. -// This is so that: -// 1. Clients that just want to await for the full result set can just call -// await engine.query('...') and will get a QueryResult that is guaranteed -// to be complete. -// 2. Clients that know how to handle the streaming can use it straight away. -class WaitableQueryResultImpl implements QueryResult, WritableQueryResult, - PromiseLike { - private impl: QueryResultImpl; - private thenCalled = false; - - constructor(errorInfo: QueryErrorInfo) { - this.impl = new QueryResultImpl(errorInfo); - } - - // QueryResult implementation. Proxies all calls to the impl object. - iter(spec: T) { - return this.impl.iter(spec); - } - firstRow(spec: T) { - return this.impl.firstRow(spec); - } - waitAllRows() { - return this.impl.waitAllRows(); - } - waitMoreRows() { - return this.impl.waitMoreRows(); - } - isComplete() { - return this.impl.isComplete(); - } - numRows() { - return this.impl.numRows(); - } - columns() { - return this.impl.columns(); - } - error() { - return this.impl.error(); - } - statementCount() { - return this.impl.statementCount(); - } - statementWithOutputCount() { - return this.impl.statementWithOutputCount(); - } - - // WritableQueryResult implementation. - appendResultBatch(resBytes: Uint8Array) { - return this.impl.appendResultBatch(resBytes); - } - - // PromiseLike implementaton. - - then(onfulfilled: any, onrejected: any): any { - assertFalse(this.thenCalled); - this.thenCalled = true; - return this.impl.ensureAllRowsPromise().then(onfulfilled, onrejected); - } - - catch(error: any): any { - return this.impl.ensureAllRowsPromise().catch(error); - } - - finally(callback: () => void): any { - return this.impl.ensureAllRowsPromise().finally(callback); - } - - // eslint and clang-format disagree on how to format get[foo](). Let - // clang-format win: - // eslint-disable-next-line keyword-spacing - get[Symbol.toStringTag](): string { - return 'Promise'; - } -} - -export function createQueryResult(errorInfo: QueryErrorInfo): QueryResult& - Promise&WritableQueryResult { - return new WaitableQueryResultImpl(errorInfo); -} diff --git a/third_party/perfetto/ui/src/common/query_result_unittest.ts b/third_party/perfetto/ui/src/common/query_result_unittest.ts deleted file mode 100644 index 50641fe7c6e2..000000000000 --- a/third_party/perfetto/ui/src/common/query_result_unittest.ts +++ /dev/null @@ -1,463 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import protoNamespace from '../gen/protos'; - -import { - createQueryResult, - decodeInt64Varint, - NUM, - NUM_NULL, - STR, - STR_NULL, -} from './query_result'; - -const T = protoNamespace.perfetto.protos.QueryResult.CellsBatch.CellType; -const QueryResultProto = protoNamespace.perfetto.protos.QueryResult; - -test('QueryResult.SimpleOneRow', () => { - const batch = QueryResultProto.CellsBatch.create({ - cells: [T.CELL_STRING, T.CELL_VARINT, T.CELL_STRING, T.CELL_FLOAT64], - varintCells: [42], - stringCells: ['the foo', 'the bar'].join('\0'), - float64Cells: [42.42], - isLastBatch: true, - }); - const resProto = QueryResultProto.create({ - columnNames: ['a_str', 'b_int', 'c_str', 'd_float'], - batch: [batch], - }); - - const qr = createQueryResult({query: 'Some query'}); - qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); - expect(qr.isComplete()).toBe(true); - expect(qr.numRows()).toBe(1); - - // First try iterating without selecting any column. - { - const iter = qr.iter({}); - expect(iter.valid()).toBe(true); - iter.next(); - expect(iter.valid()).toBe(false); - } - - // Then select only two of them. - { - const iter = qr.iter({c_str: STR, d_float: NUM}); - expect(iter.valid()).toBe(true); - expect(iter.c_str).toBe('the bar'); - expect(iter.d_float).toBeCloseTo(42.42); - iter.next(); - expect(iter.valid()).toBe(false); - } - - // If a column is not present in the result set, iter() should throw. - expect(() => qr.iter({nx: NUM})).toThrowError(/\bnx\b.*not found/); -}); - -test('QueryResult.BigNumbers', () => { - const numAndExpectedStr = [ - [0, '0'], - [-1, '-1'], - [-1000, '-1000'], - [1e12, '1000000000000'], - [1e12 * -1, '-1000000000000'], - [((1 << 31) - 1) | 0, '2147483647'], - [1 << 31, '-2147483648'], - [Number.MAX_SAFE_INTEGER, '9007199254740991'], - [Number.MIN_SAFE_INTEGER, '-9007199254740991'], - ]; - const batch = QueryResultProto.CellsBatch.create({ - cells: new Array(numAndExpectedStr.length).fill(T.CELL_VARINT), - varintCells: numAndExpectedStr.map((x) => x[0]) as number[], - isLastBatch: true, - }); - const resProto = QueryResultProto.create({ - columnNames: ['n'], - batch: [batch], - }); - - const qr = createQueryResult({query: 'Some query'}); - qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); - const actual: string[] = []; - for (const iter = qr.iter({n: NUM}); iter.valid(); iter.next()) { - actual.push(BigInt(iter.n).toString()); - } - expect(actual).toEqual(numAndExpectedStr.map((x) => x[1]) as string[]); -}); - -test('QueryResult.Floats', () => { - const floats = [ - 0.0, - 1.0, - -1.0, - 3.14159265358, - Number.MIN_SAFE_INTEGER, - Number.MAX_SAFE_INTEGER, - Number.NEGATIVE_INFINITY, - Number.POSITIVE_INFINITY, - Number.NaN, - ]; - const batch = QueryResultProto.CellsBatch.create({ - cells: new Array(floats.length).fill(T.CELL_FLOAT64), - float64Cells: floats, - isLastBatch: true, - }); - const resProto = QueryResultProto.create({ - columnNames: ['n'], - batch: [batch], - }); - - const qr = createQueryResult({query: 'Some query'}); - qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); - const actual: number[] = []; - for (const iter = qr.iter({n: NUM}); iter.valid(); iter.next()) { - actual.push(iter.n); - } - expect(actual).toEqual(floats); -}); - -test('QueryResult.Strings', () => { - const strings = [ - 'a', - '', - '', - 'hello world', - 'In einem Bächlein helle da schoß in froher Eil', - '色は匂へど散りぬるを我が世誰ぞ常ならん有為の奥山今日越えて浅き夢見じ酔ひもせず', - ]; - const batch = QueryResultProto.CellsBatch.create({ - cells: new Array(strings.length).fill(T.CELL_STRING), - stringCells: strings.join('\0'), - isLastBatch: true, - }); - const resProto = QueryResultProto.create({ - columnNames: ['s'], - batch: [batch], - }); - - const qr = createQueryResult({query: 'Some query'}); - qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); - const actual: string[] = []; - for (const iter = qr.iter({s: STR}); iter.valid(); iter.next()) { - actual.push(iter.s); - } - expect(actual).toEqual(strings); -}); - -test('QueryResult.NullChecks', () => { - const cells: number[] = []; - cells.push(T.CELL_VARINT, T.CELL_NULL); - cells.push(T.CELL_NULL, T.CELL_STRING); - cells.push(T.CELL_VARINT, T.CELL_STRING); - const batch = QueryResultProto.CellsBatch.create({ - cells, - varintCells: [1, 2], - stringCells: ['a', 'b'].join('\0'), - isLastBatch: true, - }); - const resProto = QueryResultProto.create({ - columnNames: ['n', 's'], - batch: [batch], - }); - - const qr = createQueryResult({query: 'Some query'}); - qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); - const actualNums = new Array(); - const actualStrings = new Array(); - for (const iter = qr.iter({n: NUM_NULL, s: STR_NULL}); iter.valid(); - iter.next()) { - actualNums.push(iter.n); - actualStrings.push(iter.s); - } - expect(actualNums).toEqual([1, null, 2]); - expect(actualStrings).toEqual([null, 'a', 'b']); - - // Check that using NUM / STR throws. - expect(() => qr.iter({n: NUM_NULL, s: STR})) - .toThrowError(/col: 's'.*is NULL.*not expected/); - expect(() => qr.iter({n: NUM, s: STR_NULL})) - .toThrowError(/col: 'n'.*is NULL.*not expected/); - expect(qr.iter({n: NUM_NULL})).toBeTruthy(); - expect(qr.iter({s: STR_NULL})).toBeTruthy(); -}); - -test('QueryResult.EarlyError', () => { - const resProto = QueryResultProto.create({ - columnNames: [], - batch: [{isLastBatch: true}], - error: 'Oh dear, this SQL query is too complicated, I give up', - }); - const qr = createQueryResult({query: 'Some query'}); - qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); - expect(qr.error()).toContain('Oh dear'); - expect(qr.isComplete()).toBe(true); - const iter = qr.iter({}); - expect(iter.valid()).toBe(false); -}); - -test('QueryResult.LateError', () => { - const resProto = QueryResultProto.create({ - columnNames: ['n'], - batch: [ - { - cells: [T.CELL_VARINT], - varintCells: [1], - }, - { - cells: [T.CELL_VARINT], - varintCells: [2], - isLastBatch: true, - }, - ], - error: 'I tried, I was getting there, but then I failed', - }); - const qr = createQueryResult({query: 'Some query'}); - qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); - expect(qr.error()).toContain('I failed'); - const rows: number[] = []; - for (const iter = qr.iter({n: NUM}); iter.valid(); iter.next()) { - rows.push(iter.n); - } - expect(rows).toEqual([1, 2]); - expect(qr.isComplete()).toBe(true); -}); - - -test('QueryResult.MultipleBatches', async () => { - const batch1 = QueryResultProto.create({ - columnNames: ['n'], - batch: [{ - cells: [T.CELL_VARINT], - varintCells: [1], - isLastBatch: false, - }], - }); - const batch2 = QueryResultProto.create({ - batch: [{ - cells: [T.CELL_VARINT], - varintCells: [2], - isLastBatch: true, - }], - }); - - const qr = createQueryResult({query: 'Some query'}); - expect(qr.isComplete()).toBe(false); - - qr.appendResultBatch(QueryResultProto.encode(batch1).finish()); - qr.appendResultBatch(QueryResultProto.encode(batch2).finish()); - - const awaitRes = await qr; - - expect(awaitRes.isComplete()).toBe(true); - expect(qr.isComplete()).toBe(true); - - expect(awaitRes.numRows()).toBe(2); - expect(qr.numRows()).toBe(2); -}); - - -// Regression test for b/194891824 . -test('QueryResult.DuplicateColumnNames', () => { - const batch = QueryResultProto.CellsBatch.create({ - cells: [ - T.CELL_VARINT, - T.CELL_STRING, - T.CELL_FLOAT64, - T.CELL_STRING, - T.CELL_STRING, - ], - varintCells: [42], - stringCells: ['a', 'b', 'c'].join('\0'), - float64Cells: [4.2], - isLastBatch: true, - }); - const resProto = QueryResultProto.create({ - columnNames: ['x', 'y', 'x', 'x', 'y'], - batch: [batch], - }); - - const qr = createQueryResult({query: 'Some query'}); - qr.appendResultBatch(QueryResultProto.encode(resProto).finish()); - expect(qr.isComplete()).toBe(true); - expect(qr.numRows()).toBe(1); - expect(qr.columns()).toEqual(['x', 'y', 'x_1', 'x_2', 'y_1']); - // First try iterating without selecting any column. - { - const iter = qr.iter({x: NUM, y: STR, x_1: NUM, x_2: STR, y_1: STR}); - expect(iter.valid()).toBe(true); - expect(iter.x).toBe(42); - expect(iter.y).toBe('a'); - expect(iter.x_1).toBe(4.2); - expect(iter.x_2).toBe('b'); - expect(iter.y_1).toBe('c'); - iter.next(); - expect(iter.valid()).toBe(false); - } - expect(() => qr.iter({x_3: NUM})).toThrowError(/\bx_3\b.*not found/); -}); - - -test('QueryResult.WaitMoreRows', async () => { - const batchA = QueryResultProto.CellsBatch.create({ - cells: [T.CELL_VARINT], - varintCells: [42], - isLastBatch: false, - }); - const resProtoA = QueryResultProto.create({ - columnNames: ['a_int'], - batch: [batchA], - }); - - const qr = createQueryResult({query: 'Some query'}); - qr.appendResultBatch(QueryResultProto.encode(resProtoA).finish()); - - const batchB = QueryResultProto.CellsBatch.create({ - cells: [T.CELL_VARINT], - varintCells: [43], - isLastBatch: true, - }); - const resProtoB = QueryResultProto.create({ - columnNames: [], - batch: [batchB], - }); - - const waitPromise = qr.waitMoreRows(); - const appendPromise = new Promise((resolve, _) => { - setTimeout(() => { - qr.appendResultBatch(QueryResultProto.encode(resProtoB).finish()); - resolve(); - }, 0); - }); - - expect(qr.isComplete()).toBe(false); - expect(qr.numRows()).toBe(1); - - await Promise.all([waitPromise, appendPromise]); - - expect(qr.isComplete()).toBe(true); - expect(qr.numRows()).toBe(2); -}); - -describe('decodeInt64Varint', () => { - test('Parsing empty input should throw an error', () => { - expect(() => decodeInt64Varint(new Uint8Array(), 0)) - .toThrow('Index out of range'); - }); - - test('Parsing single byte positive integers', () => { - const testData: Array<[Uint8Array, BigInt]> = [ - [new Uint8Array([0x00]), 0n], - [new Uint8Array([0x01]), 1n], - [new Uint8Array([0x7f]), 127n], - ]; - - testData.forEach(([input, expected]) => { - expect(decodeInt64Varint(input, 0)).toEqual(expected); - }); - }); - - test('Parsing multi-byte positive integers', () => { - const testData: Array<[Uint8Array, BigInt]> = [ - [new Uint8Array([0x80, 0x01]), 128n], - [new Uint8Array([0xff, 0x7f]), 16383n], - [new Uint8Array([0x80, 0x80, 0x01]), 16384n], - [new Uint8Array([0xff, 0xff, 0x7f]), 2097151n], - [ - new Uint8Array([ - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0x00, - ]), - 9223372036854775807n, - ], - ]; - - testData.forEach(([input, expected]) => { - expect(decodeInt64Varint(input, 0)).toEqual(expected); - }); - }); - - test('Parsing negative integers', () => { - const testData: Array<[Uint8Array, BigInt]> = [ - [ - new Uint8Array([ - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0x01, - ]), - -1n, - ], - [ - new Uint8Array([ - 0xfe, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0xff, - 0x01, - ]), - -2n, - ], - [ - new Uint8Array([ - 0x80, - 0x80, - 0x80, - 0x80, - 0x80, - 0x80, - 0x80, - 0x80, - 0x80, - 0x01, - ]), - -9223372036854775808n, - ], - ]; - - testData.forEach(([input, expected]) => { - expect(decodeInt64Varint(input, 0)).toEqual(expected); - }); - }); - - test('Parsing with incomplete varint should throw an error', () => { - const testData: Array = [ - new Uint8Array([0x80]), - new Uint8Array([0x80, 0x80]), - ]; - - testData.forEach((input) => { - expect(() => decodeInt64Varint(input, 0)).toThrow('Index out of range'); - }); - }); -}); diff --git a/third_party/perfetto/ui/src/common/query_utils.ts b/third_party/perfetto/ui/src/common/query_utils.ts deleted file mode 100644 index 0c9a8556fab3..000000000000 --- a/third_party/perfetto/ui/src/common/query_utils.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -enum EscapeFlag { - CaseInsensitive = 1, - MatchAny = 2, -} - -function escape(s: string, flags?: number): string { - flags = flags === undefined ? 0 : flags; - // See https://www.sqlite.org/lang_expr.html#:~:text=A%20string%20constant - s = s.replace(/\'/g, '\'\''); - s = s.replace(/\[/g, '[[]'); - if (flags & EscapeFlag.CaseInsensitive) { - s = s.replace(/[a-zA-Z]/g, (m) => { - const lower = m.toLowerCase(); - const upper = m.toUpperCase(); - return `[${lower}${upper}]`; - }); - } - s = s.replace(/\?/g, '[?]'); - s = s.replace(/\*/g, '[*]'); - if (flags & EscapeFlag.MatchAny) { - s = `*${s}*`; - } - s = `'${s}'`; - return s; -} - -export function escapeQuery(s: string): string { - return escape(s); -} - -export function escapeSearchQuery(s: string): string { - return escape(s, EscapeFlag.CaseInsensitive | EscapeFlag.MatchAny); -} - -export function escapeGlob(s: string): string { - // For globs we are only preoccupied by mismatching single quotes. - s = s.replace(/\'/g, '\'\''); - return `'*${s}*'`; -} diff --git a/third_party/perfetto/ui/src/common/query_utils_unittest.ts b/third_party/perfetto/ui/src/common/query_utils_unittest.ts deleted file mode 100644 index 3ac9869307fe..000000000000 --- a/third_party/perfetto/ui/src/common/query_utils_unittest.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {escapeGlob, escapeQuery, escapeSearchQuery} from './query_utils'; - -test('escapeQuery', () => { - expect(escapeQuery(``)).toEqual(`''`); - expect(escapeQuery(`'`)).toEqual(`''''`); - expect(escapeQuery(`hello`)).toEqual(`'hello'`); - expect(escapeQuery('foo\'bar')).toEqual(`'foo''bar'`); - expect(escapeQuery('*_*')).toEqual(`'[*]_[*]'`); - expect(escapeQuery('[]?')).toEqual(`'[[]][?]'`); -}); - -test('escapeSearchQuery', () => { - expect(escapeSearchQuery(``)).toEqual(`'**'`); - expect(escapeSearchQuery(`hello`)).toEqual(`'*[hH][eE][lL][lL][oO]*'`); - expect(escapeSearchQuery('a\'b')).toEqual(`'*[aA]''[bB]*'`); - expect(escapeSearchQuery('*_*')).toEqual(`'*[*]_[*]*'`); - expect(escapeSearchQuery('[]?')).toEqual(`'*[[]][?]*'`); -}); - -test('escapeGlob', () => { - expect(escapeGlob(``)).toEqual(`'**'`); - expect(escapeGlob(`'`)).toEqual(`'*''*'`); - expect(escapeGlob(`hello`)).toEqual(`'*hello*'`); - expect(escapeGlob('foo\'bar')).toEqual(`'*foo''bar*'`); - expect(escapeGlob('*_*')).toEqual(`'**_**'`); - expect(escapeGlob('[]?')).toEqual(`'*[]?*'`); -}); diff --git a/third_party/perfetto/ui/src/common/recordingV2/adb_connection_impl.ts b/third_party/perfetto/ui/src/common/recordingV2/adb_connection_impl.ts deleted file mode 100644 index 9fae9268c0de..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/adb_connection_impl.ts +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {_TextDecoder} from 'custom_utils'; - -import {defer} from '../../base/deferred'; -import {ArrayBufferBuilder} from '../array_buffer_builder'; - -import {AdbFileHandler} from './adb_file_handler'; -import { - AdbConnection, - ByteStream, - OnDisconnectCallback, - OnMessageCallback, -} from './recording_interfaces_v2'; - -const textDecoder = new _TextDecoder(); - -export abstract class AdbConnectionImpl implements AdbConnection { - // onStatus and onDisconnect are set to callbacks passed from the caller. - // This happens for instance in the AndroidWebusbTarget, which instantiates - // them with callbacks passed from the UI. - onStatus: OnMessageCallback = () => {}; - onDisconnect: OnDisconnectCallback = (_) => {}; - - // Starts a shell command, and returns a promise resolved when the command - // completes. - async shellAndWaitCompletion(cmd: string): Promise { - const adbStream = await this.shell(cmd); - const onStreamingEnded = defer(); - - // We wait for the stream to be closed by the device, which happens - // after the shell command is successfully received. - adbStream.addOnStreamCloseCallback(() => { - onStreamingEnded.resolve(); - }); - return onStreamingEnded; - } - - // Starts a shell command, then gathers all its output and returns it as - // a string. - async shellAndGetOutput(cmd: string): Promise { - const adbStream = await this.shell(cmd); - const commandOutput = new ArrayBufferBuilder(); - const onStreamingEnded = defer(); - - adbStream.addOnStreamDataCallback((data: Uint8Array) => { - commandOutput.append(data); - }); - adbStream.addOnStreamCloseCallback(() => { - onStreamingEnded.resolve( - textDecoder.decode(commandOutput.toArrayBuffer())); - }); - return onStreamingEnded; - } - - async push(binary: Uint8Array, path: string): Promise { - const byteStream = await this.openStream('sync:'); - await (new AdbFileHandler(byteStream)).pushBinary(binary, path); - // We need to wait until the bytestream is closed. Otherwise, we can have a - // race condition: - // If this is the last stream, it will try to disconnect the device. In the - // meantime, the caller might create another stream which will try to open - // the device. - await byteStream.closeAndWaitForTeardown(); - } - - abstract shell(cmd: string): Promise; - - abstract canConnectWithoutContention(): Promise; - - abstract connectSocket(path: string): Promise; - - abstract disconnect(disconnectMessage?: string): Promise; - - protected abstract openStream(destination: string): Promise; -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/adb_connection_over_websocket.ts b/third_party/perfetto/ui/src/common/recordingV2/adb_connection_over_websocket.ts deleted file mode 100644 index 7287b654a4a0..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/adb_connection_over_websocket.ts +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {_TextDecoder} from 'custom_utils'; - -import {defer, Deferred} from '../../base/deferred'; - -import {AdbConnectionImpl} from './adb_connection_impl'; -import {RecordingError} from './recording_error_handling'; -import { - ByteStream, - OnDisconnectCallback, - OnStreamCloseCallback, - OnStreamDataCallback, -} from './recording_interfaces_v2'; -import { - ALLOW_USB_DEBUGGING, - buildAbdWebsocketCommand, - WEBSOCKET_UNABLE_TO_CONNECT, -} from './recording_utils'; - -const textDecoder = new _TextDecoder(); - -export class AdbConnectionOverWebsocket extends AdbConnectionImpl { - private streams = new Set(); - - onDisconnect: OnDisconnectCallback = (_) => {}; - - constructor( - private deviceSerialNumber: string, private websocketUrl: string) { - super(); - } - - shell(cmd: string): Promise { - return this.openStream('shell:' + cmd); - } - - connectSocket(path: string): Promise { - return this.openStream(path); - } - - protected async openStream(destination: string): - Promise { - return AdbOverWebsocketStream.create( - this.websocketUrl, - destination, - this.deviceSerialNumber, - this.closeStream.bind(this)); - } - - // The disconnection for AdbConnectionOverWebsocket is synchronous, but this - // method is async to have a common interface with other types of connections - // which are async. - async disconnect(disconnectMessage?: string): Promise { - for (const stream of this.streams) { - stream.close(); - } - this.onDisconnect(disconnectMessage); - } - - closeStream(stream: AdbOverWebsocketStream): void { - if (this.streams.has(stream)) { - this.streams.delete(stream); - } - } - - // There will be no contention for the websocket connection, because it will - // communicate with the 'adb server' running on the computer which opened - // Perfetto. - canConnectWithoutContention(): Promise { - return Promise.resolve(true); - } -} - -// An AdbOverWebsocketStream instantiates a websocket connection to the device. -// It exposes an API to write commands to this websocket and read its output. -export class AdbOverWebsocketStream implements ByteStream { - private websocket: WebSocket; - // commandSentSignal gets resolved if we successfully connect to the device - // and send the command this socket wraps. commandSentSignal gets rejected if - // we fail to connect to the device. - private commandSentSignal = defer(); - // We store a promise for each messge while the message is processed. - // This way, if the websocket server closes the connection, we first process - // all previously received messages and only afterwards disconnect. - // An application is when the stream wraps a shell command. The websocket - // server will reply and then immediately disconnect. - private messageProcessedSignals: Set> = new Set(); - - private _isConnected = false; - private onStreamDataCallbacks: OnStreamDataCallback[] = []; - private onStreamCloseCallbacks: OnStreamCloseCallback[] = []; - - private constructor( - websocketUrl: string, destination: string, deviceSerialNumber: string, - private removeFromConnection: (stream: AdbOverWebsocketStream) => void) { - this.websocket = new WebSocket(websocketUrl); - - this.websocket.onopen = this.onOpen.bind(this, deviceSerialNumber); - this.websocket.onmessage = this.onMessage.bind(this, destination); - // The websocket may be closed by the websocket server. This happens - // for instance when we get the full result of a shell command. - this.websocket.onclose = this.onClose.bind(this); - } - - addOnStreamDataCallback(onStreamData: OnStreamDataCallback) { - this.onStreamDataCallbacks.push(onStreamData); - } - - addOnStreamCloseCallback(onStreamClose: OnStreamCloseCallback) { - this.onStreamCloseCallbacks.push(onStreamClose); - } - - // Used by the connection object to signal newly received data, not exposed - // in the interface. - signalStreamData(data: Uint8Array): void { - for (const onStreamData of this.onStreamDataCallbacks) { - onStreamData(data); - } - } - - // Used by the connection object to signal the stream is closed, not exposed - // in the interface. - signalStreamClosed(): void { - for (const onStreamClose of this.onStreamCloseCallbacks) { - onStreamClose(); - } - this.onStreamDataCallbacks = []; - this.onStreamCloseCallbacks = []; - } - - // We close the websocket and notify the AdbConnection to remove this stream. - close(): void { - // If the websocket connection is still open (ie. the close did not - // originate from the server), we close the websocket connection. - if (this.websocket.readyState === this.websocket.OPEN) { - this.websocket.close(); - // We remove the 'onclose' callback so the 'close' method doesn't get - // executed twice. - this.websocket.onclose = null; - } - this._isConnected = false; - this.removeFromConnection(this); - this.signalStreamClosed(); - } - - // For websocket, the teardown happens synchronously. - async closeAndWaitForTeardown(): Promise { - this.close(); - } - - write(msg: string|Uint8Array): void { - this.websocket.send(msg); - } - - isConnected(): boolean { - return this._isConnected; - } - - private async onOpen(deviceSerialNumber: string): Promise { - this.websocket.send( - buildAbdWebsocketCommand(`host:transport:${deviceSerialNumber}`)); - } - - private async onMessage(destination: string, evt: MessageEvent): - Promise { - const messageProcessed = defer(); - this.messageProcessedSignals.add(messageProcessed); - try { - if (!this._isConnected) { - const txt = await evt.data.text(); - const prefix = txt.substr(0, 4); - if (prefix === 'OKAY') { - this._isConnected = true; - this.websocket.send(buildAbdWebsocketCommand(destination)); - this.commandSentSignal.resolve(this); - } else if (prefix === 'FAIL' && txt.includes('device unauthorized')) { - this.commandSentSignal.reject( - new RecordingError(ALLOW_USB_DEBUGGING)); - this.close(); - } else { - this.commandSentSignal.reject( - new RecordingError(WEBSOCKET_UNABLE_TO_CONNECT)); - this.close(); - } - } else { - // Upon a successful connection we first receive an 'OKAY' message. - // After that, we receive messages with traced binary payloads. - const arrayBufferResponse = await evt.data.arrayBuffer(); - if (textDecoder.decode(arrayBufferResponse) !== 'OKAY') { - this.signalStreamData(new Uint8Array(arrayBufferResponse)); - } - } - messageProcessed.resolve(); - } finally { - this.messageProcessedSignals.delete(messageProcessed); - } - } - - private async onClose(): Promise { - // Wait for all messages to be processed before closing the connection. - await Promise.allSettled(this.messageProcessedSignals); - this.close(); - } - - static create( - websocketUrl: string, destination: string, deviceSerialNumber: string, - removeFromConnection: (stream: AdbOverWebsocketStream) => void): - Promise { - return (new AdbOverWebsocketStream( - websocketUrl, - destination, - deviceSerialNumber, - removeFromConnection)) - .commandSentSignal; - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/adb_connection_over_webusb.ts b/third_party/perfetto/ui/src/common/recordingV2/adb_connection_over_webusb.ts deleted file mode 100644 index d957aabad492..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/adb_connection_over_webusb.ts +++ /dev/null @@ -1,610 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {_TextDecoder, _TextEncoder} from 'custom_utils'; - -import {defer, Deferred} from '../../base/deferred'; -import {assertExists, assertFalse, assertTrue} from '../../base/logging'; -import {CmdType} from '../../controller/adb_interfaces'; - -import {AdbConnectionImpl} from './adb_connection_impl'; -import {AdbKeyManager, maybeStoreKey} from './auth/adb_key_manager'; -import { - RecordingError, - wrapRecordingError, -} from './recording_error_handling'; -import { - ByteStream, - OnStreamCloseCallback, - OnStreamDataCallback, -} from './recording_interfaces_v2'; -import {ALLOW_USB_DEBUGGING, findInterfaceAndEndpoint} from './recording_utils'; - -const textEncoder = new _TextEncoder(); -const textDecoder = new _TextDecoder(); - -export const VERSION_WITH_CHECKSUM = 0x01000000; -export const VERSION_NO_CHECKSUM = 0x01000001; -export const DEFAULT_MAX_PAYLOAD_BYTES = 256 * 1024; - -export enum AdbState { - DISCONNECTED = 0, - // Authentication steps, see AdbConnectionOverWebUsb's handleAuthentication(). - AUTH_STARTED = 1, - AUTH_WITH_PRIVATE = 2, - AUTH_WITH_PUBLIC = 3, - - CONNECTED = 4, -} - -enum AuthCmd { - TOKEN = 1, - SIGNATURE = 2, - RSAPUBLICKEY = 3, -} - -function generateChecksum(data: Uint8Array): number { - let res = 0; - for (let i = 0; i < data.byteLength; i++) res += data[i]; - return res & 0xFFFFFFFF; -} - -// Message to be written to the adb connection. Contains the message itself -// and the corresponding stream identifier. -interface WriteQueueElement { - message: Uint8Array; - localStreamId: number; -} - -export class AdbConnectionOverWebusb extends AdbConnectionImpl { - private state: AdbState = AdbState.DISCONNECTED; - private connectingStreams = new Map>(); - private streams = new Set(); - private maxPayload = DEFAULT_MAX_PAYLOAD_BYTES; - private writeInProgress = false; - private writeQueue: WriteQueueElement[] = []; - - // Devices after Dec 2017 don't use checksum. This will be auto-detected - // during the connection. - private useChecksum = true; - - private lastStreamId = 0; - private usbInterfaceNumber?: number; - private usbReadEndpoint = -1; - private usbWriteEpEndpoint = -1; - private isUsbReceiveLoopRunning = false; - - private pendingConnPromises: Array> = []; - - // We use a key pair for authenticating with the device, which we do in - // two ways: - // - Firstly, signing with the private key. - // - Secondly, sending over the public key (at which point the device asks the - // user for permissions). - // Once we've sent the public key, for future recordings we only need to - // sign with the private key, so the user doesn't need to give permissions - // again. - constructor(private device: USBDevice, private keyManager: AdbKeyManager) { - super(); - } - - - shell(cmd: string): Promise { - return this.openStream('shell:' + cmd); - } - - connectSocket(path: string): Promise { - return this.openStream(path); - } - - async canConnectWithoutContention(): Promise { - await this.device.open(); - const usbInterfaceNumber = await this.setupUsbInterface(); - try { - await this.device.claimInterface(usbInterfaceNumber); - await this.device.releaseInterface(usbInterfaceNumber); - return true; - } catch (e) { - return false; - } - } - - protected async openStream(destination: string): - Promise { - const streamId = ++this.lastStreamId; - const connectingStream = defer(); - this.connectingStreams.set(streamId, connectingStream); - // We create the stream before trying to establish the connection, so - // that if we fail to connect, we will reject the connecting stream. - await this.ensureConnectionEstablished(); - await this.sendMessage('OPEN', streamId, 0, destination); - return connectingStream; - } - - private async ensureConnectionEstablished(): Promise { - if (this.state === AdbState.CONNECTED) { - return; - } - - if (this.state === AdbState.DISCONNECTED) { - await this.device.open(); - if (!(await this.canConnectWithoutContention())) { - await this.device.reset(); - } - const usbInterfaceNumber = await this.setupUsbInterface(); - await this.device.claimInterface(usbInterfaceNumber); - } - - await this.startAdbAuth(); - if (!this.isUsbReceiveLoopRunning) { - this.usbReceiveLoop(); - } - const connPromise = defer(); - this.pendingConnPromises.push(connPromise); - await connPromise; - } - - private async setupUsbInterface(): Promise { - const interfaceAndEndpoint = findInterfaceAndEndpoint(this.device); - // `findInterfaceAndEndpoint` will always return a non-null value because - // we check for this in 'android_webusb_target_factory'. If no interface and - // endpoints are found, we do not create a target, so we can not connect to - // it, so we will never reach this logic. - const {configurationValue, usbInterfaceNumber, endpoints} = - assertExists(interfaceAndEndpoint); - this.usbInterfaceNumber = usbInterfaceNumber; - this.usbReadEndpoint = this.findEndpointNumber(endpoints, 'in'); - this.usbWriteEpEndpoint = this.findEndpointNumber(endpoints, 'out'); - assertTrue(this.usbReadEndpoint >= 0 && this.usbWriteEpEndpoint >= 0); - await this.device.selectConfiguration(configurationValue); - return usbInterfaceNumber; - } - - async streamClose(stream: AdbOverWebusbStream): Promise { - const otherStreamsQueue = this.writeQueue.filter( - (queueElement) => queueElement.localStreamId !== stream.localStreamId); - const droppedPacketCount = - this.writeQueue.length - otherStreamsQueue.length; - if (droppedPacketCount > 0) { - console.debug(`Dropping ${ - droppedPacketCount} queued messages due to stream closing.`); - this.writeQueue = otherStreamsQueue; - } - - this.streams.delete(stream); - if (this.streams.size === 0) { - // We disconnect BEFORE calling `signalStreamClosed`. Otherwise, there can - // be a race condition: - // Stream A: streamA.onStreamClose - // Stream B: device.open - // Stream A: device.releaseInterface - // Stream B: device.transferOut -> CRASH - await this.disconnect(); - } - stream.signalStreamClosed(); - } - - streamWrite(msg: string|Uint8Array, stream: AdbOverWebusbStream): void { - const raw = (typeof msg === 'string') ? textEncoder.encode(msg) : msg; - if (this.writeInProgress) { - this.writeQueue.push({message: raw, localStreamId: stream.localStreamId}); - return; - } - this.writeInProgress = true; - this.sendMessage('WRTE', stream.localStreamId, stream.remoteStreamId, raw); - } - - // We disconnect in 2 cases: - // 1. When we close the last stream of the connection. This is to prevent the - // browser holding onto the USB interface after having finished a trace - // recording, which would make it impossible to use "adb shell" from the same - // machine until the browser is closed. - // 2. When we get a USB disconnect event. This happens for instance when the - // device is unplugged. - async disconnect(disconnectMessage?: string): Promise { - if (this.state === AdbState.DISCONNECTED) { - return; - } - // Clear the resources in a synchronous method, because this can be used - // for error handling callbacks as well. - this.reachDisconnectState(disconnectMessage); - - // We have already disconnected so there is no need to pass a callback - // which clears resources or notifies the user into 'wrapRecordingError'. - await wrapRecordingError( - this.device.releaseInterface(assertExists(this.usbInterfaceNumber)), - () => {}); - this.usbInterfaceNumber = undefined; - } - - // This is a synchronous method which clears all resources. - // It can be used as a callback for error handling. - reachDisconnectState(disconnectMessage?: string): void { - // We need to delete the streams BEFORE checking the Adb state because: - // - // We create streams before changing the Adb state from DISCONNECTED. - // In case we can not claim the device, we will create a stream, but fail - // to connect to the WebUSB device so the state will remain DISCONNECTED. - const streamsToDelete = this.connectingStreams.entries(); - // Clear the streams before rejecting so we are not caught in a loop of - // handling promise rejections. - this.connectingStreams.clear(); - for (const [id, stream] of streamsToDelete) { - stream.reject( - `Failed to open stream with id ${id} because adb was disconnected.`); - } - - if (this.state === AdbState.DISCONNECTED) { - return; - } - - this.state = AdbState.DISCONNECTED; - this.writeInProgress = false; - - this.writeQueue = []; - - this.streams.forEach((stream) => stream.close()); - this.onDisconnect(disconnectMessage); - } - - private async startAdbAuth(): Promise { - const VERSION = - this.useChecksum ? VERSION_WITH_CHECKSUM : VERSION_NO_CHECKSUM; - this.state = AdbState.AUTH_STARTED; - await this.sendMessage('CNXN', VERSION, this.maxPayload, 'host:1:UsbADB'); - } - - private findEndpointNumber( - endpoints: USBEndpoint[], direction: 'out'|'in', type = 'bulk'): number { - const ep = - endpoints.find((ep) => ep.type === type && ep.direction === direction); - - if (ep) return ep.endpointNumber; - - throw new RecordingError(`Cannot find ${direction} endpoint`); - } - - private async usbReceiveLoop(): Promise { - assertFalse(this.isUsbReceiveLoopRunning); - this.isUsbReceiveLoopRunning = true; - for (; this.state !== AdbState.DISCONNECTED;) { - const res = await this.wrapUsb( - this.device.transferIn(this.usbReadEndpoint, ADB_MSG_SIZE)); - if (!res) { - this.isUsbReceiveLoopRunning = false; - return; - } - if (res.status !== 'ok') { - // Log and ignore messages with invalid status. These can occur - // when the device is connected/disconnected repeatedly. - console.error( - `Received message with unexpected status '${res.status}'`); - continue; - } - - const msg = AdbMsg.decodeHeader(res.data!); - if (msg.dataLen > 0) { - const resp = await this.wrapUsb( - this.device.transferIn(this.usbReadEndpoint, msg.dataLen)); - if (!resp) { - this.isUsbReceiveLoopRunning = false; - return; - } - msg.data = new Uint8Array( - resp.data!.buffer, resp.data!.byteOffset, resp.data!.byteLength); - } - - if (this.useChecksum && generateChecksum(msg.data) !== msg.dataChecksum) { - // We ignore messages with an invalid checksum. These sometimes appear - // when the page is re-loaded in a middle of a recording. - continue; - } - // The server can still send messages streams for previous streams. - // This happens for instance if we record, reload the recording page and - // then record again. We can also receive a 'WRTE' or 'OKAY' after - // we have sent a 'CLSE' and marked the state as disconnected. - if ((msg.cmd === 'CLSE' || msg.cmd === 'WRTE') && - !this.getStreamForLocalStreamId(msg.arg1)) { - continue; - } else if ( - msg.cmd === 'OKAY' && !this.connectingStreams.has(msg.arg1) && - !this.getStreamForLocalStreamId(msg.arg1)) { - continue; - } else if ( - msg.cmd === 'AUTH' && msg.arg0 === AuthCmd.TOKEN && - this.state === AdbState.AUTH_WITH_PUBLIC) { - // If we start a recording but fail because of a faulty physical - // connection to the device, when we start a new recording, we will - // received multiple AUTH tokens, of which we should ignore all but - // one. - continue; - } - - // handle the ADB message from the device - if (msg.cmd === 'CLSE') { - assertExists(this.getStreamForLocalStreamId(msg.arg1)).close(); - } else if (msg.cmd === 'AUTH' && msg.arg0 === AuthCmd.TOKEN) { - const key = await this.keyManager.getKey(); - if (this.state === AdbState.AUTH_STARTED) { - // During this step, we send back the token received signed with our - // private key. If the device has previously received our public key, - // the dialog asking for user confirmation will not be displayed on - // the device. - this.state = AdbState.AUTH_WITH_PRIVATE; - await this.sendMessage( - 'AUTH', AuthCmd.SIGNATURE, 0, key.sign(msg.data)); - } else { - // If our signature with the private key is not accepted by the - // device, we generate a new keypair and send the public key. - this.state = AdbState.AUTH_WITH_PUBLIC; - await this.sendMessage( - 'AUTH', AuthCmd.RSAPUBLICKEY, 0, key.getPublicKey() + '\0'); - this.onStatus(ALLOW_USB_DEBUGGING); - await maybeStoreKey(key); - } - } else if (msg.cmd === 'CNXN') { - assertTrue( - [AdbState.AUTH_WITH_PRIVATE, AdbState.AUTH_WITH_PUBLIC].includes( - this.state)); - this.state = AdbState.CONNECTED; - this.maxPayload = msg.arg1; - - const deviceVersion = msg.arg0; - - if (![VERSION_WITH_CHECKSUM, VERSION_NO_CHECKSUM].includes( - deviceVersion)) { - throw new RecordingError(`Version ${msg.arg0} not supported.`); - } - this.useChecksum = deviceVersion === VERSION_WITH_CHECKSUM; - this.state = AdbState.CONNECTED; - - // This will resolve the promises awaited by - // "ensureConnectionEstablished". - this.pendingConnPromises.forEach( - (connPromise) => connPromise.resolve()); - this.pendingConnPromises = []; - } else if (msg.cmd === 'OKAY') { - if (this.connectingStreams.has(msg.arg1)) { - const connectingStream = - assertExists(this.connectingStreams.get(msg.arg1)); - const stream = new AdbOverWebusbStream(this, msg.arg1, msg.arg0); - this.streams.add(stream); - this.connectingStreams.delete(msg.arg1); - connectingStream.resolve(stream); - } else { - assertTrue(this.writeInProgress); - this.writeInProgress = false; - for (; this.writeQueue.length;) { - // We go through the queued writes and choose the first one - // corresponding to a stream that's still active. - const queuedElement = assertExists(this.writeQueue.shift()); - const queuedStream = - this.getStreamForLocalStreamId(queuedElement.localStreamId); - if (queuedStream) { - queuedStream.write(queuedElement.message); - break; - } - } - } - } else if (msg.cmd === 'WRTE') { - const stream = assertExists(this.getStreamForLocalStreamId(msg.arg1)); - await this.sendMessage( - 'OKAY', stream.localStreamId, stream.remoteStreamId); - stream.signalStreamData(msg.data); - } else { - this.isUsbReceiveLoopRunning = false; - throw new RecordingError( - `Unexpected message ${msg} in state ${this.state}`); - } - } - this.isUsbReceiveLoopRunning = false; - } - - private getStreamForLocalStreamId(localStreamId: number): AdbOverWebusbStream - |undefined { - for (const stream of this.streams) { - if (stream.localStreamId === localStreamId) { - return stream; - } - } - return undefined; - } - - // The header and the message data must be sent consecutively. Using 2 awaits - // Another message can interleave after the first header has been sent, - // resulting in something like [header1] [header2] [data1] [data2]; - // In this way we are waiting both promises to be resolved before continuing. - private async sendMessage( - cmd: CmdType, arg0: number, arg1: number, - data?: Uint8Array|string): Promise { - const msg = - AdbMsg.create({cmd, arg0, arg1, data, useChecksum: this.useChecksum}); - - const msgHeader = msg.encodeHeader(); - const msgData = msg.data; - assertTrue( - msgHeader.length <= this.maxPayload && - msgData.length <= this.maxPayload); - - const sendPromises = [this.wrapUsb( - this.device.transferOut(this.usbWriteEpEndpoint, msgHeader.buffer))]; - if (msg.data.length > 0) { - sendPromises.push(this.wrapUsb( - this.device.transferOut(this.usbWriteEpEndpoint, msgData.buffer))); - } - await Promise.all(sendPromises); - } - - private wrapUsb(promise: Promise): Promise { - return wrapRecordingError(promise, this.reachDisconnectState.bind(this)); - } -} - -// An AdbOverWebusbStream is instantiated after the creation of a socket to the -// device. Thanks to this, we can send commands and receive their output. -// Messages are received in the main adb class, and are forwarded to an instance -// of this class based on a stream id match. -export class AdbOverWebusbStream implements ByteStream { - private adbConnection: AdbConnectionOverWebusb; - private _isConnected: boolean; - private onStreamDataCallbacks: OnStreamDataCallback[] = []; - private onStreamCloseCallbacks: OnStreamCloseCallback[] = []; - localStreamId: number; - remoteStreamId = -1; - - constructor( - adb: AdbConnectionOverWebusb, localStreamId: number, - remoteStreamId: number) { - this.adbConnection = adb; - this.localStreamId = localStreamId; - this.remoteStreamId = remoteStreamId; - // When the stream is created, the connection has been already established. - this._isConnected = true; - } - - addOnStreamDataCallback(onStreamData: OnStreamDataCallback): void { - this.onStreamDataCallbacks.push(onStreamData); - } - - addOnStreamCloseCallback(onStreamClose: OnStreamCloseCallback): void { - this.onStreamCloseCallbacks.push(onStreamClose); - } - - // Used by the connection object to signal newly received data, not exposed - // in the interface. - signalStreamData(data: Uint8Array): void { - for (const onStreamData of this.onStreamDataCallbacks) { - onStreamData(data); - } - } - - // Used by the connection object to signal the stream is closed, not exposed - // in the interface. - signalStreamClosed(): void { - for (const onStreamClose of this.onStreamCloseCallbacks) { - onStreamClose(); - } - this.onStreamDataCallbacks = []; - this.onStreamCloseCallbacks = []; - } - - - close(): void { - this.closeAndWaitForTeardown(); - } - - async closeAndWaitForTeardown(): Promise { - this._isConnected = false; - await this.adbConnection.streamClose(this); - } - - write(msg: string|Uint8Array): void { - this.adbConnection.streamWrite(msg, this); - } - - isConnected(): boolean { - return this._isConnected; - } -} - -const ADB_MSG_SIZE = 6 * 4; // 6 * int32. - -class AdbMsg { - data: Uint8Array; - readonly cmd: CmdType; - readonly arg0: number; - readonly arg1: number; - readonly dataLen: number; - readonly dataChecksum: number; - readonly useChecksum: boolean; - - constructor( - cmd: CmdType, arg0: number, arg1: number, dataLen: number, - dataChecksum: number, useChecksum = false) { - assertTrue(cmd.length === 4); - this.cmd = cmd; - this.arg0 = arg0; - this.arg1 = arg1; - this.dataLen = dataLen; - this.data = new Uint8Array(dataLen); - this.dataChecksum = dataChecksum; - this.useChecksum = useChecksum; - } - - static create({cmd, arg0, arg1, data, useChecksum = true}: { - cmd: CmdType; arg0: number; arg1: number; - data?: Uint8Array | string; - useChecksum?: boolean; - }): AdbMsg { - const encodedData = this.encodeData(data); - const msg = new AdbMsg(cmd, arg0, arg1, encodedData.length, 0, useChecksum); - msg.data = encodedData; - return msg; - } - - get dataStr() { - return textDecoder.decode(this.data); - } - - toString() { - return `${this.cmd} [${this.arg0},${this.arg1}] ${this.dataStr}`; - } - - // A brief description of the message can be found here: - // https://android.googlesource.com/platform/system/core/+/master/adb/protocol.txt - // - // struct amessage { - // uint32_t command; // command identifier constant - // uint32_t arg0; // first argument - // uint32_t arg1; // second argument - // uint32_t data_length;// length of payload (0 is allowed) - // uint32_t data_check; // checksum of data payload - // uint32_t magic; // command ^ 0xffffffff - // }; - static decodeHeader(dv: DataView): AdbMsg { - assertTrue(dv.byteLength === ADB_MSG_SIZE); - const cmd = textDecoder.decode(dv.buffer.slice(0, 4)) as CmdType; - const cmdNum = dv.getUint32(0, true); - const arg0 = dv.getUint32(4, true); - const arg1 = dv.getUint32(8, true); - const dataLen = dv.getUint32(12, true); - const dataChecksum = dv.getUint32(16, true); - const cmdChecksum = dv.getUint32(20, true); - assertTrue(cmdNum === (cmdChecksum ^ 0xFFFFFFFF)); - return new AdbMsg(cmd, arg0, arg1, dataLen, dataChecksum); - } - - encodeHeader(): Uint8Array { - const buf = new Uint8Array(ADB_MSG_SIZE); - const dv = new DataView(buf.buffer); - const cmdBytes: Uint8Array = textEncoder.encode(this.cmd); - const rawMsg = AdbMsg.encodeData(this.data); - const checksum = this.useChecksum ? generateChecksum(rawMsg) : 0; - for (let i = 0; i < 4; i++) dv.setUint8(i, cmdBytes[i]); - - dv.setUint32(4, this.arg0, true); - dv.setUint32(8, this.arg1, true); - dv.setUint32(12, rawMsg.byteLength, true); - dv.setUint32(16, checksum, true); - dv.setUint32(20, dv.getUint32(0, true) ^ 0xFFFFFFFF, true); - - return buf; - } - - static encodeData(data?: Uint8Array|string): Uint8Array { - if (data === undefined) return new Uint8Array([]); - if (typeof data === 'string') return textEncoder.encode(data + '\0'); - return data; - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/adb_file_handler.ts b/third_party/perfetto/ui/src/common/recordingV2/adb_file_handler.ts deleted file mode 100644 index d659adb0d2d7..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/adb_file_handler.ts +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {_TextDecoder} from 'custom_utils'; - -import {defer, Deferred} from '../../base/deferred'; -import {assertFalse} from '../../base/logging'; -import {ArrayBufferBuilder} from '../array_buffer_builder'; - -import {RecordingError} from './recording_error_handling'; -import {ByteStream} from './recording_interfaces_v2'; -import { - BINARY_PUSH_FAILURE, - BINARY_PUSH_UNKNOWN_RESPONSE, -} from './recording_utils'; - -// https://cs.android.com/android/platform/superproject/+/master:packages/ -// modules/adb/file_sync_protocol.h;l=144 -const MAX_SYNC_SEND_CHUNK_SIZE = 64 * 1024; - -// Adb does not accurately send some file permissions. If you need a special set -// of permissions, do not rely on this value. Rather, send a shell command which -// explicitly sets permissions, such as: -// 'shell:chmod ${permissions} ${path}' -const FILE_PERMISSIONS = 2 ** 15 + 0o644; - -const textDecoder = new _TextDecoder(); - -// For details about the protocol, see: -// https://cs.android.com/android/platform/superproject/+/master:packages/modules/adb/SYNC.TXT -export class AdbFileHandler { - private sentByteCount = 0; - private isPushOngoing: boolean = false; - - constructor(private byteStream: ByteStream) {} - - async pushBinary(binary: Uint8Array, path: string): Promise { - // For a given byteStream, we only support pushing one binary at a time. - assertFalse(this.isPushOngoing); - this.isPushOngoing = true; - const transferFinished = defer(); - - this.byteStream.addOnStreamDataCallback( - (data) => this.onStreamData(data, transferFinished)); - this.byteStream.addOnStreamCloseCallback(() => this.isPushOngoing = false); - - const sendMessage = new ArrayBufferBuilder(); - // 'SEND' is the API method used to send a file to device. - sendMessage.append('SEND'); - // The remote file name is split into two parts separated by the last - // comma (","). The first part is the actual path, while the second is a - // decimal encoded file mode containing the permissions of the file on - // device. - sendMessage.append(path.length + 6); - sendMessage.append(path); - sendMessage.append(','); - sendMessage.append(FILE_PERMISSIONS.toString()); - this.byteStream.write(new Uint8Array(sendMessage.toArrayBuffer())); - - while (!(await this.sendNextDataChunk(binary))) - ; - - return transferFinished; - } - - private onStreamData(data: Uint8Array, transferFinished: Deferred) { - this.sentByteCount = 0; - const response = textDecoder.decode(data); - if (response.split('\n')[0].includes('FAIL')) { - // Sample failure response (when the file is transferred successfully - // but the date is not formatted correctly): - // 'OKAYFAIL\npath too long' - transferFinished.reject( - new RecordingError(`${BINARY_PUSH_FAILURE}: ${response}`)); - } else if (textDecoder.decode(data).substring(0, 4) === 'OKAY') { - // In case of success, the server responds to the last request with - // 'OKAY'. - transferFinished.resolve(); - } else { - throw new RecordingError(`${BINARY_PUSH_UNKNOWN_RESPONSE}: ${response}`); - } - } - - private async sendNextDataChunk(binary: Uint8Array): Promise { - const endPosition = Math.min( - this.sentByteCount + MAX_SYNC_SEND_CHUNK_SIZE, binary.byteLength); - const chunk = await binary.slice(this.sentByteCount, endPosition); - // The file is sent in chunks. Each chunk is prefixed with "DATA" and the - // chunk length. This is repeated until the entire file is transferred. Each - // chunk must not be larger than 64k. - const chunkLength = chunk.byteLength; - const dataMessage = new ArrayBufferBuilder(); - dataMessage.append('DATA'); - dataMessage.append(chunkLength); - dataMessage.append( - new Uint8Array(chunk.buffer, chunk.byteOffset, chunkLength)); - - this.sentByteCount += chunkLength; - const isDone = this.sentByteCount === binary.byteLength; - - if (isDone) { - // When the file is transferred a sync request "DONE" is sent, together - // with a timestamp, representing the last modified time for the file. The - // server responds to this last request. - dataMessage.append('DONE'); - // We send the date in seconds. - dataMessage.append(Math.floor(Date.now() / 1000)); - } - this.byteStream.write(new Uint8Array(dataMessage.toArrayBuffer())); - return isDone; - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/auth/adb_auth.ts b/third_party/perfetto/ui/src/common/recordingV2/auth/adb_auth.ts deleted file mode 100644 index 71f7d19b8448..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/auth/adb_auth.ts +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {BigInteger, RSAKey} from 'jsbn-rsa'; - -import {assertExists, assertTrue} from '../../../base/logging'; -import { - base64Decode, - base64Encode, - hexEncode, -} from '../../../base/string_utils'; -import {RecordingError} from '../recording_error_handling'; - -const WORD_SIZE = 4; -const MODULUS_SIZE_BITS = 2048; -const MODULUS_SIZE = MODULUS_SIZE_BITS / 8; -const MODULUS_SIZE_WORDS = MODULUS_SIZE / WORD_SIZE; -const PUBKEY_ENCODED_SIZE = 3 * WORD_SIZE + 2 * MODULUS_SIZE; -const ADB_WEB_CRYPTO_ALGORITHM = { - name: 'RSASSA-PKCS1-v1_5', - hash: {name: 'SHA-1'}, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537 - modulusLength: MODULUS_SIZE_BITS, -}; - -const ADB_WEB_CRYPTO_EXPORTABLE = true; -const ADB_WEB_CRYPTO_OPERATIONS: KeyUsage[] = ['sign']; - -const SIGNING_ASN1_PREFIX = [ - 0x00, - 0x30, - 0x21, - 0x30, - 0x09, - 0x06, - 0x05, - 0x2B, - 0x0E, - 0x03, - 0x02, - 0x1A, - 0x05, - 0x00, - 0x04, - 0x14, -]; - -const R32 = BigInteger.ONE.shiftLeft(32); // 1 << 32 - -interface ValidJsonWebKey { - n: string; - e: string; - d: string; - p: string; - q: string; - dp: string; - dq: string; - qi: string; -} - -function isValidJsonWebKey(key: JsonWebKey): key is ValidJsonWebKey { - return key.n !== undefined && key.e !== undefined && key.d !== undefined && - key.p !== undefined && key.q !== undefined && key.dp !== undefined && - key.dq !== undefined && key.qi !== undefined; -} - -// Convert a BigInteger to an array of a specified size in bytes. -function bigIntToFixedByteArray(bn: BigInteger, size: number): Uint8Array { - const paddedBnBytes = bn.toByteArray(); - let firstNonZeroIndex = 0; - while (firstNonZeroIndex < paddedBnBytes.length && - paddedBnBytes[firstNonZeroIndex] === 0) { - firstNonZeroIndex++; - } - const bnBytes = Uint8Array.from(paddedBnBytes.slice(firstNonZeroIndex)); - const res = new Uint8Array(size); - assertTrue(bnBytes.length <= res.length); - res.set(bnBytes, res.length - bnBytes.length); - return res; -} - -export class AdbKey { - // We use this JsonWebKey to: - // - create a private key and sign with it - // - create a public key and send it to the device - // - serialize the JsonWebKey and send it to the device (or retrieve it - // from the device and deserialize) - jwkPrivate: ValidJsonWebKey; - - private constructor(jwkPrivate: ValidJsonWebKey) { - this.jwkPrivate = jwkPrivate; - } - - static async GenerateNewKeyPair(): Promise { - // Construct a new CryptoKeyPair and keep its private key in JWB format. - const keyPair = await crypto.subtle.generateKey( - ADB_WEB_CRYPTO_ALGORITHM, - ADB_WEB_CRYPTO_EXPORTABLE, - ADB_WEB_CRYPTO_OPERATIONS); - const jwkPrivate = await crypto.subtle.exportKey('jwk', keyPair.privateKey); - if (!isValidJsonWebKey(jwkPrivate)) { - throw new RecordingError('Could not generate a valid private key.'); - } - return new AdbKey(jwkPrivate); - } - - static DeserializeKey(serializedKey: string): AdbKey { - return new AdbKey(JSON.parse(serializedKey)); - } - - // Perform an RSA signing operation for the ADB auth challenge. - // - // For the RSA signature, the token is expected to have already - // had the SHA-1 message digest applied. - // - // However, the adb token we receive from the device is made up of 20 randomly - // generated bytes that are treated like a SHA-1. Therefore, we need to update - // the message format. - sign(token: Uint8Array): Uint8Array { - const rsaKey = new RSAKey(); - rsaKey.setPrivateEx( - hexEncode(base64Decode(this.jwkPrivate.n)), - hexEncode(base64Decode(this.jwkPrivate.e)), - hexEncode(base64Decode(this.jwkPrivate.d)), - hexEncode(base64Decode(this.jwkPrivate.p)), - hexEncode(base64Decode(this.jwkPrivate.q)), - hexEncode(base64Decode(this.jwkPrivate.dp)), - hexEncode(base64Decode(this.jwkPrivate.dq)), - hexEncode(base64Decode(this.jwkPrivate.qi))); - assertTrue(rsaKey.n.bitLength() === MODULUS_SIZE_BITS); - - // Message Layout (size equals that of the key modulus): - // 00 01 FF FF FF FF ... FF [ASN.1 PREFIX] [TOKEN] - const message = new Uint8Array(MODULUS_SIZE); - - // Initially fill the buffer with the padding - message.fill(0xFF); - - // add prefix - message[0] = 0x00; - message[1] = 0x01; - - // add the ASN.1 prefix - message.set( - SIGNING_ASN1_PREFIX, - message.length - SIGNING_ASN1_PREFIX.length - token.length); - - // then the actual token at the end - message.set(token, message.length - token.length); - - const messageInteger = new BigInteger(Array.from(message)); - const signature = rsaKey.doPrivate(messageInteger); - return new Uint8Array(bigIntToFixedByteArray(signature, MODULUS_SIZE)); - } - - // Construct public key to match the adb format: - // go/codesearch/rvc-arc/system/core/libcrypto_utils/android_pubkey.c;l=38-53 - getPublicKey(): string { - const rsaKey = new RSAKey(); - rsaKey.setPublic( - hexEncode(base64Decode((assertExists(this.jwkPrivate.n)))), - hexEncode(base64Decode((assertExists(this.jwkPrivate.e))))); - - const n0inv = R32.subtract(rsaKey.n.modInverse(R32)).intValue(); - const r = BigInteger.ONE.shiftLeft(1).pow(MODULUS_SIZE_BITS); - const rr = r.multiply(r).mod(rsaKey.n); - - const buffer = new ArrayBuffer(PUBKEY_ENCODED_SIZE); - const dv = new DataView(buffer); - dv.setUint32(0, MODULUS_SIZE_WORDS, true); - dv.setUint32(WORD_SIZE, n0inv, true); - - const dvU8 = new Uint8Array(dv.buffer, dv.byteOffset, dv.byteLength); - dvU8.set( - bigIntToFixedByteArray(rsaKey.n, MODULUS_SIZE).reverse(), - 2 * WORD_SIZE); - dvU8.set( - bigIntToFixedByteArray(rr, MODULUS_SIZE).reverse(), - 2 * WORD_SIZE + MODULUS_SIZE); - - dv.setUint32(2 * WORD_SIZE + 2 * MODULUS_SIZE, rsaKey.e, true); - return base64Encode(dvU8) + ' ui.perfetto.dev'; - } - - serializeKey(): string { - return JSON.stringify(this.jwkPrivate); - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/auth/adb_key_manager.ts b/third_party/perfetto/ui/src/common/recordingV2/auth/adb_key_manager.ts deleted file mode 100644 index 73575ead2fbc..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/auth/adb_key_manager.ts +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {globals} from '../../../frontend/globals'; - -import {AdbKey} from './adb_auth'; - -function isPasswordCredential(cred: Credential| - null): cred is PasswordCredential { - return cred !== null && cred.type === 'password'; -} - -function hasPasswordCredential() { - return 'PasswordCredential' in window; -} - -// how long we will store the key in memory -const KEY_IN_MEMORY_TIMEOUT = 1000 * 60 * 30; // 30 minutes - -// Update credential store with the given key. -export async function maybeStoreKey(key: AdbKey): Promise { - if (!hasPasswordCredential()) { - return; - } - const credential = new PasswordCredential({ - id: 'webusb-adb-key', - password: key.serializeKey(), - name: 'WebUSB ADB Key', - iconURL: `${globals.root}assets/favicon.png`, - }); - // The 'Save password?' Chrome dialogue only appears if the key is - // not already stored in Chrome. - await navigator.credentials.store(credential); - // 'preventSilentAccess' guarantees the user is always notified when - // credentials are accessed. Sometimes the user is asked to click a button - // and other times only a notification is shown temporarily. - await navigator.credentials.preventSilentAccess(); -} - -export class AdbKeyManager { - private key?: AdbKey; - // Id of timer used to expire the key kept in memory. - private keyInMemoryTimerId?: ReturnType; - - // Finds a key, by priority: - // - looking in memory (i.e. this.key) - // - looking in the credential store - // - and finally creating one from scratch if needed - async getKey(): Promise { - // 1. If we have a private key in memory, we return it. - if (this.key) { - return this.key; - } - - // 2. We try to get the private key from the browser. - // The mediation is set as 'optional', because we use - // 'preventSilentAccess', which sometimes requests the user to click - // on a button to allow the auth, but sometimes only shows a - // notification and does not require the user to click on anything. - // If we had set mediation to 'required', the user would have been - // asked to click on a button every time. - if (hasPasswordCredential()) { - const options: PasswordCredentialRequestOptions = { - password: true, - mediation: 'optional', - }; - const credential = await navigator.credentials.get(options); - if (isPasswordCredential(credential)) { - return this.assignKey(AdbKey.DeserializeKey(credential.password)); - } - } - - // 3. We generate a new key pair. - return this.assignKey(await AdbKey.GenerateNewKeyPair()); - } - - // Assigns the key a new value, sets a timeout for storing the key in memory - // and then returns the new key. - private assignKey(key: AdbKey): AdbKey { - this.key = key; - if (this.keyInMemoryTimerId) { - clearTimeout(this.keyInMemoryTimerId); - } - this.keyInMemoryTimerId = - setTimeout(() => this.key = undefined, KEY_IN_MEMORY_TIMEOUT); - return key; - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/auth/credentials_interfaces.d.ts b/third_party/perfetto/ui/src/common/recordingV2/auth/credentials_interfaces.d.ts deleted file mode 100644 index f127ad599a91..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/auth/credentials_interfaces.d.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Typescript interfaces for PasswordCredential don't exist as of -// lib.dom es2020 (see tsconfig.json), so we had to define them here. -declare global { - export interface PasswordCredentialData { - readonly id: string; - readonly name: string; - readonly iconURL: string; - readonly password: string; - } - - export class PasswordCredential extends Credential { - password: string; - constructor(data: PasswordCredentialData); - } - - export interface PasswordCredentialRequestOptions extends - CredentialRequestOptions { - password?: boolean; - } -} - -// we can only augment the global scope from an external module -export {}; diff --git a/third_party/perfetto/ui/src/common/recordingV2/chrome_traced_tracing_session.ts b/third_party/perfetto/ui/src/common/recordingV2/chrome_traced_tracing_session.ts deleted file mode 100644 index e36a5aaa0e1a..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/chrome_traced_tracing_session.ts +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {defer, Deferred} from '../../base/deferred'; -import {assertExists, assertTrue} from '../../base/logging'; -import {binaryDecode, binaryEncode} from '../../base/string_utils'; -import { - ChromeExtensionMessage, - isChromeExtensionError, - isChromeExtensionStatus, - isGetCategoriesResponse, -} from '../../controller/chrome_proxy_record_controller'; -import { - isDisableTracingResponse, - isEnableTracingResponse, - isFreeBuffersResponse, - isGetTraceStatsResponse, - isReadBuffersResponse, -} from '../../controller/consumer_port_types'; -import { - EnableTracingRequest, - IBufferStats, - ISlice, - TraceConfig, -} from '../protos'; -import {RecordingError} from './recording_error_handling'; -import { - TracingSession, - TracingSessionListener, -} from './recording_interfaces_v2'; -import { - BUFFER_USAGE_INCORRECT_FORMAT, - BUFFER_USAGE_NOT_ACCESSIBLE, - EXTENSION_ID, - MALFORMED_EXTENSION_MESSAGE, -} from './recording_utils'; - -// This class implements the protocol described in -// https://perfetto.dev/docs/design-docs/api-and-abi#tracing-protocol-abi -// However, with the Chrome extension we communicate using JSON messages. -export class ChromeTracedTracingSession implements TracingSession { - // Needed for ReadBufferResponse: all the trace packets are split into - // several slices. |partialPacket| is the buffer for them. Once we receive a - // slice with the flag |lastSliceForPacket|, a new packet is created. - private partialPacket: ISlice[] = []; - - // For concurrent calls to 'GetCategories', we return the same value. - private pendingGetCategoriesMessage?: Deferred; - - private pendingStatsMessages = new Array>(); - - // Port through which we communicate with the extension. - private chromePort: chrome.runtime.Port; - // True when Perfetto is connected via the port to the tracing session. - private isPortConnected: boolean; - - constructor(private tracingSessionListener: TracingSessionListener) { - this.chromePort = chrome.runtime.connect(EXTENSION_ID); - this.isPortConnected = true; - } - - start(config: TraceConfig): void { - if (!this.isPortConnected) return; - const duration = config.durationMs; - this.tracingSessionListener.onStatus(`Recording in progress${ - duration ? ' for ' + duration.toString() + ' ms' : ''}...`); - - const enableTracingRequest = new EnableTracingRequest(); - enableTracingRequest.traceConfig = config; - const enableTracingRequestProto = binaryEncode( - EnableTracingRequest.encode(enableTracingRequest).finish()); - this.chromePort.postMessage( - {method: 'EnableTracing', requestData: enableTracingRequestProto}); - } - - // The 'cancel' method will end the tracing session and will NOT return the - // trace. Therefore, we do not need to keep the connection open. - cancel(): void { - if (!this.isPortConnected) return; - this.terminateConnection(); - } - - // The 'stop' method will end the tracing session and cause the trace to be - // returned via a callback. We maintain the connection to the target so we can - // extract the trace. - // See 'DisableTracing' in: - // https://perfetto.dev/docs/design-docs/life-of-a-tracing-session - stop(): void { - if (!this.isPortConnected) return; - this.chromePort.postMessage({method: 'DisableTracing'}); - } - - getCategories(): Promise { - if (!this.isPortConnected) { - throw new RecordingError( - 'Attempting to get categories from a ' + - 'disconnected tracing session.'); - } - if (this.pendingGetCategoriesMessage) { - return this.pendingGetCategoriesMessage; - } - - this.chromePort.postMessage({method: 'GetCategories'}); - return this.pendingGetCategoriesMessage = defer(); - } - - async getTraceBufferUsage(): Promise { - if (!this.isPortConnected) return 0; - const bufferStats = await this.getBufferStats(); - let percentageUsed = -1; - for (const buffer of bufferStats) { - const used = assertExists(buffer.bytesWritten); - const total = assertExists(buffer.bufferSize); - if (total >= 0) { - percentageUsed = Math.max(percentageUsed, used / total); - } - } - - if (percentageUsed === -1) { - throw new RecordingError(BUFFER_USAGE_INCORRECT_FORMAT); - } - return percentageUsed; - } - - initConnection(): void { - this.chromePort.onMessage.addListener((message: ChromeExtensionMessage) => { - this.handleExtensionMessage(message); - }); - } - - private getBufferStats(): Promise { - this.chromePort.postMessage({method: 'GetTraceStats'}); - - const statsMessage = defer(); - this.pendingStatsMessages.push(statsMessage); - return statsMessage; - } - - private terminateConnection(): void { - this.chromePort.postMessage({method: 'FreeBuffers'}); - this.clearState(); - } - - private clearState() { - this.chromePort.disconnect(); - this.isPortConnected = false; - for (const statsMessage of this.pendingStatsMessages) { - statsMessage.reject(new RecordingError(BUFFER_USAGE_NOT_ACCESSIBLE)); - } - this.pendingStatsMessages = []; - this.pendingGetCategoriesMessage = undefined; - } - - private handleExtensionMessage(message: ChromeExtensionMessage) { - if (isChromeExtensionError(message)) { - this.terminateConnection(); - this.tracingSessionListener.onError(message.error); - } else if (isChromeExtensionStatus(message)) { - this.tracingSessionListener.onStatus(message.status); - } else if (isReadBuffersResponse(message)) { - if (!message.slices) { - return; - } - for (const messageSlice of message.slices) { - // The extension sends the binary data as a string. - // see http://shortn/_oPmO2GT6Vb - if (typeof messageSlice.data !== 'string') { - throw new RecordingError(MALFORMED_EXTENSION_MESSAGE); - } - const decodedSlice = { - data: binaryDecode(messageSlice.data), - }; - this.partialPacket.push(decodedSlice); - if (messageSlice.lastSliceForPacket) { - let bufferSize = 0; - for (const slice of this.partialPacket) { - bufferSize += slice.data!.length; - } - - const completeTrace = new Uint8Array(bufferSize); - let written = 0; - for (const slice of this.partialPacket) { - const data = slice.data!; - completeTrace.set(data, written); - written += data.length; - } - // The trace already comes encoded as a proto. - this.tracingSessionListener.onTraceData(completeTrace); - this.terminateConnection(); - } - } - } else if (isGetCategoriesResponse(message)) { - assertExists(this.pendingGetCategoriesMessage) - .resolve(message.categories); - this.pendingGetCategoriesMessage = undefined; - } else if (isEnableTracingResponse(message)) { - // Once the service notifies us that a tracing session is enabled, - // we can start streaming the response using 'ReadBuffers'. - this.chromePort.postMessage({method: 'ReadBuffers'}); - } else if (isGetTraceStatsResponse(message)) { - const maybePendingStatsMessage = this.pendingStatsMessages.shift(); - if (maybePendingStatsMessage) { - maybePendingStatsMessage.resolve( - message?.traceStats?.bufferStats || []); - } - } else if (isFreeBuffersResponse(message)) { - // No action required. If we successfully read a whole trace, - // we close the connection. Alternatively, if the tracing finishes - // with an exception or if the user cancels it, we also close the - // connection. - } else { - assertTrue(isDisableTracingResponse(message)); - // No action required. Same reasoning as for FreeBuffers. - } - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/host_os_byte_stream.ts b/third_party/perfetto/ui/src/common/recordingV2/host_os_byte_stream.ts deleted file mode 100644 index dd46ba814f18..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/host_os_byte_stream.ts +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {defer} from '../../base/deferred'; - -import { - ByteStream, - OnStreamCloseCallback, - OnStreamDataCallback, -} from './recording_interfaces_v2'; - -// A HostOsByteStream instantiates a websocket connection to the host OS. -// It exposes an API to write commands to this websocket and read its output. -export class HostOsByteStream implements ByteStream { - // handshakeSignal will be resolved with the stream when the websocket - // connection becomes open. - private handshakeSignal = defer(); - private _isConnected: boolean = false; - private websocket: WebSocket; - private onStreamDataCallbacks: OnStreamDataCallback[] = []; - private onStreamCloseCallbacks: OnStreamCloseCallback[] = []; - - private constructor(websocketUrl: string) { - this.websocket = new WebSocket(websocketUrl); - this.websocket.onmessage = this.onMessage.bind(this); - this.websocket.onopen = this.onOpen.bind(this); - } - - addOnStreamDataCallback(onStreamData: OnStreamDataCallback): void { - this.onStreamDataCallbacks.push(onStreamData); - } - - addOnStreamCloseCallback(onStreamClose: OnStreamCloseCallback): void { - this.onStreamCloseCallbacks.push(onStreamClose); - } - - close(): void { - this.websocket.close(); - for (const onStreamClose of this.onStreamCloseCallbacks) { - onStreamClose(); - } - this.onStreamDataCallbacks = []; - this.onStreamCloseCallbacks = []; - } - - async closeAndWaitForTeardown(): Promise { - this.close(); - } - - isConnected(): boolean { - return this._isConnected; - } - - write(msg: string|Uint8Array): void { - this.websocket.send(msg); - } - - private async onMessage(evt: MessageEvent) { - for (const onStreamData of this.onStreamDataCallbacks) { - const arrayBufferResponse = await evt.data.arrayBuffer(); - onStreamData(new Uint8Array(arrayBufferResponse)); - } - } - - private onOpen() { - this._isConnected = true; - this.handshakeSignal.resolve(this); - } - - static create(websocketUrl: string): Promise { - return (new HostOsByteStream(websocketUrl)).handshakeSignal; - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/recording_config_utils.ts b/third_party/perfetto/ui/src/common/recordingV2/recording_config_utils.ts deleted file mode 100644 index 2840b3ca05ca..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/recording_config_utils.ts +++ /dev/null @@ -1,692 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -import {base64Encode} from '../../base/string_utils'; -import {RecordConfig} from '../../controller/record_config_types'; -import { - AndroidLogConfig, - AndroidLogId, - AndroidPowerConfig, - BufferConfig, - ChromeConfig, - DataSourceConfig, - FtraceConfig, - HeapprofdConfig, - JavaContinuousDumpConfig, - JavaHprofConfig, - MeminfoCounters, - NativeContinuousDumpConfig, - NetworkPacketTraceConfig, - ProcessStatsConfig, - SysStatsConfig, - TraceConfig, - TrackEventConfig, - VmstatCounters, -} from '../protos'; - -import {TargetInfo} from './recording_interfaces_v2'; - -export interface ConfigProtoEncoded { - configProtoText?: string; - configProtoBase64?: string; - hasDataSources: boolean; -} - -export class RecordingConfigUtils { - private lastConfig?: RecordConfig; - private lastTargetInfo?: TargetInfo; - private configProtoText?: string; - private configProtoBase64?: string; - private hasDataSources: boolean = false; - - fetchLatestRecordCommand(recordConfig: RecordConfig, targetInfo: TargetInfo): - ConfigProtoEncoded { - if (recordConfig === this.lastConfig && - targetInfo === this.lastTargetInfo) { - return { - configProtoText: this.configProtoText, - configProtoBase64: this.configProtoBase64, - hasDataSources: this.hasDataSources, - }; - } - this.lastConfig = recordConfig; - this.lastTargetInfo = targetInfo; - - const traceConfig = genTraceConfig(recordConfig, targetInfo); - const configProto = TraceConfig.encode(traceConfig).finish(); - this.configProtoText = toPbtxt(configProto); - this.configProtoBase64 = base64Encode(configProto); - this.hasDataSources = traceConfig.dataSources.length > 0; - return { - configProtoText: this.configProtoText, - configProtoBase64: this.configProtoBase64, - hasDataSources: this.hasDataSources, - }; - } -} - -export function genTraceConfig( - uiCfg: RecordConfig, targetInfo: TargetInfo): TraceConfig { - const androidApiLevel = (targetInfo.targetType === 'ANDROID') ? - targetInfo.androidApiLevel : - undefined; - const protoCfg = new TraceConfig(); - protoCfg.durationMs = uiCfg.durationMs; - - // Auxiliary buffer for slow-rate events. - // Set to 1/8th of the main buffer size, with reasonable limits. - let slowBufSizeKb = uiCfg.bufferSizeMb * (1024 / 8); - slowBufSizeKb = Math.min(slowBufSizeKb, 2 * 1024); - slowBufSizeKb = Math.max(slowBufSizeKb, 256); - - // Main buffer for ftrace and other high-freq events. - const fastBufSizeKb = uiCfg.bufferSizeMb * 1024 - slowBufSizeKb; - - protoCfg.buffers.push(new BufferConfig()); - protoCfg.buffers.push(new BufferConfig()); - protoCfg.buffers[1].sizeKb = slowBufSizeKb; - protoCfg.buffers[0].sizeKb = fastBufSizeKb; - - if (uiCfg.mode === 'STOP_WHEN_FULL') { - protoCfg.buffers[0].fillPolicy = BufferConfig.FillPolicy.DISCARD; - protoCfg.buffers[1].fillPolicy = BufferConfig.FillPolicy.DISCARD; - } else { - protoCfg.buffers[0].fillPolicy = BufferConfig.FillPolicy.RING_BUFFER; - protoCfg.buffers[1].fillPolicy = BufferConfig.FillPolicy.RING_BUFFER; - protoCfg.flushPeriodMs = 30000; - if (uiCfg.mode === 'LONG_TRACE') { - protoCfg.writeIntoFile = true; - protoCfg.fileWritePeriodMs = uiCfg.fileWritePeriodMs; - protoCfg.maxFileSizeBytes = uiCfg.maxFileSizeMb * 1e6; - } - - // Clear incremental state every 5 seconds when tracing into a ring - // buffer. - const incStateConfig = new TraceConfig.IncrementalStateConfig(); - incStateConfig.clearPeriodMs = 5000; - protoCfg.incrementalStateConfig = incStateConfig; - } - - const ftraceEvents = new Set(uiCfg.ftrace ? uiCfg.ftraceEvents : []); - const atraceCats = new Set(uiCfg.atrace ? uiCfg.atraceCats : []); - const atraceApps = new Set(); - const chromeCategories = new Set(); - uiCfg.chromeCategoriesSelected.forEach((it) => chromeCategories.add(it)); - uiCfg.chromeHighOverheadCategoriesSelected.forEach( - (it) => chromeCategories.add(it)); - - let procThreadAssociationPolling = false; - let procThreadAssociationFtrace = false; - let trackInitialOomScore = false; - - if (uiCfg.cpuSched) { - procThreadAssociationPolling = true; - procThreadAssociationFtrace = true; - uiCfg.ftrace = true; - if (androidApiLevel && androidApiLevel >= 31) { - uiCfg.symbolizeKsyms = true; - } - ftraceEvents.add('sched/sched_switch'); - ftraceEvents.add('power/suspend_resume'); - ftraceEvents.add('sched/sched_wakeup'); - ftraceEvents.add('sched/sched_wakeup_new'); - ftraceEvents.add('sched/sched_waking'); - ftraceEvents.add('power/suspend_resume'); - } - - let sysStatsCfg: SysStatsConfig|undefined = undefined; - - if (uiCfg.cpuFreq) { - ftraceEvents.add('power/cpu_frequency'); - ftraceEvents.add('power/cpu_idle'); - ftraceEvents.add('power/suspend_resume'); - - sysStatsCfg = new SysStatsConfig(); - sysStatsCfg.cpufreqPeriodMs = uiCfg.cpuFreqPollMs; - } - - if (uiCfg.gpuFreq) { - ftraceEvents.add('power/gpu_frequency'); - } - - if (uiCfg.gpuMemTotal) { - ftraceEvents.add('gpu_mem/gpu_mem_total'); - - if (targetInfo.targetType !== 'CHROME') { - const ds = new TraceConfig.DataSource(); - ds.config = new DataSourceConfig(); - ds.config.name = 'android.gpu.memory'; - protoCfg.dataSources.push(ds); - } - } - - if (uiCfg.cpuSyscall) { - ftraceEvents.add('raw_syscalls/sys_enter'); - ftraceEvents.add('raw_syscalls/sys_exit'); - } - - if (uiCfg.batteryDrain) { - const ds = new TraceConfig.DataSource(); - ds.config = new DataSourceConfig(); - if (targetInfo.targetType === 'CHROME_OS' || - targetInfo.targetType === 'LINUX') { - ds.config.name = 'linux.sysfs_power'; - } else { - ds.config.name = 'android.power'; - ds.config.androidPowerConfig = new AndroidPowerConfig(); - ds.config.androidPowerConfig.batteryPollMs = uiCfg.batteryDrainPollMs; - ds.config.androidPowerConfig.batteryCounters = [ - AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CAPACITY_PERCENT, - AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CHARGE, - AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CURRENT, - ]; - ds.config.androidPowerConfig.collectPowerRails = true; - } - if (targetInfo.targetType !== 'CHROME') { - protoCfg.dataSources.push(ds); - } - } - - if (uiCfg.boardSensors) { - ftraceEvents.add('regulator/regulator_set_voltage'); - ftraceEvents.add('regulator/regulator_set_voltage_complete'); - ftraceEvents.add('power/clock_enable'); - ftraceEvents.add('power/clock_disable'); - ftraceEvents.add('power/clock_set_rate'); - ftraceEvents.add('power/suspend_resume'); - } - - if (uiCfg.cpuCoarse) { - if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); - sysStatsCfg.statPeriodMs = uiCfg.cpuCoarsePollMs; - sysStatsCfg.statCounters = [ - SysStatsConfig.StatCounters.STAT_CPU_TIMES, - SysStatsConfig.StatCounters.STAT_FORK_COUNT, - ]; - } - - if (uiCfg.memHiFreq) { - procThreadAssociationPolling = true; - procThreadAssociationFtrace = true; - ftraceEvents.add('mm_event/mm_event_record'); - ftraceEvents.add('kmem/rss_stat'); - ftraceEvents.add('ion/ion_stat'); - ftraceEvents.add('dmabuf_heap/dma_heap_stat'); - ftraceEvents.add('kmem/ion_heap_grow'); - ftraceEvents.add('kmem/ion_heap_shrink'); - } - - if (procThreadAssociationFtrace) { - ftraceEvents.add('sched/sched_process_exit'); - ftraceEvents.add('sched/sched_process_free'); - ftraceEvents.add('task/task_newtask'); - ftraceEvents.add('task/task_rename'); - } - - if (uiCfg.meminfo) { - if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); - sysStatsCfg.meminfoPeriodMs = uiCfg.meminfoPeriodMs; - sysStatsCfg.meminfoCounters = uiCfg.meminfoCounters.map((name) => { - return MeminfoCounters[name as any as number] as any as number; - }); - } - - if (uiCfg.vmstat) { - if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); - sysStatsCfg.vmstatPeriodMs = uiCfg.vmstatPeriodMs; - sysStatsCfg.vmstatCounters = uiCfg.vmstatCounters.map((name) => { - return VmstatCounters[name as any as number] as any as number; - }); - } - - if (uiCfg.memLmk) { - // For in-kernel LMK (roughly older devices until Go and Pixel 3). - ftraceEvents.add('lowmemorykiller/lowmemory_kill'); - - // For userspace LMKd (newer devices). - // 'lmkd' is not really required because the code in lmkd.c emits events - // with ATRACE_TAG_ALWAYS. We need something just to ensure that the final - // config will enable atrace userspace events. - atraceApps.add('lmkd'); - - ftraceEvents.add('oom/oom_score_adj_update'); - procThreadAssociationPolling = true; - trackInitialOomScore = true; - } - - let heapprofd: HeapprofdConfig|undefined = undefined; - if (uiCfg.heapProfiling) { - // TODO(hjd): Check or inform user if buffer size are too small. - const cfg = new HeapprofdConfig(); - cfg.samplingIntervalBytes = uiCfg.hpSamplingIntervalBytes; - if (uiCfg.hpSharedMemoryBuffer >= 8192 && - uiCfg.hpSharedMemoryBuffer % 4096 === 0) { - cfg.shmemSizeBytes = uiCfg.hpSharedMemoryBuffer; - } - for (const value of uiCfg.hpProcesses.split('\n')) { - if (value === '') { - // Ignore empty lines - } else if (isNaN(+value)) { - cfg.processCmdline.push(value); - } else { - cfg.pid.push(+value); - } - } - if (uiCfg.hpContinuousDumpsInterval > 0) { - const cdc = cfg.continuousDumpConfig = new NativeContinuousDumpConfig(); - cdc.dumpIntervalMs = uiCfg.hpContinuousDumpsInterval; - if (uiCfg.hpContinuousDumpsPhase > 0) { - cdc.dumpPhaseMs = uiCfg.hpContinuousDumpsPhase; - } - } - cfg.blockClient = uiCfg.hpBlockClient; - if (uiCfg.hpAllHeaps) { - cfg.allHeaps = true; - } - heapprofd = cfg; - } - - let javaHprof: JavaHprofConfig|undefined = undefined; - if (uiCfg.javaHeapDump) { - const cfg = new JavaHprofConfig(); - for (const value of uiCfg.jpProcesses.split('\n')) { - if (value === '') { - // Ignore empty lines - } else if (isNaN(+value)) { - cfg.processCmdline.push(value); - } else { - cfg.pid.push(+value); - } - } - if (uiCfg.jpContinuousDumpsInterval > 0) { - const cdc = cfg.continuousDumpConfig = new JavaContinuousDumpConfig(); - cdc.dumpIntervalMs = uiCfg.jpContinuousDumpsInterval; - if (uiCfg.hpContinuousDumpsPhase > 0) { - cdc.dumpPhaseMs = uiCfg.jpContinuousDumpsPhase; - } - } - javaHprof = cfg; - } - - if (uiCfg.procStats || procThreadAssociationPolling || trackInitialOomScore) { - const ds = new TraceConfig.DataSource(); - ds.config = new DataSourceConfig(); - ds.config.targetBuffer = 1; // Aux - ds.config.name = 'linux.process_stats'; - ds.config.processStatsConfig = new ProcessStatsConfig(); - if (uiCfg.procStats) { - ds.config.processStatsConfig.procStatsPollMs = uiCfg.procStatsPeriodMs; - } - if (procThreadAssociationPolling || trackInitialOomScore) { - ds.config.processStatsConfig.scanAllProcessesOnStart = true; - } - if (targetInfo.targetType !== 'CHROME') { - protoCfg.dataSources.push(ds); - } - } - - if (uiCfg.androidLogs) { - const ds = new TraceConfig.DataSource(); - ds.config = new DataSourceConfig(); - ds.config.name = 'android.log'; - ds.config.androidLogConfig = new AndroidLogConfig(); - ds.config.androidLogConfig.logIds = uiCfg.androidLogBuffers.map((name) => { - return AndroidLogId[name as any as number] as any as number; - }); - - if (targetInfo.targetType !== 'CHROME') { - protoCfg.dataSources.push(ds); - } - } - - if (uiCfg.androidFrameTimeline) { - const ds = new TraceConfig.DataSource(); - ds.config = new DataSourceConfig(); - ds.config.name = 'android.surfaceflinger.frametimeline'; - if (targetInfo.targetType !== 'CHROME') { - protoCfg.dataSources.push(ds); - } - } - - if (uiCfg.androidGameInterventionList) { - const ds = new TraceConfig.DataSource(); - ds.config = new DataSourceConfig(); - ds.config.name = 'android.game_interventions'; - if (targetInfo.targetType !== 'CHROME') { - protoCfg.dataSources.push(ds); - } - } - - if (uiCfg.androidNetworkTracing) { - if (targetInfo.targetType !== 'CHROME') { - const net = new TraceConfig.DataSource(); - net.config = new DataSourceConfig(); - net.config.name = 'android.network_packets'; - net.config.networkPacketTraceConfig = new NetworkPacketTraceConfig(); - net.config.networkPacketTraceConfig.pollMs = - uiCfg.androidNetworkTracingPollMs; - protoCfg.dataSources.push(net); - - // Record package info so that Perfetto can display the package name for - // network packet events based on the event uid. - const pkg = new TraceConfig.DataSource(); - pkg.config = new DataSourceConfig(); - pkg.config.name = 'android.packages_list'; - protoCfg.dataSources.push(pkg); - } - } - - if (uiCfg.chromeLogs) { - chromeCategories.add('log'); - } - - if (uiCfg.taskScheduling) { - chromeCategories.add('toplevel'); - chromeCategories.add('toplevel.flow'); - chromeCategories.add('scheduler'); - chromeCategories.add('sequence_manager'); - chromeCategories.add('disabled-by-default-toplevel.flow'); - } - - if (uiCfg.ipcFlows) { - chromeCategories.add('toplevel'); - chromeCategories.add('toplevel.flow'); - chromeCategories.add('disabled-by-default-ipc.flow'); - chromeCategories.add('mojom'); - } - - if (uiCfg.jsExecution) { - chromeCategories.add('toplevel'); - chromeCategories.add('v8'); - } - - if (uiCfg.webContentRendering) { - chromeCategories.add('toplevel'); - chromeCategories.add('blink'); - chromeCategories.add('cc'); - chromeCategories.add('gpu'); - } - - if (uiCfg.uiRendering) { - chromeCategories.add('toplevel'); - chromeCategories.add('cc'); - chromeCategories.add('gpu'); - chromeCategories.add('viz'); - chromeCategories.add('ui'); - chromeCategories.add('views'); - } - - if (uiCfg.inputEvents) { - chromeCategories.add('toplevel'); - chromeCategories.add('benchmark'); - chromeCategories.add('evdev'); - chromeCategories.add('input'); - chromeCategories.add('disabled-by-default-toplevel.flow'); - } - - if (uiCfg.navigationAndLoading) { - chromeCategories.add('loading'); - chromeCategories.add('net'); - chromeCategories.add('netlog'); - chromeCategories.add('navigation'); - chromeCategories.add('browser'); - } - - if (chromeCategories.size !== 0) { - let chromeRecordMode; - if (uiCfg.mode === 'STOP_WHEN_FULL') { - chromeRecordMode = 'record-until-full'; - } else { - chromeRecordMode = 'record-continuously'; - } - const configStruct = { - record_mode: chromeRecordMode, - included_categories: [...chromeCategories.values()], - // Only include explicitly selected categories - excluded_categories: ['*'], - memory_dump_config: {}, - }; - if (chromeCategories.has('disabled-by-default-memory-infra')) { - configStruct.memory_dump_config = { - allowed_dump_modes: ['background', 'light', 'detailed'], - triggers: [{ - min_time_between_dumps_ms: 10000, - mode: 'detailed', - type: 'periodic_interval', - }], - }; - } - const chromeConfig = new ChromeConfig(); - chromeConfig.clientPriority = ChromeConfig.ClientPriority.USER_INITIATED; - chromeConfig.privacyFilteringEnabled = uiCfg.chromePrivacyFiltering; - chromeConfig.traceConfig = JSON.stringify(configStruct); - - const traceDs = new TraceConfig.DataSource(); - traceDs.config = new DataSourceConfig(); - traceDs.config.name = 'org.chromium.trace_event'; - traceDs.config.chromeConfig = chromeConfig; - protoCfg.dataSources.push(traceDs); - - // Configure "track_event" datasource for the Chrome SDK build. - const trackEventDs = new TraceConfig.DataSource(); - trackEventDs.config = new DataSourceConfig(); - trackEventDs.config.name = 'track_event'; - trackEventDs.config.chromeConfig = chromeConfig; - trackEventDs.config.trackEventConfig = new TrackEventConfig(); - trackEventDs.config.trackEventConfig.disabledCategories = ['*']; - trackEventDs.config.trackEventConfig.enabledCategories = - [...chromeCategories.values(), '__metadata']; - trackEventDs.config.trackEventConfig.enableThreadTimeSampling = true; - trackEventDs.config.trackEventConfig.timestampUnitMultiplier = 1000; - trackEventDs.config.trackEventConfig.filterDynamicEventNames = - uiCfg.chromePrivacyFiltering; - trackEventDs.config.trackEventConfig.filterDebugAnnotations = - uiCfg.chromePrivacyFiltering; - protoCfg.dataSources.push(trackEventDs); - - const metadataDs = new TraceConfig.DataSource(); - metadataDs.config = new DataSourceConfig(); - metadataDs.config.name = 'org.chromium.trace_metadata'; - metadataDs.config.chromeConfig = chromeConfig; - protoCfg.dataSources.push(metadataDs); - - if (chromeCategories.has('disabled-by-default-memory-infra')) { - const memoryDs = new TraceConfig.DataSource(); - memoryDs.config = new DataSourceConfig(); - memoryDs.config.name = 'org.chromium.memory_instrumentation'; - memoryDs.config.chromeConfig = chromeConfig; - protoCfg.dataSources.push(memoryDs); - - const HeapProfDs = new TraceConfig.DataSource(); - HeapProfDs.config = new DataSourceConfig(); - HeapProfDs.config.name = 'org.chromium.native_heap_profiler'; - HeapProfDs.config.chromeConfig = chromeConfig; - protoCfg.dataSources.push(HeapProfDs); - } - - if (chromeCategories.has('disabled-by-default-cpu_profiler') || - chromeCategories.has('disabled-by-default-cpu_profiler.debug')) { - const dataSource = new TraceConfig.DataSource(); - dataSource.config = new DataSourceConfig(); - dataSource.config.name = 'org.chromium.sampler_profiler'; - dataSource.config.chromeConfig = chromeConfig; - protoCfg.dataSources.push(dataSource); - } - } - - // Keep these last. The stages above can enrich them. - - if (sysStatsCfg !== undefined && targetInfo.targetType !== 'CHROME') { - const ds = new TraceConfig.DataSource(); - ds.config = new DataSourceConfig(); - ds.config.name = 'linux.sys_stats'; - ds.config.sysStatsConfig = sysStatsCfg; - protoCfg.dataSources.push(ds); - } - - if (heapprofd !== undefined && targetInfo.targetType !== 'CHROME') { - const ds = new TraceConfig.DataSource(); - ds.config = new DataSourceConfig(); - ds.config.targetBuffer = 0; - ds.config.name = 'android.heapprofd'; - ds.config.heapprofdConfig = heapprofd; - protoCfg.dataSources.push(ds); - } - - if (javaHprof !== undefined && targetInfo.targetType !== 'CHROME') { - const ds = new TraceConfig.DataSource(); - ds.config = new DataSourceConfig(); - ds.config.targetBuffer = 0; - ds.config.name = 'android.java_hprof'; - ds.config.javaHprofConfig = javaHprof; - protoCfg.dataSources.push(ds); - } - - if (uiCfg.ftrace || uiCfg.atrace || ftraceEvents.size > 0 || - atraceCats.size > 0 || atraceApps.size > 0) { - const ds = new TraceConfig.DataSource(); - ds.config = new DataSourceConfig(); - ds.config.name = 'linux.ftrace'; - ds.config.ftraceConfig = new FtraceConfig(); - // Override the advanced ftrace parameters only if the user has ticked the - // "Advanced ftrace config" tab. - if (uiCfg.ftrace) { - if (uiCfg.ftraceBufferSizeKb) { - ds.config.ftraceConfig.bufferSizeKb = uiCfg.ftraceBufferSizeKb; - } - if (uiCfg.ftraceDrainPeriodMs) { - ds.config.ftraceConfig.drainPeriodMs = uiCfg.ftraceDrainPeriodMs; - } - if (uiCfg.symbolizeKsyms) { - ds.config.ftraceConfig.symbolizeKsyms = true; - ftraceEvents.add('sched/sched_blocked_reason'); - } - for (const line of uiCfg.ftraceExtraEvents.split('\n')) { - if (line.trim().length > 0) ftraceEvents.add(line.trim()); - } - } - - if (uiCfg.atrace) { - if (uiCfg.allAtraceApps) { - atraceApps.clear(); - atraceApps.add('*'); - } else { - for (const line of uiCfg.atraceApps.split('\n')) { - if (line.trim().length > 0) atraceApps.add(line.trim()); - } - } - } - - if (atraceCats.size > 0 || atraceApps.size > 0) { - ftraceEvents.add('ftrace/print'); - } - - let ftraceEventsArray: string[] = []; - if (androidApiLevel && androidApiLevel === 28) { - for (const ftraceEvent of ftraceEvents) { - // On P, we don't support groups so strip all group names from ftrace - // events. - const groupAndName = ftraceEvent.split('/'); - if (groupAndName.length !== 2) { - ftraceEventsArray.push(ftraceEvent); - continue; - } - // Filter out any wildcard event groups which was not supported - // before Q. - if (groupAndName[1] === '*') { - continue; - } - ftraceEventsArray.push(groupAndName[1]); - } - } else { - ftraceEventsArray = Array.from(ftraceEvents); - } - - ds.config.ftraceConfig.ftraceEvents = ftraceEventsArray; - ds.config.ftraceConfig.atraceCategories = Array.from(atraceCats); - ds.config.ftraceConfig.atraceApps = Array.from(atraceApps); - - if (androidApiLevel && androidApiLevel >= 31) { - const compact = new FtraceConfig.CompactSchedConfig(); - compact.enabled = true; - ds.config.ftraceConfig.compactSched = compact; - } - - if (targetInfo.targetType !== 'CHROME') { - protoCfg.dataSources.push(ds); - } - } - - return protoCfg; -} - -function toPbtxt(configBuffer: Uint8Array): string { - const msg = TraceConfig.decode(configBuffer); - const json = msg.toJSON(); - function snakeCase(s: string): string { - return s.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase()); - } - // With the ahead of time compiled protos we can't seem to tell which - // fields are enums. - function isEnum(value: string): boolean { - return value.startsWith('MEMINFO_') || value.startsWith('VMSTAT_') || - value.startsWith('STAT_') || value.startsWith('LID_') || - value.startsWith('BATTERY_COUNTER_') || value === 'DISCARD' || - value === 'RING_BUFFER'; - } - // Since javascript doesn't have 64 bit numbers when converting protos to - // json the proto library encodes them as strings. This is lossy since - // we can't tell which strings that look like numbers are actually strings - // and which are actually numbers. Ideally we would reflect on the proto - // definition somehow but for now we just hard code keys which have this - // problem in the config. - function is64BitNumber(key: string): boolean { - return [ - 'maxFileSizeBytes', - 'samplingIntervalBytes', - 'shmemSizeBytes', - 'pid', - ].includes(key); - } - function* message(msg: {}, indent: number): IterableIterator { - for (const [key, value] of Object.entries(msg)) { - const isRepeated = Array.isArray(value); - const isNested = typeof value === 'object' && !isRepeated; - for (const entry of (isRepeated ? value as Array<{}>: [value])) { - yield ' '.repeat(indent) + `${snakeCase(key)}${isNested ? '' : ':'} `; - if (typeof entry === 'string') { - if (isEnum(entry) || is64BitNumber(key)) { - yield entry; - } else { - yield `"${entry.replace(new RegExp('"', 'g'), '\\"')}"`; - } - } else if (typeof entry === 'number') { - yield entry.toString(); - } else if (typeof entry === 'boolean') { - yield entry.toString(); - } else if (typeof entry === 'object' && entry !== null) { - yield '{\n'; - yield* message(entry, indent + 4); - yield ' '.repeat(indent) + '}'; - } else { - throw new Error(`Record proto entry "${entry}" with unexpected type ${ - typeof entry}`); - } - yield '\n'; - } - } - } - return [...message(json, 0)].join(''); -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/recording_error_handling.ts b/third_party/perfetto/ui/src/common/recordingV2/recording_error_handling.ts deleted file mode 100644 index ca03929189b7..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/recording_error_handling.ts +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - showAllowUSBDebugging, - showConnectionLostError, - showExtensionNotInstalled, - showFailedToPushBinary, - showIssueParsingTheTracedResponse, - showNoDeviceSelected, - showWebsocketConnectionIssue, - showWebUSBErrorV2, -} from '../../frontend/error_dialog'; -import {getErrorMessage} from '../errors'; - -import {OnMessageCallback} from './recording_interfaces_v2'; -import { - ALLOW_USB_DEBUGGING, - BINARY_PUSH_FAILURE, - BINARY_PUSH_UNKNOWN_RESPONSE, - EXTENSION_NOT_INSTALLED, - NO_DEVICE_SELECTED, - PARSING_UNABLE_TO_DECODE_METHOD, - PARSING_UNKNWON_REQUEST_ID, - PARSING_UNRECOGNIZED_MESSAGE, - PARSING_UNRECOGNIZED_PORT, - WEBSOCKET_UNABLE_TO_CONNECT, -} from './recording_utils'; - - -// The pattern for handling recording error can have the following nesting in -// case of errors: -// A. wrapRecordingError -> wraps a promise -// B. onFailure -> has user defined logic and calls showRecordingModal -// C. showRecordingModal -> shows UX for a given error; this is not called -// directly by wrapRecordingError, because we want the caller (such as the -// UI) to dictate the UX - -// This method takes a promise and a callback to be execute in case the promise -// fails. It then awaits the promise and executes the callback in case of -// failure. In the recording code it is used to wrap: -// 1. Acessing the WebUSB API. -// 2. Methods returning promises which can be rejected. For instance: -// a) When the user clicks 'Add a new device' but then doesn't select a valid -// device. -// b) When the user starts a tracing session, but cancels it before they -// authorize the session on the device. -export async function wrapRecordingError( - promise: Promise, onFailure: OnMessageCallback): Promise { - try { - return await promise; - } catch (e) { - // Sometimes the message is wrapped in an Error object, sometimes not, so - // we make sure we transform it into a string. - const errorMessage = getErrorMessage(e); - onFailure(errorMessage); - return undefined; - } -} - -// Shows a modal for every known type of error which can arise during recording. -// In this way, errors occuring at different levels of the recording process -// can be handled in a central location. -export function showRecordingModal(message: string): void { - if ([ - 'Unable to claim interface.', - 'The specified endpoint is not part of a claimed and selected ' + - 'alternate interface.', - // thrown when calling the 'reset' method on a WebUSB device. - 'Unable to reset the device.', - ].some((partOfMessage) => message.includes(partOfMessage))) { - showWebUSBErrorV2(); - } else if ( - [ - 'A transfer error has occurred.', - 'The device was disconnected.', - 'The transfer was cancelled.', - ].some((partOfMessage) => message.includes(partOfMessage)) || - isDeviceDisconnectedError(message)) { - showConnectionLostError(); - } else if (message === ALLOW_USB_DEBUGGING) { - showAllowUSBDebugging(); - } else if (isMessageComposedOf( - message, - [BINARY_PUSH_FAILURE, BINARY_PUSH_UNKNOWN_RESPONSE])) { - showFailedToPushBinary(message.substring(message.indexOf(':') + 1)); - } else if (message === NO_DEVICE_SELECTED) { - showNoDeviceSelected(); - } else if (WEBSOCKET_UNABLE_TO_CONNECT === message) { - showWebsocketConnectionIssue(message); - } else if (message === EXTENSION_NOT_INSTALLED) { - showExtensionNotInstalled(); - } else if (isMessageComposedOf(message, [ - PARSING_UNKNWON_REQUEST_ID, - PARSING_UNABLE_TO_DECODE_METHOD, - PARSING_UNRECOGNIZED_PORT, - PARSING_UNRECOGNIZED_MESSAGE, - ])) { - showIssueParsingTheTracedResponse(message); - } else { - throw new Error(`${message}`); - } -} - -function isDeviceDisconnectedError(message: string) { - return message.includes('Device with serial') && - message.includes('was disconnected.'); -} - -function isMessageComposedOf(message: string, issues: string[]) { - for (const issue of issues) { - if (message.includes(issue)) { - return true; - } - } - return false; -} - -// Exception thrown by the Recording logic. -export class RecordingError extends Error {} diff --git a/third_party/perfetto/ui/src/common/recordingV2/recording_interfaces_v2.ts b/third_party/perfetto/ui/src/common/recordingV2/recording_interfaces_v2.ts deleted file mode 100644 index 974d277845c0..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/recording_interfaces_v2.ts +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {TraceConfig} from '../protos'; - -// TargetFactory connects, disconnects and keeps track of targets. -// There is one factory for AndroidWebusb, AndroidWebsocket, Chrome etc. -// For instance, the AndroidWebusb factory returns a RecordingTargetV2 for each -// device. -export interface TargetFactory { - // Store the kind explicitly as a string as opposed to using class.kind in - // case we ever minify our code. - readonly kind: string; - - // Setter for OnTargetChange, which is executed when a target is - // added/removed or when its information is updated. - setOnTargetChange(onTargetChange: OnTargetChangeCallback): void; - - getName(): string; - - listTargets(): RecordingTargetV2[]; - // Returns recording problems that we encounter when not directly using the - // target. For instance we connect webusb devices when Perfetto is loaded. If - // there is an issue with connecting a webusb device, we do not want to crash - // all of Perfetto, as the user may not want to use the recording - // functionality at all. - listRecordingProblems(): string[]; - - connectNewTarget(): Promise; -} - -export interface DataSource { - name: string; - - // Contains information that is opaque to the recording code. The caller can - // use the DataSource name to type cast the DataSource descriptor. - // For targets calling QueryServiceState, 'descriptor' will hold the - // datasource descriptor: - // https://source.corp.google.com/android/external/perfetto/protos/perfetto/ - // common/data_source_descriptor.proto;l=28-60 - // For Chrome, 'descriptor' will contain the answer received from - // 'GetCategories': - // https://source.corp.google.com/android/external/perfetto/ui/src/ - // chrome_extension/chrome_tracing_controller.ts;l=220 - descriptor: unknown; -} - -// Common fields for all types of targetInfo: Chrome, Android, Linux etc. -interface TargetInfoBase { - name: string; - - // The dataSources exposed by a target. They are fetched from the target - // (ex: using QSS for Android or GetCategories for Chrome). - dataSources: DataSource[]; -} - -export interface AndroidTargetInfo extends TargetInfoBase { - targetType: 'ANDROID'; - - // This is the Android API level. For instance, it can be 32, 31, 30 etc. - // It is the "API level" column here: - // https://source.android.com/setup/start/build-numbers - androidApiLevel?: number; -} - -export interface ChromeTargetInfo extends TargetInfoBase { - targetType: 'CHROME'|'CHROME_OS'; -} - -export interface HostOsTargetInfo extends TargetInfoBase { - targetType: 'LINUX'|'MACOS'; -} - -// Holds information about a target. It's used by the UI and the logic which -// generates a config. -export type TargetInfo = AndroidTargetInfo|ChromeTargetInfo|HostOsTargetInfo; - -// RecordingTargetV2 is subclassed by Android devices and the Chrome browser/OS. -// It creates tracing sessions which are used by the UI. For Android, it manages -// the connection with the device. -export interface RecordingTargetV2 { - // Allows targets to surface target specific information such as - // well known key/value pairs: OS, targetType('ANDROID', 'CHROME', etc.) - getInfo(): TargetInfo; - - // Disconnects the target. - disconnect(disconnectMessage?: string): Promise; - - // Returns true if we are able to connect to the target without interfering - // with other processes. For example, for adb devices connected over WebUSB, - // this will be false when we can not claim the interface (Which most likely - // means that 'adb server' is running locally.). After querrying this method, - // the caller can decide if they want to connect to the target and as a side - // effect take the connection away from other processes. - canConnectWithoutContention(): Promise; - - // Whether the recording target can be used in a tracing session. For example, - // virtual targets do not support a tracing session. - canCreateTracingSession(recordingMode?: string): boolean; - - // Some target information can only be obtained after connecting to the - // target. This will establish a connection and retrieve data such as - // dataSources and apiLevel for Android. - fetchTargetInfo(tracingSessionListener: TracingSessionListener): - Promise; - - createTracingSession(tracingSessionListener: TracingSessionListener): - Promise; -} - -// TracingSession is used by the UI to record a trace. Depending on user -// actions, the UI can start/stop/cancel a session. During the recording, it -// provides updates about buffer usage. It is subclassed by -// TracedTracingSession, which manages the communication with traced and has -// logic for encoding/decoding Perfetto client requests/replies. -export interface TracingSession { - // Starts the tracing session. - start(config: TraceConfig): void; - - // Will stop the tracing session and NOT return any trace. - cancel(): void; - - // Will stop the tracing session. The implementing class may also return - // the trace using a callback. - stop(): void; - - // Returns the percentage of the trace buffer that is currently being - // occupied. - getTraceBufferUsage(): Promise; -} - -// Connection with an Adb device. Implementations will have logic specific to -// the connection protocol used(Ex: WebSocket, WebUsb). -export interface AdbConnection { - // Will push a binary to a given path. - push(binary: ArrayBuffer, path: string): Promise; - - // Will issue a shell command to the device. - shell(cmd: string): Promise; - - // Will establish a connection(a ByteStream) with the device. - connectSocket(path: string): Promise; - - // Returns true if we are able to connect without interfering - // with other processes. For example, for adb devices connected over WebUSB, - // this will be false when we can not claim the interface (Which most likely - // means that 'adb server' is running locally.). - canConnectWithoutContention(): Promise; - - // Ends the connection. - disconnect(disconnectMessage?: string): Promise; -} - -// A stream for a connection between a target and a tracing session. -export interface ByteStream { - // The caller can add callbacks, to be executed when the stream receives new - // data or when it finished closing itself. - addOnStreamDataCallback(onStreamData: OnStreamDataCallback): void; - addOnStreamCloseCallback(onStreamClose: OnStreamCloseCallback): void; - - isConnected(): boolean; - write(data: string|Uint8Array): void; - - close(): void; - closeAndWaitForTeardown(): Promise; -} - -// Handles binary messages received over the ByteStream. -export interface OnStreamDataCallback { - (data: Uint8Array): void; -} - -// Called when the ByteStream is closed. -export interface OnStreamCloseCallback { - (): void; -} - -// OnTraceDataCallback will return the entire trace when it has been fully -// assembled. This will be changed in the following CL aosp/2057640. -export interface OnTraceDataCallback { - (trace: Uint8Array): void; -} - -// Handles messages that are useful in the UI and that occur at any layer of the -// recording (trace, connection). The messages includes both status messages and -// error messages. -export interface OnMessageCallback { - (message: string): void; -} - -// Handles the loss of the connection at the connection layer (used by the -// AdbConnection). -export interface OnDisconnectCallback { - (errorMessage?: string): void; -} - -// Called when there is a change of targets or within a target. -// For instance, it's used when an Adb device becomes connected/disconnected. -// It's also executed by a target when the information it stores gets updated. -export interface OnTargetChangeCallback { - (): void; -} - -// A collection of callbacks that is passed to RecordingTargetV2 and -// subsequently to TracingSession. The callbacks are decided by the UI, so the -// recording code is not coupled with the rendering logic. -export interface TracingSessionListener { - onTraceData: OnTraceDataCallback; - onStatus: OnMessageCallback; - onDisconnect: OnDisconnectCallback; - onError: OnMessageCallback; -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/recording_page_controller.ts b/third_party/perfetto/ui/src/common/recordingV2/recording_page_controller.ts deleted file mode 100644 index a77edf194473..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/recording_page_controller.ts +++ /dev/null @@ -1,522 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertExists, assertTrue} from '../../base/logging'; -import {globals} from '../../frontend/globals'; -import {autosaveConfigStore} from '../../frontend/record_config'; -import { - DEFAULT_ADB_WEBSOCKET_URL, - DEFAULT_TRACED_WEBSOCKET_URL, -} from '../../frontend/recording/recording_ui_utils'; -import { - couldNotClaimInterface, -} from '../../frontend/recording/reset_interface_modal'; -import {Actions} from '../actions'; -import {TRACE_SUFFIX} from '../constants'; -import {TraceConfig} from '../protos'; -import {currentDateHourAndMinute} from '../time'; - -import {genTraceConfig} from './recording_config_utils'; -import {RecordingError, showRecordingModal} from './recording_error_handling'; -import { - RecordingTargetV2, - TargetInfo, - TracingSession, - TracingSessionListener, -} from './recording_interfaces_v2'; -import { - BUFFER_USAGE_NOT_ACCESSIBLE, - RECORDING_IN_PROGRESS, -} from './recording_utils'; -import { - ANDROID_WEBSOCKET_TARGET_FACTORY, - AndroidWebsocketTargetFactory, -} from './target_factories/android_websocket_target_factory'; -import { - ANDROID_WEBUSB_TARGET_FACTORY, -} from './target_factories/android_webusb_target_factory'; -import { - HOST_OS_TARGET_FACTORY, - HostOsTargetFactory, -} from './target_factories/host_os_target_factory'; -import {targetFactoryRegistry} from './target_factory_registry'; - -// The recording page can be in any of these states. It can transition between -// states: -// a) because of a user actions - pressing a UI button ('Start', 'Stop', -// 'Cancel', 'Force reset' of the target), selecting a different target in -// the UI, authorizing authentication on an Android device, -// pulling the cable which connects an Android device. -// b) automatically - if there is no need to reset the device or if the user -// has previously authorised the device to be debugged via USB. -// -// Recording state machine: https://screenshot.googleplex.com/BaX5EGqQMajgV7G -export enum RecordingState { - NO_TARGET = 0, - TARGET_SELECTED = 1, - // P1 stands for 'Part 1', where we first connect to the device in order to - // obtain target information. - ASK_TO_FORCE_P1 = 2, - AUTH_P1 = 3, - TARGET_INFO_DISPLAYED = 4, - // P2 stands for 'Part 2', where we connect to device for the 2nd+ times, to - // record a tracing session. - ASK_TO_FORCE_P2 = 5, - AUTH_P2 = 6, - RECORDING = 7, - WAITING_FOR_TRACE_DISPLAY = 8, -} - -// Wraps a tracing session promise while the promise is being resolved (e.g. -// while we are awaiting for ADB auth). -class TracingSessionWrapper { - private tracingSession?: TracingSession = undefined; - private isCancelled = false; - // We only execute the logic in the callbacks if this TracingSessionWrapper - // is the one referenced by the controller. Otherwise this can hold a - // tracing session which the user has already cancelled, so it shouldn't - // influence the UI. - private tracingSessionListener: TracingSessionListener = { - onTraceData: (trace: Uint8Array) => - this.controller.maybeOnTraceData(this, trace), - onStatus: (message) => this.controller.maybeOnStatus(this, message), - onDisconnect: (errorMessage?: string) => - this.controller.maybeOnDisconnect(this, errorMessage), - onError: (errorMessage: string) => - this.controller.maybeOnError(this, errorMessage), - }; - - private target: RecordingTargetV2; - private controller: RecordingPageController; - - constructor(target: RecordingTargetV2, controller: RecordingPageController) { - this.target = target; - this.controller = controller; - } - - async start(traceConfig: TraceConfig) { - let stateGeneratioNr = this.controller.getStateGeneration(); - const createSession = async () => { - try { - this.controller.maybeSetState( - this, RecordingState.AUTH_P2, stateGeneratioNr); - stateGeneratioNr += 1; - - const session = - await this.target.createTracingSession(this.tracingSessionListener); - - // We check the `isCancelled` to see if the user has cancelled the - // tracing session before it becomes available in TracingSessionWrapper. - if (this.isCancelled) { - session.cancel(); - return; - } - - this.tracingSession = session; - this.controller.maybeSetState( - this, RecordingState.RECORDING, stateGeneratioNr); - // When the session is resolved, the traceConfig has been instantiated. - this.tracingSession.start(assertExists(traceConfig)); - } catch (e) { - this.tracingSessionListener.onError(e.message); - } - }; - - if (await this.target.canConnectWithoutContention()) { - await createSession(); - } else { - // If we need to reset the connection to be able to connect, we ask - // the user if they want to reset the connection. - this.controller.maybeSetState( - this, RecordingState.ASK_TO_FORCE_P2, stateGeneratioNr); - stateGeneratioNr += 1; - couldNotClaimInterface( - createSession, () => this.controller.maybeClearRecordingState(this)); - } - } - - async fetchTargetInfo() { - let stateGeneratioNr = this.controller.getStateGeneration(); - const createSession = async () => { - try { - this.controller.maybeSetState( - this, RecordingState.AUTH_P1, stateGeneratioNr); - stateGeneratioNr += 1; - await this.target.fetchTargetInfo(this.tracingSessionListener); - this.controller.maybeSetState( - this, RecordingState.TARGET_INFO_DISPLAYED, stateGeneratioNr); - } catch (e) { - this.tracingSessionListener.onError(e.message); - } - }; - - if (await this.target.canConnectWithoutContention()) { - await createSession(); - } else { - // If we need to reset the connection to be able to connect, we ask - // the user if they want to reset the connection. - this.controller.maybeSetState( - this, RecordingState.ASK_TO_FORCE_P1, stateGeneratioNr); - stateGeneratioNr += 1; - couldNotClaimInterface( - createSession, - () => this.controller.maybeSetState( - this, RecordingState.TARGET_SELECTED, stateGeneratioNr)); - } - } - - cancel() { - if (this.tracingSession) { - this.tracingSession.cancel(); - } else { - // In some cases, the tracingSession may not be available to the - // TracingSessionWrapper when the user cancels it. - // For instance: - // 1. The user clicked 'Start'. - // 2. They clicked 'Stop' without authorizing on the device. - // 3. They clicked 'Start'. - // 4. They authorized on the device. - // In these cases, we want to cancel the tracing session as soon as it - // becomes available. Therefore, we keep the `isCancelled` boolean and - // check it when we receive the tracing session. - this.isCancelled = true; - } - this.controller.maybeClearRecordingState(this); - } - - stop() { - const stateGeneratioNr = this.controller.getStateGeneration(); - if (this.tracingSession) { - this.tracingSession.stop(); - this.controller.maybeSetState( - this, RecordingState.WAITING_FOR_TRACE_DISPLAY, stateGeneratioNr); - } else { - // In some cases, the tracingSession may not be available to the - // TracingSessionWrapper when the user stops it. - // For instance: - // 1. The user clicked 'Start'. - // 2. They clicked 'Stop' without authorizing on the device. - // 3. They clicked 'Start'. - // 4. They authorized on the device. - // In these cases, we want to cancel the tracing session as soon as it - // becomes available. Therefore, we keep the `isCancelled` boolean and - // check it when we receive the tracing session. - this.isCancelled = true; - this.controller.maybeClearRecordingState(this); - } - } - - getTraceBufferUsage(): Promise { - if (!this.tracingSession) { - throw new RecordingError(BUFFER_USAGE_NOT_ACCESSIBLE); - } - return this.tracingSession.getTraceBufferUsage(); - } -} - -// Keeps track of the state the Ui is in. Has methods which are executed on -// user actions such as starting/stopping/cancelling a tracing session. -export class RecordingPageController { - // State of the recording page. This is set by user actions and/or automatic - // transitions. This is queried by the UI in order to - private state: RecordingState = RecordingState.NO_TARGET; - // Currently selected target. - private target?: RecordingTargetV2 = undefined; - // We wrap the tracing session in an object, because for some targets - // (Ex: Android) it is only created after we have succesfully authenticated - // with the target. - private tracingSessionWrapper?: TracingSessionWrapper = undefined; - // How much of the buffer is used for the current tracing session. - private bufferUsagePercentage: number = 0; - // A counter for state modifications. We use this to ensure that state - // transitions don't override one another in async functions. - private stateGeneration = 0; - - getBufferUsagePercentage(): number { - return this.bufferUsagePercentage; - } - - getState(): RecordingState { - return this.state; - } - - getStateGeneration(): number { - return this.stateGeneration; - } - - maybeSetState( - tracingSessionWrapper: TracingSessionWrapper, state: RecordingState, - stateGeneration: number): void { - if (this.tracingSessionWrapper !== tracingSessionWrapper) { - return; - } - if (stateGeneration !== this.stateGeneration) { - throw new RecordingError('Recording page state transition out of order.'); - } - this.setState(state); - globals.dispatch(Actions.setRecordingStatus({status: undefined})); - globals.rafScheduler.scheduleFullRedraw(); - } - - maybeClearRecordingState(tracingSessionWrapper: TracingSessionWrapper): void { - if (this.tracingSessionWrapper === tracingSessionWrapper) { - this.clearRecordingState(); - } - } - - maybeOnTraceData( - tracingSessionWrapper: TracingSessionWrapper, trace: Uint8Array) { - if (this.tracingSessionWrapper !== tracingSessionWrapper) { - return; - } - globals.dispatch(Actions.openTraceFromBuffer({ - title: 'Recorded trace', - buffer: trace.buffer, - fileName: `trace_${currentDateHourAndMinute()}${TRACE_SUFFIX}`, - })); - this.clearRecordingState(); - } - - maybeOnStatus(tracingSessionWrapper: TracingSessionWrapper, message: string) { - if (this.tracingSessionWrapper !== tracingSessionWrapper) { - return; - } - // For the 'Recording in progress for 7000ms we don't show a - // modal.' - if (message.startsWith(RECORDING_IN_PROGRESS)) { - globals.dispatch(Actions.setRecordingStatus({status: message})); - } else { - // For messages such as 'Please allow USB debugging on your - // device, which require a user action, we show a modal. - showRecordingModal(message); - } - } - - maybeOnDisconnect( - tracingSessionWrapper: TracingSessionWrapper, errorMessage?: string) { - if (this.tracingSessionWrapper !== tracingSessionWrapper) { - return; - } - if (errorMessage) { - showRecordingModal(errorMessage); - } - this.clearRecordingState(); - this.onTargetChange(); - } - - maybeOnError( - tracingSessionWrapper: TracingSessionWrapper, errorMessage: string) { - if (this.tracingSessionWrapper !== tracingSessionWrapper) { - return; - } - showRecordingModal(errorMessage); - this.clearRecordingState(); - } - - getTargetInfo(): TargetInfo|undefined { - if (!this.target) { - return undefined; - } - return this.target.getInfo(); - } - - canCreateTracingSession() { - if (!this.target) { - return false; - } - return this.target.canCreateTracingSession(); - } - - selectTarget(selectedTarget?: RecordingTargetV2) { - assertTrue( - RecordingState.NO_TARGET <= this.state && - this.state < RecordingState.RECORDING); - // If the selected target exists and is the same as the previous one, we - // don't need to do anything. - if (selectedTarget && selectedTarget === this.target) { - return; - } - - // We assign the new target and redraw the page. - this.target = selectedTarget; - - if (!this.target) { - this.setState(RecordingState.NO_TARGET); - globals.rafScheduler.scheduleFullRedraw(); - return; - } - this.setState(RecordingState.TARGET_SELECTED); - globals.rafScheduler.scheduleFullRedraw(); - - this.tracingSessionWrapper = this.createTracingSessionWrapper(this.target); - this.tracingSessionWrapper.fetchTargetInfo(); - } - - async addAndroidDevice(): Promise { - try { - const target = - await targetFactoryRegistry.get(ANDROID_WEBUSB_TARGET_FACTORY) - .connectNewTarget(); - this.selectTarget(target); - } catch (e) { - if (e instanceof RecordingError) { - showRecordingModal(e.message); - } else { - throw e; - } - } - } - - onTargetSelection(targetName: string): void { - assertTrue( - RecordingState.NO_TARGET <= this.state && - this.state < RecordingState.RECORDING); - const allTargets = targetFactoryRegistry.listTargets(); - this.selectTarget(allTargets.find((t) => t.getInfo().name === targetName)); - } - - onStartRecordingPressed(): void { - assertTrue(RecordingState.TARGET_INFO_DISPLAYED === this.state); - location.href = '#!/record/instructions'; - autosaveConfigStore.save(globals.state.recordConfig); - - const target = this.getTarget(); - const targetInfo = target.getInfo(); - globals.logging.logEvent( - 'Record Trace', `Record trace (${targetInfo.targetType})`); - const traceConfig = genTraceConfig(globals.state.recordConfig, targetInfo); - - this.tracingSessionWrapper = this.createTracingSessionWrapper(target); - this.tracingSessionWrapper.start(traceConfig); - } - - onCancel() { - assertTrue( - RecordingState.AUTH_P2 <= this.state && - this.state <= RecordingState.RECORDING); - // The 'Cancel' button will only be shown after a `tracingSessionWrapper` - // is created. - this.getTracingSessionWrapper().cancel(); - } - - onStop() { - assertTrue( - RecordingState.AUTH_P2 <= this.state && - this.state <= RecordingState.RECORDING); - // The 'Stop' button will only be shown after a `tracingSessionWrapper` - // is created. - this.getTracingSessionWrapper().stop(); - } - - async fetchBufferUsage() { - assertTrue(this.state >= RecordingState.AUTH_P2); - if (!this.tracingSessionWrapper) return; - const session = this.tracingSessionWrapper; - - try { - const usage = await session.getTraceBufferUsage(); - if (this.tracingSessionWrapper === session) { - this.bufferUsagePercentage = usage; - } - } catch (e) { - // We ignore RecordingErrors because they are not necessary for the trace - // to be successfully collected. - if (!(e instanceof RecordingError)) { - throw e; - } - } - // We redraw if: - // 1. We received a correct buffer usage value. - // 2. We receive a RecordingError. - globals.rafScheduler.scheduleFullRedraw(); - } - - initFactories() { - assertTrue(this.state <= RecordingState.TARGET_INFO_DISPLAYED); - for (const targetFactory of targetFactoryRegistry.listTargetFactories()) { - if (targetFactory) { - targetFactory.setOnTargetChange(this.onTargetChange.bind(this)); - } - } - - if (targetFactoryRegistry.has(ANDROID_WEBSOCKET_TARGET_FACTORY)) { - const websocketTargetFactory = - targetFactoryRegistry.get(ANDROID_WEBSOCKET_TARGET_FACTORY) as - AndroidWebsocketTargetFactory; - websocketTargetFactory.tryEstablishWebsocket(DEFAULT_ADB_WEBSOCKET_URL); - } - if (targetFactoryRegistry.has(HOST_OS_TARGET_FACTORY)) { - const websocketTargetFactory = - targetFactoryRegistry.get(HOST_OS_TARGET_FACTORY) as - HostOsTargetFactory; - websocketTargetFactory.tryEstablishWebsocket( - DEFAULT_TRACED_WEBSOCKET_URL); - } - } - - shouldShowTargetSelection(): boolean { - return RecordingState.NO_TARGET <= this.state && - this.state < RecordingState.RECORDING; - } - - shouldShowStopCancelButtons(): boolean { - return RecordingState.AUTH_P2 <= this.state && - this.state <= RecordingState.RECORDING; - } - - private onTargetChange() { - const allTargets = targetFactoryRegistry.listTargets(); - // If the change happens for an existing target, the controller keeps the - // currently selected target in focus. - if (this.target && allTargets.includes(this.target)) { - globals.rafScheduler.scheduleFullRedraw(); - return; - } - // If the change happens to a new target or the controller does not have a - // defined target, the selection process again is run again. - this.selectTarget(); - } - - private createTracingSessionWrapper(target: RecordingTargetV2): - TracingSessionWrapper { - return new TracingSessionWrapper(target, this); - } - - private clearRecordingState(): void { - this.bufferUsagePercentage = 0; - this.tracingSessionWrapper = undefined; - this.setState(RecordingState.TARGET_INFO_DISPLAYED); - globals.dispatch(Actions.setRecordingStatus({status: undefined})); - // Redrawing because this method has changed the RecordingState, which will - // affect the display of the record_page. - globals.rafScheduler.scheduleFullRedraw(); - } - - private setState(state: RecordingState) { - this.state = state; - this.stateGeneration += 1; - } - - private getTarget(): RecordingTargetV2 { - assertTrue(RecordingState.TARGET_INFO_DISPLAYED === this.state); - return assertExists(this.target); - } - - private getTracingSessionWrapper(): TracingSessionWrapper { - assertTrue( - RecordingState.ASK_TO_FORCE_P2 <= this.state && - this.state <= RecordingState.RECORDING); - return assertExists(this.tracingSessionWrapper); - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/recording_utils.ts b/third_party/perfetto/ui/src/common/recordingV2/recording_utils.ts deleted file mode 100644 index 72d2761826e7..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/recording_utils.ts +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Begin Websocket //////////////////////////////////////////////////////// - -export const WEBSOCKET_UNABLE_TO_CONNECT = - 'Unable to connect to device via websocket.'; - -// https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1 -export const WEBSOCKET_CLOSED_ABNORMALLY_CODE = 1006; - -// The messages read by the adb server have their length prepended in hex. -// This method adds the length at the beginning of the message. -// Example: 'host:track-devices' -> '0012host:track-devices' -// go/codesearch/aosp-android11/system/core/adb/SERVICES.TXT -export function buildAbdWebsocketCommand(cmd: string) { - const hdr = cmd.length.toString(16).padStart(4, '0'); - return hdr + cmd; -} - -// Sample user agent for Chrome on Mac OS: -// 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 -// (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36' -export function isMacOs(userAgent: string) { - return userAgent.toLowerCase().includes(' mac os '); -} - -// Sample user agent for Chrome on Linux: -// Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) -// Chrome/105.0.0.0 Safari/537.36 -export function isLinux(userAgent: string) { - return userAgent.toLowerCase().includes(' linux '); -} - -// Sample user agent for Chrome on Chrome OS: -// "Mozilla/5.0 (X11; CrOS x86_64 14816.99.0) AppleWebKit/537.36 -// (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36" -// This condition is wider, in the unlikely possibility of different casing, -export function isCrOS(userAgent: string) { - return userAgent.toLowerCase().includes(' cros '); -} - -// End Websocket ////////////////////////////////////////////////////////// - -// Begin Adb ////////////////////////////////////////////////////////////// - -export const BINARY_PUSH_FAILURE = 'BinaryPushFailure'; -export const BINARY_PUSH_UNKNOWN_RESPONSE = 'BinaryPushUnknownResponse'; - -// In case the device doesn't have the tracebox, we upload the latest version -// to this path. -export const TRACEBOX_DEVICE_PATH = '/data/local/tmp/tracebox'; - -// Experimentally, this takes 900ms on the first fetch and 20-30ms after -// because of caching. -export const TRACEBOX_FETCH_TIMEOUT = 30000; - -// Message shown to the user when they need to allow authentication on the -// device in order to connect. -export const ALLOW_USB_DEBUGGING = - 'Please allow USB debugging on device and try again.'; - -// If the Android device has the tracing service on it (from API version 29), -// then we can connect to this consumer socket. -export const DEFAULT_TRACED_CONSUMER_SOCKET_PATH = - 'localfilesystem:/dev/socket/traced_consumer'; - -// If the Android device does not have the tracing service on it (before API -// version 29), we will have to push the tracebox on the device. Then, we -// can connect to this consumer socket (using it does not require system admin -// privileges). -export const CUSTOM_TRACED_CONSUMER_SOCKET_PATH = - 'localabstract:traced_consumer'; - -// End Adb ///////////////////////////////////////////////////////////////// - - -// Begin Webusb /////////////////////////////////////////////////////////// - -export const NO_DEVICE_SELECTED = 'No device selected.'; - -export interface UsbInterfaceAndEndpoint { - readonly configurationValue: number; - readonly usbInterfaceNumber: number; - readonly endpoints: USBEndpoint[]; -} - -export const ADB_DEVICE_FILTER = { - classCode: 255, // USB vendor specific code - subclassCode: 66, // Android vendor specific subclass - protocolCode: 1, // Adb protocol -}; - -export function findInterfaceAndEndpoint(device: USBDevice): - UsbInterfaceAndEndpoint|undefined { - const adbDeviceFilter = ADB_DEVICE_FILTER; - for (const config of device.configurations) { - for (const interface_ of config.interfaces) { - for (const alt of interface_.alternates) { - if (alt.interfaceClass === adbDeviceFilter.classCode && - alt.interfaceSubclass === adbDeviceFilter.subclassCode && - alt.interfaceProtocol === adbDeviceFilter.protocolCode) { - return { - configurationValue: config.configurationValue, - usbInterfaceNumber: interface_.interfaceNumber, - endpoints: alt.endpoints, - }; - } // if (alternate) - } // for (interface.alternates) - } // for (configuration.interfaces) - } // for (configurations) - - return undefined; -} - -// End Webusb ////////////////////////////////////////////////////////////// - - -// Begin Chrome /////////////////////////////////////////////////////////// - -export const EXTENSION_ID = 'lfmkphfpdbjijhpomgecfikhfohaoine'; -export const EXTENSION_URL = - `https://chrome.google.com/webstore/detail/perfetto-ui/${EXTENSION_ID}`; -export const EXTENSION_NAME = 'Chrome extension'; -export const EXTENSION_NOT_INSTALLED = - `To trace Chrome from the Perfetto UI, you need to install our - ${EXTENSION_URL} and then reload this page.`; - -export const MALFORMED_EXTENSION_MESSAGE = 'Malformed extension message.'; -export const BUFFER_USAGE_NOT_ACCESSIBLE = 'Buffer usage not accessible'; -export const BUFFER_USAGE_INCORRECT_FORMAT = - 'The buffer usage data has am incorrect format'; - -// End Chrome ///////////////////////////////////////////////////////////// - - -// Begin Traced ////////////////////////////////////////////////////////// - -export const RECORDING_IN_PROGRESS = 'Recording in progress'; -export const PARSING_UNKNWON_REQUEST_ID = 'Unknown request id'; -export const PARSING_UNABLE_TO_DECODE_METHOD = 'Unable to decode method'; -export const PARSING_UNRECOGNIZED_PORT = 'Unrecognized consumer port response'; -export const PARSING_UNRECOGNIZED_MESSAGE = 'Unrecognized frame message'; - -// End Traced /////////////////////////////////////////////////////////// diff --git a/third_party/perfetto/ui/src/common/recordingV2/target_factories/android_websocket_target_factory.ts b/third_party/perfetto/ui/src/common/recordingV2/target_factories/android_websocket_target_factory.ts deleted file mode 100644 index c33bc64806b3..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/target_factories/android_websocket_target_factory.ts +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {RECORDING_V2_FLAG} from '../../feature_flags'; -import { - OnTargetChangeCallback, - RecordingTargetV2, - TargetFactory, -} from '../recording_interfaces_v2'; -import { - buildAbdWebsocketCommand, - WEBSOCKET_CLOSED_ABNORMALLY_CODE, -} from '../recording_utils'; -import {targetFactoryRegistry} from '../target_factory_registry'; -import {AndroidWebsocketTarget} from '../targets/android_websocket_target'; - -export const ANDROID_WEBSOCKET_TARGET_FACTORY = 'AndroidWebsocketTargetFactory'; - -// https://cs.android.com/android/platform/superproject/+/master:packages/ -// modules/adb/SERVICES.TXT;l=135 -const PREFIX_LENGTH = 4; - -// information received over the websocket regarding a device -// Ex: "${serialNumber} authorized" -interface ListedDevice { - serialNumber: string; - // Full list of connection states can be seen at: - // go/codesearch/android/packages/modules/adb/adb.cpp;l=115-139 - connectionState: string; -} - -// Contains the result of parsing a message received over websocket. -interface ParsingResult { - listedDevices: ListedDevice[]; - messageRemainder: string; -} - -// We issue the command 'track-devices' which will encode the short form -// of the device: -// see go/codesearch/android/packages/modules/adb/services.cpp;l=244-245 -// and go/codesearch/android/packages/modules/adb/transport.cpp;l=1417-1420 -// Therefore a line will contain solely the device serial number and the -// connectionState (and no other properties). -function parseListedDevice(line: string): ListedDevice|undefined { - const parts = line.split('\t'); - if (parts.length === 2) { - return { - serialNumber: parts[0], - connectionState: parts[1], - }; - } - return undefined; -} - -export function parseWebsocketResponse(message: string): ParsingResult { - // A response we receive on the websocket contains multiple messages: - // "{m1.length}{m1.payload}{m2.length}{m2.payload}..." - // where m1, m2 are messages - // Each message has the form: - // "{message.length}SN1\t${connectionState1}\nSN2\t${connectionState2}\n..." - // where SN1, SN2 are device serial numbers - // and connectionState1, connectionState2 are adb connection states, created - // here: go/codesearch/android/packages/modules/adb/adb.cpp;l=115-139 - const latestStatusByDevice: Map = new Map(); - while (message.length >= PREFIX_LENGTH) { - const payloadLength = parseInt(message.substring(0, PREFIX_LENGTH), 16); - const prefixAndPayloadLength = PREFIX_LENGTH + payloadLength; - if (message.length < prefixAndPayloadLength) { - break; - } - - const payload = message.substring(PREFIX_LENGTH, prefixAndPayloadLength); - for (const line of payload.split('\n')) { - const listedDevice = parseListedDevice(line); - if (listedDevice) { - // We overwrite previous states for the same serial number. - latestStatusByDevice.set( - listedDevice.serialNumber, listedDevice.connectionState); - } - } - message = message.substring(prefixAndPayloadLength); - } - const listedDevices: ListedDevice[] = []; - for (const [serialNumber, connectionState] of latestStatusByDevice - .entries()) { - listedDevices.push({serialNumber, connectionState}); - } - return {listedDevices, messageRemainder: message}; -} - -export class WebsocketConnection { - private targets: Map = - new Map(); - private pendingData: string = ''; - - constructor( - private websocket: WebSocket, - private maybeClearConnection: (connection: WebsocketConnection) => void, - private onTargetChange: OnTargetChangeCallback) { - this.initWebsocket(); - } - - listTargets(): RecordingTargetV2[] { - return Array.from(this.targets.values()); - } - - // Setup websocket callbacks. - initWebsocket(): void { - this.websocket.onclose = (ev: CloseEvent) => { - if (ev.code === WEBSOCKET_CLOSED_ABNORMALLY_CODE) { - console.info( - `It's safe to ignore the 'WebSocket connection to ${ - this.websocket.url} error above, if present. It occurs when ` + - 'checking the connection to the local Websocket server.'); - } - this.maybeClearConnection(this); - this.close(); - }; - - // once the websocket is open, we start tracking the devices - this.websocket.onopen = () => { - this.websocket.send(buildAbdWebsocketCommand('host:track-devices')); - }; - - this.websocket.onmessage = async (evt: MessageEvent) => { - let resp = await evt.data.text(); - if (resp.substr(0, 4) === 'OKAY') { - resp = resp.substr(4); - } - const parsingResult = parseWebsocketResponse(this.pendingData + resp); - this.pendingData = parsingResult.messageRemainder; - this.trackDevices(parsingResult.listedDevices); - }; - } - - close() { - // The websocket connection may have already been closed by the websocket - // server. - if (this.websocket.readyState === this.websocket.OPEN) { - this.websocket.close(); - } - // Disconnect all the targets, to release all the websocket connections that - // they hold and end their tracing sessions. - for (const target of this.targets.values()) { - target.disconnect(); - } - this.targets.clear(); - if (this.onTargetChange) { - this.onTargetChange(); - } - } - - getUrl() { - return this.websocket.url; - } - - // Handle messages received over the websocket regarding devices connecting - // or disconnecting. - private trackDevices(listedDevices: ListedDevice[]) { - // When a SN becomes offline, we should remove it from the list - // of targets. Otherwise, we should check if it maps to a target. If the - // SN does not map to a target, we should create one for it. - let targetsUpdated = false; - for (const listedDevice of listedDevices) { - if (['offline', 'unknown'].includes(listedDevice.connectionState)) { - const target = this.targets.get(listedDevice.serialNumber); - if (target === undefined) { - continue; - } - target.disconnect(); - this.targets.delete(listedDevice.serialNumber); - targetsUpdated = true; - } else if (!this.targets.has(listedDevice.serialNumber)) { - this.targets.set( - listedDevice.serialNumber, - new AndroidWebsocketTarget( - listedDevice.serialNumber, - this.websocket.url, - this.onTargetChange)); - targetsUpdated = true; - } - } - - // Notify the calling code that the list of targets has been updated. - if (targetsUpdated) { - this.onTargetChange(); - } - } -} - -export class AndroidWebsocketTargetFactory implements TargetFactory { - readonly kind = ANDROID_WEBSOCKET_TARGET_FACTORY; - private onTargetChange: OnTargetChangeCallback = () => {}; - private websocketConnection?: WebsocketConnection; - - getName() { - return 'Android Websocket'; - } - - listTargets(): RecordingTargetV2[] { - return this.websocketConnection ? this.websocketConnection.listTargets() : - []; - } - - listRecordingProblems(): string[] { - return []; - } - - // This interface method can not return anything because a websocket target - // can not be created on user input. It can only be created when the websocket - // server detects a new target. - connectNewTarget(): Promise { - return Promise.reject(new Error( - 'The websocket can only automatically connect targets ' + - 'when they become available.')); - } - - tryEstablishWebsocket(websocketUrl: string) { - if (this.websocketConnection) { - if (this.websocketConnection.getUrl() === websocketUrl) { - return; - } else { - this.websocketConnection.close(); - } - } - - const websocket = new WebSocket(websocketUrl); - this.websocketConnection = new WebsocketConnection( - websocket, this.maybeClearConnection, this.onTargetChange); - } - - maybeClearConnection(connection: WebsocketConnection): void { - if (this.websocketConnection === connection) { - this.websocketConnection = undefined; - } - } - - setOnTargetChange(onTargetChange: OnTargetChangeCallback) { - this.onTargetChange = onTargetChange; - } -} - -// We only want to instantiate this class if Recording V2 is enabled. -if (RECORDING_V2_FLAG.get()) { - targetFactoryRegistry.register(new AndroidWebsocketTargetFactory()); -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/target_factories/android_websocket_target_factory_unittest.ts b/third_party/perfetto/ui/src/common/recordingV2/target_factories/android_websocket_target_factory_unittest.ts deleted file mode 100644 index 943ab66d71c5..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/target_factories/android_websocket_target_factory_unittest.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {parseWebsocketResponse} from './android_websocket_target_factory'; - -test('parse device disconnection', () => { - const message = '001702121FQC20XXXX\toffline\n'; - const response = parseWebsocketResponse(message); - expect(response.messageRemainder).toEqual(''); - expect(response.listedDevices.length).toEqual(1); - expect(response.listedDevices[0].serialNumber).toEqual('02121FQC20XXXX'); - expect(response.listedDevices[0].connectionState).toEqual('offline'); -}); - -test('parse two devices connected in the same message', () => { - const message = '003202121FQC20XXXX\tdevice\n06131FDD40YYYY\tunauthorized\n'; - const response = parseWebsocketResponse(message); - expect(response.messageRemainder).toEqual(''); - expect(response.listedDevices.length).toEqual(2); - expect(response.listedDevices[0].serialNumber).toEqual('02121FQC20XXXX'); - expect(response.listedDevices[0].connectionState).toEqual('device'); - expect(response.listedDevices[1].serialNumber).toEqual('06131FDD40YYYY'); - expect(response.listedDevices[1].connectionState).toEqual('unauthorized'); -}); - -test('parse device connection in multiple messages', () => { - const message = '001702121FQC20XXXX\toffline\n001602121FQC20XXXX\tdevice\n' + - '001602121FQC20XXXX\tdevice\n'; - const response = parseWebsocketResponse(message); - expect(response.messageRemainder).toEqual(''); - expect(response.listedDevices.length).toEqual(1); - expect(response.listedDevices[0].serialNumber).toEqual('02121FQC20XXXX'); - expect(response.listedDevices[0].connectionState).toEqual('device'); -}); - -test('parse with remainder', () => { - const remainder = 'FFFFsome_other_stuff'; - const message = `001602121FQC20XXXX\tdevice\n${remainder}`; - const response = parseWebsocketResponse(message); - expect(response.messageRemainder).toEqual(remainder); - expect(response.listedDevices.length).toEqual(1); - expect(response.listedDevices[0].serialNumber).toEqual('02121FQC20XXXX'); - expect(response.listedDevices[0].connectionState).toEqual('device'); -}); diff --git a/third_party/perfetto/ui/src/common/recordingV2/target_factories/android_webusb_target_factory.ts b/third_party/perfetto/ui/src/common/recordingV2/target_factories/android_webusb_target_factory.ts deleted file mode 100644 index e498d63f4fb0..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/target_factories/android_webusb_target_factory.ts +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertExists} from '../../../base/logging'; -import {getErrorMessage} from '../../errors'; -import {RECORDING_V2_FLAG} from '../../feature_flags'; -import {AdbKeyManager} from '../auth/adb_key_manager'; -import {RecordingError} from '../recording_error_handling'; -import { - OnTargetChangeCallback, - RecordingTargetV2, - TargetFactory, -} from '../recording_interfaces_v2'; -import {ADB_DEVICE_FILTER, findInterfaceAndEndpoint} from '../recording_utils'; -import {targetFactoryRegistry} from '../target_factory_registry'; -import {AndroidWebusbTarget} from '../targets/android_webusb_target'; - -export const ANDROID_WEBUSB_TARGET_FACTORY = 'AndroidWebusbTargetFactory'; -const SERIAL_NUMBER_ISSUE = 'an invalid serial number'; -const ADB_INTERFACE_ISSUE = 'an incompatible adb interface'; - -interface DeviceValidity { - isValid: boolean; - issues: string[]; -} - -function createDeviceErrorMessage(device: USBDevice, issue: string): string { - const productName = device.productName; - return `USB device${productName ? ' ' + productName : ''} has ${issue}`; -} - -export class AndroidWebusbTargetFactory implements TargetFactory { - readonly kind = ANDROID_WEBUSB_TARGET_FACTORY; - onTargetChange: OnTargetChangeCallback = () => {}; - private recordingProblems: string[] = []; - private targets: Map = - new Map(); - // AdbKeyManager should only be instantiated once, so we can use the same key - // for all devices. - private keyManager: AdbKeyManager = new AdbKeyManager(); - - constructor(private usb: USB) { - this.init(); - } - - getName() { - return 'Android WebUsb'; - } - - listTargets(): RecordingTargetV2[] { - return Array.from(this.targets.values()); - } - - listRecordingProblems(): string[] { - return this.recordingProblems; - } - - async connectNewTarget(): Promise { - let device: USBDevice; - try { - device = await this.usb.requestDevice({filters: [ADB_DEVICE_FILTER]}); - } catch (e) { - throw new RecordingError(getErrorMessage(e)); - } - - const deviceValid = this.checkDeviceValidity(device); - if (!deviceValid.isValid) { - throw new RecordingError(deviceValid.issues.join('\n')); - } - - const androidTarget = - new AndroidWebusbTarget(device, this.keyManager, this.onTargetChange); - this.targets.set(assertExists(device.serialNumber), androidTarget); - return androidTarget; - } - - setOnTargetChange(onTargetChange: OnTargetChangeCallback) { - this.onTargetChange = onTargetChange; - } - - private async init() { - for (const device of await this.usb.getDevices()) { - if (this.checkDeviceValidity(device).isValid) { - this.targets.set( - assertExists(device.serialNumber), - new AndroidWebusbTarget( - device, this.keyManager, this.onTargetChange)); - } - } - - this.usb.addEventListener('connect', (ev: USBConnectionEvent) => { - if (this.checkDeviceValidity(ev.device).isValid) { - this.targets.set( - assertExists(ev.device.serialNumber), - new AndroidWebusbTarget( - ev.device, this.keyManager, this.onTargetChange)); - this.onTargetChange(); - } - }); - - this.usb.addEventListener('disconnect', async (ev: USBConnectionEvent) => { - // We don't check device validity when disconnecting because if the device - // is invalid we would not have connected in the first place. - const serialNumber = assertExists(ev.device.serialNumber); - await assertExists(this.targets.get(serialNumber)) - .disconnect(`Device with serial ${serialNumber} was disconnected.`); - this.targets.delete(serialNumber); - this.onTargetChange(); - }); - } - - private checkDeviceValidity(device: USBDevice): DeviceValidity { - const deviceValidity: DeviceValidity = {isValid: true, issues: []}; - if (!device.serialNumber) { - deviceValidity.issues.push( - createDeviceErrorMessage(device, SERIAL_NUMBER_ISSUE)); - deviceValidity.isValid = false; - } - if (!findInterfaceAndEndpoint(device)) { - deviceValidity.issues.push( - createDeviceErrorMessage(device, ADB_INTERFACE_ISSUE)); - deviceValidity.isValid = false; - } - this.recordingProblems.push(...deviceValidity.issues); - return deviceValidity; - } -} - -// We only want to instantiate this class if: -// 1. The browser implements the USB functionality. -// 2. Recording V2 is enabled. -if (navigator.usb && RECORDING_V2_FLAG.get()) { - targetFactoryRegistry.register(new AndroidWebusbTargetFactory(navigator.usb)); -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts b/third_party/perfetto/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts deleted file mode 100644 index 1650934f0af8..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {RecordingError} from '../recording_error_handling'; -import { - OnTargetChangeCallback, - RecordingTargetV2, - TargetFactory, -} from '../recording_interfaces_v2'; -import { - EXTENSION_ID, - EXTENSION_NOT_INSTALLED, - isCrOS, -} from '../recording_utils'; -import {targetFactoryRegistry} from '../target_factory_registry'; -import {ChromeTarget} from '../targets/chrome_target'; - -export const CHROME_TARGET_FACTORY = 'ChromeTargetFactory'; - -export class ChromeTargetFactory implements TargetFactory { - readonly kind = CHROME_TARGET_FACTORY; - // We only check the connection once at the beginning to: - // a) Avoid creating a 'Port' object every time 'getInfo' is called. - // b) When a new Port is created, the extension starts communicating with it - // and leaves aside the old Port objects, so creating a new Port would break - // any ongoing tracing session. - isExtensionInstalled: boolean = false; - private targets: ChromeTarget[] = []; - - constructor() { - this.init(); - } - - init() { - const testPort = chrome.runtime.connect(EXTENSION_ID); - this.isExtensionInstalled = !!testPort; - testPort.disconnect(); - - if (!this.isExtensionInstalled) { - return; - } - this.targets.push(new ChromeTarget('Chrome', 'CHROME')); - if (isCrOS(navigator.userAgent)) { - this.targets.push(new ChromeTarget('ChromeOS', 'CHROME_OS')); - } - } - - connectNewTarget(): Promise { - throw new RecordingError( - 'Can not create a new Chrome target.' + - 'All Chrome targets are created at factory initialisation.'); - } - - getName(): string { - return 'Chrome'; - } - - listRecordingProblems(): string[] { - const recordingProblems = []; - if (!this.isExtensionInstalled) { - recordingProblems.push(EXTENSION_NOT_INSTALLED); - } - return recordingProblems; - } - - listTargets(): RecordingTargetV2[] { - return this.targets; - } - - setOnTargetChange(onTargetChange: OnTargetChangeCallback): void { - for (const target of this.targets) { - target.onTargetChange = onTargetChange; - } - } -} - -// We only instantiate the factory if Perfetto UI is open in the Chrome browser. -if (window.chrome && chrome.runtime) { - targetFactoryRegistry.register(new ChromeTargetFactory()); -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts b/third_party/perfetto/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts deleted file mode 100644 index c12365d8409c..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {isCrOS, isLinux, isMacOs} from '../recording_utils'; - -test('parse Chrome on Chrome OS user agent', () => { - const userAgent = 'Mozilla/5.0 (X11; CrOS x86_64 14816.99.0) ' + - 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 ' + - 'Safari/537.36'; - expect(isCrOS(userAgent)).toBe(true); - expect(isMacOs(userAgent)).toBe(false); - expect(isLinux(userAgent)).toBe(false); -}); - -test('parse Chrome on Mac user agent', () => { - const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' + - 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'; - expect(isCrOS(userAgent)).toBe(false); - expect(isMacOs(userAgent)).toBe(true); - expect(isLinux(userAgent)).toBe(false); -}); - -test('parse Chrome on Linux user agent', () => { - const userAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' + - '(KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'; - expect(isCrOS(userAgent)).toBe(false); - expect(isMacOs(userAgent)).toBe(false); - expect(isLinux(userAgent)).toBe(true); -}); diff --git a/third_party/perfetto/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts b/third_party/perfetto/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts deleted file mode 100644 index d7342c08eb77..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {RecordingError} from '../recording_error_handling'; -import { - OnTargetChangeCallback, - RecordingTargetV2, - TargetFactory, -} from '../recording_interfaces_v2'; -import {isLinux, isMacOs} from '../recording_utils'; -import {targetFactoryRegistry} from '../target_factory_registry'; -import {HostOsTarget} from '../targets/host_os_target'; - -export const HOST_OS_TARGET_FACTORY = 'HostOsTargetFactory'; - -export class HostOsTargetFactory implements TargetFactory { - readonly kind = HOST_OS_TARGET_FACTORY; - private target?: HostOsTarget; - private onTargetChange: OnTargetChangeCallback = () => {}; - - connectNewTarget(): Promise { - throw new RecordingError( - 'Can not create a new Host OS target.' + - 'The Host OS target is created at factory initialisation.'); - } - - getName(): string { - return 'HostOs'; - } - - listRecordingProblems(): string[] { - return []; - } - - listTargets(): RecordingTargetV2[] { - if (this.target) { - return [this.target]; - } - return []; - } - - tryEstablishWebsocket(websocketUrl: string) { - if (this.target) { - if (this.target.getUrl() === websocketUrl) { - return; - } else { - this.target.disconnect(); - } - } - this.target = new HostOsTarget( - websocketUrl, this.maybeClearTarget.bind(this), this.onTargetChange); - this.onTargetChange(); - } - - maybeClearTarget(target: HostOsTarget): void { - if (this.target === target) { - this.target = undefined; - this.onTargetChange(); - } - } - - setOnTargetChange(onTargetChange: OnTargetChangeCallback): void { - this.onTargetChange = onTargetChange; - } -} - -// We instantiate the host target factory only on Mac and Linux. -if (isMacOs(navigator.userAgent) || isLinux(navigator.userAgent)) { - targetFactoryRegistry.register(new HostOsTargetFactory()); -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/target_factories/index.ts b/third_party/perfetto/ui/src/common/recordingV2/target_factories/index.ts deleted file mode 100644 index 3c1e3af2215e..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/target_factories/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import './android_webusb_target_factory'; -import './android_websocket_target_factory'; -import './chrome_target_factory'; -import './host_os_target_factory'; -import './virtual_target_factory'; diff --git a/third_party/perfetto/ui/src/common/recordingV2/target_factories/virtual_target_factory.ts b/third_party/perfetto/ui/src/common/recordingV2/target_factories/virtual_target_factory.ts deleted file mode 100644 index b882168b2bbc..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/target_factories/virtual_target_factory.ts +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {RecordingError} from '../recording_error_handling'; -import { - OnTargetChangeCallback, - RecordingTargetV2, - TargetFactory, -} from '../recording_interfaces_v2'; -import {targetFactoryRegistry} from '../target_factory_registry'; -import {AndroidVirtualTarget} from '../targets/android_virtual_target'; - -const VIRTUAL_TARGET_FACTORY = 'VirtualTargetFactory'; - -export class VirtualTargetFactory implements TargetFactory { - readonly kind: string = VIRTUAL_TARGET_FACTORY; - private targets: AndroidVirtualTarget[]; - - constructor() { - this.targets = []; - this.targets.push(new AndroidVirtualTarget('Android Q', 29)); - this.targets.push(new AndroidVirtualTarget('Android P', 28)); - this.targets.push(new AndroidVirtualTarget('Android O-', 27)); - } - - connectNewTarget(): Promise { - throw new RecordingError( - 'Can not create a new virtual target.' + - 'All virtual targets are created at factory initialisation.'); - } - - getName(): string { - return 'Virtual'; - } - - listRecordingProblems(): string[] { - return []; - } - - listTargets(): RecordingTargetV2[] { - return this.targets; - } - - // Virtual targets won't change. - setOnTargetChange(_: OnTargetChangeCallback): void {} -} - -targetFactoryRegistry.register(new VirtualTargetFactory()); diff --git a/third_party/perfetto/ui/src/common/recordingV2/target_factory_registry.ts b/third_party/perfetto/ui/src/common/recordingV2/target_factory_registry.ts deleted file mode 100644 index f68ef8eab73b..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/target_factory_registry.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Registry} from '../registry'; - -import {RecordingTargetV2, TargetFactory} from './recording_interfaces_v2'; - -export class TargetFactoryRegistry extends Registry { - listTargets(): RecordingTargetV2[] { - const targets: RecordingTargetV2[] = []; - for (const factory of this.registry.values()) { - for (const target of factory.listTargets()) { - targets.push(target); - } - } - return targets; - } - - listTargetFactories(): TargetFactory[] { - return Array.from(this.registry.values()); - } - - listRecordingProblems(): string[] { - const recordingProblems: string[] = []; - for (const factory of this.registry.values()) { - for (const recordingProblem of factory.listRecordingProblems()) { - recordingProblems.push(recordingProblem); - } - } - return recordingProblems; - } -} - -export const targetFactoryRegistry = new TargetFactoryRegistry((f) => { - return f.kind; -}); diff --git a/third_party/perfetto/ui/src/common/recordingV2/targets/android_target.ts b/third_party/perfetto/ui/src/common/recordingV2/targets/android_target.ts deleted file mode 100644 index b39266c841dc..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/targets/android_target.ts +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {fetchWithTimeout} from '../../../base/http_utils'; -import {VERSION} from '../../../gen/perfetto_version'; -import {AdbConnectionImpl} from '../adb_connection_impl'; -import { - DataSource, - OnTargetChangeCallback, - RecordingTargetV2, - TargetInfo, - TracingSession, - TracingSessionListener, -} from '../recording_interfaces_v2'; -import { - CUSTOM_TRACED_CONSUMER_SOCKET_PATH, - DEFAULT_TRACED_CONSUMER_SOCKET_PATH, - TRACEBOX_DEVICE_PATH, - TRACEBOX_FETCH_TIMEOUT, -} from '../recording_utils'; -import {TracedTracingSession} from '../traced_tracing_session'; - -export abstract class AndroidTarget implements RecordingTargetV2 { - private consumerSocketPath = DEFAULT_TRACED_CONSUMER_SOCKET_PATH; - protected androidApiLevel?: number; - protected dataSources?: DataSource[]; - - protected constructor( - private adbConnection: AdbConnectionImpl, - private onTargetChange: OnTargetChangeCallback) {} - - abstract getInfo(): TargetInfo; - - // This is called when a usb USBConnectionEvent of type 'disconnect' event is - // emitted. This event is emitted when the USB connection is lost (example: - // when the user unplugged the connecting cable). - async disconnect(disconnectMessage?: string): Promise { - await this.adbConnection.disconnect(disconnectMessage); - } - - // Starts a tracing session in order to fetch information such as apiLevel - // and dataSources from the device. Then, it cancels the session. - async fetchTargetInfo(listener: TracingSessionListener): Promise { - const tracingSession = await this.createTracingSession(listener); - tracingSession.cancel(); - } - - // We do not support long tracing on Android. - canCreateTracingSession(recordingMode: string): boolean { - return recordingMode !== 'LONG_TRACE'; - } - - async createTracingSession(tracingSessionListener: TracingSessionListener): - Promise { - this.adbConnection.onStatus = tracingSessionListener.onStatus; - this.adbConnection.onDisconnect = tracingSessionListener.onDisconnect; - - if (!this.androidApiLevel) { - // 1. Fetch the API version from the device. - const version = await this.adbConnection.shellAndGetOutput( - 'getprop ro.build.version.sdk'); - this.androidApiLevel = Number(version); - - this.onTargetChange(); - - // 2. For older OS versions we push the tracebox binary. - if (this.androidApiLevel < 29) { - await this.pushTracebox(); - this.consumerSocketPath = CUSTOM_TRACED_CONSUMER_SOCKET_PATH; - - await this.adbConnection.shellAndWaitCompletion( - this.composeTraceboxCommand('traced')); - await this.adbConnection.shellAndWaitCompletion( - this.composeTraceboxCommand('traced_probes')); - } - } - - const adbStream = - await this.adbConnection.connectSocket(this.consumerSocketPath); - - // 3. Start a tracing session. - const tracingSession = - new TracedTracingSession(adbStream, tracingSessionListener); - await tracingSession.initConnection(); - - if (!this.dataSources) { - // 4. Fetch dataSources from QueryServiceState. - this.dataSources = await tracingSession.queryServiceState(); - - this.onTargetChange(); - } - return tracingSession; - } - - async pushTracebox() { - const arch = await this.fetchArchitecture(); - const shortVersion = VERSION.split('-')[0]; - const requestUrl = - `https://commondatastorage.googleapis.com/perfetto-luci-artifacts/${ - shortVersion}/${arch}/tracebox`; - const fetchResponse = await fetchWithTimeout( - requestUrl, {method: 'get'}, TRACEBOX_FETCH_TIMEOUT); - const traceboxBin = await fetchResponse.arrayBuffer(); - await this.adbConnection.push( - new Uint8Array(traceboxBin), TRACEBOX_DEVICE_PATH); - - // We explicitly set the tracebox permissions because adb does not reliably - // set permissions when uploading the binary. - await this.adbConnection.shellAndWaitCompletion( - `chmod 755 ${TRACEBOX_DEVICE_PATH}`); - } - - async fetchArchitecture() { - const abiList = await this.adbConnection.shellAndGetOutput( - 'getprop ro.vendor.product.cpu.abilist'); - // If multiple ABIs are allowed, the 64bit ones should have higher priority. - if (abiList.includes('arm64-v8a')) { - return 'android-arm64'; - } else if (abiList.includes('x86')) { - return 'android-x86'; - } else if (abiList.includes('armeabi-v7a') || abiList.includes('armeabi')) { - return 'android-arm'; - } else if (abiList.includes('x86_64')) { - return 'android-x64'; - } - // Most devices have arm64 architectures, so we should return this if - // nothing else is found. - return 'android-arm64'; - } - - canConnectWithoutContention(): Promise { - return this.adbConnection.canConnectWithoutContention(); - } - - composeTraceboxCommand(applet: string) { - // 1. Set the consumer socket. - return 'PERFETTO_CONSUMER_SOCK_NAME=@traced_consumer ' + - // 2. Set the producer socket. - 'PERFETTO_PRODUCER_SOCK_NAME=@traced_producer ' + - // 3. Start the applet in the background. - `/data/local/tmp/tracebox ${applet} --background`; - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/targets/android_virtual_target.ts b/third_party/perfetto/ui/src/common/recordingV2/targets/android_virtual_target.ts deleted file mode 100644 index b63269945be2..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/targets/android_virtual_target.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {RecordingError} from '../recording_error_handling'; -import { - RecordingTargetV2, - TargetInfo, - TracingSession, - TracingSessionListener, -} from '../recording_interfaces_v2'; - -export class AndroidVirtualTarget implements RecordingTargetV2 { - constructor(private name: string, private androidApiLevel: number) {} - - canConnectWithoutContention(): Promise { - return Promise.resolve(true); - } - - canCreateTracingSession(): boolean { - return false; - } - - createTracingSession(_: TracingSessionListener): Promise { - throw new RecordingError( - 'Can not create tracing session for a virtual target'); - } - - disconnect(_?: string): Promise { - throw new RecordingError('Can not disconnect from a virtual target'); - } - - fetchTargetInfo(_: TracingSessionListener): Promise { - return Promise.resolve(); - } - - getInfo(): TargetInfo { - return { - name: this.name, - androidApiLevel: this.androidApiLevel, - targetType: 'ANDROID', - dataSources: [], - }; - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/targets/android_websocket_target.ts b/third_party/perfetto/ui/src/common/recordingV2/targets/android_websocket_target.ts deleted file mode 100644 index ab4f13055b08..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/targets/android_websocket_target.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {AdbConnectionOverWebsocket} from '../adb_connection_over_websocket'; -import { - OnTargetChangeCallback, - TargetInfo, -} from '../recording_interfaces_v2'; -import {AndroidTarget} from './android_target'; - -export class AndroidWebsocketTarget extends AndroidTarget { - constructor( - private serialNumber: string, websocketUrl: string, - onTargetChange: OnTargetChangeCallback) { - super( - new AdbConnectionOverWebsocket(serialNumber, websocketUrl), - onTargetChange); - } - - getInfo(): TargetInfo { - return { - targetType: 'ANDROID', - // 'androidApiLevel' will be populated after ADB authorization. - androidApiLevel: this.androidApiLevel, - dataSources: this.dataSources || [], - name: this.serialNumber + ' WebSocket', - }; - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/targets/android_webusb_target.ts b/third_party/perfetto/ui/src/common/recordingV2/targets/android_webusb_target.ts deleted file mode 100644 index 4a69f13bca76..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/targets/android_webusb_target.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertExists} from '../../../base/logging'; -import {AdbConnectionOverWebusb} from '../adb_connection_over_webusb'; -import {AdbKeyManager} from '../auth/adb_key_manager'; -import { - OnTargetChangeCallback, - TargetInfo, -} from '../recording_interfaces_v2'; -import {AndroidTarget} from './android_target'; - -export class AndroidWebusbTarget extends AndroidTarget { - constructor( - private device: USBDevice, keyManager: AdbKeyManager, - onTargetChange: OnTargetChangeCallback) { - super(new AdbConnectionOverWebusb(device, keyManager), onTargetChange); - } - - getInfo(): TargetInfo { - const name = assertExists(this.device.productName) + ' ' + - assertExists(this.device.serialNumber) + ' WebUsb'; - return { - targetType: 'ANDROID', - // 'androidApiLevel' will be populated after ADB authorization. - androidApiLevel: this.androidApiLevel, - dataSources: this.dataSources || [], - name, - }; - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/targets/chrome_target.ts b/third_party/perfetto/ui/src/common/recordingV2/targets/chrome_target.ts deleted file mode 100644 index f87b7b16547d..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/targets/chrome_target.ts +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {ChromeTracedTracingSession} from '../chrome_traced_tracing_session'; -import { - ChromeTargetInfo, - OnTargetChangeCallback, - RecordingTargetV2, - TracingSession, - TracingSessionListener, -} from '../recording_interfaces_v2'; - -export class ChromeTarget implements RecordingTargetV2 { - onTargetChange?: OnTargetChangeCallback; - private chromeCategories?: string[]; - - constructor(private name: string, private targetType: 'CHROME'|'CHROME_OS') {} - - getInfo(): ChromeTargetInfo { - return { - targetType: this.targetType, - name: this.name, - dataSources: - [{name: 'chromeCategories', descriptor: this.chromeCategories}], - }; - } - - // Chrome targets are created after we check that the extension is installed, - // so they support tracing sessions. - canCreateTracingSession(): boolean { - return true; - } - - async createTracingSession(tracingSessionListener: TracingSessionListener): - Promise { - const tracingSession = - new ChromeTracedTracingSession(tracingSessionListener); - tracingSession.initConnection(); - - if (!this.chromeCategories) { - // Fetch chrome categories from the extension. - this.chromeCategories = await tracingSession.getCategories(); - if (this.onTargetChange) { - this.onTargetChange(); - } - } - - return tracingSession; - } - - // Starts a tracing session in order to fetch chrome categories from the - // device. Then, it cancels the session. - async fetchTargetInfo(tracingSessionListener: TracingSessionListener): - Promise { - const tracingSession = - await this.createTracingSession(tracingSessionListener); - tracingSession.cancel(); - } - - disconnect(_disconnectMessage?: string): Promise { - return Promise.resolve(undefined); - } - - // We can connect to the Chrome target without taking the connection away - // from another process. - async canConnectWithoutContention(): Promise { - return true; - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/targets/host_os_target.ts b/third_party/perfetto/ui/src/common/recordingV2/targets/host_os_target.ts deleted file mode 100644 index 6b13025e907f..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/targets/host_os_target.ts +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {HostOsByteStream} from '../host_os_byte_stream'; -import {RecordingError} from '../recording_error_handling'; -import { - DataSource, - HostOsTargetInfo, - OnDisconnectCallback, - OnTargetChangeCallback, - RecordingTargetV2, - TracingSession, - TracingSessionListener, -} from '../recording_interfaces_v2'; -import { - isLinux, - isMacOs, - WEBSOCKET_CLOSED_ABNORMALLY_CODE, -} from '../recording_utils'; -import {TracedTracingSession} from '../traced_tracing_session'; - -export class HostOsTarget implements RecordingTargetV2 { - private readonly targetType: 'LINUX'|'MACOS'; - private readonly name: string; - private websocket: WebSocket; - private streams = new Set(); - private dataSources?: DataSource[]; - private onDisconnect: OnDisconnectCallback = (_) => {}; - - constructor( - websocketUrl: string, - private maybeClearTarget: (target: HostOsTarget) => void, - private onTargetChange: OnTargetChangeCallback) { - if (isMacOs(navigator.userAgent)) { - this.name = 'MacOS'; - this.targetType = 'MACOS'; - } else if (isLinux(navigator.userAgent)) { - this.name = 'Linux'; - this.targetType = 'LINUX'; - } else { - throw new RecordingError( - 'Host OS target created on an unsupported operating system.'); - } - - this.websocket = new WebSocket(websocketUrl); - this.websocket.onclose = this.onClose.bind(this); - // 'onError' gets called when the websocketURL where the UI tries to connect - // is disallowed by the Content Security Policy. In this case, we disconnect - // the target. - this.websocket.onerror = this.disconnect.bind(this); - } - - getInfo(): HostOsTargetInfo { - return { - targetType: this.targetType, - name: this.name, - dataSources: this.dataSources || [], - }; - } - - canCreateTracingSession(): boolean { - return true; - } - - async createTracingSession(tracingSessionListener: TracingSessionListener): - Promise { - this.onDisconnect = tracingSessionListener.onDisconnect; - - const osStream = await HostOsByteStream.create(this.getUrl()); - this.streams.add(osStream); - const tracingSession = - new TracedTracingSession(osStream, tracingSessionListener); - await tracingSession.initConnection(); - - if (!this.dataSources) { - this.dataSources = await tracingSession.queryServiceState(); - this.onTargetChange(); - } - return tracingSession; - } - - // Starts a tracing session in order to fetch data sources from the - // device. Then, it cancels the session. - async fetchTargetInfo(tracingSessionListener: TracingSessionListener): - Promise { - const tracingSession = - await this.createTracingSession(tracingSessionListener); - tracingSession.cancel(); - } - - async disconnect(): Promise { - if (this.websocket.readyState === this.websocket.OPEN) { - this.websocket.close(); - // We remove the 'onclose' callback so the 'disconnect' method doesn't get - // executed twice. - this.websocket.onclose = null; - } - for (const stream of this.streams) { - stream.close(); - } - // We remove the existing target from the factory if present. - this.maybeClearTarget(this); - // We run the onDisconnect callback in case this target is used for tracing. - this.onDisconnect(); - } - - // We can connect to the Host OS without taking the connection away from - // another process. - async canConnectWithoutContention(): Promise { - return true; - } - - getUrl() { - return this.websocket.url; - } - - private onClose(ev: CloseEvent): void { - if (ev.code === WEBSOCKET_CLOSED_ABNORMALLY_CODE) { - console.info( - `It's safe to ignore the 'WebSocket connection to ${ - this.getUrl()} error above, if present. It occurs when ` + - 'checking the connection to the local Websocket server.'); - } - this.disconnect(); - } -} diff --git a/third_party/perfetto/ui/src/common/recordingV2/traced_tracing_session.ts b/third_party/perfetto/ui/src/common/recordingV2/traced_tracing_session.ts deleted file mode 100644 index 0b1f656ca19f..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/traced_tracing_session.ts +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import protobuf from 'protobufjs/minimal'; - -import {defer, Deferred} from '../../base/deferred'; -import {assertExists, assertFalse, assertTrue} from '../../base/logging'; -import { - DisableTracingRequest, - DisableTracingResponse, - EnableTracingRequest, - EnableTracingResponse, - FreeBuffersRequest, - FreeBuffersResponse, - GetTraceStatsRequest, - GetTraceStatsResponse, - IBufferStats, - IMethodInfo, - IPCFrame, - ISlice, - QueryServiceStateRequest, - QueryServiceStateResponse, - ReadBuffersRequest, - ReadBuffersResponse, - TraceConfig, -} from '../protos'; - -import {RecordingError} from './recording_error_handling'; -import { - ByteStream, - DataSource, - TracingSession, - TracingSessionListener, -} from './recording_interfaces_v2'; -import { - BUFFER_USAGE_INCORRECT_FORMAT, - BUFFER_USAGE_NOT_ACCESSIBLE, - PARSING_UNABLE_TO_DECODE_METHOD, - PARSING_UNKNWON_REQUEST_ID, - PARSING_UNRECOGNIZED_MESSAGE, - PARSING_UNRECOGNIZED_PORT, - RECORDING_IN_PROGRESS, -} from './recording_utils'; - -// See wire_protocol.proto for more details. -const WIRE_PROTOCOL_HEADER_SIZE = 4; -// See basic_types.h (kIPCBufferSize) for more details. -const MAX_IPC_BUFFER_SIZE = 128 * 1024; - -const PROTO_LEN_DELIMITED_WIRE_TYPE = 2; -const TRACE_PACKET_PROTO_ID = 1; -const TRACE_PACKET_PROTO_TAG = - (TRACE_PACKET_PROTO_ID << 3) | PROTO_LEN_DELIMITED_WIRE_TYPE; - -function parseMessageSize(buffer: Uint8Array) { - const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.length); - return dv.getUint32(0, true); -} - -// This class implements the protocol described in -// https://perfetto.dev/docs/design-docs/api-and-abi#tracing-protocol-abi -export class TracedTracingSession implements TracingSession { - // Buffers received wire protocol data. - private incomingBuffer = new Uint8Array(MAX_IPC_BUFFER_SIZE); - private bufferedPartLength = 0; - private currentFrameLength?: number; - - private availableMethods: IMethodInfo[] = []; - private serviceId = -1; - - private resolveBindingPromise!: Deferred; - private requestMethods = new Map(); - - // Needed for ReadBufferResponse: all the trace packets are split into - // several slices. |partialPacket| is the buffer for them. Once we receive a - // slice with the flag |lastSliceForPacket|, a new packet is created. - private partialPacket: ISlice[] = []; - // Accumulates trace packets into a proto trace file.. - private traceProtoWriter = protobuf.Writer.create(); - - // Accumulates DataSource objects from QueryServiceStateResponse, - // which can have >1 replies for each query - // go/codesearch/android/external/perfetto/protos/ - // perfetto/ipc/consumer_port.proto;l=243-246 - private pendingDataSources: DataSource[] = []; - - // For concurrent calls to 'QueryServiceState', we return the same value. - private pendingQssMessage?: Deferred; - - // Wire protocol request ID. After each request it is increased. It is needed - // to keep track of the type of request, and parse the response correctly. - private requestId = 1; - - private pendingStatsMessages = new Array>(); - - // The bytestream is obtained when creating a connection with a target. - // For instance, the AdbStream is obtained from a connection with an Adb - // device. - constructor( - private byteStream: ByteStream, - private tracingSessionListener: TracingSessionListener) { - this.byteStream.addOnStreamDataCallback( - (data) => this.handleReceivedData(data)); - this.byteStream.addOnStreamCloseCallback(() => this.clearState()); - } - - queryServiceState(): Promise { - if (this.pendingQssMessage) { - return this.pendingQssMessage; - } - - const requestProto = - QueryServiceStateRequest.encode(new QueryServiceStateRequest()) - .finish(); - this.rpcInvoke('QueryServiceState', requestProto); - - return this.pendingQssMessage = defer(); - } - - start(config: TraceConfig): void { - const duration = config.durationMs; - this.tracingSessionListener.onStatus(`${RECORDING_IN_PROGRESS}${ - duration ? ' for ' + duration.toString() + ' ms' : ''}...`); - - const enableTracingRequest = new EnableTracingRequest(); - enableTracingRequest.traceConfig = config; - const enableTracingRequestProto = - EnableTracingRequest.encode(enableTracingRequest).finish(); - this.rpcInvoke('EnableTracing', enableTracingRequestProto); - } - - cancel(): void { - this.terminateConnection(); - } - - stop(): void { - const requestProto = - DisableTracingRequest.encode(new DisableTracingRequest()).finish(); - this.rpcInvoke('DisableTracing', requestProto); - } - - async getTraceBufferUsage(): Promise { - if (!this.byteStream.isConnected()) { - // TODO(octaviant): make this more in line with the other trace buffer - // error cases. - return 0; - } - const bufferStats = await this.getBufferStats(); - let percentageUsed = -1; - for (const buffer of bufferStats) { - if (!Number.isFinite(buffer.bytesWritten) || - !Number.isFinite(buffer.bufferSize)) { - continue; - } - const used = assertExists(buffer.bytesWritten); - const total = assertExists(buffer.bufferSize); - if (total >= 0) { - percentageUsed = Math.max(percentageUsed, used / total); - } - } - - if (percentageUsed === -1) { - return Promise.reject(new RecordingError(BUFFER_USAGE_INCORRECT_FORMAT)); - } - return percentageUsed; - } - - initConnection(): Promise { - // bind IPC methods - const requestId = this.requestId++; - const frame = new IPCFrame({ - requestId, - msgBindService: new IPCFrame.BindService({serviceName: 'ConsumerPort'}), - }); - this.writeFrame(frame); - - // We shouldn't bind multiple times to the service in the same tracing - // session. - assertFalse(!!this.resolveBindingPromise); - this.resolveBindingPromise = defer(); - return this.resolveBindingPromise; - } - - private getBufferStats(): Promise { - const getTraceStatsRequestProto = - GetTraceStatsRequest.encode(new GetTraceStatsRequest()).finish(); - try { - this.rpcInvoke('GetTraceStats', getTraceStatsRequestProto); - } catch (e) { - // GetTraceStats was introduced only on Android 10. - this.raiseError(e); - } - - const statsMessage = defer(); - this.pendingStatsMessages.push(statsMessage); - return statsMessage; - } - - private terminateConnection(): void { - this.clearState(); - const requestProto = - FreeBuffersRequest.encode(new FreeBuffersRequest()).finish(); - this.rpcInvoke('FreeBuffers', requestProto); - this.byteStream.close(); - } - - private clearState() { - for (const statsMessage of this.pendingStatsMessages) { - statsMessage.reject(new RecordingError(BUFFER_USAGE_NOT_ACCESSIBLE)); - } - this.pendingStatsMessages = []; - this.pendingDataSources = []; - this.pendingQssMessage = undefined; - } - - private rpcInvoke(methodName: string, argsProto: Uint8Array): void { - if (!this.byteStream.isConnected()) { - return; - } - const method = this.availableMethods.find((m) => m.name === methodName); - if (!method || !method.id) { - throw new RecordingError( - `Method ${methodName} not supported by the target`); - } - const requestId = this.requestId++; - const frame = new IPCFrame({ - requestId, - msgInvokeMethod: new IPCFrame.InvokeMethod( - {serviceId: this.serviceId, methodId: method.id, argsProto}), - }); - this.requestMethods.set(requestId, methodName); - this.writeFrame(frame); - } - - private writeFrame(frame: IPCFrame): void { - const frameProto: Uint8Array = IPCFrame.encode(frame).finish(); - const frameLen = frameProto.length; - const buf = new Uint8Array(WIRE_PROTOCOL_HEADER_SIZE + frameLen); - const dv = new DataView(buf.buffer); - dv.setUint32(0, frameProto.length, /* littleEndian */ true); - for (let i = 0; i < frameLen; i++) { - dv.setUint8(WIRE_PROTOCOL_HEADER_SIZE + i, frameProto[i]); - } - this.byteStream.write(buf); - } - - private handleReceivedData(rawData: Uint8Array): void { - // we parse the length of the next frame if it's available - if (this.currentFrameLength === undefined && - this.canCompleteLengthHeader(rawData)) { - const remainingFrameBytes = - WIRE_PROTOCOL_HEADER_SIZE - this.bufferedPartLength; - this.appendToIncomingBuffer(rawData.subarray(0, remainingFrameBytes)); - rawData = rawData.subarray(remainingFrameBytes); - - this.currentFrameLength = parseMessageSize(this.incomingBuffer); - this.bufferedPartLength = 0; - } - - // Parse all complete frames. - while (this.currentFrameLength !== undefined && - this.bufferedPartLength + rawData.length >= - this.currentFrameLength) { - // Read the remaining part of this message. - const bytesToCompleteMessage = - this.currentFrameLength - this.bufferedPartLength; - this.appendToIncomingBuffer(rawData.subarray(0, bytesToCompleteMessage)); - this.parseFrame(this.incomingBuffer.subarray(0, this.currentFrameLength)); - this.bufferedPartLength = 0; - // Remove the data just parsed. - rawData = rawData.subarray(bytesToCompleteMessage); - - if (!this.canCompleteLengthHeader(rawData)) { - this.currentFrameLength = undefined; - break; - } - this.currentFrameLength = parseMessageSize(rawData); - rawData = rawData.subarray(WIRE_PROTOCOL_HEADER_SIZE); - } - - // Buffer the remaining data (part of the next message). - this.appendToIncomingBuffer(rawData); - } - - private canCompleteLengthHeader(newData: Uint8Array): boolean { - return newData.length + this.bufferedPartLength > WIRE_PROTOCOL_HEADER_SIZE; - } - - private appendToIncomingBuffer(array: Uint8Array): void { - this.incomingBuffer.set(array, this.bufferedPartLength); - this.bufferedPartLength += array.length; - } - - private parseFrame(frameBuffer: Uint8Array): void { - // Get a copy of the ArrayBuffer to avoid the original being overriden. - // See 170256902#comment21 - const frame = IPCFrame.decode(frameBuffer.slice()); - if (frame.msg === 'msgBindServiceReply') { - const msgBindServiceReply = frame.msgBindServiceReply; - if (msgBindServiceReply && msgBindServiceReply.methods && - msgBindServiceReply.serviceId) { - assertTrue(msgBindServiceReply.success === true); - this.availableMethods = msgBindServiceReply.methods; - this.serviceId = msgBindServiceReply.serviceId; - this.resolveBindingPromise.resolve(); - } - } else if (frame.msg === 'msgInvokeMethodReply') { - const msgInvokeMethodReply = frame.msgInvokeMethodReply; - // We process messages without a `replyProto` field (for instance - // `FreeBuffers` does not have `replyProto`). However, we ignore messages - // without a valid 'success' field. - if (!msgInvokeMethodReply || !msgInvokeMethodReply.success) { - return; - } - - const method = this.requestMethods.get(frame.requestId); - if (!method) { - this.raiseError(`${PARSING_UNKNWON_REQUEST_ID}: ${frame.requestId}`); - return; - } - const decoder = decoders.get(method); - if (decoder === undefined) { - this.raiseError(`${PARSING_UNABLE_TO_DECODE_METHOD}: ${method}`); - return; - } - const data = {...decoder(msgInvokeMethodReply.replyProto)}; - - if (method === 'ReadBuffers') { - if (data.slices) { - for (const slice of data.slices) { - this.partialPacket.push(slice); - if (slice.lastSliceForPacket) { - let bufferSize = 0; - for (const slice of this.partialPacket) { - bufferSize += slice.data!.length; - } - const tracePacket = new Uint8Array(bufferSize); - let written = 0; - for (const slice of this.partialPacket) { - const data = slice.data!; - tracePacket.set(data, written); - written += data.length; - } - this.traceProtoWriter.uint32(TRACE_PACKET_PROTO_TAG); - this.traceProtoWriter.bytes(tracePacket); - this.partialPacket = []; - } - } - } - if (msgInvokeMethodReply.hasMore === false) { - this.tracingSessionListener.onTraceData( - this.traceProtoWriter.finish()); - this.terminateConnection(); - } - } else if (method === 'EnableTracing') { - const readBuffersRequestProto = - ReadBuffersRequest.encode(new ReadBuffersRequest()).finish(); - this.rpcInvoke('ReadBuffers', readBuffersRequestProto); - } else if (method === 'GetTraceStats') { - const maybePendingStatsMessage = this.pendingStatsMessages.shift(); - if (maybePendingStatsMessage) { - maybePendingStatsMessage.resolve(data?.traceStats?.bufferStats || []); - } - } else if (method === 'FreeBuffers') { - // No action required. If we successfully read a whole trace, - // we close the connection. Alternatively, if the tracing finishes - // with an exception or if the user cancels it, we also close the - // connection. - } else if (method === 'DisableTracing') { - // No action required. Same reasoning as for FreeBuffers. - } else if (method === 'QueryServiceState') { - const dataSources = - (data as QueryServiceStateResponse)?.serviceState?.dataSources || - []; - for (const dataSource of dataSources) { - const name = dataSource?.dsDescriptor?.name; - if (name) { - this.pendingDataSources.push( - {name, descriptor: dataSource.dsDescriptor}); - } - } - if (msgInvokeMethodReply.hasMore === false) { - assertExists(this.pendingQssMessage).resolve(this.pendingDataSources); - this.pendingDataSources = []; - this.pendingQssMessage = undefined; - } - } else { - this.raiseError(`${PARSING_UNRECOGNIZED_PORT}: ${method}`); - } - } else { - this.raiseError(`${PARSING_UNRECOGNIZED_MESSAGE}: ${frame.msg}`); - } - } - - private raiseError(message: string): void { - this.terminateConnection(); - this.tracingSessionListener.onError(message); - } -} - -const decoders = - new Map() - .set('EnableTracing', EnableTracingResponse.decode) - .set('FreeBuffers', FreeBuffersResponse.decode) - .set('ReadBuffers', ReadBuffersResponse.decode) - .set('DisableTracing', DisableTracingResponse.decode) - .set('GetTraceStats', GetTraceStatsResponse.decode) - .set('QueryServiceState', QueryServiceStateResponse.decode); diff --git a/third_party/perfetto/ui/src/common/recordingV2/websocket_menu_controller.ts b/third_party/perfetto/ui/src/common/recordingV2/websocket_menu_controller.ts deleted file mode 100644 index 023781036f85..000000000000 --- a/third_party/perfetto/ui/src/common/recordingV2/websocket_menu_controller.ts +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - ADB_ENDPOINT, - DEFAULT_WEBSOCKET_URL, - TRACED_ENDPOINT, -} from '../../frontend/recording/recording_ui_utils'; - -import {TargetFactory} from './recording_interfaces_v2'; -import { - ANDROID_WEBSOCKET_TARGET_FACTORY, - AndroidWebsocketTargetFactory, -} from './target_factories/android_websocket_target_factory'; -import { - HOST_OS_TARGET_FACTORY, - HostOsTargetFactory, -} from './target_factories/host_os_target_factory'; -import {targetFactoryRegistry} from './target_factory_registry'; - -// The WebsocketMenuController will handle paths for all factories which -// connect over websocket. At present, these are: -// - adb websocket factory -// - host OS websocket factory -export class WebsocketMenuController { - private path: string = DEFAULT_WEBSOCKET_URL; - - getPath(): string { - return this.path; - } - - setPath(path: string): void { - this.path = path; - } - - onPathChange(): void { - if (targetFactoryRegistry.has(ANDROID_WEBSOCKET_TARGET_FACTORY)) { - const androidTargetFactory = - targetFactoryRegistry.get(ANDROID_WEBSOCKET_TARGET_FACTORY) as - AndroidWebsocketTargetFactory; - androidTargetFactory.tryEstablishWebsocket(this.path + ADB_ENDPOINT); - } - - if (targetFactoryRegistry.has(HOST_OS_TARGET_FACTORY)) { - const hostTargetFactory = - targetFactoryRegistry.get(HOST_OS_TARGET_FACTORY) as - HostOsTargetFactory; - hostTargetFactory.tryEstablishWebsocket(this.path + TRACED_ENDPOINT); - } - } - - getTargetFactories(): TargetFactory[] { - const targetFactories = []; - if (targetFactoryRegistry.has(ANDROID_WEBSOCKET_TARGET_FACTORY)) { - targetFactories.push( - targetFactoryRegistry.get(ANDROID_WEBSOCKET_TARGET_FACTORY)); - } - if (targetFactoryRegistry.has(HOST_OS_TARGET_FACTORY)) { - targetFactories.push(targetFactoryRegistry.get(HOST_OS_TARGET_FACTORY)); - } - return targetFactories; - } -} diff --git a/third_party/perfetto/ui/src/common/registry.ts b/third_party/perfetto/ui/src/common/registry.ts deleted file mode 100644 index 1658834a39d9..000000000000 --- a/third_party/perfetto/ui/src/common/registry.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export interface HasKind { kind: string; } - -export class Registry { - private key: (t: T) => string; - protected registry: Map; - - static kindRegistry(): Registry { - return new Registry((t) => t.kind); - } - - constructor(key: (t: T) => string) { - this.registry = new Map(); - this.key = key; - } - - register(registrant: T) { - const kind = this.key(registrant); - if (this.registry.has(kind)) { - throw new Error(`Registrant ${kind} already exists in the registry`); - } - this.registry.set(kind, registrant); - } - - has(kind: string): boolean { - return this.registry.has(kind); - } - - get(kind: string): T { - const registrant = this.registry.get(kind); - if (registrant === undefined) { - throw new Error(`${kind} has not been registered.`); - } - return registrant; - } - - // Support iteration: for (const foo of fooRegistry.values()) { ... } - * values() { - yield* this.registry.values(); - } - - unregisterAllForTesting(): void { - this.registry.clear(); - } -} diff --git a/third_party/perfetto/ui/src/common/registry_unittest.ts b/third_party/perfetto/ui/src/common/registry_unittest.ts deleted file mode 100644 index 58a04e4c7a8a..000000000000 --- a/third_party/perfetto/ui/src/common/registry_unittest.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Registry} from './registry'; - -interface Registrant { - kind: string; - n: number; -} - -test('registry returns correct registrant', () => { - const registry = Registry.kindRegistry(); - - const a: Registrant = {kind: 'a', n: 1}; - const b: Registrant = {kind: 'b', n: 2}; - registry.register(a); - registry.register(b); - - expect(registry.get('a')).toBe(a); - expect(registry.get('b')).toBe(b); -}); - -test('registry throws error on kind collision', () => { - const registry = Registry.kindRegistry(); - - const a1: Registrant = {kind: 'a', n: 1}; - const a2: Registrant = {kind: 'a', n: 2}; - - registry.register(a1); - expect(() => registry.register(a2)).toThrow(); -}); - -test('registry throws error on non-existent track', () => { - const registry = Registry.kindRegistry(); - expect(() => registry.get('foo')).toThrow(); -}); - -test('registry allows iteration', () => { - const registry = Registry.kindRegistry(); - const a: Registrant = {kind: 'a', n: 1}; - const b: Registrant = {kind: 'b', n: 2}; - registry.register(a); - registry.register(b); - - const values = [...registry.values()]; - expect(values.length).toBe(2); - expect(values.includes(a)).toBe(true); - expect(values.includes(b)).toBe(true); -}); diff --git a/third_party/perfetto/ui/src/common/schema.ts b/third_party/perfetto/ui/src/common/schema.ts deleted file mode 100644 index 6dc59140647c..000000000000 --- a/third_party/perfetto/ui/src/common/schema.ts +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {EngineProxy} from './engine'; -import {STR} from './query_result'; - -const CACHED_SCHEMAS = new WeakMap(); - -export class SchemaError extends Error { - constructor(message: string) { - super(message); - } -} - -// POJO representing the table structure of trace_processor. -// Exposed for testing. -export interface DatabaseInfo { - tables: TableInfo[]; -} - -interface TableInfo { - name: string; - parent?: TableInfo; - columns: ColumnInfo[]; -} - -interface ColumnInfo { - name: string; -} - -async function getColumns( - engine: EngineProxy, table: string): Promise { - const result = await engine.query(`PRAGMA table_info(${table});`); - const it = result.iter({ - name: STR, - }); - const columns = []; - for (; it.valid(); it.next()) { - columns.push({name: it['name']}); - } - return columns; -} - -// Opinionated view on the schema of the given trace_processor instance -// suitable for EventSets to use for query generation. -export class DatabaseSchema { - private tableToKeys: Map>; - - constructor(info: DatabaseInfo) { - this.tableToKeys = new Map(); - for (const tableInfo of info.tables) { - const columns = new Set(tableInfo.columns.map((c) => c.name)); - this.tableToKeys.set(tableInfo.name, columns); - } - } - - // Return all the EventSet keys available for a given table. This - // includes the direct columns on the table (and all parent tables) - // as well as all direct and indirect joinable tables where the join - // is N:1 or 1:1. e.g. for the table thread_slice we also include - // the columns from thread, process, thread_track etc. - getKeys(tableName: string): Set { - const columns = this.tableToKeys.get(tableName); - if (columns === undefined) { - throw new SchemaError(`Unknown table '${tableName}'`); - } - return columns; - } -} - -// Deliberately not exported. Users should call getSchema below and -// participate in cacheing. -async function createSchema(engine: EngineProxy): Promise { - const tables: TableInfo[] = []; - const result = await engine.query(`SELECT name from perfetto_tables;`); - const it = result.iter({ - name: STR, - }); - for (; it.valid(); it.next()) { - const name = it['name']; - tables.push({ - name, - columns: await getColumns(engine, name), - }); - } - - const database: DatabaseInfo = { - tables, - }; - - return new DatabaseSchema(database); -} - -// Get the schema for the given engine (from the cache if possible). -// The schemas are per-engine (i.e. they can't be statically determined -// at build time) since we might be in httpd mode and not-running -// against the version of trace_processor we build with. -export async function getSchema(engine: EngineProxy): Promise { - const schema = CACHED_SCHEMAS.get(engine); - if (schema === undefined) { - const newSchema = await createSchema(engine); - CACHED_SCHEMAS.set(engine, newSchema); - return newSchema; - } - return schema; -} diff --git a/third_party/perfetto/ui/src/common/schema_unittest.ts b/third_party/perfetto/ui/src/common/schema_unittest.ts deleted file mode 100644 index 609eaacf6487..000000000000 --- a/third_party/perfetto/ui/src/common/schema_unittest.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {DatabaseInfo, DatabaseSchema, SchemaError} from './schema'; - -test('DatabaseSchema > getKeys', () => { - const info: DatabaseInfo = { - tables: [ - { - name: 'slice', - columns: [ - {name: 'id'}, - {name: 'ts'}, - {name: 'dur'}, - ], - }, - ], - }; - const schema = new DatabaseSchema(info); - expect(schema.getKeys('slice')).toEqual(new Set(['id', 'ts', 'dur'])); -}); - -test('DatabaseSchema > getKeys > Sad path', () => { - const info: DatabaseInfo = { - tables: [], - }; - const schema = new DatabaseSchema(info); - expect(() => schema.getKeys('foo')).toThrow(SchemaError); -}); diff --git a/third_party/perfetto/ui/src/common/search_data.ts b/third_party/perfetto/ui/src/common/search_data.ts deleted file mode 100644 index 0969c1d4412d..000000000000 --- a/third_party/perfetto/ui/src/common/search_data.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export interface SearchSummary { - tsStarts: Float64Array; - tsEnds: Float64Array; - count: Uint8Array; -} - -export interface CurrentSearchResults { - sliceIds: Float64Array; - tsStarts: Float64Array; - utids: Float64Array; - trackIds: string[]; - sources: string[]; - totalResults: number; -} diff --git a/third_party/perfetto/ui/src/common/selection_observer.ts b/third_party/perfetto/ui/src/common/selection_observer.ts deleted file mode 100644 index 0bd284ceba35..000000000000 --- a/third_party/perfetto/ui/src/common/selection_observer.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Selection} from './state'; - -export type SelectionChangedObserver = - (newSelection?: Selection, oldSelection?: Selection) => void; - -const selectionObservers: SelectionChangedObserver[] = []; - -export function onSelectionChanged( - newSelection?: Selection, oldSelection?: Selection) { - for (const observer of selectionObservers) { - observer(newSelection, oldSelection); - } -} - -export function addSelectionChangeObserver(observer: SelectionChangedObserver): - void { - selectionObservers.push(observer); -} diff --git a/third_party/perfetto/ui/src/common/state.ts b/third_party/perfetto/ui/src/common/state.ts deleted file mode 100644 index d4d07b70d371..000000000000 --- a/third_party/perfetto/ui/src/common/state.ts +++ /dev/null @@ -1,948 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {RecordConfig} from '../controller/record_config_types'; -import { - Aggregation, - PivotTree, - TableColumn, -} from '../frontend/pivot_table_types'; -import {Direction} from './event_set'; - -/** - * A plain js object, holding objects of type |Class| keyed by string id. - * We use this instead of using |Map| object since it is simpler and faster to - * serialize for use in postMessage. - */ -export interface ObjectById { [id: string]: Class; } - -export interface Timestamped { - lastUpdate: number; -} - -export type OmniboxMode = 'SEARCH'|'COMMAND'; - -export interface OmniboxState { - omnibox: string; - mode: OmniboxMode; -} - -export interface VisibleState extends Timestamped { - startSec: number; - endSec: number; - resolution: number; -} - -export interface AreaSelection { - kind: 'AREA'; - areaId: string; - // When an area is marked it will be assigned a unique note id and saved as - // an AreaNote for the user to return to later. id = 0 is the special id that - // is overwritten when a new area is marked. Any other id is a persistent - // marking that will not be overwritten. - // When not set, the area selection will be replaced with any - // new area selection (i.e. not saved anywhere). - noteId?: string; -} - -export type AreaById = Area&{id: string}; - -export interface Area { - startSec: number; - endSec: number; - tracks: string[]; -} - -export const MAX_TIME = 180; - -// 3: TrackKindPriority and related sorting changes. -// 5: Move a large number of items off frontendLocalState and onto state. -// 6: Common PivotTableConfig and pivot table specific PivotTableState. -// 7: Split Chrome categories in two and add 'symbolize ksyms' flag. -// 8: Rename several variables -// "[...]HeapProfileFlamegraph[...]" -> "[...]Flamegraph[...]". -// 9: Add a field to track last loaded recording profile name -// 10: Change last loaded profile tracking type to accommodate auto-save. -// 11: Rename updateChromeCategories to fetchChromeCategories. -// 12: Add a field to cache mapping from UI track ID to trace track ID in order -// to speed up flow arrows rendering. -// 13: FlamegraphState changed to support area selection. -// 14: Changed the type of uiTrackIdByTraceTrackId from `Map` to an object with -// typed key/value because a `Map` does not preserve type during -// serialisation+deserialisation. -// 15: Added state for Pivot Table V2 -// 16: Added boolean tracking if the flamegraph modal was dismissed -// 17: -// - add currentEngineId to track the id of the current engine -// - remove nextNoteId, nextAreaId and use nextId as a unique counter for all -// indexing except the indexing of the engines -// 18: areaSelection change see b/235869542 -// 19: Added visualisedArgs state. -// 20: Refactored thread sorting order. -// 21: Updated perf sample selection to include a ts range instead of single ts -// 22: Add log selection kind. -// 23: Add log filtering criteria for Android log entries. -// 24: Store only a single Engine. -// 25: Move omnibox state off VisibleState. -// 26: Add tags for filtering Android log entries. -// 27. Add a text entry for filtering Android log entries. -// 28. Add a boolean indicating if non matching log entries are hidden. -// 29. Add ftrace state. <-- Borked, state contains a non-serializable object. -// 30. Convert ftraceFilter.excludedNames from Set to string[]. -export const STATE_VERSION = 30; - -export const SCROLLING_TRACK_GROUP = 'ScrollingTracks'; - -export type EngineMode = 'WASM'|'HTTP_RPC'; - -export type NewEngineMode = 'USE_HTTP_RPC_IF_AVAILABLE'|'FORCE_BUILTIN_WASM'; - -// Tracks within track groups (usually corresponding to processes) are sorted. -// As we want to group all tracks related to a given thread together, we use -// two keys: -// - Primary key corresponds to a priority of a track block (all tracks related -// to a given thread or a single track if it's not thread-associated). -// - Secondary key corresponds to a priority of a given thread-associated track -// within its thread track block. -// Each track will have a sort key, which either a primary sort key -// (for non-thread tracks) or a tid and secondary sort key (mapping of tid to -// primary sort key is done independently). -export enum PrimaryTrackSortKey { - DEBUG_SLICE_TRACK, - NULL_TRACK, - PROCESS_SCHEDULING_TRACK, - PROCESS_SUMMARY_TRACK, - EXPECTED_FRAMES_SLICE_TRACK, - ACTUAL_FRAMES_SLICE_TRACK, - PERF_SAMPLES_PROFILE_TRACK, - HEAP_PROFILE_TRACK, - MAIN_THREAD, - RENDER_THREAD, - GPU_COMPLETION_THREAD, - CHROME_IO_THREAD, - CHROME_COMPOSITOR_THREAD, - ORDINARY_THREAD, - COUNTER_TRACK, - ASYNC_SLICE_TRACK, - ORDINARY_TRACK, -} - -// Key that is used to sort tracks within a block of tracks associated with a -// given thread. -export enum InThreadTrackSortKey { - THREAD_COUNTER_TRACK, - THREAD_SCHEDULING_STATE_TRACK, - CPU_STACK_SAMPLES_TRACK, - VISUALISED_ARGS_TRACK, - ORDINARY, - DEFAULT_TRACK, -} - -// Sort key used for sorting tracks associated with a thread. -export type ThreadTrackSortKey = { - utid: number, - priority: InThreadTrackSortKey, -} - -// Sort key for all tracks: both thread-associated and non-thread associated. -export type TrackSortKey = PrimaryTrackSortKey|ThreadTrackSortKey; - -// Mapping which defines order for threads within a given process. -export type UtidToTrackSortKey = { - [utid: number]: { - tid?: number, sortKey: PrimaryTrackSortKey, - } -} - -export enum ProfileType { - HEAP_PROFILE = 'heap_profile', - NATIVE_HEAP_PROFILE = 'heap_profile:libc.malloc', - JAVA_HEAP_SAMPLES = 'heap_profile:com.android.art', - JAVA_HEAP_GRAPH = 'graph', - PERF_SAMPLE = 'perf', -} - -export type FlamegraphStateViewingOption = - 'SPACE'|'ALLOC_SPACE'|'OBJECTS'|'ALLOC_OBJECTS'|'PERF_SAMPLES'; - -export interface CallsiteInfo { - id: number; - parentId: number; - depth: number; - name?: string; - totalSize: number; - selfSize: number; - mapping: string; - merged: boolean; - highlighted: boolean; - location?: string; -} - -export interface TraceFileSource { - type: 'FILE'; - file: File; -} - -export interface TraceArrayBufferSource { - type: 'ARRAY_BUFFER'; - buffer: ArrayBuffer; - title: string; - url?: string; - fileName?: string; - - // |uuid| is set only when loading via ?local_cache_key=1234. When set, - // this matches global.state.traceUuid, with the exception of the following - // time window: When a trace T1 is loaded and the user loads another trace T2, - // this |uuid| will be == T2, but the globals.state.traceUuid will be - // temporarily == T1 until T2 has been loaded (consistently to what happens - // with all other state fields). - uuid?: string; - // if |localOnly| is true then the trace should not be shared or downloaded. - localOnly?: boolean; -} - -export interface TraceUrlSource { - type: 'URL'; - url: string; -} - -export interface TraceHttpRpcSource { - type: 'HTTP_RPC'; -} - -export type TraceSource = - TraceFileSource|TraceArrayBufferSource|TraceUrlSource|TraceHttpRpcSource; - -export interface TrackState { - id: string; - engineId: string; - kind: string; - name: string; - labels?: string[]; - trackSortKey: TrackSortKey; - trackGroup?: string; - config: { - trackId?: number; - trackIds?: number[]; - }; -} - -export interface TrackGroupState { - id: string; - engineId: string; - name: string; - collapsed: boolean; - tracks: string[]; // Child track ids. -} - -export interface EngineConfig { - id: string; - mode?: EngineMode; // Is undefined until |ready| is true. - ready: boolean; - failed?: string; // If defined the engine has crashed with the given message. - source: TraceSource; -} - -export interface QueryConfig { - id: string; - engineId?: string; - query: string; -} - -export interface PermalinkConfig { - requestId?: string; // Set by the frontend to request a new permalink. - hash?: string; // Set by the controller when the link has been created. - isRecordingConfig?: - boolean; // this permalink request is for a recording config only -} - -export interface TraceTime { - startSec: number; - endSec: number; -} - -export interface FrontendLocalState { - visibleState: VisibleState; -} - -export interface Status { - msg: string; - timestamp: number; // Epoch in seconds (Date.now() / 1000). -} - -export interface Note { - noteType: 'DEFAULT'; - id: string; - timestamp: number; - color: string; - text: string; -} - -export interface AreaNote { - noteType: 'AREA'; - id: string; - areaId: string; - color: string; - text: string; -} - -export interface NoteSelection { - kind: 'NOTE'; - id: string; -} - -export interface SliceSelection { - kind: 'SLICE'; - id: number; -} - -export interface DebugSliceSelection { - kind: 'DEBUG_SLICE'; - id: number; - sqlTableName: string; - startS: number; - durationS: number; -} - -export interface CounterSelection { - kind: 'COUNTER'; - leftTs: number; - rightTs: number; - id: number; -} - -export interface HeapProfileSelection { - kind: 'HEAP_PROFILE'; - id: number; - upid: number; - ts: number; - type: ProfileType; -} - -export interface PerfSamplesSelection { - kind: 'PERF_SAMPLES'; - id: number; - upid: number; - leftTs: number; - rightTs: number; - type: ProfileType; -} - -export interface FlamegraphState { - kind: 'FLAMEGRAPH_STATE'; - upids: number[]; - startNs: number; - endNs: number; - type: ProfileType; - viewingOption: FlamegraphStateViewingOption; - focusRegex: string; - expandedCallsite?: CallsiteInfo; -} - -export interface CpuProfileSampleSelection { - kind: 'CPU_PROFILE_SAMPLE'; - id: number; - utid: number; - ts: number; -} - -export interface ChromeSliceSelection { - kind: 'CHROME_SLICE'; - id: number; - table: string; -} - -export interface ThreadStateSelection { - kind: 'THREAD_STATE'; - id: number; -} - -export interface LogSelection { - kind: 'LOG'; - id: number; - trackId: string; -} - -export type Selection = - (NoteSelection|SliceSelection|CounterSelection|HeapProfileSelection| - CpuProfileSampleSelection|ChromeSliceSelection|ThreadStateSelection| - AreaSelection|PerfSamplesSelection|LogSelection|DebugSliceSelection)& - {trackId?: string}; -export type SelectionKind = Selection['kind']; // 'THREAD_STATE' | 'SLICE' ... - -export interface Pagination { - offset: number; - count: number; -} - -export type StringListPatch = ['add' | 'remove', string]; - -export interface FtraceFilterPatch { - excludedNames: StringListPatch[]; -} - -export interface RecordingTarget { - name: string; - os: TargetOs; -} - -export interface AdbRecordingTarget extends RecordingTarget { - serial: string; -} - -export interface Sorting { - column: string; - direction: 'DESC'|'ASC'; -} - -export interface AggregationState { - id: string; - sorting?: Sorting; -} - -export interface MetricsState { - availableMetrics?: string[]; // Undefined until list is loaded. - selectedIndex?: number; - requestedMetric?: string; // Unset after metric request is handled. -} - -// Auxiliary metadata needed to parse the query result, as well as to render it -// correctly. Generated together with the text of query and passed without the -// change to the query response. -export interface PivotTableQueryMetadata { - pivotColumns: TableColumn[]; - aggregationColumns: Aggregation[]; - countIndex: number; -} - -// Everything that's necessary to run the query for pivot table -export interface PivotTableQuery { - text: string; - metadata: PivotTableQueryMetadata; -} - -// Pivot table query result -export interface PivotTableResult { - // Hierarchical pivot structure on top of rows - tree: PivotTree; - // Copy of the query metadata from the request, bundled up with the query - // result to ensure the correct rendering. - metadata: PivotTableQueryMetadata; -} - -// Input parameters to check whether the pivot table needs to be re-queried. -export interface PivotTableAreaState { - areaId: string; - tracks: string[]; -} - -export type SortDirection = keyof typeof Direction; - -export interface PivotTableState { - // Currently selected area, if null, pivot table is not going to be visible. - selectionArea?: PivotTableAreaState; - - // Query response - queryResult: PivotTableResult|null; - - // Selected pivots for tables other than slice. - // Because of the query generation, pivoting happens first on non-slice - // pivots; therefore, those can't be put after slice pivots. In order to - // maintain the separation more clearly, slice and non-slice pivots are - // located in separate arrays. - selectedPivots: TableColumn[]; - - // Selected aggregation columns. Stored same way as pivots. - selectedAggregations: Aggregation[]; - - // Whether the pivot table results should be constrained to the selected area. - constrainToArea: boolean; - - // Set to true by frontend to request controller to perform the query to - // acquire the necessary data from the engine. - queryRequested: boolean; - - // Argument names in the current trace, used for autocompletion purposes. - argumentNames: string[]; -} - -export interface LoadedConfigNone { - type: 'NONE'; -} - -export interface LoadedConfigAutomatic { - type: 'AUTOMATIC'; -} - -export interface LoadedConfigNamed { - type: 'NAMED'; - name: string; -} - -export type LoadedConfig = - LoadedConfigNone|LoadedConfigAutomatic|LoadedConfigNamed; - -export interface NonSerializableState { - pivotTable: PivotTableState; -} - -export interface LogFilteringCriteria { - minimumLevel: number; - tags: string[]; - textEntry: string; - hideNonMatching: boolean; -} - -export interface FtraceFilterState { - // We use an exclude list rather than include list for filtering events, as we - // want to include all events by default but we won't know what names are - // present initially. - excludedNames: string[]; -} - -export interface State { - version: number; - nextId: string; - - /** - * State of the ConfigEditor. - */ - recordConfig: RecordConfig; - displayConfigAsPbtxt: boolean; - lastLoadedConfig: LoadedConfig; - - /** - * Open traces. - */ - newEngineMode: NewEngineMode; - engine?: EngineConfig; - traceTime: TraceTime; - traceUuid?: string; - trackGroups: ObjectById; - tracks: ObjectById; - uiTrackIdByTraceTrackId: {[key: number]: string;}; - utidToThreadSortKey: UtidToTrackSortKey; - areas: ObjectById; - aggregatePreferences: ObjectById; - visibleTracks: string[]; - scrollingTracks: string[]; - pinnedTracks: string[]; - debugTrackId?: string; - lastTrackReloadRequest?: number; - queries: ObjectById; - metrics: MetricsState; - permalink: PermalinkConfig; - notes: ObjectById; - status: Status; - currentSelection: Selection|null; - currentFlamegraphState: FlamegraphState|null; - logsPagination: Pagination; - ftracePagination: Pagination; - ftraceFilter: FtraceFilterState; - traceConversionInProgress: boolean; - visualisedArgs: string[]; - - /** - * This state is updated on the frontend at 60Hz and eventually syncronised to - * the controller at 10Hz. When the controller sends state updates to the - * frontend the frontend has special logic to pick whichever version of this - * key is most up to date. - */ - frontendLocalState: FrontendLocalState; - - // Show track perf debugging overlay - perfDebug: boolean; - - // Show the sidebar extended - sidebarVisible: boolean; - - // Hovered and focused events - hoveredUtid: number; - hoveredPid: number; - hoverCursorTimestamp: number; - hoveredNoteTimestamp: number; - highlightedSliceId: number; - focusedFlowIdLeft: number; - focusedFlowIdRight: number; - pendingScrollId?: number; - - searchIndex: number; - currentTab?: string; - - /** - * Trace recording - */ - recordingInProgress: boolean; - recordingCancelled: boolean; - extensionInstalled: boolean; - flamegraphModalDismissed: boolean; - recordingTarget: RecordingTarget; - availableAdbDevices: AdbRecordingTarget[]; - lastRecordingError?: string; - recordingStatus?: string; - - fetchChromeCategories: boolean; - chromeCategories: string[]|undefined; - - // Special key: this part of the state is not going to be serialized when - // using permalink. Can be used to store those parts of the state that can't - // be serialized at the moment, such as ES6 Set and Map. - nonSerializableState: NonSerializableState; - - // Android logs filtering state. - logFilteringCriteria: LogFilteringCriteria; - - // Omnibox info. - omniboxState: OmniboxState; -} - -export const defaultTraceTime = { - startSec: 0, - endSec: 10, -}; - -export declare type RecordMode = - 'STOP_WHEN_FULL' | 'RING_BUFFER' | 'LONG_TRACE'; - -// 'Q','P','O' for Android, 'L' for Linux, 'C' for Chrome. -export declare type TargetOs = 'S' | 'R' | 'Q' | 'P' | 'O' | 'C' | 'L' | 'CrOS'; - -export function isAndroidP(target: RecordingTarget) { - return target.os === 'P'; -} - -export function isAndroidTarget(target: RecordingTarget) { - return ['Q', 'P', 'O'].includes(target.os); -} - -export function isChromeTarget(target: RecordingTarget) { - return ['C', 'CrOS'].includes(target.os); -} - -export function isCrOSTarget(target: RecordingTarget) { - return target.os === 'CrOS'; -} - -export function isLinuxTarget(target: RecordingTarget) { - return target.os === 'L'; -} - -export function isAdbTarget(target: RecordingTarget): - target is AdbRecordingTarget { - return !!(target as AdbRecordingTarget).serial; -} - -export function hasActiveProbes(config: RecordConfig) { - const fieldsWithEmptyResult = new Set( - ['hpBlockClient', 'allAtraceApps', 'chromePrivacyFiltering']); - let key: keyof RecordConfig; - for (key in config) { - if (typeof (config[key]) === 'boolean' && config[key] === true && - !fieldsWithEmptyResult.has(key)) { - return true; - } - } - if (config.chromeCategoriesSelected.length > 0) { - return true; - } - return config.chromeHighOverheadCategoriesSelected.length > 0; -} - -export function getDefaultRecordingTargets(): RecordingTarget[] { - return [ - {os: 'Q', name: 'Android Q+ / 10+'}, - {os: 'P', name: 'Android P / 9'}, - {os: 'O', name: 'Android O- / 8-'}, - {os: 'C', name: 'Chrome'}, - {os: 'CrOS', name: 'Chrome OS (system trace)'}, - {os: 'L', name: 'Linux desktop'}, - ]; -} - -export function getBuiltinChromeCategoryList(): string[] { - // List of static Chrome categories, last updated at 2023-04-04 from HEAD of - // Chromium's //base/trace_event/builtin_categories.h. - return [ - 'accessibility', - 'AccountFetcherService', - 'android_webview', - 'aogh', - 'audio', - 'base', - 'benchmark', - 'blink', - 'blink.animations', - 'blink.bindings', - 'blink.console', - 'blink.net', - 'blink.resource', - 'blink.user_timing', - 'blink.worker', - 'blink_style', - 'Blob', - 'browser', - 'browsing_data', - 'CacheStorage', - 'Calculators', - 'CameraStream', - 'cppgc', - 'camera', - 'cast_app', - 'cast_perf_test', - 'cast.mdns', - 'cast.mdns.socket', - 'cast.stream', - 'cc', - 'cc.debug', - 'cdp.perf', - 'chromeos', - 'cma', - 'compositor', - 'content', - 'content_capture', - 'delegated_ink_trails', - 'device', - 'devtools', - 'devtools.contrast', - 'devtools.timeline', - 'disk_cache', - 'download', - 'download_service', - 'drm', - 'drmcursor', - 'dwrite', - 'DXVA_Decoding', - 'evdev', - 'event', - 'event_latency', - 'exo', - 'extensions', - 'explore_sites', - 'FileSystem', - 'file_system_provider', - 'fledge', - 'fonts', - 'GAMEPAD', - 'gpu', - 'gpu.angle', - 'gpu.angle.texture_metrics', - 'gpu.capture', - 'headless', - 'history', - 'hwoverlays', - 'identity', - 'ime', - 'IndexedDB', - 'input', - 'io', - 'ipc', - 'Java', - 'jni', - 'jpeg', - 'latency', - 'latencyInfo', - 'leveldb', - 'loading', - 'log', - 'login', - 'media', - 'media_router', - 'memory', - 'midi', - 'mojom', - 'mus', - 'native', - 'navigation', - 'net', - 'netlog', - 'offline_pages', - 'omnibox', - 'oobe', - 'openscreen', - 'ozone', - 'partition_alloc', - 'passwords', - 'p2p', - 'page-serialization', - 'paint_preview', - 'pepper', - 'PlatformMalloc', - 'power', - 'ppapi', - 'ppapi_proxy', - 'print', - 'raf_investigation', - 'rail', - 'renderer', - 'renderer_host', - 'renderer.scheduler', - 'resources', - 'RLZ', - 'ServiceWorker', - 'SiteEngagement', - 'safe_browsing', - 'scheduler', - 'scheduler.long_tasks', - 'screenlock_monitor', - 'segmentation_platform', - 'sequence_manager', - 'service_manager', - 'sharing', - 'shell', - 'shortcut_viewer', - 'shutdown', - 'skia', - 'sql', - 'stadia_media', - 'stadia_rtc', - 'startup', - 'sync', - 'system_apps', - 'test_gpu', - 'toplevel', - 'toplevel.flow', - 'ui', - 'v8', - 'v8.execute', - 'v8.wasm', - 'ValueStoreFrontend::Backend', - 'views', - 'views.frame', - 'viz', - 'vk', - 'wakeup.flow', - 'wayland', - 'webaudio', - 'webengine.fidl', - 'weblayer', - 'WebCore', - 'webrtc', - 'webrtc_stats', - 'xr', - 'disabled-by-default-android_view_hierarchy', - 'disabled-by-default-animation-worklet', - 'disabled-by-default-audio', - 'disabled-by-default-audio-worklet', - 'disabled-by-default-audio.latency', - 'disabled-by-default-base', - 'disabled-by-default-blink.debug', - 'disabled-by-default-blink.debug.display_lock', - 'disabled-by-default-blink.debug.layout', - 'disabled-by-default-blink.debug.layout.scrollbars', - 'disabled-by-default-blink.debug.layout.trees', - 'disabled-by-default-blink.feature_usage', - 'disabled-by-default-blink.image_decoding', - 'disabled-by-default-blink.invalidation', - 'disabled-by-default-identifiability', - 'disabled-by-default-identifiability.high_entropy_api', - 'disabled-by-default-cc', - 'disabled-by-default-cc.debug', - 'disabled-by-default-cc.debug.cdp-perf', - 'disabled-by-default-cc.debug.display_items', - 'disabled-by-default-cc.debug.lcd_text', - 'disabled-by-default-cc.debug.picture', - 'disabled-by-default-cc.debug.scheduler', - 'disabled-by-default-cc.debug.scheduler.frames', - 'disabled-by-default-cc.debug.scheduler.now', - 'disabled-by-default-content.verbose', - 'disabled-by-default-cpu_profiler', - 'disabled-by-default-cppgc', - 'disabled-by-default-cpu_profiler.debug', - 'disabled-by-default-devtools.screenshot', - 'disabled-by-default-devtools.timeline', - 'disabled-by-default-devtools.timeline.frame', - 'disabled-by-default-devtools.timeline.inputs', - 'disabled-by-default-devtools.timeline.invalidationTracking', - 'disabled-by-default-devtools.timeline.layers', - 'disabled-by-default-devtools.timeline.picture', - 'disabled-by-default-devtools.timeline.stack', - 'disabled-by-default-file', - 'disabled-by-default-fonts', - 'disabled-by-default-gpu_cmd_queue', - 'disabled-by-default-gpu.dawn', - 'disabled-by-default-gpu.debug', - 'disabled-by-default-gpu.decoder', - 'disabled-by-default-gpu.device', - 'disabled-by-default-gpu.service', - 'disabled-by-default-gpu.vulkan.vma', - 'disabled-by-default-histogram_samples', - 'disabled-by-default-java-heap-profiler', - 'disabled-by-default-layer-element', - 'disabled-by-default-layout_shift.debug', - 'disabled-by-default-lifecycles', - 'disabled-by-default-loading', - 'disabled-by-default-mediastream', - 'disabled-by-default-memory-infra', - 'disabled-by-default-memory-infra.v8.code_stats', - 'disabled-by-default-mojom', - 'disabled-by-default-net', - 'disabled-by-default-network', - 'disabled-by-default-paint-worklet', - 'disabled-by-default-power', - 'disabled-by-default-renderer.scheduler', - 'disabled-by-default-renderer.scheduler.debug', - 'disabled-by-default-sequence_manager', - 'disabled-by-default-sequence_manager.debug', - 'disabled-by-default-sequence_manager.verbose_snapshots', - 'disabled-by-default-skia', - 'disabled-by-default-skia.gpu', - 'disabled-by-default-skia.gpu.cache', - 'disabled-by-default-skia.shaders', - 'disabled-by-default-SyncFileSystem', - 'disabled-by-default-system_stats', - 'disabled-by-default-thread_pool_diagnostics', - 'disabled-by-default-toplevel.ipc', - 'disabled-by-default-user_action_samples', - 'disabled-by-default-v8.compile', - 'disabled-by-default-v8.cpu_profiler', - 'disabled-by-default-v8.gc', - 'disabled-by-default-v8.gc_stats', - 'disabled-by-default-v8.ic_stats', - 'disabled-by-default-v8.inspector', - 'disabled-by-default-v8.runtime', - 'disabled-by-default-v8.runtime_stats', - 'disabled-by-default-v8.runtime_stats_sampling', - 'disabled-by-default-v8.stack_trace', - 'disabled-by-default-v8.turbofan', - 'disabled-by-default-v8.wasm.detailed', - 'disabled-by-default-v8.wasm.turbofan', - 'disabled-by-default-video_and_image_capture', - 'disabled-by-default-viz.gpu_composite_time', - 'disabled-by-default-viz.debug.overlay_planes', - 'disabled-by-default-viz.hit_testing_flow', - 'disabled-by-default-viz.overdraw', - 'disabled-by-default-viz.quads', - 'disabled-by-default-viz.surface_id_flow', - 'disabled-by-default-viz.surface_lifetime', - 'disabled-by-default-viz.triangles', - 'disabled-by-default-webaudio.audionode', - 'disabled-by-default-webgpu', - 'disabled-by-default-webrtc', - 'disabled-by-default-worker.scheduler', - 'disabled-by-default-xr.debug', - ]; -} - -export function getContainingTrackId(state: State, trackId: string): null| - string { - const track = state.tracks[trackId]; - if (!track) { - return null; - } - const parentId = track.trackGroup; - if (!parentId) { - return null; - } - return parentId; -} diff --git a/third_party/perfetto/ui/src/common/state_unittest.ts b/third_party/perfetto/ui/src/common/state_unittest.ts deleted file mode 100644 index 9b5a064fb175..000000000000 --- a/third_party/perfetto/ui/src/common/state_unittest.ts +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {createEmptyState} from './empty_state'; -import {getContainingTrackId, PrimaryTrackSortKey, State} from './state'; -import {deserializeStateObject, serializeStateObject} from './upload_utils'; - -test('createEmptyState', () => { - const state: State = createEmptyState(); - expect(state.engine).toEqual(undefined); -}); - -test('getContainingTrackId', () => { - const state: State = createEmptyState(); - state.tracks['a'] = { - id: 'a', - engineId: 'engine', - kind: 'Foo', - name: 'a track', - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - config: {}, - }; - - state.tracks['b'] = { - id: 'b', - engineId: 'engine', - kind: 'Foo', - name: 'b track', - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - config: {}, - trackGroup: 'containsB', - }; - - expect(getContainingTrackId(state, 'z')).toEqual(null); - expect(getContainingTrackId(state, 'a')).toEqual(null); - expect(getContainingTrackId(state, 'b')).toEqual('containsB'); -}); - -test('state is serializable', () => { - const state: State = createEmptyState(); - const json = serializeStateObject(state); - const restored: State = deserializeStateObject(json); - - // Remove nonSerializableState from original - const serializableState: any = state as any; - delete serializableState['nonSerializableState']; - - // Remove any undefined values from original as JSON doesn't serialize them - for (const key in serializableState) { - if (serializableState[key] === undefined) { - delete serializableState[key]; - } - } - - expect(serializableState).toEqual(restored); -}); diff --git a/third_party/perfetto/ui/src/common/thread_state.ts b/third_party/perfetto/ui/src/common/thread_state.ts deleted file mode 100644 index d38de4a499a3..000000000000 --- a/third_party/perfetto/ui/src/common/thread_state.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const states: {[key: string]: string} = { - 'R': 'Runnable', - 'S': 'Sleeping', - 'D': 'Uninterruptible Sleep', - 'T': 'Stopped', - 't': 'Traced', - 'X': 'Exit (Dead)', - 'Z': 'Exit (Zombie)', - 'x': 'Task Dead', - 'I': 'Idle', - 'K': 'Wake Kill', - 'W': 'Waking', - 'P': 'Parked', - 'N': 'No Load', - '+': '(Preempted)', -}; - -export function translateState( - state: string|undefined|null, ioWait: boolean|undefined = undefined) { - if (state === undefined) return ''; - if (state === 'Running') { - return state; - } - if (state === null) { - return 'Unknown'; - } - let result = states[state[0]]; - if (ioWait === true) { - result += ' (IO)'; - } else if (ioWait === false) { - result += ' (non-IO)'; - } - for (let i = 1; i < state.length; i++) { - result += state[i] === '+' ? ' ' : ' + '; - result += states[state[i]]; - } - // state is some string we don't know how to translate. - if (result === undefined) return state; - - return result; -} diff --git a/third_party/perfetto/ui/src/common/time.ts b/third_party/perfetto/ui/src/common/time.ts deleted file mode 100644 index ea5e9d8a60af..000000000000 --- a/third_party/perfetto/ui/src/common/time.ts +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertTrue} from '../base/logging'; - -const EPSILON = 0.0000000001; - -// TODO(hjd): Combine with timeToCode. -export function timeToString(sec: number) { - const units = ['s', 'ms', 'us', 'ns']; - const sign = Math.sign(sec); - let n = Math.abs(sec); - let u = 0; - while (n < 1 && n !== 0 && u < units.length - 1) { - n *= 1000; - u++; - } - return `${sign < 0 ? '-' : ''}${Math.round(n * 10) / 10} ${units[u]}`; -} - -export function fromNs(ns: number) { - return ns / 1e9; -} - -export function toNsFloor(seconds: number) { - return Math.floor(seconds * 1e9); -} - -export function toNsCeil(seconds: number) { - return Math.ceil(seconds * 1e9); -} - -export function toNs(seconds: number) { - return Math.round(seconds * 1e9); -} - -// 1000000023ns -> "1.000 000 023" -export function formatTimestamp(sec: number) { - const parts = sec.toFixed(9).split('.'); - parts[1] = parts[1].replace(/\B(?=(\d{3})+(?!\d))/g, ' '); - return parts.join('.'); -} - -// TODO(hjd): Rename to formatTimestampWithUnits -// 1000000023ns -> "1s 23ns" -export function timeToCode(sec: number): string { - let result = ''; - let ns = Math.round(sec * 1e9); - if (ns < 1) return '0s'; - const unitAndValue = [ - ['m', 60000000000], - ['s', 1000000000], - ['ms', 1000000], - ['us', 1000], - ['ns', 1], - ]; - unitAndValue.forEach((pair) => { - const unit = pair[0] as string; - const val = pair[1] as number; - if (ns >= val) { - const i = Math.floor(ns / val); - ns -= i * val; - result += i.toLocaleString() + unit + ' '; - } - }); - return result.slice(0, -1); -} - -export function currentDateHourAndMinute(): string { - const date = new Date(); - return `${date.toISOString().substr(0, 10)}-${date.getHours()}-${ - date.getMinutes()}`; -} - -export class TimeSpan { - readonly start: number; - readonly end: number; - - constructor(start: number, end: number) { - assertTrue(start <= end); - this.start = start; - this.end = end; - } - - clone() { - return new TimeSpan(this.start, this.end); - } - - equals(other: TimeSpan): boolean { - return Math.abs(this.start - other.start) < EPSILON && - Math.abs(this.end - other.end) < EPSILON; - } - - get duration() { - return this.end - this.start; - } - - isInBounds(sec: number) { - return this.start <= sec && sec <= this.end; - } - - add(sec: number): TimeSpan { - return new TimeSpan(this.start + sec, this.end + sec); - } - - contains(other: TimeSpan) { - return this.start <= other.start && other.end <= this.end; - } -} diff --git a/third_party/perfetto/ui/src/common/time_unittest.ts b/third_party/perfetto/ui/src/common/time_unittest.ts deleted file mode 100644 index 7bfead11e5bc..000000000000 --- a/third_party/perfetto/ui/src/common/time_unittest.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {TimeSpan, timeToCode} from './time'; - -test('seconds to code', () => { - expect(timeToCode(3)).toEqual('3s'); - expect(timeToCode(60)).toEqual('1m'); - expect(timeToCode(63)).toEqual('1m 3s'); - expect(timeToCode(63.2)).toEqual('1m 3s 200ms'); - expect(timeToCode(63.2221)).toEqual('1m 3s 222ms 100us'); - expect(timeToCode(63.2221111)).toEqual('1m 3s 222ms 111us 100ns'); - expect(timeToCode(0.2221111)).toEqual('222ms 111us 100ns'); - expect(timeToCode(0.000001)).toEqual('1us'); - expect(timeToCode(0.000003)).toEqual('3us'); - expect(timeToCode(1.000001)).toEqual('1s 1us'); - expect(timeToCode(200.00000003)).toEqual('3m 20s 30ns'); - expect(timeToCode(0)).toEqual('0s'); -}); - -test('Time span equality', () => { - expect((new TimeSpan(0, 1)).equals(new TimeSpan(0, 1))).toBe(true); - expect((new TimeSpan(0, 1)).equals(new TimeSpan(0, 2))).toBe(false); - expect((new TimeSpan(0, 1)).equals(new TimeSpan(0, 1 + Number.EPSILON))) - .toBe(true); -}); diff --git a/third_party/perfetto/ui/src/common/track_data.ts b/third_party/perfetto/ui/src/common/track_data.ts deleted file mode 100644 index 9af7fff5ed1c..000000000000 --- a/third_party/perfetto/ui/src/common/track_data.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// TODO(hjd): Refactor into method on TrackController -export const LIMIT = 10000; - -export interface TrackData { - start: number; - end: number; - resolution: number; - length: number; -} diff --git a/third_party/perfetto/ui/src/common/upload_utils.ts b/third_party/perfetto/ui/src/common/upload_utils.ts deleted file mode 100644 index 0c979217822e..000000000000 --- a/third_party/perfetto/ui/src/common/upload_utils.ts +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {RecordConfig} from '../controller/record_config_types'; - -export const BUCKET_NAME = 'perfetto-ui-data'; -import {v4 as uuidv4} from 'uuid'; -import {State} from './state'; - -export async function saveTrace(trace: File|ArrayBuffer): Promise { - // TODO(hjd): This should probably also be a hash but that requires - // trace processor support. - const name = uuidv4(); - const url = 'https://www.googleapis.com/upload/storage/v1/b/' + - `${BUCKET_NAME}/o?uploadType=media` + - `&name=${name}&predefinedAcl=publicRead`; - const response = await fetch(url, { - method: 'post', - headers: {'Content-Type': 'application/octet-stream;'}, - body: trace, - }); - await response.json(); - return `https://storage.googleapis.com/${BUCKET_NAME}/${name}`; -} - -// Bigint's are not serializable using JSON.stringify, so we use a special -// object when serialising -export type SerializedBigint = { - __kind: 'bigint', - value: string -}; - -// Check if a value looks like a serialized bigint -export function isSerializedBigint(value: unknown): value is SerializedBigint { - if (value === null) { - return false; - } - if (typeof value !== 'object') { - return false; - } - if ('__kind' in value && 'value' in value) { - return value.__kind === 'bigint' && typeof value.value === 'string'; - } - return false; -} - -export function serializeStateObject(object: unknown): string { - const json = JSON.stringify(object, (key, value) => { - if (typeof value === 'bigint') { - return { - __kind: 'bigint', - value: value.toString(), - }; - } - return key === 'nonSerializableState' ? undefined : value; - }); - return json; -} - -export function deserializeStateObject(json: string): any { - const object = JSON.parse(json, (_key, value) => { - if (isSerializedBigint(value)) { - return BigInt(value.value); - } - return value; - }); - return object; -} - -export async function saveState(stateOrConfig: State| - RecordConfig): Promise { - const text = serializeStateObject(stateOrConfig); - const hash = await toSha256(text); - const url = 'https://www.googleapis.com/upload/storage/v1/b/' + - `${BUCKET_NAME}/o?uploadType=media` + - `&name=${hash}&predefinedAcl=publicRead`; - const response = await fetch(url, { - method: 'post', - headers: { - 'Content-Type': 'application/json; charset=utf-8', - }, - body: text, - }); - await response.json(); - return hash; -} - -// This has a bug: -// x.toString(16) doesn't zero pad so if the digest is: -// [23, 7, 42, ...] -// You get: -// ['17', '7', '2a', ...] = 1772a... -// Rather than: -// ['17', '07', '2a', ...] = 17072a... -// As you ought to (and as the hexdigest is computed by e.g. Python). -// Unfortunately there are a lot of old permalinks out there so we -// still need this broken implementation to check their hashes. -export async function buggyToSha256(str: string): Promise { - const buffer = new TextEncoder().encode(str); - const digest = await crypto.subtle.digest('SHA-256', buffer); - return Array.from(new Uint8Array(digest)).map((x) => x.toString(16)).join(''); -} - -export async function toSha256(str: string): Promise { - const buffer = new TextEncoder().encode(str); - const digest = await crypto.subtle.digest('SHA-256', buffer); - return Array.from(new Uint8Array(digest)) - .map((x) => x.toString(16).padStart(2, '0')) - .join(''); -} diff --git a/third_party/perfetto/ui/src/common/upload_utils_unittest.ts b/third_party/perfetto/ui/src/common/upload_utils_unittest.ts deleted file mode 100644 index 4c0b8ce8dc63..000000000000 --- a/third_party/perfetto/ui/src/common/upload_utils_unittest.ts +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - deserializeStateObject, - isSerializedBigint, - serializeStateObject, -} from './upload_utils'; - -describe('isSerializedBigint', () => { - it('should return true for a valid serialized bigint', () => { - const value = { - __kind: 'bigint', - value: '1234567890', - }; - expect(isSerializedBigint(value)).toBeTruthy(); - }); - - it('should return false for a null value', () => { - expect(isSerializedBigint(null)).toBeFalsy(); - }); - - it('should return false for a non-object value', () => { - expect(isSerializedBigint(123)).toBeFalsy(); - }); - - it('should return false for a non-serialized bigint value', () => { - const value = { - __kind: 'not-bigint', - value: '1234567890', - }; - expect(isSerializedBigint(value)).toBeFalsy(); - }); -}); - -describe('serializeStateObject', () => { - it('should serialize a simple object', () => { - const object = { - a: 1, - b: 2, - c: 3, - }; - const expectedJson = `{"a":1,"b":2,"c":3}`; - expect(serializeStateObject(object)).toEqual(expectedJson); - }); - - it('should serialize a bigint', () => { - const object = { - a: 123456789123456789n, - }; - const expectedJson = - `{"a":{"__kind":"bigint","value":"123456789123456789"}}`; - expect(serializeStateObject(object)).toEqual(expectedJson); - }); - - it('should not serialize a non-serializable property', () => { - const object = { - a: 1, - b: 2, - c: 3, - nonSerializableState: 4, - }; - const expectedJson = `{"a":1,"b":2,"c":3}`; - expect(serializeStateObject(object)).toEqual(expectedJson); - }); -}); - -describe('deserializeStateObject', () => { - it('should deserialize a simple object', () => { - const json = `{"a":1,"b":2,"c":3}`; - const expectedObject = { - a: 1, - b: 2, - c: 3, - }; - expect(deserializeStateObject(json)).toEqual(expectedObject); - }); - - it('should deserialize a bigint', () => { - const json = `{"a":{"__kind":"bigint","value":"123456789123456789"}}`; - const expectedObject = { - a: 123456789123456789n, - }; - expect(deserializeStateObject(json)).toEqual(expectedObject); - }); - - it('should deserialize a null', () => { - const json = `{"a":null}`; - const expectedObject = { - a: null, - }; - expect(deserializeStateObject(json)).toEqual(expectedObject); - }); -}); diff --git a/third_party/perfetto/ui/src/common/wasm_engine_proxy.ts b/third_party/perfetto/ui/src/common/wasm_engine_proxy.ts deleted file mode 100644 index 92c7157d0d6e..000000000000 --- a/third_party/perfetto/ui/src/common/wasm_engine_proxy.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertExists, assertTrue} from '../base/logging'; - -import {Engine, LoadingTracker} from './engine'; -import {EngineWorkerInitMessage} from './worker_messages'; - -let bundlePath: string; -let idleWasmWorker: Worker; -let activeWasmWorker: Worker; - -export function initWasm(root: string) { - bundlePath = root + 'engine_bundle.js'; - idleWasmWorker = new Worker(bundlePath); -} - -// This method is called trace_controller whenever a new trace is loaded. -export function resetEngineWorker(): MessagePort { - const channel = new MessageChannel(); - const port = channel.port1; - - // We keep always an idle worker around, the first one is created by the - // main() below, so we can hide the latency of the Wasm initialization. - if (activeWasmWorker !== undefined) { - activeWasmWorker.terminate(); - } - - // Swap the active worker with the idle one and create a new idle worker - // for the next trace. - activeWasmWorker = assertExists(idleWasmWorker); - const msg: EngineWorkerInitMessage = {enginePort: port}; - activeWasmWorker.postMessage(msg, [port]); - idleWasmWorker = new Worker(bundlePath); - return channel.port2; -} - -/** - * This implementation of Engine uses a WASM backend hosted in a separate - * worker thread. - */ -export class WasmEngineProxy extends Engine { - readonly id: string; - private port: MessagePort; - - constructor(id: string, port: MessagePort, loadingTracker?: LoadingTracker) { - super(loadingTracker); - this.id = id; - this.port = port; - this.port.onmessage = this.onMessage.bind(this); - } - - onMessage(m: MessageEvent) { - assertTrue(m.data instanceof Uint8Array); - super.onRpcResponseBytes(m.data as Uint8Array); - } - - rpcSendRequestBytes(data: Uint8Array): void { - // We deliberately don't use a transfer list because protobufjs reuses the - // same buffer when encoding messages (which is good, because creating a new - // TypedArray for each decode operation would be too expensive). - this.port.postMessage(data); - } -} diff --git a/third_party/perfetto/ui/src/common/worker_messages.ts b/third_party/perfetto/ui/src/common/worker_messages.ts deleted file mode 100644 index 166b1cce1382..000000000000 --- a/third_party/perfetto/ui/src/common/worker_messages.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This file defines the API of messages exchanged between frontend and -// {engine, controller} worker when bootstrapping the workers. -// Those messages are sent only once. The rest of the communication happens -// over the MessagePort(s) that are sent in the init message. - -// This is so we can create all the workers in a central place in the frontend -// (Safari still doesn't spawning workers from other workers) but then let them -// communicate by sending the right MessagePort to them. - -// Frontend -> Engine initialization message. -export interface EngineWorkerInitMessage { - // The port used to receive engine messages (e.g., query commands). - // The controller owns the other end of the MessageChannel - // (see resetEngineWorker()). - enginePort: MessagePort; -} diff --git a/third_party/perfetto/ui/src/controller/adb.ts b/third_party/perfetto/ui/src/controller/adb.ts deleted file mode 100644 index 7b22e7f0f223..000000000000 --- a/third_party/perfetto/ui/src/controller/adb.ts +++ /dev/null @@ -1,660 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {_TextDecoder, _TextEncoder} from 'custom_utils'; - -import {assertExists} from '../base/logging'; - -import {Adb, AdbMsg, AdbStream, CmdType} from './adb_interfaces'; - -const textEncoder = new _TextEncoder(); -const textDecoder = new _TextDecoder(); - -export const VERSION_WITH_CHECKSUM = 0x01000000; -export const VERSION_NO_CHECKSUM = 0x01000001; -export const DEFAULT_MAX_PAYLOAD_BYTES = 256 * 1024; - -export enum AdbState { - DISCONNECTED = 0, - // Authentication steps, see AdbOverWebUsb's handleAuthentication(). - AUTH_STEP1 = 1, - AUTH_STEP2 = 2, - AUTH_STEP3 = 3, - - CONNECTED = 2, -} - -enum AuthCmd { - TOKEN = 1, - SIGNATURE = 2, - RSAPUBLICKEY = 3, -} - -const DEVICE_NOT_SET_ERROR = 'Device not set.'; - -// This class is a basic TypeScript implementation of adb that only supports -// shell commands. It is used to send the start tracing command to the connected -// android device, and to automatically pull the trace after the end of the -// recording. It works through the webUSB API. A brief description of how it -// works is the following: -// - The connection with the device is initiated by findAndConnect, which shows -// a dialog with a list of connected devices. Once one is selected the -// authentication begins. The authentication has to pass different steps, as -// described in the "handeAuthentication" method. -// - AdbOverWebUsb tracks the state of the authentication via a state machine -// (see AdbState). -// - A Message handler loop is executed to keep receiving the messages. -// - All the messages received from the device are passed to "onMessage" that is -// implemented as a state machine. -// - When a new shell is established, it becomes an AdbStream, and is kept in -// the "streams" map. Each time a message from the device is for a specific -// previously opened stream, the "onMessage" function will forward it to the -// stream (identified by a number). -export class AdbOverWebUsb implements Adb { - state: AdbState = AdbState.DISCONNECTED; - streams = new Map(); - devProps = ''; - maxPayload = DEFAULT_MAX_PAYLOAD_BYTES; - key?: CryptoKeyPair; - onConnected = () => {}; - - // Devices after Dec 2017 don't use checksum. This will be auto-detected - // during the connection. - useChecksum = true; - - private lastStreamId = 0; - private dev?: USBDevice; - private usbInterfaceNumber?: number; - private usbReadEndpoint = -1; - private usbWriteEpEndpoint = -1; - private filter = { - classCode: 255, // USB vendor specific code - subclassCode: 66, // Android vendor specific subclass - protocolCode: 1, // Adb protocol - }; - - async findDevice() { - if (!('usb' in navigator)) { - throw new Error('WebUSB not supported by the browser (requires HTTPS)'); - } - return navigator.usb.requestDevice({filters: [this.filter]}); - } - - async getPairedDevices() { - try { - return navigator.usb.getDevices(); - } catch (e) { // WebUSB not available. - return Promise.resolve([]); - } - } - - async connect(device: USBDevice): Promise { - // If we are already connected, we are also already authenticated, so we can - // skip doing the authentication again. - if (this.state === AdbState.CONNECTED) { - if (this.dev === device && device.opened) { - this.onConnected(); - this.onConnected = () => {}; - return; - } - // Another device was connected. - await this.disconnect(); - } - - this.dev = device; - this.useChecksum = true; - this.key = await AdbOverWebUsb.initKey(); - - await this.dev.open(); - - const {configValue, usbInterfaceNumber, endpoints} = - this.findInterfaceAndEndpoint(); - this.usbInterfaceNumber = usbInterfaceNumber; - - this.usbReadEndpoint = this.findEndpointNumber(endpoints, 'in'); - this.usbWriteEpEndpoint = this.findEndpointNumber(endpoints, 'out'); - - console.assert(this.usbReadEndpoint >= 0 && this.usbWriteEpEndpoint >= 0); - - await this.dev.selectConfiguration(configValue); - await this.dev.claimInterface(usbInterfaceNumber); - - await this.startAuthentication(); - - // This will start a message handler loop. - this.receiveDeviceMessages(); - // The promise will be resolved after the handshake. - return new Promise((resolve, _) => this.onConnected = resolve); - } - - async disconnect(): Promise { - if (this.state === AdbState.DISCONNECTED) { - return; - } - this.state = AdbState.DISCONNECTED; - - if (!this.dev) return; - - new Map(this.streams).forEach((stream, _id) => stream.setClosed()); - console.assert(this.streams.size === 0); - - await this.dev.releaseInterface(assertExists(this.usbInterfaceNumber)); - this.dev = undefined; - this.usbInterfaceNumber = undefined; - } - - async startAuthentication() { - // USB connected, now let's authenticate. - const VERSION = - this.useChecksum ? VERSION_WITH_CHECKSUM : VERSION_NO_CHECKSUM; - this.state = AdbState.AUTH_STEP1; - await this.send('CNXN', VERSION, this.maxPayload, 'host:1:UsbADB'); - } - - findInterfaceAndEndpoint() { - if (!this.dev) throw Error(DEVICE_NOT_SET_ERROR); - for (const config of this.dev.configurations) { - for (const interface_ of config.interfaces) { - for (const alt of interface_.alternates) { - if (alt.interfaceClass === this.filter.classCode && - alt.interfaceSubclass === this.filter.subclassCode && - alt.interfaceProtocol === this.filter.protocolCode) { - return { - configValue: config.configurationValue, - usbInterfaceNumber: interface_.interfaceNumber, - endpoints: alt.endpoints, - }; - } // if (alternate) - } // for (interface.alternates) - } // for (configuration.interfaces) - } // for (configurations) - - throw Error('Cannot find interfaces and endpoints'); - } - - findEndpointNumber( - endpoints: USBEndpoint[], direction: 'out'|'in', type = 'bulk'): number { - const ep = - endpoints.find((ep) => ep.type === type && ep.direction === direction); - - if (ep) return ep.endpointNumber; - - throw Error(`Cannot find ${direction} endpoint`); - } - - receiveDeviceMessages() { - this.recv() - .then((msg) => { - this.onMessage(msg); - this.receiveDeviceMessages(); - }) - .catch((e) => { - // Ignore error with "DEVICE_NOT_SET_ERROR" message since it is always - // thrown after the device disconnects. - if (e.message !== DEVICE_NOT_SET_ERROR) { - console.error(`Exception in recv: ${e.name}. error: ${e.message}`); - } - this.disconnect(); - }); - } - - async onMessage(msg: AdbMsg) { - if (!this.key) throw Error('ADB key not initialized'); - - if (msg.cmd === 'AUTH' && msg.arg0 === AuthCmd.TOKEN) { - this.handleAuthentication(msg); - } else if (msg.cmd === 'CNXN') { - console.assert( - [AdbState.AUTH_STEP2, AdbState.AUTH_STEP3].includes(this.state)); - this.state = AdbState.CONNECTED; - this.handleConnectedMessage(msg); - } else if (this.state === AdbState.CONNECTED && [ - 'OKAY', - 'WRTE', - 'CLSE', - ].indexOf(msg.cmd) >= 0) { - const stream = this.streams.get(msg.arg1); - if (!stream) { - console.warn(`Received message ${msg} for unknown stream ${msg.arg1}`); - return; - } - stream.onMessage(msg); - } else { - console.error(`Unexpected message `, msg, ` in state ${this.state}`); - } - } - - async handleAuthentication(msg: AdbMsg) { - if (!this.key) throw Error('ADB key not initialized'); - - console.assert(msg.cmd === 'AUTH' && msg.arg0 === AuthCmd.TOKEN); - const token = msg.data; - - if (this.state === AdbState.AUTH_STEP1) { - // During this step, we send back the token received signed with our - // private key. If the device has previously received our public key, the - // dialog will not be displayed. Otherwise we will receive another message - // ending up in AUTH_STEP3. - this.state = AdbState.AUTH_STEP2; - - const signedToken = - await signAdbTokenWithPrivateKey(this.key.privateKey, token); - this.send('AUTH', AuthCmd.SIGNATURE, 0, new Uint8Array(signedToken)); - return; - } - - console.assert(this.state === AdbState.AUTH_STEP2); - - // During this step, we send our public key. The dialog will appear, and - // if the user chooses to remember our public key, it will be - // saved, so that the next time we will only pass through AUTH_STEP1. - this.state = AdbState.AUTH_STEP3; - const encodedPubKey = await encodePubKey(this.key.publicKey); - this.send('AUTH', AuthCmd.RSAPUBLICKEY, 0, encodedPubKey); - } - - private handleConnectedMessage(msg: AdbMsg) { - console.assert(msg.cmd === 'CNXN'); - - this.maxPayload = msg.arg1; - this.devProps = textDecoder.decode(msg.data); - - const deviceVersion = msg.arg0; - - if (![VERSION_WITH_CHECKSUM, VERSION_NO_CHECKSUM].includes(deviceVersion)) { - console.error('Version ', msg.arg0, ' not really supported!'); - } - this.useChecksum = deviceVersion === VERSION_WITH_CHECKSUM; - this.state = AdbState.CONNECTED; - - // This will resolve the promise returned by "onConnect" - this.onConnected(); - this.onConnected = () => {}; - } - - shell(cmd: string): Promise { - return this.openStream('shell:' + cmd); - } - - socket(path: string): Promise { - return this.openStream('localfilesystem:' + path); - } - - openStream(svc: string): Promise { - const stream = new AdbStreamImpl(this, ++this.lastStreamId); - this.streams.set(stream.localStreamId, stream); - this.send('OPEN', stream.localStreamId, 0, svc); - - // The stream will resolve this promise once it receives the - // acknowledgement message from the device. - return new Promise((resolve, reject) => { - stream.onConnect = () => { - stream.onClose = () => {}; - resolve(stream); - }; - stream.onClose = () => - reject(new Error(`Failed to openStream svc=${svc}`)); - }); - } - - async shellOutputAsString(cmd: string): Promise { - const shell = await this.shell(cmd); - - return new Promise((resolve, _) => { - const output: string[] = []; - shell.onData = (raw) => output.push(textDecoder.decode(raw)); - shell.onClose = () => resolve(output.join()); - }); - } - - async send( - cmd: CmdType, arg0: number, arg1: number, data?: Uint8Array|string) { - await this.sendMsg(AdbMsgImpl.create( - {cmd, arg0, arg1, data, useChecksum: this.useChecksum})); - } - - // The header and the message data must be sent consecutively. Using 2 awaits - // Another message can interleave after the first header has been sent, - // resulting in something like [header1] [header2] [data1] [data2]; - // In this way we are waiting both promises to be resolved before continuing. - async sendMsg(msg: AdbMsgImpl) { - const sendPromises = [this.sendRaw(msg.encodeHeader())]; - if (msg.data.length > 0) sendPromises.push(this.sendRaw(msg.data)); - await Promise.all(sendPromises); - } - - async recv(): Promise { - const res = await this.recvRaw(ADB_MSG_SIZE); - console.assert(res.status === 'ok'); - const msg = AdbMsgImpl.decodeHeader(res.data!); - - if (msg.dataLen > 0) { - const resp = await this.recvRaw(msg.dataLen); - msg.data = new Uint8Array( - resp.data!.buffer, resp.data!.byteOffset, resp.data!.byteLength); - } - if (this.useChecksum) { - console.assert(AdbOverWebUsb.checksum(msg.data) === msg.dataChecksum); - } - return msg; - } - - static async initKey(): Promise { - const KEY_SIZE = 2048; - - const keySpec = { - name: 'RSASSA-PKCS1-v1_5', - modulusLength: KEY_SIZE, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: {name: 'SHA-1'}, - }; - - const key = await crypto.subtle.generateKey( - keySpec, /* extractable=*/ true, ['sign', 'verify']); - return key; - } - - static checksum(data: Uint8Array): number { - let res = 0; - for (let i = 0; i < data.byteLength; i++) res += data[i]; - return res & 0xFFFFFFFF; - } - - sendRaw(buf: Uint8Array): Promise { - console.assert(buf.length <= this.maxPayload); - if (!this.dev) throw Error(DEVICE_NOT_SET_ERROR); - return this.dev.transferOut(this.usbWriteEpEndpoint, buf.buffer); - } - - recvRaw(dataLen: number): Promise { - if (!this.dev) throw Error(DEVICE_NOT_SET_ERROR); - return this.dev.transferIn(this.usbReadEndpoint, dataLen); - } -} - -enum AdbStreamState { - WAITING_INITIAL_OKAY = 0, - CONNECTED = 1, - CLOSED = 2 -} - - -// An AdbStream is instantiated after the creation of a shell to the device. -// Thanks to this, we can send commands and receive their output. Messages are -// received in the main adb class, and are forwarded to an instance of this -// class based on a stream id match. Also streams have an initialization flow: -// 1. WAITING_INITIAL_OKAY: waiting for first "OKAY" message. Once received, -// the next state will be "CONNECTED". -// 2. CONNECTED: ready to receive or send messages. -// 3. WRITING: this is needed because we must receive an ack after sending -// each message (so, before sending the next one). For this reason, many -// subsequent "write" calls will result in different messages in the -// writeQueue. After each new acknowledgement ('OKAY') a new one will be -// sent. When the queue is empty, the state will return to CONNECTED. -// 4. CLOSED: entered when the device closes the stream or close() is called. -// For shell commands, the stream is closed after the command completed. -export class AdbStreamImpl implements AdbStream { - private adb: AdbOverWebUsb; - localStreamId: number; - private remoteStreamId = -1; - private state: AdbStreamState = AdbStreamState.WAITING_INITIAL_OKAY; - private writeQueue: Uint8Array[] = []; - - private sendInProgress = false; - - onData: AdbStreamReadCallback = (_) => {}; - onConnect = () => {}; - onClose = () => {}; - - constructor(adb: AdbOverWebUsb, localStreamId: number) { - this.adb = adb; - this.localStreamId = localStreamId; - } - - close() { - console.assert(this.state === AdbStreamState.CONNECTED); - - if (this.writeQueue.length > 0) { - console.error(`Dropping ${ - this.writeQueue.length} queued messages due to stream closing.`); - this.writeQueue = []; - } - - this.adb.send('CLSE', this.localStreamId, this.remoteStreamId); - } - - async write(msg: string|Uint8Array) { - const raw = (typeof msg === 'string') ? textEncoder.encode(msg) : msg; - if (this.sendInProgress || - this.state === AdbStreamState.WAITING_INITIAL_OKAY) { - this.writeQueue.push(raw); - return; - } - console.assert(this.state === AdbStreamState.CONNECTED); - this.sendInProgress = true; - await this.adb.send('WRTE', this.localStreamId, this.remoteStreamId, raw); - } - - setClosed() { - this.state = AdbStreamState.CLOSED; - this.adb.streams.delete(this.localStreamId); - this.onClose(); - } - - onMessage(msg: AdbMsgImpl) { - console.assert(msg.arg1 === this.localStreamId); - - if (this.state === AdbStreamState.WAITING_INITIAL_OKAY && - msg.cmd === 'OKAY') { - this.remoteStreamId = msg.arg0; - this.state = AdbStreamState.CONNECTED; - this.onConnect(); - return; - } - - if (msg.cmd === 'WRTE') { - this.adb.send('OKAY', this.localStreamId, this.remoteStreamId); - this.onData(msg.data); - return; - } - - if (msg.cmd === 'OKAY') { - console.assert(this.sendInProgress); - this.sendInProgress = false; - const queuedMsg = this.writeQueue.shift(); - if (queuedMsg !== undefined) this.write(queuedMsg); - return; - } - - if (msg.cmd === 'CLSE') { - this.setClosed(); - return; - } - console.error( - `Unexpected stream msg ${msg.toString()} in state ${this.state}`); - } -} - -interface AdbStreamReadCallback { - (raw: Uint8Array): void; -} - -const ADB_MSG_SIZE = 6 * 4; // 6 * int32. - -export class AdbMsgImpl implements AdbMsg { - cmd: CmdType; - arg0: number; - arg1: number; - data: Uint8Array; - dataLen: number; - dataChecksum: number; - - useChecksum: boolean; - - constructor( - cmd: CmdType, arg0: number, arg1: number, dataLen: number, - dataChecksum: number, useChecksum = false) { - console.assert(cmd.length === 4); - this.cmd = cmd; - this.arg0 = arg0; - this.arg1 = arg1; - this.dataLen = dataLen; - this.data = new Uint8Array(dataLen); - this.dataChecksum = dataChecksum; - this.useChecksum = useChecksum; - } - - - static create({cmd, arg0, arg1, data, useChecksum = true}: { - cmd: CmdType; arg0: number; arg1: number; - data?: Uint8Array | string; - useChecksum?: boolean; - }): AdbMsgImpl { - const encodedData = this.encodeData(data); - const msg = - new AdbMsgImpl(cmd, arg0, arg1, encodedData.length, 0, useChecksum); - msg.data = encodedData; - return msg; - } - - get dataStr() { - return textDecoder.decode(this.data); - } - - toString() { - return `${this.cmd} [${this.arg0},${this.arg1}] ${this.dataStr}`; - } - - // A brief description of the message can be found here: - // https://android.googlesource.com/platform/system/core/+/master/adb/protocol.txt - // - // struct amessage { - // uint32_t command; // command identifier constant - // uint32_t arg0; // first argument - // uint32_t arg1; // second argument - // uint32_t data_length;// length of payload (0 is allowed) - // uint32_t data_check; // checksum of data payload - // uint32_t magic; // command ^ 0xffffffff - // }; - static decodeHeader(dv: DataView): AdbMsgImpl { - console.assert(dv.byteLength === ADB_MSG_SIZE); - const cmd = textDecoder.decode(dv.buffer.slice(0, 4)) as CmdType; - const cmdNum = dv.getUint32(0, true); - const arg0 = dv.getUint32(4, true); - const arg1 = dv.getUint32(8, true); - const dataLen = dv.getUint32(12, true); - const dataChecksum = dv.getUint32(16, true); - const cmdChecksum = dv.getUint32(20, true); - console.assert(cmdNum === (cmdChecksum ^ 0xFFFFFFFF)); - return new AdbMsgImpl(cmd, arg0, arg1, dataLen, dataChecksum); - } - - encodeHeader(): Uint8Array { - const buf = new Uint8Array(ADB_MSG_SIZE); - const dv = new DataView(buf.buffer); - const cmdBytes: Uint8Array = textEncoder.encode(this.cmd); - const rawMsg = AdbMsgImpl.encodeData(this.data); - const checksum = this.useChecksum ? AdbOverWebUsb.checksum(rawMsg) : 0; - for (let i = 0; i < 4; i++) dv.setUint8(i, cmdBytes[i]); - - dv.setUint32(4, this.arg0, true); - dv.setUint32(8, this.arg1, true); - dv.setUint32(12, rawMsg.byteLength, true); - dv.setUint32(16, checksum, true); - dv.setUint32(20, dv.getUint32(0, true) ^ 0xFFFFFFFF, true); - - return buf; - } - - static encodeData(data?: Uint8Array|string): Uint8Array { - if (data === undefined) return new Uint8Array([]); - if (typeof data === 'string') return textEncoder.encode(data + '\0'); - return data; - } -} - - -function base64StringToArray(s: string) { - const decoded = atob(s.replace(/-/g, '+').replace(/_/g, '/')); - return [...decoded].map((char) => char.charCodeAt(0)); -} - -const ANDROID_PUBKEY_MODULUS_SIZE = 2048; -const MODULUS_SIZE_BYTES = ANDROID_PUBKEY_MODULUS_SIZE / 8; - -// RSA Public keys are encoded in a rather unique way. It's a base64 encoded -// struct of 524 bytes in total as follows (see -// libcrypto_utils/android_pubkey.c): -// -// typedef struct RSAPublicKey { -// // Modulus length. This must be ANDROID_PUBKEY_MODULUS_SIZE. -// uint32_t modulus_size_words; -// -// // Precomputed montgomery parameter: -1 / n[0] mod 2^32 -// uint32_t n0inv; -// -// // RSA modulus as a little-endian array. -// uint8_t modulus[ANDROID_PUBKEY_MODULUS_SIZE]; -// -// // Montgomery parameter R^2 as a little-endian array of little-endian -// words. uint8_t rr[ANDROID_PUBKEY_MODULUS_SIZE]; -// -// // RSA modulus: 3 or 65537 -// uint32_t exponent; -// } RSAPublicKey; -// -// However, the Montgomery params (n0inv and rr) are not really used, see -// comment in android_pubkey_decode() ("Note that we don't extract the -// montgomery parameters...") -async function encodePubKey(key: CryptoKey) { - const expPubKey = await crypto.subtle.exportKey('jwk', key); - const nArr = base64StringToArray(expPubKey.n as string).reverse(); - const eArr = base64StringToArray(expPubKey.e as string).reverse(); - - const arr = new Uint8Array(3 * 4 + 2 * MODULUS_SIZE_BYTES); - const dv = new DataView(arr.buffer); - dv.setUint32(0, MODULUS_SIZE_BYTES / 4, true); - - // The Mongomery params (n0inv and rr) are not computed. - dv.setUint32(4, 0 /* n0inv*/, true); - // Modulus - for (let i = 0; i < MODULUS_SIZE_BYTES; i++) dv.setUint8(8 + i, nArr[i]); - - // rr: - for (let i = 0; i < MODULUS_SIZE_BYTES; i++) { - dv.setUint8(8 + MODULUS_SIZE_BYTES + i, 0 /* rr*/); - } - // Exponent - for (let i = 0; i < 4; i++) { - dv.setUint8(8 + (2 * MODULUS_SIZE_BYTES) + i, eArr[i]); - } - return btoa(String.fromCharCode(...new Uint8Array(dv.buffer))) + - ' perfetto@webusb'; -} - -// TODO(nicomazz): This token signature will be useful only when we save the -// generated keys. So far, we are not doing so. As a consequence, a dialog is -// displayed every time a tracing session is started. -// The reason why it has not already been implemented is that the standard -// crypto.subtle.sign function assumes that the input needs hashing, which is -// not the case for ADB, where the 20 bytes token is already hashed. -// A solution to this is implementing a custom private key signature with a js -// implementation of big integers. Maybe, wrapping the key like in the following -// CL can work: -// https://android-review.googlesource.com/c/platform/external/perfetto/+/1105354/18 -async function signAdbTokenWithPrivateKey( - _privateKey: CryptoKey, token: Uint8Array): Promise { - // This function is not implemented. - return token.buffer; -} diff --git a/third_party/perfetto/ui/src/controller/adb_base_controller.ts b/third_party/perfetto/ui/src/controller/adb_base_controller.ts deleted file mode 100644 index bbafbc325898..000000000000 --- a/third_party/perfetto/ui/src/controller/adb_base_controller.ts +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {extractDurationFromTraceConfig} from '../base/trace_config_utils'; -import {extractTraceConfig} from '../base/trace_config_utils'; -import {isAdbTarget} from '../common/state'; -import {globals} from '../frontend/globals'; - -import {Adb} from './adb_interfaces'; -import {ReadBuffersResponse} from './consumer_port_types'; -import {Consumer, RpcConsumerPort} from './record_controller_interfaces'; - -export enum AdbConnectionState { - READY_TO_CONNECT, - AUTH_IN_PROGRESS, - CONNECTED, - CLOSED -} - -interface Command { - method: string; - params: Uint8Array; -} - -export abstract class AdbBaseConsumerPort extends RpcConsumerPort { - // Contains the commands sent while the authentication is in progress. They - // will all be executed afterwards. If the device disconnects, they are - // removed. - private commandQueue: Command[] = []; - - protected adb: Adb; - protected state = AdbConnectionState.READY_TO_CONNECT; - protected device?: USBDevice; - - protected constructor(adb: Adb, consumer: Consumer) { - super(consumer); - this.adb = adb; - } - - async handleCommand(method: string, params: Uint8Array) { - try { - if (method === 'FreeBuffers') { - // When we finish tracing, we disconnect the adb device interface. - // Otherwise, we will keep holding the device interface and won't allow - // adb to access it. https://wicg.github.io/webusb/#abusing-a-device - // "Lastly, since USB devices are unable to distinguish requests from - // multiple sources, operating systems only allow a USB interface to - // have a single owning user-space or kernel-space driver." - this.state = AdbConnectionState.CLOSED; - await this.adb.disconnect(); - } else if (method === 'EnableTracing') { - this.state = AdbConnectionState.READY_TO_CONNECT; - } - - if (this.state === AdbConnectionState.CLOSED) return; - - this.commandQueue.push({method, params}); - - if (this.state === AdbConnectionState.READY_TO_CONNECT || - this.deviceDisconnected()) { - this.state = AdbConnectionState.AUTH_IN_PROGRESS; - this.device = await this.findDevice(); - if (!this.device) { - this.state = AdbConnectionState.READY_TO_CONNECT; - const target = globals.state.recordingTarget; - throw Error(`Device with serial ${ - isAdbTarget(target) ? target.serial : 'n/a'} not found.`); - } - - this.sendStatus(`Please allow USB debugging on device. - If you press cancel, reload the page.`); - - await this.adb.connect(this.device); - - // During the authentication the device may have been disconnected. - if (!globals.state.recordingInProgress || this.deviceDisconnected()) { - throw Error('Recording not in progress after adb authorization.'); - } - - this.state = AdbConnectionState.CONNECTED; - this.sendStatus('Device connected.'); - } - - if (this.state === AdbConnectionState.AUTH_IN_PROGRESS) return; - - console.assert(this.state === AdbConnectionState.CONNECTED); - - for (const cmd of this.commandQueue) this.invoke(cmd.method, cmd.params); - - this.commandQueue = []; - } catch (e) { - this.commandQueue = []; - this.state = AdbConnectionState.READY_TO_CONNECT; - this.sendErrorMessage(e.message); - } - } - - private deviceDisconnected() { - return !this.device || !this.device.opened; - } - - setDurationStatus(enableTracingProto: Uint8Array) { - const traceConfigProto = extractTraceConfig(enableTracingProto); - if (!traceConfigProto) return; - const duration = extractDurationFromTraceConfig(traceConfigProto); - this.sendStatus(`Recording in progress${ - duration ? ' for ' + duration.toString() + ' ms' : ''}...`); - } - - abstract invoke(method: string, argsProto: Uint8Array): void; - - generateChunkReadResponse(data: Uint8Array, last = false): - ReadBuffersResponse { - return { - type: 'ReadBuffersResponse', - slices: [{data, lastSliceForPacket: last}], - }; - } - - async findDevice(): Promise { - if (!('usb' in navigator)) return undefined; - const connectedDevice = globals.state.recordingTarget; - if (!isAdbTarget(connectedDevice)) return undefined; - const devices = await navigator.usb.getDevices(); - return devices.find((d) => d.serialNumber === connectedDevice.serial); - } -} diff --git a/third_party/perfetto/ui/src/controller/adb_interfaces.ts b/third_party/perfetto/ui/src/controller/adb_interfaces.ts deleted file mode 100644 index b347c395e64e..000000000000 --- a/third_party/perfetto/ui/src/controller/adb_interfaces.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -export interface Adb { - connect(device: USBDevice): Promise; - disconnect(): Promise; - // Executes a shell command non-interactively. - shell(cmd: string): Promise; - // Waits until the shell get closed, and returns all the output. - shellOutputAsString(cmd: string): Promise; - // Opens a connection to a UNIX socket. - socket(path: string): Promise; -} - -export interface AdbStream { - write(msg: string|Uint8Array): Promise; - onMessage(message: AdbMsg): void; - close(): void; - setClosed(): void; - - onConnect: VoidCallback; - onClose: VoidCallback; - onData: (raw: Uint8Array) => void; -} - -export class MockAdb implements Adb { - connect(_: USBDevice): Promise { - return Promise.resolve(); - } - - disconnect(): Promise { - return Promise.resolve(); - } - - shell(_: string): Promise { - return Promise.resolve(new MockAdbStream()); - } - - shellOutputAsString(_: string): Promise { - return Promise.resolve(''); - } - - socket(_: string): Promise { - return Promise.resolve(new MockAdbStream()); - } -} - -export class MockAdbStream implements AdbStream { - write(_: string|Uint8Array): Promise { - return Promise.resolve(); - } - onMessage = (_: AdbMsg) => {}; - close() {} - setClosed() {} - - onConnect = () => {}; - onClose = () => {}; - onData = (_: Uint8Array) => {}; -} - -export declare type CmdType = - 'CNXN' | 'AUTH' | 'CLSE' | 'OKAY' | 'WRTE' | 'OPEN'; - -export interface AdbMsg { - cmd: CmdType; - arg0: number; - arg1: number; - data: Uint8Array; - dataLen: number; - dataChecksum: number; -} diff --git a/third_party/perfetto/ui/src/controller/adb_jsdomtest.ts b/third_party/perfetto/ui/src/controller/adb_jsdomtest.ts deleted file mode 100644 index ae7cce86e509..000000000000 --- a/third_party/perfetto/ui/src/controller/adb_jsdomtest.ts +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {TextEncoder} from 'util'; - -import { - AdbMsgImpl, - AdbOverWebUsb, - AdbState, - DEFAULT_MAX_PAYLOAD_BYTES, - VERSION_WITH_CHECKSUM, -} from './adb'; - -test('startAuthentication', async () => { - const adb = new AdbOverWebUsb(); - - const sendRaw = jest.fn(); - adb.sendRaw = sendRaw; - const recvRaw = jest.fn(); - adb.recvRaw = recvRaw; - - - const expectedAuthMessage = AdbMsgImpl.create({ - cmd: 'CNXN', - arg0: VERSION_WITH_CHECKSUM, - arg1: DEFAULT_MAX_PAYLOAD_BYTES, - data: 'host:1:UsbADB', - useChecksum: true, - }); - await adb.startAuthentication(); - - expect(sendRaw).toHaveBeenCalledTimes(2); - expect(sendRaw).toBeCalledWith(expectedAuthMessage.encodeHeader()); - expect(sendRaw).toBeCalledWith(expectedAuthMessage.data); -}); - -test('connectedMessage', async () => { - const adb = new AdbOverWebUsb(); - adb.key = {} as unknown as CryptoKeyPair; - adb.state = AdbState.AUTH_STEP2; - - const onConnected = jest.fn(); - adb.onConnected = onConnected; - - const expectedMaxPayload = 42; - const connectedMsg = AdbMsgImpl.create({ - cmd: 'CNXN', - arg0: VERSION_WITH_CHECKSUM, - arg1: expectedMaxPayload, - data: new TextEncoder().encode('device'), - useChecksum: true, - }); - await adb.onMessage(connectedMsg); - - expect(adb.state).toBe(AdbState.CONNECTED); - expect(adb.maxPayload).toBe(expectedMaxPayload); - expect(adb.devProps).toBe('device'); - expect(adb.useChecksum).toBe(true); - expect(onConnected).toHaveBeenCalledTimes(1); -}); - - -test('shellOpening', () => { - const adb = new AdbOverWebUsb(); - const openStream = jest.fn(); - adb.openStream = openStream; - - adb.shell('test'); - expect(openStream).toBeCalledWith('shell:test'); -}); diff --git a/third_party/perfetto/ui/src/controller/adb_record_controller_jsdomtest.ts b/third_party/perfetto/ui/src/controller/adb_record_controller_jsdomtest.ts deleted file mode 100644 index 21b28988125a..000000000000 --- a/third_party/perfetto/ui/src/controller/adb_record_controller_jsdomtest.ts +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {dingus} from 'dingusjs'; - -import {utf8Encode} from '../base/string_utils'; -import {perfetto} from '../gen/protos'; - -import {AdbStream, MockAdb, MockAdbStream} from './adb_interfaces'; -import {AdbConsumerPort} from './adb_shell_controller'; -import {Consumer} from './record_controller_interfaces'; - -function generateMockConsumer(): Consumer { - return { - onConsumerPortResponse: jest.fn(), - onError: jest.fn(), - onStatus: jest.fn(), - }; -} -const mainCallback = generateMockConsumer(); -const adbMock = new MockAdb(); -const adbController = new AdbConsumerPort(adbMock, mainCallback); -const mockIntArray = new Uint8Array(); - -const enableTracingRequest = new perfetto.protos.EnableTracingRequest(); -enableTracingRequest.traceConfig = new perfetto.protos.TraceConfig(); -const enableTracingRequestProto = - perfetto.protos.EnableTracingRequest.encode(enableTracingRequest).finish(); - - -test('handleCommand', async () => { - adbController.findDevice = () => { - return Promise.resolve(dingus()); - }; - - const enableTracing = jest.fn(); - adbController.enableTracing = enableTracing; - await adbController.invoke('EnableTracing', mockIntArray); - expect(enableTracing).toHaveBeenCalledTimes(1); - - const readBuffers = jest.fn(); - adbController.readBuffers = readBuffers; - adbController.invoke('ReadBuffers', mockIntArray); - expect(readBuffers).toHaveBeenCalledTimes(1); - - const sendErrorMessage = jest.fn(); - adbController.sendErrorMessage = sendErrorMessage; - adbController.invoke('unknown', mockIntArray); - expect(sendErrorMessage).toBeCalledWith('Method not recognized: unknown'); -}); - -test('enableTracing', async () => { - const mainCallback = generateMockConsumer(); - const adbMock = new MockAdb(); - const adbController = new AdbConsumerPort(adbMock, mainCallback); - - adbController.sendErrorMessage = - jest.fn().mockImplementation((s) => console.error(s)); - - const findDevice = jest.fn().mockImplementation(() => { - return Promise.resolve({} as unknown as USBDevice); - }); - adbController.findDevice = findDevice; - - const connectToDevice = - jest.fn().mockImplementation((_: USBDevice) => Promise.resolve()); - adbMock.connect = connectToDevice; - - const stream: AdbStream = new MockAdbStream(); - const adbShell = - jest.fn().mockImplementation((_: string) => Promise.resolve(stream)); - adbMock.shell = adbShell; - - - const sendMessage = jest.fn(); - adbController.sendMessage = sendMessage; - - adbController.generateStartTracingCommand = (_) => 'CMD'; - - await adbController.enableTracing(enableTracingRequestProto); - expect(adbShell).toBeCalledWith('CMD'); - expect(sendMessage).toHaveBeenCalledTimes(0); - - - stream.onData(utf8Encode('starting tracing Wrote 123 bytes')); - stream.onClose(); - - expect(adbController.sendErrorMessage).toHaveBeenCalledTimes(0); - expect(sendMessage).toBeCalledWith({type: 'EnableTracingResponse'}); -}); - - -test('generateStartTracing', () => { - adbController.traceDestFile = 'DEST'; - const testArray = new Uint8Array(1); - testArray[0] = 65; - const generatedCmd = adbController.generateStartTracingCommand(testArray); - expect(generatedCmd) - .toBe(`echo '${btoa('A')}' | base64 -d | perfetto -c - -o DEST`); -}); - -test('tracingEndedSuccessfully', () => { - expect( - adbController.tracingEndedSuccessfully( - 'Connected to the Perfetto traced service, starting tracing for 10000 ms\nWrote 564 bytes into /data/misc/perfetto-traces/trace')) - .toBe(true); - expect( - adbController.tracingEndedSuccessfully( - 'Connected to the Perfetto traced service, starting tracing for 10000 ms')) - .toBe(false); - expect( - adbController.tracingEndedSuccessfully( - 'Connected to the Perfetto traced service, starting tracing for 0 ms')) - .toBe(false); -}); diff --git a/third_party/perfetto/ui/src/controller/adb_shell_controller.ts b/third_party/perfetto/ui/src/controller/adb_shell_controller.ts deleted file mode 100644 index 4c7b6a26bd8c..000000000000 --- a/third_party/perfetto/ui/src/controller/adb_shell_controller.ts +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {_TextDecoder} from 'custom_utils'; - -import {base64Encode} from '../base/string_utils'; -import {extractTraceConfig} from '../base/trace_config_utils'; - -import {AdbBaseConsumerPort, AdbConnectionState} from './adb_base_controller'; -import {Adb, AdbStream} from './adb_interfaces'; -import {ReadBuffersResponse} from './consumer_port_types'; -import {Consumer} from './record_controller_interfaces'; - -enum AdbShellState { - READY, - RECORDING, - FETCHING -} -const DEFAULT_DESTINATION_FILE = '/data/misc/perfetto-traces/trace-by-ui'; -const textDecoder = new _TextDecoder(); - -export class AdbConsumerPort extends AdbBaseConsumerPort { - traceDestFile = DEFAULT_DESTINATION_FILE; - shellState: AdbShellState = AdbShellState.READY; - private recordShell?: AdbStream; - - constructor(adb: Adb, consumer: Consumer) { - super(adb, consumer); - this.adb = adb; - } - - async invoke(method: string, params: Uint8Array) { - // ADB connection & authentication is handled by the superclass. - console.assert(this.state === AdbConnectionState.CONNECTED); - - switch (method) { - case 'EnableTracing': - this.enableTracing(params); - break; - case 'ReadBuffers': - this.readBuffers(); - break; - case 'DisableTracing': - this.disableTracing(); - break; - case 'FreeBuffers': - this.freeBuffers(); - break; - case 'GetTraceStats': - break; - default: - this.sendErrorMessage(`Method not recognized: ${method}`); - break; - } - } - - async enableTracing(enableTracingProto: Uint8Array) { - try { - const traceConfigProto = extractTraceConfig(enableTracingProto); - if (!traceConfigProto) { - this.sendErrorMessage('Invalid config.'); - return; - } - - await this.startRecording(traceConfigProto); - this.setDurationStatus(enableTracingProto); - } catch (e) { - this.sendErrorMessage(e.message); - } - } - - async startRecording(configProto: Uint8Array) { - this.shellState = AdbShellState.RECORDING; - const recordCommand = this.generateStartTracingCommand(configProto); - this.recordShell = await this.adb.shell(recordCommand); - const output: string[] = []; - this.recordShell.onData = (raw) => output.push(textDecoder.decode(raw)); - this.recordShell.onClose = () => { - const response = output.join(); - if (!this.tracingEndedSuccessfully(response)) { - this.sendErrorMessage(response); - this.shellState = AdbShellState.READY; - return; - } - this.sendStatus('Recording ended successfully. Fetching the trace..'); - this.sendMessage({type: 'EnableTracingResponse'}); - this.recordShell = undefined; - }; - } - - tracingEndedSuccessfully(response: string): boolean { - return !response.includes(' 0 ms') && response.includes('Wrote '); - } - - async readBuffers() { - console.assert(this.shellState === AdbShellState.RECORDING); - this.shellState = AdbShellState.FETCHING; - - const readTraceShell = - await this.adb.shell(this.generateReadTraceCommand()); - readTraceShell.onData = (raw) => - this.sendMessage(this.generateChunkReadResponse(raw)); - - readTraceShell.onClose = () => { - this.sendMessage( - this.generateChunkReadResponse(new Uint8Array(), /* last */ true)); - }; - } - - async getPidFromShellAsString() { - const pidStr = - await this.adb.shellOutputAsString(`ps -u shell | grep perfetto`); - // We used to use awk '{print $2}' but older phones/Go phones don't have - // awk installed. Instead we implement similar functionality here. - const awk = pidStr.split(' ').filter((str) => str !== ''); - if (awk.length < 1) { - throw Error(`Unabled to find perfetto pid in string "${pidStr}"`); - } - return awk[1]; - } - - async disableTracing() { - if (!this.recordShell) return; - try { - // We are not using 'pidof perfetto' so that we can use more filters. 'ps - // -u shell' is meant to catch processes started from shell, so if there - // are other ongoing tracing sessions started by others, we are not - // killing them. - const pid = await this.getPidFromShellAsString(); - - if (pid.length === 0 || isNaN(Number(pid))) { - throw Error(`Perfetto pid not found. Impossible to stop/cancel the - recording. Command output: ${pid}`); - } - // Perfetto stops and finalizes the tracing session on SIGINT. - const killOutput = - await this.adb.shellOutputAsString(`kill -SIGINT ${pid}`); - - if (killOutput.length !== 0) { - throw Error(`Unable to kill perfetto: ${killOutput}`); - } - } catch (e) { - this.sendErrorMessage(e.message); - } - } - - freeBuffers() { - this.shellState = AdbShellState.READY; - if (this.recordShell) { - this.recordShell.close(); - this.recordShell = undefined; - } - } - - generateChunkReadResponse(data: Uint8Array, last = false): - ReadBuffersResponse { - return { - type: 'ReadBuffersResponse', - slices: [{data, lastSliceForPacket: last}], - }; - } - - generateReadTraceCommand(): string { - // We attempt to delete the trace file after tracing. On a non-root shell, - // this will fail (due to selinux denial), but perfetto cmd will be able to - // override the file later. However, on a root shell, we need to clean up - // the file since perfetto cmd might otherwise fail to override it in a - // future session. - return `gzip -c ${this.traceDestFile} && rm -f ${this.traceDestFile}`; - } - - generateStartTracingCommand(tracingConfig: Uint8Array) { - const configBase64 = base64Encode(tracingConfig); - const perfettoCmd = `perfetto -c - -o ${this.traceDestFile}`; - return `echo '${configBase64}' | base64 -d | ${perfettoCmd}`; - } -} diff --git a/third_party/perfetto/ui/src/controller/adb_socket_controller.ts b/third_party/perfetto/ui/src/controller/adb_socket_controller.ts deleted file mode 100644 index 87af792429a6..000000000000 --- a/third_party/perfetto/ui/src/controller/adb_socket_controller.ts +++ /dev/null @@ -1,370 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import protobuf from 'protobufjs/minimal'; - -import {perfetto} from '../gen/protos'; - -import {AdbBaseConsumerPort, AdbConnectionState} from './adb_base_controller'; -import {Adb, AdbStream} from './adb_interfaces'; -import { - isReadBuffersResponse, -} from './consumer_port_types'; -import {Consumer} from './record_controller_interfaces'; - -enum SocketState { - DISCONNECTED, - BINDING_IN_PROGRESS, - BOUND, -} - -// See wire_protocol.proto for more details. -const WIRE_PROTOCOL_HEADER_SIZE = 4; -const MAX_IPC_BUFFER_SIZE = 128 * 1024; - -const PROTO_LEN_DELIMITED_WIRE_TYPE = 2; -const TRACE_PACKET_PROTO_ID = 1; -const TRACE_PACKET_PROTO_TAG = - (TRACE_PACKET_PROTO_ID << 3) | PROTO_LEN_DELIMITED_WIRE_TYPE; - -declare type Frame = perfetto.protos.IPCFrame; -declare type IMethodInfo = - perfetto.protos.IPCFrame.BindServiceReply.IMethodInfo; -declare type ISlice = perfetto.protos.ReadBuffersResponse.ISlice; - -interface Command { - method: string; - params: Uint8Array; -} - -const TRACED_SOCKET = '/dev/socket/traced_consumer'; - -export class AdbSocketConsumerPort extends AdbBaseConsumerPort { - private socketState = SocketState.DISCONNECTED; - - private socket?: AdbStream; - // Wire protocol request ID. After each request it is increased. It is needed - // to keep track of the type of request, and parse the response correctly. - private requestId = 1; - - // Buffers received wire protocol data. - private incomingBuffer = new Uint8Array(MAX_IPC_BUFFER_SIZE); - private incomingBufferLen = 0; - private frameToParseLen = 0; - - private availableMethods: IMethodInfo[] = []; - private serviceId = -1; - - private resolveBindingPromise!: VoidFunction; - private requestMethods = new Map(); - - // Needed for ReadBufferResponse: all the trace packets are split into - // several slices. |partialPacket| is the buffer for them. Once we receive a - // slice with the flag |lastSliceForPacket|, a new packet is created. - private partialPacket: ISlice[] = []; - // Accumulates trace packets into a proto trace file.. - private traceProtoWriter = protobuf.Writer.create(); - - private socketCommandQueue: Command[] = []; - - constructor(adb: Adb, consumer: Consumer) { - super(adb, consumer); - } - - async invoke(method: string, params: Uint8Array) { - // ADB connection & authentication is handled by the superclass. - console.assert(this.state === AdbConnectionState.CONNECTED); - this.socketCommandQueue.push({method, params}); - - if (this.socketState === SocketState.BINDING_IN_PROGRESS) return; - if (this.socketState === SocketState.DISCONNECTED) { - this.socketState = SocketState.BINDING_IN_PROGRESS; - await this.listenForMessages(); - await this.bind(); - this.traceProtoWriter = protobuf.Writer.create(); - this.socketState = SocketState.BOUND; - } - - console.assert(this.socketState === SocketState.BOUND); - - for (const cmd of this.socketCommandQueue) { - this.invokeInternal(cmd.method, cmd.params); - } - this.socketCommandQueue = []; - } - - private invokeInternal(method: string, argsProto: Uint8Array) { - // Socket is bound in invoke(). - console.assert(this.socketState === SocketState.BOUND); - const requestId = this.requestId++; - const methodId = this.findMethodId(method); - if (methodId === undefined) { - // This can happen with 'GetTraceStats': it seems that not all the Android - // <= 9 devices support it. - console.error(`Method ${method} not supported by the target`); - return; - } - const frame = new perfetto.protos.IPCFrame({ - requestId, - msgInvokeMethod: new perfetto.protos.IPCFrame.InvokeMethod( - {serviceId: this.serviceId, methodId, argsProto}), - }); - this.requestMethods.set(requestId, method); - this.sendFrame(frame); - - if (method === 'EnableTracing') this.setDurationStatus(argsProto); - } - - static generateFrameBufferToSend(frame: Frame): Uint8Array { - const frameProto: Uint8Array = - perfetto.protos.IPCFrame.encode(frame).finish(); - const frameLen = frameProto.length; - const buf = new Uint8Array(WIRE_PROTOCOL_HEADER_SIZE + frameLen); - const dv = new DataView(buf.buffer); - dv.setUint32(0, frameProto.length, /* littleEndian */ true); - for (let i = 0; i < frameLen; i++) { - dv.setUint8(WIRE_PROTOCOL_HEADER_SIZE + i, frameProto[i]); - } - return buf; - } - - async sendFrame(frame: Frame) { - console.assert(this.socket !== undefined); - if (!this.socket) return; - const buf = AdbSocketConsumerPort.generateFrameBufferToSend(frame); - await this.socket.write(buf); - } - - async listenForMessages() { - this.socket = await this.adb.socket(TRACED_SOCKET); - this.socket.onData = (raw) => this.handleReceivedData(raw); - this.socket.onClose = () => { - this.socketState = SocketState.DISCONNECTED; - this.socketCommandQueue = []; - }; - } - - private parseMessageSize(buffer: Uint8Array) { - const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.length); - return dv.getUint32(0, true); - } - - private parseMessage(frameBuffer: Uint8Array) { - // Copy message to new array: - const buf = new ArrayBuffer(frameBuffer.byteLength); - const arr = new Uint8Array(buf); - arr.set(frameBuffer); - const frame = perfetto.protos.IPCFrame.decode(arr); - this.handleIncomingFrame(frame); - } - - private incompleteSizeHeader() { - if (!this.frameToParseLen) { - console.assert(this.incomingBufferLen < WIRE_PROTOCOL_HEADER_SIZE); - return true; - } - return false; - } - - private canCompleteSizeHeader(newData: Uint8Array) { - return newData.length + this.incomingBufferLen > WIRE_PROTOCOL_HEADER_SIZE; - } - - private canParseFullMessage(newData: Uint8Array) { - return this.frameToParseLen && - this.incomingBufferLen + newData.length >= this.frameToParseLen; - } - - private appendToIncomingBuffer(array: Uint8Array) { - this.incomingBuffer.set(array, this.incomingBufferLen); - this.incomingBufferLen += array.length; - } - - handleReceivedData(newData: Uint8Array) { - if (this.incompleteSizeHeader() && this.canCompleteSizeHeader(newData)) { - const newDataBytesToRead = - WIRE_PROTOCOL_HEADER_SIZE - this.incomingBufferLen; - // Add to the incoming buffer the remaining bytes to arrive at - // WIRE_PROTOCOL_HEADER_SIZE - this.appendToIncomingBuffer(newData.subarray(0, newDataBytesToRead)); - newData = newData.subarray(newDataBytesToRead); - - this.frameToParseLen = this.parseMessageSize(this.incomingBuffer); - this.incomingBufferLen = 0; - } - - // Parse all complete messages in incomingBuffer and newData. - while (this.canParseFullMessage(newData)) { - // All the message is in the newData buffer. - if (this.incomingBufferLen === 0) { - this.parseMessage(newData.subarray(0, this.frameToParseLen)); - newData = newData.subarray(this.frameToParseLen); - } else { // We need to complete the local buffer. - // Read the remaining part of this message. - const bytesToCompleteMessage = - this.frameToParseLen - this.incomingBufferLen; - this.appendToIncomingBuffer( - newData.subarray(0, bytesToCompleteMessage)); - this.parseMessage( - this.incomingBuffer.subarray(0, this.frameToParseLen)); - this.incomingBufferLen = 0; - // Remove the data just parsed. - newData = newData.subarray(bytesToCompleteMessage); - } - this.frameToParseLen = 0; - if (!this.canCompleteSizeHeader(newData)) break; - - this.frameToParseLen = - this.parseMessageSize(newData.subarray(0, WIRE_PROTOCOL_HEADER_SIZE)); - newData = newData.subarray(WIRE_PROTOCOL_HEADER_SIZE); - } - // Buffer the remaining data (part of the next header + message). - this.appendToIncomingBuffer(newData); - } - - decodeResponse( - requestId: number, responseProto: Uint8Array, hasMore = false) { - const method = this.requestMethods.get(requestId); - if (!method) { - console.error(`Unknown request id: ${requestId}`); - this.sendErrorMessage(`Wire protocol error.`); - return; - } - const decoder = decoders.get(method); - if (decoder === undefined) { - console.error(`Unable to decode method: ${method}`); - return; - } - const decodedResponse = decoder(responseProto); - const response = {type: `${method}Response`, ...decodedResponse}; - - // TODO(nicomazz): Fix this. - // We assemble all the trace and then send it back to the main controller. - // This is a temporary solution, that will be changed in a following CL, - // because now both the chrome consumer port and the other adb consumer port - // send back the entire trace, while the correct behavior should be to send - // back the slices, that are assembled by the main record controller. - if (isReadBuffersResponse(response)) { - if (response.slices) this.handleSlices(response.slices); - if (!hasMore) this.sendReadBufferResponse(); - return; - } - this.sendMessage(response); - } - - handleSlices(slices: ISlice[]) { - for (const slice of slices) { - this.partialPacket.push(slice); - if (slice.lastSliceForPacket) { - const tracePacket = this.generateTracePacket(this.partialPacket); - this.traceProtoWriter.uint32(TRACE_PACKET_PROTO_TAG); - this.traceProtoWriter.bytes(tracePacket); - this.partialPacket = []; - } - } - } - - generateTracePacket(slices: ISlice[]): Uint8Array { - let bufferSize = 0; - for (const slice of slices) bufferSize += slice.data!.length; - const fullBuffer = new Uint8Array(bufferSize); - let written = 0; - for (const slice of slices) { - const data = slice.data!; - fullBuffer.set(data, written); - written += data.length; - } - return fullBuffer; - } - - sendReadBufferResponse() { - this.sendMessage(this.generateChunkReadResponse( - this.traceProtoWriter.finish(), /* last */ true)); - this.traceProtoWriter = protobuf.Writer.create(); - } - - bind() { - console.assert(this.socket !== undefined); - const requestId = this.requestId++; - const frame = new perfetto.protos.IPCFrame({ - requestId, - msgBindService: new perfetto.protos.IPCFrame.BindService( - {serviceName: 'ConsumerPort'}), - }); - return new Promise((resolve, _) => { - this.resolveBindingPromise = resolve; - this.sendFrame(frame); - }); - } - - findMethodId(method: string): number|undefined { - const methodObject = this.availableMethods.find((m) => m.name === method); - if (methodObject && methodObject.id) return methodObject.id; - return undefined; - } - - static async hasSocketAccess(device: USBDevice, adb: Adb): Promise { - await adb.connect(device); - try { - const socket = await adb.socket(TRACED_SOCKET); - socket.close(); - return true; - } catch (e) { - return false; - } - } - - handleIncomingFrame(frame: perfetto.protos.IPCFrame) { - const requestId = frame.requestId; - switch (frame.msg) { - case 'msgBindServiceReply': { - const msgBindServiceReply = frame.msgBindServiceReply; - if (msgBindServiceReply && msgBindServiceReply.methods && - msgBindServiceReply.serviceId) { - console.assert(msgBindServiceReply.success); - this.availableMethods = msgBindServiceReply.methods; - this.serviceId = msgBindServiceReply.serviceId; - this.resolveBindingPromise(); - this.resolveBindingPromise = () => {}; - } - return; - } - case 'msgInvokeMethodReply': { - const msgInvokeMethodReply = frame.msgInvokeMethodReply; - if (msgInvokeMethodReply && msgInvokeMethodReply.replyProto) { - if (!msgInvokeMethodReply.success) { - console.error( - 'Unsuccessful method invocation: ', msgInvokeMethodReply); - return; - } - this.decodeResponse( - requestId, - msgInvokeMethodReply.replyProto, - msgInvokeMethodReply.hasMore === true); - } - return; - } - default: - console.error(`not recognized frame message: ${frame.msg}`); - } - } -} - -const decoders = - new Map() - .set('EnableTracing', perfetto.protos.EnableTracingResponse.decode) - .set('FreeBuffers', perfetto.protos.FreeBuffersResponse.decode) - .set('ReadBuffers', perfetto.protos.ReadBuffersResponse.decode) - .set('DisableTracing', perfetto.protos.DisableTracingResponse.decode) - .set('GetTraceStats', perfetto.protos.GetTraceStatsResponse.decode); diff --git a/third_party/perfetto/ui/src/controller/aggregation/aggregation_controller.ts b/third_party/perfetto/ui/src/controller/aggregation/aggregation_controller.ts deleted file mode 100644 index f92f4fafe301..000000000000 --- a/third_party/perfetto/ui/src/controller/aggregation/aggregation_controller.ts +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - AggregateData, - Column, - ColumnDef, - ThreadStateExtra, -} from '../../common/aggregation_data'; -import {Engine} from '../../common/engine'; -import {NUM} from '../../common/query_result'; -import {Area, Sorting} from '../../common/state'; -import {globals} from '../../frontend/globals'; -import {publishAggregateData} from '../../frontend/publish'; -import {AreaSelectionHandler} from '../area_selection_handler'; -import {Controller} from '../controller'; - -export interface AggregationControllerArgs { - engine: Engine; - kind: string; -} - -function isStringColumn(column: Column): boolean { - return column.kind === 'STRING' || column.kind === 'STATE'; -} - -export abstract class AggregationController extends Controller<'main'> { - readonly kind: string; - private areaSelectionHandler: AreaSelectionHandler; - private previousSorting?: Sorting; - private requestingData = false; - private queuedRequest = false; - - abstract createAggregateView(engine: Engine, area: Area): Promise; - - abstract getExtra(engine: Engine, area: Area): Promise; - - abstract getTabName(): string; - abstract getDefaultSorting(): Sorting; - abstract getColumnDefinitions(): ColumnDef[]; - - constructor(private args: AggregationControllerArgs) { - super('main'); - this.kind = this.args.kind; - this.areaSelectionHandler = new AreaSelectionHandler(); - } - - run() { - const selection = globals.state.currentSelection; - if (selection === null || selection.kind !== 'AREA') { - publishAggregateData({ - data: { - tabName: this.getTabName(), - columns: [], - strings: [], - columnSums: [], - }, - kind: this.args.kind, - }); - return; - } - const aggregatePreferences = - globals.state.aggregatePreferences[this.args.kind]; - - const sortingChanged = aggregatePreferences && - this.previousSorting !== aggregatePreferences.sorting; - const [hasAreaChanged, area] = this.areaSelectionHandler.getAreaChange(); - if ((!hasAreaChanged && !sortingChanged) || !area) return; - - if (this.requestingData) { - this.queuedRequest = true; - } else { - this.requestingData = true; - if (sortingChanged) this.previousSorting = aggregatePreferences.sorting; - this.getAggregateData(area, hasAreaChanged) - .then((data) => publishAggregateData({data, kind: this.args.kind})) - .finally(() => { - this.requestingData = false; - if (this.queuedRequest) { - this.queuedRequest = false; - this.run(); - } - }); - } - } - - async getAggregateData(area: Area, areaChanged: boolean): - Promise { - if (areaChanged) { - const viewExists = await this.createAggregateView(this.args.engine, area); - if (!viewExists) { - return { - tabName: this.getTabName(), - columns: [], - strings: [], - columnSums: [], - }; - } - } - - const defs = this.getColumnDefinitions(); - const colIds = defs.map((col) => col.columnId); - const pref = globals.state.aggregatePreferences[this.kind]; - let sorting = `${this.getDefaultSorting().column} ${ - this.getDefaultSorting().direction}`; - if (pref && pref.sorting) { - sorting = `${pref.sorting.column} ${pref.sorting.direction}`; - } - const query = `select ${colIds} from ${this.kind} order by ${sorting}`; - const result = await this.args.engine.query(query); - - const numRows = result.numRows(); - const columns = defs.map((def) => this.columnFromColumnDef(def, numRows)); - const columnSums = await Promise.all(defs.map((def) => this.getSum(def))); - const extraData = await this.getExtra(this.args.engine, area); - const extra = extraData ? extraData : undefined; - const data: AggregateData = - {tabName: this.getTabName(), columns, columnSums, strings: [], extra}; - - const stringIndexes = new Map(); - function internString(str: string) { - let idx = stringIndexes.get(str); - if (idx !== undefined) return idx; - idx = data.strings.length; - data.strings.push(str); - stringIndexes.set(str, idx); - return idx; - } - - const it = result.iter({}); - for (let i = 0; it.valid(); it.next(), ++i) { - for (const column of data.columns) { - const item = it.get(column.columnId); - if (item === null) { - column.data[i] = isStringColumn(column) ? internString('NULL') : 0; - } else if (typeof item === 'string') { - column.data[i] = internString(item); - } else if (item instanceof Uint8Array) { - column.data[i] = internString(''); - } else if (typeof item === 'bigint') { - // TODO(stevegolton) Handle potential loss of precision - column.data[i] = Number(item); - } else { - column.data[i] = item; - } - } - } - - return data; - } - - async getSum(def: ColumnDef): Promise { - if (!def.sum) return ''; - const result = await this.args.engine.query( - `select ifnull(sum(${def.columnId}), 0) as s from ${this.kind}`); - let sum = result.firstRow({s: NUM}).s; - if (def.kind === 'TIMESTAMP_NS') { - sum = sum / 1e6; - } - return `${sum}`; - } - - columnFromColumnDef(def: ColumnDef, numRows: number): Column { - // TODO(hjd): The Column type should be based on the - // ColumnDef type or vice versa to avoid this cast. - return { - title: def.title, - kind: def.kind, - data: new def.columnConstructor(numRows), - columnId: def.columnId, - } as Column; - } -} diff --git a/third_party/perfetto/ui/src/controller/aggregation/counter_aggregation_controller.ts b/third_party/perfetto/ui/src/controller/aggregation/counter_aggregation_controller.ts deleted file mode 100644 index dd7f14aad759..000000000000 --- a/third_party/perfetto/ui/src/controller/aggregation/counter_aggregation_controller.ts +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {ColumnDef} from '../../common/aggregation_data'; -import {Engine} from '../../common/engine'; -import {Area, Sorting} from '../../common/state'; -import {toNs} from '../../common/time'; -import {globals} from '../../frontend/globals'; -import {Config, COUNTER_TRACK_KIND} from '../../tracks/counter'; - -import {AggregationController} from './aggregation_controller'; - -export class CounterAggregationController extends AggregationController { - async createAggregateView(engine: Engine, area: Area) { - await engine.query(`drop view if exists ${this.kind};`); - - const ids = []; - for (const trackId of area.tracks) { - const track = globals.state.tracks[trackId]; - // Track will be undefined for track groups. - if (track !== undefined && track.kind === COUNTER_TRACK_KIND) { - const config = track.config as Config; - // TODO(hjd): Also aggregate annotation (with namespace) counters. - if (config.namespace === undefined) { - ids.push(config.trackId); - } - } - } - if (ids.length === 0) return false; - - const query = `create view ${this.kind} as select - name, - count(1) as count, - round(sum(weighted_value)/${ - toNs(area.endSec) - toNs(area.startSec)}, 2) as avg_value, - last as last_value, - first as first_value, - max(last) - min(first) as delta_value, - round((max(last) - min(first))/${area.endSec - area.startSec}, 2) as rate, - min(value) as min_value, - max(value) as max_value - from - (select *, - (min(ts + dur, ${toNs(area.endSec)}) - max(ts,${toNs(area.startSec)})) - * value as weighted_value, - first_value(value) over - (partition by track_id order by ts) as first, - last_value(value) over - (partition by track_id order by ts - range between unbounded preceding and unbounded following) as last - from experimental_counter_dur - where track_id in (${ids}) - and ts + dur >= ${toNs(area.startSec)} and - ts <= ${toNs(area.endSec)}) - join counter_track - on track_id = counter_track.id - group by track_id`; - - await engine.query(query); - return true; - } - - getColumnDefinitions(): ColumnDef[] { - return [ - { - title: 'Name', - kind: 'STRING', - columnConstructor: Uint16Array, - columnId: 'name', - }, - { - title: 'Delta value', - kind: 'NUMBER', - columnConstructor: Float64Array, - columnId: 'delta_value', - }, - { - title: 'Rate /s', - kind: 'Number', - columnConstructor: Float64Array, - columnId: 'rate', - }, - { - title: 'Weighted avg value', - kind: 'Number', - columnConstructor: Float64Array, - columnId: 'avg_value', - }, - { - title: 'Count', - kind: 'Number', - columnConstructor: Float64Array, - columnId: 'count', - sum: true, - }, - { - title: 'First value', - kind: 'NUMBER', - columnConstructor: Float64Array, - columnId: 'first_value', - }, - { - title: 'Last value', - kind: 'NUMBER', - columnConstructor: Float64Array, - columnId: 'last_value', - }, - { - title: 'Min value', - kind: 'NUMBER', - columnConstructor: Float64Array, - columnId: 'min_value', - }, - { - title: 'Max value', - kind: 'NUMBER', - columnConstructor: Float64Array, - columnId: 'max_value', - }, - - ]; - } - - async getExtra() {} - - getTabName() { - return 'Counters'; - } - - getDefaultSorting(): Sorting { - return {column: 'name', direction: 'DESC'}; - } -} diff --git a/third_party/perfetto/ui/src/controller/aggregation/cpu_aggregation_controller.ts b/third_party/perfetto/ui/src/controller/aggregation/cpu_aggregation_controller.ts deleted file mode 100644 index 94d3950a07e6..000000000000 --- a/third_party/perfetto/ui/src/controller/aggregation/cpu_aggregation_controller.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {ColumnDef} from '../../common/aggregation_data'; -import {Engine} from '../../common/engine'; -import {Area, Sorting} from '../../common/state'; -import {toNs} from '../../common/time'; -import {globals} from '../../frontend/globals'; -import {Config, CPU_SLICE_TRACK_KIND} from '../../tracks/cpu_slices'; - -import {AggregationController} from './aggregation_controller'; - - -export class CpuAggregationController extends AggregationController { - async createAggregateView(engine: Engine, area: Area) { - await engine.query(`drop view if exists ${this.kind};`); - - const selectedCpus = []; - for (const trackId of area.tracks) { - const track = globals.state.tracks[trackId]; - // Track will be undefined for track groups. - if (track !== undefined && track.kind === CPU_SLICE_TRACK_KIND) { - selectedCpus.push((track.config as Config).cpu); - } - } - if (selectedCpus.length === 0) return false; - - const query = `create view ${this.kind} as - SELECT process.name as process_name, pid, thread.name as thread_name, - tid, sum(dur) AS total_dur, - sum(dur)/count(1) as avg_dur, - count(1) as occurrences - FROM process - JOIN thread USING(upid) - JOIN thread_state USING(utid) - WHERE cpu IN (${selectedCpus}) AND - state = "Running" AND - thread_state.ts + thread_state.dur > ${toNs(area.startSec)} AND - thread_state.ts < ${toNs(area.endSec)} group by utid`; - - await engine.query(query); - return true; - } - - getTabName() { - return 'CPU by thread'; - } - - async getExtra() {} - - getDefaultSorting(): Sorting { - return {column: 'total_dur', direction: 'DESC'}; - } - - getColumnDefinitions(): ColumnDef[] { - return [ - { - title: 'Process', - kind: 'STRING', - columnConstructor: Uint16Array, - columnId: 'process_name', - }, - { - title: 'PID', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'pid', - }, - { - title: 'Thread', - kind: 'STRING', - columnConstructor: Uint16Array, - columnId: 'thread_name', - }, - { - title: 'TID', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'tid', - }, - { - title: 'Wall duration (ms)', - kind: 'TIMESTAMP_NS', - columnConstructor: Float64Array, - columnId: 'total_dur', - sum: true, - }, - { - title: 'Avg Wall duration (ms)', - kind: 'TIMESTAMP_NS', - columnConstructor: Float64Array, - columnId: 'avg_dur', - }, - { - title: 'Occurrences', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'occurrences', - sum: true, - }, - ]; - } -} diff --git a/third_party/perfetto/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts b/third_party/perfetto/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts deleted file mode 100644 index b28e49656709..000000000000 --- a/third_party/perfetto/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {ColumnDef} from '../../common/aggregation_data'; -import {Engine} from '../../common/engine'; -import {Area, Sorting} from '../../common/state'; -import {toNs} from '../../common/time'; -import {globals} from '../../frontend/globals'; -import {Config, CPU_SLICE_TRACK_KIND} from '../../tracks/cpu_slices'; - -import {AggregationController} from './aggregation_controller'; - -export class CpuByProcessAggregationController extends AggregationController { - async createAggregateView(engine: Engine, area: Area) { - await engine.query(`drop view if exists ${this.kind};`); - - const selectedCpus = []; - for (const trackId of area.tracks) { - const track = globals.state.tracks[trackId]; - // Track will be undefined for track groups. - if (track !== undefined && track.kind === CPU_SLICE_TRACK_KIND) { - selectedCpus.push((track.config as Config).cpu); - } - } - if (selectedCpus.length === 0) return false; - - const query = `create view ${this.kind} as - SELECT process.name as process_name, pid, - sum(dur) AS total_dur, - sum(dur)/count(1) as avg_dur, - count(1) as occurrences - FROM process - JOIN thread USING(upid) - JOIN thread_state USING(utid) - WHERE cpu IN (${selectedCpus}) AND - state = "Running" AND - thread_state.ts + thread_state.dur > ${toNs(area.startSec)} AND - thread_state.ts < ${toNs(area.endSec)} group by upid`; - - await engine.query(query); - return true; - } - - getTabName() { - return 'CPU by process'; - } - - async getExtra() {} - - getDefaultSorting(): Sorting { - return {column: 'total_dur', direction: 'DESC'}; - } - - getColumnDefinitions(): ColumnDef[] { - return [ - { - title: 'Process', - kind: 'STRING', - columnConstructor: Uint16Array, - columnId: 'process_name', - }, - { - title: 'PID', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'pid', - }, - { - title: 'Wall duration (ms)', - kind: 'TIMESTAMP_NS', - columnConstructor: Float64Array, - columnId: 'total_dur', - sum: true, - }, - { - title: 'Avg Wall duration (ms)', - kind: 'TIMESTAMP_NS', - columnConstructor: Float64Array, - columnId: 'avg_dur', - }, - { - title: 'Occurrences', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'occurrences', - sum: true, - }, - ]; - } -} diff --git a/third_party/perfetto/ui/src/controller/aggregation/frame_aggregation_controller.ts b/third_party/perfetto/ui/src/controller/aggregation/frame_aggregation_controller.ts deleted file mode 100644 index 97e1f86f0b2b..000000000000 --- a/third_party/perfetto/ui/src/controller/aggregation/frame_aggregation_controller.ts +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {ColumnDef} from '../../common/aggregation_data'; -import {Engine} from '../../common/engine'; -import {Area, Sorting} from '../../common/state'; -import {toNs} from '../../common/time'; -import {globals} from '../../frontend/globals'; -import { - ACTUAL_FRAMES_SLICE_TRACK_KIND, - Config, -} from '../../tracks/actual_frames'; - -import {AggregationController} from './aggregation_controller'; - -export class FrameAggregationController extends AggregationController { - async createAggregateView(engine: Engine, area: Area) { - await engine.query(`drop view if exists ${this.kind};`); - - const selectedSqlTrackIds = []; - for (const trackId of area.tracks) { - const track = globals.state.tracks[trackId]; - // Track will be undefined for track groups. - if (track !== undefined && - track.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) { - selectedSqlTrackIds.push((track.config as Config).trackIds); - } - } - if (selectedSqlTrackIds.length === 0) return false; - - const query = `create view ${this.kind} as - SELECT - jank_type, - count(1) as occurrences, - MIN(dur) as minDur, - AVG(dur) as meanDur, - MAX(dur) as maxDur - FROM actual_frame_timeline_slice - WHERE track_id IN (${selectedSqlTrackIds}) AND - ts + dur > ${toNs(area.startSec)} AND - ts < ${toNs(area.endSec)} group by jank_type`; - - await engine.query(query); - return true; - } - - getTabName() { - return 'Frames'; - } - - async getExtra() {} - - getDefaultSorting(): Sorting { - return {column: 'occurrences', direction: 'DESC'}; - } - - getColumnDefinitions(): ColumnDef[] { - return [ - { - title: 'Jank Type', - kind: 'STRING', - columnConstructor: Uint16Array, - columnId: 'jank_type', - }, - { - title: 'Min duration', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'minDur', - }, - { - title: 'Max duration', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'maxDur', - }, - { - title: 'Mean duration', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'meanDur', - }, - { - title: 'Occurrences', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'occurrences', - sum: true, - }, - ]; - } -} diff --git a/third_party/perfetto/ui/src/controller/aggregation/slice_aggregation_controller.ts b/third_party/perfetto/ui/src/controller/aggregation/slice_aggregation_controller.ts deleted file mode 100644 index ebb80007b1d8..000000000000 --- a/third_party/perfetto/ui/src/controller/aggregation/slice_aggregation_controller.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {ColumnDef} from '../../common/aggregation_data'; -import {Engine} from '../../common/engine'; -import {Area, Sorting} from '../../common/state'; -import {toNs} from '../../common/time'; -import {globals} from '../../frontend/globals'; -import { - ASYNC_SLICE_TRACK_KIND, - Config as AsyncSliceConfig, -} from '../../tracks/async_slices'; -import { - Config as SliceConfig, - SLICE_TRACK_KIND, -} from '../../tracks/chrome_slices'; - -import {AggregationController} from './aggregation_controller'; - -export function getSelectedTrackIds(area: Area): number[] { - const selectedTrackIds = []; - for (const trackId of area.tracks) { - const track = globals.state.tracks[trackId]; - // Track will be undefined for track groups. - if (track !== undefined) { - if (track.kind === SLICE_TRACK_KIND) { - selectedTrackIds.push((track.config as SliceConfig).trackId); - } - if (track.kind === ASYNC_SLICE_TRACK_KIND) { - const config = track.config as AsyncSliceConfig; - for (const id of config.trackIds) { - selectedTrackIds.push(id); - } - } - } - } - return selectedTrackIds; -} - -export class SliceAggregationController extends AggregationController { - async createAggregateView(engine: Engine, area: Area) { - await engine.query(`drop view if exists ${this.kind};`); - - const selectedTrackIds = getSelectedTrackIds(area); - - if (selectedTrackIds.length === 0) return false; - - const query = `create view ${this.kind} as - SELECT - name, - sum(dur) AS total_dur, - sum(dur)/count(1) as avg_dur, - count(1) as occurrences - FROM slices - WHERE track_id IN (${selectedTrackIds}) AND - ts + dur > ${toNs(area.startSec)} AND - ts < ${toNs(area.endSec)} group by name`; - - await engine.query(query); - return true; - } - - getTabName() { - return 'Slices'; - } - - async getExtra() {} - - getDefaultSorting(): Sorting { - return {column: 'total_dur', direction: 'DESC'}; - } - - getColumnDefinitions(): ColumnDef[] { - return [ - { - title: 'Name', - kind: 'STRING', - columnConstructor: Uint32Array, - columnId: 'name', - }, - { - title: 'Wall duration (ms)', - kind: 'TIMESTAMP_NS', - columnConstructor: Float64Array, - columnId: 'total_dur', - sum: true, - }, - { - title: 'Avg Wall duration (ms)', - kind: 'TIMESTAMP_NS', - columnConstructor: Float64Array, - columnId: 'avg_dur', - }, - { - title: 'Occurrences', - kind: 'NUMBER', - columnConstructor: Uint32Array, - columnId: 'occurrences', - sum: true, - }, - ]; - } -} diff --git a/third_party/perfetto/ui/src/controller/aggregation/thread_aggregation_controller.ts b/third_party/perfetto/ui/src/controller/aggregation/thread_aggregation_controller.ts deleted file mode 100644 index ddfadaea0619..000000000000 --- a/third_party/perfetto/ui/src/controller/aggregation/thread_aggregation_controller.ts +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {ColumnDef, ThreadStateExtra} from '../../common/aggregation_data'; -import {Engine} from '../../common/engine'; -import {NUM, NUM_NULL, STR_NULL} from '../../common/query_result'; -import {Area, Sorting} from '../../common/state'; -import {translateState} from '../../common/thread_state'; -import {toNs} from '../../common/time'; -import {globals} from '../../frontend/globals'; -import { - Config, - THREAD_STATE_TRACK_KIND, -} from '../../tracks/thread_state'; - -import {AggregationController} from './aggregation_controller'; - -export class ThreadAggregationController extends AggregationController { - private utids?: number[]; - - setThreadStateUtids(tracks: string[]) { - this.utids = []; - for (const trackId of tracks) { - const track = globals.state.tracks[trackId]; - // Track will be undefined for track groups. - if (track !== undefined && track.kind === THREAD_STATE_TRACK_KIND) { - this.utids.push((track.config as Config).utid); - } - } - } - - async createAggregateView(engine: Engine, area: Area) { - await engine.query(`drop view if exists ${this.kind};`); - this.setThreadStateUtids(area.tracks); - if (this.utids === undefined || this.utids.length === 0) return false; - - const query = ` - create view ${this.kind} as - SELECT - process.name as process_name, - pid, - thread.name as thread_name, - tid, - state || ',' || IFNULL(io_wait, 'NULL') as concat_state, - sum(dur) AS total_dur, - sum(dur)/count(1) as avg_dur, - count(1) as occurrences - FROM process - JOIN thread USING(upid) - JOIN thread_state USING(utid) - WHERE utid IN (${this.utids}) AND - thread_state.ts + thread_state.dur > ${toNs(area.startSec)} AND - thread_state.ts < ${toNs(area.endSec)} - GROUP BY utid, concat_state - `; - - await engine.query(query); - return true; - } - - async getExtra(engine: Engine, area: Area): Promise { - this.setThreadStateUtids(area.tracks); - if (this.utids === undefined || this.utids.length === 0) return; - - const query = - `select state, io_wait as ioWait, sum(dur) as totalDur from process - JOIN thread USING(upid) - JOIN thread_state USING(utid) - WHERE utid IN (${this.utids}) AND thread_state.ts + thread_state.dur > ${ - toNs(area.startSec)} AND - thread_state.ts < ${toNs(area.endSec)} - GROUP BY state, io_wait`; - const result = await engine.query(query); - - const it = result.iter({ - state: STR_NULL, - ioWait: NUM_NULL, - totalDur: NUM, - }); - - const summary: ThreadStateExtra = { - kind: 'THREAD_STATE', - states: [], - values: new Float64Array(result.numRows()), - totalMs: 0, - }; - summary.totalMs = 0; - for (let i = 0; it.valid(); ++i, it.next()) { - const state = it.state == null ? undefined : it.state; - const ioWait = it.ioWait === null ? undefined : it.ioWait > 0; - summary.states.push(translateState(state, ioWait)); - const ms = it.totalDur / 1000000; - summary.values[i] = ms; - summary.totalMs += ms; - } - return summary; - } - - getColumnDefinitions(): ColumnDef[] { - return [ - { - title: 'Process', - kind: 'STRING', - columnConstructor: Uint16Array, - columnId: 'process_name', - }, - { - title: 'PID', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'pid', - }, - { - title: 'Thread', - kind: 'STRING', - columnConstructor: Uint16Array, - columnId: 'thread_name', - }, - { - title: 'TID', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'tid', - }, - { - title: 'State', - kind: 'STATE', - columnConstructor: Uint16Array, - columnId: 'concat_state', - }, - { - title: 'Wall duration (ms)', - kind: 'TIMESTAMP_NS', - columnConstructor: Float64Array, - columnId: 'total_dur', - sum: true, - }, - { - title: 'Avg Wall duration (ms)', - kind: 'TIMESTAMP_NS', - columnConstructor: Float64Array, - columnId: 'avg_dur', - }, - { - title: 'Occurrences', - kind: 'NUMBER', - columnConstructor: Uint16Array, - columnId: 'occurrences', - sum: true, - }, - ]; - } - - getTabName() { - return 'Thread States'; - } - - getDefaultSorting(): Sorting { - return {column: 'total_dur', direction: 'DESC'}; - } -} diff --git a/third_party/perfetto/ui/src/controller/app_controller.ts b/third_party/perfetto/ui/src/controller/app_controller.ts deleted file mode 100644 index 580e3307df3e..000000000000 --- a/third_party/perfetto/ui/src/controller/app_controller.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {RECORDING_V2_FLAG} from '../common/feature_flags'; -import {globals} from '../frontend/globals'; - -import {Child, Controller, ControllerInitializerAny} from './controller'; -import {PermalinkController} from './permalink_controller'; -import {RecordController} from './record_controller'; -import {TraceController} from './trace_controller'; - -// The root controller for the entire app. It handles the lifetime of all -// the other controllers (e.g., track and query controllers) according to the -// global state. -export class AppController extends Controller<'main'> { - // extensionPort is needed for the RecordController to communicate with the - // extension through the frontend. This is because the controller is running - // on a worker, and isn't able to directly send messages to the extension. - private extensionPort: MessagePort; - - constructor(extensionPort: MessagePort) { - super('main'); - this.extensionPort = extensionPort; - } - - // This is the root method that is called every time the controller tree is - // re-triggered. This can happen due to: - // - An action received from the frontend. - // - An internal promise of a nested controller being resolved and manually - // re-triggering the controllers. - run() { - const childControllers: ControllerInitializerAny[] = - [Child('permalink', PermalinkController, {})]; - if (!RECORDING_V2_FLAG.get()) { - childControllers.push(Child( - 'record', RecordController, {extensionPort: this.extensionPort})); - } - if (globals.state.engine !== undefined) { - const engineCfg = globals.state.engine; - childControllers.push(Child(engineCfg.id, TraceController, engineCfg.id)); - } - return childControllers; - } -} diff --git a/third_party/perfetto/ui/src/controller/area_selection_handler.ts b/third_party/perfetto/ui/src/controller/area_selection_handler.ts deleted file mode 100644 index b1d1c7e71110..000000000000 --- a/third_party/perfetto/ui/src/controller/area_selection_handler.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Area, AreaById} from '../common/state'; -import {globals} from '../frontend/globals'; - -export class AreaSelectionHandler { - private previousArea?: Area; - - getAreaChange(): [boolean, AreaById|undefined] { - const currentSelection = globals.state.currentSelection; - if (currentSelection === null || currentSelection.kind !== 'AREA') { - return [false, undefined]; - } - - const selectedArea = globals.state.areas[currentSelection.areaId]; - // Area is considered changed if: - // 1. The new area is defined and the old area undefined. - // 2. The new area is undefined and the old area defined (viceversa from 1). - // 3. Both areas are defined but their start or end times differ. - // 4. Both areas are defined but their tracks differ. - let hasAreaChanged = (!!this.previousArea !== !!selectedArea); - if (selectedArea && this.previousArea) { - // There seems to be an issue with clang-format http://shortn/_Pt98d5MCjG - // where `a ||= b` is formatted to `a || = b`, by inserting a space which - // breaks the operator. - // Therefore, we are using the pattern `a = a || b` instead. - hasAreaChanged = hasAreaChanged || - selectedArea.startSec !== this.previousArea.startSec; - hasAreaChanged = - hasAreaChanged || selectedArea.endSec !== this.previousArea.endSec; - hasAreaChanged = hasAreaChanged || - selectedArea.tracks.length !== this.previousArea.tracks.length; - for (let i = 0; i < selectedArea.tracks.length; ++i) { - hasAreaChanged = hasAreaChanged || - selectedArea.tracks[i] !== this.previousArea.tracks[i]; - } - } - - if (hasAreaChanged) { - this.previousArea = selectedArea; - } - - return [hasAreaChanged, selectedArea]; - } -} diff --git a/third_party/perfetto/ui/src/controller/area_selection_handler_unittest.ts b/third_party/perfetto/ui/src/controller/area_selection_handler_unittest.ts deleted file mode 100644 index c5a27c032a6f..000000000000 --- a/third_party/perfetto/ui/src/controller/area_selection_handler_unittest.ts +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {createEmptyState} from '../common/empty_state'; -import {AreaById} from '../common/state'; -import {globals} from '../frontend/globals'; - -import {AreaSelectionHandler} from './area_selection_handler'; - -test('validAreaAfterUndefinedArea', () => { - const areaId = '0'; - const latestArea: AreaById = {startSec: 0, endSec: 1, tracks: [], id: areaId}; - globals.state = createEmptyState(); - globals.state.currentSelection = {kind: 'AREA', areaId}; - globals.state.areas[areaId] = latestArea; - - const areaSelectionHandler = new AreaSelectionHandler(); - const [hasAreaChanged, selectedArea] = areaSelectionHandler.getAreaChange(); - - expect(hasAreaChanged).toEqual(true); - expect(selectedArea).toEqual(latestArea); -}); - -test('UndefinedAreaAfterValidArea', () => { - const previousAreaId = '0'; - const previous: - AreaById = {startSec: 0, endSec: 1, tracks: [], id: previousAreaId}; - globals.state = createEmptyState(); - globals.state.currentSelection = { - kind: 'AREA', - areaId: previousAreaId, - }; - globals.state.areas[previousAreaId] = previous; - const areaSelectionHandler = new AreaSelectionHandler(); - areaSelectionHandler.getAreaChange(); - - const currentAreaId = '1'; - globals.state.currentSelection = { - kind: 'AREA', - areaId: currentAreaId, - }; - const [hasAreaChanged, selectedArea] = areaSelectionHandler.getAreaChange(); - - expect(hasAreaChanged).toEqual(true); - expect(selectedArea).toEqual(undefined); -}); - -test('UndefinedAreaAfterUndefinedArea', () => { - globals.state.currentSelection = {kind: 'AREA', areaId: '0'}; - const areaSelectionHandler = new AreaSelectionHandler(); - areaSelectionHandler.getAreaChange(); - - globals.state.currentSelection = {kind: 'AREA', areaId: '1'}; - const [hasAreaChanged, selectedArea] = areaSelectionHandler.getAreaChange(); - - expect(hasAreaChanged).toEqual(true); - expect(selectedArea).toEqual(undefined); -}); - -test('validAreaAfterValidArea', () => { - const previousAreaId = '0'; - const previous: - AreaById = {startSec: 0, endSec: 1, tracks: [], id: previousAreaId}; - globals.state = createEmptyState(); - globals.state.currentSelection = { - kind: 'AREA', - areaId: previousAreaId, - }; - globals.state.areas[previousAreaId] = previous; - const areaSelectionHandler = new AreaSelectionHandler(); - areaSelectionHandler.getAreaChange(); - - const currentAreaId = '1'; - const current: - AreaById = {startSec: 1, endSec: 2, tracks: [], id: currentAreaId}; - globals.state.currentSelection = { - kind: 'AREA', - areaId: currentAreaId, - }; - globals.state.areas[currentAreaId] = current; - const [hasAreaChanged, selectedArea] = areaSelectionHandler.getAreaChange(); - - expect(hasAreaChanged).toEqual(true); - expect(selectedArea).toEqual(current); -}); - -test('sameAreaSelected', () => { - const previousAreaId = '0'; - const previous: - AreaById = {startSec: 0, endSec: 1, tracks: [], id: previousAreaId}; - globals.state = createEmptyState(); - globals.state.currentSelection = { - kind: 'AREA', - areaId: previousAreaId, - }; - globals.state.areas[previousAreaId] = previous; - const areaSelectionHandler = new AreaSelectionHandler(); - areaSelectionHandler.getAreaChange(); - - const currentAreaId = '0'; - const current: - AreaById = {startSec: 0, endSec: 1, tracks: [], id: currentAreaId}; - globals.state.currentSelection = { - kind: 'AREA', - areaId: currentAreaId, - }; - globals.state.areas[currentAreaId] = current; - const [hasAreaChanged, selectedArea] = areaSelectionHandler.getAreaChange(); - - expect(hasAreaChanged).toEqual(false); - expect(selectedArea).toEqual(current); -}); - -test('NonAreaSelectionAfterUndefinedArea', () => { - globals.state.currentSelection = {kind: 'AREA', areaId: '0'}; - const areaSelectionHandler = new AreaSelectionHandler(); - areaSelectionHandler.getAreaChange(); - - globals.state - .currentSelection = {kind: 'COUNTER', leftTs: 0, rightTs: 0, id: 1}; - const [hasAreaChanged, selectedArea] = areaSelectionHandler.getAreaChange(); - - expect(hasAreaChanged).toEqual(false); - expect(selectedArea).toEqual(undefined); -}); diff --git a/third_party/perfetto/ui/src/controller/args_parser.ts b/third_party/perfetto/ui/src/controller/args_parser.ts deleted file mode 100644 index 30afa1e08678..000000000000 --- a/third_party/perfetto/ui/src/controller/args_parser.ts +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - Args, - ArgsTree, - ArgsTreeArray, - ArgsTreeMap, - isArgTreeArray, - isArgTreeMap, -} from '../common/arg_types'; - -// Converts a flats sequence of key-value pairs into a JSON-like nested -// structure. Dots in keys are used to create a nested dictionary, indices in -// brackets used to create nested array. For example, consider the following -// sequence of key-value pairs: -// -// simple_key = simple_value -// thing.key = value -// thing.point[0].x = 10 -// thing.point[0].y = 20 -// thing.point[1].x = 0 -// thing.point[1].y = -10 -// -// It's going to be converted to a following object: -// -// { -// "simple_key": "simple_value", -// "thing": { -// "key": "value", -// "point": [ -// { "x": "10", "y": "20" }, -// { "x": "0", "y": "-10" } -// ] -// } -// } -export function parseArgs(args: Args): ArgsTree|undefined { - const result: ArgsTreeMap = {}; - for (const [key, value] of args) { - if (typeof value === 'string') { - fillObject(result, key.split('.'), value); - } - } - return result; -} - -function getOrCreateMap( - object: ArgsTreeMap|ArgsTreeArray, key: string|number): ArgsTreeMap { - let value: ArgsTree; - if (isArgTreeMap(object) && typeof key === 'string') { - value = object[key]; - } else if (isArgTreeArray(object) && typeof key === 'number') { - value = object[key]; - } else { - throw new Error('incompatible parameters to getOrCreateSubmap'); - } - - if (value !== undefined) { - if (isArgTreeMap(value)) { - return value; - } else { - // There is a value, but it's not a map - something wrong with the key set - throw new Error('inconsistent keys'); - } - } - - value = {}; - if (isArgTreeMap(object) && typeof key === 'string') { - object[key] = value; - } else if (isArgTreeArray(object) && typeof key === 'number') { - object[key] = value; - } - - return value; -} - -function getOrCreateArray(object: ArgsTreeMap, key: string): ArgsTree[] { - let value = object[key]; - if (value !== undefined) { - if (isArgTreeArray(value)) { - return value; - } else { - // There is a value, but it's not an array - something wrong with the key - // set - throw new Error('inconsistent keys'); - } - } - - value = []; - object[key] = value; - return value; -} - -function fillObject(object: ArgsTreeMap, path: string[], value: string) { - let current = object; - for (let i = 0; i < path.length - 1; i++) { - const [part, index] = parsePathSegment(path[i]); - if (index === undefined) { - current = getOrCreateMap(current, part); - } else { - const array = getOrCreateArray(current, part); - current = getOrCreateMap(array, index); - } - } - - const [part, index] = parsePathSegment(path[path.length - 1]); - if (index === undefined) { - current[part] = value; - } else { - const array = getOrCreateArray(current, part); - array[index] = value; - } -} - -// Segment is either a simple key (e.g. "foo") or a key with an index (e.g. -// "bar[42]"). This function returns a pair of key and index (if present). -function parsePathSegment(segment: string): [string, number?] { - if (!segment.endsWith(']')) { - return [segment, undefined]; - } - - const indexStart = segment.indexOf('['); - const indexString = segment.substring(indexStart + 1, segment.length - 1); - return [segment.substring(0, indexStart), Math.floor(Number(indexString))]; -} diff --git a/third_party/perfetto/ui/src/controller/chrome_proxy_record_controller.ts b/third_party/perfetto/ui/src/controller/chrome_proxy_record_controller.ts deleted file mode 100644 index 1444478abd7f..000000000000 --- a/third_party/perfetto/ui/src/controller/chrome_proxy_record_controller.ts +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {binaryDecode, binaryEncode} from '../base/string_utils'; -import {TRACE_SUFFIX} from '../common/constants'; - -import { - ConsumerPortResponse, - hasProperty, - isReadBuffersResponse, - Typed, -} from './consumer_port_types'; -import {Consumer, RpcConsumerPort} from './record_controller_interfaces'; - -export interface ChromeExtensionError extends Typed { - error: string; -} - -export interface ChromeExtensionStatus extends Typed { - status: string; -} - -export interface GetCategoriesResponse extends Typed { - categories: string[]; -} - -export type ChromeExtensionMessage = ChromeExtensionError|ChromeExtensionStatus| - ConsumerPortResponse|GetCategoriesResponse; - -export function isChromeExtensionError(obj: Typed): - obj is ChromeExtensionError { - return obj.type === 'ChromeExtensionError'; -} - -export function isChromeExtensionStatus(obj: Typed): - obj is ChromeExtensionStatus { - return obj.type === 'ChromeExtensionStatus'; -} - -function isObject(obj: unknown): obj is object { - return typeof obj === 'object' && obj !== null; -} - -export function isGetCategoriesResponse(obj: unknown): - obj is GetCategoriesResponse { - if (!(isObject(obj) && hasProperty(obj, 'type') && - obj.type === 'GetCategoriesResponse')) { - return false; - } - - return hasProperty(obj, 'categories') && Array.isArray(obj.categories); -} - -// This class acts as a proxy from the record controller (running in a worker), -// to the frontend. This is needed because we can't directly talk with the -// extension from a web-worker, so we use a MessagePort to communicate with the -// frontend, that will consecutively forward it to the extension. - -// Rationale for the binaryEncode / binaryDecode calls below: -// Messages to/from extensions need to be JSON serializable. ArrayBuffers are -// not supported. For this reason here we use binaryEncode/Decode. -// See https://developer.chrome.com/extensions/messaging#simple - -export class ChromeExtensionConsumerPort extends RpcConsumerPort { - private extensionPort: MessagePort; - - constructor(extensionPort: MessagePort, consumer: Consumer) { - super(consumer); - this.extensionPort = extensionPort; - this.extensionPort.onmessage = this.onExtensionMessage.bind(this); - } - - onExtensionMessage(message: {data: ChromeExtensionMessage}) { - if (isChromeExtensionError(message.data)) { - this.sendErrorMessage(message.data.error); - return; - } - if (isChromeExtensionStatus(message.data)) { - this.sendStatus(message.data.status); - return; - } - - // In this else branch message.data will be a ConsumerPortResponse. - if (isReadBuffersResponse(message.data) && message.data.slices) { - const slice = message.data.slices[0].data as unknown as string; - message.data.slices[0].data = binaryDecode(slice); - } - this.sendMessage(message.data); - } - - handleCommand(method: string, requestData: Uint8Array): void { - const reqEncoded = binaryEncode(requestData); - this.extensionPort.postMessage({method, requestData: reqEncoded}); - } - - getRecordedTraceSuffix(): string { - return `${TRACE_SUFFIX}.gz`; - } -} diff --git a/third_party/perfetto/ui/src/controller/consumer_port_types.ts b/third_party/perfetto/ui/src/controller/consumer_port_types.ts deleted file mode 100644 index 8b3cd453c0a9..000000000000 --- a/third_party/perfetto/ui/src/controller/consumer_port_types.ts +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {perfetto} from '../gen/protos'; - -export interface Typed { - type: string; -} - -// A type guard that can be used in order to be able to access the property of -// an object in a checked manner. -export function hasProperty( - obj: T, prop: P): obj is T&{[prop in P]: unknown} { - return obj.hasOwnProperty(prop); -} - -export function isTyped(obj: object): obj is Typed { - return obj.hasOwnProperty('type'); -} - -export interface ReadBuffersResponse extends - Typed, perfetto.protos.IReadBuffersResponse {} -export interface EnableTracingResponse extends - Typed, perfetto.protos.IEnableTracingResponse {} -export interface GetTraceStatsResponse extends - Typed, perfetto.protos.IGetTraceStatsResponse {} -export interface FreeBuffersResponse extends - Typed, perfetto.protos.IFreeBuffersResponse {} -export interface GetCategoriesResponse extends Typed {} -export interface DisableTracingResponse extends - Typed, perfetto.protos.IDisableTracingResponse {} - -export type ConsumerPortResponse = - EnableTracingResponse|ReadBuffersResponse|GetTraceStatsResponse| - GetCategoriesResponse|FreeBuffersResponse|DisableTracingResponse; - -export function isReadBuffersResponse(obj: Typed): obj is ReadBuffersResponse { - return obj.type === 'ReadBuffersResponse'; -} - -export function isEnableTracingResponse(obj: Typed): - obj is EnableTracingResponse { - return obj.type === 'EnableTracingResponse'; -} - -export function isGetTraceStatsResponse(obj: Typed): - obj is GetTraceStatsResponse { - return obj.type === 'GetTraceStatsResponse'; -} - -export function isFreeBuffersResponse(obj: Typed): obj is FreeBuffersResponse { - return obj.type === 'FreeBuffersResponse'; -} - -export function isDisableTracingResponse(obj: Typed): - obj is DisableTracingResponse { - return obj.type === 'DisableTracingResponse'; -} diff --git a/third_party/perfetto/ui/src/controller/controller.ts b/third_party/perfetto/ui/src/controller/controller.ts deleted file mode 100644 index a246279d123c..000000000000 --- a/third_party/perfetto/ui/src/controller/controller.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export type ControllerAny = Controller; - -export interface ControllerFactory { - new(args: ConstructorArgs): ControllerAny; -} - -interface ControllerInitializer { - id: string; - factory: ControllerFactory; - args: ConstructorArgs; -} - -export type ControllerInitializerAny = ControllerInitializer; - -export function Child( - id: string, - factory: ControllerFactory, - args: ConstructorArgs): ControllerInitializer { - return {id, factory, args}; -} - -export type Children = ControllerInitializerAny[]; - -export abstract class Controller { - // This is about the local FSM state, has nothing to do with the global - // app state. - private _stateChanged = false; - private _inRunner = false; - private _state: StateType; - private _children = new Map(); - - constructor(initialState: StateType) { - this._state = initialState; - } - - abstract run(): Children|void; - onDestroy(): void {} - - // Invokes the current controller subtree, recursing into children. - // While doing so handles lifecycle of child controllers. - // This method should be called only by the runControllers() method in - // globals.ts. Exposed publicly for testing. - invoke(): boolean { - if (this._inRunner) throw new Error('Reentrancy in Controller'); - this._stateChanged = false; - this._inRunner = true; - const resArray = this.run(); - let triggerAnotherRun = this._stateChanged; - this._stateChanged = false; - - const nextChildren = new Map(); - if (resArray !== undefined) { - for (const childConfig of resArray) { - if (nextChildren.has(childConfig.id)) { - throw new Error(`Duplicate children controller ${childConfig.id}`); - } - nextChildren.set(childConfig.id, childConfig); - } - } - const dtors = new Array<(() => void)>(); - const runners = new Array<(() => boolean)>(); - for (const key of this._children.keys()) { - if (nextChildren.has(key)) continue; - const instance = this._children.get(key)!; - this._children.delete(key); - dtors.push(() => instance.onDestroy()); - } - for (const nextChild of nextChildren.values()) { - if (!this._children.has(nextChild.id)) { - const instance = new nextChild.factory(nextChild.args); - this._children.set(nextChild.id, instance); - } - const instance = this._children.get(nextChild.id)!; - runners.push(() => instance.invoke()); - } - - for (const dtor of dtors) dtor(); // Invoke all onDestroy()s. - - // Invoke all runner()s. - for (const runner of runners) { - const recursiveRes = runner(); - triggerAnotherRun = triggerAnotherRun || recursiveRes; - } - - this._inRunner = false; - return triggerAnotherRun; - } - - setState(state: StateType) { - if (!this._inRunner) { - throw new Error('Cannot setState() outside of the run() method'); - } - this._stateChanged = state !== this._state; - this._state = state; - } - - get state(): StateType { - return this._state; - } -} diff --git a/third_party/perfetto/ui/src/controller/controller_unittest.ts b/third_party/perfetto/ui/src/controller/controller_unittest.ts deleted file mode 100644 index 90410869e460..000000000000 --- a/third_party/perfetto/ui/src/controller/controller_unittest.ts +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - Child, - Controller, -} from './controller'; - -const _onCreate = jest.fn(); -const _onDestroy = jest.fn(); -const _run = jest.fn(); - -type MockStates = 'idle'|'state1'|'state2'|'state3'; -class MockController extends Controller { - constructor(public type: string) { - super('idle'); - _onCreate(this.type); - } - - run() { - return _run(this.type); - } - - onDestroy() { - return _onDestroy(this.type); - } -} - -function runControllerTree(rootController: MockController): void { - for (let runAgain = true, i = 0; runAgain; i++) { - if (i >= 100) throw new Error('Controller livelock'); - runAgain = rootController.invoke(); - } -} - -beforeEach(() => { - _onCreate.mockClear(); - _onCreate.mockReset(); - _onDestroy.mockClear(); - _onDestroy.mockReset(); - _run.mockClear(); - _run.mockReset(); -}); - -test('singleControllerNoTransition', () => { - const rootCtl = new MockController('root'); - runControllerTree(rootCtl); - expect(_run).toHaveBeenCalledTimes(1); - expect(_run).toHaveBeenCalledWith('root'); -}); - -test('singleControllerThreeTransitions', () => { - const rootCtl = new MockController('root'); - _run.mockImplementation(() => { - if (rootCtl.state === 'idle') { - rootCtl.setState('state1'); - } else if (rootCtl.state === 'state1') { - rootCtl.setState('state2'); - } - }); - runControllerTree(rootCtl); - expect(_run).toHaveBeenCalledTimes(3); - expect(_run).toHaveBeenCalledWith('root'); -}); - -test('nestedControllers', () => { - const rootCtl = new MockController('root'); - let nextState: MockStates = 'idle'; - _run.mockImplementation((type: string) => { - if (type !== 'root') return; - rootCtl.setState(nextState); - if (rootCtl.state === 'idle') return; - - if (rootCtl.state === 'state1') { - return [ - Child('child1', MockController, 'child1'), - ]; - } - if (rootCtl.state === 'state2') { - return [ - Child('child1', MockController, 'child1'), - Child('child2', MockController, 'child2'), - ]; - } - if (rootCtl.state === 'state3') { - return [ - Child('child1', MockController, 'child1'), - Child('child3', MockController, 'child3'), - ]; - } - throw new Error('Not reached'); - }); - runControllerTree(rootCtl); - expect(_run).toHaveBeenCalledWith('root'); - expect(_run).toHaveBeenCalledTimes(1); - - // Transition the root controller to state1. This will create the first child - // and re-run both (because of the idle -> state1 transition). - _run.mockClear(); - _onCreate.mockClear(); - nextState = 'state1'; - runControllerTree(rootCtl); - expect(_onCreate).toHaveBeenCalledWith('child1'); - expect(_onCreate).toHaveBeenCalledTimes(1); - expect(_run).toHaveBeenCalledWith('root'); - expect(_run).toHaveBeenCalledWith('child1'); - expect(_run).toHaveBeenCalledTimes(4); - - // Transition the root controller to state2. This will create the 2nd child - // and run the three of them (root + 2 chilren) two times. - _run.mockClear(); - _onCreate.mockClear(); - nextState = 'state2'; - runControllerTree(rootCtl); - expect(_onCreate).toHaveBeenCalledWith('child2'); - expect(_onCreate).toHaveBeenCalledTimes(1); - expect(_run).toHaveBeenCalledWith('root'); - expect(_run).toHaveBeenCalledWith('child1'); - expect(_run).toHaveBeenCalledWith('child2'); - expect(_run).toHaveBeenCalledTimes(6); - - // Transition the root controller to state3. This will create the 3rd child - // and remove the 2nd one. - _run.mockClear(); - _onCreate.mockClear(); - nextState = 'state3'; - runControllerTree(rootCtl); - expect(_onCreate).toHaveBeenCalledWith('child3'); - expect(_onDestroy).toHaveBeenCalledWith('child2'); - expect(_onCreate).toHaveBeenCalledTimes(1); - expect(_run).toHaveBeenCalledWith('root'); - expect(_run).toHaveBeenCalledWith('child1'); - expect(_run).toHaveBeenCalledWith('child3'); - expect(_run).toHaveBeenCalledTimes(6); - - // Finally transition back to the idle state. All children should be removed. - _run.mockClear(); - _onCreate.mockClear(); - _onDestroy.mockClear(); - nextState = 'idle'; - runControllerTree(rootCtl); - expect(_onDestroy).toHaveBeenCalledWith('child1'); - expect(_onDestroy).toHaveBeenCalledWith('child3'); - expect(_onCreate).toHaveBeenCalledTimes(0); - expect(_onDestroy).toHaveBeenCalledTimes(2); - expect(_run).toHaveBeenCalledWith('root'); - expect(_run).toHaveBeenCalledTimes(2); -}); diff --git a/third_party/perfetto/ui/src/controller/cpu_profile_controller.ts b/third_party/perfetto/ui/src/controller/cpu_profile_controller.ts deleted file mode 100644 index 0f61cb274d31..000000000000 --- a/third_party/perfetto/ui/src/controller/cpu_profile_controller.ts +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Engine} from '../common/engine'; -import {NUM, STR} from '../common/query_result'; -import {CallsiteInfo, CpuProfileSampleSelection} from '../common/state'; -import {CpuProfileDetails, globals} from '../frontend/globals'; -import {publishCpuProfileDetails} from '../frontend/publish'; - -import {Controller} from './controller'; - -export interface CpuProfileControllerArgs { - engine: Engine; -} - -export class CpuProfileController extends Controller<'main'> { - private lastSelectedSample?: CpuProfileSampleSelection; - private requestingData = false; - private queuedRunRequest = false; - - constructor(private args: CpuProfileControllerArgs) { - super('main'); - } - - run() { - const selection = globals.state.currentSelection; - if (!selection || selection.kind !== 'CPU_PROFILE_SAMPLE') { - return; - } - - const selectedSample = selection as CpuProfileSampleSelection; - if (!this.shouldRequestData(selectedSample)) { - return; - } - - if (this.requestingData) { - this.queuedRunRequest = true; - return; - } - - this.requestingData = true; - publishCpuProfileDetails({}); - this.lastSelectedSample = this.copyCpuProfileSample(selection); - - this.getSampleData(selectedSample.id) - .then((sampleData) => { - if (sampleData !== undefined && selectedSample && - this.lastSelectedSample && - this.lastSelectedSample.id === selectedSample.id) { - const cpuProfileDetails: CpuProfileDetails = { - id: selectedSample.id, - ts: selectedSample.ts, - utid: selectedSample.utid, - stack: sampleData, - }; - - publishCpuProfileDetails(cpuProfileDetails); - } - }) - .finally(() => { - this.requestingData = false; - if (this.queuedRunRequest) { - this.queuedRunRequest = false; - this.run(); - } - }); - } - - private copyCpuProfileSample(cpuProfileSample: CpuProfileSampleSelection): - CpuProfileSampleSelection { - return { - kind: cpuProfileSample.kind, - id: cpuProfileSample.id, - utid: cpuProfileSample.utid, - ts: cpuProfileSample.ts, - }; - } - - private shouldRequestData(selection: CpuProfileSampleSelection) { - return this.lastSelectedSample === undefined || - (this.lastSelectedSample !== undefined && - (this.lastSelectedSample.id !== selection.id)); - } - - async getSampleData(id: number) { - // The goal of the query is to get all the frames of - // the callstack at the callsite given by |id|. To do this, it does - // the following: - // 1. Gets the leaf callsite id for the sample given by |id|. - // 2. For this callsite, get all the frame ids and depths - // for the frame and all ancestors in the callstack. - // 3. For each frame, get the mapping name (i.e. library which - // contains the frame). - // 4. Symbolize each frame using the symbol table if possible. - // 5. Sort the query by the depth of the callstack frames. - const sampleQuery = ` - SELECT - samples.id as id, - IFNULL( - ( - SELECT name - FROM stack_profile_symbol symbol - WHERE symbol.symbol_set_id = spf.symbol_set_id - LIMIT 1 - ), - COALESCE(spf.deobfuscated_name, spf.name) - ) AS name, - spm.name AS mapping - FROM cpu_profile_stack_sample AS samples - LEFT JOIN ( - SELECT - id, - frame_id, - depth - FROM stack_profile_callsite - UNION ALL - SELECT - leaf.id AS id, - callsite.frame_id AS frame_id, - callsite.depth AS depth - FROM stack_profile_callsite leaf - JOIN experimental_ancestor_stack_profile_callsite(leaf.id) AS callsite - ) AS callsites - ON samples.callsite_id = callsites.id - LEFT JOIN stack_profile_frame AS spf - ON callsites.frame_id = spf.id - LEFT JOIN stack_profile_mapping AS spm - ON spf.mapping = spm.id - WHERE samples.id = ${id} - ORDER BY callsites.depth; - `; - - const callsites = await this.args.engine.query(sampleQuery); - - if (callsites.numRows() === 0) { - return undefined; - } - - const it = callsites.iter({ - id: NUM, - name: STR, - mapping: STR, - }); - - const sampleData: CallsiteInfo[] = []; - for (; it.valid(); it.next()) { - sampleData.push({ - id: it.id, - totalSize: 0, - depth: 0, - parentId: 0, - name: it.name, - selfSize: 0, - mapping: it.mapping, - merged: false, - highlighted: false, - }); - } - - return sampleData; - } -} diff --git a/third_party/perfetto/ui/src/controller/flamegraph_controller.ts b/third_party/perfetto/ui/src/controller/flamegraph_controller.ts deleted file mode 100644 index e94531dbe723..000000000000 --- a/third_party/perfetto/ui/src/controller/flamegraph_controller.ts +++ /dev/null @@ -1,507 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Actions} from '../common/actions'; -import {Engine} from '../common/engine'; -import { - ALLOC_SPACE_MEMORY_ALLOCATED_KEY, - DEFAULT_VIEWING_OPTION, - expandCallsites, - findRootSize, - mergeCallsites, - OBJECTS_ALLOCATED_KEY, - OBJECTS_ALLOCATED_NOT_FREED_KEY, - PERF_SAMPLES_KEY, - SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, -} from '../common/flamegraph_util'; -import {NUM, STR} from '../common/query_result'; -import {CallsiteInfo, FlamegraphState, ProfileType} from '../common/state'; -import {toNs} from '../common/time'; -import {FlamegraphDetails, globals} from '../frontend/globals'; -import {publishFlamegraphDetails} from '../frontend/publish'; -import { - Config as PerfSampleConfig, - PERF_SAMPLES_PROFILE_TRACK_KIND, -} from '../tracks/perf_samples_profile'; - -import {AreaSelectionHandler} from './area_selection_handler'; -import {Controller} from './controller'; - -export function profileType(s: string): ProfileType { - if (isProfileType(s)) { - return s; - } - if (s.startsWith('heap_profile')) { - return ProfileType.HEAP_PROFILE; - } - throw new Error('Unknown type ${s}'); -} - -function isProfileType(s: string): s is ProfileType { - return Object.values(ProfileType).includes(s as ProfileType); -} - -function getFlamegraphType(type: ProfileType) { - switch (type) { - case ProfileType.HEAP_PROFILE: - case ProfileType.NATIVE_HEAP_PROFILE: - case ProfileType.JAVA_HEAP_SAMPLES: - return 'native'; - case ProfileType.JAVA_HEAP_GRAPH: - return 'graph'; - case ProfileType.PERF_SAMPLE: - return 'perf'; - default: - throw new Error(`Unexpected profile type ${profileType}`); - } -} - -export interface FlamegraphControllerArgs { - engine: Engine; -} -const MIN_PIXEL_DISPLAYED = 1; - -class TablesCache { - private engine: Engine; - private cache: Map; - private prefix: string; - private tableId: number; - private cacheSizeLimit: number; - - constructor(engine: Engine, prefix: string) { - this.engine = engine; - this.cache = new Map(); - this.prefix = prefix; - this.tableId = 0; - this.cacheSizeLimit = 10; - } - - async getTableName(query: string): Promise { - let tableName = this.cache.get(query); - if (tableName === undefined) { - // TODO(hjd): This should be LRU. - if (this.cache.size > this.cacheSizeLimit) { - for (const name of this.cache.values()) { - await this.engine.query(`drop table ${name}`); - } - this.cache.clear(); - } - tableName = `${this.prefix}_${this.tableId++}`; - await this.engine.query( - `create temp table if not exists ${tableName} as ${query}`); - this.cache.set(query, tableName); - } - return tableName; - } -} - -export class FlamegraphController extends Controller<'main'> { - private flamegraphDatasets: Map = new Map(); - private lastSelectedFlamegraphState?: FlamegraphState; - private requestingData = false; - private queuedRequest = false; - private flamegraphDetails: FlamegraphDetails = {}; - private areaSelectionHandler: AreaSelectionHandler; - private cache: TablesCache; - - constructor(private args: FlamegraphControllerArgs) { - super('main'); - this.cache = new TablesCache(args.engine, 'grouped_callsites'); - this.areaSelectionHandler = new AreaSelectionHandler(); - } - - run() { - const [hasAreaChanged, area] = this.areaSelectionHandler.getAreaChange(); - if (hasAreaChanged) { - const upids = []; - if (!area) { - this.checkCompletionAndPublishFlamegraph( - {...globals.flamegraphDetails, isInAreaSelection: false}); - return; - } - for (const trackId of area.tracks) { - const trackState = globals.state.tracks[trackId]; - if (!trackState || - trackState.kind !== PERF_SAMPLES_PROFILE_TRACK_KIND) { - continue; - } - upids.push((trackState.config as PerfSampleConfig).upid); - } - if (upids.length === 0) { - this.checkCompletionAndPublishFlamegraph( - {...globals.flamegraphDetails, isInAreaSelection: false}); - return; - } - globals.dispatch(Actions.openFlamegraph({ - upids, - startNs: toNs(area.startSec), - endNs: toNs(area.endSec), - type: ProfileType.PERF_SAMPLE, - viewingOption: PERF_SAMPLES_KEY, - })); - } - const selection = globals.state.currentFlamegraphState; - if (!selection || !this.shouldRequestData(selection)) { - return; - } - if (this.requestingData) { - this.queuedRequest = true; - return; - } - this.requestingData = true; - - this.assembleFlamegraphDetails(selection, area !== undefined); - } - - private async assembleFlamegraphDetails( - selection: FlamegraphState, isInAreaSelection: boolean) { - const selectedFlamegraphState = {...selection}; - const flamegraphMetadata = await this.getFlamegraphMetadata( - selection.type, - selectedFlamegraphState.startNs, - selectedFlamegraphState.endNs, - selectedFlamegraphState.upids); - if (flamegraphMetadata !== undefined) { - Object.assign(this.flamegraphDetails, flamegraphMetadata); - } - - // TODO(hjd): Clean this up. - if (this.lastSelectedFlamegraphState && - this.lastSelectedFlamegraphState.focusRegex !== selection.focusRegex) { - this.flamegraphDatasets.clear(); - } - - this.lastSelectedFlamegraphState = {...selection}; - - const expandedId = selectedFlamegraphState.expandedCallsite ? - selectedFlamegraphState.expandedCallsite.id : - -1; - const rootSize = selectedFlamegraphState.expandedCallsite === undefined ? - undefined : - selectedFlamegraphState.expandedCallsite.totalSize; - - const key = `${selectedFlamegraphState.upids};${ - selectedFlamegraphState.startNs};${selectedFlamegraphState.endNs}`; - - try { - const flamegraphData = await this.getFlamegraphData( - key, - selectedFlamegraphState.viewingOption ? - selectedFlamegraphState.viewingOption : - DEFAULT_VIEWING_OPTION, - selection.startNs, - selection.endNs, - selectedFlamegraphState.upids, - selectedFlamegraphState.type, - selectedFlamegraphState.focusRegex); - if (flamegraphData !== undefined && selection && - selection.kind === selectedFlamegraphState.kind && - selection.startNs === selectedFlamegraphState.startNs && - selection.endNs === selectedFlamegraphState.endNs) { - const expandedFlamegraphData = - expandCallsites(flamegraphData, expandedId); - this.prepareAndMergeCallsites( - expandedFlamegraphData, - this.lastSelectedFlamegraphState.viewingOption, - isInAreaSelection, - rootSize, - this.lastSelectedFlamegraphState.expandedCallsite); - } - } finally { - this.requestingData = false; - if (this.queuedRequest) { - this.queuedRequest = false; - this.run(); - } - } - } - - private shouldRequestData(selection: FlamegraphState) { - return selection.kind === 'FLAMEGRAPH_STATE' && - (this.lastSelectedFlamegraphState === undefined || - (this.lastSelectedFlamegraphState.startNs !== selection.startNs || - this.lastSelectedFlamegraphState.endNs !== selection.endNs || - this.lastSelectedFlamegraphState.type !== selection.type || - !FlamegraphController.areArraysEqual( - this.lastSelectedFlamegraphState.upids, selection.upids) || - this.lastSelectedFlamegraphState.viewingOption !== - selection.viewingOption || - this.lastSelectedFlamegraphState.focusRegex !== - selection.focusRegex || - this.lastSelectedFlamegraphState.expandedCallsite !== - selection.expandedCallsite)); - } - - private prepareAndMergeCallsites( - flamegraphData: CallsiteInfo[], - viewingOption: string|undefined = DEFAULT_VIEWING_OPTION, - isInAreaSelection: boolean, rootSize?: number, - expandedCallsite?: CallsiteInfo) { - this.flamegraphDetails.flamegraph = mergeCallsites( - flamegraphData, this.getMinSizeDisplayed(flamegraphData, rootSize)); - this.flamegraphDetails.expandedCallsite = expandedCallsite; - this.flamegraphDetails.viewingOption = viewingOption; - this.flamegraphDetails.isInAreaSelection = isInAreaSelection; - this.checkCompletionAndPublishFlamegraph(this.flamegraphDetails); - } - - private async checkCompletionAndPublishFlamegraph(flamegraphDetails: - FlamegraphDetails) { - flamegraphDetails.graphIncomplete = - (await this.args.engine.query(`select value from stats - where severity = 'error' and name = 'heap_graph_non_finalized_graph'`)) - .firstRow({value: NUM}) - .value > 0; - publishFlamegraphDetails(flamegraphDetails); - } - - async getFlamegraphData( - baseKey: string, viewingOption: string, startNs: number, endNs: number, - upids: number[], type: ProfileType, - focusRegex: string): Promise { - let currentData: CallsiteInfo[]; - const key = `${baseKey}-${viewingOption}`; - if (this.flamegraphDatasets.has(key)) { - currentData = this.flamegraphDatasets.get(key)!; - } else { - // TODO(hjd): Show loading state. - - // Collecting data for drawing flamegraph for selected profile. - // Data needs to be in following format: - // id, name, parent_id, depth, total_size - const tableName = await this.prepareViewsAndTables( - startNs, endNs, upids, type, focusRegex); - currentData = await this.getFlamegraphDataFromTables( - tableName, viewingOption, focusRegex); - this.flamegraphDatasets.set(key, currentData); - } - return currentData; - } - - async getFlamegraphDataFromTables( - tableName: string, viewingOption = DEFAULT_VIEWING_OPTION, - focusRegex: string) { - let orderBy = ''; - let totalColumnName: 'cumulativeSize'|'cumulativeAllocSize'| - 'cumulativeCount'|'cumulativeAllocCount' = 'cumulativeSize'; - let selfColumnName: 'size'|'count' = 'size'; - // TODO(fmayer): Improve performance so this is no longer necessary. - // Alternatively consider collapsing frames of the same label. - const maxDepth = 100; - switch (viewingOption) { - case ALLOC_SPACE_MEMORY_ALLOCATED_KEY: - orderBy = `where cumulative_alloc_size > 0 and depth < ${ - maxDepth} order by depth, parent_id, - cumulative_alloc_size desc, name`; - totalColumnName = 'cumulativeAllocSize'; - selfColumnName = 'size'; - break; - case OBJECTS_ALLOCATED_NOT_FREED_KEY: - orderBy = `where cumulative_count > 0 and depth < ${ - maxDepth} order by depth, parent_id, - cumulative_count desc, name`; - totalColumnName = 'cumulativeCount'; - selfColumnName = 'count'; - break; - case OBJECTS_ALLOCATED_KEY: - orderBy = `where cumulative_alloc_count > 0 and depth < ${ - maxDepth} order by depth, parent_id, - cumulative_alloc_count desc, name`; - totalColumnName = 'cumulativeAllocCount'; - selfColumnName = 'count'; - break; - case PERF_SAMPLES_KEY: - case SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY: - orderBy = `where cumulative_size > 0 and depth < ${ - maxDepth} order by depth, parent_id, - cumulative_size desc, name`; - totalColumnName = 'cumulativeSize'; - selfColumnName = 'size'; - break; - default: - break; - } - - const callsites = await this.args.engine.query(` - SELECT - id as hash, - IFNULL(IFNULL(DEMANGLE(name), name), '[NULL]') as name, - IFNULL(parent_id, -1) as parentHash, - depth, - cumulative_size as cumulativeSize, - cumulative_alloc_size as cumulativeAllocSize, - cumulative_count as cumulativeCount, - cumulative_alloc_count as cumulativeAllocCount, - map_name as mapping, - size, - count, - IFNULL(source_file, '') as sourceFile, - IFNULL(line_number, -1) as lineNumber - from ${tableName} ${orderBy}`); - - const flamegraphData: CallsiteInfo[] = []; - const hashToindex: Map = new Map(); - const it = callsites.iter({ - hash: NUM, - name: STR, - parentHash: NUM, - depth: NUM, - cumulativeSize: NUM, - cumulativeAllocSize: NUM, - cumulativeCount: NUM, - cumulativeAllocCount: NUM, - mapping: STR, - sourceFile: STR, - lineNumber: NUM, - size: NUM, - count: NUM, - }); - for (let i = 0; it.valid(); ++i, it.next()) { - const hash = it.hash; - let name = it.name; - const parentHash = it.parentHash; - const depth = it.depth; - const totalSize = it[totalColumnName]; - const selfSize = it[selfColumnName]; - const mapping = it.mapping; - const highlighted = focusRegex !== '' && - name.toLocaleLowerCase().includes(focusRegex.toLocaleLowerCase()); - const parentId = - hashToindex.has(+parentHash) ? hashToindex.get(+parentHash)! : -1; - - let location: string|undefined; - if (/[a-zA-Z]/i.test(it.sourceFile)) { - location = it.sourceFile; - if (it.lineNumber !== -1) { - location += `:${it.lineNumber}`; - } - } - - if (depth === maxDepth - 1) { - name += ' [tree truncated]'; - } - // Instead of hash, we will store index of callsite in this original array - // as an id of callsite. That way, we have quicker access to parent and it - // will stay unique: - hashToindex.set(hash, i); - - flamegraphData.push({ - id: i, - totalSize, - depth, - parentId, - name, - selfSize, - mapping, - merged: false, - highlighted, - location, - }); - } - return flamegraphData; - } - - private async prepareViewsAndTables( - startNs: number, endNs: number, upids: number[], type: ProfileType, - focusRegex: string): Promise { - // Creating unique names for views so we can reuse and not delete them - // for each marker. - let focusRegexConditional = ''; - if (focusRegex !== '') { - focusRegexConditional = `and focus_str = '${focusRegex}'`; - } - const flamegraphType = getFlamegraphType(type); - - /* - * TODO(octaviant) this branching should be eliminated for simplicity. - */ - if (type === ProfileType.PERF_SAMPLE) { - let upidConditional = `upid = ${upids[0]}`; - if (upids.length > 1) { - upidConditional = - `upid_group = '${FlamegraphController.serializeUpidGroup(upids)}'`; - } - return this.cache.getTableName( - `select id, name, map_name, parent_id, depth, cumulative_size, - cumulative_alloc_size, cumulative_count, cumulative_alloc_count, - size, alloc_size, count, alloc_count, source_file, line_number - from experimental_flamegraph - where profile_type = '${flamegraphType}' and ${startNs} <= ts and - ts <= ${endNs} and ${upidConditional} - ${focusRegexConditional}`); - } - return this.cache.getTableName( - `select id, name, map_name, parent_id, depth, cumulative_size, - cumulative_alloc_size, cumulative_count, cumulative_alloc_count, - size, alloc_size, count, alloc_count, source_file, line_number - from experimental_flamegraph - where profile_type = '${flamegraphType}' - and ts = ${endNs} - and upid = ${upids[0]} - ${focusRegexConditional}`); - } - - getMinSizeDisplayed(flamegraphData: CallsiteInfo[], rootSize?: number): - number { - const timeState = globals.state.frontendLocalState.visibleState; - let width = (timeState.endSec - timeState.startSec) / timeState.resolution; - // TODO(168048193): Remove screen size hack: - width = Math.max(width, 800); - if (rootSize === undefined) { - rootSize = findRootSize(flamegraphData); - } - return MIN_PIXEL_DISPLAYED * rootSize / width; - } - - async getFlamegraphMetadata( - type: ProfileType, startNs: number, endNs: number, upids: number[]) { - // Don't do anything if selection of the marker stayed the same. - if ((this.lastSelectedFlamegraphState !== undefined && - ((this.lastSelectedFlamegraphState.startNs === startNs && - this.lastSelectedFlamegraphState.endNs === endNs && - FlamegraphController.areArraysEqual( - this.lastSelectedFlamegraphState.upids, upids))))) { - return undefined; - } - - // Collecting data for more information about profile, such as: - // total memory allocated, memory that is allocated and not freed. - const upidGroup = FlamegraphController.serializeUpidGroup(upids); - - const result = await this.args.engine.query( - `select pid from process where upid in (${upidGroup})`); - const it = result.iter({pid: NUM}); - const pids = []; - for (let i = 0; it.valid(); ++i, it.next()) { - pids.push(it.pid); - } - return {startNs, durNs: endNs - startNs, pids, upids, type}; - } - - private static areArraysEqual(a: number[], b: number[]) { - if (a.length !== b.length) { - return false; - } - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) { - return false; - } - } - return true; - } - - private static serializeUpidGroup(upids: number[]) { - return new Array(upids).join(); - } -} diff --git a/third_party/perfetto/ui/src/controller/flow_events_controller.ts b/third_party/perfetto/ui/src/controller/flow_events_controller.ts deleted file mode 100644 index d89b514ff349..000000000000 --- a/third_party/perfetto/ui/src/controller/flow_events_controller.ts +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Engine} from '../common/engine'; -import {featureFlags} from '../common/feature_flags'; -import {NUM, STR_NULL} from '../common/query_result'; -import {Area} from '../common/state'; -import {fromNs, toNs} from '../common/time'; -import {Flow, globals} from '../frontend/globals'; -import {publishConnectedFlows, publishSelectedFlows} from '../frontend/publish'; -import { - ACTUAL_FRAMES_SLICE_TRACK_KIND, - Config as ActualConfig, -} from '../tracks/actual_frames'; -import { - Config as SliceConfig, - SLICE_TRACK_KIND, -} from '../tracks/chrome_slices'; - -import {Controller} from './controller'; - -export interface FlowEventsControllerArgs { - engine: Engine; -} - -const SHOW_INDIRECT_PRECEDING_FLOWS_FLAG = featureFlags.register({ - id: 'showIndirectPrecedingFlows', - name: 'Show indirect preceding flows', - description: 'Show indirect preceding flows (connected through ancestor ' + - 'slices) when a slice is selected.', - defaultValue: false, -}); - - -export class FlowEventsController extends Controller<'main'> { - private lastSelectedSliceId?: number; - private lastSelectedArea?: Area; - private lastSelectedKind: 'CHROME_SLICE'|'AREA'|'NONE' = 'NONE'; - - constructor(private args: FlowEventsControllerArgs) { - super('main'); - - // Create |CHROME_CUSTOME_SLICE_NAME| helper, which combines slice name - // and args for some slices (scheduler tasks and mojo messages) for more - // helpful messages. - // In the future, it should be replaced with this a more scalable and - // customisable solution. - // Note that a function here is significantly faster than a join. - this.args.engine.query(` - SELECT CREATE_FUNCTION( - 'CHROME_CUSTOM_SLICE_NAME(slice_id LONG)', - 'STRING', - 'select case - when name="Receive mojo message" then - printf("Receive mojo message (interface=%s, hash=%s)", - EXTRACT_ARG(arg_set_id, - "chrome_mojo_event_info.mojo_interface_tag"), - EXTRACT_ARG(arg_set_id, "chrome_mojo_event_info.ipc_hash")) - when name="ThreadControllerImpl::RunTask" or - name="ThreadPool_RunTask" then - printf("RunTask(posted_from=%s:%s)", - EXTRACT_ARG(arg_set_id, "task.posted_from.file_name"), - EXTRACT_ARG(arg_set_id, "task.posted_from.function_name")) - end - from slice where id=$slice_id' - );`); - } - - queryFlowEvents(query: string, callback: (flows: Flow[]) => void) { - this.args.engine.query(query).then((result) => { - const flows: Flow[] = []; - const it = result.iter({ - beginSliceId: NUM, - beginTrackId: NUM, - beginSliceName: STR_NULL, - beginSliceChromeCustomName: STR_NULL, - beginSliceCategory: STR_NULL, - beginSliceStartTs: NUM, - beginSliceEndTs: NUM, - beginDepth: NUM, - beginThreadName: STR_NULL, - beginProcessName: STR_NULL, - endSliceId: NUM, - endTrackId: NUM, - endSliceName: STR_NULL, - endSliceChromeCustomName: STR_NULL, - endSliceCategory: STR_NULL, - endSliceStartTs: NUM, - endSliceEndTs: NUM, - endDepth: NUM, - endThreadName: STR_NULL, - endProcessName: STR_NULL, - name: STR_NULL, - category: STR_NULL, - id: NUM, - }); - for (; it.valid(); it.next()) { - const beginSliceId = it.beginSliceId; - const beginTrackId = it.beginTrackId; - const beginSliceName = - it.beginSliceName === null ? 'NULL' : it.beginSliceName; - const beginSliceChromeCustomName = - it.beginSliceChromeCustomName === null ? - undefined : - it.beginSliceChromeCustomName; - const beginSliceCategory = - it.beginSliceCategory === null ? 'NULL' : it.beginSliceCategory; - const beginSliceStartTs = fromNs(it.beginSliceStartTs); - const beginSliceEndTs = fromNs(it.beginSliceEndTs); - const beginDepth = it.beginDepth; - const beginThreadName = - it.beginThreadName === null ? 'NULL' : it.beginThreadName; - const beginProcessName = - it.beginProcessName === null ? 'NULL' : it.beginProcessName; - - const endSliceId = it.endSliceId; - const endTrackId = it.endTrackId; - const endSliceName = - it.endSliceName === null ? 'NULL' : it.endSliceName; - const endSliceChromeCustomName = it.endSliceChromeCustomName === null ? - undefined : - it.endSliceChromeCustomName; - const endSliceCategory = - it.endSliceCategory === null ? 'NULL' : it.endSliceCategory; - const endSliceStartTs = fromNs(it.endSliceStartTs); - const endSliceEndTs = fromNs(it.endSliceEndTs); - const endDepth = it.endDepth; - const endThreadName = - it.endThreadName === null ? 'NULL' : it.endThreadName; - const endProcessName = - it.endProcessName === null ? 'NULL' : it.endProcessName; - - // Category and name present only in version 1 flow events - // It is most likelly NULL for all other versions - const category = it.category === null ? undefined : it.category; - const name = it.name === null ? undefined : it.name; - const id = it.id; - - flows.push({ - id, - begin: { - trackId: beginTrackId, - sliceId: beginSliceId, - sliceName: beginSliceName, - sliceChromeCustomName: beginSliceChromeCustomName, - sliceCategory: beginSliceCategory, - sliceStartTs: beginSliceStartTs, - sliceEndTs: beginSliceEndTs, - depth: beginDepth, - threadName: beginThreadName, - processName: beginProcessName, - }, - end: { - trackId: endTrackId, - sliceId: endSliceId, - sliceName: endSliceName, - sliceChromeCustomName: endSliceChromeCustomName, - sliceCategory: endSliceCategory, - sliceStartTs: endSliceStartTs, - sliceEndTs: endSliceEndTs, - depth: endDepth, - threadName: endThreadName, - processName: endProcessName, - }, - dur: endSliceStartTs - beginSliceEndTs, - category, - name, - }); - } - callback(flows); - }); - } - - sliceSelected(sliceId: number) { - if (this.lastSelectedKind === 'CHROME_SLICE' && - this.lastSelectedSliceId === sliceId) { - return; - } - this.lastSelectedSliceId = sliceId; - this.lastSelectedKind = 'CHROME_SLICE'; - - const connectedFlows = SHOW_INDIRECT_PRECEDING_FLOWS_FLAG.get() ? - `( - select * from directly_connected_flow(${sliceId}) - union - select * from preceding_flow(${sliceId}) - )` : - `directly_connected_flow(${sliceId})`; - - const query = ` - select - f.slice_out as beginSliceId, - t1.track_id as beginTrackId, - t1.name as beginSliceName, - CHROME_CUSTOM_SLICE_NAME(t1.slice_id) as beginSliceChromeCustomName, - t1.category as beginSliceCategory, - t1.ts as beginSliceStartTs, - (t1.ts+t1.dur) as beginSliceEndTs, - t1.depth as beginDepth, - (thread_out.name || ' ' || thread_out.tid) as beginThreadName, - (process_out.name || ' ' || process_out.pid) as beginProcessName, - f.slice_in as endSliceId, - t2.track_id as endTrackId, - t2.name as endSliceName, - CHROME_CUSTOM_SLICE_NAME(t2.slice_id) as endSliceChromeCustomName, - t2.category as endSliceCategory, - t2.ts as endSliceStartTs, - (t2.ts+t2.dur) as endSliceEndTs, - t2.depth as endDepth, - (thread_in.name || ' ' || thread_in.tid) as endThreadName, - (process_in.name || ' ' || process_in.pid) as endProcessName, - extract_arg(f.arg_set_id, 'cat') as category, - extract_arg(f.arg_set_id, 'name') as name, - f.id as id - from ${connectedFlows} f - join slice t1 on f.slice_out = t1.slice_id - join slice t2 on f.slice_in = t2.slice_id - left join thread_track track_out on track_out.id = t1.track_id - left join thread thread_out on thread_out.utid = track_out.utid - left join thread_track track_in on track_in.id = t2.track_id - left join thread thread_in on thread_in.utid = track_in.utid - left join process process_out on process_out.upid = thread_out.upid - left join process process_in on process_in.upid = thread_in.upid - `; - this.queryFlowEvents( - query, (flows: Flow[]) => publishConnectedFlows(flows)); - } - - areaSelected(areaId: string) { - const area = globals.state.areas[areaId]; - if (this.lastSelectedKind === 'AREA' && this.lastSelectedArea && - this.lastSelectedArea.tracks.join(',') === area.tracks.join(',') && - this.lastSelectedArea.endSec === area.endSec && - this.lastSelectedArea.startSec === area.startSec) { - return; - } - - this.lastSelectedArea = area; - this.lastSelectedKind = 'AREA'; - - const trackIds: number[] = []; - - for (const uiTrackId of area.tracks) { - const track = globals.state.tracks[uiTrackId]; - if (track === undefined) { - continue; - } - if (track.kind === SLICE_TRACK_KIND) { - trackIds.push((track.config as SliceConfig).trackId); - } else if (track.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) { - const actualConfig = track.config as ActualConfig; - for (const trackId of actualConfig.trackIds) { - trackIds.push(trackId); - } - } - } - - const tracks = `(${trackIds.join(',')})`; - - const startNs = toNs(area.startSec); - const endNs = toNs(area.endSec); - - const query = ` - select - f.slice_out as beginSliceId, - t1.track_id as beginTrackId, - t1.name as beginSliceName, - CHROME_CUSTOM_SLICE_NAME(t1.slice_id) as beginSliceChromeCustomName, - t1.category as beginSliceCategory, - t1.ts as beginSliceStartTs, - (t1.ts+t1.dur) as beginSliceEndTs, - t1.depth as beginDepth, - NULL as beginThreadName, - NULL as beginProcessName, - f.slice_in as endSliceId, - t2.track_id as endTrackId, - t2.name as endSliceName, - CHROME_CUSTOM_SLICE_NAME(t2.slice_id) as endSliceChromeCustomName, - t2.category as endSliceCategory, - t2.ts as endSliceStartTs, - (t2.ts+t2.dur) as endSliceEndTs, - t2.depth as endDepth, - NULL as endThreadName, - NULL as endProcessName, - extract_arg(f.arg_set_id, 'cat') as category, - extract_arg(f.arg_set_id, 'name') as name, - f.id as id - from flow f - join slice t1 on f.slice_out = t1.slice_id - join slice t2 on f.slice_in = t2.slice_id - where - (t1.track_id in ${tracks} - and (t1.ts+t1.dur <= ${endNs} and t1.ts+t1.dur >= ${startNs})) - or - (t2.track_id in ${tracks} - and (t2.ts <= ${endNs} and t2.ts >= ${startNs})) - `; - this.queryFlowEvents(query, (flows: Flow[]) => publishSelectedFlows(flows)); - } - - refreshVisibleFlows() { - const selection = globals.state.currentSelection; - if (!selection) { - this.lastSelectedKind = 'NONE'; - publishConnectedFlows([]); - publishSelectedFlows([]); - return; - } - - // TODO(b/155483804): This is a hack as annotation slices don't contain - // flows. We should tidy this up when fixing this bug. - if (selection && selection.kind === 'CHROME_SLICE' && - selection.table !== 'annotation') { - this.sliceSelected(selection.id); - } else { - publishConnectedFlows([]); - } - - if (selection && selection.kind === 'AREA') { - this.areaSelected(selection.areaId); - } else { - publishSelectedFlows([]); - } - } - - run() { - this.refreshVisibleFlows(); - } -} diff --git a/third_party/perfetto/ui/src/controller/ftrace_controller.ts b/third_party/perfetto/ui/src/controller/ftrace_controller.ts deleted file mode 100644 index f558a4a76f75..000000000000 --- a/third_party/perfetto/ui/src/controller/ftrace_controller.ts +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Engine} from '../common/engine'; -import {NUM, STR, STR_NULL} from '../common/query_result'; -import {FtraceFilterState, Pagination} from '../common/state'; -import {TimeSpan, toNsCeil, toNsFloor} from '../common/time'; -import {FtraceEvent, globals} from '../frontend/globals'; -import {publishFtracePanelData} from '../frontend/publish'; -import {ratelimit} from '../frontend/rate_limiters'; - -import {Controller} from './controller'; - -export interface FtraceControllerArgs { - engine: Engine; -} - -interface RetVal { - events: FtraceEvent[]; - offset: number; - numEvents: number; -} - -export class FtraceController extends Controller<'main'> { - private engine: Engine; - private oldSpan: TimeSpan = new TimeSpan(0, 0); - private oldFtraceFilter?: FtraceFilterState; - private oldPagination?: Pagination; - - constructor({engine}: FtraceControllerArgs) { - super('main'); - this.engine = engine; - } - - run() { - if (this.shouldUpdate()) { - this.oldSpan = globals.frontendLocalState.visibleWindowTime.clone(); - this.oldFtraceFilter = globals.state.ftraceFilter; - this.oldPagination = globals.state.ftracePagination; - if (globals.state.ftracePagination.count > 0) { - this.lookupFtraceEventsRateLimited(); - } - } - } - - private lookupFtraceEventsRateLimited = ratelimit(() => { - const {offset, count} = globals.state.ftracePagination; - // The formatter doesn't like formatted chained methods :( - const promise = this.lookupFtraceEvents(offset, count); - promise.then(({events, offset, numEvents}: RetVal) => { - publishFtracePanelData({events, offset, numEvents}); - }); - }, 250); - - private shouldUpdate(): boolean { - // Has the visible window moved? - const visibleWindow = globals.frontendLocalState.visibleWindowTime; - if (this.oldSpan.start !== visibleWindow.start || - this.oldSpan.end !== visibleWindow.end) { - return true; - } - - // Has the pagination changed? - if (this.oldPagination !== globals.state.ftracePagination) { - return true; - } - - // Has the filter changed? - if (this.oldFtraceFilter !== globals.state.ftraceFilter) { - return true; - } - - return false; - } - - async lookupFtraceEvents(offset: number, count: number): Promise { - const appState = globals.state; - const frontendState = globals.frontendLocalState; - const {start, end} = frontendState.visibleWindowTime; - - const startNs = toNsFloor(start); - const endNs = toNsCeil(end); - - const excludeList = appState.ftraceFilter.excludedNames; - const excludeListSql = excludeList.map((s) => `'${s}'`).join(','); - - // TODO(stevegolton): This query can be slow when traces are huge. - // The number of events is only used for correctly sizing the panel's - // scroll container so that the scrollbar works as if the panel were fully - // populated. - // Perhaps we could work out some UX that doesn't need this. - let queryRes = await this.engine.query(` - select count(id) as numEvents - from ftrace_event - where - ftrace_event.name not in (${excludeListSql}) and - ts >= ${startNs} and ts <= ${endNs} - `); - const {numEvents} = queryRes.firstRow({numEvents: NUM}); - - queryRes = await this.engine.query(` - select - ftrace_event.id as id, - ftrace_event.ts as ts, - ftrace_event.name as name, - ftrace_event.cpu as cpu, - thread.name as thread, - process.name as process, - to_ftrace(ftrace_event.id) as args - from ftrace_event - left join thread - on ftrace_event.utid = thread.utid - left join process - on thread.upid = process.upid - where - ftrace_event.name not in (${excludeListSql}) and - ts >= ${startNs} and ts <= ${endNs} - order by id - limit ${count} offset ${offset};`); - const events: FtraceEvent[] = []; - const it = queryRes.iter( - { - id: NUM, - ts: NUM, - name: STR, - cpu: NUM, - thread: STR_NULL, - process: STR_NULL, - args: STR, - }, - ); - for (let row = 0; it.valid(); it.next(), row++) { - events.push({ - id: it.id, - ts: it.ts, - name: it.name, - cpu: it.cpu, - thread: it.thread, - process: it.process, - args: it.args, - }); - } - return {events, offset, numEvents}; - } -}; diff --git a/third_party/perfetto/ui/src/controller/index.ts b/third_party/perfetto/ui/src/controller/index.ts deleted file mode 100644 index ffb481b81dad..000000000000 --- a/third_party/perfetto/ui/src/controller/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import '../gen/all_tracks'; -import '../common/recordingV2/target_factories'; - -import {assertExists, assertTrue} from '../base/logging'; - -import {AppController} from './app_controller'; -import {ControllerAny} from './controller'; - -let rootController: ControllerAny; -let runningControllers = false; - -export function initController(extensionPort: MessagePort) { - assertTrue(rootController === undefined); - rootController = new AppController(extensionPort); -} - -export function runControllers() { - if (runningControllers) throw new Error('Re-entrant call detected'); - - // Run controllers locally until all state machines reach quiescence. - let runAgain = true; - for (let iter = 0; runAgain; iter++) { - if (iter > 100) throw new Error('Controllers are stuck in a livelock'); - runningControllers = true; - try { - runAgain = assertExists(rootController).invoke(); - } finally { - runningControllers = false; - } - } -} diff --git a/third_party/perfetto/ui/src/controller/loading_manager.ts b/third_party/perfetto/ui/src/controller/loading_manager.ts deleted file mode 100644 index 5b0e0cf18259..000000000000 --- a/third_party/perfetto/ui/src/controller/loading_manager.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {LoadingTracker} from '../common/engine'; -import {publishLoading} from '../frontend/publish'; - -// Used to keep track of whether the engine is currently querying. -export class LoadingManager implements LoadingTracker { - private static _instance: LoadingManager; - private numQueuedQueries = 0; - private numLastUpdate = 0; - - static get getInstance(): LoadingManager { - return this._instance || (this._instance = new this()); - } - - beginLoading() { - this.update(1); - } - - endLoading() { - this.update(-1); - } - - private update(change: number) { - this.numQueuedQueries += change; - if (this.numQueuedQueries === 0 || - Math.abs(this.numLastUpdate - this.numQueuedQueries) > 2) { - this.numLastUpdate = this.numQueuedQueries; - publishLoading(this.numQueuedQueries); - } - } -} diff --git a/third_party/perfetto/ui/src/controller/logs_controller.ts b/third_party/perfetto/ui/src/controller/logs_controller.ts deleted file mode 100644 index d8f9b3a96bc5..000000000000 --- a/third_party/perfetto/ui/src/controller/logs_controller.ts +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Engine} from '../common/engine'; -import { - LogBounds, - LogBoundsKey, - LogEntries, - LogEntriesKey, - LogExistsKey, -} from '../common/logs'; -import {NUM, STR} from '../common/query_result'; -import {escapeGlob, escapeQuery} from '../common/query_utils'; -import {LogFilteringCriteria} from '../common/state'; -import {fromNs, TimeSpan, toNsCeil, toNsFloor} from '../common/time'; -import {globals} from '../frontend/globals'; -import {publishTrackData} from '../frontend/publish'; - -import {Controller} from './controller'; - -async function updateLogBounds( - engine: Engine, span: TimeSpan): Promise { - const vizStartNs = toNsFloor(span.start); - const vizEndNs = toNsCeil(span.end); - - const countResult = await engine.query(`select - ifnull(min(ts), 0) as minTs, - ifnull(max(ts), 0) as maxTs, - count(ts) as countTs - from filtered_logs - where ts >= ${vizStartNs} - and ts <= ${vizEndNs}`); - - const countRow = countResult.firstRow({minTs: NUM, maxTs: NUM, countTs: NUM}); - - const firstRowNs = countRow.minTs; - const lastRowNs = countRow.maxTs; - const total = countRow.countTs; - - const minResult = await engine.query(` - select ifnull(max(ts), 0) as maxTs from filtered_logs where ts < ${ - vizStartNs}`); - const startNs = minResult.firstRow({maxTs: NUM}).maxTs; - - const maxResult = await engine.query(` - select ifnull(min(ts), 0) as minTs from filtered_logs where ts > ${ - vizEndNs}`); - const endNs = maxResult.firstRow({minTs: NUM}).minTs; - - const startTs = startNs ? fromNs(startNs) : 0; - const endTs = endNs ? fromNs(endNs) : Number.MAX_SAFE_INTEGER; - const firstRowTs = firstRowNs ? fromNs(firstRowNs) : endTs; - const lastRowTs = lastRowNs ? fromNs(lastRowNs) : startTs; - return { - startTs, - endTs, - firstRowTs, - lastRowTs, - total, - }; -} - -async function updateLogEntries( - engine: Engine, span: TimeSpan, pagination: Pagination): - Promise { - const vizStartNs = toNsFloor(span.start); - const vizEndNs = toNsCeil(span.end); - const vizSqlBounds = `ts >= ${vizStartNs} and ts <= ${vizEndNs}`; - - const rowsResult = await engine.query(` - select - ts, - prio, - ifnull(tag, '[NULL]') as tag, - ifnull(msg, '[NULL]') as msg, - is_msg_highlighted as isMsgHighlighted, - is_process_highlighted as isProcessHighlighted, - ifnull(process_name, '') as processName - from filtered_logs - where ${vizSqlBounds} - order by ts - limit ${pagination.start}, ${pagination.count} - `); - - const timestamps = []; - const priorities = []; - const tags = []; - const messages = []; - const isHighlighted = []; - const processName = []; - - const it = rowsResult.iter({ - ts: NUM, - prio: NUM, - tag: STR, - msg: STR, - isMsgHighlighted: NUM, - isProcessHighlighted: NUM, - processName: STR, - }); - for (; it.valid(); it.next()) { - timestamps.push(it.ts); - priorities.push(it.prio); - tags.push(it.tag); - messages.push(it.msg); - isHighlighted.push( - it.isMsgHighlighted === 1 || it.isProcessHighlighted === 1); - processName.push(it.processName); - } - - return { - offset: pagination.start, - timestamps, - priorities, - tags, - messages, - isHighlighted, - processName, - }; -} - -class Pagination { - private _offset: number; - private _count: number; - - constructor(offset: number, count: number) { - this._offset = offset; - this._count = count; - } - - get start() { - return this._offset; - } - - get count() { - return this._count; - } - - get end() { - return this._offset + this._count; - } - - contains(other: Pagination): boolean { - return this.start <= other.start && other.end <= this.end; - } - - grow(n: number): Pagination { - const newStart = Math.max(0, this.start - n / 2); - const newCount = this.count + n; - return new Pagination(newStart, newCount); - } -} - -export interface LogsControllerArgs { - engine: Engine; -} - -/** - * LogsController looks at three parts of the state: - * 1. The visible trace window - * 2. The requested offset and count the log lines to display - * 3. The log filtering criteria. - * And keeps two bits of published information up to date: - * 1. The total number of log messages in visible range - * 2. The logs lines that should be displayed - * Based on the log filtering criteria, it also builds the filtered_logs view - * and keeps it up to date. - */ -export class LogsController extends Controller<'main'> { - private engine: Engine; - private span: TimeSpan; - private pagination: Pagination; - private hasLogs = false; - private logFilteringCriteria?: LogFilteringCriteria; - private requestingData = false; - private queuedRunRequest = false; - - constructor(args: LogsControllerArgs) { - super('main'); - this.engine = args.engine; - this.span = new TimeSpan(0, 10); - this.pagination = new Pagination(0, 0); - this.hasAnyLogs().then((exists) => { - this.hasLogs = exists; - publishTrackData({ - id: LogExistsKey, - data: { - exists, - }, - }); - }); - } - - async hasAnyLogs() { - const result = await this.engine.query(` - select count(*) as cnt from android_logs - `); - return result.firstRow({cnt: NUM}).cnt > 0; - } - - run() { - if (!this.hasLogs) return; - if (this.requestingData) { - this.queuedRunRequest = true; - return; - } - this.requestingData = true; - this.updateLogTracks().finally(() => { - this.requestingData = false; - if (this.queuedRunRequest) { - this.queuedRunRequest = false; - this.run(); - } - }); - } - - private async updateLogTracks() { - const traceTime = globals.state.frontendLocalState.visibleState; - const newSpan = new TimeSpan(traceTime.startSec, traceTime.endSec); - const oldSpan = this.span; - - const pagination = globals.state.logsPagination; - // This can occur when loading old traces. - // TODO(hjd): Fix the problem of accessing state from a previous version of - // the UI in a general way. - if (pagination === undefined) { - return; - } - - const {offset, count} = pagination; - const requestedPagination = new Pagination(offset, count); - const oldPagination = this.pagination; - - const newFilteringCriteria = - this.logFilteringCriteria !== globals.state.logFilteringCriteria; - const needBoundsUpdate = !oldSpan.equals(newSpan) || newFilteringCriteria; - const needEntriesUpdate = - !oldPagination.contains(requestedPagination) || needBoundsUpdate; - - if (newFilteringCriteria) { - this.logFilteringCriteria = globals.state.logFilteringCriteria; - await this.engine.query('drop view if exists filtered_logs'); - - const globMatch = LogsController.composeGlobMatch( - this.logFilteringCriteria.hideNonMatching, - this.logFilteringCriteria.textEntry); - let selectedRows = `select prio, ts, tag, msg, - process.name as process_name, ${globMatch} - from android_logs - left join thread using(utid) - left join process using(upid) - where prio >= ${this.logFilteringCriteria.minimumLevel}`; - if (this.logFilteringCriteria.tags.length) { - selectedRows += ` and tag in (${ - LogsController.serializeTags(this.logFilteringCriteria.tags)})`; - } - - // We extract only the rows which will be visible. - await this.engine.query(`create view filtered_logs as select * - from (${selectedRows}) - where is_msg_chosen is 1 or is_process_chosen is 1`); - } - - if (needBoundsUpdate) { - this.span = newSpan; - const logBounds = await updateLogBounds(this.engine, newSpan); - publishTrackData({ - id: LogBoundsKey, - data: logBounds, - }); - } - - if (needEntriesUpdate) { - this.pagination = requestedPagination.grow(100); - const logEntries = - await updateLogEntries(this.engine, newSpan, this.pagination); - publishTrackData({ - id: LogEntriesKey, - data: logEntries, - }); - } - } - - private static serializeTags(tags: string[]) { - return tags.map((tag) => escapeQuery(tag)).join(); - } - - private static composeGlobMatch(isCollaped: boolean, textEntry: string) { - if (isCollaped) { - // If the entries are collapsed, we won't highlight any lines. - return `msg glob ${escapeGlob(textEntry)} as is_msg_chosen, - (process.name is not null and process.name glob ${ - escapeGlob(textEntry)}) as is_process_chosen, - 0 as is_msg_highlighted, - 0 as is_process_highlighted`; - } else if (!textEntry) { - // If there is no text entry, we will show all lines, but won't highlight. - // any. - return `1 as is_msg_chosen, - 1 as is_process_chosen, - 0 as is_msg_highlighted, - 0 as is_process_highlighted`; - } else { - return `1 as is_msg_chosen, - 1 as is_process_chosen, - msg glob ${escapeGlob(textEntry)} as is_msg_highlighted, - (process.name is not null and process.name glob ${ - escapeGlob(textEntry)}) as is_process_highlighted`; - } - } -} diff --git a/third_party/perfetto/ui/src/controller/metrics_controller.ts b/third_party/perfetto/ui/src/controller/metrics_controller.ts deleted file mode 100644 index 236503d820c4..000000000000 --- a/third_party/perfetto/ui/src/controller/metrics_controller.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Actions} from '../common/actions'; -import {Engine} from '../common/engine'; -import {QueryError} from '../common/query_result'; -import {globals} from '../frontend/globals'; -import {publishMetricResult} from '../frontend/publish'; - -import {Controller} from './controller'; - -export class MetricsController extends Controller<'main'> { - private engine: Engine; - private currentlyRunningMetric?: string; - - constructor(args: {engine: Engine}) { - super('main'); - this.engine = args.engine; - this.run(); - } - - private async computeMetric(name: string) { - if (name === this.currentlyRunningMetric) return; - this.currentlyRunningMetric = name; - try { - const metricResult = await this.engine.computeMetric([name]); - publishMetricResult({ - name, - resultString: metricResult.metricsAsPrototext || undefined, - }); - } catch (e) { - if (e instanceof QueryError) { - // Reroute error to be displated differently when metric is run through - // metric page. - publishMetricResult({name, error: e.message}); - } else { - throw e; - } - } - globals.dispatch(Actions.resetMetricRequest({name})); - this.currentlyRunningMetric = undefined; - } - - run() { - const {requestedMetric} = globals.state.metrics; - if (!requestedMetric) return; - this.computeMetric(requestedMetric); - } -} diff --git a/third_party/perfetto/ui/src/controller/permalink_controller.ts b/third_party/perfetto/ui/src/controller/permalink_controller.ts deleted file mode 100644 index ad58b27ae9a2..000000000000 --- a/third_party/perfetto/ui/src/controller/permalink_controller.ts +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {produce} from 'immer'; - -import {assertExists} from '../base/logging'; -import {Actions} from '../common/actions'; -import {ConversionJobStatus} from '../common/conversion_jobs'; -import { - createEmptyNonSerializableState, - createEmptyState, -} from '../common/empty_state'; -import {EngineConfig, ObjectById, State} from '../common/state'; -import {STATE_VERSION} from '../common/state'; -import { - BUCKET_NAME, - buggyToSha256, - deserializeStateObject, - saveState, - saveTrace, - toSha256, -} from '../common/upload_utils'; -import {globals} from '../frontend/globals'; -import {publishConversionJobStatusUpdate} from '../frontend/publish'; -import {Router} from '../frontend/router'; - -import {Controller} from './controller'; -import {RecordConfig, recordConfigValidator} from './record_config_types'; -import {runValidator} from './validators'; - -interface MultiEngineState { - currentEngineId?: string; - engines: ObjectById -} - -function isMultiEngineState(state: State| - MultiEngineState): state is MultiEngineState { - if ((state as MultiEngineState).engines !== undefined) { - return true; - } - return false; -} - -export class PermalinkController extends Controller<'main'> { - private lastRequestId?: string; - constructor() { - super('main'); - } - - run() { - if (globals.state.permalink.requestId === undefined || - globals.state.permalink.requestId === this.lastRequestId) { - return; - } - const requestId = assertExists(globals.state.permalink.requestId); - this.lastRequestId = requestId; - - // if the |hash| is not set, this is a request to create a permalink. - if (globals.state.permalink.hash === undefined) { - const isRecordingConfig = - assertExists(globals.state.permalink.isRecordingConfig); - - const jobName = 'create_permalink'; - publishConversionJobStatusUpdate({ - jobName, - jobStatus: ConversionJobStatus.InProgress, - }); - - PermalinkController.createPermalink(isRecordingConfig) - .then((hash) => { - globals.dispatch(Actions.setPermalink({requestId, hash})); - }) - .finally(() => { - publishConversionJobStatusUpdate({ - jobName, - jobStatus: ConversionJobStatus.NotRunning, - }); - }); - return; - } - - // Otherwise, this is a request to load the permalink. - PermalinkController.loadState(globals.state.permalink.hash) - .then((stateOrConfig) => { - if (PermalinkController.isRecordConfig(stateOrConfig)) { - // This permalink state only contains a RecordConfig. Show the - // recording page with the config, but keep other state as-is. - const validConfig = - runValidator(recordConfigValidator, stateOrConfig as unknown) - .result; - globals.dispatch(Actions.setRecordConfig({config: validConfig})); - Router.navigate('#!/record'); - return; - } - globals.dispatch(Actions.setState({newState: stateOrConfig})); - this.lastRequestId = stateOrConfig.permalink.requestId; - }); - } - - private static upgradeState(state: State): State { - if (state.version !== STATE_VERSION) { - const newState = createEmptyState(); - // Old permalinks from state versions prior to version 24 - // have multiple engines of which only one is identified as the - // current engine via currentEngineId. Handle this case: - if (isMultiEngineState(state)) { - const engineId = state.currentEngineId; - if (engineId !== undefined) { - newState.engine = state.engines[engineId]; - } - } else { - newState.engine = state.engine; - } - - if (newState.engine !== undefined) { - newState.engine.ready = false; - } - - const message = `Unable to parse old state version. Discarding state ` + - `and loading trace.`; - console.warn(message); - PermalinkController.updateStatus(message); - return newState; - } else { - // Loaded state is presumed to be compatible with the State type - // definition in the app. However, a non-serializable part has to be - // recreated. - state.nonSerializableState = createEmptyNonSerializableState(); - } - return state; - } - - private static isRecordConfig(stateOrConfig: State| - RecordConfig): stateOrConfig is RecordConfig { - const mode = (stateOrConfig as {mode?: string}).mode; - return mode !== undefined && - ['STOP_WHEN_FULL', 'RING_BUFFER', 'LONG_TRACE'].includes(mode); - } - - private static async createPermalink(isRecordingConfig: boolean): - Promise { - let uploadState: State|RecordConfig = globals.state; - - if (isRecordingConfig) { - uploadState = globals.state.recordConfig; - } else { - const engine = assertExists(globals.getCurrentEngine()); - let dataToUpload: File|ArrayBuffer|undefined = undefined; - let traceName = `trace ${engine.id}`; - if (engine.source.type === 'FILE') { - dataToUpload = engine.source.file; - traceName = dataToUpload.name; - } else if (engine.source.type === 'ARRAY_BUFFER') { - dataToUpload = engine.source.buffer; - } else if (engine.source.type !== 'URL') { - throw new Error(`Cannot share trace ${JSON.stringify(engine.source)}`); - } - - if (dataToUpload !== undefined) { - PermalinkController.updateStatus(`Uploading ${traceName}`); - const url = await saveTrace(dataToUpload); - // Convert state to use URLs and remove permalink. - uploadState = produce(globals.state, (draft) => { - assertExists(draft.engine).source = {type: 'URL', url}; - draft.permalink = {}; - }); - } - } - - // Upload state. - PermalinkController.updateStatus(`Creating permalink...`); - const hash = await saveState(uploadState); - PermalinkController.updateStatus(`Permalink ready`); - return hash; - } - - private static async loadState(id: string): Promise { - const url = `https://storage.googleapis.com/${BUCKET_NAME}/${id}`; - const response = await fetch(url); - if (!response.ok) { - throw new Error( - `Could not fetch permalink.\n` + - `Are you sure the id (${id}) is correct?\n` + - `URL: ${url}`); - } - const text = await response.text(); - const stateHash = await toSha256(text); - const state = deserializeStateObject(text); - if (stateHash !== id) { - // Old permalinks incorrectly dropped some digits from the - // hexdigest of the SHA256. We don't want to invalidate those - // links so we also compute the old string and try that here - // also. - const buggyStateHash = await buggyToSha256(text); - if (buggyStateHash !== id) { - throw new Error(`State hash does not match ${id} vs. ${stateHash}`); - } - } - if (!this.isRecordConfig(state)) { - return this.upgradeState(state); - } - return state; - } - - private static updateStatus(msg: string): void { - // TODO(hjd): Unify loading updates. - globals.dispatch(Actions.updateStatus({ - msg, - timestamp: Date.now() / 1000, - })); - } -} diff --git a/third_party/perfetto/ui/src/controller/pivot_table_controller.ts b/third_party/perfetto/ui/src/controller/pivot_table_controller.ts deleted file mode 100644 index e91ec983553b..000000000000 --- a/third_party/perfetto/ui/src/controller/pivot_table_controller.ts +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import {Actions} from '../common/actions'; -import {DEFAULT_CHANNEL, getCurrentChannel} from '../common/channels'; -import {Engine} from '../common/engine'; -import {featureFlags} from '../common/feature_flags'; -import {ColumnType, STR} from '../common/query_result'; -import { - AreaSelection, - PivotTableQuery, - PivotTableQueryMetadata, - PivotTableResult, - PivotTableState, -} from '../common/state'; -import {globals} from '../frontend/globals'; -import { - aggregationIndex, - generateQueryFromState, -} from '../frontend/pivot_table_query_generator'; -import {Aggregation, PivotTree} from '../frontend/pivot_table_types'; - -import {Controller} from './controller'; - -export const PIVOT_TABLE_REDUX_FLAG = featureFlags.register({ - id: 'pivotTable', - name: 'Pivot tables V2', - description: 'Second version of pivot table', - // Enabled in canary and autopush by default. - defaultValue: getCurrentChannel() !== DEFAULT_CHANNEL, -}); - -function expectNumber(value: ColumnType): number { - if (typeof value === 'number') { - return value; - } else if (typeof value === 'bigint') { - return Number(value); - } - throw new Error(`number or bigint was expected, got ${typeof value}`); -} - -// Auxiliary class to build the tree from query response. -export class PivotTableTreeBuilder { - private readonly root: PivotTree; - queryMetadata: PivotTableQueryMetadata; - - get pivotColumnsCount(): number { - return this.queryMetadata.pivotColumns.length; - } - - get aggregateColumns(): Aggregation[] { - return this.queryMetadata.aggregationColumns; - } - - constructor(queryMetadata: PivotTableQueryMetadata, firstRow: ColumnType[]) { - this.queryMetadata = queryMetadata; - this.root = this.createNode(firstRow); - let tree = this.root; - for (let i = 0; i + 1 < this.pivotColumnsCount; i++) { - const value = firstRow[i]; - tree = this.insertChild(tree, value, this.createNode(firstRow)); - } - tree.rows.push(firstRow); - } - - // Add incoming row to the tree being built. - ingestRow(row: ColumnType[]) { - let tree = this.root; - this.updateAggregates(tree, row); - for (let i = 0; i + 1 < this.pivotColumnsCount; i++) { - const nextTree = tree.children.get(row[i]); - if (nextTree === undefined) { - // Insert the new node into the tree, and make variable `tree` point - // to the newly created node. - tree = this.insertChild(tree, row[i], this.createNode(row)); - } else { - this.updateAggregates(nextTree, row); - tree = nextTree; - } - } - tree.rows.push(row); - } - - build(): PivotTree { - return this.root; - } - - updateAggregates(tree: PivotTree, row: ColumnType[]) { - const countIndex = this.queryMetadata.countIndex; - const treeCount = - countIndex >= 0 ? expectNumber(tree.aggregates[countIndex]) : 0; - const rowCount = countIndex >= 0 ? - expectNumber( - row[aggregationIndex(this.pivotColumnsCount, countIndex)]) : - 0; - - for (let i = 0; i < this.aggregateColumns.length; i++) { - const agg = this.aggregateColumns[i]; - - const currAgg = tree.aggregates[i]; - const childAgg = row[aggregationIndex(this.pivotColumnsCount, i)]; - if (typeof currAgg === 'number' && typeof childAgg === 'number') { - switch (agg.aggregationFunction) { - case 'SUM': - case 'COUNT': - tree.aggregates[i] = currAgg + childAgg; - break; - case 'MAX': - tree.aggregates[i] = Math.max(currAgg, childAgg); - break; - case 'MIN': - tree.aggregates[i] = Math.min(currAgg, childAgg); - break; - case 'AVG': { - const currSum = currAgg * treeCount; - const addSum = childAgg * rowCount; - tree.aggregates[i] = (currSum + addSum) / (treeCount + rowCount); - break; - } - } - } - } - tree.aggregates[this.aggregateColumns.length] = treeCount + rowCount; - } - - // Helper method that inserts child node into the tree and returns it, used - // for more concise modification of local variable pointing to the current - // node being built. - insertChild(tree: PivotTree, key: ColumnType, child: PivotTree): PivotTree { - tree.children.set(key, child); - - return child; - } - - // Initialize PivotTree from a row. - createNode(row: ColumnType[]): PivotTree { - const aggregates = []; - - for (let j = 0; j < this.aggregateColumns.length; j++) { - aggregates.push(row[aggregationIndex(this.pivotColumnsCount, j)]); - } - aggregates.push(row[aggregationIndex( - this.pivotColumnsCount, this.aggregateColumns.length)]); - - return { - isCollapsed: false, - children: new Map(), - aggregates, - rows: [], - }; - } -} - -function createEmptyQueryResult(metadata: PivotTableQueryMetadata): - PivotTableResult { - return { - tree: { - aggregates: [], - isCollapsed: false, - children: new Map(), - rows: [], - }, - metadata, - }; -} - -// Controller responsible for showing the panel with pivot table, as well as -// executing its queries and post-processing query results. -export class PivotTableController extends Controller<{}> { - static detailsCount = 0; - engine: Engine; - lastQueryAreaId = ''; - lastQueryAreaTracks = new Set(); - requestedArgumentNames = false; - - constructor(args: {engine: Engine}) { - super({}); - this.engine = args.engine; - } - - sameTracks(tracks: Set) { - if (this.lastQueryAreaTracks.size !== tracks.size) { - return false; - } - - // ES6 Set does not have .every method, only Array does. - for (const track in tracks) { - if (!this.lastQueryAreaTracks.has(track)) { - return false; - } - } - - return true; - } - - shouldRerun(state: PivotTableState, selection: AreaSelection) { - if (state.selectionArea === undefined) { - return false; - } - - const newTracks = new Set(globals.state.areas[selection.areaId].tracks); - if (this.lastQueryAreaId !== state.selectionArea.areaId || - !this.sameTracks(newTracks)) { - this.lastQueryAreaId = state.selectionArea.areaId; - this.lastQueryAreaTracks = newTracks; - return true; - } - return false; - } - - async processQuery(query: PivotTableQuery) { - const result = await this.engine.query(query.text); - try { - await result.waitAllRows(); - } catch { - // waitAllRows() frequently throws an exception, which is ignored in - // its other calls, so it's ignored here as well. - } - - const columns = result.columns(); - - const it = result.iter({}); - function nextRow(): ColumnType[] { - const row: ColumnType[] = []; - for (const column of columns) { - row.push(it.get(column)); - } - it.next(); - return row; - } - - if (!it.valid()) { - // Iterator is invalid after creation; means that there are no rows - // satisfying filtering criteria. Return an empty tree. - globals.dispatch(Actions.setPivotStateQueryResult( - {queryResult: createEmptyQueryResult(query.metadata)})); - return; - } - - const treeBuilder = new PivotTableTreeBuilder(query.metadata, nextRow()); - while (it.valid()) { - treeBuilder.ingestRow(nextRow()); - } - - globals.dispatch(Actions.setPivotStateQueryResult( - {queryResult: {tree: treeBuilder.build(), metadata: query.metadata}})); - globals.dispatch(Actions.setCurrentTab({tab: 'pivot_table'})); - } - - async requestArgumentNames() { - this.requestedArgumentNames = true; - const result = await this.engine.query(` - select distinct flat_key from args - `); - const it = result.iter({flat_key: STR}); - - const argumentNames = []; - while (it.valid()) { - argumentNames.push(it.flat_key); - it.next(); - } - - globals.dispatch(Actions.setPivotTableArgumentNames({argumentNames})); - } - - - run() { - if (!PIVOT_TABLE_REDUX_FLAG.get()) { - return; - } - - if (!this.requestedArgumentNames) { - this.requestArgumentNames(); - } - - const pivotTableState = globals.state.nonSerializableState.pivotTable; - const selection = globals.state.currentSelection; - - if (pivotTableState.queryRequested || - (selection !== null && selection.kind === 'AREA' && - this.shouldRerun(pivotTableState, selection))) { - globals.dispatch( - Actions.setPivotTableQueryRequested({queryRequested: false})); - // Need to re-run the existing query, clear the current result. - globals.dispatch(Actions.setPivotStateQueryResult({queryResult: null})); - this.processQuery(generateQueryFromState(pivotTableState)); - } - - if (selection !== null && selection.kind === 'AREA' && - (pivotTableState.selectionArea === undefined || - pivotTableState.selectionArea.areaId !== selection.areaId)) { - globals.dispatch(Actions.togglePivotTable({areaId: selection.areaId})); - } - } -} diff --git a/third_party/perfetto/ui/src/controller/pivot_table_tree_builder_unittest.ts b/third_party/perfetto/ui/src/controller/pivot_table_tree_builder_unittest.ts deleted file mode 100644 index 5ab93f954c10..000000000000 --- a/third_party/perfetto/ui/src/controller/pivot_table_tree_builder_unittest.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import {PivotTableTreeBuilder} from './pivot_table_controller'; - -describe('Pivot Table tree builder', () => { - test('aggregates averages correctly', () => { - const builder = new PivotTableTreeBuilder( - { - pivotColumns: [ - {kind: 'regular', table: 'slice', column: 'category'}, - {kind: 'regular', table: 'slice', column: 'name'}, - ], - aggregationColumns: [{ - aggregationFunction: 'AVG', - column: {kind: 'regular', table: 'slice', column: 'dur'}, - }], - countIndex: 1, - }, - ['cat1', 'name1', 80.0, 2]); - - builder.ingestRow(['cat1', 'name2', 20.0, 1]); - builder.ingestRow(['cat2', 'name3', 20.0, 1]); - - // With two rows of average value 80.0, and two of average value 20.0; - // the total sum is 80.0 * 2 + 20.0 + 20.0 = 200.0 over four slices. The - // average value should be 200.0 / 4 = 50.0 - expect(builder.build().aggregates[0]).toBeCloseTo(50.0); - }); -}); diff --git a/third_party/perfetto/ui/src/controller/record_config_types.ts b/third_party/perfetto/ui/src/controller/record_config_types.ts deleted file mode 100644 index a23acfd80f4a..000000000000 --- a/third_party/perfetto/ui/src/controller/record_config_types.ts +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - oneOf, - num, - bool, - arrayOf, - str, - requiredStr, - record, - runValidator, - ValidatedType, -} from './validators'; - -const recordModes = ['STOP_WHEN_FULL', 'RING_BUFFER', 'LONG_TRACE'] as const; -export const recordConfigValidator = record({ - mode: oneOf(recordModes, 'STOP_WHEN_FULL'), - durationMs: num(10000.0), - maxFileSizeMb: num(100), - fileWritePeriodMs: num(2500), - bufferSizeMb: num(64.0), - - cpuSched: bool(), - cpuFreq: bool(), - cpuFreqPollMs: num(1000), - cpuSyscall: bool(), - - gpuFreq: bool(), - gpuMemTotal: bool(), - - ftrace: bool(), - atrace: bool(), - ftraceEvents: arrayOf(str()), - ftraceExtraEvents: str(), - atraceCats: arrayOf(str()), - allAtraceApps: bool(true), - atraceApps: str(), - ftraceBufferSizeKb: num(0), - ftraceDrainPeriodMs: num(0), - androidLogs: bool(), - androidLogBuffers: arrayOf(str()), - androidFrameTimeline: bool(), - androidGameInterventionList: bool(), - androidNetworkTracing: bool(), - androidNetworkTracingPollMs: num(250), - - cpuCoarse: bool(), - cpuCoarsePollMs: num(1000), - - batteryDrain: bool(), - batteryDrainPollMs: num(1000), - - boardSensors: bool(), - - memHiFreq: bool(), - meminfo: bool(), - meminfoPeriodMs: num(1000), - meminfoCounters: arrayOf(str()), - - vmstat: bool(), - vmstatPeriodMs: num(1000), - vmstatCounters: arrayOf(str()), - - heapProfiling: bool(), - hpSamplingIntervalBytes: num(4096), - hpProcesses: str(), - hpContinuousDumpsPhase: num(), - hpContinuousDumpsInterval: num(), - hpSharedMemoryBuffer: num(8 * 1048576), - hpBlockClient: bool(true), - hpAllHeaps: bool(), - - javaHeapDump: bool(), - jpProcesses: str(), - jpContinuousDumpsPhase: num(), - jpContinuousDumpsInterval: num(), - - memLmk: bool(), - procStats: bool(), - procStatsPeriodMs: num(1000), - - chromeCategoriesSelected: arrayOf(str()), - chromeHighOverheadCategoriesSelected: arrayOf(str()), - chromePrivacyFiltering: bool(), - - chromeLogs: bool(), - taskScheduling: bool(), - ipcFlows: bool(), - jsExecution: bool(), - webContentRendering: bool(), - uiRendering: bool(), - inputEvents: bool(), - navigationAndLoading: bool(), - - symbolizeKsyms: bool(), -}); -export const namedRecordConfigValidator = record( - {title: requiredStr, key: requiredStr, config: recordConfigValidator}); -export type NamedRecordConfig = - ValidatedType; -export type RecordConfig = ValidatedType; - -export function createEmptyRecordConfig(): RecordConfig { - return runValidator(recordConfigValidator, {}).result; -} diff --git a/third_party/perfetto/ui/src/controller/record_controller.ts b/third_party/perfetto/ui/src/controller/record_controller.ts deleted file mode 100644 index 598c85e978bd..000000000000 --- a/third_party/perfetto/ui/src/controller/record_controller.ts +++ /dev/null @@ -1,427 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Message, Method, rpc, RPCImplCallback} from 'protobufjs'; - -import {base64Encode} from '../base/string_utils'; -import {Actions} from '../common/actions'; -import {TRACE_SUFFIX} from '../common/constants'; -import { - ConsumerPort, - TraceConfig, -} from '../common/protos'; -import {genTraceConfig} from '../common/recordingV2/recording_config_utils'; -import {TargetInfo} from '../common/recordingV2/recording_interfaces_v2'; -import { - AdbRecordingTarget, - isAdbTarget, - isChromeTarget, - RecordingTarget, -} from '../common/state'; -import {globals} from '../frontend/globals'; -import {publishBufferUsage, publishTrackData} from '../frontend/publish'; - -import {AdbOverWebUsb} from './adb'; -import {AdbConsumerPort} from './adb_shell_controller'; -import {AdbSocketConsumerPort} from './adb_socket_controller'; -import {ChromeExtensionConsumerPort} from './chrome_proxy_record_controller'; -import { - ConsumerPortResponse, - GetTraceStatsResponse, - isDisableTracingResponse, - isEnableTracingResponse, - isFreeBuffersResponse, - isGetTraceStatsResponse, - isReadBuffersResponse, -} from './consumer_port_types'; -import {Controller} from './controller'; -import {RecordConfig} from './record_config_types'; -import {Consumer, RpcConsumerPort} from './record_controller_interfaces'; - -type RPCImplMethod = (Method|rpc.ServiceMethod, Message<{}>>); - -export function genConfigProto( - uiCfg: RecordConfig, target: RecordingTarget): Uint8Array { - return TraceConfig.encode(convertToRecordingV2Input(uiCfg, target)).finish(); -} - -// This method converts the 'RecordingTarget' to the 'TargetInfo' used by V2 of -// the recording code. It is used so the logic is not duplicated and does not -// diverge. -// TODO(octaviant) delete this once we switch to RecordingV2. -function convertToRecordingV2Input( - uiCfg: RecordConfig, target: RecordingTarget): TraceConfig { - let targetType: 'ANDROID'|'CHROME'|'CHROME_OS'|'LINUX'; - let androidApiLevel!: number; - switch (target.os) { - case 'L': - targetType = 'LINUX'; - break; - case 'C': - targetType = 'CHROME'; - break; - case 'CrOS': - targetType = 'CHROME_OS'; - break; - case 'S': - androidApiLevel = 31; - targetType = 'ANDROID'; - break; - case 'R': - androidApiLevel = 30; - targetType = 'ANDROID'; - break; - case 'Q': - androidApiLevel = 29; - targetType = 'ANDROID'; - break; - case 'P': - androidApiLevel = 28; - targetType = 'ANDROID'; - break; - default: - androidApiLevel = 26; - targetType = 'ANDROID'; - } - - let targetInfo: TargetInfo; - if (targetType === 'ANDROID') { - targetInfo = { - targetType, - androidApiLevel, - dataSources: [], - name: '', - }; - } else if (targetType === 'CHROME' || targetType === 'CHROME_OS') { - targetInfo = { - targetType, - dataSources: [], - name: '', - }; - } else { - targetInfo = {targetType, dataSources: [], name: ''}; - } - - return genTraceConfig(uiCfg, targetInfo); -} - -export function toPbtxt(configBuffer: Uint8Array): string { - const msg = TraceConfig.decode(configBuffer); - const json = msg.toJSON(); - function snakeCase(s: string): string { - return s.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase()); - } - // With the ahead of time compiled protos we can't seem to tell which - // fields are enums. - function isEnum(value: string): boolean { - return value.startsWith('MEMINFO_') || value.startsWith('VMSTAT_') || - value.startsWith('STAT_') || value.startsWith('LID_') || - value.startsWith('BATTERY_COUNTER_') || value === 'DISCARD' || - value === 'RING_BUFFER'; - } - // Since javascript doesn't have 64 bit numbers when converting protos to - // json the proto library encodes them as strings. This is lossy since - // we can't tell which strings that look like numbers are actually strings - // and which are actually numbers. Ideally we would reflect on the proto - // definition somehow but for now we just hard code keys which have this - // problem in the config. - function is64BitNumber(key: string): boolean { - return [ - 'maxFileSizeBytes', - 'samplingIntervalBytes', - 'shmemSizeBytes', - 'pid', - ].includes(key); - } - function* message(msg: {}, indent: number): IterableIterator { - for (const [key, value] of Object.entries(msg)) { - const isRepeated = Array.isArray(value); - const isNested = typeof value === 'object' && !isRepeated; - for (const entry of (isRepeated ? value as Array<{}> : [value])) { - yield ' '.repeat(indent) + `${snakeCase(key)}${isNested ? '' : ':'} `; - if (typeof entry === 'string') { - if (isEnum(entry) || is64BitNumber(key)) { - yield entry; - } else { - yield `"${entry.replace(new RegExp('"', 'g'), '\\"')}"`; - } - } else if (typeof entry === 'number') { - yield entry.toString(); - } else if (typeof entry === 'boolean') { - yield entry.toString(); - } else if (typeof entry === 'object' && entry !== null) { - yield '{\n'; - yield* message(entry, indent + 4); - yield ' '.repeat(indent) + '}'; - } else { - throw new Error(`Record proto entry "${entry}" with unexpected type ${ - typeof entry}`); - } - yield '\n'; - } - } - } - return [...message(json, 0)].join(''); -} - -export class RecordController extends Controller<'main'> implements Consumer { - private config: RecordConfig|null = null; - private readonly extensionPort: MessagePort; - private recordingInProgress = false; - private consumerPort: ConsumerPort; - private traceBuffer: Uint8Array[] = []; - private bufferUpdateInterval: ReturnType|undefined; - private adb = new AdbOverWebUsb(); - private recordedTraceSuffix = TRACE_SUFFIX; - private fetchedCategories = false; - - // We have a different controller for each targetOS. The correct one will be - // created when needed, and stored here. When the key is a string, it is the - // serial of the target (used for android devices). When the key is a single - // char, it is the 'targetOS' - private controllerPromises = new Map>(); - - constructor(args: {extensionPort: MessagePort}) { - super('main'); - this.consumerPort = ConsumerPort.create(this.rpcImpl.bind(this)); - this.extensionPort = args.extensionPort; - } - - run() { - // TODO(eseckler): Use ConsumerPort's QueryServiceState instead - // of posting a custom extension message to retrieve the category list. - if (globals.state.fetchChromeCategories && !this.fetchedCategories) { - this.fetchedCategories = true; - if (globals.state.extensionInstalled) { - this.extensionPort.postMessage({method: 'GetCategories'}); - } - globals.dispatch(Actions.setFetchChromeCategories({fetch: false})); - } - if (globals.state.recordConfig === this.config && - globals.state.recordingInProgress === this.recordingInProgress) { - return; - } - this.config = globals.state.recordConfig; - - const configProto = - genConfigProto(this.config, globals.state.recordingTarget); - const configProtoText = toPbtxt(configProto); - const configProtoBase64 = base64Encode(configProto); - const commandline = ` - echo '${configProtoBase64}' | - base64 --decode | - adb shell "perfetto -c - -o /data/misc/perfetto-traces/trace" && - adb pull /data/misc/perfetto-traces/trace /tmp/trace - `; - const traceConfig = - convertToRecordingV2Input(this.config, globals.state.recordingTarget); - // TODO(hjd): This should not be TrackData after we unify the stores. - publishTrackData({ - id: 'config', - data: { - commandline, - pbBase64: configProtoBase64, - pbtxt: configProtoText, - traceConfig, - }, - }); - - // If the recordingInProgress boolean state is different, it means that we - // have to start or stop recording a trace. - if (globals.state.recordingInProgress === this.recordingInProgress) return; - this.recordingInProgress = globals.state.recordingInProgress; - - if (this.recordingInProgress) { - this.startRecordTrace(traceConfig); - } else { - this.stopRecordTrace(); - } - } - - startRecordTrace(traceConfig: TraceConfig) { - this.scheduleBufferUpdateRequests(); - this.traceBuffer = []; - this.consumerPort.enableTracing({traceConfig}); - } - - stopRecordTrace() { - if (this.bufferUpdateInterval) clearInterval(this.bufferUpdateInterval); - this.consumerPort.disableTracing({}); - } - - scheduleBufferUpdateRequests() { - if (this.bufferUpdateInterval) clearInterval(this.bufferUpdateInterval); - this.bufferUpdateInterval = setInterval(() => { - this.consumerPort.getTraceStats({}); - }, 200); - } - - readBuffers() { - this.consumerPort.readBuffers({}); - } - - onConsumerPortResponse(data: ConsumerPortResponse) { - if (data === undefined) return; - if (isReadBuffersResponse(data)) { - if (!data.slices || data.slices.length === 0) return; - // TODO(nicomazz): handle this as intended by consumer_port.proto. - console.assert(data.slices.length === 1); - if (data.slices[0].data) this.traceBuffer.push(data.slices[0].data); - // The line underneath is 'misusing' the format ReadBuffersResponse. - // The boolean field 'lastSliceForPacket' is used as 'lastPacketInTrace'. - // See http://shortn/_53WB8A1aIr. - if (data.slices[0].lastSliceForPacket) this.onTraceComplete(); - } else if (isEnableTracingResponse(data)) { - this.readBuffers(); - } else if (isGetTraceStatsResponse(data)) { - const percentage = this.getBufferUsagePercentage(data); - if (percentage) { - publishBufferUsage({percentage}); - } - } else if (isFreeBuffersResponse(data)) { - // No action required. - } else if (isDisableTracingResponse(data)) { - // No action required. - } else { - console.error('Unrecognized consumer port response:', data); - } - } - - onTraceComplete() { - this.consumerPort.freeBuffers({}); - globals.dispatch(Actions.setRecordingStatus({status: undefined})); - if (globals.state.recordingCancelled) { - globals.dispatch( - Actions.setLastRecordingError({error: 'Recording cancelled.'})); - this.traceBuffer = []; - return; - } - const trace = this.generateTrace(); - globals.dispatch(Actions.openTraceFromBuffer({ - title: 'Recorded trace', - buffer: trace.buffer, - fileName: `recorded_trace${this.recordedTraceSuffix}`, - })); - this.traceBuffer = []; - } - - // TODO(nicomazz): stream each chunk into the trace processor, instead of - // creating a big long trace. - generateTrace() { - let traceLen = 0; - for (const chunk of this.traceBuffer) traceLen += chunk.length; - const completeTrace = new Uint8Array(traceLen); - let written = 0; - for (const chunk of this.traceBuffer) { - completeTrace.set(chunk, written); - written += chunk.length; - } - return completeTrace; - } - - getBufferUsagePercentage(data: GetTraceStatsResponse): number { - if (!data.traceStats || !data.traceStats.bufferStats) return 0.0; - let maximumUsage = 0; - for (const buffer of data.traceStats.bufferStats) { - const used = buffer.bytesWritten as number; - const total = buffer.bufferSize as number; - maximumUsage = Math.max(maximumUsage, used / total); - } - return maximumUsage; - } - - onError(message: string) { - // TODO(octaviant): b/204998302 - console.error('Error in record controller: ', message); - globals.dispatch( - Actions.setLastRecordingError({error: message.substr(0, 150)})); - globals.dispatch(Actions.stopRecording({})); - } - - onStatus(message: string) { - globals.dispatch(Actions.setRecordingStatus({status: message})); - } - - // Depending on the recording target, different implementation of the - // consumer_port will be used. - // - Chrome target: This forwards the messages that have to be sent - // to the extension to the frontend. This is necessary because this - // controller is running in a separate worker, that can't directly send - // messages to the extension. - // - Android device target: WebUSB is used to communicate using the adb - // protocol. Actually, there is no full consumer_port implementation, but - // only the support to start tracing and fetch the file. - async getTargetController(target: RecordingTarget): Promise { - const identifier = RecordController.getTargetIdentifier(target); - - // The reason why caching the target 'record controller' Promise is that - // multiple rcp calls can happen while we are trying to understand if an - // android device has a socket connection available or not. - const precedentPromise = this.controllerPromises.get(identifier); - if (precedentPromise) return precedentPromise; - - const controllerPromise = - new Promise(async (resolve, _) => { - let controller: RpcConsumerPort|undefined = undefined; - if (isChromeTarget(target)) { - controller = - new ChromeExtensionConsumerPort(this.extensionPort, this); - } else if (isAdbTarget(target)) { - this.onStatus(`Please allow USB debugging on device. - If you press cancel, reload the page.`); - const socketAccess = await this.hasSocketAccess(target); - - controller = socketAccess ? - new AdbSocketConsumerPort(this.adb, this) : - new AdbConsumerPort(this.adb, this); - } else { - throw Error(`No device connected`); - } - - if (!controller) throw Error(`Unknown target: ${target}`); - resolve(controller); - }); - - this.controllerPromises.set(identifier, controllerPromise); - return controllerPromise; - } - - private static getTargetIdentifier(target: RecordingTarget): string { - return isAdbTarget(target) ? target.serial : target.os; - } - - private async hasSocketAccess(target: AdbRecordingTarget) { - const devices = await navigator.usb.getDevices(); - const device = devices.find((d) => d.serialNumber === target.serial); - console.assert(device); - if (!device) return Promise.resolve(false); - return AdbSocketConsumerPort.hasSocketAccess(device, this.adb); - } - - private async rpcImpl( - method: RPCImplMethod, requestData: Uint8Array, - _callback: RPCImplCallback) { - try { - const state = globals.state; - // TODO(hjd): This is a bit weird. We implicitly send each RPC message to - // whichever target is currently selected (creating that target if needed) - // it would be nicer if the setup/teardown was more explicit. - const target = await this.getTargetController(state.recordingTarget); - this.recordedTraceSuffix = target.getRecordedTraceSuffix(); - target.handleCommand(method.name, requestData); - } catch (e) { - console.error(`error invoking ${method}: ${e.message}`); - } - } -} diff --git a/third_party/perfetto/ui/src/controller/record_controller_interfaces.ts b/third_party/perfetto/ui/src/controller/record_controller_interfaces.ts deleted file mode 100644 index e9662fdfba54..000000000000 --- a/third_party/perfetto/ui/src/controller/record_controller_interfaces.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {TRACE_SUFFIX} from '../common/constants'; -import {ConsumerPortResponse} from './consumer_port_types'; - -export type ErrorCallback = (_: string) => void; -export type StatusCallback = (_: string) => void; - -export abstract class RpcConsumerPort { - // The responses of the call invocations should be sent through this listener. - // This is done by the 3 "send" methods in this abstract class. - private consumerPortListener: Consumer; - - protected constructor(consumerPortListener: Consumer) { - this.consumerPortListener = consumerPortListener; - } - - // RequestData is the proto representing the arguments of the function call. - abstract handleCommand(methodName: string, requestData: Uint8Array): void; - - sendMessage(data: ConsumerPortResponse) { - this.consumerPortListener.onConsumerPortResponse(data); - } - - sendErrorMessage(message: string) { - this.consumerPortListener.onError(message); - } - - sendStatus(status: string) { - this.consumerPortListener.onStatus(status); - } - - // Allows the recording controller to customise the suffix added to recorded - // traces when they are downloaded. In the general case this will be - // .perfetto-trace however if the trace is recorded compressed if could be - // .perfetto-trace.gz etc. - getRecordedTraceSuffix(): string { - return TRACE_SUFFIX; - } -} - -export interface Consumer { - onConsumerPortResponse(data: ConsumerPortResponse): void; - onError: ErrorCallback; - onStatus: StatusCallback; -} diff --git a/third_party/perfetto/ui/src/controller/record_controller_jsdomtest.ts b/third_party/perfetto/ui/src/controller/record_controller_jsdomtest.ts deleted file mode 100644 index f1602908b08a..000000000000 --- a/third_party/perfetto/ui/src/controller/record_controller_jsdomtest.ts +++ /dev/null @@ -1,420 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertExists} from '../base/logging'; -import {TraceConfig} from '../common/protos'; - -import {createEmptyRecordConfig} from './record_config_types'; -import {genConfigProto, toPbtxt} from './record_controller'; - -test('encodeConfig', () => { - const config = createEmptyRecordConfig(); - config.durationMs = 20000; - const result = - TraceConfig.decode(genConfigProto(config, {os: 'Q', name: 'Android Q'})); - expect(result.durationMs).toBe(20000); -}); - -test('SysConfig', () => { - const config = createEmptyRecordConfig(); - config.cpuSyscall = true; - const result = - TraceConfig.decode(genConfigProto(config, {os: 'Q', name: 'Android Q'})); - const sources = assertExists(result.dataSources); - const srcConfig = assertExists(sources[0].config); - const ftraceConfig = assertExists(srcConfig.ftraceConfig); - const ftraceEvents = assertExists(ftraceConfig.ftraceEvents); - expect(ftraceEvents.includes('raw_syscalls/sys_enter')).toBe(true); - expect(ftraceEvents.includes('raw_syscalls/sys_exit')).toBe(true); -}); - -test('cpu scheduling includes kSyms if OS >= S', () => { - const config = createEmptyRecordConfig(); - config.cpuSched = true; - const result = - TraceConfig.decode(genConfigProto(config, {os: 'S', name: 'Android S'})); - const sources = assertExists(result.dataSources); - const srcConfig = assertExists(sources[1].config); - const ftraceConfig = assertExists(srcConfig.ftraceConfig); - const ftraceEvents = assertExists(ftraceConfig.ftraceEvents); - expect(ftraceConfig.symbolizeKsyms).toBe(true); - expect(ftraceEvents.includes('sched/sched_blocked_reason')).toBe(true); -}); - -test('cpu scheduling does not include kSyms if OS <= S', () => { - const config = createEmptyRecordConfig(); - config.cpuSched = true; - const result = - TraceConfig.decode(genConfigProto(config, {os: 'Q', name: 'Android Q'})); - const sources = assertExists(result.dataSources); - const srcConfig = assertExists(sources[1].config); - const ftraceConfig = assertExists(srcConfig.ftraceConfig); - const ftraceEvents = assertExists(ftraceConfig.ftraceEvents); - expect(ftraceConfig.symbolizeKsyms).toBe(false); - expect(ftraceEvents.includes('sched/sched_blocked_reason')).toBe(false); -}); - -test('kSyms can be enabled individually', () => { - const config = createEmptyRecordConfig(); - config.ftrace = true; - config.symbolizeKsyms = true; - const result = - TraceConfig.decode(genConfigProto(config, {os: 'Q', name: 'Android Q'})); - const sources = assertExists(result.dataSources); - const srcConfig = assertExists(sources[0].config); - const ftraceConfig = assertExists(srcConfig.ftraceConfig); - const ftraceEvents = assertExists(ftraceConfig.ftraceEvents); - expect(ftraceConfig.symbolizeKsyms).toBe(true); - expect(ftraceEvents.includes('sched/sched_blocked_reason')).toBe(true); -}); - -test('kSyms can be disabled individually', () => { - const config = createEmptyRecordConfig(); - config.ftrace = true; - config.symbolizeKsyms = false; - const result = - TraceConfig.decode(genConfigProto(config, {os: 'Q', name: 'Android Q'})); - const sources = assertExists(result.dataSources); - const srcConfig = assertExists(sources[0].config); - const ftraceConfig = assertExists(srcConfig.ftraceConfig); - const ftraceEvents = assertExists(ftraceConfig.ftraceEvents); - expect(ftraceConfig.symbolizeKsyms).toBe(false); - expect(ftraceEvents.includes('sched/sched_blocked_reason')).toBe(false); -}); - -test('toPbtxt', () => { - const config = { - durationMs: 1000, - maxFileSizeBytes: 43, - buffers: [ - { - sizeKb: 42, - }, - ], - dataSources: [{ - config: { - name: 'linux.ftrace', - targetBuffer: 1, - ftraceConfig: { - ftraceEvents: ['sched_switch', 'print'], - }, - }, - }], - producers: [ - { - producerName: 'perfetto.traced_probes', - }, - ], - }; - - const text = toPbtxt(TraceConfig.encode(config).finish()); - - expect(text).toEqual(`buffers: { - size_kb: 42 -} -data_sources: { - config { - name: "linux.ftrace" - target_buffer: 1 - ftrace_config { - ftrace_events: "sched_switch" - ftrace_events: "print" - } - } -} -duration_ms: 1000 -producers: { - producer_name: "perfetto.traced_probes" -} -max_file_size_bytes: 43 -`); -}); - -test('ChromeConfig', () => { - const config = createEmptyRecordConfig(); - config.ipcFlows = true; - config.jsExecution = true; - config.mode = 'STOP_WHEN_FULL'; - const result = - TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'})); - const sources = assertExists(result.dataSources); - - const traceConfigSource = assertExists(sources[0].config); - expect(traceConfigSource.name).toBe('org.chromium.trace_event'); - const chromeConfig = assertExists(traceConfigSource.chromeConfig); - expect(chromeConfig.privacyFilteringEnabled).toBe(false); - const traceConfig = assertExists(chromeConfig.traceConfig); - - const trackEventConfigSource = assertExists(sources[1].config); - expect(trackEventConfigSource.name).toBe('track_event'); - const trackEventConfig = - assertExists(trackEventConfigSource.trackEventConfig); - expect(trackEventConfig.filterDynamicEventNames).toBe(false); - expect(trackEventConfig.filterDebugAnnotations).toBe(false); - const chromeConfigT = assertExists(trackEventConfigSource.chromeConfig); - const traceConfigT = assertExists(chromeConfigT.traceConfig); - - const metadataConfigSource = assertExists(sources[2].config); - expect(metadataConfigSource.name).toBe('org.chromium.trace_metadata'); - const chromeConfigM = assertExists(metadataConfigSource.chromeConfig); - const traceConfigM = assertExists(chromeConfigM.traceConfig); - - const expectedTraceConfig = '{"record_mode":"record-until-full",' + - '"included_categories":' + - '["toplevel","toplevel.flow","disabled-by-default-ipc.flow",' + - '"mojom","v8"],' + - '"excluded_categories":["*"],' + - '"memory_dump_config":{}}'; - expect(traceConfig).toEqual(expectedTraceConfig); - expect(traceConfigT).toEqual(expectedTraceConfig); - expect(traceConfigM).toEqual(expectedTraceConfig); -}); - -test('ChromeConfig with privacy filtering', () => { - const config = createEmptyRecordConfig(); - config.ipcFlows = true; - config.jsExecution = true; - config.mode = 'STOP_WHEN_FULL'; - config.chromePrivacyFiltering = true; - const result = - TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'})); - const sources = assertExists(result.dataSources); - - const traceConfigSource = assertExists(sources[0].config); - expect(traceConfigSource.name).toBe('org.chromium.trace_event'); - const chromeConfig = assertExists(traceConfigSource.chromeConfig); - expect(chromeConfig.privacyFilteringEnabled).toBe(true); - - const trackEventConfigSource = assertExists(sources[1].config); - expect(trackEventConfigSource.name).toBe('track_event'); - const trackEventConfig = - assertExists(trackEventConfigSource.trackEventConfig); - expect(trackEventConfig.filterDynamicEventNames).toBe(true); - expect(trackEventConfig.filterDebugAnnotations).toBe(true); -}); - -test('ChromeMemoryConfig', () => { - const config = createEmptyRecordConfig(); - config.chromeHighOverheadCategoriesSelected = - ['disabled-by-default-memory-infra']; - const result = - TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'})); - const sources = assertExists(result.dataSources); - - const traceConfigSource = assertExists(sources[0].config); - expect(traceConfigSource.name).toBe('org.chromium.trace_event'); - const chromeConfig = assertExists(traceConfigSource.chromeConfig); - const traceConfig = assertExists(chromeConfig.traceConfig); - - const trackEventConfigSource = assertExists(sources[1].config); - expect(trackEventConfigSource.name).toBe('track_event'); - const chromeConfigT = assertExists(trackEventConfigSource.chromeConfig); - const traceConfigT = assertExists(chromeConfigT.traceConfig); - - const metadataConfigSource = assertExists(sources[2].config); - expect(metadataConfigSource.name).toBe('org.chromium.trace_metadata'); - const chromeConfigM = assertExists(metadataConfigSource.chromeConfig); - const traceConfigM = assertExists(chromeConfigM.traceConfig); - - const miConfigSource = assertExists(sources[3].config); - expect(miConfigSource.name).toBe('org.chromium.memory_instrumentation'); - const chromeConfigI = assertExists(miConfigSource.chromeConfig); - const traceConfigI = assertExists(chromeConfigI.traceConfig); - - const hpConfigSource = assertExists(sources[4].config); - expect(hpConfigSource.name).toBe('org.chromium.native_heap_profiler'); - const chromeConfigH = assertExists(hpConfigSource.chromeConfig); - const traceConfigH = assertExists(chromeConfigH.traceConfig); - - const expectedTraceConfig = '{"record_mode":"record-until-full",' + - '"included_categories":["disabled-by-default-memory-infra"],' + - '"excluded_categories":["*"],' + - '"memory_dump_config":{"allowed_dump_modes":["background",' + - '"light","detailed"],"triggers":[{"min_time_between_dumps_ms":' + - '10000,"mode":"detailed","type":"periodic_interval"}]}}'; - expect(traceConfig).toEqual(expectedTraceConfig); - expect(traceConfigT).toEqual(expectedTraceConfig); - expect(traceConfigM).toEqual(expectedTraceConfig); - expect(traceConfigI).toEqual(expectedTraceConfig); - expect(traceConfigH).toEqual(expectedTraceConfig); -}); - -test('ChromeCpuProfilerConfig', () => { - const config = createEmptyRecordConfig(); - config.chromeHighOverheadCategoriesSelected = - ['disabled-by-default-cpu_profiler']; - const decoded = - TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'})); - const sources = assertExists(decoded.dataSources); - - const traceConfigSource = assertExists(sources[0].config); - expect(traceConfigSource.name).toBe('org.chromium.trace_event'); - const traceEventChromeConfig = assertExists(traceConfigSource.chromeConfig); - const traceEventConfig = assertExists(traceEventChromeConfig.traceConfig); - - const trackEventConfigSource = assertExists(sources[1].config); - expect(trackEventConfigSource.name).toBe('track_event'); - const chromeConfigT = assertExists(trackEventConfigSource.chromeConfig); - const traceConfigT = assertExists(chromeConfigT.traceConfig); - - const metadataConfigSource = assertExists(sources[2].config); - expect(metadataConfigSource.name).toBe('org.chromium.trace_metadata'); - const traceMetadataChromeConfig = - assertExists(metadataConfigSource.chromeConfig); - const traceMetadataConfig = - assertExists(traceMetadataChromeConfig.traceConfig); - - const profilerConfigSource = assertExists(sources[3].config); - expect(profilerConfigSource.name).toBe('org.chromium.sampler_profiler'); - const profilerChromeConfig = assertExists(profilerConfigSource.chromeConfig); - const profilerConfig = assertExists(profilerChromeConfig.traceConfig); - - const expectedTraceConfig = '{"record_mode":"record-until-full",' + - '"included_categories":["disabled-by-default-cpu_profiler"],' + - '"excluded_categories":["*"],"memory_dump_config":{}}'; - expect(traceEventConfig).toEqual(expectedTraceConfig); - expect(traceConfigT).toEqual(expectedTraceConfig); - expect(traceMetadataConfig).toEqual(expectedTraceConfig); - expect(profilerConfig).toEqual(expectedTraceConfig); -}); - -test('ChromeCpuProfilerDebugConfig', () => { - const config = createEmptyRecordConfig(); - config.chromeHighOverheadCategoriesSelected = - ['disabled-by-default-cpu_profiler.debug']; - const decoded = - TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'})); - const sources = assertExists(decoded.dataSources); - - const traceConfigSource = assertExists(sources[0].config); - expect(traceConfigSource.name).toBe('org.chromium.trace_event'); - const traceEventChromeConfig = assertExists(traceConfigSource.chromeConfig); - const traceEventConfig = assertExists(traceEventChromeConfig.traceConfig); - - const trackEventConfigSource = assertExists(sources[1].config); - expect(trackEventConfigSource.name).toBe('track_event'); - const chromeConfigT = assertExists(trackEventConfigSource.chromeConfig); - const traceConfigT = assertExists(chromeConfigT.traceConfig); - - const metadataConfigSource = assertExists(sources[2].config); - expect(metadataConfigSource.name).toBe('org.chromium.trace_metadata'); - const traceMetadataChromeConfig = - assertExists(metadataConfigSource.chromeConfig); - const traceMetadataConfig = - assertExists(traceMetadataChromeConfig.traceConfig); - - const profilerConfigSource = assertExists(sources[3].config); - expect(profilerConfigSource.name).toBe('org.chromium.sampler_profiler'); - const profilerChromeConfig = assertExists(profilerConfigSource.chromeConfig); - const profilerConfig = assertExists(profilerChromeConfig.traceConfig); - - const expectedTraceConfig = '{"record_mode":"record-until-full",' + - '"included_categories":["disabled-by-default-cpu_profiler.debug"],' + - '"excluded_categories":["*"],"memory_dump_config":{}}'; - expect(traceConfigT).toEqual(expectedTraceConfig); - expect(traceEventConfig).toEqual(expectedTraceConfig); - expect(traceMetadataConfig).toEqual(expectedTraceConfig); - expect(profilerConfig).toEqual(expectedTraceConfig); -}); - -test('ChromeConfigRingBuffer', () => { - const config = createEmptyRecordConfig(); - config.ipcFlows = true; - config.jsExecution = true; - config.mode = 'RING_BUFFER'; - const result = - TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'})); - const sources = assertExists(result.dataSources); - - const traceConfigSource = assertExists(sources[0].config); - expect(traceConfigSource.name).toBe('org.chromium.trace_event'); - const chromeConfig = assertExists(traceConfigSource.chromeConfig); - const traceConfig = assertExists(chromeConfig.traceConfig); - - const trackEventConfigSource = assertExists(sources[1].config); - expect(trackEventConfigSource.name).toBe('track_event'); - const chromeConfigT = assertExists(trackEventConfigSource.chromeConfig); - const traceConfigT = assertExists(chromeConfigT.traceConfig); - - const metadataConfigSource = assertExists(sources[2].config); - expect(metadataConfigSource.name).toBe('org.chromium.trace_metadata'); - const chromeConfigM = assertExists(metadataConfigSource.chromeConfig); - const traceConfigM = assertExists(chromeConfigM.traceConfig); - - const expectedTraceConfig = '{"record_mode":"record-continuously",' + - '"included_categories":' + - '["toplevel","toplevel.flow","disabled-by-default-ipc.flow",' + - '"mojom","v8"],' + - '"excluded_categories":["*"],"memory_dump_config":{}}'; - expect(traceConfig).toEqual(expectedTraceConfig); - expect(traceConfigT).toEqual(expectedTraceConfig); - expect(traceConfigM).toEqual(expectedTraceConfig); -}); - -test('ChromeConfigLongTrace', () => { - const config = createEmptyRecordConfig(); - config.ipcFlows = true; - config.jsExecution = true; - config.mode = 'RING_BUFFER'; - const result = - TraceConfig.decode(genConfigProto(config, {os: 'C', name: 'Chrome'})); - const sources = assertExists(result.dataSources); - - const traceConfigSource = assertExists(sources[0].config); - expect(traceConfigSource.name).toBe('org.chromium.trace_event'); - const chromeConfig = assertExists(traceConfigSource.chromeConfig); - const traceConfig = assertExists(chromeConfig.traceConfig); - - const trackEventConfigSource = assertExists(sources[1].config); - expect(trackEventConfigSource.name).toBe('track_event'); - const chromeConfigT = assertExists(trackEventConfigSource.chromeConfig); - const traceConfigT = assertExists(chromeConfigT.traceConfig); - - const metadataConfigSource = assertExists(sources[2].config); - expect(metadataConfigSource.name).toBe('org.chromium.trace_metadata'); - const chromeConfigM = assertExists(metadataConfigSource.chromeConfig); - const traceConfigM = assertExists(chromeConfigM.traceConfig); - - const expectedTraceConfig = '{"record_mode":"record-continuously",' + - '"included_categories":' + - '["toplevel","toplevel.flow","disabled-by-default-ipc.flow",' + - '"mojom","v8"],' + - '"excluded_categories":["*"],"memory_dump_config":{}}'; - expect(traceConfig).toEqual(expectedTraceConfig); - expect(traceConfigT).toEqual(expectedTraceConfig); - expect(traceConfigM).toEqual(expectedTraceConfig); -}); - -test('ChromeConfigToPbtxt', () => { - const config = { - dataSources: [{ - config: { - name: 'org.chromium.trace_event', - chromeConfig: - {traceConfig: JSON.stringify({included_categories: ['v8']})}, - }, - }], - }; - const text = toPbtxt(TraceConfig.encode(config).finish()); - - expect(text).toEqual(`data_sources: { - config { - name: "org.chromium.trace_event" - chrome_config { - trace_config: "{\\"included_categories\\":[\\"v8\\"]}" - } - } -} -`); -}); diff --git a/third_party/perfetto/ui/src/controller/search_controller.ts b/third_party/perfetto/ui/src/controller/search_controller.ts deleted file mode 100644 index d1df094d201f..000000000000 --- a/third_party/perfetto/ui/src/controller/search_controller.ts +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {sqliteString} from '../base/string_utils'; -import {Engine} from '../common/engine'; -import {NUM, STR} from '../common/query_result'; -import {escapeSearchQuery} from '../common/query_utils'; -import {CurrentSearchResults, SearchSummary} from '../common/search_data'; -import {TimeSpan} from '../common/time'; -import {toNs} from '../common/time'; -import {globals} from '../frontend/globals'; -import {publishSearch, publishSearchResult} from '../frontend/publish'; - -import {Controller} from './controller'; - -export interface SearchControllerArgs { - engine: Engine; -} - -export class SearchController extends Controller<'main'> { - private engine: Engine; - private previousSpan: TimeSpan; - private previousResolution: number; - private previousSearch: string; - private updateInProgress: boolean; - private setupInProgress: boolean; - - constructor(args: SearchControllerArgs) { - super('main'); - this.engine = args.engine; - this.previousSpan = new TimeSpan(0, 1); - this.previousSearch = ''; - this.updateInProgress = false; - this.setupInProgress = true; - this.previousResolution = 1; - this.setup().finally(() => { - this.setupInProgress = false; - this.run(); - }); - } - - private async setup() { - await this.query(`create virtual table search_summary_window - using window;`); - await this.query(`create virtual table search_summary_sched_span using - span_join(sched PARTITIONED cpu, search_summary_window);`); - await this.query(`create virtual table search_summary_slice_span using - span_join(slice PARTITIONED track_id, search_summary_window);`); - } - - run() { - if (this.setupInProgress || this.updateInProgress) { - return; - } - - const visibleState = globals.state.frontendLocalState.visibleState; - const omniboxState = globals.state.omniboxState; - if (visibleState === undefined || omniboxState === undefined || - omniboxState.mode === 'COMMAND') { - return; - } - const newSpan = new TimeSpan(visibleState.startSec, visibleState.endSec); - const newSearch = omniboxState.omnibox; - let newResolution = visibleState.resolution; - if (this.previousSpan.contains(newSpan) && - this.previousResolution === newResolution && - newSearch === this.previousSearch) { - return; - } - - - // TODO(hjd): We should restrict this to the start of the trace but - // that is not easily available here. - // N.B. Timestamps can be negative. - const start = newSpan.start - newSpan.duration; - const end = newSpan.end + newSpan.duration; - this.previousSpan = new TimeSpan(start, end); - this.previousResolution = newResolution; - this.previousSearch = newSearch; - if (newSearch === '' || newSearch.length < 4) { - publishSearch({ - tsStarts: new Float64Array(0), - tsEnds: new Float64Array(0), - count: new Uint8Array(0), - }); - publishSearchResult({ - sliceIds: new Float64Array(0), - tsStarts: new Float64Array(0), - utids: new Float64Array(0), - sources: [], - trackIds: [], - totalResults: 0, - }); - return; - } - - let startNs = toNs(newSpan.start); - let endNs = toNs(newSpan.end); - - // TODO(hjd): We shouldn't need to be so defensive here: - if (!Number.isFinite(startNs)) { - startNs = 0; - } - if (!Number.isFinite(endNs)) { - endNs = 1; - } - if (!Number.isFinite(newResolution)) { - newResolution = 1; - } - - this.updateInProgress = true; - const computeSummary = this.update(newSearch, startNs, endNs, newResolution) - .then((summary) => { - publishSearch(summary); - }); - - const computeResults = - this.specificSearch(newSearch).then((searchResults) => { - publishSearchResult(searchResults); - }); - - Promise.all([computeSummary, computeResults]) - .finally(() => { - this.updateInProgress = false; - this.run(); - }); - } - - onDestroy() {} - - private async update( - search: string, startNs: number, endNs: number, - resolution: number): Promise { - const quantumNs = Math.round(resolution * 10 * 1e9); - - const searchLiteral = escapeSearchQuery(search); - - startNs = Math.floor(startNs / quantumNs) * quantumNs; - - const windowDur = Math.max(endNs - startNs, 1); - await this.query(`update search_summary_window set - window_start=${startNs}, - window_dur=${windowDur}, - quantum=${quantumNs} - where rowid = 0;`); - - const utidRes = await this.query(`select utid from thread join process - using(upid) where thread.name glob ${searchLiteral} - or process.name glob ${searchLiteral}`); - - const utids = []; - for (const it = utidRes.iter({utid: NUM}); it.valid(); it.next()) { - utids.push(it.utid); - } - - const cpus = await this.engine.getCpus(); - const maxCpu = Math.max(...cpus, -1); - - const res = await this.query(` - select - (quantum_ts * ${quantumNs} + ${startNs})/1e9 as tsStart, - ((quantum_ts+1) * ${quantumNs} + ${startNs})/1e9 as tsEnd, - min(count(*), 255) as count - from ( - select - quantum_ts - from search_summary_sched_span - where utid in (${utids.join(',')}) and cpu <= ${maxCpu} - union all - select - quantum_ts - from search_summary_slice_span - where name glob ${searchLiteral} - ) - group by quantum_ts - order by quantum_ts;`); - - const numRows = res.numRows(); - const summary = { - tsStarts: new Float64Array(numRows), - tsEnds: new Float64Array(numRows), - count: new Uint8Array(numRows), - }; - - const it = res.iter({tsStart: NUM, tsEnd: NUM, count: NUM}); - for (let row = 0; it.valid(); it.next(), ++row) { - summary.tsStarts[row] = it.tsStart; - summary.tsEnds[row] = it.tsEnd; - summary.count[row] = it.count; - } - return summary; - } - - private async specificSearch(search: string) { - const searchLiteral = escapeSearchQuery(search); - // TODO(hjd): we should avoid recomputing this every time. This will be - // easier once the track table has entries for all the tracks. - const cpuToTrackId = new Map(); - for (const track of Object.values(globals.state.tracks)) { - if (track.kind === 'CpuSliceTrack') { - cpuToTrackId.set((track.config as {cpu: number}).cpu, track.id); - continue; - } - } - - const utidRes = await this.query(`select utid from thread join process - using(upid) where - thread.name glob ${searchLiteral} or - process.name glob ${searchLiteral}`); - const utids = []; - for (const it = utidRes.iter({utid: NUM}); it.valid(); it.next()) { - utids.push(it.utid); - } - - const queryRes = await this.query(` - select - id as sliceId, - ts, - 'cpu' as source, - cpu as sourceId, - utid - from sched where utid in (${utids.join(',')}) - union - select - slice_id as sliceId, - ts, - 'track' as source, - track_id as sourceId, - 0 as utid - from slice - where slice.name glob ${searchLiteral} - or ( - 0 != CAST(${(sqliteString(search))} AS INT) and - sliceId = CAST(${(sqliteString(search))} AS INT) - ) - union - select - slice_id as sliceId, - ts, - 'track' as source, - track_id as sourceId, - 0 as utid - from slice - join args using(arg_set_id) - where string_value glob ${searchLiteral} or key glob ${searchLiteral} - union - select - id as sliceId, - ts, - 'log' as source, - 0 as sourceId, - utid - from android_logs where msg glob ${searchLiteral} - order by ts - - `); - - const rows = queryRes.numRows(); - const searchResults: CurrentSearchResults = { - sliceIds: new Float64Array(rows), - tsStarts: new Float64Array(rows), - utids: new Float64Array(rows), - trackIds: [], - sources: [], - totalResults: 0, - }; - - const it = queryRes.iter( - {sliceId: NUM, ts: NUM, source: STR, sourceId: NUM, utid: NUM}); - for (; it.valid(); it.next()) { - let trackId = undefined; - if (it.source === 'cpu') { - trackId = cpuToTrackId.get(it.sourceId); - } else if (it.source === 'track') { - trackId = globals.state.uiTrackIdByTraceTrackId[it.sourceId]; - } else if (it.source === 'log') { - const logTracks = Object.values(globals.state.tracks) - .filter((t) => t.kind === 'AndroidLogTrack'); - if (logTracks.length > 0) { - trackId = logTracks[0].id; - } - } - - // The .get() calls above could return undefined, this isn't just an else. - if (trackId === undefined) { - continue; - } - - const i = searchResults.totalResults++; - searchResults.trackIds.push(trackId); - searchResults.sources.push(it.source); - searchResults.sliceIds[i] = it.sliceId; - searchResults.tsStarts[i] = it.ts; - searchResults.utids[i] = it.utid; - } - return searchResults; - } - - private async query(query: string) { - const result = await this.engine.query(query); - return result; - } -} diff --git a/third_party/perfetto/ui/src/controller/selection_controller.ts b/third_party/perfetto/ui/src/controller/selection_controller.ts deleted file mode 100644 index 66cf3d005660..000000000000 --- a/third_party/perfetto/ui/src/controller/selection_controller.ts +++ /dev/null @@ -1,522 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertTrue} from '../base/logging'; -import {Arg, Args} from '../common/arg_types'; -import {Engine} from '../common/engine'; -import { - NUM, - NUM_NULL, - STR, - STR_NULL, -} from '../common/query_result'; -import {ChromeSliceSelection} from '../common/state'; -import {fromNs, toNs} from '../common/time'; -import {SliceDetails, ThreadStateDetails} from '../frontend/globals'; -import {globals} from '../frontend/globals'; -import { - publishCounterDetails, - publishSliceDetails, - publishThreadStateDetails, -} from '../frontend/publish'; -import {SLICE_TRACK_KIND} from '../tracks/chrome_slices'; - -import {parseArgs} from './args_parser'; -import {Controller} from './controller'; - -export interface SelectionControllerArgs { - engine: Engine; -} - -interface ThreadDetails { - tid: number; - threadName?: string; -} - -interface ProcessDetails { - pid?: number; - processName?: string; - uid?: number; - packageName?: string; - versionCode?: number; -} - -// This class queries the TP for the details on a specific slice that has -// been clicked. -export class SelectionController extends Controller<'main'> { - private lastSelectedId?: number|string; - private lastSelectedKind?: string; - constructor(private args: SelectionControllerArgs) { - super('main'); - } - - run() { - const selection = globals.state.currentSelection; - if (!selection || selection.kind === 'AREA') return; - - const selectWithId = - ['SLICE', 'COUNTER', 'CHROME_SLICE', 'HEAP_PROFILE', 'THREAD_STATE']; - if (!selectWithId.includes(selection.kind) || - (selectWithId.includes(selection.kind) && - selection.id === this.lastSelectedId && - selection.kind === this.lastSelectedKind)) { - return; - } - const selectedId = selection.id; - const selectedKind = selection.kind; - this.lastSelectedId = selectedId; - this.lastSelectedKind = selectedKind; - - if (selectedId === undefined) return; - - if (selection.kind === 'COUNTER') { - this.counterDetails(selection.leftTs, selection.rightTs, selection.id) - .then((results) => { - if (results !== undefined && selection && - selection.kind === selectedKind && - selection.id === selectedId) { - publishCounterDetails(results); - } - }); - } else if (selection.kind === 'SLICE') { - this.sliceDetails(selectedId as number); - } else if (selection.kind === 'THREAD_STATE') { - this.threadStateDetails(selection.id); - } else if (selection.kind === 'CHROME_SLICE') { - this.chromeSliceDetails(selection); - } - } - - async chromeSliceDetails(selection: ChromeSliceSelection) { - const selectedId = selection.id; - const table = selection.table; - - let leafTable: string; - let promisedArgs: Promise; - // TODO(b/155483804): This is a hack to ensure annotation slices are - // selectable for now. We should tidy this up when improving this class. - if (table === 'annotation') { - leafTable = 'annotation_slice'; - promisedArgs = Promise.resolve(new Map()); - } else { - const result = await this.args.engine.query(` - SELECT - type as leafTable, - arg_set_id as argSetId - FROM slice WHERE id = ${selectedId}`); - - if (result.numRows() === 0) { - return; - } - - const row = result.firstRow({ - leafTable: STR, - argSetId: NUM, - }); - - leafTable = row.leafTable; - const argSetId = row.argSetId; - promisedArgs = this.getArgs(argSetId); - } - - const promisedDetails = this.args.engine.query(` - SELECT *, ABS_TIME_STR(ts) as absTime FROM ${leafTable} WHERE id = ${ - selectedId}; - `); - - const [details, args] = await Promise.all([promisedDetails, promisedArgs]); - - if (details.numRows() <= 0) return; - const rowIter = details.iter({}); - assertTrue(rowIter.valid()); - - // A few columns are hard coded as part of the SliceDetails interface. - // Long term these should be handled generically as args but for now - // handle them specially: - let ts = undefined; - let absTime = undefined; - let dur = undefined; - let name = undefined; - let category = undefined; - let threadDur = undefined; - let threadTs = undefined; - let trackId = undefined; - - // We select all columns from the leafTable to ensure that we include - // additional fields from the child tables (like `thread_dur` from - // `thread_slice` or `frame_number` from `frame_slice`). - // However, this also includes some basic columns (especially from `slice`) - // that are not interesting (i.e. `arg_set_id`, which has already been used - // to resolve and show the arguments) and should not be shown to the user. - const ignoredColumns = [ - 'type', - 'depth', - 'parent_id', - 'stack_id', - 'parent_stack_id', - 'arg_set_id', - 'thread_instruction_count', - 'thread_instruction_delta', - ]; - - for (const k of details.columns()) { - const v = rowIter.get(k); - switch (k) { - case 'id': - break; - case 'ts': - ts = fromNs(Number(v)) - globals.state.traceTime.startSec; - break; - case 'thread_ts': - threadTs = fromNs(Number(v)); - break; - case 'absTime': - if (v) absTime = `${v}`; - break; - case 'name': - name = `${v}`; - break; - case 'dur': - dur = fromNs(Number(v)); - break; - case 'thread_dur': - threadDur = fromNs(Number(v)); - break; - case 'category': - case 'cat': - category = `${v}`; - break; - case 'track_id': - trackId = Number(v); - break; - default: - if (!ignoredColumns.includes(k)) args.set(k, `${v}`); - } - } - - const argsTree = parseArgs(args); - const selected: SliceDetails = { - id: selectedId, - ts, - threadTs, - absTime, - dur, - threadDur, - name, - category, - args, - argsTree, - }; - - if (trackId !== undefined) { - const columnInfo = (await this.args.engine.query(` - WITH - leafTrackTable AS (SELECT type FROM track WHERE id = ${trackId}), - cols AS ( - SELECT name - FROM pragma_table_info((SELECT type FROM leafTrackTable)) - ) - SELECT - type as leafTrackTable, - 'upid' in cols AS hasUpid, - 'utid' in cols AS hasUtid - FROM leafTrackTable - `)).firstRow({hasUpid: NUM, hasUtid: NUM, leafTrackTable: STR}); - const hasUpid = columnInfo.hasUpid !== 0; - const hasUtid = columnInfo.hasUtid !== 0; - - if (hasUtid) { - const utid = (await this.args.engine.query(` - SELECT utid - FROM ${columnInfo.leafTrackTable} - WHERE id = ${trackId}; - `)).firstRow({ - utid: NUM, - }).utid; - Object.assign(selected, await this.computeThreadDetails(utid)); - } else if (hasUpid) { - const upid = (await this.args.engine.query(` - SELECT upid - FROM ${columnInfo.leafTrackTable} - WHERE id = ${trackId}; - `)).firstRow({ - upid: NUM, - }).upid; - Object.assign(selected, await this.computeProcessDetails(upid)); - } - } - - // Check selection is still the same on completion of query. - if (selection === globals.state.currentSelection) { - publishSliceDetails(selected); - } - } - - async getArgs(argId: number): Promise { - const args = new Map(); - const query = ` - select - key AS name, - display_value AS value - FROM args - WHERE arg_set_id = ${argId} - `; - const result = await this.args.engine.query(query); - const it = result.iter({ - name: STR, - value: STR_NULL, - }); - for (; it.valid(); it.next()) { - const name = it.name; - const value = it.value || 'NULL'; - if (name === 'destination slice id' && !isNaN(Number(value))) { - const destTrackId = await this.getDestTrackId(value); - args.set( - 'Destination Slice', - {kind: 'SLICE', trackId: destTrackId, sliceId: Number(value)}); - } else { - args.set(name, value); - } - } - return args; - } - - async getDestTrackId(sliceId: string): Promise { - const trackIdQuery = `select track_id as trackId from slice - where slice_id = ${sliceId}`; - const result = await this.args.engine.query(trackIdQuery); - const trackIdTp = result.firstRow({trackId: NUM}).trackId; - // TODO(hjd): If we had a consistent mapping from TP track_id - // UI track id for slice tracks this would be unnecessary. - let trackId = ''; - for (const track of Object.values(globals.state.tracks)) { - if (track.kind === SLICE_TRACK_KIND && - (track.config as {trackId: number}).trackId === Number(trackIdTp)) { - trackId = track.id; - break; - } - } - return trackId; - } - - // TODO(altimin): We currently rely on the ThreadStateDetails for supporting - // marking the area (the rest goes is handled by ThreadStateTab - // directly. Refactor it to be plugin-friendly and remove this. - async threadStateDetails(id: number) { - const query = ` - SELECT - ts, - thread_state.dur as dur - from thread_state - where thread_state.id = ${id} - `; - const result = await this.args.engine.query(query); - - const selection = globals.state.currentSelection; - if (result.numRows() > 0 && selection) { - const row = result.firstRow({ - ts: NUM, - dur: NUM, - }); - const ts = row.ts; - const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec; - const dur = fromNs(row.dur); - const selected: ThreadStateDetails = {ts: timeFromStart, dur}; - publishThreadStateDetails(selected); - } - } - - async sliceDetails(id: number) { - const sqlQuery = `SELECT - sched.ts, - sched.dur, - sched.priority, - sched.end_state as endState, - sched.utid, - sched.cpu, - thread_state.id as threadStateId - FROM sched left join thread_state using(ts, utid, cpu) - WHERE sched.id = ${id}`; - const result = await this.args.engine.query(sqlQuery); - // Check selection is still the same on completion of query. - const selection = globals.state.currentSelection; - if (result.numRows() > 0 && selection) { - const row = result.firstRow({ - ts: NUM, - dur: NUM, - priority: NUM, - endState: STR_NULL, - utid: NUM, - cpu: NUM, - threadStateId: NUM_NULL, - }); - const ts = row.ts; - const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec; - const dur = fromNs(row.dur); - const priority = row.priority; - const endState = row.endState; - const utid = row.utid; - const cpu = row.cpu; - const threadStateId = row.threadStateId || undefined; - const selected: SliceDetails = { - ts: timeFromStart, - dur, - priority, - endState, - cpu, - id, - utid, - threadStateId, - }; - Object.assign(selected, await this.computeThreadDetails(utid)); - - this.schedulingDetails(ts, utid) - .then((wakeResult) => { - Object.assign(selected, wakeResult); - }) - .finally(() => { - publishSliceDetails(selected); - }); - } - } - - async counterDetails(ts: number, rightTs: number, id: number) { - const counter = await this.args.engine.query( - `SELECT value, track_id as trackId FROM counter WHERE id = ${id}`); - const row = counter.iter({ - value: NUM, - trackId: NUM, - }); - const value = row.value; - const trackId = row.trackId; - // Finding previous value. If there isn't previous one, it will return 0 for - // ts and value. - const previous = await this.args.engine.query(`SELECT - MAX(ts), - IFNULL(value, 0) as value - FROM counter WHERE ts < ${ts} and track_id = ${trackId}`); - const previousValue = previous.firstRow({value: NUM}).value; - const endTs = - rightTs !== -1 ? rightTs : toNs(globals.state.traceTime.endSec); - const delta = value - previousValue; - const duration = endTs - ts; - const startTime = fromNs(ts) - globals.state.traceTime.startSec; - const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId]; - const name = uiTrackId ? globals.state.tracks[uiTrackId].name : undefined; - return {startTime, value, delta, duration, name}; - } - - async schedulingDetails(ts: number, utid: number|Long) { - // Find the ts of the first wakeup before the current slice. - const wakeResult = await this.args.engine.query(` - select ts, waker_utid as wakerUtid - from thread_state - where utid = ${utid} and ts < ${ts} and state = 'R' - order by ts desc - limit 1 - `); - if (wakeResult.numRows() === 0) { - return undefined; - } - - const wakeFirstRow = wakeResult.firstRow({ts: NUM, wakerUtid: NUM_NULL}); - const wakeupTs = wakeFirstRow.ts; - const wakerUtid = wakeFirstRow.wakerUtid; - if (wakerUtid === null) { - return undefined; - } - - // Find the previous sched slice for the current utid. - const prevSchedResult = await this.args.engine.query(` - select ts - from sched - where utid = ${utid} and ts < ${ts} - order by ts desc - limit 1 - `); - - // If this is the first sched slice for this utid or if the wakeup found - // was after the previous slice then we know the wakeup was for this slice. - if (prevSchedResult.numRows() !== 0 && - wakeupTs < prevSchedResult.firstRow({ts: NUM}).ts) { - return undefined; - } - - // Find the sched slice with the utid of the waker running when the - // sched wakeup occurred. This is the waker. - const wakerResult = await this.args.engine.query(` - select cpu - from sched - where - utid = ${wakerUtid} and - ts < ${wakeupTs} and - ts + dur >= ${wakeupTs}; - `); - if (wakerResult.numRows() === 0) { - return undefined; - } - - const wakerRow = wakerResult.firstRow({cpu: NUM}); - return {wakeupTs: fromNs(wakeupTs), wakerUtid, wakerCpu: wakerRow.cpu}; - } - - async computeThreadDetails(utid: number): - Promise { - const threadInfo = (await this.args.engine.query(` - SELECT tid, name, upid - FROM thread - WHERE utid = ${utid}; - `)).firstRow({tid: NUM, name: STR_NULL, upid: NUM_NULL}); - const threadDetails = { - tid: threadInfo.tid, - threadName: threadInfo.name || undefined, - }; - if (threadInfo.upid) { - return Object.assign( - {}, threadDetails, await this.computeProcessDetails(threadInfo.upid)); - } - return threadDetails; - } - - async computeProcessDetails(upid: number): Promise { - const details: ProcessDetails = {}; - const processResult = (await this.args.engine.query(` - SELECT pid, name, uid FROM process WHERE upid = ${upid}; - `)).firstRow({pid: NUM, name: STR_NULL, uid: NUM_NULL}); - details.pid = processResult.pid; - details.processName = processResult.name || undefined; - if (processResult.uid === null) { - return details; - } - details.uid = processResult.uid; - - const packageResult = await this.args.engine.query(` - SELECT - package_name as packageName, - version_code as versionCode - FROM package_list WHERE uid = ${details.uid}; - `); - // The package_list table is not populated in some traces so we need to - // check if the result has returned any rows. - if (packageResult.numRows() > 0) { - const packageDetails = packageResult.firstRow({ - packageName: STR, - versionCode: NUM, - }); - details.packageName = packageDetails.packageName; - details.versionCode = packageDetails.versionCode; - } - return details; - } -} diff --git a/third_party/perfetto/ui/src/controller/trace_controller.ts b/third_party/perfetto/ui/src/controller/trace_controller.ts deleted file mode 100644 index b704e87561c5..000000000000 --- a/third_party/perfetto/ui/src/controller/trace_controller.ts +++ /dev/null @@ -1,936 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertExists, assertTrue} from '../base/logging'; -import { - Actions, - DeferredAction, -} from '../common/actions'; -import {cacheTrace} from '../common/cache_manager'; -import {Engine} from '../common/engine'; -import {featureFlags, Flag, PERF_SAMPLE_FLAG} from '../common/feature_flags'; -import {HttpRpcEngine} from '../common/http_rpc_engine'; -import { - getEnabledMetatracingCategories, - isMetatracingEnabled, -} from '../common/metatracing'; -import {NUM, NUM_NULL, QueryError, STR, STR_NULL} from '../common/query_result'; -import {onSelectionChanged} from '../common/selection_observer'; -import {defaultTraceTime, EngineMode, ProfileType} from '../common/state'; -import {TimeSpan, toNs, toNsCeil, toNsFloor} from '../common/time'; -import {resetEngineWorker, WasmEngineProxy} from '../common/wasm_engine_proxy'; -import {BottomTabList} from '../frontend/bottom_tab'; -import { - FtraceStat, - globals, - QuantizedLoad, - ThreadDesc, -} from '../frontend/globals'; -import {showModal} from '../frontend/modal'; -import { - publishFtraceCounters, - publishMetricError, - publishOverviewData, - publishThreads, -} from '../frontend/publish'; -import {Router} from '../frontend/router'; - -import { - CounterAggregationController, -} from './aggregation/counter_aggregation_controller'; -import { - CpuAggregationController, -} from './aggregation/cpu_aggregation_controller'; -import { - CpuByProcessAggregationController, -} from './aggregation/cpu_by_process_aggregation_controller'; -import { - FrameAggregationController, -} from './aggregation/frame_aggregation_controller'; -import { - SliceAggregationController, -} from './aggregation/slice_aggregation_controller'; -import { - ThreadAggregationController, -} from './aggregation/thread_aggregation_controller'; -import {Child, Children, Controller} from './controller'; -import { - CpuProfileController, - CpuProfileControllerArgs, -} from './cpu_profile_controller'; -import { - FlamegraphController, - FlamegraphControllerArgs, - profileType, -} from './flamegraph_controller'; -import { - FlowEventsController, - FlowEventsControllerArgs, -} from './flow_events_controller'; -import {FtraceController} from './ftrace_controller'; -import {LoadingManager} from './loading_manager'; -import {LogsController} from './logs_controller'; -import {MetricsController} from './metrics_controller'; -import { - PIVOT_TABLE_REDUX_FLAG, - PivotTableController, -} from './pivot_table_controller'; -import {SearchController} from './search_controller'; -import { - SelectionController, - SelectionControllerArgs, -} from './selection_controller'; -import { - TraceErrorController, -} from './trace_error_controller'; -import { - TraceBufferStream, - TraceFileStream, - TraceHttpStream, - TraceStream, -} from './trace_stream'; -import {TrackControllerArgs, trackControllerRegistry} from './track_controller'; -import {decideTracks} from './track_decider'; -import {VisualisedArgController} from './visualised_args_controller'; - -type States = 'init' | 'loading_trace' | 'ready'; - -const METRICS = [ - 'android_startup', - 'android_ion', - 'android_lmk', - 'android_dma_heap', - 'android_surfaceflinger', - 'android_batt', - 'android_other_traces', - 'chrome_dropped_frames', - 'chrome_long_latency', - 'trace_metadata', - 'android_trusty_workqueues', -]; -const FLAGGED_METRICS: Array<[Flag, string]> = METRICS.map((m) => { - const id = `forceMetric${m}`; - let name = m.split('_').join(' '); - name = name[0].toUpperCase() + name.slice(1); - name = 'Metric: ' + name; - const flag = featureFlags.register({ - id, - name, - description: `Overrides running the '${m}' metric at import time.`, - defaultValue: true, - }); - return [flag, m]; -}); - -const ENABLE_CHROME_RELIABLE_RANGE_ZOOM_FLAG = featureFlags.register({ - id: 'enableChromeReliableRangeZoom', - name: 'Enable Chrome reliable range zoom', - description: 'Automatically zoom into the reliable range for Chrome traces', - defaultValue: false, -}); - -const ENABLE_CHROME_RELIABLE_RANGE_ANNOTATION_FLAG = featureFlags.register({ - id: 'enableChromeReliableRangeAnnotation', - name: 'Enable Chrome reliable range annotation', - description: 'Automatically adds an annotation for the reliable range start', - defaultValue: false, -}); - -// The following flags control TraceProcessor Config. -const CROP_TRACK_EVENTS_FLAG = featureFlags.register({ - id: 'cropTrackEvents', - name: 'Crop track events', - description: 'Ignores track events outside of the range of interest', - defaultValue: false, -}); -const INGEST_FTRACE_IN_RAW_TABLE_FLAG = featureFlags.register({ - id: 'ingestFtraceInRawTable', - name: 'Ingest ftrace in raw table', - description: 'Enables ingestion of typed ftrace events into the raw table', - defaultValue: true, -}); -const ANALYZE_TRACE_PROTO_CONTENT_FLAG = featureFlags.register({ - id: 'analyzeTraceProtoContent', - name: 'Analyze trace proto content', - description: 'Enables trace proto content analysis', - defaultValue: false, -}); - -// A local storage key where the indication that JSON warning has been shown is -// stored. -const SHOWN_JSON_WARNING_KEY = 'shownJsonWarning'; - -function showJsonWarning() { - showModal({ - title: 'Warning', - content: - m('div', - m('span', - 'Perfetto UI features are limited for JSON traces. ', - 'We recommend recording ', - m('a', - {href: 'https://perfetto.dev/docs/quickstart/chrome-tracing'}, - 'proto-format traces'), - ' from Chrome.'), - m('br')), - buttons: [], - }); -} - -// TraceController handles handshakes with the frontend for everything that -// concerns a single trace. It owns the WASM trace processor engine, handles -// tracks data and SQL queries. There is one TraceController instance for each -// trace opened in the UI (for now only one trace is supported). -export class TraceController extends Controller { - private readonly engineId: string; - private engine?: Engine; - - constructor(engineId: string) { - super('init'); - this.engineId = engineId; - } - - run() { - const engineCfg = assertExists(globals.state.engine); - switch (this.state) { - case 'init': - this.loadTrace() - .then((mode) => { - globals.dispatch(Actions.setEngineReady({ - engineId: this.engineId, - ready: true, - mode, - })); - }) - .catch((err) => { - this.updateStatus(`${err}`); - throw err; - }); - this.updateStatus('Opening trace'); - this.setState('loading_trace'); - break; - - case 'loading_trace': - // Stay in this state until loadTrace() returns and marks the engine as - // ready. - if (this.engine === undefined || !engineCfg.ready) return; - this.setState('ready'); - break; - - case 'ready': - // At this point we are ready to serve queries and handle tracks. - const engine = assertExists(this.engine); - const childControllers: Children = []; - - // Create a TrackController for each track. - for (const trackId of Object.keys(globals.state.tracks)) { - const trackCfg = globals.state.tracks[trackId]; - if (trackCfg.engineId !== this.engineId) continue; - if (!trackControllerRegistry.has(trackCfg.kind)) continue; - const trackCtlFactory = trackControllerRegistry.get(trackCfg.kind); - const trackArgs: TrackControllerArgs = {trackId, engine}; - childControllers.push(Child(trackId, trackCtlFactory, trackArgs)); - } - - for (const argName of globals.state.visualisedArgs) { - childControllers.push( - Child(argName, VisualisedArgController, {argName, engine})); - } - - const selectionArgs: SelectionControllerArgs = {engine}; - childControllers.push( - Child('selection', SelectionController, selectionArgs)); - - const flowEventsArgs: FlowEventsControllerArgs = {engine}; - childControllers.push( - Child('flowEvents', FlowEventsController, flowEventsArgs)); - - const cpuProfileArgs: CpuProfileControllerArgs = {engine}; - childControllers.push( - Child('cpuProfile', CpuProfileController, cpuProfileArgs)); - - const flamegraphArgs: FlamegraphControllerArgs = {engine}; - childControllers.push( - Child('flamegraph', FlamegraphController, flamegraphArgs)); - childControllers.push(Child( - 'cpu_aggregation', - CpuAggregationController, - {engine, kind: 'cpu_aggregation'})); - childControllers.push(Child( - 'thread_aggregation', - ThreadAggregationController, - {engine, kind: 'thread_state_aggregation'})); - childControllers.push(Child( - 'cpu_process_aggregation', - CpuByProcessAggregationController, - {engine, kind: 'cpu_by_process_aggregation'})); - if (!PIVOT_TABLE_REDUX_FLAG.get()) { - // Pivot table is supposed to handle the use cases the slice - // aggregation panel is used right now. When a flag to use pivot - // tables is enabled, do not add slice aggregation controller. - childControllers.push(Child( - 'slice_aggregation', - SliceAggregationController, - {engine, kind: 'slice_aggregation'})); - } - childControllers.push(Child( - 'counter_aggregation', - CounterAggregationController, - {engine, kind: 'counter_aggregation'})); - childControllers.push(Child( - 'frame_aggregation', - FrameAggregationController, - {engine, kind: 'frame_aggregation'})); - childControllers.push(Child('search', SearchController, { - engine, - app: globals, - })); - childControllers.push( - Child('pivot_table', PivotTableController, {engine})); - - childControllers.push(Child('logs', LogsController, { - engine, - app: globals, - })); - - childControllers.push( - Child('ftrace', FtraceController, {engine, app: globals})); - - childControllers.push( - Child('traceError', TraceErrorController, {engine})); - childControllers.push(Child('metrics', MetricsController, {engine})); - - return childControllers; - - default: - throw new Error(`unknown state ${this.state}`); - } - return; - } - - onDestroy() { - globals.engines.delete(this.engineId); - } - - private async loadTrace(): Promise { - this.updateStatus('Creating trace processor'); - // Check if there is any instance of the trace_processor_shell running in - // HTTP RPC mode (i.e. trace_processor_shell -D). - let engineMode: EngineMode; - let useRpc = false; - if (globals.state.newEngineMode === 'USE_HTTP_RPC_IF_AVAILABLE') { - useRpc = (await HttpRpcEngine.checkConnection()).connected; - } - let engine; - if (useRpc) { - console.log('Opening trace using native accelerator over HTTP+RPC'); - engineMode = 'HTTP_RPC'; - engine = new HttpRpcEngine(this.engineId, LoadingManager.getInstance); - engine.errorHandler = (err) => { - globals.dispatch( - Actions.setEngineFailed({mode: 'HTTP_RPC', failure: `${err}`})); - throw err; - }; - } else { - console.log('Opening trace using built-in WASM engine'); - engineMode = 'WASM'; - const enginePort = resetEngineWorker(); - engine = new WasmEngineProxy( - this.engineId, enginePort, LoadingManager.getInstance); - engine.resetTraceProcessor({ - cropTrackEvents: CROP_TRACK_EVENTS_FLAG.get(), - ingestFtraceInRawTable: INGEST_FTRACE_IN_RAW_TABLE_FLAG.get(), - analyzeTraceProtoContent: ANALYZE_TRACE_PROTO_CONTENT_FLAG.get(), - }); - } - this.engine = engine; - - if (isMetatracingEnabled()) { - this.engine.enableMetatrace( - assertExists(getEnabledMetatracingCategories())); - } - globals.bottomTabList = new BottomTabList(engine.getProxy('BottomTabList')); - - globals.engines.set(this.engineId, engine); - globals.dispatch(Actions.setEngineReady({ - engineId: this.engineId, - ready: false, - mode: engineMode, - })); - const engineCfg = assertExists(globals.state.engine); - assertTrue(engineCfg.id === this.engineId); - let traceStream: TraceStream | undefined; - if (engineCfg.source.type === 'FILE') { - traceStream = new TraceFileStream(engineCfg.source.file); - } else if (engineCfg.source.type === 'ARRAY_BUFFER') { - traceStream = new TraceBufferStream(engineCfg.source.buffer); - } else if (engineCfg.source.type === 'URL') { - traceStream = new TraceHttpStream(engineCfg.source.url); - } else if (engineCfg.source.type === 'HTTP_RPC') { - traceStream = undefined; - } else { - throw new Error(`Unknown source: ${JSON.stringify(engineCfg.source)}`); - } - - // |traceStream| can be undefined in the case when we are using the external - // HTTP+RPC endpoint and the trace processor instance has already loaded - // a trace (because it was passed as a cmdline argument to - // trace_processor_shell). In this case we don't want the UI to load any - // file/stream and we just want to jump to the loading phase. - if (traceStream !== undefined) { - const tStart = performance.now(); - for (; ;) { - const res = await traceStream.readChunk(); - await this.engine.parse(res.data); - const elapsed = (performance.now() - tStart) / 1000; - let status = 'Loading trace '; - if (res.bytesTotal > 0) { - const progress = Math.round(res.bytesRead / res.bytesTotal * 100); - status += `${progress}%`; - } else { - status += `${Math.round(res.bytesRead / 1e6)} MB`; - } - status += ` - ${Math.ceil(res.bytesRead / elapsed / 1e6)} MB/s`; - this.updateStatus(status); - if (res.eof) break; - } - await this.engine.notifyEof(); - } else { - assertTrue(this.engine instanceof HttpRpcEngine); - await this.engine.restoreInitialTables(); - } - - // traceUuid will be '' if the trace is not cacheable (URL or RPC). - const traceUuid = await this.cacheCurrentTrace(); - - const traceTime = await this.engine.getTraceTimeBounds(); - const startSec = traceTime.start; - const endSec = traceTime.end; - const traceTimeState = { - startSec, - endSec, - }; - - const shownJsonWarning = - window.localStorage.getItem(SHOWN_JSON_WARNING_KEY) !== null; - - // Show warning if the trace is in JSON format. - const query = `select str_value from metadata where name = 'trace_type'`; - const result = await assertExists(this.engine).query(query); - const traceType = result.firstRow({str_value: STR}).str_value; - const isJsonTrace = traceType == 'json'; - if (!shownJsonWarning) { - // When in embedded mode, the host app will control which trace format - // it passes to Perfetto, so we don't need to show this warning. - if (isJsonTrace && !globals.embeddedMode) { - showJsonWarning(); - // Save that the warning has been shown. Value is irrelevant since only - // the presence of key is going to be checked. - window.localStorage.setItem(SHOWN_JSON_WARNING_KEY, 'true'); - } - } - - const emptyOmniboxState = { - omnibox: '', - mode: globals.state.omniboxState.mode || 'SEARCH', - }; - - const actions: DeferredAction[] = [ - Actions.setOmnibox(emptyOmniboxState), - Actions.setTraceUuid({traceUuid}), - Actions.setTraceTime(traceTimeState), - ]; - - const [startVisibleTime, endVisibleTime] = - await computeVisibleTime(startSec, endSec, isJsonTrace, this.engine); - // We don't know the resolution at this point. However this will be - // replaced in 50ms so a guess is fine. - const resolution = (endVisibleTime - startVisibleTime) / 1000; - actions.push(Actions.setVisibleTraceTime({ - startSec: startVisibleTime, - endSec: endVisibleTime, - lastUpdate: Date.now() / 1000, - resolution, - })); - - globals.dispatchMultiple(actions); - Router.navigate(`#!/viewer?local_cache_key=${traceUuid}`); - - // Make sure the helper views are available before we start adding tracks. - await this.initialiseHelperViews(); - - { - // When we reload from a permalink don't create extra tracks: - const {pinnedTracks, tracks} = globals.state; - if (!pinnedTracks.length && !Object.keys(tracks).length) { - await this.listTracks(); - } - } - - await this.listThreads(); - await this.loadTimelineOverview(traceTime); - - { - // Pull out the counts ftrace events by name - const query = `select - name, - count(name) as cnt - from ftrace_event - group by name - order by cnt desc`; - const result = await assertExists(this.engine).query(query); - const counters: FtraceStat[] = []; - const it = result.iter({name: STR, cnt: NUM}); - for (let row = 0; it.valid(); it.next(), row++) { - counters.push({name: it.name, count: it.cnt}); - } - publishFtraceCounters(counters); - } - - globals.dispatch(Actions.sortThreadTracks({})); - globals.dispatch(Actions.maybeExpandOnlyTrackGroup({})); - - await this.selectFirstHeapProfile(); - if (PERF_SAMPLE_FLAG.get()) { - await this.selectPerfSample(); - } - - // If the trace was shared via a permalink, it might already have a - // selection. Emit onSelectionChanged to ensure that the components (like - // current selection details) react to it. - if (globals.state.currentSelection !== null) { - onSelectionChanged(globals.state.currentSelection, undefined); - } - - // Trace Processor doesn't support the reliable range feature for JSON - // traces. - if (!isJsonTrace && ENABLE_CHROME_RELIABLE_RANGE_ANNOTATION_FLAG.get()) { - const reliableRangeStart = await computeTraceReliableRangeStart(engine); - if (reliableRangeStart > 0) { - globals.dispatch(Actions.addAutomaticNote({ - timestamp: reliableRangeStart, - color: '#ff0000', - text: 'Reliable Range Start', - })); - } - } - - return engineMode; - } - - private async selectPerfSample() { - const query = `select upid - from perf_sample - join thread using (utid) - where callsite_id is not null - order by ts desc limit 1`; - const profile = await assertExists(this.engine).query(query); - if (profile.numRows() !== 1) return; - const row = profile.firstRow({upid: NUM}); - const upid = row.upid; - const leftTs = toNs(globals.state.traceTime.startSec); - const rightTs = toNs(globals.state.traceTime.endSec); - globals.dispatch(Actions.selectPerfSamples( - {id: 0, upid, leftTs, rightTs, type: ProfileType.PERF_SAMPLE})); - } - - private async selectFirstHeapProfile() { - const query = `select * from ( - select - min(ts) AS ts, - 'heap_profile:' || group_concat(distinct heap_name) AS type, - upid - from heap_profile_allocation - group by upid - union - select distinct graph_sample_ts as ts, 'graph' as type, upid - from heap_graph_object) - order by ts limit 1`; - const profile = await assertExists(this.engine).query(query); - if (profile.numRows() !== 1) return; - const row = profile.firstRow({ts: NUM, type: STR, upid: NUM}); - const ts = row.ts; - const type = profileType(row.type); - const upid = row.upid; - globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type})); - } - - private async listTracks() { - this.updateStatus('Loading tracks'); - const engine = assertExists(this.engine); - const actions = await decideTracks(this.engineId, engine); - globals.dispatchMultiple(actions); - } - - private async listThreads() { - this.updateStatus('Reading thread list'); - const query = `select - utid, - tid, - pid, - ifnull(thread.name, '') as threadName, - ifnull( - case when length(process.name) > 0 then process.name else null end, - thread.name) as procName, - process.cmdline as cmdline - from (select * from thread order by upid) as thread - left join (select * from process order by upid) as process - using(upid)`; - const result = await assertExists(this.engine).query(query); - const threads: ThreadDesc[] = []; - const it = result.iter({ - utid: NUM, - tid: NUM, - pid: NUM_NULL, - threadName: STR, - procName: STR_NULL, - cmdline: STR_NULL, - }); - for (; it.valid(); it.next()) { - const utid = it.utid; - const tid = it.tid; - const pid = it.pid === null ? undefined : it.pid; - const threadName = it.threadName; - const procName = it.procName === null ? undefined : it.procName; - const cmdline = it.cmdline === null ? undefined : it.cmdline; - threads.push({utid, tid, threadName, pid, procName, cmdline}); - } - publishThreads(threads); - } - - private async loadTimelineOverview(traceTime: TimeSpan) { - const engine = assertExists(this.engine); - const numSteps = 100; - const stepSec = traceTime.duration / numSteps; - let hasSchedOverview = false; - for (let step = 0; step < numSteps; step++) { - this.updateStatus( - 'Loading overview ' + - `${Math.round((step + 1) / numSteps * 1000) / 10}%`); - const startSec = traceTime.start + step * stepSec; - const startNs = toNsFloor(startSec); - const endSec = startSec + stepSec; - const endNs = toNsCeil(endSec); - - // Sched overview. - const schedResult = await engine.query( - `select sum(dur)/${stepSec}/1e9 as load, cpu from sched ` + - `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` + - 'group by cpu order by cpu'); - const schedData: {[key: string]: QuantizedLoad} = {}; - const it = schedResult.iter({load: NUM, cpu: NUM}); - for (; it.valid(); it.next()) { - const load = it.load; - const cpu = it.cpu; - schedData[cpu] = {startSec, endSec, load}; - hasSchedOverview = true; - } - publishOverviewData(schedData); - } - - if (hasSchedOverview) { - return; - } - - // Slices overview. - const traceStartNs = toNs(traceTime.start); - const stepSecNs = toNs(stepSec); - const sliceResult = await engine.query(`select - bucket, - upid, - ifnull(sum(utid_sum) / cast(${stepSecNs} as float), 0) as load - from thread - inner join ( - select - ifnull(cast((ts - ${traceStartNs})/${stepSecNs} as int), 0) as bucket, - sum(dur) as utid_sum, - utid - from slice - inner join thread_track on slice.track_id = thread_track.id - group by bucket, utid - ) using(utid) - where upid is not null - group by bucket, upid`); - - const slicesData: {[key: string]: QuantizedLoad[]} = {}; - const it = sliceResult.iter({bucket: NUM, upid: NUM, load: NUM}); - for (; it.valid(); it.next()) { - const bucket = it.bucket; - const upid = it.upid; - const load = it.load; - - const startSec = traceTime.start + stepSec * bucket; - const endSec = startSec + stepSec; - - const upidStr = upid.toString(); - let loadArray = slicesData[upidStr]; - if (loadArray === undefined) { - loadArray = slicesData[upidStr] = []; - } - loadArray.push({startSec, endSec, load}); - } - publishOverviewData(slicesData); - } - - private async cacheCurrentTrace(): Promise { - const engine = assertExists(this.engine); - const result = await engine.query(`select str_value as uuid from metadata - where name = 'trace_uuid'`); - if (result.numRows() === 0) { - // One of the cases covered is an empty trace. - return ''; - } - const traceUuid = result.firstRow({uuid: STR}).uuid; - const engineConfig = assertExists(globals.state.engine); - assertTrue(engineConfig.id === this.engineId); - if (!(await cacheTrace(engineConfig.source, traceUuid))) { - // If the trace is not cacheable (cacheable means it has been opened from - // URL or RPC) only append '?local_cache_key' to the URL, without the - // local_cache_key value. Doing otherwise would cause an error if the tab - // is discarded or the user hits the reload button because the trace is - // not in the cache. - return ''; - } - return traceUuid; - } - - async initialiseHelperViews() { - const engine = assertExists(this.engine); - - this.updateStatus('Creating annotation counter track table'); - // Create the helper tables for all the annotations related data. - // NULL in min/max means "figure it out per track in the usual way". - await engine.query(` - CREATE TABLE annotation_counter_track( - id INTEGER PRIMARY KEY, - name STRING, - __metric_name STRING, - upid INTEGER, - min_value DOUBLE, - max_value DOUBLE - ); - `); - this.updateStatus('Creating annotation slice track table'); - await engine.query(` - CREATE TABLE annotation_slice_track( - id INTEGER PRIMARY KEY, - name STRING, - __metric_name STRING, - upid INTEGER, - group_name STRING - ); - `); - - this.updateStatus('Creating annotation counter table'); - await engine.query(` - CREATE TABLE annotation_counter( - id BIGINT, - track_id INT, - ts BIGINT, - value DOUBLE, - PRIMARY KEY (track_id, ts) - ) WITHOUT ROWID; - `); - this.updateStatus('Creating annotation slice table'); - await engine.query(` - CREATE TABLE annotation_slice( - id INTEGER PRIMARY KEY, - track_id INT, - ts BIGINT, - dur BIGINT, - thread_dur BIGINT, - depth INT, - cat STRING, - name STRING, - UNIQUE(track_id, ts) - ); - `); - - - const availableMetrics = []; - const metricsResult = await engine.query('select name from trace_metrics'); - for (const it = metricsResult.iter({name: STR}); it.valid(); it.next()) { - availableMetrics.push(it.name); - } - globals.dispatch(Actions.setAvailableMetrics({availableMetrics})); - - const availableMetricsSet = new Set(availableMetrics); - for (const [flag, metric] of FLAGGED_METRICS) { - if (!flag.get() || !availableMetricsSet.has(metric)) { - continue; - } - - this.updateStatus(`Computing ${metric} metric`); - try { - // We don't care about the actual result of metric here as we are just - // interested in the annotation tracks. - await engine.computeMetric([metric]); - } catch (e) { - if (e instanceof QueryError) { - publishMetricError('MetricError: ' + e.message); - continue; - } else { - throw e; - } - } - - this.updateStatus(`Inserting data for ${metric} metric`); - try { - const result = await engine.query(`pragma table_info(${metric}_event)`); - let hasSliceName = false; - let hasDur = false; - let hasUpid = false; - let hasValue = false; - let hasGroupName = false; - const it = result.iter({name: STR}); - for (; it.valid(); it.next()) { - const name = it.name; - hasSliceName = hasSliceName || name === 'slice_name'; - hasDur = hasDur || name === 'dur'; - hasUpid = hasUpid || name === 'upid'; - hasValue = hasValue || name === 'value'; - hasGroupName = hasGroupName || name === 'group_name'; - } - - const upidColumnSelect = hasUpid ? 'upid' : '0 AS upid'; - const upidColumnWhere = hasUpid ? 'upid' : '0'; - const groupNameColumn = - hasGroupName ? 'group_name' : 'NULL AS group_name'; - if (hasSliceName && hasDur) { - await engine.query(` - INSERT INTO annotation_slice_track( - name, __metric_name, upid, group_name) - SELECT DISTINCT - track_name, - '${metric}' as metric_name, - ${upidColumnSelect}, - ${groupNameColumn} - FROM ${metric}_event - WHERE track_type = 'slice' - `); - await engine.query(` - INSERT INTO annotation_slice( - track_id, ts, dur, thread_dur, depth, cat, name - ) - SELECT - t.id AS track_id, - ts, - dur, - NULL as thread_dur, - 0 AS depth, - a.track_name as cat, - slice_name AS name - FROM ${metric}_event a - JOIN annotation_slice_track t - ON a.track_name = t.name AND t.__metric_name = '${metric}' - ORDER BY t.id, ts - `); - } - - if (hasValue) { - const minMax = await engine.query(` - SELECT - IFNULL(MIN(value), 0) as minValue, - IFNULL(MAX(value), 0) as maxValue - FROM ${metric}_event - WHERE ${upidColumnWhere} != 0`); - const row = minMax.firstRow({minValue: NUM, maxValue: NUM}); - await engine.query(` - INSERT INTO annotation_counter_track( - name, __metric_name, min_value, max_value, upid) - SELECT DISTINCT - track_name, - '${metric}' as metric_name, - CASE ${upidColumnWhere} WHEN 0 THEN NULL ELSE ${row.minValue} END, - CASE ${upidColumnWhere} WHEN 0 THEN NULL ELSE ${row.maxValue} END, - ${upidColumnSelect} - FROM ${metric}_event - WHERE track_type = 'counter' - `); - await engine.query(` - INSERT INTO annotation_counter(id, track_id, ts, value) - SELECT - -1 as id, - t.id AS track_id, - ts, - value - FROM ${metric}_event a - JOIN annotation_counter_track t - ON a.track_name = t.name AND t.__metric_name = '${metric}' - ORDER BY t.id, ts - `); - } - } catch (e) { - if (e instanceof QueryError) { - publishMetricError('MetricError: ' + e.message); - } else { - throw e; - } - } - } - } - - private updateStatus(msg: string): void { - globals.dispatch(Actions.updateStatus({ - msg, - timestamp: Date.now() / 1000, - })); - } -} - -async function computeTraceReliableRangeStart(engine: Engine): Promise { - const result = - await engine.query(`SELECT RUN_METRIC('chrome/chrome_reliable_range.sql'); - SELECT start FROM chrome_reliable_range`); - const bounds = result.firstRow({start: NUM}); - return bounds.start / 1e9; -} - -async function computeVisibleTime( - traceStartSec: number, - traceEndSec: number, - isJsonTrace: boolean, - engine: Engine): Promise<[number, number]> { - // if we have non-default visible state, update the visible time to it - const previousVisibleState = globals.state.frontendLocalState.visibleState; - if (!(previousVisibleState.startSec === defaultTraceTime.startSec && - previousVisibleState.endSec === defaultTraceTime.endSec) && - (previousVisibleState.startSec >= traceStartSec && - previousVisibleState.endSec <= traceEndSec)) { - return [previousVisibleState.startSec, previousVisibleState.endSec]; - } - - // initialise visible time to the trace time bounds - let visibleStartSec = traceStartSec; - let visibleEndSec = traceEndSec; - - // compare start and end with metadata computed by the trace processor - const mdTime = await engine.getTracingMetadataTimeBounds(); - // make sure the bounds hold - if (Math.max(visibleStartSec, mdTime.start) < - Math.min(visibleEndSec, mdTime.end)) { - visibleStartSec = - Math.max(visibleStartSec, mdTime.start); - visibleEndSec = Math.min(visibleEndSec, mdTime.end); - } - - // Trace Processor doesn't support the reliable range feature for JSON - // traces. - if (!isJsonTrace && ENABLE_CHROME_RELIABLE_RANGE_ZOOM_FLAG.get()) { - const reliableRangeStart = await computeTraceReliableRangeStart(engine); - visibleStartSec = Math.max(visibleStartSec, reliableRangeStart); - } - - return [visibleStartSec, visibleEndSec]; -} diff --git a/third_party/perfetto/ui/src/controller/trace_error_controller.ts b/third_party/perfetto/ui/src/controller/trace_error_controller.ts deleted file mode 100644 index ad491f79e040..000000000000 --- a/third_party/perfetto/ui/src/controller/trace_error_controller.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Engine} from '../common/engine'; -import {NUM} from '../common/query_result'; -import {publishTraceErrors} from '../frontend/publish'; - -import {Controller} from './controller'; - -export interface TraceErrorControllerArgs { - engine: Engine; -} - -export class TraceErrorController extends Controller<'main'> { - private hasRun = false; - constructor(private args: TraceErrorControllerArgs) { - super('main'); - } - - run() { - if (this.hasRun) { - return; - } - this.hasRun = true; - const engine = this.args.engine; - engine - .query( - `SELECT sum(value) as sumValue FROM stats WHERE severity != 'info'`) - .then((result) => { - const errors = result.firstRow({sumValue: NUM}).sumValue; - publishTraceErrors(errors); - }); - } -} diff --git a/third_party/perfetto/ui/src/controller/trace_stream.ts b/third_party/perfetto/ui/src/controller/trace_stream.ts deleted file mode 100644 index 6ccb3d0dcb8a..000000000000 --- a/third_party/perfetto/ui/src/controller/trace_stream.ts +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {defer, Deferred} from '../base/deferred'; -import {assertExists, assertTrue} from '../base/logging'; - -const SLICE_SIZE = 32 * 1024 * 1024; - -// The object returned by TraceStream.readChunk() promise. -export interface TraceChunk { - data: Uint8Array; - eof: boolean; - bytesRead: number; - bytesTotal: number; -} - -// Base interface for loading trace data in chunks. -// The caller has to call readChunk() until TraceChunk.eof == true. -export interface TraceStream { - readChunk(): Promise; -} - -// Loads a trace from a File object. For the "open file" use case. -export class TraceFileStream implements TraceStream { - private traceFile: Blob; - private reader: FileReader; - private pendingRead?: Deferred; - private bytesRead = 0; - - constructor(traceFile: Blob) { - this.traceFile = traceFile; - this.reader = new FileReader(); - this.reader.onloadend = () => this.onLoad(); - } - - private onLoad() { - const pendingRead = assertExists(this.pendingRead); - this.pendingRead = undefined; - if (this.reader.error) { - pendingRead.reject(this.reader.error); - return; - } - const res = assertExists(this.reader.result) as ArrayBuffer; - this.bytesRead += res.byteLength; - pendingRead.resolve({ - data: new Uint8Array(res), - eof: this.bytesRead >= this.traceFile.size, - bytesRead: this.bytesRead, - bytesTotal: this.traceFile.size, - }); - } - - readChunk(): Promise { - const sliceEnd = Math.min(this.bytesRead + SLICE_SIZE, this.traceFile.size); - const slice = this.traceFile.slice(this.bytesRead, sliceEnd); - this.pendingRead = defer(); - this.reader.readAsArrayBuffer(slice); - return this.pendingRead; - } -} - -// Loads a trace from an ArrayBuffer. For the window.open() + postMessage -// use-case, used by other dashboards (see post_message_handler.ts). -export class TraceBufferStream implements TraceStream { - private traceBuf: ArrayBuffer; - private bytesRead = 0; - - constructor(traceBuf: ArrayBuffer) { - this.traceBuf = traceBuf; - } - - readChunk(): Promise { - assertTrue(this.bytesRead <= this.traceBuf.byteLength); - const len = Math.min(SLICE_SIZE, this.traceBuf.byteLength - this.bytesRead); - const data = new Uint8Array(this.traceBuf, this.bytesRead, len); - this.bytesRead += len; - return Promise.resolve({ - data, - eof: this.bytesRead >= this.traceBuf.byteLength, - bytesRead: this.bytesRead, - bytesTotal: this.traceBuf.byteLength, - }); - } -} - -// Loads a stream from a URL via fetch(). For the permalink (?s=UUID) and -// open url (?url=http://...) cases. -export class TraceHttpStream implements TraceStream { - private bytesRead = 0; - private bytesTotal = 0; - private uri: string; - private httpStream?: ReadableStreamDefaultReader; - - constructor(uri: string) { - assertTrue(uri.startsWith('http://') || uri.startsWith('https://')); - this.uri = uri; - } - - async readChunk(): Promise { - // Initialize the fetch() job on the first read request. - if (this.httpStream === undefined) { - const response = await fetch(this.uri); - if (response.status !== 200) { - throw new Error(`HTTP ${response.status} - ${response.statusText}`); - } - const len = response.headers.get('Content-Length'); - this.bytesTotal = len ? Number.parseInt(len, 10) : 0; - this.httpStream = response.body!.getReader(); - } - - let eof = false; - let bytesRead = 0; - const chunks = []; - - // httpStream can return very small chunks which can slow down - // TraceProcessor. Here we accumulate chunks until we get at least 32mb - // or hit EOF. - while (!eof && bytesRead < 32 * 1024 * 1024) { - const res = await this.httpStream.read(); - if (res.value) { - chunks.push(res.value); - bytesRead += res.value.length; - } - eof = res.done; - } - - let data; - if (chunks.length === 1) { - data = chunks[0]; - } else { - // Stitch all the chunks into one big array: - data = new Uint8Array(bytesRead); - let offset = 0; - for (const chunk of chunks) { - data.set(chunk, offset); - offset += chunk.length; - } - } - - this.bytesRead += data.length; - - return { - data, - eof, - bytesRead: this.bytesRead, - bytesTotal: this.bytesTotal, - }; - } -} diff --git a/third_party/perfetto/ui/src/controller/track_controller.ts b/third_party/perfetto/ui/src/controller/track_controller.ts deleted file mode 100644 index 46d1f1f265f1..000000000000 --- a/third_party/perfetto/ui/src/controller/track_controller.ts +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertExists, assertTrue} from '../base/logging'; -import {Engine} from '../common/engine'; -import {Registry} from '../common/registry'; -import {TraceTime, TrackState} from '../common/state'; -import {fromNs, toNs} from '../common/time'; -import {LIMIT, TrackData} from '../common/track_data'; -import {globals} from '../frontend/globals'; -import {publishTrackData} from '../frontend/publish'; - -import {Controller} from './controller'; -import {ControllerFactory} from './controller'; - -interface TrackConfig {} - -type TrackConfigWithNamespace = TrackConfig&{namespace: string}; - -// Allow to override via devtools for testing (note, needs to be done in the -// controller-thread). -(self as {} as {quantPx: number}).quantPx = 1; - -// TrackController is a base class overridden by track implementations (e.g., -// sched slices, nestable slices, counters). -export abstract class TrackController< - Config extends TrackConfig, Data extends TrackData = TrackData> extends - Controller<'main'> { - readonly trackId: string; - readonly engine: Engine; - private data?: TrackData; - private requestingData = false; - private queuedRequest = false; - private isSetup = false; - private lastReloadHandled = 0; - - // We choose 100000 as the table size to cache as this is roughly the point - // where SQLite sorts start to become expensive. - private static readonly MIN_TABLE_SIZE_TO_CACHE = 100000; - - constructor(args: TrackControllerArgs) { - super('main'); - this.trackId = args.trackId; - this.engine = args.engine; - } - - protected pxSize(): number { - return (self as {} as {quantPx: number}).quantPx; - } - - // Can be overriden by the track implementation to allow one time setup work - // to be performed before the first onBoundsChange invcation. - async onSetup() {} - - // Can be overriden by the track implementation to allow some one-off work - // when requested reload (e.g. recalculating height). - async onReload() {} - - // Must be overridden by the track implementation. Is invoked when the track - // frontend runs out of cached data. The derived track controller is expected - // to publish new track data in response to this call. - abstract onBoundsChange(start: number, end: number, resolution: number): - Promise; - - get trackState(): TrackState { - return assertExists(globals.state.tracks[this.trackId]); - } - - get config(): Config { - return this.trackState.config as Config; - } - - configHasNamespace(config: TrackConfig): config is TrackConfigWithNamespace { - return 'namespace' in config; - } - - namespaceTable(tableName: string): string { - if (this.configHasNamespace(this.config)) { - return this.config.namespace + '_' + tableName; - } else { - return tableName; - } - } - - publish(data: Data): void { - this.data = data; - publishTrackData({id: this.trackId, data}); - } - - // Returns a valid SQL table name with the given prefix that should be unique - // for each track. - tableName(prefix: string) { - // Derive table name from, since that is unique for each track. - // Track ID can be UUID but '-' is not valid for sql table name. - const idSuffix = this.trackId.split('-').join('_'); - return `${prefix}_${idSuffix}`; - } - - shouldSummarize(resolution: number): boolean { - // |resolution| is in s/px (to nearest power of 10) assuming a display - // of ~1000px 0.0008 is 0.8s. - return resolution >= 0.0008; - } - - protected async query(query: string) { - const result = await this.engine.query(query); - return result; - } - - private shouldReload(): boolean { - const {lastTrackReloadRequest} = globals.state; - return !!lastTrackReloadRequest && - this.lastReloadHandled < lastTrackReloadRequest; - } - - private markReloadHandled() { - this.lastReloadHandled = globals.state.lastTrackReloadRequest || 0; - } - - shouldRequestData(traceTime: TraceTime): boolean { - if (this.data === undefined) return true; - if (this.shouldReload()) return true; - - // If at the limit only request more data if the view has moved. - const atLimit = this.data.length === LIMIT; - if (atLimit) { - // We request more data than the window, so add window duration to find - // the previous window. - const prevWindowStart = - this.data.start + (traceTime.startSec - traceTime.endSec); - return traceTime.startSec !== prevWindowStart; - } - - // Otherwise request more data only when out of range of current data or - // resolution has changed. - const inRange = traceTime.startSec >= this.data.start && - traceTime.endSec <= this.data.end; - return !inRange || - this.data.resolution !== - globals.state.frontendLocalState.visibleState.resolution; - } - - // Decides, based on the length of the trace and the number of rows - // provided whether a TrackController subclass should cache its quantized - // data. Returns the bucket size (in ns) if caching should happen and - // undefined otherwise. - // Subclasses should call this in their setup function - cachedBucketSizeNs(numRows: number): number|undefined { - // Ensure that we're not caching when the table size isn't even that big. - if (numRows < TrackController.MIN_TABLE_SIZE_TO_CACHE) { - return undefined; - } - - const bounds = globals.state.traceTime; - const traceDurNs = toNs(bounds.endSec - bounds.startSec); - - // For large traces, going through the raw table in the most zoomed-out - // states can be very expensive as this can involve going through O(millions - // of rows). The cost of this becomes high even for just iteration but is - // especially slow as quantization involves a SQLite sort on the quantized - // timestamp (for the group by). - // - // To get around this, we can cache a pre-quantized table which we can then - // in zoomed-out situations and fall back to the real table when zoomed in - // (which naturally constrains the amount of data by virtue of the window - // covering a smaller timespan) - // - // This method computes that cached table by computing an approximation for - // the bucket size we would use when totally zoomed out and then going a few - // resolution levels down which ensures that our cached table works for more - // than the literally most zoomed out state. Moving down a resolution level - // is defined as moving down a power of 2; this matches the logic in - // |globals.getCurResolution|. - // - // TODO(lalitm): in the future, we should consider having a whole set of - // quantized tables each of which cover some portion of resolution lvel - // range. As each table covers a large number of resolution levels, even 3-4 - // tables should really cover the all concievable trace sizes. This set - // could be computed by looking at the number of events being processed one - // level below the cached table and computing another layer of caching if - // that count is too high (with respect to MIN_TABLE_SIZE_TO_CACHE). - - // 4k monitors have 3840 horizontal pixels so use that for a worst case - // approximation of the window width. - const approxWidthPx = 3840; - - // Compute the outermost bucket size. This acts as a starting point for - // computing the cached size. - const outermostResolutionLevel = - Math.ceil(Math.log2(traceDurNs / approxWidthPx)); - const outermostBucketNs = Math.pow(2, outermostResolutionLevel); - - // This constant decides how many resolution levels down from our outermost - // bucket computation we want to be able to use the cached table. - // We've chosen 7 as it seems to be empircally seems to be a good fit for - // trace data. - const resolutionLevelsCovered = 7; - - // If we've got less resolution levels in the trace than the number of - // resolution levels we want to go down, bail out because this cached - // table is really not going to be used enough. - if (outermostResolutionLevel < resolutionLevelsCovered) { - return Number.MAX_SAFE_INTEGER; - } - - // Another way to look at moving down resolution levels is to consider how - // many sub-intervals we are splitting the bucket into. - const bucketSubIntervals = Math.pow(2, resolutionLevelsCovered); - - // Calculate the smallest bucket we want our table to be able to handle by - // dividing the outermsot bucket by the number of subintervals we should - // divide by. - const cachedBucketSizeNs = outermostBucketNs / bucketSubIntervals; - - // Our logic above should make sure this is an integer but double check that - // here as an assertion before returning. - assertTrue(Number.isInteger(cachedBucketSizeNs)); - - return cachedBucketSizeNs; - } - - run() { - const visibleState = globals.state.frontendLocalState.visibleState; - if (visibleState === undefined || visibleState.resolution === undefined || - visibleState.resolution === Infinity) { - return; - } - const dur = visibleState.endSec - visibleState.startSec; - if (globals.state.visibleTracks.includes(this.trackId) && - this.shouldRequestData(visibleState)) { - if (this.requestingData) { - this.queuedRequest = true; - } else { - this.requestingData = true; - let promise = Promise.resolve(); - if (!this.isSetup) { - promise = this.onSetup(); - } else if (this.shouldReload()) { - promise = this.onReload().then(() => this.markReloadHandled()); - } - promise - .then(() => { - this.isSetup = true; - let resolution = visibleState.resolution; - // TODO(hjd): We shouldn't have to be so defensive here. - if (Math.log2(toNs(resolution)) % 1 !== 0) { - // resolution is in pixels per second so 1000 means - // 1px = 1ms. - resolution = - fromNs(Math.pow(2, Math.floor(Math.log2(toNs(1000))))); - } - return this.onBoundsChange( - visibleState.startSec - dur, - visibleState.endSec + dur, - resolution); - }) - .then((data) => { - this.publish(data); - }) - .finally(() => { - this.requestingData = false; - if (this.queuedRequest) { - this.queuedRequest = false; - this.run(); - } - }); - } - } - } -} - -export interface TrackControllerArgs { - trackId: string; - engine: Engine; -} - -export interface TrackControllerFactory extends - ControllerFactory { - kind: string; -} - -export const trackControllerRegistry = - Registry.kindRegistry(); diff --git a/third_party/perfetto/ui/src/controller/track_decider.ts b/third_party/perfetto/ui/src/controller/track_decider.ts deleted file mode 100644 index 66aeba784943..000000000000 --- a/third_party/perfetto/ui/src/controller/track_decider.ts +++ /dev/null @@ -1,1888 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {v4 as uuidv4} from 'uuid'; - -import {assertExists} from '../base/logging'; -import {sqliteString} from '../base/string_utils'; -import { - Actions, - AddTrackArgs, - DeferredAction, -} from '../common/actions'; -import {Engine, EngineProxy} from '../common/engine'; -import {featureFlags, PERF_SAMPLE_FLAG} from '../common/feature_flags'; -import {pluginManager} from '../common/plugins'; -import { - NUM, - NUM_NULL, - STR, - STR_NULL, -} from '../common/query_result'; -import { - InThreadTrackSortKey, - PrimaryTrackSortKey, - SCROLLING_TRACK_GROUP, - TrackSortKey, - UtidToTrackSortKey, -} from '../common/state'; -import {ACTUAL_FRAMES_SLICE_TRACK_KIND} from '../tracks/actual_frames'; -import {ANDROID_LOGS_TRACK_KIND} from '../tracks/android_log'; -import {ASYNC_SLICE_TRACK_KIND} from '../tracks/async_slices'; -import { - decideTracks as scrollJankDecideTracks, -} from '../tracks/chrome_scroll_jank'; -import {SLICE_TRACK_KIND} from '../tracks/chrome_slices'; -import {COUNTER_TRACK_KIND, CounterScaleOptions} from '../tracks/counter'; -import {CPU_FREQ_TRACK_KIND} from '../tracks/cpu_freq'; -import {CPU_PROFILE_TRACK_KIND} from '../tracks/cpu_profile'; -import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices'; -import { - EXPECTED_FRAMES_SLICE_TRACK_KIND, -} from '../tracks/expected_frames'; -import {FTRACE_RAW_TRACK_KIND} from '../tracks/ftrace'; -import {HEAP_PROFILE_TRACK_KIND} from '../tracks/heap_profile'; -import {NULL_TRACK_KIND} from '../tracks/null_track'; -import { - PERF_SAMPLES_PROFILE_TRACK_KIND, -} from '../tracks/perf_samples_profile'; -import { - PROCESS_SCHEDULING_TRACK_KIND, -} from '../tracks/process_scheduling'; -import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary'; -import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state'; - -const TRACKS_V2_FLAG = featureFlags.register({ - id: 'tracksV2.1', - name: 'Tracks V2', - description: 'Show tracks built on top of the Track V2 API.', - defaultValue: false, -}); - -const MEM_DMA_COUNTER_NAME = 'mem.dma_heap'; -const MEM_DMA = 'mem.dma_buffer'; -const MEM_ION = 'mem.ion'; -const F2FS_IOSTAT_TAG = 'f2fs_iostat.'; -const F2FS_IOSTAT_GROUP_NAME = 'f2fs_iostat'; -const F2FS_IOSTAT_LAT_TAG = 'f2fs_iostat_latency.'; -const F2FS_IOSTAT_LAT_GROUP_NAME = 'f2fs_iostat_latency'; -const DISK_IOSTAT_TAG = 'diskstat.'; -const DISK_IOSTAT_GROUP_NAME = 'diskstat'; -const UFS_CMD_TAG = 'io.ufs.command.tag'; -const UFS_CMD_TAG_GROUP_NAME = 'io.ufs.command.tags'; -const BUDDY_INFO_TAG = 'mem.buddyinfo'; -// NB: Userspace wakelocks start with "WakeLock" not "Wakelock". -const KERNEL_WAKELOCK_REGEX = new RegExp('^Wakelock.*$'); -const KERNEL_WAKELOCK_GROUP = 'Kernel wakelocks'; -const NETWORK_TRACK_REGEX = new RegExp('^.* (Received|Transmitted)( KB)?$'); -const NETWORK_TRACK_GROUP = 'Networking'; -const ENTITY_RESIDENCY_REGEX = new RegExp('^Entity residency:'); -const ENTITY_RESIDENCY_GROUP = 'Entity residency'; - -// Sets the default 'scale' for counter tracks. If the regex matches -// then the paired mode is used. Entries are in priority order so the -// first match wins. -const COUNTER_REGEX: [RegExp, CounterScaleOptions][] = [ - // Power counters make more sense in rate mode since you're typically - // interested in the slope of the graph rather than the absolute - // value. - [new RegExp('^power\..*$'), 'RATE'], - // Same for network counters. - [NETWORK_TRACK_REGEX, 'RATE'], - // Entity residency - [ENTITY_RESIDENCY_REGEX, 'RATE'], -]; - -function getCounterScale(name: string): CounterScaleOptions|undefined { - for (const [re, scale] of COUNTER_REGEX) { - if (name.match(re)) { - return scale; - } - } - return undefined; -} - -export async function decideTracks( - engineId: string, engine: Engine): Promise { - return (new TrackDecider(engineId, engine)).decideTracks(); -} - -class TrackDecider { - private engineId: string; - private engine: Engine; - private upidToUuid = new Map(); - private utidToUuid = new Map(); - private tracksToAdd: AddTrackArgs[] = []; - private addTrackGroupActions: DeferredAction[] = []; - - constructor(engineId: string, engine: Engine) { - this.engineId = engineId; - this.engine = engine; - } - - static getTrackName(args: Partial<{ - name: string | null, - utid: number, - processName: string|null, - pid: number|null, - threadName: string|null, - tid: number|null, - upid: number|null, - kind: string, - threadTrack: boolean - }>) { - const { - name, - upid, - utid, - processName, - threadName, - pid, - tid, - kind, - threadTrack, - } = args; - - const hasName = name !== undefined && name !== null && name !== '[NULL]'; - const hasUpid = upid !== undefined && upid !== null; - const hasUtid = utid !== undefined && utid !== null; - const hasProcessName = processName !== undefined && processName !== null; - const hasThreadName = threadName !== undefined && threadName !== null; - const hasTid = tid !== undefined && tid !== null; - const hasPid = pid !== undefined && pid !== null; - const hasKind = kind !== undefined; - const isThreadTrack = threadTrack !== undefined && threadTrack; - - // If we don't have any useful information (better than - // upid/utid) we show the track kind to help with tracking - // down where this is coming from. - const kindSuffix = hasKind ? ` (${kind})` : ''; - - if (isThreadTrack && hasName && hasTid) { - return `${name} (${tid})`; - } else if (hasName) { - return `${name}`; - } else if (hasUpid && hasPid && hasProcessName) { - return `${processName} ${pid}`; - } else if (hasUpid && hasPid) { - return `Process ${pid}`; - } else if (hasThreadName && hasTid) { - return `${threadName} ${tid}`; - } else if (hasTid) { - return `Thread ${tid}`; - } else if (hasUpid) { - return `upid: ${upid}${kindSuffix}`; - } else if (hasUtid) { - return `utid: ${utid}${kindSuffix}`; - } else if (hasKind) { - return `Unnamed ${kind}`; - } - return 'Unknown'; - } - - async addCpuSchedulingTracks(): Promise { - const cpus = await this.engine.getCpus(); - for (const cpu of cpus) { - this.tracksToAdd.push({ - engineId: this.engineId, - kind: CPU_SLICE_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - name: `Cpu ${cpu}`, - trackGroup: SCROLLING_TRACK_GROUP, - config: { - cpu, - }, - }); - } - } - - async addCpuFreqTracks(engine: EngineProxy): Promise { - const cpus = await this.engine.getCpus(); - - const maxCpuFreqResult = await engine.query(` - select ifnull(max(value), 0) as freq - from counter c - inner join cpu_counter_track t on c.track_id = t.id - where name = 'cpufreq'; - `); - const maxCpuFreq = maxCpuFreqResult.firstRow({freq: NUM}).freq; - - for (const cpu of cpus) { - // Only add a cpu freq track if we have - // cpu freq data. - // TODO(hjd): Find a way to display cpu idle - // events even if there are no cpu freq events. - const cpuFreqIdleResult = await engine.query(` - select - id as cpuFreqId, - ( - select id - from cpu_counter_track - where name = 'cpuidle' - and cpu = ${cpu} - limit 1 - ) as cpuIdleId - from cpu_counter_track - where name = 'cpufreq' and cpu = ${cpu} - limit 1; - `); - - if (cpuFreqIdleResult.numRows() > 0) { - const row = cpuFreqIdleResult.firstRow({ - cpuFreqId: NUM, - cpuIdleId: NUM_NULL, - }); - const freqTrackId = row.cpuFreqId; - const idleTrackId = row.cpuIdleId === null ? undefined : row.cpuIdleId; - - this.tracksToAdd.push({ - engineId: this.engineId, - kind: CPU_FREQ_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - name: `Cpu ${cpu} Frequency`, - trackGroup: SCROLLING_TRACK_GROUP, - config: { - cpu, - maximumValue: maxCpuFreq, - freqTrackId, - idleTrackId, - }, - }); - } - } - } - - async addGlobalAsyncTracks(engine: EngineProxy): Promise { - const rawGlobalAsyncTracks = await engine.query(` - with global_tracks as materialized ( - select - track.parent_id as parent_id, - track.id as track_id, - track.name as name, - count(1) cnt - from track - join slice on slice.track_id = track.id - where - track.type = "track" - or track.type = "gpu_track" - or track.type = "cpu_track" - group by track_id - having cnt > 0 - ), - global_tracks_grouped as ( - select - parent_id, - name, - group_concat(track_id) as trackIds, - count(track_id) as trackCount - from global_tracks track - group by parent_id, name - ) - select - t.parent_id as parentId, - p.name as parentName, - t.name as name, - t.trackIds as trackIds, - max_layout_depth(t.trackCount, t.trackIds) as maxDepth - from global_tracks_grouped AS t - left join track p on (t.parent_id = p.id) - order by p.name, t.name; - `); - const it = rawGlobalAsyncTracks.iter({ - name: STR_NULL, - parentName: STR_NULL, - parentId: NUM_NULL, - trackIds: STR, - maxDepth: NUM, - }); - - const parentIdToGroupId = new Map(); - - for (; it.valid(); it.next()) { - const kind = ASYNC_SLICE_TRACK_KIND; - const rawName = it.name === null ? undefined : it.name; - const rawParentName = it.parentName === null ? undefined : it.parentName; - const name = TrackDecider.getTrackName({name: rawName, kind}); - const rawTrackIds = it.trackIds; - const trackIds = rawTrackIds.split(',').map((v) => Number(v)); - const parentTrackId = it.parentId; - const maxDepth = it.maxDepth; - let trackGroup = SCROLLING_TRACK_GROUP; - - if (parentTrackId !== null) { - const groupId = parentIdToGroupId.get(parentTrackId); - if (groupId === undefined) { - trackGroup = uuidv4(); - parentIdToGroupId.set(parentTrackId, trackGroup); - - const parentName = - TrackDecider.getTrackName({name: rawParentName, kind}); - - const summaryTrackId = uuidv4(); - this.tracksToAdd.push({ - id: summaryTrackId, - engineId: this.engineId, - kind: NULL_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.NULL_TRACK, - trackGroup: undefined, - name: parentName, - config: {}, - }); - - this.addTrackGroupActions.push(Actions.addTrackGroup({ - engineId: this.engineId, - summaryTrackId, - name: parentName, - id: trackGroup, - collapsed: true, - })); - } else { - trackGroup = groupId; - } - } - - const track = { - engineId: this.engineId, - kind, - trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK, - trackGroup, - name, - config: { - maxDepth, - trackIds, - }, - }; - - this.tracksToAdd.push(track); - } - } - - async addGpuFreqTracks(engine: EngineProxy): Promise { - const numGpus = await this.engine.getNumberOfGpus(); - const maxGpuFreqResult = await engine.query(` - select ifnull(max(value), 0) as maximumValue - from counter c - inner join gpu_counter_track t on c.track_id = t.id - where name = 'gpufreq'; - `); - const maximumValue = - maxGpuFreqResult.firstRow({maximumValue: NUM}).maximumValue; - - for (let gpu = 0; gpu < numGpus; gpu++) { - // Only add a gpu freq track if we have - // gpu freq data. - const freqExistsResult = await engine.query(` - select id - from gpu_counter_track - where name = 'gpufreq' and gpu_id = ${gpu} - limit 1; - `); - if (freqExistsResult.numRows() > 0) { - const trackId = freqExistsResult.firstRow({id: NUM}).id; - this.tracksToAdd.push({ - engineId: this.engineId, - kind: COUNTER_TRACK_KIND, - name: `Gpu ${gpu} Frequency`, - trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK, - trackGroup: SCROLLING_TRACK_GROUP, - config: { - trackId, - maximumValue, - }, - }); - } - } - } - - async addGlobalCounterTracks(engine: EngineProxy): Promise { - // Add global or GPU counter tracks that are not bound to any pid/tid. - const globalCounters = await engine.query(` - select name, id - from ( - select name, id - from counter_track - where type = 'counter_track' - union - select name, id - from gpu_counter_track - where name != 'gpufreq' - ) - order by name - `); - - const it = globalCounters.iter({ - name: STR, - id: NUM, - }); - - for (; it.valid(); it.next()) { - const name = it.name; - const trackId = it.id; - this.tracksToAdd.push({ - engineId: this.engineId, - kind: COUNTER_TRACK_KIND, - name, - trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK, - trackGroup: SCROLLING_TRACK_GROUP, - config: { - name, - trackId, - scale: getCounterScale(name), - }, - }); - } - } - - async addCpuPerfCounterTracks(engine: EngineProxy): Promise { - // Perf counter tracks are bound to CPUs, follow the scheduling and - // frequency track naming convention ("Cpu N ..."). - // Note: we might not have a track for a given cpu if no data was seen from - // it. This might look surprising in the UI, but placeholder tracks are - // wasteful as there's no way of collapsing global counter tracks at the - // moment. - const result = await engine.query(` - select printf("Cpu %u %s", cpu, name) as name, id - from perf_counter_track as pct - order by perf_session_id asc, pct.name asc, cpu asc - `); - - const it = result.iter({ - name: STR, - id: NUM, - }); - - for (; it.valid(); it.next()) { - const name = it.name; - const trackId = it.id; - this.tracksToAdd.push({ - engineId: this.engineId, - kind: COUNTER_TRACK_KIND, - name, - trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK, - trackGroup: SCROLLING_TRACK_GROUP, - config: { - name, - trackId, - scale: getCounterScale(name), - }, - }); - } - } - - async groupGlobalIonTracks(): Promise { - const ionTracks: AddTrackArgs[] = []; - let hasSummary = false; - for (const track of this.tracksToAdd) { - const isIon = track.name.startsWith(MEM_ION); - const isIonCounter = track.name === MEM_ION; - const isDmaHeapCounter = track.name === MEM_DMA_COUNTER_NAME; - const isDmaBuffferSlices = track.name === MEM_DMA; - if (isIon || isIonCounter || isDmaHeapCounter || isDmaBuffferSlices) { - ionTracks.push(track); - } - hasSummary = hasSummary || isIonCounter; - hasSummary = hasSummary || isDmaHeapCounter; - } - - if (ionTracks.length === 0 || !hasSummary) { - return; - } - - const id = uuidv4(); - const summaryTrackId = uuidv4(); - let foundSummary = false; - - for (const track of ionTracks) { - if (!foundSummary && - [MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.name)) { - foundSummary = true; - track.id = summaryTrackId; - track.trackGroup = undefined; - } else { - track.trackGroup = id; - } - } - - const addGroup = Actions.addTrackGroup({ - engineId: this.engineId, - summaryTrackId, - name: MEM_DMA_COUNTER_NAME, - id, - collapsed: true, - }); - this.addTrackGroupActions.push(addGroup); - } - - async groupGlobalIostatTracks(tag: string, group: string): Promise { - const iostatTracks: AddTrackArgs[] = []; - const devMap = new Map(); - - for (const track of this.tracksToAdd) { - if (track.name.startsWith(tag)) { - iostatTracks.push(track); - } - } - - if (iostatTracks.length === 0) { - return; - } - - for (const track of iostatTracks) { - const name = track.name.split('.', 3); - - if (!devMap.has(name[1])) { - devMap.set(name[1], uuidv4()); - } - track.name = name[2]; - track.trackGroup = devMap.get(name[1]); - } - - for (const [key, value] of devMap) { - const groupName = group + key; - const summaryTrackId = uuidv4(); - - this.tracksToAdd.push({ - id: summaryTrackId, - engineId: this.engineId, - kind: NULL_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.NULL_TRACK, - name: groupName, - trackGroup: undefined, - config: {}, - }); - - const addGroup = Actions.addTrackGroup({ - engineId: this.engineId, - summaryTrackId, - name: groupName, - id: value, - collapsed: true, - }); - this.addTrackGroupActions.push(addGroup); - } - } - - async groupGlobalUfsCmdTagTracks(tag: string, group: string): Promise { - const ufsCmdTagTracks: AddTrackArgs[] = []; - - for (const track of this.tracksToAdd) { - if (track.name.startsWith(tag)) { - ufsCmdTagTracks.push(track); - } - } - - if (ufsCmdTagTracks.length === 0) { - return; - } - - const id = uuidv4(); - const summaryTrackId = uuidv4(); - ufsCmdTagTracks[0].id = summaryTrackId; - for (const track of ufsCmdTagTracks) { - track.trackGroup = id; - } - - const addGroup = Actions.addTrackGroup({ - engineId: this.engineId, - summaryTrackId, - name: group, - id, - collapsed: true, - }); - this.addTrackGroupActions.push(addGroup); - } - - async groupGlobalBuddyInfoTracks(): Promise { - const buddyInfoTracks: AddTrackArgs[] = []; - const devMap = new Map(); - - for (const track of this.tracksToAdd) { - if (track.name.startsWith(BUDDY_INFO_TAG)) { - buddyInfoTracks.push(track); - } - } - - if (buddyInfoTracks.length === 0) { - return; - } - - for (const track of buddyInfoTracks) { - const tokens = track.name.split('['); - const node = tokens[1].slice(0, -1); - const zone = tokens[2].slice(0, -1); - const size = tokens[3].slice(0, -1); - - const groupName = 'Buddyinfo: Node: ' + node + ' Zone: ' + zone; - if (!devMap.has(groupName)) { - devMap.set(groupName, uuidv4()); - } - track.name = 'Size: ' + size; - track.trackGroup = devMap.get(groupName); - } - - for (const [key, value] of devMap) { - const groupName = key; - const summaryTrackId = uuidv4(); - - this.tracksToAdd.push({ - id: summaryTrackId, - engineId: this.engineId, - kind: NULL_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.NULL_TRACK, - name: groupName, - trackGroup: undefined, - config: {}, - }); - - const addGroup = Actions.addTrackGroup({ - engineId: this.engineId, - summaryTrackId, - name: groupName, - id: value, - collapsed: true, - }); - this.addTrackGroupActions.push(addGroup); - } - } - - async groupTracksByRegex(regex: RegExp, groupName: string): Promise { - let groupUuid = undefined; - - for (const track of this.tracksToAdd) { - if (regex.test(track.name)) { - if (groupUuid === undefined) { - groupUuid = uuidv4(); - } - track.trackGroup = groupUuid; - } - } - - if (groupUuid !== undefined) { - const summaryTrackId = uuidv4(); - this.tracksToAdd.push({ - id: summaryTrackId, - engineId: this.engineId, - kind: NULL_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.NULL_TRACK, - name: groupName, - trackGroup: undefined, - config: {}, - }); - - const addGroup = Actions.addTrackGroup({ - engineId: this.engineId, - summaryTrackId, - name: groupName, - id: groupUuid, - collapsed: true, - }); - this.addTrackGroupActions.push(addGroup); - } - } - - applyDefaultCounterScale(): void { - for (const track of this.tracksToAdd) { - if (track.kind === COUNTER_TRACK_KIND) { - const scaleConfig = { - scale: getCounterScale(track.name), - }; - track.config = Object.assign({}, track.config, scaleConfig); - } - } - } - - async addLogsTrack(engine: EngineProxy): Promise { - const result = - await engine.query(`select count(1) as cnt from android_logs`); - const count = result.firstRow({cnt: NUM}).cnt; - - if (count > 0) { - this.tracksToAdd.push({ - engineId: this.engineId, - kind: ANDROID_LOGS_TRACK_KIND, - name: 'Android logs', - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - trackGroup: SCROLLING_TRACK_GROUP, - config: {}, - }); - } - } - - async addFtraceTrack(engine: EngineProxy): Promise { - const query = `select distinct cpu - from ftrace_event - where cpu + 1 > 1 or utid + 1 > 1`; - - const result = await engine.query(query); - const it = result.iter({cpu: NUM}); - - let groupUuid = undefined; - let summaryTrackId = undefined; - - // use the first one as the summary track - for (let row = 0; it.valid(); it.next(), row++) { - if (groupUuid === undefined) { - groupUuid = 'ftrace-track-group'; - summaryTrackId = uuidv4(); - this.tracksToAdd.push({ - engineId: this.engineId, - kind: NULL_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.NULL_TRACK, - name: `Ftrace Events`, - trackGroup: undefined, - config: {}, - id: summaryTrackId, - }); - } - this.tracksToAdd.push({ - engineId: this.engineId, - kind: FTRACE_RAW_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - name: `Ftrace Events Cpu ${it.cpu}`, - trackGroup: groupUuid, - config: {cpu: it.cpu}, - }); - } - - if (groupUuid !== undefined && summaryTrackId !== undefined) { - const addGroup = Actions.addTrackGroup({ - engineId: this.engineId, - name: 'Ftrace Events', - id: groupUuid, - collapsed: true, - summaryTrackId, - }); - this.addTrackGroupActions.push(addGroup); - } - } - - async addAnnotationTracks(engine: EngineProxy): Promise { - const sliceResult = await engine.query(` - select id, name, upid, group_name - from annotation_slice_track - order by name - `); - - const sliceIt = sliceResult.iter({ - id: NUM, - name: STR, - upid: NUM, - group_name: STR_NULL, - }); - - interface GroupIds { - id: string; - summaryTrackId: string; - } - - const groupNameToIds = new Map(); - - for (; sliceIt.valid(); sliceIt.next()) { - const id = sliceIt.id; - const name = sliceIt.name; - const upid = sliceIt.upid; - const groupName = sliceIt.group_name; - - let summaryTrackId = undefined; - let trackGroupId = - upid === 0 ? SCROLLING_TRACK_GROUP : this.upidToUuid.get(upid); - - if (groupName) { - // If this is the first track encountered for a certain group, - // create an id for the group and use this track as the group's - // summary track. - const groupIds = groupNameToIds.get(groupName); - if (groupIds) { - trackGroupId = groupIds.id; - } else { - trackGroupId = uuidv4(); - summaryTrackId = uuidv4(); - groupNameToIds.set(groupName, { - id: trackGroupId, - summaryTrackId, - }); - } - } - - this.tracksToAdd.push({ - id: summaryTrackId, - engineId: this.engineId, - kind: SLICE_TRACK_KIND, - name, - trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK, - trackGroup: trackGroupId, - config: { - maxDepth: 0, - namespace: 'annotation', - trackId: id, - }, - }); - } - - for (const [groupName, groupIds] of groupNameToIds) { - const addGroup = Actions.addTrackGroup({ - engineId: this.engineId, - summaryTrackId: groupIds.summaryTrackId, - name: groupName, - id: groupIds.id, - collapsed: true, - }); - this.addTrackGroupActions.push(addGroup); - } - - const counterResult = await engine.query(` - SELECT - id, - name, - upid, - min_value as minValue, - max_value as maxValue - FROM annotation_counter_track`); - - const counterIt = counterResult.iter({ - id: NUM, - name: STR, - upid: NUM, - minValue: NUM_NULL, - maxValue: NUM_NULL, - }); - - for (; counterIt.valid(); counterIt.next()) { - const id = counterIt.id; - const name = counterIt.name; - const upid = counterIt.upid; - const minimumValue = - counterIt.minValue === null ? undefined : counterIt.minValue; - const maximumValue = - counterIt.maxValue === null ? undefined : counterIt.maxValue; - this.tracksToAdd.push({ - engineId: this.engineId, - kind: 'CounterTrack', - name, - trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK, - trackGroup: upid === 0 ? SCROLLING_TRACK_GROUP : - this.upidToUuid.get(upid), - config: { - name, - namespace: 'annotation', - trackId: id, - minimumValue, - maximumValue, - }, - }); - } - } - - async addThreadStateTracks(engine: EngineProxy): Promise { - const result = await engine.query(` - select - utid, - tid, - upid, - pid, - thread.name as threadName - from - thread_state - left join thread using(utid) - left join process using(upid) - where utid != 0 - group by utid`); - - const it = result.iter({ - utid: NUM, - upid: NUM_NULL, - tid: NUM_NULL, - pid: NUM_NULL, - threadName: STR_NULL, - }); - for (; it.valid(); it.next()) { - const utid = it.utid; - const tid = it.tid; - const upid = it.upid; - const threadName = it.threadName; - const uuid = this.getUuidUnchecked(utid, upid); - if (uuid === undefined) { - // If a thread has no scheduling activity (i.e. the sched table has zero - // rows for that uid) no track group will be created and we want to skip - // the track creation as well. - continue; - } - const kind = THREAD_STATE_TRACK_KIND; - this.tracksToAdd.push({ - engineId: this.engineId, - kind, - name: TrackDecider.getTrackName({utid, tid, threadName, kind}), - trackGroup: uuid, - trackSortKey: { - utid, - priority: InThreadTrackSortKey.THREAD_SCHEDULING_STATE_TRACK, - }, - config: {utid, tid}, - }); - } - } - - async addThreadCpuSampleTracks(engine: EngineProxy): Promise { - const result = await engine.query(` - select - utid, - tid, - upid, - thread.name as threadName - from - thread - join (select utid - from cpu_profile_stack_sample group by utid - ) using(utid) - left join process using(upid) - where utid != 0 - group by utid`); - - const it = result.iter({ - utid: NUM, - upid: NUM_NULL, - tid: NUM_NULL, - threadName: STR_NULL, - }); - for (; it.valid(); it.next()) { - const utid = it.utid; - const upid = it.upid; - const threadName = it.threadName; - const uuid = this.getUuid(utid, upid); - this.tracksToAdd.push({ - engineId: this.engineId, - kind: CPU_PROFILE_TRACK_KIND, - trackSortKey: { - utid, - priority: InThreadTrackSortKey.CPU_STACK_SAMPLES_TRACK, - }, - name: `${threadName} (CPU Stack Samples)`, - trackGroup: uuid, - config: {utid}, - }); - } - } - - async addThreadCounterTracks(engine: EngineProxy): Promise { - const result = await engine.query(` - select - thread_counter_track.name as trackName, - utid, - upid, - tid, - thread.name as threadName, - thread_counter_track.id as trackId, - thread.start_ts as startTs, - thread.end_ts as endTs - from thread_counter_track - join thread using(utid) - left join process using(upid) - where thread_counter_track.name != 'thread_time' - `); - - const it = result.iter({ - trackName: STR_NULL, - utid: NUM, - upid: NUM_NULL, - tid: NUM_NULL, - threadName: STR_NULL, - startTs: NUM_NULL, - trackId: NUM, - endTs: NUM_NULL, - }); - for (; it.valid(); it.next()) { - const utid = it.utid; - const tid = it.tid; - const upid = it.upid; - const trackId = it.trackId; - const trackName = it.trackName; - const threadName = it.threadName; - const uuid = this.getUuid(utid, upid); - const startTs = it.startTs === null ? undefined : it.startTs; - const endTs = it.endTs === null ? undefined : it.endTs; - const kind = COUNTER_TRACK_KIND; - const name = TrackDecider.getTrackName( - {name: trackName, utid, tid, kind, threadName, threadTrack: true}); - this.tracksToAdd.push({ - engineId: this.engineId, - kind, - name, - trackSortKey: { - utid, - priority: InThreadTrackSortKey.ORDINARY, - }, - trackGroup: uuid, - config: { - name, - trackId, - startTs, - endTs, - tid, - }, - }); - } - } - - async addProcessAsyncSliceTracks(engine: EngineProxy): Promise { - const result = await engine.query(` - with process_async_tracks as materialized ( - select - process_track.upid as upid, - process_track.name as trackName, - process.name as processName, - process.pid as pid, - group_concat(process_track.id) as trackIds, - count(1) as trackCount - from process_track - left join process using(upid) - where - process_track.name is null or - process_track.name not like "% Timeline" - group by - process_track.upid, - process_track.name - ) - select - t.*, - max_layout_depth(t.trackCount, t.trackIds) as maxDepth - from process_async_tracks t; - `); - - const it = result.iter({ - upid: NUM, - trackName: STR_NULL, - trackIds: STR, - processName: STR_NULL, - pid: NUM_NULL, - maxDepth: NUM, - }); - for (; it.valid(); it.next()) { - const upid = it.upid; - const trackName = it.trackName; - const rawTrackIds = it.trackIds; - const trackIds = rawTrackIds.split(',').map((v) => Number(v)); - const processName = it.processName; - const pid = it.pid; - const maxDepth = it.maxDepth; - - const uuid = this.getUuid(0, upid); - - const kind = ASYNC_SLICE_TRACK_KIND; - const name = TrackDecider.getTrackName( - {name: trackName, upid, pid, processName, kind}); - this.tracksToAdd.push({ - engineId: this.engineId, - kind, - name, - trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK, - trackGroup: uuid, - config: { - trackIds, - maxDepth, - }, - }); - } - } - - async addActualFramesTracks(engine: EngineProxy): Promise { - const result = await engine.query(` - with process_async_tracks as materialized ( - select - process_track.upid as upid, - process_track.name as trackName, - process.name as processName, - process.pid as pid, - group_concat(process_track.id) as trackIds, - count(1) as trackCount - from process_track - left join process using(upid) - where process_track.name = "Actual Timeline" - group by - process_track.upid, - process_track.name - ) - select - t.*, - max_layout_depth(t.trackCount, t.trackIds) as maxDepth - from process_async_tracks t; - `); - - const it = result.iter({ - upid: NUM, - trackName: STR_NULL, - trackIds: STR, - processName: STR_NULL, - pid: NUM_NULL, - maxDepth: NUM, - }); - for (; it.valid(); it.next()) { - const upid = it.upid; - const trackName = it.trackName; - const rawTrackIds = it.trackIds; - const trackIds = rawTrackIds.split(',').map((v) => Number(v)); - const processName = it.processName; - const pid = it.pid; - const maxDepth = it.maxDepth; - - const uuid = this.getUuid(0, upid); - - const kind = ACTUAL_FRAMES_SLICE_TRACK_KIND; - const name = TrackDecider.getTrackName( - {name: trackName, upid, pid, processName, kind}); - this.tracksToAdd.push({ - engineId: this.engineId, - kind, - name, - trackSortKey: PrimaryTrackSortKey.ACTUAL_FRAMES_SLICE_TRACK, - trackGroup: uuid, - config: { - trackIds, - maxDepth, - }, - }); - } - } - - async addExpectedFramesTracks(engine: EngineProxy): Promise { - const result = await engine.query(` - with process_async_tracks as materialized ( - select - process_track.upid as upid, - process_track.name as trackName, - process.name as processName, - process.pid as pid, - group_concat(process_track.id) as trackIds, - count(1) as trackCount - from process_track - left join process using(upid) - where process_track.name = "Expected Timeline" - group by - process_track.upid, - process_track.name - ) - select - t.*, - max_layout_depth(t.trackCount, t.trackIds) as maxDepth - from process_async_tracks t; - `); - - const it = result.iter({ - upid: NUM, - trackName: STR_NULL, - trackIds: STR, - processName: STR_NULL, - pid: NUM_NULL, - maxDepth: NUM, - }); - - for (; it.valid(); it.next()) { - const upid = it.upid; - const trackName = it.trackName; - const rawTrackIds = it.trackIds; - const trackIds = rawTrackIds.split(',').map((v) => Number(v)); - const processName = it.processName; - const pid = it.pid; - const maxDepth = it.maxDepth; - - const uuid = this.getUuid(0, upid); - - const kind = EXPECTED_FRAMES_SLICE_TRACK_KIND; - const name = TrackDecider.getTrackName( - {name: trackName, upid, pid, processName, kind}); - this.tracksToAdd.push({ - engineId: this.engineId, - kind, - name, - trackSortKey: PrimaryTrackSortKey.EXPECTED_FRAMES_SLICE_TRACK, - trackGroup: uuid, - config: { - trackIds, - maxDepth, - }, - }); - } - } - - async addThreadSliceTracks(engine: EngineProxy): Promise { - const result = await engine.query(` - select - thread_track.utid as utid, - thread_track.id as trackId, - thread_track.name as trackName, - EXTRACT_ARG(thread_track.source_arg_set_id, - 'is_root_in_scope') as isDefaultTrackForScope, - tid, - thread.name as threadName, - max(slice.depth) as maxDepth, - process.upid as upid - from slice - join thread_track on slice.track_id = thread_track.id - join thread using(utid) - left join process using(upid) - group by thread_track.id - `); - - const it = result.iter({ - utid: NUM, - trackId: NUM, - trackName: STR_NULL, - isDefaultTrackForScope: NUM_NULL, - tid: NUM_NULL, - threadName: STR_NULL, - maxDepth: NUM, - upid: NUM_NULL, - }); - for (; it.valid(); it.next()) { - const utid = it.utid; - const trackId = it.trackId; - const trackName = it.trackName; - // Note that !!null === false. - const isDefaultTrackForScope = !!it.isDefaultTrackForScope; - const tid = it.tid; - const threadName = it.threadName; - const upid = it.upid; - const maxDepth = it.maxDepth; - - const uuid = this.getUuid(utid, upid); - - const kind = SLICE_TRACK_KIND; - const name = TrackDecider.getTrackName( - {name: trackName, utid, tid, threadName, kind}); - this.tracksToAdd.push({ - engineId: this.engineId, - kind, - name, - trackGroup: uuid, - trackSortKey: { - utid, - priority: isDefaultTrackForScope ? - InThreadTrackSortKey.DEFAULT_TRACK : - InThreadTrackSortKey.ORDINARY, - }, - config: { - trackId, - maxDepth, - tid, - }, - }); - - if (TRACKS_V2_FLAG.get()) { - this.tracksToAdd.push({ - engineId: this.engineId, - kind: 'GenericSliceTrack', - name, - trackGroup: uuid, - trackSortKey: { - utid, - priority: isDefaultTrackForScope ? - InThreadTrackSortKey.DEFAULT_TRACK : - InThreadTrackSortKey.ORDINARY, - }, - config: {sqlTrackId: trackId}, - }); - } - } - } - - async addProcessCounterTracks(engine: EngineProxy): Promise { - const result = await engine.query(` - select - process_counter_track.id as trackId, - process_counter_track.name as trackName, - upid, - process.pid, - process.name as processName, - process.start_ts as startTs, - process.end_ts as endTs - from process_counter_track - join process using(upid); - `); - const it = result.iter({ - trackId: NUM, - trackName: STR_NULL, - upid: NUM, - pid: NUM_NULL, - processName: STR_NULL, - startTs: NUM_NULL, - endTs: NUM_NULL, - }); - for (let i = 0; it.valid(); ++i, it.next()) { - const pid = it.pid; - const upid = it.upid; - const trackId = it.trackId; - const trackName = it.trackName; - const processName = it.processName; - const uuid = this.getUuid(0, upid); - const startTs = it.startTs === null ? undefined : it.startTs; - const endTs = it.endTs === null ? undefined : it.endTs; - const kind = COUNTER_TRACK_KIND; - const name = TrackDecider.getTrackName( - {name: trackName, upid, pid, kind, processName}); - this.tracksToAdd.push({ - engineId: this.engineId, - kind, - name, - trackSortKey: await this.resolveTrackSortKeyForProcessCounterTrack( - upid, trackName || undefined), - trackGroup: uuid, - config: { - name, - trackId, - startTs, - endTs, - }, - }); - } - } - - async addProcessHeapProfileTracks(engine: EngineProxy): Promise { - const result = await engine.query(` - select distinct(upid) from heap_profile_allocation - union - select distinct(upid) from heap_graph_object - `); - for (const it = result.iter({upid: NUM}); it.valid(); it.next()) { - const upid = it.upid; - const uuid = this.getUuid(0, upid); - this.tracksToAdd.push({ - engineId: this.engineId, - kind: HEAP_PROFILE_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.HEAP_PROFILE_TRACK, - name: `Heap Profile`, - trackGroup: uuid, - config: {upid}, - }); - } - } - - async addProcessPerfSamplesTracks(engine: EngineProxy): Promise { - const result = await engine.query(` - select distinct upid, pid - from perf_sample join thread using (utid) join process using (upid) - where callsite_id is not null - `); - for (const it = result.iter({upid: NUM, pid: NUM}); it.valid(); it.next()) { - const upid = it.upid; - const pid = it.pid; - const uuid = this.getUuid(0, upid); - this.tracksToAdd.push({ - engineId: this.engineId, - kind: PERF_SAMPLES_PROFILE_TRACK_KIND, - trackSortKey: PrimaryTrackSortKey.PERF_SAMPLES_PROFILE_TRACK, - name: `Callstacks ${pid}`, - trackGroup: uuid, - config: {upid}, - }); - } - } - - getUuidUnchecked(utid: number, upid: number|null) { - return upid === null ? this.utidToUuid.get(utid) : - this.upidToUuid.get(upid); - } - - getUuid(utid: number, upid: number|null) { - return assertExists(this.getUuidUnchecked(utid, upid)); - } - - getOrCreateUuid(utid: number, upid: number|null) { - let uuid = this.getUuidUnchecked(utid, upid); - if (uuid === undefined) { - uuid = uuidv4(); - if (upid === null) { - this.utidToUuid.set(utid, uuid); - } else { - this.upidToUuid.set(upid, uuid); - } - } - return uuid; - } - - setUuidForUpid(upid: number, uuid: string) { - this.upidToUuid.set(upid, uuid); - } - - async addKernelThreadGrouping(engine: EngineProxy): Promise { - // Identify kernel threads if this is a linux system trace, and sufficient - // process information is available. Kernel threads are identified by being - // children of kthreadd (always pid 2). - // The query will return the kthreadd process row first, which must exist - // for any other kthreads to be returned by the query. - // TODO(rsavitski): figure out how to handle the idle process (swapper), - // which has pid 0 but appears as a distinct process (with its own comm) on - // each cpu. It'd make sense to exclude its thread state track, but still - // put process-scoped tracks in this group. - const result = await engine.query(` - select - t.utid, p.upid, (case p.pid when 2 then 1 else 0 end) isKthreadd - from - thread t - join process p using (upid) - left join process parent on (p.parent_upid = parent.upid) - join - (select true from metadata m - where (m.name = 'system_name' and m.str_value = 'Linux') - union - select 1 from (select true from sched limit 1)) - where - p.pid = 2 or parent.pid = 2 - order by isKthreadd desc - `); - - const it = result.iter({ - utid: NUM, - upid: NUM, - }); - - // Not applying kernel thread grouping. - if (!it.valid()) { - return; - } - - // Create the track group. Use kthreadd's PROCESS_SUMMARY_TRACK for the - // main track. It doesn't summarise the kernel threads within the group, - // but creating a dedicated track type is out of scope at the time of - // writing. - const kthreadGroupUuid = uuidv4(); - const summaryTrackId = uuidv4(); - this.tracksToAdd.push({ - id: summaryTrackId, - engineId: this.engineId, - kind: PROCESS_SUMMARY_TRACK, - trackSortKey: PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK, - name: `Kernel thread summary`, - config: {pidForColor: 2, upid: it.upid, utid: it.utid}, - }); - const addTrackGroup = Actions.addTrackGroup({ - engineId: this.engineId, - summaryTrackId, - name: `Kernel threads`, - id: kthreadGroupUuid, - collapsed: true, - }); - this.addTrackGroupActions.push(addTrackGroup); - - // Set the group for all kernel threads (including kthreadd itself). - for (; it.valid(); it.next()) { - this.setUuidForUpid(it.upid, kthreadGroupUuid); - } - } - - async addProcessTrackGroups(engine: EngineProxy): Promise { - // We want to create groups of tracks in a specific order. - // The tracks should be grouped: - // by upid - // or (if upid is null) by utid - // the groups should be sorted by: - // Chrome-based process rank based on process names (e.g. Browser) - // has a heap profile or not - // total cpu time *for the whole parent process* - // process name - // upid - // thread name - // utid - const result = await engine.query(` - select - the_tracks.upid, - the_tracks.utid, - total_dur as hasSched, - hasHeapProfiles, - process.pid as pid, - thread.tid as tid, - process.name as processName, - thread.name as threadName, - ifnull(( - select group_concat(string_value) - from args - where - process.arg_set_id is not null and - arg_set_id = process.arg_set_id and - flat_key = 'chrome.process_label' - ), '') AS chromeProcessLabels, - (case process.name - when 'Browser' then 3 - when 'Gpu' then 2 - when 'Renderer' then 1 - else 0 - end) as chromeProcessRank - from ( - select upid, 0 as utid from process_track - union - select upid, 0 as utid from process_counter_track - union - select upid, utid from thread_counter_track join thread using(utid) - union - select upid, utid from thread_track join thread using(utid) - union - select upid, utid from sched join thread using(utid) group by utid - union - select upid, 0 as utid from ( - select distinct upid - from perf_sample join thread using (utid) join process using (upid) - where callsite_id is not null) - union - select upid, utid from ( - select distinct(utid) from cpu_profile_stack_sample - ) join thread using(utid) - union - select distinct(upid) as upid, 0 as utid from heap_profile_allocation - union - select distinct(upid) as upid, 0 as utid from heap_graph_object - ) the_tracks - left join ( - select upid, sum(thread_total_dur) as total_dur - from ( - select utid, sum(dur) as thread_total_dur - from sched where dur != -1 and utid != 0 - group by utid - ) - join thread using (utid) - group by upid - ) using(upid) - left join ( - select - distinct(upid) as upid, - true as hasHeapProfiles - from heap_profile_allocation - union - select - distinct(upid) as upid, - true as hasHeapProfiles - from heap_graph_object - ) using (upid) - left join ( - select - thread.upid as upid, - sum(cnt) as perfSampleCount - from ( - select utid, count(*) as cnt - from perf_sample where callsite_id is not null - group by utid - ) join thread using (utid) - group by thread.upid - ) using (upid) - left join ( - select - process.upid as upid, - sum(cnt) as sliceCount - from (select track_id, count(*) as cnt from slice group by track_id) - left join thread_track on track_id = thread_track.id - left join thread on thread_track.utid = thread.utid - left join process_track on track_id = process_track.id - join process on process.upid = thread.upid - or process_track.upid = process.upid - where process.upid is not null - group by process.upid - ) using (upid) - left join thread using(utid) - left join process using(upid) - order by - chromeProcessRank desc, - hasHeapProfiles desc, - perfSampleCount desc, - total_dur desc, - sliceCount desc, - processName asc nulls last, - the_tracks.upid asc nulls last, - threadName asc nulls last, - the_tracks.utid asc nulls last; - `); - - const it = result.iter({ - utid: NUM, - upid: NUM_NULL, - tid: NUM_NULL, - pid: NUM_NULL, - threadName: STR_NULL, - processName: STR_NULL, - hasSched: NUM_NULL, - hasHeapProfiles: NUM_NULL, - chromeProcessLabels: STR, - }); - for (; it.valid(); it.next()) { - const utid = it.utid; - const tid = it.tid; - const upid = it.upid; - const pid = it.pid; - const threadName = it.threadName; - const processName = it.processName; - const hasSched = !!it.hasSched; - const hasHeapProfiles = !!it.hasHeapProfiles; - - // Group by upid if present else by utid. - let pUuid = - upid === null ? this.utidToUuid.get(utid) : this.upidToUuid.get(upid); - // These should only happen once for each track group. - if (pUuid === undefined) { - pUuid = this.getOrCreateUuid(utid, upid); - const summaryTrackId = uuidv4(); - - const pidForColor = pid || tid || upid || utid || 0; - const kind = - hasSched ? PROCESS_SCHEDULING_TRACK_KIND : PROCESS_SUMMARY_TRACK; - - this.tracksToAdd.push({ - id: summaryTrackId, - engineId: this.engineId, - kind, - trackSortKey: hasSched ? - PrimaryTrackSortKey.PROCESS_SCHEDULING_TRACK : - PrimaryTrackSortKey.PROCESS_SUMMARY_TRACK, - name: `${upid === null ? tid : pid} summary`, - config: {pidForColor, upid, utid, tid}, - labels: it.chromeProcessLabels.split(','), - }); - - const name = TrackDecider.getTrackName( - {utid, processName, pid, threadName, tid, upid}); - const addTrackGroup = Actions.addTrackGroup({ - engineId: this.engineId, - summaryTrackId, - name, - id: pUuid, - // Perf profiling tracks remain collapsed, otherwise we would have too - // many expanded process tracks for some perf traces, leading to - // jankyness. - collapsed: !hasHeapProfiles, - }); - - this.addTrackGroupActions.push(addTrackGroup); - } - } - } - - private async computeThreadOrderingMetadata(): Promise { - const result = await this.engine.query(` - select - utid, - tid, - pid, - thread.name as threadName - from thread - left join process using(upid)`); - - const it = result.iter({ - utid: NUM, - tid: NUM_NULL, - pid: NUM_NULL, - threadName: STR_NULL, - }); - - const threadOrderingMetadata: UtidToTrackSortKey = {}; - for (; it.valid(); it.next()) { - threadOrderingMetadata[it.utid] = { - tid: it.tid === null ? undefined : it.tid, - sortKey: TrackDecider.getThreadSortKey(it.threadName, it.tid, it.pid), - }; - } - return threadOrderingMetadata; - } - - private async defineMaxLayoutDepthSqlFunction(): Promise { - await this.engine.query(` - select create_function( - 'max_layout_depth(track_count INT, track_ids STRING)', - 'INT', - ' - select ifnull(iif( - $track_count = 1, - ( - select max(depth) - from slice - where track_id = cast($track_ids AS int) - ), - ( - select max(layout_depth) - from experimental_slice_layout($track_ids) - ) - ), 0); - ' - ); - `); - } - - async addPluginTracks(): Promise { - const promises = pluginManager.findPotentialTracks(this.engine); - const groups = await Promise.all(promises); - for (const infos of groups) { - for (const info of infos) { - this.tracksToAdd.push({ - engineId: this.engineId, - kind: info.trackKind, - name: info.name, - // TODO(hjd): Fix how sorting works. Plugins should expose - // 'sort keys' which the user can use to choose a sort order. - trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK, - trackGroup: SCROLLING_TRACK_GROUP, - config: info.config, - }); - } - } - } - - async decideTracks(): Promise { - await this.defineMaxLayoutDepthSqlFunction(); - - // Add first the global tracks that don't require per-process track groups. - await this.addCpuSchedulingTracks(); - await this.addFtraceTrack( - this.engine.getProxy('TrackDecider::addFtraceTrack')); - await this.addCpuFreqTracks( - this.engine.getProxy('TrackDecider::addCpuFreqTracks')); - await this.addGlobalAsyncTracks( - this.engine.getProxy('TrackDecider::addGlobalAsyncTracks')); - await this.addGpuFreqTracks( - this.engine.getProxy('TrackDecider::addGpuFreqTracks')); - await this.addCpuPerfCounterTracks( - this.engine.getProxy('TrackDecider::addCpuPerfCounterTracks')); - await this.addPluginTracks(); - await this.addAnnotationTracks( - this.engine.getProxy('TrackDecider::addAnnotationTracks')); - await this.groupGlobalIonTracks(); - await this.groupGlobalIostatTracks(F2FS_IOSTAT_TAG, F2FS_IOSTAT_GROUP_NAME); - await this.groupGlobalIostatTracks( - F2FS_IOSTAT_LAT_TAG, F2FS_IOSTAT_LAT_GROUP_NAME); - await this.groupGlobalIostatTracks(DISK_IOSTAT_TAG, DISK_IOSTAT_GROUP_NAME); - await this.groupGlobalUfsCmdTagTracks(UFS_CMD_TAG, UFS_CMD_TAG_GROUP_NAME); - await this.groupGlobalBuddyInfoTracks(); - await this.groupTracksByRegex(KERNEL_WAKELOCK_REGEX, KERNEL_WAKELOCK_GROUP); - await this.groupTracksByRegex(NETWORK_TRACK_REGEX, NETWORK_TRACK_GROUP); - await this.groupTracksByRegex( - ENTITY_RESIDENCY_REGEX, ENTITY_RESIDENCY_GROUP); - - // Pre-group all kernel "threads" (actually processes) if this is a linux - // system trace. Below, addProcessTrackGroups will skip them due to an - // existing group uuid, and addThreadStateTracks will fill in the - // per-thread tracks. Quirk: since all threads will appear to be - // TrackKindPriority.MAIN_THREAD, any process-level tracks will end up - // pushed to the bottom of the group in the UI. - await this.addKernelThreadGrouping( - this.engine.getProxy('TrackDecider::addKernelThreadGrouping')); - - // Create the per-process track groups. Note that this won't necessarily - // create a track per process. If a process has been completely idle and has - // no sched events, no track group will be emitted. - // Will populate this.addTrackGroupActions - await this.addProcessTrackGroups( - this.engine.getProxy('TrackDecider::addProcessTrackGroups')); - - await this.addProcessHeapProfileTracks( - this.engine.getProxy('TrackDecider::addProcessHeapProfileTracks')); - if (PERF_SAMPLE_FLAG.get()) { - await this.addProcessPerfSamplesTracks( - this.engine.getProxy('TrackDecider::addProcessPerfSamplesTracks')); - } - await this.addProcessCounterTracks( - this.engine.getProxy('TrackDecider::addProcessCounterTracks')); - await this.addProcessAsyncSliceTracks( - this.engine.getProxy('TrackDecider::addProcessAsyncSliceTrack')); - await this.addActualFramesTracks( - this.engine.getProxy('TrackDecider::addActualFramesTracks')); - await this.addExpectedFramesTracks( - this.engine.getProxy('TrackDecider::addExpectedFramesTracks')); - await this.addThreadCounterTracks( - this.engine.getProxy('TrackDecider::addThreadCounterTracks')); - await this.addThreadStateTracks( - this.engine.getProxy('TrackDecider::addThreadStateTracks')); - await this.addThreadSliceTracks( - this.engine.getProxy('TrackDecider::addThreadSliceTracks')); - await this.addThreadCpuSampleTracks( - this.engine.getProxy('TrackDecider::addThreadCpuSampleTracks')); - await this.addLogsTrack(this.engine.getProxy('TrackDecider::addLogsTrack')); - - // TODO(hjd): Move into plugin API. - { - const result = scrollJankDecideTracks(this.engine, (utid, upid) => { - return this.getUuid(utid, upid); - }); - if (result !== null) { - const {tracksToAdd} = await result; - this.tracksToAdd.push(...tracksToAdd); - } - } - - this.addTrackGroupActions.push( - Actions.addTracks({tracks: this.tracksToAdd})); - - const threadOrderingMetadata = await this.computeThreadOrderingMetadata(); - this.addTrackGroupActions.push( - Actions.setUtidToTrackSortKey({threadOrderingMetadata})); - - this.applyDefaultCounterScale(); - - return this.addTrackGroupActions; - } - - // Some process counter tracks are tied to specific threads based on their - // name. - private async resolveTrackSortKeyForProcessCounterTrack( - upid: number, threadName?: string): Promise { - if (threadName !== 'GPU completion') { - return PrimaryTrackSortKey.COUNTER_TRACK; - } - const result = await this.engine.query(` - select utid - from thread - where upid=${upid} and name=${sqliteString(threadName)} - `); - const it = result.iter({ - utid: NUM, - }); - for (; it; it.next()) { - return { - utid: it.utid, - priority: InThreadTrackSortKey.THREAD_COUNTER_TRACK, - }; - } - return PrimaryTrackSortKey.COUNTER_TRACK; - } - - private static getThreadSortKey( - threadName?: string|null, tid?: number|null, - pid?: number|null): PrimaryTrackSortKey { - if (pid !== undefined && pid !== null && pid === tid) { - return PrimaryTrackSortKey.MAIN_THREAD; - } - if (threadName === undefined || threadName === null) { - return PrimaryTrackSortKey.ORDINARY_THREAD; - } - - // Chrome main threads should always come first within their process. - if (threadName === 'CrBrowserMain' || threadName === 'CrRendererMain' || - threadName === 'CrGpuMain') { - return PrimaryTrackSortKey.MAIN_THREAD; - } - - // Chrome IO threads should always come immediately after the main thread. - if (threadName === 'Chrome_ChildIOThread' || - threadName === 'Chrome_IOThread') { - return PrimaryTrackSortKey.CHROME_IO_THREAD; - } - - // A Chrome process can have only one compositor thread, so we want to put - // it next to other named processes. - if (threadName === 'Compositor' || threadName === 'VizCompositorThread') { - return PrimaryTrackSortKey.CHROME_COMPOSITOR_THREAD; - } - - switch (true) { - case /.*RenderThread.*/.test(threadName): - return PrimaryTrackSortKey.RENDER_THREAD; - case /.*GPU completion.*/.test(threadName): - return PrimaryTrackSortKey.GPU_COMPLETION_THREAD; - default: - return PrimaryTrackSortKey.ORDINARY_THREAD; - } - } -} diff --git a/third_party/perfetto/ui/src/controller/validators.ts b/third_party/perfetto/ui/src/controller/validators.ts deleted file mode 100644 index c35c69dd3a26..000000000000 --- a/third_party/perfetto/ui/src/controller/validators.ts +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Execution context of object validator -interface ValidatorContext { - // Path to the current value starting from the root. Object field names are - // stored as is, array indices are wrapped to square brackets. Represented - // as an array to avoid unnecessary string concatenation: parts are going to - // be concatenated into a single string when reporting errors, which should - // not happen on a happy path. - // Example: ["config", "androidLogBuffers", "1"] when parsing object - // accessible through expression `root.config.androidLogBuffers[1]` - path: string[]; - - // Paths from the root to extraneous keys in a validated object. - extraKeys: string[]; - - // Paths from the root to keys containing values of wrong type in validated - // object. - invalidKeys: string[]; -} - -// Validator accepting arbitrary data structure and returning a typed value. -// Can throw an error if a part of the value does not have a reasonable -// default. -export interface Validator { - validate(input: unknown, context: ValidatorContext): T; -} - -// Helper function to flatten array of path chunks into a single string -// Example: ["config", "androidLogBuffers", "1"] is mapped to -// "config.androidLogBuffers[1]". -function renderPath(path: string[]): string { - let result = ''; - for (let i = 0; i < path.length; i++) { - if (i > 0 && !path[i].startsWith('[')) { - result += '.'; - } - result += path[i]; - } - return result; -} - -export class ValidationError extends Error {} - -// Abstract class for validating simple values, such as strings and booleans. -// Allows to avoid repetition of most of the code related to validation of -// these. -abstract class PrimitiveValidator implements Validator { - defaultValue: T; - required: boolean; - - constructor(defaultValue: T, required: boolean) { - this.defaultValue = defaultValue; - this.required = required; - } - - // Abstract method that checks whether passed input has correct type. - abstract predicate(input: unknown): input is T; - - validate(input: unknown, context: ValidatorContext): T { - if (this.predicate(input)) { - return input; - } - if (this.required) { - throw new ValidationError(renderPath(context.path)); - } - if (input !== undefined) { - // The value is defined, but does not conform to the expected type; - // proceed with returning the default value but report the key. - context.invalidKeys.push(renderPath(context.path)); - } - return this.defaultValue; - } -} - - -class StringValidator extends PrimitiveValidator { - predicate(input: unknown): input is string { - return typeof input === 'string'; - } -} - -class NumberValidator extends PrimitiveValidator { - predicate(input: unknown): input is number { - return typeof input === 'number'; - } -} - -class BooleanValidator extends PrimitiveValidator { - predicate(input: unknown): input is boolean { - return typeof input === 'boolean'; - } -} - -// Type-level function returning resulting type of a validator. -export type ValidatedType = T extends Validator? S : never; - -// Type-level function traversing a record of validator and returning record -// with the same keys and valid types. -export type RecordValidatedType = { - [k in keyof T]: ValidatedType -}; - -// Combinator for validators: takes a record of validators, and returns a -// validator for a record where record's fields passed to validator with the -// same name. -// -// Generic parameter T is instantiated to type of record of validators, and -// should be provided implicitly by type inference due to verbosity of its -// instantiations. -class RecordValidator>> implements - Validator> { - validators: T; - - constructor(validators: T) { - this.validators = validators; - } - - validate(input: unknown, context: ValidatorContext): RecordValidatedType { - // If value is missing or of incorrect type, empty record is still processed - // in the loop below to initialize default fields of the nested object. - let o: object = {}; - if (typeof input === 'object' && input !== null) { - o = input; - } else if (input !== undefined) { - context.invalidKeys.push(renderPath(context.path)); - } - - const result: Partial> = {}; - // Separate declaration is required to avoid assigning `string` type to `k`. - for (const k in this.validators) { - if (this.validators.hasOwnProperty(k)) { - context.path.push(k); - const validator = this.validators[k]; - - // Accessing value of `k` of `o` is safe because `undefined` values are - // considered to indicate a missing value and handled appropriately by - // every provided validator. - const valid = - validator.validate((o as Record)[k], context); - - result[k] = valid as ValidatedType; - context.path.pop(); - } - } - - // Check if passed object has any extra keys to be reported as such. - for (const key of Object.keys(o)) { - if (!this.validators.hasOwnProperty(key)) { - context.path.push(key); - context.extraKeys.push(renderPath(context.path)); - context.path.pop(); - } - } - return result as RecordValidatedType; - } -} - -// Validator checking whether a value is one of preset values. Used in order to -// provide easy validation for union of literal types. -class OneOfValidator implements Validator { - validValues: readonly T[]; - defaultValue: T; - - constructor(validValues: readonly T[], defaultValue: T) { - this.defaultValue = defaultValue; - this.validValues = validValues; - } - - validate(input: unknown, context: ValidatorContext): T { - if (this.validValues.includes(input as T)) { - return input as T; - } else if (input !== undefined) { - context.invalidKeys.push(renderPath(context.path)); - } - return this.defaultValue; - } -} - -// Validator for an array of elements, applying the same element validator for -// each element of an array. Uses empty array as a default value. -class ArrayValidator implements Validator { - elementValidator: Validator; - - constructor(elementValidator: Validator) { - this.elementValidator = elementValidator; - } - - validate(input: unknown, context: ValidatorContext): T[] { - const result: T[] = []; - if (Array.isArray(input)) { - for (let i = 0; i < input.length; i++) { - context.path.push(`[${i}]`); - result.push(this.elementValidator.validate(input[i], context)); - context.path.pop(); - } - } else if (input !== undefined) { - context.invalidKeys.push(renderPath(context.path)); - } - return result; - } -} - -// Wrapper container for validation result contaiting diagnostic information in -// addition to the resulting typed value. -export interface ValidationResult { - result: T; - invalidKeys: string[]; - extraKeys: string[]; -} - -// Wrapper for running a validator initializing the context. -export function runValidator( - validator: Validator, input: unknown): ValidationResult { - const context: ValidatorContext = { - path: [], - invalidKeys: [], - extraKeys: [], - }; - const result = validator.validate(input, context); - return { - result, - invalidKeys: context.invalidKeys, - extraKeys: context.extraKeys, - }; -} - -// Shorthands for the validator classes above enabling concise notation. -export function str(defaultValue = ''): StringValidator { - return new StringValidator(defaultValue, false); -} - -export const requiredStr = new StringValidator('', true); - -export function num(defaultValue = 0): NumberValidator { - return new NumberValidator(defaultValue, false); -} - -export function bool(defaultValue = false): BooleanValidator { - return new BooleanValidator(defaultValue, false); -} - -export function record>>( - validators: T): RecordValidator { - return new RecordValidator(validators); -} - -export function oneOf( - values: readonly T[], defaultValue: T): OneOfValidator { - return new OneOfValidator(values, defaultValue); -} - -export function arrayOf(elementValidator: Validator): ArrayValidator { - return new ArrayValidator(elementValidator); -} diff --git a/third_party/perfetto/ui/src/controller/validators_unittest.ts b/third_party/perfetto/ui/src/controller/validators_unittest.ts deleted file mode 100644 index eec9aa5ea953..000000000000 --- a/third_party/perfetto/ui/src/controller/validators_unittest.ts +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { - arrayOf, - num, - oneOf, - record, - requiredStr, - runValidator, - ValidatedType, - ValidationError, -} from './validators'; - -const colors = ['RED', 'GREEN', 'BLUE'] as const; - -type Color = typeof colors[number]; - -const point = record({ - id: requiredStr, - color: oneOf(colors, 'RED'), - x: num(), - y: num(1), - properties: record({mass: num(10)}), -}); - -type Point = ValidatedType; - -const nested = - record({deeply: record({nested: record({array: arrayOf(point)})})}); - -test('validator ensures presence of required fields', () => { - expect(() => { - runValidator(point, {}); - }).toThrow(ValidationError); -}); - -test('validator ensures correct type of required fields', () => { - expect(() => { - runValidator(point, {id: 0}); - }).toThrow(ValidationError); -}); - -test('validator fills default values', () => { - const p: Point = runValidator(point, {id: 'test'}).result; - - expect(p.color).toEqual('RED'); - expect(p.x).toEqual(0); - expect(p.y).toEqual(1); - expect(p.properties.mass).toEqual(10); -}); - -test('validator uses provided values', () => { - const p: Point = - runValidator( - point, - {id: 'test', x: 100, y: 200, color: 'GREEN', properties: {mass: 20}}) - .result; - - expect(p.color).toEqual('GREEN'); - expect(p.x).toEqual(100); - expect(p.y).toEqual(200); - expect(p.properties.mass).toEqual(20); -}); - -test('validator keeps information about extra and invalid keys', () => { - const result = runValidator(point, { - id: 'test', - x: 'should not be a string', - extra: 'should not be here', - properties: {mass: 'should be a number', weight: 'should not be here'}, - }); - - expect(result.extraKeys).toContain('extra'); - expect(result.extraKeys).toContain('properties.weight'); - expect(result.invalidKeys).toContain('x'); - expect(result.invalidKeys).toContain('properties.mass'); -}); - -test('validator correctly keeps track of path when reporting keys', () => { - const result = runValidator(nested, { - extra1: 0, - deeply: { - extra2: 1, - nested: { - array: [ - {id: 'point1', x: 'should not be a string'}, - {id: 'point2', extra3: 'should not be here'}, - ], - }, - }, - }); - - expect(result.extraKeys).toContain('extra1'); - expect(result.extraKeys).toContain('deeply.extra2'); - expect(result.extraKeys).toContain('deeply.nested.array[1].extra3'); - expect(result.invalidKeys).toContain('deeply.nested.array[0].x'); -}); diff --git a/third_party/perfetto/ui/src/controller/visualised_args_controller.ts b/third_party/perfetto/ui/src/controller/visualised_args_controller.ts deleted file mode 100644 index 8e709f544b88..000000000000 --- a/third_party/perfetto/ui/src/controller/visualised_args_controller.ts +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {v4 as uuidv4} from 'uuid'; - -import {Actions, AddTrackArgs} from '../common/actions'; -import {Engine} from '../common/engine'; -import {NUM} from '../common/query_result'; -import {InThreadTrackSortKey} from '../common/state'; -import {globals} from '../frontend/globals'; -import { - VISUALISED_ARGS_SLICE_TRACK_KIND, -} from '../tracks/visualised_args/index'; - -import {Controller} from './controller'; - -export interface VisualisedArgControllerArgs { - argName: string; - engine: Engine; -} - -export class VisualisedArgController extends Controller<'init'|'running'> { - private engine: Engine; - private argName: string; - private escapedArgName: string; - private tableName: string; - private addedTrackIds: string[]; - - constructor(args: VisualisedArgControllerArgs) { - super('init'); - this.argName = args.argName; - this.engine = args.engine; - this.escapedArgName = this.argName.replace(/[^a-zA-Z]/g, '_'); - this.tableName = `__arg_visualisation_helper_${this.escapedArgName}_slice`; - this.addedTrackIds = []; - } - - onDestroy() { - this.engine.query(`drop table if exists ${this.tableName}`); - globals.dispatch( - Actions.removeVisualisedArgTracks({trackIds: this.addedTrackIds})); - } - - async createTracks() { - const result = await this.engine.query(` - drop table if exists ${this.tableName}; - - create table ${this.tableName} as - with slice_with_arg as ( - select - slice.id, - slice.track_id, - slice.ts, - slice.dur, - slice.thread_dur, - NULL as cat, - args.display_value as name - from slice - join args using (arg_set_id) - where args.key='${this.argName}' - ) - select - *, - (select count() - from ancestor_slice(s1.id) s2 - join slice_with_arg s3 on s2.id=s3.id - ) as depth - from slice_with_arg s1 - order by id; - - select - track_id as trackId, - max(depth) as maxDepth - from ${this.tableName} - group by track_id; - `); - - const tracksToAdd: AddTrackArgs[] = []; - const it = result.iter({'trackId': NUM, 'maxDepth': NUM}); - for (; it.valid(); it.next()) { - const track = - globals.state - .tracks[globals.state.uiTrackIdByTraceTrackId[it.trackId]]; - const utid = (track.trackSortKey as {utid?: number}).utid; - const id = uuidv4(); - this.addedTrackIds.push(id); - tracksToAdd.push({ - id, - trackGroup: track.trackGroup, - engineId: this.engine.id, - kind: VISUALISED_ARGS_SLICE_TRACK_KIND, - name: this.argName, - trackSortKey: utid === undefined ? - track.trackSortKey : - {utid, priority: InThreadTrackSortKey.VISUALISED_ARGS_TRACK}, - config: { - maxDepth: it.maxDepth, - namespace: `__arg_visualisation_helper_${this.escapedArgName}`, - trackId: it.trackId, - argName: this.argName, - tid: (track.config as {tid?: number}).tid, - }, - }); - } - globals.dispatch(Actions.addTracks({tracks: tracksToAdd})); - globals.dispatch(Actions.sortThreadTracks({})); - } - - run() { - switch (this.state) { - case 'init': - this.createTracks(); - this.setState('running'); - break; - case 'running': - // Nothing to do here. - break; - default: - throw new Error(`Unexpected state ${this.state}`); - } - } -} diff --git a/third_party/perfetto/ui/src/engine/index.ts b/third_party/perfetto/ui/src/engine/index.ts deleted file mode 100644 index 8a7c27e6021c..000000000000 --- a/third_party/perfetto/ui/src/engine/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertExists} from '../base/logging'; -import {EngineWorkerInitMessage} from '../common/worker_messages'; -import {WasmBridge} from './wasm_bridge'; - -const selfWorker = self as {} as Worker; -const wasmBridge = new WasmBridge(); - -// There are two message handlers here: -// 1. The Worker (self.onmessage) handler. -// 2. The MessagePort handler. -// When the app bootstraps, frontend/index.ts creates a MessageChannel and sends -// one end to the controller (the other worker) and the other end to us, so that -// the controller can interact with the Wasm worker without roundtrips through -// the frontend. -// The sequence of actions is the following: -// 1. The frontend does one postMessage({port: MessagePort}) on the Worker -// scope. This message transfers the MessagePort (whose other end is -// connected to the Conotroller). This is the only postMessage we'll ever -// receive here. -// 2. All the other messages (i.e. the TraceProcessor RPC binary pipe) will be -// received on the MessagePort. - -// Receives the boostrap message from the frontend with the MessagePort. -selfWorker.onmessage = (msg: MessageEvent) => { - const port = assertExists((msg.data as EngineWorkerInitMessage).enginePort); - wasmBridge.initialize(port); -}; diff --git a/third_party/perfetto/ui/src/engine/wasm_bridge.ts b/third_party/perfetto/ui/src/engine/wasm_bridge.ts deleted file mode 100644 index 9cbcadac1b4f..000000000000 --- a/third_party/perfetto/ui/src/engine/wasm_bridge.ts +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {defer} from '../base/deferred'; -import {assertExists, assertTrue} from '../base/logging'; -import initTraceProcessor from '../gen/trace_processor'; - -// The Initialize() call will allocate a buffer of REQ_BUF_SIZE bytes which -// will be used to copy the input request data. This is to avoid passing the -// input data on the stack, which has a limited (~1MB) size. -// The buffer will be allocated by the C++ side and reachable at -// HEAPU8[reqBufferAddr, +REQ_BUFFER_SIZE]. -const REQ_BUF_SIZE = 32 * 1024 * 1024; - -// The end-to-end interaction between JS and Wasm is as follows: -// - [JS] Inbound data received by the worker (onmessage() in engine/index.ts). -// - [JS] onRpcDataReceived() (this file) -// - [C++] trace_processor_on_rpc_request (wasm_bridge.cc) -// - [C++] some TraceProcessor::method() -// for (batch in result_rows) -// - [C++] RpcResponseFunction(bytes) (wasm_bridge.cc) -// - [JS] onReply() (this file) -// - [JS] postMessage() (this file) -export class WasmBridge { - // When this promise has resolved it is safe to call callWasm. - whenInitialized: Promise; - - private aborted: boolean; - private connection: initTraceProcessor.Module; - private reqBufferAddr = 0; - private lastStderr: string[] = []; - private messagePort?: MessagePort; - - constructor() { - this.aborted = false; - const deferredRuntimeInitialized = defer(); - this.connection = initTraceProcessor({ - locateFile: (s: string) => s, - print: (line: string) => console.log(line), - printErr: (line: string) => this.appendAndLogErr(line), - onRuntimeInitialized: () => deferredRuntimeInitialized.resolve(), - }); - this.whenInitialized = deferredRuntimeInitialized.then(() => { - const fn = this.connection.addFunction(this.onReply.bind(this), 'vii'); - this.reqBufferAddr = this.connection.ccall( - 'trace_processor_rpc_init', - /* return=*/ 'number', - /* args=*/['number', 'number'], - [fn, REQ_BUF_SIZE]); - }); - } - - initialize(port: MessagePort) { - // Ensure that initialize() is called only once. - assertTrue(this.messagePort === undefined); - this.messagePort = port; - // Note: setting .onmessage implicitly calls port.start() and dispatches the - // queued messages. addEventListener('message') doesn't. - this.messagePort.onmessage = this.onMessage.bind(this); - } - - onMessage(msg: MessageEvent) { - if (this.aborted) { - throw new Error('Wasm module crashed'); - } - assertTrue(msg.data instanceof Uint8Array); - const data = msg.data as Uint8Array; - let wrSize = 0; - // If the request data is larger than our JS<>Wasm interop buffer, split it - // into multiple writes. The RPC channel is byte-oriented and is designed to - // deal with arbitrary fragmentations. - while (wrSize < data.length) { - const sliceLen = Math.min(data.length - wrSize, REQ_BUF_SIZE); - const dataSlice = data.subarray(wrSize, wrSize + sliceLen); - this.connection.HEAPU8.set(dataSlice, this.reqBufferAddr); - wrSize += sliceLen; - try { - this.connection.ccall( - 'trace_processor_on_rpc_request', // C function name. - 'void', // Return type. - ['number'], // Arg types. - [sliceLen], // Args. - ); - } catch (err) { - this.aborted = true; - let abortReason = `${err}`; - if (err instanceof Error) { - abortReason = `${err.name}: ${err.message}\n${err.stack}`; - } - abortReason += '\n\nstderr: \n' + this.lastStderr.join('\n'); - throw new Error(abortReason); - } - } // while(wrSize < data.length) - } - - // This function is bound and passed to Initialize and is called by the C++ - // code while in the ccall(trace_processor_on_rpc_request). - private onReply(heapPtr: number, size: number) { - const data = this.connection.HEAPU8.slice(heapPtr, heapPtr + size); - assertExists(this.messagePort).postMessage(data, [data.buffer]); - } - - private appendAndLogErr(line: string) { - console.warn(line); - // Keep the last N lines in the |lastStderr| buffer. - this.lastStderr.push(line); - if (this.lastStderr.length > 512) { - this.lastStderr.shift(); - } - } -} diff --git a/third_party/perfetto/ui/src/frontend/aggregation_panel.ts b/third_party/perfetto/ui/src/frontend/aggregation_panel.ts deleted file mode 100644 index ddcb6baf56fe..000000000000 --- a/third_party/perfetto/ui/src/frontend/aggregation_panel.ts +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {Actions} from '../common/actions'; -import { - AggregateData, - Column, - ThreadStateExtra, -} from '../common/aggregation_data'; -import {colorForState, textColorForState} from '../common/colorizer'; -import {translateState} from '../common/thread_state'; - -import {globals} from './globals'; -import {Panel} from './panel'; - -export interface AggregationPanelAttrs { - data: AggregateData; - kind: string; -} - -export class AggregationPanel extends Panel { - view({attrs}: m.CVnode) { - return m( - '.details-panel', - m('.details-panel-heading.aggregation', - attrs.data.extra !== undefined && - attrs.data.extra.kind === 'THREAD_STATE' ? - this.showStateSummary(attrs.data.extra) : - null, - this.showTimeRange(), - m('table', - m('tr', - attrs.data.columns.map( - (col) => this.formatColumnHeading(col, attrs.kind))), - m('tr.sum', attrs.data.columnSums.map((sum) => { - const sumClass = sum === '' ? 'td' : 'td.sum-data'; - return m(sumClass, sum); - })))), - m( - '.details-table.aggregation', - m('table', this.getRows(attrs.data)), - )); - } - - formatColumnHeading(col: Column, id: string) { - const pref = globals.state.aggregatePreferences[id]; - let sortIcon = ''; - if (pref && pref.sorting && pref.sorting.column === col.columnId) { - sortIcon = pref.sorting.direction === 'DESC' ? 'arrow_drop_down' : - 'arrow_drop_up'; - } - return m( - 'th', - { - onclick: () => { - globals.dispatch( - Actions.updateAggregateSorting({id, column: col.columnId})); - }, - }, - col.title, - m('i.material-icons', sortIcon)); - } - - getRows(data: AggregateData) { - if (data.columns.length === 0) return; - const rows = []; - for (let i = 0; i < data.columns[0].data.length; i++) { - const row = []; - for (let j = 0; j < data.columns.length; j++) { - row.push(m('td', this.getFormattedData(data, i, j))); - } - rows.push(m('tr', row)); - } - return rows; - } - - getFormattedData(data: AggregateData, rowIndex: number, columnIndex: number) { - switch (data.columns[columnIndex].kind) { - case 'STRING': - return data.strings[data.columns[columnIndex].data[rowIndex]]; - case 'TIMESTAMP_NS': - return `${data.columns[columnIndex].data[rowIndex] / 1000000}`; - case 'STATE': { - const concatState = - data.strings[data.columns[columnIndex].data[rowIndex]]; - const split = concatState.split(','); - const ioWait = - split[1] === 'NULL' ? undefined : !!Number.parseInt(split[1], 10); - return translateState(split[0], ioWait); - } - case 'NUMBER': - default: - return data.columns[columnIndex].data[rowIndex]; - } - } - - showTimeRange() { - const selection = globals.state.currentSelection; - if (selection === null || selection.kind !== 'AREA') return undefined; - const selectedArea = globals.state.areas[selection.areaId]; - const rangeDurationMs = (selectedArea.endSec - selectedArea.startSec) * 1e3; - return m('.time-range', `Selected range: ${rangeDurationMs.toFixed(6)} ms`); - } - - // Thread state aggregation panel only - showStateSummary(data: ThreadStateExtra) { - if (data === undefined) return undefined; - const states = []; - for (let i = 0; i < data.states.length; i++) { - const color = colorForState(data.states[i]); - const textColor = textColorForState(data.states[i]); - const width = data.values[i] / data.totalMs * 100; - states.push( - m('.state', - { - style: { - background: `hsl(${color.h},${color.s}%,${color.l}%)`, - color: `${textColor}`, - width: `${width}%`, - }, - }, - `${data.states[i]}: ${data.values[i]} ms`)); - } - return m('.states', states); - } - - renderCanvas() {} -} diff --git a/third_party/perfetto/ui/src/frontend/analytics.ts b/third_party/perfetto/ui/src/frontend/analytics.ts deleted file mode 100644 index 1e298aeff7b1..000000000000 --- a/third_party/perfetto/ui/src/frontend/analytics.ts +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {getCurrentChannel} from '../common/channels'; -import {VERSION} from '../gen/perfetto_version'; - -import {globals} from './globals'; -import {Router} from './router'; - -type TraceCategories = 'Trace Actions'|'Record Trace'|'User Actions'; -const ANALYTICS_ID = 'UA-137828855-1'; -const PAGE_TITLE = 'no-page-title'; - -export function initAnalytics() { - // Only initialize logging on the official site and on localhost (to catch - // analytics bugs when testing locally). - // Skip analytics is the fragment has "testing=1", this is used by UI tests. - // Skip analytics in embeddedMode since iFrames do not have the same access to - // local storage. - if ((window.location.origin.startsWith('http://localhost:') || - window.location.origin.endsWith('.perfetto.dev')) && - !globals.testing && !globals.embeddedMode) { - return new AnalyticsImpl(); - } - return new NullAnalytics(); -} - -const gtagGlobals = window as {} as { - dataLayer: any[]; - gtag: (command: string, event: string|Date, args?: {}) => void; -}; - -export interface Analytics { - initialize(): void; - updatePath(_: string): void; - logEvent(_x: TraceCategories|null, _y: string): void; - logError(_x: string, _y?: boolean): void; - isEnabled(): boolean; -} - -export class NullAnalytics implements Analytics { - initialize() {} - updatePath(_: string) {} - logEvent(_x: TraceCategories|null, _y: string) {} - logError(_x: string) {} - isEnabled(): boolean { - return false; - } -} - -class AnalyticsImpl implements Analytics { - private initialized_ = false; - - constructor() { - // The code below is taken from the official Google Analytics docs [1] and - // adapted to TypeScript. We have it here rather than as an inline script - // in index.html (as suggested by GA's docs) because inline scripts don't - // play nicely with the CSP policy, at least in Firefox (Firefox doesn't - // support all CSP 3 features we use). - // [1] https://developers.google.com/analytics/devguides/collection/gtagjs . - gtagGlobals.dataLayer = gtagGlobals.dataLayer || []; - - function gtagFunction(..._: any[]) { - // This needs to be a function and not a lambda. |arguments| behaves - // slightly differently in a lambda and breaks GA. - gtagGlobals.dataLayer.push(arguments); - } - gtagGlobals.gtag = gtagFunction; - gtagGlobals.gtag('js', new Date()); - } - - // This is callled only after the script that sets isInternalUser loads. - // It is fine to call updatePath() and log*() functions before initialize(). - // The gtag() function internally enqueues all requests into |dataLayer|. - initialize() { - if (this.initialized_) return; - this.initialized_ = true; - const script = document.createElement('script'); - script.src = 'https://www.googletagmanager.com/gtag/js?id=' + ANALYTICS_ID; - script.defer = true; - document.head.appendChild(script); - const route = Router.parseUrl(window.location.href).page || '/'; - console.log( - `GA initialized. route=${route}`, - `isInternalUser=${globals.isInternalUser}`); - // GA's reccomendation for SPAs is to disable automatic page views and - // manually send page_view events. See: - // https://developers.google.com/analytics/devguides/collection/gtagjs/pages#manual_pageviews - gtagGlobals.gtag('config', ANALYTICS_ID, { - allow_google_signals: false, - anonymize_ip: true, - page_path: route, - referrer: document.referrer.split('?')[0], - send_page_view: false, - page_title: PAGE_TITLE, - dimension1: globals.isInternalUser ? '1' : '0', - dimension2: VERSION, - dimension3: getCurrentChannel(), - }); - this.updatePath(route); - } - - updatePath(path: string) { - gtagGlobals.gtag( - 'event', 'page_view', {page_path: path, page_title: PAGE_TITLE}); - } - - logEvent(category: TraceCategories|null, event: string) { - gtagGlobals.gtag('event', event, {event_category: category}); - } - - logError(description: string, fatal = true) { - gtagGlobals.gtag('event', 'exception', {description, fatal}); - } - - isEnabled(): boolean { - return true; - } -} diff --git a/third_party/perfetto/ui/src/frontend/analyze_page.ts b/third_party/perfetto/ui/src/frontend/analyze_page.ts deleted file mode 100644 index 85ad308f0a95..000000000000 --- a/third_party/perfetto/ui/src/frontend/analyze_page.ts +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -import m from 'mithril'; - -import {EngineProxy} from '../common/engine'; -import {QueryResponse, runQuery} from '../common/queries'; - -import {addTab} from './bottom_tab'; -import {globals} from './globals'; -import {createPage} from './pages'; -import {QueryHistoryComponent, queryHistoryStorage} from './query_history'; -import {QueryResultTab} from './query_result_tab'; -import {QueryTable} from './query_table'; - -const INPUT_PLACEHOLDER = 'Enter query and press Cmd/Ctrl + Enter'; -const INPUT_MIN_LINES = 2; -const INPUT_MAX_LINES = 10; -const INPUT_LINE_HEIGHT_EM = 1.2; -const TAB_SPACES = 2; -const TAB_SPACES_STRING = ' '.repeat(TAB_SPACES); - -interface AnalyzePageState { - enteredText: string; - executedQuery?: string; - queryResult?: QueryResponse; -} - -const state: AnalyzePageState = { - enteredText: '', -}; - -export function runAnalyzeQuery(query: string) { - state.executedQuery = query; - state.queryResult = undefined; - const engine = getEngine(); - if (engine) { - runQuery(query, engine).then((resp: QueryResponse) => { - addTab({ - kind: QueryResultTab.kind, - tag: 'analyze_page_query', - config: { - query: query, - title: 'Standalone Query', - prefetchedResponse: resp, - }, - }); - // We might have started to execute another query. Ignore it in that case. - if (state.executedQuery !== query) { - return; - } - state.queryResult = resp; - globals.rafScheduler.scheduleFullRedraw(); - }); - } -} - -function getEngine(): EngineProxy|undefined { - const engineId = globals.getCurrentEngine()?.id; - if (engineId === undefined) { - return undefined; - } - const engine = globals.engines.get(engineId)?.getProxy('AnalyzePage'); - return engine; -} - -class QueryInput implements m.ClassComponent { - // How many lines to display if the user hasn't resized the input box. - displayLines = INPUT_MIN_LINES; - - static onKeyDown(e: Event) { - const event = e as KeyboardEvent; - const target = e.target as HTMLTextAreaElement; - const {selectionStart, selectionEnd} = target; - - if (event.code === 'Enter' && (event.metaKey || event.ctrlKey)) { - event.preventDefault(); - let query = target.value; - if (selectionEnd > selectionStart) { - query = query.substring(selectionStart, selectionEnd); - } - if (!query) return; - queryHistoryStorage.saveQuery(query); - - runAnalyzeQuery(query); - } - - if (event.code === 'Tab') { - // Handle tabs to insert spaces. - event.preventDefault(); - const lastLineBreak = target.value.lastIndexOf('\n', selectionEnd); - - if (selectionStart === selectionEnd || lastLineBreak < selectionStart) { - // Selection does not contain line breaks, therefore is on a single - // line. In this case, replace the selection with spaces. Replacement is - // done via document.execCommand as opposed to direct manipulation of - // element's value attribute because modifying latter programmatically - // drops the edit history which breaks undo/redo functionality. - document.execCommand('insertText', false, TAB_SPACES_STRING); - } else { - this.handleMultilineTab(target, event); - } - } - } - - // Handle Tab press when the current selection is multiline: find all the - // lines intersecting with the selection, and either indent or dedent (if - // Shift key is held) them. - private static handleMultilineTab( - target: HTMLTextAreaElement, event: KeyboardEvent) { - const {selectionStart, selectionEnd} = target; - const firstLineBreak = target.value.lastIndexOf('\n', selectionStart - 1); - - // If no line break is found (selection begins at the first line), - // replacementStart would have the correct value of 0. - const replacementStart = firstLineBreak + 1; - const replacement = target.value.substring(replacementStart, selectionEnd) - .split('\n') - .map((line) => { - if (event.shiftKey) { - // When Shift is held, remove whitespace at the - // beginning - return this.dedent(line); - } else { - return TAB_SPACES_STRING + line; - } - }) - .join('\n'); - // Select the range to be replaced. - target.setSelectionRange(replacementStart, selectionEnd); - document.execCommand('insertText', false, replacement); - // Restore the selection to match the previous selection, allowing to chain - // indent operations by just pressing Tab several times. - target.setSelectionRange( - replacementStart, replacementStart + replacement.length); - } - - // Chop off up to TAB_SPACES leading spaces from a string. - private static dedent(line: string): string { - let i = 0; - while (i < line.length && i < TAB_SPACES && line[i] === ' ') { - i++; - } - return line.substring(i); - } - - onInput(textareaValue: string) { - const textareaLines = textareaValue.split('\n').length; - const clampedNumLines = - Math.min(Math.max(textareaLines, INPUT_MIN_LINES), INPUT_MAX_LINES); - this.displayLines = clampedNumLines; - state.enteredText = textareaValue; - globals.rafScheduler.scheduleFullRedraw(); - } - - // This method exists because unfortunatley setting custom properties on an - // element's inline style attribue doesn't seem to work in mithril, even - // though the docs claim so. - setHeightBeforeResize(node: HTMLElement) { - // +2em for some extra breathing space to account for padding. - const heightEm = this.displayLines * INPUT_LINE_HEIGHT_EM + 2; - // We set a height based on the number of lines that we want to display by - // default. If the user resizes the textbox using the resize handle in the - // bottom-right corner, this height is overridden. - node.style.setProperty('--height-before-resize', `${heightEm}em`); - // TODO(dproy): The resized height is lost if user navigates away from the - // page and comes back. - } - - oncreate(vnode: m.VnodeDOM) { - // This makes sure query persists if user navigates to other pages and comes - // back to analyze page. - const existingQuery = state.enteredText; - const textarea = vnode.dom as HTMLTextAreaElement; - if (existingQuery) { - textarea.value = existingQuery; - this.onInput(existingQuery); - } - - this.setHeightBeforeResize(textarea); - } - - onupdate(vnode: m.VnodeDOM) { - this.setHeightBeforeResize(vnode.dom as HTMLElement); - } - - view() { - return m('textarea.query-input', { - placeholder: INPUT_PLACEHOLDER, - onkeydown: (e: Event) => QueryInput.onKeyDown(e), - oninput: (e: Event) => - this.onInput((e.target as HTMLTextAreaElement).value), - }); - } -} - - -export const AnalyzePage = createPage({ - view() { - return m( - '.analyze-page', - m(QueryInput), - state.executedQuery === undefined ? null : m(QueryTable, { - query: state.executedQuery, - resp: state.queryResult, - onClose: () => { - state.executedQuery = undefined; - state.queryResult = undefined; - globals.rafScheduler.scheduleFullRedraw(); - }, - }), - m(QueryHistoryComponent)); - }, -}); diff --git a/third_party/perfetto/ui/src/frontend/anchor.ts b/third_party/perfetto/ui/src/frontend/anchor.ts deleted file mode 100644 index 49c1a256af5d..000000000000 --- a/third_party/perfetto/ui/src/frontend/anchor.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -interface AnchorAttrs { - // Optional icon to show at the end of the content. - icon?: string; - // Remaining items. - [htmlAttrs: string]: any; -} - -export class Anchor implements m.ClassComponent { - view({attrs, children}: m.CVnode) { - const {icon, ...htmlAttrs} = attrs; - - return m( - 'a.pf-anchor', - htmlAttrs, - icon && m('i.material-icons', icon), - children, - ); - } -} diff --git a/third_party/perfetto/ui/src/frontend/android_bug_tool.ts b/third_party/perfetto/ui/src/frontend/android_bug_tool.ts deleted file mode 100644 index c36e3d8e9466..000000000000 --- a/third_party/perfetto/ui/src/frontend/android_bug_tool.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import {defer} from '../base/deferred'; - -enum WebContentScriptMessageType { - UNKNOWN, - CONVERT_OBJECT_URL, - CONVERT_OBJECT_URL_RESPONSE, -} - -const ANDROID_BUG_TOOL_EXTENSION_ID = 'mbbaofdfoekifkfpgehgffcpagbbjkmj'; - -interface Attachment { - name: string; - objectUrl: string; - restrictionSeverity: number; -} - -interface ConvertObjectUrlResponse { - action: WebContentScriptMessageType.CONVERT_OBJECT_URL_RESPONSE; - attachments: Attachment[]; - issueAccessLevel: string; - issueId: string; - issueTitle: string; -} - -export interface TraceFromBuganizer { - issueId: string; - issueTitle: string; - file: File; -} - -export function loadAndroidBugToolInfo(): Promise { - const deferred = defer(); - - // Request to convert the blob object url "blob:chrome-extension://xxx" - // the chrome extension has to a web downloadable url "blob:http://xxx". - chrome.runtime.sendMessage( - ANDROID_BUG_TOOL_EXTENSION_ID, - {action: WebContentScriptMessageType.CONVERT_OBJECT_URL}, - async (response: ConvertObjectUrlResponse) => { - switch (response.action) { - case WebContentScriptMessageType.CONVERT_OBJECT_URL_RESPONSE: - if (response.attachments?.length > 0) { - const filesBlobPromises = - response.attachments.map(async (attachment) => { - const fileQueryResponse = await fetch(attachment.objectUrl); - const blob = await fileQueryResponse.blob(); - // Note: The blob's media type is always set to "image/png". - // Clone blob to clear media type. - return new File([blob], attachment.name); - }); - const files = await Promise.all(filesBlobPromises); - deferred.resolve({ - issueId: response.issueId, - issueTitle: response.issueTitle, - file: files[0], - }); - } else { - throw new Error('Got no attachements from extension'); - } - break; - default: - throw new Error(`Received unhandled response code (${ - response.action}) from extension.`); - } - }); - return deferred; -} diff --git a/third_party/perfetto/ui/src/frontend/animation.ts b/third_party/perfetto/ui/src/frontend/animation.ts deleted file mode 100644 index 1a6ca930af28..000000000000 --- a/third_party/perfetto/ui/src/frontend/animation.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {globals} from './globals'; - -export class Animation { - private startMs = 0; - private endMs = 0; - private boundOnAnimationFrame = this.onAnimationFrame.bind(this); - - constructor(private onAnimationStep: (timeSinceStartMs: number) => void) {} - - start(durationMs: number) { - const nowMs = performance.now(); - - // If the animation is already happening, just update its end time. - if (nowMs <= this.endMs) { - this.endMs = nowMs + durationMs; - return; - } - this.startMs = nowMs; - this.endMs = nowMs + durationMs; - globals.rafScheduler.start(this.boundOnAnimationFrame); - } - - stop() { - this.endMs = 0; - globals.rafScheduler.stop(this.boundOnAnimationFrame); - } - - get startTimeMs(): number { - return this.startMs; - } - - private onAnimationFrame(nowMs: number) { - if (nowMs >= this.endMs) { - globals.rafScheduler.stop(this.boundOnAnimationFrame); - return; - } - this.onAnimationStep(Math.max(Math.round(nowMs - this.startMs), 0)); - } -} diff --git a/third_party/perfetto/ui/src/frontend/base_slice_track.ts b/third_party/perfetto/ui/src/frontend/base_slice_track.ts deleted file mode 100644 index ac3cad07b0e9..000000000000 --- a/third_party/perfetto/ui/src/frontend/base_slice_track.ts +++ /dev/null @@ -1,814 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertExists} from '../base/logging'; -import {Actions} from '../common/actions'; -import {cropText, drawIncompleteSlice} from '../common/canvas_utils'; -import { - colorCompare, - colorToStr, - UNEXPECTED_PINK_COLOR, -} from '../common/colorizer'; -import {NUM} from '../common/query_result'; -import {Selection, SelectionKind} from '../common/state'; -import {fromNs, toNs} from '../common/time'; - -import {checkerboardExcept} from './checkerboard'; -import {globals} from './globals'; -import {Slice} from './slice'; -import {DEFAULT_SLICE_LAYOUT, SliceLayout} from './slice_layout'; -import {NewTrackArgs, SliceRect, Track} from './track'; -import {BUCKETS_PER_PIXEL, CacheKey, TrackCache} from './track_cache'; - -// The common class that underpins all tracks drawing slices. - -export const SLICE_FLAGS_INCOMPLETE = 1; -export const SLICE_FLAGS_INSTANT = 2; - -// Slices smaller than this don't get any text: -const SLICE_MIN_WIDTH_FOR_TEXT_PX = 5; -const SLICE_MIN_WIDTH_PX = 1 / BUCKETS_PER_PIXEL; -const CHEVRON_WIDTH_PX = 10; -const DEFAULT_SLICE_COLOR = UNEXPECTED_PINK_COLOR; - -// Exposed and standalone to allow for testing without making this -// visible to subclasses. -function filterVisibleSlices( - slices: S[], startS: number, endS: number): S[] { - // Here we aim to reduce the number of slices we have to draw - // by ignoring those that are not visible. A slice is visible iff: - // slice.start + slice.duration >= start && slice.start <= end - // It's allowable to include slices which aren't visible but we - // must not exclude visible slices. - // We could filter this.slices using this condition but since most - // often we should have the case where there are: - // - First a bunch of non-visible slices to the left of the viewport - // - Then a bunch of visible slices within the viewport - // - Finally a second bunch of non-visible slices to the right of the - // viewport. - // It seems more sensible to identify the left-most and right-most - // visible slices then 'slice' to select these slices and everything - // between. - - // We do not need to handle non-ending slices (where dur = -1 - // but the slice is drawn as 'infinite' length) as this is handled - // by a special code path. - // TODO(hjd): Implement special code path. - - // While the slices are guaranteed to be ordered by timestamp we must - // consider async slices (which are not perfectly nested). This is to - // say if we see slice A then B it is guaranteed the A.start <= B.start - // but there is no guarantee that (A.end < B.start XOR A.end >= B.end). - // Due to this is not possible to use binary search to find the first - // visible slice. Consider the following situation: - // start V V end - // AAA CCC DDD EEEEEEE - // BBBBBBBBBBBB GGG - // FFFFFFF - // B is visible but A and C are not. In general there could be - // arbitrarily many slices between B and D which are not visible. - - // You could binary search to find D (i.e. the first slice which - // starts after |start|) then work backwards to find B. - // The last visible slice is simpler, since the slices are sorted - // by timestamp you can binary search for the last slice such - // that slice.start <= end. - - // One specific edge case that will come up often is when: - // For all slice in slices: slice.startS > endS (e.g. all slices are to the - // right). Since the slices are sorted by startS we can check this easily: - const maybeFirstSlice: S|undefined = slices[0]; - if (maybeFirstSlice && maybeFirstSlice.startS > endS) { - return []; - } - // It's not possible to easily check the analogous edge case where all slices - // are to the left: - // For all slice in slices: slice.startS + slice.durationS < startS - // as the slices are not ordered by 'endS'. - - // As described above you could do some clever binary search combined with - // iteration however that seems quite complicated and error prone so instead - // the idea of the code below is that we iterate forward though the - // array incrementing startIdx until we find the first visible slice - // then backwards through the array decrementing endIdx until we find the - // last visible slice. In the worst case we end up doing one full pass on - // the array. This code is robust to slices not being sorted. - let startIdx = 0; - let endIdx = slices.length; - for (; startIdx < endIdx; ++startIdx) { - const slice = slices[startIdx]; - const sliceEndS = slice.startS + slice.durationS; - if (sliceEndS >= startS && slice.startS <= endS) { - break; - } - } - for (; startIdx < endIdx; --endIdx) { - const slice = slices[endIdx - 1]; - const sliceEndS = slice.startS + slice.durationS; - if (sliceEndS >= startS && slice.startS <= endS) { - break; - } - } - return slices.slice(startIdx, endIdx); -} - -export const filterVisibleSlicesForTesting = filterVisibleSlices; - -// The minimal set of columns that any table/view must expose to render tracks. -// Note: this class assumes that, at the SQL level, slices are: -// - Not temporally overlapping (unless they are nested at inner depth). -// - Strictly stacked (i.e. a slice at depth N+1 cannot be larger than any -// slices at depth 0..N. -// If you need temporally overlapping slices, look at AsyncSliceTrack, which -// merges several tracks into one visual track. -export const BASE_SLICE_ROW = { - id: NUM, // The slice ID, for selection / lookups. - tsq: NUM, // Quantized |ts|. This class owns the quantization logic. - tsqEnd: NUM, // Quantized |ts+dur|. The end bucket. - ts: NUM, // Start time in nanoseconds. - dur: NUM, // Duration in nanoseconds. -1 = incomplete, 0 = instant. - depth: NUM, // Vertical depth. -}; - -export type BaseSliceRow = typeof BASE_SLICE_ROW; - -// These properties change @ 60FPS and shouldn't be touched by the subclass. -// since the Impl doesn't see every frame attempting to reason on them in a -// subclass will run in to issues. -interface SliceInternal { - x: number; - w: number; -} - -// We use this to avoid exposing subclasses to the properties that live on -// SliceInternal. Within BaseSliceTrack the underlying storage and private -// methods use CastInternal (i.e. whatever the subclass requests -// plus our implementation fields) but when we call 'virtual' methods that -// the subclass should implement we use just T['slice'] hiding x & w. -type CastInternal = S&SliceInternal; - -// The meta-type which describes the types used to extend the BaseSliceTrack. -// Derived classes can extend this interface to override these types if needed. -export interface BaseSliceTrackTypes { - slice: Slice; - row: BaseSliceRow; - config: {}; -} - -export abstract class BaseSliceTrack extends - Track { - protected sliceLayout: SliceLayout = {...DEFAULT_SLICE_LAYOUT}; - - // This is the over-skirted cached bounds: - private slicesKey: CacheKey = CacheKey.zero(); - - // This is the currently 'cached' slices: - private slices = new Array>(); - - // This is the slices cache: - private cache: TrackCache>> = - new TrackCache(5); - - private readonly tableName: string; - private maxDurNs = 0; - private sqlState: 'UNINITIALIZED'|'INITIALIZING'|'QUERY_PENDING'| - 'QUERY_DONE' = 'UNINITIALIZED'; - private extraSqlColumns: string[]; - - private charWidth = -1; - private hoverPos?: {x: number, y: number}; - protected hoveredSlice?: T['slice']; - private hoverTooltip: string[] = []; - private maxDataDepth = 0; - - // Computed layout. - private computedTrackHeight = 0; - private computedSliceHeight = 0; - private computedRowSpacing = 0; - - // True if this track (and any views tables it might have created) has been - // destroyed. This is unfortunately error prone (since we must manually check - // this between each query). - // TODO(hjd): Replace once we have cancellable query sequences. - private isDestroyed = false; - - // Extension points. - // Each extension point should take a dedicated argument type (e.g., - // OnSliceOverArgs {slice?: T['slice']}) so it makes future extensions - // non-API-breaking (e.g. if we want to add the X position). - abstract initSqlTable(_tableName: string): Promise; - getRowSpec(): T['row'] { - return BASE_SLICE_ROW; - } - onSliceOver(_args: OnSliceOverArgs): void {} - onSliceOut(_args: OnSliceOutArgs): void {} - onSliceClick(_args: OnSliceClickArgs): void {} - - // The API contract of onUpdatedSlices() is: - // - I am going to draw these slices in the near future. - // - I am not going to draw any slice that I haven't passed here first. - // - This is guaranteed to be called at least once on every global - // state update. - // - This is NOT guaranteed to be called on every frame. For instance you - // cannot use this to do some colour-based animation. - onUpdatedSlices(slices: Array): void { - this.highlightHovererdAndSameTitle(slices); - } - - // TODO(hjd): Remove. - drawSchedLatencyArrow( - _: CanvasRenderingContext2D, _selectedSlice?: T['slice']): void {} - - constructor(args: NewTrackArgs) { - super(args); - this.frontendOnly = true; // Disable auto checkerboarding. - // TODO(hjd): Handle pinned tracks, which current cause a crash - // since the tableName we generate is the same for both. - this.tableName = `track_${this.trackId}`.replace(/[^a-zA-Z0-9_]+/g, '_'); - - // Work out the extra columns. - // This is the union of the embedder-defined columns and the base columns - // we know about (ts, dur, ...). - const allCols = Object.keys(this.getRowSpec()); - const baseCols = Object.keys(BASE_SLICE_ROW); - this.extraSqlColumns = allCols.filter((key) => !baseCols.includes(key)); - } - - setSliceLayout(sliceLayout: SliceLayout) { - if (sliceLayout.minDepth > sliceLayout.maxDepth) { - const {maxDepth, minDepth} = sliceLayout; - throw new Error(`minDepth ${minDepth} must be <= maxDepth ${maxDepth}`); - } - this.sliceLayout = sliceLayout; - } - - onFullRedraw(): void { - // Give a chance to the embedder to change colors and other stuff. - this.onUpdatedSlices(this.slices); - } - - protected isSelectionHandled(selection: Selection): boolean { - // TODO(hjd): Remove when updating selection. - // We shouldn't know here about CHROME_SLICE. Maybe should be set by - // whatever deals with that. Dunno the namespace of selection is weird. For - // most cases in non-ambiguous (because most things are a 'slice'). But some - // others (e.g. THREAD_SLICE) have their own ID namespace so we need this. - const supportedSelectionKinds: SelectionKind[] = ['SLICE', 'CHROME_SLICE']; - return supportedSelectionKinds.includes(selection.kind); - } - - renderCanvas(ctx: CanvasRenderingContext2D): void { - // TODO(hjd): fonts and colors should come from the CSS and not hardcoded - // here. - const timeScale = globals.frontendLocalState.timeScale; - const vizTime = globals.frontendLocalState.visibleWindowTime; - - { - const windowSizePx = Math.max(1, timeScale.endPx - timeScale.startPx); - const rawStartNs = toNs(vizTime.start); - const rawEndNs = toNs(vizTime.end); - const rawSlicesKey = CacheKey.create(rawStartNs, rawEndNs, windowSizePx); - - // If the visible time range is outside the cached area, requests - // asynchronously new data from the SQL engine. - this.maybeRequestData(rawSlicesKey); - } - - // In any case, draw whatever we have (which might be stale/incomplete). - - let charWidth = this.charWidth; - if (charWidth < 0) { - // TODO(hjd): Centralize font measurement/invalidation. - ctx.font = '12px Roboto Condensed'; - charWidth = this.charWidth = ctx.measureText('dbpqaouk').width / 8; - } - - // Filter only the visible slices. |this.slices| will have more slices than - // needed because maybeRequestData() over-fetches to handle small pan/zooms. - // We don't want to waste time drawing slices that are off screen. - const vizSlices = this.getVisibleSlicesInternal(vizTime.start, vizTime.end); - - let selection = globals.state.currentSelection; - - if (!selection || !this.isSelectionHandled(selection)) { - selection = null; - } - - // Believe it or not, doing 4xO(N) passes is ~2x faster than trying to draw - // everything in one go. The key is that state changes operations on the - // canvas (e.g., color, fonts) dominate any number crunching we do in JS. - - this.updateSliceAndTrackHeight(); - const sliceHeight = this.computedSliceHeight; - const padding = this.sliceLayout.padding; - const rowSpacing = this.computedRowSpacing; - - // First pass: compute geometry of slices. - let selSlice: CastInternal|undefined; - - // pxEnd is the last visible pixel in the visible viewport. Drawing - // anything < 0 or > pxEnd doesn't produce any visible effect as it goes - // beyond the visible portion of the canvas. - const pxEnd = Math.floor(timeScale.timeToPx(vizTime.end)); - - for (const slice of vizSlices) { - // Compute the basic geometry for any visible slice, even if only - // partially visible. This might end up with a negative x if the - // slice starts before the visible time or with a width that overflows - // pxEnd. - slice.x = timeScale.timeToPx(slice.startS); - slice.w = timeScale.deltaTimeToPx(slice.durationS); - if (slice.flags & SLICE_FLAGS_INSTANT) { - // In the case of an instant slice, set the slice geometry on the - // bounding box that will contain the chevron. - slice.x -= CHEVRON_WIDTH_PX / 2; - slice.w = CHEVRON_WIDTH_PX; - } else { - // If the slice is an actual slice, intersect the slice geometry with - // the visible viewport (this affects only the first and last slice). - // This is so that text is always centered even if we are zoomed in. - // Visually if we have - // [ visible viewport ] - // [ slice ] - // The resulting geometry will be: - // [slice] - // So that the slice title stays within the visible region. - const sliceVizLimit = Math.min(slice.x + slice.w, pxEnd); - slice.x = Math.max(slice.x, 0); - slice.w = sliceVizLimit - slice.x; - } - - if (selection && (selection as {id: number}).id === slice.id) { - selSlice = slice; - } - } - - // Second pass: fill slices by color. - // The .slice() turned out to be an unintended pun. - const vizSlicesByColor = vizSlices.slice(); - vizSlicesByColor.sort((a, b) => colorCompare(a.color, b.color)); - let lastColor = undefined; - for (const slice of vizSlicesByColor) { - if (slice.color !== lastColor) { - lastColor = slice.color; - ctx.fillStyle = colorToStr(slice.color); - } - const y = padding + slice.depth * (sliceHeight + rowSpacing); - if (slice.flags & SLICE_FLAGS_INSTANT) { - this.drawChevron(ctx, slice.x, y, sliceHeight); - } else if (slice.flags & SLICE_FLAGS_INCOMPLETE) { - const w = Math.max(slice.w - 2, 2); - drawIncompleteSlice(ctx, slice.x, y, w, sliceHeight); - } else { - const w = Math.max(slice.w, SLICE_MIN_WIDTH_PX); - ctx.fillRect(slice.x, y, w, sliceHeight); - } - } - - // Third pass, draw the titles (e.g., process name for sched slices). - ctx.fillStyle = '#fff'; - ctx.textAlign = 'center'; - ctx.font = '12px Roboto Condensed'; - ctx.textBaseline = 'middle'; - for (const slice of vizSlices) { - if ((slice.flags & SLICE_FLAGS_INSTANT) || !slice.title || - slice.w < SLICE_MIN_WIDTH_FOR_TEXT_PX) { - continue; - } - - const title = cropText(slice.title, charWidth, slice.w); - const rectXCenter = slice.x + slice.w / 2; - const y = padding + slice.depth * (sliceHeight + rowSpacing); - const yDiv = slice.subTitle ? 3 : 2; - const yMidPoint = Math.floor(y + sliceHeight / yDiv) - 0.5; - ctx.fillText(title, rectXCenter, yMidPoint); - } - - // Fourth pass, draw the subtitles (e.g., thread name for sched slices). - ctx.fillStyle = 'rgba(255, 255, 255, 0.6)'; - ctx.font = '10px Roboto Condensed'; - for (const slice of vizSlices) { - if (slice.w < SLICE_MIN_WIDTH_FOR_TEXT_PX || !slice.subTitle || - (slice.flags & SLICE_FLAGS_INSTANT)) { - continue; - } - const rectXCenter = slice.x + slice.w / 2; - const subTitle = cropText(slice.subTitle, charWidth, slice.w); - const y = padding + slice.depth * (sliceHeight + rowSpacing); - const yMidPoint = Math.ceil(y + sliceHeight * 2 / 3) + 1.5; - ctx.fillText(subTitle, rectXCenter, yMidPoint); - } - - // Draw a thicker border around the selected slice (or chevron). - if (selSlice !== undefined) { - const color = selSlice.color; - const y = padding + selSlice.depth * (sliceHeight + rowSpacing); - ctx.strokeStyle = `hsl(${color.h}, ${color.s}%, 30%)`; - ctx.beginPath(); - const THICKNESS = 3; - ctx.lineWidth = THICKNESS; - ctx.strokeRect( - selSlice.x, y - THICKNESS / 2, selSlice.w, sliceHeight + THICKNESS); - ctx.closePath(); - } - - // If the cached trace slices don't fully cover the visible time range, - // show a gray rectangle with a "Loading..." label. - checkerboardExcept( - ctx, - this.getHeight(), - timeScale.timeToPx(vizTime.start), - timeScale.timeToPx(vizTime.end), - timeScale.timeToPx(fromNs(this.slicesKey.startNs)), - timeScale.timeToPx(fromNs(this.slicesKey.endNs))); - - // TODO(hjd): Remove this. - // The only thing this does is drawing the sched latency arrow. We should - // have some abstraction for that arrow (ideally the same we'd use for - // flows). - this.drawSchedLatencyArrow(ctx, selSlice); - - // If a slice is hovered, draw the tooltip. - const tooltip = this.hoverTooltip; - if (this.hoveredSlice !== undefined && tooltip.length > 0 && - this.hoverPos !== undefined) { - if (tooltip.length === 1) { - this.drawTrackHoverTooltip(ctx, this.hoverPos, tooltip[0]); - } else { - this.drawTrackHoverTooltip(ctx, this.hoverPos, tooltip[0], tooltip[1]); - } - } // if (hoveredSlice) - } - - onDestroy() { - super.onDestroy(); - this.isDestroyed = true; - this.engine.query(`DROP VIEW IF EXISTS ${this.tableName}`); - } - - // This method figures out if the visible window is outside the bounds of - // the cached data and if so issues new queries (i.e. sorta subsumes the - // onBoundsChange). - private async maybeRequestData(rawSlicesKey: CacheKey) { - // Important: this method is async and is invoked on every frame. Care - // must be taken to avoid piling up queries on every frame, hence the FSM. - if (this.sqlState === 'UNINITIALIZED') { - this.sqlState = 'INITIALIZING'; - - if (this.isDestroyed) { - return; - } - await this.initSqlTable(this.tableName); - - if (this.isDestroyed) { - return; - } - const queryRes = await this.engine.query(`select - ifnull(max(dur), 0) as maxDur, count(1) as rowCount - from ${this.tableName}`); - const row = queryRes.firstRow({maxDur: NUM, rowCount: NUM}); - this.maxDurNs = row.maxDur; - this.sqlState = 'QUERY_DONE'; - } else if ( - this.sqlState === 'INITIALIZING' || this.sqlState === 'QUERY_PENDING') { - return; - } - - if (rawSlicesKey.isCoveredBy(this.slicesKey)) { - return; // We have the data already, no need to re-query - } - - // Determine the cache key: - const slicesKey = rawSlicesKey.normalize(); - if (!rawSlicesKey.isCoveredBy(slicesKey)) { - throw new Error(`Normalization error ${slicesKey.toString()} ${ - rawSlicesKey.toString()}`); - } - - const maybeCachedSlices = this.cache.lookup(slicesKey); - if (maybeCachedSlices) { - this.slicesKey = slicesKey; - this.onUpdatedSlices(maybeCachedSlices); - this.slices = maybeCachedSlices; - return; - } - - this.sqlState = 'QUERY_PENDING'; - const bucketNs = slicesKey.bucketNs; - let queryTsq; - let queryTsqEnd; - // When we're zoomed into the level of single ns there is no point - // doing quantization (indeed it causes bad artifacts) so instead - // we use ts / ts+dur directly. - if (bucketNs === 1) { - queryTsq = 'ts'; - queryTsqEnd = 'ts + dur'; - } else { - queryTsq = `(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`; - queryTsqEnd = `(ts + dur + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`; - } - - const extraCols = this.extraSqlColumns.join(','); - let depthCol = 'depth'; - let maybeGroupByDepth = 'depth, '; - const layout = this.sliceLayout; - const isFlat = (layout.maxDepth - layout.minDepth) <= 1; - // maxDepth === minDepth only makes sense if track is empty which on the - // one hand isn't very useful (and so maybe should be an error) on the - // other hand I can see it happening if someone does: - // minDepth = min(slices.depth); maxDepth = max(slices.depth); - // and slices is empty, so we treat that as flat. - if (isFlat) { - depthCol = `${this.sliceLayout.minDepth} as depth`; - maybeGroupByDepth = ''; - } - - // TODO(hjd): Re-reason and improve this query: - // - Materialize the unfinished slices one off. - // - Avoid the union if we know we don't have any -1 slices. - // - Maybe we don't need the union at all and can deal in TS? - if (this.isDestroyed) { - this.sqlState = 'QUERY_DONE'; - return; - } - // TODO(hjd): Count and expose the number of slices summarized in - // each bucket? - const queryRes = await this.engine.query(` - with q1 as ( - select - ${queryTsq} as tsq, - ${queryTsqEnd} as tsqEnd, - ts, - max(dur) as dur, - id, - ${depthCol} - ${extraCols ? ',' + extraCols : ''} - from ${this.tableName} - where - ts >= ${slicesKey.startNs - this.maxDurNs /* - durNs */} and - ts <= ${slicesKey.endNs /* + durNs */} - group by ${maybeGroupByDepth} tsq - order by tsq), - q2 as ( - select - ${queryTsq} as tsq, - ${queryTsqEnd} as tsqEnd, - ts, - -1 as dur, - id, - ${depthCol} - ${extraCols ? ',' + extraCols : ''} - from ${this.tableName} - where dur = -1 - group by ${maybeGroupByDepth} tsq - ) - select min(dur) as _unused, * from - (select * from q1 union all select * from q2) - group by ${maybeGroupByDepth} tsq - order by tsq - `); - - // Here convert each row to a Slice. We do what we can do - // generically in the base class, and delegate the rest to the impl - // via that rowToSlice() abstract call. - const slices = new Array>(queryRes.numRows()); - const it = queryRes.iter(this.getRowSpec()); - - let maxDataDepth = this.maxDataDepth; - this.slicesKey = slicesKey; - for (let i = 0; it.valid(); it.next(), ++i) { - maxDataDepth = Math.max(maxDataDepth, it.depth); - // Construct the base slice. The Impl will construct and return - // the full derived T["slice"] (e.g. CpuSlice) in the - // rowToSlice() method. - slices[i] = this.rowToSliceInternal(it); - } - this.maxDataDepth = maxDataDepth; - this.onUpdatedSlices(slices); - this.cache.insert(slicesKey, slices); - this.slices = slices; - - this.sqlState = 'QUERY_DONE'; - globals.rafScheduler.scheduleRedraw(); - } - - private rowToSliceInternal(row: T['row']): CastInternal { - const slice = this.rowToSlice(row) as CastInternal; - slice.x = -1; - slice.w = -1; - return slice; - } - - rowToSlice(row: T['row']): T['slice'] { - const startNsQ = row.tsq; - const endNsQ = row.tsqEnd; - let flags = 0; - if (row.dur === -1) { - flags |= SLICE_FLAGS_INCOMPLETE; - } else if (row.dur === 0) { - flags |= SLICE_FLAGS_INSTANT; - } - - return { - id: row.id, - startS: fromNs(startNsQ), - durationS: fromNs(endNsQ - startNsQ), - flags, - depth: row.depth, - title: '', - subTitle: '', - - // The derived class doesn't need to initialize these. They are - // rewritten on every renderCanvas() call. We just need to initialize - // them to something. - baseColor: DEFAULT_SLICE_COLOR, - color: DEFAULT_SLICE_COLOR, - }; - } - - private findSlice({x, y}: {x: number, y: number}): undefined|Slice { - const trackHeight = this.computedTrackHeight; - const sliceHeight = this.computedSliceHeight; - const padding = this.sliceLayout.padding; - const rowSpacing = this.computedRowSpacing; - - // Need at least a draw pass to resolve the slice layout. - if (sliceHeight === 0) { - return undefined; - } - - if (y >= padding && y <= trackHeight - padding) { - const depth = Math.floor((y - padding) / (sliceHeight + rowSpacing)); - for (const slice of this.slices) { - if (slice.depth === depth && slice.x <= x && x <= slice.x + slice.w) { - return slice; - } - } - } - - return undefined; - } - - onMouseMove(position: {x: number, y: number}): void { - this.hoverPos = position; - this.updateHoveredSlice(this.findSlice(position)); - } - - onMouseOut(): void { - this.updateHoveredSlice(undefined); - } - - private updateHoveredSlice(slice?: T['slice']): void { - const lastHoveredSlice = this.hoveredSlice; - this.hoveredSlice = slice; - - // Only notify the Impl if the hovered slice changes: - if (slice === lastHoveredSlice) return; - - if (this.hoveredSlice === undefined) { - globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1})); - this.onSliceOut({slice: assertExists(lastHoveredSlice)}); - this.hoverTooltip = []; - this.hoverPos = undefined; - } else { - const args: OnSliceOverArgs = {slice: this.hoveredSlice}; - globals.dispatch( - Actions.setHighlightedSliceId({sliceId: this.hoveredSlice.id})); - this.onSliceOver(args); - this.hoverTooltip = args.tooltip || []; - } - } - - onMouseClick(position: {x: number, y: number}): boolean { - const slice = this.findSlice(position); - if (slice === undefined) { - return false; - } - const args: OnSliceClickArgs = {slice}; - this.onSliceClick(args); - return true; - } - - private getVisibleSlicesInternal(startS: number, endS: number): - Array> { - return filterVisibleSlices>( - this.slices, startS, endS); - } - - private updateSliceAndTrackHeight() { - const lay = this.sliceLayout; - - const rows = - Math.min(Math.max(this.maxDataDepth + 1, lay.minDepth), lay.maxDepth); - - // Compute the track height. - let trackHeight; - if (lay.heightMode === 'FIXED') { - trackHeight = lay.fixedHeight; - } else { - trackHeight = 2 * lay.padding + rows * (lay.sliceHeight + lay.rowSpacing); - } - - // Compute the slice height. - let sliceHeight: number; - let rowSpacing: number = lay.rowSpacing; - if (lay.heightMode === 'FIXED') { - const rowHeight = (trackHeight - 2 * lay.padding) / rows; - sliceHeight = Math.floor(Math.max(rowHeight - lay.rowSpacing, 0.5)); - rowSpacing = Math.max(lay.rowSpacing, rowHeight - sliceHeight); - rowSpacing = Math.floor(rowSpacing * 2) / 2; - } else { - sliceHeight = lay.sliceHeight; - } - this.computedSliceHeight = sliceHeight; - this.computedTrackHeight = trackHeight; - this.computedRowSpacing = rowSpacing; - } - - private drawChevron( - ctx: CanvasRenderingContext2D, x: number, y: number, h: number) { - // Draw an upward facing chevrons, in order: A, B, C, D, and back to A. - // . (x, y) - // A - // ### - // ##C## - // ## ## - // D B - // . (x + CHEVRON_WIDTH_PX, y + h) - const HALF_CHEVRON_WIDTH_PX = CHEVRON_WIDTH_PX / 2; - const midX = x + HALF_CHEVRON_WIDTH_PX; - ctx.beginPath(); - ctx.moveTo(midX, y); // A. - ctx.lineTo(x + CHEVRON_WIDTH_PX, y + h); // B. - ctx.lineTo(midX, y + h - HALF_CHEVRON_WIDTH_PX); // C. - ctx.lineTo(x, y + h); // D. - ctx.lineTo(midX, y); // Back to A. - ctx.closePath(); - ctx.fill(); - } - - // This is a good default implementation for highlighting slices. By default - // onUpdatedSlices() calls this. However, if the XxxSliceTrack impl overrides - // onUpdatedSlices() this gives them a chance to call the highlighting without - // having to reimplement it. - protected highlightHovererdAndSameTitle(slices: Slice[]) { - for (const slice of slices) { - const isHovering = globals.state.highlightedSliceId === slice.id || - (this.hoveredSlice && this.hoveredSlice.title === slice.title); - if (isHovering) { - slice.color = { - c: slice.baseColor.c, - h: slice.baseColor.h, - s: slice.baseColor.s, - l: 30, - }; - } else { - slice.color = slice.baseColor; - } - } - } - - getHeight(): number { - this.updateSliceAndTrackHeight(); - return this.computedTrackHeight; - } - - getSliceRect(_tStart: number, _tEnd: number, _depth: number): SliceRect - |undefined { - // TODO(hjd): Implement this as part of updating flow events. - return undefined; - } -} - -// This is the argument passed to onSliceOver(args). -// This is really a workaround for the fact that TypeScript doesn't allow -// inner types within a class (whether the class is templated or not). -export interface OnSliceOverArgs { - // Input args (BaseSliceTrack -> Impl): - slice: S; // The slice being hovered. - - // Output args (Impl -> BaseSliceTrack): - tooltip?: string[]; // One entry per row, up to a max of 2. -} - -export interface OnSliceOutArgs { - // Input args (BaseSliceTrack -> Impl): - slice: S; // The slice which is not hovered anymore. -} - -export interface OnSliceClickArgs { - // Input args (BaseSliceTrack -> Impl): - slice: S; // The slice which is clicked. -} diff --git a/third_party/perfetto/ui/src/frontend/base_slice_track_unittest.ts b/third_party/perfetto/ui/src/frontend/base_slice_track_unittest.ts deleted file mode 100644 index 7dd109dee00e..000000000000 --- a/third_party/perfetto/ui/src/frontend/base_slice_track_unittest.ts +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {GRAY_COLOR} from '../common/colorizer'; - -import { - filterVisibleSlicesForTesting as filterVisibleSlices, -} from './base_slice_track'; -import {Slice} from './slice'; - -function slice(startS: number, durationS: number): Slice { - return { - id: 42, - startS, - durationS, - depth: 0, - flags: 0, - title: '', - subTitle: '', - baseColor: GRAY_COLOR, - color: GRAY_COLOR, - }; -} - -const s = slice; - -test('filterVisibleSlices', () => { - expect(filterVisibleSlices([], 0, 100)).toEqual([]); - expect(filterVisibleSlices([s(10, 80)], 0, 100)).toEqual([s(10, 80)]); - expect(filterVisibleSlices([s(0, 20)], 10, 100)).toEqual([s(0, 20)]); - expect(filterVisibleSlices([s(0, 10)], 10, 100)).toEqual([s(0, 10)]); - expect(filterVisibleSlices([s(100, 10)], 10, 100)).toEqual([s(100, 10)]); - expect(filterVisibleSlices([s(10, 0)], 10, 100)).toEqual([s(10, 0)]); - expect(filterVisibleSlices([s(100, 0)], 10, 100)).toEqual([s(100, 0)]); - expect(filterVisibleSlices([s(0, 5)], 10, 90)).toEqual([]); - expect(filterVisibleSlices([s(95, 5)], 10, 90)).toEqual([]); - expect(filterVisibleSlices([s(0, 5), s(95, 5)], 10, 90)).toEqual([]); - expect(filterVisibleSlices( - [ - s(0, 5), - s(50, 0), - s(95, 5), - ], - 10, - 90)) - .toEqual([ - s(50, 0), - ]); - expect(filterVisibleSlices( - [ - s(0, 5), - s(1, 9), - s(6, 3), - ], - 10, - 90)) - .toContainEqual(s(1, 9)); - expect(filterVisibleSlices( - [ - s(0, 5), - s(1, 9), - s(6, 3), - s(50, 0), - ], - 10, - 90)) - .toContainEqual(s(1, 9)); - expect(filterVisibleSlices( - [ - s(85, 10), - s(100, 10), - ], - 10, - 90)) - .toEqual([ - s(85, 10), - ]); - expect(filterVisibleSlices( - [ - s(0, 100), - - ], - 10, - 90)) - .toEqual([ - s(0, 100), - ]); - expect(filterVisibleSlices( - [ - s(0, 1), - s(1, 1), - s(2, 1), - s(3, 1), - s(4, 1), - s(5, 10), - s(6, 1), - s(7, 1), - s(8, 1), - s(9, 1), - ], - 10, - 90)) - .toContainEqual(s(5, 10)); -}); diff --git a/third_party/perfetto/ui/src/frontend/bottom_tab.ts b/third_party/perfetto/ui/src/frontend/bottom_tab.ts deleted file mode 100644 index f78bd0c2c96d..000000000000 --- a/third_party/perfetto/ui/src/frontend/bottom_tab.ts +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; -import {v4 as uuidv4} from 'uuid'; - -import {Actions} from '../common/actions'; -import {EngineProxy} from '../common/engine'; -import {Registry} from '../common/registry'; -import {globals} from './globals'; - -import {Panel, PanelSize, PanelVNode} from './panel'; - -export interface NewBottomTabArgs { - engine: EngineProxy; - tag?: string; - uuid: string; - config: {}; -} - -// Interface for allowing registration and creation of bottom tabs. -// See comments on |TrackCreator| for more details. -export interface BottomTabCreator { - readonly kind: string; - - create(args: NewBottomTabArgs): BottomTab; -} - -export const bottomTabRegistry = Registry.kindRegistry(); - -// Period to wait for the newly-added tabs which are loading before showing -// them to the user. This period is short enough to not be user-visible, -// while being long enough for most of the simple queries to complete, reducing -// flickering in the UI. -const NEW_LOADING_TAB_DELAY_MS = 50; - -// An interface representing a bottom tab displayed on the panel in the bottom -// of the ui (e.g. "Current Selection"). -// -// The implementations of this class are provided by different plugins, which -// register the implementations with bottomTabRegistry, keyed by a unique name -// for each type of BottomTab. -// -// Lifetime: the instances of this class are owned by BottomTabPanel and exist -// for as long as a tab header is shown to the user in the bottom tab list (with -// minor exceptions, like a small grace period between when the tab is related). -// -// BottomTab implementations should pass the unique identifier(s) for the -// content displayed via the |Config| and fetch additional details via Engine -// instead of relying on getting the data from the global storage. For example, -// for tabs corresponding to details of the selected objects on a track, a new -// BottomTab should be created for each new selection. -export abstract class BottomTabBase { - // Config for this details panel. Should be serializable. - protected readonly config: Config; - // Engine for running queries and fetching additional data. - protected readonly engine: EngineProxy; - // Optional tag, which is used to ensure that only one tab - // with the same tag can exist - adding a new tab with the same tag - // (e.g. 'current_selection') would close the previous one. This - // also can be used to close existing tab. - readonly tag?: string; - // Unique id for this details panel. Can be used to close previously opened - // panel. - readonly uuid: string; - - constructor(args: NewBottomTabArgs) { - this.config = args.config as Config; - this.engine = args.engine; - this.tag = args.tag; - this.uuid = args.uuid; - } - - // Entry point for customisation of the displayed title for this panel. - abstract getTitle(): string; - - // Generate a mithril node for this component. - abstract createPanelVnode(): PanelVNode; - - // API for the tab to notify the TabList that it's still preparing the data. - // If true, adding a new tab will be delayed for a short while (~50ms) to - // reduce the flickering. - // - // Note: it's a "poll" rather than "push" API: there is no explicit API - // for the tabs to notify the tab list, as the tabs are expected to schedule - // global redraw anyway and the tab list will poll the tabs as necessary - // during the redraw. - isLoading(): boolean { - return false; - } -} - - -// BottomTabBase provides a more generic API allowing users to provide their -// custom mithril component, which would allow them to listen to mithril -// lifecycle events. Most cases, however, don't need them and BottomTab -// provides a simplified API for the common case. -export abstract class BottomTab extends BottomTabBase { - constructor(args: NewBottomTabArgs) { - super(args); - } - - // These methods are direct counterparts to renderCanvas and view with - // slightly changes names to prevent cases when `BottomTab` will - // be accidentally used a mithril component. - abstract renderTabCanvas(ctx: CanvasRenderingContext2D, size: PanelSize): - void; - abstract viewTab(): void|m.Children; - - createPanelVnode(): m.Vnode { - return m( - BottomTabAdapter, - {key: this.uuid, panel: this} as BottomTabAdapterAttrs); - } -} - -interface BottomTabAdapterAttrs { - panel: BottomTab; -} - -class BottomTabAdapter extends Panel { - renderCanvas( - ctx: CanvasRenderingContext2D, size: PanelSize, - vnode: PanelVNode): void { - vnode.attrs.panel.renderTabCanvas(ctx, size); - } - - view(vnode: m.CVnode): void|m.Children { - return vnode.attrs.panel.viewTab(); - } -} - -export type AddTabArgs = { - kind: string, - config: {}, - tag?: string, - // Whether to make the new tab current. True by default. - select?: boolean; -}; - -export type AddTabResult = - { - uuid: string; - } - -// Shorthand for globals.bottomTabList.addTab(...) & redraw. -// Ignored when bottomTabList does not exist (e.g. no trace is open in the UI). -export function -addTab(args: AddTabArgs) { - const tabList = globals.bottomTabList; - if (!tabList) { - return; - } - tabList.addTab(args); - globals.rafScheduler.scheduleFullRedraw(); -} - - -// Shorthand for globals.bottomTabList.closeTabById(...) & redraw. -// Ignored when bottomTabList does not exist (e.g. no trace is open in the UI). -export function -closeTab(uuid: string) { - const tabList = globals.bottomTabList; - if (!tabList) { - return; - } - tabList.closeTabById(uuid); - globals.rafScheduler.scheduleFullRedraw(); -} - -interface PendingTab { - tab: BottomTabBase, args: AddTabArgs, startTime: number, -} - -function tabSelectionKey(tab: BottomTabBase) { - return tab.tag ?? tab.uuid; -} - -export class BottomTabList { - private tabs: BottomTabBase[] = []; - private pendingTabs: PendingTab[] = []; - private engine: EngineProxy; - private scheduledFlushSetTimeoutId?: number; - - constructor(engine: EngineProxy) { - this.engine = engine; - } - - getTabs(): BottomTabBase[] { - this.flushPendingTabs(); - return this.tabs; - } - - // Add and create a new panel with given kind and config, replacing an - // existing panel with the same tag if needed. Returns the uuid of a newly - // created panel (which can be used in the future to close it). - addTab(args: AddTabArgs): AddTabResult { - const uuid = uuidv4(); - const newPanel = bottomTabRegistry.get(args.kind).create({ - engine: this.engine, - uuid, - config: args.config, - tag: args.tag, - }); - - this.pendingTabs.push({ - tab: newPanel, - args, - startTime: window.performance.now(), - }); - this.flushPendingTabs(); - - return { - uuid, - }; - } - - closeTabByTag(tag: string) { - const index = this.tabs.findIndex((tab) => tab.tag === tag); - if (index !== -1) { - this.removeTabAtIndex(index); - } - // User closing a tab by tag should affect pending tabs as well, as these - // tabs were requested to be added to the tab list before this call. - this.pendingTabs = this.pendingTabs.filter(({tab}) => tab.tag !== tag); - } - - closeTabById(uuid: string) { - const index = this.tabs.findIndex((tab) => tab.uuid === uuid); - if (index !== -1) { - this.removeTabAtIndex(index); - } - // User closing a tab by id should affect pending tabs as well, as these - // tabs were requested to be added to the tab list before this call. - this.pendingTabs = this.pendingTabs.filter(({tab}) => tab.uuid !== uuid); - } - - private removeTabAtIndex(index: number) { - const tab = this.tabs[index]; - this.tabs.splice(index, 1); - // If the current tab was closed, select the tab to the right of it. - // If the closed tab was current and last in the tab list, select the tab - // that became last. - if (tab.uuid === globals.state.currentTab && this.tabs.length > 0) { - const newActiveIndex = index === this.tabs.length ? index - 1 : index; - globals.dispatch(Actions.setCurrentTab( - {tab: tabSelectionKey(this.tabs[newActiveIndex])})); - } - globals.rafScheduler.scheduleFullRedraw(); - } - - // Check the list of the pending tabs and add the ones that are ready - // (either tab.isLoading returns false or NEW_LOADING_TAB_DELAY_MS ms elapsed - // since this tab was added). - // Note: the pending tabs are stored in a queue to preserve the action order, - // which matters for cases like adding tabs with the same tag. - private flushPendingTabs() { - const currentTime = window.performance.now(); - while (this.pendingTabs.length > 0) { - const {tab, args, startTime} = this.pendingTabs[0]; - - // This is a dirty hack^W^W low-lift solution for the world where some - // "current selection" panels are implemented by BottomTabs and some by - // details_panel.ts computing vnodes dynamically. Naive implementation - // will: a) stop showing the old panel (because - // globals.state.currentSelection changes). b) not showing the new - // 'current_selection' tab yet. This will result in temporary shifting - // focus to another tab (as no tab with 'current_selection' tag will - // exist). - // - // To counteract this, short-circuit this logic and when: - // a) no tag with 'current_selection' tag exists in the list of currently - // displayed tabs and b) we are adding a tab with 'current_selection' tag. - // add it immediately without waiting. - // TODO(altimin): Remove this once all places have switched to be using - // BottomTab to display panels. - const currentSelectionTabAlreadyExists = - this.tabs.filter((tab) => tab.tag === 'current_selection').length > 0; - const dirtyHackForCurrentSelectionApplies = - tab.tag === 'current_selection' && !currentSelectionTabAlreadyExists; - - const elapsedTimeMs = currentTime - startTime; - if (tab.isLoading() && elapsedTimeMs < NEW_LOADING_TAB_DELAY_MS && - !dirtyHackForCurrentSelectionApplies) { - this.schedulePendingTabsFlush(NEW_LOADING_TAB_DELAY_MS - elapsedTimeMs); - // The first tab is not ready yet, wait. - return; - } - this.pendingTabs.shift(); - - const index = - args.tag ? this.tabs.findIndex((tab) => tab.tag === args.tag) : -1; - if (index === -1) { - this.tabs.push(tab); - } else { - this.tabs[index] = tab; - } - - if (args.select === undefined || args.select === true) { - globals.dispatch(Actions.setCurrentTab({tab: tabSelectionKey(tab)})); - } - } - } - - private schedulePendingTabsFlush(waitTimeMs: number) { - if (this.scheduledFlushSetTimeoutId) { - // The flush is already pending, no action is required. - return; - } - setTimeout(() => { - this.scheduledFlushSetTimeoutId = undefined; - this.flushPendingTabs(); - }, waitTimeMs); - } -} diff --git a/third_party/perfetto/ui/src/frontend/checkerboard.ts b/third_party/perfetto/ui/src/frontend/checkerboard.ts deleted file mode 100644 index 55b4eb760864..000000000000 --- a/third_party/perfetto/ui/src/frontend/checkerboard.ts +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -const LOADING_TEXT = 'Loading...'; -let LOADING_TEXT_WIDTH = 0; - -// Checker board the range [leftPx, rightPx]. -export function checkerboard( - ctx: CanvasRenderingContext2D, - heightPx: number, - leftPx: number, - rightPx: number): void { - const widthPx = rightPx - leftPx; - ctx.font = '12px Roboto Condensed'; - ctx.fillStyle = '#eee'; - ctx.fillRect(leftPx, 0, widthPx, heightPx); - ctx.fillStyle = '#666'; - const oldBaseline = ctx.textBaseline; - ctx.textBaseline = 'middle'; - if (LOADING_TEXT_WIDTH === 0) { - LOADING_TEXT_WIDTH = ctx.measureText(LOADING_TEXT).width; - } - if (LOADING_TEXT_WIDTH <= widthPx) { - ctx.fillText( - LOADING_TEXT, - leftPx + widthPx / 2 - LOADING_TEXT_WIDTH / 2, - heightPx / 2); - } - ctx.textBaseline = oldBaseline; -} - -// Checker board everything between [startPx, endPx] except [leftPx, rightPx]. -export function checkerboardExcept( - ctx: CanvasRenderingContext2D, - heightPx: number, - startPx: number, - endPx: number, - leftPx: number, - rightPx: number): void { - // [leftPx, rightPx] doesn't overlap [startPx, endPx] at all: - if (rightPx <= startPx || leftPx >= endPx) { - checkerboard(ctx, heightPx, startPx, endPx); - return; - } - - // Checkerboard [startPx, leftPx]: - if (leftPx > startPx) { - checkerboard(ctx, heightPx, startPx, leftPx); - } - - // Checkerboard [rightPx, endPx]: - if (rightPx < endPx) { - checkerboard(ctx, heightPx, rightPx, endPx); - } -} diff --git a/third_party/perfetto/ui/src/frontend/chrome_slice_panel.ts b/third_party/perfetto/ui/src/frontend/chrome_slice_panel.ts deleted file mode 100644 index 6c982cfe93d1..000000000000 --- a/third_party/perfetto/ui/src/frontend/chrome_slice_panel.ts +++ /dev/null @@ -1,525 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {sqliteString} from '../base/string_utils'; -import {Actions} from '../common/actions'; -import {Arg, ArgsTree, isArgTreeArray, isArgTreeMap} from '../common/arg_types'; -import {timeToCode} from '../common/time'; - -import {FlowPoint, globals, SliceDetails} from './globals'; -import {PanelSize} from './panel'; -import {PopupMenuButton, PopupMenuItem} from './popup_menu'; -import {runQueryInNewTab} from './query_result_tab'; -import {verticalScrollToTrack} from './scroll_helper'; -import {SlicePanel} from './slice_panel'; - -interface ContextMenuItem { - name: string; - shouldDisplay(slice: SliceDetails): boolean; - getAction(slice: SliceDetails): void; -} - -const ITEMS: ContextMenuItem[] = [ - { - name: 'Average duration', - shouldDisplay: (slice: SliceDetails) => slice.name !== undefined, - getAction: (slice: SliceDetails) => runQueryInNewTab( - `SELECT AVG(dur) / 1e9 FROM slice WHERE name = '${slice.name!}'`, - `${slice.name} average dur`, - ), - }, - { - name: 'Binder by TXN', - shouldDisplay: () => true, - getAction: () => runQueryInNewTab( - `SELECT IMPORT('android.binder'); - - SELECT * - FROM android_sync_binder_metrics_by_txn - ORDER BY client_dur DESC`, - 'Binder by TXN', - ), - }, - { - name: 'Lock graph', - shouldDisplay: (slice: SliceDetails) => slice.id !== undefined, - getAction: (slice: SliceDetails) => runQueryInNewTab( - `SELECT IMPORT('android.monitor_contention'); - DROP TABLE IF EXISTS FAST; - CREATE TABLE FAST - AS - WITH slice_process AS ( - SELECT process.name, process.upid FROM slice - JOIN thread_track ON thread_track.id = slice.track_id - JOIN thread USING(utid) - JOIN process USING(upid) - WHERE slice.id = ${slice.id!} - ) - SELECT *, - IIF(blocked_thread_name LIKE 'binder:%', 'binder', blocked_thread_name) - AS blocked_thread_name_norm, - IIF(blocking_thread_name LIKE 'binder:%', 'binder', blocking_thread_name) - AS blocking_thread_name_norm - FROM android_monitor_contention_chain, slice_process - WHERE android_monitor_contention_chain.upid = slice_process.upid; - - WITH - R AS ( - SELECT - id, - dur, - CAT_STACKS(blocked_thread_name_norm || ':' || short_blocked_method, - blocking_thread_name_norm || ':' || short_blocking_method) AS stack - FROM FAST - WHERE parent_id IS NULL - UNION ALL - SELECT - c.id, - c.dur AS dur, - CAT_STACKS(stack, blocking_thread_name_norm || ':' || short_blocking_method) AS stack - FROM FAST c, R AS p - WHERE p.id = c.parent_id - ) - SELECT TITLE.process_name, EXPERIMENTAL_PROFILE(stack, 'duration', 'ns', dur) AS pprof - FROM R, (SELECT process_name FROM FAST LIMIT 1) TITLE;`, - 'Lock graph', - ), - }, -]; - -function getSliceContextMenuItems(slice: SliceDetails): PopupMenuItem[] { - return ITEMS.filter((item) => item.shouldDisplay(slice)).map((item) => { - return { - itemType: 'regular', - text: item.name, - callback: () => item.getAction(slice), - }; - }); -} - -// Table row contents is one of two things: -// 1. Key-value pair -interface TableRow { - kind: 'TableRow'; - key: string; - value: Arg; - - // Whether it's an argument (from the `args` table) or whether it's a property - // of the slice (i.e. `dur`, coming from `slice` table). Args have additional - // actions associated with them. - isArg: boolean; - - // A full key for the arguments displayed in a tree. - full_key?: string; -} - -// 2. Common prefix for values in an array -interface TableHeader { - kind: 'TableHeader'; - header: string; -} - -type RowContents = TableRow|TableHeader; - -function isTableHeader(contents: RowContents): contents is TableHeader { - return contents.kind === 'TableHeader'; -} - -function appendPrefix(p1: string, p2: string): string { - if (p1.length === 0) { - return p2; - } - return `${p1}.${p2}`; -} - -interface Row { - // How many columns (empty or with an index) precede a key - indentLevel: number; - // Optional tooltip to be displayed on the key. Used to display the full key, - // which has to be reconstructed from the information that might not even be - // visible on the screen otherwise. - tooltip?: string; - contents: RowContents; -} - -class TableBuilder { - // Row data generated by builder - rows: Row[] = []; - indentLevel = 0; - - // Maximum indent level of a key, used to determine total number of columns - maxIndent = 0; - - // Add a key-value pair into the table - add(key: string, value: Arg) { - this.rows.push({ - indentLevel: 0, - contents: {kind: 'TableRow', key, value, isArg: false}, - }); - } - - // Add arguments tree into the table - addTree(tree: ArgsTree) { - this.addTreeInternal(tree, '', ''); - } - - private addTreeInternal( - record: ArgsTree, prefix: string, completePrefix: string) { - if (isArgTreeArray(record)) { - if (record.length === 1) { - this.addTreeInternal(record[0], `${prefix}[0]`, `${completePrefix}[0]`); - return; - } - - // Add the current prefix as a separate row - if (prefix.length > 0) { - this.rows.push({ - indentLevel: this.indentLevel, - contents: {kind: 'TableHeader', header: prefix}, - tooltip: completePrefix, - }); - } - - this.indentLevel++; - for (let i = 0; i < record.length; i++) { - // Prefix is empty for array elements because we don't want to repeat - // the common prefix - this.addTreeInternal(record[i], `[${i}]`, `${completePrefix}[${i}]`); - } - this.indentLevel--; - } else if (isArgTreeMap(record)) { - const entries = Object.entries(record); - if (entries.length === 1) { - // Don't want to create a level of indirection in case object contains - // only one value; think of it like file browser in IDEs not showing - // intermediate nodes for common hierarchy corresponding to Java package - // prefix (e.g. "com/google/perfetto"). - // - // In this case, add key as a prefix part. - const [key, value] = entries[0]; - this.addTreeInternal( - value, - appendPrefix(prefix, key), - appendPrefix(completePrefix, key)); - } else { - if (prefix.length > 0) { - const row = this.indentLevel; - this.rows.push({ - indentLevel: row, - contents: {kind: 'TableHeader', header: prefix}, - tooltip: completePrefix, - }); - this.indentLevel++; - } - for (const [key, value] of entries) { - this.addTreeInternal(value, key, appendPrefix(completePrefix, key)); - } - if (prefix.length > 0) { - this.indentLevel--; - } - } - } else { - // Leaf value in the tree: add to the table - const row = this.indentLevel; - this.rows.push({ - indentLevel: row, - contents: { - kind: 'TableRow', - key: prefix, - value: record, - full_key: completePrefix, - isArg: true, - }, - tooltip: completePrefix, - }); - } - } -} - -export class ChromeSliceDetailsPanel extends SlicePanel { - view() { - const sliceInfo = globals.sliceDetails; - if (sliceInfo.ts !== undefined && sliceInfo.dur !== undefined && - sliceInfo.name !== undefined) { - const defaultBuilder = new TableBuilder(); - defaultBuilder.add('Name', sliceInfo.name); - defaultBuilder.add( - 'Category', - !sliceInfo.category || sliceInfo.category === '[NULL]' ? - 'N/A' : - sliceInfo.category); - defaultBuilder.add('Start time', timeToCode(sliceInfo.ts)); - if (sliceInfo.absTime !== undefined) { - defaultBuilder.add('Absolute Time', sliceInfo.absTime); - } - defaultBuilder.add( - 'Duration', this.computeDuration(sliceInfo.ts, sliceInfo.dur)); - if (sliceInfo.threadTs !== undefined && - sliceInfo.threadDur !== undefined) { - // If we have valid thread duration, also display a percentage of - // |threadDur| compared to |dur|. - const threadDurFractionSuffix = sliceInfo.threadDur === -1 ? - '' : - ` (${(sliceInfo.threadDur / sliceInfo.dur * 100).toFixed(2)}%)`; - defaultBuilder.add( - 'Thread duration', - this.computeDuration(sliceInfo.threadTs, sliceInfo.threadDur) + - threadDurFractionSuffix); - } - - for (const [key, value] of this.getProcessThreadDetails(sliceInfo)) { - if (value !== undefined) { - defaultBuilder.add(key, value); - } - } - - defaultBuilder.add( - 'Slice ID', - (sliceInfo.id !== undefined) ? sliceInfo.id.toString() : 'Unknown'); - if (sliceInfo.description) { - for (const [key, value] of sliceInfo.description) { - defaultBuilder.add(key, value); - } - } - return m( - '.details-panel', - m('.details-panel-heading', m('h2', `Slice Details`)), - m('.details-table-multicolumn', [ - this.renderTable(defaultBuilder, '.half-width-panel'), - this.renderRhs(sliceInfo), - ])); - } else { - return m( - '.details-panel', - m('.details-panel-heading', - m( - 'h2', - `Slice Details`, - ))); - } - } - - private fillFlowPanel( - name: string, flows: {flow: FlowPoint, dur: number}[], - includeProcessName: boolean, result: Map) { - if (flows.length === 0) return; - - const builder = new TableBuilder(); - for (const {flow, dur} of flows) { - builder.add('Slice', { - kind: 'SLICE', - sliceId: flow.sliceId, - trackId: globals.state.uiTrackIdByTraceTrackId[flow.trackId], - description: flow.sliceChromeCustomName === undefined ? - flow.sliceName : - flow.sliceChromeCustomName, - }); - builder.add('Delay', timeToCode(dur)); - builder.add( - 'Thread', - includeProcessName ? `${flow.threadName} (${flow.processName})` : - flow.threadName); - } - result.set(name, builder); - } - - renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {} - - fillArgs(slice: SliceDetails, builder: TableBuilder) { - if (slice.argsTree && slice.args) { - // Parsed arguments are available, need only to iterate over them to get - // slice references - for (const [key, value] of slice.args) { - if (typeof value !== 'string') { - builder.add(key, value); - } - } - builder.addTree(slice.argsTree); - } else if (slice.args) { - // Parsing has failed, but arguments are available: display them in a flat - // 2-column table - for (const [key, value] of slice.args) { - builder.add(key, value); - } - } - } - - private getArgumentContextMenuItems(argument: TableRow): PopupMenuItem[] { - if (argument.full_key === undefined) return []; - if (typeof argument.value !== 'string') return []; - const argValue: string = argument.value; - - const fullKey = argument.full_key; - return [ - { - itemType: 'regular', - text: 'Copy full key', - callback: () => { - navigator.clipboard.writeText(fullKey); - }, - }, - { - itemType: 'regular', - text: 'Find slices with the same arg value', - callback: () => { - runQueryInNewTab( - ` - select slice.* - from slice - join args using (arg_set_id) - where key=${sqliteString(fullKey)} and display_value=${ - sqliteString(argValue)} - `, - `Arg: ${sqliteString(fullKey)}=${sqliteString(argValue)}`); - }, - }, - { - itemType: 'regular', - text: 'Visualise argument values', - callback: () => { - globals.dispatch(Actions.addVisualisedArg({argName: fullKey})); - }, - }, - ]; - } - - renderRhs(sliceInfo: SliceDetails): m.Vnode { - const builders = new Map(); - - const immediatelyPrecedingByFlowSlices = []; - const immediatelyFollowingByFlowSlices = []; - for (const flow of globals.connectedFlows) { - if (flow.begin.sliceId === sliceInfo.id) { - immediatelyFollowingByFlowSlices.push({flow: flow.end, dur: flow.dur}); - } - if (flow.end.sliceId === sliceInfo.id) { - immediatelyPrecedingByFlowSlices.push( - {flow: flow.begin, dur: flow.dur}); - } - } - - // This is Chrome-specific bits: - const isRunTask = sliceInfo.name === 'ThreadControllerImpl::RunTask' || - sliceInfo.name === 'ThreadPool_RunTask'; - const isPostTask = sliceInfo.name === 'ThreadPool_PostTask' || - sliceInfo.name === 'SequenceManager PostTask'; - - // RunTask and PostTask are always same-process, so we can skip - // emitting process name for them. - this.fillFlowPanel( - 'Preceding flows', - immediatelyPrecedingByFlowSlices, - !isRunTask, - builders); - this.fillFlowPanel( - 'Following flows', - immediatelyFollowingByFlowSlices, - !isPostTask, - builders); - - const argsBuilder = new TableBuilder(); - this.fillArgs(sliceInfo, argsBuilder); - builders.set('Arguments', argsBuilder); - - const rows: m.Vnode[] = []; - for (const [name, builder] of builders) { - rows.push(m('h3', name)); - rows.push(this.renderTable(builder)); - } - - const contextMenuItems = getSliceContextMenuItems(sliceInfo); - if (contextMenuItems.length > 0) { - rows.push( - m(PopupMenuButton, - { - icon: 'arrow_drop_down', - items: contextMenuItems, - }, - 'Contextual Options')); - } - - return m('.half-width-panel', rows); - } - - renderTable(builder: TableBuilder, additionalClasses: string = ''): m.Vnode { - const rows: m.Vnode[] = []; - for (const row of builder.rows) { - const renderedRow: m.Vnode[] = []; - const paddingLeft = `${row.indentLevel * 20}px`; - if (isTableHeader(row.contents)) { - renderedRow.push( - m('th', - { - colspan: 2, - title: row.tooltip, - style: {'padding-left': paddingLeft}, - }, - row.contents.header)); - } else { - const contents: any[] = [row.contents.key]; - if (row.contents.isArg) { - contents.push( - m('span.context-wrapper', m.trust(' '), m(PopupMenuButton, { - icon: 'arrow_drop_down', - items: this.getArgumentContextMenuItems(row.contents), - }))); - } - - renderedRow.push( - m('th', - {title: row.tooltip, style: {'padding-left': paddingLeft}}, - contents)); - const value = row.contents.value; - if (typeof value === 'string') { - renderedRow.push(m('td.value', this.mayLinkify(value))); - } else { - // Type of value being a record is not propagated into the callback - // for some reason, extracting necessary parts as constants instead. - const sliceId = value.sliceId; - const trackId = value.trackId; - renderedRow.push( - m('td', - m('i.material-icons.grey', - { - onclick: () => { - globals.makeSelection(Actions.selectChromeSlice( - {id: sliceId, trackId, table: 'slice'})); - // Ideally we want to have a callback to - // findCurrentSelection after this selection has been - // made. Here we do not have the info for horizontally - // scrolling to ts. - verticalScrollToTrack(trackId, true); - }, - title: 'Go to destination slice', - }, - 'call_made'), - value.description)); - } - } - - rows.push(m('tr', renderedRow)); - } - - return m(`table.auto-layout${additionalClasses}`, rows); - } - - private mayLinkify(what: string): string|m.Vnode { - if (what.startsWith('http://') || what.startsWith('https://')) { - return m('a', {href: what, target: '_blank'}, what); - } - return what; - } -} diff --git a/third_party/perfetto/ui/src/frontend/classnames.ts b/third_party/perfetto/ui/src/frontend/classnames.ts deleted file mode 100644 index b2f600766fbc..000000000000 --- a/third_party/perfetto/ui/src/frontend/classnames.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// It's common to want to have a class depending on a boolean flag, in which -// case we use `flag && className` which evaluates to either false or a string, -// which is why false is included in definition of ArgType. -type ArgType = string|false|undefined|ArgType[]; - -// Join class names together into valid HTML class attributes -// Falsey elements are ignored -// Nested arrays are flattened -export function classNames(...args: ArgType[]): string { - return args.flat().filter((x) => x).join(' '); -} diff --git a/third_party/perfetto/ui/src/frontend/classnames_unittest.ts b/third_party/perfetto/ui/src/frontend/classnames_unittest.ts deleted file mode 100644 index 29546db91e11..000000000000 --- a/third_party/perfetto/ui/src/frontend/classnames_unittest.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {classNames} from './classnames'; - -test('classnames', () => { - expect(classNames('foo', 'bar')).toEqual('foo bar'); - expect(classNames('foo', '', 'bar')).toEqual('foo bar'); - expect(classNames(false, 'foo', 'bar')).toEqual('foo bar'); - expect(classNames(undefined, 'foo', 'bar')).toEqual('foo bar'); - expect(classNames('foo', 'bar', ['baz', 'qux'])).toEqual('foo bar baz qux'); - expect(classNames('foo bar', 'baz')).toEqual('foo bar baz'); -}); - -test('example usecase with flags', () => { - const foo = true; - const bar = false; - const baz = true; - expect(classNames( - foo && 'foo', - bar && 'bar', - baz && 'baz', - )) - .toEqual('foo baz'); -}); - -test('example usecase with possibly undefined classnames', () => { - let fooClass: string|undefined; - const barClass = 'bar'; - expect(classNames( - fooClass, - barClass, - )) - .toEqual('bar'); -}); diff --git a/third_party/perfetto/ui/src/frontend/clipboard.ts b/third_party/perfetto/ui/src/frontend/clipboard.ts deleted file mode 100644 index c7a27d79b89d..000000000000 --- a/third_party/perfetto/ui/src/frontend/clipboard.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Actions} from '../common/actions'; -import {QueryResponse} from '../common/queries'; - -import {globals} from './globals'; - -export function onClickCopy(url: string) { - return (e: Event) => { - e.preventDefault(); - copyToClipboard(url); - globals.dispatch(Actions.updateStatus( - {msg: 'Link copied into the clipboard', timestamp: Date.now() / 1000})); - }; -} - -export async function copyToClipboard(text: string): Promise { - try { - // TODO(hjd): Fix typescript type for navigator. - await(navigator as any).clipboard.writeText(text); - } catch (err) { - console.error(`Failed to copy "${text}" to clipboard: ${err}`); - } -} - -export async function queryResponseToClipboard(resp: QueryResponse): - Promise { - const lines: string[][] = []; - lines.push(resp.columns); - for (const row of resp.rows) { - const line = []; - for (const col of resp.columns) { - const value = row[col]; - line.push(value === null ? 'NULL' : value.toString()); - } - lines.push(line); - } - copyToClipboard(lines.map((line) => line.join('\t')).join('\n')); -} - -export function download(file: File, name?: string): void { - const url = URL.createObjectURL(file); - const a = document.createElement('a'); - a.href = url; - a.download = name === undefined ? file.name : name; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); -} - - diff --git a/third_party/perfetto/ui/src/frontend/cookie_consent.ts b/third_party/perfetto/ui/src/frontend/cookie_consent.ts deleted file mode 100644 index 08847b32a5c3..000000000000 --- a/third_party/perfetto/ui/src/frontend/cookie_consent.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {globals} from './globals'; - -const COOKIE_ACK_KEY = 'cookieAck'; - -export class CookieConsent implements m.ClassComponent { - private showCookieConsent = true; - - oninit() { - this.showCookieConsent = true; - if (!globals.logging.isEnabled() || - localStorage.getItem(COOKIE_ACK_KEY) === 'true') { - this.showCookieConsent = false; - } - } - - view() { - if (!this.showCookieConsent) return; - return m( - '.cookie-consent', - m('.cookie-text', - `This site uses cookies from Google to deliver its services and to - analyze traffic.`), - m('.buttons', - m('button', - m('a', - { - href: 'https://policies.google.com/technologies/cookies', - target: '_blank', - }, - 'More details')), - m('button', - { - onclick: () => { - this.showCookieConsent = false; - localStorage.setItem(COOKIE_ACK_KEY, 'true'); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - 'OK')), - ); - } -} diff --git a/third_party/perfetto/ui/src/frontend/counter_panel.ts b/third_party/perfetto/ui/src/frontend/counter_panel.ts deleted file mode 100644 index 99d384174fae..000000000000 --- a/third_party/perfetto/ui/src/frontend/counter_panel.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {fromNs, timeToCode} from '../common/time'; - -import {globals} from './globals'; -import {Panel} from './panel'; - -interface CounterDetailsPanelAttrs {} - -export class CounterDetailsPanel extends Panel { - view() { - const counterInfo = globals.counterDetails; - if (counterInfo && counterInfo.startTime && - counterInfo.name !== undefined && counterInfo.value !== undefined && - counterInfo.delta !== undefined && counterInfo.duration !== undefined) { - return m( - '.details-panel', - m('.details-panel-heading', m('h2', `Counter Details`)), - m( - '.details-table', - [m('table', - [ - m('tr', m('th', `Name`), m('td', `${counterInfo.name}`)), - m('tr', - m('th', `Start time`), - m('td', `${timeToCode(counterInfo.startTime)}`)), - m('tr', - m('th', `Value`), - m('td', `${counterInfo.value.toLocaleString()}`)), - m('tr', - m('th', `Delta`), - m('td', `${counterInfo.delta.toLocaleString()}`)), - m('tr', - m('th', `Duration`), - m('td', `${timeToCode(fromNs(counterInfo.duration))}`)), - ])], - )); - } else { - return m( - '.details-panel', - m('.details-panel-heading', m('h2', `Counter Details`))); - } - } - - renderCanvas() {} -} diff --git a/third_party/perfetto/ui/src/frontend/cpu_profile_panel.ts b/third_party/perfetto/ui/src/frontend/cpu_profile_panel.ts deleted file mode 100644 index 6632f19bd59b..000000000000 --- a/third_party/perfetto/ui/src/frontend/cpu_profile_panel.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {CallsiteInfo} from '../common/state'; -import {globals} from './globals'; -import {Panel} from './panel'; - -interface CpuProfileDetailsPanelAttrs {} - -export class CpuProfileDetailsPanel extends Panel { - view() { - const sampleDetails = globals.cpuProfileDetails; - const header = - m('.details-panel-heading', m('h2', `CPU Profile Sample Details`)); - if (!sampleDetails || sampleDetails.id === undefined) { - return m('.details-panel', header); - } - - return m( - '.details-panel', - header, - m('table', this.getStackText(sampleDetails.stack))); - } - - getStackText(stack?: CallsiteInfo[]): m.Vnode[] { - if (!stack) return []; - - const result = []; - for (let i = stack.length - 1; i >= 0; --i) { - result.push(m('tr', m('td', stack[i].name), m('td', stack[i].mapping))); - } - - return result; - } - - renderCanvas() {} -} diff --git a/third_party/perfetto/ui/src/frontend/css_constants.ts b/third_party/perfetto/ui/src/frontend/css_constants.ts deleted file mode 100644 index aef02e313550..000000000000 --- a/third_party/perfetto/ui/src/frontend/css_constants.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This code can be used in unittests where we can't read CSS variables. -// Also we cannot have global constructors beacause when the javascript is -// loaded, the CSS might not be ready yet. -export let TRACK_SHELL_WIDTH = 100; -export let SIDEBAR_WIDTH = 100; -export let TRACK_BORDER_COLOR = '#ffc0cb'; -export let TOPBAR_HEIGHT = 48; -export let SELECTION_STROKE_COLOR = '#00344596'; -export let SELECTION_FILL_COLOR = '#8398e64d'; -export let OVERVIEW_TIMELINE_NON_VISIBLE_COLOR = '#c8c8c8cc'; -export let DEFAULT_DETAILS_CONTENT_HEIGHT = 280; -export const SELECTED_LOG_ROWS_COLOR = '#D2EFE0'; -export let BACKGROUND_COLOR = '#ffffff'; -export let FOREGROUND_COLOR = '#222'; - -export function initCssConstants() { - TRACK_SHELL_WIDTH = getCssNum('--track-shell-width') || TRACK_SHELL_WIDTH; - SIDEBAR_WIDTH = getCssNum('--sidebar-width') || SIDEBAR_WIDTH; - TRACK_BORDER_COLOR = getCssStr('--track-border-color') || TRACK_BORDER_COLOR; - TOPBAR_HEIGHT = getCssNum('--topbar-height') || TOPBAR_HEIGHT; - SELECTION_STROKE_COLOR = - getCssStr('--selection-stroke-color') || SELECTION_STROKE_COLOR; - SELECTION_FILL_COLOR = - getCssStr('--selection-fill-color') || SELECTION_FILL_COLOR; - OVERVIEW_TIMELINE_NON_VISIBLE_COLOR = - getCssStr('--overview-timeline-non-visible-color') || - OVERVIEW_TIMELINE_NON_VISIBLE_COLOR; - DEFAULT_DETAILS_CONTENT_HEIGHT = - getCssNum('--details-content-height') || DEFAULT_DETAILS_CONTENT_HEIGHT; - BACKGROUND_COLOR = getCssStr('--main-background-color') || BACKGROUND_COLOR; - FOREGROUND_COLOR = getCssStr('--main-foreground-color') || FOREGROUND_COLOR; -} - -function getCssStr(prop: string): string|undefined { - if (typeof window === 'undefined') return undefined; - const body = window.document.body; - return window.getComputedStyle(body).getPropertyValue(prop); -} - -function getCssNum(prop: string): number|undefined { - const str = getCssStr(prop); - if (str === undefined) return undefined; - const match = str.match(/^\W*(\d+)px(|\!important')$/); - if (!match) throw Error(`Could not parse CSS property "${str}" as a number`); - return Number(match[1]); -} diff --git a/third_party/perfetto/ui/src/frontend/debug.ts b/third_party/perfetto/ui/src/frontend/debug.ts deleted file mode 100644 index fae7a832f5a5..000000000000 --- a/third_party/perfetto/ui/src/frontend/debug.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {Actions} from '../common/actions'; -import {getSchema} from '../common/schema'; - -import {globals} from './globals'; - -declare global { - interface Window { - m: typeof m; - getSchema: typeof getSchema; - globals: typeof globals; - Actions: typeof Actions; - } -} - -export function registerDebugGlobals() { - window.getSchema = getSchema; - window.m = m; - window.globals = globals; - window.Actions = Actions; -} diff --git a/third_party/perfetto/ui/src/frontend/details_panel.ts b/third_party/perfetto/ui/src/frontend/details_panel.ts deleted file mode 100644 index 6e5a985f14d4..000000000000 --- a/third_party/perfetto/ui/src/frontend/details_panel.ts +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {Actions} from '../common/actions'; -import {isEmptyData} from '../common/aggregation_data'; -import {LogExists, LogExistsKey} from '../common/logs'; -import {addSelectionChangeObserver} from '../common/selection_observer'; -import {Selection} from '../common/state'; -import {DebugSliceDetailsTab} from '../tracks/debug/details_tab'; - -import {AggregationPanel} from './aggregation_panel'; -import {ChromeSliceDetailsPanel} from './chrome_slice_panel'; -import {CounterDetailsPanel} from './counter_panel'; -import {CpuProfileDetailsPanel} from './cpu_profile_panel'; -import {DEFAULT_DETAILS_CONTENT_HEIGHT} from './css_constants'; -import {DragGestureHandler} from './drag_gesture_handler'; -import {FlamegraphDetailsPanel} from './flamegraph_panel'; -import { - FlowEventsAreaSelectedPanel, - FlowEventsPanel, -} from './flow_events_panel'; -import {FtracePanel} from './ftrace_panel'; -import {globals} from './globals'; -import {LogPanel} from './logs_panel'; -import {NotesEditorTab} from './notes_panel'; -import {AnyAttrsVnode, PanelContainer} from './panel_container'; -import {PivotTable} from './pivot_table'; -import {SliceDetailsPanel} from './slice_details_panel'; -import {ThreadStateTab} from './thread_state_tab'; - -const UP_ICON = 'keyboard_arrow_up'; -const DOWN_ICON = 'keyboard_arrow_down'; -const DRAG_HANDLE_HEIGHT_PX = 28; - -function getDetailsHeight() { - // This needs to be a function instead of a const to ensure the CSS constants - // have been initialized by the time we perform this calculation; - return DEFAULT_DETAILS_CONTENT_HEIGHT + DRAG_HANDLE_HEIGHT_PX; -} - -function getFullScreenHeight() { - const panelContainer = - document.querySelector('.pan-and-zoom-content') as HTMLElement; - if (panelContainer !== null) { - return panelContainer.clientHeight; - } else { - return getDetailsHeight(); - } -} - -function hasLogs(): boolean { - const data = globals.trackDataStore.get(LogExistsKey) as LogExists; - return data && data.exists; -} - -interface Tab { - key: string; - name: string; -} - -interface DragHandleAttrs { - height: number; - resize: (height: number) => void; - tabs: Tab[]; - currentTabKey?: string; -} - -class DragHandle implements m.ClassComponent { - private dragStartHeight = 0; - private height = 0; - private previousHeight = this.height; - private resize: (height: number) => void = () => {}; - private isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX; - private isFullscreen = false; - // We can't get real fullscreen height until the pan_and_zoom_handler exists. - private fullscreenHeight = getDetailsHeight(); - - oncreate({dom, attrs}: m.CVnodeDOM) { - this.resize = attrs.resize; - this.height = attrs.height; - this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX; - this.fullscreenHeight = getFullScreenHeight(); - const elem = dom as HTMLElement; - new DragGestureHandler( - elem, - this.onDrag.bind(this), - this.onDragStart.bind(this), - this.onDragEnd.bind(this)); - } - - onupdate({attrs}: m.CVnodeDOM) { - this.resize = attrs.resize; - this.height = attrs.height; - this.isClosed = this.height <= DRAG_HANDLE_HEIGHT_PX; - } - - onDrag(_x: number, y: number) { - const newHeight = - Math.floor(this.dragStartHeight + (DRAG_HANDLE_HEIGHT_PX / 2) - y); - this.isClosed = newHeight <= DRAG_HANDLE_HEIGHT_PX; - this.isFullscreen = newHeight >= this.fullscreenHeight; - this.resize(newHeight); - globals.rafScheduler.scheduleFullRedraw(); - } - - onDragStart(_x: number, _y: number) { - this.dragStartHeight = this.height; - } - - onDragEnd() {} - - view({attrs}: m.CVnode) { - const icon = this.isClosed ? UP_ICON : DOWN_ICON; - const title = this.isClosed ? 'Show panel' : 'Hide panel'; - const renderTab = (tab: Tab) => { - if (attrs.currentTabKey === tab.key) { - return m('.tab[active]', tab.name); - } - return m( - '.tab', - { - onclick: () => { - globals.dispatch(Actions.setCurrentTab({tab: tab.key})); - }, - }, - tab.name); - }; - return m( - '.handle', - m('.tabs', attrs.tabs.map(renderTab)), - m('.buttons', - m('i.material-icons', - { - onclick: () => { - this.isClosed = false; - this.isFullscreen = true; - this.resize(this.fullscreenHeight); - globals.rafScheduler.scheduleFullRedraw(); - }, - title: 'Open fullscreen', - disabled: this.isFullscreen, - }, - 'vertical_align_top'), - m('i.material-icons', - { - onclick: () => { - if (this.height === DRAG_HANDLE_HEIGHT_PX) { - this.isClosed = false; - if (this.previousHeight === 0) { - this.previousHeight = getDetailsHeight(); - } - this.resize(this.previousHeight); - } else { - this.isFullscreen = false; - this.isClosed = true; - this.previousHeight = this.height; - this.resize(DRAG_HANDLE_HEIGHT_PX); - } - globals.rafScheduler.scheduleFullRedraw(); - }, - title, - }, - icon))); - } -} - -function handleSelectionChange(newSelection?: Selection, _?: Selection): void { - const currentSelectionTag = 'current_selection'; - const bottomTabList = globals.bottomTabList; - if (!bottomTabList) return; - if (newSelection === undefined) { - bottomTabList.closeTabByTag(currentSelectionTag); - return; - } - switch (newSelection.kind) { - case 'NOTE': - bottomTabList.addTab({ - kind: NotesEditorTab.kind, - tag: currentSelectionTag, - config: { - id: newSelection.id, - }, - }); - break; - case 'AREA': - if (newSelection.noteId !== undefined) { - bottomTabList.addTab({ - kind: NotesEditorTab.kind, - tag: currentSelectionTag, - config: { - id: newSelection.noteId, - }, - }); - } - break; - case 'THREAD_STATE': - bottomTabList.addTab({ - kind: ThreadStateTab.kind, - tag: currentSelectionTag, - config: { - id: newSelection.id, - }, - }); - break; - case 'DEBUG_SLICE': - bottomTabList.addTab({ - kind: DebugSliceDetailsTab.kind, - tag: currentSelectionTag, - config: { - sqlTableName: newSelection.sqlTableName, - id: newSelection.id, - }, - }); - break; - default: - bottomTabList.closeTabByTag(currentSelectionTag); - } -} -addSelectionChangeObserver(handleSelectionChange); - -export class DetailsPanel implements m.ClassComponent { - private detailsHeight = getDetailsHeight(); - - view() { - interface DetailsPanel { - key: string; - name: string; - vnode: AnyAttrsVnode; - } - - const detailsPanels: DetailsPanel[] = []; - - if (globals.bottomTabList) { - for (const tab of globals.bottomTabList.getTabs()) { - detailsPanels.push({ - key: tab.tag ?? tab.uuid, - name: tab.getTitle(), - vnode: tab.createPanelVnode(), - }); - } - } - - const curSelection = globals.state.currentSelection; - if (curSelection) { - switch (curSelection.kind) { - case 'NOTE': - // Handled in handleSelectionChange. - break; - case 'AREA': - if (globals.flamegraphDetails.isInAreaSelection) { - detailsPanels.push({ - key: 'flamegraph_selection', - name: 'Flamegraph Selection', - vnode: m(FlamegraphDetailsPanel, {key: 'flamegraph'}), - }); - } - break; - case 'SLICE': - detailsPanels.push({ - key: 'current_selection', - name: 'Current Selection', - vnode: m(SliceDetailsPanel, { - key: 'slice', - }), - }); - break; - case 'COUNTER': - detailsPanels.push({ - key: 'current_selection', - name: 'Current Selection', - vnode: m(CounterDetailsPanel, { - key: 'counter', - }), - }); - break; - case 'PERF_SAMPLES': - case 'HEAP_PROFILE': - detailsPanels.push({ - key: 'current_selection', - name: 'Current Selection', - vnode: m(FlamegraphDetailsPanel, {key: 'flamegraph'}), - }); - break; - case 'CPU_PROFILE_SAMPLE': - detailsPanels.push({ - key: 'current_selection', - name: 'Current Selection', - vnode: m(CpuProfileDetailsPanel, { - key: 'cpu_profile_sample', - }), - }); - break; - case 'CHROME_SLICE': - detailsPanels.push({ - key: 'current_selection', - name: 'Current Selection', - vnode: m(ChromeSliceDetailsPanel, {key: 'chrome_slice'}), - }); - break; - default: - break; - } - } - if (hasLogs()) { - detailsPanels.push({ - key: 'android_logs', - name: 'Android Logs', - vnode: m(LogPanel, {key: 'logs_panel'}), - }); - } - - const trackGroup = globals.state.trackGroups['ftrace-track-group']; - if (trackGroup) { - const {collapsed} = trackGroup; - if (!collapsed) { - detailsPanels.push({ - key: 'ftrace_events', - name: 'Ftrace Events', - vnode: m(FtracePanel, {key: 'ftrace_panel'}), - }); - } - } - - if (globals.state.nonSerializableState.pivotTable.selectionArea !== - undefined) { - detailsPanels.push({ - key: 'pivot_table', - name: 'Pivot Table', - vnode: m(PivotTable, { - key: 'pivot_table', - selectionArea: - globals.state.nonSerializableState.pivotTable.selectionArea, - }), - }); - } - - if (globals.connectedFlows.length > 0) { - detailsPanels.push({ - key: 'bound_flows', - name: 'Flow Events', - vnode: m(FlowEventsPanel, {key: 'flow_events'}), - }); - } - - for (const [key, value] of globals.aggregateDataStore.entries()) { - if (!isEmptyData(value)) { - detailsPanels.push({ - key: value.tabName, - name: value.tabName, - vnode: m(AggregationPanel, {kind: key, key, data: value}), - }); - } - } - - // Add this after all aggregation panels, to make it appear after 'Slices' - if (globals.selectedFlows.length > 0) { - detailsPanels.push({ - key: 'selected_flows', - name: 'Flow Events', - vnode: m(FlowEventsAreaSelectedPanel, {key: 'flow_events_area'}), - }); - } - - let currentTabDetails = - detailsPanels.find((tab) => tab.key === globals.state.currentTab); - if (currentTabDetails === undefined && detailsPanels.length > 0) { - currentTabDetails = detailsPanels[0]; - } - - const panel = currentTabDetails?.vnode; - const panels = panel ? [panel] : []; - - return m( - '.details-content', - { - style: { - height: `${this.detailsHeight}px`, - display: detailsPanels.length > 0 ? null : 'none', - }, - }, - m(DragHandle, { - resize: (height: number) => { - this.detailsHeight = Math.max(height, DRAG_HANDLE_HEIGHT_PX); - }, - height: this.detailsHeight, - tabs: detailsPanels.map((tab) => { - return {key: tab.key, name: tab.name}; - }), - currentTabKey: currentTabDetails?.key, - }), - m('.details-panel-container.x-scrollable', - m(PanelContainer, {doesScroll: true, panels, kind: 'DETAILS'}))); - } -} diff --git a/third_party/perfetto/ui/src/frontend/download_utils.ts b/third_party/perfetto/ui/src/frontend/download_utils.ts deleted file mode 100644 index 5f760604b113..000000000000 --- a/third_party/perfetto/ui/src/frontend/download_utils.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Initiate download of a resource identified by |url| into |filename|. -export function downloadUrl(fileName: string, url: string) { - const a = document.createElement('a'); - a.href = url; - a.download = fileName; - a.target = '_blank'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); -} - -// Initiate download of |data| a file with a given name. -export function downloadData(fileName: string, ...data: Uint8Array[]) { - const blob = new Blob(data, {type: 'application/octet-stream'}); - const url = URL.createObjectURL(blob); - downloadUrl(fileName, url); -} diff --git a/third_party/perfetto/ui/src/frontend/drag/border_drag_strategy.ts b/third_party/perfetto/ui/src/frontend/drag/border_drag_strategy.ts deleted file mode 100644 index df450fc2d7f9..000000000000 --- a/third_party/perfetto/ui/src/frontend/drag/border_drag_strategy.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -import {TimeScale} from '../time_scale'; - -import {DragStrategy} from './drag_strategy'; - -export class BorderDragStrategy extends DragStrategy { - private moveStart = false; - - constructor(timeScale: TimeScale, private pixelBounds: [number, number]) { - super(timeScale); - } - - onDrag(x: number) { - let tStart = - this.timeScale.pxToTime(this.moveStart ? x : this.pixelBounds[0]); - let tEnd = - this.timeScale.pxToTime(!this.moveStart ? x : this.pixelBounds[1]); - if (tStart > tEnd) { - this.moveStart = !this.moveStart; - [tEnd, tStart] = [tStart, tEnd]; - } - super.updateGlobals(tStart, tEnd); - this.pixelBounds = - [this.timeScale.timeToPx(tStart), this.timeScale.timeToPx(tEnd)]; - } - - onDragStart(x: number) { - this.moveStart = - Math.abs(x - this.pixelBounds[0]) < Math.abs(x - this.pixelBounds[1]); - } -} diff --git a/third_party/perfetto/ui/src/frontend/drag/drag_strategy.ts b/third_party/perfetto/ui/src/frontend/drag/drag_strategy.ts deleted file mode 100644 index 289684987820..000000000000 --- a/third_party/perfetto/ui/src/frontend/drag/drag_strategy.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -import {TimeSpan} from '../../common/time'; -import {globals} from '../globals'; -import {TimeScale} from '../time_scale'; - -export abstract class DragStrategy { - constructor(protected timeScale: TimeScale) {} - - abstract onDrag(x: number): void; - - abstract onDragStart(x: number): void; - - protected updateGlobals(tStart: number, tEnd: number) { - const vizTime = new TimeSpan(tStart, tEnd); - globals.frontendLocalState.updateVisibleTime(vizTime); - globals.rafScheduler.scheduleRedraw(); - } -} diff --git a/third_party/perfetto/ui/src/frontend/drag/inner_drag_strategy.ts b/third_party/perfetto/ui/src/frontend/drag/inner_drag_strategy.ts deleted file mode 100644 index 2af1b391f50c..000000000000 --- a/third_party/perfetto/ui/src/frontend/drag/inner_drag_strategy.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -import {TimeScale} from '../time_scale'; -import {DragStrategy} from './drag_strategy'; - -export class InnerDragStrategy extends DragStrategy { - private dragStartPx = 0; - - constructor(timeScale: TimeScale, private pixelBounds: [number, number]) { - super(timeScale); - } - - onDrag(x: number) { - const move = x - this.dragStartPx; - const tStart = this.timeScale.pxToTime(this.pixelBounds[0] + move); - const tEnd = this.timeScale.pxToTime(this.pixelBounds[1] + move); - super.updateGlobals(tStart, tEnd); - } - - onDragStart(x: number) { - this.dragStartPx = x; - } -} diff --git a/third_party/perfetto/ui/src/frontend/drag/outer_drag_strategy.ts b/third_party/perfetto/ui/src/frontend/drag/outer_drag_strategy.ts deleted file mode 100644 index 648b50d4b70a..000000000000 --- a/third_party/perfetto/ui/src/frontend/drag/outer_drag_strategy.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -import {DragStrategy} from './drag_strategy'; - -export class OuterDragStrategy extends DragStrategy { - private dragStartPx = 0; - - onDrag(x: number) { - const dragBeginTime = this.timeScale.pxToTime(this.dragStartPx); - const dragEndTime = this.timeScale.pxToTime(x); - const tStart = Math.min(dragBeginTime, dragEndTime); - const tEnd = Math.max(dragBeginTime, dragEndTime); - super.updateGlobals(tStart, tEnd); - } - - onDragStart(x: number) { - this.dragStartPx = x; - } -} diff --git a/third_party/perfetto/ui/src/frontend/drag_gesture_handler.ts b/third_party/perfetto/ui/src/frontend/drag_gesture_handler.ts deleted file mode 100644 index 30c0a385562d..000000000000 --- a/third_party/perfetto/ui/src/frontend/drag_gesture_handler.ts +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export class DragGestureHandler { - private readonly boundOnMouseDown = this.onMouseDown.bind(this); - private readonly boundOnMouseMove = this.onMouseMove.bind(this); - private readonly boundOnMouseUp = this.onMouseUp.bind(this); - private clientRect?: DOMRect; - private pendingMouseDownEvent?: MouseEvent; - private _isDragging = false; - - constructor( - private element: HTMLElement, - private onDrag: (x: number, y: number) => void, - private onDragStarted: (x: number, y: number) => void = () => {}, - private onDragFinished = () => {}) { - element.addEventListener('mousedown', this.boundOnMouseDown); - } - - private onMouseDown(e: MouseEvent) { - this._isDragging = true; - document.body.addEventListener('mousemove', this.boundOnMouseMove); - document.body.addEventListener('mouseup', this.boundOnMouseUp); - this.pendingMouseDownEvent = e; - // Prevent interactions with other DragGestureHandlers and event listeners - e.stopPropagation(); - } - - // We don't start the drag gesture on mouse down, instead we wait until - // the mouse has moved at least 1px. This prevents accidental drags that - // were meant to be clicks. - private startDragGesture(e: MouseEvent) { - this.clientRect = this.element.getBoundingClientRect(); - this.onDragStarted( - e.clientX - this.clientRect.left, e.clientY - this.clientRect.top); - } - - private onMouseMove(e: MouseEvent) { - if (e.buttons === 0) { - return this.onMouseUp(e); - } - if (this.pendingMouseDownEvent && - (Math.abs(e.clientX - this.pendingMouseDownEvent.clientX) > 1 || - Math.abs(e.clientY - this.pendingMouseDownEvent.clientY) > 1)) { - this.startDragGesture(this.pendingMouseDownEvent); - this.pendingMouseDownEvent = undefined; - } - if (!this.pendingMouseDownEvent) { - this.onDrag( - e.clientX - this.clientRect!.left, e.clientY - this.clientRect!.top); - } - e.stopPropagation(); - } - - private onMouseUp(e: MouseEvent) { - this._isDragging = false; - document.body.removeEventListener('mousemove', this.boundOnMouseMove); - document.body.removeEventListener('mouseup', this.boundOnMouseUp); - if (!this.pendingMouseDownEvent) { - this.onDragFinished(); - } - e.stopPropagation(); - } - - get isDragging() { - return this._isDragging; - } -} diff --git a/third_party/perfetto/ui/src/frontend/error_dialog.ts b/third_party/perfetto/ui/src/frontend/error_dialog.ts deleted file mode 100644 index e3a28a5cff7a..000000000000 --- a/third_party/perfetto/ui/src/frontend/error_dialog.ts +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {assertExists} from '../base/logging'; -import {RECORDING_V2_FLAG} from '../common/feature_flags'; -import {EXTENSION_URL} from '../common/recordingV2/recording_utils'; -import {TraceUrlSource} from '../common/state'; -import {saveTrace} from '../common/upload_utils'; - -import {globals} from './globals'; -import {showModal} from './modal'; -import {isShareable} from './trace_attrs'; - -// Never show more than one dialog per minute. -const MIN_REPORT_PERIOD_MS = 60000; -let timeLastReport = 0; - -// Keeps the last ERR_QUEUE_MAX_LEN errors while the dialog is throttled. -const queuedErrors = new Array(); -const ERR_QUEUE_MAX_LEN = 10; - -export function maybeShowErrorDialog(errLog: string) { - globals.logging.logError(errLog); - const now = performance.now(); - - // Here we rely on the exception message from onCannotGrowMemory function - if (errLog.includes('Cannot enlarge memory')) { - showOutOfMemoryDialog(); - // Refresh timeLastReport to prevent a different error showing a dialog - timeLastReport = now; - return; - } - - if (!RECORDING_V2_FLAG.get()) { - if (errLog.includes('Unable to claim interface')) { - showWebUSBError(); - timeLastReport = now; - return; - } - - if (errLog.includes('A transfer error has occurred') || - errLog.includes('The device was disconnected') || - errLog.includes('The transfer was cancelled')) { - showConnectionLostError(); - timeLastReport = now; - return; - } - } - - if (errLog.includes('(ERR:fmt)')) { - showUnknownFileError(); - return; - } - - if (errLog.includes('(ERR:rpc_seq)')) { - showRpcSequencingError(); - return; - } - - if (timeLastReport > 0 && now - timeLastReport <= MIN_REPORT_PERIOD_MS) { - queuedErrors.unshift(errLog); - if (queuedErrors.length > ERR_QUEUE_MAX_LEN) queuedErrors.pop(); - console.log('Suppressing crash dialog, last error notified too soon.'); - return; - } - timeLastReport = now; - - // Append queued errors. - while (queuedErrors.length > 0) { - const queuedErr = queuedErrors.shift(); - errLog += `\n\n---------------------------------------\n${queuedErr}`; - } - - const errTitle = errLog.split('\n', 1)[0].substr(0, 80); - const userDescription = ''; - let checked = false; - const engine = globals.getCurrentEngine(); - - const shareTraceSection: m.Vnode[] = []; - if (isShareable() && !urlExists()) { - shareTraceSection.push( - m(`input[type=checkbox]`, { - checked, - oninput: (ev: InputEvent) => { - checked = (ev.target as HTMLInputElement).checked; - if (checked && engine && engine.source.type === 'FILE') { - saveTrace(engine.source.file).then((url) => { - const errMessage = createErrorMessage(errLog, checked, url); - renderModal( - errTitle, errMessage, userDescription, shareTraceSection); - return; - }); - } - const errMessage = createErrorMessage(errLog, checked); - renderModal( - errTitle, errMessage, userDescription, shareTraceSection); - }, - }), - m('span', `Check this box to share the current trace for debugging - purposes.`), - m('div.modal-small', - `This will create a permalink to this trace, you may - leave it unchecked and attach the trace manually - to the bug if preferred.`)); - } - renderModal( - errTitle, - createErrorMessage(errLog, checked), - userDescription, - shareTraceSection); -} - -function renderModal( - errTitle: string, - errMessage: string, - userDescription: string, - shareTraceSection: m.Vnode[]) { - showModal({ - title: 'Oops, something went wrong. Please file a bug.', - content: - m('div', - m('.modal-logs', errMessage), - m('span', `Please provide any additional details describing - how the crash occurred:`), - m('textarea.modal-textarea', { - rows: 3, - maxlength: 1000, - oninput: (ev: InputEvent) => { - userDescription = (ev.target as HTMLTextAreaElement).value; - }, - onkeydown: (e: Event) => { - e.stopPropagation(); - }, - onkeyup: (e: Event) => { - e.stopPropagation(); - }, - }), - shareTraceSection), - buttons: [ - { - text: 'File a bug (Googlers only)', - primary: true, - id: 'file_bug', - action: () => { - window.open( - createLink(errTitle, errMessage, userDescription), '_blank'); - }, - }, - ], - }); -} - -// If there is a trace URL to share, we don't have to show the upload checkbox. -function urlExists() { - const engine = globals.getCurrentEngine(); - return engine !== undefined && - (engine.source.type === 'ARRAY_BUFFER' || engine.source.type === 'URL') && - engine.source.url !== undefined; -} - -function createErrorMessage(errLog: string, checked: boolean, url?: string) { - let errMessage = ''; - const engine = globals.getCurrentEngine(); - if (checked && url !== undefined) { - errMessage += `Trace: ${url}`; - } else if (urlExists()) { - errMessage += - `Trace: ${(assertExists(engine).source as TraceUrlSource).url}`; - } else { - errMessage += 'To assist with debugging please attach or link to the ' + - 'trace you were viewing.'; - } - return errMessage + '\n\n' + - 'Viewed on: ' + self.location.origin + '\n\n' + errLog; -} - -function createLink( - errTitle: string, errMessage: string, userDescription: string): string { - let link = 'https://goto.google.com/perfetto-ui-bug'; - link += '?title=' + encodeURIComponent(`UI Error: ${errTitle}`); - link += '&description='; - if (userDescription !== '') { - link += - encodeURIComponent('User description:\n' + userDescription + '\n\n'); - } - link += encodeURIComponent(errMessage); - // 8kb is common limit on request size so restrict links to that long: - return link.substr(0, 8000); -} - -function showOutOfMemoryDialog() { - const url = - 'https://perfetto.dev/docs/quickstart/trace-analysis#get-trace-processor'; - - const tpCmd = 'curl -LO https://get.perfetto.dev/trace_processor\n' + - 'chmod +x ./trace_processor\n' + - 'trace_processor --httpd /path/to/trace.pftrace\n' + - '# Reload the UI, it will prompt to use the HTTP+RPC interface'; - showModal({ - title: 'Oops! Your WASM trace processor ran out of memory', - content: m( - 'div', - m('span', - 'The in-memory representation of the trace is too big ' + - 'for the browser memory limits (typically 2GB per tab).'), - m('br'), - m('span', - 'You can work around this problem by using the trace_processor ' + - 'native binary as an accelerator for the UI as follows:'), - m('br'), - m('br'), - m('.modal-bash', tpCmd), - m('br'), - m('span', 'For details see '), - m('a', {href: url, target: '_blank'}, url), - ), - buttons: [], - }); -} - -function showUnknownFileError() { - showModal({ - title: 'Cannot open this file', - content: m( - 'div', - m('p', - 'The file opened doesn\'t look like a Perfetto trace or any ' + - 'other format recognized by the Perfetto TraceProcessor.'), - m('p', 'Formats supported:'), - m( - 'ul', - m('li', 'Perfetto protobuf trace'), - m('li', 'chrome://tracing JSON'), - m('li', 'Android systrace'), - m('li', 'Fuchsia trace'), - m('li', 'Ninja build log'), - ), - ), - buttons: [], - }); -} - -function showWebUSBError() { - showModal({ - title: 'A WebUSB error occurred', - content: m( - 'div', - m('span', `Is adb already running on the host? Run this command and - try again.`), - m('br'), - m('.modal-bash', '> adb kill-server'), - m('br'), - m('span', 'For details see '), - m('a', {href: 'http://b/159048331', target: '_blank'}, 'b/159048331'), - ), - buttons: [], - }); -} - -export function showWebUSBErrorV2() { - showModal({ - title: 'A WebUSB error occurred', - content: m( - 'div', - m('span', `Is adb already running on the host? Run this command and - try again.`), - m('br'), - m('.modal-bash', '> adb kill-server'), - m('br'), - // The statement below covers the following edge case: - // 1. 'adb server' is running on the device. - // 2. The user selects the new Android target, so we try to fetch the - // OS version and do QSS. - // 3. The error modal is shown. - // 4. The user runs 'adb kill-server'. - // At this point we don't have a trigger to try fetching the OS version - // + QSS again. Therefore, the user will need to refresh the page. - m('span', - 'If after running \'adb kill-server\', you don\'t see ' + - 'a \'Start Recording\' button on the page and you don\'t see ' + - '\'Allow USB debugging\' on the device, ' + - 'you will need to reload this page.'), - m('br'), - m('br'), - m('span', 'For details see '), - m('a', {href: 'http://b/159048331', target: '_blank'}, 'b/159048331'), - ), - buttons: [], - }); -} - -export function showConnectionLostError(): void { - showModal({ - title: 'Connection with the ADB device lost', - content: m( - 'div', - m('span', `Please connect the device again to restart the recording.`), - m('br')), - buttons: [], - }); -} - -export function showAllowUSBDebugging(): void { - showModal({ - title: 'Could not connect to the device', - content: m( - 'div', m('span', 'Please allow USB debugging on the device.'), m('br')), - buttons: [], - }); -} - -export function showNoDeviceSelected(): void { - showModal({ - title: 'No device was selected for recording', - content: - m('div', - m('span', `If you want to connect to an ADB device, - please select it from the list.`), - m('br')), - buttons: [], - }); -} - -export function showExtensionNotInstalled(): void { - showModal({ - title: 'Perfetto Chrome extension not installed', - content: - m('div', - m('.note', - `To trace Chrome from the Perfetto UI, you need to install our `, - m('a', {href: EXTENSION_URL, target: '_blank'}, 'Chrome extension'), - ' and then reload this page.'), - m('br')), - buttons: [], - }); -} - -export function showWebsocketConnectionIssue(message: string): void { - showModal({ - title: 'Unable to connect to the device via websocket', - content: m('div', m('span', message), m('br')), - buttons: [], - }); -} - -export function showIssueParsingTheTracedResponse(message: string): void { - showModal({ - title: 'A problem was encountered while connecting to' + - ' the Perfetto tracing service', - content: m('div', m('span', message), m('br')), - buttons: [], - }); -} - -export function showFailedToPushBinary(message: string): void { - showModal({ - title: 'Failed to push a binary to the device', - content: - m('div', - m('span', - 'This can happen if your Android device has an OS version lower ' + - 'than Q. Perfetto tried to push the latest version of its ' + - 'embedded binary but failed.'), - m('br'), - m('br'), - m('span', 'Error message:'), - m('br'), - m('span', message)), - buttons: [], - }); -} - -function showRpcSequencingError() { - showModal({ - title: 'A TraceProcessor RPC error occurred', - content: m( - 'div', - m('p', 'The trace processor RPC sequence ID was broken'), - m('p', `This can happen when using a HTTP trace processor instance and -either accidentally sharing this between multiple tabs or -restarting the trace processor while still in use by UI.`), - m('p', `Please refresh this tab and ensure that trace processor is used -at most one tab at a time.`), - ), - buttons: [], - }); -} diff --git a/third_party/perfetto/ui/src/frontend/events.ts b/third_party/perfetto/ui/src/frontend/events.ts deleted file mode 100644 index 88e044bec216..000000000000 --- a/third_party/perfetto/ui/src/frontend/events.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// layerX and layerY aren't standardized but there is no drop-in replacement -// (offsetX/offsetY have slightly different semantics) and they are still -// present in the browsers we care about, so for now we create an extended -// version of MouseEvent we can use instead. -// See also: -// https://github.com/microsoft/TypeScript/issues/35634#issuecomment-564765179 -export interface PerfettoMouseEvent extends MouseEvent { - layerX: number; - layerY: number; -} diff --git a/third_party/perfetto/ui/src/frontend/file_drop_handler.ts b/third_party/perfetto/ui/src/frontend/file_drop_handler.ts deleted file mode 100644 index 89f38c049c3b..000000000000 --- a/third_party/perfetto/ui/src/frontend/file_drop_handler.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Actions} from '../common/actions'; -import {globals} from './globals'; - -let lastDragTarget: EventTarget|null = null; - -export function installFileDropHandler() { - window.ondragenter = (evt: DragEvent) => { - evt.preventDefault(); - evt.stopPropagation(); - lastDragTarget = evt.target; - if (dragEventHasFiles(evt)) { - document.body.classList.add('filedrag'); - } - }; - - window.ondragleave = (evt: DragEvent) => { - evt.preventDefault(); - evt.stopPropagation(); - if (evt.target === lastDragTarget) { - document.body.classList.remove('filedrag'); - } - }; - - window.ondrop = (evt: DragEvent) => { - evt.preventDefault(); - evt.stopPropagation(); - document.body.classList.remove('filedrag'); - if (evt.dataTransfer && dragEventHasFiles(evt)) { - const file = evt.dataTransfer.files[0]; - if (file) { - globals.dispatch(Actions.openTraceFromFile({file})); - } - } - evt.preventDefault(); - }; - - window.ondragover = (evt: DragEvent) => { - evt.preventDefault(); - evt.stopPropagation(); - }; -} - -function dragEventHasFiles(event: DragEvent): boolean { - if (event.dataTransfer && event.dataTransfer.types) { - for (const type of event.dataTransfer.types) { - if (type === 'Files') return true; - } - } - return false; -} diff --git a/third_party/perfetto/ui/src/frontend/flags_page.ts b/third_party/perfetto/ui/src/frontend/flags_page.ts deleted file mode 100644 index 05af3a078bfe..000000000000 --- a/third_party/perfetto/ui/src/frontend/flags_page.ts +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {channelChanged, getNextChannel, setChannel} from '../common/channels'; -import {featureFlags, Flag, OverrideState} from '../common/feature_flags'; - -import {globals} from './globals'; -import {createPage} from './pages'; - -const RELEASE_PROCESS_URL = - 'https://perfetto.dev/docs/visualization/perfetto-ui-release-process'; - -interface FlagOption { - id: string; - name: string; -} - -interface SelectWidgetAttrs { - label: string; - description: m.Children; - options: FlagOption[]; - selected: string; - onSelect: (id: string) => void; -} - -class SelectWidget implements m.ClassComponent { - view(vnode: m.Vnode) { - const attrs = vnode.attrs; - return m( - '.flag-widget', - m('label', attrs.label), - m( - 'select', - { - onchange: (e: InputEvent) => { - const value = (e.target as HTMLSelectElement).value; - attrs.onSelect(value); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - attrs.options.map((o) => { - const selected = o.id === attrs.selected; - return m('option', {value: o.id, selected}, o.name); - }), - ), - m('.description', attrs.description), - ); - } -} - -interface FlagWidgetAttrs { - flag: Flag; -} - -class FlagWidget implements m.ClassComponent { - view(vnode: m.Vnode) { - const flag = vnode.attrs.flag; - const defaultState = flag.defaultValue ? 'Enabled' : 'Disabled'; - return m(SelectWidget, { - label: flag.name, - description: flag.description, - options: [ - {id: OverrideState.DEFAULT, name: `Default (${defaultState})`}, - {id: OverrideState.TRUE, name: 'Enabled'}, - {id: OverrideState.FALSE, name: 'Disabled'}, - ], - selected: flag.overriddenState(), - onSelect: (value: string) => { - switch (value) { - case OverrideState.TRUE: - flag.set(true); - break; - case OverrideState.FALSE: - flag.set(false); - break; - default: - case OverrideState.DEFAULT: - flag.reset(); - break; - } - }, - }); - } -} - -export const FlagsPage = createPage({ - view() { - const needsReload = channelChanged(); - return m( - '.flags-page', - m( - '.flags-content', - m('h1', 'Feature flags'), - needsReload && - [ - m('h2', 'Please reload for your changes to take effect'), - ], - m(SelectWidget, { - label: 'Release channel', - description: [ - 'Which release channel of the UI to use. See ', - m('a', - { - href: RELEASE_PROCESS_URL, - }, - 'Release Process'), - ' for more information.', - ], - options: [ - {id: 'stable', name: 'Stable (default)'}, - {id: 'canary', name: 'Canary'}, - {id: 'autopush', name: 'Autopush'}, - ], - selected: getNextChannel(), - onSelect: (id) => setChannel(id), - }), - m('button', - { - onclick: () => { - featureFlags.resetAll(); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - 'Reset all below'), - - featureFlags.allFlags().map((flag) => m(FlagWidget, {flag})), - )); - }, -}); diff --git a/third_party/perfetto/ui/src/frontend/flamegraph.ts b/third_party/perfetto/ui/src/frontend/flamegraph.ts deleted file mode 100644 index bf2554db6e8b..000000000000 --- a/third_party/perfetto/ui/src/frontend/flamegraph.ts +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {searchSegment} from '../base/binary_search'; -import {cropText} from '../common/canvas_utils'; - -import {CallsiteInfo} from '../common/state'; - -interface Node { - width: number; - x: number; - nextXForChildren: number; - size: number; -} - -interface CallsiteInfoWidth { - callsite: CallsiteInfo; - width: number; -} - -// Height of one 'row' on the flame chart including 1px of whitespace -// below the box. -const NODE_HEIGHT = 18; - -export const FLAMEGRAPH_HOVERED_COLOR = 'hsl(224, 45%, 55%)'; - -export function findRootSize(data: CallsiteInfo[]) { - let totalSize = 0; - let i = 0; - while (i < data.length && data[i].depth === 0) { - totalSize += data[i].totalSize; - i++; - } - return totalSize; -} - -export interface NodeRendering { - totalSize?: string; - selfSize?: string; -} - -export class Flamegraph { - private nodeRendering: NodeRendering = {}; - private flamegraphData: CallsiteInfo[]; - private highlightSomeNodes = false; - private maxDepth = -1; - private totalSize = -1; - // Initialised on first draw() call - private labelCharWidth = 0; - private labelFontStyle = '12px Roboto Mono'; - private rolloverFontStyle = '12px Roboto Condensed'; - // Key for the map is depth followed by x coordinate - `depth;x` - private graphData: Map = new Map(); - private xStartsPerDepth: Map = new Map(); - - private hoveredX = -1; - private hoveredY = -1; - private hoveredCallsite?: CallsiteInfo; - private clickedCallsite?: CallsiteInfo; - - private startingY = 0; - - constructor(flamegraphData: CallsiteInfo[]) { - this.flamegraphData = flamegraphData; - this.findMaxDepth(); - } - - private findMaxDepth() { - this.maxDepth = - Math.max(...this.flamegraphData.map((value) => value.depth)); - } - - // Instead of highlighting the interesting nodes, we actually want to - // de-emphasize the non-highlighted nodes. Returns true if there - // are any highlighted nodes in the flamegraph. - private highlightingExists() { - this.highlightSomeNodes = this.flamegraphData.some((e) => e.highlighted); - } - - generateColor(name: string, isGreyedOut = false, highlighted: boolean): - string { - if (isGreyedOut) { - return '#d9d9d9'; - } - if (name === 'unknown' || name === 'root') { - return '#c0c0c0'; - } - let x = 0; - for (let i = 0; i < name.length; i += 1) { - x += name.charCodeAt(i) % 64; - } - x = x % 360; - let l = '76'; - // Make non-highlighted node lighter. - if (this.highlightSomeNodes && !highlighted) { - l = '90'; - } - return `hsl(${x}deg, 45%, ${l}%)`; - } - - // Caller will have to call draw method after updating data to have updated - // graph. - updateDataIfChanged( - nodeRendering: NodeRendering, flamegraphData: CallsiteInfo[], - clickedCallsite?: CallsiteInfo) { - this.nodeRendering = nodeRendering; - this.clickedCallsite = clickedCallsite; - if (this.flamegraphData === flamegraphData) { - return; - } - this.flamegraphData = flamegraphData; - this.clickedCallsite = clickedCallsite; - this.findMaxDepth(); - this.highlightingExists(); - // Finding total size of roots. - this.totalSize = findRootSize(flamegraphData); - } - - draw( - ctx: CanvasRenderingContext2D, width: number, height: number, x = 0, - y = 0, unit = 'B') { - if (this.flamegraphData === undefined) { - return; - } - - ctx.font = this.labelFontStyle; - ctx.textBaseline = 'middle'; - if (this.labelCharWidth === 0) { - this.labelCharWidth = ctx.measureText('_').width; - } - - this.startingY = y; - - // For each node, we use this map to get information about it's parent - // (total size of it, width and where it starts in graph) so we can - // calculate it's own position in graph. - const nodesMap = new Map(); - let currentY = y; - nodesMap.set(-1, {width, nextXForChildren: x, size: this.totalSize, x}); - - // Initialize data needed for click/hover behavior. - this.graphData = new Map(); - this.xStartsPerDepth = new Map(); - - // Draw root node. - ctx.fillStyle = this.generateColor('root', false, false); - ctx.fillRect(x, currentY, width, NODE_HEIGHT - 1); - const text = cropText( - `root: ${ - this.displaySize( - this.totalSize, unit, unit === 'B' ? 1024 : 1000)}`, - this.labelCharWidth, - width - 2); - ctx.fillStyle = 'black'; - ctx.fillText(text, x + 5, currentY + (NODE_HEIGHT - 1) / 2); - currentY += NODE_HEIGHT; - - // Set style for borders. - ctx.strokeStyle = 'white'; - ctx.lineWidth = 0.5; - - for (let i = 0; i < this.flamegraphData.length; i++) { - if (currentY > height) { - break; - } - const value = this.flamegraphData[i]; - const parentNode = nodesMap.get(value.parentId); - if (parentNode === undefined) { - continue; - } - - const isClicked = this.clickedCallsite !== undefined; - const isFullWidth = - isClicked && value.depth <= this.clickedCallsite!.depth; - const isGreyedOut = - isClicked && value.depth < this.clickedCallsite!.depth; - - const parent = value.parentId; - const parentSize = parent === -1 ? this.totalSize : parentNode.size; - // Calculate node's width based on its proportion in parent. - const width = - (isFullWidth ? 1 : value.totalSize / parentSize) * parentNode.width; - - const currentX = parentNode.nextXForChildren; - currentY = y + NODE_HEIGHT * (value.depth + 1); - - // Draw node. - const name = this.getCallsiteName(value); - ctx.fillStyle = this.generateColor(name, isGreyedOut, value.highlighted); - ctx.fillRect(currentX, currentY, width, NODE_HEIGHT - 1); - - // Set current node's data in map for children to use. - nodesMap.set(value.id, { - width, - nextXForChildren: currentX, - size: value.totalSize, - x: currentX, - }); - // Update next x coordinate in parent. - nodesMap.set(value.parentId, { - width: parentNode.width, - nextXForChildren: currentX + width, - size: parentNode.size, - x: parentNode.x, - }); - - // Draw name. - const labelPaddingPx = 5; - const maxLabelWidth = width - labelPaddingPx * 2; - let text = cropText(name, this.labelCharWidth, maxLabelWidth); - // If cropped text and the original text are within 20% we keep the - // original text and just squish it a bit. - if (text.length * 1.2 > name.length) { - text = name; - } - ctx.fillStyle = 'black'; - ctx.fillText( - text, - currentX + labelPaddingPx, - currentY + (NODE_HEIGHT - 1) / 2, - maxLabelWidth); - - // Draw border on the right of node. - ctx.beginPath(); - ctx.moveTo(currentX + width, currentY); - ctx.lineTo(currentX + width, currentY + NODE_HEIGHT); - ctx.stroke(); - - // Add this node for recognizing in click/hover. - // Map graphData contains one callsite which is on that depth and X - // start. Map xStartsPerDepth for each depth contains all X start - // coordinates that callsites on that level have. - this.graphData.set( - `${value.depth};${currentX}`, {callsite: value, width}); - const xStarts = this.xStartsPerDepth.get(value.depth); - if (xStarts === undefined) { - this.xStartsPerDepth.set(value.depth, [currentX]); - } else { - xStarts.push(currentX); - } - } - - // Draw the tooltip. - if (this.hoveredX > -1 && this.hoveredY > -1 && this.hoveredCallsite) { - // Must set these before measureText below. - ctx.font = this.rolloverFontStyle; - ctx.textBaseline = 'top'; - - // Size in px of the border around the text and the edge of the rollover - // background. - const paddingPx = 8; - // Size in px of the x and y offset between the mouse and the top left - // corner of the rollover box. - const offsetPx = 4; - - const lines: string[] = []; - - let textWidth = this.addToTooltip( - this.getCallsiteName(this.hoveredCallsite), - width - paddingPx, - ctx, - lines); - if (this.hoveredCallsite.location != null) { - textWidth = Math.max( - textWidth, - this.addToTooltip( - this.hoveredCallsite.location, width, ctx, lines)); - } - textWidth = Math.max( - textWidth, - this.addToTooltip(this.hoveredCallsite.mapping, width, ctx, lines)); - - if (this.nodeRendering.totalSize !== undefined) { - const percentage = - this.hoveredCallsite.totalSize / this.totalSize * 100; - const totalSizeText = `${this.nodeRendering.totalSize}: ${ - this.displaySize( - this.hoveredCallsite.totalSize, - unit, - unit === 'B' ? 1024 : 1000)} (${percentage.toFixed(2)}%)`; - textWidth = Math.max( - textWidth, this.addToTooltip(totalSizeText, width, ctx, lines)); - } - - if (this.nodeRendering.selfSize !== undefined && - this.hoveredCallsite.selfSize > 0) { - const selfPercentage = - this.hoveredCallsite.selfSize / this.totalSize * 100; - const selfSizeText = `${this.nodeRendering.selfSize}: ${ - this.displaySize( - this.hoveredCallsite.selfSize, - unit, - unit === 'B' ? 1024 : 1000)} (${selfPercentage.toFixed(2)}%)`; - textWidth = Math.max( - textWidth, this.addToTooltip(selfSizeText, width, ctx, lines)); - } - - // Compute a line height as the bounding box height + 50%: - const heightSample = ctx.measureText( - 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); - const lineHeight = - Math.round(heightSample.actualBoundingBoxDescent * 1.5); - - const rectWidth = textWidth + 2 * paddingPx; - const rectHeight = lineHeight * lines.length + 2 * paddingPx; - - let rectXStart = this.hoveredX + offsetPx; - let rectYStart = this.hoveredY + offsetPx; - - if (rectXStart + rectWidth > width) { - rectXStart = width - rectWidth; - } - - if (rectYStart + rectHeight > height) { - rectYStart = height - rectHeight; - } - - ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'; - ctx.fillRect(rectXStart, rectYStart, rectWidth, rectHeight); - ctx.fillStyle = 'hsl(200, 50%, 40%)'; - ctx.textAlign = 'left'; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - ctx.fillText( - line, - rectXStart + paddingPx, - rectYStart + paddingPx + i * lineHeight); - } - } - } - - private addToTooltip( - text: string, width: number, ctx: CanvasRenderingContext2D, - lines: string[]): number { - const lineSplitter: LineSplitter = - splitIfTooBig(text, width, ctx.measureText(text).width); - lines.push(...lineSplitter.lines); - return lineSplitter.lineWidth; - } - - private getCallsiteName(value: CallsiteInfo): string { - return value.name === undefined || value.name === '' ? 'unknown' : - value.name; - } - - private displaySize(totalSize: number, unit: string, step = 1024): string { - if (unit === '') return totalSize.toLocaleString(); - if (totalSize === 0) return `0 ${unit}`; - const units = [ - ['', 1], - ['K', step], - ['M', Math.pow(step, 2)], - ['G', Math.pow(step, 3)], - ]; - let unitsIndex = Math.trunc(Math.log(totalSize) / Math.log(step)); - unitsIndex = unitsIndex > units.length - 1 ? units.length - 1 : unitsIndex; - const result = totalSize / +units[unitsIndex][1]; - const resultString = totalSize % +units[unitsIndex][1] === 0 ? - result.toString() : - result.toFixed(2); - return `${resultString} ${units[unitsIndex][0]}${unit}`; - } - - onMouseMove({x, y}: {x: number, y: number}) { - this.hoveredX = x; - this.hoveredY = y; - this.hoveredCallsite = this.findSelectedCallsite(x, y); - const isCallsiteSelected = this.hoveredCallsite !== undefined; - if (!isCallsiteSelected) { - this.onMouseOut(); - } - return isCallsiteSelected; - } - - onMouseOut() { - this.hoveredX = -1; - this.hoveredY = -1; - this.hoveredCallsite = undefined; - } - - onMouseClick({x, y}: {x: number, y: number}): CallsiteInfo|undefined { - const clickedCallsite = this.findSelectedCallsite(x, y); - // TODO(b/148596659): Allow to expand [merged] callsites. Currently, - // this expands to the biggest of the nodes that were merged, which - // is confusing, so we disallow clicking on them. - if (clickedCallsite === undefined || clickedCallsite.merged) { - return undefined; - } - return clickedCallsite; - } - - private findSelectedCallsite(x: number, y: number): CallsiteInfo|undefined { - const depth = - Math.trunc((y - this.startingY) / NODE_HEIGHT) - 1; // at 0 is root - if (depth >= 0 && this.xStartsPerDepth.has(depth)) { - const startX = this.searchSmallest(this.xStartsPerDepth.get(depth)!, x); - const result = this.graphData.get(`${depth};${startX}`); - if (result !== undefined) { - const width = result.width; - return startX + width >= x ? result.callsite : undefined; - } - } - return undefined; - } - - searchSmallest(haystack: number[], needle: number): number { - haystack = haystack.sort((n1, n2) => n1 - n2); - const [left] = searchSegment(haystack, needle); - return left === -1 ? -1 : haystack[left]; - } - - getHeight(): number { - return this.flamegraphData.length === 0 ? 0 : - (this.maxDepth + 2) * NODE_HEIGHT; - } -} - -export interface LineSplitter { - lineWidth: number; - lines: string[]; -} - -export function splitIfTooBig( - line: string, width: number, lineWidth: number): LineSplitter { - if (line === '') return {lineWidth, lines: []}; - const lines: string[] = []; - const charWidth = lineWidth / line.length; - const maxWidth = width - 32; - const maxLineLen = Math.trunc(maxWidth / charWidth); - while (line.length > 0) { - lines.push(line.slice(0, maxLineLen)); - line = line.slice(maxLineLen); - } - lineWidth = Math.min(maxLineLen * charWidth, lineWidth); - return {lineWidth, lines}; -} diff --git a/third_party/perfetto/ui/src/frontend/flamegraph_panel.ts b/third_party/perfetto/ui/src/frontend/flamegraph_panel.ts deleted file mode 100644 index b8246b3c33f0..000000000000 --- a/third_party/perfetto/ui/src/frontend/flamegraph_panel.ts +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {assertExists, assertTrue} from '../base/logging'; -import {Actions} from '../common/actions'; -import { - ALLOC_SPACE_MEMORY_ALLOCATED_KEY, - OBJECTS_ALLOCATED_KEY, - OBJECTS_ALLOCATED_NOT_FREED_KEY, - PERF_SAMPLES_KEY, - SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, -} from '../common/flamegraph_util'; -import { - CallsiteInfo, - FlamegraphStateViewingOption, - ProfileType, -} from '../common/state'; -import {timeToCode} from '../common/time'; -import {profileType} from '../controller/flamegraph_controller'; - -import {PerfettoMouseEvent} from './events'; -import {Flamegraph, NodeRendering} from './flamegraph'; -import {globals} from './globals'; -import {Modal, ModalDefinition} from './modal'; -import {Panel, PanelSize} from './panel'; -import {debounce} from './rate_limiters'; -import {Router} from './router'; -import {getCurrentTrace} from './sidebar'; -import {convertTraceToPprofAndDownload} from './trace_converter'; -import {Button} from './widgets/button'; - -interface FlamegraphDetailsPanelAttrs {} - -const HEADER_HEIGHT = 30; - -function toSelectedCallsite(c: CallsiteInfo|undefined): string { - if (c !== undefined && c.name !== undefined) { - return c.name; - } - return '(none)'; -} - -const RENDER_SELF_AND_TOTAL: NodeRendering = { - selfSize: 'Self', - totalSize: 'Total', -}; -const RENDER_OBJ_COUNT: NodeRendering = { - selfSize: 'Self objects', - totalSize: 'Subtree objects', -}; - -export class FlamegraphDetailsPanel extends Panel { - private profileType?: ProfileType = undefined; - private ts = 0; - private pids: number[] = []; - private flamegraph: Flamegraph = new Flamegraph([]); - private focusRegex = ''; - private updateFocusRegexDebounced = debounce(() => { - this.updateFocusRegex(); - }, 20); - - view() { - const flamegraphDetails = globals.flamegraphDetails; - if (flamegraphDetails && flamegraphDetails.type !== undefined && - flamegraphDetails.startNs !== undefined && - flamegraphDetails.durNs !== undefined && - flamegraphDetails.pids !== undefined && - flamegraphDetails.upids !== undefined) { - this.profileType = profileType(flamegraphDetails.type); - this.ts = flamegraphDetails.startNs + flamegraphDetails.durNs; - this.pids = flamegraphDetails.pids; - if (flamegraphDetails.flamegraph) { - this.flamegraph.updateDataIfChanged( - this.nodeRendering(), flamegraphDetails.flamegraph); - } - const height = flamegraphDetails.flamegraph ? - this.flamegraph.getHeight() + HEADER_HEIGHT : - 0; - return m( - '.details-panel', - { - onclick: (e: PerfettoMouseEvent) => { - if (this.flamegraph !== undefined) { - this.onMouseClick({y: e.layerY, x: e.layerX}); - } - return false; - }, - onmousemove: (e: PerfettoMouseEvent) => { - if (this.flamegraph !== undefined) { - this.onMouseMove({y: e.layerY, x: e.layerX}); - globals.rafScheduler.scheduleRedraw(); - } - }, - onmouseout: () => { - if (this.flamegraph !== undefined) { - this.onMouseOut(); - } - }, - }, - this.maybeShowModal(flamegraphDetails.graphIncomplete), - m('.details-panel-heading.flamegraph-profile', - {onclick: (e: MouseEvent) => e.stopPropagation()}, - [ - m('div.options', - [ - m('div.title', this.getTitle()), - this.getViewingOptionButtons(), - ]), - m('div.details', - [ - m('div.selected', - `Selected function: ${ - toSelectedCallsite( - flamegraphDetails.expandedCallsite)}`), - m('div.time', - `Snapshot time: ${timeToCode(flamegraphDetails.durNs)}`), - m('input[type=text][placeholder=Focus]', { - oninput: (e: Event) => { - const target = (e.target as HTMLInputElement); - this.focusRegex = target.value; - this.updateFocusRegexDebounced(); - }, - // Required to stop hot-key handling: - onkeydown: (e: Event) => e.stopPropagation(), - }), - (this.profileType === ProfileType.NATIVE_HEAP_PROFILE || - this.profileType === ProfileType.JAVA_HEAP_SAMPLES) && - m(Button, { - icon: 'file_download', - onclick: () => { - this.downloadPprof(); - }, - }), - ]), - ]), - m(`div[style=height:${height}px]`), - ); - } else { - return m( - '.details-panel', - m('.details-panel-heading', m('h2', `Flamegraph Profile`))); - } - } - - - private maybeShowModal(graphIncomplete?: boolean) { - if (!graphIncomplete || globals.state.flamegraphModalDismissed) { - return undefined; - } - return m(Modal, { - title: 'The flamegraph is incomplete', - vAlign: 'TOP', - content: m('div', - 'The current trace does not have a fully formed flamegraph'), - buttons: [ - { - text: 'Show the errors', - primary: true, - action: () => Router.navigate('#!/info'), - }, - { - text: 'Skip', - action: () => { - globals.dispatch(Actions.dismissFlamegraphModal({})); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - ], - } as ModalDefinition); - } - - private getTitle(): string { - switch (this.profileType!) { - case ProfileType.HEAP_PROFILE: - return 'Heap profile:'; - case ProfileType.NATIVE_HEAP_PROFILE: - return 'Native heap profile:'; - case ProfileType.JAVA_HEAP_SAMPLES: - return 'Java heap samples:'; - case ProfileType.JAVA_HEAP_GRAPH: - return 'Java heap graph:'; - case ProfileType.PERF_SAMPLE: - return 'Profile:'; - default: - throw new Error('unknown type'); - } - } - - private nodeRendering(): NodeRendering { - if (this.profileType === undefined) { - return {}; - } - const viewingOption = globals.state.currentFlamegraphState!.viewingOption; - switch (this.profileType) { - case ProfileType.JAVA_HEAP_GRAPH: - if (viewingOption === OBJECTS_ALLOCATED_NOT_FREED_KEY) { - return RENDER_OBJ_COUNT; - } else { - return RENDER_SELF_AND_TOTAL; - } - case ProfileType.HEAP_PROFILE: - case ProfileType.NATIVE_HEAP_PROFILE: - case ProfileType.JAVA_HEAP_SAMPLES: - case ProfileType.PERF_SAMPLE: - return RENDER_SELF_AND_TOTAL; - default: - throw new Error('unknown type'); - } - } - - private updateFocusRegex() { - globals.dispatch(Actions.changeFocusFlamegraphState({ - focusRegex: this.focusRegex, - })); - } - - getViewingOptionButtons(): m.Children { - return m( - 'div', - ...FlamegraphDetailsPanel.selectViewingOptions( - assertExists(this.profileType))); - } - - downloadPprof() { - const engine = globals.getCurrentEngine(); - if (!engine) return; - getCurrentTrace() - .then((file) => { - assertTrue( - this.pids.length === 1, - 'Native profiles can only contain one pid.'); - convertTraceToPprofAndDownload(file, this.pids[0], this.ts); - }) - .catch((error) => { - throw new Error(`Failed to get current trace ${error}`); - }); - } - - private changeFlamegraphData() { - const data = globals.flamegraphDetails; - const flamegraphData = data.flamegraph === undefined ? [] : data.flamegraph; - this.flamegraph.updateDataIfChanged( - this.nodeRendering(), flamegraphData, data.expandedCallsite); - } - - renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) { - this.changeFlamegraphData(); - const current = globals.state.currentFlamegraphState; - if (current === null) return; - const unit = - current.viewingOption === SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY || - current.viewingOption === ALLOC_SPACE_MEMORY_ALLOCATED_KEY ? - 'B' : - ''; - this.flamegraph.draw(ctx, size.width, size.height, 0, HEADER_HEIGHT, unit); - } - - onMouseClick({x, y}: {x: number, y: number}): boolean { - const expandedCallsite = this.flamegraph.onMouseClick({x, y}); - globals.dispatch(Actions.expandFlamegraphState({expandedCallsite})); - return true; - } - - onMouseMove({x, y}: {x: number, y: number}): boolean { - this.flamegraph.onMouseMove({x, y}); - return true; - } - - onMouseOut() { - this.flamegraph.onMouseOut(); - } - - private static selectViewingOptions(profileType: ProfileType) { - switch (profileType) { - case ProfileType.PERF_SAMPLE: - return [this.buildButtonComponent(PERF_SAMPLES_KEY, 'Samples')]; - case ProfileType.JAVA_HEAP_GRAPH: - return [ - this.buildButtonComponent( - SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, 'Size'), - this.buildButtonComponent(OBJECTS_ALLOCATED_NOT_FREED_KEY, 'Objects'), - ]; - case ProfileType.HEAP_PROFILE: - return [ - this.buildButtonComponent( - SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, 'Unreleased size'), - this.buildButtonComponent( - OBJECTS_ALLOCATED_NOT_FREED_KEY, 'Unreleased count'), - this.buildButtonComponent( - ALLOC_SPACE_MEMORY_ALLOCATED_KEY, 'Total size'), - this.buildButtonComponent(OBJECTS_ALLOCATED_KEY, 'Total count'), - ]; - case ProfileType.NATIVE_HEAP_PROFILE: - return [ - this.buildButtonComponent( - SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY, 'Unreleased malloc size'), - this.buildButtonComponent( - OBJECTS_ALLOCATED_NOT_FREED_KEY, 'Unreleased malloc count'), - this.buildButtonComponent( - ALLOC_SPACE_MEMORY_ALLOCATED_KEY, 'Total malloc size'), - this.buildButtonComponent( - OBJECTS_ALLOCATED_KEY, 'Total malloc count'), - ]; - case ProfileType.JAVA_HEAP_SAMPLES: - return [ - this.buildButtonComponent( - ALLOC_SPACE_MEMORY_ALLOCATED_KEY, 'Total allocation size'), - this.buildButtonComponent( - OBJECTS_ALLOCATED_KEY, 'Total allocation count'), - ]; - default: - throw new Error(`Unexpected profile type ${profileType}`); - } - } - - private static buildButtonComponent( - viewingOption: FlamegraphStateViewingOption, text: string) { - const active = - (globals.state.currentFlamegraphState !== null && - globals.state.currentFlamegraphState.viewingOption === viewingOption); - return m(Button, { - label: text, - active, - minimal: true, - onclick: () => { - globals.dispatch(Actions.changeViewFlamegraphState({viewingOption})); - }, - }); - } -} diff --git a/third_party/perfetto/ui/src/frontend/flamegraph_unittest.ts b/third_party/perfetto/ui/src/frontend/flamegraph_unittest.ts deleted file mode 100644 index 0cc463ce3621..000000000000 --- a/third_party/perfetto/ui/src/frontend/flamegraph_unittest.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {splitIfTooBig} from './flamegraph'; - -test('textGoingToMultipleLines', () => { - const text = 'Dummy text to go to multiple lines.'; - - const lineSplitter = splitIfTooBig(text, 7 + 32, text.length); - - expect(lineSplitter).toEqual({ - lines: ['Dummy t', 'ext to ', 'go to m', 'ultiple', ' lines.'], - lineWidth: 7, - }); -}); - -test('emptyText', () => { - const text = ''; - - const lineSplitter = splitIfTooBig(text, 10, 5); - - expect(lineSplitter).toEqual({lines: [], lineWidth: 5}); -}); - -test('textEnoughForOneLine', () => { - const text = 'Dummy text to go to one lines.'; - - const lineSplitter = splitIfTooBig(text, text.length + 32, text.length); - - expect(lineSplitter).toEqual({lines: [text], lineWidth: text.length}); -}); - -test('textGoingToTwoLines', () => { - const text = 'Dummy text to go to two lines.'; - - const lineSplitter = splitIfTooBig(text, text.length / 2 + 32, text.length); - - expect(lineSplitter).toEqual({ - lines: ['Dummy text to g', 'o to two lines.'], - lineWidth: text.length / 2, - }); -}); diff --git a/third_party/perfetto/ui/src/frontend/flow_events_panel.ts b/third_party/perfetto/ui/src/frontend/flow_events_panel.ts deleted file mode 100644 index 755c75d65fcd..000000000000 --- a/third_party/perfetto/ui/src/frontend/flow_events_panel.ts +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {Actions} from '../common/actions'; -import {timeToCode} from '../common/time'; - -import {Flow, globals} from './globals'; -import {BLANK_CHECKBOX, CHECKBOX} from './icons'; -import {Panel, PanelSize} from './panel'; - -export const ALL_CATEGORIES = '_all_'; - -export function getFlowCategories(flow: Flow): string[] { - const categories: string[] = []; - // v1 flows have their own categories - if (flow.category) { - categories.push(...flow.category.split(',')); - return categories; - } - const beginCats = flow.begin.sliceCategory.split(','); - const endCats = flow.end.sliceCategory.split(','); - categories.push(...new Set([...beginCats, ...endCats])); - return categories; -} - -export class FlowEventsPanel extends Panel { - view() { - const selection = globals.state.currentSelection; - if (!selection || selection.kind !== 'CHROME_SLICE') { - return; - } - - const flowClickHandler = (sliceId: number, trackId: number) => { - const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId]; - if (uiTrackId) { - globals.makeSelection( - Actions.selectChromeSlice( - {id: sliceId, trackId: uiTrackId, table: 'slice'}), - 'bound_flows'); - } - }; - - // Can happen only for flow events version 1 - const haveCategories = - globals.connectedFlows.filter((flow) => flow.category).length > 0; - - const columns = [ - m('th', 'Direction'), - m('th', 'Duration'), - m('th', 'Connected Slice ID'), - m('th', 'Connected Slice Name'), - m('th', 'Thread Out'), - m('th', 'Thread In'), - m('th', 'Process Out'), - m('th', 'Process In'), - ]; - - if (haveCategories) { - columns.push(m('th', 'Flow Category')); - columns.push(m('th', 'Flow Name')); - } - - const rows = [m('tr', columns)]; - - // Fill the table with all the directly connected flow events - globals.connectedFlows.forEach((flow) => { - if (selection.id !== flow.begin.sliceId && - selection.id !== flow.end.sliceId) { - return; - } - - const outgoing = selection.id === flow.begin.sliceId; - const otherEnd = (outgoing ? flow.end : flow.begin); - - const args = { - onclick: () => flowClickHandler(otherEnd.sliceId, otherEnd.trackId), - onmousemove: () => globals.dispatch( - Actions.setHighlightedSliceId({sliceId: otherEnd.sliceId})), - onmouseleave: () => - globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1})), - }; - - const data = [ - m('td.flow-link', args, outgoing ? 'Outgoing' : 'Incoming'), - m('td.flow-link', args, timeToCode(flow.dur)), - m('td.flow-link', args, otherEnd.sliceId.toString()), - m('td.flow-link', args, otherEnd.sliceName), - m('td.flow-link', args, flow.begin.threadName), - m('td.flow-link', args, flow.end.threadName), - m('td.flow-link', args, flow.begin.processName), - m('td.flow-link', args, flow.end.processName), - ]; - - if (haveCategories) { - data.push(m('td.flow-info', flow.category || '-')); - data.push(m('td.flow-info', flow.name || '-')); - } - - rows.push(m('tr', data)); - }); - - return m('.details-panel', [ - m('.details-panel-heading', m('h2', `Flow events`)), - m('.flow-events-table', m('table', rows)), - ]); - } - - renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {} -} - -export class FlowEventsAreaSelectedPanel extends Panel { - view() { - const selection = globals.state.currentSelection; - if (!selection || selection.kind !== 'AREA') { - return; - } - - const columns = [ - m('th', 'Flow Category'), - m('th', 'Number of flows'), - m('th', - 'Show', - m('a.warning', - m('i.material-icons', 'warning'), - m('.tooltip', - 'Showing a large number of flows may impact performance.'))), - ]; - - const rows = [m('tr', columns)]; - - const categoryToFlowsNum = new Map(); - - globals.selectedFlows.forEach((flow) => { - const categories = getFlowCategories(flow); - categories.forEach((cat) => { - if (!categoryToFlowsNum.has(cat)) { - categoryToFlowsNum.set(cat, 0); - } - categoryToFlowsNum.set(cat, categoryToFlowsNum.get(cat)! + 1); - }); - }); - - const allWasChecked = globals.visibleFlowCategories.get(ALL_CATEGORIES); - rows.push(m('tr.sum', [ - m('td.sum-data', 'All'), - m('td.sum-data', globals.selectedFlows.length), - m('td.sum-data', - m('i.material-icons', - { - onclick: () => { - if (allWasChecked) { - globals.visibleFlowCategories.clear(); - } else { - categoryToFlowsNum.forEach((_, cat) => { - globals.visibleFlowCategories.set(cat, true); - }); - } - globals.visibleFlowCategories.set(ALL_CATEGORIES, !allWasChecked); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - allWasChecked ? CHECKBOX : BLANK_CHECKBOX)), - ])); - - categoryToFlowsNum.forEach((num, cat) => { - const wasChecked = globals.visibleFlowCategories.get(cat) || - globals.visibleFlowCategories.get(ALL_CATEGORIES); - const data = [ - m('td.flow-info', cat), - m('td.flow-info', num), - m('td.flow-info', - m('i.material-icons', - { - onclick: () => { - if (wasChecked) { - globals.visibleFlowCategories.set(ALL_CATEGORIES, false); - } - globals.visibleFlowCategories.set(cat, !wasChecked); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - wasChecked ? CHECKBOX : BLANK_CHECKBOX)), - ]; - rows.push(m('tr', data)); - }); - - return m('.details-panel', [ - m('.details-panel-heading', m('h2', `Selected flow events`)), - m('.flow-events-table', m('table', rows)), - ]); - } - - renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {} -} diff --git a/third_party/perfetto/ui/src/frontend/flow_events_renderer.ts b/third_party/perfetto/ui/src/frontend/flow_events_renderer.ts deleted file mode 100644 index 909dcb7f3bf9..000000000000 --- a/third_party/perfetto/ui/src/frontend/flow_events_renderer.ts +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {TRACK_SHELL_WIDTH} from './css_constants'; -import {ALL_CATEGORIES, getFlowCategories} from './flow_events_panel'; -import {Flow, FlowPoint, globals} from './globals'; -import {PanelVNode} from './panel'; -import {SliceRect} from './track'; -import {TrackGroupPanel} from './track_group_panel'; -import {TrackPanel} from './track_panel'; - -const TRACK_GROUP_CONNECTION_OFFSET = 5; -const TRIANGLE_SIZE = 5; -const CIRCLE_RADIUS = 3; -const BEZIER_OFFSET = 30; - -const CONNECTED_FLOW_HUE = 10; -const SELECTED_FLOW_HUE = 230; - -const DEFAULT_FLOW_WIDTH = 2; -const FOCUSED_FLOW_WIDTH = 3; - -const HIGHLIGHTED_FLOW_INTENSITY = 45; -const FOCUSED_FLOW_INTENSITY = 55; -const DEFAULT_FLOW_INTENSITY = 70; - -type LineDirection = 'LEFT'|'RIGHT'|'UP'|'DOWN'; -type ConnectionType = 'TRACK'|'TRACK_GROUP'; - -interface TrackPanelInfo { - panel: TrackPanel; - yStart: number; -} - -interface TrackGroupPanelInfo { - panel: TrackGroupPanel; - yStart: number; - height: number; -} - -function hasTrackId(obj: {}): obj is {trackId: number} { - return (obj as {trackId?: number}).trackId !== undefined; -} - -function hasManyTrackIds(obj: {}): obj is {trackIds: number[]} { - return (obj as {trackIds?: number}).trackIds !== undefined; -} - -function hasId(obj: {}): obj is {id: number} { - return (obj as {id?: number}).id !== undefined; -} - -function hasTrackGroupId(obj: {}): obj is {trackGroupId: string} { - return (obj as {trackGroupId?: string}).trackGroupId !== undefined; -} - -export class FlowEventsRendererArgs { - trackIdToTrackPanel: Map; - groupIdToTrackGroupPanel: Map; - - constructor(public canvasWidth: number, public canvasHeight: number) { - this.trackIdToTrackPanel = new Map(); - this.groupIdToTrackGroupPanel = new Map(); - } - - registerPanel(panel: PanelVNode, yStart: number, height: number) { - if (panel.state instanceof TrackPanel && hasId(panel.attrs)) { - const config = globals.state.tracks[panel.attrs.id].config; - if (hasTrackId(config)) { - this.trackIdToTrackPanel.set( - config.trackId, {panel: panel.state, yStart}); - } - if (hasManyTrackIds(config)) { - for (const trackId of config.trackIds) { - this.trackIdToTrackPanel.set(trackId, {panel: panel.state, yStart}); - } - } - } else if ( - panel.state instanceof TrackGroupPanel && - hasTrackGroupId(panel.attrs)) { - this.groupIdToTrackGroupPanel.set( - panel.attrs.trackGroupId, {panel: panel.state, yStart, height}); - } - } -} - -export class FlowEventsRenderer { - private getTrackGroupIdByTrackId(trackId: number): string|undefined { - const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId]; - return uiTrackId ? globals.state.tracks[uiTrackId].trackGroup : undefined; - } - - private getTrackGroupYCoordinate( - args: FlowEventsRendererArgs, trackId: number): number|undefined { - const trackGroupId = this.getTrackGroupIdByTrackId(trackId); - if (!trackGroupId) { - return undefined; - } - const trackGroupInfo = args.groupIdToTrackGroupPanel.get(trackGroupId); - if (!trackGroupInfo) { - return undefined; - } - return trackGroupInfo.yStart + trackGroupInfo.height - - TRACK_GROUP_CONNECTION_OFFSET; - } - - private getTrackYCoordinate(args: FlowEventsRendererArgs, trackId: number): - number|undefined { - return args.trackIdToTrackPanel.get(trackId) ?.yStart; - } - - private getYConnection( - args: FlowEventsRendererArgs, trackId: number, - rect?: SliceRect): {y: number, connection: ConnectionType}|undefined { - if (!rect) { - const y = this.getTrackGroupYCoordinate(args, trackId); - if (y === undefined) { - return undefined; - } - return {y, connection: 'TRACK_GROUP'}; - } - const y = (this.getTrackYCoordinate(args, trackId) || 0) + rect.top + - rect.height * 0.5; - - return { - y: Math.min(Math.max(0, y), args.canvasHeight), - connection: 'TRACK', - }; - } - - private getXCoordinate(ts: number): number { - return globals.frontendLocalState.timeScale.timeToPx(ts); - } - - private getSliceRect(args: FlowEventsRendererArgs, point: FlowPoint): - SliceRect|undefined { - const trackPanel = args.trackIdToTrackPanel.get(point.trackId) ?.panel; - if (!trackPanel) { - return undefined; - } - return trackPanel.getSliceRect( - point.sliceStartTs, point.sliceEndTs, point.depth); - } - - render(ctx: CanvasRenderingContext2D, args: FlowEventsRendererArgs) { - ctx.save(); - ctx.translate(TRACK_SHELL_WIDTH, 0); - ctx.rect(0, 0, args.canvasWidth - TRACK_SHELL_WIDTH, args.canvasHeight); - ctx.clip(); - - globals.connectedFlows.forEach((flow) => { - this.drawFlow(ctx, args, flow, CONNECTED_FLOW_HUE); - }); - - globals.selectedFlows.forEach((flow) => { - const categories = getFlowCategories(flow); - for (const cat of categories) { - if (globals.visibleFlowCategories.get(cat) || - globals.visibleFlowCategories.get(ALL_CATEGORIES)) { - this.drawFlow(ctx, args, flow, SELECTED_FLOW_HUE); - break; - } - } - }); - - ctx.restore(); - } - - private drawFlow( - ctx: CanvasRenderingContext2D, args: FlowEventsRendererArgs, flow: Flow, - hue: number) { - const beginSliceRect = this.getSliceRect(args, flow.begin); - const endSliceRect = this.getSliceRect(args, flow.end); - - const beginYConnection = - this.getYConnection(args, flow.begin.trackId, beginSliceRect); - const endYConnection = - this.getYConnection(args, flow.end.trackId, endSliceRect); - - if (!beginYConnection || !endYConnection) { - return; - } - - let beginDir: LineDirection = 'LEFT'; - let endDir: LineDirection = 'RIGHT'; - if (beginYConnection.connection === 'TRACK_GROUP') { - beginDir = beginYConnection.y > endYConnection.y ? 'DOWN' : 'UP'; - } - if (endYConnection.connection === 'TRACK_GROUP') { - endDir = endYConnection.y > beginYConnection.y ? 'DOWN' : 'UP'; - } - - const begin = { - x: this.getXCoordinate(flow.begin.sliceEndTs), - y: beginYConnection.y, - dir: beginDir, - }; - const end = { - x: this.getXCoordinate(flow.end.sliceStartTs), - y: endYConnection.y, - dir: endDir, - }; - const highlighted = flow.end.sliceId === globals.state.highlightedSliceId || - flow.begin.sliceId === globals.state.highlightedSliceId; - const focused = flow.id === globals.state.focusedFlowIdLeft || - flow.id === globals.state.focusedFlowIdRight; - - let intensity = DEFAULT_FLOW_INTENSITY; - let width = DEFAULT_FLOW_WIDTH; - if (focused) { - intensity = FOCUSED_FLOW_INTENSITY; - width = FOCUSED_FLOW_WIDTH; - } - if (highlighted) { - intensity = HIGHLIGHTED_FLOW_INTENSITY; - } - this.drawFlowArrow(ctx, begin, end, hue, intensity, width); - } - - private getDeltaX(dir: LineDirection, offset: number): number { - switch (dir) { - case 'LEFT': - return -offset; - case 'RIGHT': - return offset; - case 'UP': - return 0; - case 'DOWN': - return 0; - default: - return 0; - } - } - - private getDeltaY(dir: LineDirection, offset: number): number { - switch (dir) { - case 'LEFT': - return 0; - case 'RIGHT': - return 0; - case 'UP': - return -offset; - case 'DOWN': - return offset; - default: - return 0; - } - } - - private drawFlowArrow( - ctx: CanvasRenderingContext2D, - begin: {x: number, y: number, dir: LineDirection}, - end: {x: number, y: number, dir: LineDirection}, hue: number, - intensity: number, width: number) { - const hasArrowHead = Math.abs(begin.x - end.x) > 3 * TRIANGLE_SIZE; - const END_OFFSET = - (((end.dir === 'RIGHT' || end.dir === 'LEFT') && hasArrowHead) ? - TRIANGLE_SIZE : - 0); - const color = `hsl(${hue}, 50%, ${intensity}%)`; - // draw curved line from begin to end (bezier curve) - ctx.strokeStyle = color; - ctx.lineWidth = width; - ctx.beginPath(); - ctx.moveTo(begin.x, begin.y); - ctx.bezierCurveTo( - begin.x - this.getDeltaX(begin.dir, BEZIER_OFFSET), - begin.y - this.getDeltaY(begin.dir, BEZIER_OFFSET), - end.x - this.getDeltaX(end.dir, BEZIER_OFFSET + END_OFFSET), - end.y - this.getDeltaY(end.dir, BEZIER_OFFSET + END_OFFSET), - end.x - this.getDeltaX(end.dir, END_OFFSET), - end.y - this.getDeltaY(end.dir, END_OFFSET)); - ctx.stroke(); - - // TODO (andrewbb): probably we should add a parameter 'MarkerType' to be - // able to choose what marker we want to draw _before_ the function call. - // e.g. triangle, circle, square? - if (begin.dir !== 'RIGHT' && begin.dir !== 'LEFT') { - // draw a circle if we the line has a vertical connection - ctx.fillStyle = color; - ctx.beginPath(); - ctx.arc(begin.x, begin.y, 3, 0, 2 * Math.PI); - ctx.closePath(); - ctx.fill(); - } - - - if (end.dir !== 'RIGHT' && end.dir !== 'LEFT') { - // draw a circle if we the line has a vertical connection - ctx.fillStyle = color; - ctx.beginPath(); - ctx.arc(end.x, end.y, CIRCLE_RADIUS, 0, 2 * Math.PI); - ctx.closePath(); - ctx.fill(); - } else if (hasArrowHead) { - this.drawArrowHead(end, ctx, color); - } - } - - private drawArrowHead( - end: {x: number; y: number; dir: LineDirection}, - ctx: CanvasRenderingContext2D, color: string) { - const dx = this.getDeltaX(end.dir, TRIANGLE_SIZE); - const dy = this.getDeltaY(end.dir, TRIANGLE_SIZE); - // draw small triangle - ctx.fillStyle = color; - ctx.beginPath(); - ctx.moveTo(end.x, end.y); - ctx.lineTo(end.x - dx - dy, end.y + dx - dy); - ctx.lineTo(end.x - dx + dy, end.y - dx - dy); - ctx.closePath(); - ctx.fill(); - } -} diff --git a/third_party/perfetto/ui/src/frontend/frontend_local_state.ts b/third_party/perfetto/ui/src/frontend/frontend_local_state.ts deleted file mode 100644 index b24f69a33211..000000000000 --- a/third_party/perfetto/ui/src/frontend/frontend_local_state.ts +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertTrue} from '../base/logging'; -import {Actions} from '../common/actions'; -import {HttpRpcState} from '../common/http_rpc_engine'; -import { - Area, - FrontendLocalState as FrontendState, - Timestamped, - VisibleState, -} from '../common/state'; -import {TimeSpan} from '../common/time'; - -import {globals} from './globals'; -import {ratelimit} from './rate_limiters'; -import {TimeScale} from './time_scale'; - -interface Range { - start?: number; - end?: number; -} - -function chooseLatest(current: T, next: T): T { - if (next !== current && next.lastUpdate > current.lastUpdate) { - // |next| is from state. Callers may mutate the return value of - // this function so we need to clone |next| to prevent bad mutations - // of state: - return Object.assign({}, next); - } - return current; -} - -function capBetween(t: number, start: number, end: number) { - return Math.min(Math.max(t, start), end); -} - -// Calculate the space a scrollbar takes up so that we can subtract it from -// the canvas width. -function calculateScrollbarWidth() { - const outer = document.createElement('div'); - outer.style.overflowY = 'scroll'; - const inner = document.createElement('div'); - outer.appendChild(inner); - document.body.appendChild(outer); - const width = - outer.getBoundingClientRect().width - inner.getBoundingClientRect().width; - document.body.removeChild(outer); - return width; -} - -/** - * State that is shared between several frontend components, but not the - * controller. This state is updated at 60fps. - */ -export class FrontendLocalState { - visibleWindowTime = new TimeSpan(0, 10); - timeScale = new TimeScale(this.visibleWindowTime, [0, 0]); - showPanningHint = false; - showCookieConsent = false; - visibleTracks = new Set(); - prevVisibleTracks = new Set(); - scrollToTrackId?: string|number; - httpRpcState: HttpRpcState = {connected: false}; - newVersionAvailable = false; - - // This is used to calculate the tracks within a Y range for area selection. - areaY: Range = {}; - - private scrollBarWidth?: number; - - private _visibleState: VisibleState = { - lastUpdate: 0, - startSec: 0, - endSec: 10, - resolution: 1, - }; - - private _selectedArea?: Area; - - // TODO: there is some redundancy in the fact that both |visibleWindowTime| - // and a |timeScale| have a notion of time range. That should live in one - // place only. - - getScrollbarWidth() { - if (this.scrollBarWidth === undefined) { - this.scrollBarWidth = calculateScrollbarWidth(); - } - return this.scrollBarWidth; - } - - setHttpRpcState(httpRpcState: HttpRpcState) { - this.httpRpcState = httpRpcState; - globals.rafScheduler.scheduleFullRedraw(); - } - - addVisibleTrack(trackId: string) { - this.visibleTracks.add(trackId); - } - - // Called when beginning a canvas redraw. - clearVisibleTracks() { - this.visibleTracks.clear(); - } - - // Called when the canvas redraw is complete. - sendVisibleTracks() { - if (this.prevVisibleTracks.size !== this.visibleTracks.size || - ![...this.prevVisibleTracks].every( - (value) => this.visibleTracks.has(value))) { - globals.dispatch( - Actions.setVisibleTracks({tracks: Array.from(this.visibleTracks)})); - this.prevVisibleTracks = new Set(this.visibleTracks); - } - } - - mergeState(state: FrontendState): void { - // This is unfortunately subtle. This class mutates this._visibleState. - // Since we may not mutate |state| (in order to make immer's immutable - // updates work) this means that we have to make a copy of the visibleState. - // when updating it. We don't want to have to do that unnecessarily so - // chooseLatest returns a shallow clone of state.visibleState *only* when - // that is the newer state. All of these complications should vanish when - // we remove this class. - const previousVisibleState = this._visibleState; - this._visibleState = chooseLatest(this._visibleState, state.visibleState); - const visibleStateWasUpdated = previousVisibleState !== this._visibleState; - if (visibleStateWasUpdated) { - this.updateLocalTime( - new TimeSpan(this._visibleState.startSec, this._visibleState.endSec)); - } - } - - selectArea( - startSec: number, endSec: number, - tracks = this._selectedArea ? this._selectedArea.tracks : []) { - assertTrue(endSec >= startSec); - this.showPanningHint = true; - this._selectedArea = {startSec, endSec, tracks}, - globals.rafScheduler.scheduleFullRedraw(); - } - - deselectArea() { - this._selectedArea = undefined; - globals.rafScheduler.scheduleRedraw(); - } - - get selectedArea(): Area|undefined { - return this._selectedArea; - } - - private ratelimitedUpdateVisible = ratelimit(() => { - globals.dispatch(Actions.setVisibleTraceTime(this._visibleState)); - }, 50); - - private updateLocalTime(ts: TimeSpan) { - const traceTime = globals.state.traceTime; - const startSec = capBetween(ts.start, traceTime.startSec, traceTime.endSec); - const endSec = capBetween(ts.end, traceTime.startSec, traceTime.endSec); - this.visibleWindowTime = new TimeSpan(startSec, endSec); - this.timeScale.setTimeBounds(this.visibleWindowTime); - this.updateResolution(); - } - - private updateResolution() { - this._visibleState.lastUpdate = Date.now() / 1000; - this._visibleState.resolution = globals.getCurResolution(); - this.ratelimitedUpdateVisible(); - } - - updateVisibleTime(ts: TimeSpan) { - this.updateLocalTime(ts); - this._visibleState.lastUpdate = Date.now() / 1000; - this._visibleState.startSec = this.visibleWindowTime.start; - this._visibleState.endSec = this.visibleWindowTime.end; - this._visibleState.resolution = globals.getCurResolution(); - this.ratelimitedUpdateVisible(); - } - - getVisibleStateBounds(): [number, number] { - return [this.visibleWindowTime.start, this.visibleWindowTime.end]; - } - - // Whenever start/end px of the timeScale is changed, update - // the resolution. - updateLocalLimits(pxStart: number, pxEnd: number) { - // Numbers received here can be negative or equal, but we should fix that - // before updating the timescale. - pxStart = Math.max(0, pxStart); - pxEnd = Math.max(0, pxEnd); - if (pxStart === pxEnd) pxEnd = pxStart + 1; - this.timeScale.setLimitsPx(pxStart, pxEnd); - this.updateResolution(); - } -} diff --git a/third_party/perfetto/ui/src/frontend/ftrace_panel.ts b/third_party/perfetto/ui/src/frontend/ftrace_panel.ts deleted file mode 100644 index 8de83cb40080..000000000000 --- a/third_party/perfetto/ui/src/frontend/ftrace_panel.ts +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; -import {StringListPatch} from 'src/common/state'; - -import {assertExists} from '../base/logging'; -import {Actions} from '../common/actions'; -import {colorForString} from '../common/colorizer'; -import {formatTimestamp} from '../common/time'; - -import {globals} from './globals'; -import {Panel} from './panel'; -import { - MultiSelect, - MultiSelectDiff, - Option as MultiSelectOption, -} from './widgets/multiselect'; -import {PopupPosition} from './widgets/popup'; - -const ROW_H = 20; -const PAGE_SIZE = 250; - -// This class is quite a weird one. The state looks something like this: -// -// view() -> renders the panel from the data, for now we have no idea what size -// the scroll window is going to be so we don't know how many rows to ask for, -// and the number of rendered rows in our state is likely going to be 0 or wrong -// -// oncreate() -> we now know how many rows we need to display and our scroll -// offset. This is where we as our controller to update the rows, which could -// take some time. Record the first and last row we can see. Attach scroll -// handler to the scrolly window here. -// -// onScroll() -> we know the window has been scrolled, we need to see if things -// have changed enough to constitute a redraw. - -// Another call to view() can come at any time, as a reusult of the controller -// giving us some data. -// -export class FtracePanel extends Panel<{}> { - private page: number = 0; - private pageCount: number = 0; - - view(_: m.CVnode<{}>) { - return m( - '.ftrace-panel', - m( - '.sticky', - [ - this.renderRowsLabel(), - this.renderFilterPanel(), - ], - ), - this.renderRows(), - ); - } - - private scrollContainer(dom: Element): HTMLElement { - const el = dom.parentElement!.parentElement!.parentElement; - return assertExists(el); - } - - oncreate({dom}: m.CVnodeDOM) { - const sc = this.scrollContainer(dom); - sc.addEventListener('scroll', this.onScroll); - this.recomputeVisibleRowsAndUpdate(sc); - } - - onupdate({dom}: m.CVnodeDOM) { - const sc = this.scrollContainer(dom); - this.recomputeVisibleRowsAndUpdate(sc); - } - - recomputeVisibleRowsAndUpdate(scrollContainer: HTMLElement) { - const prevPage = this.page; - const prevPageCount = this.pageCount; - - const visibleRowOffset = Math.floor(scrollContainer.scrollTop / ROW_H); - const visibleRowCount = Math.ceil(scrollContainer.clientHeight / ROW_H); - - // Work out which "page" we're on - this.page = Math.floor(visibleRowOffset / PAGE_SIZE) - 1; - this.pageCount = Math.ceil(visibleRowCount / PAGE_SIZE) + 2; - - if (this.page !== prevPage || this.pageCount !== prevPageCount) { - globals.dispatch(Actions.updateFtracePagination({ - offset: Math.max(0, this.page) * PAGE_SIZE, - count: this.pageCount * PAGE_SIZE, - })); - } - } - - onremove({dom}: m.CVnodeDOM) { - const sc = this.scrollContainer(dom); - sc.removeEventListener('scroll', this.onScroll); - - globals.dispatch(Actions.updateFtracePagination({ - offset: 0, - count: 0, - })); - } - - onScroll = (e: Event) => { - const scrollContainer = e.target as HTMLElement; - this.recomputeVisibleRowsAndUpdate(scrollContainer); - }; - - onRowOver(ts: number) { - globals.dispatch(Actions.setHoverCursorTimestamp({ts})); - } - - onRowOut() { - globals.dispatch(Actions.setHoverCursorTimestamp({ts: -1})); - } - - private renderRowsLabel() { - if (globals.ftracePanelData) { - const {numEvents} = globals.ftracePanelData; - return m('.ftrace-rows-label', `Ftrace Events (${numEvents})`); - } else { - return m('.ftrace-rows-label', 'Ftrace Rows'); - } - } - - private renderFilterPanel() { - if (!globals.ftraceCounters) { - return null; - } - - const options: MultiSelectOption[] = - globals.ftraceCounters.map(({name, count}) => { - return { - id: name, - name: `${name} (${count})`, - checked: !globals.state.ftraceFilter.excludedNames.some( - (excluded: string) => excluded === name), - }; - }); - - return m( - MultiSelect, - { - label: 'Filter by name', - icon: 'filter_list_alt', - popupPosition: PopupPosition.Top, - options, - onChange: (diffs: MultiSelectDiff[]) => { - const excludedNames: StringListPatch[] = diffs.map( - ({id, checked}) => [checked ? 'remove' : 'add', id], - ); - globals.dispatchMultiple([ - Actions.updateFtraceFilter({excludedNames}), - Actions.requestTrackReload({}), - ]); - }, - }, - ); - } - - // Render all the rows including the first title row - private renderRows() { - const data = globals.ftracePanelData; - const rows: m.Children = []; - - rows.push(m( - `.row`, - m('.cell.row-header', 'Timestamp'), - m('.cell.row-header', 'Name'), - m('.cell.row-header', 'CPU'), - m('.cell.row-header', 'Process'), - m('.cell.row-header', 'Args'), - )); - - if (data) { - const {events, offset, numEvents} = data; - for (let i = 0; i < events.length; i++) { - const {ts, name, cpu, process, args} = events[i]; - - const timestamp = - formatTimestamp(ts / 1e9 - globals.state.traceTime.startSec); - - const rank = i + offset; - - const color = colorForString(name); - const hsl = `hsl( - ${color.h}, - ${color.s - 20}%, - ${Math.min(color.l + 10, 60)}% - )`; - - rows.push(m( - `.row`, - { - style: {top: `${(rank + 1.0) * ROW_H}px`}, - onmouseover: this.onRowOver.bind(this, ts / 1e9), - onmouseout: this.onRowOut.bind(this), - }, - m('.cell', timestamp), - m('.cell', m('span.colour', {style: {background: hsl}}), name), - m('.cell', cpu), - m('.cell', process), - m('.cell', args), - )); - } - return m('.rows', {style: {height: `${numEvents * ROW_H}px`}}, rows); - } else { - return m('.rows', rows); - } - } - - renderCanvas() {} -} diff --git a/third_party/perfetto/ui/src/frontend/globals.ts b/third_party/perfetto/ui/src/frontend/globals.ts deleted file mode 100644 index 0ca10a0777dd..000000000000 --- a/third_party/perfetto/ui/src/frontend/globals.ts +++ /dev/null @@ -1,642 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertExists} from '../base/logging'; -import {Actions, DeferredAction} from '../common/actions'; -import {AggregateData} from '../common/aggregation_data'; -import {Args, ArgsTree} from '../common/arg_types'; -import { - ConversionJobName, - ConversionJobStatus, -} from '../common/conversion_jobs'; -import {createEmptyState} from '../common/empty_state'; -import {Engine} from '../common/engine'; -import {MetricResult} from '../common/metric_data'; -import {CurrentSearchResults, SearchSummary} from '../common/search_data'; -import {CallsiteInfo, EngineConfig, ProfileType, State} from '../common/state'; -import {fromNs, toNs} from '../common/time'; - -import {Analytics, initAnalytics} from './analytics'; -import {BottomTabList} from './bottom_tab'; -import {FrontendLocalState} from './frontend_local_state'; -import {RafScheduler} from './raf_scheduler'; -import {Router} from './router'; -import {ServiceWorkerController} from './service_worker_controller'; - -type Dispatch = (action: DeferredAction) => void; -type TrackDataStore = Map; -type QueryResultsStore = Map; -type AggregateDataStore = Map; -type Description = Map; - -export interface SliceDetails { - ts?: number; - absTime?: string; - dur?: number; - threadTs?: number; - threadDur?: number; - priority?: number; - endState?: string|null; - cpu?: number; - id?: number; - threadStateId?: number; - utid?: number; - wakeupTs?: number; - wakerUtid?: number; - wakerCpu?: number; - category?: string; - name?: string; - tid?: number; - threadName?: string; - pid?: number; - processName?: string; - uid?: number; - packageName?: string; - versionCode?: number; - args?: Args; - argsTree?: ArgsTree; - description?: Description; -} - -export interface FlowPoint { - trackId: number; - - sliceName: string; - sliceCategory: string; - sliceId: number; - sliceStartTs: number; - sliceEndTs: number; - // Thread and process info. Only set in sliceSelected not in areaSelected as - // the latter doesn't display per-flow info and it'd be a waste to join - // additional tables for undisplayed info in that case. Nothing precludes - // adding this in a future iteration however. - threadName: string; - processName: string; - - depth: number; - - // TODO(altimin): Ideally we should have a generic mechanism for allowing to - // customise the name here, but for now we are hardcording a few - // Chrome-specific bits in the query here. - sliceChromeCustomName?: string; -} - -export interface Flow { - id: number; - - begin: FlowPoint; - end: FlowPoint; - dur: number; - - category?: string; - name?: string; -} - -export interface CounterDetails { - startTime?: number; - value?: number; - delta?: number; - duration?: number; - name?: string; -} - -export interface ThreadStateDetails { - ts?: number; - dur?: number; -} - -export interface FlamegraphDetails { - type?: ProfileType; - id?: number; - startNs?: number; - durNs?: number; - pids?: number[]; - upids?: number[]; - flamegraph?: CallsiteInfo[]; - expandedCallsite?: CallsiteInfo; - viewingOption?: string; - expandedId?: number; - // isInAreaSelection is true if a flamegraph is part of the current area - // selection. - isInAreaSelection?: boolean; - // When heap_graph_non_finalized_graph has a count >0, we mark the graph - // as incomplete. - graphIncomplete?: boolean; -} - -export interface CpuProfileDetails { - id?: number; - ts?: number; - utid?: number; - stack?: CallsiteInfo[]; -} - -export interface QuantizedLoad { - startSec: number; - endSec: number; - load: number; -} -type OverviewStore = Map; - -export interface ThreadDesc { - utid: number; - tid: number; - threadName: string; - pid?: number; - procName?: string; - cmdline?: string; -} -type ThreadMap = Map; - -export interface FtraceEvent { - id: number; - ts: number; - name: string; - cpu: number; - thread: string|null; - process: string|null; - args: string; -} - -export interface FtracePanelData { - events: FtraceEvent[]; - offset: number; - numEvents: number; // Number of events in the visible window -} - -export interface FtraceStat { - name: string; - count: number; -} - -function getRoot() { - // Works out the root directory where the content should be served from - // e.g. `http://origin/v1.2.3/`. - const script = document.currentScript as HTMLScriptElement; - - // Needed for DOM tests, that do not have script element. - if (script === null) { - return ''; - } - - let root = script.src; - root = root.substr(0, root.lastIndexOf('/') + 1); - return root; -} - -/** - * Global accessors for state/dispatch in the frontend. - */ -class Globals { - readonly root = getRoot(); - - bottomTabList?: BottomTabList = undefined; - - private _testing = false; - private _dispatch?: Dispatch = undefined; - private _state?: State = undefined; - private _frontendLocalState?: FrontendLocalState = undefined; - private _rafScheduler?: RafScheduler = undefined; - private _serviceWorkerController?: ServiceWorkerController = undefined; - private _logging?: Analytics = undefined; - private _isInternalUser: boolean|undefined = undefined; - - // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads. - private _trackDataStore?: TrackDataStore = undefined; - private _queryResults?: QueryResultsStore = undefined; - private _overviewStore?: OverviewStore = undefined; - private _aggregateDataStore?: AggregateDataStore = undefined; - private _threadMap?: ThreadMap = undefined; - private _sliceDetails?: SliceDetails = undefined; - private _threadStateDetails?: ThreadStateDetails = undefined; - private _connectedFlows?: Flow[] = undefined; - private _selectedFlows?: Flow[] = undefined; - private _visibleFlowCategories?: Map = undefined; - private _counterDetails?: CounterDetails = undefined; - private _flamegraphDetails?: FlamegraphDetails = undefined; - private _cpuProfileDetails?: CpuProfileDetails = undefined; - private _numQueriesQueued = 0; - private _bufferUsage?: number = undefined; - private _recordingLog?: string = undefined; - private _traceErrors?: number = undefined; - private _metricError?: string = undefined; - private _metricResult?: MetricResult = undefined; - private _jobStatus?: Map = undefined; - private _router?: Router = undefined; - private _embeddedMode?: boolean = undefined; - private _hideSidebar?: boolean = undefined; - private _ftraceCounters?: FtraceStat[] = undefined; - private _ftracePanelData?: FtracePanelData = undefined; - - // TODO(hjd): Remove once we no longer need to update UUID on redraw. - private _publishRedraw?: () => void = undefined; - - private _currentSearchResults: CurrentSearchResults = { - sliceIds: new Float64Array(0), - tsStarts: new Float64Array(0), - utids: new Float64Array(0), - trackIds: [], - sources: [], - totalResults: 0, - }; - searchSummary: SearchSummary = { - tsStarts: new Float64Array(0), - tsEnds: new Float64Array(0), - count: new Uint8Array(0), - }; - - engines = new Map(); - - initialize(dispatch: Dispatch, router: Router) { - this._dispatch = dispatch; - this._router = router; - this._state = createEmptyState(); - this._frontendLocalState = new FrontendLocalState(); - this._rafScheduler = new RafScheduler(); - this._serviceWorkerController = new ServiceWorkerController(); - this._testing = - self.location && self.location.search.indexOf('testing=1') >= 0; - this._logging = initAnalytics(); - - // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads. - this._trackDataStore = new Map(); - this._queryResults = new Map(); - this._overviewStore = new Map(); - this._aggregateDataStore = new Map(); - this._threadMap = new Map(); - this._sliceDetails = {}; - this._connectedFlows = []; - this._selectedFlows = []; - this._visibleFlowCategories = new Map(); - this._counterDetails = {}; - this._threadStateDetails = {}; - this._flamegraphDetails = {}; - this._cpuProfileDetails = {}; - this.engines.clear(); - } - - get router(): Router { - return assertExists(this._router); - } - - get publishRedraw(): () => void { - return this._publishRedraw || (() => {}); - } - - set publishRedraw(f: () => void) { - this._publishRedraw = f; - } - - get state(): State { - return assertExists(this._state); - } - - set state(state: State) { - this._state = assertExists(state); - } - - get dispatch(): Dispatch { - return assertExists(this._dispatch); - } - - dispatchMultiple(actions: DeferredAction[]): void { - const dispatch = this.dispatch; - for (const action of actions) { - dispatch(action); - } - } - - get frontendLocalState() { - return assertExists(this._frontendLocalState); - } - - get rafScheduler() { - return assertExists(this._rafScheduler); - } - - get logging() { - return assertExists(this._logging); - } - - get serviceWorkerController() { - return assertExists(this._serviceWorkerController); - } - - // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads. - get overviewStore(): OverviewStore { - return assertExists(this._overviewStore); - } - - get trackDataStore(): TrackDataStore { - return assertExists(this._trackDataStore); - } - - get queryResults(): QueryResultsStore { - return assertExists(this._queryResults); - } - - get threads() { - return assertExists(this._threadMap); - } - - get sliceDetails() { - return assertExists(this._sliceDetails); - } - - set sliceDetails(click: SliceDetails) { - this._sliceDetails = assertExists(click); - } - - get threadStateDetails() { - return assertExists(this._threadStateDetails); - } - - set threadStateDetails(click: ThreadStateDetails) { - this._threadStateDetails = assertExists(click); - } - - get connectedFlows() { - return assertExists(this._connectedFlows); - } - - set connectedFlows(connectedFlows: Flow[]) { - this._connectedFlows = assertExists(connectedFlows); - } - - get selectedFlows() { - return assertExists(this._selectedFlows); - } - - set selectedFlows(selectedFlows: Flow[]) { - this._selectedFlows = assertExists(selectedFlows); - } - - get visibleFlowCategories() { - return assertExists(this._visibleFlowCategories); - } - - set visibleFlowCategories(visibleFlowCategories: Map) { - this._visibleFlowCategories = assertExists(visibleFlowCategories); - } - - get counterDetails() { - return assertExists(this._counterDetails); - } - - set counterDetails(click: CounterDetails) { - this._counterDetails = assertExists(click); - } - - get aggregateDataStore(): AggregateDataStore { - return assertExists(this._aggregateDataStore); - } - - get flamegraphDetails() { - return assertExists(this._flamegraphDetails); - } - - set flamegraphDetails(click: FlamegraphDetails) { - this._flamegraphDetails = assertExists(click); - } - - get traceErrors() { - return this._traceErrors; - } - - setTraceErrors(arg: number) { - this._traceErrors = arg; - } - - get metricError() { - return this._metricError; - } - - setMetricError(arg: string) { - this._metricError = arg; - } - - get metricResult() { - return this._metricResult; - } - - setMetricResult(result: MetricResult) { - this._metricResult = result; - } - - get cpuProfileDetails() { - return assertExists(this._cpuProfileDetails); - } - - set cpuProfileDetails(click: CpuProfileDetails) { - this._cpuProfileDetails = assertExists(click); - } - - set numQueuedQueries(value: number) { - this._numQueriesQueued = value; - } - - get numQueuedQueries() { - return this._numQueriesQueued; - } - - get bufferUsage() { - return this._bufferUsage; - } - - get recordingLog() { - return this._recordingLog; - } - - get currentSearchResults() { - return this._currentSearchResults; - } - - set currentSearchResults(results: CurrentSearchResults) { - this._currentSearchResults = results; - } - - get hasFtrace(): boolean { - return Boolean(this._ftraceCounters && this._ftraceCounters.length > 0); - } - - get ftraceCounters(): FtraceStat[]|undefined { - return this._ftraceCounters; - } - - set ftraceCounters(value: FtraceStat[]|undefined) { - this._ftraceCounters = value; - } - - getConversionJobStatus(name: ConversionJobName): ConversionJobStatus { - return this.getJobStatusMap().get(name) || ConversionJobStatus.NotRunning; - } - - setConversionJobStatus(name: ConversionJobName, status: ConversionJobStatus) { - const map = this.getJobStatusMap(); - if (status === ConversionJobStatus.NotRunning) { - map.delete(name); - } else { - map.set(name, status); - } - } - - private getJobStatusMap(): Map { - if (!this._jobStatus) { - this._jobStatus = new Map(); - } - return this._jobStatus; - } - - get embeddedMode(): boolean { - return !!this._embeddedMode; - } - - set embeddedMode(value: boolean) { - this._embeddedMode = value; - } - - get hideSidebar(): boolean { - return !!this._hideSidebar; - } - - set hideSidebar(value: boolean) { - this._hideSidebar = value; - } - - setBufferUsage(bufferUsage: number) { - this._bufferUsage = bufferUsage; - } - - setTrackData(id: string, data: {}) { - this.trackDataStore.set(id, data); - } - - setRecordingLog(recordingLog: string) { - this._recordingLog = recordingLog; - } - - setAggregateData(kind: string, data: AggregateData) { - this.aggregateDataStore.set(kind, data); - } - - getCurResolution() { - // Truncate the resolution to the closest power of 2 (in nanosecond space). - // We choose to work in ns space because resolution is consumed be track - // controllers for quantization and they rely on resolution to be a power - // of 2 in nanosecond form. This is property does not hold if we work in - // second space. - // - // This effectively means the resolution changes approximately every 6 zoom - // levels. Logic: each zoom level represents a delta of 0.1 * (visible - // window span). Therefore, zooming out by six levels is 1.1^6 ~= 2. - // Similarily, zooming in six levels is 0.9^6 ~= 0.5. - const pxToSec = this.frontendLocalState.timeScale.deltaPxToDuration(1); - // TODO(b/186265930): Remove once fixed: - if (!isFinite(pxToSec)) { - // Resolution is in pixels per second so 1000 means 1px = 1ms. - console.error(`b/186265930: Bad pxToSec suppressed ${pxToSec}`); - return fromNs(Math.pow(2, Math.floor(Math.log2(toNs(1000))))); - } - const pxToNs = Math.max(toNs(pxToSec), 1); - const resolution = fromNs(Math.pow(2, Math.floor(Math.log2(pxToNs)))); - const log2 = Math.log2(toNs(resolution)); - if (log2 % 1 !== 0) { - throw new Error(`Resolution should be a power of two. - pxToSec: ${pxToSec}, - pxToNs: ${pxToNs}, - resolution: ${resolution}, - log2: ${Math.log2(toNs(resolution))}`); - } - return resolution; - } - - getCurrentEngine(): EngineConfig|undefined { - return this.state.engine; - } - - get ftracePanelData(): FtracePanelData|undefined { - return this._ftracePanelData; - } - - set ftracePanelData(data: FtracePanelData|undefined) { - this._ftracePanelData = data; - } - - makeSelection(action: DeferredAction<{}>, tabToOpen = 'current_selection') { - // A new selection should cancel the current search selection. - globals.dispatch(Actions.setSearchIndex({index: -1})); - const tab = action.type === 'deselect' ? undefined : tabToOpen; - globals.dispatch(Actions.setCurrentTab({tab})); - globals.dispatch(action); - } - - resetForTesting() { - this._dispatch = undefined; - this._state = undefined; - this._frontendLocalState = undefined; - this._rafScheduler = undefined; - this._serviceWorkerController = undefined; - - // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads. - this._trackDataStore = undefined; - this._queryResults = undefined; - this._overviewStore = undefined; - this._threadMap = undefined; - this._sliceDetails = undefined; - this._threadStateDetails = undefined; - this._aggregateDataStore = undefined; - this._numQueriesQueued = 0; - this._metricResult = undefined; - this._currentSearchResults = { - sliceIds: new Float64Array(0), - tsStarts: new Float64Array(0), - utids: new Float64Array(0), - trackIds: [], - sources: [], - totalResults: 0, - }; - } - - // This variable is set by the is_internal_user.js script if the user is a - // googler. This is used to avoid exposing features that are not ready yet - // for public consumption. The gated features themselves are not secret. - // If a user has been detected as a Googler once, make that sticky in - // localStorage, so that we keep treating them as such when they connect over - // public networks. - get isInternalUser() { - if (this._isInternalUser === undefined) { - this._isInternalUser = localStorage.getItem('isInternalUser') === '1'; - } - return this._isInternalUser; - } - - set isInternalUser(value: boolean) { - localStorage.setItem('isInternalUser', value ? '1' : '0'); - this._isInternalUser = value; - } - - get testing() { - return this._testing; - } - - // Used when switching to the legacy TraceViewer UI. - // Most resources are cleaned up by replacing the current |window| object, - // however pending RAFs and workers seem to outlive the |window| and need to - // be cleaned up explicitly. - shutdown() { - this._rafScheduler!.shutdown(); - } -} - -export const globals = new Globals(); diff --git a/third_party/perfetto/ui/src/frontend/gridline_helper.ts b/third_party/perfetto/ui/src/frontend/gridline_helper.ts deleted file mode 100644 index 1c9dbfec8c31..000000000000 --- a/third_party/perfetto/ui/src/frontend/gridline_helper.ts +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {assertTrue} from '../base/logging'; -import {roundDownNearest} from '../base/math_utils'; -import {TRACK_BORDER_COLOR, TRACK_SHELL_WIDTH} from './css_constants'; -import {globals} from './globals'; -import {TimeScale} from './time_scale'; - -// Returns the optimal step size (in seconds) and tick pattern of ticks within -// the step. The returned step size has two properties: (1) It is 1, 2, or 5, -// multiplied by some integer power of 10. (2) It is maximised given the -// constraint: |range| / stepSize <= |maxNumberOfSteps|. -export function getStepSize( - range: number, maxNumberOfSteps: number): [number, string] { - // First, get the largest possible power of 10 that is smaller than the - // desired step size, and use it as our initial step size. - // For example, if the range is 2345ms and the desired steps is 10, then the - // minimum step size is 234.5ms so the step size will initialise to 100. - const minStepSize = range / maxNumberOfSteps; - const zeros = Math.floor(Math.log10(minStepSize)); - const initialStepSize = Math.pow(10, zeros); - - // We know that |initialStepSize| is a power of 10, and - // initialStepSize <= desiredStepSize <= 10 * initialStepSize. There are four - // possible candidates for final step size: 1, 2, 5 or 10 * initialStepSize. - // For our example above, this would result in a step size of 500ms, as both - // 100ms and 200ms are smaller than the minimum step size of 234.5ms. - // We pick the candidate that minimizes the step size without letting the - // number of steps exceed |maxNumberOfSteps|. The factor we pick to also - // determines the pattern of ticks. This pattern is represented using a string - // where: - // | = Major tick - // : = Medium tick - // . = Minor tick - const stepSizeMultipliers: [number, string][] = - [[1, '|....:....'], [2, '|.:.'], [5, '|....'], [10, '|....:....']]; - - for (const [multiplier, pattern] of stepSizeMultipliers) { - const newStepSize = multiplier * initialStepSize; - const numberOfNewSteps = range / newStepSize; - if (numberOfNewSteps <= maxNumberOfSteps) { - return [newStepSize, pattern]; - } - } - - throw new Error('Something has gone horribly wrong with maths'); -} - -function tickPatternToArray(pattern: string): TickType[] { - const array = Array.from(pattern); - return array.map((char) => { - switch (char) { - case '|': - return TickType.MAJOR; - case ':': - return TickType.MEDIUM; - case '.': - return TickType.MINOR; - default: - // This is almost certainly a developer/fat-finger error - throw Error(`Invalid char "${char}" in pattern "${pattern}"`); - } - }); -} - -// Assuming a number only has one non-zero decimal digit, find the number of -// decimal places required to accurately print that number. I.e. the parameter -// we should pass to number.toFixed(x). To account for floating point -// innaccuracies when representing numbers in base-10, we only take the first -// nonzero fractional digit into account. E.g. -// 1.0 -> 0 -// 0.5 -> 1 -// 0.009 -> 3 -// 0.00007 -> 5 -// 30000 -> 0 -// 0.30000000000000004 -> 1 -export function guessDecimalPlaces(val: number): number { - const neglog10 = -Math.floor(Math.log10(val)); - const clamped = Math.max(0, neglog10); - return clamped; -} - -export enum TickType { - MAJOR, - MEDIUM, - MINOR -} - -export interface Tick { - type: TickType; - time: number; - position: number; -} - -const MIN_PX_PER_STEP = 80; - -// An iterable which generates a series of ticks for a given timescale. -export class TickGenerator implements Iterable { - private _tickPattern: TickType[]; - private _patternSize: number; - - constructor(private scale: TimeScale, {minLabelPx = MIN_PX_PER_STEP} = {}) { - assertTrue(minLabelPx > 0, 'minLabelPx cannot be lte 0'); - assertTrue(scale.widthPx > 0, 'widthPx cannot be lte 0'); - assertTrue( - scale.timeSpan.duration > 0, 'timeSpan.duration cannot be lte 0'); - - const desiredSteps = scale.widthPx / minLabelPx; - const [size, pattern] = getStepSize(scale.timeSpan.duration, desiredSteps); - this._patternSize = size; - this._tickPattern = tickPatternToArray(pattern); - } - - // Returns an iterable, so this object can be iterated over directly using the - // `for x of y` notation. The use of a generator here is just to make things - // more elegant than creating an array of ticks and building an iterator for - // it. - * [Symbol.iterator](): Generator { - const span = this.scale.timeSpan; - const stepSize = this._patternSize / this._tickPattern.length; - const start = roundDownNearest(span.start, this._patternSize); - const timeAtStep = (i: number) => start + (i * stepSize); - - // Iterating using steps instead of - // for (let s = start; s < span.end; s += stepSize) because if start is much - // larger than stepSize we can enter an infinite loop due to floating - // point precision errors. - for (let i = 0; timeAtStep(i) < span.end; i++) { - const time = timeAtStep(i); - if (time >= span.start) { - const position = Math.floor(this.scale.timeToPx(time)); - const type = this._tickPattern[i % this._tickPattern.length]; - yield {type, time, position}; - } - } - } - - // The number of decimal places labels should be printed with, assuming labels - // are only printed on major ticks. - get digits(): number { - return guessDecimalPlaces(this._patternSize); - } -} - -// Gets the timescale associated with the current visible window. -export function timeScaleForVisibleWindow( - startPx: number, endPx: number): TimeScale { - const span = globals.frontendLocalState.visibleWindowTime; - const spanRelative = span.add(-globals.state.traceTime.startSec); - return new TimeScale(spanRelative, [startPx, endPx]); -} - -export function drawGridLines( - ctx: CanvasRenderingContext2D, - width: number, - height: number): void { - ctx.strokeStyle = TRACK_BORDER_COLOR; - ctx.lineWidth = 1; - - const timeScale = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, width); - if (timeScale.timeSpan.duration > 0 && timeScale.widthPx > 0) { - for (const {type, position} of new TickGenerator(timeScale)) { - if (type === TickType.MAJOR) { - ctx.beginPath(); - ctx.moveTo(position + 0.5, 0); - ctx.lineTo(position + 0.5, height); - ctx.stroke(); - } - } - } -} diff --git a/third_party/perfetto/ui/src/frontend/gridline_helper_unittest.ts b/third_party/perfetto/ui/src/frontend/gridline_helper_unittest.ts deleted file mode 100644 index 3b6dcacada35..000000000000 --- a/third_party/perfetto/ui/src/frontend/gridline_helper_unittest.ts +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {TimeSpan} from '../common/time'; - -import {getStepSize, Tick, TickGenerator, TickType} from './gridline_helper'; -import {TimeScale} from './time_scale'; - -const pattern1 = '|....:....'; -const pattern2 = '|.:.'; -const pattern5 = '|....'; -const timeScale = new TimeScale(new TimeSpan(0, 1), [1, 2]); - -test('gridline helper to have sensible step sizes', () => { - expect(getStepSize(10, 14)).toEqual([1, pattern1]); - expect(getStepSize(30, 14)).toEqual([5, pattern5]); - expect(getStepSize(60, 14)).toEqual([5, pattern5]); - expect(getStepSize(100, 14)).toEqual([10, pattern1]); - - expect(getStepSize(10, 21)).toEqual([0.5, pattern5]); - expect(getStepSize(30, 21)).toEqual([2, pattern2]); - expect(getStepSize(60, 21)).toEqual([5, pattern5]); - expect(getStepSize(100, 21)).toEqual([5, pattern5]); - - expect(getStepSize(10, 3)).toEqual([5, pattern5]); - expect(getStepSize(30, 3)).toEqual([10, pattern1]); - expect(getStepSize(60, 3)).toEqual([20, pattern2]); - expect(getStepSize(100, 3)).toEqual([50, pattern5]); - - expect(getStepSize(800, 4)).toEqual([200, pattern2]); -}); - -test('gridline helper to scale to very small and very large values', () => { - expect(getStepSize(.01, 14)).toEqual([.001, pattern1]); - expect(getStepSize(10000, 14)).toEqual([1000, pattern1]); -}); - -test('gridline helper to always return a reasonable number of steps', () => { - for (let i = 1; i <= 1000; i++) { - const [stepSize, _] = getStepSize(i, 14); - expect(Math.round(i / stepSize)).toBeGreaterThanOrEqual(6); - expect(Math.round(i / stepSize)).toBeLessThanOrEqual(14); - } -}); - -describe('TickGenerator with range 0.0-1.0 and room for 2 labels', () => { - let tickGen: TickGenerator|undefined = undefined; - beforeAll(() => { - const timeSpan = new TimeSpan(0.0, 1.0); - const timeScale = new TimeScale(timeSpan, [0, 200]); - tickGen = new TickGenerator(timeScale, {minLabelPx: 100}); - }); - it('should produce major ticks at 0.5s and minor ticks at 0.1s starting at 0', - () => { - const expected = [ - {type: TickType.MAJOR, time: 0.0}, - {type: TickType.MINOR, time: 0.1}, - {type: TickType.MINOR, time: 0.2}, - {type: TickType.MINOR, time: 0.3}, - {type: TickType.MINOR, time: 0.4}, - {type: TickType.MAJOR, time: 0.5}, - {type: TickType.MINOR, time: 0.6}, - {type: TickType.MINOR, time: 0.7}, - {type: TickType.MINOR, time: 0.8}, - {type: TickType.MINOR, time: 0.9}, - ]; - const actual = Array.from(tickGen!); - expectTicksEqual(actual, expected); - }); - it('should tell us to use 1 decimal place for labels', () => { - expect(tickGen!.digits).toEqual(1); - }); -}); - -describe('TickGenerator with range 0.3-1.3 and room for 2 labels', () => { - let tickGen: TickGenerator|undefined = undefined; - beforeAll(() => { - const timeSpan = new TimeSpan(0.3, 1.3); - const timeScale = new TimeScale(timeSpan, [0, 200]); - tickGen = new TickGenerator(timeScale, {minLabelPx: 100}); - }); - it('should produce major ticks at 0.5s and minor ticks at 0.1s starting at 0', - () => { - const expected = [ - {type: TickType.MINOR, time: 0.3}, - {type: TickType.MINOR, time: 0.4}, - {type: TickType.MAJOR, time: 0.5}, - {type: TickType.MINOR, time: 0.6}, - {type: TickType.MINOR, time: 0.7}, - {type: TickType.MINOR, time: 0.8}, - {type: TickType.MINOR, time: 0.9}, - {type: TickType.MAJOR, time: 1.0}, - {type: TickType.MINOR, time: 1.1}, - {type: TickType.MINOR, time: 1.2}, - ]; - const actual = Array.from(tickGen!); - expectTicksEqual(actual, expected); - }); - it('should tell us to use 1 decimal place for labels', () => { - expect(tickGen!.digits).toEqual(1); - }); -}); - -describe('TickGenerator with range 0.0-0.2 and room for 1 label', () => { - let tickGen: TickGenerator|undefined = undefined; - beforeAll(() => { - const timeSpan = new TimeSpan(0.0, 0.2); - const timeScale = new TimeScale(timeSpan, [0, 100]); - tickGen = new TickGenerator(timeScale, {minLabelPx: 100}); - }); - it('should produce major ticks at 0.2s and minor ticks at 0.1s starting at 0', - () => { - const expected = [ - {type: TickType.MAJOR, time: 0.0}, - {type: TickType.MINOR, time: 0.05}, - {type: TickType.MEDIUM, time: 0.1}, - {type: TickType.MINOR, time: 0.15}, - ]; - const actual = Array.from(tickGen!); - expectTicksEqual(actual, expected); - }); - it('should tell us to use 1 decimal place for labels', () => { - expect(tickGen!.digits).toEqual(1); - }); -}); - -describe('TickGenerator with range 0.0-0.1 and room for 1 label', () => { - let tickGen: TickGenerator|undefined = undefined; - beforeAll(() => { - const timeSpan = new TimeSpan(0.0, 0.1); - const timeScale = new TimeScale(timeSpan, [0, 100]); - tickGen = new TickGenerator(timeScale, {minLabelPx: 100}); - }); - it('should produce major ticks at 0.1s & minor ticks at 0.02s starting at 0', - () => { - const expected = [ - {type: TickType.MAJOR, time: 0.0}, - {type: TickType.MINOR, time: 0.01}, - {type: TickType.MINOR, time: 0.02}, - {type: TickType.MINOR, time: 0.03}, - {type: TickType.MINOR, time: 0.04}, - {type: TickType.MEDIUM, time: 0.05}, - {type: TickType.MINOR, time: 0.06}, - {type: TickType.MINOR, time: 0.07}, - {type: TickType.MINOR, time: 0.08}, - {type: TickType.MINOR, time: 0.09}, - ]; - const actual = Array.from(tickGen!); - expect(tickGen!.digits).toEqual(1); - expectTicksEqual(actual, expected); - }); - it('should tell us to use 1 decimal place for labels', () => { - expect(tickGen!.digits).toEqual(1); - }); -}); - -describe('TickGenerator with a very small timespan', () => { - let tickGen: TickGenerator|undefined = undefined; - beforeAll(() => { - const timeSpan = new TimeSpan(0.0, 1e-9); - const timeScale = new TimeScale(timeSpan, [0, 100]); - tickGen = new TickGenerator(timeScale, {minLabelPx: 100}); - }); - it('should generate minor ticks at 2e-10s and one major tick at the start', - () => { - const expected = [ - {type: TickType.MAJOR, time: 0.0}, - {type: TickType.MINOR, time: 1e-10}, - {type: TickType.MINOR, time: 2e-10}, - {type: TickType.MINOR, time: 3e-10}, - {type: TickType.MINOR, time: 4e-10}, - {type: TickType.MEDIUM, time: 5e-10}, - {type: TickType.MINOR, time: 6e-10}, - {type: TickType.MINOR, time: 7e-10}, - {type: TickType.MINOR, time: 8e-10}, - {type: TickType.MINOR, time: 9e-10}, - ]; - const actual = Array.from(tickGen!); - expectTicksEqual(actual, expected); - }); - it('should tell us to use 9 decimal places for labels', () => { - expect(tickGen!.digits).toEqual(9); - }); -}); - -describe('TickGenerator with a very large timespan', () => { - let tickGen: TickGenerator|undefined = undefined; - beforeAll(() => { - const timeSpan = new TimeSpan(0.0, 1e9); - const timeScale = new TimeScale(timeSpan, [0, 100]); - tickGen = new TickGenerator(timeScale, {minLabelPx: 100}); - }); - it('should generate minor ticks at 2e8 and one major tick at the start', - () => { - const expected = [ - {type: TickType.MAJOR, time: 0.0}, - {type: TickType.MINOR, time: 1e8}, - {type: TickType.MINOR, time: 2e8}, - {type: TickType.MINOR, time: 3e8}, - {type: TickType.MINOR, time: 4e8}, - {type: TickType.MEDIUM, time: 5e8}, - {type: TickType.MINOR, time: 6e8}, - {type: TickType.MINOR, time: 7e8}, - {type: TickType.MINOR, time: 8e8}, - {type: TickType.MINOR, time: 9e8}, - ]; - const actual = Array.from(tickGen!); - expectTicksEqual(actual, expected); - }); - it('should tell us to use 0 decimal places for labels', () => { - expect(tickGen!.digits).toEqual(0); - }); -}); - -describe('TickGenerator where the timespan has a dynamic range of 1e12', () => { - // This is the equivalent of zooming in to the nanosecond level, 1000 seconds - // into a trace Note: this is about the limit of what this generator can - // handle. - let tickGen: TickGenerator|undefined = undefined; - beforeAll(() => { - const timeSpan = new TimeSpan(1000, 1000.000000001); - const timeScale = new TimeScale(timeSpan, [0, 100]); - tickGen = new TickGenerator(timeScale, {minLabelPx: 100}); - }); - it('should generate minor ticks at 1e-10s and one major tick at the start', - () => { - const expected = [ - {type: TickType.MAJOR, time: 1000.0000000000}, - {type: TickType.MINOR, time: 1000.0000000001}, - {type: TickType.MINOR, time: 1000.0000000002}, - {type: TickType.MINOR, time: 1000.0000000003}, - {type: TickType.MINOR, time: 1000.0000000004}, - {type: TickType.MEDIUM, time: 1000.0000000005}, - {type: TickType.MINOR, time: 1000.0000000006}, - {type: TickType.MINOR, time: 1000.0000000007}, - {type: TickType.MINOR, time: 1000.0000000008}, - {type: TickType.MINOR, time: 1000.0000000009}, - ]; - const actual = Array.from(tickGen!); - expectTicksEqual(actual, expected); - }); - it('should tell us to use 9 decimal places for labels', () => { - expect(tickGen!.digits).toEqual(9); - }); -}); - -describe( - 'TickGenerator where the timespan has a ridiculously huge dynamic range', - () => { - // We don't expect this to work, just wanna make sure it doesn't crash or - // get stuck - it('should not crash or get stuck in an infinite loop', () => { - const timeSpan = new TimeSpan(1000, 1000.000000000001); - const timeScale = new TimeScale(timeSpan, [0, 100]); - new TickGenerator(timeScale); - }); - }); - -describe( - 'TickGenerator where the timespan has a ridiculously huge dynamic range', - () => { - // We don't expect this to work, just wanna make sure it doesn't crash or - // get stuck - it('should not crash or get stuck in an infinite loop', () => { - const timeSpan = new TimeSpan(1000, 1000.000000000001); - const timeScale = new TimeScale(timeSpan, [0, 100]); - new TickGenerator(timeScale); - }); - }); - -test('TickGenerator constructed with a 0 width throws an error', () => { - expect(() => { - const timeScale = new TimeScale(new TimeSpan(0.0, 1.0), [0, 0]); - new TickGenerator(timeScale); - }).toThrow(Error); -}); - -test( - 'TickGenerator constructed with desiredPxPerStep of 0 throws an error', - () => { - expect(() => { - new TickGenerator(timeScale, {minLabelPx: 0}); - }).toThrow(Error); - }); - -test('TickGenerator constructed with a 0 duration throws an error', () => { - expect(() => { - const timeScale = new TimeScale(new TimeSpan(0.0, 0.0), [0, 1]); - new TickGenerator(timeScale); - }).toThrow(Error); -}); - -function expectTicksEqual(actual: Tick[], expected: any[]) { - // TODO(stevegolton) We could write a custom matcher for this; this approach - // produces cryptic error messages. - expect(actual.length).toEqual(expected.length); - for (let i = 0; i < actual.length; ++i) { - const ex = expected[i]; - const ac = actual[i]; - expect(ac.type).toEqual(ex.type); - expect(ac.time).toBeCloseTo(ex.time, 9); - } -} diff --git a/third_party/perfetto/ui/src/frontend/help_modal.ts b/third_party/perfetto/ui/src/frontend/help_modal.ts deleted file mode 100644 index 079c1c6ea069..000000000000 --- a/third_party/perfetto/ui/src/frontend/help_modal.ts +++ /dev/null @@ -1,191 +0,0 @@ - -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {globals} from './globals'; -import { - KeyboardLayoutMap, - nativeKeyboardLayoutMap, - NotSupportedError, -} from './keyboard_layout_map'; -import {showModal} from './modal'; -import {KeyMapping} from './pan_and_zoom_handler'; -import {Spinner} from './widgets/spinner'; - -export function toggleHelp() { - globals.logging.logEvent('User Actions', 'Show help'); - showHelp(); -} - -function keycap(glyph: m.Children): m.Children { - return m('.keycap', glyph); -} - -// A fallback keyboard map based on the QWERTY keymap. Converts keyboard event -// codes to their associated glyphs on an English QWERTY keyboard. -class EnglishQwertyKeyboardLayoutMap implements KeyboardLayoutMap { - get(code: string): string { - // Converts 'KeyX' -> 'x' - return code.replace(/^Key([A-Z])$/, '$1').toLowerCase(); - } -} - -class KeyMappingsHelp implements m.ClassComponent { - private keyMap?: KeyboardLayoutMap; - - oninit() { - nativeKeyboardLayoutMap() - .then((keyMap: KeyboardLayoutMap) => { - this.keyMap = keyMap; - globals.rafScheduler.scheduleFullRedraw(); - }) - .catch((e) => { - if (e instanceof NotSupportedError || - e.toString().includes('SecurityError')) { - // Keyboard layout is unavailable. Since showing the keyboard - // mappings correct for the user's keyboard layout is a nice-to- - // have, and users with non-QWERTY layouts are usually aware of the - // fact that the are using non-QWERTY layouts, we resort to showing - // English QWERTY mappings as a best-effort approach. - // The alternative would be to show key mappings for all keyboard - // layouts which is not feasible. - this.keyMap = new EnglishQwertyKeyboardLayoutMap(); - globals.rafScheduler.scheduleFullRedraw(); - } else { - // Something unexpected happened. Either the browser doesn't conform - // to the keyboard API spec, or the keyboard API spec has changed! - throw e; - } - }); - } - - view(_: m.Vnode): m.Children { - const ctrlOrCmd = - window.navigator.platform.indexOf('Mac') !== -1 ? 'Cmd' : 'Ctrl'; - - const queryPageInstructions = globals.hideSidebar ? [] : [ - m('h2', 'Making SQL queries from the query page'), - m('table', - m('tr', - m('td', keycap('Ctrl'), ' + ', keycap('Enter')), - m('td', 'Execute query')), - m('tr', - m('td', keycap('Ctrl'), ' + ', keycap('Enter'), ' (with selection)'), - m('td', 'Execute selection'))), - ]; - - const sidebarInstructions = globals.hideSidebar ? - [] : - [m('tr', - m('td', keycap(ctrlOrCmd), ' + ', keycap('b')), - m('td', 'Toggle display of sidebar'))]; - - return m( - '.help', - m('h2', 'Navigation'), - m( - 'table', - m( - 'tr', - m('td', - this.codeToKeycap(KeyMapping.KEY_ZOOM_IN), - '/', - this.codeToKeycap(KeyMapping.KEY_ZOOM_OUT)), - m('td', 'Zoom in/out'), - ), - m( - 'tr', - m('td', - this.codeToKeycap(KeyMapping.KEY_PAN_LEFT), - '/', - this.codeToKeycap(KeyMapping.KEY_PAN_RIGHT)), - m('td', 'Pan left/right'), - ), - ), - m('h2', 'Mouse Controls'), - m('table', - m('tr', m('td', 'Click'), m('td', 'Select event')), - m('tr', m('td', 'Ctrl + Scroll wheel'), m('td', 'Zoom in/out')), - m('tr', m('td', 'Click + Drag'), m('td', 'Select area')), - m('tr', m('td', 'Shift + Click + Drag'), m('td', 'Pan left/right'))), - m('h2', 'Making SQL queries from the viewer page'), - m('table', - m('tr', - m('td', keycap(':'), ' in the (empty) search box'), - m('td', 'Switch to query input')), - m('tr', m('td', keycap('Enter')), m('td', 'Execute query')), - m('tr', - m('td', keycap('Ctrl'), ' + ', keycap('Enter')), - m('td', - 'Execute query and pin output ' + - '(output will not be replaced by regular query input)'))), - ...queryPageInstructions, - m('h2', 'Other'), - m( - 'table', - m('tr', - m('td', keycap('f'), ' (with event selected)'), - m('td', 'Scroll + zoom to current selection')), - m('tr', - m('td', keycap('['), '/', keycap(']'), ' (with event selected)'), - m('td', - 'Select next/previous slice that is connected by a flow.', - m('br'), - 'If there are multiple flows,' + - 'the one that is in focus (bold) is selected')), - m('tr', - m('td', - keycap(ctrlOrCmd), - ' + ', - keycap('['), - '/', - keycap(']'), - ' (with event selected)'), - m('td', 'Switch focus to another flow')), - m('tr', - m('td', keycap('m'), ' (with event or area selected)'), - m('td', 'Mark the area (temporarily)')), - m('tr', - m('td', - keycap('Shift'), - ' + ', - keycap('m'), - ' (with event or area selected)'), - m('td', 'Mark the area (persistently)')), - m('tr', - m('td', keycap(ctrlOrCmd), ' + ', keycap('a')), - m('td', 'Select all')), - ...sidebarInstructions, - m('tr', m('td', keycap('?')), m('td', 'Show help')), - )); - } - - private codeToKeycap(code: string): m.Children { - if (this.keyMap) { - return keycap(this.keyMap.get(code)); - } else { - return keycap(m(Spinner)); - } - } -} - -function showHelp() { - showModal({ - title: 'Perfetto Help', - content: () => m(KeyMappingsHelp), - buttons: [], - }); -} diff --git a/third_party/perfetto/ui/src/frontend/home_page.ts b/third_party/perfetto/ui/src/frontend/home_page.ts deleted file mode 100644 index 11342b04a25b..000000000000 --- a/third_party/perfetto/ui/src/frontend/home_page.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {channelChanged, getNextChannel, setChannel} from '../common/channels'; - -import {globals} from './globals'; -import {createPage} from './pages'; - - -export const HomePage = createPage({ - view() { - return m( - '.page.home-page', - m( - '.home-page-center', - m('.home-page-title', 'Perfetto'), - m(`img.logo[src=${globals.root}assets/logo-3d.png]`), - m( - 'div.channel-select', - m('div', - 'Feeling adventurous? Try our bleeding edge Canary version'), - m( - 'fieldset', - mkChan('stable'), - mkChan('canary'), - m('.highlight'), - ), - m(`.home-page-reload${channelChanged() ? '.show' : ''}`, - 'You need to reload the page for the changes to have effect'), - ), - ), - m('a.privacy', - {href: 'https://policies.google.com/privacy', target: '_blank'}, - 'Privacy policy')); - }, -}); - -function mkChan(chan: string) { - const checked = getNextChannel() === chan ? '[checked=true]' : ''; - return [ - m(`input[type=radio][name=chan][id=chan_${chan}]${checked}`, { - onchange: () => { - setChannel(chan); - }, - }), - m(`label[for=chan_${chan}]`, chan), - ]; -} diff --git a/third_party/perfetto/ui/src/frontend/hsluv_cache.ts b/third_party/perfetto/ui/src/frontend/hsluv_cache.ts deleted file mode 100644 index 44cafa68ab0d..000000000000 --- a/third_party/perfetto/ui/src/frontend/hsluv_cache.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {hsluvToHex} from 'hsluv'; - -class HsluvCache { - storage = new Map(); - - get(hue: number, saturation: number, lightness: number): string { - const key = hue * 1e6 + saturation * 1e3 + lightness; - const value = this.storage.get(key); - - if (value === undefined) { - const computed = hsluvToHex([hue, saturation, lightness]); - this.storage.set(key, computed); - return computed; - } - - return value; - } -} - -const cache = new HsluvCache(); - -export function cachedHsluvToHex( - hue: number, saturation: number, lightness: number): string { - return cache.get(hue, saturation, lightness); -} diff --git a/third_party/perfetto/ui/src/frontend/icons.ts b/third_party/perfetto/ui/src/frontend/icons.ts deleted file mode 100644 index d30ba54ae18e..000000000000 --- a/third_party/perfetto/ui/src/frontend/icons.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export const BLANK_CHECKBOX = 'check_box_outline_blank'; -export const CHECKBOX = 'check_box'; -export const INDETERMINATE_CHECKBOX = 'indeterminate_check_box'; - -export const EXPAND_DOWN = 'expand_more'; -export const EXPAND_UP = 'expand_less'; - -export const PIN = 'push_pin'; - -export const LIBRARY_ADD_CHECK = 'library_add_check'; - -export const SELECT_ALL = 'select_all'; -export const DESELECT = 'deselect'; - -export const STAR = 'star'; diff --git a/third_party/perfetto/ui/src/frontend/index.ts b/third_party/perfetto/ui/src/frontend/index.ts deleted file mode 100644 index cef61e83331d..000000000000 --- a/third_party/perfetto/ui/src/frontend/index.ts +++ /dev/null @@ -1,366 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Need to turn off Long -import '../common/query_result'; - -import {Patch, produce} from 'immer'; -import m from 'mithril'; - -import {defer} from '../base/deferred'; -import {assertExists, reportError, setErrorHandler} from '../base/logging'; -import {Actions, DeferredAction, StateActions} from '../common/actions'; -import {createEmptyState} from '../common/empty_state'; -import {RECORDING_V2_FLAG} from '../common/feature_flags'; -import {initializeImmerJs} from '../common/immer_init'; -import {pluginManager, pluginRegistry} from '../common/plugins'; -import {onSelectionChanged} from '../common/selection_observer'; -import {State} from '../common/state'; -import {initWasm} from '../common/wasm_engine_proxy'; -import {initController, runControllers} from '../controller'; -import { - isGetCategoriesResponse, -} from '../controller/chrome_proxy_record_controller'; - -import {AnalyzePage} from './analyze_page'; -import {initCssConstants} from './css_constants'; -import {registerDebugGlobals} from './debug'; -import {maybeShowErrorDialog} from './error_dialog'; -import {installFileDropHandler} from './file_drop_handler'; -import {FlagsPage} from './flags_page'; -import {globals} from './globals'; -import {HomePage} from './home_page'; -import {initLiveReloadIfLocalhost} from './live_reload'; -import {MetricsPage} from './metrics_page'; -import {postMessageHandler} from './post_message_handler'; -import {RecordPage, updateAvailableAdbDevices} from './record_page'; -import {RecordPageV2} from './record_page_v2'; -import {Router} from './router'; -import {CheckHttpRpcConnection} from './rpc_http_dialog'; -import {TraceInfoPage} from './trace_info_page'; -import {maybeOpenTraceFromRoute} from './trace_url_handler'; -import {ViewerPage} from './viewer_page'; -import {WidgetsPage} from './widgets_page'; - -const EXTENSION_ID = 'lfmkphfpdbjijhpomgecfikhfohaoine'; - -class FrontendApi { - private state: State; - - constructor() { - this.state = createEmptyState(); - } - - dispatchMultiple(actions: DeferredAction[]) { - const oldState = this.state; - const patches: Patch[] = []; - for (const action of actions) { - const originalLength = patches.length; - const morePatches = this.applyAction(action); - patches.length += morePatches.length; - for (let i = 0; i < morePatches.length; ++i) { - patches[i + originalLength] = morePatches[i]; - } - } - - if (this.state === oldState) { - return; - } - - // Update overall state. - globals.state = this.state; - - // If the visible time in the global state has been updated more recently - // than the visible time handled by the frontend @ 60fps, update it. This - // typically happens when restoring the state from a permalink. - globals.frontendLocalState.mergeState(this.state.frontendLocalState); - - // Only redraw if something other than the frontendLocalState changed. - let key: keyof State; - for (key in this.state) { - if (key !== 'frontendLocalState' && key !== 'visibleTracks' && - oldState[key] !== this.state[key]) { - globals.rafScheduler.scheduleFullRedraw(); - break; - } - } - - if (this.state.currentSelection !== oldState.currentSelection) { - // TODO(altimin): Currently we are not triggering this when changing - // the set of selected tracks via toggling per-track checkboxes. - // Fix that. - onSelectionChanged( - this.state.currentSelection || undefined, - oldState.currentSelection || undefined); - } - - if (patches.length > 0) { - // Need to avoid reentering the controller so move this to a - // separate task. - setTimeout(() => { - runControllers(); - }, 0); - } - } - - private applyAction(action: DeferredAction): Patch[] { - const patches: Patch[] = []; - - // 'produce' creates a immer proxy which wraps the current state turning - // all imperative mutations of the state done in the callback into - // immutable changes to the returned state. - this.state = produce( - this.state, - (draft) => { - (StateActions as any)[action.type](draft, action.args); - }, - (morePatches, _) => { - const originalLength = patches.length; - patches.length += morePatches.length; - for (let i = 0; i < morePatches.length; ++i) { - patches[i + originalLength] = morePatches[i]; - } - }); - return patches; - } -} - -function setExtensionAvailability(available: boolean) { - globals.dispatch(Actions.setExtensionAvailable({ - available, - })); -} - -function initGlobalsFromQueryString() { - const queryString = window.location.search; - globals.embeddedMode = queryString.includes('mode=embedded'); - globals.hideSidebar = queryString.includes('hideSidebar=true'); -} - -function setupContentSecurityPolicy() { - // Note: self and sha-xxx must be quoted, urls data: and blob: must not. - const policy = { - 'default-src': [ - `'self'`, - // Google Tag Manager bootstrap. - `'sha256-LirUKeorCU4uRNtNzr8tlB11uy8rzrdmqHCX38JSwHY='`, - ], - 'script-src': [ - `'self'`, - // TODO(b/201596551): this is required for Wasm after crrev.com/c/3179051 - // and should be replaced with 'wasm-unsafe-eval'. - `'unsafe-eval'`, - 'https://*.google.com', - 'https://*.googleusercontent.com', - 'https://www.googletagmanager.com', - 'https://www.google-analytics.com', - ], - 'object-src': ['none'], - 'connect-src': [ - `'self'`, - 'http://127.0.0.1:9001', // For trace_processor_shell --httpd. - 'ws://127.0.0.1:9001', // Ditto, for the websocket RPC. - 'ws://127.0.0.1:8037', // For the adb websocket server. - 'https://www.google-analytics.com', - 'https://*.googleapis.com', // For Google Cloud Storage fetches. - 'blob:', - 'data:', - ], - 'img-src': [ - `'self'`, - 'data:', - 'blob:', - 'https://www.google-analytics.com', - 'https://www.googletagmanager.com', - ], - 'navigate-to': ['https://*.perfetto.dev', 'self'], - }; - const meta = document.createElement('meta'); - meta.httpEquiv = 'Content-Security-Policy'; - let policyStr = ''; - for (const [key, list] of Object.entries(policy)) { - policyStr += `${key} ${list.join(' ')}; `; - } - meta.content = policyStr; - document.head.appendChild(meta); -} - -function main() { - setupContentSecurityPolicy(); - - // Load the css. The load is asynchronous and the CSS is not ready by the time - // appenChild returns. - const cssLoadPromise = defer(); - const css = document.createElement('link'); - css.rel = 'stylesheet'; - css.href = globals.root + 'perfetto.css'; - css.onload = () => cssLoadPromise.resolve(); - css.onerror = (err) => cssLoadPromise.reject(err); - const favicon = document.head.querySelector('#favicon') as HTMLLinkElement; - if (favicon) favicon.href = globals.root + 'assets/favicon.png'; - - // Load the script to detect if this is a Googler (see comments on globals.ts) - // and initialize GA after that (or after a timeout if something goes wrong). - const script = document.createElement('script'); - script.src = - 'https://storage.cloud.google.com/perfetto-ui-internal/is_internal_user.js'; - script.async = true; - script.onerror = () => globals.logging.initialize(); - script.onload = () => globals.logging.initialize(); - setTimeout(() => globals.logging.initialize(), 5000); - - document.head.append(script, css); - - // Add Error handlers for JS error and for uncaught exceptions in promises. - setErrorHandler((err: string) => maybeShowErrorDialog(err)); - window.addEventListener('error', (e) => reportError(e)); - window.addEventListener('unhandledrejection', (e) => reportError(e)); - - const extensionLocalChannel = new MessageChannel(); - - initWasm(globals.root); - initializeImmerJs(); - initController(extensionLocalChannel.port1); - - const dispatch = (action: DeferredAction) => { - frontendApi.dispatchMultiple([action]); - }; - - const router = new Router({ - '/': HomePage, - '/viewer': ViewerPage, - '/record': RECORDING_V2_FLAG.get() ? RecordPageV2 : RecordPage, - '/query': AnalyzePage, - '/flags': FlagsPage, - '/metrics': MetricsPage, - '/info': TraceInfoPage, - '/widgets': WidgetsPage, - }); - router.onRouteChanged = (route) => { - globals.rafScheduler.scheduleFullRedraw(); - maybeOpenTraceFromRoute(route); - }; - - // This must be called before calling `globals.initialize` so that the - // `embeddedMode` global is set. - initGlobalsFromQueryString(); - - globals.initialize(dispatch, router); - globals.serviceWorkerController.install(); - - const frontendApi = new FrontendApi(); - globals.publishRedraw = () => globals.rafScheduler.scheduleFullRedraw(); - - // We proxy messages between the extension and the controller because the - // controller's worker can't access chrome.runtime. - const extensionPort = window.chrome && chrome.runtime ? - chrome.runtime.connect(EXTENSION_ID) : - undefined; - - setExtensionAvailability(extensionPort !== undefined); - - if (extensionPort) { - extensionPort.onDisconnect.addListener((_) => { - setExtensionAvailability(false); - void chrome.runtime.lastError; // Needed to not receive an error log. - }); - // This forwards the messages from the extension to the controller. - extensionPort.onMessage.addListener( - (message: object, _port: chrome.runtime.Port) => { - if (isGetCategoriesResponse(message)) { - globals.dispatch(Actions.setChromeCategories(message)); - return; - } - extensionLocalChannel.port2.postMessage(message); - }); - } - - // This forwards the messages from the controller to the extension - extensionLocalChannel.port2.onmessage = ({data}) => { - if (extensionPort) extensionPort.postMessage(data); - }; - - // Put debug variables in the global scope for better debugging. - registerDebugGlobals(); - - // Prevent pinch zoom. - document.body.addEventListener('wheel', (e: MouseEvent) => { - if (e.ctrlKey) e.preventDefault(); - }, {passive: false}); - - cssLoadPromise.then(() => onCssLoaded()); - - if (globals.testing) { - document.body.classList.add('testing'); - } - - // Initialize all plugins: - for (const plugin of pluginRegistry.values()) { - pluginManager.activatePlugin(plugin.pluginId); - } -} - - -function onCssLoaded() { - initCssConstants(); - // Clear all the contents of the initial page (e.g. the

 error message)
-  // And replace it with the root 
element which will be used by mithril. - document.body.innerHTML = '
'; - const main = assertExists(document.body.querySelector('main')); - globals.rafScheduler.domRedraw = () => { - m.render(main, globals.router.resolve()); - }; - - initLiveReloadIfLocalhost(); - - if (!RECORDING_V2_FLAG.get()) { - updateAvailableAdbDevices(); - try { - navigator.usb.addEventListener( - 'connect', () => updateAvailableAdbDevices()); - navigator.usb.addEventListener( - 'disconnect', () => updateAvailableAdbDevices()); - } catch (e) { - console.error('WebUSB API not supported'); - } - } - - // Will update the chip on the sidebar footer that notifies that the RPC is - // connected. Has no effect on the controller (which will repeat this check - // before creating a new engine). - // Don't auto-open any trace URLs until we get a response here because we may - // accidentially clober the state of an open trace processor instance - // otherwise. - CheckHttpRpcConnection().then(() => { - if (!globals.embeddedMode) { - installFileDropHandler(); - } - - // Don't allow postMessage or opening trace from route when the user says - // that they want to reuse the already loaded trace in trace processor. - const engine = globals.getCurrentEngine(); - if (engine && engine.source.type === 'HTTP_RPC') { - return; - } - - // Add support for opening traces from postMessage(). - window.addEventListener('message', postMessageHandler, {passive: true}); - - // Handles the initial ?local_cache_key=123 or ?s=permalink or ?url=... - // cases. - maybeOpenTraceFromRoute(Router.parseUrl(window.location.href)); - }); -} - -main(); diff --git a/third_party/perfetto/ui/src/frontend/keyboard_event_handler.ts b/third_party/perfetto/ui/src/frontend/keyboard_event_handler.ts deleted file mode 100644 index 7da04de6a0d4..000000000000 --- a/third_party/perfetto/ui/src/frontend/keyboard_event_handler.ts +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Actions} from '../common/actions'; -import {Area} from '../common/state'; - -import {Flow, globals} from './globals'; -import {toggleHelp} from './help_modal'; -import { - focusHorizontalRange, - verticalScrollToTrack, -} from './scroll_helper'; -import {executeSearch} from './search_handler'; - -const INSTANT_FOCUS_DURATION_S = 1 / 1e9; // 1 ns. -type Direction = 'Forward'|'Backward'; - -// Handles all key events than are not handled by the -// pan and zoom handler. Returns true if the event was handled. -export function handleKey(e: KeyboardEvent, down: boolean): boolean { - const key = e.key.toLowerCase(); - const selection = globals.state.currentSelection; - const noModifiers = !(e.ctrlKey || e.metaKey || e.altKey || e.shiftKey); - const ctrlOrMeta = (e.ctrlKey || e.metaKey) && !(e.altKey || e.shiftKey); - // No other modifiers other than possibly Shift. - const maybeShift = !(e.ctrlKey || e.metaKey || e.altKey); - - if (down && 'm' === key && maybeShift) { - if (selection && selection.kind === 'AREA') { - globals.dispatch(Actions.toggleMarkCurrentArea({persistent: e.shiftKey})); - } else if (selection) { - lockSliceSpan(e.shiftKey); - } - return true; - } - if (down && 'f' === key && noModifiers) { - findCurrentSelection(); - return true; - } - if (down && 'a' === key && ctrlOrMeta) { - let tracksToSelect: string[] = []; - - const selection = globals.state.currentSelection; - if (selection !== null && selection.kind === 'AREA') { - const area = globals.state.areas[selection.areaId]; - const coversEntireTimeRange = - globals.state.traceTime.startSec === area.startSec && - globals.state.traceTime.endSec === area.endSec; - if (!coversEntireTimeRange) { - // If the current selection is an area which does not cover the entire - // time range, preserve the list of selected tracks and expand the time - // range. - tracksToSelect = area.tracks; - } else { - // If the entire time range is already covered, update the selection to - // cover all tracks. - tracksToSelect = Object.keys(globals.state.tracks); - } - } else { - // If the current selection is not an area, select all. - tracksToSelect = Object.keys(globals.state.tracks); - } - globals.dispatch(Actions.selectArea({ - area: { - startSec: globals.state.traceTime.startSec, - endSec: globals.state.traceTime.endSec, - tracks: tracksToSelect, - }, - })); - e.preventDefault(); - return true; - } - if (down && 'b' === key && ctrlOrMeta) { - globals.dispatch(Actions.toggleSidebar({})); - return true; - } - if (down && '?' === key && maybeShift) { - toggleHelp(); - return true; - } - if (down && 'enter' === key && maybeShift) { - e.preventDefault(); - executeSearch(e.shiftKey); - return true; - } - if (down && 'escape' === key) { - globals.frontendLocalState.deselectArea(); - globals.makeSelection(Actions.deselect({})); - globals.dispatch(Actions.removeNote({id: '0'})); - return true; - } - if (down && ']' === key && ctrlOrMeta) { - focusOtherFlow('Forward'); - return true; - } - if (down && ']' === key && noModifiers) { - moveByFocusedFlow('Forward'); - return true; - } - if (down && '[' === key && ctrlOrMeta) { - focusOtherFlow('Backward'); - return true; - } - if (down && '[' === key && noModifiers) { - moveByFocusedFlow('Backward'); - return true; - } - return false; -} - -// Search |boundFlows| for |flowId| and return the id following it. -// Returns the first flow id if nothing was found or |flowId| was the last flow -// in |boundFlows|, and -1 if |boundFlows| is empty -function findAnotherFlowExcept(boundFlows: Flow[], flowId: number): number { - let selectedFlowFound = false; - - if (boundFlows.length === 0) { - return -1; - } - - for (const flow of boundFlows) { - if (selectedFlowFound) { - return flow.id; - } - - if (flow.id === flowId) { - selectedFlowFound = true; - } - } - return boundFlows[0].id; -} - -// Change focus to the next flow event (matching the direction) -function focusOtherFlow(direction: Direction) { - if (!globals.state.currentSelection || - globals.state.currentSelection.kind !== 'CHROME_SLICE') { - return; - } - const sliceId = globals.state.currentSelection.id; - if (sliceId === -1) { - return; - } - - const boundFlows = globals.connectedFlows.filter( - (flow) => flow.begin.sliceId === sliceId && direction === 'Forward' || - flow.end.sliceId === sliceId && direction === 'Backward'); - - if (direction === 'Backward') { - const nextFlowId = - findAnotherFlowExcept(boundFlows, globals.state.focusedFlowIdLeft); - globals.dispatch(Actions.setHighlightedFlowLeftId({flowId: nextFlowId})); - } else { - const nextFlowId = - findAnotherFlowExcept(boundFlows, globals.state.focusedFlowIdRight); - globals.dispatch(Actions.setHighlightedFlowRightId({flowId: nextFlowId})); - } -} - -// Select the slice connected to the flow in focus -function moveByFocusedFlow(direction: Direction): void { - if (!globals.state.currentSelection || - globals.state.currentSelection.kind !== 'CHROME_SLICE') { - return; - } - - const sliceId = globals.state.currentSelection.id; - const flowId = - (direction === 'Backward' ? globals.state.focusedFlowIdLeft : - globals.state.focusedFlowIdRight); - - if (sliceId === -1 || flowId === -1) { - return; - } - - // Find flow that is in focus and select corresponding slice - for (const flow of globals.connectedFlows) { - if (flow.id === flowId) { - const flowPoint = (direction === 'Backward' ? flow.begin : flow.end); - const uiTrackId = - globals.state.uiTrackIdByTraceTrackId[flowPoint.trackId]; - if (uiTrackId) { - globals.makeSelection(Actions.selectChromeSlice({ - id: flowPoint.sliceId, - trackId: uiTrackId, - table: 'slice', - scroll: true, - })); - } - } - } -} - -function findTimeRangeOfSelection(): {startTs: number, endTs: number} { - const selection = globals.state.currentSelection; - let startTs = -1; - let endTs = -1; - if (selection === null) { - return {startTs, endTs}; - } else if (selection.kind === 'SLICE' || selection.kind === 'CHROME_SLICE') { - const slice = globals.sliceDetails; - if (slice.ts && slice.dur !== undefined && slice.dur > 0) { - startTs = slice.ts + globals.state.traceTime.startSec; - endTs = startTs + slice.dur; - } else if (slice.ts) { - startTs = slice.ts + globals.state.traceTime.startSec; - // This will handle either: - // a)slice.dur === -1 -> unfinished slice - // b)slice.dur === 0 -> instant event - endTs = slice.dur === -1 ? globals.state.traceTime.endSec : - startTs + INSTANT_FOCUS_DURATION_S; - } - } else if (selection.kind === 'THREAD_STATE') { - const threadState = globals.threadStateDetails; - if (threadState.ts && threadState.dur) { - startTs = threadState.ts + globals.state.traceTime.startSec; - endTs = startTs + threadState.dur; - } - } else if (selection.kind === 'COUNTER') { - startTs = selection.leftTs; - endTs = selection.rightTs; - } else if (selection.kind === 'AREA') { - const selectedArea = globals.state.areas[selection.areaId]; - if (selectedArea) { - startTs = selectedArea.startSec; - endTs = selectedArea.endSec; - } - } else if (selection.kind === 'NOTE') { - const selectedNote = globals.state.notes[selection.id]; - // Notes can either be default or area notes. Area notes are handled - // above in the AREA case. - if (selectedNote && selectedNote.noteType === 'DEFAULT') { - startTs = selectedNote.timestamp; - endTs = selectedNote.timestamp + INSTANT_FOCUS_DURATION_S; - } - } else if (selection.kind === 'LOG') { - // TODO(hjd): Make focus selection work for logs. - } else if (selection.kind === 'DEBUG_SLICE') { - startTs = selection.startS; - if (selection.durationS > 0) { - endTs = startTs + selection.durationS; - } else { - endTs = startTs + INSTANT_FOCUS_DURATION_S; - } - } - - return {startTs, endTs}; -} - - -function lockSliceSpan(persistent = false) { - const range = findTimeRangeOfSelection(); - if (range.startTs !== -1 && range.endTs !== -1 && - globals.state.currentSelection !== null) { - const tracks = globals.state.currentSelection.trackId ? - [globals.state.currentSelection.trackId] : - []; - const area: Area = {startSec: range.startTs, endSec: range.endTs, tracks}; - globals.dispatch(Actions.markArea({area, persistent})); - } -} - -export function findCurrentSelection() { - const selection = globals.state.currentSelection; - if (selection === null) return; - - const range = findTimeRangeOfSelection(); - if (range.startTs !== -1 && range.endTs !== -1) { - focusHorizontalRange(range.startTs, range.endTs); - } - - if (selection.trackId) { - verticalScrollToTrack(selection.trackId, true); - } -} diff --git a/third_party/perfetto/ui/src/frontend/keyboard_layout_map.ts b/third_party/perfetto/ui/src/frontend/keyboard_layout_map.ts deleted file mode 100644 index d5d23174708b..000000000000 --- a/third_party/perfetto/ui/src/frontend/keyboard_layout_map.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// A keyboard layout map that converts key codes to their equivalent glyphs for -// a given keyboard layout (e.g. 'KeyX' -> 'x'). -export interface KeyboardLayoutMap { - get(code: string): string|undefined; -} - -export class NotSupportedError extends Error {} - -// Fetch the user's keyboard layout map. -// This function is merely a wrapper around the keyboard API, which throws a -// specific error when used in browsers that don't support it. -export async function nativeKeyboardLayoutMap(): Promise { - // Browser's that don't support the Keyboard API won't have a keyboard - // property in their window.navigator object. - // Note: it seems this is also what Chrome does when the website is accessed - // through an insecure connection. - if ('keyboard' in window.navigator) { - // Typescript's dom library doesn't know about this feature, so we must - // take some liberties when it comes to relaxing types - const keyboard = (window.navigator as any).keyboard; - return await keyboard.getLayoutMap(); - } else { - throw new NotSupportedError('Keyboard API is not supported'); - } -} diff --git a/third_party/perfetto/ui/src/frontend/legacy_trace_viewer.ts b/third_party/perfetto/ui/src/frontend/legacy_trace_viewer.ts deleted file mode 100644 index e5242a9ea715..000000000000 --- a/third_party/perfetto/ui/src/frontend/legacy_trace_viewer.ts +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; -import {inflate} from 'pako'; -import {assertTrue} from '../base/logging'; -import {globals} from './globals'; -import {showModal} from './modal'; - -const CTRACE_HEADER = 'TRACE:\n'; - -async function isCtrace(file: File): Promise { - const fileName = file.name.toLowerCase(); - - if (fileName.endsWith('.ctrace')) { - return true; - } - - // .ctrace files sometimes end with .txt. We can detect these via - // the presence of TRACE: near the top of the file. - if (fileName.endsWith('.txt')) { - const header = await readText(file.slice(0, 128)); - if (header.includes(CTRACE_HEADER)) { - return true; - } - } - - return false; -} - -function readText(blob: Blob): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => { - if (typeof reader.result === 'string') { - return resolve(reader.result); - } - }; - reader.onerror = (err) => { - reject(err); - }; - reader.readAsText(blob); - }); -} - -export async function isLegacyTrace(file: File): Promise { - const fileName = file.name.toLowerCase(); - if (fileName.endsWith('.json') || fileName.endsWith('.json.gz') || - fileName.endsWith('.zip') || fileName.endsWith('.html')) { - return true; - } - - if (await isCtrace(file)) { - return true; - } - - // Sometimes systrace formatted traces end with '.trace'. This is a - // little generic to assume all such traces are systrace format though - // so we read the beginning of the file and check to see if is has the - // systrace header (several comment lines): - if (fileName.endsWith('.trace')) { - const header = await readText(file.slice(0, 512)); - const lines = header.split('\n'); - let commentCount = 0; - for (const line of lines) { - if (line.startsWith('#')) { - commentCount++; - } - } - if (commentCount > 5) { - return true; - } - } - - return false; -} - -export async function openFileWithLegacyTraceViewer(file: File) { - const reader = new FileReader(); - reader.onload = () => { - if (reader.result instanceof ArrayBuffer) { - return openBufferWithLegacyTraceViewer( - file.name, reader.result, reader.result.byteLength); - } else { - const str = reader.result as string; - return openBufferWithLegacyTraceViewer(file.name, str, str.length); - } - }; - reader.onerror = (err) => { - console.error(err); - }; - if (file.name.endsWith('.gz') || file.name.endsWith('.zip') || - await isCtrace(file)) { - reader.readAsArrayBuffer(file); - } else { - reader.readAsText(file); - } -} - -export function openBufferWithLegacyTraceViewer( - name: string, data: ArrayBuffer|string, size: number) { - if (data instanceof ArrayBuffer) { - assertTrue(size <= data.byteLength); - if (size !== data.byteLength) { - data = data.slice(0, size); - } - - // Handle .ctrace files. - const enc = new TextDecoder('utf-8'); - const header = enc.decode(data.slice(0, 128)); - if (header.includes(CTRACE_HEADER)) { - const offset = header.indexOf(CTRACE_HEADER) + CTRACE_HEADER.length; - data = inflate(new Uint8Array(data.slice(offset)), {to: 'string'}); - } - } - - // The location.pathname mangling is to make this code work also when hosted - // in a non-root sub-directory, for the case of CI artifacts. - const catapultUrl = globals.root + 'assets/catapult_trace_viewer.html'; - const newWin = window.open(catapultUrl) as Window; - if (newWin) { - // Popup succeedeed. - newWin.addEventListener('load', (e: Event) => { - const doc = (e.target as Document); - const ctl = doc.querySelector('x-profiling-view') as TraceViewerAPI; - ctl.setActiveTrace(name, data); - }); - return; - } - - // Popup blocker detected. - showModal({ - title: 'Open trace in the legacy Catapult Trace Viewer', - content: m( - 'div', - m('div', 'You are seeing this interstitial because popups are blocked'), - m('div', 'Enable popups to skip this dialog next time.')), - buttons: [{ - text: 'Open legacy UI', - primary: true, - action: () => openBufferWithLegacyTraceViewer(name, data, size), - }], - }); -} - -// TraceViewer method that we wire up to trigger the file load. -interface TraceViewerAPI extends Element { - setActiveTrace(name: string, data: ArrayBuffer|string): void; -} diff --git a/third_party/perfetto/ui/src/frontend/live_reload.ts b/third_party/perfetto/ui/src/frontend/live_reload.ts deleted file mode 100644 index 387134dd40b5..000000000000 --- a/third_party/perfetto/ui/src/frontend/live_reload.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {featureFlags} from '../common/feature_flags'; -import {globals} from './globals'; - -let lastReloadDialogTime = 0; -const kMinTimeBetweenDialogsMs = 10000; -const changedPaths = new Set(); - -export function initLiveReloadIfLocalhost() { - if (!location.origin.startsWith('http://localhost:')) return; - if (globals.embeddedMode) return; - - const monitor = new EventSource('/live_reload'); - monitor.onmessage = (msg) => { - const change = msg.data; - console.log('Live reload:', change); - changedPaths.add(change); - if (change.endsWith('.css')) { - reloadCSS(); - } else if (change.endsWith('.html') || change.endsWith('.js')) { - reloadDelayed(); - } - }; - monitor.onerror = (err) => { - // In most cases the error is fired on reload, when the socket disconnects. - // Delay the error and the reconnection, so in the case of a reload we don't - // see any midleading message. - setTimeout(() => console.error('LiveReload SSE error', err), 1000); - }; -} - -function reloadCSS() { - const css = document.querySelector('link[rel=stylesheet]') as HTMLLinkElement; - if (!css) return; - const parent = css.parentElement!; - parent.removeChild(css); - parent.appendChild(css); -} - -const rapidReloadFlag = featureFlags.register({ - id: 'rapidReload', - name: 'Development: rapid live reload', - defaultValue: false, - description: 'During development, instantly reload the page on change. ' + - 'Enables lower latency of live reload at the cost of potential ' + - 'multiple re-reloads.', - devOnly: true, -}); - -function reloadDelayed() { - setTimeout(() => { - let pathsStr = ''; - for (const path of changedPaths) { - pathsStr += path + '\n'; - } - changedPaths.clear(); - if (Date.now() - lastReloadDialogTime < kMinTimeBetweenDialogsMs) return; - const reload = - rapidReloadFlag.get() || confirm(`${pathsStr}changed, click to reload`); - lastReloadDialogTime = Date.now(); - if (reload) { - window.location.reload(); - } - }, rapidReloadFlag.get() ? 0 : 1000); -} diff --git a/third_party/perfetto/ui/src/frontend/logs_filters.ts b/third_party/perfetto/ui/src/frontend/logs_filters.ts deleted file mode 100644 index 432913f91a1c..000000000000 --- a/third_party/perfetto/ui/src/frontend/logs_filters.ts +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {Actions} from '../common/actions'; -import {globals} from './globals'; - -export const LOG_PRIORITIES = - ['-', '-', 'Verbose', 'Debug', 'Info', 'Warn', 'Error', 'Fatal']; -const IGNORED_STATES = 2; - -interface LogPriorityWidgetAttrs { - options: string[]; - selectedIndex: number; - onSelect: (id: number) => void; -} - -interface LogTagChipAttrs { - name: string; - removeTag: (name: string) => void; -} - -interface LogTagsWidgetAttrs { - tags: string[]; -} - -interface FilterByTextWidgetAttrs { - hideNonMatching: boolean; -} - -class LogPriorityWidget implements m.ClassComponent { - view(vnode: m.Vnode) { - const attrs = vnode.attrs; - const optionComponents = []; - for (let i = IGNORED_STATES; i < attrs.options.length; i++) { - const selected = i === attrs.selectedIndex; - optionComponents.push( - m('option', {value: i, selected}, attrs.options[i])); - } - return m( - 'select', - { - onchange: (e: InputEvent) => { - const selectionValue = (e.target as HTMLSelectElement).value; - attrs.onSelect(Number(selectionValue)); - }, - }, - optionComponents, - ); - } -} - -class LogTagChip implements m.ClassComponent { - view({attrs}: m.CVnode) { - return m( - '.chip', - m('.chip-text', attrs.name), - m('button.chip-button', - { - onclick: () => { - attrs.removeTag(attrs.name); - }, - }, - '×')); - } -} - -class LogTagsWidget implements m.ClassComponent { - removeTag(tag: string) { - globals.dispatch(Actions.removeLogTag({tag})); - } - - view(vnode: m.Vnode) { - const tags = vnode.attrs.tags; - return m( - '.tag-container', - m('.chips', tags.map((tag) => m(LogTagChip, { - name: tag, - removeTag: this.removeTag.bind(this), - }))), - m(`input.chip-input[placeholder='Add new tag']`, { - onkeydown: (e: KeyboardEvent) => { - // This is to avoid zooming on 'w'(and other unexpected effects - // of key presses in this input field). - e.stopPropagation(); - const htmlElement = e.target as HTMLInputElement; - - // When the user clicks 'Backspace' we delete the previous tag. - if (e.key === 'Backspace' && tags.length > 0 && - htmlElement.value === '') { - globals.dispatch( - Actions.removeLogTag({tag: tags[tags.length - 1]})); - return; - } - - if (e.key !== 'Enter') { - return; - } - if (htmlElement.value === '') { - return; - } - globals.dispatch( - Actions.addLogTag({tag: htmlElement.value.trim()})); - htmlElement.value = ''; - }, - })); - } -} - -class LogTextWidget implements m.ClassComponent { - view() { - return m( - '.tag-container', m(`input.chip-input[placeholder='Search log text']`, { - onkeydown: (e: KeyboardEvent) => { - // This is to avoid zooming on 'w'(and other unexpected effects - // of key presses in this input field). - e.stopPropagation(); - }, - - onkeyup: (e: KeyboardEvent) => { - // We want to use the value of the input field after it has been - // updated with the latest key (onkeyup). - const htmlElement = e.target as HTMLInputElement; - globals.dispatch( - Actions.updateLogFilterText({textEntry: htmlElement.value})); - }, - })); - } -} - -class FilterByTextWidget implements m.ClassComponent { - view({attrs}: m.Vnode) { - const icon = attrs.hideNonMatching ? 'unfold_less' : 'unfold_more'; - const tooltip = attrs.hideNonMatching ? 'Expand all and view highlighted' : - 'Collapse all'; - return m( - '.filter-widget', - m('.tooltip', tooltip), - m('i.material-icons', - { - onclick: () => { - globals.dispatch(Actions.toggleCollapseByTextEntry({})); - }, - }, - icon)); - } -} - -export class LogsFilters implements m.ClassComponent { - view(_: m.CVnode<{}>) { - return m( - '.log-filters', - m('.log-label', 'Log Level'), - m(LogPriorityWidget, { - options: LOG_PRIORITIES, - selectedIndex: globals.state.logFilteringCriteria.minimumLevel, - onSelect: (minimumLevel) => { - globals.dispatch(Actions.setMinimumLogLevel({minimumLevel})); - }, - }), - m(LogTagsWidget, {tags: globals.state.logFilteringCriteria.tags}), - m(LogTextWidget), - m(FilterByTextWidget, { - hideNonMatching: globals.state.logFilteringCriteria.hideNonMatching, - })); - } -} diff --git a/third_party/perfetto/ui/src/frontend/logs_panel.ts b/third_party/perfetto/ui/src/frontend/logs_panel.ts deleted file mode 100644 index 18ed325ba938..000000000000 --- a/third_party/perfetto/ui/src/frontend/logs_panel.ts +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {assertExists} from '../base/logging'; -import {Actions} from '../common/actions'; -import { - LogBounds, - LogBoundsKey, - LogEntries, - LogEntriesKey, -} from '../common/logs'; -import {formatTimestamp} from '../common/time'; -import {TimeSpan} from '../common/time'; - -import {SELECTED_LOG_ROWS_COLOR} from './css_constants'; -import {globals} from './globals'; -import {LOG_PRIORITIES, LogsFilters} from './logs_filters'; -import {Panel} from './panel'; - -const ROW_H = 20; - -export class LogPanel extends Panel<{}> { - private scrollContainer?: HTMLElement; - private bounds?: LogBounds; - private entries?: LogEntries; - - private visibleRowOffset = 0; - private visibleRowCount = 0; - - recomputeVisibleRowsAndUpdate() { - const scrollContainer = assertExists(this.scrollContainer); - - const prevOffset = this.visibleRowOffset; - const prevCount = this.visibleRowCount; - this.visibleRowOffset = Math.floor(scrollContainer.scrollTop / ROW_H); - this.visibleRowCount = Math.ceil(scrollContainer.clientHeight / ROW_H); - - if (this.visibleRowOffset !== prevOffset || - this.visibleRowCount !== prevCount) { - globals.dispatch(Actions.updateLogsPagination({ - offset: this.visibleRowOffset, - count: this.visibleRowCount, - })); - } - } - - oncreate({dom}: m.CVnodeDOM) { - this.scrollContainer = assertExists( - dom.parentElement!.parentElement!.parentElement as HTMLElement); - this.scrollContainer.addEventListener( - 'scroll', this.onScroll.bind(this), {passive: true}); - this.bounds = globals.trackDataStore.get(LogBoundsKey) as LogBounds; - this.entries = globals.trackDataStore.get(LogEntriesKey) as LogEntries; - this.recomputeVisibleRowsAndUpdate(); - } - - onbeforeupdate(_: m.CVnodeDOM) { - this.bounds = globals.trackDataStore.get(LogBoundsKey) as LogBounds; - this.entries = globals.trackDataStore.get(LogEntriesKey) as LogEntries; - this.recomputeVisibleRowsAndUpdate(); - } - - onScroll() { - if (this.scrollContainer === undefined) return; - this.recomputeVisibleRowsAndUpdate(); - globals.rafScheduler.scheduleFullRedraw(); - } - - onRowOver(ts: number) { - globals.dispatch(Actions.setHoverCursorTimestamp({ts})); - } - - onRowOut() { - globals.dispatch(Actions.setHoverCursorTimestamp({ts: -1})); - } - - private totalRows(): - {isStale: boolean, total: number, offset: number, count: number} { - if (!this.bounds) { - return {isStale: false, total: 0, offset: 0, count: 0}; - } - const {total, startTs, endTs, firstRowTs, lastRowTs} = this.bounds; - const vis = globals.frontendLocalState.visibleWindowTime; - const leftSpan = new TimeSpan(startTs, firstRowTs); - const rightSpan = new TimeSpan(lastRowTs, endTs); - - const isStaleLeft = !leftSpan.isInBounds(vis.start); - const isStaleRight = !rightSpan.isInBounds(vis.end); - const isStale = isStaleLeft || isStaleRight; - const offset = Math.min(this.visibleRowOffset, total); - const visCount = Math.min(total - offset, this.visibleRowCount); - return {isStale, total, count: visCount, offset}; - } - - view(_: m.CVnode<{}>) { - const {isStale, total, offset, count} = this.totalRows(); - - const hasProcessNames = this.entries && - this.entries.processName.filter((name) => name).length > 0; - - const rows: m.Children = []; - rows.push( - m(`.row`, - m('.cell.row-header', 'Timestamp'), - m('.cell.row-header', 'Level'), - m('.cell.row-header', 'Tag'), - hasProcessNames ? m('.cell.with-process.row-header', 'Process name') : - undefined, - hasProcessNames ? m('.cell.with-process.row-header', 'Message') : - m('.cell.no-process.row-header', 'Message'), - m('br'))); - if (this.entries) { - const offset = this.entries.offset; - const timestamps = this.entries.timestamps; - const priorities = this.entries.priorities; - const tags = this.entries.tags; - const messages = this.entries.messages; - const processNames = this.entries.processName; - for (let i = 0; i < this.entries.timestamps.length; i++) { - const priorityLetter = LOG_PRIORITIES[priorities[i]][0]; - const ts = timestamps[i]; - const prioClass = priorityLetter || ''; - const style: {top: string, backgroundColor?: string} = { - // 1.5 is for the width of the header - top: `${(offset + i + 1.5) * ROW_H}px`, - }; - if (this.entries.isHighlighted[i]) { - style.backgroundColor = SELECTED_LOG_ROWS_COLOR; - } - - rows.push( - m(`.row.${prioClass}`, - { - 'class': isStale ? 'stale' : '', - style, - 'onmouseover': this.onRowOver.bind(this, ts / 1e9), - 'onmouseout': this.onRowOut.bind(this), - }, - m('.cell', - formatTimestamp(ts / 1e9 - globals.state.traceTime.startSec)), - m('.cell', priorityLetter || '?'), - m('.cell', tags[i]), - hasProcessNames ? m('.cell.with-process', processNames[i]) : - undefined, - hasProcessNames ? m('.cell.with-process', messages[i]) : - m('.cell.no-process', messages[i]), - m('br'))); - } - } - - return m( - '.log-panel', - m('header', - { - 'class': isStale ? 'stale' : '', - }, - [ - m('.log-rows-label', - `Logs rows [${offset}, ${offset + count}] / ${total}`), - m(LogsFilters), - ]), - m('.rows', {style: {height: `${total * ROW_H}px`}}, rows)); - } - - renderCanvas() {} -} diff --git a/third_party/perfetto/ui/src/frontend/metrics_page.ts b/third_party/perfetto/ui/src/frontend/metrics_page.ts deleted file mode 100644 index 96920289deec..000000000000 --- a/third_party/perfetto/ui/src/frontend/metrics_page.ts +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {Actions} from '../common/actions'; -import {globals} from './globals'; -import {createPage} from './pages'; -import {Button} from './widgets/button'; - -function getCurrSelectedMetric() { - const {availableMetrics, selectedIndex} = globals.state.metrics; - if (!availableMetrics) return undefined; - if (selectedIndex === undefined) return undefined; - return availableMetrics[selectedIndex]; -} - -class MetricResult implements m.ClassComponent { - view() { - const metricResult = globals.metricResult; - if (metricResult === undefined) return undefined; - const currSelection = getCurrSelectedMetric(); - if (!(metricResult && metricResult.name === currSelection)) { - return undefined; - } - if (metricResult.error !== undefined) { - return m('pre.metric-error', metricResult.error); - } - if (metricResult.resultString !== undefined) { - return m('pre', metricResult.resultString); - } - return undefined; - } -} - -class MetricPicker implements m.ClassComponent { - view() { - const {availableMetrics, selectedIndex} = globals.state.metrics; - if (availableMetrics === undefined) return 'Loading metrics...'; - if (availableMetrics.length === 0) return 'No metrics available'; - if (selectedIndex === undefined) { - throw Error('Should not happen when avaibleMetrics is non-empty'); - } - - return m('div', [ - 'Select a metric:', - m('select', - { - selectedIndex: globals.state.metrics.selectedIndex, - onchange: (e: InputEvent) => { - globals.dispatch(Actions.setMetricSelectedIndex( - {index: (e.target as HTMLSelectElement).selectedIndex})); - }, - }, - availableMetrics.map( - (metric) => m('option', {value: metric, key: metric}, metric))), - m(Button, { - onclick: () => globals.dispatch(Actions.requestSelectedMetric({})), - label: 'Run', - }), - ]); - } -} - -export const MetricsPage = createPage({ - view() { - return m( - '.metrics-page', - m(MetricPicker), - m(MetricResult), - ); - }, -}); diff --git a/third_party/perfetto/ui/src/frontend/modal.ts b/third_party/perfetto/ui/src/frontend/modal.ts deleted file mode 100644 index 217b9b0f87a5..000000000000 --- a/third_party/perfetto/ui/src/frontend/modal.ts +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -// This module deals with modal dialogs. Unlike most components, here we want to -// render the DOM elements outside of the corresponding vdom tree. For instance -// we might want to instantiate a modal dialog all the way down from a nested -// Mithril sub-component, but we want the result dom element to be nested under -// the root . -// -// This is achieved by splitting: -// 1. ModalContainer: it's the placeholder (e.g., the thing that should be added -// under ) where the DOM elements will be rendered into. This is NOT -// a mithril component itself. -// 2. Modal: is the Mithril component with the actual VDOM->DOM handling. -// This can be used directly in the cases where the modal DOM should be -// placed presicely where the corresponding Mithril VDOM is. -// In turn this is split into Modal and ModalImpl, to deal with fade-out, see -// comments around onbeforeremove. - -// Usage (in the case of DOM not matching VDOM): -// - Create a ModalContainer instance somewhere (e.g. a singleton for the case -// of the full-screen modal dialog). -// - In the view() method of the component that should host the DOM elements -// (e.g. in the root pages.ts) do the following: -// view() { -// return m('main', -// m('h2', ...) -// m(modalContainerInstance.mithrilComponent); -// } -// -// - In the view() method of the nested component that wants to show the modal -// dialog do the following: -// view() { -// if (shouldShowModalDialog) { -// modalContainerInstance.update({title: 'Foo', content, buttons: ...}); -// } -// return m('.nested-widget', -// m('div', ...)); -// } -// -// For one-show use-cases it's still possible to just use: -// showModal({title: 'Foo', content, buttons: ...}); - -import m from 'mithril'; -import {defer} from '../base/deferred'; -import {assertExists, assertTrue} from '../base/logging'; -import {globals} from './globals'; - -export interface ModalDefinition { - title: string; - content: m.Children|(() => m.Children); - vAlign?: 'MIDDLE' /* default */ | 'TOP'; - buttons?: Button[]; - close?: boolean; - onClose?: () => void; -} - -export interface Button { - text: string; - primary?: boolean; - id?: string; - action?: () => void; -} - -// The component that handles the actual modal dialog. Note that this uses -// position: absolute, so the modal dialog will be relative to the surrounding -// DOM. -// We need to split this into two components (Modal and ModalImpl) so that we -// can handle the fade-out animation via onbeforeremove. The problem here is -// that onbeforeremove is emitted only when the *parent* component removes the -// children from the vdom hierarchy. So we need a parent/child in our control to -// trigger this. -export class Modal implements m.ClassComponent { - private requestClose = false; - - close() { - // The next view pass will kick-off the modalFadeOut CSS animation by - // appending the .modal-hidden CSS class. - this.requestClose = true; - globals.rafScheduler.scheduleFullRedraw(); - } - - view(vnode: m.Vnode) { - if (this.requestClose || vnode.attrs.close) { - return null; - } - - return m(ModalImpl, {...vnode.attrs, parent: this} as ModalImplAttrs); - } -} - -interface ModalImplAttrs extends ModalDefinition { - parent: Modal; -} - -// The component that handles the actual modal dialog. Note that this uses -// position: absolute, so the modal dialog will be relative to the surrounding -// DOM. -class ModalImpl implements m.ClassComponent { - private parent ?: Modal; - private onClose?: () => void; - - view({attrs}: m.Vnode) { - this.onClose = attrs.onClose; - this.parent = attrs.parent; - - const buttons: Array> = []; - for (const button of attrs.buttons || []) { - buttons.push(m('button.modal-btn', { - class: button.primary ? 'modal-btn-primary' : '', - id: button.id, - onclick: () => { - attrs.parent.close(); - if (button.action !== undefined) button.action(); - }, - }, - button.text)); - } - - const aria = '[aria-labelledby=mm-title][aria-model][role=dialog]'; - const align = attrs.vAlign === 'TOP' ? '.modal-dialog-valign-top' : ''; - return m( - '.modal-backdrop', - { - onclick: this.onclick.bind(this), - onkeyup: this.onkeyupdown.bind(this), - onkeydown: this.onkeyupdown.bind(this), - // onanimationend: this.onanimationend.bind(this), - tabIndex: 0, - }, - m( - `.modal-dialog${align}${aria}`, - m( - 'header', - m('h2', {id: 'mm-title'}, attrs.title), - m( - 'button[aria-label=Close Modal]', - {onclick: () => attrs.parent.close()}, - m.trust('✕'), - ), - ), - m('main', this.renderContent(attrs.content)), - m('footer', buttons), - )); - } - - private renderContent(content: m.Children|(() => m.Children)): m.Children { - if (typeof content === 'function') { - return content(); - } else { - return content; - } - } - - oncreate(vnode: m.VnodeDOM) { - if (vnode.dom instanceof HTMLElement) { - // Focus the newly created dialog, so that we react to Escape keydown - // even if the user has not clicked yet on any element. - // If there is a primary button, focus that, so Enter does the default - // action. If not just focus the whole dialog. - const primaryBtn = vnode.dom.querySelector('.modal-btn-primary'); - if (primaryBtn) { - (primaryBtn as HTMLElement).focus(); - } else { - vnode.dom.focus(); - } - // If the modal dialog is instantiated in a tall scrollable container, - // make sure to scroll it into the view. - vnode.dom.scrollIntoView({'block': 'center'}); - } - } - - - onbeforeremove(vnode: m.VnodeDOM) { - const removePromise = defer(); - vnode.dom.addEventListener('animationend', () => removePromise.resolve()); - vnode.dom.classList.add('modal-fadeout'); - - // Retuning `removePromise` will cause Mithril to defer the actual component - // removal until the fade-out animation is done. - return removePromise; - } - - onremove() { - if (this.onClose !== undefined) { - this.onClose(); - globals.rafScheduler.scheduleFullRedraw(); - } - } - - onclick(e: MouseEvent) { - e.stopPropagation(); - // Only react when clicking on the backdrop. Don't close if the user clicks - // on the dialog itself. - const t = e.target; - if (t instanceof Element && t.classList.contains('modal-backdrop')) { - assertExists(this.parent).close(); - } - } - - onkeyupdown(e: KeyboardEvent) { - e.stopPropagation(); - if (e.key === 'Escape' && e.type !== 'keyup') { - assertExists(this.parent).close(); - } - } -} - - -// This is deliberately NOT a Mithril component. We want to manage the lifetime -// independently (outside of Mithril), so we can render from outside the current -// vdom sub-tree. ModalContainer instances should be singletons / globals. -export class ModalContainer { - private attrs?: ModalDefinition; - private generation = 1; // Start with a generation > `closeGeneration`. - private closeGeneration = 0; - - // This is the mithril component that is exposed to the embedder (e.g. see - // pages.ts). The caller is supposed to hyperscript this while building the - // vdom tree that should host the modal dialog. - readonly mithrilComponent = { - container: this, - view: - function() { - const thiz = this.container; - const attrs = thiz.attrs; - if (attrs === undefined) { - return null; - } - return [m(Modal, { - ...attrs, - onClose: () => { - // Remember the fact that the dialog was dismissed, in case the - // whole ModalContainer gets instantiated from a different page - // (which would cause the Modal to be destroyed and recreated). - thiz.closeGeneration = thiz.generation; - if (thiz.attrs?.onClose !== undefined) { - thiz.attrs.onClose(); - globals.rafScheduler.scheduleFullRedraw(); - } - }, - close: thiz.closeGeneration === thiz.generation ? true : - attrs.close, - key: thiz.generation, - })]; - }, - }; - - // This should be called to show a new modal dialog. The modal dialog will - // be shown the next time something calls render() in a Mithril draw pass. - // This enforces the creation of a new dialog. - createNew(attrs: ModalDefinition) { - this.generation++; - this.updateVdom(attrs); - } - - // Updates the current dialog or creates a new one if not existing. If a - // dialog exists already, this will update the DOM of the existing dialog. - // This should be called in at view() time by a nested Mithril component which - // wants to display a modal dialog (but wants it to render outside). - updateVdom(attrs: ModalDefinition) { - this.attrs = attrs; - } - - close() { - this.closeGeneration = this.generation; - globals.rafScheduler.scheduleFullRedraw(); - } -} - -// This is the default instance used for full-screen modal dialogs. -// page.ts calls `m(fullscreenModalContainer.mithrilComponent)` in its view(). -export const fullscreenModalContainer = new ModalContainer(); - - -export async function showModal(attrs: ModalDefinition): Promise { - // When using showModal, the caller cannot pass an onClose promise. It should - // use the returned promised instead. onClose is only for clients using the - // Mithril component directly. - assertTrue(attrs.onClose === undefined); - const promise = defer(); - fullscreenModalContainer.createNew({ - ...attrs, - onClose: () => promise.resolve(), - }); - globals.rafScheduler.scheduleFullRedraw(); - return promise; -} diff --git a/third_party/perfetto/ui/src/frontend/named_slice_track.ts b/third_party/perfetto/ui/src/frontend/named_slice_track.ts deleted file mode 100644 index ac1ba8e120b0..000000000000 --- a/third_party/perfetto/ui/src/frontend/named_slice_track.ts +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Actions} from '../common/actions'; -import { - Color, - hslForSlice, -} from '../common/colorizer'; -import {STR_NULL} from '../common/query_result'; - -import { - BASE_SLICE_ROW, - BaseSliceTrack, - BaseSliceTrackTypes, - OnSliceClickArgs, - OnSliceOverArgs, -} from './base_slice_track'; -import {globals} from './globals'; -import {NewTrackArgs} from './track'; - -export const NAMED_SLICE_ROW = { - // Base columns (tsq, ts, dur, id, depth). - ...BASE_SLICE_ROW, - - // Impl-specific columns. - name: STR_NULL, -}; -export type NamedSliceRow = typeof NAMED_SLICE_ROW; - -export interface NamedSliceTrackTypes extends BaseSliceTrackTypes { - row: NamedSliceRow; -} - -export abstract class NamedSliceTrack< - T extends NamedSliceTrackTypes = NamedSliceTrackTypes> extends - BaseSliceTrack { - constructor(args: NewTrackArgs) { - super(args); - } - - // This is used by the base class to call iter(). - getRowSpec(): T['row'] { - return NAMED_SLICE_ROW; - } - - // Converts a SQL result row to an "Impl" Slice. - rowToSlice(row: T['row']): T['slice'] { - const baseSlice = super.rowToSlice(row); - // Ignore PIDs or numeric arguments when hashing. - const name = row.name || ''; - const nameForHashing = name.replace(/\s?\d+/g, ''); - const hsl = hslForSlice(nameForHashing, /* isSelected=*/ false); - // We cache the color so we hash only once per query. - const baseColor: Color = {c: '', h: hsl[0], s: hsl[1], l: hsl[2]}; - return {...baseSlice, title: name, baseColor}; - } - - onSliceOver(args: OnSliceOverArgs) { - const name = args.slice.title; - args.tooltip = [name]; - } - - onSliceClick(args: OnSliceClickArgs) { - globals.makeSelection(Actions.selectChromeSlice({ - id: args.slice.id, - trackId: this.trackId, - - // |table| here can be either 'slice' or 'annotation'. The - // AnnotationSliceTrack overrides the onSliceClick and sets this to - // 'annotation' - table: 'slice', - })); - } -} diff --git a/third_party/perfetto/ui/src/frontend/notes_panel.ts b/third_party/perfetto/ui/src/frontend/notes_panel.ts deleted file mode 100644 index d3eec073342a..000000000000 --- a/third_party/perfetto/ui/src/frontend/notes_panel.ts +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use size file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {Actions} from '../common/actions'; -import {randomColor} from '../common/colorizer'; -import {AreaNote, Note} from '../common/state'; -import {timeToString} from '../common/time'; - -import { - BottomTab, - bottomTabRegistry, - NewBottomTabArgs, -} from './bottom_tab'; -import {TRACK_SHELL_WIDTH} from './css_constants'; -import {PerfettoMouseEvent} from './events'; -import {globals} from './globals'; -import { - TickGenerator, - TickType, - timeScaleForVisibleWindow, -} from './gridline_helper'; -import {Panel, PanelSize} from './panel'; -import {isTraceLoaded} from './sidebar'; - -const FLAG_WIDTH = 16; -const AREA_TRIANGLE_WIDTH = 10; -const FLAG = `\uE153`; - -function toSummary(s: string) { - const newlineIndex = s.indexOf('\n') > 0 ? s.indexOf('\n') : s.length; - return s.slice(0, Math.min(newlineIndex, s.length, 16)); -} - -function getStartTimestamp(note: Note|AreaNote) { - if (note.noteType === 'AREA') { - return globals.state.areas[note.areaId].startSec; - } else { - return note.timestamp; - } -} - -export class NotesPanel extends Panel { - hoveredX: null|number = null; - - oncreate({dom}: m.CVnodeDOM) { - dom.addEventListener('mousemove', (e: Event) => { - this.hoveredX = (e as PerfettoMouseEvent).layerX - TRACK_SHELL_WIDTH; - globals.rafScheduler.scheduleRedraw(); - }, {passive: true}); - dom.addEventListener('mouseenter', (e: Event) => { - this.hoveredX = (e as PerfettoMouseEvent).layerX - TRACK_SHELL_WIDTH; - globals.rafScheduler.scheduleRedraw(); - }); - dom.addEventListener('mouseout', () => { - this.hoveredX = null; - globals.dispatch(Actions.setHoveredNoteTimestamp({ts: -1})); - }, {passive: true}); - } - - view() { - const allCollapsed = Object.values(globals.state.trackGroups) - .every((group) => group.collapsed); - - return m( - '.notes-panel', - { - onclick: (e: PerfettoMouseEvent) => { - this.onClick(e.layerX - TRACK_SHELL_WIDTH, e.layerY); - e.stopPropagation(); - }, - }, - isTraceLoaded() ? - [ - m('button', - { - onclick: (e: Event) => { - e.preventDefault(); - globals.dispatch(Actions.toggleAllTrackGroups( - {collapsed: !allCollapsed})); - }, - }, - m('i.material-icons', - {title: allCollapsed ? 'Expand all' : 'Collapse all'}, - allCollapsed ? 'unfold_more' : 'unfold_less')), - m('button', - { - onclick: (e: Event) => { - e.preventDefault(); - globals.dispatch(Actions.clearAllPinnedTracks({})); - }, - }, - m('i.material-icons', - {title: 'Clear all pinned tracks'}, - 'clear_all')), - ] : - ''); - } - - renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) { - const timeScale = globals.frontendLocalState.timeScale; - let aNoteIsHovered = false; - - ctx.fillStyle = '#999'; - ctx.fillRect(TRACK_SHELL_WIDTH - 2, 0, 2, size.height); - const relScale = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width); - if (relScale.timeSpan.duration > 0 && relScale.widthPx > 0) { - for (const {type, position} of new TickGenerator(relScale)) { - if (type === TickType.MAJOR) ctx.fillRect(position, 0, 1, size.height); - } - } - - ctx.textBaseline = 'bottom'; - ctx.font = '10px Helvetica'; - - for (const note of Object.values(globals.state.notes)) { - const timestamp = getStartTimestamp(note); - // TODO(hjd): We should still render area selection marks in viewport is - // *within* the area (e.g. both lhs and rhs are out of bounds). - if ((note.noteType !== 'AREA' && !timeScale.timeInBounds(timestamp)) || - (note.noteType === 'AREA' && - !timeScale.timeInBounds(globals.state.areas[note.areaId].endSec) && - !timeScale.timeInBounds( - globals.state.areas[note.areaId].startSec))) { - continue; - } - const currentIsHovered = - this.hoveredX && this.mouseOverNote(this.hoveredX, note); - if (currentIsHovered) aNoteIsHovered = true; - - const selection = globals.state.currentSelection; - const isSelected = selection !== null && - ((selection.kind === 'NOTE' && selection.id === note.id) || - (selection.kind === 'AREA' && selection.noteId === note.id)); - const x = timeScale.timeToPx(timestamp); - const left = Math.floor(x + TRACK_SHELL_WIDTH); - - // Draw flag or marker. - if (note.noteType === 'AREA') { - const area = globals.state.areas[note.areaId]; - this.drawAreaMarker( - ctx, - left, - Math.floor(timeScale.timeToPx(area.endSec) + TRACK_SHELL_WIDTH), - note.color, - isSelected); - } else { - this.drawFlag(ctx, left, size.height, note.color, isSelected); - } - - if (note.text) { - const summary = toSummary(note.text); - const measured = ctx.measureText(summary); - // Add a white semi-transparent background for the text. - ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; - ctx.fillRect( - left + FLAG_WIDTH + 2, size.height + 2, measured.width + 2, -12); - ctx.fillStyle = '#3c4b5d'; - ctx.fillText(summary, left + FLAG_WIDTH + 3, size.height + 1); - } - } - - // A real note is hovered so we don't need to see the preview line. - // TODO(hjd): Change cursor to pointer here. - if (aNoteIsHovered) { - globals.dispatch(Actions.setHoveredNoteTimestamp({ts: -1})); - } - - // View preview note flag when hovering on notes panel. - if (!aNoteIsHovered && this.hoveredX !== null) { - const timestamp = timeScale.pxToTime(this.hoveredX); - if (timeScale.timeInBounds(timestamp)) { - globals.dispatch(Actions.setHoveredNoteTimestamp({ts: timestamp})); - const x = timeScale.timeToPx(timestamp); - const left = Math.floor(x + TRACK_SHELL_WIDTH); - this.drawFlag(ctx, left, size.height, '#aaa', /* fill */ true); - } - } - } - - private drawAreaMarker( - ctx: CanvasRenderingContext2D, x: number, xEnd: number, color: string, - fill: boolean) { - ctx.fillStyle = color; - ctx.strokeStyle = color; - const topOffset = 10; - // Don't draw in the track shell section. - if (x >= globals.frontendLocalState.timeScale.startPx + TRACK_SHELL_WIDTH) { - // Draw left triangle. - ctx.beginPath(); - ctx.moveTo(x, topOffset); - ctx.lineTo(x, topOffset + AREA_TRIANGLE_WIDTH); - ctx.lineTo(x + AREA_TRIANGLE_WIDTH, topOffset); - ctx.lineTo(x, topOffset); - if (fill) ctx.fill(); - ctx.stroke(); - } - // Draw right triangle. - ctx.beginPath(); - ctx.moveTo(xEnd, topOffset); - ctx.lineTo(xEnd, topOffset + AREA_TRIANGLE_WIDTH); - ctx.lineTo(xEnd - AREA_TRIANGLE_WIDTH, topOffset); - ctx.lineTo(xEnd, topOffset); - if (fill) ctx.fill(); - ctx.stroke(); - - // Start line after track shell section, join triangles. - const startDraw = Math.max( - x, globals.frontendLocalState.timeScale.startPx + TRACK_SHELL_WIDTH); - ctx.beginPath(); - ctx.moveTo(startDraw, topOffset); - ctx.lineTo(xEnd, topOffset); - ctx.stroke(); - } - - private drawFlag( - ctx: CanvasRenderingContext2D, x: number, height: number, color: string, - fill?: boolean) { - const prevFont = ctx.font; - const prevBaseline = ctx.textBaseline; - ctx.textBaseline = 'alphabetic'; - // Adjust height for icon font. - ctx.font = '24px Material Symbols Sharp'; - ctx.fillStyle = color; - ctx.strokeStyle = color; - // The ligatures have padding included that means the icon is not drawn - // exactly at the x value. This adjusts for that. - const iconPadding = 6; - if (fill) { - ctx.fillText(FLAG, x - iconPadding, height + 2); - } else { - ctx.strokeText(FLAG, x - iconPadding, height + 2.5); - } - ctx.font = prevFont; - ctx.textBaseline = prevBaseline; - } - - - private onClick(x: number, _: number) { - if (x < 0) return; - const timeScale = globals.frontendLocalState.timeScale; - const timestamp = timeScale.pxToTime(x); - for (const note of Object.values(globals.state.notes)) { - if (this.hoveredX && this.mouseOverNote(this.hoveredX, note)) { - if (note.noteType === 'AREA') { - globals.makeSelection( - Actions.reSelectArea({areaId: note.areaId, noteId: note.id})); - } else { - globals.makeSelection(Actions.selectNote({id: note.id})); - } - return; - } - } - const color = randomColor(); - globals.makeSelection(Actions.addNote({timestamp, color})); - } - - private mouseOverNote(x: number, note: AreaNote|Note): boolean { - const timeScale = globals.frontendLocalState.timeScale; - const noteX = timeScale.timeToPx(getStartTimestamp(note)); - if (note.noteType === 'AREA') { - const noteArea = globals.state.areas[note.areaId]; - return (noteX <= x && x < noteX + AREA_TRIANGLE_WIDTH) || - (timeScale.timeToPx(noteArea.endSec) > x && - x > timeScale.timeToPx(noteArea.endSec) - AREA_TRIANGLE_WIDTH); - } else { - const width = FLAG_WIDTH; - return noteX <= x && x < noteX + width; - } - } -} - -interface NotesEditorTabConfig { - id: string; -} - -export class NotesEditorTab extends BottomTab { - static readonly kind = 'org.perfetto.NotesEditorTab'; - - static create(args: NewBottomTabArgs): NotesEditorTab { - return new NotesEditorTab(args); - } - - constructor(args: NewBottomTabArgs) { - super(args); - } - - renderTabCanvas() {} - - getTitle() { - return 'Current Selection'; - } - - viewTab() { - const note = globals.state.notes[this.config.id]; - if (note === undefined) { - return m('.', `No Note with id ${this.config.id}`); - } - const startTime = - getStartTimestamp(note) - globals.state.traceTime.startSec; - return m( - '.notes-editor-panel', - m('.notes-editor-panel-heading-bar', - m('.notes-editor-panel-heading', - `Annotation at ${timeToString(startTime)}`), - m('input[type=text]', { - onkeydown: (e: Event) => { - e.stopImmediatePropagation(); - }, - value: note.text, - onchange: (e: InputEvent) => { - const newText = (e.target as HTMLInputElement).value; - globals.dispatch(Actions.changeNoteText({ - id: this.config.id, - newText, - })); - }, - }), - m('span.color-change', `Change color: `, m('input[type=color]', { - value: note.color, - onchange: (e: Event) => { - const newColor = (e.target as HTMLInputElement).value; - globals.dispatch(Actions.changeNoteColor({ - id: this.config.id, - newColor, - })); - }, - })), - m('button', - { - onclick: () => { - globals.dispatch(Actions.removeNote({id: this.config.id})); - globals.dispatch(Actions.setCurrentTab({tab: undefined})); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - 'Remove')), - ); - } -} - -bottomTabRegistry.register(NotesEditorTab); diff --git a/third_party/perfetto/ui/src/frontend/overview_timeline_panel.ts b/third_party/perfetto/ui/src/frontend/overview_timeline_panel.ts deleted file mode 100644 index 76b451582b45..000000000000 --- a/third_party/perfetto/ui/src/frontend/overview_timeline_panel.ts +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {assertExists} from '../base/logging'; -import {hueForCpu} from '../common/colorizer'; -import {TimeSpan} from '../common/time'; - -import { - OVERVIEW_TIMELINE_NON_VISIBLE_COLOR, - SIDEBAR_WIDTH, - TRACK_SHELL_WIDTH, -} from './css_constants'; -import {BorderDragStrategy} from './drag/border_drag_strategy'; -import {DragStrategy} from './drag/drag_strategy'; -import {InnerDragStrategy} from './drag/inner_drag_strategy'; -import {OuterDragStrategy} from './drag/outer_drag_strategy'; -import {DragGestureHandler} from './drag_gesture_handler'; -import {globals} from './globals'; -import {TickGenerator, TickType} from './gridline_helper'; -import {Panel, PanelSize} from './panel'; -import {TimeScale} from './time_scale'; - -export class OverviewTimelinePanel extends Panel { - private static HANDLE_SIZE_PX = 5; - - private width = 0; - private gesture?: DragGestureHandler; - private timeScale?: TimeScale; - private totTime = new TimeSpan(0, 0); - private dragStrategy?: DragStrategy; - private readonly boundOnMouseMove = this.onMouseMove.bind(this); - - // Must explicitly type now; arguments types are no longer auto-inferred. - // https://github.com/Microsoft/TypeScript/issues/1373 - onupdate({dom}: m.CVnodeDOM) { - this.width = dom.getBoundingClientRect().width; - this.totTime = new TimeSpan( - globals.state.traceTime.startSec, globals.state.traceTime.endSec); - this.timeScale = new TimeScale( - this.totTime, [TRACK_SHELL_WIDTH, assertExists(this.width)]); - - if (this.gesture === undefined) { - this.gesture = new DragGestureHandler( - dom as HTMLElement, - this.onDrag.bind(this), - this.onDragStart.bind(this), - this.onDragEnd.bind(this)); - } - } - - oncreate(vnode: m.CVnodeDOM) { - this.onupdate(vnode); - (vnode.dom as HTMLElement) - .addEventListener('mousemove', this.boundOnMouseMove); - } - - onremove({dom}: m.CVnodeDOM) { - (dom as HTMLElement) - .removeEventListener('mousemove', this.boundOnMouseMove); - } - - view() { - return m('.overview-timeline'); - } - - renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) { - if (this.width === undefined) return; - if (this.timeScale === undefined) return; - const headerHeight = 20; - const tracksHeight = size.height - headerHeight; - const timeSpan = new TimeSpan(0, this.totTime.duration); - - const timeScale = new TimeScale(timeSpan, [TRACK_SHELL_WIDTH, this.width]); - - if (timeScale.timeSpan.duration > 0 && timeScale.widthPx > 0) { - const tickGen = new TickGenerator(timeScale); - - // Draw time labels on the top header. - ctx.font = '10px Roboto Condensed'; - ctx.fillStyle = '#999'; - for (const {type, time, position} of tickGen) { - const xPos = Math.round(position); - if (xPos <= 0) continue; - if (xPos > this.width) break; - if (type === TickType.MAJOR) { - ctx.fillRect(xPos - 1, 0, 1, headerHeight - 5); - ctx.fillText(time.toFixed(tickGen.digits) + ' s', xPos + 5, 18); - } else if (type == TickType.MEDIUM) { - ctx.fillRect(xPos - 1, 0, 1, 8); - } else if (type == TickType.MINOR) { - ctx.fillRect(xPos - 1, 0, 1, 5); - } - } - } - - // Draw mini-tracks with quanitzed density for each process. - if (globals.overviewStore.size > 0) { - const numTracks = globals.overviewStore.size; - let y = 0; - const trackHeight = (tracksHeight - 1) / numTracks; - for (const key of globals.overviewStore.keys()) { - const loads = globals.overviewStore.get(key)!; - for (let i = 0; i < loads.length; i++) { - const xStart = Math.floor(this.timeScale.timeToPx(loads[i].startSec)); - const xEnd = Math.ceil(this.timeScale.timeToPx(loads[i].endSec)); - const yOff = Math.floor(headerHeight + y * trackHeight); - const lightness = Math.ceil((1 - loads[i].load * 0.7) * 100); - ctx.fillStyle = `hsl(${hueForCpu(y)}, 50%, ${lightness}%)`; - ctx.fillRect(xStart, yOff, xEnd - xStart, Math.ceil(trackHeight)); - } - y++; - } - } - - // Draw bottom border. - ctx.fillStyle = '#dadada'; - ctx.fillRect(0, size.height - 1, this.width, 1); - - // Draw semi-opaque rects that occlude the non-visible time range. - const [vizStartPx, vizEndPx] = - OverviewTimelinePanel.extractBounds(this.timeScale); - - ctx.fillStyle = OVERVIEW_TIMELINE_NON_VISIBLE_COLOR; - ctx.fillRect( - TRACK_SHELL_WIDTH - 1, - headerHeight, - vizStartPx - TRACK_SHELL_WIDTH, - tracksHeight); - ctx.fillRect(vizEndPx, headerHeight, this.width - vizEndPx, tracksHeight); - - // Draw brushes. - ctx.fillStyle = '#999'; - ctx.fillRect(vizStartPx - 1, headerHeight, 1, tracksHeight); - ctx.fillRect(vizEndPx, headerHeight, 1, tracksHeight); - - const hbarWidth = OverviewTimelinePanel.HANDLE_SIZE_PX; - const hbarHeight = tracksHeight * 0.4; - // Draw handlebar - ctx.fillRect( - vizStartPx - Math.floor(hbarWidth / 2) - 1, - headerHeight, - hbarWidth, - hbarHeight); - ctx.fillRect( - vizEndPx - Math.floor(hbarWidth / 2), - headerHeight, - hbarWidth, - hbarHeight); - } - - private onMouseMove(e: MouseEvent) { - if (this.gesture === undefined || this.gesture.isDragging) { - return; - } - (e.target as HTMLElement).style.cursor = this.chooseCursor(e.x); - } - - private chooseCursor(x: number) { - if (this.timeScale === undefined) return 'default'; - const [vizStartPx, vizEndPx] = - OverviewTimelinePanel.extractBounds(this.timeScale); - const startBound = vizStartPx - 1 + SIDEBAR_WIDTH; - const endBound = vizEndPx + SIDEBAR_WIDTH; - if (OverviewTimelinePanel.inBorderRange(x, startBound) || - OverviewTimelinePanel.inBorderRange(x, endBound)) { - return 'ew-resize'; - } else if (x < SIDEBAR_WIDTH + TRACK_SHELL_WIDTH) { - return 'default'; - } else if (x < startBound || endBound < x) { - return 'crosshair'; - } else { - return 'all-scroll'; - } - } - - onDrag(x: number) { - if (this.dragStrategy === undefined) return; - this.dragStrategy.onDrag(x); - } - - onDragStart(x: number) { - if (this.timeScale === undefined) return; - const pixelBounds = OverviewTimelinePanel.extractBounds(this.timeScale); - if (OverviewTimelinePanel.inBorderRange(x, pixelBounds[0]) || - OverviewTimelinePanel.inBorderRange(x, pixelBounds[1])) { - this.dragStrategy = new BorderDragStrategy(this.timeScale, pixelBounds); - } else if (x < pixelBounds[0] || pixelBounds[1] < x) { - this.dragStrategy = new OuterDragStrategy(this.timeScale); - } else { - this.dragStrategy = new InnerDragStrategy(this.timeScale, pixelBounds); - } - this.dragStrategy.onDragStart(x); - } - - onDragEnd() { - this.dragStrategy = undefined; - } - - private static extractBounds(timeScale: TimeScale): [number, number] { - const vizTime = globals.frontendLocalState.getVisibleStateBounds(); - return [ - Math.floor(timeScale.timeToPx(vizTime[0])), - Math.ceil(timeScale.timeToPx(vizTime[1])), - ]; - } - - private static inBorderRange(a: number, b: number): boolean { - return Math.abs(a - b) < this.HANDLE_SIZE_PX / 2; - } -} diff --git a/third_party/perfetto/ui/src/frontend/pages.ts b/third_party/perfetto/ui/src/frontend/pages.ts deleted file mode 100644 index 8d48cc746b1b..000000000000 --- a/third_party/perfetto/ui/src/frontend/pages.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {Actions} from '../common/actions'; - -import {onClickCopy} from './clipboard'; -import {CookieConsent} from './cookie_consent'; -import {globals} from './globals'; -import {fullscreenModalContainer} from './modal'; -import {Sidebar} from './sidebar'; -import {Topbar} from './topbar'; - -function renderPermalink(): m.Children { - const permalink = globals.state.permalink; - if (!permalink.requestId || !permalink.hash) return null; - const url = `${self.location.origin}/#!/?s=${permalink.hash}`; - const linkProps = {title: 'Click to copy the URL', onclick: onClickCopy(url)}; - - return m('.alert-permalink', [ - m('div', 'Permalink: ', m(`a[href=${url}]`, linkProps, url)), - m('button', - { - onclick: () => globals.dispatch(Actions.clearPermalink({})), - }, - m('i.material-icons.disallow-selection', 'close')), - ]); -} - -class Alerts implements m.ClassComponent { - view() { - return m('.alerts', renderPermalink()); - } -} - -// Wrap component with common UI elements (nav bar etc). -export function createPage(component: m.Component): - m.Component { - const pageComponent = { - view({attrs}: m.Vnode) { - const children = [ - m(Sidebar), - m(Topbar), - m(Alerts), - m(component, attrs), - m(CookieConsent), - m(fullscreenModalContainer.mithrilComponent), - ]; - if (globals.state.perfDebug) { - children.push(m('.perf-stats')); - } - return children; - }, - }; - - return pageComponent; -} - -export interface PageAttrs { - subpage?: string; -} diff --git a/third_party/perfetto/ui/src/frontend/pan_and_zoom_handler.ts b/third_party/perfetto/ui/src/frontend/pan_and_zoom_handler.ts deleted file mode 100644 index 7018e342aaaf..000000000000 --- a/third_party/perfetto/ui/src/frontend/pan_and_zoom_handler.ts +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Animation} from './animation'; -import {DragGestureHandler} from './drag_gesture_handler'; -import {globals} from './globals'; -import {handleKey} from './keyboard_event_handler'; - -// When first starting to pan or zoom, move at least this many units. -const INITIAL_PAN_STEP_PX = 50; -const INITIAL_ZOOM_STEP = 0.1; - -// The snappiness (spring constant) of pan and zoom animations [0..1]. -const SNAP_FACTOR = 0.4; - -// How much the velocity of a pan or zoom animation increases per millisecond. -const ACCELERATION_PER_MS = 1 / 50; - -// The default duration of a pan or zoom animation. The animation may run longer -// if the user keeps holding the respective button down or shorter if the button -// is released. This value so chosen so that it is longer than the typical key -// repeat timeout to avoid breaks in the animation. -const DEFAULT_ANIMATION_DURATION = 700; - -// The minimum number of units to pan or zoom per frame (before the -// ACCELERATION_PER_MS multiplier is applied). -const ZOOM_RATIO_PER_FRAME = 0.008; -const KEYBOARD_PAN_PX_PER_FRAME = 8; - -// Scroll wheel animation steps. -const HORIZONTAL_WHEEL_PAN_SPEED = 1; -const WHEEL_ZOOM_SPEED = -0.02; - -const EDITING_RANGE_CURSOR = 'ew-resize'; -const DRAG_CURSOR = 'default'; -const PAN_CURSOR = 'move'; - -// Use key mapping based on the 'KeyboardEvent.code' property vs the -// 'KeyboardEvent.key', because the former corresponds to the physical key -// position rather than the glyph printed on top of it, and is unaffected by -// the user's keyboard layout. -// For example, 'KeyW' always corresponds to the key at the physical location of -// the 'w' key on an English QWERTY keyboard, regardless of the user's keyboard -// layout, or at least the layout they have configured in their OS. -// Seeing as most users use the keys in the English QWERTY "WASD" position for -// controlling kb+mouse applications like games, it's a good bet that these are -// the keys most poeple are going to find natural for navigating the UI. -// See https://www.w3.org/TR/uievents-code/#key-alphanumeric-writing-system -export enum KeyMapping { - KEY_PAN_LEFT = 'KeyA', - KEY_PAN_RIGHT = 'KeyD', - KEY_ZOOM_IN = 'KeyW', - KEY_ZOOM_OUT = 'KeyS', -} - -enum Pan { - None = 0, - Left = -1, - Right = 1 -} -function keyToPan(e: KeyboardEvent): Pan { - if (e.code === KeyMapping.KEY_PAN_LEFT) return Pan.Left; - if (e.code === KeyMapping.KEY_PAN_RIGHT) return Pan.Right; - return Pan.None; -} - -enum Zoom { - None = 0, - In = 1, - Out = -1 -} -function keyToZoom(e: KeyboardEvent): Zoom { - if (e.code === KeyMapping.KEY_ZOOM_IN) return Zoom.In; - if (e.code === KeyMapping.KEY_ZOOM_OUT) return Zoom.Out; - return Zoom.None; -} - -/** - * Enables horizontal pan and zoom with mouse-based drag and WASD navigation. - */ -export class PanAndZoomHandler { - private mousePositionX: number|null = null; - private boundOnMouseMove = this.onMouseMove.bind(this); - private boundOnWheel = this.onWheel.bind(this); - private boundOnKeyDown = this.onKeyDown.bind(this); - private boundOnKeyUp = this.onKeyUp.bind(this); - private shiftDown = false; - private panning: Pan = Pan.None; - private panOffsetPx = 0; - private targetPanOffsetPx = 0; - private zooming: Zoom = Zoom.None; - private zoomRatio = 0; - private targetZoomRatio = 0; - private panAnimation = new Animation(this.onPanAnimationStep.bind(this)); - private zoomAnimation = new Animation(this.onZoomAnimationStep.bind(this)); - - private element: HTMLElement; - private contentOffsetX: number; - private onPanned: (movedPx: number) => void; - private onZoomed: (zoomPositionPx: number, zoomRatio: number) => void; - private editSelection: (currentPx: number) => boolean; - private onSelection: - (dragStartX: number, dragStartY: number, prevX: number, currentX: number, - currentY: number, editing: boolean) => void; - private endSelection: (edit: boolean) => void; - - constructor({ - element, - contentOffsetX, - onPanned, - onZoomed, - editSelection, - onSelection, - endSelection, - }: { - element: HTMLElement, - contentOffsetX: number, - onPanned: (movedPx: number) => void, - onZoomed: (zoomPositionPx: number, zoomRatio: number) => void, - editSelection: (currentPx: number) => boolean, - onSelection: - (dragStartX: number, dragStartY: number, prevX: number, - currentX: number, currentY: number, editing: boolean) => void, - endSelection: (edit: boolean) => void, - }) { - this.element = element; - this.contentOffsetX = contentOffsetX; - this.onPanned = onPanned; - this.onZoomed = onZoomed; - this.editSelection = editSelection; - this.onSelection = onSelection; - this.endSelection = endSelection; - - document.body.addEventListener('keydown', this.boundOnKeyDown); - document.body.addEventListener('keyup', this.boundOnKeyUp); - this.element.addEventListener('mousemove', this.boundOnMouseMove); - this.element.addEventListener('wheel', this.boundOnWheel, {passive: true}); - - let prevX = -1; - let dragStartX = -1; - let dragStartY = -1; - let edit = false; - new DragGestureHandler( - this.element, - (x, y) => { - if (this.shiftDown) { - this.onPanned(prevX - x); - } else { - this.onSelection(dragStartX, dragStartY, prevX, x, y, edit); - } - prevX = x; - }, - (x, y) => { - prevX = x; - dragStartX = x; - dragStartY = y; - edit = this.editSelection(x); - // Set the cursor style based on where the cursor is when the drag - // starts. - if (edit) { - this.element.style.cursor = EDITING_RANGE_CURSOR; - } else if (!this.shiftDown) { - this.element.style.cursor = DRAG_CURSOR; - } - }, - () => { - // Reset the cursor now the drag has ended. - this.element.style.cursor = this.shiftDown ? PAN_CURSOR : DRAG_CURSOR; - dragStartX = -1; - dragStartY = -1; - this.endSelection(edit); - }); - } - - - shutdown() { - document.body.removeEventListener('keydown', this.boundOnKeyDown); - document.body.removeEventListener('keyup', this.boundOnKeyUp); - this.element.removeEventListener('mousemove', this.boundOnMouseMove); - this.element.removeEventListener('wheel', this.boundOnWheel); - } - - private onPanAnimationStep(msSinceStartOfAnimation: number) { - const step = (this.targetPanOffsetPx - this.panOffsetPx) * SNAP_FACTOR; - if (this.panning !== Pan.None) { - const velocity = 1 + msSinceStartOfAnimation * ACCELERATION_PER_MS; - // Pan at least as fast as the snapping animation to avoid a - // discontinuity. - const targetStep = Math.max(KEYBOARD_PAN_PX_PER_FRAME * velocity, step); - this.targetPanOffsetPx += this.panning * targetStep; - } - this.panOffsetPx += step; - if (Math.abs(step) > 1e-1) { - this.onPanned(step); - } else { - this.panAnimation.stop(); - } - } - - private onZoomAnimationStep(msSinceStartOfAnimation: number) { - if (this.mousePositionX === null) return; - const step = (this.targetZoomRatio - this.zoomRatio) * SNAP_FACTOR; - if (this.zooming !== Zoom.None) { - const velocity = 1 + msSinceStartOfAnimation * ACCELERATION_PER_MS; - // Zoom at least as fast as the snapping animation to avoid a - // discontinuity. - const targetStep = Math.max(ZOOM_RATIO_PER_FRAME * velocity, step); - this.targetZoomRatio += this.zooming * targetStep; - } - this.zoomRatio += step; - if (Math.abs(step) > 1e-6) { - this.onZoomed(this.mousePositionX, step); - } else { - this.zoomAnimation.stop(); - } - } - - private onMouseMove(e: MouseEvent) { - const pageOffset = globals.state.sidebarVisible && !globals.hideSidebar ? - this.contentOffsetX : - 0; - // We can't use layerX here because there are many layers in this element. - this.mousePositionX = e.clientX - pageOffset; - // Only change the cursor when hovering, the DragGestureHandler handles - // changing the cursor during drag events. This avoids the problem of - // the cursor flickering between styles if you drag fast and get too - // far from the current time range. - if (e.buttons === 0) { - if (this.editSelection(this.mousePositionX)) { - this.element.style.cursor = EDITING_RANGE_CURSOR; - } else { - this.element.style.cursor = this.shiftDown ? PAN_CURSOR : DRAG_CURSOR; - } - } - } - - private onWheel(e: WheelEvent) { - if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) { - this.onPanned(e.deltaX * HORIZONTAL_WHEEL_PAN_SPEED); - globals.rafScheduler.scheduleRedraw(); - } else if (e.ctrlKey && this.mousePositionX) { - const sign = e.deltaY < 0 ? -1 : 1; - const deltaY = sign * Math.log2(1 + Math.abs(e.deltaY)); - this.onZoomed(this.mousePositionX, deltaY * WHEEL_ZOOM_SPEED); - globals.rafScheduler.scheduleRedraw(); - } - } - - private onKeyDown(e: KeyboardEvent) { - this.updateShift(e.shiftKey); - - // Handle key events that are not pan or zoom. - if (handleKey(e, true)) return; - - if (keyToPan(e) !== Pan.None) { - if (this.panning !== keyToPan(e)) { - this.panAnimation.stop(); - this.panOffsetPx = 0; - this.targetPanOffsetPx = keyToPan(e) * INITIAL_PAN_STEP_PX; - } - this.panning = keyToPan(e); - this.panAnimation.start(DEFAULT_ANIMATION_DURATION); - } - - if (keyToZoom(e) !== Zoom.None) { - if (this.zooming !== keyToZoom(e)) { - this.zoomAnimation.stop(); - this.zoomRatio = 0; - this.targetZoomRatio = keyToZoom(e) * INITIAL_ZOOM_STEP; - } - this.zooming = keyToZoom(e); - this.zoomAnimation.start(DEFAULT_ANIMATION_DURATION); - } - } - - private onKeyUp(e: KeyboardEvent) { - this.updateShift(e.shiftKey); - - // Handle key events that are not pan or zoom. - if (handleKey(e, false)) return; - - if (keyToPan(e) === this.panning) { - this.panning = Pan.None; - } - if (keyToZoom(e) === this.zooming) { - this.zooming = Zoom.None; - } - } - - // TODO(hjd): Move this shift handling into the viewer page. - private updateShift(down: boolean) { - if (down === this.shiftDown) return; - this.shiftDown = down; - if (this.shiftDown) { - this.element.style.cursor = PAN_CURSOR; - } else if (this.mousePositionX) { - this.element.style.cursor = DRAG_CURSOR; - } - } -} diff --git a/third_party/perfetto/ui/src/frontend/panel.ts b/third_party/perfetto/ui/src/frontend/panel.ts deleted file mode 100644 index b33197ccd1fe..000000000000 --- a/third_party/perfetto/ui/src/frontend/panel.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -export interface PanelSize { - width: number; - height: number; -} - -export abstract class Panel implements m.ClassComponent { - abstract renderCanvas( - ctx: CanvasRenderingContext2D, size: PanelSize, - vnode: PanelVNode): void; - abstract view(vnode: m.CVnode): m.Children|null|void; -} - - -export type PanelVNode = m.Vnode>; - -export function isPanelVNode(vnode: m.Vnode): vnode is PanelVNode<{}> { - const tag = vnode.tag as {}; - return ( - typeof tag === 'function' && 'prototype' in tag && - tag.prototype instanceof Panel); -} diff --git a/third_party/perfetto/ui/src/frontend/panel_container.ts b/third_party/perfetto/ui/src/frontend/panel_container.ts deleted file mode 100644 index 4c6576fde7b9..000000000000 --- a/third_party/perfetto/ui/src/frontend/panel_container.ts +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {assertExists, assertFalse, assertTrue} from '../base/logging'; - -import { - SELECTION_STROKE_COLOR, - TOPBAR_HEIGHT, - TRACK_SHELL_WIDTH, -} from './css_constants'; -import { - FlowEventsRenderer, - FlowEventsRendererArgs, -} from './flow_events_renderer'; -import {globals} from './globals'; -import {isPanelVNode, Panel, PanelSize} from './panel'; -import { - debugNow, - perfDebug, - perfDisplay, - RunningStatistics, - runningStatStr, -} from './perf'; -import {TrackGroupAttrs} from './viewer_page'; - -// If the panel container scrolls, the backing canvas height is -// SCROLLING_CANVAS_OVERDRAW_FACTOR * parent container height. -const SCROLLING_CANVAS_OVERDRAW_FACTOR = 1.2; - -// We need any here so we can accept vnodes with arbitrary attrs. -export type AnyAttrsVnode = m.Vnode; - -export interface Attrs { - panels: AnyAttrsVnode[]; - doesScroll: boolean; - kind: 'TRACKS'|'OVERVIEW'|'DETAILS'; -} - -interface PanelInfo { - id: string; // Can be == '' for singleton panels. - vnode: AnyAttrsVnode; - height: number; - width: number; - x: number; - y: number; -} - -export class PanelContainer implements m.ClassComponent { - // These values are updated with proper values in oncreate. - private parentWidth = 0; - private parentHeight = 0; - private scrollTop = 0; - private panelInfos: PanelInfo[] = []; - private panelContainerTop = 0; - private panelContainerHeight = 0; - private panelByKey = new Map(); - private totalPanelHeight = 0; - private canvasHeight = 0; - - private flowEventsRenderer: FlowEventsRenderer; - - private panelPerfStats = new WeakMap(); - private perfStats = { - totalPanels: 0, - panelsOnCanvas: 0, - renderStats: new RunningStatistics(10), - }; - - // Attrs received in the most recent mithril redraw. We receive a new vnode - // with new attrs on every redraw, and we cache it here so that resize - // listeners and canvas redraw callbacks can access it. - private attrs: Attrs; - - private ctx?: CanvasRenderingContext2D; - - private onResize: () => void = () => {}; - private parentOnScroll: () => void = () => {}; - private canvasRedrawer: () => void; - - get canvasOverdrawFactor() { - return this.attrs.doesScroll ? SCROLLING_CANVAS_OVERDRAW_FACTOR : 1; - } - - getPanelsInRegion(startX: number, endX: number, startY: number, endY: number): - AnyAttrsVnode[] { - const minX = Math.min(startX, endX); - const maxX = Math.max(startX, endX); - const minY = Math.min(startY, endY); - const maxY = Math.max(startY, endY); - const panels: AnyAttrsVnode[] = []; - for (let i = 0; i < this.panelInfos.length; i++) { - const pos = this.panelInfos[i]; - const realPosX = pos.x - TRACK_SHELL_WIDTH; - if (realPosX + pos.width >= minX && realPosX <= maxX && - pos.y + pos.height >= minY && pos.y <= maxY && - pos.vnode.attrs.selectable) { - panels.push(pos.vnode); - } - } - return panels; - } - - // This finds the tracks covered by the in-progress area selection. When - // editing areaY is not set, so this will not be used. - handleAreaSelection() { - const area = globals.frontendLocalState.selectedArea; - if (area === undefined || - globals.frontendLocalState.areaY.start === undefined || - globals.frontendLocalState.areaY.end === undefined || - this.panelInfos.length === 0) { - return; - } - // Only get panels from the current panel container if the selection began - // in this container. - const panelContainerTop = this.panelInfos[0].y; - const panelContainerBottom = this.panelInfos[this.panelInfos.length - 1].y + - this.panelInfos[this.panelInfos.length - 1].height; - if (globals.frontendLocalState.areaY.start + TOPBAR_HEIGHT < - panelContainerTop || - globals.frontendLocalState.areaY.start + TOPBAR_HEIGHT > - panelContainerBottom) { - return; - } - - // The Y value is given from the top of the pan and zoom region, we want it - // from the top of the panel container. The parent offset corrects that. - const panels = this.getPanelsInRegion( - globals.frontendLocalState.timeScale.timeToPx(area.startSec), - globals.frontendLocalState.timeScale.timeToPx(area.endSec), - globals.frontendLocalState.areaY.start + TOPBAR_HEIGHT, - globals.frontendLocalState.areaY.end + TOPBAR_HEIGHT); - // Get the track ids from the panels. - const tracks = []; - for (const panel of panels) { - if (panel.attrs.id !== undefined) { - tracks.push(panel.attrs.id); - continue; - } - if (panel.attrs.trackGroupId !== undefined) { - const trackGroup = globals.state.trackGroups[panel.attrs.trackGroupId]; - // Only select a track group and all child tracks if it is closed. - if (trackGroup.collapsed) { - tracks.push(panel.attrs.trackGroupId); - for (const track of trackGroup.tracks) { - tracks.push(track); - } - } - } - } - globals.frontendLocalState.selectArea(area.startSec, area.endSec, tracks); - } - - constructor(vnode: m.CVnode) { - this.attrs = vnode.attrs; - this.canvasRedrawer = () => this.redrawCanvas(); - globals.rafScheduler.addRedrawCallback(this.canvasRedrawer); - perfDisplay.addContainer(this); - this.flowEventsRenderer = new FlowEventsRenderer(); - } - - oncreate(vnodeDom: m.CVnodeDOM) { - // Save the canvas context in the state. - const canvas = - vnodeDom.dom.querySelector('.main-canvas') as HTMLCanvasElement; - const ctx = canvas.getContext('2d'); - if (!ctx) { - throw Error('Cannot create canvas context'); - } - this.ctx = ctx; - - this.readParentSizeFromDom(vnodeDom.dom); - this.readPanelHeightsFromDom(vnodeDom.dom); - - this.updateCanvasDimensions(); - this.repositionCanvas(); - - // Save the resize handler in the state so we can remove it later. - // TODO: Encapsulate resize handling better. - this.onResize = () => { - this.readParentSizeFromDom(vnodeDom.dom); - this.updateCanvasDimensions(); - this.repositionCanvas(); - globals.rafScheduler.scheduleFullRedraw(); - }; - - // Once ResizeObservers are out, we can stop accessing the window here. - window.addEventListener('resize', this.onResize); - - // TODO(dproy): Handle change in doesScroll attribute. - if (this.attrs.doesScroll) { - this.parentOnScroll = () => { - this.scrollTop = assertExists(vnodeDom.dom.parentElement).scrollTop; - this.repositionCanvas(); - globals.rafScheduler.scheduleRedraw(); - }; - vnodeDom.dom.parentElement!.addEventListener( - 'scroll', this.parentOnScroll, {passive: true}); - } - } - - onremove({attrs, dom}: m.CVnodeDOM) { - window.removeEventListener('resize', this.onResize); - globals.rafScheduler.removeRedrawCallback(this.canvasRedrawer); - if (attrs.doesScroll) { - dom.parentElement!.removeEventListener('scroll', this.parentOnScroll); - } - perfDisplay.removeContainer(this); - } - - isTrackGroupAttrs(attrs: unknown): attrs is TrackGroupAttrs { - return (attrs as {collapsed?: boolean}).collapsed !== undefined; - } - - renderPanel(node: AnyAttrsVnode, key: string, extraClass = ''): m.Vnode { - assertFalse(this.panelByKey.has(key)); - this.panelByKey.set(key, node); - - return m( - `.panel${extraClass}`, - {key, 'data-key': key}, - perfDebug() ? - [node, m('.debug-panel-border', {key: 'debug-panel-border'})] : - node); - } - - // Render a tree of panels into one vnode. Argument `path` is used to build - // `key` attribute for intermediate tree vnodes: otherwise Mithril internals - // will complain about keyed and non-keyed vnodes mixed together. - renderTree(node: AnyAttrsVnode, path: string): m.Vnode { - if (this.isTrackGroupAttrs(node.attrs)) { - return m( - 'div', - {key: path}, - this.renderPanel( - node.attrs.header, - `${path}-header`, - node.attrs.collapsed ? '' : '.sticky'), - ...node.attrs.childTracks.map( - (child, index) => this.renderTree(child, `${path}-${index}`))); - } - return this.renderPanel(node, assertExists(node.key) as string); - } - - view({attrs}: m.CVnode) { - this.attrs = attrs; - this.panelByKey.clear(); - const children = attrs.panels.map( - (panel, index) => this.renderTree(panel, `track-tree-${index}`)); - - return [ - m( - '.scroll-limiter', - m('canvas.main-canvas'), - ), - m('.panels', children), - ]; - } - - onupdate(vnodeDom: m.CVnodeDOM) { - const totalPanelHeightChanged = this.readPanelHeightsFromDom(vnodeDom.dom); - const parentSizeChanged = this.readParentSizeFromDom(vnodeDom.dom); - const canvasSizeShouldChange = - parentSizeChanged || !this.attrs.doesScroll && totalPanelHeightChanged; - if (canvasSizeShouldChange) { - this.updateCanvasDimensions(); - this.repositionCanvas(); - if (this.attrs.kind === 'TRACKS') { - globals.frontendLocalState.updateLocalLimits( - 0, this.parentWidth - TRACK_SHELL_WIDTH); - } - this.redrawCanvas(); - } - } - - private updateCanvasDimensions() { - this.canvasHeight = Math.floor( - this.attrs.doesScroll ? this.parentHeight * this.canvasOverdrawFactor : - this.totalPanelHeight); - const ctx = assertExists(this.ctx); - const canvas = assertExists(ctx.canvas); - canvas.style.height = `${this.canvasHeight}px`; - - // If're we're non-scrolling canvas and the scroll-limiter should always - // have the same height. Enforce this by explicitly setting the height. - if (!this.attrs.doesScroll) { - const scrollLimiter = canvas.parentElement; - if (scrollLimiter) { - scrollLimiter.style.height = `${this.canvasHeight}px`; - } - } - - const dpr = window.devicePixelRatio; - ctx.canvas.width = this.parentWidth * dpr; - ctx.canvas.height = this.canvasHeight * dpr; - ctx.scale(dpr, dpr); - } - - private repositionCanvas() { - const canvas = assertExists(assertExists(this.ctx).canvas); - const canvasYStart = - Math.floor(this.scrollTop - this.getCanvasOverdrawHeightPerSide()); - canvas.style.transform = `translateY(${canvasYStart}px)`; - } - - // Reads dimensions of parent node. Returns true if read dimensions are - // different from what was cached in the state. - private readParentSizeFromDom(dom: Element): boolean { - const oldWidth = this.parentWidth; - const oldHeight = this.parentHeight; - const clientRect = assertExists(dom.parentElement).getBoundingClientRect(); - // On non-MacOS if there is a solid scroll bar it can cover important - // pixels, reduce the size of the canvas so it doesn't overlap with - // the scroll bar. - this.parentWidth = - clientRect.width - globals.frontendLocalState.getScrollbarWidth(); - this.parentHeight = clientRect.height; - return this.parentHeight !== oldHeight || this.parentWidth !== oldWidth; - } - - // Reads dimensions of panels. Returns true if total panel height is different - // from what was cached in state. - private readPanelHeightsFromDom(dom: Element): boolean { - const prevHeight = this.totalPanelHeight; - this.panelInfos = []; - this.totalPanelHeight = 0; - const domRect = dom.getBoundingClientRect(); - this.panelContainerTop = domRect.y; - this.panelContainerHeight = domRect.height; - - dom.parentElement!.querySelectorAll('.panel').forEach((panel) => { - const key = assertExists(panel.getAttribute('data-key')); - const vnode = assertExists(this.panelByKey.get(key)); - - // NOTE: the id can be undefined for singletons like overview timeline. - const id = vnode.attrs.id || vnode.attrs.trackGroupId || ''; - const rect = panel.getBoundingClientRect(); - this.panelInfos.push({ - id, - height: rect.height, - width: rect.width, - x: rect.x, - y: rect.y, - vnode, - }); - this.totalPanelHeight += rect.height; - }); - - return this.totalPanelHeight !== prevHeight; - } - - private overlapsCanvas(yStart: number, yEnd: number) { - return yEnd > 0 && yStart < this.canvasHeight; - } - - private redrawCanvas() { - const redrawStart = debugNow(); - if (!this.ctx) return; - this.ctx.clearRect(0, 0, this.parentWidth, this.canvasHeight); - const canvasYStart = - Math.floor(this.scrollTop - this.getCanvasOverdrawHeightPerSide()); - - this.handleAreaSelection(); - - let panelYStart = 0; - let totalOnCanvas = 0; - const flowEventsRendererArgs = - new FlowEventsRendererArgs(this.parentWidth, this.canvasHeight); - for (let i = 0; i < this.panelInfos.length; i++) { - const panel = this.panelInfos[i].vnode; - const panelHeight = this.panelInfos[i].height; - const yStartOnCanvas = panelYStart - canvasYStart; - - if (!isPanelVNode(panel)) { - throw new Error('Vnode passed to panel container is not a panel'); - } - - flowEventsRendererArgs.registerPanel(panel, yStartOnCanvas, panelHeight); - - if (!this.overlapsCanvas(yStartOnCanvas, yStartOnCanvas + panelHeight)) { - panelYStart += panelHeight; - continue; - } - - totalOnCanvas++; - - this.ctx.save(); - this.ctx.translate(0, yStartOnCanvas); - const clipRect = new Path2D(); - const size = {width: this.parentWidth, height: panelHeight}; - clipRect.rect(0, 0, size.width, size.height); - this.ctx.clip(clipRect); - const beforeRender = debugNow(); - panel.state.renderCanvas(this.ctx, size, panel); - this.updatePanelStats( - i, panel.state, debugNow() - beforeRender, this.ctx, size); - this.ctx.restore(); - panelYStart += panelHeight; - } - - this.drawTopLayerOnCanvas(); - this.flowEventsRenderer.render(this.ctx, flowEventsRendererArgs); - // Collect performance as the last thing we do. - const redrawDur = debugNow() - redrawStart; - this.updatePerfStats(redrawDur, this.panelInfos.length, totalOnCanvas); - } - - // The panels each draw on the canvas but some details need to be drawn across - // the whole canvas rather than per panel. - private drawTopLayerOnCanvas() { - if (!this.ctx) return; - const area = globals.frontendLocalState.selectedArea; - if (area === undefined || - globals.frontendLocalState.areaY.start === undefined || - globals.frontendLocalState.areaY.end === undefined) { - return; - } - if (this.panelInfos.length === 0 || area.tracks.length === 0) return; - - // Find the minY and maxY of the selected tracks in this panel container. - let selectedTracksMinY = this.panelContainerHeight + this.panelContainerTop; - let selectedTracksMaxY = this.panelContainerTop; - let trackFromCurrentContainerSelected = false; - for (let i = 0; i < this.panelInfos.length; i++) { - if (area.tracks.includes(this.panelInfos[i].id)) { - trackFromCurrentContainerSelected = true; - selectedTracksMinY = Math.min(selectedTracksMinY, this.panelInfos[i].y); - selectedTracksMaxY = Math.max( - selectedTracksMaxY, - this.panelInfos[i].y + this.panelInfos[i].height); - } - } - - // No box should be drawn if there are no selected tracks in the current - // container. - if (!trackFromCurrentContainerSelected) { - return; - } - - const startX = globals.frontendLocalState.timeScale.timeToPx(area.startSec); - const endX = globals.frontendLocalState.timeScale.timeToPx(area.endSec); - // To align with where to draw on the canvas subtract the first panel Y. - selectedTracksMinY -= this.panelContainerTop; - selectedTracksMaxY -= this.panelContainerTop; - this.ctx.save(); - this.ctx.strokeStyle = SELECTION_STROKE_COLOR; - this.ctx.lineWidth = 1; - const canvasYStart = - Math.floor(this.scrollTop - this.getCanvasOverdrawHeightPerSide()); - this.ctx.translate(TRACK_SHELL_WIDTH, -canvasYStart); - this.ctx.strokeRect( - startX, - selectedTracksMaxY, - endX - startX, - selectedTracksMinY - selectedTracksMaxY); - this.ctx.restore(); - } - - private updatePanelStats( - panelIndex: number, panel: Panel, renderTime: number, - ctx: CanvasRenderingContext2D, size: PanelSize) { - if (!perfDebug()) return; - let renderStats = this.panelPerfStats.get(panel); - if (renderStats === undefined) { - renderStats = new RunningStatistics(); - this.panelPerfStats.set(panel, renderStats); - } - renderStats.addValue(renderTime); - - const statW = 300; - ctx.fillStyle = 'hsl(97, 100%, 96%)'; - ctx.fillRect(size.width - statW, size.height - 20, statW, 20); - ctx.fillStyle = 'hsla(122, 77%, 22%)'; - const statStr = `Panel ${panelIndex + 1} | ` + runningStatStr(renderStats); - ctx.fillText(statStr, size.width - statW, size.height - 10); - } - - private updatePerfStats( - renderTime: number, totalPanels: number, panelsOnCanvas: number) { - if (!perfDebug()) return; - this.perfStats.renderStats.addValue(renderTime); - this.perfStats.totalPanels = totalPanels; - this.perfStats.panelsOnCanvas = panelsOnCanvas; - } - - renderPerfStats(index: number) { - assertTrue(perfDebug()); - return [m( - 'section', - m('div', `Panel Container ${index + 1}`), - m('div', - `${this.perfStats.totalPanels} panels, ` + - `${this.perfStats.panelsOnCanvas} on canvas.`), - m('div', runningStatStr(this.perfStats.renderStats)))]; - } - - private getCanvasOverdrawHeightPerSide() { - const overdrawHeight = (this.canvasOverdrawFactor - 1) * this.parentHeight; - return overdrawHeight / 2; - } -} diff --git a/third_party/perfetto/ui/src/frontend/perf.ts b/third_party/perfetto/ui/src/frontend/perf.ts deleted file mode 100644 index 504bafef50e1..000000000000 --- a/third_party/perfetto/ui/src/frontend/perf.ts +++ /dev/null @@ -1,119 +0,0 @@ - -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {Actions} from '../common/actions'; -import {globals} from './globals'; -import {PanelContainer} from './panel_container'; - -// Shorthand for if globals perf debug mode is on. -export const perfDebug = () => globals.state.perfDebug; - -// Returns performance.now() if perfDebug is enabled, otherwise 0. -// This is needed because calling performance.now is generally expensive -// and should not be done for every frame. -export const debugNow = () => perfDebug() ? performance.now() : 0; - -// Returns execution time of |fn| if perf debug mode is on. Returns 0 otherwise. -export function measure(fn: () => void): number { - const start = debugNow(); - fn(); - return debugNow() - start; -} - -// Stores statistics about samples, and keeps a fixed size buffer of most recent -// samples. -export class RunningStatistics { - private _count = 0; - private _mean = 0; - private _lastValue = 0; - private _ptr = 0; - - private buffer: number[] = []; - - constructor(private _maxBufferSize = 10) {} - - addValue(value: number) { - this._lastValue = value; - if (this.buffer.length >= this._maxBufferSize) { - this.buffer[this._ptr++] = value; - if (this._ptr >= this.buffer.length) { - this._ptr -= this.buffer.length; - } - } else { - this.buffer.push(value); - } - - this._mean = (this._mean * this._count + value) / (this._count + 1); - this._count++; - } - - get mean() { - return this._mean; - } - get count() { - return this._count; - } - get bufferMean() { - return this.buffer.reduce((sum, v) => sum + v, 0) / this.buffer.length; - } - get bufferSize() { - return this.buffer.length; - } - get maxBufferSize() { - return this._maxBufferSize; - } - get last() { - return this._lastValue; - } -} - -// Returns a summary string representation of a RunningStatistics object. -export function runningStatStr(stat: RunningStatistics) { - return `Last: ${stat.last.toFixed(2)}ms | ` + - `Avg: ${stat.mean.toFixed(2)}ms | ` + - `Avg${stat.maxBufferSize}: ${stat.bufferMean.toFixed(2)}ms`; -} - -// Globals singleton class that renders performance stats for the whole app. -class PerfDisplay { - private containers: PanelContainer[] = []; - addContainer(container: PanelContainer) { - this.containers.push(container); - } - - removeContainer(container: PanelContainer) { - const i = this.containers.indexOf(container); - this.containers.splice(i, 1); - } - - renderPerfStats() { - if (!perfDebug()) return; - const perfDisplayEl = document.querySelector('.perf-stats'); - if (!perfDisplayEl) return; - m.render(perfDisplayEl, [ - m('section', globals.rafScheduler.renderPerfStats()), - m('button.close-button', - { - onclick: () => globals.dispatch(Actions.togglePerfDebug({})), - }, - m('i.material-icons', 'close')), - this.containers.map((c, i) => m('section', c.renderPerfStats(i))), - ]); - } -} - -export const perfDisplay = new PerfDisplay(); diff --git a/third_party/perfetto/ui/src/frontend/perf_unittest.ts b/third_party/perfetto/ui/src/frontend/perf_unittest.ts deleted file mode 100644 index 5ba357c2d26b..000000000000 --- a/third_party/perfetto/ui/src/frontend/perf_unittest.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {RunningStatistics} from './perf'; - -test('buffer size is accurate before reaching max capacity', () => { - const buf = new RunningStatistics(10); - - for (let i = 0; i < 10; i++) { - buf.addValue(i); - expect(buf.bufferSize).toEqual(i + 1); - } -}); - -test('buffer size is accurate after reaching max capacity', () => { - const buf = new RunningStatistics(10); - - for (let i = 0; i < 10; i++) { - buf.addValue(i); - } - - for (let i = 0; i < 10; i++) { - buf.addValue(i); - expect(buf.bufferSize).toEqual(10); - } -}); - -test('buffer mean is accurate before reaching max capacity', () => { - const buf = new RunningStatistics(10); - - buf.addValue(1); - buf.addValue(2); - buf.addValue(3); - - expect(buf.bufferMean).toBeCloseTo(2); -}); - -test('buffer mean is accurate after reaching max capacity', () => { - const buf = new RunningStatistics(10); - - for (let i = 0; i < 20; i++) { - buf.addValue(2); - } - - expect(buf.bufferMean).toBeCloseTo(2); -}); diff --git a/third_party/perfetto/ui/src/frontend/pivot_table.ts b/third_party/perfetto/ui/src/frontend/pivot_table.ts deleted file mode 100644 index 530868f6542b..000000000000 --- a/third_party/perfetto/ui/src/frontend/pivot_table.ts +++ /dev/null @@ -1,558 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import m from 'mithril'; - -import {sqliteString} from '../base/string_utils'; -import {Actions} from '../common/actions'; -import {DropDirection} from '../common/dragndrop_logic'; -import {COUNT_AGGREGATION} from '../common/empty_state'; -import {ColumnType} from '../common/query_result'; -import { - Area, - PivotTableAreaState, - PivotTableResult, - SortDirection, -} from '../common/state'; -import {fromNs, timeToCode} from '../common/time'; - -import {globals} from './globals'; -import {Panel} from './panel'; -import { - aggregationIndex, - areaFilter, - extractArgumentExpression, - sliceAggregationColumns, - tables, -} from './pivot_table_query_generator'; -import { - Aggregation, - AggregationFunction, - columnKey, - PivotTree, - TableColumn, -} from './pivot_table_types'; -import {PopupMenuButton, popupMenuIcon, PopupMenuItem} from './popup_menu'; -import {runQueryInNewTab} from './query_result_tab'; -import {ReorderableCell, ReorderableCellGroup} from './reorderable_cells'; -import {AttributeModalHolder} from './tables/attribute_modal_holder'; - - -interface PathItem { - tree: PivotTree; - nextKey: ColumnType; -} - -interface PivotTableAttrs { - selectionArea: PivotTableAreaState; -} - -interface DrillFilter { - column: TableColumn; - value: ColumnType; -} - -function drillFilterColumnName(column: TableColumn): string { - switch (column.kind) { - case 'argument': - return extractArgumentExpression(column.argument, 'slice'); - case 'regular': - return `${column.table}.${column.column}`; - } -} - -// Convert DrillFilter to SQL condition to be used in WHERE clause. -function renderDrillFilter(filter: DrillFilter): string { - const column = drillFilterColumnName(filter.column); - if (filter.value === null) { - return `${column} IS NULL`; - } else if (typeof filter.value === 'number') { - return `${column} = ${filter.value}`; - } else if (filter.value instanceof Uint8Array) { - throw new Error(`BLOB as DrillFilter not implemented`); - } else if (typeof filter.value === 'bigint') { - return `${column} = ${filter.value}`; - } - return `${column} = ${sqliteString(filter.value)}`; -} - -function readableColumnName(column: TableColumn) { - switch (column.kind) { - case 'argument': - return `Argument ${column.argument}`; - case 'regular': - return `${column.table}.${column.column}`; - } -} - -export function markFirst(index: number) { - if (index === 0) { - return '.first'; - } - return ''; -} - -export class PivotTable extends Panel { - constructor() { - super(); - this.attributeModalHolder = new AttributeModalHolder((arg) => { - globals.dispatch(Actions.setPivotTablePivotSelected({ - column: {kind: 'argument', argument: arg}, - selected: true, - })); - globals.dispatch( - Actions.setPivotTableQueryRequested({queryRequested: true})); - }); - } - - get pivotState() { - return globals.state.nonSerializableState.pivotTable; - } - get constrainToArea() { - return globals.state.nonSerializableState.pivotTable.constrainToArea; - } - - renderCanvas(): void {} - - renderDrillDownCell(area: Area, filters: DrillFilter[]) { - return m( - 'td', - m('button', - { - title: 'All corresponding slices', - onclick: () => { - const queryFilters = filters.map(renderDrillFilter); - if (this.constrainToArea) { - queryFilters.push(areaFilter(area)); - } - const query = ` - select slice.* from slice - left join thread_track on slice.track_id = thread_track.id - left join thread using (utid) - left join process using (upid) - where ${queryFilters.join(' and \n')} - `; - // TODO(ddrone): the UI of running query as if it was a canned or - // custom query is a temporary one, replace with a proper UI. - runQueryInNewTab(query, 'Pivot table details'); - }, - }, - m('i.material-icons', 'arrow_right'))); - } - - renderSectionRow( - area: Area, path: PathItem[], tree: PivotTree, - result: PivotTableResult): m.Vnode { - const renderedCells = []; - for (let j = 0; j + 1 < path.length; j++) { - renderedCells.push(m('td', m('span.indent', ' '), `${path[j].nextKey}`)); - } - - const treeDepth = result.metadata.pivotColumns.length; - const colspan = treeDepth - path.length + 1; - const button = - m('button', - { - onclick: () => { - tree.isCollapsed = !tree.isCollapsed; - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - m('i.material-icons', - tree.isCollapsed ? 'expand_more' : 'expand_less')); - - renderedCells.push( - m('td', {colspan}, button, `${path[path.length - 1].nextKey}`)); - - for (let i = 0; i < result.metadata.aggregationColumns.length; i++) { - const renderedValue = this.renderCell( - result.metadata.aggregationColumns[i].column, tree.aggregates[i]); - renderedCells.push(m('td' + markFirst(i), renderedValue)); - } - - const drillFilters: DrillFilter[] = []; - for (let i = 0; i < path.length; i++) { - drillFilters.push({ - value: `${path[i].nextKey}`, - column: result.metadata.pivotColumns[i], - }); - } - - renderedCells.push(this.renderDrillDownCell(area, drillFilters)); - return m('tr', renderedCells); - } - - renderCell(column: TableColumn, value: ColumnType): string { - if (column.kind === 'regular' && - (column.column === 'dur' || column.column === 'thread_dur')) { - if (typeof value === 'number') { - return timeToCode(fromNs(value)); - } - } - return `${value}`; - } - - renderTree( - area: Area, path: PathItem[], tree: PivotTree, result: PivotTableResult, - sink: m.Vnode[]) { - if (tree.isCollapsed) { - sink.push(this.renderSectionRow(area, path, tree, result)); - return; - } - if (tree.children.size > 0) { - // Avoid rendering the intermediate results row for the root of tree - // and in case there's only one child subtree. - if (!tree.isCollapsed && path.length > 0 && tree.children.size !== 1) { - sink.push(this.renderSectionRow(area, path, tree, result)); - } - for (const [key, childTree] of tree.children.entries()) { - path.push({tree: childTree, nextKey: key}); - this.renderTree(area, path, childTree, result, sink); - path.pop(); - } - return; - } - - // Avoid rendering the intermediate results row if it has only one leaf - // row. - if (!tree.isCollapsed && path.length > 0 && tree.rows.length > 1) { - sink.push(this.renderSectionRow(area, path, tree, result)); - } - for (const row of tree.rows) { - const renderedCells = []; - const drillFilters: DrillFilter[] = []; - const treeDepth = result.metadata.pivotColumns.length; - for (let j = 0; j < treeDepth; j++) { - const value = this.renderCell(result.metadata.pivotColumns[j], row[j]); - if (j < path.length) { - renderedCells.push(m('td', m('span.indent', ' '), value)); - } else { - renderedCells.push(m(`td`, value)); - } - drillFilters.push( - {column: result.metadata.pivotColumns[j], value: row[j]}); - } - for (let j = 0; j < result.metadata.aggregationColumns.length; j++) { - const value = row[aggregationIndex(treeDepth, j)]; - const renderedValue = this.renderCell( - result.metadata.aggregationColumns[j].column, value); - renderedCells.push(m('td.aggregation' + markFirst(j), renderedValue)); - } - - renderedCells.push(this.renderDrillDownCell(area, drillFilters)); - sink.push(m('tr', renderedCells)); - } - } - - renderTotalsRow(queryResult: PivotTableResult) { - const overallValuesRow = - [m('td.total-values', - {'colspan': queryResult.metadata.pivotColumns.length}, - m('strong', 'Total values:'))]; - for (let i = 0; i < queryResult.metadata.aggregationColumns.length; i++) { - overallValuesRow.push( - m('td' + markFirst(i), - this.renderCell( - queryResult.metadata.aggregationColumns[i].column, - queryResult.tree.aggregates[i]))); - } - overallValuesRow.push(m('td')); - return m('tr', overallValuesRow); - } - - sortingItem(aggregationIndex: number, order: SortDirection): PopupMenuItem { - return { - itemType: 'regular', - text: order === 'DESC' ? 'Highest first' : 'Lowest first', - callback() { - globals.dispatch( - Actions.setPivotTableSortColumn({aggregationIndex, order})); - globals.dispatch( - Actions.setPivotTableQueryRequested({queryRequested: true})); - }, - }; - } - - readableAggregationName(aggregation: Aggregation) { - if (aggregation.aggregationFunction === 'COUNT') { - return 'Count'; - } - return `${aggregation.aggregationFunction}(${ - readableColumnName(aggregation.column)})`; - } - - aggregationPopupItem( - aggregation: Aggregation, index: number, - nameOverride?: string): PopupMenuItem { - return { - itemType: 'regular', - text: nameOverride ?? readableColumnName(aggregation.column), - callback: () => { - globals.dispatch( - Actions.addPivotTableAggregation({aggregation, after: index})); - globals.dispatch( - Actions.setPivotTableQueryRequested({queryRequested: true})); - }, - }; - } - - aggregationPopupTableGroup(table: string, columns: string[], index: number): - PopupMenuItem|undefined { - const items = []; - for (const column of columns) { - const tableColumn: TableColumn = {kind: 'regular', table, column}; - items.push(this.aggregationPopupItem( - {aggregationFunction: 'SUM', column: tableColumn}, index)); - } - - if (items.length === 0) { - return undefined; - } - - return { - itemType: 'group', - itemId: `aggregations-${table}`, - text: `Add ${table} aggregation`, - children: items, - }; - } - - renderAggregationHeaderCell( - aggregation: Aggregation, index: number, - removeItem: boolean): ReorderableCell { - const popupItems: PopupMenuItem[] = []; - const state = globals.state.nonSerializableState.pivotTable; - if (aggregation.sortDirection === undefined) { - popupItems.push( - this.sortingItem(index, 'DESC'), this.sortingItem(index, 'ASC')); - } else { - // Table is already sorted by the same column, return one item with - // opposite direction. - popupItems.push(this.sortingItem( - index, aggregation.sortDirection === 'DESC' ? 'ASC' : 'DESC')); - } - const otherAggs: AggregationFunction[] = ['SUM', 'MAX', 'MIN', 'AVG']; - if (aggregation.aggregationFunction !== 'COUNT') { - for (const otherAgg of otherAggs) { - if (aggregation.aggregationFunction === otherAgg) { - continue; - } - - popupItems.push({ - itemType: 'regular', - text: otherAgg, - callback() { - globals.dispatch(Actions.setPivotTableAggregationFunction( - {index, function: otherAgg})); - globals.dispatch( - Actions.setPivotTableQueryRequested({queryRequested: true})); - }, - }); - } - } - - if (removeItem) { - popupItems.push({ - itemType: 'regular', - text: 'Remove', - callback: () => { - globals.dispatch(Actions.removePivotTableAggregation({index})); - globals.dispatch( - Actions.setPivotTableQueryRequested({queryRequested: true})); - }, - }); - } - - let hasCount = false; - for (const agg of state.selectedAggregations.values()) { - if (agg.aggregationFunction === 'COUNT') { - hasCount = true; - } - } - - if (!hasCount) { - popupItems.push(this.aggregationPopupItem( - COUNT_AGGREGATION, index, 'Add count aggregation')); - } - - const sliceAggregationsItem = this.aggregationPopupTableGroup( - 'slice', sliceAggregationColumns, index); - if (sliceAggregationsItem !== undefined) { - popupItems.push(sliceAggregationsItem); - } - - return { - extraClass: '.aggregation' + markFirst(index), - content: [ - this.readableAggregationName(aggregation), - m(PopupMenuButton, { - icon: popupMenuIcon(aggregation.sortDirection), - items: popupItems, - }), - ], - }; - } - - attributeModalHolder: AttributeModalHolder; - - renderPivotColumnHeader( - queryResult: PivotTableResult, pivot: TableColumn, - selectedPivots: Set): ReorderableCell { - const items: PopupMenuItem[] = [{ - itemType: 'regular', - text: 'Add argument pivot', - callback: () => { - this.attributeModalHolder.start(); - }, - }]; - if (queryResult.metadata.pivotColumns.length > 1) { - items.push({ - itemType: 'regular', - text: 'Remove', - callback() { - globals.dispatch(Actions.setPivotTablePivotSelected( - {column: pivot, selected: false})); - globals.dispatch( - Actions.setPivotTableQueryRequested({queryRequested: true})); - }, - }); - } - - for (const table of tables) { - const group: PopupMenuItem[] = []; - for (const columnName of table.columns) { - const column: TableColumn = { - kind: 'regular', - table: table.name, - column: columnName, - }; - if (selectedPivots.has(columnKey(column))) { - continue; - } - - group.push({ - itemType: 'regular', - text: columnName, - callback() { - globals.dispatch( - Actions.setPivotTablePivotSelected({column, selected: true})); - globals.dispatch( - Actions.setPivotTableQueryRequested({queryRequested: true})); - }, - }); - } - items.push({ - itemType: 'group', - itemId: `pivot-${table.name}`, - text: `Add ${table.name} pivot`, - children: group, - }); - } - - return { - content: [ - readableColumnName(pivot), - m(PopupMenuButton, {icon: 'more_horiz', items}), - ], - }; - } - - renderResultsTable(attrs: PivotTableAttrs) { - const state = globals.state.nonSerializableState.pivotTable; - if (state.queryResult === null) { - return m('div', 'Loading...'); - } - const queryResult: PivotTableResult = state.queryResult; - - const renderedRows: m.Vnode[] = []; - const tree = state.queryResult.tree; - - if (tree.children.size === 0 && tree.rows.length === 0) { - // Empty result, render a special message - return m('.empty-result', 'No slices in the current selection.'); - } - - this.renderTree( - globals.state.areas[attrs.selectionArea.areaId], - [], - tree, - state.queryResult, - renderedRows); - - const selectedPivots = - new Set(this.pivotState.selectedPivots.map(columnKey)); - const pivotTableHeaders = state.selectedPivots.map( - (pivot) => - this.renderPivotColumnHeader(queryResult, pivot, selectedPivots)); - - const removeItem = state.queryResult.metadata.aggregationColumns.length > 1; - const aggregationTableHeaders = - state.queryResult.metadata.aggregationColumns.map( - (aggregation, index) => this.renderAggregationHeaderCell( - aggregation, index, removeItem)); - - return m( - 'table.pivot-table', - m('thead', - // First row of the table, containing names of pivot and aggregation - // columns, as well as popup menus to modify the columns. Last cell - // is empty because of an extra column with "drill down" button for - // each pivot table row. - m('tr.header', - m(ReorderableCellGroup, { - cells: pivotTableHeaders, - onReorder: ( - from: number, to: number, direction: DropDirection) => { - globals.dispatch( - Actions.changePivotTablePivotOrder({from, to, direction})); - globals.dispatch(Actions.setPivotTableQueryRequested( - {queryRequested: true})); - }, - }), - m(ReorderableCellGroup, { - cells: aggregationTableHeaders, - onReorder: - (from: number, to: number, direction: DropDirection) => { - globals.dispatch(Actions.changePivotTableAggregationOrder( - {from, to, direction})); - globals.dispatch(Actions.setPivotTableQueryRequested( - {queryRequested: true})); - }, - }), - m('td.menu', m(PopupMenuButton, { - icon: 'menu', - items: [{ - itemType: 'regular', - text: state.constrainToArea ? - 'Query data for the whole timeline' : - 'Constrain to selected area', - callback: () => { - globals.dispatch(Actions.setPivotTableConstrainToArea( - {constrain: !state.constrainToArea})); - globals.dispatch(Actions.setPivotTableQueryRequested( - {queryRequested: true})); - }, - }], - })))), - m('tbody', this.renderTotalsRow(state.queryResult), renderedRows)); - } - - view({attrs}: m.Vnode): m.Children { - this.attributeModalHolder.update(); - - return m('.pivot-table', this.renderResultsTable(attrs)); - } -} diff --git a/third_party/perfetto/ui/src/frontend/pivot_table_argument_popup.ts b/third_party/perfetto/ui/src/frontend/pivot_table_argument_popup.ts deleted file mode 100644 index 6479d00b5866..000000000000 --- a/third_party/perfetto/ui/src/frontend/pivot_table_argument_popup.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import m from 'mithril'; -import {globals} from './globals'; - -interface ArgumentPopupArgs { - onArgumentChange: (arg: string) => void; - knownArguments: string[]; -} - -function longestString(array: string[]): string { - if (array.length === 0) { - return ''; - } - - let answer = array[0]; - for (let i = 1; i < array.length; i++) { - if (array[i].length > answer.length) { - answer = array[i]; - } - } - return answer; -} - -// Component rendering popup for entering an argument name to use as a pivot. -export class ArgumentPopup implements m.ClassComponent { - argument = ''; - - setArgument(attrs: ArgumentPopupArgs, arg: string) { - this.argument = arg; - attrs.onArgumentChange(arg); - globals.rafScheduler.scheduleFullRedraw(); - } - - renderMatches(attrs: ArgumentPopupArgs): m.Child[] { - const result: m.Child[] = []; - - for (const option of attrs.knownArguments) { - // Would be great to have smarter fuzzy matching, but in the meantime - // simple substring check should work fine. - const index = option.indexOf(this.argument); - - if (index === -1) { - continue; - } - - if (result.length === 10) { - break; - } - - result.push( - m('div', - { - onclick: () => { - this.setArgument(attrs, option); - }, - }, - option.substring(0, index), - // Highlight the matching part with bold font - m('strong', this.argument), - option.substring(index + this.argument.length))); - } - - return result; - } - - view({attrs}: m.Vnode): m.Child { - return m( - '.name-completion', - m('input', { - oncreate: (vnode: m.VnodeDOM) => - (vnode.dom as HTMLInputElement).focus(), - oninput: (e: Event) => { - const input = e.target as HTMLInputElement; - this.setArgument(attrs, input.value); - }, - value: this.argument, - }), - m('.arguments-popup-sizer', longestString(attrs.knownArguments)), - this.renderMatches(attrs)); - } -} diff --git a/third_party/perfetto/ui/src/frontend/pivot_table_query_generator.ts b/third_party/perfetto/ui/src/frontend/pivot_table_query_generator.ts deleted file mode 100644 index 0c61f561834b..000000000000 --- a/third_party/perfetto/ui/src/frontend/pivot_table_query_generator.ts +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import {sqliteString} from '../base/string_utils'; -import { - Area, - PivotTableQuery, - PivotTableState, -} from '../common/state'; -import {toNs} from '../common/time'; -import { - getSelectedTrackIds, -} from '../controller/aggregation/slice_aggregation_controller'; - -import {globals} from './globals'; -import { - Aggregation, - TableColumn, -} from './pivot_table_types'; - -export interface Table { - name: string; - columns: string[]; -} - -export const sliceTable = { - name: 'slice', - columns: ['type', 'ts', 'dur', 'category', 'name', 'depth'], -}; - -// Columns of `slice` table available for aggregation. -export const sliceAggregationColumns = [ - 'ts', - 'dur', - 'depth', - 'thread_ts', - 'thread_dur', - 'thread_instruction_count', - 'thread_instruction_delta', -]; - -// List of available tables to query, used to populate selectors of pivot -// columns in the UI. -export const tables: Table[] = [ - sliceTable, - { - name: 'process', - columns: [ - 'type', - 'pid', - 'name', - 'parent_upid', - 'uid', - 'android_appid', - 'cmdline', - ], - }, - {name: 'thread', columns: ['type', 'name', 'tid', 'upid', 'is_main_thread']}, - {name: 'thread_track', columns: ['type', 'name', 'utid']}, -]; - -// Queried "table column" is either: -// 1. A real one, represented as object with table and column name. -// 2. Pseudo-column 'count' that's rendered as '1' in SQL to use in queries like -// `select sum(1), name from slice group by name`. - -export interface RegularColumn { - kind: 'regular'; - table: string; - column: string; -} - -export interface ArgumentColumn { - kind: 'argument'; - argument: string; -} - -// Exception thrown by query generator in case incoming parameters are not -// suitable in order to build a correct query; these are caught by the UI and -// displayed to the user. -export class QueryGeneratorError extends Error {} - -// Internal column name for different rollover levels of aggregate columns. -function aggregationAlias(aggregationIndex: number): string { - return `agg_${aggregationIndex}`; -} - -export function areaFilter(area: Area): string { - return ` - ts + dur > ${toNs(area.startSec)} - and ts < ${toNs(area.endSec)} - and track_id in (${getSelectedTrackIds(area).join(', ')}) - `; -} - -export function expression(column: TableColumn): string { - switch (column.kind) { - case 'regular': - return `${column.table}.${column.column}`; - case 'argument': - return extractArgumentExpression(column.argument, 'slice'); - } -} - -function aggregationExpression(aggregation: Aggregation): string { - if (aggregation.aggregationFunction === 'COUNT') { - return 'COUNT()'; - } - return `${aggregation.aggregationFunction}(${ - expression(aggregation.column)})`; -} - -export function extractArgumentExpression(argument: string, table?: string) { - const prefix = table === undefined ? '' : `${table}.`; - return `extract_arg(${prefix}arg_set_id, ${sqliteString(argument)})`; -} - -export function aggregationIndex(pivotColumns: number, aggregationNo: number) { - return pivotColumns + aggregationNo; -} - -export function generateQueryFromState(state: PivotTableState): - PivotTableQuery { - if (state.selectionArea === undefined) { - throw new QueryGeneratorError('Should not be called without area'); - } - - const sliceTableAggregations = [...state.selectedAggregations.values()]; - if (sliceTableAggregations.length === 0) { - throw new QueryGeneratorError('No aggregations selected'); - } - - const pivots = state.selectedPivots; - - const aggregations = sliceTableAggregations.map( - (agg, index) => - `${aggregationExpression(agg)} as ${aggregationAlias(index)}`); - const countIndex = aggregations.length; - // Extra count aggregation, needed in order to compute combined averages. - aggregations.push('COUNT() as hidden_count'); - - const renderedPivots = pivots.map(expression); - const sortClauses: string[] = []; - for (let i = 0; i < sliceTableAggregations.length; i++) { - const sortDirection = sliceTableAggregations[i].sortDirection; - if (sortDirection !== undefined) { - sortClauses.push(`${aggregationAlias(i)} ${sortDirection}`); - } - } - - const joins = ` - left join thread_track on thread_track.id = slice.track_id - left join thread using (utid) - left join process using (upid) - `; - - const whereClause = state.constrainToArea ? - `where ${areaFilter(globals.state.areas[state.selectionArea.areaId])}` : - ''; - const text = ` - select - ${renderedPivots.concat(aggregations).join(',\n')} - from slice - ${pivots.length > 0 ? joins : ''} - ${whereClause} - group by ${renderedPivots.join(', ')} - ${sortClauses.length > 0 ? ('order by ' + sortClauses.join(', ')) : ''} - `; - - return { - text, - metadata: { - pivotColumns: pivots, - aggregationColumns: sliceTableAggregations, - countIndex, - }, - }; -} diff --git a/third_party/perfetto/ui/src/frontend/pivot_table_types.ts b/third_party/perfetto/ui/src/frontend/pivot_table_types.ts deleted file mode 100644 index 0e4913bb5554..000000000000 --- a/third_party/perfetto/ui/src/frontend/pivot_table_types.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {EqualsBuilder} from '../common/comparator_builder'; -import {ColumnType} from '../common/query_result'; -import {SortDirection} from '../common/state'; - -// Node in the hierarchical pivot tree. Only leaf nodes contain data from the -// query result. -export interface PivotTree { - // Whether the node should be collapsed in the UI, false by default and can - // be toggled with the button. - isCollapsed: boolean; - - // Non-empty only in internal nodes. - children: Map; - aggregates: ColumnType[]; - - // Non-empty only in leaf nodes. - rows: ColumnType[][]; -} - -export type AggregationFunction = 'COUNT'|'SUM'|'MIN'|'MAX'|'AVG'; - -// Queried "table column" is either: -// 1. A real one, represented as object with table and column name. -// 2. Pseudo-column 'count' that's rendered as '1' in SQL to use in queries like -// `select sum(1), name from slice group by name`. - -export interface RegularColumn { - kind: 'regular'; - table: string; - column: string; -} - -export interface ArgumentColumn { - kind: 'argument'; - argument: string; -} - -export type TableColumn = RegularColumn|ArgumentColumn; - -export function tableColumnEquals(t1: TableColumn, t2: TableColumn): boolean { - switch (t1.kind) { - case 'argument': { - return t2.kind === 'argument' && t1.argument === t2.argument; - } - case 'regular': { - return t2.kind === 'regular' && t1.table === t2.table && - t1.column === t2.column; - } - } -} - -export function toggleEnabled( - compare: (fst: T, snd: T) => boolean, - arr: T[], - column: T, - enabled: boolean): void { - if (enabled && arr.find((value) => compare(column, value)) === undefined) { - arr.push(column); - } - if (!enabled) { - const index = arr.findIndex((value) => compare(column, value)); - if (index !== -1) { - arr.splice(index, 1); - } - } -} - -export interface Aggregation { - aggregationFunction: AggregationFunction; - column: TableColumn; - - // If the aggregation is sorted, the field contains a sorting direction. - sortDirection?: SortDirection; -} - -export function aggregationEquals(agg1: Aggregation, agg2: Aggregation) { - return new EqualsBuilder(agg1, agg2) - .comparePrimitive((agg) => agg.aggregationFunction) - .compare(tableColumnEquals, (agg) => agg.column) - .equals(); -} - -// Used to convert TableColumn to a string in order to store it in a Map, as -// ES6 does not support compound Set/Map keys. This function should only be used -// for interning keys, and does not have any requirements beyond different -// TableColumn objects mapping to different strings. -export function columnKey(tableColumn: TableColumn): string { - switch (tableColumn.kind) { - case 'argument': { - return `argument:${tableColumn.argument}`; - } - case 'regular': { - return `${tableColumn.table}.${tableColumn.column}`; - } - } -} - -export function aggregationKey(aggregation: Aggregation): string { - return `${aggregation.aggregationFunction}:${columnKey(aggregation.column)}`; -} diff --git a/third_party/perfetto/ui/src/frontend/popup_menu.ts b/third_party/perfetto/ui/src/frontend/popup_menu.ts deleted file mode 100644 index 5edabba9066c..000000000000 --- a/third_party/perfetto/ui/src/frontend/popup_menu.ts +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {SortDirection} from '../common/state'; - -import {globals} from './globals'; - -export interface RegularPopupMenuItem { - itemType: 'regular'; - // Display text - text: string; - // Action on menu item click - callback: () => void; -} - -// Helper function for simplifying defining menus. -export function menuItem( - text: string, action: () => void): RegularPopupMenuItem { - return { - itemType: 'regular', - text, - callback: action, - }; -} - -export interface GroupPopupMenuItem { - itemType: 'group'; - text: string; - itemId: string; - children: PopupMenuItem[]; -} - -export type PopupMenuItem = RegularPopupMenuItem|GroupPopupMenuItem; - -export interface PopupMenuButtonAttrs { - // Icon for button opening a menu - icon: string; - // List of popup menu items - items: PopupMenuItem[]; -} - -// To ensure having at most one popup menu on the screen at a time, we need to -// listen to click events on the whole page and close currently opened popup, if -// there's any. This class, used as a singleton, does exactly that. -class PopupHolder { - // Invariant: global listener should be register if and only if this.popup is - // not undefined. - popup: PopupMenuButton|undefined = undefined; - initialized = false; - listener: (e: MouseEvent) => void; - - constructor() { - this.listener = (e: MouseEvent) => { - // Only handle those events that are not part of dropdown menu themselves. - const hasDropdown = - e.composedPath().find(PopupHolder.isDropdownElement) !== undefined; - if (!hasDropdown) { - this.ensureHidden(); - } - }; - } - - static isDropdownElement(target: EventTarget) { - if (target instanceof HTMLElement) { - return target.tagName === 'DIV' && target.classList.contains('dropdown'); - } - return false; - } - - ensureHidden() { - if (this.popup !== undefined) { - this.popup.setVisible(false); - } - } - - clear() { - if (this.popup !== undefined) { - this.popup = undefined; - window.removeEventListener('click', this.listener); - } - } - - showPopup(popup: PopupMenuButton) { - this.ensureHidden(); - this.popup = popup; - window.addEventListener('click', this.listener); - } -} - -// Singleton instance of PopupHolder -const popupHolder = new PopupHolder(); - -// For a table column that can be sorted; the standard popup icon should -// reflect the current sorting direction. This function returns an icon -// corresponding to optional SortDirection according to which the column is -// sorted. (Optional because column might be unsorted) -export function popupMenuIcon(sortDirection?: SortDirection) { - switch (sortDirection) { - case undefined: - return 'more_horiz'; - case 'DESC': - return 'arrow_drop_down'; - case 'ASC': - return 'arrow_drop_up'; - } -} - -// Component that displays a button that shows a popup menu on click. -export class PopupMenuButton implements m.ClassComponent { - popupShown = false; - expandedGroups: Set = new Set(); - - setVisible(visible: boolean) { - this.popupShown = visible; - if (this.popupShown) { - popupHolder.showPopup(this); - } else { - popupHolder.clear(); - } - globals.rafScheduler.scheduleFullRedraw(); - } - - renderItem(item: PopupMenuItem): m.Child { - switch (item.itemType) { - case 'regular': - return m( - 'button.open-menu', - { - onclick: () => { - item.callback(); - // Hide the menu item after the action has been invoked - this.setVisible(false); - }, - }, - item.text); - case 'group': - const isExpanded = this.expandedGroups.has(item.itemId); - return m( - 'div', - m('button.open-menu.disallow-selection', - { - onclick: () => { - if (this.expandedGroups.has(item.itemId)) { - this.expandedGroups.delete(item.itemId); - } else { - this.expandedGroups.add(item.itemId); - } - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - // Show text with up/down arrow, depending on expanded state. - item.text + (isExpanded ? ' \u25B2' : ' \u25BC')), - isExpanded ? m('div.nested-menu', - item.children.map((item) => this.renderItem(item))) : - null); - } - } - - view(vnode: m.Vnode) { - return m( - '.dropdown', - m( - '.dropdown-button', - { - onclick: () => { - this.setVisible(!this.popupShown); - }, - }, - vnode.children, - m('i.material-icons', vnode.attrs.icon), - ), - m(this.popupShown ? '.popup-menu.opened' : '.popup-menu.closed', - vnode.attrs.items.map((item) => this.renderItem(item)))); - } -} diff --git a/third_party/perfetto/ui/src/frontend/post_message_handler.ts b/third_party/perfetto/ui/src/frontend/post_message_handler.ts deleted file mode 100644 index ed9d0787bae8..000000000000 --- a/third_party/perfetto/ui/src/frontend/post_message_handler.ts +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {Actions, PostedScrollToRange, PostedTrace} from '../common/actions'; - -import {initCssConstants} from './css_constants'; -import {globals} from './globals'; -import {toggleHelp} from './help_modal'; -import {showModal} from './modal'; -import {focusHorizontalRange} from './scroll_helper'; - -interface PostedTraceWrapped { - perfetto: PostedTrace; -} - -interface PostedScrollToRangeWrapped { - perfetto: PostedScrollToRange; -} - -// Returns whether incoming traces should be opened automatically or should -// instead require a user interaction. -function isTrustedOrigin(origin: string): boolean { - const TRUSTED_ORIGINS = [ - 'https://chrometto.googleplex.com', - 'https://uma.googleplex.com', - 'https://android-build.googleplex.com', - ]; - if (origin === window.origin) return true; - if (TRUSTED_ORIGINS.includes(origin)) return true; - - const hostname = new URL(origin).hostname; - if (hostname.endsWith('corp.google.com')) return true; - if (hostname === 'localhost' || hostname === '127.0.0.1') return true; - return false; -} - -// Returns whether we should ignore a given message based on the value of -// the 'perfettoIgnore' field in the event data. -function shouldGracefullyIgnoreMessage(messageEvent: MessageEvent) { - return messageEvent.data.perfettoIgnore === true; -} - -// The message handler supports loading traces from an ArrayBuffer. -// There is no other requirement than sending the ArrayBuffer as the |data| -// property. However, since this will happen across different origins, it is not -// possible for the source website to inspect whether the message handler is -// ready, so the message handler always replies to a 'PING' message with 'PONG', -// which indicates it is ready to receive a trace. -export function postMessageHandler(messageEvent: MessageEvent) { - if (shouldGracefullyIgnoreMessage(messageEvent)) { - // This message should not be handled in this handler, - // because it will be handled elsewhere. - return; - } - - if (messageEvent.origin === 'https://tagassistant.google.com') { - // The GA debugger, does a window.open() and sends messages to the GA - // script. Ignore them. - return; - } - - if (document.readyState !== 'complete') { - console.error('Ignoring message - document not ready yet.'); - return; - } - - const fromOpener = messageEvent.source === window.opener; - const fromIframeHost = messageEvent.source === window.parent; - // This adds support for the folowing flow: - // * A (page that whats to open a trace in perfetto) opens B - // * B (does something to get the traceBuffer) - // * A is navigated to Perfetto UI - // * B sends the traceBuffer to A - // * closes itself - const fromOpenee = (messageEvent.source as WindowProxy).opener === window; - - if (messageEvent.source === null || - !(fromOpener || fromIframeHost || fromOpenee)) { - // This can happen if an extension tries to postMessage. - return; - } - - if (!('data' in messageEvent)) { - throw new Error('Incoming message has no data property'); - } - - if (messageEvent.data === 'PING') { - // Cross-origin messaging means we can't read |messageEvent.source|, but - // it still needs to be of the correct type to be able to invoke the - // correct version of postMessage(...). - const windowSource = messageEvent.source as Window; - windowSource.postMessage('PONG', messageEvent.origin); - return; - } - - if (messageEvent.data === 'SHOW-HELP') { - toggleHelp(); - return; - } - - if (messageEvent.data === 'RELOAD-CSS-CONSTANTS') { - initCssConstants(); - return; - } - - let postedScrollToRange: PostedScrollToRange; - if (isPostedScrollToRange(messageEvent.data)) { - postedScrollToRange = messageEvent.data.perfetto; - scrollToTimeRange(postedScrollToRange); - return; - } - - let postedTrace: PostedTrace; - let keepApiOpen = false; - if (isPostedTraceWrapped(messageEvent.data)) { - postedTrace = sanitizePostedTrace(messageEvent.data.perfetto); - if (postedTrace.keepApiOpen) { - keepApiOpen = true; - } - } else if (messageEvent.data instanceof ArrayBuffer) { - postedTrace = {title: 'External trace', buffer: messageEvent.data}; - } else { - console.warn( - 'Unknown postMessage() event received. If you are trying to open a ' + - 'trace via postMessage(), this is a bug in your code. If not, this ' + - 'could be due to some Chrome extension.'); - console.log('origin:', messageEvent.origin, 'data:', messageEvent.data); - return; - } - - if (postedTrace.buffer.byteLength === 0) { - throw new Error('Incoming message trace buffer is empty'); - } - - if (!keepApiOpen) { - /* Removing this event listener to avoid callers posting the trace multiple - * times. If the callers add an event listener which upon receiving 'PONG' - * posts the trace to ui.perfetto.dev, the callers can receive multiple - * 'PONG' messages and accidentally post the trace multiple times. This was - * part of the cause of b/182502595. - */ - window.removeEventListener('message', postMessageHandler); - } - - const openTrace = () => { - // For external traces, we need to disable other features such as - // downloading and sharing a trace. - postedTrace.localOnly = true; - globals.dispatch(Actions.openTraceFromBuffer(postedTrace)); - }; - - // If the origin is trusted open the trace directly. - if (isTrustedOrigin(messageEvent.origin)) { - openTrace(); - return; - } - - // If not ask the user if they expect this and trust the origin. - showModal({ - title: 'Open trace?', - content: - m('div', - m('div', `${messageEvent.origin} is trying to open a trace file.`), - m('div', 'Do you trust the origin and want to proceed?')), - buttons: [ - {text: 'NO', primary: true}, - {text: 'YES', primary: false, action: openTrace}, - ], - }); -} - -function sanitizePostedTrace(postedTrace: PostedTrace): PostedTrace { - const result: PostedTrace = { - title: sanitizeString(postedTrace.title), - buffer: postedTrace.buffer, - keepApiOpen: postedTrace.keepApiOpen, - }; - if (postedTrace.url !== undefined) { - result.url = sanitizeString(postedTrace.url); - } - return result; -} - -function sanitizeString(str: string): string { - return str.replace(/[^A-Za-z0-9.\-_#:/?=&;%+$ ]/g, ' '); -} - -function isTraceViewerReady(): boolean { - return !!(globals.getCurrentEngine()?.ready); -} - -const _maxScrollToRangeAttempts = 20; -async function scrollToTimeRange( - postedScrollToRange: PostedScrollToRange, maxAttempts?: number) { - const ready = isTraceViewerReady(); - if (!ready) { - if (maxAttempts === undefined) { - maxAttempts = 0; - } - if (maxAttempts > _maxScrollToRangeAttempts) { - console.warn('Could not scroll to time range. Trace viewer not ready.'); - return; - } - setTimeout(scrollToTimeRange, 200, postedScrollToRange, maxAttempts + 1); - } else { - focusHorizontalRange( - postedScrollToRange.timeStart, - postedScrollToRange.timeEnd, - postedScrollToRange.viewPercentage); - } -} - -function isPostedScrollToRange(obj: unknown): - obj is PostedScrollToRangeWrapped { - const wrapped = obj as PostedScrollToRangeWrapped; - if (wrapped.perfetto === undefined) { - return false; - } - return wrapped.perfetto.timeStart !== undefined || - wrapped.perfetto.timeEnd !== undefined; -} - -function isPostedTraceWrapped(obj: any): obj is PostedTraceWrapped { - const wrapped = obj as PostedTraceWrapped; - if (wrapped.perfetto === undefined) { - return false; - } - return wrapped.perfetto.buffer !== undefined && - wrapped.perfetto.title !== undefined; -} diff --git a/third_party/perfetto/ui/src/frontend/publish.ts b/third_party/perfetto/ui/src/frontend/publish.ts deleted file mode 100644 index 7632e539ce2e..000000000000 --- a/third_party/perfetto/ui/src/frontend/publish.ts +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (C) 2021 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Actions} from '../common/actions'; -import {AggregateData, isEmptyData} from '../common/aggregation_data'; -import {ConversionJobStatusUpdate} from '../common/conversion_jobs'; -import { - LogBoundsKey, - LogEntriesKey, - LogExists, - LogExistsKey, -} from '../common/logs'; -import {MetricResult} from '../common/metric_data'; -import {CurrentSearchResults, SearchSummary} from '../common/search_data'; - -import { - CounterDetails, - CpuProfileDetails, - FlamegraphDetails, - Flow, - FtracePanelData, - FtraceStat, - globals, - QuantizedLoad, - SliceDetails, - ThreadDesc, - ThreadStateDetails, -} from './globals'; -import {findCurrentSelection} from './keyboard_event_handler'; - -export function publishOverviewData( - data: {[key: string]: QuantizedLoad|QuantizedLoad[]}) { - for (const [key, value] of Object.entries(data)) { - if (!globals.overviewStore.has(key)) { - globals.overviewStore.set(key, []); - } - if (value instanceof Array) { - globals.overviewStore.get(key)!.push(...value); - } else { - globals.overviewStore.get(key)!.push(value); - } - } - globals.rafScheduler.scheduleRedraw(); -} - -export function publishTrackData(args: {id: string, data: {}}) { - globals.setTrackData(args.id, args.data); - if ([LogExistsKey, LogBoundsKey, LogEntriesKey].includes(args.id)) { - const data = globals.trackDataStore.get(LogExistsKey) as LogExists; - if (data && data.exists) globals.rafScheduler.scheduleFullRedraw(); - } else { - globals.rafScheduler.scheduleRedraw(); - } -} - -export function publishMetricResult(metricResult: MetricResult) { - globals.setMetricResult(metricResult); - globals.publishRedraw(); -} - -export function publishSelectedFlows(selectedFlows: Flow[]) { - globals.selectedFlows = selectedFlows; - globals.publishRedraw(); -} - -export function publishCounterDetails(click: CounterDetails) { - globals.counterDetails = click; - globals.publishRedraw(); -} - -export function publishFlamegraphDetails(click: FlamegraphDetails) { - globals.flamegraphDetails = click; - globals.publishRedraw(); -} - -export function publishCpuProfileDetails(details: CpuProfileDetails) { - globals.cpuProfileDetails = details; - globals.publishRedraw(); -} - -export function publishFtraceCounters(counters: FtraceStat[]) { - globals.ftraceCounters = counters; - globals.publishRedraw(); -} - -export function publishConversionJobStatusUpdate( - job: ConversionJobStatusUpdate) { - globals.setConversionJobStatus(job.jobName, job.jobStatus); - globals.publishRedraw(); -} - -export function publishLoading(numQueuedQueries: number) { - globals.numQueuedQueries = numQueuedQueries; - // TODO(hjd): Clean up loadingAnimation given that this now causes a full - // redraw anyways. Also this should probably just go via the global state. - globals.rafScheduler.scheduleFullRedraw(); -} - -export function publishBufferUsage(args: {percentage: number}) { - globals.setBufferUsage(args.percentage); - globals.publishRedraw(); -} - -export function publishSearch(args: SearchSummary) { - globals.searchSummary = args; - globals.publishRedraw(); -} - -export function publishSearchResult(args: CurrentSearchResults) { - globals.currentSearchResults = args; - globals.publishRedraw(); -} - -export function publishRecordingLog(args: {logs: string}) { - globals.setRecordingLog(args.logs); - globals.publishRedraw(); -} - -export function publishTraceErrors(numErrors: number) { - globals.setTraceErrors(numErrors); - globals.publishRedraw(); -} - -export function publishMetricError(error: string) { - globals.setMetricError(error); - globals.logging.logError(error, false); - globals.publishRedraw(); -} - -export function publishAggregateData( - args: {data: AggregateData, kind: string}) { - globals.setAggregateData(args.kind, args.data); - if (!isEmptyData(args.data)) { - globals.dispatch(Actions.setCurrentTab({tab: args.data.tabName})); - } - globals.publishRedraw(); -} - -export function publishQueryResult(args: {id: string, data?: {}}) { - globals.queryResults.set(args.id, args.data); - globals.dispatch(Actions.setCurrentTab({tab: `query_result_${args.id}`})); - globals.publishRedraw(); -} - -export function publishThreads(data: ThreadDesc[]) { - globals.threads.clear(); - data.forEach((thread) => { - globals.threads.set(thread.utid, thread); - }); - globals.publishRedraw(); -} - -export function publishSliceDetails(click: SliceDetails) { - globals.sliceDetails = click; - const id = click.id; - if (id !== undefined && id === globals.state.pendingScrollId) { - findCurrentSelection(); - globals.dispatch(Actions.setCurrentTab({tab: 'slice'})); - globals.dispatch(Actions.clearPendingScrollId({id: undefined})); - } - globals.publishRedraw(); -} - -export function publishThreadStateDetails(click: ThreadStateDetails) { - globals.threadStateDetails = click; - globals.publishRedraw(); -} - -export function publishConnectedFlows(connectedFlows: Flow[]) { - globals.connectedFlows = connectedFlows; - // If a chrome slice is selected and we have any flows in connectedFlows - // we will find the flows on the right and left of that slice to set a default - // focus. In all other cases the focusedFlowId(Left|Right) will be set to -1. - globals.dispatch(Actions.setHighlightedFlowLeftId({flowId: -1})); - globals.dispatch(Actions.setHighlightedFlowRightId({flowId: -1})); - if (globals.state.currentSelection?.kind === 'CHROME_SLICE') { - const sliceId = globals.state.currentSelection.id; - for (const flow of globals.connectedFlows) { - if (flow.begin.sliceId === sliceId) { - globals.dispatch(Actions.setHighlightedFlowRightId({flowId: flow.id})); - } - if (flow.end.sliceId === sliceId) { - globals.dispatch(Actions.setHighlightedFlowLeftId({flowId: flow.id})); - } - } - } - - globals.publishRedraw(); -} - -export function publishFtracePanelData(data: FtracePanelData) { - globals.ftracePanelData = data; - globals.publishRedraw(); -} diff --git a/third_party/perfetto/ui/src/frontend/query_history.ts b/third_party/perfetto/ui/src/frontend/query_history.ts deleted file mode 100644 index 53823221b735..000000000000 --- a/third_party/perfetto/ui/src/frontend/query_history.ts +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {globals} from './globals'; -import {STAR} from './icons'; - -import { - arrayOf, - bool, - record, - runValidator, - str, - ValidatedType, -} from '../controller/validators'; -import {assertTrue} from '../base/logging'; -import {Icon} from './widgets/icon'; -import {runAnalyzeQuery} from './analyze_page'; - -const QUERY_HISTORY_KEY = 'queryHistory'; - -export class QueryHistoryComponent implements m.ClassComponent { - view(): m.Child { - const unstarred: HistoryItemComponentAttrs[] = []; - const starred: HistoryItemComponentAttrs[] = []; - for (let i = queryHistoryStorage.data.length - 1; i >= 0; i--) { - const entry = queryHistoryStorage.data[i]; - const arr = entry.starred ? starred : unstarred; - arr.push({index: i, entry}); - } - return m( - '.query-history', - m('header.overview', - `Query history (${queryHistoryStorage.data.length} queries)`), - starred.map((attrs) => m(HistoryItemComponent, attrs)), - unstarred.map((attrs) => m(HistoryItemComponent, attrs))); - } -} - -export interface HistoryItemComponentAttrs { - index: number; - entry: QueryHistoryEntry; -} - -export class HistoryItemComponent implements - m.ClassComponent { - view(vnode: m.Vnode): m.Child { - const query = vnode.attrs.entry.query; - return m( - '.history-item', - m('.history-item-buttons', - m( - 'button', - { - onclick: () => { - queryHistoryStorage.setStarred( - vnode.attrs.index, !vnode.attrs.entry.starred); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - m(Icon, {icon: STAR, filled: vnode.attrs.entry.starred}), - ), - m('button', - { - onclick: () => runAnalyzeQuery(query), - }, - m(Icon, {icon: 'play_arrow'})), - m('button', - { - onclick: () => { - queryHistoryStorage.remove(vnode.attrs.index); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - m(Icon, {icon: 'delete'}))), - m('pre', query)); - } -} - -class HistoryStorage { - data: QueryHistory; - maxItems = 50; - - constructor() { - this.data = this.load(); - } - - saveQuery(query: string) { - const items = this.data; - let firstUnstarred = -1; - let countUnstarred = 0; - for (let i = 0; i < items.length; i++) { - if (!items[i].starred) { - countUnstarred++; - if (firstUnstarred === -1) { - firstUnstarred = i; - } - } - - if (items[i].query === query) { - // Query is already in the history, no need to save - return; - } - } - - if (countUnstarred >= this.maxItems) { - assertTrue(firstUnstarred !== -1); - items.splice(firstUnstarred, 1); - } - - items.push({query, starred: false}); - this.save(); - } - - setStarred(index: number, starred: boolean) { - assertTrue(index >= 0 && index < this.data.length); - this.data[index].starred = starred; - this.save(); - } - - remove(index: number) { - assertTrue(index >= 0 && index < this.data.length); - this.data.splice(index, 1); - this.save(); - } - - private load(): QueryHistory { - const value = window.localStorage.getItem(QUERY_HISTORY_KEY); - if (value === null) { - return []; - } - - return runValidator(queryHistoryValidator, JSON.parse(value)).result; - } - - private save() { - window.localStorage.setItem(QUERY_HISTORY_KEY, JSON.stringify(this.data)); - } -} - -const queryHistoryEntryValidator = record({query: str(), starred: bool()}); - -type QueryHistoryEntry = ValidatedType; - -const queryHistoryValidator = arrayOf(queryHistoryEntryValidator); - -type QueryHistory = ValidatedType; - -export const queryHistoryStorage = new HistoryStorage(); diff --git a/third_party/perfetto/ui/src/frontend/query_result_tab.ts b/third_party/perfetto/ui/src/frontend/query_result_tab.ts deleted file mode 100644 index e0eb6445a707..000000000000 --- a/third_party/perfetto/ui/src/frontend/query_result_tab.ts +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (C) 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; -import {v4 as uuidv4} from 'uuid'; - -import {assertExists} from '../base/logging'; -import {QueryResponse, runQuery} from '../common/queries'; -import {QueryError} from '../common/query_result'; -import { - AddDebugTrackMenu, - uuidToViewName, -} from '../tracks/debug/add_debug_track_menu'; - -import { - addTab, - BottomTab, - bottomTabRegistry, - closeTab, - NewBottomTabArgs, -} from './bottom_tab'; -import {globals} from './globals'; -import {QueryTable} from './query_table'; -import {Button} from './widgets/button'; -import {Popup, PopupPosition} from './widgets/popup'; - - -export function runQueryInNewTab(query: string, title: string, tag?: string) { - return addTab({ - kind: QueryResultTab.kind, - tag, - config: { - query, - title, - }, - }); -} - -interface QueryResultTabConfig { - readonly query: string; - readonly title: string; - // Optional data to display in this tab instead of fetching it again - // (e.g. when duplicating an existing tab which already has the data). - readonly prefetchedResponse?: QueryResponse; -} - -export class QueryResultTab extends BottomTab { - static readonly kind = 'org.perfetto.QueryResultTab'; - - queryResponse?: QueryResponse; - sqlViewName?: string; - - static create(args: NewBottomTabArgs): QueryResultTab { - return new QueryResultTab(args); - } - - constructor(args: NewBottomTabArgs) { - super(args); - - this.initTrack(args); - } - - async initTrack(args: NewBottomTabArgs) { - let uuid = ''; - if (this.config.prefetchedResponse !== undefined) { - this.queryResponse = this.config.prefetchedResponse; - uuid = args.uuid; - } else { - const result = await runQuery(this.config.query, this.engine); - this.queryResponse = result; - globals.rafScheduler.scheduleFullRedraw(); - if (result.error !== undefined) { - return; - } - - uuid = uuidv4(); - } - - if (uuid !== '') { - this.sqlViewName = await this.createViewForDebugTrack(uuid); - if (this.sqlViewName) { - globals.rafScheduler.scheduleFullRedraw(); - } - } - } - - getTitle(): string { - const suffix = - this.queryResponse ? ` (${this.queryResponse.rows.length})` : ''; - return `${this.config.title}${suffix}`; - } - - viewTab(): m.Child { - return m(QueryTable, { - query: this.config.query, - resp: this.queryResponse, - onClose: () => closeTab(this.uuid), - contextButtons: [ - this.sqlViewName === undefined ? - null : - m(Popup, - { - trigger: m(Button, {label: 'Show debug track', minimal: true}), - position: PopupPosition.Top, - }, - m(AddDebugTrackMenu, { - sqlViewName: this.sqlViewName, - columns: assertExists(this.queryResponse).columns, - engine: this.engine, - })), - ], - }); - } - - isLoading() { - return this.queryResponse === undefined; - } - - renderTabCanvas() {} - - async createViewForDebugTrack(uuid: string): Promise { - const viewId = uuidToViewName(uuid); - // Assuming that the query results come from a SELECT query, try creating a - // view to allow us to reuse it for further queries. - // TODO(altimin): This should get the actual query that was used to - // generate the results from the SQL query iterator. - try { - const createViewResult = await this.engine.query( - `create view ${viewId} as ${this.config.query}`); - if (createViewResult.error()) { - // If it failed, do nothing. - return ''; - } - } catch (e) { - if (e instanceof QueryError) { - // If it failed, do nothing. - return ''; - } - throw e; - } - return viewId; - } -} - -bottomTabRegistry.register(QueryResultTab); diff --git a/third_party/perfetto/ui/src/frontend/query_table.ts b/third_party/perfetto/ui/src/frontend/query_table.ts deleted file mode 100644 index c27ecc29a509..000000000000 --- a/third_party/perfetto/ui/src/frontend/query_table.ts +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -import m from 'mithril'; - -import {Actions} from '../common/actions'; -import {QueryResponse} from '../common/queries'; -import {ColumnType, Row} from '../common/query_result'; -import {fromNs} from '../common/time'; -import {Anchor} from './anchor'; - -import {copyToClipboard, queryResponseToClipboard} from './clipboard'; -import {downloadData} from './download_utils'; -import {globals} from './globals'; -import {Panel} from './panel'; -import {Router} from './router'; -import { - focusHorizontalRange, - verticalScrollToTrack, -} from './scroll_helper'; -import {Button} from './widgets/button'; - -interface QueryTableRowAttrs { - row: Row; - columns: string[]; -} - -// Convert column value to number if it's a bigint or a number, otherwise throw -function colToNumber(colValue: ColumnType): number { - if (typeof colValue === 'bigint') { - return Number(colValue); - } else if (typeof colValue === 'number') { - return colValue; - } else { - throw Error('Value is not a number or a bigint'); - } -} - -class QueryTableRow implements m.ClassComponent { - static columnsContainsSliceLocation(columns: string[]) { - const requiredColumns = ['ts', 'dur', 'track_id']; - for (const col of requiredColumns) { - if (!columns.includes(col)) return false; - } - return true; - } - - static rowOnClickHandler( - event: Event, row: Row, nextTab: 'CurrentSelection'|'QueryResults') { - // TODO(dproy): Make click handler work from analyze page. - if (Router.parseUrl(window.location.href).page !== '/viewer') return; - // If the click bubbles up to the pan and zoom handler that will deselect - // the slice. - event.stopPropagation(); - - const sliceStart = fromNs(colToNumber(row.ts)); - // row.dur can be negative. Clamp to 1ns. - const sliceDur = fromNs(Math.max(colToNumber(row.dur), 1)); - const sliceEnd = sliceStart + sliceDur; - const trackId: number = colToNumber(row.track_id); - const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId]; - if (uiTrackId === undefined) return; - verticalScrollToTrack(uiTrackId, true); - // TODO(stevegolton) Soon this function will only accept Bigints - focusHorizontalRange(sliceStart, sliceEnd); - - let sliceId: number|undefined; - if (row.type?.toString().includes('slice')) { - sliceId = colToNumber(row.id); - } else { - sliceId = colToNumber(row.slice_id); - } - if (sliceId !== undefined) { - globals.makeSelection( - Actions.selectChromeSlice( - {id: sliceId, trackId: uiTrackId, table: 'slice'}), - nextTab === 'QueryResults' ? globals.state.currentTab : - 'current_selection'); - } - } - - view(vnode: m.Vnode) { - const cells = []; - const {row, columns} = vnode.attrs; - for (const col of columns) { - const value = row[col]; - if (value instanceof Uint8Array) { - cells.push( - m('td', - m(Anchor, - { - onclick: () => downloadData(`${col}.blob`, value), - }, - `Blob (${value.length} bytes)`))); - } else if (typeof value === 'bigint') { - cells.push(m('td', value.toString())); - } else { - cells.push(m('td', value)); - } - } - const containsSliceLocation = - QueryTableRow.columnsContainsSliceLocation(columns); - const maybeOnClick = containsSliceLocation ? - (e: Event) => QueryTableRow.rowOnClickHandler(e, row, 'QueryResults') : - null; - const maybeOnDblClick = containsSliceLocation ? - (e: Event) => - QueryTableRow.rowOnClickHandler(e, row, 'CurrentSelection') : - null; - return m( - 'tr', - { - 'onclick': maybeOnClick, - // TODO(altimin): Consider improving the logic here (e.g. delay?) to - // account for cases when dblclick fires late. - 'ondblclick': maybeOnDblClick, - 'clickable': containsSliceLocation, - }, - cells); - } -} - -interface QueryTableContentAttrs { - resp: QueryResponse; -} - -class QueryTableContent implements m.ClassComponent { - private previousResponse?: QueryResponse; - - onbeforeupdate(vnode: m.CVnode) { - return vnode.attrs.resp !== this.previousResponse; - } - - view(vnode: m.CVnode) { - const resp = vnode.attrs.resp; - this.previousResponse = resp; - const cols = []; - for (const col of resp.columns) { - cols.push(m('td', col)); - } - const tableHeader = m('tr', cols); - - const rows = - resp.rows.map((row) => m(QueryTableRow, {row, columns: resp.columns})); - - if (resp.error) { - return m('.query-error', `SQL error: ${resp.error}`); - } else { - return m( - '.query-table-container.x-scrollable', - m('table.query-table', m('thead', tableHeader), m('tbody', rows))); - } - } -} - -interface QueryTableAttrs { - query: string; - onClose: () => void; - resp?: QueryResponse; - contextButtons?: m.Child[]; -} - -export class QueryTable extends Panel { - view(vnode: m.CVnode) { - const resp = vnode.attrs.resp; - - const header: m.Child[] = [ - m('span', - resp ? `Query result - ${Math.round(resp.durationMs)} ms` : - `Query - running`), - m('span.code.text-select', vnode.attrs.query), - m('span.spacer'), - ...(vnode.attrs.contextButtons ?? []), - m(Button, { - label: 'Copy query', - minimal: true, - onclick: () => { - copyToClipboard(vnode.attrs.query); - }, - }), - ]; - if (resp) { - if (resp.error === undefined) { - header.push(m(Button, { - label: 'Copy result (.tsv)', - minimal: true, - onclick: () => { - queryResponseToClipboard(resp); - }, - })); - } - } - header.push(m(Button, { - label: 'Close', - minimal: true, - onclick: () => vnode.attrs.onClose(), - })); - - const headers = [m('header.overview', ...header)]; - - if (resp === undefined) { - return m('div', ...headers); - } - - if (resp.statementWithOutputCount > 1) { - headers.push( - m('header.overview', - `${resp.statementWithOutputCount} out of ${resp.statementCount} ` + - `statements returned a result. Only the results for the last ` + - `statement are displayed in the table below.`)); - } - - return m('div', ...headers, m(QueryTableContent, {resp})); - } - - renderCanvas() {} -} diff --git a/third_party/perfetto/ui/src/frontend/raf_scheduler.ts b/third_party/perfetto/ui/src/frontend/raf_scheduler.ts deleted file mode 100644 index 4571e7e58456..000000000000 --- a/third_party/perfetto/ui/src/frontend/raf_scheduler.ts +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import m from 'mithril'; - -import {assertTrue} from '../base/logging'; - -import {globals} from './globals'; - -import { - debugNow, - measure, - perfDebug, - perfDisplay, - RunningStatistics, - runningStatStr, -} from './perf'; - -function statTableHeader() { - return m( - 'tr', - m('th', ''), - m('th', 'Last (ms)'), - m('th', 'Avg (ms)'), - m('th', 'Avg-10 (ms)')); -} - -function statTableRow(title: string, stat: RunningStatistics) { - return m( - 'tr', - m('td', title), - m('td', stat.last.toFixed(2)), - m('td', stat.mean.toFixed(2)), - m('td', stat.bufferMean.toFixed(2))); -} - -export type ActionCallback = (nowMs: number) => void; -export type RedrawCallback = (nowMs: number) => void; - -// This class orchestrates all RAFs in the UI. It ensures that there is only -// one animation frame handler overall and that callbacks are called in -// predictable order. There are two types of callbacks here: -// - actions (e.g. pan/zoon animations), which will alter the "fast" -// (main-thread-only) state (e.g. update visible time bounds @ 60 fps). -// - redraw callbacks that will repaint canvases. -// This class guarantees that, on each frame, redraw callbacks are called after -// all action callbacks. -export class RafScheduler { - private actionCallbacks = new Set(); - private canvasRedrawCallbacks = new Set(); - private _syncDomRedraw: RedrawCallback = (_) => {}; - private hasScheduledNextFrame = false; - private requestedFullRedraw = false; - private isRedrawing = false; - private _shutdown = false; - - private perfStats = { - rafActions: new RunningStatistics(), - rafCanvas: new RunningStatistics(), - rafDom: new RunningStatistics(), - rafTotal: new RunningStatistics(), - domRedraw: new RunningStatistics(), - }; - - start(cb: ActionCallback) { - this.actionCallbacks.add(cb); - this.maybeScheduleAnimationFrame(); - } - - stop(cb: ActionCallback) { - this.actionCallbacks.delete(cb); - } - - addRedrawCallback(cb: RedrawCallback) { - this.canvasRedrawCallbacks.add(cb); - } - - removeRedrawCallback(cb: RedrawCallback) { - this.canvasRedrawCallbacks.delete(cb); - } - - // Schedule re-rendering of canvas only. - scheduleRedraw() { - this.maybeScheduleAnimationFrame(true); - } - - shutdown() { - this._shutdown = true; - } - - set domRedraw(cb: RedrawCallback) { - this._syncDomRedraw = cb; - } - - // Schedule re-rendering of virtual DOM and canvas. - scheduleFullRedraw() { - this.requestedFullRedraw = true; - this.maybeScheduleAnimationFrame(true); - } - - syncDomRedraw(nowMs: number) { - const redrawStart = debugNow(); - this._syncDomRedraw(nowMs); - if (perfDebug()) { - this.perfStats.domRedraw.addValue(debugNow() - redrawStart); - } - } - - get hasPendingRedraws(): boolean { - return this.isRedrawing || this.hasScheduledNextFrame; - } - - private syncCanvasRedraw(nowMs: number) { - const redrawStart = debugNow(); - if (this.isRedrawing) return; - globals.frontendLocalState.clearVisibleTracks(); - this.isRedrawing = true; - for (const redraw of this.canvasRedrawCallbacks) redraw(nowMs); - this.isRedrawing = false; - globals.frontendLocalState.sendVisibleTracks(); - if (perfDebug()) { - this.perfStats.rafCanvas.addValue(debugNow() - redrawStart); - } - } - - private maybeScheduleAnimationFrame(force = false) { - if (this.hasScheduledNextFrame) return; - if (this.actionCallbacks.size !== 0 || force) { - this.hasScheduledNextFrame = true; - window.requestAnimationFrame(this.onAnimationFrame.bind(this)); - } - } - - private onAnimationFrame(nowMs: number) { - if (this._shutdown) return; - const rafStart = debugNow(); - this.hasScheduledNextFrame = false; - - const doFullRedraw = this.requestedFullRedraw; - this.requestedFullRedraw = false; - - const actionTime = measure(() => { - for (const action of this.actionCallbacks) action(nowMs); - }); - - const domTime = measure(() => { - if (doFullRedraw) this.syncDomRedraw(nowMs); - }); - const canvasTime = measure(() => this.syncCanvasRedraw(nowMs)); - - const totalRafTime = debugNow() - rafStart; - this.updatePerfStats(actionTime, domTime, canvasTime, totalRafTime); - perfDisplay.renderPerfStats(); - - this.maybeScheduleAnimationFrame(); - } - - private updatePerfStats( - actionsTime: number, domTime: number, canvasTime: number, - totalRafTime: number) { - if (!perfDebug()) return; - this.perfStats.rafActions.addValue(actionsTime); - this.perfStats.rafDom.addValue(domTime); - this.perfStats.rafCanvas.addValue(canvasTime); - this.perfStats.rafTotal.addValue(totalRafTime); - } - - renderPerfStats() { - assertTrue(perfDebug()); - return m( - 'div', - m('div', - [ - m('button', - {onclick: () => this.scheduleRedraw()}, - 'Do Canvas Redraw'), - ' | ', - m('button', - {onclick: () => this.scheduleFullRedraw()}, - 'Do Full Redraw'), - ]), - m('div', - 'Raf Timing ' + - '(Total may not add up due to imprecision)'), - m('table', - statTableHeader(), - statTableRow('Actions', this.perfStats.rafActions), - statTableRow('Dom', this.perfStats.rafDom), - statTableRow('Canvas', this.perfStats.rafCanvas), - statTableRow('Total', this.perfStats.rafTotal)), - m('div', - 'Dom redraw: ' + - `Count: ${this.perfStats.domRedraw.count} | ` + - runningStatStr(this.perfStats.domRedraw))); - } -} diff --git a/third_party/perfetto/ui/src/frontend/rate_limiters.ts b/third_party/perfetto/ui/src/frontend/rate_limiters.ts deleted file mode 100644 index babf3f4970e4..000000000000 --- a/third_party/perfetto/ui/src/frontend/rate_limiters.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Returns a wrapper around |f| which calls f at most once every |ms|ms. -export function ratelimit(f: Function, ms: number): Function { - let inProgess = false; - return () => { - if (inProgess) { - return; - } - inProgess = true; - setTimeout(() => { - f(); - inProgess = false; - }, ms); - }; -} - -// Returns a wrapper around |f| which waits for a |ms|ms pause in calls -// before calling |f|. -export function debounce(f: Function, ms: number): Function { - let timerId: undefined|ReturnType; - return () => { - if (timerId) { - clearTimeout(timerId); - } - timerId = setTimeout(() => { - f(); - timerId = undefined; - }, ms); - }; -} diff --git a/third_party/perfetto/ui/src/frontend/record_config.ts b/third_party/perfetto/ui/src/frontend/record_config.ts deleted file mode 100644 index 53465956aa60..000000000000 --- a/third_party/perfetto/ui/src/frontend/record_config.ts +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {getDefaultRecordingTargets, RecordingTarget} from '../common/state'; -import { - createEmptyRecordConfig, - NamedRecordConfig, - namedRecordConfigValidator, - RecordConfig, - recordConfigValidator, -} from '../controller/record_config_types'; -import {runValidator, ValidationResult} from '../controller/validators'; - -const LOCAL_STORAGE_RECORD_CONFIGS_KEY = 'recordConfigs'; -const LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY = 'autosaveConfig'; -const LOCAL_STORAGE_RECORD_TARGET_OS_KEY = 'recordTargetOS'; - -export class RecordConfigStore { - recordConfigs: Array>; - recordConfigNames: Set; - - constructor() { - this.recordConfigs = []; - this.recordConfigNames = new Set(); - this.reloadFromLocalStorage(); - } - - private _save() { - window.localStorage.setItem( - LOCAL_STORAGE_RECORD_CONFIGS_KEY, - JSON.stringify(this.recordConfigs.map((x) => x.result))); - } - - save(recordConfig: RecordConfig, title?: string): void { - // We reload from local storage in case of concurrent - // modifications of local storage from a different tab. - this.reloadFromLocalStorage(); - - const savedTitle = title ? title : new Date().toJSON(); - const config: NamedRecordConfig = { - title: savedTitle, - config: recordConfig, - key: new Date().toJSON(), - }; - - this.recordConfigs.push({result: config, invalidKeys: [], extraKeys: []}); - this.recordConfigNames.add(savedTitle); - - this._save(); - } - - overwrite(recordConfig: RecordConfig, key: string) { - // We reload from local storage in case of concurrent - // modifications of local storage from a different tab. - this.reloadFromLocalStorage(); - - const found = this.recordConfigs.find((e) => e.result.key === key); - if (found === undefined) { - throw new Error('trying to overwrite non-existing config'); - } - - found.result.config = recordConfig; - - this._save(); - } - - delete(key: string): void { - // We reload from local storage in case of concurrent - // modifications of local storage from a different tab. - this.reloadFromLocalStorage(); - - let idx = -1; - for (let i = 0; i < this.recordConfigs.length; ++i) { - if (this.recordConfigs[i].result.key === key) { - idx = i; - break; - } - } - - if (idx !== -1) { - this.recordConfigNames.delete(this.recordConfigs[idx].result.title); - this.recordConfigs.splice(idx, 1); - this._save(); - } else { - // TODO(bsebastien): Show a warning message to the user in the UI. - console.warn('The config selected doesn\'t exist any more'); - } - } - - private clearRecordConfigs(): void { - this.recordConfigs = []; - this.recordConfigNames.clear(); - this._save(); - } - - reloadFromLocalStorage(): void { - const configsLocalStorage = - window.localStorage.getItem(LOCAL_STORAGE_RECORD_CONFIGS_KEY); - - if (configsLocalStorage) { - this.recordConfigNames.clear(); - - try { - const validConfigLocalStorage: - Array> = []; - const parsedConfigsLocalStorage = JSON.parse(configsLocalStorage); - - // Check if it's an array. - if (!Array.isArray(parsedConfigsLocalStorage)) { - this.clearRecordConfigs(); - return; - } - - for (let i = 0; i < parsedConfigsLocalStorage.length; ++i) { - try { - validConfigLocalStorage.push(runValidator( - namedRecordConfigValidator, parsedConfigsLocalStorage[i])); - } catch { - // Parsing failed with unrecoverable error (e.g. title or key are - // missing), ignore the result. - console.log( - 'Validation of saved record config has failed: ' + - JSON.stringify(parsedConfigsLocalStorage[i])); - } - } - - this.recordConfigs = validConfigLocalStorage; - this._save(); - } catch (e) { - this.clearRecordConfigs(); - } - } else { - this.clearRecordConfigs(); - } - } - - canSave(title: string) { - return !this.recordConfigNames.has(title); - } -} - -// This class is a singleton to avoid many instances -// conflicting as they attempt to edit localStorage. -export const recordConfigStore = new RecordConfigStore(); - -export class AutosaveConfigStore { - config: RecordConfig; - - // Whether the current config is a default one or has been saved before. - // Used to determine whether the button to load "last started config" should - // be present in the recording profiles list. - hasSavedConfig: boolean; - - constructor() { - this.hasSavedConfig = false; - this.config = createEmptyRecordConfig(); - const savedItem = - window.localStorage.getItem(LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY); - if (savedItem === null) { - return; - } - const parsed = JSON.parse(savedItem); - if (parsed !== null && typeof parsed === 'object') { - this.config = runValidator(recordConfigValidator, parsed).result; - this.hasSavedConfig = true; - } - } - - get(): RecordConfig { - return this.config; - } - - save(newConfig: RecordConfig) { - window.localStorage.setItem( - LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY, JSON.stringify(newConfig)); - this.config = newConfig; - this.hasSavedConfig = true; - } -} - -export const autosaveConfigStore = new AutosaveConfigStore(); - -export class RecordTargetStore { - recordTargetOS: string|null; - - constructor() { - this.recordTargetOS = - window.localStorage.getItem(LOCAL_STORAGE_RECORD_TARGET_OS_KEY); - } - - get(): string|null { - return this.recordTargetOS; - } - - getValidTarget(): RecordingTarget { - const validTargets = getDefaultRecordingTargets(); - const savedOS = this.get(); - - const validSavedTarget = validTargets.find((el) => el.os === savedOS); - return validSavedTarget || validTargets[0]; - } - - save(newTargetOS: string) { - window.localStorage.setItem( - LOCAL_STORAGE_RECORD_TARGET_OS_KEY, newTargetOS); - this.recordTargetOS = newTargetOS; - } -} - -export const recordTargetStore = new RecordTargetStore(); diff --git a/third_party/perfetto/ui/src/frontend/record_page.ts b/third_party/perfetto/ui/src/frontend/record_page.ts deleted file mode 100644 index ab2040b2dd57..000000000000 --- a/third_party/perfetto/ui/src/frontend/record_page.ts +++ /dev/null @@ -1,777 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -import m from 'mithril'; - -import {Actions} from '../common/actions'; -import {featureFlags} from '../common/feature_flags'; -import { - AdbRecordingTarget, - getDefaultRecordingTargets, - hasActiveProbes, - isAdbTarget, - isAndroidP, - isAndroidTarget, - isChromeTarget, - isCrOSTarget, - isLinuxTarget, - LoadedConfig, - MAX_TIME, - RecordingTarget, -} from '../common/state'; -import {AdbOverWebUsb} from '../controller/adb'; -import { - createEmptyRecordConfig, - RecordConfig, -} from '../controller/record_config_types'; - -import {globals} from './globals'; -import {createPage, PageAttrs} from './pages'; -import { - autosaveConfigStore, - recordConfigStore, - recordTargetStore, -} from './record_config'; -import { - CodeSnippet, -} from './record_widgets'; -import {AdvancedSettings} from './recording/advanced_settings'; -import {AndroidSettings} from './recording/android_settings'; -import {ChromeSettings} from './recording/chrome_settings'; -import {CpuSettings} from './recording/cpu_settings'; -import {GpuSettings} from './recording/gpu_settings'; -import {MemorySettings} from './recording/memory_settings'; -import {PowerSettings} from './recording/power_settings'; -import {RecordingSectionAttrs} from './recording/recording_sections'; -import {RecordingSettings} from './recording/recording_settings'; - -export const PERSIST_CONFIG_FLAG = featureFlags.register({ - id: 'persistConfigsUI', - name: 'Config persistence UI', - description: 'Show experimental config persistence UI on the record page.', - defaultValue: true, -}); - -export const RECORDING_SECTIONS = [ - 'buffers', - 'instructions', - 'config', - 'cpu', - 'gpu', - 'power', - 'memory', - 'android', - 'chrome', - 'advanced', -]; - -function RecordHeader() { - return m( - '.record-header', - m('.top-part', - m('.target-and-status', - RecordingPlatformSelection(), - RecordingStatusLabel(), - ErrorLabel()), - recordingButtons()), - RecordingNotes()); -} - -function RecordingPlatformSelection() { - if (globals.state.recordingInProgress) return []; - - const availableAndroidDevices = globals.state.availableAdbDevices; - const recordingTarget = globals.state.recordingTarget; - - const targets = []; - for (const {os, name} of getDefaultRecordingTargets()) { - targets.push(m('option', {value: os}, name)); - } - for (const d of availableAndroidDevices) { - targets.push(m('option', {value: d.serial}, d.name)); - } - - const selectedIndex = isAdbTarget(recordingTarget) ? - targets.findIndex((node) => node.attrs.value === recordingTarget.serial) : - targets.findIndex((node) => node.attrs.value === recordingTarget.os); - - return m( - '.target', - m( - 'label', - 'Target platform:', - m('select', - { - selectedIndex, - onchange: (e: Event) => { - onTargetChange((e.target as HTMLSelectElement).value); - }, - onupdate: (select) => { - // Work around mithril bug - // (https://github.com/MithrilJS/mithril.js/issues/2107): We may - // update the select's options while also changing the - // selectedIndex at the same time. The update of selectedIndex - // may be applied before the new options are added to the select - // element. Because the new selectedIndex may be outside of the - // select's options at that time, we have to reselect the - // correct index here after any new children were added. - (select.dom as HTMLSelectElement).selectedIndex = selectedIndex; - }, - }, - ...targets), - ), - m('.chip', - {onclick: addAndroidDevice}, - m('button', 'Add ADB Device'), - m('i.material-icons', 'add'))); -} - -// |target| can be the TargetOs or the android serial. -function onTargetChange(target: string) { - const recordingTarget: RecordingTarget = - globals.state.availableAdbDevices.find((d) => d.serial === target) || - getDefaultRecordingTargets().find((t) => t.os === target) || - getDefaultRecordingTargets()[0]; - - if (isChromeTarget(recordingTarget)) { - globals.dispatch(Actions.setFetchChromeCategories({fetch: true})); - } - - globals.dispatch(Actions.setRecordingTarget({target: recordingTarget})); - recordTargetStore.save(target); - globals.rafScheduler.scheduleFullRedraw(); -} - -function Instructions(cssClass: string) { - return m( - `.record-section.instructions${cssClass}`, - m('header', 'Recording command'), - PERSIST_CONFIG_FLAG.get() ? - m('button.permalinkconfig', - { - onclick: () => { - globals.dispatch( - Actions.createPermalink({isRecordingConfig: true})); - }, - }, - 'Share recording settings') : - null, - RecordingSnippet(), - BufferUsageProgressBar(), - m('.buttons', StopCancelButtons()), - recordingLog()); -} - -export function loadedConfigEqual( - cfg1: LoadedConfig, cfg2: LoadedConfig): boolean { - return cfg1.type === 'NAMED' && cfg2.type === 'NAMED' ? - cfg1.name === cfg2.name : - cfg1.type === cfg2.type; -} - -export function loadConfigButton( - config: RecordConfig, configType: LoadedConfig): m.Vnode { - return m( - 'button', - { - class: 'config-button', - title: 'Apply configuration settings', - disabled: loadedConfigEqual(configType, globals.state.lastLoadedConfig), - onclick: () => { - globals.dispatch(Actions.setRecordConfig({config, configType})); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - m('i.material-icons', 'file_upload')); -} - -export function displayRecordConfigs() { - const configs = []; - if (autosaveConfigStore.hasSavedConfig) { - configs.push(m('.config', [ - m('span.title-config', m('strong', 'Latest started recording')), - loadConfigButton(autosaveConfigStore.get(), {type: 'AUTOMATIC'}), - ])); - } - for (const validated of recordConfigStore.recordConfigs) { - const item = validated.result; - configs.push(m('.config', [ - m('span.title-config', item.title), - loadConfigButton(item.config, {type: 'NAMED', name: item.title}), - m('button', - { - class: 'config-button', - title: 'Overwrite configuration with current settings', - onclick: () => { - if (confirm(`Overwrite config "${ - item.title}" with current settings?`)) { - recordConfigStore.overwrite(globals.state.recordConfig, item.key); - globals.dispatch(Actions.setRecordConfig({ - config: item.config, - configType: {type: 'NAMED', name: item.title}, - })); - globals.rafScheduler.scheduleFullRedraw(); - } - }, - }, - m('i.material-icons', 'save')), - m('button', - { - class: 'config-button', - title: 'Remove configuration', - onclick: () => { - recordConfigStore.delete(item.key); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - m('i.material-icons', 'delete')), - ])); - - const errorItems = []; - for (const extraKey of validated.extraKeys) { - errorItems.push(m('li', `${extraKey} is unrecognised`)); - } - for (const invalidKey of validated.invalidKeys) { - errorItems.push(m('li', `${invalidKey} contained an invalid value`)); - } - - if (errorItems.length > 0) { - configs.push( - m('.parsing-errors', - 'One or more errors have been found while loading configuration "' + - item.title + '". Loading is possible, but make sure to check ' + - 'the settings afterwards.', - m('ul', errorItems))); - } - } - return configs; -} - -export const ConfigTitleState = { - title: '', - getTitle: () => { - return ConfigTitleState.title; - }, - setTitle: (value: string) => { - ConfigTitleState.title = value; - }, - clearTitle: () => { - ConfigTitleState.title = ''; - }, -}; - -export function Configurations(cssClass: string) { - const canSave = recordConfigStore.canSave(ConfigTitleState.getTitle()); - return m( - `.record-section${cssClass}`, - m('header', 'Save and load configurations'), - m('.input-config', - [ - m('input', { - value: ConfigTitleState.title, - placeholder: 'Title for config', - oninput() { - ConfigTitleState.setTitle(this.value); - globals.rafScheduler.scheduleFullRedraw(); - }, - }), - m('button', - { - class: 'config-button', - disabled: !canSave, - title: canSave ? 'Save current config' : - 'Duplicate name, saving disabled', - onclick: () => { - recordConfigStore.save( - globals.state.recordConfig, ConfigTitleState.getTitle()); - globals.rafScheduler.scheduleFullRedraw(); - ConfigTitleState.clearTitle(); - }, - }, - m('i.material-icons', 'save')), - m('button', - { - class: 'config-button', - title: 'Clear current configuration', - onclick: () => { - if (confirm( - 'Current configuration will be cleared. ' + - 'Are you sure?')) { - globals.dispatch(Actions.setRecordConfig({ - config: createEmptyRecordConfig(), - configType: {type: 'NONE'}, - })); - globals.rafScheduler.scheduleFullRedraw(); - } - }, - }, - m('i.material-icons', 'delete_forever')), - ]), - displayRecordConfigs()); -} - -function BufferUsageProgressBar() { - if (!globals.state.recordingInProgress) return []; - - const bufferUsage = globals.bufferUsage ? globals.bufferUsage : 0.0; - // Buffer usage is not available yet on Android. - if (bufferUsage === 0) return []; - - return m( - 'label', - 'Buffer usage: ', - m('progress', {max: 100, value: bufferUsage * 100})); -} - -function RecordingNotes() { - const sideloadUrl = - 'https://perfetto.dev/docs/contributing/build-instructions#get-the-code'; - const linuxUrl = 'https://perfetto.dev/docs/quickstart/linux-tracing'; - const cmdlineUrl = - 'https://perfetto.dev/docs/quickstart/android-tracing#perfetto-cmdline'; - const extensionURL = `https://chrome.google.com/webstore/detail/ - perfetto-ui/lfmkphfpdbjijhpomgecfikhfohaoine`; - - const notes: m.Children = []; - - const msgFeatNotSupported = - m('span', `Some probes are only supported in Perfetto versions running - on Android Q+. `); - - const msgPerfettoNotSupported = - m('span', `Perfetto is not supported natively before Android P. `); - - const msgSideload = - m('span', - `If you have a rooted device you can `, - m('a', - {href: sideloadUrl, target: '_blank'}, - `sideload the latest version of - Perfetto.`)); - - const msgRecordingNotSupported = - m('.note', - `Recording Perfetto traces from the UI is not supported natively - before Android Q. If you are using a P device, please select 'Android P' - as the 'Target Platform' and `, - m('a', - {href: cmdlineUrl, target: '_blank'}, - `collect the trace using ADB.`)); - - const msgChrome = - m('.note', - `To trace Chrome from the Perfetto UI, you need to install our `, - m('a', {href: extensionURL, target: '_blank'}, 'Chrome extension'), - ' and then reload this page.'); - - const msgLinux = - m('.note', - `Use this `, - m('a', {href: linuxUrl, target: '_blank'}, `quickstart guide`), - ` to get started with tracing on Linux.`); - - const msgLongTraces = m( - '.note', - `Recording in long trace mode through the UI is not supported. Please copy - the command and `, - m('a', - {href: cmdlineUrl, target: '_blank'}, - `collect the trace using ADB.`)); - - const msgZeroProbes = - m('.note', - 'It looks like you didn\'t add any probes. ' + - 'Please add at least one to get a non-empty trace.'); - - if (!hasActiveProbes(globals.state.recordConfig)) { - notes.push(msgZeroProbes); - } - - if (isAdbTarget(globals.state.recordingTarget)) { - notes.push(msgRecordingNotSupported); - } - switch (globals.state.recordingTarget.os) { - case 'Q': - break; - case 'P': - notes.push(m('.note', msgFeatNotSupported, msgSideload)); - break; - case 'O': - notes.push(m('.note', msgPerfettoNotSupported, msgSideload)); - break; - case 'L': - notes.push(msgLinux); - break; - case 'C': - if (!globals.state.extensionInstalled) notes.push(msgChrome); - break; - case 'CrOS': - if (!globals.state.extensionInstalled) notes.push(msgChrome); - break; - default: - } - if (globals.state.recordConfig.mode === 'LONG_TRACE') { - notes.unshift(msgLongTraces); - } - - return notes.length > 0 ? m('div', notes) : []; -} - -function RecordingSnippet() { - const target = globals.state.recordingTarget; - - // We don't need commands to start tracing on chrome - if (isChromeTarget(target)) { - return globals.state.extensionInstalled && - !globals.state.recordingInProgress ? - m('div', - m('label', - `To trace Chrome from the Perfetto UI you just have to press - 'Start Recording'.`)) : - []; - } - return m(CodeSnippet, {text: getRecordCommand(target)}); -} - -function getRecordCommand(target: RecordingTarget) { - const data = globals.trackDataStore.get('config') as - {commandline: string, pbtxt: string, pbBase64: string} | - null; - - const cfg = globals.state.recordConfig; - let time = cfg.durationMs / 1000; - - if (time > MAX_TIME) { - time = MAX_TIME; - } - - const pbBase64 = data ? data.pbBase64 : ''; - const pbtx = data ? data.pbtxt : ''; - let cmd = ''; - if (isAndroidP(target)) { - cmd += `echo '${pbBase64}' | \n`; - cmd += 'base64 --decode | \n'; - cmd += 'adb shell "perfetto -c - -o /data/misc/perfetto-traces/trace"\n'; - } else { - cmd += - isAndroidTarget(target) ? 'adb shell perfetto \\\n' : 'perfetto \\\n'; - cmd += ' -c - --txt \\\n'; - cmd += ' -o /data/misc/perfetto-traces/trace \\\n'; - cmd += '< globals.dispatch(Actions.stopRecording({}))}, - 'Stop'); - - const cancel = - m(`button`, - {onclick: () => globals.dispatch(Actions.cancelRecording({}))}, - 'Cancel'); - - return [stop, cancel]; -} - -function onStartRecordingPressed() { - location.href = '#!/record/instructions'; - globals.rafScheduler.scheduleFullRedraw(); - autosaveConfigStore.save(globals.state.recordConfig); - - const target = globals.state.recordingTarget; - if (isAndroidTarget(target) || isChromeTarget(target)) { - globals.logging.logEvent('Record Trace', `Record trace (${target.os})`); - globals.dispatch(Actions.startRecording({})); - } -} - -function RecordingStatusLabel() { - const recordingStatus = globals.state.recordingStatus; - if (!recordingStatus) return []; - return m('label', recordingStatus); -} - -export function ErrorLabel() { - const lastRecordingError = globals.state.lastRecordingError; - if (!lastRecordingError) return []; - return m('label.error-label', `Error: ${lastRecordingError}`); -} - -function recordingLog() { - const logs = globals.recordingLog; - if (logs === undefined) return []; - return m('.code-snippet.no-top-bar', m('code', logs)); -} - -// The connection must be done in the frontend. After it, the serial ID will -// be inserted in the state, and the worker will be able to connect to the -// correct device. -async function addAndroidDevice() { - let device: USBDevice; - try { - device = await new AdbOverWebUsb().findDevice(); - } catch (e) { - const err = `No device found: ${e.name}: ${e.message}`; - console.error(err, e); - alert(err); - return; - } - - if (!device.serialNumber) { - console.error('serial number undefined'); - return; - } - - // After the user has selected a device with the chrome UI, it will be - // available when listing all the available device from WebUSB. Therefore, - // we update the list of available devices. - await updateAvailableAdbDevices(device.serialNumber); -} - -export async function updateAvailableAdbDevices( - preferredDeviceSerial?: string) { - const devices = await new AdbOverWebUsb().getPairedDevices(); - - let recordingTarget: AdbRecordingTarget|undefined = undefined; - - const availableAdbDevices: AdbRecordingTarget[] = []; - devices.forEach((d) => { - if (d.productName && d.serialNumber) { - // TODO(nicomazz): At this stage, we can't know the OS version, so we - // assume it is 'Q'. This can create problems with devices with an old - // version of perfetto. The os detection should be done after the adb - // connection, from adb_record_controller - availableAdbDevices.push( - {name: d.productName, serial: d.serialNumber, os: 'Q'}); - if (preferredDeviceSerial && preferredDeviceSerial === d.serialNumber) { - recordingTarget = availableAdbDevices[availableAdbDevices.length - 1]; - } - } - }); - - globals.dispatch( - Actions.setAvailableAdbDevices({devices: availableAdbDevices})); - selectAndroidDeviceIfAvailable(availableAdbDevices, recordingTarget); - globals.rafScheduler.scheduleFullRedraw(); - return availableAdbDevices; -} - -function selectAndroidDeviceIfAvailable( - availableAdbDevices: AdbRecordingTarget[], - recordingTarget?: RecordingTarget) { - if (!recordingTarget) { - recordingTarget = globals.state.recordingTarget; - } - const deviceConnected = isAdbTarget(recordingTarget); - const connectedDeviceDisconnected = deviceConnected && - availableAdbDevices.find( - (e) => e.serial === - (recordingTarget as AdbRecordingTarget).serial) === undefined; - - if (availableAdbDevices.length) { - // If there's an Android device available and the current selection isn't - // one, select the Android device by default. If the current device isn't - // available anymore, but another Android device is, select the other - // Android device instead. - if (!deviceConnected || connectedDeviceDisconnected) { - recordingTarget = availableAdbDevices[0]; - } - - globals.dispatch(Actions.setRecordingTarget({target: recordingTarget})); - return; - } - - // If the currently selected device was disconnected, reset the recording - // target to the default one. - if (connectedDeviceDisconnected) { - globals.dispatch( - Actions.setRecordingTarget({target: getDefaultRecordingTargets()[0]})); - } -} - -function recordMenu(routePage: string) { - const target = globals.state.recordingTarget; - const chromeProbe = - m('a[href="#!/record/chrome"]', - m(`li${routePage === 'chrome' ? '.active' : ''}`, - m('i.material-icons', 'laptop_chromebook'), - m('.title', 'Chrome'), - m('.sub', 'Chrome traces'))); - const cpuProbe = - m('a[href="#!/record/cpu"]', - m(`li${routePage === 'cpu' ? '.active' : ''}`, - m('i.material-icons', 'subtitles'), - m('.title', 'CPU'), - m('.sub', 'CPU usage, scheduling, wakeups'))); - const gpuProbe = - m('a[href="#!/record/gpu"]', - m(`li${routePage === 'gpu' ? '.active' : ''}`, - m('i.material-icons', 'aspect_ratio'), - m('.title', 'GPU'), - m('.sub', 'GPU frequency, memory'))); - const powerProbe = - m('a[href="#!/record/power"]', - m(`li${routePage === 'power' ? '.active' : ''}`, - m('i.material-icons', 'battery_charging_full'), - m('.title', 'Power'), - m('.sub', 'Battery and other energy counters'))); - const memoryProbe = - m('a[href="#!/record/memory"]', - m(`li${routePage === 'memory' ? '.active' : ''}`, - m('i.material-icons', 'memory'), - m('.title', 'Memory'), - m('.sub', 'Physical mem, VM, LMK'))); - const androidProbe = - m('a[href="#!/record/android"]', - m(`li${routePage === 'android' ? '.active' : ''}`, - m('i.material-icons', 'android'), - m('.title', 'Android apps & svcs'), - m('.sub', 'atrace and logcat'))); - const advancedProbe = - m('a[href="#!/record/advanced"]', - m(`li${routePage === 'advanced' ? '.active' : ''}`, - m('i.material-icons', 'settings'), - m('.title', 'Advanced settings'), - m('.sub', 'Complicated stuff for wizards'))); - const recInProgress = globals.state.recordingInProgress; - - const probes = []; - if (isCrOSTarget(target) || isLinuxTarget(target)) { - probes.push(cpuProbe, powerProbe, memoryProbe, chromeProbe, advancedProbe); - } else if (isChromeTarget(target)) { - probes.push(chromeProbe); - } else { - probes.push( - cpuProbe, - gpuProbe, - powerProbe, - memoryProbe, - androidProbe, - chromeProbe, - advancedProbe); - } - - return m( - '.record-menu', - { - class: recInProgress ? 'disabled' : '', - onclick: () => globals.rafScheduler.scheduleFullRedraw(), - }, - m('header', 'Trace config'), - m('ul', - m('a[href="#!/record/buffers"]', - m(`li${routePage === 'buffers' ? '.active' : ''}`, - m('i.material-icons', 'tune'), - m('.title', 'Recording settings'), - m('.sub', 'Buffer mode, size and duration'))), - m('a[href="#!/record/instructions"]', - m(`li${routePage === 'instructions' ? '.active' : ''}`, - m('i.material-icons-filled.rec', 'fiber_manual_record'), - m('.title', 'Recording command'), - m('.sub', 'Manually record trace'))), - PERSIST_CONFIG_FLAG.get() ? - m('a[href="#!/record/config"]', - { - onclick: () => { - recordConfigStore.reloadFromLocalStorage(); - }, - }, - m(`li${routePage === 'config' ? '.active' : ''}`, - m('i.material-icons', 'save'), - m('.title', 'Saved configs'), - m('.sub', 'Manage local configs'))) : - null), - m('header', 'Probes'), - m('ul', probes)); -} - -export function maybeGetActiveCss(routePage: string, section: string): string { - return routePage === section ? '.active' : ''; -} - -export const RecordPage = createPage({ - view({attrs}: m.Vnode) { - const pages: m.Children = []; - // we need to remove the `/` character from the route - let routePage = attrs.subpage ? attrs.subpage.substr(1) : ''; - if (!RECORDING_SECTIONS.includes(routePage)) { - routePage = 'buffers'; - } - pages.push(recordMenu(routePage)); - - pages.push(m(RecordingSettings, { - dataSources: [], - cssClass: maybeGetActiveCss(routePage, 'buffers'), - } as RecordingSectionAttrs)); - pages.push(Instructions(maybeGetActiveCss(routePage, 'instructions'))); - pages.push(Configurations(maybeGetActiveCss(routePage, 'config'))); - - const settingsSections = new Map([ - ['cpu', CpuSettings], - ['gpu', GpuSettings], - ['power', PowerSettings], - ['memory', MemorySettings], - ['android', AndroidSettings], - ['chrome', ChromeSettings], - ['advanced', AdvancedSettings], - ]); - for (const [section, component] of settingsSections.entries()) { - pages.push(m(component, { - dataSources: [], - cssClass: maybeGetActiveCss(routePage, section), - } as RecordingSectionAttrs)); - } - - return m( - '.record-page', - globals.state.recordingInProgress ? m('.hider') : [], - m('.record-container', - RecordHeader(), - m('.record-container-content', recordMenu(routePage), pages))); - }, -}); diff --git a/third_party/perfetto/ui/src/frontend/record_page_v2.ts b/third_party/perfetto/ui/src/frontend/record_page_v2.ts deleted file mode 100644 index 3ce482f89e99..000000000000 --- a/third_party/perfetto/ui/src/frontend/record_page_v2.ts +++ /dev/null @@ -1,565 +0,0 @@ -// Copyright (C) 2022 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -import m from 'mithril'; -import {Attributes} from 'mithril'; - -import {assertExists} from '../base/logging'; -import {Actions} from '../common/actions'; -import { - RecordingConfigUtils, -} from '../common/recordingV2/recording_config_utils'; -import { - ChromeTargetInfo, - RecordingTargetV2, - TargetInfo, -} from '../common/recordingV2/recording_interfaces_v2'; -import { - RecordingPageController, - RecordingState, -} from '../common/recordingV2/recording_page_controller'; -import { - EXTENSION_NAME, - EXTENSION_URL, -} from '../common/recordingV2/recording_utils'; -import { - targetFactoryRegistry, -} from '../common/recordingV2/target_factory_registry'; - -import {globals} from './globals'; -import {fullscreenModalContainer} from './modal'; -import {createPage, PageAttrs} from './pages'; -import {recordConfigStore} from './record_config'; -import { - Configurations, - maybeGetActiveCss, - PERSIST_CONFIG_FLAG, - RECORDING_SECTIONS, -} from './record_page'; -import {CodeSnippet} from './record_widgets'; -import {AdvancedSettings} from './recording/advanced_settings'; -import {AndroidSettings} from './recording/android_settings'; -import {ChromeSettings} from './recording/chrome_settings'; -import {CpuSettings} from './recording/cpu_settings'; -import {GpuSettings} from './recording/gpu_settings'; -import {MemorySettings} from './recording/memory_settings'; -import {PowerSettings} from './recording/power_settings'; -import {RecordingSectionAttrs} from './recording/recording_sections'; -import {RecordingSettings} from './recording/recording_settings'; -import { - FORCE_RESET_MESSAGE, -} from './recording/recording_ui_utils'; -import {addNewTarget} from './recording/reset_target_modal'; - -const START_RECORDING_MESSAGE = 'Start Recording'; - -const controller = new RecordingPageController(); -const recordConfigUtils = new RecordingConfigUtils(); -// Whether the target selection modal is displayed. -let shouldDisplayTargetModal: boolean = false; - -// Options for displaying a target selection menu. -export interface TargetSelectionOptions { - // css attributes passed to the mithril components which displays the target - // selection menu. - attributes: Attributes; - // Whether the selection should be preceded by a text label. - shouldDisplayLabel: boolean; -} - -function isChromeTargetInfo(targetInfo: TargetInfo): - targetInfo is ChromeTargetInfo { - return ['CHROME', 'CHROME_OS'].includes(targetInfo.targetType); -} - -function RecordHeader() { - const platformSelection = RecordingPlatformSelection(); - const statusLabel = RecordingStatusLabel(); - const buttons = RecordingButton(); - const notes = RecordingNotes(); - if (!platformSelection && !statusLabel && !buttons && !notes) { - // The header should not be displayed when it has no content. - return undefined; - } - return m( - '.record-header', - m('.top-part', - m('.target-and-status', platformSelection, statusLabel), - buttons), - notes); -} - -function RecordingPlatformSelection() { - // Don't show the platform selector while we are recording a trace. - if (controller.getState() >= RecordingState.RECORDING) return undefined; - - return m( - '.target', - m('.chip', - { - onclick: () => { - shouldDisplayTargetModal = true; - fullscreenModalContainer.createNew(addNewTargetModal()); - globals.rafScheduler.scheduleFullRedraw(); - }, - }, - m('button', 'Add new recording target'), - m('i.material-icons', 'add')), - targetSelection()); -} - -function addNewTargetModal() { - return { - ...addNewTarget(controller), - onClose: () => shouldDisplayTargetModal = false, - }; -} - -export function targetSelection(): m.Vnode|undefined { - if (!controller.shouldShowTargetSelection()) { - return undefined; - } - - const targets: RecordingTargetV2[] = targetFactoryRegistry.listTargets(); - const targetNames = []; - const targetInfo = controller.getTargetInfo(); - if (!targetInfo) { - targetNames.push(m('option', 'PLEASE_SELECT_TARGET')); - } - - let selectedIndex = 0; - for (let i = 0; i < targets.length; i++) { - const targetName = targets[i].getInfo().name; - targetNames.push(m('option', targetName)); - if (targetInfo && targetName === targetInfo.name) { - selectedIndex = i; - } - } - - return m( - 'label', - 'Target platform:', - m('select', - { - selectedIndex, - onchange: (e: Event) => { - controller.onTargetSelection((e.target as HTMLSelectElement).value); - }, - onupdate: (select) => { - // Work around mithril bug - // (https://github.com/MithrilJS/mithril.js/issues/2107): We may - // update the select's options while also changing the - // selectedIndex at the same time. The update of selectedIndex - // may be applied before the new options are added to the select - // element. Because the new selectedIndex may be outside of the - // select's options at that time, we have to reselect the - // correct index here after any new children were added. - (select.dom as HTMLSelectElement).selectedIndex = selectedIndex; - }, - }, - ...targetNames), - ); -} - -// This will display status messages which are informative, but do not require -// user action, such as: "Recording in progress for X seconds" in the recording -// page header. -function RecordingStatusLabel() { - const recordingStatus = globals.state.recordingStatus; - if (!recordingStatus) return undefined; - return m('label', recordingStatus); -} - -function Instructions(cssClass: string) { - if (controller.getState() < RecordingState.TARGET_SELECTED) { - return undefined; - } - // We will have a valid target at this step because we checked the state. - const targetInfo = assertExists(controller.getTargetInfo()); - - return m( - `.record-section.instructions${cssClass}`, - m('header', 'Recording command'), - (PERSIST_CONFIG_FLAG.get()) ? - m('button.permalinkconfig', - { - onclick: () => { - globals.dispatch( - Actions.createPermalink({isRecordingConfig: true})); - }, - }, - 'Share recording settings') : - null, - RecordingSnippet(targetInfo), - BufferUsageProgressBar(), - m('.buttons', StopCancelButtons())); -} - -function BufferUsageProgressBar() { - // Show the Buffer Usage bar only after we start recording a trace. - if (controller.getState() !== RecordingState.RECORDING) { - return undefined; - } - - controller.fetchBufferUsage(); - - const bufferUsage = controller.getBufferUsagePercentage(); - // Buffer usage is not available yet on Android. - if (bufferUsage === 0) return undefined; - - return m( - 'label', - 'Buffer usage: ', - m('progress', {max: 100, value: bufferUsage * 100})); -} - -function RecordingNotes() { - if (controller.getState() !== RecordingState.TARGET_INFO_DISPLAYED) { - return undefined; - } - // We will have a valid target at this step because we checked the state. - const targetInfo = assertExists(controller.getTargetInfo()); - - const linuxUrl = 'https://perfetto.dev/docs/quickstart/linux-tracing'; - const cmdlineUrl = - 'https://perfetto.dev/docs/quickstart/android-tracing#perfetto-cmdline'; - - const notes: m.Children = []; - - const msgFeatNotSupported = - m('span', `Some probes are only supported in Perfetto versions running - on Android Q+. Therefore, Perfetto will sideload the latest version onto - the device.`); - - const msgPerfettoNotSupported = m( - 'span', - `Perfetto is not supported natively before Android P. Therefore, Perfetto - will sideload the latest version onto the device.`); - - const msgLinux = - m('.note', - `Use this `, - m('a', {href: linuxUrl, target: '_blank'}, `quickstart guide`), - ` to get started with tracing on Linux.`); - - const msgLongTraces = m( - '.note', - `Recording in long trace mode through the UI is not supported. Please copy - the command and `, - m('a', - {href: cmdlineUrl, target: '_blank'}, - `collect the trace using ADB.`)); - - if (!recordConfigUtils - .fetchLatestRecordCommand(globals.state.recordConfig, targetInfo) - .hasDataSources) { - notes.push( - m('.note', - 'It looks like you didn\'t add any probes. ' + - 'Please add at least one to get a non-empty trace.')); - } - - targetFactoryRegistry.listRecordingProblems().map((recordingProblem) => { - if (recordingProblem.includes(EXTENSION_URL)) { - // Special case for rendering the link to the Chrome extension. - const parts = recordingProblem.split(EXTENSION_URL); - notes.push( - m('.note', - parts[0], - m('a', {href: EXTENSION_URL, target: '_blank'}, EXTENSION_NAME), - parts[1])); - } - }); - - switch (targetInfo.targetType) { - case 'LINUX': - notes.push(msgLinux); - break; - case 'ANDROID': { - const androidApiLevel = targetInfo.androidApiLevel; - if (androidApiLevel === 28) { - notes.push(m('.note', msgFeatNotSupported)); - } else if (androidApiLevel && androidApiLevel <= 27) { - notes.push(m('.note', msgPerfettoNotSupported)); - } - break; - } - default: - } - - if (globals.state.recordConfig.mode === 'LONG_TRACE') { - notes.unshift(msgLongTraces); - } - - return notes.length > 0 ? m('div', notes) : undefined; -} - -function RecordingSnippet(targetInfo: TargetInfo) { - // We don't need commands to start tracing on chrome - if (isChromeTargetInfo(targetInfo)) { - if (controller.getState() > RecordingState.AUTH_P2) { - // If the UI has started tracing, don't display a message guiding the user - // to start recording. - return undefined; - } - return m( - 'div', - m('label', `To trace Chrome from the Perfetto UI you just have to press - '${START_RECORDING_MESSAGE}'.`)); - } - return m(CodeSnippet, {text: getRecordCommand(targetInfo)}); -} - -function getRecordCommand(targetInfo: TargetInfo): string { - const recordCommand = recordConfigUtils.fetchLatestRecordCommand( - globals.state.recordConfig, targetInfo); - - const pbBase64 = recordCommand ? recordCommand.configProtoBase64 : ''; - const pbtx = recordCommand ? recordCommand.configProtoText : ''; - let cmd = ''; - if (targetInfo.targetType === 'ANDROID' && - targetInfo.androidApiLevel === 28) { - cmd += `echo '${pbBase64}' | \n`; - cmd += 'base64 --decode | \n'; - cmd += 'adb shell "perfetto -c - -o /data/misc/perfetto-traces/trace"\n'; - } else { - cmd += targetInfo.targetType === 'ANDROID' ? 'adb shell perfetto \\\n' : - 'perfetto \\\n'; - cmd += ' -c - --txt \\\n'; - cmd += ' -o /data/misc/perfetto-traces/trace \\\n'; - cmd += '< controller.onStartRecordingPressed(), - }, - START_RECORDING_MESSAGE)); -} - -function StopCancelButtons() { - // Show the Stop/Cancel buttons only while we are recording a trace. - if (!controller.shouldShowStopCancelButtons()) { - return undefined; - } - - const stop = - m(`button.selected`, {onclick: () => controller.onStop()}, 'Stop'); - - const cancel = m(`button`, {onclick: () => controller.onCancel()}, 'Cancel'); - - return [stop, cancel]; -} - -function recordMenu(routePage: string) { - const chromeProbe = - m('a[href="#!/record/chrome"]', - m(`li${routePage === 'chrome' ? '.active' : ''}`, - m('i.material-icons', 'laptop_chromebook'), - m('.title', 'Chrome'), - m('.sub', 'Chrome traces'))); - const cpuProbe = - m('a[href="#!/record/cpu"]', - m(`li${routePage === 'cpu' ? '.active' : ''}`, - m('i.material-icons', 'subtitles'), - m('.title', 'CPU'), - m('.sub', 'CPU usage, scheduling, wakeups'))); - const gpuProbe = - m('a[href="#!/record/gpu"]', - m(`li${routePage === 'gpu' ? '.active' : ''}`, - m('i.material-icons', 'aspect_ratio'), - m('.title', 'GPU'), - m('.sub', 'GPU frequency, memory'))); - const powerProbe = - m('a[href="#!/record/power"]', - m(`li${routePage === 'power' ? '.active' : ''}`, - m('i.material-icons', 'battery_charging_full'), - m('.title', 'Power'), - m('.sub', 'Battery and other energy counters'))); - const memoryProbe = - m('a[href="#!/record/memory"]', - m(`li${routePage === 'memory' ? '.active' : ''}`, - m('i.material-icons', 'memory'), - m('.title', 'Memory'), - m('.sub', 'Physical mem, VM, LMK'))); - const androidProbe = - m('a[href="#!/record/android"]', - m(`li${routePage === 'android' ? '.active' : ''}`, - m('i.material-icons', 'android'), - m('.title', 'Android apps & svcs'), - m('.sub', 'atrace and logcat'))); - const advancedProbe = - m('a[href="#!/record/advanced"]', - m(`li${routePage === 'advanced' ? '.active' : ''}`, - m('i.material-icons', 'settings'), - m('.title', 'Advanced settings'), - m('.sub', 'Complicated stuff for wizards'))); - - // We only display the probes when we have a valid target, so it's not - // possible for the target to be undefined here. - const targetType = assertExists(controller.getTargetInfo()).targetType; - const probes = []; - if (targetType === 'CHROME_OS' || targetType === 'LINUX') { - probes.push(cpuProbe, powerProbe, memoryProbe, chromeProbe, advancedProbe); - } else if (targetType === 'CHROME') { - probes.push(chromeProbe); - } else { - probes.push( - cpuProbe, - gpuProbe, - powerProbe, - memoryProbe, - androidProbe, - chromeProbe, - advancedProbe); - } - - return m( - '.record-menu', - { - class: controller.getState() > RecordingState.TARGET_INFO_DISPLAYED ? - 'disabled' : - '', - onclick: () => globals.rafScheduler.scheduleFullRedraw(), - }, - m('header', 'Trace config'), - m('ul', - m('a[href="#!/record/buffers"]', - m(`li${routePage === 'buffers' ? '.active' : ''}`, - m('i.material-icons', 'tune'), - m('.title', 'Recording settings'), - m('.sub', 'Buffer mode, size and duration'))), - m('a[href="#!/record/instructions"]', - m(`li${routePage === 'instructions' ? '.active' : ''}`, - m('i.material-icons-filled.rec', 'fiber_manual_record'), - m('.title', 'Recording command'), - m('.sub', 'Manually record trace'))), - PERSIST_CONFIG_FLAG.get() ? - m('a[href="#!/record/config"]', - { - onclick: () => { - recordConfigStore.reloadFromLocalStorage(); - }, - }, - m(`li${routePage === 'config' ? '.active' : ''}`, - m('i.material-icons', 'save'), - m('.title', 'Saved configs'), - m('.sub', 'Manage local configs'))) : - null), - m('header', 'Probes'), - m('ul', probes)); -} - -function getRecordContainer(subpage?: string): m.Vnode { - const components: m.Children[] = [RecordHeader()]; - if (controller.getState() === RecordingState.NO_TARGET) { - components.push(m('.full-centered', 'Please connect a valid target.')); - return m('.record-container', components); - } else if (controller.getState() <= RecordingState.ASK_TO_FORCE_P1) { - components.push( - m('.full-centered', - 'Can not access the device without resetting the ' + - `connection. Please refresh the page, then click ` + - `'${FORCE_RESET_MESSAGE}.'`)); - return m('.record-container', components); - } else if (controller.getState() === RecordingState.AUTH_P1) { - components.push( - m('.full-centered', 'Please allow USB debugging on the device.')); - return m('.record-container', components); - } else if ( - controller.getState() === RecordingState.WAITING_FOR_TRACE_DISPLAY) { - components.push( - m('.full-centered', 'Waiting for the trace to be collected.')); - return m('.record-container', components); - } - - const pages: m.Children = []; - // we need to remove the `/` character from the route - let routePage = subpage ? subpage.substr(1) : ''; - if (!RECORDING_SECTIONS.includes(routePage)) { - routePage = 'buffers'; - } - pages.push(recordMenu(routePage)); - - pages.push(m(RecordingSettings, { - dataSources: [], - cssClass: maybeGetActiveCss(routePage, 'buffers'), - } as RecordingSectionAttrs)); - pages.push(Instructions(maybeGetActiveCss(routePage, 'instructions'))); - pages.push(Configurations(maybeGetActiveCss(routePage, 'config'))); - - const settingsSections = new Map([ - ['cpu', CpuSettings], - ['gpu', GpuSettings], - ['power', PowerSettings], - ['memory', MemorySettings], - ['android', AndroidSettings], - ['chrome', ChromeSettings], - ['advanced', AdvancedSettings], - ]); - for (const [section, component] of settingsSections.entries()) { - pages.push(m(component, { - dataSources: controller.getTargetInfo()?.dataSources || [], - cssClass: maybeGetActiveCss(routePage, section), - } as RecordingSectionAttrs)); - } - - components.push(m('.record-container-content', pages)); - return m('.record-container', components); -} - -export const RecordPageV2 = createPage({ - - oninit(): void { - controller.initFactories(); - }, - - view({attrs}: m.Vnode): void | - m.Children { - if (shouldDisplayTargetModal) { - fullscreenModalContainer.updateVdom(addNewTargetModal()); - } - - return m( - '.record-page', - controller.getState() > RecordingState.TARGET_INFO_DISPLAYED ? - m('.hider') : - [], - getRecordContainer(attrs.subpage)); - }, -}); diff --git a/third_party/perfetto/ui/src/frontend/record_widgets.ts b/third_party/perfetto/ui/src/frontend/record_widgets.ts deleted file mode 100644 index 2cb1fd3f5a46..000000000000 --- a/third_party/perfetto/ui/src/frontend/record_widgets.ts +++ /dev/null @@ -1,434 +0,0 @@ -// Copyright (C) 2019 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import {Draft, produce} from 'immer'; -import m from 'mithril'; - -import {assertExists} from '../base/logging'; -import {Actions} from '../common/actions'; -import {RecordConfig} from '../controller/record_config_types'; - -import {copyToClipboard} from './clipboard'; -import {globals} from './globals'; - -declare type Setter = (draft: Draft, val: T) => void; -declare type Getter = (cfg: RecordConfig) => T; - -function defaultSort(a: string, b: string) { - return a.localeCompare(b); -} - -// +---------------------------------------------------------------------------+ -// | Docs link with 'i' in circle icon. | -// +---------------------------------------------------------------------------+ - -interface DocsChipAttrs { - href: string; -} - -class DocsChip implements m.ClassComponent { - view({attrs}: m.CVnode) { - return m( - 'a.inline-chip', - {href: attrs.href, title: 'Open docs in new tab', target: '_blank'}, - m('i.material-icons', 'info'), - ' Docs'); - } -} - -// +---------------------------------------------------------------------------+ -// | Probe: the rectangular box on the right-hand-side with a toggle box. | -// +---------------------------------------------------------------------------+ - -export interface ProbeAttrs { - title: string; - img: string|null; - compact?: boolean; - descr: m.Children; - isEnabled: Getter; - setEnabled: Setter; -} - -export class Probe implements m.ClassComponent { - view({attrs, children}: m.CVnode) { - const onToggle = (enabled: boolean) => { - const traceCfg = produce(globals.state.recordConfig, (draft) => { - attrs.setEnabled(draft, enabled); - }); - globals.dispatch(Actions.setRecordConfig({config: traceCfg})); - }; - - const enabled = attrs.isEnabled(globals.state.recordConfig); - - return m( - `.probe${attrs.compact ? '.compact' : ''}${enabled ? '.enabled' : ''}`, - attrs.img && m('img', { - src: `${globals.root}assets/${attrs.img}`, - onclick: () => onToggle(!enabled), - }), - m('label', - m(`input[type=checkbox]`, { - checked: enabled, - oninput: (e: InputEvent) => { - onToggle((e.target as HTMLInputElement).checked); - }, - }), - m('span', attrs.title)), - attrs.compact ? - '' : - m('div', m('div', attrs.descr), m('.probe-config', children))); - } -} - -export function CompactProbe(args: { - title: string, - isEnabled: Getter, - setEnabled: Setter -}) { - return m(Probe, { - title: args.title, - img: null, - compact: true, - descr: '', - isEnabled: args.isEnabled, - setEnabled: args.setEnabled, - } as ProbeAttrs); -} - -// +-------------------------------------------------------------+ -// | Toggle: an on/off switch. -// +-------------------------------------------------------------+ - -export interface ToggleAttrs { - title: string; - descr: string; - cssClass?: string; - isEnabled: Getter; - setEnabled: Setter; -} - -export class Toggle implements m.ClassComponent { - view({attrs}: m.CVnode) { - const onToggle = (enabled: boolean) => { - const traceCfg = produce(globals.state.recordConfig, (draft) => { - attrs.setEnabled(draft, enabled); - }); - globals.dispatch(Actions.setRecordConfig({config: traceCfg})); - }; - - const enabled = attrs.isEnabled(globals.state.recordConfig); - - return m( - `.toggle${enabled ? '.enabled' : ''}${attrs.cssClass || ''}`, - m('label', - m(`input[type=checkbox]`, { - checked: enabled, - oninput: (e: InputEvent) => { - onToggle((e.target as HTMLInputElement).checked); - }, - }), - m('span', attrs.title)), - m('.descr', attrs.descr)); - } -} - -// +---------------------------------------------------------------------------+ -// | Slider: draggable horizontal slider with numeric spinner. | -// +---------------------------------------------------------------------------+ - -export interface SliderAttrs { - title: string; - icon?: string; - cssClass?: string; - isTime?: boolean; - unit: string; - values: number[]; - get: Getter; - set: Setter; - min?: number; - description?: string; - disabled?: boolean; - zeroIsDefault?: boolean; -} - -export class Slider implements m.ClassComponent { - onValueChange(attrs: SliderAttrs, newVal: number) { - const traceCfg = produce(globals.state.recordConfig, (draft) => { - attrs.set(draft, newVal); - }); - globals.dispatch(Actions.setRecordConfig({config: traceCfg})); - } - - onTimeValueChange(attrs: SliderAttrs, hms: string) { - try { - const date = new Date(`1970-01-01T${hms}.000Z`); - if (isNaN(date.getTime())) return; - this.onValueChange(attrs, date.getTime()); - } catch { - } - } - - onSliderChange(attrs: SliderAttrs, newIdx: number) { - this.onValueChange(attrs, attrs.values[newIdx]); - } - - view({attrs}: m.CVnode) { - const id = attrs.title.replace(/[^a-z0-9]/gmi, '_').toLowerCase(); - const maxIdx = attrs.values.length - 1; - const val = attrs.get(globals.state.recordConfig); - let min = attrs.min || 1; - if (attrs.zeroIsDefault) { - min = Math.min(0, min); - } - const description = attrs.description; - const disabled = attrs.disabled; - - // Find the index of the closest value in the slider. - let idx = 0; - for (; idx < attrs.values.length && attrs.values[idx] < val; idx++) { - } - - let spinnerCfg = {}; - if (attrs.isTime) { - spinnerCfg = { - type: 'text', - pattern: '(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}', // hh:mm:ss - value: new Date(val).toISOString().substr(11, 8), - oninput: (e: InputEvent) => { - this.onTimeValueChange(attrs, (e.target as HTMLInputElement).value); - }, - }; - } else { - const isDefault = attrs.zeroIsDefault && val === 0; - spinnerCfg = { - type: 'number', - value: isDefault ? '' : val, - placeholder: isDefault ? '(default)' : '', - oninput: (e: InputEvent) => { - this.onValueChange(attrs, +(e.target as HTMLInputElement).value); - }, - }; - } - return m( - '.slider' + (attrs.cssClass || ''), - m('header', attrs.title), - description ? m('header.descr', attrs.description) : '', - attrs.icon !== undefined ? m('i.material-icons', attrs.icon) : [], - m(`input[id="${id}"][type=range][min=0][max=${maxIdx}][value=${idx}]`, { - disabled, - oninput: (e: InputEvent) => { - this.onSliderChange(attrs, +(e.target as HTMLInputElement).value); - }, - }), - m(`input.spinner[min=${min}][for=${id}]`, spinnerCfg), - m('.unit', attrs.unit)); - } -} - -// +---------------------------------------------------------------------------+ -// | Dropdown: wrapper around